Adapt bootstrap to support two clients
This commit is contained in:
parent
5044b3ca3b
commit
9963daf3bf
4 changed files with 128 additions and 21 deletions
|
@ -222,7 +222,7 @@
|
||||||
"@indutny/parallel-prettier": "3.0.0",
|
"@indutny/parallel-prettier": "3.0.0",
|
||||||
"@indutny/rezip-electron": "2.0.1",
|
"@indutny/rezip-electron": "2.0.1",
|
||||||
"@napi-rs/canvas": "0.1.61",
|
"@napi-rs/canvas": "0.1.61",
|
||||||
"@signalapp/mock-server": "13.0.1",
|
"@signalapp/mock-server": "13.1.0",
|
||||||
"@storybook/addon-a11y": "8.4.4",
|
"@storybook/addon-a11y": "8.4.4",
|
||||||
"@storybook/addon-actions": "8.4.4",
|
"@storybook/addon-actions": "8.4.4",
|
||||||
"@storybook/addon-controls": "8.4.4",
|
"@storybook/addon-controls": "8.4.4",
|
||||||
|
|
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
|
@ -430,8 +430,8 @@ importers:
|
||||||
specifier: 0.1.61
|
specifier: 0.1.61
|
||||||
version: 0.1.61
|
version: 0.1.61
|
||||||
'@signalapp/mock-server':
|
'@signalapp/mock-server':
|
||||||
specifier: 13.0.1
|
specifier: 13.1.0
|
||||||
version: 13.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)
|
version: 13.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)
|
||||||
'@storybook/addon-a11y':
|
'@storybook/addon-a11y':
|
||||||
specifier: 8.4.4
|
specifier: 8.4.4
|
||||||
version: 8.4.4(storybook@8.4.4(bufferutil@4.0.9)(prettier@3.3.3)(utf-8-validate@5.0.10))
|
version: 8.4.4(storybook@8.4.4(bufferutil@4.0.9)(prettier@3.3.3)(utf-8-validate@5.0.10))
|
||||||
|
@ -2770,8 +2770,8 @@ packages:
|
||||||
'@signalapp/libsignal-client@0.76.0':
|
'@signalapp/libsignal-client@0.76.0':
|
||||||
resolution: {integrity: sha512-wQZFC79GAUeee8pf+aDK5Gii0HbQoCAv/oTn1Ht7d5mFq2pw/L0jRcv3j9DgVYodzCOlnanfto3apfA6eN/Whw==}
|
resolution: {integrity: sha512-wQZFC79GAUeee8pf+aDK5Gii0HbQoCAv/oTn1Ht7d5mFq2pw/L0jRcv3j9DgVYodzCOlnanfto3apfA6eN/Whw==}
|
||||||
|
|
||||||
'@signalapp/mock-server@13.0.1':
|
'@signalapp/mock-server@13.1.0':
|
||||||
resolution: {integrity: sha512-1rT0fYyqEad64GnZRrFVhNsgKpPS+pvyyk8iOGUHqnqnf818yLIYHblS/5m/cNcvHyC/BBqdtgRHAsfGNqkuZw==}
|
resolution: {integrity: sha512-CuDNLNEBMzwIs5jr7Lx9F4YFoRD62s7WgPGtm3qpaggixSQtabjMC7AKSR0xvaHcZpYZtBU5jcGK8Roguo9nuw==}
|
||||||
|
|
||||||
'@signalapp/parchment-cjs@3.0.1':
|
'@signalapp/parchment-cjs@3.0.1':
|
||||||
resolution: {integrity: sha512-hSBMQ1M7wE4GcC8ZeNtvpJF+DAJg3eIRRf1SiHS3I3Algav/sgJJNm6HIYm6muHuK7IJmuEjkL3ILSXgmu0RfQ==}
|
resolution: {integrity: sha512-hSBMQ1M7wE4GcC8ZeNtvpJF+DAJg3eIRRf1SiHS3I3Algav/sgJJNm6HIYm6muHuK7IJmuEjkL3ILSXgmu0RfQ==}
|
||||||
|
@ -12466,7 +12466,7 @@ snapshots:
|
||||||
type-fest: 4.26.1
|
type-fest: 4.26.1
|
||||||
uuid: 11.0.2
|
uuid: 11.0.2
|
||||||
|
|
||||||
'@signalapp/mock-server@13.0.1(bufferutil@4.0.9)(utf-8-validate@5.0.10)':
|
'@signalapp/mock-server@13.1.0(bufferutil@4.0.9)(utf-8-validate@5.0.10)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@indutny/parallel-prettier': 3.0.0(prettier@3.3.3)
|
'@indutny/parallel-prettier': 3.0.0(prettier@3.3.3)
|
||||||
'@signalapp/libsignal-client': 0.60.2
|
'@signalapp/libsignal-client': 0.60.2
|
||||||
|
|
|
@ -117,6 +117,9 @@ export type BootstrapOptions = Readonly<{
|
||||||
contactPreKeyCount?: number;
|
contactPreKeyCount?: number;
|
||||||
|
|
||||||
useLegacyStorageEncryption?: boolean;
|
useLegacyStorageEncryption?: boolean;
|
||||||
|
|
||||||
|
// Optional. specify a server to use instead of creating and initializing one.
|
||||||
|
server?: Server;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type EphemeralBackupType = Readonly<
|
export type EphemeralBackupType = Readonly<
|
||||||
|
@ -207,7 +210,7 @@ const DEFAULT_REMOTE_CONFIG = [
|
||||||
//
|
//
|
||||||
export class Bootstrap {
|
export class Bootstrap {
|
||||||
public readonly server: Server;
|
public readonly server: Server;
|
||||||
public readonly cdn3Path: string;
|
public readonly cdn3Path?: string;
|
||||||
|
|
||||||
readonly #options: BootstrapInternalOptions;
|
readonly #options: BootstrapInternalOptions;
|
||||||
#privContacts?: ReadonlyArray<PrimaryDevice>;
|
#privContacts?: ReadonlyArray<PrimaryDevice>;
|
||||||
|
@ -221,11 +224,13 @@ export class Bootstrap {
|
||||||
readonly #randomId = crypto.randomBytes(8).toString('hex');
|
readonly #randomId = crypto.randomBytes(8).toString('hex');
|
||||||
|
|
||||||
constructor(options: BootstrapOptions = {}) {
|
constructor(options: BootstrapOptions = {}) {
|
||||||
this.cdn3Path = path.join(
|
this.cdn3Path =
|
||||||
os.tmpdir(),
|
options.server === undefined
|
||||||
`mock-signal-cdn3-${this.#randomId}`
|
? path.join(os.tmpdir(), `mock-signal-cdn3-${this.#randomId}`)
|
||||||
);
|
: undefined;
|
||||||
this.server = new Server({
|
this.server =
|
||||||
|
options.server ??
|
||||||
|
new Server({
|
||||||
// Limit number of storage read keys for easier testing
|
// Limit number of storage read keys for easier testing
|
||||||
maxStorageReadKeys: MAX_STORAGE_READ_KEYS,
|
maxStorageReadKeys: MAX_STORAGE_READ_KEYS,
|
||||||
cdn3Path: this.cdn3Path,
|
cdn3Path: this.cdn3Path,
|
||||||
|
@ -254,10 +259,14 @@ export class Bootstrap {
|
||||||
public async init(): Promise<void> {
|
public async init(): Promise<void> {
|
||||||
debug('initializing');
|
debug('initializing');
|
||||||
|
|
||||||
|
if (this.#options.server === undefined) {
|
||||||
await this.server.listen(0);
|
await this.server.listen(0);
|
||||||
|
|
||||||
const { port } = this.server.address();
|
const { port } = this.server.address();
|
||||||
debug('started server on port=%d', port);
|
debug('started server on port=%d', port);
|
||||||
|
} else {
|
||||||
|
debug('existing server listening on port = ', this.server.address().port);
|
||||||
|
}
|
||||||
|
|
||||||
const totalContactCount =
|
const totalContactCount =
|
||||||
this.#options.contactCount +
|
this.#options.contactCount +
|
||||||
|
@ -367,7 +376,9 @@ export class Bootstrap {
|
||||||
...[this.#storagePath, this.cdn3Path].map(tmpPath =>
|
...[this.#storagePath, this.cdn3Path].map(tmpPath =>
|
||||||
tmpPath ? fs.rm(tmpPath, { recursive: true }) : Promise.resolve()
|
tmpPath ? fs.rm(tmpPath, { recursive: true }) : Promise.resolve()
|
||||||
),
|
),
|
||||||
this.server.close(),
|
this.#options.server === undefined
|
||||||
|
? this.server.close()
|
||||||
|
: Promise.resolve(),
|
||||||
this.#lastApp?.close(),
|
this.#lastApp?.close(),
|
||||||
]),
|
]),
|
||||||
new Promise(resolve => setTimeout(resolve, CLOSE_TIMEOUT).unref()),
|
new Promise(resolve => setTimeout(resolve, CLOSE_TIMEOUT).unref()),
|
||||||
|
|
96
ts/test-mock/calling/callMessages_test.ts
Normal file
96
ts/test-mock/calling/callMessages_test.ts
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
// Copyright 2025 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { StorageState } from '@signalapp/mock-server';
|
||||||
|
import * as durations from '../../util/durations';
|
||||||
|
import type { App } from '../playwright';
|
||||||
|
import { Bootstrap } from '../bootstrap';
|
||||||
|
import { typeIntoInput, waitForEnabledComposer } from '../helpers';
|
||||||
|
|
||||||
|
describe('callMessages', function (this: Mocha.Suite) {
|
||||||
|
this.timeout(durations.MINUTE);
|
||||||
|
|
||||||
|
let bootstrap1: Bootstrap;
|
||||||
|
let bootstrap2: Bootstrap;
|
||||||
|
let app1: App;
|
||||||
|
let app2: App;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
bootstrap1 = new Bootstrap();
|
||||||
|
await bootstrap1.init();
|
||||||
|
|
||||||
|
bootstrap2 = new Bootstrap({ server: bootstrap1.server });
|
||||||
|
await bootstrap2.init();
|
||||||
|
|
||||||
|
let state1 = StorageState.getEmpty();
|
||||||
|
state1 = state1.updateAccount({
|
||||||
|
profileKey: bootstrap1.phone.profileKey.serialize(),
|
||||||
|
});
|
||||||
|
|
||||||
|
state1 = state1.addContact(bootstrap2.phone, {
|
||||||
|
whitelisted: true,
|
||||||
|
profileKey: bootstrap2.phone.profileKey.serialize(),
|
||||||
|
givenName: 'Contact2',
|
||||||
|
});
|
||||||
|
|
||||||
|
state1 = state1.pin(bootstrap2.phone);
|
||||||
|
|
||||||
|
await bootstrap1.phone.setStorageState(state1);
|
||||||
|
|
||||||
|
app1 = await bootstrap1.link();
|
||||||
|
|
||||||
|
let state2 = StorageState.getEmpty();
|
||||||
|
state2 = state2.updateAccount({
|
||||||
|
profileKey: bootstrap2.phone.profileKey.serialize(),
|
||||||
|
});
|
||||||
|
|
||||||
|
state2 = state2.addContact(bootstrap1.phone, {
|
||||||
|
whitelisted: true,
|
||||||
|
profileKey: bootstrap1.phone.profileKey.serialize(),
|
||||||
|
givenName: 'Contact1',
|
||||||
|
});
|
||||||
|
|
||||||
|
state2 = state2.pin(bootstrap1.phone);
|
||||||
|
await bootstrap2.phone.setStorageState(state2);
|
||||||
|
|
||||||
|
app2 = await bootstrap2.link();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async function (this: Mocha.Context) {
|
||||||
|
if (!bootstrap1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await bootstrap2.maybeSaveLogs(this.currentTest, app2);
|
||||||
|
await bootstrap1.maybeSaveLogs(this.currentTest, app1);
|
||||||
|
|
||||||
|
await app2.close();
|
||||||
|
await app1.close();
|
||||||
|
|
||||||
|
await bootstrap2.teardown();
|
||||||
|
await bootstrap1.teardown();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can send a message from one client to another', async () => {
|
||||||
|
const window1 = await app1.getWindow();
|
||||||
|
const leftPane1 = window1.locator('#LeftPane');
|
||||||
|
|
||||||
|
await leftPane1
|
||||||
|
.locator(`[data-testid="${bootstrap2.phone.device.aci}"]`)
|
||||||
|
.click();
|
||||||
|
const window2 = await app2.getWindow();
|
||||||
|
|
||||||
|
const messageBody = 'Hello world';
|
||||||
|
const compositionInput = await waitForEnabledComposer(window1);
|
||||||
|
await typeIntoInput(compositionInput, messageBody, '');
|
||||||
|
await compositionInput.press('Enter');
|
||||||
|
|
||||||
|
const leftPane = window2.locator('#LeftPane');
|
||||||
|
await leftPane
|
||||||
|
.locator(`[data-testid="${bootstrap1.phone.device.aci}"]`)
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await window2
|
||||||
|
.locator(`.module-message__text >> "${messageBody}"`)
|
||||||
|
.waitFor();
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue