From 4a55ac4c866d28635a4a9e1f0bd0d1040b3cf76f Mon Sep 17 00:00:00 2001 From: yash-signal Date: Wed, 29 Jan 2025 00:07:41 -0600 Subject: [PATCH] Release Notes Channel: Support image attachments (#9587) --- package-lock.json | 8 +- package.json | 2 +- ts/services/releaseNotesFetcher.ts | 146 +++++++++++++----- ts/test-mock/bootstrap.ts | 1 + ts/test-mock/helpers.ts | 12 +- .../release-notes/release_notes_test.ts | 21 ++- .../release-notes/release-notes-v2.json | 69 +++++++++ .../en.json | 9 ++ .../static/release-notes/call_links.png | Bin 0 -> 37641 bytes ts/textsecure/WebAPI.ts | 39 ++++- 10 files changed, 257 insertions(+), 50 deletions(-) create mode 100644 ts/test-mock/updates-data/dynamic/release-notes/release-notes-v2.json create mode 100644 ts/test-mock/updates-data/static/release-notes/1813845b-cc98-4210-a07c-164f9b328ca8/en.json create mode 100644 ts/test-mock/updates-data/static/release-notes/call_links.png diff --git a/package-lock.json b/package-lock.json index 81c3fb3c14..df2254cb60 100644 --- a/package-lock.json +++ b/package-lock.json @@ -123,7 +123,7 @@ "@indutny/parallel-prettier": "3.0.0", "@indutny/rezip-electron": "2.0.1", "@napi-rs/canvas": "0.1.61", - "@signalapp/mock-server": "10.4.1", + "@signalapp/mock-server": "10.5.0", "@storybook/addon-a11y": "8.4.4", "@storybook/addon-actions": "8.4.4", "@storybook/addon-controls": "8.4.4", @@ -6491,9 +6491,9 @@ } }, "node_modules/@signalapp/mock-server": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@signalapp/mock-server/-/mock-server-10.4.1.tgz", - "integrity": "sha512-9aBmFbOx3KfbN4Ptcx5PBRnVnROe6A58rRoErB/w1x+SSW9TjRHZxviJfgNpt9nGUOreryzOBsONSzWBIE12nQ==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@signalapp/mock-server/-/mock-server-10.5.0.tgz", + "integrity": "sha512-w7KXcWYRPXhAxYWzeBNesu+A9DO6FYJUhg52md3M9yQkPR2Yr/YBvc9zBhFo5fafZmkaRoxDoz9y29Ivd5ZykQ==", "dev": true, "license": "AGPL-3.0-only", "dependencies": { diff --git a/package.json b/package.json index 32c6e47b7d..ed6a3083a6 100644 --- a/package.json +++ b/package.json @@ -214,7 +214,7 @@ "@indutny/parallel-prettier": "3.0.0", "@indutny/rezip-electron": "2.0.1", "@napi-rs/canvas": "0.1.61", - "@signalapp/mock-server": "10.4.1", + "@signalapp/mock-server": "10.5.0", "@storybook/addon-a11y": "8.4.4", "@storybook/addon-actions": "8.4.4", "@storybook/addon-controls": "8.4.4", diff --git a/ts/services/releaseNotesFetcher.ts b/ts/services/releaseNotesFetcher.ts index 2e947b5428..cdff63af5a 100644 --- a/ts/services/releaseNotesFetcher.ts +++ b/ts/services/releaseNotesFetcher.ts @@ -27,6 +27,8 @@ import type { } from '../textsecure/WebAPI'; import type { WithRequiredProperties } from '../types/Util'; import { MessageModel } from '../models/messages'; +import { stringToMIMEType } from '../types/MIME'; +import { isNotNil } from '../util/isNotNil'; const FETCH_INTERVAL = 3 * durations.DAY; const ERROR_RETRY_DELAY = 3 * durations.HOUR; @@ -103,7 +105,8 @@ export class ReleaseNotesFetcher { note: ManifestReleaseNoteType ): Promise { if (!window.textsecure.server) { - return undefined; + log.info('ReleaseNotesFetcher: WebAPI unavailable'); + throw new Error('WebAPI unavailable'); } const { uuid, ctaId, link } = note; @@ -157,15 +160,75 @@ export class ReleaseNotesFetcher { async #processReleaseNotes( notes: ReadonlyArray ): Promise { + if (!window.textsecure.server) { + log.info('ReleaseNotesFetcher: WebAPI unavailable'); + throw new Error('WebAPI unavailable'); + } const sortedNotes = [...notes].sort( (a: ManifestReleaseNoteType, b: ManifestReleaseNoteType) => semver.compare(a.desktopMinVersion, b.desktopMinVersion) ); - const hydratedNotes = []; - for (const note of sortedNotes) { - // eslint-disable-next-line no-await-in-loop - hydratedNotes.push(await this.#getReleaseNote(note)); - } + + const hydratedNotesWithRawAttachments = ( + await Promise.all( + sortedNotes.map(async note => { + if (!window.textsecure.server) { + log.info('ReleaseNotesFetcher: WebAPI unavailable'); + throw new Error('WebAPI unavailable'); + } + if (!note) { + return null; + } + + const hydratedNote = await this.#getReleaseNote(note); + if (!hydratedNote) { + return null; + } + if (hydratedNote.media) { + const { imageData: rawAttachmentData, contentType } = + await window.textsecure.server.getReleaseNoteImageAttachment( + hydratedNote.media + ); + + return { + hydratedNote, + rawAttachmentData, + contentType: hydratedNote.mediaContentType ?? contentType, + }; + } + + return { hydratedNote, rawAttachmentData: null, contentType: null }; + }) + ) + ).filter(isNotNil); + + const hydratedNotes = await Promise.all( + hydratedNotesWithRawAttachments.map( + async ({ hydratedNote, rawAttachmentData, contentType }) => { + if (rawAttachmentData && !contentType) { + throw new Error('Content type is missing from attachment'); + } + + if (!rawAttachmentData || !contentType) { + return { hydratedNote, processedAttachment: null }; + } + + const localAttachment = + await window.Signal.Migrations.writeNewAttachmentData( + rawAttachmentData + ); + + const processedAttachment = + await window.Signal.Migrations.processNewAttachment({ + ...localAttachment, + contentType: stringToMIMEType(contentType), + }); + + return { hydratedNote, processedAttachment }; + } + ) + ); + if (!hydratedNotes.length) { log.warn('ReleaseNotesFetcher: No hydrated notes available, stopping'); return; @@ -176,39 +239,44 @@ export class ReleaseNotesFetcher { await window.ConversationController.getOrCreateSignalConversation(); const messages: Array = []; - hydratedNotes.forEach(async (note, index) => { - if (!note) { - return; + hydratedNotes.forEach( + ({ hydratedNote: note, processedAttachment }, index) => { + if (!note) { + return; + } + + const { title, body } = note; + const messageBody = `${title}\n\n${body}`; + const bodyRanges = [ + { start: 0, length: title.length, style: BodyRange.Style.BOLD }, + ]; + const timestamp = Date.now() + index; + + const message = new MessageModel({ + ...generateMessageId(incrementMessageCounter()), + ...(processedAttachment + ? { attachments: [processedAttachment] } + : {}), + body: messageBody, + bodyRanges, + conversationId: signalConversation.id, + readStatus: ReadStatus.Unread, + seenStatus: SeenStatus.Unseen, + received_at_ms: timestamp, + sent_at: timestamp, + serverTimestamp: timestamp, + sourceDevice: 1, + sourceServiceId: signalConversation.getServiceId(), + timestamp, + type: 'incoming', + }); + + window.MessageCache.register(message); + drop(signalConversation.onNewMessage(message)); + + messages.push(message.attributes); } - - const { title, body } = note; - const messageBody = `${title}\n\n${body}`; - const bodyRanges = [ - { start: 0, length: title.length, style: BodyRange.Style.BOLD }, - ]; - const timestamp = Date.now() + index; - - const message = new MessageModel({ - ...generateMessageId(incrementMessageCounter()), - body: messageBody, - bodyRanges, - conversationId: signalConversation.id, - readStatus: ReadStatus.Unread, - seenStatus: SeenStatus.Unseen, - received_at_ms: timestamp, - sent_at: timestamp, - serverTimestamp: timestamp, - sourceDevice: 1, - sourceServiceId: signalConversation.getServiceId(), - timestamp, - type: 'incoming', - }); - - window.MessageCache.register(message); - drop(signalConversation.onNewMessage(message)); - - messages.push(message.attributes); - }); + ); await Promise.all( messages.map(message => saveNewMessageBatcher.add(message)) @@ -276,7 +344,7 @@ export class ReleaseNotesFetcher { log.info( `ReleaseNotesFetcher: Processing ${validNotes.length} new release notes` ); - drop(this.#processReleaseNotes(validNotes)); + await this.#processReleaseNotes(validNotes); } else { log.info('ReleaseNotesFetcher: No new release notes'); } diff --git a/ts/test-mock/bootstrap.ts b/ts/test-mock/bootstrap.ts index 365403e78b..b018036a23 100644 --- a/ts/test-mock/bootstrap.ts +++ b/ts/test-mock/bootstrap.ts @@ -191,6 +191,7 @@ export class Bootstrap { // Limit number of storage read keys for easier testing maxStorageReadKeys: MAX_STORAGE_READ_KEYS, cdn3Path: this.cdn3Path, + updates2Path: path.join(__dirname, 'updates-data'), }); this.#options = { diff --git a/ts/test-mock/helpers.ts b/ts/test-mock/helpers.ts index 10483f9af1..4bcf8c7738 100644 --- a/ts/test-mock/helpers.ts +++ b/ts/test-mock/helpers.ts @@ -253,13 +253,21 @@ export async function createGroup( return group; } +export async function clickOnConversationWithAci( + page: Page, + aci: string +): Promise { + const leftPane = page.locator('#LeftPane'); + await leftPane.getByTestId(aci).click(); +} + export async function clickOnConversation( page: Page, contact: PrimaryDevice ): Promise { - const leftPane = page.locator('#LeftPane'); - await leftPane.getByTestId(contact.device.aci).click(); + await clickOnConversationWithAci(page, contact.device.aci); } + export async function pinContact( phone: PrimaryDevice, contact: PrimaryDevice diff --git a/ts/test-mock/release-notes/release_notes_test.ts b/ts/test-mock/release-notes/release_notes_test.ts index 41a6bd5363..7b0250b341 100644 --- a/ts/test-mock/release-notes/release_notes_test.ts +++ b/ts/test-mock/release-notes/release_notes_test.ts @@ -2,13 +2,17 @@ // SPDX-License-Identifier: AGPL-3.0-only import createDebug from 'debug'; -import { assert } from 'chai'; +import { expect } from 'playwright/test'; import type { App } from '../playwright'; import { Bootstrap } from '../bootstrap'; import { MINUTE } from '../../util/durations'; import { SIGNAL_ACI } from '../../types/SignalConversation'; +import { + clickOnConversationWithAci, + getTimelineMessageWithText, +} from '../helpers'; export const debug = createDebug('mock:test:releaseNotes'); @@ -37,7 +41,7 @@ describe('release notes', function (this: Mocha.Suite) { await bootstrap.teardown(); }); - it('shows release notes', async () => { + it('shows release notes with an image', async () => { const firstWindow = await app.getWindow(); await firstWindow.evaluate('window.SignalCI.resetReleaseNotesFetcher()'); @@ -52,6 +56,17 @@ describe('release notes', function (this: Mocha.Suite) { const releaseNoteConversation = leftPane.getByTestId(SIGNAL_ACI); await releaseNoteConversation.waitFor(); - assert.isTrue(await releaseNoteConversation.isVisible()); + await expect(releaseNoteConversation).toBeVisible(); + + await clickOnConversationWithAci(secondWindow, SIGNAL_ACI); + + const timelineMessage = await getTimelineMessageWithText( + secondWindow, + 'Call links' + ); + + await expect( + timelineMessage.locator('img.module-image__image') + ).toBeVisible(); }); }); diff --git a/ts/test-mock/updates-data/dynamic/release-notes/release-notes-v2.json b/ts/test-mock/updates-data/dynamic/release-notes/release-notes-v2.json new file mode 100644 index 0000000000..ea79791d37 --- /dev/null +++ b/ts/test-mock/updates-data/dynamic/release-notes/release-notes-v2.json @@ -0,0 +1,69 @@ +{ + "announcements": [ + { + "ctaId": "chat_folder", + "androidMinVersion": "9999", + "uuid": "b1b8cafb-11e8-490b-858d-dd26fc47c58a" + }, + { + "ctaId": "calls_tab", + "desktopMinVersion": "7.30.0", + "androidMinVersion": "1482", + "uuid": "1813845b-cc98-4210-a07c-164f9b328ca8" + } + ], + "megaphones": [ + { + "primaryCtaId": "donate", + "secondaryCtaId": "snooze", + "countries": "27:1000000,298:1000000,299:1000000,30:1000000,31:1000000,32:1000000,33:1000000,34:1000000,351:1000000,352:1000000,353:1000000,354:1000000,356:1000000,357:1000000,358:1000000,359:1000000,36:1000000,370:1000000,371:1000000,372:1000000,373:1000000,374:1000000,375:1000000,377:1000000,378:1000000,379:1000000,380:1000000,381:1000000,382:1000000,385:1000000,386:1000000,39:1000000,40:1000000,41:1000000,420:1000000,421:1000000,423:1000000,43:1000000,44:1000000,45:1000000,46:1000000,47:1000000,48:1000000,49:1000000,506:1000000,51:1000000,52:1000000,54:1000000,55:1000000,57:1000000,60:1000000,61:1000000,64:1000000,65:1000000,7:1000000,81:1000000,852:1000000,853:1000000,86:1000000,886:1000000,966:1000000,970:1000000,971:1000000,972:1000000,973:1000000,974:1000000,994:1000000,995:1000000", + "iosMinVersion": "6.1.0.17", + "priority": 100, + "dontShowBeforeEpochSeconds": 1732024800, + "uuid": "2f4f0c57-8e3b-437d-9d4b-6b84139cf5d7", + "showForNumberOfDays": 30, + "conditionalId": "standard_donate", + "dontShowAfterEpochSeconds": 1734616800, + "secondaryCtaData": { "snoozeDurationDays": [5, 7, 9, 100] } + }, + { + "primaryCtaId": "donate", + "secondaryCtaId": "snooze", + "androidMinVersion": "1164", + "countries": "20:1000000,212:1000000,213:1000000,216:1000000,218:1000000,221:1000000,223:1000000,225:1000000,226:1000000,229:1000000,230:1000000,233:1000000,234:1000000,237:1000000,243:1000000,244:1000000,254:1000000,255:1000000,256:1000000,27:1000000,30:1000000,31:1000000,32:1000000,33:1000000,34:1000000,351:1000000,352:1000000,353:1000000,354:1000000,355:1000000,356:1000000,357:1000000,358:1000000,359:1000000,36:1000000,370:1000000,371:1000000,372:1000000,373:1000000,374:1000000,380:1000000,381:1000000,382:1000000,383:1000000,385:1000000,386:1000000,387:1000000,389:1000000,39:1000000,40:1000000,41:1000000,420:1000000,421:1000000,43:1000000,44:1000000,45:1000000,46:1000000,47:1000000,48:1000000,49:1000000,502:1000000,503:1000000,505:1000000,506:1000000,507:1000000,51:1000000,52:1000000,54:1000000,55:1000000,56:1000000,57:1000000,58:1000000,591:1000000,593:1000000,595:1000000,596:1000000,598:1000000,60:1000000,61:1000000,62:1000000,63:1000000,64:1000000,65:1000000,66:1000000,7:1000000,81:1000000,82:1000000,84:1000000,852:1000000,853:1000000,855:1000000,880:1000000,886:1000000,90:1000000,91:1000000,92:1000000,93:1000000,94:1000000,95:1000000,961:1000000,962:1000000,964:1000000,965:1000000,966:1000000,967:1000000,968:1000000,971:1000000,972:1000000,973:1000000,974:1000000,977:1000000,994:1000000,995:1000000,996:1000000", + "priority": 100, + "dontShowBeforeEpochSeconds": 1732024800, + "uuid": "0ef1b765-1887-4620-b6a7-e4284720acde", + "showForNumberOfDays": 30, + "conditionalId": "standard_donate", + "dontShowAfterEpochSeconds": 1734616800, + "secondaryCtaData": { "snoozeDurationDays": [5, 7, 9, 100] } + }, + { + "primaryCtaId": "donate", + "secondaryCtaId": "snooze", + "countries": "1:1000000", + "iosMinVersion": "6.1.0.17", + "priority": 100, + "dontShowBeforeEpochSeconds": 1732024800, + "uuid": "999f2573-7d83-4d60-b045-259de448da8d", + "showForNumberOfDays": 30, + "conditionalId": "standard_donate", + "dontShowAfterEpochSeconds": 1734616800, + "secondaryCtaData": { "snoozeDurationDays": [5, 7, 9, 100] } + }, + { + "primaryCtaId": "donate", + "secondaryCtaId": "snooze", + "androidMinVersion": "1164", + "countries": "1:1000000", + "priority": 100, + "dontShowBeforeEpochSeconds": 1732024800, + "uuid": "9c3f0ba5-cce1-400b-b3b1-af34d5f073a5", + "showForNumberOfDays": 30, + "conditionalId": "standard_donate", + "dontShowAfterEpochSeconds": 1734616800, + "secondaryCtaData": { "snoozeDurationDays": [5, 7, 9, 100] } + } + ] +} diff --git a/ts/test-mock/updates-data/static/release-notes/1813845b-cc98-4210-a07c-164f9b328ca8/en.json b/ts/test-mock/updates-data/static/release-notes/1813845b-cc98-4210-a07c-164f9b328ca8/en.json new file mode 100644 index 0000000000..4e05b0b187 --- /dev/null +++ b/ts/test-mock/updates-data/static/release-notes/1813845b-cc98-4210-a07c-164f9b328ca8/en.json @@ -0,0 +1,9 @@ +{ + "mediaHeight": "864", + "mediaWidth": "1536", + "media": "/static/release-notes/call_links.png", + "uuid": "1813845b-cc98-4210-a07c-164f9b328ca8", + "title": "Introducing Call Links", + "body": "Call links are the missing link for calendar invites and impromptu gatherings. Now you can quickly create an easy link that anyone on Signal can use to join a group call without having to join a Signal group chat first.\n\nCall links are reusable and ideal for recurring phone dates with your best friends or weekly check-ins with your coworkers. You can manage your call links, control approval settings, and copy links from the calls tab for quick sharing. ", + "callToActionText": "Create Call Link" +} diff --git a/ts/test-mock/updates-data/static/release-notes/call_links.png b/ts/test-mock/updates-data/static/release-notes/call_links.png new file mode 100644 index 0000000000000000000000000000000000000000..c9f9c98a95cb6f30fbb0ced80b662911ad0a9554 GIT binary patch literal 37641 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 +export type GetReleaseNoteImageAttachmentResultType = Readonly<{ + imageData: Uint8Array; + contentType: string | null; +}>; + export type CallLinkCreateAuthResponseType = Readonly<{ credential: string; }>; @@ -1417,6 +1422,9 @@ export type WebAPIType = { ) => Promise; getReleaseNotesManifest: () => Promise; getReleaseNotesManifestHash: () => Promise; + getReleaseNoteImageAttachment: ( + path: string + ) => Promise; getSticker: (packId: string, stickerId: number) => Promise; getStickerPackManifest: (packId: string) => Promise; getStorageCredentials: MessageSender['getStorageCredentials']; @@ -1890,6 +1898,7 @@ export function initialize({ getReleaseNoteHash, getReleaseNotesManifest, getReleaseNotesManifestHash, + getReleaseNoteImageAttachment, getTransferArchive, getSenderCertificate, getSocketStatus, @@ -2257,6 +2266,29 @@ export function initialize({ return etag; } + async function getReleaseNoteImageAttachment( + path: string + ): Promise { + const { origin: expectedOrigin } = new URL(resourcesUrl); + const url = `${resourcesUrl}${path}`; + const { origin } = new URL(url); + strictAssert(origin === expectedOrigin, `Unexpected origin: ${origin}`); + + const { data: imageData, contentType } = await _outerAjax(url, { + certificateAuthority, + proxyUrl, + responseType: 'byteswithdetails', + timeout: 0, + type: 'GET', + version, + }); + + return { + imageData, + contentType, + }; + } + async function getStorageManifest( options: StorageServiceCallOptionsType = {} ): Promise { @@ -3917,7 +3949,12 @@ export function initialize({ if (options?.downloadOffset) { targetHeaders.range = `bytes=${options.downloadOffset}-`; } - streamWithDetails = await _outerAjax(`${cdnUrl}${cdnPath}`, { + const { origin: expectedOrigin } = new URL(cdnUrl); + const fullCdnUrl = `${cdnUrl}${cdnPath}`; + const { origin } = new URL(fullCdnUrl); + strictAssert(origin === expectedOrigin, `Unexpected origin: ${origin}`); + + streamWithDetails = await _outerAjax(fullCdnUrl, { headers: targetHeaders, certificateAuthority, disableRetries: options?.disableRetries,