From 45cfa6b38b66231c74e088d91731e51c436816e7 Mon Sep 17 00:00:00 2001 From: William Grant Date: Tue, 22 Aug 2023 14:09:09 +1000 Subject: [PATCH] feat: fixed groups migration v34 --- ts/node/migration/helpers/v31.ts | 4 +- ts/node/migration/helpers/v34.ts | 129 ++++++++++++++++++++++++- ts/node/migration/sessionMigrations.ts | 74 +++++++++++++- 3 files changed, 198 insertions(+), 9 deletions(-) diff --git a/ts/node/migration/helpers/v31.ts b/ts/node/migration/helpers/v31.ts index 1f164a9a0..71ba35b8c 100644 --- a/ts/node/migration/helpers/v31.ts +++ b/ts/node/migration/helpers/v31.ts @@ -303,7 +303,7 @@ function getLegacyGroupInfoFromDBValues({ function insertLegacyGroupIntoWrapper( legacyGroup: Pick< ConversationAttributes, - 'id' | 'priority' | 'displayNameInProfile' | 'lastJoinedTimestamp' | 'expireTimer' + 'id' | 'priority' | 'displayNameInProfile' | 'lastJoinedTimestamp' > & { members: string; groupAdmins: string }, // members and groupAdmins are still stringified here userGroupConfigWrapper: UserGroupsWrapperNode, volatileInfoConfigWrapper: ConvoInfoVolatileWrapperNode, @@ -315,7 +315,6 @@ function insertLegacyGroupIntoWrapper( const { priority, id, - // expireTimer, groupAdmins, members, displayNameInProfile, @@ -330,7 +329,6 @@ function insertLegacyGroupIntoWrapper( const wrapperLegacyGroup = getLegacyGroupInfoFromDBValues({ id, priority, - // expireTimer, // FIXME WILL add expirationMode here groupAdmins, members, displayNameInProfile, diff --git a/ts/node/migration/helpers/v34.ts b/ts/node/migration/helpers/v34.ts index 4eb97608a..0cadab880 100644 --- a/ts/node/migration/helpers/v34.ts +++ b/ts/node/migration/helpers/v34.ts @@ -4,12 +4,25 @@ import { ContactInfoSet, ContactsConfigWrapperNode, DisappearingMessageConversationType, + LegacyGroupInfo, + LegacyGroupMemberInfo, + UserGroupsWrapperNode, } from 'libsession_util_nodejs'; import { isEmpty, isEqual } from 'lodash'; -import { CONVERSATION_PRIORITIES } from '../../../models/conversationAttributes'; +import { from_hex } from 'libsodium-wrappers-sumo'; +import { + CONVERSATION_PRIORITIES, + ConversationAttributes, +} from '../../../models/conversationAttributes'; import { fromHexToArray } from '../../../session/utils/String'; import { checkTargetMigration, hasDebugEnvVariable } from '../utils'; -import { ConfigDumpRow, CONFIG_DUMP_TABLE } from '../../../types/sqlSharedTypes'; +import { + ConfigDumpRow, + CONFIG_DUMP_TABLE, + maybeArrayJSONtoArray, +} from '../../../types/sqlSharedTypes'; +import { HexKeyPair } from '../../../receiver/keypairs'; +import { sqlNode } from '../../sql'; const targetVersion = 34; @@ -187,8 +200,120 @@ function updateContactInContactWrapper( } } +function getLegacyGroupInfoFromDBValues({ + id, + priority, + members: maybeMembers, + displayNameInProfile, + expirationType, + expireTimer, + encPubkeyHex, + encSeckeyHex, + groupAdmins: maybeAdmins, + lastJoinedTimestamp, +}: Pick< + ConversationAttributes, + | 'id' + | 'priority' + | 'displayNameInProfile' + | 'lastJoinedTimestamp' + | 'expirationType' + | 'expireTimer' +> & { + encPubkeyHex: string; + encSeckeyHex: string; + members: string | Array; + groupAdmins: string | Array; +}) { + const admins: Array = maybeArrayJSONtoArray(maybeAdmins); + const members: Array = maybeArrayJSONtoArray(maybeMembers); + + const wrappedMembers: Array = (members || []).map(m => { + return { + isAdmin: admins.includes(m), + pubkeyHex: m, + }; + }); + const legacyGroup: LegacyGroupInfo = { + pubkeyHex: id, + disappearingTimerSeconds: + expirationType && + (expirationType as DisappearingMessageConversationType) !== 'off' && + !!expireTimer && + expireTimer > 0 + ? expireTimer + : 0, + name: displayNameInProfile || '', + priority: priority || 0, + members: wrappedMembers, + encPubkey: !isEmpty(encPubkeyHex) ? from_hex(encPubkeyHex) : new Uint8Array(), + encSeckey: !isEmpty(encSeckeyHex) ? from_hex(encSeckeyHex) : new Uint8Array(), + joinedAtSeconds: Math.floor(lastJoinedTimestamp / 1000), + }; + + return legacyGroup; +} + +function updateLegacyGroupInWrapper( + legacyGroup: Pick< + ConversationAttributes, + | 'id' + | 'priority' + | 'displayNameInProfile' + | 'lastJoinedTimestamp' + | 'expirationType' + | 'expireTimer' + > & { members: string; groupAdmins: string }, // members and groupAdmins are still stringified here + userGroupConfigWrapper: UserGroupsWrapperNode, + db: BetterSqlite3.Database, + version: number +) { + checkTargetMigration(version, targetVersion); + + const { + priority, + id, + expirationType, + expireTimer, + groupAdmins, + members, + displayNameInProfile, + lastJoinedTimestamp, + } = legacyGroup; + + const latestEncryptionKeyPairHex = sqlNode.getLatestClosedGroupEncryptionKeyPair( + legacyGroup.id, + db + ) as HexKeyPair | undefined; + + const wrapperLegacyGroup = getLegacyGroupInfoFromDBValues({ + id, + priority, + expirationType, + expireTimer, + groupAdmins, + members, + displayNameInProfile, + encPubkeyHex: latestEncryptionKeyPairHex?.publicHex || '', + encSeckeyHex: latestEncryptionKeyPairHex?.privateHex || '', + lastJoinedTimestamp, + }); + + try { + hasDebugEnvVariable && + console.info('Inserting legacy group into wrapper: ', wrapperLegacyGroup); + const success = userGroupConfigWrapper.setLegacyGroup(wrapperLegacyGroup); + hasDebugEnvVariable && console.info('legacy group into wrapper success: ', success); + } catch (e) { + console.error( + `userGroupConfigWrapper.set during migration failed with ${e.message} for legacyGroup.id: "${legacyGroup.id}". Skipping that legacy group entirely` + ); + } +} + export const V34 = { fetchConfigDumps, writeConfigDumps, updateContactInContactWrapper, + updateLegacyGroupInWrapper, }; diff --git a/ts/node/migration/sessionMigrations.ts b/ts/node/migration/sessionMigrations.ts index b0e04159e..4f9bb5e0c 100644 --- a/ts/node/migration/sessionMigrations.ts +++ b/ts/node/migration/sessionMigrations.ts @@ -1736,6 +1736,7 @@ function updateToSessionSchemaVersion34(currentVersion: number, db: BetterSqlite ); } } + // endregion // region Disappearing Messages Private Conversations @@ -1806,7 +1807,7 @@ function updateToSessionSchemaVersion34(currentVersion: number, db: BetterSqlite ); } else { console.log( - '===================== contacts config config wrapper dump found =======================' + '===================== contacts config wrapper dump not found =======================' ); } } @@ -1815,11 +1816,76 @@ function updateToSessionSchemaVersion34(currentVersion: number, db: BetterSqlite // endregion // region Disappearing Messages Groups - db.prepare( - `UPDATE ${CONVERSATIONS_TABLE} SET + const groupConversationsInfo = db + .prepare( + `UPDATE ${CONVERSATIONS_TABLE} SET expirationType = $expirationType WHERE type = 'group' AND id LIKE '05%' AND expireTimer > 0;` - ).run({ expirationType: 'deleteAfterSend' }); + ) + .run({ expirationType: 'deleteAfterSend' }); + + if (groupConversationsInfo.changes) { + // this filter is based on the `isLegacyGroupToStoreInWrapper` function. Note, it has been expanded to check if disappearing messages is on + const legacyGroupsToWriteInWrapper = db + .prepare( + `SELECT * FROM ${CONVERSATIONS_TABLE} WHERE type = 'group' AND active_at > 0 AND id LIKE '05%' AND NOT isKickedFromGroup AND NOT left AND expirationType = 'deleteAfterSend' AND expireTimer > 0;` + ) + .all({}); + + if (isArray(legacyGroupsToWriteInWrapper) && legacyGroupsToWriteInWrapper.length) { + // Get existing config wrapper dumps and update them + const userGroupsConfigWrapperDump = MIGRATION_HELPERS.V34.fetchConfigDumps( + db, + targetVersion, + publicKeyHex, + 'UserGroupsConfig' + ); + + if (userGroupsConfigWrapperDump) { + const userGroupsConfigData = userGroupsConfigWrapperDump.data; + const userGroupsConfigWrapper = new UserGroupsWrapperNode( + privateEd25519, + userGroupsConfigData + ); + + console.info( + `===================== Starting legacy group wrapper update length: ${legacyGroupsToWriteInWrapper?.length} =======================` + ); + + legacyGroupsToWriteInWrapper.forEach(legacyGroup => { + try { + hasDebugEnvVariable && + console.info('Updating legacy group: ', JSON.stringify(legacyGroup)); + + MIGRATION_HELPERS.V34.updateLegacyGroupInWrapper( + legacyGroup, + userGroupsConfigWrapper, + db, + targetVersion + ); + } catch (e) { + console.info(`failed to insert legacy group with ${e.message}`, legacyGroup); + } + }); + + // dump the wrapper content and save it to the DB + MIGRATION_HELPERS.V34.writeConfigDumps( + db, + targetVersion, + publicKeyHex, + 'UserGroupsConfig', + userGroupsConfigWrapper.dump() + ); + console.info( + '===================== Done with legacy group inserting =======================' + ); + } else { + console.log( + '===================== user groups config wrapper dump found =======================' + ); + } + } + } // endregion