feat: handle legacy disappearing messages more gracefully due to protobuf issues

added utility function for checking for undefined properties on a protobuf, renamed expireTimer to expirationTimer in some places
pull/2660/head
William Grant 2 years ago
parent 4c4bc045d7
commit 6dd340ca6c

@ -1109,7 +1109,7 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
: isLegacyMessage : isLegacyMessage
? DisappearingMessageConversationSetting[3] ? DisappearingMessageConversationSetting[3]
: 'off'; : 'off';
const expireTimer = isLegacyMessage const expirationTimer = isLegacyMessage
? Number(dataMessage.expireTimer) ? Number(dataMessage.expireTimer)
: content.expirationTimer; : content.expirationTimer;
const lastDisappearingMessageChangeTimestamp = content.lastDisappearingMessageChangeTimestamp const lastDisappearingMessageChangeTimestamp = content.lastDisappearingMessageChangeTimestamp
@ -1117,10 +1117,10 @@ export class MessageModel extends Backbone.Model<MessageAttributes> {
: undefined; : undefined;
let expireUpdate: DisappearingMessageUpdate | null = null; let expireUpdate: DisappearingMessageUpdate | null = null;
if (expirationType && expireTimer !== undefined) { if (expirationType && expirationTimer !== undefined) {
expireUpdate = { expireUpdate = {
expirationType, expirationType,
expireTimer, expirationTimer,
lastDisappearingMessageChangeTimestamp, lastDisappearingMessageChangeTimestamp,
}; };
} }

@ -1226,8 +1226,8 @@ function updateToSessionSchemaVersion30(currentVersion: number, db: BetterSqlite
db.prepare(`ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN hasOutdatedClient TEXT;`).run(); db.prepare(`ALTER TABLE ${CONVERSATIONS_TABLE} ADD COLUMN hasOutdatedClient TEXT;`).run();
// TODO update to agreed value between platforms // TODO update to agreed value between platforms
const disappearingMessagesV2ReleaseTimestamp = 1680339600000; // unix 01/04/2023 09:00 // const disappearingMessagesV2ReleaseTimestamp = 1680339600000; // unix 01/04/2023 09:00
// const disappearingMessagesV2ReleaseTimestamp = 1677488400000; // unix 27/02/2023 09:00 const disappearingMessagesV2ReleaseTimestamp = 1677488400000; // unix 27/02/2023 09:00
// support disppearing messages legacy mode until after the platform agreed timestamp // support disppearing messages legacy mode until after the platform agreed timestamp
if (Date.now() < disappearingMessagesV2ReleaseTimestamp) { if (Date.now() < disappearingMessagesV2ReleaseTimestamp) {

@ -1,3 +1,4 @@
import { signalservice as SignalService } from './compiled'; import { signalservice as SignalService } from './compiled';
import { ProtobufUtils } from './utils';
export { SignalService }; export { SignalService, ProtobufUtils };

@ -0,0 +1,15 @@
/**
* This function is used to check that an optional property on a Protobuf object is not undefined or using a type-specific default value.
* https://protobuf.dev/programming-guides/proto/#optional
*
* @param object - A Protobuf/JavaScript object
* @param property - The property you want make sure is not undefined
* @returns true if the property is defined or false if undefined or using a type-specific default value
*/
function hasDefinedProperty<A extends {}, B extends PropertyKey & keyof A>(object: A, property: B) {
return object.hasOwnProperty(property) !== false;
}
export const ProtobufUtils = {
hasDefinedProperty,
};

@ -31,6 +31,7 @@ import { appendFetchAvatarAndProfileJob } from './userProfileImageUpdates';
import { import {
DisappearingMessageConversationSetting, DisappearingMessageConversationSetting,
DisappearingMessageUpdate, DisappearingMessageUpdate,
DisappearingMessageUtils,
setExpirationStartTimestamp, setExpirationStartTimestamp,
} from '../util/expiringMessages'; } from '../util/expiringMessages';
import { checkIsFeatureReleased } from '../util/releaseFeature'; import { checkIsFeatureReleased } from '../util/releaseFeature';
@ -409,43 +410,55 @@ export async function innerHandleSwarmContentMessage(
dataMessage.profileKey = null; dataMessage.profileKey = null;
} }
// debugger;
// TODO legacy messages support will be removed in a future release // TODO legacy messages support will be removed in a future release
// We will only support legacy disappearing messages for a short period before disappearing messages v2 is unlocked // We will only support legacy disappearing messages for a short period before disappearing messages v2 is unlocked
const isDisappearingMessagesV2Released = await checkIsFeatureReleased( const isDisappearingMessagesV2Released = await checkIsFeatureReleased(
'Disappearing Messages V2' 'Disappearing Messages V2'
); );
const isLegacy = Boolean( const isLegacyContentMessage = DisappearingMessageUtils.isLegacyContentMessage(content);
(!content.expirationType && !content.expirationTimer) ||
content.expirationType === SignalService.Content.ExpirationType.UNKNOWN
);
const isLegacyMessage = Boolean( const isLegacyMessage = Boolean(
isLegacy && dataMessage.expireTimer && dataMessage.expireTimer > -1 isLegacyContentMessage &&
DisappearingMessageUtils.isLegacyDataMessage(dataMessage as SignalService.DataMessage)
); );
// NOTE When a legacy client sends a Conversation Setting Message dataMessage.expirationType and dataMessage.expireTimer can possibly be undefined. // NOTE When a legacy client sends a Conversation Setting Message dataMessage.expirationType and dataMessage.expireTimer can possibly be undefined.
const isLegacyConversationSettingMessage = Boolean( const isLegacyConversationSettingMessage = Boolean(
isLegacy && dataMessage.flags === SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE isLegacyContentMessage &&
dataMessage.flags === SignalService.DataMessage.Flags.EXPIRATION_TIMER_UPDATE
); );
const expireTimer = isDisappearingMessagesV2Released debugger;
let expirationTimer = isDisappearingMessagesV2Released
? content.expirationTimer ? content.expirationTimer
: isLegacyMessage : isLegacyMessage
? Number(dataMessage.expireTimer) ? Number(dataMessage.expireTimer)
: 0; : content.expirationTimer;
// TODO legacy messages support will be removed in a future release // TODO legacy messages support will be removed in a future release
const expirationType = isDisappearingMessagesV2Released // TODO so close! just have to fix the case of turning off disappearing messages in modern clients. Probably if the timer is zero then override any settings.
? DisappearingMessageConversationSetting[isLegacy ? 3 : content.expirationType] let expirationType =
: isLegacyMessage && expireTimer > 0 expirationTimer === 0
? DisappearingMessageConversationSetting[3] ? 'off'
: 'off'; : isDisappearingMessagesV2Released
? DisappearingMessageConversationSetting[
isLegacyContentMessage ? 3 : content.expirationType
]
: DisappearingMessageConversationSetting[3];
const lastDisappearingMessageChangeTimestamp = content.lastDisappearingMessageChangeTimestamp const lastDisappearingMessageChangeTimestamp = content.lastDisappearingMessageChangeTimestamp
? Number(content.lastDisappearingMessageChangeTimestamp) ? Number(content.lastDisappearingMessageChangeTimestamp)
: undefined; : undefined;
// TODO legacy messages support will be removed in a future release
// if it is a legacy message and disappearing messages v2 is released then we ignore it and use the local client's conversation settings
if (isDisappearingMessagesV2Released && isLegacyContentMessage) {
window.log.info(`WIP: received a legacy disappearing message after v2 was released.`);
expirationType = conversationModelForUIUpdate.get('expirationType');
expirationTimer = conversationModelForUIUpdate.get('expireTimer');
}
const expireUpdate: DisappearingMessageUpdate = { const expireUpdate: DisappearingMessageUpdate = {
expirationType, expirationType,
expireTimer, expirationTimer,
lastDisappearingMessageChangeTimestamp, lastDisappearingMessageChangeTimestamp,
isLegacyConversationSettingMessage, isLegacyConversationSettingMessage,
isLegacyMessage, isLegacyMessage,

@ -249,21 +249,13 @@ export async function handleSwarmDataMessage(
} else { } else {
let { let {
expirationType, expirationType,
expireTimer, // TODO renamed expireTimer to expirationTimer
expirationTimer: expireTimer,
lastDisappearingMessageChangeTimestamp, lastDisappearingMessageChangeTimestamp,
isLegacyConversationSettingMessage, isLegacyConversationSettingMessage,
isLegacyMessage,
isDisappearingMessagesV2Released, isDisappearingMessagesV2Released,
} = expireUpdate; } = expireUpdate;
// TODO legacy messages support will be removed in a future release
// if it is a legacy message and disappearing messages v2 is released then we ignore it and use the local client's conversation settings
if (isDisappearingMessagesV2Released && isLegacyMessage) {
window.log.info(`WIP: received a legacy disappearing message after v2 was released.`);
expirationType = convoToAddMessageTo.get('expirationType');
expireTimer = convoToAddMessageTo.get('expireTimer');
}
msgModel.set({ msgModel.set({
expirationType, expirationType,
expireTimer, expireTimer,

@ -328,8 +328,6 @@ export async function handleMessageJob(
) || messageModel.get('timestamp')} in conversation ${conversation.idForLogging()}` ) || messageModel.get('timestamp')} in conversation ${conversation.idForLogging()}`
); );
window.log.info(`WIP: handleMessageJob()`, messageModel, conversation, regularDataMessage);
const sendingDeviceConversation = await getConversationController().getOrCreateAndWait( const sendingDeviceConversation = await getConversationController().getOrCreateAndWait(
source, source,
ConversationTypeEnum.PRIVATE ConversationTypeEnum.PRIVATE

@ -300,7 +300,12 @@ const buildSyncExpireTimerMessage = (
timestamp: number, timestamp: number,
syncTarget: string syncTarget: string
) => { ) => {
const { expirationType, expireTimer, lastDisappearingMessageChangeTimestamp } = expireUpdate; const {
expirationType,
// TODO rename expireTimer to expirationTimer
expirationTimer: expireTimer,
lastDisappearingMessageChangeTimestamp,
} = expireUpdate;
return new ExpirationTimerUpdateMessage({ return new ExpirationTimerUpdateMessage({
identifier, identifier,

@ -8,6 +8,7 @@ import { initWallClockListener } from './wallClockListener';
import { Data } from '../data/data'; import { Data } from '../data/data';
import { getConversationController } from '../session/conversations'; import { getConversationController } from '../session/conversations';
import { getNowWithNetworkOffset } from '../session/apis/snode_api/SNodeAPI'; import { getNowWithNetworkOffset } from '../session/apis/snode_api/SNodeAPI';
import { ProtobufUtils, SignalService } from '../protobuf';
// TODO Might need to be improved by using an enum // TODO Might need to be improved by using an enum
// TODO do we need to add legacy here now that it's explicitly in the protbuf? // TODO do we need to add legacy here now that it's explicitly in the protbuf?
@ -24,8 +25,7 @@ export const DEFAULT_TIMER_OPTION = {
export type DisappearingMessageUpdate = { export type DisappearingMessageUpdate = {
expirationType: DisappearingMessageType; expirationType: DisappearingMessageType;
// TODO rename to expirationTimer? expirationTimer: number;
expireTimer: number;
// This is used for the expirationTimerUpdate // This is used for the expirationTimerUpdate
lastDisappearingMessageChangeTimestamp?: number; lastDisappearingMessageChangeTimestamp?: number;
isLegacyConversationSettingMessage?: boolean; isLegacyConversationSettingMessage?: boolean;
@ -33,6 +33,27 @@ export type DisappearingMessageUpdate = {
isDisappearingMessagesV2Released?: boolean; isDisappearingMessagesV2Released?: boolean;
}; };
// TODO legacy messages support will be removed in a future release
// NOTE We need this to check for legacy disappearing messages where the expirationType and expireTimer should be undefined on the ContentMessage
function isLegacyContentMessage(contentMessage: SignalService.Content): boolean {
return (
(contentMessage.expirationType === SignalService.Content.ExpirationType.UNKNOWN ||
!ProtobufUtils.hasDefinedProperty(contentMessage, 'expirationType')) &&
!ProtobufUtils.hasDefinedProperty(contentMessage, 'expirationTimer')
);
}
function isLegacyDataMessage(dataMessage: SignalService.DataMessage): boolean {
return (
ProtobufUtils.hasDefinedProperty(dataMessage, 'expireTimer') && dataMessage.expireTimer > -1
);
}
export const DisappearingMessageUtils = {
isLegacyContentMessage,
isLegacyDataMessage,
};
export async function destroyMessagesAndUpdateRedux( export async function destroyMessagesAndUpdateRedux(
messages: Array<{ messages: Array<{
conversationKey: string; conversationKey: string;

@ -1,8 +1,8 @@
import { Data } from '../data/data'; import { Data } from '../data/data';
// TODO update to agreed value between platforms // TODO update to agreed value between platforms
const featureReleaseTimestamp = 1680339600000; // unix 01/04/2023 09:00 // const featureReleaseTimestamp = 1680339600000; // unix 01/04/2023 09:00
// const featureReleaseTimestamp = 1677488400000; // unix 27/02/2023 09:00 const featureReleaseTimestamp = 1677488400000; // unix 27/02/2023 09:00
let isFeatureReleased: boolean | undefined; let isFeatureReleased: boolean | undefined;
/** /**

Loading…
Cancel
Save