diff --git a/.npmrc b/.npmrc index 84c643cd5f..92a6b97c6d 100644 --- a/.npmrc +++ b/.npmrc @@ -2,6 +2,7 @@ legacy-peer-deps=true public-hoist-pattern[]=*eslint-* minimum-release-age=14400 minimum-release-age-exclude[]=@signalapp/* +minimum-release-age-exclude[]=@indutny/* minimum-release-age-exclude[]=electron minimum-release-age-exclude[]=react minimum-release-age-exclude[]=react-dom diff --git a/package.json b/package.json index 262c6fc19f..aced8f038a 100644 --- a/package.json +++ b/package.json @@ -240,7 +240,7 @@ "@indutny/parallel-prettier": "3.0.0", "@indutny/rezip-electron": "3.0.2", "@napi-rs/canvas": "0.1.61", - "@signalapp/mock-server": "15.2.3", + "@signalapp/mock-server": "17.0.1", "@storybook/addon-a11y": "8.4.4", "@storybook/addon-actions": "8.4.4", "@storybook/addon-controls": "8.4.4", @@ -399,7 +399,8 @@ "dmg-builder": "patches/dmg-builder.patch", "eslint-plugin-better-tailwindcss": "patches/eslint-plugin-better-tailwindcss.patch", "framer-motion@6.5.1": "patches/framer-motion@6.5.1.patch", - "fs-xattr": "patches/fs-xattr.patch" + "fs-xattr": "patches/fs-xattr.patch", + "@types/chai": "patches/@types__chai.patch" }, "onlyBuiltDependencies": [ "@indutny/mac-screen-share", diff --git a/patches/@types__chai.patch b/patches/@types__chai.patch new file mode 100644 index 0000000000..c47e7fc44c --- /dev/null +++ b/patches/@types__chai.patch @@ -0,0 +1,13 @@ +diff --git a/index.d.ts b/index.d.ts +index f1cdab73a0635bd87322d75bcc0efdb83b57b790..c695f174320f34e5f8fd6367ebd9ded4a727b819 100644 +--- a/index.d.ts ++++ b/index.d.ts +@@ -480,7 +480,7 @@ declare namespace Chai { + * @param expected Potential expected value. + * @param message Message to display on error. + */ +- strictEqual(actual: T, expected: T, message?: string): void; ++ strictEqual(actual: unknown, expected: T, message?: string): asserts actual is T; + + /** + * Asserts strict inequality (!==) of actual and expected. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c62c130b9e..b4ab10638d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,9 @@ overrides: react-popper@2.3.0>react-dom: 18.3.1 patchedDependencies: + '@types/chai': + hash: 610a7faa9e043c420d54f80984cbbbe4d0c5940d76cf57de8112e93a70862fb1 + path: patches/@types__chai.patch '@types/express@4.17.21': hash: 85d9b3f3cac67003e41b22245281f53b51d7d1badd0bcc222d547ab802599bae path: patches/@types+express+4.17.21.patch @@ -439,8 +442,8 @@ importers: specifier: 0.1.61 version: 0.1.61 '@signalapp/mock-server': - specifier: 15.2.3 - version: 15.2.3(bufferutil@4.0.9)(utf-8-validate@5.0.10) + specifier: 17.0.1 + version: 17.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10) '@storybook/addon-a11y': specifier: 8.4.4 version: 8.4.4(storybook@8.4.4(bufferutil@4.0.9)(prettier@3.7.4)(utf-8-validate@5.0.10)) @@ -500,7 +503,7 @@ importers: version: 5.16.6 '@types/chai': specifier: 4.3.16 - version: 4.3.16 + version: 4.3.16(patch_hash=610a7faa9e043c420d54f80984cbbbe4d0c5940d76cf57de8112e93a70862fb1) '@types/chai-as-promised': specifier: 7.1.4 version: 7.1.4 @@ -1708,6 +1711,10 @@ packages: peerDependencies: prettier: ^3.0.0 + '@indutny/protopiler@1.0.0-rc.24': + resolution: {integrity: sha512-qLKwdz+CXDg20I9wL+g7p6Zjo2EHvXFJFzft1useHeB9NYo4PSyBOmid1Ug5Lhc0klDycDv7vldEbx4sEV1Wzw==} + hasBin: true + '@indutny/range-finder@1.3.4': resolution: {integrity: sha512-UWNRZZCp3f97NJuhP0IgBktob+kPDFsnyKx42PDwPQsAa/ZczyeLdhfz3tD8zK1wVC+OkOXAM6YkBAgEv9Xhaw==} @@ -3492,8 +3499,8 @@ packages: '@signalapp/minimask@1.0.1': resolution: {integrity: sha512-QAwo0joA60urTNbW9RIz6vLKQjy+jdVtH7cvY0wD9PVooD46MAjE40MLssp4xUJrph91n2XvtJ3pbEUDrmT2AA==} - '@signalapp/mock-server@15.2.3': - resolution: {integrity: sha512-/mohwJK5X50Y8pYPRNCG3Noi6VNtVstEnVmEbbL01XLz+2GH0iKa8e1F1XDkqYVfu13Gl8U7o6k/oYo4NP57sA==} + '@signalapp/mock-server@17.0.1': + resolution: {integrity: sha512-uiPYsF0dD38ZCdkAKRMzNjaeT34JXeE5tq/nxAMnlcnlxpS5XXL0BbMhr0+S2dM2bXz0GmHpIHPyBLwTbXGlVw==} '@signalapp/parchment-cjs@3.0.1': resolution: {integrity: sha512-hSBMQ1M7wE4GcC8ZeNtvpJF+DAJg3eIRRf1SiHS3I3Algav/sgJJNm6HIYm6muHuK7IJmuEjkL3ILSXgmu0RfQ==} @@ -11853,6 +11860,8 @@ snapshots: prettier: 3.7.4 rxjs: 6.6.7 + '@indutny/protopiler@1.0.0-rc.24': {} + '@indutny/range-finder@1.3.4': dependencies: readable-stream: 4.7.0 @@ -14278,19 +14287,18 @@ snapshots: '@signalapp/minimask@1.0.1': {} - '@signalapp/mock-server@15.2.3(bufferutil@4.0.9)(utf-8-validate@5.0.10)': + '@signalapp/mock-server@17.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)': dependencies: '@indutny/parallel-prettier': 3.0.0(prettier@3.7.4) + '@indutny/protopiler': 1.0.0-rc.24 '@signalapp/libsignal-client': 0.76.7 '@tus/file-store': 1.5.1 '@tus/server': 1.10.2 debug: 4.3.7(supports-color@8.1.1) is-plain-obj: 3.0.0 - long: 5.2.3 micro: 9.4.1 microrouter: 3.1.3 prettier: 3.7.4 - protobufjs: 7.3.2(patch_hash=0ae0fcb7c2b673e67231536164cc4841642d16c8a26578de4d43637e2a6f1774) type-fest: 4.26.1 url-pattern: 1.0.3 uuid: 8.3.2 @@ -14919,9 +14927,9 @@ snapshots: '@types/chai-as-promised@7.1.4': dependencies: - '@types/chai': 4.3.16 + '@types/chai': 4.3.16(patch_hash=610a7faa9e043c420d54f80984cbbbe4d0c5940d76cf57de8112e93a70862fb1) - '@types/chai@4.3.16': {} + '@types/chai@4.3.16(patch_hash=610a7faa9e043c420d54f80984cbbbe4d0c5940d76cf57de8112e93a70862fb1)': {} '@types/config@3.3.5': {} diff --git a/ts/services/storage.preload.ts b/ts/services/storage.preload.ts index 51116bb0a0..9145f7b379 100644 --- a/ts/services/storage.preload.ts +++ b/ts/services/storage.preload.ts @@ -2250,7 +2250,7 @@ async function sync({ if (window.SignalCI) { window.SignalCI.handleEvent('storageServiceComplete', { - manifestVersion: version, + manifestVersion: BigInt(version), }); } diff --git a/ts/test-mock/backups/backups_test.node.ts b/ts/test-mock/backups/backups_test.node.ts index 0be0b2e6b7..d90c6e396d 100644 --- a/ts/test-mock/backups/backups_test.node.ts +++ b/ts/test-mock/backups/backups_test.node.ts @@ -9,7 +9,6 @@ import createDebug from 'debug'; import { Proto, StorageState } from '@signalapp/mock-server'; import { assert } from 'chai'; import { expect } from 'playwright/test'; -import Long from 'long'; import * as Bytes from '../../Bytes.std.js'; import { generateStoryDistributionId } from '../../types/StoryDistributionId.std.js'; @@ -82,7 +81,7 @@ describe('backups', function (this: Mocha.Suite) { givenName: phone.profileName, readReceipts: true, hasCompletedUsernameOnboarding: true, - backupTier: Long.fromNumber(BackupLevel.Paid), + backupTier: BigInt(BackupLevel.Paid), }); state = state.addContact(friend, { @@ -108,6 +107,7 @@ describe('backups', function (this: Mocha.Suite) { isBlockList: true, name: MY_STORY_ID, recipientServiceIdsBinary: [pinned.device.aciBinary], + deletedAtTimestamp: null, }, }, }); @@ -121,6 +121,7 @@ describe('backups', function (this: Mocha.Suite) { isBlockList: false, name: 'friend', recipientServiceIdsBinary: [friend.device.aciBinary], + deletedAtTimestamp: null, }, }, }); @@ -134,8 +135,16 @@ describe('backups', function (this: Mocha.Suite) { id: Bytes.fromHex(generateNotificationProfileId()), name: notificationProfileName1, color: 0xffff0000, - createdAtMs: Long.fromNumber(now), + createdAtMs: BigInt(now), allowAllCalls: true, + emoji: null, + allowAllMentions: null, + allowedMembers: null, + scheduleEnabled: null, + scheduleStartTime: null, + scheduleEndTime: null, + scheduleDaysEnabled: null, + deletedAtTimestampMs: null, }, }, }); @@ -148,8 +157,16 @@ describe('backups', function (this: Mocha.Suite) { id: Bytes.fromHex(generateNotificationProfileId()), name: notificationProfileName2, color: 0xff00ff00, - createdAtMs: Long.fromNumber(now + 1), + createdAtMs: BigInt(now + 1), allowAllMentions: true, + emoji: null, + allowAllCalls: null, + allowedMembers: null, + scheduleEnabled: null, + scheduleStartTime: null, + scheduleEndTime: null, + scheduleDaysEnabled: null, + deletedAtTimestampMs: null, }, }, }); diff --git a/ts/test-mock/benchmarks/call_history_search_bench.node.ts b/ts/test-mock/benchmarks/call_history_search_bench.node.ts index 3c4a063e2c..a108a68402 100644 --- a/ts/test-mock/benchmarks/call_history_search_bench.node.ts +++ b/ts/test-mock/benchmarks/call_history_search_bench.node.ts @@ -4,7 +4,6 @@ import type { PrimaryDevice } from '@signalapp/mock-server'; import { Proto, StorageState } from '@signalapp/mock-server'; -import Long from 'long'; import lodash from 'lodash'; import { expect } from 'playwright/test'; import { Bootstrap, debug, RUN_COUNT, DISCARD_COUNT } from './fixtures.node.js'; @@ -96,15 +95,22 @@ Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise => { desktop, { syncMessage: { + content: 'callEvent', callEvent: { - peerId: uuidToBytes(contact.device.aci), - callId: Long.fromNumber(timestamp), - timestamp: Long.fromNumber(timestamp), + conversationId: uuidToBytes(contact.device.aci), + callId: BigInt(timestamp), + timestamp: BigInt(timestamp), type, direction, event, }, + read: null, + stickerPackOperation: null, + viewed: null, + padding: null, }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }, { timestamp } ); diff --git a/ts/test-mock/bootstrap.node.ts b/ts/test-mock/bootstrap.node.ts index d5ecf86514..5af7952bb4 100644 --- a/ts/test-mock/bootstrap.node.ts +++ b/ts/test-mock/bootstrap.node.ts @@ -694,7 +694,7 @@ export class Bootstrap { public async encryptAndStoreAttachmentOnCDN( data: Buffer, contentType: MIMEType - ): Promise { + ): Promise { const cdnKey = uuid(); const keys = generateAttachmentKeys(); const cdnNumber = 3; @@ -716,12 +716,22 @@ export class Bootstrap { return { size: data.byteLength, contentType, + attachmentIdentifier: 'cdnKey', cdnKey, cdnNumber, key: keys, digest, - chunkSize, - incrementalMac, + chunkSize: chunkSize ?? null, + incrementalMac: incrementalMac ?? null, + clientUuid: null, + thumbnail: null, + fileName: null, + flags: null, + width: null, + height: null, + caption: null, + blurHash: null, + uploadTimestamp: null, }; } diff --git a/ts/test-mock/helpers.node.ts b/ts/test-mock/helpers.node.ts index 9b8f492e63..4081933cce 100644 --- a/ts/test-mock/helpers.node.ts +++ b/ts/test-mock/helpers.node.ts @@ -8,14 +8,14 @@ import { PrimaryDevice, Proto, StorageState, + EMPTY_DATA_MESSAGE, } from '@signalapp/mock-server'; import { assert } from 'chai'; -import Long from 'long'; import type { Locator, Page } from 'playwright'; import { expect } from 'playwright/test'; -import type { SignalService } from '../protobuf/index.std.js'; import { strictAssert } from '../util/assert.std.js'; import { SECOND } from '../util/durations/constants.std.js'; +import { toNumber } from '../util/toNumber.std.js'; const debug = createDebug('mock:test:helpers'); @@ -147,8 +147,8 @@ function maybeWrapInSyncMessage({ isSync: boolean; to: PrimaryDevice | Device; sentTo?: Array; - dataMessage: Proto.IDataMessage; -}): Proto.IContent { + dataMessage: Proto.DataMessage.Params; +}): Proto.Content.Params { return isSync ? { syncMessage: { @@ -159,11 +159,31 @@ function maybeWrapInSyncMessage({ unidentifiedStatus: (sentTo ?? [to]).map(contact => ({ destinationServiceIdBinary: getDevice(contact).aciBinary, destination: getDevice(contact).number, + unidentified: null, + destinationPniIdentityKey: null, + destinationServiceId: null, })), + destinationE164: null, + expirationStartTimestamp: null, + isRecipientUpdate: null, + storyMessage: null, + storyMessageRecipients: null, + editMessage: null, + destinationServiceId: null, }, + read: null, + stickerPackOperation: null, + viewed: null, + padding: null, }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, } - : { dataMessage }; + : { + dataMessage, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, + }; } function isToGroup(to: Device | PrimaryDevice | GroupInfo): to is GroupInfo { @@ -184,10 +204,10 @@ export function sendTextMessage({ from: PrimaryDevice; to: PrimaryDevice | Device | GroupInfo; text: string | undefined; - attachments?: Array; - sticker?: Proto.DataMessage.ISticker; - preview?: Proto.IPreview; - quote?: Proto.DataMessage.IQuote; + attachments?: Array; + sticker?: Proto.DataMessage.Sticker.Params; + preview?: Proto.Preview.Params; + quote?: Proto.DataMessage.Quote.Params; desktop: Device; timestamp?: number; }): Promise { @@ -200,18 +220,20 @@ export function sendTextMessage({ isSync, to: to as PrimaryDevice, dataMessage: { - body: text, - attachments, - sticker, + ...EMPTY_DATA_MESSAGE, + body: text ?? null, + attachments: attachments ?? null, + sticker: sticker ?? null, preview: preview == null ? null : [preview], - quote, - timestamp: Long.fromNumber(timestamp), + quote: quote ?? null, + timestamp: BigInt(timestamp), groupV2: groupInfo ? { masterKey: groupInfo.group.masterKey, revision: groupInfo.group.revision, + groupChange: null, } - : undefined, + : null, }, sentTo: groupInfo ? groupInfo.members : [to as PrimaryDevice | Device], }), @@ -243,11 +265,14 @@ export function sendReaction({ isSync, to, dataMessage: { - timestamp: Long.fromNumber(reactionTimestamp), + ...EMPTY_DATA_MESSAGE, + timestamp: BigInt(reactionTimestamp), reaction: { emoji, targetAuthorAciBinary: getDevice(targetAuthor).aciRawUuid, - targetSentTimestamp: Long.fromNumber(targetMessageTimestamp), + targetSentTimestamp: BigInt(targetMessageTimestamp), + remove: null, + targetAuthorAci: null, }, }, }), @@ -391,10 +416,11 @@ export async function sendMessageWithAttachments( text: string, filePaths: Array ): Promise<{ - attachments: Array; + attachments: Array; timestamp: number; }> { await composerAttachFiles(page, filePaths); + debug('sending message'); const input = await waitForEnabledComposer(page); await typeIntoInput(input, text, ''); @@ -433,7 +459,7 @@ export async function sendMessageWithAttachments( ); return { attachments, - timestamp: receivedMessage.dataMessage.timestamp.toNumber(), + timestamp: toNumber(receivedMessage.dataMessage.timestamp), }; } } diff --git a/ts/test-mock/messaging/attachments_test.node.ts b/ts/test-mock/messaging/attachments_test.node.ts index b7ffccce5a..1fb05cc1b8 100644 --- a/ts/test-mock/messaging/attachments_test.node.ts +++ b/ts/test-mock/messaging/attachments_test.node.ts @@ -4,10 +4,13 @@ import createDebug from 'debug'; import { assert } from 'chai'; import { expect } from 'playwright/test'; -import { type PrimaryDevice, StorageState } from '@signalapp/mock-server'; +import { + type PrimaryDevice, + type Proto, + StorageState, +} from '@signalapp/mock-server'; import { join } from 'node:path'; import { access, readFile } from 'node:fs/promises'; -import Long from 'long'; import { v4 } from 'uuid'; import type { App } from '../playwright.node.js'; @@ -23,7 +26,6 @@ import { strictAssert } from '../../util/assert.std.js'; import { IMAGE_PNG, VIDEO_MP4 } from '../../types/MIME.std.js'; import { toBase64 } from '../../Bytes.std.js'; import type { AttachmentType } from '../../types/Attachment.std.js'; -import type { SignalService } from '../../protobuf/index.std.js'; export const debug = createDebug('mock:test:attachments'); @@ -408,23 +410,23 @@ describe('attachments', function (this: Mocha.Suite) { await page.getByTestId(pinned.device.aci).click(); const plaintextVideo = await readFile(VIDEO_PATH); - const recentPointer: SignalService.IAttachmentPointer = { + const recentPointer: Proto.AttachmentPointer.Params = { ...(await bootstrap.encryptAndStoreAttachmentOnCDN( plaintextVideo, VIDEO_MP4 )), clientUuid: Buffer.from(v4(), 'utf8'), - uploadTimestamp: Long.fromNumber(Date.now() - 2 * durations.DAY), + uploadTimestamp: BigInt(Date.now() - 2 * durations.DAY), fileName: 'incoming filename', }; const plaintextCat = await readFile(CAT_PATH); - const stalePointer: SignalService.IAttachmentPointer = { + const stalePointer: Proto.AttachmentPointer.Params = { ...(await bootstrap.encryptAndStoreAttachmentOnCDN( plaintextCat, IMAGE_PNG )), - uploadTimestamp: Long.fromNumber(Date.now() - 4 * durations.DAY), + uploadTimestamp: BigInt(Date.now() - 4 * durations.DAY), }; const incomingVideoTimestamp = bootstrap.getTimestamp(); diff --git a/ts/test-mock/messaging/backfill_test.node.ts b/ts/test-mock/messaging/backfill_test.node.ts index 80de2d5281..de39b93ffc 100644 --- a/ts/test-mock/messaging/backfill_test.node.ts +++ b/ts/test-mock/messaging/backfill_test.node.ts @@ -8,10 +8,10 @@ import { join } from 'node:path'; import createDebug from 'debug'; import type { Page } from 'playwright'; import assert from 'node:assert'; -import Long from 'long'; import { LONG_MESSAGE, IMAGE_JPEG } from '../../types/MIME.std.js'; import * as durations from '../../util/durations/index.std.js'; +import { toNumber } from '../../util/toNumber.std.js'; import type { App } from '../playwright.node.js'; import { Bootstrap } from '../bootstrap.node.js'; import { @@ -29,9 +29,20 @@ const SNOW_PATH = join(FIXTURES_PATH, 'snow.jpg'); const { Status } = Proto.SyncMessage.AttachmentBackfillResponse.AttachmentData; function createResponse( - response: Proto.SyncMessage.IAttachmentBackfillResponse -): Proto.IContent { - return { syncMessage: { attachmentBackfillResponse: response } }; + response: Proto.SyncMessage.AttachmentBackfillResponse.Params +): Proto.Content.Params { + return { + syncMessage: { + content: 'attachmentBackfillResponse', + attachmentBackfillResponse: response, + read: null, + stickerPackOperation: null, + viewed: null, + padding: null, + }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, + }; } describe('attachment backfill', function (this: Mocha.Suite) { @@ -41,9 +52,9 @@ describe('attachment backfill', function (this: Mocha.Suite) { let app: App; let page: Page; let unknownContact: PrimaryDevice; - let textAttachment: Proto.IAttachmentPointer; - let catAttachment: Proto.IAttachmentPointer; - let snowAttachment: Proto.IAttachmentPointer; + let textAttachment: Proto.AttachmentPointer.Params; + let catAttachment: Proto.AttachmentPointer.Params; + let snowAttachment: Proto.AttachmentPointer.Params; beforeEach(async () => { bootstrap = new Bootstrap({ contactCount: 1, unknownContactCount: 1 }); @@ -93,9 +104,24 @@ describe('attachment backfill', function (this: Mocha.Suite) { desktop, text: 'look at this pic!', attachments: [ - { ...textAttachment, cdnKey: 'text-not-found' }, - { ...catAttachment, cdnKey: 'cat-not-found' }, - { ...snowAttachment, cdnKey: 'snow-not-found' }, + { + ...textAttachment, + attachmentIdentifier: 'cdnKey', + cdnKey: 'text-not-found', + cdnId: null, + }, + { + ...catAttachment, + attachmentIdentifier: 'cdnKey', + cdnKey: 'cat-not-found', + cdnId: null, + }, + { + ...snowAttachment, + attachmentIdentifier: 'cdnKey', + cdnKey: 'snow-not-found', + cdnId: null, + }, ], timestamp, }); @@ -116,22 +142,25 @@ describe('attachment backfill', function (this: Mocha.Suite) { await startDownload.click(); debug('waiting for backfill request'); - const { - syncMessage: { attachmentBackfillRequest: request }, - } = await phone.waitForSyncMessage(entry => { - return entry.syncMessage.attachmentBackfillRequest != null; + const { syncMessage } = await phone.waitForSyncMessage(entry => { + return entry.syncMessage.content === 'attachmentBackfillRequest'; }); + assert.strictEqual(syncMessage.content, 'attachmentBackfillRequest'); + const request = syncMessage.attachmentBackfillRequest; + assert(request != null); assert.deepEqual( - request?.targetConversation?.threadServiceIdBinary, + request.targetConversation?.threadServiceIdBinary, unknownContact.device.aciBinary ); assert.deepEqual( - request?.targetMessage?.authorServiceIdBinary, + request.targetMessage?.authorServiceIdBinary, unknownContact.device.aciBinary ); assert.strictEqual( - request?.targetMessage?.sentTimestamp?.toNumber(), + request?.targetMessage?.sentTimestamp == null + ? null + : toNumber(request?.targetMessage?.sentTimestamp), timestamp ); @@ -245,7 +274,14 @@ describe('attachment backfill', function (this: Mocha.Suite) { to: desktop, desktop, text: undefined, - attachments: [{ ...catAttachment, cdnKey: 'cat-not-found' }], + attachments: [ + { + ...catAttachment, + attachmentIdentifier: 'cdnKey', + cdnKey: 'cat-not-found', + cdnId: null, + }, + ], timestamp, }); @@ -280,7 +316,14 @@ describe('attachment backfill', function (this: Mocha.Suite) { to: desktop, desktop, text: undefined, - attachments: [{ ...catAttachment, cdnKey: 'cat-not-found' }], + attachments: [ + { + ...catAttachment, + attachmentIdentifier: 'cdnKey', + cdnKey: 'cat-not-found', + cdnId: null, + }, + ], timestamp, }); @@ -300,22 +343,25 @@ describe('attachment backfill', function (this: Mocha.Suite) { await startDownload.click(); debug('waiting for request'); - const { - syncMessage: { attachmentBackfillRequest: request }, - } = await phone.waitForSyncMessage(entry => { - return entry.syncMessage.attachmentBackfillRequest != null; + const { syncMessage } = await phone.waitForSyncMessage(entry => { + return entry.syncMessage.content === 'attachmentBackfillRequest'; }); + assert.strictEqual(syncMessage.content, 'attachmentBackfillRequest'); + const request = syncMessage.attachmentBackfillRequest; + assert(request != null); assert.deepEqual( - request?.targetConversation?.threadServiceIdBinary, + request.targetConversation?.threadServiceIdBinary, unknownContact.device.aciBinary ); assert.deepEqual( - request?.targetMessage?.authorServiceIdBinary, + request.targetMessage?.authorServiceIdBinary, unknownContact.device.aciBinary ); assert.strictEqual( - request?.targetMessage?.sentTimestamp?.toNumber(), + request.targetMessage?.sentTimestamp == null + ? null + : toNumber(request.targetMessage?.sentTimestamp), timestamp ); @@ -357,7 +403,12 @@ describe('attachment backfill', function (this: Mocha.Suite) { ), stickerId: 1, emoji: '🐈', - data: { ...catAttachment, cdnKey: 'cat-not-found' }, + data: { + ...catAttachment, + attachmentIdentifier: 'cdnKey', + cdnKey: 'cat-not-found', + cdnId: null, + }, }, timestamp, }); @@ -378,22 +429,25 @@ describe('attachment backfill', function (this: Mocha.Suite) { await startDownload.click(); debug('waiting for backfill request'); - const { - syncMessage: { attachmentBackfillRequest: request }, - } = await phone.waitForSyncMessage(entry => { - return entry.syncMessage.attachmentBackfillRequest != null; + const { syncMessage } = await phone.waitForSyncMessage(entry => { + return entry.syncMessage.content === 'attachmentBackfillRequest'; }); + assert.strictEqual(syncMessage.content, 'attachmentBackfillRequest'); + const request = syncMessage.attachmentBackfillRequest; + assert.deepEqual( - request?.targetConversation?.threadServiceIdBinary, + request.targetConversation?.threadServiceIdBinary, unknownContact.device.aciBinary ); assert.deepEqual( - request?.targetMessage?.authorServiceIdBinary, + request.targetMessage?.authorServiceIdBinary, unknownContact.device.aciBinary ); assert.strictEqual( - request?.targetMessage?.sentTimestamp?.toNumber(), + request.targetMessage?.sentTimestamp == null + ? null + : toNumber(request.targetMessage?.sentTimestamp), timestamp ); @@ -405,6 +459,7 @@ describe('attachment backfill', function (this: Mocha.Suite) { targetMessage: request.targetMessage, attachments: { attachments: [{ status: Status.PENDING }], + longText: null, }, }), { @@ -419,7 +474,12 @@ describe('attachment backfill', function (this: Mocha.Suite) { targetConversation: request.targetConversation, targetMessage: request.targetMessage, attachments: { - attachments: [{ attachment: catAttachment }], + longText: null, + attachments: [ + { + attachment: catAttachment, + }, + ], }, }), { @@ -442,22 +502,36 @@ describe('attachment backfill', function (this: Mocha.Suite) { to: desktop, desktop, quote: { - id: Long.fromNumber(bootstrap.getTimestamp()), + id: BigInt(bootstrap.getTimestamp()), authorAciBinary: unknownContact.device.aciRawUuid, text: 'quote text', attachments: [ { contentType: IMAGE_JPEG, fileName: 'snow.jpg', - thumbnail: { ...snowAttachment, cdnKey: 'snow-not-found' }, + thumbnail: { + ...snowAttachment, + attachmentIdentifier: 'cdnKey', + cdnKey: 'snow-not-found', + cdnId: null, + }, }, ], type: Proto.DataMessage.Quote.Type.NORMAL, + bodyRanges: null, + authorAci: null, }, preview: { url: 'https://signal.org', title: 'Signal', - image: { ...catAttachment, cdnKey: 'cat-not-found' }, + image: { + ...catAttachment, + attachmentIdentifier: 'cdnKey', + cdnKey: 'cat-not-found', + cdnId: null, + }, + description: null, + date: null, }, text: 'https://signal.org', timestamp, diff --git a/ts/test-mock/messaging/edit_test.node.ts b/ts/test-mock/messaging/edit_test.node.ts index f3aea80367..0ef621d46b 100644 --- a/ts/test-mock/messaging/edit_test.node.ts +++ b/ts/test-mock/messaging/edit_test.node.ts @@ -3,12 +3,12 @@ // `window` use below is actually executed in the browser. // eslint-disable-next-line local-rules/file-suffix -import { Proto } from '@signalapp/mock-server'; +import { Proto, EMPTY_DATA_MESSAGE } from '@signalapp/mock-server'; import { Aci } from '@signalapp/libsignal-client'; import { assert } from 'chai'; import createDebug from 'debug'; -import Long from 'long'; import type { Page } from 'playwright'; +import type { RequireExactlyOne } from 'type-fest'; import type { App } from '../playwright.node.js'; import * as durations from '../../util/durations/index.std.js'; @@ -20,6 +20,7 @@ import { import { SendStatus } from '../../messages/MessageSendState.std.js'; import { drop } from '../../util/drop.std.js'; import { strictAssert } from '../../util/assert.std.js'; +import { toNumber } from '../../util/toNumber.std.js'; import { generateAci } from '../../types/ServiceId.std.js'; import { IMAGE_GIF } from '../../types/MIME.std.js'; import { typeIntoInput, waitForEnabledComposer } from '../helpers.node.js'; @@ -30,40 +31,66 @@ export const debug = createDebug('mock:test:edit'); const ACI_1 = generateAci(); const ACI_1_BINARY = Aci.parseFromServiceIdString(ACI_1).getRawUuidBytes(); -const UNPROCESSED_ATTACHMENT: Proto.IAttachmentPointer = { - cdnId: Long.fromNumber(123), +const UNPROCESSED_ATTACHMENT: Proto.AttachmentPointer.Params = { + cdnId: 123n, key: new Uint8Array([1, 2, 3]), digest: new Uint8Array([4, 5, 6]), contentType: IMAGE_GIF, size: 34, + clientUuid: null, + thumbnail: null, + incrementalMac: null, + chunkSize: null, + fileName: null, + flags: null, + width: null, + height: null, + caption: null, + blurHash: null, + uploadTimestamp: null, + cdnNumber: null, }; function wrap({ dataMessage, editMessage, -}: { - dataMessage?: Proto.IDataMessage; - editMessage?: Proto.IEditMessage; -}): Proto.IContent { +}: RequireExactlyOne<{ + dataMessage?: Proto.DataMessage.Params; + editMessage?: Proto.EditMessage.Params; +}>): Proto.Content.Params { + if (dataMessage != null) { + return { + dataMessage, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, + }; + } return { - dataMessage, editMessage, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }; } -function createMessage(body: string): Proto.IDataMessage { +function createMessage( + body: string +): Proto.DataMessage.Params & { timestamp: bigint } { return { + ...EMPTY_DATA_MESSAGE, body, - groupV2: undefined, - timestamp: Long.fromNumber(Date.now()), + groupV2: null, + timestamp: BigInt(Date.now()), }; } -function createMessageWithQuote(body: string): Proto.IDataMessage { +function createMessageWithQuote( + body: string +): Proto.DataMessage.Params & { timestamp: bigint } { return { + ...EMPTY_DATA_MESSAGE, body, quote: { - id: Long.fromNumber(1), + id: 1n, authorAciBinary: ACI_1_BINARY, text: 'text', attachments: [ @@ -73,25 +100,29 @@ function createMessageWithQuote(body: string): Proto.IDataMessage { thumbnail: UNPROCESSED_ATTACHMENT, }, ], + bodyRanges: null, + type: null, + authorAci: null, }, - groupV2: undefined, - timestamp: Long.fromNumber(Date.now()), + groupV2: null, + timestamp: BigInt(Date.now()), }; } function createEditedMessage( - targetSentTimestamp: Long | null | undefined, + targetSentTimestamp: bigint | null | undefined, body: string, timestamp = Date.now() -): Proto.IEditMessage { +): Proto.EditMessage.Params { strictAssert(targetSentTimestamp, 'timestamp missing'); return { targetSentTimestamp, dataMessage: { + ...EMPTY_DATA_MESSAGE, body, - groupV2: undefined, - timestamp: Long.fromNumber(timestamp), + groupV2: null, + timestamp: BigInt(timestamp), }, }; } @@ -146,7 +177,7 @@ describe('editing', function (this: Mocha.Suite) { const initialMessageBody = 'hey yhere'; const originalMessage = createMessage(initialMessageBody); - const originalMessageTimestamp = Number(originalMessage.timestamp); + const originalMessageTimestamp = toNumber(originalMessage.timestamp); debug('sending message'); { @@ -185,8 +216,8 @@ describe('editing', function (this: Mocha.Suite) { originalMessage.timestamp, editedMessageBody ); - const editedMessageTimestamp = Number( - editedMessage.dataMessage?.timestamp + const editedMessageTimestamp = toNumber( + editedMessage.dataMessage?.timestamp ?? 0n ); { const sendOptions = { @@ -217,7 +248,7 @@ describe('editing', function (this: Mocha.Suite) { const initialMessageBody = 'hey yhere'; const originalMessage = createMessage(initialMessageBody); - const originalMessageTimestamp = Number(originalMessage.timestamp); + const originalMessageTimestamp = toNumber(originalMessage.timestamp); debug('incoming message'); { @@ -252,7 +283,7 @@ describe('editing', function (this: Mocha.Suite) { assert.strictEqual(receiptMessage.type, Proto.ReceiptMessage.Type.READ); assert.strictEqual(receiptMessage.timestamp.length, 1); assert.strictEqual( - Number(receiptMessage.timestamp[0]), + toNumber(receiptMessage.timestamp[0]), originalMessageTimestamp ); } @@ -263,8 +294,8 @@ describe('editing', function (this: Mocha.Suite) { originalMessage.timestamp, editedMessageBody ); - const editedMessageTimestamp = Number( - editedMessage.dataMessage?.timestamp + const editedMessageTimestamp = toNumber( + editedMessage.dataMessage?.timestamp ?? 0n ); { const sendOptions = { @@ -293,7 +324,7 @@ describe('editing', function (this: Mocha.Suite) { assert.strictEqual(receiptMessage.type, Proto.ReceiptMessage.Type.READ); assert.strictEqual(receiptMessage.timestamp.length, 1); assert.strictEqual( - Number(receiptMessage.timestamp[0]), + toNumber(receiptMessage.timestamp[0]), editedMessageTimestamp ); } @@ -340,7 +371,9 @@ describe('editing', function (this: Mocha.Suite) { await sendEditedMessage( window, - originalMessage.timestamp?.toNumber() ?? 0, + (originalMessage.timestamp == null + ? null + : toNumber(originalMessage.timestamp)) ?? 0, '.2', 'edit message 1' ); @@ -348,28 +381,38 @@ describe('editing', function (this: Mocha.Suite) { debug("waiting for friend's edit message"); const { editMessage: firstEdit } = await friend.waitForEditMessage(); assert.strictEqual( - firstEdit.targetSentTimestamp?.toNumber(), - originalMessage.timestamp?.toNumber() + firstEdit.targetSentTimestamp == null + ? null + : toNumber(firstEdit.targetSentTimestamp), + originalMessage.timestamp == null + ? null + : toNumber(originalMessage.timestamp) ); assert.strictEqual(firstEdit.dataMessage?.body, 'edit message 1.2'); await sendEditedMessage( window, - originalMessage.timestamp?.toNumber() ?? 0, + (originalMessage.timestamp == null + ? null + : toNumber(originalMessage.timestamp)) ?? 0, '.3', 'edit message 1.2' ); const { editMessage: secondEdit } = await friend.waitForEditMessage(); assert.strictEqual( - secondEdit.targetSentTimestamp?.toNumber(), - firstEdit.dataMessage?.timestamp?.toNumber() + secondEdit.targetSentTimestamp == null + ? null + : toNumber(secondEdit.targetSentTimestamp), + firstEdit.dataMessage?.timestamp == null + ? null + : toNumber(firstEdit.dataMessage?.timestamp) ); assert.strictEqual(secondEdit.dataMessage?.body, 'edit message 1.2.3'); debug('opening edit history'); const secondEditMessage = window.locator( - `.module-message[data-testid="${originalMessage?.timestamp?.toNumber()}"]` + `.module-message[data-testid="${originalMessage?.timestamp == null ? null : toNumber(originalMessage?.timestamp)}"]` ); await secondEditMessage .locator('.module-message__metadata__edited') @@ -391,7 +434,7 @@ describe('editing', function (this: Mocha.Suite) { const window = await app.getWindow(); const originalMessage = createMessage('v1'); - const originalMessageTimestamp = Number(originalMessage.timestamp); + const originalMessageTimestamp = toNumber(originalMessage.timestamp); const sendOriginalMessage = async () => { debug('sending original message', originalMessageTimestamp); @@ -408,7 +451,7 @@ describe('editing', function (this: Mocha.Suite) { debug('sending all messages + edits'); let targetSentTimestamp = originalMessage.timestamp; let editTimestamp = Date.now() + 1; - const editedMessages: Array = [ + const editedMessages: Array = [ 'v2', 'v3', 'v4', @@ -419,13 +462,13 @@ describe('editing', function (this: Mocha.Suite) { body, editTimestamp ); - targetSentTimestamp = Long.fromNumber(editTimestamp); + targetSentTimestamp = BigInt(editTimestamp); editTimestamp += 1; return message; }); { const sendEditMessages = editedMessages.map(editMessage => { - const timestamp = Number(editMessage.dataMessage?.timestamp); + const timestamp = toNumber(editMessage.dataMessage?.timestamp ?? 0n); const sendOptions = { timestamp, }; @@ -460,17 +503,17 @@ describe('editing', function (this: Mocha.Suite) { const { phone, desktop } = bootstrap; const originalMessage = createMessageWithQuote('v1'); - const originalMessageTimestamp = Number(originalMessage.timestamp); + const originalMessageTimestamp = toNumber(originalMessage.timestamp); debug('sending edit'); const targetSentTimestamp = originalMessage.timestamp; const editTimestamp = Date.now() + 1; - const editMessage: Proto.IEditMessage = createEditedMessage( + const editMessage: Proto.EditMessage.Params = createEditedMessage( targetSentTimestamp, 'v2', editTimestamp ); - const timestamp = Number(editMessage.dataMessage?.timestamp); + const timestamp = toNumber(editMessage.dataMessage?.timestamp ?? 0n); drop(phone.sendRaw(desktop, wrap({ editMessage }), { timestamp })); debug('sending original message', originalMessageTimestamp); @@ -562,7 +605,7 @@ describe('editing', function (this: Mocha.Suite) { ); strictAssert(originalMessage.timestamp, 'timestamp missing'); - const originalMessageTimestamp = Number(originalMessage.timestamp); + const originalMessageTimestamp = toNumber(originalMessage.timestamp); debug('original message', { timestamp: originalMessageTimestamp }); debug("getting friend's conversationId"); @@ -592,6 +635,8 @@ describe('editing', function (this: Mocha.Suite) { type: Proto.ReceiptMessage.Type.DELIVERY, timestamp: [originalMessage.timestamp], }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }, deliveryReceiptSendOptions ); @@ -637,6 +682,8 @@ describe('editing', function (this: Mocha.Suite) { type: Proto.ReceiptMessage.Type.READ, timestamp: [originalMessage.timestamp], }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }, readReceiptSendOptions ); @@ -664,7 +711,7 @@ describe('editing', function (this: Mocha.Suite) { const { editMessage: editMessageV2 } = await friend.waitForEditMessage(); assert.strictEqual(editMessageV2.dataMessage?.body, editMessageV2Text); debug('v2 message', { - timestamp: Number(editMessageV2.dataMessage?.timestamp), + timestamp: toNumber(editMessageV2.dataMessage?.timestamp), }); // Sending a v3 edited message targetting v2 @@ -673,7 +720,9 @@ describe('editing', function (this: Mocha.Suite) { debug('sending edit message v3 desktop -> friend'); await sendEditedMessage( page, - originalMessage?.timestamp?.toNumber() ?? 0, + (originalMessage?.timestamp == null + ? null + : toNumber(originalMessage?.timestamp)) ?? 0, '3', '12' ); @@ -683,7 +732,7 @@ describe('editing', function (this: Mocha.Suite) { assert.strictEqual(editMessageV3.dataMessage?.body, editMessageV3Text); strictAssert(editMessageV3.dataMessage?.timestamp, 'timestamp missing'); - const editMessageV3Timestamp = Number( + const editMessageV3Timestamp = toNumber( editMessageV3.dataMessage.timestamp ); debug('v3 message', { timestamp: editMessageV3Timestamp }); @@ -695,7 +744,9 @@ describe('editing', function (this: Mocha.Suite) { debug('sending edit message v4 desktop -> friend'); await sendEditedMessage( page, - originalMessage?.timestamp?.toNumber() ?? 0, + (originalMessage?.timestamp == null + ? null + : toNumber(originalMessage?.timestamp)) ?? 0, '4', '123' ); @@ -705,7 +756,7 @@ describe('editing', function (this: Mocha.Suite) { assert.strictEqual(editMessageV4.dataMessage?.body, editMessageV4Text); strictAssert(editMessageV4.dataMessage?.timestamp, 'timestamp missing'); - const editMessageV4Timestamp = Number( + const editMessageV4Timestamp = toNumber( editMessageV4.dataMessage.timestamp ); debug('v4 message', { timestamp: editMessageV4Timestamp }); @@ -726,6 +777,8 @@ describe('editing', function (this: Mocha.Suite) { type: 1, timestamp: [editMessageV3.dataMessage.timestamp], }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }, readReceiptSendOptions ); @@ -795,17 +848,17 @@ describe('editing', function (this: Mocha.Suite) { const { phone, desktop } = bootstrap; const originalMessage = createMessageWithQuote('v1'); - const originalMessageTimestamp = Number(originalMessage.timestamp); + const originalMessageTimestamp = toNumber(originalMessage.timestamp); debug('sending edit'); const targetSentTimestamp = originalMessage.timestamp; const editTimestamp = Date.now() + 1; - const editMessage: Proto.IEditMessage = createEditedMessage( + const editMessage: Proto.EditMessage.Params = createEditedMessage( targetSentTimestamp, 'v2', editTimestamp ); - const timestamp = Number(editMessage.dataMessage?.timestamp); + const timestamp = toNumber(editMessage.dataMessage?.timestamp ?? 0n); drop(phone.sendRaw(desktop, wrap({ editMessage }), { timestamp })); debug('sending original message', originalMessageTimestamp); @@ -843,7 +896,7 @@ describe('editing', function (this: Mocha.Suite) { const { phone, desktop } = bootstrap; const originalMessage = createMessage('v1'); - const originalMessageTimestamp = Number(originalMessage.timestamp); + const originalMessageTimestamp = toNumber(originalMessage.timestamp); const sendOriginalMessage = async () => { debug('sending original message', originalMessageTimestamp); @@ -860,7 +913,7 @@ describe('editing', function (this: Mocha.Suite) { debug('sending all messages + edits'); let targetSentTimestamp = originalMessage.timestamp; let editTimestamp = Date.now() + 1; - const editedMessages: Array = [ + const editedMessages: Array = [ 'v2', 'v3', 'v4', @@ -871,13 +924,13 @@ describe('editing', function (this: Mocha.Suite) { body, editTimestamp ); - targetSentTimestamp = Long.fromNumber(editTimestamp); + targetSentTimestamp = BigInt(editTimestamp); editTimestamp += 1; return message; }); { const sendEditMessages = editedMessages.map(editMessage => { - const timestamp = Number(editMessage.dataMessage?.timestamp); + const timestamp = toNumber(editMessage.dataMessage?.timestamp ?? 0n); const sendOptions = { timestamp, }; diff --git a/ts/test-mock/messaging/expire_timer_version_test.node.ts b/ts/test-mock/messaging/expire_timer_version_test.node.ts index 924ff19a74..215ac556d5 100644 --- a/ts/test-mock/messaging/expire_timer_version_test.node.ts +++ b/ts/test-mock/messaging/expire_timer_version_test.node.ts @@ -6,9 +6,9 @@ import { type PrimaryDevice, Proto, StorageState, + EMPTY_DATA_MESSAGE, } from '@signalapp/mock-server'; import createDebug from 'debug'; -import Long from 'long'; import * as durations from '../../util/durations/index.std.js'; import { uuidToBytes } from '../../util/uuidToBytes.std.js'; @@ -71,6 +71,8 @@ describe('messaging/expireTimerVersion', function (this: Mocha.Suite) { identifier: uuidToBytes(MY_STORY_ID), isBlockList: true, name: MY_STORY_ID, + deletedAtTimestamp: null, + recipientServiceIdsBinary: null, }, }, }); @@ -181,24 +183,41 @@ describe('messaging/expireTimerVersion', function (this: Mocha.Suite) { debug('Send a sync message'); const timestamp = bootstrap.getTimestamp(); const destinationServiceIdBinary = stranger.device.aciBinary; - const content = { + const content: Proto.Content.Params = { syncMessage: { sent: { destinationServiceIdBinary, - timestamp: Long.fromNumber(timestamp), + timestamp: BigInt(timestamp), message: { + ...EMPTY_DATA_MESSAGE, body: 'request', - timestamp: Long.fromNumber(timestamp), + timestamp: BigInt(timestamp), expireTimer: scenario.ourTimer, expireTimerVersion: scenario.ourVersion, }, unidentifiedStatus: [ { destinationServiceIdBinary, + unidentified: null, + destinationPniIdentityKey: null, + destinationServiceId: null, }, ], + destinationE164: null, + expirationStartTimestamp: null, + isRecipientUpdate: null, + storyMessage: null, + storyMessageRecipients: null, + editMessage: null, + destinationServiceId: null, }, + read: null, + stickerPackOperation: null, + viewed: null, + padding: null, }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }; const sendOptions = { timestamp, @@ -209,13 +228,16 @@ describe('messaging/expireTimerVersion', function (this: Mocha.Suite) { const sendResponse = async () => { debug('Send a response message'); const timestamp = bootstrap.getTimestamp(); - const content = { + const content: Proto.Content.Params = { dataMessage: { + ...EMPTY_DATA_MESSAGE, body: 'response', - timestamp: Long.fromNumber(timestamp), + timestamp: BigInt(timestamp), expireTimer: scenario.theirTimer, expireTimerVersion: scenario.theirVersion, }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }; const sendOptions = { timestamp, diff --git a/ts/test-mock/messaging/lightbox_test.node.ts b/ts/test-mock/messaging/lightbox_test.node.ts index 3379433752..bbdd9e55ce 100644 --- a/ts/test-mock/messaging/lightbox_test.node.ts +++ b/ts/test-mock/messaging/lightbox_test.node.ts @@ -2,8 +2,13 @@ // SPDX-License-Identifier: AGPL-3.0-only import createDebug from 'debug'; +import { assert } from 'chai'; import { expect } from 'playwright/test'; -import { type PrimaryDevice, StorageState } from '@signalapp/mock-server'; +import { + type PrimaryDevice, + type Proto, + StorageState, +} from '@signalapp/mock-server'; import * as path from 'node:path'; import type { App } from '../playwright.node.js'; import { Bootstrap } from '../bootstrap.node.js'; @@ -15,7 +20,6 @@ import { } from '../helpers.node.js'; import * as durations from '../../util/durations/index.std.js'; import { strictAssert } from '../../util/assert.std.js'; -import type { SignalService } from '../../protobuf/index.std.js'; const debug = createDebug('mock:test:lightbox'); @@ -64,7 +68,7 @@ describe('lightbox', function (this: Mocha.Suite) { async function sendAttachmentsBack( text: string, - attachments: Array + attachments: Array ) { debug(`replying with ${attachments.length} attachments`); const timestamp = bootstrap.getTimestamp(); @@ -130,10 +134,13 @@ describe('lightbox', function (this: Mocha.Suite) { await Lightbox.waitFor(); async function expectLightboxImage( - attachment: SignalService.IAttachmentPointer + attachment: Proto.AttachmentPointer.Params ) { - debug('attachment cdnKey is', typeof attachment.cdnKey); - strictAssert(attachment.cdnKey, 'Must have cdnKey'); + assert.strictEqual( + attachment.attachmentIdentifier, + 'cdnKey', + 'Must have cdnKey' + ); strictAssert(attachment.cdnKey.length > 0, 'Must have valid cdnKey'); const Object = LightboxContent.getByTestId(attachment.cdnKey); debug(`Waiting for attachment with cdnKey ${attachment.cdnKey}`); diff --git a/ts/test-mock/messaging/readSync_test.node.ts b/ts/test-mock/messaging/readSync_test.node.ts index a5523c2445..f46ef7a1c0 100644 --- a/ts/test-mock/messaging/readSync_test.node.ts +++ b/ts/test-mock/messaging/readSync_test.node.ts @@ -2,7 +2,6 @@ // SPDX-License-Identifier: AGPL-3.0-only import createDebug from 'debug'; -import Long from 'long'; import type { App } from '../playwright.node.js'; import * as durations from '../../util/durations/index.std.js'; @@ -98,9 +97,7 @@ describe('readSync', function (this: Mocha.Suite) { timestamp: bootstrap.getTimestamp(), }; - const longTimestamps = timestamps.map(timestamp => - Long.fromNumber(timestamp) - ); + const longTimestamps = timestamps.map(timestamp => BigInt(timestamp)); const senderAciBinary = friend.device.aciRawUuid; @@ -108,11 +105,18 @@ describe('readSync', function (this: Mocha.Suite) { desktop, { syncMessage: { + content: null, read: longTimestamps.map(timestamp => ({ senderAciBinary, timestamp, + senderAci: null, })), + stickerPackOperation: null, + viewed: null, + padding: null, }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }, sendOptions ); diff --git a/ts/test-mock/messaging/safety_number_test.node.ts b/ts/test-mock/messaging/safety_number_test.node.ts index 851055996a..35d31a17ba 100644 --- a/ts/test-mock/messaging/safety_number_test.node.ts +++ b/ts/test-mock/messaging/safety_number_test.node.ts @@ -56,6 +56,7 @@ describe('safety number', function (this: Mocha.Suite) { isBlockList: false, name: MY_STORY_ID, recipientServiceIdsBinary: [alice.device.aciBinary], + deletedAtTimestamp: null, }, }, }); @@ -193,6 +194,7 @@ describe('safety number', function (this: Mocha.Suite) { debug('Getting a story'); const { storyMessage } = await alice.waitForStory(); - assert.strictEqual(storyMessage.textAttachment?.text, '123'); + assert.strictEqual(storyMessage.attachment, 'textAttachment'); + assert.strictEqual(storyMessage.textAttachment.text, '123'); }); }); diff --git a/ts/test-mock/messaging/sendSync_test.node.ts b/ts/test-mock/messaging/sendSync_test.node.ts index 707eb0e360..ecc3c230ce 100644 --- a/ts/test-mock/messaging/sendSync_test.node.ts +++ b/ts/test-mock/messaging/sendSync_test.node.ts @@ -2,8 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import createDebug from 'debug'; -import Long from 'long'; -import { StorageState } from '@signalapp/mock-server'; +import { StorageState, EMPTY_DATA_MESSAGE } from '@signalapp/mock-server'; +import type { Proto } from '@signalapp/mock-server'; import type { App } from '../playwright.node.js'; import * as durations from '../../util/durations/index.std.js'; @@ -54,25 +54,44 @@ describe('sendSync', function (this: Mocha.Suite) { debug('Send a group sync sent message from phone'); const messageBody = 'Hi everybody!'; const timestamp = bootstrap.getTimestamp(); - const originalDataMessage = { + const originalDataMessage: Proto.DataMessage.Params = { + ...EMPTY_DATA_MESSAGE, body: messageBody, - timestamp: Long.fromNumber(timestamp), + timestamp: BigInt(timestamp), groupV2: { masterKey: group.masterKey, revision: group.revision, + groupChange: null, }, }; - const content = { + const content: Proto.Content.Params = { syncMessage: { sent: { - timestamp: Long.fromNumber(timestamp), + timestamp: BigInt(timestamp), message: originalDataMessage, unidentifiedStatus: members.map(member => ({ destinationServiceIdBinary: member.device.aciBinary, destination: member.device.number, + unidentified: null, + destinationPniIdentityKey: null, + destinationServiceId: null, })), + destinationE164: null, + expirationStartTimestamp: null, + isRecipientUpdate: null, + storyMessage: null, + storyMessageRecipients: null, + editMessage: null, + destinationServiceIdBinary: null, + destinationServiceId: null, }, + read: null, + stickerPackOperation: null, + viewed: null, + padding: null, }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }; const sendOptions = { timestamp, diff --git a/ts/test-mock/messaging/stories_test.node.ts b/ts/test-mock/messaging/stories_test.node.ts index a0afe6eeb4..b0bff50f41 100644 --- a/ts/test-mock/messaging/stories_test.node.ts +++ b/ts/test-mock/messaging/stories_test.node.ts @@ -2,8 +2,11 @@ // SPDX-License-Identifier: AGPL-3.0-only import createDebug from 'debug'; -import Long from 'long'; -import { Proto, StorageState } from '@signalapp/mock-server'; +import { + Proto, + StorageState, + EMPTY_DATA_MESSAGE, +} from '@signalapp/mock-server'; import type { Group } from '@signalapp/mock-server'; import * as durations from '../../util/durations/index.std.js'; @@ -51,6 +54,8 @@ describe('story/messaging', function (this: Mocha.Suite) { identifier: uuidToBytes(MY_STORY_ID), isBlockList: false, name: MY_STORY_ID, + deletedAtTimestamp: null, + recipientServiceIdsBinary: null, }, }, }); @@ -65,6 +70,7 @@ describe('story/messaging', function (this: Mocha.Suite) { isBlockList: false, name: 'first', recipientServiceIdsBinary: [first.device.aciBinary], + deletedAtTimestamp: null, }, }, }); @@ -77,6 +83,7 @@ describe('story/messaging', function (this: Mocha.Suite) { isBlockList: false, name: 'second', recipientServiceIdsBinary: [second.device.aciBinary], + deletedAtTimestamp: null, }, }, }); @@ -137,28 +144,51 @@ describe('story/messaging', function (this: Mocha.Suite) { { syncMessage: { sent: { - timestamp: Long.fromNumber(sentAt), - expirationStartTimestamp: Long.fromNumber(sentAt), + timestamp: BigInt(sentAt), + expirationStartTimestamp: BigInt(sentAt), storyMessage: { textAttachment: { text: 'hello', + textStyle: null, + textForegroundColor: null, + textBackgroundColor: null, + preview: null, + background: null, }, allowsReplies: true, + profileKey: null, + group: null, + bodyRanges: null, }, storyMessageRecipients: [ { destinationServiceIdBinary: first.device.aciBinary, distributionListIds: [DISTRIBUTION1], isAllowedToReply: true, + destinationServiceId: null, }, { destinationServiceIdBinary: second.device.aciBinary, distributionListIds: [DISTRIBUTION2], isAllowedToReply: true, + destinationServiceId: null, }, ], + destinationE164: null, + message: null, + unidentifiedStatus: null, + isRecipientUpdate: null, + editMessage: null, + destinationServiceIdBinary: null, + destinationServiceId: null, }, + read: null, + stickerPackOperation: null, + viewed: null, + padding: null, }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }, { timestamp: sentAt } ); @@ -168,13 +198,17 @@ describe('story/messaging', function (this: Mocha.Suite) { desktop, { dataMessage: { + ...EMPTY_DATA_MESSAGE, body: 'first reply', storyContext: { authorAciBinary: phone.device.aciRawUuid, - sentTimestamp: Long.fromNumber(sentAt), + sentTimestamp: BigInt(sentAt), + authorAci: null, }, - timestamp: Long.fromNumber(sentAt + 1), + timestamp: BigInt(sentAt + 1), }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }, { timestamp: sentAt + 1 } ); @@ -182,13 +216,17 @@ describe('story/messaging', function (this: Mocha.Suite) { desktop, { dataMessage: { + ...EMPTY_DATA_MESSAGE, body: 'second reply', storyContext: { authorAciBinary: phone.device.aciRawUuid, - sentTimestamp: Long.fromNumber(sentAt), + sentTimestamp: BigInt(sentAt), + authorAci: null, }, - timestamp: Long.fromNumber(sentAt + 2), + timestamp: BigInt(sentAt + 2), }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }, { timestamp: sentAt + 2 } ); @@ -242,16 +280,22 @@ describe('story/messaging', function (this: Mocha.Suite) { desktop, { dataMessage: { + ...EMPTY_DATA_MESSAGE, body: 'first reply', storyContext: { authorAciBinary: desktop.aciRawUuid, - sentTimestamp: Long.fromNumber(sentAt), + sentTimestamp: BigInt(sentAt), + authorAci: null, }, groupV2: { masterKey: group.masterKey, + revision: null, + groupChange: null, }, - timestamp: Long.fromNumber(sentAt + 1), + timestamp: BigInt(sentAt + 1), }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }, { timestamp: sentAt + 1 } ); diff --git a/ts/test-mock/messaging/unknown_contact_test.node.ts b/ts/test-mock/messaging/unknown_contact_test.node.ts index 1f67ba38c6..48633c5583 100644 --- a/ts/test-mock/messaging/unknown_contact_test.node.ts +++ b/ts/test-mock/messaging/unknown_contact_test.node.ts @@ -4,7 +4,6 @@ import type { PrimaryDevice } from '@signalapp/mock-server'; import { Proto } from '@signalapp/mock-server'; import createDebug from 'debug'; -import Long from 'long'; import type { Page } from 'playwright'; import assert from 'node:assert'; import * as durations from '../../util/durations/index.std.js'; @@ -49,11 +48,19 @@ describe('unknown contacts', function (this: Mocha.Suite) { await unknownContact.sendRaw(desktop, { callMessage: { offer: { - id: new Long(Math.floor(Math.random() * 1e10)), + id: BigInt(Math.floor(Math.random() * 1e10)), type: Proto.CallMessage.Offer.Type.OFFER_AUDIO_CALL, opaque: new Uint8Array(0), }, + answer: null, + iceUpdate: null, + busy: null, + hangup: null, + destinationDeviceId: null, + opaque: null, }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }); debug('opening conversation'); @@ -99,8 +106,16 @@ describe('unknown contacts', function (this: Mocha.Suite) { messageRequestResponse: { type: Proto.SyncMessage.MessageRequestResponse.Type.ACCEPT, threadAciBinary: unknownContact.device.aciRawUuid, + groupId: null, + threadAci: null, }, + read: null, + stickerPackOperation: null, + viewed: null, + padding: null, }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }); debug('verifying that compose is now visible'); diff --git a/ts/test-mock/playwright.node.ts b/ts/test-mock/playwright.node.ts index 6428b83d52..fd6bf27509 100644 --- a/ts/test-mock/playwright.node.ts +++ b/ts/test-mock/playwright.node.ts @@ -13,6 +13,7 @@ import type { import type { ReceiptType } from '../types/Receipt.std.js'; import { SECOND } from '../util/durations/index.std.js'; import { drop } from '../util/drop.std.js'; +import { toNumber } from '../util/toNumber.std.js'; import type { MessageAttributesType } from '../model-types.d.ts'; import type { SocketStatuses } from '../textsecure/SocketManager.preload.js'; @@ -39,7 +40,7 @@ export type ReceiptsInfoType = Readonly<{ }>; export type StorageServiceInfoType = Readonly<{ - manifestVersion: number; + manifestVersion: bigint; }>; export type AppOptionsType = Readonly<{ @@ -151,7 +152,7 @@ export class App extends EventEmitter { return this.#waitForEvent('storageServiceComplete'); } - public async waitForManifestVersion(version: number): Promise { + public async waitForManifestVersion(version: bigint): Promise { // eslint-disable-next-line no-constant-condition while (true) { // eslint-disable-next-line no-await-in-loop @@ -280,7 +281,7 @@ export class App extends EventEmitter { `window.SignalCI.getPendingEventCount(${JSON.stringify(event)})` ); - return Number(result); + return toNumber(result as bigint); } // diff --git a/ts/test-mock/pnp/accept_gv2_invite_test.node.ts b/ts/test-mock/pnp/accept_gv2_invite_test.node.ts index e6801d0efc..463775d760 100644 --- a/ts/test-mock/pnp/accept_gv2_invite_test.node.ts +++ b/ts/test-mock/pnp/accept_gv2_invite_test.node.ts @@ -213,10 +213,14 @@ describe('pnp/accept gv2 invite', function (this: Mocha.Suite) { assert(!group.getPendingMemberByServiceId(desktop.pni)); // Verify that sync message was sent. - const { syncMessage } = await phone.waitForSyncMessage(entry => - Boolean(entry.syncMessage.sent?.message?.groupV2?.groupChange) - ); - const groupChangeBuffer = syncMessage.sent?.message?.groupV2?.groupChange; + const { syncMessage } = await phone.waitForSyncMessage(entry => { + if (entry.syncMessage.content !== 'sent') { + return false; + } + return entry.syncMessage.sent.message?.groupV2?.groupChange != null; + }); + assert.strictEqual(syncMessage.content, 'sent'); + const groupChangeBuffer = syncMessage.sent.message?.groupV2?.groupChange; assert.notEqual(groupChangeBuffer, null); const groupChange = Proto.GroupChange.decode( groupChangeBuffer ?? new Uint8Array(0) diff --git a/ts/test-mock/pnp/calling_test.node.ts b/ts/test-mock/pnp/calling_test.node.ts index 626ea5d103..a9c9bfd991 100644 --- a/ts/test-mock/pnp/calling_test.node.ts +++ b/ts/test-mock/pnp/calling_test.node.ts @@ -65,9 +65,19 @@ describe('pnp/calling', function (this: Mocha.Suite) { { callMessage: { offer: { + id: null, + type: null, opaque: new Uint8Array(1), }, + answer: null, + iceUpdate: null, + busy: null, + hangup: null, + destinationDeviceId: null, + opaque: null, }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }, { timestamp: bootstrap.getTimestamp(), diff --git a/ts/test-mock/pnp/merge_test.node.ts b/ts/test-mock/pnp/merge_test.node.ts index d4a9cc2c86..430217570f 100644 --- a/ts/test-mock/pnp/merge_test.node.ts +++ b/ts/test-mock/pnp/merge_test.node.ts @@ -3,10 +3,14 @@ import { timingSafeEqual } from 'node:crypto'; import { assert } from 'chai'; -import { ServiceIdKind, Proto, StorageState } from '@signalapp/mock-server'; +import { + ServiceIdKind, + Proto, + StorageState, + EMPTY_DATA_MESSAGE, +} from '@signalapp/mock-server'; import type { PrimaryDevice } from '@signalapp/mock-server'; import createDebug from 'debug'; -import Long from 'long'; import * as durations from '../../util/durations/index.std.js'; import { uuidToBytes } from '../../util/uuidToBytes.std.js'; @@ -87,6 +91,8 @@ describe('pnp/merge', function (this: Mocha.Suite) { identifier: uuidToBytes(MY_STORY_ID), isBlockList: true, name: MY_STORY_ID, + deletedAtTimestamp: null, + recipientServiceIdsBinary: null, }, }, }); @@ -283,7 +289,7 @@ describe('pnp/merge', function (this: Mocha.Suite) { state = state.updateContact(pniContact, { pniBinary: undefined, e164: undefined, - unregisteredAtTimestamp: Long.fromNumber(bootstrap.getTimestamp()), + unregisteredAtTimestamp: BigInt(bootstrap.getTimestamp()), }); if (withPniContact) { @@ -397,10 +403,10 @@ describe('pnp/merge', function (this: Mocha.Suite) { let pniContacts = 0; let aciContacts = 0; - for (const { contact } of added) { - if (!contact) { - throw new Error('Invalid record'); - } + for (const record of added) { + assert.strictEqual(record.record, 'contact'); + + const { contact } = record; const { aciBinary, e164, pniBinary } = contact; if ( @@ -421,13 +427,14 @@ describe('pnp/merge', function (this: Mocha.Suite) { } assert.strictEqual(aciContacts, 1); assert.strictEqual(pniContacts, 1); + assert.strictEqual(removed[0].record, 'contact'); assert.deepEqual( - removed[0].contact?.pniBinary, + removed[0].contact.pniBinary, pniContact.device.pniRawUuid ); assert.deepEqual( - removed[0].contact?.aciBinary, + removed[0].contact.aciBinary, pniContact.device.aciRawUuid ); @@ -553,14 +560,15 @@ describe('pnp/merge', function (this: Mocha.Suite) { const timestamp = bootstrap.getTimestamp(); const destinationServiceIdBinary = pniContact.device[`${key}Binary`]; const destination = key === 'pni' ? pniContact.device.number : undefined; - const content = { + const content: Proto.Content.Params = { syncMessage: { sent: { destinationServiceIdBinary, - destination, - timestamp: Long.fromNumber(timestamp), + destinationE164: destination ?? null, + timestamp: BigInt(timestamp), message: { - timestamp: Long.fromNumber(timestamp), + ...EMPTY_DATA_MESSAGE, + timestamp: BigInt(timestamp), flags: Proto.DataMessage.Flags.EXPIRATION_TIMER_UPDATE, expireTimer: key === 'pni' ? 90 * 24 * 3600 : 60 * 24 * 3600, expireTimerVersion: key === 'pni' ? 3 : 4, @@ -568,11 +576,25 @@ describe('pnp/merge', function (this: Mocha.Suite) { unidentifiedStatus: [ { destinationServiceIdBinary, - destination, + unidentified: null, + destinationPniIdentityKey: null, + destinationServiceId: null, }, ], + expirationStartTimestamp: null, + isRecipientUpdate: null, + storyMessage: null, + storyMessageRecipients: null, + editMessage: null, + destinationServiceId: null, }, + read: null, + stickerPackOperation: null, + viewed: null, + padding: null, }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }; const sendOptions = { timestamp, diff --git a/ts/test-mock/pnp/phone_discovery_test.node.ts b/ts/test-mock/pnp/phone_discovery_test.node.ts index 9cb9c08979..fe1616a72f 100644 --- a/ts/test-mock/pnp/phone_discovery_test.node.ts +++ b/ts/test-mock/pnp/phone_discovery_test.node.ts @@ -72,6 +72,8 @@ describe('pnp/phone discovery', function (this: Mocha.Suite) { identifier: uuidToBytes(MY_STORY_ID), isBlockList: true, name: MY_STORY_ID, + deletedAtTimestamp: null, + recipientServiceIdsBinary: null, }, }, }); diff --git a/ts/test-mock/pnp/pni_change_test.node.ts b/ts/test-mock/pnp/pni_change_test.node.ts index d087140e29..d18e2032ac 100644 --- a/ts/test-mock/pnp/pni_change_test.node.ts +++ b/ts/test-mock/pnp/pni_change_test.node.ts @@ -138,7 +138,10 @@ describe('pnp/PNI Change', function (this: Mocha.Suite) { const updated = await phone.setStorageState( state .removeRecord(item => { - return item.record.contact?.pniBinary?.length + if (item.record.record !== 'contact') { + return false; + } + return item.record.contact.pniBinary?.length ? timingSafeEqual( item.record.contact.pniBinary, contactA.device.pniRawUuid @@ -239,7 +242,10 @@ describe('pnp/PNI Change', function (this: Mocha.Suite) { const updated = await phone.setStorageState( state .removeRecord(item => { - return item.record.contact?.pniBinary?.length + if (item.record.record !== 'contact') { + return false; + } + return item.record.contact.pniBinary?.length ? timingSafeEqual( item.record.contact.pniBinary, contactA.device.pniRawUuid @@ -345,7 +351,10 @@ describe('pnp/PNI Change', function (this: Mocha.Suite) { const updated = await phone.setStorageState( state .removeRecord(item => { - return item.record.contact?.pniBinary?.length + if (item.record.record !== 'contact') { + return false; + } + return item.record.contact.pniBinary?.length ? timingSafeEqual( item.record.contact.pniBinary, contactA.device.pniRawUuid @@ -480,7 +489,10 @@ describe('pnp/PNI Change', function (this: Mocha.Suite) { const updated = await phone.setStorageState( state .removeRecord(item => { - return item.record.contact?.pniBinary?.length + if (item.record.record !== 'contact') { + return false; + } + return item.record.contact.pniBinary?.length ? timingSafeEqual( item.record.contact.pniBinary, contactA.device.pniRawUuid @@ -516,7 +528,10 @@ describe('pnp/PNI Change', function (this: Mocha.Suite) { const updated = await phone.setStorageState( state .removeRecord(item => { - return item.record.contact?.pniBinary?.length + if (item.record.record !== 'contact') { + return false; + } + return item.record.contact.pniBinary?.length ? timingSafeEqual( item.record.contact.pniBinary, contactB.device.pniRawUuid diff --git a/ts/test-mock/pnp/pni_signature_test.node.ts b/ts/test-mock/pnp/pni_signature_test.node.ts index 3dd522ac7d..5688071b5e 100644 --- a/ts/test-mock/pnp/pni_signature_test.node.ts +++ b/ts/test-mock/pnp/pni_signature_test.node.ts @@ -2,18 +2,19 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import Long from 'long'; import { Pni } from '@signalapp/libsignal-client'; import { ServiceIdKind, Proto, ReceiptType, StorageState, + EMPTY_DATA_MESSAGE, } from '@signalapp/mock-server'; import createDebug from 'debug'; import * as durations from '../../util/durations/index.std.js'; import { uuidToBytes } from '../../util/uuidToBytes.std.js'; +import { toNumber } from '../../util/toNumber.std.js'; import { MY_STORY_ID } from '../../types/Stories.std.js'; import { Bootstrap } from '../bootstrap.node.js'; import type { App } from '../bootstrap.node.js'; @@ -61,6 +62,8 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { identifier: uuidToBytes(MY_STORY_ID), isBlockList: true, name: MY_STORY_ID, + deletedAtTimestamp: null, + recipientServiceIdsBinary: null, }, }, }); @@ -95,7 +98,7 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { await stranger.addSingleUseKey(desktop, ourKey, ServiceIdKind.PNI); const checkPniSignature = ( - message: Proto.IPniSignatureMessage | null | undefined, + message: Proto.PniSignatureMessage.Params | null | undefined, source: string ) => { if (!message) { @@ -167,7 +170,10 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { debug('Send unencrypted receipt', receiptTimestamp); await stranger.sendUnencryptedReceipt(desktop, { - messageTimestamp: dataMessage.timestamp?.toNumber() ?? 0, + messageTimestamp: + (dataMessage.timestamp == null + ? null + : toNumber(dataMessage.timestamp)) ?? 0, timestamp: receiptTimestamp, }); } @@ -196,7 +202,11 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { await stranger.sendReceipt(desktop, { type: ReceiptType.Delivery, - messageTimestamps: [dataMessage.timestamp?.toNumber() ?? 0], + messageTimestamps: [ + (dataMessage.timestamp == null + ? null + : toNumber(dataMessage.timestamp)) ?? 0, + ], timestamp: receiptTimestamp, }); // Wait for receipts to be batched and processed (+ buffer) @@ -261,25 +271,40 @@ describe('pnp/PNI Signature', function (this: Mocha.Suite) { const destinationPniIdentityKey = await stranger.device.getIdentityKey( ServiceIdKind.PNI ); - const originalDataMessage = { + const originalDataMessage: Proto.DataMessage.Params = { + ...EMPTY_DATA_MESSAGE, body: 'Hello PNI', - timestamp: Long.fromNumber(timestamp), + timestamp: BigInt(timestamp), }; - const content = { + const content: Proto.Content.Params = { syncMessage: { sent: { destinationServiceIdBinary, destinationE164, - timestamp: Long.fromNumber(timestamp), + timestamp: BigInt(timestamp), message: originalDataMessage, unidentifiedStatus: [ { destinationServiceIdBinary, destinationPniIdentityKey: destinationPniIdentityKey.serialize(), + unidentified: null, + destinationServiceId: null, }, ], + expirationStartTimestamp: null, + isRecipientUpdate: null, + storyMessage: null, + storyMessageRecipients: null, + editMessage: null, + destinationServiceId: null, }, + read: null, + stickerPackOperation: null, + viewed: null, + padding: null, }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }; const sendOptions = { timestamp, diff --git a/ts/test-mock/pnp/pni_unlink_test.node.ts b/ts/test-mock/pnp/pni_unlink_test.node.ts index 024b2af2d6..a6056065c0 100644 --- a/ts/test-mock/pnp/pni_unlink_test.node.ts +++ b/ts/test-mock/pnp/pni_unlink_test.node.ts @@ -88,8 +88,15 @@ describe('pnp/PNI DecryptionError unlink', function (this: Mocha.Suite) { desktop, { syncMessage: { + content: 'pniChangeNumber', pniChangeNumber, + read: null, + stickerPackOperation: null, + viewed: null, + padding: null, }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }, { timestamp: bootstrap.getTimestamp(), @@ -102,8 +109,15 @@ describe('pnp/PNI DecryptionError unlink', function (this: Mocha.Suite) { desktop, { syncMessage: { + content: 'pniChangeNumber', pniChangeNumber, + read: null, + stickerPackOperation: null, + viewed: null, + padding: null, }, + pniSignatureMessage: null, + senderKeyDistributionMessage: null, }, { timestamp: bootstrap.getTimestamp(), diff --git a/ts/test-mock/pnp/send_gv2_invite_test.node.ts b/ts/test-mock/pnp/send_gv2_invite_test.node.ts index f31f8e2f5f..001bc45bf7 100644 --- a/ts/test-mock/pnp/send_gv2_invite_test.node.ts +++ b/ts/test-mock/pnp/send_gv2_invite_test.node.ts @@ -73,6 +73,8 @@ describe('pnp/send gv2 invite', function (this: Mocha.Suite) { identifier: uuidToBytes(MY_STORY_ID), isBlockList: true, name: MY_STORY_ID, + deletedAtTimestamp: null, + recipientServiceIdsBinary: null, }, }, }); diff --git a/ts/test-mock/pnp/username_test.node.ts b/ts/test-mock/pnp/username_test.node.ts index d45113dd02..8c0e088ebb 100644 --- a/ts/test-mock/pnp/username_test.node.ts +++ b/ts/test-mock/pnp/username_test.node.ts @@ -67,6 +67,8 @@ describe('pnp/username', function (this: Mocha.Suite) { identifier: uuidToBytes(MY_STORY_ID), isBlockList: true, name: MY_STORY_ID, + deletedAtTimestamp: null, + recipientServiceIdsBinary: null, }, }, }); @@ -145,17 +147,19 @@ describe('pnp/username', function (this: Mocha.Suite) { 'only one record must be removed' ); + assert.strictEqual(added[0].record, 'contact'); assert.deepEqual( - added[0].contact?.aciBinary, + added[0].contact.aciBinary, usernameContact.device.aciRawUuid ); - assert.strictEqual(added[0].contact?.username, ''); + assert.strictEqual(added[0].contact.username, ''); + assert.strictEqual(removed[0].record, 'contact'); assert.deepEqual( - removed[0].contact?.aciBinary, + removed[0].contact.aciBinary, usernameContact.device.aciRawUuid ); - assert.strictEqual(removed[0].contact?.username, USERNAME); + assert.strictEqual(removed[0].contact.username, USERNAME); } if (type === 'system') { @@ -231,8 +235,9 @@ describe('pnp/username', function (this: Mocha.Suite) { assert.strictEqual(added.length, 1, 'only one record must be added'); assert.strictEqual(removed.length, 1, 'only one record must be removed'); - assert.strictEqual(added[0]?.account?.username, username); - const usernameLink = added[0]?.account?.usernameLink; + assert.strictEqual(added[0]?.record, 'account'); + assert.strictEqual(added[0].account.username, username); + const { usernameLink } = added[0].account; if (!usernameLink) { throw new Error('No username link in AccountRecord'); } @@ -282,14 +287,19 @@ describe('pnp/username', function (this: Mocha.Suite) { assert.strictEqual(added.length, 1, 'only one record must be added'); assert.strictEqual(removed.length, 1, 'only one record must be removed'); - assert.strictEqual(added[0]?.account?.username, '', 'clears username'); assert.strictEqual( - added[0]?.account?.usernameLink?.entropy?.length ?? 0, + added[0]?.record, + 'account', + 'expected updated account' + ); + assert.strictEqual(added[0].account.username, '', 'clears username'); + assert.strictEqual( + added[0].account.usernameLink?.entropy?.length ?? 0, 0, 'clears usernameLink.entropy' ); assert.strictEqual( - added[0]?.account?.usernameLink?.serverId?.length ?? 0, + added[0].account.usernameLink?.serverId?.length ?? 0, 0, 'clears usernameLink.serverId' ); diff --git a/ts/test-mock/rate-limit/story_test.node.ts b/ts/test-mock/rate-limit/story_test.node.ts index 7cf14a50f8..a1bafdd39f 100644 --- a/ts/test-mock/rate-limit/story_test.node.ts +++ b/ts/test-mock/rate-limit/story_test.node.ts @@ -46,6 +46,8 @@ describe('story/no-sender-key', function (this: Mocha.Suite) { identifier: uuidToBytes(MY_STORY_ID), isBlockList: true, name: MY_STORY_ID, + deletedAtTimestamp: null, + recipientServiceIdsBinary: null, }, }, }); @@ -123,7 +125,8 @@ describe('story/no-sender-key', function (this: Mocha.Suite) { storyMessage.profileKey ?? new Uint8Array(0) ) ); - assert.strictEqual(storyMessage.textAttachment?.text, '123'); + assert.strictEqual(storyMessage.attachment, 'textAttachment'); + assert.strictEqual(storyMessage.textAttachment.text, '123'); }) ); }); diff --git a/ts/test-mock/release-notes/megaphone_test.node.ts b/ts/test-mock/release-notes/megaphone_test.node.ts index 71c3443ff2..0269305821 100644 --- a/ts/test-mock/release-notes/megaphone_test.node.ts +++ b/ts/test-mock/release-notes/megaphone_test.node.ts @@ -6,7 +6,6 @@ import createDebug from 'debug'; import { expect } from 'playwright/test'; import { StorageState } from '@signalapp/mock-server'; import { BackupLevel } from '@signalapp/libsignal-client/zkgroup.js'; -import Long from 'long'; import type { App } from '../playwright.node.js'; import { Bootstrap } from '../bootstrap.node.js'; @@ -34,7 +33,7 @@ describe('megaphone', function (this: Mocha.Suite) { givenName: phone.profileName, readReceipts: true, hasCompletedUsernameOnboarding: true, - backupTier: Long.fromNumber(BackupLevel.Free), + backupTier: BigInt(BackupLevel.Free), }); await phone.setStorageState(state); diff --git a/ts/test-mock/storage/archive_test.node.ts b/ts/test-mock/storage/archive_test.node.ts index 2e55fd306d..d8b450d659 100644 --- a/ts/test-mock/storage/archive_test.node.ts +++ b/ts/test-mock/storage/archive_test.node.ts @@ -119,6 +119,6 @@ describe('storage service', function (this: Mocha.Suite) { debug('Verifying the final manifest version'); const finalState = await phone.expectStorageState('consistency check'); - assert.strictEqual(finalState.version, 4); + assert.strictEqual(finalState.version, 4n); }); }); diff --git a/ts/test-mock/storage/call_links_test.node.ts b/ts/test-mock/storage/call_links_test.node.ts index 7c8883ef46..81bb1219ef 100644 --- a/ts/test-mock/storage/call_links_test.node.ts +++ b/ts/test-mock/storage/call_links_test.node.ts @@ -2,7 +2,6 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import Long from 'long'; import { Proto, StorageState } from '@signalapp/mock-server'; import * as durations from '../../util/durations/index.std.js'; import type { App } from './fixtures.node.js'; @@ -45,6 +44,8 @@ describe('storage service', function (this: Mocha.Suite) { identifier: uuidToBytes(MY_STORY_ID), isBlockList: true, name: MY_STORY_ID, + deletedAtTimestamp: null, + recipientServiceIdsBinary: null, }, }, }); @@ -83,9 +84,7 @@ describe('storage service', function (this: Mocha.Suite) { const record = state.findRecord(getCallLinkRecordPredicate(roomId)); assert.ok(record, 'Saves call link record with matching roomId'); - const deletedAt = Long.fromValue( - record.record.callLink?.deletedAtTimestampMs ?? 0 - ).toNumber(); + const deletedAt = record.record.callLink.deletedAtTimestampMs ?? 0; assert.notOk(deletedAt, 'deletedAt falsey'); debug('Creating link then deleting it'); @@ -102,9 +101,8 @@ describe('storage service', function (this: Mocha.Suite) { ); assert.ok(recordToDelete, 'Saves call link record with matching roomId'); - const deletedAtBeforeDelete = Long.fromValue( - recordToDelete.record.callLink?.deletedAtTimestampMs ?? 0 - ).toNumber(); + const deletedAtBeforeDelete = + recordToDelete.record.callLink.deletedAtTimestampMs ?? 0; assert.notOk(deletedAtBeforeDelete, 'deletedAt falsey'); debug('Deleting call link'); @@ -134,9 +132,8 @@ describe('storage service', function (this: Mocha.Suite) { getCallLinkRecordPredicate(roomIdDelete) ); assert.ok(recordAfterDelete, 'Call link record still present'); - const deletedAtAfterDelete = Long.fromValue( - recordAfterDelete.record.callLink?.deletedAtTimestampMs ?? 0 - ).toNumber(); + const deletedAtAfterDelete = + recordAfterDelete.record.callLink.deletedAtTimestampMs ?? 0; assert.ok(deletedAtAfterDelete, 'deletedAt present'); }); }); diff --git a/ts/test-mock/storage/chat_folder_test.node.ts b/ts/test-mock/storage/chat_folder_test.node.ts index b5f0f716ae..e6696844c0 100644 --- a/ts/test-mock/storage/chat_folder_test.node.ts +++ b/ts/test-mock/storage/chat_folder_test.node.ts @@ -1,6 +1,5 @@ // Copyright 2025 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import Long from 'long'; import { v4 as generateUuid } from 'uuid'; import { Proto, StorageState } from '@signalapp/mock-server'; import type { Page } from 'playwright/test'; @@ -16,6 +15,7 @@ import { import { bytesToUuid, uuidToBytes } from '../../util/uuidToBytes.std.js'; import { CHAT_FOLDER_DELETED_POSITION } from '../../types/ChatFolder.std.js'; import { strictAssert } from '../../util/assert.std.js'; +import { toNumber } from '../../util/toNumber.std.js'; const IdentifierType = Proto.ManifestRecord.Identifier.Type; @@ -68,7 +68,7 @@ function createAllChatsRecord(id: string): StorageStateNewRecord { includeAllGroupChats: true, includedRecipients: [], excludedRecipients: [], - deletedAtTimestampMs: Long.fromNumber(0), + deletedAtTimestampMs: 0n, }, }, }; @@ -154,7 +154,7 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { includeAllGroupChats: true, includedRecipients: [], excludedRecipients: [], - deletedAtTimestampMs: Long.fromNumber(0), + deletedAtTimestampMs: 0n, }, }, }); @@ -175,7 +175,7 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { getChatFolderRecordPredicate('CUSTOM', ALL_GROUPS_NAME, false), item => { return { - ...item, + record: 'chatFolder', chatFolder: { ...item.chatFolder, name: ALL_GROUPS_NAME_UPDATED, @@ -201,11 +201,11 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { getChatFolderRecordPredicate('CUSTOM', ALL_GROUPS_NAME_UPDATED, false), item => { return { - ...item, + record: 'chatFolder', chatFolder: { ...item.chatFolder, position: CHAT_FOLDER_DELETED_POSITION, - deletedAtTimestampMs: Long.fromNumber(Date.now()), + deletedAtTimestampMs: BigInt(Date.now()), }, }; } @@ -304,7 +304,9 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { await expect(groupPresetBtn).toBeVisible(); expect( - found?.record.chatFolder?.deletedAtTimestampMs?.toNumber() + found?.record.chatFolder.deletedAtTimestampMs == null + ? null + : toNumber(found?.record.chatFolder.deletedAtTimestampMs) ).toBeGreaterThan(0); } }); @@ -314,12 +316,12 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { const window = await app.getWindow(); let state = await phone.expectStorageState('initial state'); - expect(state.version).toBe(1); + expect(state.version).toBe(1n); expect(state.hasRecord(ALL_CHATS_PREDICATE)).toBe(false); // wait for initial creation of story distribution list and "all chats" chat folder state = await phone.waitForStorageState({ after: state }); - expect(state.version).toBe(2); + expect(state.version).toBe(2n); expect(state.hasRecord(ALL_CHATS_PREDICATE)).toBe(true); await openChatFolderSettings(window); @@ -329,7 +331,7 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { // update record state = state.removeRecord(ALL_CHATS_PREDICATE); state = await phone.setStorageState(state); - expect(state.version).toBe(3); + expect(state.version).toBe(3n); expect(state.hasRecord(ALL_CHATS_PREDICATE)).toBe(false); // sync from phone to app @@ -342,7 +344,7 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { await groupPresetAddButton.click(); state = await phone.waitForStorageState({ after: state }); - expect(state.version).toBe(4); + expect(state.version).toBe(4n); expect(state.hasRecord(ALL_CHATS_PREDICATE)).toBe(true); }); @@ -384,7 +386,7 @@ describe('storage service/chat folders', function (this: Mocha.Suite) { // Make sure we took the updated id const item = state.findRecord(ALL_CHATS_PREDICATE); - const idBytes = item?.record.chatFolder?.id; + const idBytes = item?.record.chatFolder.id; strictAssert(idBytes != null, 'Missing all chats record with id'); const id = bytesToUuid(idBytes); strictAssert(id != null, 'All chats record id was not valid uuid'); diff --git a/ts/test-mock/storage/conflict_test.node.ts b/ts/test-mock/storage/conflict_test.node.ts index 0d2bb8df6c..c4abdfd48b 100644 --- a/ts/test-mock/storage/conflict_test.node.ts +++ b/ts/test-mock/storage/conflict_test.node.ts @@ -2,9 +2,12 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import Long from 'long'; import { expect } from 'playwright/test'; -import type { Group, StorageState } from '@signalapp/mock-server'; +import type { + Group, + StorageState, + StorageStateRecord, +} from '@signalapp/mock-server'; import { Proto } from '@signalapp/mock-server'; import * as durations from '../../util/durations/index.std.js'; @@ -80,7 +83,7 @@ describe('storage service', function (this: Mocha.Suite) { } debug('updating contact on phone without sync message'); - let archivedVersion: number; + let archivedVersion: bigint; { const state = await phone.expectStorageState('consistency check'); @@ -163,7 +166,7 @@ describe('storage service', function (this: Mocha.Suite) { } debug('updating pins on phone without sync message'); - let archivedVersion: number; + let archivedVersion: bigint; { const state = await phone.expectStorageState('consistency check'); @@ -218,20 +221,27 @@ describe('storage service', function (this: Mocha.Suite) { after: state, }); - const updatedList = newState.findRecord(({ type }) => { - return type === IdentifierType.STORY_DISTRIBUTION_LIST; - }); - assert.isFalse(updatedList?.record?.storyDistributionList?.allowsReplies); + const updatedList = newState.findRecord( + ( + record + ): record is StorageStateRecord<{ + record: 'storyDistributionList'; + storyDistributionList: Proto.StoryDistributionListRecord.Params; + }> => { + return record.type === IdentifierType.STORY_DISTRIBUTION_LIST; + } + ); + assert.isFalse(updatedList?.record.storyDistributionList.allowsReplies); } debug('updating distribution list on phone without sync'); - let archivedVersion: number; + let archivedVersion: bigint; { const state = await phone.expectStorageState('consistency check'); let newState = state.updateRecord( - ({ type }) => { - return type === IdentifierType.STORY_DISTRIBUTION_LIST; + (record): record is StorageStateRecord => { + return record.type === IdentifierType.STORY_DISTRIBUTION_LIST; }, // Just changing storage ID record => record @@ -281,10 +291,10 @@ describe('storage service', function (this: Mocha.Suite) { debug('Updating storage without sync'); const deletedAt = bootstrap.getTimestamp(); state = state.updateRecord(getCallLinkRecordPredicate(roomId), record => ({ - ...record, + record: 'callLink', callLink: { - ...(record.callLink ?? {}), - deletedAtTimestampMs: Long.fromNumber(deletedAt), + ...record.callLink, + deletedAtTimestampMs: BigInt(deletedAt), }, })); @@ -313,10 +323,9 @@ describe('storage service', function (this: Mocha.Suite) { state = await phone.waitForStorageState({ after: state }); assert.strictEqual( - state - .findRecord(getCallLinkRecordPredicate(roomId)) - ?.record.callLink?.deletedAtTimestampMs?.toNumber(), - deletedAt + state.findRecord(getCallLinkRecordPredicate(roomId))?.record.callLink + .deletedAtTimestampMs, + BigInt(deletedAt) ); assert.exists(state.findRecord(getCallLinkRecordPredicate(otherRoomId))); }); diff --git a/ts/test-mock/storage/drop_test.node.ts b/ts/test-mock/storage/drop_test.node.ts index a0603f7969..045dc2c07e 100644 --- a/ts/test-mock/storage/drop_test.node.ts +++ b/ts/test-mock/storage/drop_test.node.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { assert } from 'chai'; -import { Proto } from '@signalapp/mock-server'; +import { Proto, type StorageStateRecord } from '@signalapp/mock-server'; import * as durations from '../../util/durations/index.std.js'; import type { App, Bootstrap } from './fixtures.node.js'; @@ -58,6 +58,15 @@ describe('storage service', function (this: Mocha.Suite) { record: { groupV2: { masterKey, + blocked: null, + whitelisted: null, + archived: null, + markedUnread: null, + mutedUntilTimestamp: null, + dontNotifyForMentionsIfMuted: null, + hideStory: null, + storySendMode: null, + avatarColor: null, }, }, }) @@ -82,11 +91,11 @@ describe('storage service', function (this: Mocha.Suite) { assert.isTrue( nextState.hasRecord(({ type, record }) => { - if (type !== IdentifierType.GROUPV2) { + if (type !== IdentifierType.GROUPV2 || record.record !== 'groupV2') { return false; } - if (!record.groupV2?.masterKey) { + if (!record.groupV2.masterKey?.length) { return false; } return Buffer.from(masterKey).equals(record.groupV2.masterKey); @@ -102,9 +111,16 @@ describe('storage service', function (this: Mocha.Suite) { debug('duplicating account record'); const state = await phone.expectStorageState('consistency check'); - const oldAccount = state.findRecord(({ type }) => { - return type === IdentifierType.ACCOUNT; - }); + const oldAccount = state.findRecord( + ( + record + ): record is StorageStateRecord<{ + record: 'account'; + account: Proto.AccountRecord.Params; + }> => { + return record.type === IdentifierType.ACCOUNT; + } + ); if (oldAccount === undefined) { throw new Error('should have initial account record'); } diff --git a/ts/test-mock/storage/fixtures.node.ts b/ts/test-mock/storage/fixtures.node.ts index 8397cc5fcc..22ededd12a 100644 --- a/ts/test-mock/storage/fixtures.node.ts +++ b/ts/test-mock/storage/fixtures.node.ts @@ -104,6 +104,8 @@ export async function initStorage( identifier: uuidToBytes(MY_STORY_ID), isBlockList: true, name: MY_STORY_ID, + deletedAtTimestamp: null, + recipientServiceIdsBinary: null, }, }, }); @@ -120,6 +122,9 @@ export async function initStorage( includeAllIndividualChats: true, includeAllGroupChats: true, folderType: Proto.ChatFolderRecord.FolderType.ALL, + includedRecipients: null, + excludedRecipients: null, + deletedAtTimestampMs: null, }, }, }); @@ -190,14 +195,23 @@ export function getStickerPackLink(pack: StickerPackType): string { .toString(); } +type StickerRecord = StorageStateRecord<{ + record: 'stickerPack'; + stickerPack: Proto.StickerPackRecord.Params; +}>; + export function getStickerPackRecordPredicate( pack: StickerPackType -): (record: StorageStateRecord) => boolean { - return ({ type, record }: StorageStateRecord): boolean => { +): (record: StorageStateRecord) => record is StickerRecord { + return (stateRecord: StorageStateRecord): stateRecord is StickerRecord => { + const { type, record } = stateRecord; + if (record.record !== 'stickerPack') { + return false; + } if (type !== IdentifierType.STICKER_PACK) { return false; } - return pack.id.equals(record.stickerPack?.packId ?? EMPTY); + return pack.id.equals(record.stickerPack.packId ?? EMPTY); }; } @@ -226,11 +240,21 @@ export async function storeStickerPacks( ); } +type CallLinkRecord = StorageStateRecord<{ + record: 'callLink'; + callLink: Proto.CallLinkRecord.Params; +}>; + export function getCallLinkRecordPredicate( roomId: string -): (record: StorageStateRecord) => boolean { - return ({ type, record }: StorageStateRecord): boolean => { - const rootKeyBytes = record.callLink?.rootKey; +): (record: StorageStateRecord) => record is CallLinkRecord { + return (stateRecord: StorageStateRecord): stateRecord is CallLinkRecord => { + const { type, record } = stateRecord; + if (record.record !== 'callLink') { + return false; + } + + const rootKeyBytes = record.callLink.rootKey; if (type !== IdentifierType.CALL_LINK || rootKeyBytes == null) { return false; } @@ -240,20 +264,29 @@ export function getCallLinkRecordPredicate( }; } +type ChatFolderRecord = StorageStateRecord<{ + record: 'chatFolder'; + chatFolder: Proto.ChatFolderRecord.Params; +}>; + export function getChatFolderRecordPredicate( folderType: keyof typeof Proto.ChatFolderRecord.FolderType, name: string, deleted: boolean -): (record: StorageStateRecord) => boolean { - return ({ type, record }) => { +): (record: StorageStateRecord) => record is ChatFolderRecord { + return (stateRecord): stateRecord is ChatFolderRecord => { + const { type, record } = stateRecord; + if (record.record !== 'chatFolder') { + return false; + } + const { chatFolder } = record; if (type !== IdentifierType.CHAT_FOLDER || chatFolder == null) { return false; } - const deletedAtTimestampMs = - chatFolder.deletedAtTimestampMs?.toNumber() ?? 0; - const isDeleted = deletedAtTimestampMs > 0; + const deletedAtTimestampMs = chatFolder.deletedAtTimestampMs ?? 0n; + const isDeleted = deletedAtTimestampMs > 0n; return ( chatFolder.folderType === Proto.ChatFolderRecord.FolderType[folderType] && diff --git a/ts/test-mock/storage/max_read_keys_test.node.ts b/ts/test-mock/storage/max_read_keys_test.node.ts index d586ae7f97..6f7aeb41d6 100644 --- a/ts/test-mock/storage/max_read_keys_test.node.ts +++ b/ts/test-mock/storage/max_read_keys_test.node.ts @@ -59,6 +59,29 @@ describe('storage service', function (this: Mocha.Suite) { record: { contact: { aciBinary: toAciObject(generateAci()).getRawUuidBytes(), + e164: null, + profileKey: null, + identityKey: null, + identityState: null, + givenName: null, + familyName: null, + username: null, + blocked: null, + whitelisted: null, + archived: null, + markedUnread: null, + mutedUntilTimestamp: null, + hideStory: null, + unregisteredAtTimestamp: null, + systemGivenName: null, + systemFamilyName: null, + systemNickname: null, + hidden: null, + pniSignatureVerified: null, + nickname: null, + note: null, + avatarColor: null, + pniBinary: null, }, }, }); @@ -83,6 +106,6 @@ describe('storage service', function (this: Mocha.Suite) { debug('Verifying the final manifest version'); const finalState = await phone.expectStorageState('consistency check'); - assert.strictEqual(finalState.version, 2); + assert.strictEqual(finalState.version, 2n); }); }); diff --git a/ts/test-mock/storage/message_request_test.node.ts b/ts/test-mock/storage/message_request_test.node.ts index 00ce3db791..1c84da5512 100644 --- a/ts/test-mock/storage/message_request_test.node.ts +++ b/ts/test-mock/storage/message_request_test.node.ts @@ -64,7 +64,7 @@ describe('storage service', function (this: Mocha.Suite) { after: initialState, }); { - assert.strictEqual(postMessageState.version, 2); + assert.strictEqual(postMessageState.version, 2n); assert.isFalse(postMessageState.getContact(stranger)?.whitelisted); assert.strictEqual( postMessageState.getContact(stranger)?.profileKey?.length, @@ -85,7 +85,7 @@ describe('storage service', function (this: Mocha.Suite) { const nextState = await phone.waitForStorageState({ after: postMessageState, }); - assert.strictEqual(nextState.version, 3); + assert.strictEqual(nextState.version, 3n); assert.isTrue(nextState.getContact(stranger)?.whitelisted); // ContactRecord @@ -128,6 +128,6 @@ describe('storage service', function (this: Mocha.Suite) { debug('Verifying the final manifest version'); const finalState = await phone.expectStorageState('consistency check'); - assert.strictEqual(finalState.version, 3); + assert.strictEqual(finalState.version, 3n); }); }); diff --git a/ts/test-mock/storage/notification_profiles_test.node.ts b/ts/test-mock/storage/notification_profiles_test.node.ts index 387f7697d1..43959904d1 100644 --- a/ts/test-mock/storage/notification_profiles_test.node.ts +++ b/ts/test-mock/storage/notification_profiles_test.node.ts @@ -4,7 +4,6 @@ import assert from 'node:assert'; import { Proto, StorageState } from '@signalapp/mock-server'; import { expect } from 'playwright/test'; -import Long from 'long'; import * as Bytes from '../../Bytes.std.js'; import * as durations from '../../util/durations/index.std.js'; @@ -107,12 +106,17 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { let profileId: Uint8Array | undefined; const profilewasAdded = thirdState.hasRecord(record => { + if (record.record.record !== 'notificationProfile') { + return false; + } + + assert.ok(record.type === IdentifierType.NOTIFICATION_PROFILE); + const isMatch = - record.type === IdentifierType.NOTIFICATION_PROFILE && - record.record?.notificationProfile?.name === profileName && - record.record?.notificationProfile?.scheduleEnabled === true; + record.record.notificationProfile.name === profileName && + record.record.notificationProfile.scheduleEnabled === true; if (isMatch) { - profileId = dropNull(record.record?.notificationProfile?.id); + profileId = dropNull(record.record.notificationProfile.id); } return isMatch; @@ -148,10 +152,14 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { }); const profileScheduleIsOff = fourthState.hasRecord(record => { + if (record.record.record !== 'notificationProfile') { + return false; + } + assert.ok(record.type === IdentifierType.NOTIFICATION_PROFILE); + return ( - record.type === IdentifierType.NOTIFICATION_PROFILE && - record.record?.notificationProfile?.name === profileName && - record.record?.notificationProfile?.scheduleEnabled === false + record.record.notificationProfile.name === profileName && + record.record.notificationProfile.scheduleEnabled === false ); }); if (!profileScheduleIsOff) { @@ -176,8 +184,18 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { }); const acountRecordHasOverride = fifthState.hasRecord(record => { - const id = - record.record?.account?.notificationProfileManualOverride?.enabled?.id; + if (record.record.record !== 'account') { + return false; + } + const { notificationProfileManualOverride } = record.record.account; + if (notificationProfileManualOverride?.override == null) { + return false; + } + if (notificationProfileManualOverride.override !== 'enabled') { + return false; + } + + const { id } = notificationProfileManualOverride.enabled; return Boolean( record.type === IdentifierType.ACCOUNT && @@ -244,6 +262,9 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { DayOfWeek.THURSDAY, DayOfWeek.FRIDAY, ], + emoji: null, + allowedMembers: null, + deletedAtTimestampMs: null, }; { @@ -254,7 +275,7 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { id: notificationProfileId1, name: notificationProfileName1, color: 0xffff0000, - createdAtMs: Long.fromNumber(now + 1), + createdAtMs: BigInt(now + 1), ...DEFAULT_PROFILE, }, }, @@ -267,7 +288,7 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { id: notificationProfileId2, name: notificationProfileName2, color: 0xff00ff00, - createdAtMs: Long.fromNumber(now + 2), + createdAtMs: BigInt(now + 2), ...DEFAULT_PROFILE, allowAllCalls: false, }, @@ -278,6 +299,7 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { notificationProfileManualOverride: { enabled: { id: notificationProfileId1, + endAtTimestampMs: null, }, }, }); @@ -289,7 +311,7 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { await phone.sendFetchStorage({ timestamp: bootstrap.getTimestamp(), }); - await app.waitForManifestVersion(firstState.version + 1); + await app.waitForManifestVersion(firstState.version + 1n); debug('Now we should be on the Notification Profiles list page'); await expect( @@ -322,19 +344,25 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { throw new Error('Notification profile sync is disabled!'); } - assert.deepEqual(accountRecord?.notificationProfileManualOverride, { - enabled: { - id: notificationProfileId1, - }, - }); + assert.strictEqual( + accountRecord?.notificationProfileManualOverride?.override, + 'enabled' + ); + assert.deepEqual( + accountRecord.notificationProfileManualOverride.enabled.id, + notificationProfileId1 + ); let countOfProfiles = 0; secondState.hasRecord(record => { + if (record.record.record !== 'notificationProfile') { + return false; + } const deletedTimestamp = - record.record.notificationProfile?.deletedAtTimestampMs; + record.record.notificationProfile.deletedAtTimestampMs; if ( record.type === IdentifierType.NOTIFICATION_PROFILE && - (!deletedTimestamp || deletedTimestamp.isZero()) + (!deletedTimestamp || deletedTimestamp === 0n) ) { countOfProfiles += 1; } @@ -399,7 +427,7 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { id: notificationProfileId1, name: notificationProfileName1, color: 0xffff0000, - createdAtMs: Long.fromNumber(now + 1), + createdAtMs: BigInt(now + 1), ...DEFAULT_PROFILE, }, }, @@ -412,7 +440,7 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { id: notificationProfileId2, name: notificationProfileName2, color: 0xff00ff00, - createdAtMs: Long.fromNumber(now + 2), + createdAtMs: BigInt(now + 2), ...DEFAULT_PROFILE, allowAllCalls: false, }, @@ -426,7 +454,7 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { id: notificationProfileId3, name: notificationProfileName3, color: 0xff0000ff, - createdAtMs: Long.fromNumber(now + 3), + createdAtMs: BigInt(now + 3), ...DEFAULT_PROFILE, }, }, @@ -439,7 +467,7 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { id: notificationProfileId4, name: notificationProfileName4, color: 0xff0000ff, - createdAtMs: Long.fromNumber(now + 4), + createdAtMs: BigInt(now + 4), ...DEFAULT_PROFILE, allowAllCalls: false, scheduleStartTime: 1000, @@ -453,6 +481,7 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { notificationProfileManualOverride: { enabled: { id: notificationProfileId1, + endAtTimestampMs: null, }, }, notificationProfileSyncDisabled: false, @@ -470,7 +499,7 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { await phone.sendFetchStorage({ timestamp: bootstrap.getTimestamp(), }); - await app.waitForManifestVersion(secondState.version + 1); + await app.waitForManifestVersion(secondState.version + 1n); debug('Check what is on the list page now'); await expect( @@ -495,11 +524,15 @@ describe('storage service/notification profiles', function (this: Mocha.Suite) { let countOfProfiles = 0; thirdState.hasRecord(record => { + if (record.record.record !== 'notificationProfile') { + return false; + } + const deletedTimestamp = - record.record.notificationProfile?.deletedAtTimestampMs; + record.record.notificationProfile.deletedAtTimestampMs; if ( record.type === IdentifierType.NOTIFICATION_PROFILE && - (!deletedTimestamp || deletedTimestamp.isZero()) + (!deletedTimestamp || deletedTimestamp === 0n) ) { countOfProfiles += 1; } diff --git a/ts/test-mock/storage/pin_unpin_test.node.ts b/ts/test-mock/storage/pin_unpin_test.node.ts index 53bb97b24b..b448ec0f43 100644 --- a/ts/test-mock/storage/pin_unpin_test.node.ts +++ b/ts/test-mock/storage/pin_unpin_test.node.ts @@ -151,7 +151,7 @@ describe('storage service', function (this: Mocha.Suite) { debug('Verifying the final manifest version'); const finalState = await phone.expectStorageState('consistency check'); - assert.strictEqual(finalState.version, 5); + assert.strictEqual(finalState.version, 5n); }); } }); diff --git a/ts/test-mock/storage/sticker_test.node.ts b/ts/test-mock/storage/sticker_test.node.ts index 210736a0a1..2fe8691fb8 100644 --- a/ts/test-mock/storage/sticker_test.node.ts +++ b/ts/test-mock/storage/sticker_test.node.ts @@ -19,6 +19,7 @@ import { sendTextMessage, } from '../helpers.node.js'; import { strictAssert } from '../../util/assert.std.js'; +import { toNumber } from '../../util/toNumber.std.js'; const { StickerPackOperation } = Proto.SyncMessage; @@ -83,7 +84,7 @@ describe('stickers', function (this: Mocha.Suite) { debug('waiting for sync message'); const { syncMessage } = await phone.waitForSyncMessage(entry => - Boolean(entry.syncMessage.stickerPackOperation?.length) + Boolean(entry.syncMessage.stickerPackOperation.length) ); const [syncOp] = syncMessage.stickerPackOperation ?? []; assert.isTrue(STICKER_PACKS[0].id.equals(syncOp?.packId ?? EMPTY)); @@ -101,12 +102,12 @@ describe('stickers', function (this: Mocha.Suite) { ); assert.isTrue( STICKER_PACKS[0].key.equals( - stickerPack?.record.stickerPack?.packKey ?? EMPTY + stickerPack.record.stickerPack.packKey ?? EMPTY ), 'Wrong sticker pack key' ); assert.strictEqual( - stickerPack?.record.stickerPack?.position, + stickerPack.record.stickerPack.position, 11, 'Wrong sticker pack position' ); @@ -147,12 +148,13 @@ describe('stickers', function (this: Mocha.Suite) { 'New storage state should have sticker pack record' ); assert.deepStrictEqual( - stickerPack?.record.stickerPack?.packKey, + stickerPack.record.stickerPack.packKey, EMPTY, 'Sticker pack key should be removed' ); - const deletedAt = - stickerPack?.record.stickerPack?.deletedAtTimestamp?.toNumber() ?? 0; + const deletedAt = toNumber( + stickerPack.record.stickerPack.deletedAtTimestamp ?? 0n + ); assert.isAbove( deletedAt, Date.now() - durations.HOUR, @@ -194,12 +196,12 @@ describe('stickers', function (this: Mocha.Suite) { state.updateRecord( getStickerPackRecordPredicate(STICKER_PACKS[0]), record => ({ - ...record, + record: 'stickerPack', stickerPack: { - ...record?.stickerPack, + ...record.stickerPack, packKey: STICKER_PACKS[0].key, position: 7, - deletedAtTimestamp: undefined, + deletedAtTimestamp: null, }, }) ) @@ -235,18 +237,19 @@ describe('stickers', function (this: Mocha.Suite) { const stickerPack = stateAfter.findRecord( getStickerPackRecordPredicate(STICKER_PACKS[1]) ); - assert.ok( - stickerPack, + assert.strictEqual( + stickerPack?.record.record, + 'stickerPack', 'New storage state should have sticker pack record' ); assert.isTrue( STICKER_PACKS[1].key.equals( - stickerPack?.record.stickerPack?.packKey ?? EMPTY + stickerPack.record.stickerPack.packKey ?? EMPTY ), 'Wrong sticker pack key' ); assert.strictEqual( - stickerPack?.record.stickerPack?.position, + stickerPack.record.stickerPack.position, 12, 'Wrong sticker pack position' ); @@ -255,7 +258,7 @@ describe('stickers', function (this: Mocha.Suite) { debug('Verifying the final manifest version'); const finalState = await phone.expectStorageState('consistency check'); - assert.strictEqual(finalState.version, 5); + assert.strictEqual(finalState.version, 5n); debug( 'verifying that stickers from packs can be received and paths are deduplicated' @@ -271,6 +274,8 @@ describe('stickers', function (this: Mocha.Suite) { packId: STICKER_PACKS[0].id, packKey: STICKER_PACKS[0].key, stickerId: 0, + data: null, + emoji: null, }, timestamp: firstTimestamp, }); @@ -284,6 +289,8 @@ describe('stickers', function (this: Mocha.Suite) { packId: STICKER_PACKS[0].id, packKey: STICKER_PACKS[0].key, stickerId: 0, + data: null, + emoji: null, }, timestamp: secondTimestamp, }); diff --git a/ts/util/toNumber.std.ts b/ts/util/toNumber.std.ts new file mode 100644 index 0000000000..d1072b159e --- /dev/null +++ b/ts/util/toNumber.std.ts @@ -0,0 +1,22 @@ +// Copyright 2026 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +export function toNumber(input: bigint): number; +export function toNumber( + input: number | bigint | null | undefined +): number | null; + +export function toNumber( + input: number | bigint | null | undefined +): number | null { + if (input == null) { + return null; + } + if (typeof input === 'bigint') { + return Number(input); + } + if (Number.isFinite(input)) { + return input; + } + return null; +}