| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | // Copyright 2021 Signal Messenger, LLC
 | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-only
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-04 14:39:52 -07:00
										 |  |  | import type { Net } from '@signalapp/libsignal-client'; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | import URL from 'url'; | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  | import type { RequestInit, Response } from 'node-fetch'; | 
					
						
							|  |  |  | import { Headers } from 'node-fetch'; | 
					
						
							| 
									
										
										
										
											2021-11-09 00:32:31 +01:00
										 |  |  | import type { connection as WebSocket } from 'websocket'; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | import qs from 'querystring'; | 
					
						
							|  |  |  | import EventListener from 'events'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  | import { AbortableProcess } from '../util/AbortableProcess'; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | import { strictAssert } from '../util/assert'; | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  | import { | 
					
						
							|  |  |  |   BackOff, | 
					
						
							|  |  |  |   FIBONACCI_TIMEOUTS, | 
					
						
							|  |  |  |   EXTENDED_FIBONACCI_TIMEOUTS, | 
					
						
							|  |  |  | } from '../util/BackOff'; | 
					
						
							| 
									
										
										
										
											2021-08-26 09:10:58 -05:00
										 |  |  | import * as durations from '../util/durations'; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | import { sleep } from '../util/sleep'; | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  | import { drop } from '../util/drop'; | 
					
						
							| 
									
										
										
										
											2023-08-30 01:58:48 +02:00
										 |  |  | import { createProxyAgent } from '../util/createProxyAgent'; | 
					
						
							| 
									
										
										
										
											2024-03-20 11:05:10 -07:00
										 |  |  | import type { ProxyAgent } from '../util/createProxyAgent'; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | import { SocketStatus } from '../types/SocketStatus'; | 
					
						
							|  |  |  | import * as Errors from '../types/errors'; | 
					
						
							|  |  |  | import * as Bytes from '../Bytes'; | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  | import * as log from '../logging/log'; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-26 14:15:33 -05:00
										 |  |  | import type { | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   IncomingWebSocketRequest, | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |   IWebSocketResource, | 
					
						
							|  |  |  |   WebSocketResourceOptions, | 
					
						
							|  |  |  | } from './WebsocketResources'; | 
					
						
							|  |  |  | import WebSocketResource, { | 
					
						
							|  |  |  |   LibsignalWebSocketResource, | 
					
						
							|  |  |  |   TransportOption, | 
					
						
							|  |  |  |   WebSocketResourceWithShadowing, | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | } from './WebsocketResources'; | 
					
						
							| 
									
										
										
										
											2024-05-02 16:53:53 -04:00
										 |  |  | import { ConnectTimeoutError, HTTPError } from './Errors'; | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  | import type { IRequestHandler, WebAPICredentials } from './Types.d'; | 
					
						
							| 
									
										
										
										
											2021-11-09 00:32:31 +01:00
										 |  |  | import { connect as connectWebSocket } from './WebSocket'; | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  | import { isAlpha, isBeta, isStaging } from '../util/version'; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-26 09:10:58 -05:00
										 |  |  | const FIVE_MINUTES = 5 * durations.MINUTE; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-28 15:12:43 -07:00
										 |  |  | const JITTER = 5 * durations.SECOND; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  | const OFFLINE_KEEPALIVE_TIMEOUT_MS = 5 * durations.SECOND; | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  | export const UNAUTHENTICATED_CHANNEL_NAME = 'unauthenticated'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export const AUTHENTICATED_CHANNEL_NAME = 'authenticated'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | export type SocketManagerOptions = Readonly<{ | 
					
						
							|  |  |  |   url: string; | 
					
						
							|  |  |  |   certificateAuthority: string; | 
					
						
							|  |  |  |   version: string; | 
					
						
							|  |  |  |   proxyUrl?: string; | 
					
						
							| 
									
										
										
										
											2022-10-04 17:48:25 -07:00
										 |  |  |   hasStoriesDisabled: boolean; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | }>; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-24 08:58:40 -07:00
										 |  |  | // This class manages two websocket resources:
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  | // - Authenticated IWebSocketResource which uses supplied WebAPICredentials and
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | //   automatically reconnects on closed socket (using back off)
 | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  | // - Unauthenticated IWebSocketResource that is created on the first outgoing
 | 
					
						
							| 
									
										
										
										
											2021-08-24 08:58:40 -07:00
										 |  |  | //   unauthenticated request and is periodically rotated (5 minutes since first
 | 
					
						
							|  |  |  | //   activity on the socket).
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | //
 | 
					
						
							|  |  |  | // Incoming requests on authenticated resource are funneled into the registered
 | 
					
						
							|  |  |  | // request handlers (`registerRequestHandler`) or queued internally until at
 | 
					
						
							|  |  |  | // least one such request handler becomes available.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Incoming requests on unauthenticated resource are not currently supported.
 | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  | // IWebSocketResource is responsible for their immediate termination.
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | export class SocketManager extends EventListener { | 
					
						
							| 
									
										
										
										
											2021-09-28 15:12:43 -07:00
										 |  |  |   private backOff = new BackOff(FIBONACCI_TIMEOUTS, { | 
					
						
							|  |  |  |     jitter: JITTER, | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |   private authenticated?: AbortableProcess<IWebSocketResource>; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |   private unauthenticated?: AbortableProcess<IWebSocketResource>; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-24 08:58:40 -07:00
										 |  |  |   private unauthenticatedExpirationTimer?: NodeJS.Timeout; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   private credentials?: WebAPICredentials; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 01:13:15 +02:00
										 |  |  |   private lazyProxyAgent?: Promise<ProxyAgent>; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   private status = SocketStatus.CLOSED; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private requestHandlers = new Set<IRequestHandler>(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private incomingRequestQueue = new Array<IncomingWebSocketRequest>(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |   private isNavigatorOffline = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private privIsOnline: boolean | undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private isRemotelyExpired = false; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-04 17:48:25 -07:00
										 |  |  |   private hasStoriesDisabled: boolean; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |   private reconnectController: AbortController | undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-04 14:39:52 -07:00
										 |  |  |   constructor( | 
					
						
							|  |  |  |     private readonly libsignalNet: Net.Net, | 
					
						
							|  |  |  |     private readonly options: SocketManagerOptions | 
					
						
							|  |  |  |   ) { | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     super(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-04 17:48:25 -07:00
										 |  |  |     this.hasStoriesDisabled = options.hasStoriesDisabled; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public getStatus(): SocketStatus { | 
					
						
							|  |  |  |     return this.status; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-02 16:53:53 -04:00
										 |  |  |   private markOffline() { | 
					
						
							|  |  |  |     if (this.privIsOnline !== false) { | 
					
						
							|  |  |  |       this.privIsOnline = false; | 
					
						
							|  |  |  |       this.emit('offline'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   // Update WebAPICredentials and reconnect authenticated resource if
 | 
					
						
							|  |  |  |   // credentials changed
 | 
					
						
							|  |  |  |   public async authenticate(credentials: WebAPICredentials): Promise<void> { | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |     if (this.isRemotelyExpired) { | 
					
						
							|  |  |  |       throw new HTTPError('SocketManager remotely expired', { | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |         code: 0, | 
					
						
							|  |  |  |         headers: {}, | 
					
						
							|  |  |  |         stack: new Error().stack, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const { username, password } = credentials; | 
					
						
							|  |  |  |     if (!username && !password) { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |       log.warn('SocketManager authenticate was called without credentials'); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |       this.credentials && | 
					
						
							|  |  |  |       this.credentials.username === username && | 
					
						
							|  |  |  |       this.credentials.password === password && | 
					
						
							|  |  |  |       this.authenticated | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         await this.authenticated.getResult(); | 
					
						
							|  |  |  |       } catch (error) { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |         log.warn( | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |           'SocketManager: failed to wait for existing authenticated socket ' + | 
					
						
							|  |  |  |             ` due to error: ${Errors.toLogFormat(error)}` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.credentials = credentials; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-04 17:48:25 -07:00
										 |  |  |     log.info( | 
					
						
							|  |  |  |       'SocketManager: connecting authenticated socket ' + | 
					
						
							|  |  |  |         `(hasStoriesDisabled=${this.hasStoriesDisabled})` | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-16 13:18:42 -07:00
										 |  |  |     this.setStatus(SocketStatus.CONNECTING); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const process = this.connectResource({ | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |       name: AUTHENTICATED_CHANNEL_NAME, | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       path: '/v1/websocket/', | 
					
						
							|  |  |  |       query: { login: username, password }, | 
					
						
							| 
									
										
										
										
											2024-04-18 01:13:15 +02:00
										 |  |  |       proxyAgent: await this.getProxyAgent(), | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       resourceOptions: { | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |         name: AUTHENTICATED_CHANNEL_NAME, | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |         keepalive: { path: '/v1/keepalive' }, | 
					
						
							|  |  |  |         handleRequest: (req: IncomingWebSocketRequest): void => { | 
					
						
							|  |  |  |           this.queueOrHandleRequest(req); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2022-10-04 17:48:25 -07:00
										 |  |  |       extraHeaders: { | 
					
						
							|  |  |  |         'X-Signal-Receive-Stories': String(!this.hasStoriesDisabled), | 
					
						
							|  |  |  |       }, | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Cancel previous connect attempt or close socket
 | 
					
						
							|  |  |  |     this.authenticated?.abort(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.authenticated = process; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const reconnect = async (): Promise<void> => { | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |       if (this.isRemotelyExpired) { | 
					
						
							|  |  |  |         log.info('SocketManager: remotely expired, not reconnecting'); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       const timeout = this.backOff.getAndIncrement(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |       log.info( | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |         'SocketManager: reconnecting authenticated socket ' + | 
					
						
							|  |  |  |           `after ${timeout}ms` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |       const reconnectController = new AbortController(); | 
					
						
							|  |  |  |       this.reconnectController = reconnectController; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         await sleep(timeout, reconnectController.signal); | 
					
						
							|  |  |  |       } catch { | 
					
						
							|  |  |  |         log.info('SocketManager: reconnect cancelled'); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |         return; | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |       } finally { | 
					
						
							|  |  |  |         if (this.reconnectController === reconnectController) { | 
					
						
							|  |  |  |           this.reconnectController = undefined; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (this.authenticated) { | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |         log.info('SocketManager: authenticated socket already connecting'); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       strictAssert(this.credentials !== undefined, 'Missing credentials'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         await this.authenticate(this.credentials); | 
					
						
							|  |  |  |       } catch (error) { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |         log.info( | 
					
						
							| 
									
										
										
										
											2022-02-09 22:33:19 +02:00
										 |  |  |           'SocketManager: authenticated socket failed to reconnect ' + | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |             `due to error ${Errors.toLogFormat(error)}` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         return reconnect(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |     let authenticated: IWebSocketResource; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     try { | 
					
						
							|  |  |  |       authenticated = await process.getResult(); | 
					
						
							| 
									
										
										
										
											2021-09-16 13:18:42 -07:00
										 |  |  |       this.setStatus(SocketStatus.OPEN); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     } catch (error) { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |       log.warn( | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |         'SocketManager: authenticated socket connection failed with ' + | 
					
						
							|  |  |  |           `error: ${Errors.toLogFormat(error)}` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-15 11:44:27 -07:00
										 |  |  |       // The socket was deliberately closed, don't follow up
 | 
					
						
							|  |  |  |       if (this.authenticated !== process) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       this.dropAuthenticated(process); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       if (error instanceof HTTPError) { | 
					
						
							|  |  |  |         const { code } = error; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (code === 401 || code === 403) { | 
					
						
							|  |  |  |           this.emit('authError', error); | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-28 15:12:43 -07:00
										 |  |  |         if (!(code >= 500 && code <= 599) && code !== -1) { | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |           // No reconnect attempt should be made
 | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-03-12 12:52:02 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-05-02 16:53:53 -04:00
										 |  |  |         if (code === -1) { | 
					
						
							|  |  |  |           this.markOffline(); | 
					
						
							| 
									
										
										
										
											2024-03-12 12:52:02 -07:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2024-05-02 16:53:53 -04:00
										 |  |  |       } else if (error instanceof ConnectTimeoutError) { | 
					
						
							|  |  |  |         this.markOffline(); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |       drop(reconnect()); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-14 13:51:49 -07:00
										 |  |  |     log.info( | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |       `SocketManager: connected authenticated socket (localPort: ${authenticated.localPort()})` | 
					
						
							| 
									
										
										
										
											2023-06-14 13:51:49 -07:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-03 10:05:20 -07:00
										 |  |  |     window.logAuthenticatedConnect?.(); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     this.backOff.reset(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     authenticated.addEventListener('close', ({ code, reason }): void => { | 
					
						
							|  |  |  |       if (this.authenticated !== process) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |       log.warn( | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |         'SocketManager: authenticated socket closed ' + | 
					
						
							|  |  |  |           `with code=${code} and reason=${reason}` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       this.dropAuthenticated(process); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (code === 3000) { | 
					
						
							|  |  |  |         // Intentional disconnect
 | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-21 12:33:56 -08:00
										 |  |  |       if (code === 4409) { | 
					
						
							| 
									
										
										
										
											2024-03-18 10:08:36 -07:00
										 |  |  |         log.error('SocketManager: got 4409, connected on another device'); | 
					
						
							| 
									
										
										
										
											2024-02-21 12:33:56 -08:00
										 |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |       drop(reconnect()); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Either returns currently connecting/active authenticated
 | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |   // IWebSocketResource or connects a fresh one.
 | 
					
						
							|  |  |  |   public async getAuthenticatedResource(): Promise<IWebSocketResource> { | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     if (!this.authenticated) { | 
					
						
							|  |  |  |       strictAssert(this.credentials !== undefined, 'Missing credentials'); | 
					
						
							|  |  |  |       await this.authenticate(this.credentials); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     strictAssert(this.authenticated !== undefined, 'Authentication failed'); | 
					
						
							|  |  |  |     return this.authenticated.getResult(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |   // Creates new IWebSocketResource for AccountManager's provisioning
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   public async getProvisioningResource( | 
					
						
							|  |  |  |     handler: IRequestHandler | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |   ): Promise<IWebSocketResource> { | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |     if (this.isRemotelyExpired) { | 
					
						
							|  |  |  |       throw new Error('Remotely expired, not connecting provisioning socket'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     return this.connectResource({ | 
					
						
							| 
									
										
										
										
											2021-12-01 19:55:17 +01:00
										 |  |  |       name: 'provisioning', | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       path: '/v1/websocket/provisioning/', | 
					
						
							| 
									
										
										
										
											2024-04-18 01:13:15 +02:00
										 |  |  |       proxyAgent: await this.getProxyAgent(), | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       resourceOptions: { | 
					
						
							| 
									
										
										
										
											2023-06-06 17:36:38 -07:00
										 |  |  |         name: 'provisioning', | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |         handleRequest: (req: IncomingWebSocketRequest): void => { | 
					
						
							|  |  |  |           handler.handleRequest(req); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         keepalive: { path: '/v1/keepalive/provisioning' }, | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }).getResult(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-27 14:34:43 -08:00
										 |  |  |   // Creates new WebSocket for Art Creator provisioning
 | 
					
						
							|  |  |  |   public async connectExternalSocket({ | 
					
						
							|  |  |  |     url, | 
					
						
							|  |  |  |     extraHeaders, | 
					
						
							|  |  |  |   }: { | 
					
						
							|  |  |  |     url: string; | 
					
						
							|  |  |  |     extraHeaders?: Record<string, string>; | 
					
						
							|  |  |  |   }): Promise<WebSocket> { | 
					
						
							| 
									
										
										
										
											2024-04-18 01:13:15 +02:00
										 |  |  |     const proxyAgent = await this.getProxyAgent(); | 
					
						
							| 
									
										
										
										
											2024-03-20 11:05:10 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-27 14:34:43 -08:00
										 |  |  |     return connectWebSocket({ | 
					
						
							|  |  |  |       name: 'art-creator-provisioning', | 
					
						
							|  |  |  |       url, | 
					
						
							|  |  |  |       version: this.options.version, | 
					
						
							| 
									
										
										
										
											2024-04-18 01:13:15 +02:00
										 |  |  |       proxyAgent, | 
					
						
							| 
									
										
										
										
											2023-02-27 14:34:43 -08:00
										 |  |  |       extraHeaders, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       createResource(socket: WebSocket): WebSocket { | 
					
						
							|  |  |  |         return socket; | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     }).getResult(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   // Fetch-compatible wrapper around underlying unauthenticated/authenticated
 | 
					
						
							|  |  |  |   // websocket resources. This wrapper supports only limited number of features
 | 
					
						
							|  |  |  |   // of node-fetch despite being API compatible.
 | 
					
						
							|  |  |  |   public async fetch(url: string, init: RequestInit): Promise<Response> { | 
					
						
							|  |  |  |     const headers = new Headers(init.headers); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |     let resource: IWebSocketResource; | 
					
						
							| 
									
										
										
										
											2021-08-03 17:37:17 -07:00
										 |  |  |     if (this.isAuthenticated(headers)) { | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       resource = await this.getAuthenticatedResource(); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       resource = await this.getUnauthenticatedResource(); | 
					
						
							| 
									
										
										
										
											2021-08-24 08:58:40 -07:00
										 |  |  |       await this.startUnauthenticatedExpirationTimer(resource); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const { path } = URL.parse(url); | 
					
						
							|  |  |  |     strictAssert(path, "Fetch can't have empty path"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const { method = 'GET', body, timeout } = init; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let bodyBytes: Uint8Array | undefined; | 
					
						
							|  |  |  |     if (body === undefined) { | 
					
						
							|  |  |  |       bodyBytes = undefined; | 
					
						
							|  |  |  |     } else if (body instanceof Uint8Array) { | 
					
						
							|  |  |  |       bodyBytes = body; | 
					
						
							|  |  |  |     } else if (body instanceof ArrayBuffer) { | 
					
						
							| 
									
										
										
										
											2021-09-23 17:49:05 -07:00
										 |  |  |       throw new Error('Unsupported body type: ArrayBuffer'); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     } else if (typeof body === 'string') { | 
					
						
							|  |  |  |       bodyBytes = Bytes.fromString(body); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       throw new Error(`Unsupported body type: ${typeof body}`); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |     return resource.sendRequest({ | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       verb: method, | 
					
						
							|  |  |  |       path, | 
					
						
							|  |  |  |       body: bodyBytes, | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |       headers: Array.from(headers.entries()), | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       timeout, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public registerRequestHandler(handler: IRequestHandler): void { | 
					
						
							|  |  |  |     this.requestHandlers.add(handler); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const queue = this.incomingRequestQueue; | 
					
						
							|  |  |  |     if (queue.length === 0) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |     log.info( | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       `SocketManager: processing ${queue.length} queued incoming requests` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     this.incomingRequestQueue = []; | 
					
						
							|  |  |  |     for (const req of queue) { | 
					
						
							|  |  |  |       this.queueOrHandleRequest(req); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public unregisterRequestHandler(handler: IRequestHandler): void { | 
					
						
							|  |  |  |     this.requestHandlers.delete(handler); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-04 17:48:25 -07:00
										 |  |  |   public async onHasStoriesDisabledChange(newValue: boolean): Promise<void> { | 
					
						
							|  |  |  |     if (this.hasStoriesDisabled === newValue) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.hasStoriesDisabled = newValue; | 
					
						
							|  |  |  |     log.info( | 
					
						
							|  |  |  |       `SocketManager: reconnecting after setting hasStoriesDisabled=${newValue}` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     await this.reconnect(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public async reconnect(): Promise<void> { | 
					
						
							|  |  |  |     log.info('SocketManager.reconnect: starting...'); | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const { authenticated, unauthenticated } = this; | 
					
						
							|  |  |  |     if (authenticated) { | 
					
						
							|  |  |  |       authenticated.abort(); | 
					
						
							|  |  |  |       this.dropAuthenticated(authenticated); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (unauthenticated) { | 
					
						
							|  |  |  |       unauthenticated.abort(); | 
					
						
							|  |  |  |       this.dropUnauthenticated(unauthenticated); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (this.credentials) { | 
					
						
							|  |  |  |       this.backOff.reset(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Cancel old reconnect attempt
 | 
					
						
							|  |  |  |       this.reconnectController?.abort(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Start the new attempt
 | 
					
						
							|  |  |  |       await this.authenticate(this.credentials); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-04 17:48:25 -07:00
										 |  |  |     log.info('SocketManager.reconnect: complete.'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   // Force keep-alive checks on WebSocketResources
 | 
					
						
							|  |  |  |   public async check(): Promise<void> { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |     log.info('SocketManager.check'); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     await Promise.all([ | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |       this.checkResource(this.authenticated), | 
					
						
							|  |  |  |       this.checkResource(this.unauthenticated), | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     ]); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |   public async onNavigatorOnline(): Promise<void> { | 
					
						
							|  |  |  |     log.info('SocketManager.onNavigatorOnline'); | 
					
						
							|  |  |  |     this.isNavigatorOffline = false; | 
					
						
							|  |  |  |     this.backOff.reset(FIBONACCI_TIMEOUTS); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |     // Reconnect earlier if waiting
 | 
					
						
							|  |  |  |     if (this.credentials !== undefined) { | 
					
						
							|  |  |  |       this.reconnectController?.abort(); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       await this.authenticate(this.credentials); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |   public async onNavigatorOffline(): Promise<void> { | 
					
						
							|  |  |  |     log.info('SocketManager.onNavigatorOffline'); | 
					
						
							|  |  |  |     this.isNavigatorOffline = true; | 
					
						
							|  |  |  |     this.backOff.reset(EXTENDED_FIBONACCI_TIMEOUTS); | 
					
						
							|  |  |  |     await this.check(); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |   public async onRemoteExpiration(): Promise<void> { | 
					
						
							|  |  |  |     log.info('SocketManager.onRemoteExpiration'); | 
					
						
							|  |  |  |     this.isRemotelyExpired = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Cancel reconnect attempt if any
 | 
					
						
							|  |  |  |     this.reconnectController?.abort(); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-15 11:44:27 -07:00
										 |  |  |   public async logout(): Promise<void> { | 
					
						
							|  |  |  |     const { authenticated } = this; | 
					
						
							|  |  |  |     if (authenticated) { | 
					
						
							|  |  |  |       authenticated.abort(); | 
					
						
							|  |  |  |       this.dropAuthenticated(authenticated); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.credentials = undefined; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-02 16:43:20 -04:00
										 |  |  |   public get isOnline(): boolean | undefined { | 
					
						
							|  |  |  |     return this.privIsOnline; | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   //
 | 
					
						
							|  |  |  |   // Private
 | 
					
						
							|  |  |  |   //
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-16 13:18:42 -07:00
										 |  |  |   private setStatus(status: SocketStatus): void { | 
					
						
							|  |  |  |     if (this.status === status) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.status = status; | 
					
						
							|  |  |  |     this.emit('statusChange'); | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (this.status === SocketStatus.OPEN && !this.privIsOnline) { | 
					
						
							|  |  |  |       this.privIsOnline = true; | 
					
						
							|  |  |  |       this.emit('online'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-16 13:18:42 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 01:13:15 +02:00
										 |  |  |   private transportOption(proxyAgent: ProxyAgent | undefined): TransportOption { | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |     const { hostname } = URL.parse(this.options.url); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // transport experiment doesn't support proxy
 | 
					
						
							| 
									
										
										
										
											2024-04-18 01:13:15 +02:00
										 |  |  |     if (proxyAgent || hostname == null || !hostname.endsWith('signal.org')) { | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |       return TransportOption.Original; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // in staging, switch to using libsignal transport
 | 
					
						
							|  |  |  |     if (isStaging(this.options.version)) { | 
					
						
							|  |  |  |       return TransportOption.Libsignal; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // in alpha, switch to using libsignal transport, unless user opts out,
 | 
					
						
							|  |  |  |     // in which case switching to shadowing
 | 
					
						
							|  |  |  |     if (isAlpha(this.options.version)) { | 
					
						
							|  |  |  |       const configValue = window.Signal.RemoteConfig.isEnabled( | 
					
						
							|  |  |  |         'desktop.experimentalTransportEnabled.alpha' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return configValue | 
					
						
							|  |  |  |         ? TransportOption.Libsignal | 
					
						
							|  |  |  |         : TransportOption.ShadowingHigh; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // in beta, switch to using 'ShadowingHigh' mode, unless user opts out,
 | 
					
						
							|  |  |  |     // in which case switching to `ShadowingLow`
 | 
					
						
							|  |  |  |     if (isBeta(this.options.version)) { | 
					
						
							|  |  |  |       const configValue = window.Signal.RemoteConfig.isEnabled( | 
					
						
							|  |  |  |         'desktop.experimentalTransportEnabled.beta' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return configValue | 
					
						
							|  |  |  |         ? TransportOption.ShadowingHigh | 
					
						
							|  |  |  |         : TransportOption.ShadowingLow; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 11:46:48 -07:00
										 |  |  |     const configValue = window.Signal.RemoteConfig.isEnabled( | 
					
						
							|  |  |  |       'desktop.experimentalTransportEnabled.prod' | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     return configValue | 
					
						
							|  |  |  |       ? TransportOption.ShadowingLow | 
					
						
							|  |  |  |       : TransportOption.Original; | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private connectLibsignalUnauthenticated(): AbortableProcess<IWebSocketResource> { | 
					
						
							| 
									
										
										
										
											2024-04-04 14:39:52 -07:00
										 |  |  |     return LibsignalWebSocketResource.connect( | 
					
						
							|  |  |  |       this.libsignalNet, | 
					
						
							|  |  |  |       UNAUTHENTICATED_CHANNEL_NAME | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async getUnauthenticatedResource(): Promise<IWebSocketResource> { | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |     if (this.unauthenticated) { | 
					
						
							|  |  |  |       return this.unauthenticated.getResult(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (this.isRemotelyExpired) { | 
					
						
							|  |  |  |       throw new HTTPError('SocketManager remotely expired', { | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |         code: 0, | 
					
						
							|  |  |  |         headers: {}, | 
					
						
							|  |  |  |         stack: new Error().stack, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |     log.info('SocketManager: connecting unauthenticated socket'); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 01:13:15 +02:00
										 |  |  |     const proxyAgent = await this.getProxyAgent(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const transportOption = this.transportOption(proxyAgent); | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |     log.info( | 
					
						
							|  |  |  |       `SocketManager: connecting unauthenticated socket, transport option [${transportOption}]` | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 13:00:16 -04:00
										 |  |  |     let process: AbortableProcess<IWebSocketResource>; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |     if (transportOption === TransportOption.Libsignal) { | 
					
						
							| 
									
										
										
										
											2024-04-18 13:00:16 -04:00
										 |  |  |       process = this.connectLibsignalUnauthenticated(); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       process = this.connectResource({ | 
					
						
							|  |  |  |         name: UNAUTHENTICATED_CHANNEL_NAME, | 
					
						
							|  |  |  |         path: '/v1/websocket/', | 
					
						
							|  |  |  |         proxyAgent, | 
					
						
							|  |  |  |         resourceOptions: { | 
					
						
							|  |  |  |           name: UNAUTHENTICATED_CHANNEL_NAME, | 
					
						
							|  |  |  |           keepalive: { path: '/v1/keepalive' }, | 
					
						
							|  |  |  |           transportOption, | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     this.unauthenticated = process; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |     let unauthenticated: IWebSocketResource; | 
					
						
							| 
									
										
										
										
											2021-07-29 09:25:21 -07:00
										 |  |  |     try { | 
					
						
							|  |  |  |       unauthenticated = await this.unauthenticated.getResult(); | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |       log.info( | 
					
						
							| 
									
										
										
										
											2021-07-29 09:25:21 -07:00
										 |  |  |         'SocketManager: failed to connect unauthenticated socket ' + | 
					
						
							|  |  |  |           ` due to error: ${Errors.toLogFormat(error)}` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       this.dropUnauthenticated(process); | 
					
						
							|  |  |  |       throw error; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-14 13:51:49 -07:00
										 |  |  |     log.info( | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |       `SocketManager: connected unauthenticated socket (localPort: ${unauthenticated.localPort()})` | 
					
						
							| 
									
										
										
										
											2023-06-14 13:51:49 -07:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     unauthenticated.addEventListener('close', ({ code, reason }): void => { | 
					
						
							|  |  |  |       if (this.unauthenticated !== process) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |       log.warn( | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |         'SocketManager: unauthenticated socket closed ' + | 
					
						
							|  |  |  |           `with code=${code} and reason=${reason}` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       this.dropUnauthenticated(process); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return this.unauthenticated.getResult(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private connectResource({ | 
					
						
							| 
									
										
										
										
											2021-12-01 19:55:17 +01:00
										 |  |  |     name, | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     path, | 
					
						
							| 
									
										
										
										
											2024-04-18 01:13:15 +02:00
										 |  |  |     proxyAgent, | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     resourceOptions, | 
					
						
							|  |  |  |     query = {}, | 
					
						
							| 
									
										
										
										
											2022-10-04 17:48:25 -07:00
										 |  |  |     extraHeaders = {}, | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   }: { | 
					
						
							| 
									
										
										
										
											2021-12-01 19:55:17 +01:00
										 |  |  |     name: string; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     path: string; | 
					
						
							| 
									
										
										
										
											2024-04-18 01:13:15 +02:00
										 |  |  |     proxyAgent: ProxyAgent | undefined; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     resourceOptions: WebSocketResourceOptions; | 
					
						
							|  |  |  |     query?: Record<string, string>; | 
					
						
							| 
									
										
										
										
											2022-10-04 17:48:25 -07:00
										 |  |  |     extraHeaders?: Record<string, string>; | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |   }): AbortableProcess<IWebSocketResource> { | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     const queryWithDefaults = { | 
					
						
							|  |  |  |       agent: 'OWD', | 
					
						
							|  |  |  |       version: this.options.version, | 
					
						
							|  |  |  |       ...query, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-09 00:32:31 +01:00
										 |  |  |     const url = `${this.options.url}${path}?${qs.encode(queryWithDefaults)}`; | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |     const { version } = this.options; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-04 14:39:52 -07:00
										 |  |  |     const start = performance.now(); | 
					
						
							|  |  |  |     const webSocketResourceConnection = connectWebSocket({ | 
					
						
							| 
									
										
										
										
											2021-12-01 19:55:17 +01:00
										 |  |  |       name, | 
					
						
							| 
									
										
										
										
											2021-11-09 00:32:31 +01:00
										 |  |  |       url, | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |       version, | 
					
						
							| 
									
										
										
										
											2021-11-09 00:32:31 +01:00
										 |  |  |       certificateAuthority: this.options.certificateAuthority, | 
					
						
							| 
									
										
										
										
											2024-04-18 01:13:15 +02:00
										 |  |  |       proxyAgent, | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-04 17:48:25 -07:00
										 |  |  |       extraHeaders, | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-04 14:39:52 -07:00
										 |  |  |       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); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |       }, | 
					
						
							| 
									
										
										
										
											2021-11-09 00:32:31 +01:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2024-04-04 14:39:52 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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() | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |   private async checkResource( | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |     process?: AbortableProcess<IWebSocketResource> | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   ): Promise<void> { | 
					
						
							|  |  |  |     if (!process) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const resource = await process.getResult(); | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // Force shorter timeout if we think we might be offline
 | 
					
						
							|  |  |  |     resource.forceKeepAlive( | 
					
						
							|  |  |  |       this.isNavigatorOffline ? OFFLINE_KEEPALIVE_TIMEOUT_MS : undefined | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private dropAuthenticated( | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |     process: AbortableProcess<IWebSocketResource> | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   ): void { | 
					
						
							| 
									
										
										
										
											2021-08-24 08:58:40 -07:00
										 |  |  |     if (this.authenticated !== process) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     this.incomingRequestQueue = []; | 
					
						
							|  |  |  |     this.authenticated = undefined; | 
					
						
							| 
									
										
										
										
											2021-09-16 13:18:42 -07:00
										 |  |  |     this.setStatus(SocketStatus.CLOSED); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private dropUnauthenticated( | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |     process: AbortableProcess<IWebSocketResource> | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   ): void { | 
					
						
							| 
									
										
										
										
											2021-08-24 08:58:40 -07:00
										 |  |  |     if (this.unauthenticated !== process) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.unauthenticated = undefined; | 
					
						
							|  |  |  |     if (!this.unauthenticatedExpirationTimer) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     clearTimeout(this.unauthenticatedExpirationTimer); | 
					
						
							|  |  |  |     this.unauthenticatedExpirationTimer = undefined; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async startUnauthenticatedExpirationTimer( | 
					
						
							| 
									
										
										
										
											2024-03-14 14:08:51 -07:00
										 |  |  |     expected: IWebSocketResource | 
					
						
							| 
									
										
										
										
											2021-08-24 08:58:40 -07:00
										 |  |  |   ): Promise<void> { | 
					
						
							|  |  |  |     const process = this.unauthenticated; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     strictAssert( | 
					
						
							| 
									
										
										
										
											2021-08-24 08:58:40 -07:00
										 |  |  |       process !== undefined, | 
					
						
							|  |  |  |       'Unauthenticated socket must be connected' | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     ); | 
					
						
							| 
									
										
										
										
											2021-08-24 08:58:40 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const unauthenticated = await process.getResult(); | 
					
						
							|  |  |  |     strictAssert( | 
					
						
							|  |  |  |       unauthenticated === expected, | 
					
						
							|  |  |  |       'Unauthenticated resource should be the same' | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (this.unauthenticatedExpirationTimer) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |     log.info( | 
					
						
							| 
									
										
										
										
											2021-08-24 08:58:40 -07:00
										 |  |  |       'SocketManager: starting expiration timer for unauthenticated socket' | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     this.unauthenticatedExpirationTimer = setTimeout(async () => { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |       log.info( | 
					
						
							| 
									
										
										
										
											2021-08-24 08:58:40 -07:00
										 |  |  |         'SocketManager: shutting down unauthenticated socket after timeout' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       unauthenticated.shutdown(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // The socket is either deliberately closed or reconnected already
 | 
					
						
							|  |  |  |       if (this.unauthenticated !== process) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       this.dropUnauthenticated(process); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         await this.getUnauthenticatedResource(); | 
					
						
							|  |  |  |       } catch (error) { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |         log.warn( | 
					
						
							| 
									
										
										
										
											2021-08-24 08:58:40 -07:00
										 |  |  |           'SocketManager: failed to reconnect unauthenticated socket ' + | 
					
						
							|  |  |  |             `due to error: ${Errors.toLogFormat(error)}` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }, FIVE_MINUTES); | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private queueOrHandleRequest(req: IncomingWebSocketRequest): void { | 
					
						
							|  |  |  |     if (this.requestHandlers.size === 0) { | 
					
						
							|  |  |  |       this.incomingRequestQueue.push(req); | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |       log.info( | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |         'SocketManager: request handler unavailable, ' + | 
					
						
							|  |  |  |           `queued request. Queue size: ${this.incomingRequestQueue.length}` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     for (const handlers of this.requestHandlers) { | 
					
						
							|  |  |  |       try { | 
					
						
							|  |  |  |         handlers.handleRequest(req); | 
					
						
							|  |  |  |       } catch (error) { | 
					
						
							| 
									
										
										
										
											2021-09-17 14:27:53 -04:00
										 |  |  |         log.warn( | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |           'SocketManager: got exception while handling incoming request, ' + | 
					
						
							|  |  |  |             `error: ${Errors.toLogFormat(error)}` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-03 17:37:17 -07:00
										 |  |  |   private isAuthenticated(headers: Headers): boolean { | 
					
						
							|  |  |  |     if (!this.credentials) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const authorization = headers.get('Authorization'); | 
					
						
							|  |  |  |     if (!authorization) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const [basic, base64] = authorization.split(/\s+/, 2); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (basic.toLowerCase() !== 'basic' || !base64) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const [username, password] = Bytes.toString(Bytes.fromBase64(base64)).split( | 
					
						
							|  |  |  |       ':', | 
					
						
							|  |  |  |       2 | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return ( | 
					
						
							|  |  |  |       username === this.credentials.username && | 
					
						
							|  |  |  |       password === this.credentials.password | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 01:13:15 +02:00
										 |  |  |   private async getProxyAgent(): Promise<ProxyAgent | undefined> { | 
					
						
							|  |  |  |     if (this.options.proxyUrl && !this.lazyProxyAgent) { | 
					
						
							|  |  |  |       // Cache the promise so that we don't import concurrently.
 | 
					
						
							|  |  |  |       this.lazyProxyAgent = createProxyAgent(this.options.proxyUrl); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return this.lazyProxyAgent; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |   // EventEmitter types
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-12 17:44:20 -06:00
										 |  |  |   public override on( | 
					
						
							|  |  |  |     type: 'authError', | 
					
						
							|  |  |  |     callback: (error: HTTPError) => void | 
					
						
							|  |  |  |   ): this; | 
					
						
							|  |  |  |   public override on(type: 'statusChange', callback: () => void): this; | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |   public override on(type: 'online', callback: () => void): this; | 
					
						
							|  |  |  |   public override on(type: 'offline', callback: () => void): this; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-12 17:44:20 -06:00
										 |  |  |   public override on( | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     type: string | symbol, | 
					
						
							|  |  |  |     // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
					
						
							|  |  |  |     listener: (...args: Array<any>) => void | 
					
						
							|  |  |  |   ): this { | 
					
						
							|  |  |  |     return super.on(type, listener); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-12 17:44:20 -06:00
										 |  |  |   public override emit(type: 'authError', error: HTTPError): boolean; | 
					
						
							|  |  |  |   public override emit(type: 'statusChange'): boolean; | 
					
						
							| 
									
										
										
										
											2024-03-18 14:48:00 -07:00
										 |  |  |   public override emit(type: 'online'): boolean; | 
					
						
							|  |  |  |   public override emit(type: 'offline'): boolean; | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // eslint-disable-next-line @typescript-eslint/no-explicit-any
 | 
					
						
							| 
									
										
										
										
											2021-11-12 17:44:20 -06:00
										 |  |  |   public override emit(type: string | symbol, ...args: Array<any>): boolean { | 
					
						
							| 
									
										
										
										
											2021-07-28 14:37:09 -07:00
										 |  |  |     return super.emit(type, ...args); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } |