diff --git a/preload.js b/preload.js index f43747571..9ede62bd2 100644 --- a/preload.js +++ b/preload.js @@ -3,6 +3,8 @@ const { Storage } = require('./ts/util/storage'); const url = require('url'); +const _ = require('lodash'); + const config = url.parse(window.location.toString(), true).query; const configAny = config; @@ -29,6 +31,7 @@ window.sessionFeatureFlags = { useTestNet: Boolean( process.env.NODE_APP_INSTANCE && process.env.NODE_APP_INSTANCE.includes('testnet') ), + useDebugLogging: !_.isEmpty(process.env.SESSION_DEBUG), useClosedGroupV3: false || process.env.USE_CLOSED_GROUP_V3, useSharedUtilForUserConfig: true, debug: { @@ -234,6 +237,7 @@ const { setupi18n } = require('./ts/util/i18n'); window.Signal = data.initData(); const { getConversationController } = require('./ts/session/conversations/ConversationController'); +const { isEmpty } = require('lodash'); window.getConversationController = getConversationController; // Linux seems to periodically let the event loop stop, so this is a global workaround setInterval(() => { diff --git a/ts/components/conversation/message/message-content/OutgoingMessageStatus.tsx b/ts/components/conversation/message/message-content/OutgoingMessageStatus.tsx index f0d0c847d..c71d4097e 100644 --- a/ts/components/conversation/message/message-content/OutgoingMessageStatus.tsx +++ b/ts/components/conversation/message/message-content/OutgoingMessageStatus.tsx @@ -1,7 +1,7 @@ import { ipcRenderer } from 'electron'; import React from 'react'; import styled from 'styled-components'; -import { MessageDeliveryStatus } from '../../../../models/messageType'; +import { LastMessageStatusType } from '../../../../state/ducks/conversations'; import { SessionIcon } from '../../../icon'; const MessageStatusSendingContainer = styled.div` @@ -56,7 +56,7 @@ const MessageStatusError = ({ dataTestId }: { dataTestId?: string }) => { }; export const OutgoingMessageStatus = (props: { - status?: MessageDeliveryStatus | null; + status: LastMessageStatusType | null; dataTestId?: string; }) => { const { status, dataTestId } = props; diff --git a/ts/components/conversation/message/message-item/MessageDetail.tsx b/ts/components/conversation/message/message-item/MessageDetail.tsx index e88190cee..eb1f9b33c 100644 --- a/ts/components/conversation/message/message-item/MessageDetail.tsx +++ b/ts/components/conversation/message/message-item/MessageDetail.tsx @@ -62,14 +62,14 @@ const ContactItem = (props: { contact: ContactPropsMessageDetail }) => { const { contact } = props; const errors = contact.errors || []; - const statusComponent = !contact.isOutgoingKeyError ? ( + const statusComponent = (
- ) : null; + ); return (
diff --git a/ts/models/conversation.ts b/ts/models/conversation.ts index 6a8aa4c99..615b0c7d9 100644 --- a/ts/models/conversation.ts +++ b/ts/models/conversation.ts @@ -117,7 +117,6 @@ import { type InMemoryConvoInfos = { mentionedUs: boolean; unreadCount: number; - lastReadTimestampMessage: number | null; }; // TODO decide it it makes sense to move this to a redux slice? @@ -275,7 +274,7 @@ export class ConversationModel extends Backbone.Model { public getGroupAdmins(): Array { const groupAdmins = this.get('groupAdmins'); - return groupAdmins && groupAdmins?.length > 0 ? groupAdmins : []; + return groupAdmins && groupAdmins.length > 0 ? groupAdmins : []; } // tslint:disable-next-line: cyclomatic-complexity max-func-body-length @@ -291,8 +290,6 @@ export class ConversationModel extends Backbone.Model { const weAreModerator = this.isModerator(ourNumber); // only used for sogs const isMe = this.isMe(); const isTyping = !!this.typingTimer; - // const unreadCount = this.get('unreadCount') || undefined; - // const mentionedUs = this.get('mentionedUs') || undefined; const isKickedFromGroup = !!this.get('isKickedFromGroup'); const left = !!this.get('left'); const currentNotificationSetting = this.get('triggerNotificationsFor'); @@ -337,7 +334,7 @@ export class ConversationModel extends Backbone.Model { toRet.avatarPath = avatarPath; } - const foundContact = SessionUtilContact.getMappedValue(this.id); + const foundContact = SessionUtilContact.getContactCached(this.id); const foundCommunity = SessionUtilUserGroups.getCommunityByConvoIdCached(this.id); const foundLegacyGroup = SessionUtilUserGroups.getLegacyGroupCached(this.id); const foundVolatileInfo = SessionUtilConvoInfoVolatile.getVolatileInfoCached(this.id); @@ -498,13 +495,10 @@ export class ConversationModel extends Backbone.Model { inMemoryConvoInfos.delete(this.id); return; } - console.warn('memoryDetails', memoryDetails); - if (!inMemoryConvoInfos.get(this.id)) { inMemoryConvoInfos.set(this.id, { mentionedUs: false, unreadCount: 0, - lastReadTimestampMessage: null, }); } @@ -517,10 +511,7 @@ export class ConversationModel extends Backbone.Model { existing.unreadCount = memoryDetails.unreadCount; changes = true; } - if (existing.lastReadTimestampMessage !== memoryDetails.lastReadTimestampMessage) { - existing.lastReadTimestampMessage = memoryDetails.lastReadTimestampMessage; - changes = true; - } + if (existing.mentionedUs !== memoryDetails.mentionedUs) { existing.mentionedUs = memoryDetails.mentionedUs; changes = true; @@ -531,10 +522,6 @@ export class ConversationModel extends Backbone.Model { } } - public getCachedLastReadTimestampMessage() { - return inMemoryConvoInfos.get(this.id)?.lastReadTimestampMessage || null; - } - public async queueJob(callback: () => Promise) { // tslint:disable-next-line: no-promise-as-boolean const previous = this.pending || Promise.resolve(); @@ -1855,8 +1842,8 @@ export class ConversationModel extends Backbone.Model { return; } const lastMessageModel = messages.at(0); - const lastMessageStatus = lastMessageModel?.getMessagePropStatus() || undefined; - const lastMessageNotificationText = lastMessageModel?.getNotificationText() || undefined; + const lastMessageStatus = lastMessageModel.getMessagePropStatus() || undefined; + const lastMessageNotificationText = lastMessageModel.getNotificationText() || undefined; // we just want to set the `status` to `undefined` if there are no `lastMessageNotificationText` const lastMessageUpdate = !!lastMessageNotificationText && !isEmpty(lastMessageNotificationText) diff --git a/ts/models/message.ts b/ts/models/message.ts index 995773848..a98522403 100644 --- a/ts/models/message.ts +++ b/ts/models/message.ts @@ -713,7 +713,7 @@ export class MessageModel extends Backbone.Model { // We include numbers we didn't successfully send to so we can display errors. // Older messages don't have the recipients included on the message, so we fall // back to the conversation's current recipients - const phoneNumbers: Array = this.isIncoming() + const contacts: Array = this.isIncoming() ? [this.get('source')] : this.get('sent_to') || []; @@ -727,30 +727,21 @@ export class MessageModel extends Backbone.Model { const errors = reject(allErrors, error => Boolean(error.number)); const errorsGroupedById = groupBy(allErrors, 'number'); const finalContacts = await Promise.all( - (phoneNumbers || []).map(async id => { + (contacts || []).map(async id => { const errorsForContact = errorsGroupedById[id]; - const isOutgoingKeyError = false; const contact = this.findAndFormatContact(id); return { ...contact, - // fallback to the message status if we do not have a status with a user - // this is useful for medium groups. - status: this.getStatus(id) || this.getMessagePropStatus(), + status: this.getMessagePropStatus(), errors: errorsForContact, - isOutgoingKeyError, - isPrimaryDevice: true, profileName: contact.profileName, }; }) ); - // The prefix created here ensures that contacts with errors are listed - // first; otherwise it's alphabetical - const sortedContacts = sortBy( - finalContacts, - contact => `${contact.isPrimaryDevice ? '0' : '1'}${contact.pubkey}` - ); + // sort by pubkey + const sortedContacts = sortBy(finalContacts, contact => contact.pubkey); const toRet: MessagePropsDetails = { sentAt: this.get('sent_at') || 0, @@ -1013,19 +1004,6 @@ export class MessageModel extends Backbone.Model { return lodashSize(this.get('errors')) > 0; } - public getStatus(pubkey: string) { - const readBy = this.get('read_by') || []; - if (readBy.indexOf(pubkey) >= 0) { - return 'read'; - } - const sentTo = this.get('sent_to') || []; - if (sentTo.indexOf(pubkey) >= 0) { - return 'sent'; - } - - return null; - } - public async updateMessageHash(messageHash: string) { if (!messageHash) { window?.log?.error('Message hash not provided to update message hash'); diff --git a/ts/models/messageType.ts b/ts/models/messageType.ts index 3cc6b7d7d..6057ba5ca 100644 --- a/ts/models/messageType.ts +++ b/ts/models/messageType.ts @@ -1,11 +1,14 @@ import { defaultsDeep } from 'lodash'; import { v4 as uuidv4 } from 'uuid'; -import { CallNotificationType, PropsForMessageWithConvoProps } from '../state/ducks/conversations'; +import { + CallNotificationType, + LastMessageStatusType, + PropsForMessageWithConvoProps, +} from '../state/ducks/conversations'; import { AttachmentTypeWithPath } from '../types/Attachment'; import { Reaction, ReactionList, SortedReactionList } from '../types/Reaction'; export type MessageModelType = 'incoming' | 'outgoing'; -export type MessageDeliveryStatus = 'sending' | 'sent' | 'read' | 'error'; export interface MessageAttributes { // the id of the message @@ -48,7 +51,7 @@ export interface MessageAttributes { * timestamp is the sent_at timestamp, which is the envelope.timestamp */ timestamp?: number; - status?: MessageDeliveryStatus; + status?: LastMessageStatusType; sent_to: Array; sent: boolean; @@ -196,7 +199,7 @@ export interface MessageAttributesOptionals { unread?: number; group?: any; timestamp?: number; - status?: MessageDeliveryStatus; + status?: LastMessageStatusType; sent_to?: Array; sent?: boolean; serverId?: number; diff --git a/ts/node/migration/sessionMigrations.ts b/ts/node/migration/sessionMigrations.ts index cebd37d83..6a9ae8fcb 100644 --- a/ts/node/migration/sessionMigrations.ts +++ b/ts/node/migration/sessionMigrations.ts @@ -19,6 +19,7 @@ import { CONVERSATIONS_TABLE, dropFtsAndTriggers, GUARD_NODE_TABLE, + jsonToObject, LAST_HASHES_TABLE, MESSAGES_TABLE, NODES_FOR_PUBKEY_TABLE, @@ -1486,11 +1487,16 @@ function updateToSessionSchemaVersion30(currentVersion: number, db: BetterSqlite * Setup up the User profile wrapper with what is stored in our own conversation */ - const ourConversation = sqlNode.getConversationById(publicKeyHex, db); - if (!ourConversation) { + const ourConvoRow = db.prepare(`SELECT * FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`).get({ + id: publicKeyHex, + }); + + if (!ourConvoRow) { throw new Error('Failed to find our logged in conversation while migrating'); } + const ourConversation = jsonToObject(ourConvoRow); + // Insert the user profile into the userWrappoer const ourDbName = ourConversation.displayNameInProfile || ''; const ourDbProfileUrl = ourConversation.avatarPointer || ''; diff --git a/ts/receiver/configMessage.ts b/ts/receiver/configMessage.ts index 0520a1a72..09944bd57 100644 --- a/ts/receiver/configMessage.ts +++ b/ts/receiver/configMessage.ts @@ -309,7 +309,7 @@ async function handleLegacyGroupUpdate(latestEnvelopeTimestamp: number) { const legacyGroupsToLeaveInDB = allLegacyGroupsInDb.filter(m => { return !allLegacyGroupsIdsInWrapper.includes(m.id); }); - + // TODO we need to store the encryption keypair if needed window.log.info( `we have to join ${legacyGroupsToJoinInDB.length} legacy groups in DB compared to what is in the wrapper` ); @@ -456,6 +456,39 @@ async function handleUserGroupsUpdate(result: IncomingConfResult): Promise { @@ -463,50 +496,71 @@ async function handleConvoInfoVolatileUpdate( // if (!result.needsDump) { // return result; // } - console.error('handleConvoInfoVolatileUpdate : TODO '); const types = SessionUtilConvoInfoVolatile.getConvoInfoVolatileTypes(); for (let typeIndex = 0; typeIndex < types.length; typeIndex++) { const type = types[typeIndex]; switch (type) { case '1o1': - // Note: "Note to Self" comes here too - const privateChats = await ConvoInfoVolatileWrapperActions.getAll1o1(); - for (let index = 0; index < privateChats.length; index++) { - const fromWrapper = privateChats[index]; - const foundConvo = getConversationController().get(fromWrapper.pubkeyHex); - // TODO should we create the conversation if the conversation does not exist locally? Or assume that it should be coming from a contacts update? - if (foundConvo) { - console.warn( - `fromWrapper from getAll1o1: ${fromWrapper.pubkeyHex}: ${fromWrapper.unread}` + try { + // Note: "Note to Self" comes here too + const wrapper1o1s = await ConvoInfoVolatileWrapperActions.getAll1o1(); + for (let index = 0; index < wrapper1o1s.length; index++) { + const fromWrapper = wrapper1o1s[index]; + await applyConvoVolatileUpdateFromWrapper( + fromWrapper.pubkeyHex, + fromWrapper.unread, + fromWrapper.lastRead ); - // this should mark all the messages sent before fromWrapper.lastRead as read and update the unreadCount - await foundConvo.markReadFromConfigMessage(fromWrapper.lastRead); - // this commits to the DB, if needed - await foundConvo.markAsUnread(fromWrapper.unread, true); - - if (SessionUtilConvoInfoVolatile.isConvoToStoreInWrapper(foundConvo)) { - await SessionUtilConvoInfoVolatile.refreshConvoVolatileCached( - foundConvo.id, - foundConvo.isClosedGroup(), - false - ); - - await foundConvo.refreshInMemoryDetails(); - } } + } catch (e) { + window.log.warn('handleConvoInfoVolatileUpdate of "1o1" failed with error: ', e.message); } - console.warn('handleConvoInfoVolatileUpdate: privateChats', privateChats); break; case 'Community': - const comms = await ConvoInfoVolatileWrapperActions.getAllCommunities(); - console.warn('handleConvoInfoVolatileUpdate: comms', comms); + try { + const wrapperComms = await ConvoInfoVolatileWrapperActions.getAllCommunities(); + for (let index = 0; index < wrapperComms.length; index++) { + const fromWrapper = wrapperComms[index]; + + const convoId = getOpenGroupV2ConversationId( + fromWrapper.baseUrl, + fromWrapper.roomCasePreserved + ); + + await applyConvoVolatileUpdateFromWrapper( + convoId, + fromWrapper.unread, + fromWrapper.lastRead + ); + } + } catch (e) { + window.log.warn( + 'handleConvoInfoVolatileUpdate of "Community" failed with error: ', + e.message + ); + } break; case 'LegacyGroup': - const legacyGroup = await ConvoInfoVolatileWrapperActions.getAllLegacyGroups(); - console.warn('handleConvoInfoVolatileUpdate: legacyGroup', legacyGroup); + try { + const legacyGroups = await ConvoInfoVolatileWrapperActions.getAllLegacyGroups(); + for (let index = 0; index < legacyGroups.length; index++) { + const fromWrapper = legacyGroups[index]; + + await applyConvoVolatileUpdateFromWrapper( + fromWrapper.pubkeyHex, + fromWrapper.unread, + fromWrapper.lastRead + ); + } + } catch (e) { + window.log.warn( + 'handleConvoInfoVolatileUpdate of "LegacyGroup" failed with error: ', + e.message + ); + } break; default: diff --git a/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts b/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts index cb45e8788..4a5ad3c4a 100644 --- a/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts +++ b/ts/session/apis/open_group_api/opengroupV2/OpenGroupServerPoller.ts @@ -380,7 +380,6 @@ export const getRoomAndUpdateLastFetchTimestamp = async ( if (!newMessages.length) { // if we got no new messages, just write our last update timestamp to the db roomInfos.lastFetchTimestamp = Date.now(); - window?.log?.info(); await OpenGroupData.saveV2OpenGroupRoom(roomInfos); return null; } diff --git a/ts/session/conversations/ConversationController.ts b/ts/session/conversations/ConversationController.ts index 70994c9e3..51cae1494 100644 --- a/ts/session/conversations/ConversationController.ts +++ b/ts/session/conversations/ConversationController.ts @@ -221,26 +221,28 @@ export class ConversationController { // open group v2 } else if (conversation.isPublic()) { window?.log?.info('leaving open group v2', conversation.id); + // remove from the wrapper the entries before we remove the roomInfos, as we won't have the required community pubkey afterwards + try { + await SessionUtilUserGroups.removeCommunityFromWrapper(conversation.id, conversation.id); + await SessionUtilConvoInfoVolatile.removeCommunityFromWrapper( + conversation.id, + conversation.id + ); + } catch (e) { + window?.log?.info('SessionUtilUserGroups.removeCommunityFromWrapper failed:', e); + } + const roomInfos = OpenGroupData.getV2OpenGroupRoom(conversation.id); if (roomInfos) { getOpenGroupManager().removeRoomFromPolledRooms(roomInfos); } - // remove the roomInfos locally for this open group room + // remove the roomInfos locally for this open group room including the pubkey try { await OpenGroupData.removeV2OpenGroupRoom(conversation.id); } catch (e) { window?.log?.info('removeV2OpenGroupRoom failed:', e); } - try { - await SessionUtilUserGroups.removeCommunityFromWrapper(conversation.id, conversation.id); - await SessionUtilConvoInfoVolatile.removeCommunityFromWrapper( - conversation.id, - conversation.id - ); - } catch (e) { - window?.log?.info('SessionUtilUserGroups.removeCommunityFromWrapper failed:', e); - } } else if (conversation.isPrivate()) { // if this conversation is a private conversation it's in fact a `contact` for desktop. // we just want to remove everything related to it and set the hidden field to true diff --git a/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts b/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts index c3875c583..48d1f6494 100644 --- a/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts +++ b/ts/session/utils/job_runners/jobs/ConfigurationSyncJob.ts @@ -130,11 +130,7 @@ async function buildAndSaveDumpsToDB(changes: Array): Promise< for (let i = 0; i < changes.length; i++) { const change = changes[i]; const variant = LibSessionUtil.kindToVariant(change.message.kind); - console.warn( - `ConfigurationSyncJob.saveDumpToDB: "${variant}" updatedHash: "${ - change.updatedHash - }:${change.message.seqno.toNumber()}"` - ); + const needsDump = await LibSessionUtil.markAsPushed( variant, change.publicKey, @@ -302,11 +298,13 @@ async function queueNewJobIfNeeded() { await runners.configurationSyncRunner.addJob( new ConfigurationSyncJob({ nextAttemptTimestamp: Date.now() }) ); + window.log.debug('Scheduling ConfSyncJob: ASAP'); } else { // if we did run at t=100, and it is currently t=110, diff is 10 const diff = Math.max(Date.now() - lastRunConfigSyncJobTimestamp, 0); // but we want to run every 30, so what we need is actually `30-10` from now = 20 const leftBeforeNextTick = Math.max(defaultMsBetweenRetries - diff, 0); + window.log.debug('Scheduling ConfSyncJob: LATER'); // TODO we need to make the addJob wait for the previous addJob to be done before it can be called. await runners.configurationSyncRunner.addJob( diff --git a/ts/session/utils/libsession/libsession_utils.ts b/ts/session/utils/libsession/libsession_utils.ts index cab6742bf..3d56f9212 100644 --- a/ts/session/utils/libsession/libsession_utils.ts +++ b/ts/session/utils/libsession/libsession_utils.ts @@ -112,6 +112,7 @@ async function pendingChangesForPubkey(pubkey: string): Promise; }; @@ -60,7 +57,7 @@ export type MessagePropsDetails = { direction: MessageModelType; }; -export type LastMessageStatusType = MessageDeliveryStatus | undefined; +export type LastMessageStatusType = 'sending' | 'sent' | 'read' | 'error' | undefined; export type FindAndFormatContactType = { pubkey: string; diff --git a/ts/util/logging.ts b/ts/util/logging.ts index 659a8495f..63a3ca016 100644 --- a/ts/util/logging.ts +++ b/ts/util/logging.ts @@ -110,6 +110,9 @@ const development = window && window?.getEnvironment && window?.getEnvironment() // The Bunyan API: https://github.com/trentm/node-bunyan#log-method-api function logAtLevel(level: string, prefix: string, ...args: any) { + if (prefix === 'DEBUG' && window.sessionFeatureFlags.useDebugLogging) { + return; + } if (development) { const fn = `_${level}`; (console as any)[fn](prefix, now(), ...args); diff --git a/ts/window.d.ts b/ts/window.d.ts index 664e85c5f..a0bad52c3 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -43,6 +43,7 @@ declare global { debugNonSnodeRequests: boolean; debugOnionRequests: boolean; }; + useDebugLogging: boolean; }; SessionSnodeAPI: SessionSnodeAPI; onLogin: (pw: string) => Promise;