Storage service tests and benches in ts/test-mock
This commit is contained in:
parent
48137a498c
commit
6281d52ec6
|
@ -20,18 +20,8 @@ jobs:
|
||||||
- name: Get other system specs
|
- name: Get other system specs
|
||||||
run: uname -a
|
run: uname -a
|
||||||
|
|
||||||
- name: Configure git to use HTTPS
|
|
||||||
run: git config --global url."https://${{ secrets.AUTOMATED_GITHUB_PAT }}:x-oauth-basic@github.com".insteadOf ssh://git@github.com
|
|
||||||
|
|
||||||
- name: Clone Desktop repo
|
- name: Clone Desktop repo
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Clone Mock-Server repo
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
repository: 'signalapp/Mock-Signal-Server-Private'
|
|
||||||
path: 'Mock-Server'
|
|
||||||
ref: 'gamma'
|
|
||||||
token: ${{ secrets.AUTOMATED_GITHUB_PAT }}
|
|
||||||
|
|
||||||
- name: Setup node.js
|
- name: Setup node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
|
@ -54,104 +44,63 @@ jobs:
|
||||||
if: steps.cache-desktop-modules.outputs.cache-hit != 'true'
|
if: steps.cache-desktop-modules.outputs.cache-hit != 'true'
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Install Mock-Server node_modules
|
|
||||||
run: yarn install --frozen-lockfile
|
|
||||||
working-directory: Mock-Server
|
|
||||||
env:
|
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
|
||||||
|
|
||||||
- name: Build typescript
|
- name: Build typescript
|
||||||
run: yarn generate
|
run: yarn generate
|
||||||
- name: Bundle
|
- name: Bundle
|
||||||
run: yarn build:webpack
|
run: yarn build:webpack
|
||||||
|
|
||||||
- name: Copy CI configuration
|
|
||||||
run: |
|
|
||||||
cp -rf ./Mock-Server/config/local-development.json \
|
|
||||||
./config/local-development.json
|
|
||||||
cp -rf ./config/local-development.json ./config/local-production.json
|
|
||||||
|
|
||||||
- name: Setup hosts
|
- name: Setup hosts
|
||||||
run: sudo echo "127.0.0.1 mock.signal.org" | sudo tee -a /etc/hosts
|
run: sudo echo "127.0.0.1 mock.signal.org" | sudo tee -a /etc/hosts
|
||||||
|
|
||||||
- name: Run startup benchmarks
|
- name: Run startup benchmarks
|
||||||
run: |
|
run: |
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
rm -rf /tmp/mock
|
xvfb-run --auto-servernum node ts/test-mock/benchmarks/startup_bench.js |
|
||||||
xvfb-run --auto-servernum node Mock-Server/scripts/load-test.js \
|
tee benchmark-startup.log
|
||||||
./node_modules/.bin/electron . | tee benchmark-startup.log
|
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
env:
|
env:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
RUN_COUNT: 10
|
RUN_COUNT: 10
|
||||||
ELECTRON_ENABLE_STACK_DUMPING: on
|
ELECTRON_ENABLE_STACK_DUMPING: on
|
||||||
|
|
||||||
- name: Upload startup benchmark logs on failure
|
|
||||||
if: failure()
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: startup-logs
|
|
||||||
path: /tmp/mock/logs
|
|
||||||
|
|
||||||
- name: Run send benchmarks
|
- name: Run send benchmarks
|
||||||
run: |
|
run: |
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
rm -rf /tmp/mock
|
rm -rf /tmp/mock
|
||||||
xvfb-run --auto-servernum node Mock-Server/scripts/send-test.js \
|
xvfb-run --auto-servernum node ts/test-mock/benchmarks/send_bench.js |
|
||||||
./node_modules/.bin/electron . | tee benchmark-send.log
|
tee benchmark-send.log
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
env:
|
env:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
RUN_COUNT: 100
|
RUN_COUNT: 100
|
||||||
ELECTRON_ENABLE_STACK_DUMPING: on
|
ELECTRON_ENABLE_STACK_DUMPING: on
|
||||||
|
|
||||||
- name: Upload send benchmark logs on failure
|
|
||||||
if: failure()
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: send-logs
|
|
||||||
path: /tmp/mock/logs
|
|
||||||
|
|
||||||
- name: Run group send benchmarks
|
- name: Run group send benchmarks
|
||||||
run: |
|
run: |
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
rm -rf /tmp/mock
|
rm -rf /tmp/mock
|
||||||
xvfb-run --auto-servernum node \
|
xvfb-run --auto-servernum node \
|
||||||
Mock-Server/scripts/group-send-test.js \
|
ts/test-mock/benchmarks/group_send_bench.js | \
|
||||||
./node_modules/.bin/electron . | tee benchmark-group-send.log
|
tee benchmark-group-send.log
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
env:
|
env:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
RUN_COUNT: 100
|
RUN_COUNT: 100
|
||||||
ELECTRON_ENABLE_STACK_DUMPING: on
|
ELECTRON_ENABLE_STACK_DUMPING: on
|
||||||
|
|
||||||
- name: Upload group send benchmark logs on failure
|
|
||||||
if: failure()
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: group-send-logs
|
|
||||||
path: /tmp/mock/logs
|
|
||||||
|
|
||||||
- name: Run conversation open benchmarks
|
- name: Run conversation open benchmarks
|
||||||
run: |
|
run: |
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
rm -rf /tmp/mock
|
rm -rf /tmp/mock
|
||||||
xvfb-run --auto-servernum node \
|
xvfb-run --auto-servernum node \
|
||||||
Mock-Server/scripts/convo-open-test.js \
|
ts/test-mock/benchmarks/convo_open_bench.js | \
|
||||||
./node_modules/.bin/electron . | tee benchmark-convo-open.log
|
tee benchmark-convo-open.log
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
env:
|
env:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
RUN_COUNT: 100
|
RUN_COUNT: 100
|
||||||
ELECTRON_ENABLE_STACK_DUMPING: on
|
ELECTRON_ENABLE_STACK_DUMPING: on
|
||||||
|
|
||||||
- name: Upload conversation open benchmark logs on failure
|
|
||||||
if: failure()
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: convo-open-logs
|
|
||||||
path: /tmp/mock/logs
|
|
||||||
|
|
||||||
- name: Clone benchmark repo
|
- name: Clone benchmark repo
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -161,18 +161,8 @@ jobs:
|
||||||
- name: Get other system specs
|
- name: Get other system specs
|
||||||
run: uname -a
|
run: uname -a
|
||||||
|
|
||||||
- name: Configure git to use HTTPS
|
|
||||||
run: git config --global url."https://${{ secrets.AUTOMATED_GITHUB_PAT }}:x-oauth-basic@github.com".insteadOf ssh://git@github.com
|
|
||||||
|
|
||||||
- name: Clone Desktop repo
|
- name: Clone Desktop repo
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- name: Clone Mock-Server repo
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
repository: 'signalapp/Mock-Signal-Server-Private'
|
|
||||||
path: 'Mock-Server'
|
|
||||||
ref: 'gamma'
|
|
||||||
token: ${{ secrets.AUTOMATED_GITHUB_PAT }}
|
|
||||||
|
|
||||||
- name: Setup node.js
|
- name: Setup node.js
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
|
@ -195,40 +185,19 @@ jobs:
|
||||||
if: steps.cache-desktop-modules.outputs.cache-hit != 'true'
|
if: steps.cache-desktop-modules.outputs.cache-hit != 'true'
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
- name: Install Mock-Server node_modules
|
|
||||||
run: yarn install --frozen-lockfile
|
|
||||||
working-directory: Mock-Server
|
|
||||||
env:
|
|
||||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
|
|
||||||
|
|
||||||
- name: Build typescript
|
- name: Build typescript
|
||||||
run: yarn generate
|
run: yarn generate
|
||||||
- name: Bundle
|
- name: Bundle
|
||||||
run: yarn build:webpack
|
run: yarn build:webpack
|
||||||
|
|
||||||
- name: Copy CI configuration
|
|
||||||
run: |
|
|
||||||
cp -rf ./Mock-Server/config/local-development.json \
|
|
||||||
./config/local-development.json
|
|
||||||
cp -rf ./config/local-development.json ./config/local-production.json
|
|
||||||
|
|
||||||
- name: Setup hosts
|
- name: Setup hosts
|
||||||
run: sudo echo "127.0.0.1 mock.signal.org" | sudo tee -a /etc/hosts
|
run: sudo echo "127.0.0.1 mock.signal.org" | sudo tee -a /etc/hosts
|
||||||
|
|
||||||
- name: Run storage service tests
|
- name: Run storage service tests
|
||||||
run: |
|
run: |
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
rm -rf /tmp/mock
|
xvfb-run --auto-servernum yarn test-mock
|
||||||
xvfb-run --auto-servernum node Mock-Server/scripts/storage-service-test.js \
|
|
||||||
./node_modules/.bin/electron .
|
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
env:
|
env:
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
DEBUG: mock:scripts:*
|
DEBUG: mock:test-storage
|
||||||
|
|
||||||
- name: Upload logs on failure
|
|
||||||
if: failure()
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: logs
|
|
||||||
path: /tmp/mock/logs
|
|
||||||
|
|
|
@ -122,6 +122,7 @@ const development =
|
||||||
const isThrottlingEnabled = development || isAlpha(app.getVersion());
|
const isThrottlingEnabled = development || isAlpha(app.getVersion());
|
||||||
|
|
||||||
const enableCI = config.get<boolean>('enableCI');
|
const enableCI = config.get<boolean>('enableCI');
|
||||||
|
const forcePreloadBundle = config.get<boolean>('forcePreloadBundle');
|
||||||
|
|
||||||
const preventDisplaySleepService = new PreventDisplaySleepService(
|
const preventDisplaySleepService = new PreventDisplaySleepService(
|
||||||
powerSaveBlocker
|
powerSaveBlocker
|
||||||
|
@ -458,6 +459,9 @@ if (OS.isWindows()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createWindow() {
|
async function createWindow() {
|
||||||
|
const usePreloadBundle =
|
||||||
|
!isTestEnvironment(getEnvironment()) || forcePreloadBundle;
|
||||||
|
|
||||||
const windowOptions: Electron.BrowserWindowConstructorOptions = {
|
const windowOptions: Electron.BrowserWindowConstructorOptions = {
|
||||||
show: false,
|
show: false,
|
||||||
width: DEFAULT_WIDTH,
|
width: DEFAULT_WIDTH,
|
||||||
|
@ -480,9 +484,7 @@ async function createWindow() {
|
||||||
contextIsolation: false,
|
contextIsolation: false,
|
||||||
preload: join(
|
preload: join(
|
||||||
__dirname,
|
__dirname,
|
||||||
isTestEnvironment(getEnvironment())
|
usePreloadBundle ? '../preload.bundle.js' : '../preload.js'
|
||||||
? '../preload.js'
|
|
||||||
: '../preload.bundle.js'
|
|
||||||
),
|
),
|
||||||
nativeWindowOpen: true,
|
nativeWindowOpen: true,
|
||||||
spellcheck: await getSpellCheckSetting(),
|
spellcheck: await getSpellCheckSetting(),
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
const config = require('./app/config').default;
|
||||||
|
|
||||||
|
config.util.extendDeep(config, JSON.parse(process.env.SIGNAL_CI_CONFIG || ''));
|
||||||
|
|
||||||
|
require('./app/main');
|
|
@ -17,6 +17,7 @@
|
||||||
"sfuUrl": "https://sfu.voip.signal.org/",
|
"sfuUrl": "https://sfu.voip.signal.org/",
|
||||||
"updatesEnabled": false,
|
"updatesEnabled": false,
|
||||||
"enableCI": false,
|
"enableCI": false,
|
||||||
|
"forcePreloadBundle": false,
|
||||||
"openDevTools": false,
|
"openDevTools": false,
|
||||||
"buildCreation": 0,
|
"buildCreation": 0,
|
||||||
"buildExpiration": 0,
|
"buildExpiration": 0,
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
"test-electron": "node ts/scripts/test-electron.js",
|
"test-electron": "node ts/scripts/test-electron.js",
|
||||||
"test-release": "node ts/scripts/test-release.js",
|
"test-release": "node ts/scripts/test-release.js",
|
||||||
"test-node": "electron-mocha --file test/setup-test-node.js --recursive test/modules ts/test-node ts/test-both",
|
"test-node": "electron-mocha --file test/setup-test-node.js --recursive test/modules ts/test-node ts/test-both",
|
||||||
|
"test-mock": "mocha ts/test-mock/**/*_test.js",
|
||||||
"test-node-coverage": "nyc --reporter=lcov --reporter=text mocha --recursive test/modules ts/test-node ts/test-both",
|
"test-node-coverage": "nyc --reporter=lcov --reporter=text mocha --recursive test/modules ts/test-node ts/test-both",
|
||||||
"eslint": "eslint --cache .",
|
"eslint": "eslint --cache .",
|
||||||
"lint": "yarn format --list-different && yarn eslint",
|
"lint": "yarn format --list-different && yarn eslint",
|
||||||
|
@ -185,6 +186,7 @@
|
||||||
"@babel/preset-typescript": "7.16.0",
|
"@babel/preset-typescript": "7.16.0",
|
||||||
"@chanzuckerberg/axe-storybook-testing": "3.0.2",
|
"@chanzuckerberg/axe-storybook-testing": "3.0.2",
|
||||||
"@electron/fuses": "1.5.0",
|
"@electron/fuses": "1.5.0",
|
||||||
|
"@signalapp/mock-server": "1.0.1",
|
||||||
"@storybook/addon-actions": "5.1.11",
|
"@storybook/addon-actions": "5.1.11",
|
||||||
"@storybook/addon-knobs": "5.1.11",
|
"@storybook/addon-knobs": "5.1.11",
|
||||||
"@storybook/addons": "5.1.11",
|
"@storybook/addons": "5.1.11",
|
||||||
|
@ -197,6 +199,7 @@
|
||||||
"@types/classnames": "2.2.3",
|
"@types/classnames": "2.2.3",
|
||||||
"@types/config": "0.0.39",
|
"@types/config": "0.0.39",
|
||||||
"@types/dashdash": "1.14.0",
|
"@types/dashdash": "1.14.0",
|
||||||
|
"@types/debug": "4.1.7",
|
||||||
"@types/filesize": "3.6.0",
|
"@types/filesize": "3.6.0",
|
||||||
"@types/fs-extra": "5.0.5",
|
"@types/fs-extra": "5.0.5",
|
||||||
"@types/google-libphonenumber": "7.4.23",
|
"@types/google-libphonenumber": "7.4.23",
|
||||||
|
@ -255,6 +258,7 @@
|
||||||
"core-js": "2.6.9",
|
"core-js": "2.6.9",
|
||||||
"cross-env": "5.2.0",
|
"cross-env": "5.2.0",
|
||||||
"css-loader": "3.2.0",
|
"css-loader": "3.2.0",
|
||||||
|
"debug": "4.3.3",
|
||||||
"electron": "16.0.8",
|
"electron": "16.0.8",
|
||||||
"electron-builder": "22.14.5",
|
"electron-builder": "22.14.5",
|
||||||
"electron-mocha": "11.0.2",
|
"electron-mocha": "11.0.2",
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
/* eslint-disable no-await-in-loop, no-console */
|
||||||
|
|
||||||
|
import type { PrimaryDevice } from '@signalapp/mock-server';
|
||||||
|
|
||||||
|
import { Bootstrap, debug, stats, RUN_COUNT, DISCARD_COUNT } from './fixtures';
|
||||||
|
|
||||||
|
const CONVERSATION_SIZE = 1000; // messages
|
||||||
|
const DELAY = 50; // milliseconds
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const bootstrap = new Bootstrap({
|
||||||
|
benchmark: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await bootstrap.init();
|
||||||
|
const app = await bootstrap.link();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { server, contacts, phone, desktop } = bootstrap;
|
||||||
|
|
||||||
|
const [first, second] = contacts;
|
||||||
|
|
||||||
|
const messages = new Array<Buffer>();
|
||||||
|
debug('encrypting');
|
||||||
|
// Send messages from just two contacts
|
||||||
|
for (const contact of [second, first]) {
|
||||||
|
for (let i = 0; i < CONVERSATION_SIZE; i += 1) {
|
||||||
|
const messageTimestamp = bootstrap.getTimestamp();
|
||||||
|
messages.push(
|
||||||
|
await contact.encryptText(
|
||||||
|
desktop,
|
||||||
|
`hello from: ${contact.profileName}`,
|
||||||
|
{
|
||||||
|
timestamp: messageTimestamp,
|
||||||
|
sealed: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
messages.push(
|
||||||
|
await phone.encryptSyncRead(desktop, {
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
senderUUID: contact.device.uuid,
|
||||||
|
timestamp: messageTimestamp,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendQueue = async (): Promise<void> => {
|
||||||
|
await Promise.all(messages.map(message => server.send(desktop, message)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const measure = async (): Promise<void> => {
|
||||||
|
const window = await app.getWindow();
|
||||||
|
|
||||||
|
const leftPane = window.locator('.left-pane-wrapper');
|
||||||
|
|
||||||
|
const openConvo = async (contact: PrimaryDevice): Promise<void> => {
|
||||||
|
debug('opening conversation', contact.profileName);
|
||||||
|
const item = leftPane.locator(
|
||||||
|
'_react=BaseConversationListItem' +
|
||||||
|
`[title = ${JSON.stringify(contact.profileName)}]`
|
||||||
|
);
|
||||||
|
|
||||||
|
await item.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const deltaList = new Array<number>();
|
||||||
|
for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) {
|
||||||
|
await openConvo(runId % 2 === 0 ? first : second);
|
||||||
|
|
||||||
|
debug('waiting for timing from the app');
|
||||||
|
const { delta } = await app.waitForConversationOpen();
|
||||||
|
|
||||||
|
// Let render complete
|
||||||
|
await new Promise(resolve => setTimeout(resolve, DELAY));
|
||||||
|
|
||||||
|
if (runId >= DISCARD_COUNT) {
|
||||||
|
deltaList.push(delta);
|
||||||
|
console.log('run=%d info=%j', runId - DISCARD_COUNT, { delta });
|
||||||
|
} else {
|
||||||
|
console.log('discarded=%d info=%j', runId, { delta });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) });
|
||||||
|
};
|
||||||
|
|
||||||
|
await Promise.all([sendQueue(), measure()]);
|
||||||
|
} finally {
|
||||||
|
await app.close();
|
||||||
|
await bootstrap.teardown();
|
||||||
|
}
|
||||||
|
})();
|
|
@ -0,0 +1,67 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
/* eslint-disable no-await-in-loop, no-console */
|
||||||
|
|
||||||
|
import createDebug from 'debug';
|
||||||
|
|
||||||
|
export const debug = createDebug('mock:benchmarks');
|
||||||
|
|
||||||
|
export { Bootstrap } from '../bootstrap';
|
||||||
|
export { App } from '../playwright';
|
||||||
|
|
||||||
|
export type StatsType = {
|
||||||
|
mean: number;
|
||||||
|
stddev: number;
|
||||||
|
[key: string]: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RUN_COUNT = process.env.RUN_COUNT
|
||||||
|
? parseInt(process.env.RUN_COUNT, 10)
|
||||||
|
: 100;
|
||||||
|
|
||||||
|
export const GROUP_SIZE = process.env.GROUP_SIZE
|
||||||
|
? parseInt(process.env.GROUP_SIZE, 10)
|
||||||
|
: 8;
|
||||||
|
|
||||||
|
export const DISCARD_COUNT = process.env.DISCARD_COUNT
|
||||||
|
? parseInt(process.env.DISCARD_COUNT, 10)
|
||||||
|
: 5;
|
||||||
|
|
||||||
|
export function stats(
|
||||||
|
list: ReadonlyArray<number>,
|
||||||
|
percentiles: ReadonlyArray<number> = []
|
||||||
|
): StatsType {
|
||||||
|
if (list.length === 0) {
|
||||||
|
throw new Error('Empty list given to stats');
|
||||||
|
}
|
||||||
|
|
||||||
|
let mean = 0;
|
||||||
|
let stddev = 0;
|
||||||
|
|
||||||
|
for (const value of list) {
|
||||||
|
mean += value;
|
||||||
|
stddev += value ** 2;
|
||||||
|
}
|
||||||
|
mean /= list.length;
|
||||||
|
stddev /= list.length;
|
||||||
|
|
||||||
|
stddev -= mean ** 2;
|
||||||
|
stddev = Math.sqrt(stddev);
|
||||||
|
|
||||||
|
const sorted = list.slice().sort((a, b) => a - b);
|
||||||
|
|
||||||
|
const result: StatsType = { mean, stddev };
|
||||||
|
|
||||||
|
for (const p of percentiles) {
|
||||||
|
result[`p${p}`] = sorted[Math.floor((sorted.length * p) / 100)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can happen if electron exits prematurely
|
||||||
|
process.on('unhandledRejection', reason => {
|
||||||
|
console.error('Unhandled rejection:');
|
||||||
|
console.error(reason);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
|
@ -0,0 +1,184 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
/* eslint-disable no-await-in-loop, no-console */
|
||||||
|
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
|
import {
|
||||||
|
StorageState,
|
||||||
|
EnvelopeType,
|
||||||
|
ReceiptType,
|
||||||
|
} from '@signalapp/mock-server';
|
||||||
|
import {
|
||||||
|
Bootstrap,
|
||||||
|
debug,
|
||||||
|
stats,
|
||||||
|
RUN_COUNT,
|
||||||
|
GROUP_SIZE,
|
||||||
|
DISCARD_COUNT,
|
||||||
|
} from './fixtures';
|
||||||
|
|
||||||
|
const CONVERSATION_SIZE = 500; // messages
|
||||||
|
const LAST_MESSAGE = 'start sending messages now';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const bootstrap = new Bootstrap({
|
||||||
|
benchmark: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await bootstrap.init();
|
||||||
|
|
||||||
|
const { contacts, phone } = bootstrap;
|
||||||
|
|
||||||
|
const members = [...contacts].slice(0, GROUP_SIZE);
|
||||||
|
|
||||||
|
const group = await phone.createGroup({
|
||||||
|
title: 'Mock Group',
|
||||||
|
members: [phone, ...members],
|
||||||
|
});
|
||||||
|
|
||||||
|
await phone.setStorageState(
|
||||||
|
StorageState.getEmpty()
|
||||||
|
.addGroup(group, { whitelisted: true })
|
||||||
|
.pinGroup(group)
|
||||||
|
);
|
||||||
|
|
||||||
|
const app = await bootstrap.link();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { server, desktop } = bootstrap;
|
||||||
|
const [first] = members;
|
||||||
|
|
||||||
|
const messages = new Array<Buffer>();
|
||||||
|
debug('encrypting');
|
||||||
|
// Fill left pane
|
||||||
|
for (const contact of members.slice().reverse()) {
|
||||||
|
const messageTimestamp = bootstrap.getTimestamp();
|
||||||
|
|
||||||
|
messages.push(
|
||||||
|
await contact.encryptText(
|
||||||
|
desktop,
|
||||||
|
`hello from: ${contact.profileName}`,
|
||||||
|
{
|
||||||
|
timestamp: messageTimestamp,
|
||||||
|
sealed: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
messages.push(
|
||||||
|
await phone.encryptSyncRead(desktop, {
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
senderUUID: contact.device.uuid,
|
||||||
|
timestamp: messageTimestamp,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill group
|
||||||
|
for (let i = 0; i < CONVERSATION_SIZE; i += 1) {
|
||||||
|
const contact = members[i % members.length];
|
||||||
|
const messageTimestamp = bootstrap.getTimestamp();
|
||||||
|
|
||||||
|
const isLast = i === CONVERSATION_SIZE - 1;
|
||||||
|
|
||||||
|
messages.push(
|
||||||
|
await contact.encryptText(
|
||||||
|
desktop,
|
||||||
|
isLast ? LAST_MESSAGE : `#${i} from: ${contact.profileName}`,
|
||||||
|
{
|
||||||
|
timestamp: messageTimestamp,
|
||||||
|
sealed: true,
|
||||||
|
group,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
messages.push(
|
||||||
|
await phone.encryptSyncRead(desktop, {
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
senderUUID: contact.device.uuid,
|
||||||
|
timestamp: messageTimestamp,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
debug('encrypted');
|
||||||
|
|
||||||
|
await Promise.all(messages.map(message => server.send(desktop, message)));
|
||||||
|
|
||||||
|
const window = await app.getWindow();
|
||||||
|
|
||||||
|
debug('opening conversation');
|
||||||
|
{
|
||||||
|
const leftPane = window.locator('.left-pane-wrapper');
|
||||||
|
|
||||||
|
const item = leftPane.locator(
|
||||||
|
'_react=BaseConversationListItem' +
|
||||||
|
`[title = ${JSON.stringify(group.title)}]` +
|
||||||
|
`>> ${JSON.stringify(LAST_MESSAGE)}`
|
||||||
|
);
|
||||||
|
await item.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeline = window.locator(
|
||||||
|
'.timeline-wrapper, .ConversationView__template .react-wrapper'
|
||||||
|
);
|
||||||
|
|
||||||
|
const deltaList = new Array<number>();
|
||||||
|
for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) {
|
||||||
|
debug('finding composition input and clicking it');
|
||||||
|
const composeArea = window.locator(
|
||||||
|
'.composition-area-wrapper, ' +
|
||||||
|
'.ConversationView__template .react-wrapper'
|
||||||
|
);
|
||||||
|
|
||||||
|
const input = composeArea.locator('_react=CompositionInput');
|
||||||
|
|
||||||
|
debug('entering message text');
|
||||||
|
await input.type(`my message ${runId}`);
|
||||||
|
await input.press('Enter');
|
||||||
|
|
||||||
|
debug('waiting for message on server side');
|
||||||
|
const { body, source, envelopeType } = await first.waitForMessage();
|
||||||
|
assert.strictEqual(body, `my message ${runId}`);
|
||||||
|
assert.strictEqual(source, desktop);
|
||||||
|
assert.strictEqual(envelopeType, EnvelopeType.SenderKey);
|
||||||
|
|
||||||
|
debug('waiting for timing from the app');
|
||||||
|
const { timestamp, delta } = await app.waitForMessageSend();
|
||||||
|
|
||||||
|
debug('sending delivery receipts');
|
||||||
|
const delivery = await first.encryptReceipt(desktop, {
|
||||||
|
timestamp: timestamp + 1,
|
||||||
|
messageTimestamps: [timestamp],
|
||||||
|
type: ReceiptType.Delivery,
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.send(desktop, delivery);
|
||||||
|
|
||||||
|
debug('waiting for message state change');
|
||||||
|
const message = timeline.locator(
|
||||||
|
`_react=Message[timestamp = ${timestamp}][status = "delivered"]`
|
||||||
|
);
|
||||||
|
await message.waitFor();
|
||||||
|
|
||||||
|
if (runId >= DISCARD_COUNT) {
|
||||||
|
deltaList.push(delta);
|
||||||
|
console.log('run=%d info=%j', runId - DISCARD_COUNT, { delta });
|
||||||
|
} else {
|
||||||
|
console.log('discarded=%d info=%j', runId, { delta });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) });
|
||||||
|
} finally {
|
||||||
|
await app.close();
|
||||||
|
await bootstrap.teardown();
|
||||||
|
}
|
||||||
|
})();
|
|
@ -0,0 +1,135 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
/* eslint-disable no-await-in-loop, no-console */
|
||||||
|
|
||||||
|
import assert from 'assert';
|
||||||
|
|
||||||
|
import { ReceiptType } from '@signalapp/mock-server';
|
||||||
|
|
||||||
|
import { Bootstrap, debug, stats, RUN_COUNT, DISCARD_COUNT } from './fixtures';
|
||||||
|
|
||||||
|
const CONVERSATION_SIZE = 500; // messages
|
||||||
|
|
||||||
|
const LAST_MESSAGE = 'start sending messages now';
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const bootstrap = new Bootstrap({
|
||||||
|
benchmark: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await bootstrap.init();
|
||||||
|
const app = await bootstrap.link();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { server, contacts, phone, desktop } = bootstrap;
|
||||||
|
|
||||||
|
const [first] = contacts;
|
||||||
|
|
||||||
|
const messages = new Array<Buffer>();
|
||||||
|
debug('encrypting');
|
||||||
|
// Note: make it so that we receive the latest message from the first
|
||||||
|
// contact.
|
||||||
|
for (const contact of contacts.slice().reverse()) {
|
||||||
|
let count = 1;
|
||||||
|
if (contact === first) {
|
||||||
|
count = CONVERSATION_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i += 1) {
|
||||||
|
const messageTimestamp = bootstrap.getTimestamp();
|
||||||
|
|
||||||
|
const isLast = i === count - 1;
|
||||||
|
|
||||||
|
messages.push(
|
||||||
|
await contact.encryptText(
|
||||||
|
desktop,
|
||||||
|
isLast ? LAST_MESSAGE : `#${i} from: ${contact.profileName}`,
|
||||||
|
{
|
||||||
|
timestamp: messageTimestamp,
|
||||||
|
sealed: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
messages.push(
|
||||||
|
await phone.encryptSyncRead(desktop, {
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
senderUUID: contact.device.uuid,
|
||||||
|
timestamp: messageTimestamp,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(messages.map(message => server.send(desktop, message)));
|
||||||
|
|
||||||
|
const window = await app.getWindow();
|
||||||
|
|
||||||
|
debug('opening conversation');
|
||||||
|
{
|
||||||
|
const leftPane = window.locator('.left-pane-wrapper');
|
||||||
|
const item = leftPane.locator(
|
||||||
|
'_react=BaseConversationListItem' +
|
||||||
|
`[title = ${JSON.stringify(first.profileName)}]` +
|
||||||
|
`>> ${JSON.stringify(LAST_MESSAGE)}`
|
||||||
|
);
|
||||||
|
await item.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeline = window.locator(
|
||||||
|
'.timeline-wrapper, .ConversationView__template .react-wrapper'
|
||||||
|
);
|
||||||
|
|
||||||
|
const deltaList = new Array<number>();
|
||||||
|
for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) {
|
||||||
|
debug('finding composition input and clicking it');
|
||||||
|
const composeArea = window.locator(
|
||||||
|
'.composition-area-wrapper, ' +
|
||||||
|
'.ConversationView__template .react-wrapper'
|
||||||
|
);
|
||||||
|
const input = composeArea.locator('_react=CompositionInput');
|
||||||
|
|
||||||
|
debug('entering message text');
|
||||||
|
await input.type(`my message ${runId}`);
|
||||||
|
await input.press('Enter');
|
||||||
|
|
||||||
|
debug('waiting for message on server side');
|
||||||
|
const { body, source } = await first.waitForMessage();
|
||||||
|
assert.strictEqual(body, `my message ${runId}`);
|
||||||
|
assert.strictEqual(source, desktop);
|
||||||
|
|
||||||
|
debug('waiting for timing from the app');
|
||||||
|
const { timestamp, delta } = await app.waitForMessageSend();
|
||||||
|
|
||||||
|
debug('sending delivery receipt');
|
||||||
|
const delivery = await first.encryptReceipt(desktop, {
|
||||||
|
timestamp: timestamp + 1,
|
||||||
|
messageTimestamps: [timestamp],
|
||||||
|
type: ReceiptType.Delivery,
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.send(desktop, delivery);
|
||||||
|
|
||||||
|
debug('waiting for message state change');
|
||||||
|
const message = timeline.locator(
|
||||||
|
`_react=Message[timestamp = ${timestamp}][status = "delivered"]`
|
||||||
|
);
|
||||||
|
await message.waitFor();
|
||||||
|
|
||||||
|
if (runId >= DISCARD_COUNT) {
|
||||||
|
deltaList.push(delta);
|
||||||
|
console.log('run=%d info=%j', runId - DISCARD_COUNT, { delta });
|
||||||
|
} else {
|
||||||
|
console.log('discarded=%d info=%j', runId, { delta });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) });
|
||||||
|
} finally {
|
||||||
|
await app.close();
|
||||||
|
await bootstrap.teardown();
|
||||||
|
}
|
||||||
|
})();
|
|
@ -0,0 +1,133 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
/* eslint-disable no-await-in-loop, no-console */
|
||||||
|
|
||||||
|
import { ReceiptType } from '@signalapp/mock-server';
|
||||||
|
|
||||||
|
import { debug, Bootstrap, stats, RUN_COUNT } from './fixtures';
|
||||||
|
|
||||||
|
const MESSAGE_BATCH_SIZE = 1000; // messages
|
||||||
|
|
||||||
|
const ENABLE_RECEIPTS = Boolean(process.env.ENABLE_RECEIPTS);
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const bootstrap = new Bootstrap({
|
||||||
|
benchmark: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await bootstrap.init();
|
||||||
|
await bootstrap.linkAndClose();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { server, contacts, phone, desktop } = bootstrap;
|
||||||
|
|
||||||
|
const messagesPerSec = new Array<number>();
|
||||||
|
|
||||||
|
for (let runId = 0; runId < RUN_COUNT; runId += 1) {
|
||||||
|
// Generate messages
|
||||||
|
const messagePromises = new Array<Promise<Buffer>>();
|
||||||
|
debug('started generating messages');
|
||||||
|
|
||||||
|
for (let i = 0; i < MESSAGE_BATCH_SIZE; i += 1) {
|
||||||
|
const contact = contacts[Math.floor(i / 2) % contacts.length];
|
||||||
|
const direction = i % 2 ? 'message' : 'reply';
|
||||||
|
|
||||||
|
const messageTimestamp = bootstrap.getTimestamp();
|
||||||
|
|
||||||
|
if (direction === 'message') {
|
||||||
|
messagePromises.push(
|
||||||
|
contact.encryptText(
|
||||||
|
desktop,
|
||||||
|
`Ping from mock server ${i + 1} / ${MESSAGE_BATCH_SIZE}`,
|
||||||
|
{
|
||||||
|
timestamp: messageTimestamp,
|
||||||
|
sealed: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ENABLE_RECEIPTS) {
|
||||||
|
messagePromises.push(
|
||||||
|
phone.encryptSyncRead(desktop, {
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
senderUUID: contact.device.uuid,
|
||||||
|
timestamp: messageTimestamp,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
messagePromises.push(
|
||||||
|
phone.encryptSyncSent(
|
||||||
|
desktop,
|
||||||
|
`Pong from mock server ${i + 1} / ${MESSAGE_BATCH_SIZE}`,
|
||||||
|
{
|
||||||
|
timestamp: messageTimestamp,
|
||||||
|
destinationUUID: contact.device.uuid,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ENABLE_RECEIPTS) {
|
||||||
|
messagePromises.push(
|
||||||
|
contact.encryptReceipt(desktop, {
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
messageTimestamps: [messageTimestamp],
|
||||||
|
type: ReceiptType.Delivery,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
messagePromises.push(
|
||||||
|
contact.encryptReceipt(desktop, {
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
messageTimestamps: [messageTimestamp],
|
||||||
|
type: ReceiptType.Read,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('ended generating messages');
|
||||||
|
|
||||||
|
const messages = await Promise.all(messagePromises);
|
||||||
|
|
||||||
|
// Open the flood gates
|
||||||
|
{
|
||||||
|
debug('got synced, sending messages');
|
||||||
|
|
||||||
|
// Queue all messages
|
||||||
|
const queue = async (): Promise<void> => {
|
||||||
|
await Promise.all(
|
||||||
|
messages.map(message => {
|
||||||
|
return server.send(desktop, message);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const run = async (): Promise<void> => {
|
||||||
|
const app = await bootstrap.startApp();
|
||||||
|
const appLoadedInfo = await app.waitUntilLoaded();
|
||||||
|
|
||||||
|
console.log('run=%d info=%j', runId, appLoadedInfo);
|
||||||
|
|
||||||
|
messagesPerSec.push(appLoadedInfo.messagesPerSec);
|
||||||
|
|
||||||
|
await app.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
await Promise.all([queue(), run()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute human-readable statistics
|
||||||
|
if (messagesPerSec.length !== 0) {
|
||||||
|
console.log('stats info=%j', { messagesPerSec: stats(messagesPerSec) });
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
await bootstrap.teardown();
|
||||||
|
}
|
||||||
|
})();
|
|
@ -0,0 +1,286 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import assert from 'assert';
|
||||||
|
import fs from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
import os from 'os';
|
||||||
|
import createDebug from 'debug';
|
||||||
|
|
||||||
|
import type { Device, PrimaryDevice } from '@signalapp/mock-server';
|
||||||
|
import { Server, loadCertificates } from '@signalapp/mock-server';
|
||||||
|
import { App } from './playwright';
|
||||||
|
import * as durations from '../util/durations';
|
||||||
|
|
||||||
|
const debug = createDebug('mock:bootstrap');
|
||||||
|
|
||||||
|
const ELECTRON = path.join(
|
||||||
|
__dirname,
|
||||||
|
'..',
|
||||||
|
'..',
|
||||||
|
'node_modules',
|
||||||
|
'.bin',
|
||||||
|
'electron'
|
||||||
|
);
|
||||||
|
const CI_SCRIPT = path.join(__dirname, '..', '..', 'ci.js');
|
||||||
|
|
||||||
|
const CLOSE_TIMEOUT = 10 * 1000;
|
||||||
|
|
||||||
|
const CONTACT_FIRST_NAMES = [
|
||||||
|
'Alice',
|
||||||
|
'Bob',
|
||||||
|
'Charlie',
|
||||||
|
'Paul',
|
||||||
|
'Steve',
|
||||||
|
'William',
|
||||||
|
];
|
||||||
|
const CONTACT_LAST_NAMES = [
|
||||||
|
'Smith',
|
||||||
|
'Brown',
|
||||||
|
'Jones',
|
||||||
|
'Miller',
|
||||||
|
'Davis',
|
||||||
|
'Lopez',
|
||||||
|
'Gonazales',
|
||||||
|
];
|
||||||
|
|
||||||
|
const CONTACT_NAMES = new Array<string>();
|
||||||
|
for (const firstName of CONTACT_FIRST_NAMES) {
|
||||||
|
for (const lastName of CONTACT_LAST_NAMES) {
|
||||||
|
CONTACT_NAMES.push(`${firstName} ${lastName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_CONTACTS = CONTACT_NAMES.length;
|
||||||
|
|
||||||
|
export type BootstrapOptions = Readonly<{
|
||||||
|
extraConfig?: Record<string, unknown>;
|
||||||
|
benchmark?: boolean;
|
||||||
|
|
||||||
|
linkedDevices?: number;
|
||||||
|
contactCount?: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
type BootstrapInternalOptions = Pick<BootstrapOptions, 'extraConfig'> &
|
||||||
|
Readonly<{
|
||||||
|
benchmark: boolean;
|
||||||
|
linkedDevices: number;
|
||||||
|
contactCount: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Bootstrap is a class that prepares mock server and desktop for running
|
||||||
|
// tests/benchmarks.
|
||||||
|
//
|
||||||
|
// In general, the usage pattern is:
|
||||||
|
//
|
||||||
|
// const bootstrap = new Bootstrap();
|
||||||
|
// await bootstrap.init();
|
||||||
|
// const app = await bootstrap.link();
|
||||||
|
// await bootstrap.teardown();
|
||||||
|
//
|
||||||
|
// Once initialized `bootstrap` variable will have following useful properties:
|
||||||
|
//
|
||||||
|
// - `server` - a mock server instance
|
||||||
|
// - `desktop` - a linked device representing currently running desktop instance
|
||||||
|
// - `phone` - a primary device representing desktop's primary
|
||||||
|
// - `contacts` - a list of primary devices for contacts that are synced over
|
||||||
|
// through contact sync
|
||||||
|
//
|
||||||
|
// `bootstrap.getTimestamp()` could be used to generate consecutive timestamp
|
||||||
|
// for sending messages.
|
||||||
|
//
|
||||||
|
// All phone numbers and uuids for all contacts and ourselves are random and not
|
||||||
|
// the same between different test runs.
|
||||||
|
//
|
||||||
|
export class Bootstrap {
|
||||||
|
public readonly server = new Server();
|
||||||
|
|
||||||
|
private readonly options: BootstrapInternalOptions;
|
||||||
|
private privContacts?: ReadonlyArray<PrimaryDevice>;
|
||||||
|
private privPhone?: PrimaryDevice;
|
||||||
|
private privDesktop?: Device;
|
||||||
|
private storagePath?: string;
|
||||||
|
private timestamp: number = Date.now() - durations.MONTH;
|
||||||
|
|
||||||
|
constructor(options: BootstrapOptions = {}) {
|
||||||
|
this.options = {
|
||||||
|
linkedDevices: 5,
|
||||||
|
contactCount: MAX_CONTACTS,
|
||||||
|
benchmark: false,
|
||||||
|
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert(this.options.contactCount <= MAX_CONTACTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async init(): Promise<void> {
|
||||||
|
debug('initializing');
|
||||||
|
|
||||||
|
await this.server.listen(0);
|
||||||
|
|
||||||
|
const { port } = this.server.address();
|
||||||
|
debug('started server on port=%d', port);
|
||||||
|
|
||||||
|
const contactNames = CONTACT_NAMES.slice(0, this.options.contactCount);
|
||||||
|
|
||||||
|
this.privContacts = await Promise.all(
|
||||||
|
contactNames.map(async profileName => {
|
||||||
|
const primary = await this.server.createPrimaryDevice({ profileName });
|
||||||
|
|
||||||
|
for (let i = 0; i < this.options.linkedDevices; i += 1) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await this.server.createSecondaryDevice(primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
return primary;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.privPhone = await this.server.createPrimaryDevice({
|
||||||
|
profileName: 'Mock',
|
||||||
|
contacts: this.contacts,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.storagePath = await fs.mkdtemp(path.join(os.tmpdir(), 'mock-signal-'));
|
||||||
|
|
||||||
|
debug('setting storage path=%j', this.storagePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async teardown(): Promise<void> {
|
||||||
|
debug('tearing down');
|
||||||
|
|
||||||
|
await Promise.race([
|
||||||
|
this.storagePath
|
||||||
|
? fs.rm(this.storagePath, { recursive: true })
|
||||||
|
: Promise.resolve(),
|
||||||
|
this.server.close(),
|
||||||
|
new Promise(resolve => setTimeout(resolve, CLOSE_TIMEOUT).unref()),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async link(): Promise<App> {
|
||||||
|
debug('linking');
|
||||||
|
|
||||||
|
const app = await this.startApp();
|
||||||
|
|
||||||
|
const provision = await this.server.waitForProvision();
|
||||||
|
|
||||||
|
const provisionURL = await app.waitForProvisionURL();
|
||||||
|
|
||||||
|
this.privDesktop = await provision.complete({
|
||||||
|
provisionURL,
|
||||||
|
primaryDevice: this.phone,
|
||||||
|
});
|
||||||
|
|
||||||
|
debug('new desktop device %j', this.desktop.debugId);
|
||||||
|
|
||||||
|
const desktopKey = await this.desktop.popSingleUseKey();
|
||||||
|
await this.phone.addSingleUseKey(this.desktop, desktopKey);
|
||||||
|
|
||||||
|
for (const contact of this.contacts) {
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
const contactKey = await this.desktop.popSingleUseKey();
|
||||||
|
// eslint-disable-next-line no-await-in-loop
|
||||||
|
await contact.addSingleUseKey(this.desktop, contactKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.phone.waitForSync(this.desktop);
|
||||||
|
this.phone.resetSyncState(this.desktop);
|
||||||
|
|
||||||
|
debug('synced with %j', this.desktop.debugId);
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async linkAndClose(): Promise<void> {
|
||||||
|
const app = await this.link();
|
||||||
|
|
||||||
|
debug('closing the app after link');
|
||||||
|
await app.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async startApp(): Promise<App> {
|
||||||
|
assert(
|
||||||
|
this.storagePath !== undefined,
|
||||||
|
'Bootstrap has to be initialized first, see: bootstrap.init()'
|
||||||
|
);
|
||||||
|
|
||||||
|
debug('starting the app');
|
||||||
|
|
||||||
|
const { port } = this.server.address();
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
main: ELECTRON,
|
||||||
|
args: [CI_SCRIPT],
|
||||||
|
config: await this.generateConfig(port),
|
||||||
|
});
|
||||||
|
|
||||||
|
await app.start();
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTimestamp(): number {
|
||||||
|
const result = this.timestamp;
|
||||||
|
this.timestamp += 1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Getters
|
||||||
|
//
|
||||||
|
|
||||||
|
public get phone(): PrimaryDevice {
|
||||||
|
assert(
|
||||||
|
this.privPhone,
|
||||||
|
'Bootstrap has to be initialized first, see: bootstrap.init()'
|
||||||
|
);
|
||||||
|
return this.privPhone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get desktop(): Device {
|
||||||
|
assert(
|
||||||
|
this.privDesktop,
|
||||||
|
'Bootstrap has to be linked first, see: bootstrap.link()'
|
||||||
|
);
|
||||||
|
return this.privDesktop;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get contacts(): ReadonlyArray<PrimaryDevice> {
|
||||||
|
assert(
|
||||||
|
this.privContacts,
|
||||||
|
'Bootstrap has to be initialized first, see: bootstrap.init()'
|
||||||
|
);
|
||||||
|
return this.privContacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Private
|
||||||
|
//
|
||||||
|
|
||||||
|
private async generateConfig(port: number): Promise<string> {
|
||||||
|
const url = `https://mock.signal.org:${port}`;
|
||||||
|
return JSON.stringify({
|
||||||
|
...(await loadCertificates()),
|
||||||
|
|
||||||
|
forcePreloadBundle: this.options.benchmark,
|
||||||
|
enableCI: true,
|
||||||
|
|
||||||
|
buildExpiration: Date.now() + durations.MONTH,
|
||||||
|
storagePath: this.storagePath,
|
||||||
|
storageProfile: 'mock',
|
||||||
|
serverUrl: url,
|
||||||
|
storageUrl: url,
|
||||||
|
directoryUrl: url,
|
||||||
|
cdn: {
|
||||||
|
'0': url,
|
||||||
|
'2': url,
|
||||||
|
},
|
||||||
|
updatesEnabled: false,
|
||||||
|
|
||||||
|
...this.options.extraConfig,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { ElectronApplication, Page } from 'playwright';
|
||||||
|
import { _electron as electron } from 'playwright';
|
||||||
|
|
||||||
|
export type AppLoadedInfoType = Readonly<{
|
||||||
|
loadTime: number;
|
||||||
|
messagesPerSec: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type MessageSendInfoType = Readonly<{
|
||||||
|
timestamp: number;
|
||||||
|
delta: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type ConversationOpenInfoType = Readonly<{
|
||||||
|
delta: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type AppOptionsType = Readonly<{
|
||||||
|
main: string;
|
||||||
|
args: ReadonlyArray<string>;
|
||||||
|
config: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export class App {
|
||||||
|
private privApp: ElectronApplication | undefined;
|
||||||
|
|
||||||
|
constructor(private readonly options: AppOptionsType) {}
|
||||||
|
|
||||||
|
public async start(): Promise<void> {
|
||||||
|
this.privApp = await electron.launch({
|
||||||
|
executablePath: this.options.main,
|
||||||
|
args: this.options.args.slice(),
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
SIGNAL_CI_CONFIG: this.options.config,
|
||||||
|
},
|
||||||
|
locale: 'en',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async waitForProvisionURL(): Promise<string> {
|
||||||
|
return this.waitForEvent('provisioning-url');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async waitUntilLoaded(): Promise<AppLoadedInfoType> {
|
||||||
|
return this.waitForEvent('app-loaded');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async waitForMessageSend(): Promise<MessageSendInfoType> {
|
||||||
|
return this.waitForEvent('message:send-complete');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async waitForConversationOpen(): Promise<ConversationOpenInfoType> {
|
||||||
|
return this.waitForEvent('conversation:open');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async close(): Promise<void> {
|
||||||
|
await this.app.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getWindow(): Promise<Page> {
|
||||||
|
return this.app.firstWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async waitForEvent<T>(event: string): Promise<T> {
|
||||||
|
const window = await this.getWindow();
|
||||||
|
|
||||||
|
const result = await window.evaluate(
|
||||||
|
`window.CI.waitForEvent(${JSON.stringify(event)})`
|
||||||
|
);
|
||||||
|
|
||||||
|
return result as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get app(): ElectronApplication {
|
||||||
|
if (!this.privApp) {
|
||||||
|
throw new Error('Call ElectronWrap.start() first');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.privApp;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
|
||||||
|
import * as durations from '../../util/durations';
|
||||||
|
import type { App, Bootstrap } from './fixtures';
|
||||||
|
import { initStorage, debug } from './fixtures';
|
||||||
|
|
||||||
|
describe('storage service', function needsName() {
|
||||||
|
this.timeout(durations.MINUTE);
|
||||||
|
|
||||||
|
let bootstrap: Bootstrap;
|
||||||
|
let app: App;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
({ bootstrap, app } = await initStorage());
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await app.close();
|
||||||
|
await bootstrap.teardown();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should archive/unarchive contacts', async () => {
|
||||||
|
const { phone, contacts } = bootstrap;
|
||||||
|
const [firstContact] = contacts;
|
||||||
|
|
||||||
|
const window = await app.getWindow();
|
||||||
|
|
||||||
|
const leftPane = window.locator('.left-pane-wrapper');
|
||||||
|
const conversationStack = window.locator('.conversation-stack');
|
||||||
|
|
||||||
|
debug('archiving contact');
|
||||||
|
{
|
||||||
|
const state = await phone.expectStorageState('consistency check');
|
||||||
|
|
||||||
|
await phone.setStorageState(
|
||||||
|
state
|
||||||
|
.updateContact(firstContact, { archived: true })
|
||||||
|
.unpin(firstContact)
|
||||||
|
);
|
||||||
|
await phone.sendFetchStorage({
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await leftPane
|
||||||
|
.locator(
|
||||||
|
'_react=ConversationListItem' +
|
||||||
|
`[title = ${JSON.stringify(firstContact.profileName)}]`
|
||||||
|
)
|
||||||
|
.waitFor({ state: 'hidden' });
|
||||||
|
|
||||||
|
await leftPane
|
||||||
|
.locator('button.module-conversation-list__item--archive-button')
|
||||||
|
.waitFor();
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('unarchiving pinned contact');
|
||||||
|
{
|
||||||
|
const state = await phone.expectStorageState('consistency check');
|
||||||
|
|
||||||
|
await phone.setStorageState(
|
||||||
|
state.updateContact(firstContact, { archived: false }).pin(firstContact)
|
||||||
|
);
|
||||||
|
await phone.sendFetchStorage({
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await leftPane
|
||||||
|
.locator(
|
||||||
|
'_react=ConversationListItem' +
|
||||||
|
'[isPinned = true]' +
|
||||||
|
`[title = ${JSON.stringify(firstContact.profileName)}]`
|
||||||
|
)
|
||||||
|
.waitFor();
|
||||||
|
|
||||||
|
await leftPane
|
||||||
|
.locator('button.module-conversation-list__item--archive-button')
|
||||||
|
.waitFor({ state: 'hidden' });
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('archive pinned contact in the app');
|
||||||
|
{
|
||||||
|
const state = await phone.expectStorageState('consistency check');
|
||||||
|
|
||||||
|
await leftPane
|
||||||
|
.locator(
|
||||||
|
'_react=ConversationListItem' +
|
||||||
|
`[title = ${JSON.stringify(firstContact.profileName)}]`
|
||||||
|
)
|
||||||
|
.click();
|
||||||
|
|
||||||
|
const moreButton = conversationStack.locator(
|
||||||
|
'button.module-ConversationHeader__button--more'
|
||||||
|
);
|
||||||
|
await moreButton.click();
|
||||||
|
|
||||||
|
const archiveButton = conversationStack.locator(
|
||||||
|
'.react-contextmenu-item >> "Archive"'
|
||||||
|
);
|
||||||
|
await archiveButton.click();
|
||||||
|
|
||||||
|
const newState = await phone.waitForStorageState({
|
||||||
|
after: state,
|
||||||
|
});
|
||||||
|
assert.ok(!(await newState.isPinned(firstContact)), 'contact not pinned');
|
||||||
|
const record = await newState.getContact(firstContact);
|
||||||
|
assert.ok(record, 'contact record not found');
|
||||||
|
assert.ok(record?.archived, 'contact archived');
|
||||||
|
|
||||||
|
// AccountRecord + ContactRecord
|
||||||
|
const { added, removed } = newState.diff(state);
|
||||||
|
assert.strictEqual(added.length, 2, 'only two records must be added');
|
||||||
|
assert.strictEqual(removed.length, 2, 'only two records must be removed');
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('Verifying the final manifest version');
|
||||||
|
const finalState = await phone.expectStorageState('consistency check');
|
||||||
|
|
||||||
|
assert.strictEqual(finalState.version, 4);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import createDebug from 'debug';
|
||||||
|
import type { Group, PrimaryDevice } from '@signalapp/mock-server';
|
||||||
|
import { StorageState, Proto } from '@signalapp/mock-server';
|
||||||
|
import { App } from '../playwright';
|
||||||
|
import { Bootstrap } from '../bootstrap';
|
||||||
|
|
||||||
|
export const debug = createDebug('mock:test-storage');
|
||||||
|
|
||||||
|
export { App, Bootstrap };
|
||||||
|
|
||||||
|
const GROUP_SIZE = 8;
|
||||||
|
|
||||||
|
export type InitStorageResultType = Readonly<{
|
||||||
|
bootstrap: Bootstrap;
|
||||||
|
app: App;
|
||||||
|
group: Group;
|
||||||
|
members: Array<PrimaryDevice>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
//
|
||||||
|
// This function creates an initial storage service state that includes:
|
||||||
|
//
|
||||||
|
// - All contacts from contact sync (first contact pinned)
|
||||||
|
// - A pinned group with GROUP_SIZE members (from the contacts)
|
||||||
|
// - Account with e164 and profileKey
|
||||||
|
//
|
||||||
|
// In addition to above, this function will queue one incoming message in the
|
||||||
|
// group, and one for the first contact (so that both will appear in the left
|
||||||
|
// pane).
|
||||||
|
export async function initStorage(): Promise<InitStorageResultType> {
|
||||||
|
// Creates primary device, contacts
|
||||||
|
const bootstrap = new Bootstrap();
|
||||||
|
|
||||||
|
await bootstrap.init();
|
||||||
|
|
||||||
|
// Populate storage service
|
||||||
|
const { contacts, phone } = bootstrap;
|
||||||
|
|
||||||
|
const [firstContact] = contacts;
|
||||||
|
|
||||||
|
const members = [...contacts].slice(0, GROUP_SIZE);
|
||||||
|
|
||||||
|
const group = await phone.createGroup({
|
||||||
|
title: 'Mock Group',
|
||||||
|
members: [phone, ...members],
|
||||||
|
});
|
||||||
|
|
||||||
|
let state = StorageState.getEmpty();
|
||||||
|
|
||||||
|
state = state.updateAccount({
|
||||||
|
profileKey: phone.profileKey.serialize(),
|
||||||
|
e164: phone.device.number,
|
||||||
|
});
|
||||||
|
|
||||||
|
state = state
|
||||||
|
.addGroup(group, {
|
||||||
|
whitelisted: true,
|
||||||
|
})
|
||||||
|
.pinGroup(group);
|
||||||
|
|
||||||
|
for (const contact of contacts) {
|
||||||
|
state = state.addContact(contact, {
|
||||||
|
identityState: Proto.ContactRecord.IdentityState.VERIFIED,
|
||||||
|
whitelisted: true,
|
||||||
|
|
||||||
|
identityKey: contact.publicKey.serialize(),
|
||||||
|
profileKey: contact.profileKey.serialize(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
state = state.pin(firstContact);
|
||||||
|
|
||||||
|
await phone.setStorageState(state);
|
||||||
|
|
||||||
|
// Link new device
|
||||||
|
const app = await bootstrap.link();
|
||||||
|
|
||||||
|
const { desktop } = bootstrap;
|
||||||
|
|
||||||
|
// Send a message to the group and the first contact
|
||||||
|
const contactSend = contacts[0].sendText(desktop, 'hello from contact', {
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
sealed: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const groupSend = members[0].sendText(desktop, 'hello in group', {
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
sealed: true,
|
||||||
|
group,
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([contactSend, groupSend]);
|
||||||
|
|
||||||
|
return { bootstrap, app, group, members };
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
|
||||||
|
import * as durations from '../../util/durations';
|
||||||
|
import type { App, Bootstrap } from './fixtures';
|
||||||
|
import { initStorage, debug } from './fixtures';
|
||||||
|
|
||||||
|
describe('storage service', function needsName() {
|
||||||
|
this.timeout(durations.MINUTE);
|
||||||
|
|
||||||
|
let bootstrap: Bootstrap;
|
||||||
|
let app: App;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
({ bootstrap, app } = await initStorage());
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await app.close();
|
||||||
|
await bootstrap.teardown();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle message request state changes', async () => {
|
||||||
|
const { phone, desktop, server } = bootstrap;
|
||||||
|
|
||||||
|
debug('Creating stranger');
|
||||||
|
const stranger = await server.createPrimaryDevice({
|
||||||
|
profileName: 'Mysterious Stranger',
|
||||||
|
});
|
||||||
|
|
||||||
|
const ourKey = await desktop.popSingleUseKey();
|
||||||
|
await stranger.addSingleUseKey(desktop, ourKey);
|
||||||
|
|
||||||
|
debug('Sending a message from a stranger');
|
||||||
|
await stranger.sendText(desktop, 'Hello!', {
|
||||||
|
withProfileKey: true,
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const window = await app.getWindow();
|
||||||
|
|
||||||
|
const leftPane = window.locator('.left-pane-wrapper');
|
||||||
|
const conversationStack = window.locator('.conversation-stack');
|
||||||
|
|
||||||
|
debug('Opening conversation with a stranger');
|
||||||
|
await leftPane
|
||||||
|
.locator(
|
||||||
|
'_react=ConversationListItem' +
|
||||||
|
`[title = ${JSON.stringify(stranger.profileName)}]`
|
||||||
|
)
|
||||||
|
.click();
|
||||||
|
|
||||||
|
const initialState = await phone.expectStorageState('initial state');
|
||||||
|
assert.strictEqual(initialState.version, 1);
|
||||||
|
assert.isUndefined(initialState.getContact(stranger));
|
||||||
|
|
||||||
|
debug('Accept conversation from a stranger');
|
||||||
|
await conversationStack
|
||||||
|
.locator('.module-message-request-actions button >> "Accept"')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
debug('Verify that storage state was updated');
|
||||||
|
{
|
||||||
|
const nextState = await phone.waitForStorageState({
|
||||||
|
after: initialState,
|
||||||
|
});
|
||||||
|
assert.strictEqual(nextState.version, 2);
|
||||||
|
assert.isTrue(nextState.getContact(stranger)?.whitelisted);
|
||||||
|
|
||||||
|
// ContactRecord
|
||||||
|
const { added, removed } = nextState.diff(initialState);
|
||||||
|
assert.strictEqual(added.length, 1, 'only one record must be added');
|
||||||
|
assert.strictEqual(removed.length, 0, 'no records should be removed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stranger should receive our profile key
|
||||||
|
{
|
||||||
|
const { body, source, dataMessage } = await stranger.waitForMessage();
|
||||||
|
assert.strictEqual(body, '', 'profile key message has no body');
|
||||||
|
assert.strictEqual(
|
||||||
|
source,
|
||||||
|
desktop,
|
||||||
|
'profile key message has valid source'
|
||||||
|
);
|
||||||
|
assert.isTrue(
|
||||||
|
phone.profileKey
|
||||||
|
.serialize()
|
||||||
|
.equals(dataMessage.profileKey ?? new Uint8Array(0)),
|
||||||
|
'profile key message has correct profile key'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('Enter message text');
|
||||||
|
const composeArea = window.locator(
|
||||||
|
'.composition-area-wrapper, ' +
|
||||||
|
'.ConversationView__template .react-wrapper'
|
||||||
|
);
|
||||||
|
const input = composeArea.locator('_react=CompositionInput');
|
||||||
|
|
||||||
|
await input.type('hello stranger!');
|
||||||
|
await input.press('Enter');
|
||||||
|
|
||||||
|
{
|
||||||
|
const { body, source } = await stranger.waitForMessage();
|
||||||
|
assert.strictEqual(body, 'hello stranger!', 'text message has body');
|
||||||
|
assert.strictEqual(source, desktop, 'text message has valid source');
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('Verifying the final manifest version');
|
||||||
|
const finalState = await phone.expectStorageState('consistency check');
|
||||||
|
assert.strictEqual(finalState.version, 2);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,160 @@
|
||||||
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
/* eslint-disable no-await-in-loop */
|
||||||
|
|
||||||
|
import { assert } from 'chai';
|
||||||
|
|
||||||
|
import type { Group } from '@signalapp/mock-server';
|
||||||
|
|
||||||
|
import * as durations from '../../util/durations';
|
||||||
|
import type { App, Bootstrap } from './fixtures';
|
||||||
|
import { initStorage, debug } from './fixtures';
|
||||||
|
|
||||||
|
describe('storage service', function needsName() {
|
||||||
|
this.timeout(durations.MINUTE);
|
||||||
|
|
||||||
|
let bootstrap: Bootstrap;
|
||||||
|
let app: App;
|
||||||
|
let group: Group;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
({ bootstrap, app, group } = await initStorage());
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await app.close();
|
||||||
|
await bootstrap.teardown();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should pin/unpin groups', async () => {
|
||||||
|
const { phone, desktop, contacts } = bootstrap;
|
||||||
|
|
||||||
|
const window = await app.getWindow();
|
||||||
|
|
||||||
|
const leftPane = window.locator('.left-pane-wrapper');
|
||||||
|
const conversationStack = window.locator('.conversation-stack');
|
||||||
|
|
||||||
|
debug('Verifying that the group is pinned on startup');
|
||||||
|
await leftPane
|
||||||
|
.locator(
|
||||||
|
'_react=ConversationListItem' +
|
||||||
|
'[isPinned = true] ' +
|
||||||
|
`[title = ${JSON.stringify(group.title)}]`
|
||||||
|
)
|
||||||
|
.waitFor();
|
||||||
|
|
||||||
|
debug('Unpinning group via storage service');
|
||||||
|
{
|
||||||
|
const state = await phone.expectStorageState('initial state');
|
||||||
|
|
||||||
|
await phone.setStorageState(state.unpinGroup(group));
|
||||||
|
await phone.sendFetchStorage({
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await leftPane
|
||||||
|
.locator(
|
||||||
|
'_react=ConversationListItem' +
|
||||||
|
'[isPinned = false] ' +
|
||||||
|
`[title = ${JSON.stringify(group.title)}]`
|
||||||
|
)
|
||||||
|
.waitFor();
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('Pinning group in the app');
|
||||||
|
{
|
||||||
|
const state = await phone.expectStorageState('consistency check');
|
||||||
|
|
||||||
|
const convo = leftPane.locator(
|
||||||
|
'_react=ConversationListItem' +
|
||||||
|
'[isPinned = false] ' +
|
||||||
|
`[title = ${JSON.stringify(group.title)}]`
|
||||||
|
);
|
||||||
|
await convo.click();
|
||||||
|
|
||||||
|
const moreButton = conversationStack.locator(
|
||||||
|
'button.module-ConversationHeader__button--more'
|
||||||
|
);
|
||||||
|
await moreButton.click();
|
||||||
|
|
||||||
|
const pinButton = conversationStack.locator(
|
||||||
|
'.react-contextmenu-item >> "Pin Conversation"'
|
||||||
|
);
|
||||||
|
await pinButton.click();
|
||||||
|
|
||||||
|
const newState = await phone.waitForStorageState({
|
||||||
|
after: state,
|
||||||
|
});
|
||||||
|
assert.isTrue(await newState.isGroupPinned(group), 'group not pinned');
|
||||||
|
|
||||||
|
// AccountRecord
|
||||||
|
const { added, removed } = newState.diff(state);
|
||||||
|
assert.strictEqual(added.length, 1, 'only one record must be added');
|
||||||
|
assert.strictEqual(removed.length, 1, 'only one record must be removed');
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('Pinning > 4 conversations');
|
||||||
|
{
|
||||||
|
// We already have one group and first contact pinned so we need three
|
||||||
|
// more.
|
||||||
|
const toPin = contacts.slice(1, 4);
|
||||||
|
|
||||||
|
// To do that we need them to appear in the left pane, though.
|
||||||
|
for (const [i, contact] of toPin.entries()) {
|
||||||
|
const isLast = i === toPin.length - 1;
|
||||||
|
|
||||||
|
debug('sending a message to contact=%d', i);
|
||||||
|
await contact.sendText(desktop, 'Hello!', {
|
||||||
|
timestamp: bootstrap.getTimestamp(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = await phone.expectStorageState('consistency check');
|
||||||
|
|
||||||
|
debug('pinning contact=%d', i);
|
||||||
|
const convo = leftPane.locator(
|
||||||
|
'_react=ConversationListItem' +
|
||||||
|
`[title = ${JSON.stringify(contact.profileName)}]`
|
||||||
|
);
|
||||||
|
await convo.click();
|
||||||
|
|
||||||
|
const moreButton = conversationStack.locator(
|
||||||
|
'button.module-ConversationHeader__button--more'
|
||||||
|
);
|
||||||
|
await moreButton.click();
|
||||||
|
|
||||||
|
const pinButton = conversationStack.locator(
|
||||||
|
'.react-contextmenu-item >> "Pin Conversation"'
|
||||||
|
);
|
||||||
|
await pinButton.click();
|
||||||
|
|
||||||
|
if (isLast) {
|
||||||
|
// Storage state shouldn't be updated because we failed to pin
|
||||||
|
await window
|
||||||
|
.locator('.Toast >> "You can only pin up to 4 chats"')
|
||||||
|
.waitFor();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('verifying storage state change contact=%d', i);
|
||||||
|
const newState = await phone.waitForStorageState({
|
||||||
|
after: state,
|
||||||
|
});
|
||||||
|
assert.isTrue(await newState.isPinned(contact), 'contact not pinned');
|
||||||
|
|
||||||
|
// AccountRecord
|
||||||
|
const { added, removed } = newState.diff(state);
|
||||||
|
assert.strictEqual(added.length, 1, 'only one record must be added');
|
||||||
|
assert.strictEqual(
|
||||||
|
removed.length,
|
||||||
|
1,
|
||||||
|
'only one record must be removed'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug('Verifying the final manifest version');
|
||||||
|
const finalState = await phone.expectStorageState('consistency check');
|
||||||
|
|
||||||
|
assert.strictEqual(finalState.version, 5);
|
||||||
|
});
|
||||||
|
});
|
|
@ -167,6 +167,27 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2022-01-27T20:06:59.988Z"
|
"updated": "2022-01-27T20:06:59.988Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/@malept/flatpak-bundler/node_modules/debug/src/browser.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/@malept/flatpak-bundler/node_modules/debug/src/common.js",
|
||||||
|
"line": "\tcreateDebug.enable(createDebug.load());",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/@malept/flatpak-bundler/node_modules/debug/src/node.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "eval",
|
"rule": "eval",
|
||||||
"path": "node_modules/@protobufjs/inquire/index.js",
|
"path": "node_modules/@protobufjs/inquire/index.js",
|
||||||
|
@ -595,6 +616,28 @@
|
||||||
"updated": "2020-08-28T16:12:19.904Z",
|
"updated": "2020-08-28T16:12:19.904Z",
|
||||||
"reasonDetail": "isn't jquery"
|
"reasonDetail": "isn't jquery"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/agent-base/node_modules/debug/src/browser.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/agent-base/node_modules/debug/src/common.js",
|
||||||
|
"line": "\tcreateDebug.enable(createDebug.load());",
|
||||||
|
"reasonCategory": "falseMatch|testCode|exampleCode|otherUtilityCode|regexMatchedSafeCode|notExercisedByOurApp|ruleNeeded|usageTrusted",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z",
|
||||||
|
"reasonDetail": "<optional>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/agent-base/node_modules/debug/src/node.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-wrap(",
|
"rule": "jQuery-wrap(",
|
||||||
"path": "node_modules/asar/node_modules/commander/index.js",
|
"path": "node_modules/asar/node_modules/commander/index.js",
|
||||||
|
@ -1255,6 +1298,27 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2018-09-19T18:13:29.628Z"
|
"updated": "2018-09-19T18:13:29.628Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/electron-notarize/node_modules/debug/src/browser.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/electron-notarize/node_modules/debug/src/common.js",
|
||||||
|
"line": "\tcreateDebug.enable(createDebug.load());",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/electron-notarize/node_modules/debug/src/node.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-append(",
|
"rule": "jQuery-append(",
|
||||||
"path": "node_modules/enquirer/lib/prompts/autocomplete.js",
|
"path": "node_modules/enquirer/lib/prompts/autocomplete.js",
|
||||||
|
@ -1544,6 +1608,27 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-09-11T17:24:56.124Z"
|
"updated": "2020-09-11T17:24:56.124Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/get-uri/node_modules/debug/src/browser.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/get-uri/node_modules/debug/src/common.js",
|
||||||
|
"line": "\tcreateDebug.enable(createDebug.load());",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/get-uri/node_modules/debug/src/node.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "node_modules/global-dirs/index.js",
|
"path": "node_modules/global-dirs/index.js",
|
||||||
|
@ -1670,6 +1755,48 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2021-11-13T01:38:33.299Z"
|
"updated": "2021-11-13T01:38:33.299Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/http-proxy-agent/node_modules/debug/src/browser.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/http-proxy-agent/node_modules/debug/src/common.js",
|
||||||
|
"line": "\tcreateDebug.enable(createDebug.load());",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/http-proxy-agent/node_modules/debug/src/node.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/https-proxy-agent/node_modules/debug/src/browser.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/https-proxy-agent/node_modules/debug/src/common.js",
|
||||||
|
"line": "\tcreateDebug.enable(createDebug.load());",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/https-proxy-agent/node_modules/debug/src/node.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-$(",
|
"rule": "jQuery-$(",
|
||||||
"path": "node_modules/immutable/dist/immutable.min.js",
|
"path": "node_modules/immutable/dist/immutable.min.js",
|
||||||
|
@ -4303,6 +4430,13 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2019-03-09T00:08:44.242Z"
|
"updated": "2019-03-09T00:08:44.242Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "eval",
|
||||||
|
"path": "node_modules/micro/node_modules/depd/index.js",
|
||||||
|
"line": " var deprecatedfn = eval('(function (' + args + ') {\\n' +",
|
||||||
|
"reasonCategory": "usageTrusted",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "DOM-innerHTML",
|
"rule": "DOM-innerHTML",
|
||||||
"path": "node_modules/min-document/serialize.js",
|
"path": "node_modules/min-document/serialize.js",
|
||||||
|
@ -4469,6 +4603,27 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-09-14T16:19:54.461Z"
|
"updated": "2020-09-14T16:19:54.461Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/pac-proxy-agent/node_modules/debug/src/browser.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/pac-proxy-agent/node_modules/debug/src/common.js",
|
||||||
|
"line": "\tcreateDebug.enable(createDebug.load());",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/pac-proxy-agent/node_modules/debug/src/node.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "DOM-innerHTML",
|
"rule": "DOM-innerHTML",
|
||||||
"path": "node_modules/package-json/node_modules/@sindresorhus/is/dist/index.js",
|
"path": "node_modules/package-json/node_modules/@sindresorhus/is/dist/index.js",
|
||||||
|
@ -5138,6 +5293,27 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2018-09-15T00:38:04.183Z"
|
"updated": "2018-09-15T00:38:04.183Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/proxy-agent/node_modules/debug/src/browser.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/proxy-agent/node_modules/debug/src/common.js",
|
||||||
|
"line": "\tcreateDebug.enable(createDebug.load());",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/proxy-agent/node_modules/debug/src/node.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "DOM-document.write(",
|
"rule": "DOM-document.write(",
|
||||||
"path": "node_modules/qrcode-generator/sample.js",
|
"path": "node_modules/qrcode-generator/sample.js",
|
||||||
|
@ -6292,6 +6468,27 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2018-09-15T00:38:04.183Z"
|
"updated": "2018-09-15T00:38:04.183Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/socks-proxy-agent/node_modules/debug/src/browser.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/socks-proxy-agent/node_modules/debug/src/common.js",
|
||||||
|
"line": "\tcreateDebug.enable(createDebug.load());",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/socks-proxy-agent/node_modules/debug/src/node.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-append(",
|
"rule": "jQuery-append(",
|
||||||
"path": "node_modules/socks/build/client/socksclient.js",
|
"path": "node_modules/socks/build/client/socksclient.js",
|
||||||
|
@ -6395,6 +6592,27 @@
|
||||||
"reasonCategory": "falseMatch",
|
"reasonCategory": "falseMatch",
|
||||||
"updated": "2020-04-25T01:47:02.583Z"
|
"updated": "2020-04-25T01:47:02.583Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/sumchecker/node_modules/debug/src/browser.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/sumchecker/node_modules/debug/src/common.js",
|
||||||
|
"line": "\tcreateDebug.enable(createDebug.load());",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule": "jQuery-load(",
|
||||||
|
"path": "node_modules/sumchecker/node_modules/debug/src/node.js",
|
||||||
|
"line": "function load() {",
|
||||||
|
"reasonCategory": "falseMatch",
|
||||||
|
"updated": "2022-02-11T21:58:24.827Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule": "jQuery-append(",
|
"rule": "jQuery-append(",
|
||||||
"path": "node_modules/table/dist/createStream.js",
|
"path": "node_modules/table/dist/createStream.js",
|
||||||
|
|
|
@ -111,6 +111,7 @@ const excludedFilesRegexp = RegExp(
|
||||||
'^node_modules/esbuild/.+',
|
'^node_modules/esbuild/.+',
|
||||||
'^node_modules/@babel/.+',
|
'^node_modules/@babel/.+',
|
||||||
'^node_modules/@chanzuckerberg/axe-storybook-testing/.+',
|
'^node_modules/@chanzuckerberg/axe-storybook-testing/.+',
|
||||||
|
'^node_modules/@signalapp/mock-server/.+',
|
||||||
'^node_modules/@svgr/.+',
|
'^node_modules/@svgr/.+',
|
||||||
'^node_modules/@types/.+',
|
'^node_modules/@types/.+',
|
||||||
'^node_modules/@webassemblyjs/.+',
|
'^node_modules/@webassemblyjs/.+',
|
||||||
|
|
137
yarn.lock
137
yarn.lock
|
@ -1342,6 +1342,30 @@
|
||||||
"@react-spring/shared" "~9.4.0"
|
"@react-spring/shared" "~9.4.0"
|
||||||
"@react-spring/types" "~9.4.0"
|
"@react-spring/types" "~9.4.0"
|
||||||
|
|
||||||
|
"@signalapp/mock-server@1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-1.0.1.tgz#ae461528ca18218cf34366d5afa1c672b0ddabe0"
|
||||||
|
integrity sha512-9XYIFZwwGnFEg/WSffn3KWOHHe/ooL44+UQ3cFX68jEtgOk575EeRZaTFge+XNxzciAbDuCtkWivYCODPBJISA==
|
||||||
|
dependencies:
|
||||||
|
"@signalapp/signal-client" "0.12.1"
|
||||||
|
debug "^4.3.2"
|
||||||
|
long "^4.0.0"
|
||||||
|
micro "^9.3.4"
|
||||||
|
microrouter "^3.1.3"
|
||||||
|
protobufjs "^6.10.2"
|
||||||
|
typescript "^4.5.5"
|
||||||
|
url-pattern "^1.0.3"
|
||||||
|
uuid "^8.3.2"
|
||||||
|
ws "^8.4.2"
|
||||||
|
|
||||||
|
"@signalapp/signal-client@0.12.1":
|
||||||
|
version "0.12.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@signalapp/signal-client/-/signal-client-0.12.1.tgz#d587811e76308e53376f14fc294f8d0c0af39d91"
|
||||||
|
integrity sha512-45BJHLVvCU1BMzLL4ZRFnJ5xGUwryozstwpw/VpEDD0Asb5WoZA+G42/Urnr0TbIWg+LYBwEpc7cKZ48SgOodQ==
|
||||||
|
dependencies:
|
||||||
|
node-gyp-build "^4.2.3"
|
||||||
|
uuid "^8.3.0"
|
||||||
|
|
||||||
"@signalapp/signal-client@0.12.4":
|
"@signalapp/signal-client@0.12.4":
|
||||||
version "0.12.4"
|
version "0.12.4"
|
||||||
resolved "https://registry.yarnpkg.com/@signalapp/signal-client/-/signal-client-0.12.4.tgz#19023456c9249db6afb01762b1841e18cc3614be"
|
resolved "https://registry.yarnpkg.com/@signalapp/signal-client/-/signal-client-0.12.4.tgz#19023456c9249db6afb01762b1841e18cc3614be"
|
||||||
|
@ -1933,7 +1957,7 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/dashdash/-/dashdash-1.14.0.tgz#bfa457c2688497cf0e6695dbd522c67a9232833f"
|
resolved "https://registry.yarnpkg.com/@types/dashdash/-/dashdash-1.14.0.tgz#bfa457c2688497cf0e6695dbd522c67a9232833f"
|
||||||
integrity sha512-dBnfu9H6TVawx85FGmVEs5lYFXNwUVxn3Nqu5FHhCAi4aPvZR35W4FEMK3ljlpM2vHPGgEnCZGARF59/QGTNJw==
|
integrity sha512-dBnfu9H6TVawx85FGmVEs5lYFXNwUVxn3Nqu5FHhCAi4aPvZR35W4FEMK3ljlpM2vHPGgEnCZGARF59/QGTNJw==
|
||||||
|
|
||||||
"@types/debug@^4.1.6":
|
"@types/debug@4.1.7", "@types/debug@^4.1.6":
|
||||||
version "4.1.7"
|
version "4.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82"
|
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82"
|
||||||
integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==
|
integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==
|
||||||
|
@ -2174,6 +2198,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e"
|
||||||
integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==
|
integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==
|
||||||
|
|
||||||
|
"@types/node@>=13.7.0":
|
||||||
|
version "17.0.17"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.17.tgz#a8ddf6e0c2341718d74ee3dc413a13a042c45a0c"
|
||||||
|
integrity sha512-e8PUNQy1HgJGV3iU/Bp2+D/DXh3PYeyli8LgIwsQcs1Ar1LoaWHSIT6Rw+H2rNJmiq6SNWiDytfx8+gYj7wDHw==
|
||||||
|
|
||||||
"@types/node@^13.7.0":
|
"@types/node@^13.7.0":
|
||||||
version "13.13.41"
|
version "13.13.41"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.41.tgz#045a4981318d31a581650ce70f340a32c3461198"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.41.tgz#045a4981318d31a581650ce70f340a32c3461198"
|
||||||
|
@ -3210,6 +3239,11 @@ are-we-there-yet@~1.1.2:
|
||||||
delegates "^1.0.0"
|
delegates "^1.0.0"
|
||||||
readable-stream "^2.0.6"
|
readable-stream "^2.0.6"
|
||||||
|
|
||||||
|
arg@4.1.0:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.0.tgz#583c518199419e0037abb74062c37f8519e575f0"
|
||||||
|
integrity sha512-ZWc51jO3qegGkVh8Hwpv636EkbesNV5ZNQPCtRa+0qytRYPEs9IYT9qITY9buezqUH5uqyzlWLcufrzU2rffdg==
|
||||||
|
|
||||||
arg@^4.1.0:
|
arg@^4.1.0:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.1.tgz#485f8e7c390ce4c5f78257dbea80d4be11feda4c"
|
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.1.tgz#485f8e7c390ce4c5f78257dbea80d4be11feda4c"
|
||||||
|
@ -5031,7 +5065,7 @@ content-disposition@0.5.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "5.1.2"
|
safe-buffer "5.1.2"
|
||||||
|
|
||||||
content-type@~1.0.4:
|
content-type@1.0.4, content-type@~1.0.4:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||||
|
|
||||||
|
@ -5431,6 +5465,13 @@ debug@4, debug@4.3.2, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, de
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.1.2"
|
ms "2.1.2"
|
||||||
|
|
||||||
|
debug@4.3.3:
|
||||||
|
version "4.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||||
|
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
||||||
|
dependencies:
|
||||||
|
ms "2.1.2"
|
||||||
|
|
||||||
debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6, debug@^3.2.7:
|
debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6, debug@^3.2.7:
|
||||||
version "3.2.7"
|
version "3.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
|
||||||
|
@ -5579,6 +5620,11 @@ delegates@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||||
|
|
||||||
|
depd@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
|
||||||
|
integrity sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=
|
||||||
|
|
||||||
depd@~1.1.2:
|
depd@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||||
|
@ -8216,6 +8262,16 @@ http-deceiver@^1.2.7:
|
||||||
version "1.2.7"
|
version "1.2.7"
|
||||||
resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
|
resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87"
|
||||||
|
|
||||||
|
http-errors@1.6.2:
|
||||||
|
version "1.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736"
|
||||||
|
integrity sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=
|
||||||
|
dependencies:
|
||||||
|
depd "1.1.1"
|
||||||
|
inherits "2.0.3"
|
||||||
|
setprototypeof "1.0.3"
|
||||||
|
statuses ">= 1.3.1 < 2"
|
||||||
|
|
||||||
http-errors@1.7.2:
|
http-errors@1.7.2:
|
||||||
version "1.7.2"
|
version "1.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
|
||||||
|
@ -8338,6 +8394,11 @@ iconv-corefoundation@^1.1.6:
|
||||||
cli-truncate "^1.1.0"
|
cli-truncate "^1.1.0"
|
||||||
node-addon-api "^1.6.3"
|
node-addon-api "^1.6.3"
|
||||||
|
|
||||||
|
iconv-lite@0.4.19:
|
||||||
|
version "0.4.19"
|
||||||
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
|
||||||
|
integrity sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==
|
||||||
|
|
||||||
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
|
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4:
|
||||||
version "0.4.24"
|
version "0.4.24"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||||
|
@ -8985,7 +9046,7 @@ is-shared-array-buffer@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6"
|
resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6"
|
||||||
integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==
|
integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==
|
||||||
|
|
||||||
is-stream@^1.0.1, is-stream@^1.1.0:
|
is-stream@1.1.0, is-stream@^1.0.1, is-stream@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||||
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
||||||
|
@ -9866,6 +9927,16 @@ methods@~1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||||
|
|
||||||
|
micro@^9.3.4:
|
||||||
|
version "9.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/micro/-/micro-9.3.4.tgz#745a494e53c8916f64fb6a729f8cbf2a506b35ad"
|
||||||
|
integrity sha512-smz9naZwTG7qaFnEZ2vn248YZq9XR+XoOH3auieZbkhDL4xLOxiE+KqG8qqnBeKfXA9c1uEFGCxPN1D+nT6N7w==
|
||||||
|
dependencies:
|
||||||
|
arg "4.1.0"
|
||||||
|
content-type "1.0.4"
|
||||||
|
is-stream "1.1.0"
|
||||||
|
raw-body "2.3.2"
|
||||||
|
|
||||||
microevent.ts@~0.1.1:
|
microevent.ts@~0.1.1:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0"
|
resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0"
|
||||||
|
@ -9915,6 +9986,13 @@ micromatch@^4.0.2:
|
||||||
braces "^3.0.1"
|
braces "^3.0.1"
|
||||||
picomatch "^2.0.5"
|
picomatch "^2.0.5"
|
||||||
|
|
||||||
|
microrouter@^3.1.3:
|
||||||
|
version "3.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/microrouter/-/microrouter-3.1.3.tgz#1e45df77d3e2d773be5da129cfc7d5e6e6c86f4e"
|
||||||
|
integrity sha1-HkXfd9Pi13O+XaEpz8fV5ubIb04=
|
||||||
|
dependencies:
|
||||||
|
url-pattern "^1.0.3"
|
||||||
|
|
||||||
miller-rabin@^4.0.0:
|
miller-rabin@^4.0.0:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
|
resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
|
||||||
|
@ -11735,6 +11813,25 @@ protobufjs@6.10.2:
|
||||||
"@types/node" "^13.7.0"
|
"@types/node" "^13.7.0"
|
||||||
long "^4.0.0"
|
long "^4.0.0"
|
||||||
|
|
||||||
|
protobufjs@^6.10.2:
|
||||||
|
version "6.11.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b"
|
||||||
|
integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw==
|
||||||
|
dependencies:
|
||||||
|
"@protobufjs/aspromise" "^1.1.2"
|
||||||
|
"@protobufjs/base64" "^1.1.2"
|
||||||
|
"@protobufjs/codegen" "^2.0.4"
|
||||||
|
"@protobufjs/eventemitter" "^1.1.0"
|
||||||
|
"@protobufjs/fetch" "^1.1.0"
|
||||||
|
"@protobufjs/float" "^1.0.2"
|
||||||
|
"@protobufjs/inquire" "^1.1.0"
|
||||||
|
"@protobufjs/path" "^1.1.2"
|
||||||
|
"@protobufjs/pool" "^1.1.0"
|
||||||
|
"@protobufjs/utf8" "^1.1.0"
|
||||||
|
"@types/long" "^4.0.1"
|
||||||
|
"@types/node" ">=13.7.0"
|
||||||
|
long "^4.0.0"
|
||||||
|
|
||||||
proxy-addr@~2.0.5:
|
proxy-addr@~2.0.5:
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34"
|
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34"
|
||||||
|
@ -11962,6 +12059,16 @@ range-parser@^1.2.1, range-parser@~1.2.1:
|
||||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||||
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
|
||||||
|
|
||||||
|
raw-body@2.3.2:
|
||||||
|
version "2.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89"
|
||||||
|
integrity sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=
|
||||||
|
dependencies:
|
||||||
|
bytes "3.0.0"
|
||||||
|
http-errors "1.6.2"
|
||||||
|
iconv-lite "0.4.19"
|
||||||
|
unpipe "1.0.0"
|
||||||
|
|
||||||
raw-body@2.4.0, raw-body@^2.2.0:
|
raw-body@2.4.0, raw-body@^2.2.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
|
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
|
||||||
|
@ -13251,6 +13358,11 @@ setimmediate@^1.0.4, setimmediate@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||||
|
|
||||||
|
setprototypeof@1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04"
|
||||||
|
integrity sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=
|
||||||
|
|
||||||
setprototypeof@1.1.0:
|
setprototypeof@1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
|
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
|
||||||
|
@ -13737,7 +13849,7 @@ static-extend@^0.1.1:
|
||||||
define-property "^0.2.5"
|
define-property "^0.2.5"
|
||||||
object-copy "^0.1.0"
|
object-copy "^0.1.0"
|
||||||
|
|
||||||
"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
|
"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
|
||||||
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
|
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
|
||||||
|
@ -14558,6 +14670,11 @@ typescript@4.4.2:
|
||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86"
|
||||||
integrity sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==
|
integrity sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==
|
||||||
|
|
||||||
|
typescript@^4.5.5:
|
||||||
|
version "4.5.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
|
||||||
|
integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
|
||||||
|
|
||||||
ua-parser-js@^0.7.18:
|
ua-parser-js@^0.7.18:
|
||||||
version "0.7.28"
|
version "0.7.28"
|
||||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
|
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
|
||||||
|
@ -14737,6 +14854,11 @@ url-parse@^1.4.3, url-parse@^1.5.1:
|
||||||
querystringify "^2.1.1"
|
querystringify "^2.1.1"
|
||||||
requires-port "^1.0.0"
|
requires-port "^1.0.0"
|
||||||
|
|
||||||
|
url-pattern@^1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/url-pattern/-/url-pattern-1.0.3.tgz#0409292471b24f23c50d65a47931793d2b5acfc1"
|
||||||
|
integrity sha1-BAkpJHGyTyPFDWWkeTF5PStaz8E=
|
||||||
|
|
||||||
url@^0.11.0:
|
url@^0.11.0:
|
||||||
version "0.11.0"
|
version "0.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
||||||
|
@ -14798,7 +14920,7 @@ uuid@^3.3.2, uuid@^3.4.0:
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
|
||||||
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
|
||||||
|
|
||||||
uuid@^8.3.0:
|
uuid@^8.3.0, uuid@^8.3.2:
|
||||||
version "8.3.2"
|
version "8.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||||
|
@ -15254,6 +15376,11 @@ ws@^7.4.6:
|
||||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
|
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
|
||||||
integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==
|
integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==
|
||||||
|
|
||||||
|
ws@^8.4.2:
|
||||||
|
version "8.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f"
|
||||||
|
integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==
|
||||||
|
|
||||||
xdg-basedir@^4.0.0:
|
xdg-basedir@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
|
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13"
|
||||||
|
|
Loading…
Reference in New Issue