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
|
||||
|
|
|
@ -2426,6 +2426,9 @@ ipc.on('get-config', async event => {
|
|||
preferredSystemLocales: getPreferredSystemLocales(),
|
||||
localeOverride: getLocaleOverride(),
|
||||
version: app.getVersion(),
|
||||
libsignalNetEnvironment: config.has('libsignalNetEnvironment')
|
||||
? config.get<string>('libsignalNetEnvironment')
|
||||
: undefined,
|
||||
buildCreation: config.get<number>('buildCreation'),
|
||||
buildExpiration: config.get<number>('buildExpiration'),
|
||||
challengeUrl: config.get<string>('challengeUrl'),
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
"@react-aria/utils": "3.16.0",
|
||||
"@react-spring/web": "9.5.5",
|
||||
"@signalapp/better-sqlite3": "8.7.1",
|
||||
"@signalapp/libsignal-client": "0.42.0",
|
||||
"@signalapp/libsignal-client": "0.44.0",
|
||||
"@signalapp/ringrtc": "2.39.3",
|
||||
"@signalapp/windows-dummy-keystroke": "1.0.0",
|
||||
"@types/fabric": "4.5.3",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Net } from '@signalapp/libsignal-client';
|
||||
import URL from 'url';
|
||||
import type { RequestInit, Response } from 'node-fetch';
|
||||
import { Headers } from 'node-fetch';
|
||||
|
@ -103,7 +104,10 @@ export class SocketManager extends EventListener {
|
|||
|
||||
private reconnectController: AbortController | undefined;
|
||||
|
||||
constructor(private readonly options: SocketManagerOptions) {
|
||||
constructor(
|
||||
private readonly libsignalNet: Net.Net,
|
||||
private readonly options: SocketManagerOptions
|
||||
) {
|
||||
super();
|
||||
|
||||
this.hasStoriesDisabled = options.hasStoriesDisabled;
|
||||
|
@ -565,14 +569,9 @@ export class SocketManager extends EventListener {
|
|||
}
|
||||
|
||||
private connectLibsignalUnauthenticated(): AbortableProcess<IWebSocketResource> {
|
||||
return new AbortableProcess<IWebSocketResource>(
|
||||
`WebSocket.connect(libsignal.${UNAUTHENTICATED_CHANNEL_NAME})`,
|
||||
{
|
||||
abort() {
|
||||
// noop
|
||||
},
|
||||
},
|
||||
Promise.resolve(new LibsignalWebSocketResource(this.options.version))
|
||||
return LibsignalWebSocketResource.connect(
|
||||
this.libsignalNet,
|
||||
UNAUTHENTICATED_CHANNEL_NAME
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -666,7 +665,8 @@ export class SocketManager extends EventListener {
|
|||
const url = `${this.options.url}${path}?${qs.encode(queryWithDefaults)}`;
|
||||
const { version } = this.options;
|
||||
|
||||
return connectWebSocket({
|
||||
const start = performance.now();
|
||||
const webSocketResourceConnection = connectWebSocket({
|
||||
name,
|
||||
url,
|
||||
version,
|
||||
|
@ -675,17 +675,69 @@ export class SocketManager extends EventListener {
|
|||
|
||||
extraHeaders,
|
||||
|
||||
createResource(socket: WebSocket): IWebSocketResource {
|
||||
return !resourceOptions.transportOption ||
|
||||
resourceOptions.transportOption === TransportOption.Original
|
||||
? new WebSocketResource(socket, resourceOptions)
|
||||
: new WebSocketResourceWithShadowing(
|
||||
socket,
|
||||
resourceOptions,
|
||||
version
|
||||
createResource(socket: WebSocket): WebSocketResource {
|
||||
const duration = (performance.now() - start).toFixed(1);
|
||||
log.info(
|
||||
`WebSocketResource(${resourceOptions.name}) connected in ${duration}ms`
|
||||
);
|
||||
return new WebSocketResource(socket, resourceOptions);
|
||||
},
|
||||
});
|
||||
|
||||
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(
|
||||
|
|
|
@ -16,6 +16,7 @@ import { z } from 'zod';
|
|||
import type { Readable } from 'stream';
|
||||
import type { connection as WebSocket } from 'websocket';
|
||||
|
||||
import { Net } from '@signalapp/libsignal-client';
|
||||
import { assertDev, strictAssert } from '../util/assert';
|
||||
import { isRecord } from '../util/isRecord';
|
||||
import * as durations from '../util/durations';
|
||||
|
@ -71,6 +72,7 @@ import * as log from '../logging/log';
|
|||
import { maybeParseUrl, urlPathFromComponents } from '../util/url';
|
||||
import { SECOND } from '../util/durations';
|
||||
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
|
||||
// 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 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(
|
||||
...toReplace: ReadonlyArray<string | undefined>
|
||||
): RedactUrl {
|
||||
|
@ -588,6 +616,7 @@ type InitializeOptionsType = {
|
|||
proxyUrl: string | undefined;
|
||||
version: string;
|
||||
directoryConfig: DirectoryConfigType;
|
||||
libsignalNetEnvironment: string | undefined;
|
||||
};
|
||||
|
||||
export type MessageType = Readonly<{
|
||||
|
@ -1249,6 +1278,7 @@ export function initialize({
|
|||
contentProxyUrl,
|
||||
proxyUrl,
|
||||
version,
|
||||
libsignalNetEnvironment,
|
||||
}: InitializeOptionsType): WebAPIConnectType {
|
||||
if (!isString(url)) {
|
||||
throw new Error('WebAPI.initialize: Invalid server url');
|
||||
|
@ -1290,6 +1320,17 @@ export function initialize({
|
|||
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
|
||||
// below function definitions.
|
||||
return {
|
||||
|
@ -1313,7 +1354,7 @@ export function initialize({
|
|||
|
||||
let activeRegistration: ExplodePromiseResultType<void> | undefined;
|
||||
|
||||
const socketManager = new SocketManager({
|
||||
const socketManager = new SocketManager(libsignalNet, {
|
||||
url,
|
||||
artCreatorUrl,
|
||||
certificateAuthority,
|
||||
|
@ -1344,7 +1385,7 @@ export function initialize({
|
|||
|
||||
const { directoryUrl, directoryMRENCLAVE } = directoryConfig;
|
||||
|
||||
const cds = new CDSI({
|
||||
const cds = new CDSI(libsignalNet, {
|
||||
logger: log,
|
||||
proxyUrl,
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import { ConnectTimeoutError, HTTPError } from './Errors';
|
|||
import { handleStatusCode, translateError } from './Utils';
|
||||
|
||||
const TEN_SECONDS = 10 * durations.SECOND;
|
||||
const WEBSOCKET_CONNECT_TIMEOUT = TEN_SECONDS;
|
||||
const KEEPALIVE_INTERVAL_MS = TEN_SECONDS;
|
||||
|
||||
export type IResource = {
|
||||
|
@ -42,7 +43,7 @@ export function connect<Resource extends IResource>({
|
|||
version,
|
||||
proxyAgent,
|
||||
extraHeaders = {},
|
||||
timeout = TEN_SECONDS,
|
||||
timeout = WEBSOCKET_CONNECT_TIMEOUT,
|
||||
createResource,
|
||||
}: ConnectOptionsType<Resource>): AbortableProcess<Resource> {
|
||||
const fixedScheme = url
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
/* eslint-disable @typescript-eslint/no-namespace */
|
||||
/* eslint-disable @typescript-eslint/brace-style */
|
||||
|
||||
import { Net } from '@signalapp/libsignal-client';
|
||||
import type { connection as WebSocket, IMessage } from 'websocket';
|
||||
import Long from 'long';
|
||||
import pTimeout from 'p-timeout';
|
||||
|
@ -35,8 +34,9 @@ import net from 'net';
|
|||
import { z } from 'zod';
|
||||
import { clearInterval } from 'timers';
|
||||
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 EventTarget from './EventTarget';
|
||||
|
||||
|
@ -50,14 +50,13 @@ import { SignalService as Proto } from '../protobuf';
|
|||
import * as log from '../logging/log';
|
||||
import * as Timers from '../Timers';
|
||||
import type { IResource } from './WebSocket';
|
||||
import { isProduction, isStaging } from '../util/version';
|
||||
import { isProduction } from '../util/version';
|
||||
|
||||
import { ToastType } from '../types/Toast';
|
||||
import { AbortableProcess } from '../util/AbortableProcess';
|
||||
|
||||
const THIRTY_SECONDS = 30 * durations.SECOND;
|
||||
|
||||
const HEALTHCHECK_TIMEOUT = durations.SECOND;
|
||||
|
||||
const STATS_UPDATE_INTERVAL = durations.MINUTE;
|
||||
|
||||
const MAX_MESSAGE_SIZE = 512 * 1024;
|
||||
|
@ -83,6 +82,7 @@ export namespace IpVersion {
|
|||
}
|
||||
|
||||
const AggregatedStatsSchema = z.object({
|
||||
connectionFailures: z.number(),
|
||||
requestsCompared: z.number(),
|
||||
ipVersionMismatches: z.number(),
|
||||
unexpectedReconnects: z.number(),
|
||||
|
@ -127,6 +127,7 @@ export namespace AggregatedStats {
|
|||
export function add(a: AggregatedStats, b: AggregatedStats): AggregatedStats {
|
||||
return {
|
||||
requestsCompared: a.requestsCompared + b.requestsCompared,
|
||||
connectionFailures: a.connectionFailures + b.connectionFailures,
|
||||
healthcheckFailures: a.healthcheckFailures + b.healthcheckFailures,
|
||||
ipVersionMismatches: a.ipVersionMismatches + b.ipVersionMismatches,
|
||||
unexpectedReconnects: a.unexpectedReconnects + b.unexpectedReconnects,
|
||||
|
@ -138,6 +139,7 @@ export namespace AggregatedStats {
|
|||
export function createEmpty(): AggregatedStats {
|
||||
return {
|
||||
requestsCompared: 0,
|
||||
connectionFailures: 0,
|
||||
ipVersionMismatches: 0,
|
||||
unexpectedReconnects: 0,
|
||||
healthcheckFailures: 0,
|
||||
|
@ -152,9 +154,10 @@ export namespace AggregatedStats {
|
|||
return false;
|
||||
}
|
||||
return (
|
||||
stats.healthcheckBadStatus + stats.healthcheckFailures > 20 ||
|
||||
stats.ipVersionMismatches > 50 ||
|
||||
stats.unexpectedReconnects > 50
|
||||
stats.healthcheckBadStatus +
|
||||
stats.healthcheckFailures +
|
||||
stats.connectionFailures >
|
||||
20 || stats.unexpectedReconnects > 50
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -261,11 +264,42 @@ export interface IWebSocketResource extends IResource {
|
|||
}
|
||||
|
||||
export class LibsignalWebSocketResource implements IWebSocketResource {
|
||||
private readonly net: Net.Net;
|
||||
constructor(
|
||||
private readonly chatService: Net.ChatService,
|
||||
private readonly socketIpVersion: IpVersion | undefined
|
||||
) {}
|
||||
|
||||
constructor(version: string) {
|
||||
this.net = new Net.Net(
|
||||
isStaging(version) ? Net.Environment.Staging : Net.Environment.Production
|
||||
public static connect(
|
||||
libsignalNet: Net.Net,
|
||||
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;
|
||||
}
|
||||
|
||||
public ipVersion(): IpVersion | undefined {
|
||||
return this.socketIpVersion;
|
||||
}
|
||||
|
||||
public addEventListener(
|
||||
_name: 'close',
|
||||
_handler: (ev: CloseEvent) => void
|
||||
|
@ -281,13 +319,11 @@ export class LibsignalWebSocketResource implements IWebSocketResource {
|
|||
}
|
||||
|
||||
public close(_code?: number, _reason?: string): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.net.disconnectChatService();
|
||||
drop(this.chatService.disconnect());
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.net.disconnectChatService();
|
||||
drop(this.chatService.disconnect());
|
||||
}
|
||||
|
||||
public forceKeepAlive(): void {
|
||||
|
@ -301,16 +337,15 @@ export class LibsignalWebSocketResource implements IWebSocketResource {
|
|||
|
||||
public async sendRequestGetDebugInfo(
|
||||
options: SendRequestOptions
|
||||
): Promise<[Response, DebugInfo]> {
|
||||
const { response, debugInfo } = await this.net.unauthenticatedFetchAndDebug(
|
||||
{
|
||||
): Promise<[Response, ChatServiceDebugInfo]> {
|
||||
const { response, debugInfo } =
|
||||
await this.chatService.unauthenticatedFetchAndDebug({
|
||||
verb: options.verb,
|
||||
path: options.path,
|
||||
headers: options.headers ? options.headers : [],
|
||||
body: options.body,
|
||||
timeoutMillis: options.timeout,
|
||||
}
|
||||
);
|
||||
});
|
||||
return [
|
||||
new Response(response.body, {
|
||||
status: response.status,
|
||||
|
@ -323,9 +358,7 @@ export class LibsignalWebSocketResource implements IWebSocketResource {
|
|||
}
|
||||
|
||||
export class WebSocketResourceWithShadowing implements IWebSocketResource {
|
||||
private main: WebSocketResource;
|
||||
|
||||
private shadowing: LibsignalWebSocketResource;
|
||||
private shadowing: LibsignalWebSocketResource | undefined;
|
||||
|
||||
private stats: AggregatedStats;
|
||||
|
||||
|
@ -336,12 +369,10 @@ export class WebSocketResourceWithShadowing implements IWebSocketResource {
|
|||
private logId: string;
|
||||
|
||||
constructor(
|
||||
socket: WebSocket,
|
||||
options: WebSocketResourceOptions,
|
||||
version: string
|
||||
private readonly main: WebSocketResource,
|
||||
private readonly shadowingConnection: AbortableProcess<LibsignalWebSocketResource>,
|
||||
options: WebSocketResourceOptions
|
||||
) {
|
||||
this.main = new WebSocketResource(socket, options);
|
||||
this.shadowing = new LibsignalWebSocketResource(version);
|
||||
this.stats = AggregatedStats.createEmpty();
|
||||
this.logId = `WebSocketResourceWithShadowing(${options.name})`;
|
||||
this.statsTimer = setInterval(
|
||||
|
@ -351,6 +382,28 @@ export class WebSocketResourceWithShadowing implements IWebSocketResource {
|
|||
this.shadowingWithReporting =
|
||||
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 => {
|
||||
clearInterval(this.statsTimer);
|
||||
this.updateStats(options.name);
|
||||
|
@ -392,12 +445,20 @@ export class WebSocketResourceWithShadowing implements IWebSocketResource {
|
|||
|
||||
public close(): void {
|
||||
this.main.close();
|
||||
if (this.shadowing) {
|
||||
this.shadowing.close();
|
||||
} else {
|
||||
this.shadowingConnection.abort();
|
||||
}
|
||||
}
|
||||
|
||||
public shutdown(): void {
|
||||
this.main.shutdown();
|
||||
if (this.shadowing) {
|
||||
this.shadowing.shutdown();
|
||||
} else {
|
||||
this.shadowingConnection.abort();
|
||||
}
|
||||
}
|
||||
|
||||
public forceKeepAlive(timeout?: number): void {
|
||||
|
@ -421,12 +482,20 @@ export class WebSocketResourceWithShadowing implements IWebSocketResource {
|
|||
}
|
||||
|
||||
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 {
|
||||
const [healthCheckResult, debugInfo] =
|
||||
await this.shadowing.sendRequestGetDebugInfo({
|
||||
verb: 'GET',
|
||||
path: '/v1/keepalive',
|
||||
timeout: HEALTHCHECK_TIMEOUT,
|
||||
timeout: KEEPALIVE_TIMEOUT_MS,
|
||||
});
|
||||
this.stats.requestsCompared += 1;
|
||||
if (!isSuccessfulStatusCode(healthCheckResult.status)) {
|
||||
|
@ -435,18 +504,7 @@ export class WebSocketResourceWithShadowing implements IWebSocketResource {
|
|||
`${this.logId}: keepalive via libsignal responded with status [${healthCheckResult.status}]`
|
||||
);
|
||||
}
|
||||
const ipVersion = IpVersion.fromDebugInfoCode(debugInfo.ipType);
|
||||
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;
|
||||
}
|
||||
this.stats.unexpectedReconnects = debugInfo.reconnectCount;
|
||||
} catch (error) {
|
||||
this.stats.healthcheckFailures += 1;
|
||||
log.warn(
|
||||
|
@ -823,7 +881,7 @@ const KEEPALIVE_INTERVAL_MS = 30 * durations.SECOND;
|
|||
// immediate disconnect.
|
||||
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.
|
||||
const KEEPALIVE_TIMEOUT_MS = 30 * durations.SECOND;
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { Net } from '@signalapp/libsignal-client';
|
||||
import type { connection as WebSocket } from 'websocket';
|
||||
|
||||
import * as Bytes from '../../Bytes';
|
||||
|
@ -16,8 +17,8 @@ export type CDSIOptionsType = Readonly<{
|
|||
export class CDSI extends CDSSocketManagerBase<CDSISocket, CDSIOptionsType> {
|
||||
private readonly mrenclave: Buffer;
|
||||
|
||||
constructor(options: CDSIOptionsType) {
|
||||
super(options);
|
||||
constructor(libsignalNet: Net.Net, options: CDSIOptionsType) {
|
||||
super(libsignalNet, options);
|
||||
|
||||
this.mrenclave = Buffer.from(Bytes.fromHex(options.mrenclave));
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { RateLimitedError as NetRateLimitedError } from '@signalapp/libsignal-client';
|
||||
import {
|
||||
import type {
|
||||
RateLimitedError as NetRateLimitedError,
|
||||
Net,
|
||||
} from '@signalapp/libsignal-client';
|
||||
import {
|
||||
ErrorCode as LibSignalErrorCode,
|
||||
LibSignalErrorBase,
|
||||
} from '@signalapp/libsignal-client';
|
||||
|
@ -25,7 +27,6 @@ import type {
|
|||
} from './Types.d';
|
||||
import { RateLimitedError } from './RateLimitedError';
|
||||
import { connect as connectWebSocket } from '../WebSocket';
|
||||
import { Environment, getEnvironment } from '../../environment';
|
||||
|
||||
const REQUEST_TIMEOUT = 10 * SECOND;
|
||||
|
||||
|
@ -42,6 +43,10 @@ export abstract class CDSSocketManagerBase<
|
|||
> extends CDSBase<Options> {
|
||||
private retryAfter?: number;
|
||||
|
||||
constructor(private readonly libsignalNet: Net.Net, options: Options) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
public async request(
|
||||
options: CDSRequestOptionsType
|
||||
): Promise<CDSResponseType> {
|
||||
|
@ -106,24 +111,22 @@ export abstract class CDSSocketManagerBase<
|
|||
options: CDSRequestOptionsType
|
||||
): Promise<CDSResponseType> {
|
||||
const log = this.logger;
|
||||
const {
|
||||
acisAndAccessKeys,
|
||||
e164s,
|
||||
timeout = REQUEST_TIMEOUT,
|
||||
returnAcisWithoutUaks = false,
|
||||
} = options;
|
||||
const { acisAndAccessKeys, e164s, returnAcisWithoutUaks = false } = options;
|
||||
const auth = await this.getAuth();
|
||||
|
||||
log.info('CDSSocketManager: making request via libsignal');
|
||||
const net = new Net.Net(this.libsignalNetEnvironment());
|
||||
try {
|
||||
log.info('CDSSocketManager: starting lookup request');
|
||||
const response = await net.cdsiLookup(auth, {
|
||||
|
||||
const { timeout = REQUEST_TIMEOUT } = options;
|
||||
const response = await pTimeout(
|
||||
this.libsignalNet.cdsiLookup(auth, {
|
||||
acisAndAccessKeys,
|
||||
e164s,
|
||||
timeout,
|
||||
returnAcisWithoutUaks,
|
||||
});
|
||||
}),
|
||||
timeout
|
||||
);
|
||||
|
||||
log.info('CDSSocketManager: lookup request finished');
|
||||
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> {
|
||||
return connectWebSocket<Socket>({
|
||||
name: 'CDSSocket',
|
||||
|
|
|
@ -71,6 +71,7 @@ export const rendererConfigSchema = z.object({
|
|||
resourcesUrl: configRequiredStringSchema,
|
||||
userDataPath: configRequiredStringSchema,
|
||||
version: configRequiredStringSchema,
|
||||
libsignalNetEnvironment: configOptionalStringSchema,
|
||||
directoryConfig: directoryConfigSchema,
|
||||
|
||||
// Only used by main window
|
||||
|
|
|
@ -175,6 +175,7 @@ if (config.ciMode !== 'full' && config.environment !== 'test') {
|
|||
|
||||
type NetworkStatistics = {
|
||||
signalConnectionCount?: string;
|
||||
unauthorizedConnectionFailures?: string;
|
||||
unauthorizedRequestsCompared?: string;
|
||||
unauthorizedHealthcheckFailures?: string;
|
||||
unauthorizedHealthcheckBadStatus?: string;
|
||||
|
@ -206,6 +207,9 @@ ipc.on('additional-log-data-request', async event => {
|
|||
if (unauthorizedStats.requestsCompared > 0) {
|
||||
networkStatistics = {
|
||||
...networkStatistics,
|
||||
unauthorizedConnectionFailures: formatCountForLogging(
|
||||
unauthorizedStats.connectionFailures
|
||||
),
|
||||
unauthorizedRequestsCompared: formatCountForLogging(
|
||||
unauthorizedStats.requestsCompared
|
||||
),
|
||||
|
|
|
@ -38,6 +38,7 @@ window.WebAPI = window.textsecure.WebAPI.initialize({
|
|||
contentProxyUrl: config.contentProxyUrl,
|
||||
proxyUrl: config.proxyUrl,
|
||||
version: config.version,
|
||||
libsignalNetEnvironment: config.libsignalNetEnvironment,
|
||||
});
|
||||
|
||||
window.libphonenumberInstance = PhoneNumberUtil.getInstance();
|
||||
|
|
52
yarn.lock
52
yarn.lock
|
@ -3983,7 +3983,16 @@
|
|||
bindings "^1.5.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"
|
||||
resolved "https://registry.yarnpkg.com/@signalapp/libsignal-client/-/libsignal-client-0.42.0.tgz#259d87233f1e065ae93cf8fe758bcc2461e3e814"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/nop/-/nop-1.0.0.tgz#cb46cf7e01574aa6390858149f66897afe53c9ca"
|
||||
|
||||
|
@ -8946,10 +8955,6 @@ dmg-builder@24.6.3:
|
|||
optionalDependencies:
|
||||
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:
|
||||
version "1.0.0"
|
||||
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"
|
||||
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:
|
||||
version "1.3.0"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
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"
|
||||
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:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
||||
|
@ -18584,7 +18576,7 @@ string_decoder@~1.1.1:
|
|||
dependencies:
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
|
@ -18618,13 +18610,6 @@ strip-ansi@^6.0.0:
|
|||
dependencies:
|
||||
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:
|
||||
version "7.1.0"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
|
@ -20312,15 +20297,6 @@ wrap-ansi@^6.2.0:
|
|||
string-width "^4.1.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:
|
||||
version "8.1.0"
|
||||
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