diff --git a/ts/test-node/updater/differential_test.ts b/ts/test-node/updater/differential_test.ts index ffd0d127dc1..327548ba85b 100644 --- a/ts/test-node/updater/differential_test.ts +++ b/ts/test-node/updater/differential_test.ts @@ -338,7 +338,7 @@ describe('updater/differential', () => { await assert.isRejected( download(outFile, data, { gotOptions: { - ...getGotOptions(), + ...(await getGotOptions()), timeout: { connect: 0.5 * durations.SECOND, lookup: 0.5 * durations.SECOND, diff --git a/ts/textsecure/SocketManager.ts b/ts/textsecure/SocketManager.ts index d826b5f8c2e..4b0222a9fb9 100644 --- a/ts/textsecure/SocketManager.ts +++ b/ts/textsecure/SocketManager.ts @@ -19,6 +19,7 @@ import * as durations from '../util/durations'; import { sleep } from '../util/sleep'; import { drop } from '../util/drop'; import { createProxyAgent } from '../util/createProxyAgent'; +import type { ProxyAgent } from '../util/createProxyAgent'; import { SocketStatus } from '../types/SocketStatus'; import * as Errors from '../types/errors'; import * as Bytes from '../Bytes'; @@ -84,7 +85,7 @@ export class SocketManager extends EventListener { private credentials?: WebAPICredentials; - private readonly proxyAgent?: ReturnType; + private proxyAgent?: ProxyAgent; private status = SocketStatus.CLOSED; @@ -105,10 +106,6 @@ export class SocketManager extends EventListener { constructor(private readonly options: SocketManagerOptions) { super(); - if (options.proxyUrl) { - this.proxyAgent = createProxyAgent(options.proxyUrl); - } - this.hasStoriesDisabled = options.hasStoriesDisabled; } @@ -338,6 +335,11 @@ export class SocketManager extends EventListener { url: string; extraHeaders?: Record; }): Promise { + // Create proxy agent lazily + if (this.options.proxyUrl && !this.proxyAgent) { + this.proxyAgent = await createProxyAgent(this.options.proxyUrl); + } + return connectWebSocket({ name: 'art-creator-provisioning', url, diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index 5bfa21097d3..0344ec0928f 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -31,6 +31,7 @@ import { toWebSafeBase64, fromWebSafeBase64 } from '../util/webSafeBase64'; import { getBasicAuth } from '../util/getBasicAuth'; import { createHTTPSAgent } from '../util/createHTTPSAgent'; import { createProxyAgent } from '../util/createProxyAgent'; +import type { ProxyAgent } from '../util/createProxyAgent'; import type { SocketStatus } from '../types/SocketStatus'; import { VerificationTransport } from '../types/VerificationTransport'; import { toLogFormat } from '../types/errors'; @@ -120,7 +121,7 @@ const GET_ATTACHMENT_CHUNK_TIMEOUT = 10 * durations.SECOND; type AgentCacheType = { [name: string]: { timestamp: number; - agent: ReturnType | Agent; + agent: ProxyAgent | Agent; }; }; const agents: AgentCacheType = {}; @@ -259,7 +260,7 @@ async function _promiseAjax( } agents[cacheKey] = { agent: proxyUrl - ? createProxyAgent(proxyUrl) + ? await createProxyAgent(proxyUrl) : createHTTPSAgent({ keepAlive: !options.disableSessionResumption, maxCachedSessions: options.disableSessionResumption ? 0 : undefined, @@ -1382,17 +1383,23 @@ export function initialize({ log.warn(`${logId}: Done`); } - let fetchAgent: Agent; - if (proxyUrl) { - fetchAgent = createProxyAgent(proxyUrl); - } else { - fetchAgent = createHTTPSAgent({ - keepAlive: false, - maxCachedSessions: 0, - }); - } - const fetchForLinkPreviews: linkPreviewFetch.FetchFn = (href, init) => - fetch(href, { ...init, agent: fetchAgent }); + let fetchAgent: Agent | undefined; + const fetchForLinkPreviews: linkPreviewFetch.FetchFn = async ( + href, + init + ) => { + if (!fetchAgent) { + if (proxyUrl) { + fetchAgent = await createProxyAgent(proxyUrl); + } else { + fetchAgent = createHTTPSAgent({ + keepAlive: false, + maxCachedSessions: 0, + }); + } + } + return fetch(href, { ...init, agent: fetchAgent }); + }; // Thanks, function hoisting! return { diff --git a/ts/textsecure/WebSocket.ts b/ts/textsecure/WebSocket.ts index 610a8ab08e0..fc5dfa1a7db 100644 --- a/ts/textsecure/WebSocket.ts +++ b/ts/textsecure/WebSocket.ts @@ -9,7 +9,7 @@ import { strictAssert } from '../util/assert'; import { explodePromise } from '../util/explodePromise'; import { getUserAgent } from '../util/getUserAgent'; import * as durations from '../util/durations'; -import type { createProxyAgent } from '../util/createProxyAgent'; +import type { ProxyAgent } from '../util/createProxyAgent'; import { createHTTPSAgent } from '../util/createHTTPSAgent'; import * as log from '../logging/log'; import * as Timers from '../Timers'; @@ -28,7 +28,7 @@ export type ConnectOptionsType = Readonly<{ url: string; certificateAuthority?: string; version: string; - proxyAgent?: ReturnType; + proxyAgent?: ProxyAgent; timeout?: number; extraHeaders?: Record; diff --git a/ts/textsecure/cds/CDSBase.ts b/ts/textsecure/cds/CDSBase.ts index d2ef09ed9cb..e121df7804c 100644 --- a/ts/textsecure/cds/CDSBase.ts +++ b/ts/textsecure/cds/CDSBase.ts @@ -10,6 +10,7 @@ import type { LoggerType } from '../../types/Logging'; import { isOlderThan } from '../../util/timestamp'; import { HOUR } from '../../util/durations'; import { createProxyAgent } from '../../util/createProxyAgent'; +import type { ProxyAgent } from '../../util/createProxyAgent'; // It is 24 hours, but we don't want latency between server and client to be // count. @@ -30,15 +31,11 @@ export abstract class CDSBase< Options extends CDSBaseOptionsType = CDSBaseOptionsType > { protected readonly logger: LoggerType; - protected readonly proxyAgent?: ReturnType; + protected proxyAgent?: ProxyAgent; protected cachedAuth?: CachedAuthType; constructor(protected readonly options: Options) { this.logger = options.logger; - - if (options.proxyUrl) { - this.proxyAgent = createProxyAgent(options.proxyUrl); - } } public abstract request( @@ -46,6 +43,11 @@ export abstract class CDSBase< ): Promise; protected async getAuth(): Promise { + // Lazily create proxy agent + if (!this.proxyAgent && this.options.proxyUrl) { + this.proxyAgent = await createProxyAgent(this.options.proxyUrl); + } + if (this.cachedAuth) { if (isOlderThan(this.cachedAuth.timestamp, CACHED_AUTH_TTL)) { this.cachedAuth = undefined; diff --git a/ts/updater/common.ts b/ts/updater/common.ts index 2544381370a..0d568163af1 100644 --- a/ts/updater/common.ts +++ b/ts/updater/common.ts @@ -602,7 +602,7 @@ export abstract class Updater { this.logger.info(`downloadUpdate: Downloading signature ${signatureUrl}`); const signature = Buffer.from( - await got(signatureUrl, getGotOptions()).text(), + await got(signatureUrl, await getGotOptions()).text(), 'hex' ); @@ -614,7 +614,10 @@ export abstract class Updater { this.logger.info( `downloadUpdate: Downloading blockmap ${blockMapUrl}` ); - const blockMap = await got(blockMapUrl, getGotOptions()).buffer(); + const blockMap = await got( + blockMapUrl, + await getGotOptions() + ).buffer(); await writeFile(tempBlockMapPath, blockMap); } catch (error) { this.logger.warn( @@ -751,7 +754,7 @@ export abstract class Updater { targetUpdatePath: string, updateOnProgress = false ): Promise { - const downloadStream = got.stream(updateFileUrl, getGotOptions()); + const downloadStream = got.stream(updateFileUrl, await getGotOptions()); const writeStream = createWriteStream(targetUpdatePath); await new Promise((resolve, reject) => { @@ -930,7 +933,7 @@ export function parseYaml(yaml: string): JSONUpdateSchema { async function getUpdateYaml(): Promise { const targetUrl = getUpdateCheckUrl(); - const body = await got(targetUrl, getGotOptions()).text(); + const body = await got(targetUrl, await getGotOptions()).text(); if (!body) { throw new Error('Got unexpected response back from update check'); diff --git a/ts/updater/differential.ts b/ts/updater/differential.ts index eec80e943b4..c8a30b9c0c4 100644 --- a/ts/updater/differential.ts +++ b/ts/updater/differential.ts @@ -15,6 +15,7 @@ import { strictAssert } from '../util/assert'; import { wrapEventEmitterOnce } from '../util/wrapEventEmitterOnce'; import type { LoggerType } from '../types/Logging'; import { getGotOptions } from './got'; +import type { GotOptions } from './got'; import { checkIntegrity } from './util'; const gunzip = promisify(nativeGunzip); @@ -74,7 +75,7 @@ export type DownloadOptionsType = Readonly<{ logger?: LoggerType; // Testing - gotOptions?: ReturnType; + gotOptions?: GotOptions; }>; export type DownloadRangesOptionsType = Readonly<{ @@ -86,7 +87,7 @@ export type DownloadRangesOptionsType = Readonly<{ chunkStatusCallback: (chunkSize: number) => void; // Testing - gotOptions?: ReturnType; + gotOptions?: GotOptions; }>; export function getBlockMapFileName(fileName: string): string { @@ -212,7 +213,7 @@ export async function prepareDownload({ const newBlockMapData = await got( getBlockMapFileName(newUrl), - getGotOptions() + await getGotOptions() ).buffer(); const newBlockMap = await parseBlockMap(newBlockMapData); @@ -343,7 +344,7 @@ export async function downloadRanges( logger, abortSignal, chunkStatusCallback, - gotOptions = getGotOptions(), + gotOptions = await getGotOptions(), } = options; logger?.info('updater/downloadRanges: downloading ranges', ranges.length); diff --git a/ts/updater/got.ts b/ts/updater/got.ts index 6d2bbb21a9b..aeaed5abc6c 100644 --- a/ts/updater/got.ts +++ b/ts/updater/got.ts @@ -24,13 +24,15 @@ export function getCertificateAuthority(): string { return config.get('certificateAuthority'); } -export function getGotOptions(): GotOptions { +export type { GotOptions }; + +export async function getGotOptions(): Promise { const certificateAuthority = getCertificateAuthority(); const proxyUrl = getProxyUrl(); const agent = proxyUrl ? { - http: createProxyAgent(proxyUrl), - https: createProxyAgent(proxyUrl), + http: await createProxyAgent(proxyUrl), + https: await createProxyAgent(proxyUrl), } : { http: new HTTPAgent(), diff --git a/ts/util/createProxyAgent.ts b/ts/util/createProxyAgent.ts index 7ce3f8bf4a7..47fdd94676f 100644 --- a/ts/util/createProxyAgent.ts +++ b/ts/util/createProxyAgent.ts @@ -1,8 +1,8 @@ // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import { ProxyAgent } from 'proxy-agent'; import net from 'net'; +import type { ProxyAgent } from 'proxy-agent'; import { URL } from 'url'; import type { LookupOptions, LookupAddress } from 'dns'; import { lookup } from 'dns/promises'; @@ -25,7 +25,9 @@ const SOCKS_PROTOCOLS = new Set([ 'socks5h:', ]); -export function createProxyAgent(proxyUrl: string): ProxyAgent { +export type { ProxyAgent }; + +export async function createProxyAgent(proxyUrl: string): Promise { const { port: portStr, hostname: proxyHost, protocol } = new URL(proxyUrl); let defaultPort: number | undefined; if (protocol === 'http:') { @@ -96,6 +98,8 @@ export function createProxyAgent(proxyUrl: string): ProxyAgent { } } + const { ProxyAgent } = await import('proxy-agent'); + return new ProxyAgent({ lookup: port !== undefined