/* eslint-disable no-unused-expressions */ import * as BetterSqlite3 from '@signalapp/better-sqlite3'; import { ContactInfoSet, ContactsConfigWrapperNode, ConvoInfoVolatileWrapperNode, DisappearingMessageConversationType, } from 'libsession_util_nodejs'; import { isEmpty, isEqual, isFinite, isNumber } from 'lodash'; import { CONVERSATION_PRIORITIES } from '../../../models/conversationAttributes'; import { MESSAGES_TABLE, toSqliteBoolean } from '../../database_utility'; import { fromHexToArray } from '../../../session/utils/String'; import { checkTargetMigration, hasDebugEnvVariable } from '../utils'; const targetVersion = 34; /** * This function returns a contactInfo for the wrapper to understand from the DB values. * Created in this file so we can reuse it during the migration (node side), and from the renderer side */ function getContactInfoFromDBValues({ id, dbApproved, dbApprovedMe, dbBlocked, dbName, dbNickname, priority, dbProfileUrl, dbProfileKey, dbCreatedAtSeconds, expirationType, expireTimer, }: { id: string; dbApproved: boolean; dbApprovedMe: boolean; dbBlocked: boolean; dbNickname: string | undefined; dbName: string | undefined; priority: number; dbProfileUrl: string | undefined; dbProfileKey: string | undefined; dbCreatedAtSeconds: number; expirationType: string | undefined; expireTimer: number | undefined; }): ContactInfoSet { const wrapperContact: ContactInfoSet = { id, approved: !!dbApproved, approvedMe: !!dbApprovedMe, blocked: !!dbBlocked, priority, nickname: dbNickname, name: dbName, createdAtSeconds: dbCreatedAtSeconds, expirationMode: expirationType ? (expirationType as DisappearingMessageConversationType) : undefined, expirationTimerSeconds: !!expireTimer && isFinite(expireTimer) && expireTimer > 0 ? expireTimer * 1000 : 0, }; if ( wrapperContact.profilePicture?.url !== dbProfileUrl || !isEqual(wrapperContact.profilePicture?.key, dbProfileKey) ) { wrapperContact.profilePicture = { url: dbProfileUrl || null, key: dbProfileKey && !isEmpty(dbProfileKey) ? fromHexToArray(dbProfileKey) : null, }; } return wrapperContact; } function insertContactIntoContactWrapper( contact: any, blockedNumbers: Array, contactsConfigWrapper: ContactsConfigWrapperNode | null, // set this to null to only insert into the convo volatile wrapper (i.e. for ourConvo case) volatileConfigWrapper: ConvoInfoVolatileWrapperNode, db: BetterSqlite3.Database, version: number ) { checkTargetMigration(version, targetVersion); if (contactsConfigWrapper !== null) { const dbApproved = !!contact.isApproved || false; const dbApprovedMe = !!contact.didApproveMe || false; const dbBlocked = blockedNumbers.includes(contact.id); const priority = contact.priority || CONVERSATION_PRIORITIES.default; const wrapperContact = getContactInfoFromDBValues({ id: contact.id, dbApproved, dbApprovedMe, dbBlocked, dbName: contact.displayNameInProfile || undefined, dbNickname: contact.nickname || undefined, dbProfileKey: contact.profileKey || undefined, dbProfileUrl: contact.avatarPointer || undefined, priority, dbCreatedAtSeconds: Math.floor((contact.active_at || Date.now()) / 1000), expirationType: contact.expirationType || 'off', expireTimer: contact.expirationTimer || 0, }); try { hasDebugEnvVariable && console.info('Inserting contact into wrapper: ', wrapperContact); contactsConfigWrapper.set(wrapperContact); } catch (e) { console.error( `contactsConfigWrapper.set during migration failed with ${e.message} for id: ${contact.id}` ); // the wrapper did not like something. Try again with just the boolean fields as it's most likely the issue is with one of the strings (which could be recovered) try { hasDebugEnvVariable && console.info('Inserting edited contact into wrapper: ', contact.id); contactsConfigWrapper.set( getContactInfoFromDBValues({ id: contact.id, dbApproved, dbApprovedMe, dbBlocked, dbName: undefined, dbNickname: undefined, dbProfileKey: undefined, dbProfileUrl: undefined, priority: CONVERSATION_PRIORITIES.default, dbCreatedAtSeconds: Math.floor(Date.now() / 1000), expirationType: 'off', expireTimer: 0, }) ); } catch (err2) { // there is nothing else we can do here console.error( `contactsConfigWrapper.set during migration failed with ${err2.message} for id: ${contact.id}. Skipping contact entirely` ); } } } try { const rows = db .prepare( ` SELECT MAX(COALESCE(sent_at, 0)) AS max_sent_at FROM ${MESSAGES_TABLE} WHERE conversationId = $conversationId AND unread = $unread; ` ) .get({ conversationId: contact.id, unread: toSqliteBoolean(false), // we want to find the message read with the higher sentAt timestamp }); const maxRead = rows?.max_sent_at; const lastRead = isNumber(maxRead) && isFinite(maxRead) ? maxRead : 0; hasDebugEnvVariable && console.info(`Inserting contact into volatile wrapper maxread: ${contact.id} :${lastRead}`); volatileConfigWrapper.set1o1(contact.id, lastRead, false); } catch (e) { console.error( `volatileConfigWrapper.set1o1 during migration failed with ${e.message} for id: ${contact.id}. skipping` ); } } export const V34 = { insertContactIntoContactWrapper, };