diff --git a/protos/SignalService.proto b/protos/SignalService.proto index bd1ab0b2fe85..9bd13ab1f028 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -26,13 +26,16 @@ message Envelope { optional string source = 2; optional string sourceUuid = 11; optional uint32 sourceDevice = 7; + optional string destinationUuid = 13; // reserved 3; // formerly optional string relay = 3; optional uint64 timestamp = 5; // reserved 6; // formerly optional bytes legacyMessage = 6; optional bytes content = 8; // Contains an encrypted Content optional string serverGuid = 9; optional uint64 serverTimestamp = 10; - optional string destinationUuid = 13; + optional bool ephemeral = 12; // indicates that the message should not be persisted if the recipient is offline + optional bool urgent = 14 [default=true]; // indicates that the content is considered timely by the sender; defaults to true so senders have to opt-out to say something isn't time critical + // next: 15 } message Content { diff --git a/ts/jobs/helpers/sendDeleteForEveryone.ts b/ts/jobs/helpers/sendDeleteForEveryone.ts index 8766800f82b4..0cd7a108fa6f 100644 --- a/ts/jobs/helpers/sendDeleteForEveryone.ts +++ b/ts/jobs/helpers/sendDeleteForEveryone.ts @@ -130,6 +130,7 @@ export async function sendDeleteForEveryone( expirationStartTimestamp: null, options: sendOptions, timestamp, + urgent: false, }), { messageIds, sendType } ); @@ -186,6 +187,7 @@ export async function sendDeleteForEveryone( groupId: undefined, profileKey, options: sendOptions, + urgent: true, }), sendType, timestamp, @@ -223,6 +225,7 @@ export async function sendDeleteForEveryone( sendOptions, sendTarget: conversation.toSenderKeyTarget(), sendType: 'deleteForEveryone', + urgent: true, }), sendType, timestamp, diff --git a/ts/jobs/helpers/sendDirectExpirationTimerUpdate.ts b/ts/jobs/helpers/sendDirectExpirationTimerUpdate.ts index d387cbbc9b1a..8aac4ff464ee 100644 --- a/ts/jobs/helpers/sendDirectExpirationTimerUpdate.ts +++ b/ts/jobs/helpers/sendDirectExpirationTimerUpdate.ts @@ -105,6 +105,7 @@ export async function sendDirectExpirationTimerUpdate( expirationStartTimestamp: null, options: sendOptions, timestamp, + urgent: false, }), { messageIds: [], sendType } ); @@ -139,6 +140,7 @@ export async function sendDirectExpirationTimerUpdate( options: sendOptions, proto, timestamp, + urgent: false, }), sendType, timestamp, diff --git a/ts/jobs/helpers/sendGroupUpdate.ts b/ts/jobs/helpers/sendGroupUpdate.ts index a11ce13374a9..ee605acc2456 100644 --- a/ts/jobs/helpers/sendGroupUpdate.ts +++ b/ts/jobs/helpers/sendGroupUpdate.ts @@ -110,6 +110,7 @@ export async function sendGroupUpdate( sendOptions, sendTarget: conversation.toSenderKeyTarget(), sendType, + urgent: false, }), sendType, timestamp, diff --git a/ts/jobs/helpers/sendNormalMessage.ts b/ts/jobs/helpers/sendNormalMessage.ts index f78c16a0474e..8333a79ed8ca 100644 --- a/ts/jobs/helpers/sendNormalMessage.ts +++ b/ts/jobs/helpers/sendNormalMessage.ts @@ -232,6 +232,7 @@ export async function sendNormalMessage( sendOptions, sendTarget: conversation.toSenderKeyTarget(), sendType: 'message', + urgent: true, }) ); } else { @@ -279,6 +280,7 @@ export async function sendNormalMessage( sticker, storyContext, timestamp: messageTimestamp, + urgent: true, }); } diff --git a/ts/jobs/helpers/sendProfileKey.ts b/ts/jobs/helpers/sendProfileKey.ts index 8a8ca3452565..d24b208b9522 100644 --- a/ts/jobs/helpers/sendProfileKey.ts +++ b/ts/jobs/helpers/sendProfileKey.ts @@ -136,6 +136,7 @@ export async function sendProfileKey( options: sendOptions, proto, timestamp, + urgent: false, }); } else { if (isGroupV2(conversation.attributes) && !isNumber(revision)) { @@ -160,6 +161,7 @@ export async function sendProfileKey( sendOptions, sendTarget: conversation.toSenderKeyTarget(), sendType, + urgent: false, }); } diff --git a/ts/jobs/helpers/sendReaction.ts b/ts/jobs/helpers/sendReaction.ts index 0d00023d1317..85e954af89d3 100644 --- a/ts/jobs/helpers/sendReaction.ts +++ b/ts/jobs/helpers/sendReaction.ts @@ -239,6 +239,7 @@ export async function sendReaction( timestamp: message.get('sent_at'), } : undefined, + urgent: true, }); } else { log.info('sending group reaction message'); @@ -280,6 +281,7 @@ export async function sendReaction( sendOptions, sendTarget: conversation.toSenderKeyTarget(), sendType: 'reaction', + urgent: true, }); } ); diff --git a/ts/jobs/singleProtoJobQueue.ts b/ts/jobs/singleProtoJobQueue.ts index a826b810986b..2f38ca5f932d 100644 --- a/ts/jobs/singleProtoJobQueue.ts +++ b/ts/jobs/singleProtoJobQueue.ts @@ -2,6 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import PQueue from 'p-queue'; +import { isBoolean } from 'lodash'; import * as Bytes from '../Bytes'; import type { LoggerType } from '../types/Logging'; @@ -67,6 +68,7 @@ export class SingleProtoJobQueue extends JobQueue { messageIds = [], protoBase64, type, + urgent, } = data; log.info( `starting ${type} send to ${identifier} with timestamp ${timestamp}` @@ -116,6 +118,7 @@ export class SingleProtoJobQueue extends JobQueue { options, proto, timestamp, + urgent: isBoolean(urgent) ? urgent : true, }), { messageIds, sendType: type } ); diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index b2307dbaf750..d9a37ffae595 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -1313,12 +1313,13 @@ export class ConversationModel extends window.Backbone if (isDirectConversation(this.attributes)) { await handleMessageSend( messaging.sendMessageProtoAndWait({ - timestamp, - recipients: groupMembers, - proto: contentMessage, contentHint: ContentHint.IMPLICIT, groupId: undefined, options: sendOptions, + proto: contentMessage, + recipients: groupMembers, + timestamp, + urgent: false, }), { messageIds: [], sendType: 'typing' } ); @@ -1334,6 +1335,7 @@ export class ConversationModel extends window.Backbone sendTarget: this.toSenderKeyTarget(), sendType: 'typing', timestamp, + urgent: false, }), { messageIds: [], sendType: 'typing' } ); diff --git a/ts/models/messages.ts b/ts/models/messages.ts index 1b1c4867af1a..fced64dd350a 100644 --- a/ts/models/messages.ts +++ b/ts/models/messages.ts @@ -1698,6 +1698,7 @@ export class MessageModel extends window.Backbone.Model { conversationIdsWithSealedSender, isUpdate, options: sendOptions, + urgent: false, }), // Note: in some situations, for doNotSave messages, the message has no // id, so we provide an empty array here. diff --git a/ts/services/calling.ts b/ts/services/calling.ts index b49258cf83c4..4b754560c48a 100644 --- a/ts/services/calling.ts +++ b/ts/services/calling.ts @@ -998,6 +998,7 @@ export class CallingClass { sendOptions, sendTarget: conversation.toSenderKeyTarget(), sendType: 'callingMessage', + urgent: false, }) ), sendType: 'callingMessage', @@ -1621,6 +1622,7 @@ export class CallingClass { sendTarget: conversation.toSenderKeyTarget(), sendType: 'callingMessage', timestamp, + urgent: false, }), { messageIds: [], sendType: 'callingMessage' } ) diff --git a/ts/sql/Interface.ts b/ts/sql/Interface.ts index f5e53d3e9bdb..5fe016e2cbe2 100644 --- a/ts/sql/Interface.ts +++ b/ts/sql/Interface.ts @@ -100,6 +100,7 @@ export type SentProtoType = { contentHint: number; proto: Uint8Array; timestamp: number; + urgent: boolean; }; export type SentProtoWithMessageIdsType = SentProtoType & { messageIds: Array; diff --git a/ts/sql/Server.ts b/ts/sql/Server.ts index 9bc356caf346..f29681c17b70 100644 --- a/ts/sql/Server.ts +++ b/ts/sql/Server.ts @@ -820,14 +820,19 @@ async function insertSentProto( INSERT INTO sendLogPayloads ( contentHint, proto, - timestamp + timestamp, + urgent ) VALUES ( $contentHint, $proto, - $timestamp + $timestamp, + $urgent ); ` - ).run(proto); + ).run({ + ...proto, + urgent: proto.urgent ? 1 : 0, + }); const id = parseIntOrThrow( info.lastInsertRowid, 'insertSentProto/lastInsertRowid' @@ -1082,6 +1087,7 @@ async function getSentProtoByRecipient({ const { messageIds } = row; return { ...row, + urgent: isNumber(row.urgent) ? Boolean(row.urgent) : true, messageIds: messageIds ? messageIds.split(',') : [], }; } @@ -1093,7 +1099,10 @@ async function getAllSentProtos(): Promise> { const db = getInstance(); const rows = prepare(db, 'SELECT * FROM sendLogPayloads;').all(); - return rows; + return rows.map(row => ({ + ...row, + urgent: isNumber(row.urgent) ? Boolean(row.urgent) : true, + })); } async function _getAllSentProtoRecipients(): Promise< Array diff --git a/ts/sql/migrations/62-add-urgent-to-send-log.ts b/ts/sql/migrations/62-add-urgent-to-send-log.ts new file mode 100644 index 000000000000..ec3281d4995c --- /dev/null +++ b/ts/sql/migrations/62-add-urgent-to-send-log.ts @@ -0,0 +1,28 @@ +// Copyright 2021-2022 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import type { Database } from 'better-sqlite3'; + +import type { LoggerType } from '../../types/Logging'; + +export default function updateToSchemaVersion62( + currentVersion: number, + db: Database, + logger: LoggerType +): void { + if (currentVersion >= 62) { + return; + } + + db.transaction(() => { + db.exec( + ` + ALTER TABLE sendLogPayloads ADD COLUMN urgent INTEGER; + ` + ); + + db.pragma('user_version = 62'); + })(); + + logger.info('updateToSchemaVersion62: success!'); +} diff --git a/ts/sql/migrations/index.ts b/ts/sql/migrations/index.ts index c632d53808fe..6fee95a16c84 100644 --- a/ts/sql/migrations/index.ts +++ b/ts/sql/migrations/index.ts @@ -37,6 +37,7 @@ import updateToSchemaVersion58 from './58-update-unread'; import updateToSchemaVersion59 from './59-unprocessed-received-at-counter-index'; import updateToSchemaVersion60 from './60-update-expiring-index'; import updateToSchemaVersion61 from './61-distribution-list-storage'; +import updateToSchemaVersion62 from './62-add-urgent-to-send-log'; function updateToSchemaVersion1( currentVersion: number, @@ -1937,6 +1938,7 @@ export const SCHEMA_VERSIONS = [ updateToSchemaVersion59, updateToSchemaVersion60, updateToSchemaVersion61, + updateToSchemaVersion62, ]; export function updateSchema(db: Database, logger: LoggerType): void { diff --git a/ts/test-electron/sql/sendLog_test.ts b/ts/test-electron/sql/sendLog_test.ts index 885aed9789c9..5565f60c181f 100644 --- a/ts/test-electron/sql/sendLog_test.ts +++ b/ts/test-electron/sql/sendLog_test.ts @@ -39,6 +39,7 @@ describe('sql/sendLog', () => { contentHint: 1, proto: bytes, timestamp, + urgent: false, }; await insertSentProto(proto, { messageIds: [getUuid()], @@ -54,6 +55,7 @@ describe('sql/sendLog', () => { assert.strictEqual(actual.contentHint, proto.contentHint); assert.isTrue(constantTimeEqual(actual.proto, proto.proto)); assert.strictEqual(actual.timestamp, proto.timestamp); + assert.strictEqual(actual.urgent, proto.urgent); await removeAllSentProtos(); @@ -71,6 +73,7 @@ describe('sql/sendLog', () => { contentHint: 1, proto: bytes, timestamp, + urgent: true, }; await insertSentProto(proto, { messageIds: [getUuid(), getUuid()], @@ -80,7 +83,15 @@ describe('sql/sendLog', () => { }, }); - assert.lengthOf(await getAllSentProtos(), 1); + const allProtos = await getAllSentProtos(); + assert.lengthOf(allProtos, 1); + const actual = allProtos[0]; + + assert.strictEqual(actual.contentHint, proto.contentHint); + assert.isTrue(constantTimeEqual(actual.proto, proto.proto)); + assert.strictEqual(actual.timestamp, proto.timestamp); + assert.strictEqual(actual.urgent, proto.urgent); + assert.lengthOf(await _getAllSentProtoMessageIds(), 2); assert.lengthOf(await _getAllSentProtoRecipients(), 3); @@ -115,6 +126,7 @@ describe('sql/sendLog', () => { contentHint: 1, proto: bytes, timestamp, + urgent: false, }; await insertSentProto(proto, { messageIds: [id], @@ -146,11 +158,13 @@ describe('sql/sendLog', () => { contentHint: 7, proto: getRandomBytes(128), timestamp, + urgent: true, }; const proto2 = { contentHint: 9, proto: getRandomBytes(128), timestamp, + urgent: false, }; assert.lengthOf(await getAllSentProtos(), 0); @@ -180,6 +194,7 @@ describe('sql/sendLog', () => { contentHint: 1, proto: getRandomBytes(128), timestamp, + urgent: true, }; assert.lengthOf(await getAllSentProtos(), 0); @@ -218,16 +233,19 @@ describe('sql/sendLog', () => { contentHint: 1, proto: getRandomBytes(128), timestamp: timestamp + 10, + urgent: true, }; const proto2 = { contentHint: 2, proto: getRandomBytes(128), timestamp, + urgent: true, }; const proto3 = { contentHint: 0, proto: getRandomBytes(128), timestamp: timestamp - 15, + urgent: true, }; await insertSentProto(proto1, { messageIds: [getUuid()], @@ -279,16 +297,19 @@ describe('sql/sendLog', () => { contentHint: 1, proto: getRandomBytes(128), timestamp, + urgent: true, }; const proto2 = { contentHint: 1, proto: getRandomBytes(128), timestamp: timestamp - 10, + urgent: true, }; const proto3 = { contentHint: 1, proto: getRandomBytes(128), timestamp: timestamp - 20, + urgent: true, }; await insertSentProto(proto1, { messageIds: [messageId, getUuid()], @@ -332,6 +353,7 @@ describe('sql/sendLog', () => { contentHint: 1, proto: getRandomBytes(128), timestamp, + urgent: true, }; await insertSentProto(proto, { messageIds: [getUuid()], @@ -363,6 +385,7 @@ describe('sql/sendLog', () => { contentHint: 1, proto: getRandomBytes(128), timestamp, + urgent: true, }; await insertSentProto(proto, { messageIds: [getUuid()], @@ -412,6 +435,7 @@ describe('sql/sendLog', () => { contentHint: 1, proto: getRandomBytes(128), timestamp, + urgent: true, }; await insertSentProto(proto, { messageIds: [getUuid()], @@ -457,6 +481,7 @@ describe('sql/sendLog', () => { contentHint: 1, proto: getRandomBytes(128), timestamp, + urgent: true, }; await insertSentProto(proto, { messageIds, @@ -492,6 +517,7 @@ describe('sql/sendLog', () => { contentHint: 1, proto: getRandomBytes(128), timestamp, + urgent: true, }; await insertSentProto(proto, { messageIds: [], @@ -527,6 +553,7 @@ describe('sql/sendLog', () => { contentHint: 1, proto: getRandomBytes(128), timestamp, + urgent: true, }; await insertSentProto(proto, { messageIds: [getUuid()], @@ -555,6 +582,7 @@ describe('sql/sendLog', () => { contentHint: 1, proto: getRandomBytes(128), timestamp, + urgent: true, }; await insertSentProto(proto, { messageIds: [getUuid()], @@ -584,6 +612,7 @@ describe('sql/sendLog', () => { contentHint: 1, proto: getRandomBytes(128), timestamp, + urgent: true, }; await insertSentProto(proto, { messageIds: [getUuid()], diff --git a/ts/test-node/sql_migrations_test.ts b/ts/test-node/sql_migrations_test.ts index f9eef2835220..20baa91822b0 100644 --- a/ts/test-node/sql_migrations_test.ts +++ b/ts/test-node/sql_migrations_test.ts @@ -2326,4 +2326,35 @@ describe('SQL migrations test', () => { ); }); }); + + describe('updateToSchemaVersion62', () => { + it('adds new urgent field to sendLogPayloads', () => { + updateToVersion(62); + + const timestamp = Date.now(); + db.exec( + ` + INSERT INTO sendLogPayloads + (contentHint, timestamp, proto, urgent) + VALUES + (1, ${timestamp}, X'0123456789ABCDEF', 1); + ` + ); + + assert.strictEqual( + db.prepare('SELECT COUNT(*) FROM sendLogPayloads;').pluck().get(), + 1, + 'starting total' + ); + + const payload = db + .prepare('SELECT * FROM sendLogPayloads LIMIT 1;') + .get(); + + assert.strictEqual(payload.contentHint, 1); + assert.strictEqual(payload.timestamp, timestamp); + assert.strictEqual(payload.proto.length, 8); + assert.strictEqual(payload.urgent, 1); + }); + }); }); diff --git a/ts/textsecure/OutgoingMessage.ts b/ts/textsecure/OutgoingMessage.ts index f74860787bff..097d8b124eec 100644 --- a/ts/textsecure/OutgoingMessage.ts +++ b/ts/textsecure/OutgoingMessage.ts @@ -132,6 +132,8 @@ export default class OutgoingMessage { contentHint: number; + urgent: boolean; + recipients: Record>; sendLogCallback?: SendLogCallbackType; @@ -146,6 +148,7 @@ export default class OutgoingMessage { sendLogCallback, server, timestamp, + urgent, }: { callback: (result: CallbackResultType) => void; contentHint: number; @@ -156,6 +159,7 @@ export default class OutgoingMessage { sendLogCallback?: SendLogCallbackType; server: WebAPIType; timestamp: number; + urgent: boolean; }) { if (message instanceof Proto.DataMessage) { const content = new Proto.Content(); @@ -171,6 +175,7 @@ export default class OutgoingMessage { this.contentHint = contentHint; this.groupId = groupId; this.callback = callback; + this.urgent = urgent; this.identifiersCompleted = 0; this.errors = []; @@ -189,7 +194,7 @@ export default class OutgoingMessage { if (this.identifiersCompleted >= this.identifiers.length) { const proto = this.message; const contentProto = this.getContentProtoBytes(); - const { timestamp, contentHint, recipients } = this; + const { timestamp, contentHint, recipients, urgent } = this; let dataMessage: Uint8Array | undefined; if (proto instanceof Proto.Content && proto.dataMessage) { @@ -209,6 +214,7 @@ export default class OutgoingMessage { recipients, contentProto, timestamp, + urgent, }); } } @@ -299,16 +305,13 @@ export default class OutgoingMessage { identifier, jsonData, timestamp, - this.online, - { accessKey } + { accessKey, online: this.online, urgent: this.urgent } ); } else { - promise = this.server.sendMessages( - identifier, - jsonData, - timestamp, - this.online - ); + promise = this.server.sendMessages(identifier, jsonData, timestamp, { + online: this.online, + urgent: this.urgent, + }); } return promise.catch(e => { diff --git a/ts/textsecure/SendMessage.ts b/ts/textsecure/SendMessage.ts index 628824fb2b8f..f2c07bbd28b5 100644 --- a/ts/textsecure/SendMessage.ts +++ b/ts/textsecure/SendMessage.ts @@ -31,7 +31,6 @@ import type { GetProfileUnauthOptionsType, GroupCredentialsType, GroupLogResponseType, - MultiRecipient200ResponseType, ProfileRequestDataType, ProxiedRequestOptionsType, UploadAvatarHeadersType, @@ -150,6 +149,7 @@ export const singleProtoJobDataSchema = z.object({ messageIds: z.array(z.string()).optional(), protoBase64: z.string(), type: sendTypesEnum, + urgent: z.boolean().optional(), }); export type SingleProtoJobData = z.infer; @@ -1006,11 +1006,13 @@ export default class MessageSender { contentHint, groupId, options, + urgent, }: Readonly<{ messageOptions: MessageOptionsType; contentHint: number; groupId: string | undefined; options?: SendOptionsType; + urgent: boolean; }>): Promise { const message = await this.getHydratedMessage(messageOptions); @@ -1029,6 +1031,7 @@ export default class MessageSender { proto: message.toProto(), recipients: message.recipients || [], timestamp: message.timestamp, + urgent, }); }); } @@ -1042,6 +1045,7 @@ export default class MessageSender { recipients, sendLogCallback, timestamp, + urgent, }: Readonly<{ callback: (result: CallbackResultType) => void; contentHint: number; @@ -1051,6 +1055,7 @@ export default class MessageSender { recipients: ReadonlyArray; sendLogCallback?: SendLogCallbackType; timestamp: number; + urgent: boolean; }>): void { const rejections = window.textsecure.storage.get( 'signedKeyRotationRejected', @@ -1070,6 +1075,7 @@ export default class MessageSender { sendLogCallback, server: this.server, timestamp, + urgent, }); recipients.forEach(identifier => { @@ -1086,6 +1092,7 @@ export default class MessageSender { contentHint, groupId, options, + urgent, }: Readonly<{ timestamp: number; recipients: Array; @@ -1093,6 +1100,7 @@ export default class MessageSender { contentHint: number; groupId: string | undefined; options?: SendOptionsType; + urgent: boolean; }>): Promise { return new Promise((resolve, reject) => { const callback = (result: CallbackResultType) => { @@ -1111,6 +1119,7 @@ export default class MessageSender { proto, recipients, timestamp, + urgent, }); }); } @@ -1122,6 +1131,7 @@ export default class MessageSender { options, proto, timestamp, + urgent, }: Readonly<{ contentHint: number; groupId?: string; @@ -1129,6 +1139,7 @@ export default class MessageSender { options?: SendOptionsType; proto: Proto.DataMessage | Proto.Content | PlaintextContent; timestamp: number; + urgent: boolean; }>): Promise { assert(identifier, "Identifier can't be undefined"); return new Promise((resolve, reject) => { @@ -1147,6 +1158,7 @@ export default class MessageSender { proto, recipients: [identifier], timestamp, + urgent, }); }); } @@ -1170,6 +1182,7 @@ export default class MessageSender { sticker, storyContext, timestamp, + urgent, }: Readonly<{ attachments: ReadonlyArray | undefined; contact?: Array; @@ -1187,6 +1200,7 @@ export default class MessageSender { sticker?: StickerType; storyContext?: StoryContextType; timestamp: number; + urgent: boolean; }>): Promise { return this.sendMessage({ messageOptions: { @@ -1207,6 +1221,7 @@ export default class MessageSender { contentHint, groupId, options, + urgent, }); } @@ -1223,6 +1238,7 @@ export default class MessageSender { conversationIdsSentTo = [], conversationIdsWithSealedSender = new Set(), isUpdate, + urgent, options, }: Readonly<{ encodedDataMessage: Uint8Array; @@ -1233,6 +1249,7 @@ export default class MessageSender { conversationIdsSentTo?: Iterable; conversationIdsWithSealedSender?: Set; isUpdate?: boolean; + urgent: boolean; options?: SendOptionsType; }>): Promise { const myUuid = window.textsecure.storage.user.getCheckedUuid(); @@ -1295,6 +1312,7 @@ export default class MessageSender { timestamp, contentHint: ContentHint.RESENDABLE, options, + urgent, }); } @@ -1318,6 +1336,7 @@ export default class MessageSender { Proto.Content.encode(contentMessage).finish() ), type: 'blockSyncRequest', + urgent: false, }; } @@ -1341,6 +1360,7 @@ export default class MessageSender { Proto.Content.encode(contentMessage).finish() ), type: 'configurationSyncRequest', + urgent: false, }; } @@ -1364,6 +1384,7 @@ export default class MessageSender { Proto.Content.encode(contentMessage).finish() ), type: 'groupSyncRequest', + urgent: false, }; } @@ -1387,6 +1408,7 @@ export default class MessageSender { Proto.Content.encode(contentMessage).finish() ), type: 'contactSyncRequest', + urgent: true, }; } @@ -1410,6 +1432,7 @@ export default class MessageSender { Proto.Content.encode(contentMessage).finish() ), type: 'pniIdentitySyncRequest', + urgent: true, }; } @@ -1434,6 +1457,7 @@ export default class MessageSender { Proto.Content.encode(contentMessage).finish() ), type: 'fetchLatestManifestSync', + urgent: false, }; } @@ -1458,6 +1482,7 @@ export default class MessageSender { Proto.Content.encode(contentMessage).finish() ), type: 'fetchLocalProfileSync', + urgent: false, }; } @@ -1482,6 +1507,7 @@ export default class MessageSender { Proto.Content.encode(contentMessage).finish() ), type: 'keySyncRequest', + urgent: true, }; } @@ -1516,6 +1542,7 @@ export default class MessageSender { timestamp: Date.now(), contentHint: ContentHint.RESENDABLE, options, + urgent: true, }); } @@ -1548,6 +1575,7 @@ export default class MessageSender { timestamp: Date.now(), contentHint: ContentHint.RESENDABLE, options, + urgent: false, }); } @@ -1593,6 +1621,7 @@ export default class MessageSender { timestamp: Date.now(), contentHint: ContentHint.RESENDABLE, options, + urgent: false, }); } @@ -1634,6 +1663,7 @@ export default class MessageSender { Proto.Content.encode(contentMessage).finish() ), type: 'messageRequestSync', + urgent: false, }; } @@ -1674,6 +1704,7 @@ export default class MessageSender { Proto.Content.encode(contentMessage).finish() ), type: 'stickerPackSync', + urgent: false, }; } @@ -1718,6 +1749,7 @@ export default class MessageSender { Proto.Content.encode(contentMessage).finish() ), type: 'verificationSync', + urgent: false, }; } @@ -1743,6 +1775,7 @@ export default class MessageSender { contentHint: ContentHint.DEFAULT, groupId: undefined, options, + urgent: true, }); } @@ -1824,6 +1857,7 @@ export default class MessageSender { timestamp: Date.now(), contentHint: ContentHint.RESENDABLE, options, + urgent: false, }); } @@ -1858,6 +1892,7 @@ export default class MessageSender { Proto.Content.encode(contentMessage).finish() ), type: 'nullMessage', + urgent: false, }; } @@ -1881,6 +1916,7 @@ export default class MessageSender { contentHint: ContentHint.DEFAULT, groupId, options, + urgent: false, }); } @@ -1895,12 +1931,14 @@ export default class MessageSender { proto, sendType, timestamp, + urgent, }: Readonly<{ contentHint: number; messageId?: string; proto: Buffer; sendType: SendTypesType; timestamp: number; + urgent: boolean; }>): SendLogCallbackType { let initialSavePromise: Promise; @@ -1933,9 +1971,10 @@ export default class MessageSender { if (!initialSavePromise) { initialSavePromise = window.Signal.Data.insertSentProto( { - timestamp, - proto, contentHint, + proto, + timestamp, + urgent, }, { recipients: { [recipientUuid]: deviceIds }, @@ -1963,6 +2002,7 @@ export default class MessageSender { recipients, sendLogCallback, timestamp = Date.now(), + urgent, }: Readonly<{ contentHint: number; groupId: string | undefined; @@ -1971,6 +2011,7 @@ export default class MessageSender { recipients: ReadonlyArray; sendLogCallback?: SendLogCallbackType; timestamp: number; + urgent: boolean; }>): Promise { const myE164 = window.textsecure.storage.user.getNumber(); const myUuid = window.textsecure.storage.user.getUuid()?.toString(); @@ -1987,6 +2028,8 @@ export default class MessageSender { failoverIdentifiers: [], successfulIdentifiers: [], unidentifiedDeliveries: [], + contentHint, + urgent, }); } @@ -2008,6 +2051,7 @@ export default class MessageSender { recipients: identifiers, sendLogCallback, timestamp, + urgent, }); }); } @@ -2078,12 +2122,14 @@ export default class MessageSender { groupId, identifiers, throwIfNotInDatabase, + urgent, }: Readonly<{ contentHint: number; distributionId: string; groupId: string | undefined; identifiers: ReadonlyArray; throwIfNotInDatabase?: boolean; + urgent: boolean; }>, options?: Readonly ): Promise { @@ -2103,6 +2149,7 @@ export default class MessageSender { proto: Buffer.from(Proto.Content.encode(contentMessage).finish()), sendType: 'senderKeyDistributionMessage', timestamp, + urgent, }) : undefined; @@ -2114,6 +2161,7 @@ export default class MessageSender { recipients: identifiers, sendLogCallback, timestamp, + urgent, }); } @@ -2144,6 +2192,7 @@ export default class MessageSender { proto: Buffer.from(Proto.Content.encode(proto).finish()), sendType: 'legacyGroupChange', timestamp, + urgent: false, }) : undefined; @@ -2155,11 +2204,15 @@ export default class MessageSender { recipients: groupIdentifiers, sendLogCallback, timestamp, + urgent: false, }); } // Simple pass-throughs + // Note: instead of updating these functions, or adding new ones, remove these and go + // directly to window.textsecure.messaging.server. + async getProfile( uuid: UUID, options: GetProfileOptionsType | GetProfileUnauthOptionsType @@ -2262,15 +2315,6 @@ export default class MessageSender { return this.server.modifyGroup(changes, options, inviteLinkBase64); } - async sendWithSenderKey( - data: Readonly, - accessKeys: Readonly, - timestamp: number, - online?: boolean - ): Promise { - return this.server.sendWithSenderKey(data, accessKeys, timestamp, online); - } - async fetchLinkPreviewMetadata( href: string, abortSignal: AbortSignal diff --git a/ts/textsecure/Types.d.ts b/ts/textsecure/Types.d.ts index 91b3ca86955c..c61619f20d9a 100644 --- a/ts/textsecure/Types.d.ts +++ b/ts/textsecure/Types.d.ts @@ -263,6 +263,7 @@ export interface CallbackResultType { contentProto?: Uint8Array; timestamp?: number; recipients?: Record>; + urgent?: boolean; } export interface IRequestHandler { diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index a463e76e2932..c87b5ccdfde8 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -916,20 +916,22 @@ export type WebAPIType = { destination: string, messageArray: ReadonlyArray, timestamp: number, - online?: boolean + options: { online?: boolean; urgent?: boolean } ) => Promise; sendMessagesUnauth: ( destination: string, messageArray: ReadonlyArray, timestamp: number, - online?: boolean, - options?: { accessKey?: string } + options: { accessKey?: string; online?: boolean; urgent?: boolean } ) => Promise; sendWithSenderKey: ( payload: Uint8Array, accessKeys: Uint8Array, timestamp: number, - online?: boolean + options: { + online?: boolean; + urgent?: boolean; + } ) => Promise; setSignedPreKey: ( signedPreKey: SignedPreKeyType, @@ -2078,15 +2080,18 @@ export function initialize({ destination: string, messages: ReadonlyArray, timestamp: number, - online?: boolean, - { accessKey }: { accessKey?: string } = {} + { + accessKey, + online, + urgent = true, + }: { accessKey?: string; online?: boolean; urgent?: boolean } ) { - let jsonData; - if (online) { - jsonData = { messages, timestamp, online: true }; - } else { - jsonData = { messages, timestamp }; - } + const jsonData = { + messages, + timestamp, + online: Boolean(online), + urgent, + }; await _ajax({ call: 'messages', @@ -2103,14 +2108,14 @@ export function initialize({ destination: string, messages: ReadonlyArray, timestamp: number, - online?: boolean + { online, urgent = true }: { online?: boolean; urgent?: boolean } ) { - let jsonData; - if (online) { - jsonData = { messages, timestamp, online: true }; - } else { - jsonData = { messages, timestamp }; - } + const jsonData = { + messages, + timestamp, + online: Boolean(online), + urgent, + }; await _ajax({ call: 'messages', @@ -2121,18 +2126,31 @@ export function initialize({ }); } + function booleanToString(value: boolean | undefined): string { + return value ? 'true' : 'false'; + } + async function sendWithSenderKey( data: Uint8Array, accessKeys: Uint8Array, timestamp: number, - online?: boolean + { + online, + urgent = true, + }: { + online?: boolean; + urgent?: boolean; + } ): Promise { + const onlineParam = `&online=${booleanToString(online)}`; + const urgentParam = `&urgent=${booleanToString(urgent)}`; + const response = await _ajax({ call: 'multiRecipient', httpType: 'PUT', contentType: 'application/vnd.signal-messenger.mrm', data, - urlParameters: `?ts=${timestamp}&online=${online ? 'true' : 'false'}`, + urlParameters: `?ts=${timestamp}${onlineParam}${urgentParam}`, responseType: 'json', unauthenticated: true, accessKey: Bytes.toBase64(accessKeys), diff --git a/ts/util/handleMessageSend.ts b/ts/util/handleMessageSend.ts index 6be6b95c6069..1a5c888ab679 100644 --- a/ts/util/handleMessageSend.ts +++ b/ts/util/handleMessageSend.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { z } from 'zod'; -import { isNumber } from 'lodash'; +import { isBoolean, isNumber } from 'lodash'; import type { CallbackResultType } from '../textsecure/Types.d'; import dataInterface from '../sql/Client'; import * as log from '../logging/log'; @@ -17,38 +17,49 @@ import { SEALED_SENDER } from '../types/SealedSender'; const { insertSentProto, updateConversation } = dataInterface; export const sendTypesEnum = z.enum([ - 'blockSyncRequest', - 'pniIdentitySyncRequest', - 'callingMessage', // excluded from send log - 'configurationSyncRequest', - 'contactSyncRequest', - 'deleteForEveryone', - 'deliveryReceipt', - 'expirationTimerUpdate', - 'fetchLatestManifestSync', - 'fetchLocalProfileSync', - 'groupChange', - 'groupSyncRequest', - 'keySyncRequest', - 'legacyGroupChange', + // Core user interactions, default urgent 'message', - 'messageRequestSync', + 'callingMessage', // excluded from send log; only call-initiation messages are urgent + 'deleteForEveryone', + 'expirationTimerUpdate', // non-urgent + 'groupChange', // non-urgent + 'reaction', + 'typing', // excluded from send log; non-urgent + + // Responding to incoming messages, all non-urgent + 'deliveryReceipt', + 'readReceipt', + 'viewedReceipt', + + // Encryption housekeeping, default non-urgent 'nullMessage', 'profileKeyUpdate', - 'reaction', - 'readReceipt', - 'readSync', - 'resendFromLog', // excluded from send log - 'resetSession', + 'resendFromLog', // excluded from send log, only urgent if original message was urgent 'retryRequest', // excluded from send log - 'senderKeyDistributionMessage', + 'senderKeyDistributionMessage', // only urgent if associated message is + + // Sync messages sent during link, default non-urgent + 'blockSyncRequest', + 'configurationSyncRequest', + 'contactSyncRequest', // urgent because it blocks the link process + 'groupSyncRequest', + 'keySyncRequest', // urgent because it blocks the link process + 'pniIdentitySyncRequest', // urgent because we need our PNI to be fully functional + + // Syncs, default non-urgent + 'fetchLatestManifestSync', + 'fetchLocalProfileSync', + 'messageRequestSync', + 'readSync', // urgent 'sentSync', 'stickerPackSync', - 'typing', // excluded from send log 'verificationSync', 'viewOnceSync', 'viewSync', - 'viewedReceipt', + + // No longer used, all non-urgent + 'legacyGroupChange', + 'resetSession', ]); export type SendTypesType = z.infer; @@ -216,7 +227,7 @@ async function maybeSaveToSendLog( sendType: SendTypesType; } ): Promise { - const { contentHint, contentProto, recipients, timestamp } = result; + const { contentHint, contentProto, recipients, timestamp, urgent } = result; if (!shouldSaveProto(sendType)) { return; @@ -247,6 +258,7 @@ async function maybeSaveToSendLog( timestamp, proto: Buffer.from(contentProto), contentHint, + urgent: isBoolean(urgent) ? urgent : true, }, { messageIds, diff --git a/ts/util/handleRetry.ts b/ts/util/handleRetry.ts index ec027cee7665..08846bdc2500 100644 --- a/ts/util/handleRetry.ts +++ b/ts/util/handleRetry.ts @@ -5,7 +5,7 @@ import { DecryptionErrorMessage, PlaintextContent, } from '@signalapp/libsignal-client'; -import { isNumber } from 'lodash'; +import { isBoolean, isNumber } from 'lodash'; import * as Bytes from '../Bytes'; import { isProduction } from './version'; @@ -131,7 +131,7 @@ export async function onRetryRequest(event: RetryRequestEvent): Promise { throw new Error(`onRetryRequest/${logId}: messaging is not available!`); } - const { contentHint, messageIds, proto, timestamp } = sentProto; + const { contentHint, messageIds, proto, timestamp, urgent } = sentProto; const { contentProto, groupId } = await maybeAddSenderKeyDistributionMessage({ contentProto: Proto.Content.decode(proto), @@ -148,12 +148,13 @@ export async function onRetryRequest(event: RetryRequestEvent): Promise { ); const sendOptions = await getSendOptions(recipientConversation.attributes); const promise = messaging.sendMessageProtoAndWait({ - timestamp, - recipients: [requesterUuid], - proto: new Proto.Content(contentProto), contentHint, groupId, options: sendOptions, + proto: new Proto.Content(contentProto), + recipients: [requesterUuid], + timestamp, + urgent, }); await handleMessageSend(promise, { @@ -306,6 +307,7 @@ async function sendDistributionMessageOrNullMessage( groupId, identifiers: [requesterUuid], throwIfNotInDatabase: true, + urgent: false, }, sendOptions ), @@ -346,6 +348,7 @@ async function sendDistributionMessageOrNullMessage( Bytes.fromBase64(nullMessage.protoBase64) ), timestamp: Date.now(), + urgent: isBoolean(nullMessage.urgent) ? nullMessage.urgent : true, }), { messageIds: [], sendType: nullMessage.type } ); diff --git a/ts/util/sendToGroup.ts b/ts/util/sendToGroup.ts index 1094e0a1fa94..9078c7e2cb79 100644 --- a/ts/util/sendToGroup.ts +++ b/ts/util/sendToGroup.ts @@ -98,6 +98,7 @@ export async function sendToGroup({ sendOptions, sendTarget, sendType, + urgent, }: { abortSignal?: AbortSignal; contentHint: number; @@ -107,6 +108,7 @@ export async function sendToGroup({ sendOptions?: SendOptionsType; sendTarget: SenderKeyTargetType; sendType: SendTypesType; + urgent: boolean; }): Promise { strictAssert( window.textsecure.messaging, @@ -139,6 +141,7 @@ export async function sendToGroup({ sendTarget, sendType, timestamp, + urgent, }); } @@ -153,6 +156,7 @@ export async function sendContentMessageToGroup({ sendTarget, sendType, timestamp, + urgent, }: { contentHint: number; contentMessage: Proto.Content; @@ -164,6 +168,7 @@ export async function sendContentMessageToGroup({ sendTarget: SenderKeyTargetType; sendType: SendTypesType; timestamp: number; + urgent: boolean; }): Promise { const logId = sendTarget.idForLogging(); strictAssert( @@ -194,6 +199,7 @@ export async function sendContentMessageToGroup({ sendTarget, sendType, timestamp, + urgent, }); } catch (error: unknown) { if (!(error instanceof Error)) { @@ -217,6 +223,7 @@ export async function sendContentMessageToGroup({ proto: Buffer.from(Proto.Content.encode(contentMessage).finish()), sendType, timestamp, + urgent, }); const groupId = sendTarget.isGroupV2() ? sendTarget.getGroupId() : undefined; return window.textsecure.messaging.sendGroupProto({ @@ -227,6 +234,7 @@ export async function sendContentMessageToGroup({ recipients, sendLogCallback, timestamp, + urgent, }); } @@ -244,6 +252,7 @@ export async function sendToGroupViaSenderKey(options: { sendTarget: SenderKeyTargetType; sendType: SendTypesType; timestamp: number; + urgent: boolean; }): Promise { const { contentHint, @@ -257,6 +266,7 @@ export async function sendToGroupViaSenderKey(options: { sendTarget, sendType, timestamp, + urgent, } = options; const { ContentHint } = Proto.UnidentifiedSenderMessage.Message; @@ -421,6 +431,7 @@ export async function sendToGroupViaSenderKey(options: { distributionId, groupId, identifiers: newToMemberUuids, + urgent, }, sendOptions ? { ...sendOptions, online: false } : undefined ), @@ -495,11 +506,11 @@ export async function sendToGroupViaSenderKey(options: { }); const accessKeys = getXorOfAccessKeys(devicesForSenderKey); - const result = await window.textsecure.messaging.sendWithSenderKey( + const result = await window.textsecure.messaging.server.sendWithSenderKey( messageBuffer, accessKeys, timestamp, - online + { online, urgent } ); const parsed = multiRecipient200ResponseSchema.safeParse(result); @@ -531,6 +542,7 @@ export async function sendToGroupViaSenderKey(options: { contentHint, proto: Buffer.from(Proto.Content.encode(contentMessage).finish()), timestamp, + urgent, }, { recipients: senderKeyRecipientsWithDevices, @@ -598,6 +610,7 @@ export async function sendToGroupViaSenderKey(options: { timestamp, contentProto: Buffer.from(Proto.Content.encode(contentMessage).finish()), recipients: senderKeyRecipientsWithDevices, + urgent, }; } @@ -648,6 +661,7 @@ export async function sendToGroupViaSenderKey(options: { recipients: normalSendRecipients, sendLogCallback, timestamp, + urgent, }); return mergeSendResult({ diff --git a/ts/util/wrapWithSyncMessageSend.ts b/ts/util/wrapWithSyncMessageSend.ts index ced27b8690fc..62b24f23f681 100644 --- a/ts/util/wrapWithSyncMessageSend.ts +++ b/ts/util/wrapWithSyncMessageSend.ts @@ -89,6 +89,7 @@ export async function wrapWithSyncMessageSend({ expirationStartTimestamp: null, options, timestamp, + urgent: false, }), { messageIds, sendType: sendType === 'message' ? 'sentSync' : sendType } );