Upgrade libsignal-client to 0.44.0 and adopt API changes
This commit is contained in:
parent
37725647c8
commit
e388f13910
13 changed files with 266 additions and 138 deletions
|
@ -4343,7 +4343,7 @@ For more information on this, and how to apply and follow the GNU AGPL, see
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## attest 0.1.0, device-transfer 0.1.0, libsignal-bridge 0.1.0, libsignal-bridge-macros 0.1.0, libsignal-core 0.1.0, libsignal-ffi 0.42.0, libsignal-jni 0.42.0, libsignal-message-backup 0.1.0, libsignal-message-backup-macros 0.1.0, libsignal-net 0.1.0, libsignal-node 0.42.0, libsignal-protocol 0.1.0, libsignal-svr3 0.1.0, poksho 0.7.0, signal-crypto 0.1.0, signal-media 0.1.0, signal-neon-futures 0.1.0, signal-neon-futures-tests 0.1.0, signal-pin 0.1.0, usernames 0.1.0, zkcredential 0.1.0, zkgroup 0.9.0
|
## attest 0.1.0, device-transfer 0.1.0, libsignal-bridge 0.1.0, libsignal-bridge-macros 0.1.0, libsignal-core 0.1.0, libsignal-ffi 0.44.0, libsignal-jni 0.44.0, libsignal-message-backup 0.1.0, libsignal-message-backup-macros 0.1.0, libsignal-net 0.1.0, libsignal-node 0.44.0, libsignal-protocol 0.1.0, libsignal-svr3 0.1.0, poksho 0.7.0, signal-crypto 0.1.0, signal-media 0.1.0, signal-neon-futures 0.1.0, signal-neon-futures-tests 0.1.0, signal-pin 0.1.0, usernames 0.1.0, zkcredential 0.1.0, zkgroup 0.9.0
|
||||||
|
|
||||||
```
|
```
|
||||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||||
|
|
|
@ -2426,6 +2426,9 @@ ipc.on('get-config', async event => {
|
||||||
preferredSystemLocales: getPreferredSystemLocales(),
|
preferredSystemLocales: getPreferredSystemLocales(),
|
||||||
localeOverride: getLocaleOverride(),
|
localeOverride: getLocaleOverride(),
|
||||||
version: app.getVersion(),
|
version: app.getVersion(),
|
||||||
|
libsignalNetEnvironment: config.has('libsignalNetEnvironment')
|
||||||
|
? config.get<string>('libsignalNetEnvironment')
|
||||||
|
: undefined,
|
||||||
buildCreation: config.get<number>('buildCreation'),
|
buildCreation: config.get<number>('buildCreation'),
|
||||||
buildExpiration: config.get<number>('buildExpiration'),
|
buildExpiration: config.get<number>('buildExpiration'),
|
||||||
challengeUrl: config.get<string>('challengeUrl'),
|
challengeUrl: config.get<string>('challengeUrl'),
|
||||||
|
|
|
@ -104,7 +104,7 @@
|
||||||
"@react-aria/utils": "3.16.0",
|
"@react-aria/utils": "3.16.0",
|
||||||
"@react-spring/web": "9.5.5",
|
"@react-spring/web": "9.5.5",
|
||||||
"@signalapp/better-sqlite3": "8.7.1",
|
"@signalapp/better-sqlite3": "8.7.1",
|
||||||
"@signalapp/libsignal-client": "0.42.0",
|
"@signalapp/libsignal-client": "0.44.0",
|
||||||
"@signalapp/ringrtc": "2.39.3",
|
"@signalapp/ringrtc": "2.39.3",
|
||||||
"@signalapp/windows-dummy-keystroke": "1.0.0",
|
"@signalapp/windows-dummy-keystroke": "1.0.0",
|
||||||
"@types/fabric": "4.5.3",
|
"@types/fabric": "4.5.3",
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { Net } from '@signalapp/libsignal-client';
|
||||||
import URL from 'url';
|
import URL from 'url';
|
||||||
import type { RequestInit, Response } from 'node-fetch';
|
import type { RequestInit, Response } from 'node-fetch';
|
||||||
import { Headers } from 'node-fetch';
|
import { Headers } from 'node-fetch';
|
||||||
|
@ -103,7 +104,10 @@ export class SocketManager extends EventListener {
|
||||||
|
|
||||||
private reconnectController: AbortController | undefined;
|
private reconnectController: AbortController | undefined;
|
||||||
|
|
||||||
constructor(private readonly options: SocketManagerOptions) {
|
constructor(
|
||||||
|
private readonly libsignalNet: Net.Net,
|
||||||
|
private readonly options: SocketManagerOptions
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.hasStoriesDisabled = options.hasStoriesDisabled;
|
this.hasStoriesDisabled = options.hasStoriesDisabled;
|
||||||
|
@ -565,14 +569,9 @@ export class SocketManager extends EventListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
private connectLibsignalUnauthenticated(): AbortableProcess<IWebSocketResource> {
|
private connectLibsignalUnauthenticated(): AbortableProcess<IWebSocketResource> {
|
||||||
return new AbortableProcess<IWebSocketResource>(
|
return LibsignalWebSocketResource.connect(
|
||||||
`WebSocket.connect(libsignal.${UNAUTHENTICATED_CHANNEL_NAME})`,
|
this.libsignalNet,
|
||||||
{
|
UNAUTHENTICATED_CHANNEL_NAME
|
||||||
abort() {
|
|
||||||
// noop
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Promise.resolve(new LibsignalWebSocketResource(this.options.version))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -666,7 +665,8 @@ export class SocketManager extends EventListener {
|
||||||
const url = `${this.options.url}${path}?${qs.encode(queryWithDefaults)}`;
|
const url = `${this.options.url}${path}?${qs.encode(queryWithDefaults)}`;
|
||||||
const { version } = this.options;
|
const { version } = this.options;
|
||||||
|
|
||||||
return connectWebSocket({
|
const start = performance.now();
|
||||||
|
const webSocketResourceConnection = connectWebSocket({
|
||||||
name,
|
name,
|
||||||
url,
|
url,
|
||||||
version,
|
version,
|
||||||
|
@ -675,17 +675,69 @@ export class SocketManager extends EventListener {
|
||||||
|
|
||||||
extraHeaders,
|
extraHeaders,
|
||||||
|
|
||||||
createResource(socket: WebSocket): IWebSocketResource {
|
createResource(socket: WebSocket): WebSocketResource {
|
||||||
return !resourceOptions.transportOption ||
|
const duration = (performance.now() - start).toFixed(1);
|
||||||
resourceOptions.transportOption === TransportOption.Original
|
log.info(
|
||||||
? new WebSocketResource(socket, resourceOptions)
|
`WebSocketResource(${resourceOptions.name}) connected in ${duration}ms`
|
||||||
: new WebSocketResourceWithShadowing(
|
);
|
||||||
socket,
|
return new WebSocketResource(socket, resourceOptions);
|
||||||
resourceOptions,
|
|
||||||
version
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const shadowingModeEnabled =
|
||||||
|
!resourceOptions.transportOption ||
|
||||||
|
resourceOptions.transportOption === TransportOption.Original;
|
||||||
|
return shadowingModeEnabled
|
||||||
|
? webSocketResourceConnection
|
||||||
|
: this.connectWithShadowing(webSocketResourceConnection, resourceOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A method that takes in an `AbortableProcess<>` that establishes
|
||||||
|
* a `WebSocketResource` connection and wraps it in a process
|
||||||
|
* that also tries to establish a `LibsignalWebSocketResource` connection.
|
||||||
|
*
|
||||||
|
* The shadowing connection will not block the main one (e.g. if it takes
|
||||||
|
* longer to connect) and an error in the shadowing connection will not
|
||||||
|
* affect the overall behavior.
|
||||||
|
*
|
||||||
|
* @param mainConnection an `AbortableProcess<WebSocketResource>` responsible
|
||||||
|
* for establishing a Desktop system WebSocket connection.
|
||||||
|
* @param options `WebSocketResourceOptions` options
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private connectWithShadowing(
|
||||||
|
mainConnection: AbortableProcess<WebSocketResource>,
|
||||||
|
options: WebSocketResourceOptions
|
||||||
|
): AbortableProcess<IWebSocketResource> {
|
||||||
|
// creating an `AbortableProcess` of libsignal websocket connection
|
||||||
|
const shadowingConnection = LibsignalWebSocketResource.connect(
|
||||||
|
this.libsignalNet,
|
||||||
|
options.name
|
||||||
|
);
|
||||||
|
const shadowWrapper = async () => {
|
||||||
|
// if main connection results in an error,
|
||||||
|
// it's propagated as the error of the resulting process
|
||||||
|
const mainSocket = await mainConnection.resultPromise;
|
||||||
|
// here, we're not awaiting on `shadowingConnection.resultPromise`
|
||||||
|
// and just letting `WebSocketResourceWithShadowing`
|
||||||
|
// initiate and handle the result of the shadowing connection attempt
|
||||||
|
return new WebSocketResourceWithShadowing(
|
||||||
|
mainSocket,
|
||||||
|
shadowingConnection,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return new AbortableProcess<IWebSocketResource>(
|
||||||
|
`WebSocketResourceWithShadowing.connect(${options.name})`,
|
||||||
|
{
|
||||||
|
abort() {
|
||||||
|
mainConnection.abort();
|
||||||
|
shadowingConnection.abort();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shadowWrapper()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkResource(
|
private async checkResource(
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { z } from 'zod';
|
||||||
import type { Readable } from 'stream';
|
import type { Readable } from 'stream';
|
||||||
import type { connection as WebSocket } from 'websocket';
|
import type { connection as WebSocket } from 'websocket';
|
||||||
|
|
||||||
|
import { Net } from '@signalapp/libsignal-client';
|
||||||
import { assertDev, strictAssert } from '../util/assert';
|
import { assertDev, strictAssert } from '../util/assert';
|
||||||
import { isRecord } from '../util/isRecord';
|
import { isRecord } from '../util/isRecord';
|
||||||
import * as durations from '../util/durations';
|
import * as durations from '../util/durations';
|
||||||
|
@ -71,6 +72,7 @@ import * as log from '../logging/log';
|
||||||
import { maybeParseUrl, urlPathFromComponents } from '../util/url';
|
import { maybeParseUrl, urlPathFromComponents } from '../util/url';
|
||||||
import { SECOND } from '../util/durations';
|
import { SECOND } from '../util/durations';
|
||||||
import type { IWebSocketResource } from './WebsocketResources';
|
import type { IWebSocketResource } from './WebsocketResources';
|
||||||
|
import { Environment, getEnvironment } from '../environment';
|
||||||
|
|
||||||
// Note: this will break some code that expects to be able to use err.response when a
|
// Note: this will break some code that expects to be able to use err.response when a
|
||||||
// web request fails, because it will force it to text. But it is very useful for
|
// web request fails, because it will force it to text. But it is very useful for
|
||||||
|
@ -78,6 +80,32 @@ import type { IWebSocketResource } from './WebsocketResources';
|
||||||
const DEBUG = false;
|
const DEBUG = false;
|
||||||
const DEFAULT_TIMEOUT = 30 * SECOND;
|
const DEFAULT_TIMEOUT = 30 * SECOND;
|
||||||
|
|
||||||
|
// Libsignal has internally configured values for domain names
|
||||||
|
// (and other connectivity params) of the services.
|
||||||
|
function resolveLibsignalNetEnvironment(
|
||||||
|
appEnv: Environment,
|
||||||
|
libsignalNetEnv: string | undefined
|
||||||
|
): Net.Environment {
|
||||||
|
switch (appEnv) {
|
||||||
|
case Environment.Production:
|
||||||
|
return Net.Environment.Production;
|
||||||
|
case Environment.Development:
|
||||||
|
// In the case of the `Development` Desktop env,
|
||||||
|
// we should be checking the provided string value
|
||||||
|
// of `libsignalNetEnv`
|
||||||
|
switch (libsignalNetEnv) {
|
||||||
|
case 'production':
|
||||||
|
return Net.Environment.Production;
|
||||||
|
default:
|
||||||
|
return Net.Environment.Staging;
|
||||||
|
}
|
||||||
|
case Environment.Test:
|
||||||
|
case Environment.Staging:
|
||||||
|
default:
|
||||||
|
return Net.Environment.Staging;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function _createRedactor(
|
function _createRedactor(
|
||||||
...toReplace: ReadonlyArray<string | undefined>
|
...toReplace: ReadonlyArray<string | undefined>
|
||||||
): RedactUrl {
|
): RedactUrl {
|
||||||
|
@ -588,6 +616,7 @@ type InitializeOptionsType = {
|
||||||
proxyUrl: string | undefined;
|
proxyUrl: string | undefined;
|
||||||
version: string;
|
version: string;
|
||||||
directoryConfig: DirectoryConfigType;
|
directoryConfig: DirectoryConfigType;
|
||||||
|
libsignalNetEnvironment: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MessageType = Readonly<{
|
export type MessageType = Readonly<{
|
||||||
|
@ -1249,6 +1278,7 @@ export function initialize({
|
||||||
contentProxyUrl,
|
contentProxyUrl,
|
||||||
proxyUrl,
|
proxyUrl,
|
||||||
version,
|
version,
|
||||||
|
libsignalNetEnvironment,
|
||||||
}: InitializeOptionsType): WebAPIConnectType {
|
}: InitializeOptionsType): WebAPIConnectType {
|
||||||
if (!isString(url)) {
|
if (!isString(url)) {
|
||||||
throw new Error('WebAPI.initialize: Invalid server url');
|
throw new Error('WebAPI.initialize: Invalid server url');
|
||||||
|
@ -1290,6 +1320,17 @@ export function initialize({
|
||||||
throw new Error('WebAPI.initialize: Invalid version');
|
throw new Error('WebAPI.initialize: Invalid version');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `libsignalNet` is an instance of a class from libsignal that is responsible
|
||||||
|
// for providing network layer API and related functionality.
|
||||||
|
// It's important to have a single instance of this class as it holds
|
||||||
|
// resources that are shared across all other use cases.
|
||||||
|
const env = resolveLibsignalNetEnvironment(
|
||||||
|
getEnvironment(),
|
||||||
|
libsignalNetEnvironment
|
||||||
|
);
|
||||||
|
log.info(`libsignal net environment resolved to [${Net.Environment[env]}]`);
|
||||||
|
const libsignalNet = new Net.Net(env);
|
||||||
|
|
||||||
// Thanks to function-hoisting, we can put this return statement before all of the
|
// Thanks to function-hoisting, we can put this return statement before all of the
|
||||||
// below function definitions.
|
// below function definitions.
|
||||||
return {
|
return {
|
||||||
|
@ -1313,7 +1354,7 @@ export function initialize({
|
||||||
|
|
||||||
let activeRegistration: ExplodePromiseResultType<void> | undefined;
|
let activeRegistration: ExplodePromiseResultType<void> | undefined;
|
||||||
|
|
||||||
const socketManager = new SocketManager({
|
const socketManager = new SocketManager(libsignalNet, {
|
||||||
url,
|
url,
|
||||||
artCreatorUrl,
|
artCreatorUrl,
|
||||||
certificateAuthority,
|
certificateAuthority,
|
||||||
|
@ -1344,7 +1385,7 @@ export function initialize({
|
||||||
|
|
||||||
const { directoryUrl, directoryMRENCLAVE } = directoryConfig;
|
const { directoryUrl, directoryMRENCLAVE } = directoryConfig;
|
||||||
|
|
||||||
const cds = new CDSI({
|
const cds = new CDSI(libsignalNet, {
|
||||||
logger: log,
|
logger: log,
|
||||||
proxyUrl,
|
proxyUrl,
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { ConnectTimeoutError, HTTPError } from './Errors';
|
||||||
import { handleStatusCode, translateError } from './Utils';
|
import { handleStatusCode, translateError } from './Utils';
|
||||||
|
|
||||||
const TEN_SECONDS = 10 * durations.SECOND;
|
const TEN_SECONDS = 10 * durations.SECOND;
|
||||||
|
const WEBSOCKET_CONNECT_TIMEOUT = TEN_SECONDS;
|
||||||
const KEEPALIVE_INTERVAL_MS = TEN_SECONDS;
|
const KEEPALIVE_INTERVAL_MS = TEN_SECONDS;
|
||||||
|
|
||||||
export type IResource = {
|
export type IResource = {
|
||||||
|
@ -42,7 +43,7 @@ export function connect<Resource extends IResource>({
|
||||||
version,
|
version,
|
||||||
proxyAgent,
|
proxyAgent,
|
||||||
extraHeaders = {},
|
extraHeaders = {},
|
||||||
timeout = TEN_SECONDS,
|
timeout = WEBSOCKET_CONNECT_TIMEOUT,
|
||||||
createResource,
|
createResource,
|
||||||
}: ConnectOptionsType<Resource>): AbortableProcess<Resource> {
|
}: ConnectOptionsType<Resource>): AbortableProcess<Resource> {
|
||||||
const fixedScheme = url
|
const fixedScheme = url
|
||||||
|
|
|
@ -26,7 +26,6 @@
|
||||||
/* eslint-disable @typescript-eslint/no-namespace */
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
/* eslint-disable @typescript-eslint/brace-style */
|
/* eslint-disable @typescript-eslint/brace-style */
|
||||||
|
|
||||||
import { Net } from '@signalapp/libsignal-client';
|
|
||||||
import type { connection as WebSocket, IMessage } from 'websocket';
|
import type { connection as WebSocket, IMessage } from 'websocket';
|
||||||
import Long from 'long';
|
import Long from 'long';
|
||||||
import pTimeout from 'p-timeout';
|
import pTimeout from 'p-timeout';
|
||||||
|
@ -35,8 +34,9 @@ import net from 'net';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { clearInterval } from 'timers';
|
import { clearInterval } from 'timers';
|
||||||
import { random } from 'lodash';
|
import { random } from 'lodash';
|
||||||
import type { DebugInfo } from '@signalapp/libsignal-client/Native';
|
import type { ChatServiceDebugInfo } from '@signalapp/libsignal-client/Native';
|
||||||
|
|
||||||
|
import type { Net } from '@signalapp/libsignal-client';
|
||||||
import type { EventHandler } from './EventTarget';
|
import type { EventHandler } from './EventTarget';
|
||||||
import EventTarget from './EventTarget';
|
import EventTarget from './EventTarget';
|
||||||
|
|
||||||
|
@ -50,14 +50,13 @@ import { SignalService as Proto } from '../protobuf';
|
||||||
import * as log from '../logging/log';
|
import * as log from '../logging/log';
|
||||||
import * as Timers from '../Timers';
|
import * as Timers from '../Timers';
|
||||||
import type { IResource } from './WebSocket';
|
import type { IResource } from './WebSocket';
|
||||||
import { isProduction, isStaging } from '../util/version';
|
import { isProduction } from '../util/version';
|
||||||
|
|
||||||
import { ToastType } from '../types/Toast';
|
import { ToastType } from '../types/Toast';
|
||||||
|
import { AbortableProcess } from '../util/AbortableProcess';
|
||||||
|
|
||||||
const THIRTY_SECONDS = 30 * durations.SECOND;
|
const THIRTY_SECONDS = 30 * durations.SECOND;
|
||||||
|
|
||||||
const HEALTHCHECK_TIMEOUT = durations.SECOND;
|
|
||||||
|
|
||||||
const STATS_UPDATE_INTERVAL = durations.MINUTE;
|
const STATS_UPDATE_INTERVAL = durations.MINUTE;
|
||||||
|
|
||||||
const MAX_MESSAGE_SIZE = 512 * 1024;
|
const MAX_MESSAGE_SIZE = 512 * 1024;
|
||||||
|
@ -83,6 +82,7 @@ export namespace IpVersion {
|
||||||
}
|
}
|
||||||
|
|
||||||
const AggregatedStatsSchema = z.object({
|
const AggregatedStatsSchema = z.object({
|
||||||
|
connectionFailures: z.number(),
|
||||||
requestsCompared: z.number(),
|
requestsCompared: z.number(),
|
||||||
ipVersionMismatches: z.number(),
|
ipVersionMismatches: z.number(),
|
||||||
unexpectedReconnects: z.number(),
|
unexpectedReconnects: z.number(),
|
||||||
|
@ -127,6 +127,7 @@ export namespace AggregatedStats {
|
||||||
export function add(a: AggregatedStats, b: AggregatedStats): AggregatedStats {
|
export function add(a: AggregatedStats, b: AggregatedStats): AggregatedStats {
|
||||||
return {
|
return {
|
||||||
requestsCompared: a.requestsCompared + b.requestsCompared,
|
requestsCompared: a.requestsCompared + b.requestsCompared,
|
||||||
|
connectionFailures: a.connectionFailures + b.connectionFailures,
|
||||||
healthcheckFailures: a.healthcheckFailures + b.healthcheckFailures,
|
healthcheckFailures: a.healthcheckFailures + b.healthcheckFailures,
|
||||||
ipVersionMismatches: a.ipVersionMismatches + b.ipVersionMismatches,
|
ipVersionMismatches: a.ipVersionMismatches + b.ipVersionMismatches,
|
||||||
unexpectedReconnects: a.unexpectedReconnects + b.unexpectedReconnects,
|
unexpectedReconnects: a.unexpectedReconnects + b.unexpectedReconnects,
|
||||||
|
@ -138,6 +139,7 @@ export namespace AggregatedStats {
|
||||||
export function createEmpty(): AggregatedStats {
|
export function createEmpty(): AggregatedStats {
|
||||||
return {
|
return {
|
||||||
requestsCompared: 0,
|
requestsCompared: 0,
|
||||||
|
connectionFailures: 0,
|
||||||
ipVersionMismatches: 0,
|
ipVersionMismatches: 0,
|
||||||
unexpectedReconnects: 0,
|
unexpectedReconnects: 0,
|
||||||
healthcheckFailures: 0,
|
healthcheckFailures: 0,
|
||||||
|
@ -152,9 +154,10 @@ export namespace AggregatedStats {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
stats.healthcheckBadStatus + stats.healthcheckFailures > 20 ||
|
stats.healthcheckBadStatus +
|
||||||
stats.ipVersionMismatches > 50 ||
|
stats.healthcheckFailures +
|
||||||
stats.unexpectedReconnects > 50
|
stats.connectionFailures >
|
||||||
|
20 || stats.unexpectedReconnects > 50
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,11 +264,42 @@ export interface IWebSocketResource extends IResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class LibsignalWebSocketResource implements IWebSocketResource {
|
export class LibsignalWebSocketResource implements IWebSocketResource {
|
||||||
private readonly net: Net.Net;
|
constructor(
|
||||||
|
private readonly chatService: Net.ChatService,
|
||||||
|
private readonly socketIpVersion: IpVersion | undefined
|
||||||
|
) {}
|
||||||
|
|
||||||
constructor(version: string) {
|
public static connect(
|
||||||
this.net = new Net.Net(
|
libsignalNet: Net.Net,
|
||||||
isStaging(version) ? Net.Environment.Staging : Net.Environment.Production
|
name: string
|
||||||
|
): AbortableProcess<LibsignalWebSocketResource> {
|
||||||
|
const chatService = libsignalNet.newChatService();
|
||||||
|
const connectAsync = async () => {
|
||||||
|
try {
|
||||||
|
const debugInfo = await chatService.connectUnauthenticated();
|
||||||
|
log.info(`LibsignalWebSocketResource(${name}) connected`, debugInfo);
|
||||||
|
return new LibsignalWebSocketResource(
|
||||||
|
chatService,
|
||||||
|
IpVersion.fromDebugInfoCode(debugInfo.ipType)
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
// Handle any errors that occur during connection
|
||||||
|
log.error(
|
||||||
|
`LibsignalWebSocketResource(${name}) connection failed`,
|
||||||
|
Errors.toLogFormat(error)
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return new AbortableProcess<LibsignalWebSocketResource>(
|
||||||
|
`LibsignalWebSocketResource.connect(${name})`,
|
||||||
|
{
|
||||||
|
abort() {
|
||||||
|
// if interrupted, trying to disconnect
|
||||||
|
drop(chatService.disconnect());
|
||||||
|
},
|
||||||
|
},
|
||||||
|
connectAsync()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,6 +307,10 @@ export class LibsignalWebSocketResource implements IWebSocketResource {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ipVersion(): IpVersion | undefined {
|
||||||
|
return this.socketIpVersion;
|
||||||
|
}
|
||||||
|
|
||||||
public addEventListener(
|
public addEventListener(
|
||||||
_name: 'close',
|
_name: 'close',
|
||||||
_handler: (ev: CloseEvent) => void
|
_handler: (ev: CloseEvent) => void
|
||||||
|
@ -281,13 +319,11 @@ export class LibsignalWebSocketResource implements IWebSocketResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
public close(_code?: number, _reason?: string): void {
|
public close(_code?: number, _reason?: string): void {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
drop(this.chatService.disconnect());
|
||||||
this.net.disconnectChatService();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public shutdown(): void {
|
public shutdown(): void {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
drop(this.chatService.disconnect());
|
||||||
this.net.disconnectChatService();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public forceKeepAlive(): void {
|
public forceKeepAlive(): void {
|
||||||
|
@ -301,16 +337,15 @@ export class LibsignalWebSocketResource implements IWebSocketResource {
|
||||||
|
|
||||||
public async sendRequestGetDebugInfo(
|
public async sendRequestGetDebugInfo(
|
||||||
options: SendRequestOptions
|
options: SendRequestOptions
|
||||||
): Promise<[Response, DebugInfo]> {
|
): Promise<[Response, ChatServiceDebugInfo]> {
|
||||||
const { response, debugInfo } = await this.net.unauthenticatedFetchAndDebug(
|
const { response, debugInfo } =
|
||||||
{
|
await this.chatService.unauthenticatedFetchAndDebug({
|
||||||
verb: options.verb,
|
verb: options.verb,
|
||||||
path: options.path,
|
path: options.path,
|
||||||
headers: options.headers ? options.headers : [],
|
headers: options.headers ? options.headers : [],
|
||||||
body: options.body,
|
body: options.body,
|
||||||
timeoutMillis: options.timeout,
|
timeoutMillis: options.timeout,
|
||||||
}
|
});
|
||||||
);
|
|
||||||
return [
|
return [
|
||||||
new Response(response.body, {
|
new Response(response.body, {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
|
@ -323,9 +358,7 @@ export class LibsignalWebSocketResource implements IWebSocketResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebSocketResourceWithShadowing implements IWebSocketResource {
|
export class WebSocketResourceWithShadowing implements IWebSocketResource {
|
||||||
private main: WebSocketResource;
|
private shadowing: LibsignalWebSocketResource | undefined;
|
||||||
|
|
||||||
private shadowing: LibsignalWebSocketResource;
|
|
||||||
|
|
||||||
private stats: AggregatedStats;
|
private stats: AggregatedStats;
|
||||||
|
|
||||||
|
@ -336,12 +369,10 @@ export class WebSocketResourceWithShadowing implements IWebSocketResource {
|
||||||
private logId: string;
|
private logId: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
socket: WebSocket,
|
private readonly main: WebSocketResource,
|
||||||
options: WebSocketResourceOptions,
|
private readonly shadowingConnection: AbortableProcess<LibsignalWebSocketResource>,
|
||||||
version: string
|
options: WebSocketResourceOptions
|
||||||
) {
|
) {
|
||||||
this.main = new WebSocketResource(socket, options);
|
|
||||||
this.shadowing = new LibsignalWebSocketResource(version);
|
|
||||||
this.stats = AggregatedStats.createEmpty();
|
this.stats = AggregatedStats.createEmpty();
|
||||||
this.logId = `WebSocketResourceWithShadowing(${options.name})`;
|
this.logId = `WebSocketResourceWithShadowing(${options.name})`;
|
||||||
this.statsTimer = setInterval(
|
this.statsTimer = setInterval(
|
||||||
|
@ -351,6 +382,28 @@ export class WebSocketResourceWithShadowing implements IWebSocketResource {
|
||||||
this.shadowingWithReporting =
|
this.shadowingWithReporting =
|
||||||
options.transportOption === TransportOption.ShadowingHigh;
|
options.transportOption === TransportOption.ShadowingHigh;
|
||||||
|
|
||||||
|
// the idea is that we want to keep the shadowing connection process
|
||||||
|
// "in the background", so that the main connection wouldn't need to wait on it.
|
||||||
|
// then when we're connected, `this.shadowing` socket resource is initialized
|
||||||
|
// or an error reported in case of connection failure
|
||||||
|
const initializeAfterConnected = async () => {
|
||||||
|
try {
|
||||||
|
this.shadowing = await shadowingConnection.resultPromise;
|
||||||
|
// checking IP one time per connection
|
||||||
|
if (this.main.ipVersion() !== this.shadowing.ipVersion()) {
|
||||||
|
this.stats.ipVersionMismatches += 1;
|
||||||
|
const mainIpType = this.main.ipVersion();
|
||||||
|
const shadowIpType = this.shadowing.ipVersion();
|
||||||
|
log.warn(
|
||||||
|
`${this.logId}: libsignal websocket IP [${shadowIpType}], Desktop websocket IP [${mainIpType}]`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.stats.connectionFailures += 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
drop(initializeAfterConnected());
|
||||||
|
|
||||||
this.addEventListener('close', (_ev): void => {
|
this.addEventListener('close', (_ev): void => {
|
||||||
clearInterval(this.statsTimer);
|
clearInterval(this.statsTimer);
|
||||||
this.updateStats(options.name);
|
this.updateStats(options.name);
|
||||||
|
@ -392,12 +445,20 @@ export class WebSocketResourceWithShadowing implements IWebSocketResource {
|
||||||
|
|
||||||
public close(): void {
|
public close(): void {
|
||||||
this.main.close();
|
this.main.close();
|
||||||
this.shadowing.close();
|
if (this.shadowing) {
|
||||||
|
this.shadowing.close();
|
||||||
|
} else {
|
||||||
|
this.shadowingConnection.abort();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public shutdown(): void {
|
public shutdown(): void {
|
||||||
this.main.shutdown();
|
this.main.shutdown();
|
||||||
this.shadowing.shutdown();
|
if (this.shadowing) {
|
||||||
|
this.shadowing.shutdown();
|
||||||
|
} else {
|
||||||
|
this.shadowingConnection.abort();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public forceKeepAlive(timeout?: number): void {
|
public forceKeepAlive(timeout?: number): void {
|
||||||
|
@ -421,12 +482,20 @@ export class WebSocketResourceWithShadowing implements IWebSocketResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async sendShadowRequest(): Promise<void> {
|
private async sendShadowRequest(): Promise<void> {
|
||||||
|
// it could be that we're still connecting libsignal websocket
|
||||||
|
// in which case we're skipping the check
|
||||||
|
if (!this.shadowing) {
|
||||||
|
log.info(
|
||||||
|
`${this.logId}: skipping healthcheck - websocket not connected yet`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const [healthCheckResult, debugInfo] =
|
const [healthCheckResult, debugInfo] =
|
||||||
await this.shadowing.sendRequestGetDebugInfo({
|
await this.shadowing.sendRequestGetDebugInfo({
|
||||||
verb: 'GET',
|
verb: 'GET',
|
||||||
path: '/v1/keepalive',
|
path: '/v1/keepalive',
|
||||||
timeout: HEALTHCHECK_TIMEOUT,
|
timeout: KEEPALIVE_TIMEOUT_MS,
|
||||||
});
|
});
|
||||||
this.stats.requestsCompared += 1;
|
this.stats.requestsCompared += 1;
|
||||||
if (!isSuccessfulStatusCode(healthCheckResult.status)) {
|
if (!isSuccessfulStatusCode(healthCheckResult.status)) {
|
||||||
|
@ -435,18 +504,7 @@ export class WebSocketResourceWithShadowing implements IWebSocketResource {
|
||||||
`${this.logId}: keepalive via libsignal responded with status [${healthCheckResult.status}]`
|
`${this.logId}: keepalive via libsignal responded with status [${healthCheckResult.status}]`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const ipVersion = IpVersion.fromDebugInfoCode(debugInfo.ipType);
|
this.stats.unexpectedReconnects = debugInfo.reconnectCount;
|
||||||
if (this.main.ipVersion() !== ipVersion) {
|
|
||||||
this.stats.ipVersionMismatches += 1;
|
|
||||||
log.warn(
|
|
||||||
`${
|
|
||||||
this.logId
|
|
||||||
}: keepalive via libsignal using IP [${ipVersion}] while main is using IP [${this.main.ipVersion()}]`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (debugInfo.reconnectCount > 1) {
|
|
||||||
this.stats.unexpectedReconnects = debugInfo.reconnectCount - 1;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.stats.healthcheckFailures += 1;
|
this.stats.healthcheckFailures += 1;
|
||||||
log.warn(
|
log.warn(
|
||||||
|
@ -823,7 +881,7 @@ const KEEPALIVE_INTERVAL_MS = 30 * durations.SECOND;
|
||||||
// immediate disconnect.
|
// immediate disconnect.
|
||||||
const STALE_THRESHOLD_MS = 5 * durations.MINUTE;
|
const STALE_THRESHOLD_MS = 5 * durations.MINUTE;
|
||||||
|
|
||||||
// If we don't receive a response to keepalive request within 10 seconds -
|
// If we don't receive a response to keepalive request within 30 seconds -
|
||||||
// close the socket.
|
// close the socket.
|
||||||
const KEEPALIVE_TIMEOUT_MS = 30 * durations.SECOND;
|
const KEEPALIVE_TIMEOUT_MS = 30 * durations.SECOND;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright 2022 Signal Messenger, LLC
|
// Copyright 2022 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import type { Net } from '@signalapp/libsignal-client';
|
||||||
import type { connection as WebSocket } from 'websocket';
|
import type { connection as WebSocket } from 'websocket';
|
||||||
|
|
||||||
import * as Bytes from '../../Bytes';
|
import * as Bytes from '../../Bytes';
|
||||||
|
@ -16,8 +17,8 @@ export type CDSIOptionsType = Readonly<{
|
||||||
export class CDSI extends CDSSocketManagerBase<CDSISocket, CDSIOptionsType> {
|
export class CDSI extends CDSSocketManagerBase<CDSISocket, CDSIOptionsType> {
|
||||||
private readonly mrenclave: Buffer;
|
private readonly mrenclave: Buffer;
|
||||||
|
|
||||||
constructor(options: CDSIOptionsType) {
|
constructor(libsignalNet: Net.Net, options: CDSIOptionsType) {
|
||||||
super(options);
|
super(libsignalNet, options);
|
||||||
|
|
||||||
this.mrenclave = Buffer.from(Bytes.fromHex(options.mrenclave));
|
this.mrenclave = Buffer.from(Bytes.fromHex(options.mrenclave));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
// Copyright 2021 Signal Messenger, LLC
|
// Copyright 2021 Signal Messenger, LLC
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { RateLimitedError as NetRateLimitedError } from '@signalapp/libsignal-client';
|
import type {
|
||||||
import {
|
RateLimitedError as NetRateLimitedError,
|
||||||
Net,
|
Net,
|
||||||
|
} from '@signalapp/libsignal-client';
|
||||||
|
import {
|
||||||
ErrorCode as LibSignalErrorCode,
|
ErrorCode as LibSignalErrorCode,
|
||||||
LibSignalErrorBase,
|
LibSignalErrorBase,
|
||||||
} from '@signalapp/libsignal-client';
|
} from '@signalapp/libsignal-client';
|
||||||
|
@ -25,7 +27,6 @@ import type {
|
||||||
} from './Types.d';
|
} from './Types.d';
|
||||||
import { RateLimitedError } from './RateLimitedError';
|
import { RateLimitedError } from './RateLimitedError';
|
||||||
import { connect as connectWebSocket } from '../WebSocket';
|
import { connect as connectWebSocket } from '../WebSocket';
|
||||||
import { Environment, getEnvironment } from '../../environment';
|
|
||||||
|
|
||||||
const REQUEST_TIMEOUT = 10 * SECOND;
|
const REQUEST_TIMEOUT = 10 * SECOND;
|
||||||
|
|
||||||
|
@ -42,6 +43,10 @@ export abstract class CDSSocketManagerBase<
|
||||||
> extends CDSBase<Options> {
|
> extends CDSBase<Options> {
|
||||||
private retryAfter?: number;
|
private retryAfter?: number;
|
||||||
|
|
||||||
|
constructor(private readonly libsignalNet: Net.Net, options: Options) {
|
||||||
|
super(options);
|
||||||
|
}
|
||||||
|
|
||||||
public async request(
|
public async request(
|
||||||
options: CDSRequestOptionsType
|
options: CDSRequestOptionsType
|
||||||
): Promise<CDSResponseType> {
|
): Promise<CDSResponseType> {
|
||||||
|
@ -106,24 +111,22 @@ export abstract class CDSSocketManagerBase<
|
||||||
options: CDSRequestOptionsType
|
options: CDSRequestOptionsType
|
||||||
): Promise<CDSResponseType> {
|
): Promise<CDSResponseType> {
|
||||||
const log = this.logger;
|
const log = this.logger;
|
||||||
const {
|
const { acisAndAccessKeys, e164s, returnAcisWithoutUaks = false } = options;
|
||||||
acisAndAccessKeys,
|
|
||||||
e164s,
|
|
||||||
timeout = REQUEST_TIMEOUT,
|
|
||||||
returnAcisWithoutUaks = false,
|
|
||||||
} = options;
|
|
||||||
const auth = await this.getAuth();
|
const auth = await this.getAuth();
|
||||||
|
|
||||||
log.info('CDSSocketManager: making request via libsignal');
|
log.info('CDSSocketManager: making request via libsignal');
|
||||||
const net = new Net.Net(this.libsignalNetEnvironment());
|
|
||||||
try {
|
try {
|
||||||
log.info('CDSSocketManager: starting lookup request');
|
log.info('CDSSocketManager: starting lookup request');
|
||||||
const response = await net.cdsiLookup(auth, {
|
|
||||||
acisAndAccessKeys,
|
const { timeout = REQUEST_TIMEOUT } = options;
|
||||||
e164s,
|
const response = await pTimeout(
|
||||||
timeout,
|
this.libsignalNet.cdsiLookup(auth, {
|
||||||
returnAcisWithoutUaks,
|
acisAndAccessKeys,
|
||||||
});
|
e164s,
|
||||||
|
returnAcisWithoutUaks,
|
||||||
|
}),
|
||||||
|
timeout
|
||||||
|
);
|
||||||
|
|
||||||
log.info('CDSSocketManager: lookup request finished');
|
log.info('CDSSocketManager: lookup request finished');
|
||||||
return response as CDSResponseType;
|
return response as CDSResponseType;
|
||||||
|
@ -142,19 +145,6 @@ export abstract class CDSSocketManagerBase<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private libsignalNetEnvironment(): Net.Environment {
|
|
||||||
const env = getEnvironment();
|
|
||||||
switch (env) {
|
|
||||||
case Environment.Production:
|
|
||||||
return Net.Environment.Production;
|
|
||||||
case Environment.Development:
|
|
||||||
case Environment.Test:
|
|
||||||
case Environment.Staging:
|
|
||||||
default:
|
|
||||||
return Net.Environment.Staging;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private connect(auth: CDSAuthType): AbortableProcess<Socket> {
|
private connect(auth: CDSAuthType): AbortableProcess<Socket> {
|
||||||
return connectWebSocket<Socket>({
|
return connectWebSocket<Socket>({
|
||||||
name: 'CDSSocket',
|
name: 'CDSSocket',
|
||||||
|
|
|
@ -71,6 +71,7 @@ export const rendererConfigSchema = z.object({
|
||||||
resourcesUrl: configRequiredStringSchema,
|
resourcesUrl: configRequiredStringSchema,
|
||||||
userDataPath: configRequiredStringSchema,
|
userDataPath: configRequiredStringSchema,
|
||||||
version: configRequiredStringSchema,
|
version: configRequiredStringSchema,
|
||||||
|
libsignalNetEnvironment: configOptionalStringSchema,
|
||||||
directoryConfig: directoryConfigSchema,
|
directoryConfig: directoryConfigSchema,
|
||||||
|
|
||||||
// Only used by main window
|
// Only used by main window
|
||||||
|
|
|
@ -175,6 +175,7 @@ if (config.ciMode !== 'full' && config.environment !== 'test') {
|
||||||
|
|
||||||
type NetworkStatistics = {
|
type NetworkStatistics = {
|
||||||
signalConnectionCount?: string;
|
signalConnectionCount?: string;
|
||||||
|
unauthorizedConnectionFailures?: string;
|
||||||
unauthorizedRequestsCompared?: string;
|
unauthorizedRequestsCompared?: string;
|
||||||
unauthorizedHealthcheckFailures?: string;
|
unauthorizedHealthcheckFailures?: string;
|
||||||
unauthorizedHealthcheckBadStatus?: string;
|
unauthorizedHealthcheckBadStatus?: string;
|
||||||
|
@ -206,6 +207,9 @@ ipc.on('additional-log-data-request', async event => {
|
||||||
if (unauthorizedStats.requestsCompared > 0) {
|
if (unauthorizedStats.requestsCompared > 0) {
|
||||||
networkStatistics = {
|
networkStatistics = {
|
||||||
...networkStatistics,
|
...networkStatistics,
|
||||||
|
unauthorizedConnectionFailures: formatCountForLogging(
|
||||||
|
unauthorizedStats.connectionFailures
|
||||||
|
),
|
||||||
unauthorizedRequestsCompared: formatCountForLogging(
|
unauthorizedRequestsCompared: formatCountForLogging(
|
||||||
unauthorizedStats.requestsCompared
|
unauthorizedStats.requestsCompared
|
||||||
),
|
),
|
||||||
|
|
|
@ -38,6 +38,7 @@ window.WebAPI = window.textsecure.WebAPI.initialize({
|
||||||
contentProxyUrl: config.contentProxyUrl,
|
contentProxyUrl: config.contentProxyUrl,
|
||||||
proxyUrl: config.proxyUrl,
|
proxyUrl: config.proxyUrl,
|
||||||
version: config.version,
|
version: config.version,
|
||||||
|
libsignalNetEnvironment: config.libsignalNetEnvironment,
|
||||||
});
|
});
|
||||||
|
|
||||||
window.libphonenumberInstance = PhoneNumberUtil.getInstance();
|
window.libphonenumberInstance = PhoneNumberUtil.getInstance();
|
||||||
|
|
52
yarn.lock
52
yarn.lock
|
@ -3983,7 +3983,16 @@
|
||||||
bindings "^1.5.0"
|
bindings "^1.5.0"
|
||||||
tar "^6.1.0"
|
tar "^6.1.0"
|
||||||
|
|
||||||
"@signalapp/libsignal-client@0.42.0", "@signalapp/libsignal-client@^0.42.0":
|
"@signalapp/libsignal-client@0.44.0":
|
||||||
|
version "0.44.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@signalapp/libsignal-client/-/libsignal-client-0.44.0.tgz#c08bf33bb16276baff35a932b1f43d19d17028cf"
|
||||||
|
integrity sha512-tbxkRoNd1l2cg6aN5tyiTCy724qU/OlKpX9IECbMu/W1V77SaQS6ch0kPdR+KClBMyEtWxnd/q3/V4NMiENF4w==
|
||||||
|
dependencies:
|
||||||
|
node-gyp-build "^4.2.3"
|
||||||
|
type-fest "^3.5.0"
|
||||||
|
uuid "^8.3.0"
|
||||||
|
|
||||||
|
"@signalapp/libsignal-client@^0.42.0":
|
||||||
version "0.42.0"
|
version "0.42.0"
|
||||||
resolved "https://registry.yarnpkg.com/@signalapp/libsignal-client/-/libsignal-client-0.42.0.tgz#259d87233f1e065ae93cf8fe758bcc2461e3e814"
|
resolved "https://registry.yarnpkg.com/@signalapp/libsignal-client/-/libsignal-client-0.42.0.tgz#259d87233f1e065ae93cf8fe758bcc2461e3e814"
|
||||||
integrity sha512-03lr1LmMTSy3lto8lbdaQMvuvwqs7+fatNP3Kp6dHAnR/OoXh6Y1l493U5X86Z87XGdM0gfGntxZwZ+Qju9Dpg==
|
integrity sha512-03lr1LmMTSy3lto8lbdaQMvuvwqs7+fatNP3Kp6dHAnR/OoXh6Y1l493U5X86Z87XGdM0gfGntxZwZ+Qju9Dpg==
|
||||||
|
@ -7568,7 +7577,7 @@ caniuse-lite@^1.0.30001349, caniuse-lite@^1.0.30001541:
|
||||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001541.tgz"
|
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001541.tgz"
|
||||||
integrity sha512-bLOsqxDgTqUBkzxbNlSBt8annkDpQB9NdzdTbO2ooJ+eC/IQcvDspDc058g84ejCelF7vHUx57KIOjEecOHXaw==
|
integrity sha512-bLOsqxDgTqUBkzxbNlSBt8annkDpQB9NdzdTbO2ooJ+eC/IQcvDspDc058g84ejCelF7vHUx57KIOjEecOHXaw==
|
||||||
|
|
||||||
canvas@^2.6.1, "canvas@https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz":
|
canvas@^2.6.1, "canvas@https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz", dmg-license@^1.0.11, "dmg-license@https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz", jsdom@^15.2.1, "jsdom@https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz":
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz#cb46cf7e01574aa6390858149f66897afe53c9ca"
|
resolved "https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz#cb46cf7e01574aa6390858149f66897afe53c9ca"
|
||||||
|
|
||||||
|
@ -8946,10 +8955,6 @@ dmg-builder@24.6.3:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
dmg-license "^1.0.11"
|
dmg-license "^1.0.11"
|
||||||
|
|
||||||
dmg-license@^1.0.11, "dmg-license@https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz":
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz#cb46cf7e01574aa6390858149f66897afe53c9ca"
|
|
||||||
|
|
||||||
dns-equal@^1.0.0:
|
dns-equal@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
|
resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
|
||||||
|
@ -13378,10 +13383,6 @@ jsdoc@^4.0.0:
|
||||||
strip-json-comments "^3.1.0"
|
strip-json-comments "^3.1.0"
|
||||||
underscore "~1.13.2"
|
underscore "~1.13.2"
|
||||||
|
|
||||||
jsdom@^15.2.1, "jsdom@https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz":
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz#cb46cf7e01574aa6390858149f66897afe53c9ca"
|
|
||||||
|
|
||||||
jsesc@^1.3.0:
|
jsesc@^1.3.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
|
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b"
|
||||||
|
@ -18452,7 +18453,7 @@ string-length@^5.0.1:
|
||||||
char-regex "^2.0.0"
|
char-regex "^2.0.0"
|
||||||
strip-ansi "^7.0.1"
|
strip-ansi "^7.0.1"
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0":
|
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.2.3:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
@ -18495,15 +18496,6 @@ string-width@^4.1.0, string-width@^4.2.0:
|
||||||
is-fullwidth-code-point "^3.0.0"
|
is-fullwidth-code-point "^3.0.0"
|
||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
string-width@^4.2.3:
|
|
||||||
version "4.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
|
||||||
dependencies:
|
|
||||||
emoji-regex "^8.0.0"
|
|
||||||
is-fullwidth-code-point "^3.0.0"
|
|
||||||
strip-ansi "^6.0.1"
|
|
||||||
|
|
||||||
string-width@^5.0.1, string-width@^5.1.2:
|
string-width@^5.0.1, string-width@^5.1.2:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
||||||
|
@ -18584,7 +18576,7 @@ string_decoder@~1.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.0"
|
safe-buffer "~5.1.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
@ -18618,13 +18610,6 @@ strip-ansi@^6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex "^5.0.0"
|
ansi-regex "^5.0.0"
|
||||||
|
|
||||||
strip-ansi@^6.0.1:
|
|
||||||
version "6.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
|
||||||
dependencies:
|
|
||||||
ansi-regex "^5.0.1"
|
|
||||||
|
|
||||||
strip-ansi@^7.0.1:
|
strip-ansi@^7.0.1:
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||||
|
@ -20278,7 +20263,7 @@ workerpool@6.2.1:
|
||||||
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
|
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
|
||||||
integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
|
integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
|
||||||
|
|
||||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
|
@ -20312,15 +20297,6 @@ wrap-ansi@^6.2.0:
|
||||||
string-width "^4.1.0"
|
string-width "^4.1.0"
|
||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
wrap-ansi@^7.0.0:
|
|
||||||
version "7.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
|
||||||
dependencies:
|
|
||||||
ansi-styles "^4.0.0"
|
|
||||||
string-width "^4.1.0"
|
|
||||||
strip-ansi "^6.0.0"
|
|
||||||
|
|
||||||
wrap-ansi@^8.1.0:
|
wrap-ansi@^8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue