feat: started working on disappearing messages migration 34

note to self and private conversations are mvp, still some initial cleanup todo
pull/2971/head
William Grant 2 years ago
parent b556d2bc54
commit 22e9e6bb44

@ -14,10 +14,12 @@ Any helper functions that are exported from a helper file must have run checkTar
import { V31 } from './v31';
import { V33 } from './v33';
import { V34 } from './v34';
const MIGRATION_HELPERS = {
V31,
V33,
V34,
};
export default MIGRATION_HELPERS;

@ -0,0 +1,173 @@
/* 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<string>,
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,
};

@ -1,427 +0,0 @@
/* eslint-disable no-unused-expressions */
import * as BetterSqlite3 from '@signalapp/better-sqlite3';
import {
ContactInfoSet,
ContactsConfigWrapperNode,
ConvoInfoVolatileWrapperNode,
DisappearingMessageConversationType,
UserConfigWrapperNode,
} from 'libsession_util_nodejs';
import { isArray, isEmpty, isEqual, isFinite, isNumber } from 'lodash';
import { CONVERSATION_PRIORITIES } from '../../models/conversationAttributes';
import { CONFIG_DUMP_TABLE, ConfigDumpRow } from '../../types/sqlSharedTypes';
import { CONVERSATIONS_TABLE, MESSAGES_TABLE, toSqliteBoolean } from '../database_utility';
import { writeSessionSchemaVersion } from './sessionMigrations';
import { DisappearingMessageMode } from '../../util/expiringMessages';
import { fromHexToArray } from '../../session/utils/String';
import { getIdentityKeys } from '../sql';
import { getBlockedNumbersDuringMigration, hasDebugEnvVariable } from './utils';
/**
* Returns the logged in user conversation attributes and the keys.
* If the keys exists but a conversation for that pubkey does not exist yet, the keys are still returned
*/
function getLoggedInUserConvoDuringMigration(db: BetterSqlite3.Database) {
const ourKeys = getIdentityKeys(db);
if (!ourKeys || !ourKeys.publicKeyHex || !ourKeys.privateEd25519) {
return null;
}
const ourConversation = db.prepare(`SELECT * FROM ${CONVERSATIONS_TABLE} WHERE id = $id;`).get({
id: ourKeys.publicKeyHex,
}) as Record<string, any> | null;
return { ourKeys, ourConversation: ourConversation || null };
}
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:
// string must be a valid mode
expirationType && DisappearingMessageMode.includes(expirationType)
? (expirationType as DisappearingMessageConversationType)
: 'off',
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<string>,
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
) {
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 function updateToSessionSchemaVersion34(currentVersion: number, db: BetterSqlite3.Database) {
const targetVersion = 34;
if (currentVersion >= targetVersion) {
return;
}
// TODO we actually want to update the config wrappers that relate to disappearing messages with the type and seconds
console.log(`updateToSessionSchemaVersion${targetVersion}: starting...`);
db.transaction(() => {
try {
const loggedInUser = getLoggedInUserConvoDuringMigration(db);
if (!loggedInUser || !loggedInUser.ourKeys) {
throw new Error('privateEd25519 was empty. Considering no users are logged in');
}
const { privateEd25519, publicKeyHex } = loggedInUser.ourKeys;
// Conversation changes
// TODO can this be moved into libsession completely
db.prepare(
`ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN expirationType TEXT DEFAULT "off";`
).run();
db.prepare(
`ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN lastDisappearingMessageChangeTimestamp INTEGER DEFAULT 0;`
).run();
db.prepare(`ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN hasOutdatedClient TEXT;`).run();
// region Disappearing Messages Note to Self
const noteToSelfInfo = db
.prepare(
`UPDATE ${CONVERSATIONS_TABLE} SET
expirationType = $expirationType
WHERE id = $id AND type = 'private' AND expireTimer > 0;`
)
.run({ expirationType: 'deleteAfterSend', id: publicKeyHex });
if (noteToSelfInfo.changes) {
const ourConversation = db
.prepare(`SELECT * FROM ${CONVERSATIONS_TABLE} WHERE id = $id`)
.get({ id: publicKeyHex });
const expirySeconds = ourConversation.expireTimer || 0;
// TODO update with Audric's snippet
// Get existing config wrapper dump and update it
const userConfigWrapperDump = db
.prepare(`SELECT * FROM ${CONFIG_DUMP_TABLE} WHERE variant = 'UserConfig';`)
.get() as ConfigDumpRow | undefined;
if (userConfigWrapperDump) {
const userConfigData = userConfigWrapperDump.data;
const userProfileWrapper = new UserConfigWrapperNode(privateEd25519, userConfigData);
userProfileWrapper.setNoteToSelfExpiry(expirySeconds);
// dump the user wrapper content and save it to the DB
const userDump = userProfileWrapper.dump();
const configDumpInfo = db
.prepare(
`INSERT OR REPLACE INTO ${CONFIG_DUMP_TABLE} (
publicKey,
variant,
data
) values (
$publicKey,
$variant,
$data
);`
)
.run({
publicKey: publicKeyHex,
variant: 'UserConfig',
data: userDump,
});
// TODO Cleanup logging
console.log(
'===================== userConfigWrapperDump configDumpInfo',
configDumpInfo,
'======================='
);
} else {
console.log(
'===================== userConfigWrapperDump not found ======================='
);
}
}
// endregion
// region Disappearing Messages Private Conversations
const privateConversationsInfo = db
.prepare(
`UPDATE ${CONVERSATIONS_TABLE} SET
expirationType = $expirationType
WHERE type = 'private' AND expirationType = 'off' AND expireTimer > 0;`
)
.run({ expirationType: 'deleteAfterRead' });
if (privateConversationsInfo.changes) {
// this filter is based on the `isContactToStoreInWrapper` function. Note, it has been expanded to check if disappearing messages is on
const contactsToUpdateInWrapper = db
.prepare(
`SELECT * FROM ${CONVERSATIONS_TABLE} WHERE type = 'private' AND active_at > 0 AND priority <> ${CONVERSATION_PRIORITIES.hidden} AND (didApproveMe OR isApproved) AND id <> '$us' AND id NOT LIKE '15%' AND id NOT LIKE '25%' AND expirationType = 'deleteAfterRead' AND expireTimer > 0;`
)
.all({
us: publicKeyHex,
});
if (isArray(contactsToUpdateInWrapper) && contactsToUpdateInWrapper.length) {
const blockedNumbers = getBlockedNumbersDuringMigration(db);
const contactsWrapperDump = db
.prepare(`SELECT * FROM ${CONFIG_DUMP_TABLE} WHERE variant = 'ContactConfig';`)
.get() as ConfigDumpRow | undefined;
const volatileInfoConfigWrapperDump = db
.prepare(
`SELECT * FROM ${CONFIG_DUMP_TABLE} WHERE variant = 'ConvoInfoVolatileConfig';`
)
.get() as ConfigDumpRow | undefined;
if (contactsWrapperDump && volatileInfoConfigWrapperDump) {
const contactsData = contactsWrapperDump.data;
const contactsConfigWrapper = new ContactsConfigWrapperNode(
privateEd25519,
contactsData
);
const volatileInfoData = volatileInfoConfigWrapperDump.data;
const volatileInfoConfigWrapper = new ConvoInfoVolatileWrapperNode(
privateEd25519,
volatileInfoData
);
console.info(
`===================== Starting contact update into wrapper ${contactsToUpdateInWrapper?.length} =======================`
);
contactsToUpdateInWrapper.forEach(contact => {
insertContactIntoContactWrapper(
contact,
blockedNumbers,
contactsConfigWrapper,
volatileInfoConfigWrapper,
db
);
});
console.info(
'===================== Done with contact updating ======================='
);
// dump the user wrapper content and save it to the DB
const contactsDump = contactsConfigWrapper.dump();
const contactsDumpInfo = db
.prepare(
`INSERT OR REPLACE INTO ${CONFIG_DUMP_TABLE} (
publicKey,
variant,
data
) values (
$publicKey,
$variant,
$data
);`
)
.run({
publicKey: publicKeyHex,
variant: 'ContactConfig',
data: contactsDump,
});
// TODO Cleanup logging
console.log(
'===================== contactsConfigWrapper contactsDumpInfo',
contactsDumpInfo,
'======================='
);
const volatileInfoConfigDump = volatileInfoConfigWrapper.dump();
const volatileInfoConfigDumpInfo = db
.prepare(
`INSERT OR REPLACE INTO ${CONFIG_DUMP_TABLE} (
publicKey,
variant,
data
) values (
$publicKey,
$variant,
$data
);`
)
.run({
publicKey: publicKeyHex,
variant: 'ConvoInfoVolatileConfig',
data: volatileInfoConfigDump,
});
// TODO Cleanup logging
console.log(
'===================== volatileInfoConfigWrapper volatileInfoConfigDumpInfo',
volatileInfoConfigDumpInfo,
'======================='
);
} else {
console.log(
'===================== contactsWrapperDump or volatileInfoConfigWrapperDump was not found ======================='
);
}
}
}
// endregion
// region Disappearing Messages Groups
db.prepare(
`UPDATE ${CONVERSATIONS_TABLE} SET
expirationType = $expirationType
WHERE type = 'group' AND id LIKE '05%' AND expireTimer > 0;`
).run({ expirationType: 'deleteAfterSend' });
// endregion
// Message changes
db.prepare(`ALTER TABLE ${MESSAGES_TABLE} ADD COLUMN expirationType TEXT;`).run();
} catch (e) {
console.error(
`Failed to migrate to disappearing messages v2. Might just not have a logged in user yet? `,
e.message,
e.stack,
e
);
// if we get an exception here, most likely no users are logged in yet. We can just continue the transaction and the wrappers will be created when a user creates a new account.
}
writeSessionSchemaVersion(targetVersion, db);
})();
console.log(`updateToSessionSchemaVersion${targetVersion}: success!`);
}

@ -12,7 +12,7 @@ import {
ConversationAttributes,
} from '../../models/conversationAttributes';
import { fromHexToArray } from '../../session/utils/String';
import { CONFIG_DUMP_TABLE } from '../../types/sqlSharedTypes';
import { CONFIG_DUMP_TABLE, ConfigDumpRow } from '../../types/sqlSharedTypes';
import {
CLOSED_GROUP_V2_KEY_PAIRS_TABLE,
CONVERSATIONS_TABLE,
@ -102,6 +102,7 @@ const LOKI_SCHEMA_VERSIONS = [
updateToSessionSchemaVersion31,
updateToSessionSchemaVersion32,
updateToSessionSchemaVersion33,
updateToSessionSchemaVersion34,
];
function updateToSessionSchemaVersion1(currentVersion: number, db: BetterSqlite3.Database) {
@ -1657,6 +1658,249 @@ function updateToSessionSchemaVersion33(currentVersion: number, db: BetterSqlite
})();
}
function updateToSessionSchemaVersion34(currentVersion: number, db: BetterSqlite3.Database) {
const targetVersion = 34;
if (currentVersion >= targetVersion) {
return;
}
// TODO we actually want to update the config wrappers that relate to disappearing messages with the type and seconds
console.log(`updateToSessionSchemaVersion${targetVersion}: starting...`);
db.transaction(() => {
try {
const loggedInUser = getLoggedInUserConvoDuringMigration(db);
if (!loggedInUser || !loggedInUser.ourKeys) {
throw new Error('privateEd25519 was empty. Considering no users are logged in');
}
const { privateEd25519, publicKeyHex } = loggedInUser.ourKeys;
// Conversation changes
// TODO can this be moved into libsession completely
db.prepare(
`ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN expirationType TEXT DEFAULT "off";`
).run();
db.prepare(
`ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN lastDisappearingMessageChangeTimestamp INTEGER DEFAULT 0;`
).run();
db.prepare(`ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN hasOutdatedClient TEXT;`).run();
// region Disappearing Messages Note to Self
const noteToSelfInfo = db
.prepare(
`UPDATE ${CONVERSATIONS_TABLE} SET
expirationType = $expirationType
WHERE id = $id AND type = 'private' AND expireTimer > 0;`
)
.run({ expirationType: 'deleteAfterSend', id: publicKeyHex });
if (noteToSelfInfo.changes) {
const ourConversation = db
.prepare(`SELECT * FROM ${CONVERSATIONS_TABLE} WHERE id = $id`)
.get({ id: publicKeyHex });
const expirySeconds = ourConversation.expireTimer || 0;
// TODO update with Audric's snippet
// Get existing config wrapper dump and update it
const userConfigWrapperDump = db
.prepare(`SELECT * FROM ${CONFIG_DUMP_TABLE} WHERE variant = 'UserConfig';`)
.get() as ConfigDumpRow | undefined;
if (userConfigWrapperDump) {
const userConfigData = userConfigWrapperDump.data;
const userProfileWrapper = new UserConfigWrapperNode(privateEd25519, userConfigData);
userProfileWrapper.setNoteToSelfExpiry(expirySeconds);
// dump the user wrapper content and save it to the DB
const userDump = userProfileWrapper.dump();
const configDumpInfo = db
.prepare(
`INSERT OR REPLACE INTO ${CONFIG_DUMP_TABLE} (
publicKey,
variant,
data
) values (
$publicKey,
$variant,
$data
);`
)
.run({
publicKey: publicKeyHex,
variant: 'UserConfig',
data: userDump,
});
// TODO Cleanup logging
console.log(
'===================== userConfigWrapperDump configDumpInfo',
configDumpInfo,
'======================='
);
} else {
console.log(
'===================== userConfigWrapperDump not found ======================='
);
}
}
// endregion
// region Disappearing Messages Private Conversations
const privateConversationsInfo = db
.prepare(
`UPDATE ${CONVERSATIONS_TABLE} SET
expirationType = $expirationType
WHERE type = 'private' AND expirationType = 'off' AND expireTimer > 0;`
)
.run({ expirationType: 'deleteAfterRead' });
if (privateConversationsInfo.changes) {
// this filter is based on the `isContactToStoreInWrapper` function. Note, it has been expanded to check if disappearing messages is on
const contactsToUpdateInWrapper = db
.prepare(
`SELECT * FROM ${CONVERSATIONS_TABLE} WHERE type = 'private' AND active_at > 0 AND priority <> ${CONVERSATION_PRIORITIES.hidden} AND (didApproveMe OR isApproved) AND id <> '$us' AND id NOT LIKE '15%' AND id NOT LIKE '25%' AND expirationType = 'deleteAfterRead' AND expireTimer > 0;`
)
.all({
us: publicKeyHex,
});
if (isArray(contactsToUpdateInWrapper) && contactsToUpdateInWrapper.length) {
const blockedNumbers = getBlockedNumbersDuringMigration(db);
const contactsWrapperDump = db
.prepare(`SELECT * FROM ${CONFIG_DUMP_TABLE} WHERE variant = 'ContactConfig';`)
.get() as ConfigDumpRow | undefined;
const volatileInfoConfigWrapperDump = db
.prepare(
`SELECT * FROM ${CONFIG_DUMP_TABLE} WHERE variant = 'ConvoInfoVolatileConfig';`
)
.get() as ConfigDumpRow | undefined;
if (contactsWrapperDump && volatileInfoConfigWrapperDump) {
const contactsData = contactsWrapperDump.data;
const contactsConfigWrapper = new ContactsConfigWrapperNode(
privateEd25519,
contactsData
);
const volatileInfoData = volatileInfoConfigWrapperDump.data;
const volatileInfoConfigWrapper = new ConvoInfoVolatileWrapperNode(
privateEd25519,
volatileInfoData
);
console.info(
`===================== Starting contact update into wrapper ${contactsToUpdateInWrapper?.length} =======================`
);
contactsToUpdateInWrapper.forEach(contact => {
MIGRATION_HELPERS.V34.insertContactIntoContactWrapper(
contact,
blockedNumbers,
contactsConfigWrapper,
volatileInfoConfigWrapper,
db,
targetVersion
);
});
console.info(
'===================== Done with contact updating ======================='
);
// dump the user wrapper content and save it to the DB
const contactsDump = contactsConfigWrapper.dump();
const contactsDumpInfo = db
.prepare(
`INSERT OR REPLACE INTO ${CONFIG_DUMP_TABLE} (
publicKey,
variant,
data
) values (
$publicKey,
$variant,
$data
);`
)
.run({
publicKey: publicKeyHex,
variant: 'ContactConfig',
data: contactsDump,
});
// TODO Cleanup logging
console.log(
'===================== contactsConfigWrapper contactsDumpInfo',
contactsDumpInfo,
'======================='
);
const volatileInfoConfigDump = volatileInfoConfigWrapper.dump();
const volatileInfoConfigDumpInfo = db
.prepare(
`INSERT OR REPLACE INTO ${CONFIG_DUMP_TABLE} (
publicKey,
variant,
data
) values (
$publicKey,
$variant,
$data
);`
)
.run({
publicKey: publicKeyHex,
variant: 'ConvoInfoVolatileConfig',
data: volatileInfoConfigDump,
});
// TODO Cleanup logging
console.log(
'===================== volatileInfoConfigWrapper volatileInfoConfigDumpInfo',
volatileInfoConfigDumpInfo,
'======================='
);
} else {
console.log(
'===================== contactsWrapperDump or volatileInfoConfigWrapperDump was not found ======================='
);
}
}
}
// endregion
// region Disappearing Messages Groups
db.prepare(
`UPDATE ${CONVERSATIONS_TABLE} SET
expirationType = $expirationType
WHERE type = 'group' AND id LIKE '05%' AND expireTimer > 0;`
).run({ expirationType: 'deleteAfterSend' });
// endregion
// Message changes
db.prepare(`ALTER TABLE ${MESSAGES_TABLE} ADD COLUMN expirationType TEXT;`).run();
} catch (e) {
console.error(
`Failed to migrate to disappearing messages v2. Might just not have a logged in user yet? `,
e.message,
e.stack,
e
);
// if we get an exception here, most likely no users are logged in yet. We can just continue the transaction and the wrappers will be created when a user creates a new account.
}
writeSessionSchemaVersion(targetVersion, db);
})();
console.log(`updateToSessionSchemaVersion${targetVersion}: success!`);
}
export function printTableColumns(table: string, db: BetterSqlite3.Database) {
console.info(db.pragma(`table_info('${table}');`));
}

@ -54,9 +54,8 @@ async function insertContactFromDBIntoWrapperAndRefresh(id: string): Promise<voi
const dbApprovedMe = !!foundConvo.get('didApproveMe') || false;
const dbBlocked = !!foundConvo.isBlocked() || false;
const priority = foundConvo.get('priority') || 0;
// TODO need to sort out the migration issues related to getContactInfoFromDBValues
// const expirationType = foundConvo.get('expirationType') || undefined;
// const expireTimer = foundConvo.get('expireTimer') || 0;
const expirationType = foundConvo.get('expirationType') || undefined;
const expireTimer = foundConvo.get('expireTimer') || 0;
const wrapperContact = getContactInfoFromDBValues({
id,
@ -69,8 +68,8 @@ async function insertContactFromDBIntoWrapperAndRefresh(id: string): Promise<voi
dbProfileUrl,
priority,
dbCreatedAtSeconds: 0, // just give 0, now() will be used internally by the wrapper if the contact does not exist yet.
// expirationType,
// expireTimer,
expirationType,
expireTimer,
});
try {
window.log.debug('inserting into contact wrapper: ', JSON.stringify(wrapperContact));

@ -1,9 +1,14 @@
/* eslint-disable import/extensions */
/* eslint-disable import/no-unresolved */
// eslint-disable-next-line camelcase
import { ContactInfoSet, LegacyGroupInfo, LegacyGroupMemberInfo } from 'libsession_util_nodejs';
import {
ContactInfoSet,
DisappearingMessageConversationType,
LegacyGroupInfo,
LegacyGroupMemberInfo,
} from 'libsession_util_nodejs';
import { from_hex } from 'libsodium-wrappers-sumo';
import { isArray, isEmpty, isEqual } from 'lodash';
import { isArray, isEmpty, isEqual, isFinite } from 'lodash';
import { OpenGroupV2Room } from '../data/opengroups';
import { ConversationAttributes } from '../models/conversationAttributes';
import { OpenGroupRequestCommonType } from '../session/apis/open_group_api/opengroupV2/ApiUtil';
@ -105,7 +110,7 @@ export type SaveConversationReturn = {
} | null;
/**
* NOTE This code should always match the last known version of the same function used in a libsession migration (V31)
* NOTE This code should always match the last known version of the same function used in a libsession migration (V34)
*
* 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
@ -121,6 +126,8 @@ export function getContactInfoFromDBValues({
dbProfileUrl,
dbProfileKey,
dbCreatedAtSeconds,
expirationType,
expireTimer,
}: {
id: string;
dbApproved: boolean;
@ -132,6 +139,8 @@ export function getContactInfoFromDBValues({
dbProfileUrl: string | undefined;
dbProfileKey: string | undefined;
dbCreatedAtSeconds: number;
expirationType: string | undefined;
expireTimer: number | undefined;
}): ContactInfoSet {
const wrapperContact: ContactInfoSet = {
id,
@ -142,6 +151,11 @@ export function getContactInfoFromDBValues({
nickname: dbNickname,
name: dbName,
createdAtSeconds: dbCreatedAtSeconds,
expirationMode: expirationType
? (expirationType as DisappearingMessageConversationType)
: undefined,
expirationTimerSeconds:
!!expireTimer && isFinite(expireTimer) && expireTimer > 0 ? expireTimer * 1000 : 0,
};
if (

@ -16,7 +16,7 @@ import { ReleasedFeatures } from './releaseFeature';
// TODO do we need to add legacy here now that it's explicitly in the protobuf?
export const DisappearingMessageMode = ['deleteAfterRead', 'deleteAfterSend'];
export type DisappearingMessageType = typeof DisappearingMessageMode[number] | null;
// NOTE these cannot be imported in the nodejs side yet. We need to move the types to the own file with no window imports
// TODO legacy messages support will be removed in a future release
// TODO NOTE do we need to remove 'legacy' from here?
export const DisappearingMessageConversationSetting = ['off', ...DisappearingMessageMode, 'legacy'];

Loading…
Cancel
Save