Migrate to private class properties/methods

This commit is contained in:
Jamie Kyle 2025-01-14 11:11:52 -08:00 committed by GitHub
commit aa9f53df57
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
100 changed files with 3795 additions and 3944 deletions

View file

@ -242,7 +242,7 @@ export default class AccountManager extends EventTarget {
this.pending = Promise.resolve();
}
private async queueTask<T>(task: () => Promise<T>): Promise<T> {
async #queueTask<T>(task: () => Promise<T>): Promise<T> {
this.pendingQueue = this.pendingQueue || new PQueue({ concurrency: 1 });
const taskWithTimeout = createTaskWithTimeout(task, 'AccountManager task');
@ -328,7 +328,7 @@ export default class AccountManager extends EventTarget {
verificationCode: string,
sessionId: string
): Promise<void> {
await this.queueTask(async () => {
await this.#queueTask(async () => {
const aciKeyPair = generateKeyPair();
const pniKeyPair = generateKeyPair();
const profileKey = getRandomBytes(PROFILE_KEY_LENGTH);
@ -337,7 +337,7 @@ export default class AccountManager extends EventTarget {
const accountEntropyPool = AccountEntropyPool.generate();
const mediaRootBackupKey = BackupKey.generateRandom().serialize();
await this.createAccount({
await this.#createAccount({
type: AccountType.Primary,
number,
verificationCode,
@ -358,12 +358,12 @@ export default class AccountManager extends EventTarget {
async registerSecondDevice(
options: CreateLinkedDeviceOptionsType
): Promise<void> {
await this.queueTask(async () => {
await this.createAccount(options);
await this.#queueTask(async () => {
await this.#createAccount(options);
});
}
private getIdentityKeyOrThrow(ourServiceId: ServiceIdString): KeyPairType {
#getIdentityKeyOrThrow(ourServiceId: ServiceIdString): KeyPairType {
const { storage } = window.textsecure;
const store = storage.protocol;
let identityKey: KeyPairType | undefined;
@ -383,7 +383,7 @@ export default class AccountManager extends EventTarget {
return identityKey;
}
private async generateNewPreKeys(
async #generateNewPreKeys(
serviceIdKind: ServiceIdKind,
count = PRE_KEY_GEN_BATCH_SIZE
): Promise<Array<UploadPreKeyType>> {
@ -418,7 +418,7 @@ export default class AccountManager extends EventTarget {
}));
}
private async generateNewKyberPreKeys(
async #generateNewKyberPreKeys(
serviceIdKind: ServiceIdKind,
count = PRE_KEY_GEN_BATCH_SIZE
): Promise<Array<UploadKyberPreKeyType>> {
@ -436,7 +436,7 @@ export default class AccountManager extends EventTarget {
}
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
const identityKey = this.getIdentityKeyOrThrow(ourServiceId);
const identityKey = this.#getIdentityKeyOrThrow(ourServiceId);
const toSave: Array<Omit<KyberPreKeyType, 'id'>> = [];
const toUpload: Array<UploadKyberPreKeyType> = [];
@ -471,13 +471,13 @@ export default class AccountManager extends EventTarget {
forceUpdate = false
): Promise<void> {
const logId = `maybeUpdateKeys(${serviceIdKind})`;
await this.queueTask(async () => {
await this.#queueTask(async () => {
const { storage } = window.textsecure;
let identityKey: KeyPairType;
try {
const ourServiceId = storage.user.getCheckedServiceId(serviceIdKind);
identityKey = this.getIdentityKeyOrThrow(ourServiceId);
identityKey = this.#getIdentityKeyOrThrow(ourServiceId);
} catch (error) {
if (serviceIdKind === ServiceIdKind.PNI) {
log.info(
@ -506,7 +506,7 @@ export default class AccountManager extends EventTarget {
log.info(
`${logId}: Server prekey count is ${preKeyCount}, generating a new set`
);
preKeys = await this.generateNewPreKeys(serviceIdKind);
preKeys = await this.#generateNewPreKeys(serviceIdKind);
}
let pqPreKeys: Array<UploadKyberPreKeyType> | undefined;
@ -518,14 +518,14 @@ export default class AccountManager extends EventTarget {
log.info(
`${logId}: Server kyber prekey count is ${kyberPreKeyCount}, generating a new set`
);
pqPreKeys = await this.generateNewKyberPreKeys(serviceIdKind);
pqPreKeys = await this.#generateNewKyberPreKeys(serviceIdKind);
}
const pqLastResortPreKey = await this.maybeUpdateLastResortKyberKey(
const pqLastResortPreKey = await this.#maybeUpdateLastResortKyberKey(
serviceIdKind,
forceUpdate
);
const signedPreKey = await this.maybeUpdateSignedPreKey(
const signedPreKey = await this.#maybeUpdateSignedPreKey(
serviceIdKind,
forceUpdate
);
@ -601,7 +601,7 @@ export default class AccountManager extends EventTarget {
return false;
}
private async generateSignedPreKey(
async #generateSignedPreKey(
serviceIdKind: ServiceIdKind,
identityKey: KeyPairType
): Promise<CompatSignedPreKeyType> {
@ -625,13 +625,13 @@ export default class AccountManager extends EventTarget {
return key;
}
private async maybeUpdateSignedPreKey(
async #maybeUpdateSignedPreKey(
serviceIdKind: ServiceIdKind,
forceUpdate = false
): Promise<UploadSignedPreKeyType | undefined> {
const ourServiceId =
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
const identityKey = this.getIdentityKeyOrThrow(ourServiceId);
const identityKey = this.#getIdentityKeyOrThrow(ourServiceId);
const logId = `AccountManager.maybeUpdateSignedPreKey(${serviceIdKind}, ${ourServiceId})`;
const store = window.textsecure.storage.protocol;
@ -662,7 +662,7 @@ export default class AccountManager extends EventTarget {
return;
}
const key = await this.generateSignedPreKey(serviceIdKind, identityKey);
const key = await this.#generateSignedPreKey(serviceIdKind, identityKey);
log.info(`${logId}: Saving new signed prekey`, key.keyId);
await store.storeSignedPreKey(ourServiceId, key.keyId, key.keyPair);
@ -670,7 +670,7 @@ export default class AccountManager extends EventTarget {
return signedPreKeyToUploadSignedPreKey(key);
}
private async generateLastResortKyberKey(
async #generateLastResortKyberKey(
serviceIdKind: ServiceIdKind,
identityKey: KeyPairType
): Promise<KyberPreKeyRecord> {
@ -695,13 +695,13 @@ export default class AccountManager extends EventTarget {
return record;
}
private async maybeUpdateLastResortKyberKey(
async #maybeUpdateLastResortKyberKey(
serviceIdKind: ServiceIdKind,
forceUpdate = false
): Promise<UploadSignedPreKeyType | undefined> {
const ourServiceId =
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
const identityKey = this.getIdentityKeyOrThrow(ourServiceId);
const identityKey = this.#getIdentityKeyOrThrow(ourServiceId);
const logId = `maybeUpdateLastResortKyberKey(${serviceIdKind}, ${ourServiceId})`;
const store = window.textsecure.storage.protocol;
@ -732,7 +732,7 @@ export default class AccountManager extends EventTarget {
return;
}
const record = await this.generateLastResortKyberKey(
const record = await this.#generateLastResortKyberKey(
serviceIdKind,
identityKey
);
@ -912,22 +912,18 @@ export default class AccountManager extends EventTarget {
}
}
private async createAccount(
options: CreateAccountOptionsType
): Promise<void> {
async #createAccount(options: CreateAccountOptionsType): Promise<void> {
this.dispatchEvent(new Event('startRegistration'));
const registrationBaton = this.server.startRegistration();
try {
await this.doCreateAccount(options);
await this.#doCreateAccount(options);
} finally {
this.server.finishRegistration(registrationBaton);
}
await this.registrationDone();
await this.#registrationDone();
}
private async doCreateAccount(
options: CreateAccountOptionsType
): Promise<void> {
async #doCreateAccount(options: CreateAccountOptionsType): Promise<void> {
const {
number,
verificationCode,
@ -1032,19 +1028,19 @@ export default class AccountManager extends EventTarget {
let ourPni: PniString;
let deviceId: number;
const aciPqLastResortPreKey = await this.generateLastResortKyberKey(
const aciPqLastResortPreKey = await this.#generateLastResortKyberKey(
ServiceIdKind.ACI,
aciKeyPair
);
const pniPqLastResortPreKey = await this.generateLastResortKyberKey(
const pniPqLastResortPreKey = await this.#generateLastResortKyberKey(
ServiceIdKind.PNI,
pniKeyPair
);
const aciSignedPreKey = await this.generateSignedPreKey(
const aciSignedPreKey = await this.#generateSignedPreKey(
ServiceIdKind.ACI,
aciKeyPair
);
const pniSignedPreKey = await this.generateSignedPreKey(
const pniSignedPreKey = await this.#generateSignedPreKey(
ServiceIdKind.PNI,
pniKeyPair
);
@ -1333,8 +1329,8 @@ export default class AccountManager extends EventTarget {
window.textsecure.storage.user.getCheckedServiceId(serviceIdKind);
const logId = `AccountManager.generateKeys(${serviceIdKind}, ${ourServiceId})`;
const preKeys = await this.generateNewPreKeys(serviceIdKind, count);
const pqPreKeys = await this.generateNewKyberPreKeys(serviceIdKind, count);
const preKeys = await this.#generateNewPreKeys(serviceIdKind, count);
const pqPreKeys = await this.#generateNewKyberPreKeys(serviceIdKind, count);
log.info(
`${logId}: Generated ` +
@ -1347,13 +1343,13 @@ export default class AccountManager extends EventTarget {
await this._cleanKyberPreKeys(serviceIdKind);
return {
identityKey: this.getIdentityKeyOrThrow(ourServiceId).pubKey,
identityKey: this.#getIdentityKeyOrThrow(ourServiceId).pubKey,
preKeys,
pqPreKeys,
};
}
private async registrationDone(): Promise<void> {
async #registrationDone(): Promise<void> {
log.info('registration done');
this.dispatchEvent(new Event('registration'));
}

View file

@ -79,7 +79,7 @@ export class ParseContactsTransform extends Transform {
public contacts: Array<ContactDetailsWithAvatar> = [];
public activeContact: Proto.ContactDetails | undefined;
private unused: Uint8Array | undefined;
#unused: Uint8Array | undefined;
override async _transform(
chunk: Buffer | undefined,
@ -93,9 +93,9 @@ export class ParseContactsTransform extends Transform {
try {
let data = chunk;
if (this.unused) {
data = Buffer.concat([this.unused, data]);
this.unused = undefined;
if (this.#unused) {
data = Buffer.concat([this.#unused, data]);
this.#unused = undefined;
}
const reader = Reader.create(data);
@ -110,7 +110,7 @@ export class ParseContactsTransform extends Transform {
if (err instanceof RangeError) {
// Note: A failed decodeDelimited() does in fact update reader.pos, so we
// must reset to startPos
this.unused = data.subarray(startPos);
this.#unused = data.subarray(startPos);
done();
return;
}
@ -174,7 +174,7 @@ export class ParseContactsTransform extends Transform {
} else {
// We have an attachment, but we haven't read enough data yet. We need to
// wait for another chunk.
this.unused = data.subarray(reader.pos);
this.#unused = data.subarray(reader.pos);
done();
return;
}

File diff suppressed because it is too large Load diff

View file

@ -82,27 +82,26 @@ export type ProvisionerOptionsType = Readonly<{
const INACTIVE_SOCKET_TIMEOUT = 30 * MINUTE;
export class Provisioner {
private readonly cipher = new ProvisioningCipher();
private readonly server: WebAPIType;
private readonly appVersion: string;
private state: StateType = { step: Step.Idle };
private wsr: IWebSocketResource | undefined;
readonly #cipher = new ProvisioningCipher();
readonly #server: WebAPIType;
readonly #appVersion: string;
#state: StateType = { step: Step.Idle };
#wsr: IWebSocketResource | undefined;
constructor(options: ProvisionerOptionsType) {
this.server = options.server;
this.appVersion = options.appVersion;
this.#server = options.server;
this.#appVersion = options.appVersion;
}
public close(error = new Error('Provisioner closed')): void {
try {
this.wsr?.close();
this.#wsr?.close();
} catch {
// Best effort
}
const prevState = this.state;
this.state = { step: Step.Done };
const prevState = this.#state;
this.#state = { step: Step.Done };
if (prevState.step === Step.WaitingForURL) {
prevState.url.reject(error);
@ -113,15 +112,15 @@ export class Provisioner {
public async getURL(): Promise<string> {
strictAssert(
this.state.step === Step.Idle,
`Invalid state for getURL: ${this.state.step}`
this.#state.step === Step.Idle,
`Invalid state for getURL: ${this.#state.step}`
);
this.state = { step: Step.Connecting };
this.#state = { step: Step.Connecting };
const wsr = await this.server.getProvisioningResource({
const wsr = await this.#server.getProvisioningResource({
handleRequest: (request: IncomingWebSocketRequest) => {
try {
this.handleRequest(request);
this.#handleRequest(request);
} catch (error) {
log.error(
'Provisioner.handleRequest: failure',
@ -131,7 +130,7 @@ export class Provisioner {
}
},
});
this.wsr = wsr;
this.#wsr = wsr;
let inactiveTimer: NodeJS.Timeout | undefined;
@ -159,12 +158,12 @@ export class Provisioner {
document.addEventListener('visibilitychange', onVisibilityChange);
if (this.state.step !== Step.Connecting) {
if (this.#state.step !== Step.Connecting) {
this.close();
throw new Error('Provisioner closed early');
}
this.state = {
this.#state = {
step: Step.WaitingForURL,
url: explodePromise(),
};
@ -177,7 +176,7 @@ export class Provisioner {
}
inactiveTimer = undefined;
if (this.state.step === Step.ReadyToLink) {
if (this.#state.step === Step.ReadyToLink) {
// WebSocket close is not an issue since we no longer need it
return;
}
@ -186,15 +185,15 @@ export class Provisioner {
this.close(new Error('websocket closed'));
});
return this.state.url.promise;
return this.#state.url.promise;
}
public async waitForEnvelope(): Promise<void> {
strictAssert(
this.state.step === Step.WaitingForEnvelope,
`Invalid state for waitForEnvelope: ${this.state.step}`
this.#state.step === Step.WaitingForEnvelope,
`Invalid state for waitForEnvelope: ${this.#state.step}`
);
await this.state.done.promise;
await this.#state.done.promise;
}
public prepareLinkData({
@ -202,11 +201,11 @@ export class Provisioner {
backupFile,
}: PrepareLinkDataOptionsType): CreateLinkedDeviceOptionsType {
strictAssert(
this.state.step === Step.ReadyToLink,
`Invalid state for prepareLinkData: ${this.state.step}`
this.#state.step === Step.ReadyToLink,
`Invalid state for prepareLinkData: ${this.#state.step}`
);
const { envelope } = this.state;
this.state = { step: Step.Done };
const { envelope } = this.#state;
this.#state = { step: Step.Done };
const {
number,
@ -273,31 +272,31 @@ export class Provisioner {
public isLinkAndSync(): boolean {
strictAssert(
this.state.step === Step.ReadyToLink,
`Invalid state for prepareLinkData: ${this.state.step}`
this.#state.step === Step.ReadyToLink,
`Invalid state for prepareLinkData: ${this.#state.step}`
);
const { envelope } = this.state;
const { envelope } = this.#state;
return (
isLinkAndSyncEnabled(this.appVersion) &&
isLinkAndSyncEnabled(this.#appVersion) &&
Bytes.isNotEmpty(envelope.ephemeralBackupKey)
);
}
private handleRequest(request: IncomingWebSocketRequest): void {
const pubKey = this.cipher.getPublicKey();
#handleRequest(request: IncomingWebSocketRequest): void {
const pubKey = this.#cipher.getPublicKey();
if (
request.requestType === ServerRequestType.ProvisioningAddress &&
request.body
) {
strictAssert(
this.state.step === Step.WaitingForURL,
`Unexpected provisioning address, state: ${this.state}`
this.#state.step === Step.WaitingForURL,
`Unexpected provisioning address, state: ${this.#state}`
);
const prevState = this.state;
this.state = { step: Step.WaitingForEnvelope, done: explodePromise() };
const prevState = this.#state;
this.#state = { step: Step.WaitingForEnvelope, done: explodePromise() };
const proto = Proto.ProvisioningUuid.decode(request.body);
const { uuid } = proto;
@ -307,7 +306,9 @@ export class Provisioner {
.toAppUrl({
uuid,
pubKey: Bytes.toBase64(pubKey),
capabilities: isLinkAndSyncEnabled(this.appVersion) ? ['backup'] : [],
capabilities: isLinkAndSyncEnabled(this.#appVersion)
? ['backup']
: [],
})
.toString();
@ -320,17 +321,17 @@ export class Provisioner {
request.body
) {
strictAssert(
this.state.step === Step.WaitingForEnvelope,
`Unexpected provisioning address, state: ${this.state}`
this.#state.step === Step.WaitingForEnvelope,
`Unexpected provisioning address, state: ${this.#state}`
);
const prevState = this.state;
const prevState = this.#state;
const ciphertext = Proto.ProvisionEnvelope.decode(request.body);
const message = this.cipher.decrypt(ciphertext);
const message = this.#cipher.decrypt(ciphertext);
this.state = { step: Step.ReadyToLink, envelope: message };
this.#state = { step: Step.ReadyToLink, envelope: message };
request.respond(200, 'OK');
this.wsr?.close();
this.#wsr?.close();
prevState.done.resolve();
} else {

View file

@ -1967,7 +1967,7 @@ export default class MessageSender {
options?: Readonly<SendOptionsType>;
}>
): Promise<CallbackResultType> {
return this.sendReceiptMessage({
return this.#sendReceiptMessage({
...options,
type: Proto.ReceiptMessage.Type.DELIVERY,
});
@ -1981,7 +1981,7 @@ export default class MessageSender {
options?: Readonly<SendOptionsType>;
}>
): Promise<CallbackResultType> {
return this.sendReceiptMessage({
return this.#sendReceiptMessage({
...options,
type: Proto.ReceiptMessage.Type.READ,
});
@ -1995,13 +1995,13 @@ export default class MessageSender {
options?: Readonly<SendOptionsType>;
}>
): Promise<CallbackResultType> {
return this.sendReceiptMessage({
return this.#sendReceiptMessage({
...options,
type: Proto.ReceiptMessage.Type.VIEWED,
});
}
private async sendReceiptMessage({
async #sendReceiptMessage({
senderAci,
timestamps,
type,

View file

@ -83,37 +83,24 @@ export type SocketManagerOptions = Readonly<{
// Incoming requests on unauthenticated resource are not currently supported.
// IWebSocketResource is responsible for their immediate termination.
export class SocketManager extends EventListener {
private backOff = new BackOff(FIBONACCI_TIMEOUTS, {
#backOff = new BackOff(FIBONACCI_TIMEOUTS, {
jitter: JITTER,
});
private authenticated?: AbortableProcess<IWebSocketResource>;
private unauthenticated?: AbortableProcess<IWebSocketResource>;
private unauthenticatedExpirationTimer?: NodeJS.Timeout;
private credentials?: WebAPICredentials;
private lazyProxyAgent?: Promise<ProxyAgent>;
private status = SocketStatus.CLOSED;
private requestHandlers = new Set<IRequestHandler>();
private incomingRequestQueue = new Array<IncomingWebSocketRequest>();
private isNavigatorOffline = false;
private privIsOnline: boolean | undefined;
private isRemotelyExpired = false;
private hasStoriesDisabled: boolean;
private reconnectController: AbortController | undefined;
private envelopeCount = 0;
#authenticated?: AbortableProcess<IWebSocketResource>;
#unauthenticated?: AbortableProcess<IWebSocketResource>;
#unauthenticatedExpirationTimer?: NodeJS.Timeout;
#credentials?: WebAPICredentials;
#lazyProxyAgent?: Promise<ProxyAgent>;
#status = SocketStatus.CLOSED;
#requestHandlers = new Set<IRequestHandler>();
#incomingRequestQueue = new Array<IncomingWebSocketRequest>();
#isNavigatorOffline = false;
#privIsOnline: boolean | undefined;
#isRemotelyExpired = false;
#hasStoriesDisabled: boolean;
#reconnectController: AbortController | undefined;
#envelopeCount = 0;
constructor(
private readonly libsignalNet: Net.Net,
@ -121,16 +108,16 @@ export class SocketManager extends EventListener {
) {
super();
this.hasStoriesDisabled = options.hasStoriesDisabled;
this.#hasStoriesDisabled = options.hasStoriesDisabled;
}
public getStatus(): SocketStatus {
return this.status;
return this.#status;
}
private markOffline() {
if (this.privIsOnline !== false) {
this.privIsOnline = false;
#markOffline() {
if (this.#privIsOnline !== false) {
this.#privIsOnline = false;
this.emit('offline');
}
}
@ -138,7 +125,7 @@ export class SocketManager extends EventListener {
// Update WebAPICredentials and reconnect authenticated resource if
// credentials changed
public async authenticate(credentials: WebAPICredentials): Promise<void> {
if (this.isRemotelyExpired) {
if (this.#isRemotelyExpired) {
throw new HTTPError('SocketManager remotely expired', {
code: 0,
headers: {},
@ -153,13 +140,13 @@ export class SocketManager extends EventListener {
}
if (
this.credentials &&
this.credentials.username === username &&
this.credentials.password === password &&
this.authenticated
this.#credentials &&
this.#credentials.username === username &&
this.#credentials.password === password &&
this.#authenticated
) {
try {
await this.authenticated.getResult();
await this.#authenticated.getResult();
} catch (error) {
log.warn(
'SocketManager: failed to wait for existing authenticated socket ' +
@ -169,61 +156,61 @@ export class SocketManager extends EventListener {
return;
}
this.credentials = credentials;
this.#credentials = credentials;
log.info(
'SocketManager: connecting authenticated socket ' +
`(hasStoriesDisabled=${this.hasStoriesDisabled})`
`(hasStoriesDisabled=${this.#hasStoriesDisabled})`
);
this.setStatus(SocketStatus.CONNECTING);
this.#setStatus(SocketStatus.CONNECTING);
const proxyAgent = await this.getProxyAgent();
const proxyAgent = await this.#getProxyAgent();
const useLibsignalTransport =
window.Signal.RemoteConfig.isEnabled(
'desktop.experimentalTransport.enableAuth'
) && this.transportOption(proxyAgent) === TransportOption.Libsignal;
) && this.#transportOption(proxyAgent) === TransportOption.Libsignal;
const process = useLibsignalTransport
? connectAuthenticatedLibsignal({
libsignalNet: this.libsignalNet,
name: AUTHENTICATED_CHANNEL_NAME,
credentials: this.credentials,
credentials: this.#credentials,
handler: (req: IncomingWebSocketRequest): void => {
this.queueOrHandleRequest(req);
this.#queueOrHandleRequest(req);
},
receiveStories: !this.hasStoriesDisabled,
receiveStories: !this.#hasStoriesDisabled,
keepalive: { path: '/v1/keepalive' },
})
: this.connectResource({
: this.#connectResource({
name: AUTHENTICATED_CHANNEL_NAME,
path: '/v1/websocket/',
resourceOptions: {
name: AUTHENTICATED_CHANNEL_NAME,
keepalive: { path: '/v1/keepalive' },
handleRequest: (req: IncomingWebSocketRequest): void => {
this.queueOrHandleRequest(req);
this.#queueOrHandleRequest(req);
},
},
extraHeaders: {
Authorization: getBasicAuth({ username, password }),
'X-Signal-Receive-Stories': String(!this.hasStoriesDisabled),
'X-Signal-Receive-Stories': String(!this.#hasStoriesDisabled),
},
proxyAgent,
});
// Cancel previous connect attempt or close socket
this.authenticated?.abort();
this.#authenticated?.abort();
this.authenticated = process;
this.#authenticated = process;
const reconnect = async (): Promise<void> => {
if (this.isRemotelyExpired) {
if (this.#isRemotelyExpired) {
log.info('SocketManager: remotely expired, not reconnecting');
return;
}
const timeout = this.backOff.getAndIncrement();
const timeout = this.#backOff.getAndIncrement();
log.info(
'SocketManager: reconnecting authenticated socket ' +
@ -231,7 +218,7 @@ export class SocketManager extends EventListener {
);
const reconnectController = new AbortController();
this.reconnectController = reconnectController;
this.#reconnectController = reconnectController;
try {
await sleep(timeout, reconnectController.signal);
@ -239,20 +226,20 @@ export class SocketManager extends EventListener {
log.info('SocketManager: reconnect cancelled');
return;
} finally {
if (this.reconnectController === reconnectController) {
this.reconnectController = undefined;
if (this.#reconnectController === reconnectController) {
this.#reconnectController = undefined;
}
}
if (this.authenticated) {
if (this.#authenticated) {
log.info('SocketManager: authenticated socket already connecting');
return;
}
strictAssert(this.credentials !== undefined, 'Missing credentials');
strictAssert(this.#credentials !== undefined, 'Missing credentials');
try {
await this.authenticate(this.credentials);
await this.authenticate(this.#credentials);
} catch (error) {
log.info(
'SocketManager: authenticated socket failed to reconnect ' +
@ -265,7 +252,7 @@ export class SocketManager extends EventListener {
let authenticated: IWebSocketResource;
try {
authenticated = await process.getResult();
this.setStatus(SocketStatus.OPEN);
this.#setStatus(SocketStatus.OPEN);
} catch (error) {
log.warn(
'SocketManager: authenticated socket connection failed with ' +
@ -273,11 +260,11 @@ export class SocketManager extends EventListener {
);
// The socket was deliberately closed, don't follow up
if (this.authenticated !== process) {
if (this.#authenticated !== process) {
return;
}
this.dropAuthenticated(process);
this.#dropAuthenticated(process);
if (error instanceof HTTPError) {
const { code } = error;
@ -293,10 +280,10 @@ export class SocketManager extends EventListener {
}
if (code === -1) {
this.markOffline();
this.#markOffline();
}
} else if (error instanceof ConnectTimeoutError) {
this.markOffline();
this.#markOffline();
} else if (
error instanceof LibSignalErrorBase &&
error.code === ErrorCode.DeviceDelinked
@ -320,11 +307,11 @@ export class SocketManager extends EventListener {
);
window.logAuthenticatedConnect?.();
this.envelopeCount = 0;
this.backOff.reset();
this.#envelopeCount = 0;
this.#backOff.reset();
authenticated.addEventListener('close', ({ code, reason }): void => {
if (this.authenticated !== process) {
if (this.#authenticated !== process) {
return;
}
@ -332,7 +319,7 @@ export class SocketManager extends EventListener {
'SocketManager: authenticated socket closed ' +
`with code=${code} and reason=${reason}`
);
this.dropAuthenticated(process);
this.#dropAuthenticated(process);
if (code === NORMAL_DISCONNECT_CODE) {
// Intentional disconnect
@ -351,27 +338,27 @@ export class SocketManager extends EventListener {
// Either returns currently connecting/active authenticated
// IWebSocketResource or connects a fresh one.
public async getAuthenticatedResource(): Promise<IWebSocketResource> {
if (!this.authenticated) {
strictAssert(this.credentials !== undefined, 'Missing credentials');
await this.authenticate(this.credentials);
if (!this.#authenticated) {
strictAssert(this.#credentials !== undefined, 'Missing credentials');
await this.authenticate(this.#credentials);
}
strictAssert(this.authenticated !== undefined, 'Authentication failed');
return this.authenticated.getResult();
strictAssert(this.#authenticated !== undefined, 'Authentication failed');
return this.#authenticated.getResult();
}
// Creates new IWebSocketResource for AccountManager's provisioning
public async getProvisioningResource(
handler: IRequestHandler
): Promise<IWebSocketResource> {
if (this.isRemotelyExpired) {
if (this.#isRemotelyExpired) {
throw new Error('Remotely expired, not connecting provisioning socket');
}
return this.connectResource({
return this.#connectResource({
name: 'provisioning',
path: '/v1/websocket/provisioning/',
proxyAgent: await this.getProxyAgent(),
proxyAgent: await this.#getProxyAgent(),
resourceOptions: {
name: 'provisioning',
handleRequest: (req: IncomingWebSocketRequest): void => {
@ -390,7 +377,7 @@ export class SocketManager extends EventListener {
url: string;
extraHeaders?: Record<string, string>;
}): Promise<WebSocket> {
const proxyAgent = await this.getProxyAgent();
const proxyAgent = await this.#getProxyAgent();
return connectWebSocket({
name: 'art-creator-provisioning',
@ -412,11 +399,11 @@ export class SocketManager extends EventListener {
const headers = new Headers(init.headers);
let resource: IWebSocketResource;
if (this.isAuthenticated(headers)) {
if (this.#isAuthenticated(headers)) {
resource = await this.getAuthenticatedResource();
} else {
resource = await this.getUnauthenticatedResource();
await this.startUnauthenticatedExpirationTimer(resource);
resource = await this.#getUnauthenticatedResource();
await this.#startUnauthenticatedExpirationTimer(resource);
}
const { path } = URL.parse(url);
@ -460,9 +447,9 @@ export class SocketManager extends EventListener {
}
public registerRequestHandler(handler: IRequestHandler): void {
this.requestHandlers.add(handler);
this.#requestHandlers.add(handler);
const queue = this.incomingRequestQueue;
const queue = this.#incomingRequestQueue;
if (queue.length === 0) {
return;
}
@ -470,22 +457,22 @@ export class SocketManager extends EventListener {
log.info(
`SocketManager: processing ${queue.length} queued incoming requests`
);
this.incomingRequestQueue = [];
this.#incomingRequestQueue = [];
for (const req of queue) {
this.queueOrHandleRequest(req);
this.#queueOrHandleRequest(req);
}
}
public unregisterRequestHandler(handler: IRequestHandler): void {
this.requestHandlers.delete(handler);
this.#requestHandlers.delete(handler);
}
public async onHasStoriesDisabledChange(newValue: boolean): Promise<void> {
if (this.hasStoriesDisabled === newValue) {
if (this.#hasStoriesDisabled === newValue) {
return;
}
this.hasStoriesDisabled = newValue;
this.#hasStoriesDisabled = newValue;
log.info(
`SocketManager: reconnecting after setting hasStoriesDisabled=${newValue}`
);
@ -495,24 +482,25 @@ export class SocketManager extends EventListener {
public async reconnect(): Promise<void> {
log.info('SocketManager.reconnect: starting...');
const { authenticated, unauthenticated } = this;
const unauthenticated = this.#unauthenticated;
const authenticated = this.#authenticated;
if (authenticated) {
authenticated.abort();
this.dropAuthenticated(authenticated);
this.#dropAuthenticated(authenticated);
}
if (unauthenticated) {
unauthenticated.abort();
this.dropUnauthenticated(unauthenticated);
this.#dropUnauthenticated(unauthenticated);
}
if (this.credentials) {
this.backOff.reset();
if (this.#credentials) {
this.#backOff.reset();
// Cancel old reconnect attempt
this.reconnectController?.abort();
this.#reconnectController?.abort();
// Start the new attempt
await this.authenticate(this.credentials);
await this.authenticate(this.#credentials);
}
log.info('SocketManager.reconnect: complete.');
@ -522,71 +510,71 @@ export class SocketManager extends EventListener {
public async check(): Promise<void> {
log.info('SocketManager.check');
await Promise.all([
this.checkResource(this.authenticated),
this.checkResource(this.unauthenticated),
this.#checkResource(this.#authenticated),
this.#checkResource(this.#unauthenticated),
]);
}
public async onNavigatorOnline(): Promise<void> {
log.info('SocketManager.onNavigatorOnline');
this.isNavigatorOffline = false;
this.backOff.reset(FIBONACCI_TIMEOUTS);
this.#isNavigatorOffline = false;
this.#backOff.reset(FIBONACCI_TIMEOUTS);
// Reconnect earlier if waiting
if (this.credentials !== undefined) {
this.reconnectController?.abort();
await this.authenticate(this.credentials);
if (this.#credentials !== undefined) {
this.#reconnectController?.abort();
await this.authenticate(this.#credentials);
}
}
public async onNavigatorOffline(): Promise<void> {
log.info('SocketManager.onNavigatorOffline');
this.isNavigatorOffline = true;
this.backOff.reset(EXTENDED_FIBONACCI_TIMEOUTS);
this.#isNavigatorOffline = true;
this.#backOff.reset(EXTENDED_FIBONACCI_TIMEOUTS);
await this.check();
}
public async onRemoteExpiration(): Promise<void> {
log.info('SocketManager.onRemoteExpiration');
this.isRemotelyExpired = true;
this.#isRemotelyExpired = true;
// Cancel reconnect attempt if any
this.reconnectController?.abort();
this.#reconnectController?.abort();
}
public async logout(): Promise<void> {
const { authenticated } = this;
const authenticated = this.#authenticated;
if (authenticated) {
authenticated.abort();
this.dropAuthenticated(authenticated);
this.#dropAuthenticated(authenticated);
}
this.credentials = undefined;
this.#credentials = undefined;
}
public get isOnline(): boolean | undefined {
return this.privIsOnline;
return this.#privIsOnline;
}
//
// Private
//
private setStatus(status: SocketStatus): void {
if (this.status === status) {
#setStatus(status: SocketStatus): void {
if (this.#status === status) {
return;
}
this.status = status;
this.#status = status;
this.emit('statusChange');
if (this.status === SocketStatus.OPEN && !this.privIsOnline) {
this.privIsOnline = true;
if (this.#status === SocketStatus.OPEN && !this.#privIsOnline) {
this.#privIsOnline = true;
this.emit('online');
}
}
private transportOption(proxyAgent: ProxyAgent | undefined): TransportOption {
#transportOption(proxyAgent: ProxyAgent | undefined): TransportOption {
const { hostname } = URL.parse(this.options.url);
// transport experiment doesn't support proxy
@ -629,17 +617,17 @@ export class SocketManager extends EventListener {
: TransportOption.Original;
}
private async getUnauthenticatedResource(): Promise<IWebSocketResource> {
async #getUnauthenticatedResource(): Promise<IWebSocketResource> {
// awaiting on `this.getProxyAgent()` needs to happen here
// so that there are no calls to `await` between checking
// the value of `this.unauthenticated` and assigning it later in this function
const proxyAgent = await this.getProxyAgent();
const proxyAgent = await this.#getProxyAgent();
if (this.unauthenticated) {
return this.unauthenticated.getResult();
if (this.#unauthenticated) {
return this.#unauthenticated.getResult();
}
if (this.isRemotelyExpired) {
if (this.#isRemotelyExpired) {
throw new HTTPError('SocketManager remotely expired', {
code: 0,
headers: {},
@ -649,7 +637,7 @@ export class SocketManager extends EventListener {
log.info('SocketManager: connecting unauthenticated socket');
const transportOption = this.transportOption(proxyAgent);
const transportOption = this.#transportOption(proxyAgent);
log.info(
`SocketManager: connecting unauthenticated socket, transport option [${transportOption}]`
);
@ -663,7 +651,7 @@ export class SocketManager extends EventListener {
keepalive: { path: '/v1/keepalive' },
});
} else {
process = this.connectResource({
process = this.#connectResource({
name: UNAUTHENTICATED_CHANNEL_NAME,
path: '/v1/websocket/',
proxyAgent,
@ -675,17 +663,17 @@ export class SocketManager extends EventListener {
});
}
this.unauthenticated = process;
this.#unauthenticated = process;
let unauthenticated: IWebSocketResource;
try {
unauthenticated = await this.unauthenticated.getResult();
unauthenticated = await this.#unauthenticated.getResult();
} catch (error) {
log.info(
'SocketManager: failed to connect unauthenticated socket ' +
` due to error: ${Errors.toLogFormat(error)}`
);
this.dropUnauthenticated(process);
this.#dropUnauthenticated(process);
throw error;
}
@ -694,7 +682,7 @@ export class SocketManager extends EventListener {
);
unauthenticated.addEventListener('close', ({ code, reason }): void => {
if (this.unauthenticated !== process) {
if (this.#unauthenticated !== process) {
return;
}
@ -703,13 +691,13 @@ export class SocketManager extends EventListener {
`with code=${code} and reason=${reason}`
);
this.dropUnauthenticated(process);
this.#dropUnauthenticated(process);
});
return this.unauthenticated.getResult();
return this.#unauthenticated.getResult();
}
private connectResource({
#connectResource({
name,
path,
proxyAgent,
@ -757,7 +745,10 @@ export class SocketManager extends EventListener {
resourceOptions.transportOption === TransportOption.Original;
return shadowingModeEnabled
? webSocketResourceConnection
: this.connectWithShadowing(webSocketResourceConnection, resourceOptions);
: this.#connectWithShadowing(
webSocketResourceConnection,
resourceOptions
);
}
/**
@ -774,7 +765,7 @@ export class SocketManager extends EventListener {
* @param options `WebSocketResourceOptions` options
* @private
*/
private connectWithShadowing(
#connectWithShadowing(
mainConnection: AbortableProcess<WebSocketResource>,
options: WebSocketResourceOptions
): AbortableProcess<IWebSocketResource> {
@ -809,7 +800,7 @@ export class SocketManager extends EventListener {
);
}
private async checkResource(
async #checkResource(
process?: AbortableProcess<IWebSocketResource>
): Promise<void> {
if (!process) {
@ -820,41 +811,37 @@ export class SocketManager extends EventListener {
// Force shorter timeout if we think we might be offline
resource.forceKeepAlive(
this.isNavigatorOffline ? OFFLINE_KEEPALIVE_TIMEOUT_MS : undefined
this.#isNavigatorOffline ? OFFLINE_KEEPALIVE_TIMEOUT_MS : undefined
);
}
private dropAuthenticated(
process: AbortableProcess<IWebSocketResource>
): void {
if (this.authenticated !== process) {
#dropAuthenticated(process: AbortableProcess<IWebSocketResource>): void {
if (this.#authenticated !== process) {
return;
}
this.incomingRequestQueue = [];
this.authenticated = undefined;
this.setStatus(SocketStatus.CLOSED);
this.#incomingRequestQueue = [];
this.#authenticated = undefined;
this.#setStatus(SocketStatus.CLOSED);
}
private dropUnauthenticated(
process: AbortableProcess<IWebSocketResource>
): void {
if (this.unauthenticated !== process) {
#dropUnauthenticated(process: AbortableProcess<IWebSocketResource>): void {
if (this.#unauthenticated !== process) {
return;
}
this.unauthenticated = undefined;
if (!this.unauthenticatedExpirationTimer) {
this.#unauthenticated = undefined;
if (!this.#unauthenticatedExpirationTimer) {
return;
}
clearTimeout(this.unauthenticatedExpirationTimer);
this.unauthenticatedExpirationTimer = undefined;
clearTimeout(this.#unauthenticatedExpirationTimer);
this.#unauthenticatedExpirationTimer = undefined;
}
private async startUnauthenticatedExpirationTimer(
async #startUnauthenticatedExpirationTimer(
expected: IWebSocketResource
): Promise<void> {
const process = this.unauthenticated;
const process = this.#unauthenticated;
strictAssert(
process !== undefined,
'Unauthenticated socket must be connected'
@ -866,28 +853,28 @@ export class SocketManager extends EventListener {
'Unauthenticated resource should be the same'
);
if (this.unauthenticatedExpirationTimer) {
if (this.#unauthenticatedExpirationTimer) {
return;
}
log.info(
'SocketManager: starting expiration timer for unauthenticated socket'
);
this.unauthenticatedExpirationTimer = setTimeout(async () => {
this.#unauthenticatedExpirationTimer = setTimeout(async () => {
log.info(
'SocketManager: shutting down unauthenticated socket after timeout'
);
unauthenticated.shutdown();
// The socket is either deliberately closed or reconnected already
if (this.unauthenticated !== process) {
if (this.#unauthenticated !== process) {
return;
}
this.dropUnauthenticated(process);
this.#dropUnauthenticated(process);
try {
await this.getUnauthenticatedResource();
await this.#getUnauthenticatedResource();
} catch (error) {
log.warn(
'SocketManager: failed to reconnect unauthenticated socket ' +
@ -897,22 +884,22 @@ export class SocketManager extends EventListener {
}, FIVE_MINUTES);
}
private queueOrHandleRequest(req: IncomingWebSocketRequest): void {
#queueOrHandleRequest(req: IncomingWebSocketRequest): void {
if (req.requestType === ServerRequestType.ApiMessage) {
this.envelopeCount += 1;
if (this.envelopeCount === 1) {
this.#envelopeCount += 1;
if (this.#envelopeCount === 1) {
this.emit('firstEnvelope', req);
}
}
if (this.requestHandlers.size === 0) {
this.incomingRequestQueue.push(req);
if (this.#requestHandlers.size === 0) {
this.#incomingRequestQueue.push(req);
log.info(
'SocketManager: request handler unavailable, ' +
`queued request. Queue size: ${this.incomingRequestQueue.length}`
`queued request. Queue size: ${this.#incomingRequestQueue.length}`
);
return;
}
for (const handlers of this.requestHandlers) {
for (const handlers of this.#requestHandlers) {
try {
handlers.handleRequest(req);
} catch (error) {
@ -924,8 +911,8 @@ export class SocketManager extends EventListener {
}
}
private isAuthenticated(headers: Headers): boolean {
if (!this.credentials) {
#isAuthenticated(headers: Headers): boolean {
if (!this.#credentials) {
return false;
}
@ -946,17 +933,17 @@ export class SocketManager extends EventListener {
);
return (
username === this.credentials.username &&
password === this.credentials.password
username === this.#credentials.username &&
password === this.#credentials.password
);
}
private async getProxyAgent(): Promise<ProxyAgent | undefined> {
if (this.options.proxyUrl && !this.lazyProxyAgent) {
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);
this.#lazyProxyAgent = createProxyAgent(this.options.proxyUrl);
}
return this.lazyProxyAgent;
return this.#lazyProxyAgent;
}
// EventEmitter types

View file

@ -18,13 +18,10 @@ export class Storage implements StorageInterface {
public readonly blocked: Blocked;
private ready = false;
private readyCallbacks: Array<() => void> = [];
private items: Partial<Access> = Object.create(null);
private privProtocol: SignalProtocolStore | undefined;
#ready = false;
#readyCallbacks: Array<() => void> = [];
#items: Partial<Access> = Object.create(null);
#privProtocol: SignalProtocolStore | undefined;
constructor() {
this.user = new User(this);
@ -35,14 +32,14 @@ export class Storage implements StorageInterface {
get protocol(): SignalProtocolStore {
assertDev(
this.privProtocol !== undefined,
this.#privProtocol !== undefined,
'SignalProtocolStore not initialized'
);
return this.privProtocol;
return this.#privProtocol;
}
set protocol(value: SignalProtocolStore) {
this.privProtocol = value;
this.#privProtocol = value;
}
// `StorageInterface` implementation
@ -60,11 +57,11 @@ export class Storage implements StorageInterface {
key: K,
defaultValue?: Access[K]
): Access[K] | undefined {
if (!this.ready) {
if (!this.#ready) {
log.warn('Called storage.get before storage is ready. key:', key);
}
const item = this.items[key];
const item = this.#items[key];
if (item === undefined) {
return defaultValue;
}
@ -76,22 +73,22 @@ export class Storage implements StorageInterface {
key: K,
value: Access[K]
): Promise<void> {
if (!this.ready) {
if (!this.#ready) {
log.warn('Called storage.put before storage is ready. key:', key);
}
this.items[key] = value;
this.#items[key] = value;
await DataWriter.createOrUpdateItem({ id: key, value });
window.reduxActions?.items.putItemExternal(key, value);
}
public async remove<K extends keyof Access>(key: K): Promise<void> {
if (!this.ready) {
if (!this.#ready) {
log.warn('Called storage.remove before storage is ready. key:', key);
}
delete this.items[key];
delete this.#items[key];
await DataWriter.removeItemById(key);
window.reduxActions?.items.removeItemExternal(key);
@ -100,29 +97,29 @@ export class Storage implements StorageInterface {
// Regular methods
public onready(callback: () => void): void {
if (this.ready) {
if (this.#ready) {
callback();
} else {
this.readyCallbacks.push(callback);
this.#readyCallbacks.push(callback);
}
}
public async fetch(): Promise<void> {
this.reset();
Object.assign(this.items, await DataReader.getAllItems());
Object.assign(this.#items, await DataReader.getAllItems());
this.ready = true;
this.callListeners();
this.#ready = true;
this.#callListeners();
}
public reset(): void {
this.ready = false;
this.items = Object.create(null);
this.#ready = false;
this.#items = Object.create(null);
}
public getItemsState(): Partial<Access> {
if (!this.ready) {
if (!this.#ready) {
log.warn('Called getItemsState before storage is ready');
}
@ -130,8 +127,7 @@ export class Storage implements StorageInterface {
const state = Object.create(null);
// TypeScript isn't smart enough to figure out the types automatically.
const { items } = this;
const items = this.#items;
const allKeys = Object.keys(items) as Array<keyof typeof items>;
for (const key of allKeys) {
@ -141,12 +137,12 @@ export class Storage implements StorageInterface {
return state;
}
private callListeners(): void {
if (!this.ready) {
#callListeners(): void {
if (!this.#ready) {
return;
}
const callbacks = this.readyCallbacks;
this.readyCallbacks = [];
const callbacks = this.#readyCallbacks;
this.#readyCallbacks = [];
callbacks.forEach(callback => callback());
}
}

View file

@ -15,7 +15,7 @@ import { singleProtoJobQueue } from '../jobs/singleProtoJobQueue';
import * as Errors from '../types/errors';
class SyncRequestInner extends EventTarget {
private started = false;
#started = false;
contactSync?: boolean;
@ -44,14 +44,14 @@ class SyncRequestInner extends EventTarget {
}
async start(): Promise<void> {
if (this.started) {
if (this.#started) {
assertDev(
false,
'SyncRequestInner: started more than once. Doing nothing'
);
return;
}
this.started = true;
this.#started = true;
if (window.ConversationController.areWePrimaryDevice()) {
log.warn('SyncRequest.start: We are primary device; returning early');
@ -108,7 +108,7 @@ class SyncRequestInner extends EventTarget {
}
export default class SyncRequest {
private inner: SyncRequestInner;
#inner: SyncRequestInner;
addEventListener: (
name: 'success' | 'timeout',
@ -122,12 +122,12 @@ export default class SyncRequest {
constructor(receiver: MessageReceiver, timeoutMillis?: number) {
const inner = new SyncRequestInner(receiver, timeoutMillis);
this.inner = inner;
this.#inner = inner;
this.addEventListener = inner.addEventListener.bind(inner);
this.removeEventListener = inner.removeEventListener.bind(inner);
}
start(): void {
void this.inner.start();
void this.#inner.start();
}
}

View file

@ -41,16 +41,16 @@ export class UpdateKeysListener {
}
clearTimeoutIfNecessary(this.timeout);
this.timeout = setTimeout(() => this.runWhenOnline(), waitTime);
this.timeout = setTimeout(() => this.#runWhenOnline(), waitTime);
}
private scheduleNextUpdate(): void {
#scheduleNextUpdate(): void {
const now = Date.now();
const nextTime = now + UPDATE_INTERVAL;
void window.textsecure.storage.put(UPDATE_TIME_STORAGE_KEY, nextTime);
}
private async run(): Promise<void> {
async #run(): Promise<void> {
log.info('UpdateKeysListener: Updating keys...');
try {
const accountManager = window.getAccountManager();
@ -79,7 +79,7 @@ export class UpdateKeysListener {
}
}
this.scheduleNextUpdate();
this.#scheduleNextUpdate();
this.setTimeoutForNextRun();
} catch (error) {
const errorString =
@ -93,9 +93,9 @@ export class UpdateKeysListener {
}
}
private runWhenOnline() {
#runWhenOnline() {
if (window.textsecure.server?.isOnline()) {
void this.run();
void this.#run();
} else {
log.info(
'UpdateKeysListener: We are offline; will update keys when we are next online'

View file

@ -193,7 +193,7 @@ export class IncomingWebSocketRequestLibsignal
export class IncomingWebSocketRequestLegacy
implements IncomingWebSocketRequest
{
private readonly id: Long;
readonly #id: Long;
public readonly requestType: ServerRequestType;
@ -209,7 +209,7 @@ export class IncomingWebSocketRequestLegacy
strictAssert(request.verb, 'request without verb');
strictAssert(request.path, 'request without path');
this.id = request.id;
this.#id = request.id;
this.requestType = resolveType(request.path, request.verb);
this.body = dropNull(request.body);
this.timestamp = resolveTimestamp(request.headers || []);
@ -218,7 +218,7 @@ export class IncomingWebSocketRequestLegacy
public respond(status: number, message: string): void {
const bytes = Proto.WebSocketMessage.encode({
type: Proto.WebSocketMessage.Type.RESPONSE,
response: { id: this.id, message, status },
response: { id: this.#id, message, status },
}).finish();
this.sendBytes(Buffer.from(bytes));
@ -479,7 +479,7 @@ export class LibsignalWebSocketResource
// socket alive using websocket pings, so we don't need a timer-based
// keepalive mechanism. But we still send one-off keepalive requests when
// things change (see forceKeepAlive()).
private keepalive: KeepAliveSender;
#keepalive: KeepAliveSender;
constructor(
private readonly chatService: Net.ChatConnection,
@ -490,7 +490,7 @@ export class LibsignalWebSocketResource
) {
super();
this.keepalive = new KeepAliveSender(this, this.logId, keepalive);
this.#keepalive = new KeepAliveSender(this, this.logId, keepalive);
}
public localPort(): number {
@ -551,7 +551,7 @@ export class LibsignalWebSocketResource
}
public forceKeepAlive(timeout?: number): void {
drop(this.keepalive.send(timeout));
drop(this.#keepalive.send(timeout));
}
public async sendRequest(options: SendRequestOptions): Promise<Response> {
@ -578,28 +578,24 @@ export class LibsignalWebSocketResource
}
export class WebSocketResourceWithShadowing implements IWebSocketResource {
private shadowing: LibsignalWebSocketResource | undefined;
private stats: AggregatedStats;
private statsTimer: NodeJS.Timeout;
private shadowingWithReporting: boolean;
private logId: string;
#shadowing: LibsignalWebSocketResource | undefined;
#stats: AggregatedStats;
#statsTimer: NodeJS.Timeout;
#shadowingWithReporting: boolean;
#logId: string;
constructor(
private readonly main: WebSocketResource,
private readonly shadowingConnection: AbortableProcess<LibsignalWebSocketResource>,
options: WebSocketResourceOptions
) {
this.stats = AggregatedStats.createEmpty();
this.logId = `WebSocketResourceWithShadowing(${options.name})`;
this.statsTimer = setInterval(
() => this.updateStats(options.name),
this.#stats = AggregatedStats.createEmpty();
this.#logId = `WebSocketResourceWithShadowing(${options.name})`;
this.#statsTimer = setInterval(
() => this.#updateStats(options.name),
STATS_UPDATE_INTERVAL
);
this.shadowingWithReporting =
this.#shadowingWithReporting =
options.transportOption === TransportOption.ShadowingHigh;
// the idea is that we want to keep the shadowing connection process
@ -608,33 +604,33 @@ export class WebSocketResourceWithShadowing implements IWebSocketResource {
// or an error reported in case of connection failure
const initializeAfterConnected = async () => {
try {
this.shadowing = await shadowingConnection.resultPromise;
this.#shadowing = await shadowingConnection.resultPromise;
// checking IP one time per connection
if (this.main.ipVersion() !== this.shadowing.ipVersion()) {
this.stats.ipVersionMismatches += 1;
if (this.main.ipVersion() !== this.#shadowing.ipVersion()) {
this.#stats.ipVersionMismatches += 1;
const mainIpType = this.main.ipVersion();
const shadowIpType = this.shadowing.ipVersion();
const shadowIpType = this.#shadowing.ipVersion();
log.warn(
`${this.logId}: libsignal websocket IP [${shadowIpType}], Desktop websocket IP [${mainIpType}]`
`${this.#logId}: libsignal websocket IP [${shadowIpType}], Desktop websocket IP [${mainIpType}]`
);
}
} catch (error) {
this.stats.connectionFailures += 1;
this.#stats.connectionFailures += 1;
}
};
drop(initializeAfterConnected());
this.addEventListener('close', (_ev): void => {
clearInterval(this.statsTimer);
this.updateStats(options.name);
clearInterval(this.#statsTimer);
this.#updateStats(options.name);
});
}
private updateStats(name: string) {
#updateStats(name: string) {
const storedStats = AggregatedStats.loadOrCreateEmpty(name);
let updatedStats = AggregatedStats.add(storedStats, this.stats);
let updatedStats = AggregatedStats.add(storedStats, this.#stats);
if (
this.shadowingWithReporting &&
this.#shadowingWithReporting &&
AggregatedStats.shouldReportError(updatedStats) &&
!isProduction(window.getVersion())
) {
@ -642,14 +638,14 @@ export class WebSocketResourceWithShadowing implements IWebSocketResource {
toastType: ToastType.TransportError,
});
log.warn(
`${this.logId}: experimental transport toast displayed, flushing transport statistics before resetting`,
`${this.#logId}: experimental transport toast displayed, flushing transport statistics before resetting`,
updatedStats
);
updatedStats = AggregatedStats.createEmpty();
updatedStats.lastToastTimestamp = Date.now();
}
AggregatedStats.store(updatedStats, name);
this.stats = AggregatedStats.createEmpty();
this.#stats = AggregatedStats.createEmpty();
}
public localPort(): number | undefined {
@ -665,9 +661,9 @@ export class WebSocketResourceWithShadowing implements IWebSocketResource {
public close(code = NORMAL_DISCONNECT_CODE, reason?: string): void {
this.main.close(code, reason);
if (this.shadowing) {
this.shadowing.close(code, reason);
this.shadowing = undefined;
if (this.#shadowing) {
this.#shadowing.close(code, reason);
this.#shadowing = undefined;
} else {
this.shadowingConnection.abort();
}
@ -675,9 +671,9 @@ export class WebSocketResourceWithShadowing implements IWebSocketResource {
public shutdown(): void {
this.main.shutdown();
if (this.shadowing) {
this.shadowing.shutdown();
this.shadowing = undefined;
if (this.#shadowing) {
this.#shadowing.shutdown();
this.#shadowing = undefined;
} else {
this.shadowingConnection.abort();
}
@ -695,48 +691,48 @@ export class WebSocketResourceWithShadowing implements IWebSocketResource {
// attempting to run a healthcheck on a libsignal transport.
if (
isSuccessfulStatusCode(response.status) &&
this.shouldSendShadowRequest()
this.#shouldSendShadowRequest()
) {
drop(this.sendShadowRequest());
drop(this.#sendShadowRequest());
}
return response;
}
private async sendShadowRequest(): Promise<void> {
async #sendShadowRequest(): Promise<void> {
// In the shadowing mode, it could be that we're either
// still connecting libsignal websocket or have already closed it.
// In those cases we're not running shadowing check.
if (!this.shadowing) {
if (!this.#shadowing) {
log.info(
`${this.logId}: skipping healthcheck - websocket not connected or already closed`
`${this.#logId}: skipping healthcheck - websocket not connected or already closed`
);
return;
}
try {
const healthCheckResult = await this.shadowing.sendRequest({
const healthCheckResult = await this.#shadowing.sendRequest({
verb: 'GET',
path: '/v1/keepalive',
timeout: KEEPALIVE_TIMEOUT_MS,
});
this.stats.requestsCompared += 1;
this.#stats.requestsCompared += 1;
if (!isSuccessfulStatusCode(healthCheckResult.status)) {
this.stats.healthcheckBadStatus += 1;
this.#stats.healthcheckBadStatus += 1;
log.warn(
`${this.logId}: keepalive via libsignal responded with status [${healthCheckResult.status}]`
`${this.#logId}: keepalive via libsignal responded with status [${healthCheckResult.status}]`
);
}
} catch (error) {
this.stats.healthcheckFailures += 1;
this.#stats.healthcheckFailures += 1;
log.warn(
`${this.logId}: failed to send keepalive via libsignal`,
`${this.#logId}: failed to send keepalive via libsignal`,
Errors.toLogFormat(error)
);
}
}
private shouldSendShadowRequest(): boolean {
return this.shadowingWithReporting || random(0, 100) < 10;
#shouldSendShadowRequest(): boolean {
return this.#shadowingWithReporting || random(0, 100) < 10;
}
}
@ -748,28 +744,21 @@ export default class WebSocketResource
extends EventTarget
implements IWebSocketResource
{
private outgoingId = Long.fromNumber(1, true);
#outgoingId = Long.fromNumber(1, true);
#closed = false;
private closed = false;
private readonly outgoingMap = new Map<
readonly #outgoingMap = new Map<
string,
(result: SendRequestResult) => void
>();
private readonly boundOnMessage: (message: IMessage) => void;
private activeRequests = new Set<IncomingWebSocketRequest | string>();
private shuttingDown = false;
private shutdownTimer?: Timers.Timeout;
private readonly logId: string;
private readonly localSocketPort: number | undefined;
private readonly socketIpVersion: IpVersion | undefined;
readonly #boundOnMessage: (message: IMessage) => void;
#activeRequests = new Set<IncomingWebSocketRequest | string>();
#shuttingDown = false;
#shutdownTimer?: Timers.Timeout;
readonly #logId: string;
readonly #localSocketPort: number | undefined;
readonly #socketIpVersion: IpVersion | undefined;
// Public for tests
public readonly keepalive?: KeepAlive;
@ -780,25 +769,25 @@ export default class WebSocketResource
) {
super();
this.logId = `WebSocketResource(${options.name})`;
this.localSocketPort = socket.socket.localPort;
this.#logId = `WebSocketResource(${options.name})`;
this.#localSocketPort = socket.socket.localPort;
if (!socket.socket.localAddress) {
this.socketIpVersion = undefined;
this.#socketIpVersion = undefined;
}
if (socket.socket.localAddress == null) {
this.socketIpVersion = undefined;
this.#socketIpVersion = undefined;
} else if (net.isIPv4(socket.socket.localAddress)) {
this.socketIpVersion = IpVersion.IPv4;
this.#socketIpVersion = IpVersion.IPv4;
} else if (net.isIPv6(socket.socket.localAddress)) {
this.socketIpVersion = IpVersion.IPv6;
this.#socketIpVersion = IpVersion.IPv6;
} else {
this.socketIpVersion = undefined;
this.#socketIpVersion = undefined;
}
this.boundOnMessage = this.onMessage.bind(this);
this.#boundOnMessage = this.#onMessage.bind(this);
socket.on('message', this.boundOnMessage);
socket.on('message', this.#boundOnMessage);
if (options.keepalive) {
const keepalive = new KeepAlive(
@ -811,26 +800,26 @@ export default class WebSocketResource
keepalive.reset();
socket.on('close', () => this.keepalive?.stop());
socket.on('error', (error: Error) => {
log.warn(`${this.logId}: WebSocket error`, Errors.toLogFormat(error));
log.warn(`${this.#logId}: WebSocket error`, Errors.toLogFormat(error));
});
}
socket.on('close', (code, reason) => {
this.closed = true;
this.#closed = true;
log.warn(`${this.logId}: Socket closed`);
log.warn(`${this.#logId}: Socket closed`);
this.dispatchEvent(new CloseEvent(code, reason || 'normal'));
});
this.addEventListener('close', () => this.onClose());
this.addEventListener('close', () => this.#onClose());
}
public ipVersion(): IpVersion | undefined {
return this.socketIpVersion;
return this.#socketIpVersion;
}
public localPort(): number | undefined {
return this.localSocketPort;
return this.#localSocketPort;
}
public override addEventListener(
@ -843,12 +832,15 @@ export default class WebSocketResource
}
public async sendRequest(options: SendRequestOptions): Promise<Response> {
const id = this.outgoingId;
const id = this.#outgoingId;
const idString = id.toString();
strictAssert(!this.outgoingMap.has(idString), 'Duplicate outgoing request');
strictAssert(
!this.#outgoingMap.has(idString),
'Duplicate outgoing request'
);
// Note that this automatically wraps
this.outgoingId = this.outgoingId.add(1);
this.#outgoingId = this.#outgoingId.add(1);
const bytes = Proto.WebSocketMessage.encode({
type: Proto.WebSocketMessage.Type.REQUEST,
@ -871,25 +863,25 @@ export default class WebSocketResource
'WebSocket request byte size exceeded'
);
strictAssert(!this.shuttingDown, 'Cannot send request, shutting down');
this.addActive(idString);
strictAssert(!this.#shuttingDown, 'Cannot send request, shutting down');
this.#addActive(idString);
const promise = new Promise<SendRequestResult>((resolve, reject) => {
let timer = options.timeout
? Timers.setTimeout(() => {
this.removeActive(idString);
this.#removeActive(idString);
this.close(UNEXPECTED_DISCONNECT_CODE, 'Request timed out');
reject(new Error(`Request timed out; id: [${idString}]`));
}, options.timeout)
: undefined;
this.outgoingMap.set(idString, result => {
this.#outgoingMap.set(idString, result => {
if (timer !== undefined) {
Timers.clearTimeout(timer);
timer = undefined;
}
this.keepalive?.reset();
this.removeActive(idString);
this.#removeActive(idString);
resolve(result);
});
});
@ -908,58 +900,58 @@ export default class WebSocketResource
}
public close(code = NORMAL_DISCONNECT_CODE, reason?: string): void {
if (this.closed) {
log.info(`${this.logId}.close: Already closed! ${code}/${reason}`);
if (this.#closed) {
log.info(`${this.#logId}.close: Already closed! ${code}/${reason}`);
return;
}
log.info(`${this.logId}.close(${code})`);
log.info(`${this.#logId}.close(${code})`);
if (this.keepalive) {
this.keepalive.stop();
}
this.socket.close(code, reason);
this.socket.removeListener('message', this.boundOnMessage);
this.socket.removeListener('message', this.#boundOnMessage);
// On linux the socket can wait a long time to emit its close event if we've
// lost the internet connection. On the order of minutes. This speeds that
// process up.
Timers.setTimeout(() => {
if (this.closed) {
if (this.#closed) {
return;
}
log.warn(`${this.logId}.close: Dispatching our own socket close event`);
log.warn(`${this.#logId}.close: Dispatching our own socket close event`);
this.dispatchEvent(new CloseEvent(code, reason || 'normal'));
}, 5 * durations.SECOND);
}
public shutdown(): void {
if (this.closed) {
if (this.#closed) {
return;
}
if (this.activeRequests.size === 0) {
log.info(`${this.logId}.shutdown: no active requests, closing`);
if (this.#activeRequests.size === 0) {
log.info(`${this.#logId}.shutdown: no active requests, closing`);
this.close(NORMAL_DISCONNECT_CODE, 'Shutdown');
return;
}
this.shuttingDown = true;
this.#shuttingDown = true;
log.info(`${this.logId}.shutdown: shutting down`);
this.shutdownTimer = Timers.setTimeout(() => {
if (this.closed) {
log.info(`${this.#logId}.shutdown: shutting down`);
this.#shutdownTimer = Timers.setTimeout(() => {
if (this.#closed) {
return;
}
log.warn(`${this.logId}.shutdown: Failed to shutdown gracefully`);
log.warn(`${this.#logId}.shutdown: Failed to shutdown gracefully`);
this.close(NORMAL_DISCONNECT_CODE, 'Shutdown');
}, THIRTY_SECONDS);
}
private onMessage({ type, binaryData }: IMessage): void {
#onMessage({ type, binaryData }: IMessage): void {
if (type !== 'binary' || !binaryData) {
throw new Error(`Unsupported websocket message type: ${type}`);
}
@ -976,7 +968,7 @@ export default class WebSocketResource
const incomingRequest = new IncomingWebSocketRequestLegacy(
message.request,
(bytes: Buffer): void => {
this.removeActive(incomingRequest);
this.#removeActive(incomingRequest);
strictAssert(
bytes.length <= MAX_MESSAGE_SIZE,
@ -986,12 +978,12 @@ export default class WebSocketResource
}
);
if (this.shuttingDown) {
if (this.#shuttingDown) {
incomingRequest.respond(-1, 'Shutting down');
return;
}
this.addActive(incomingRequest);
this.#addActive(incomingRequest);
handleRequest(incomingRequest);
} else if (
message.type === Proto.WebSocketMessage.Type.RESPONSE &&
@ -1001,8 +993,8 @@ export default class WebSocketResource
strictAssert(response.id, 'response without id');
const responseIdString = response.id.toString();
const resolve = this.outgoingMap.get(responseIdString);
this.outgoingMap.delete(responseIdString);
const resolve = this.#outgoingMap.get(responseIdString);
this.#outgoingMap.delete(responseIdString);
if (!resolve) {
throw new Error(`Received response for unknown request ${response.id}`);
@ -1017,9 +1009,9 @@ export default class WebSocketResource
}
}
private onClose(): void {
const outgoing = new Map(this.outgoingMap);
this.outgoingMap.clear();
#onClose(): void {
const outgoing = new Map(this.#outgoingMap);
this.#outgoingMap.clear();
for (const resolve of outgoing.values()) {
resolve({
@ -1031,30 +1023,30 @@ export default class WebSocketResource
}
}
private addActive(request: IncomingWebSocketRequest | string): void {
this.activeRequests.add(request);
#addActive(request: IncomingWebSocketRequest | string): void {
this.#activeRequests.add(request);
}
private removeActive(request: IncomingWebSocketRequest | string): void {
if (!this.activeRequests.has(request)) {
log.warn(`${this.logId}.removeActive: removing unknown request`);
#removeActive(request: IncomingWebSocketRequest | string): void {
if (!this.#activeRequests.has(request)) {
log.warn(`${this.#logId}.removeActive: removing unknown request`);
return;
}
this.activeRequests.delete(request);
if (this.activeRequests.size !== 0) {
this.#activeRequests.delete(request);
if (this.#activeRequests.size !== 0) {
return;
}
if (!this.shuttingDown) {
if (!this.#shuttingDown) {
return;
}
if (this.shutdownTimer) {
Timers.clearTimeout(this.shutdownTimer);
this.shutdownTimer = undefined;
if (this.#shutdownTimer) {
Timers.clearTimeout(this.#shutdownTimer);
this.#shutdownTimer = undefined;
}
log.info(`${this.logId}.removeActive: shutdown complete`);
log.info(`${this.#logId}.removeActive: shutdown complete`);
this.close(NORMAL_DISCONNECT_CODE, 'Shutdown');
}
@ -1109,7 +1101,7 @@ const LOG_KEEPALIVE_AFTER_MS = 500;
* intervals.
*/
class KeepAliveSender {
private path: string;
#path: string;
protected wsr: IWebSocketResource;
@ -1121,7 +1113,7 @@ class KeepAliveSender {
opts: KeepAliveOptionsType = {}
) {
this.logId = `WebSocketResources.KeepAlive(${name})`;
this.path = opts.path ?? '/';
this.#path = opts.path ?? '/';
this.wsr = websocketResource;
}
@ -1133,7 +1125,7 @@ class KeepAliveSender {
const { status } = await pTimeout(
this.wsr.sendRequest({
verb: 'GET',
path: this.path,
path: this.#path,
}),
timeout
);
@ -1176,9 +1168,8 @@ class KeepAliveSender {
* {@link KeepAliveSender}.
*/
class KeepAlive extends KeepAliveSender {
private keepAliveTimer: Timers.Timeout | undefined;
private lastAliveAt: number = Date.now();
#keepAliveTimer: Timers.Timeout | undefined;
#lastAliveAt: number = Date.now();
constructor(
websocketResource: WebSocketResource,
@ -1189,18 +1180,18 @@ class KeepAlive extends KeepAliveSender {
}
public stop(): void {
this.clearTimers();
this.#clearTimers();
}
public override async send(timeout = KEEPALIVE_TIMEOUT_MS): Promise<boolean> {
this.clearTimers();
this.#clearTimers();
const isStale = isOlderThan(this.lastAliveAt, STALE_THRESHOLD_MS);
const isStale = isOlderThan(this.#lastAliveAt, STALE_THRESHOLD_MS);
if (isStale) {
log.info(`${this.logId}.send: disconnecting due to stale state`);
this.wsr.close(
UNEXPECTED_DISCONNECT_CODE,
`Last keepalive request was too far in the past: ${this.lastAliveAt}`
`Last keepalive request was too far in the past: ${this.#lastAliveAt}`
);
return false;
}
@ -1216,20 +1207,20 @@ class KeepAlive extends KeepAliveSender {
}
public reset(): void {
this.lastAliveAt = Date.now();
this.#lastAliveAt = Date.now();
this.clearTimers();
this.#clearTimers();
this.keepAliveTimer = Timers.setTimeout(
this.#keepAliveTimer = Timers.setTimeout(
() => this.send(),
KEEPALIVE_INTERVAL_MS
);
}
private clearTimers(): void {
if (this.keepAliveTimer) {
Timers.clearTimeout(this.keepAliveTimer);
this.keepAliveTimer = undefined;
#clearTimers(): void {
if (this.#keepAliveTimer) {
Timers.clearTimeout(this.#keepAliveTimer);
this.#keepAliveTimer = undefined;
}
}
}

View file

@ -15,12 +15,12 @@ export type CDSIOptionsType = Readonly<{
CDSSocketManagerBaseOptionsType;
export class CDSI extends CDSSocketManagerBase<CDSISocket, CDSIOptionsType> {
private readonly mrenclave: Buffer;
readonly #mrenclave: Buffer;
constructor(libsignalNet: Net.Net, options: CDSIOptionsType) {
super(libsignalNet, options);
this.mrenclave = Buffer.from(Bytes.fromHex(options.mrenclave));
this.#mrenclave = Buffer.from(Bytes.fromHex(options.mrenclave));
}
protected override getSocketUrl(): string {
@ -33,7 +33,7 @@ export class CDSI extends CDSSocketManagerBase<CDSISocket, CDSIOptionsType> {
return new CDSISocket({
logger: this.logger,
socket,
mrenclave: this.mrenclave,
mrenclave: this.#mrenclave,
});
}
}

View file

@ -14,7 +14,7 @@ export type CDSISocketOptionsType = Readonly<{
CDSSocketBaseOptionsType;
export class CDSISocket extends CDSSocketBase<CDSISocketOptionsType> {
private privCdsClient: Cds2Client | undefined;
#privCdsClient: Cds2Client | undefined;
public override async handshake(): Promise<void> {
strictAssert(
@ -31,23 +31,23 @@ export class CDSISocket extends CDSSocketBase<CDSISocketOptionsType> {
const earliestValidTimestamp = new Date();
strictAssert(
this.privCdsClient === undefined,
this.#privCdsClient === undefined,
'CDSI handshake called twice'
);
this.privCdsClient = Cds2Client.new(
this.#privCdsClient = Cds2Client.new(
this.options.mrenclave,
attestationMessage,
earliestValidTimestamp
);
}
this.socket.sendBytes(this.cdsClient.initialRequest());
this.socket.sendBytes(this.#cdsClient.initialRequest());
{
const { done, value: message } = await this.socketIterator.next();
strictAssert(!done, 'CDSI socket expected handshake data');
this.cdsClient.completeHandshake(message);
this.#cdsClient.completeHandshake(message);
}
this.state = CDSSocketState.Established;
@ -57,7 +57,7 @@ export class CDSISocket extends CDSSocketBase<CDSISocketOptionsType> {
_version: number,
request: Buffer
): Promise<void> {
this.socket.sendBytes(this.cdsClient.establishedSend(request));
this.socket.sendBytes(this.#cdsClient.establishedSend(request));
const { done, value: ciphertext } = await this.socketIterator.next();
strictAssert(!done, 'CDSISocket.sendRequest(): expected token message');
@ -70,7 +70,7 @@ export class CDSISocket extends CDSSocketBase<CDSISocketOptionsType> {
strictAssert(token, 'CDSISocket.sendRequest(): expected token');
this.socket.sendBytes(
this.cdsClient.establishedSend(
this.#cdsClient.establishedSend(
Buffer.from(
Proto.CDSClientRequest.encode({
tokenAck: true,
@ -83,15 +83,15 @@ export class CDSISocket extends CDSSocketBase<CDSISocketOptionsType> {
protected override async decryptResponse(
ciphertext: Buffer
): Promise<Buffer> {
return this.cdsClient.establishedRecv(ciphertext);
return this.#cdsClient.establishedRecv(ciphertext);
}
//
// Private
//
private get cdsClient(): Cds2Client {
strictAssert(this.privCdsClient, 'CDSISocket did not start handshake');
return this.privCdsClient;
get #cdsClient(): Cds2Client {
strictAssert(this.#privCdsClient, 'CDSISocket did not start handshake');
return this.#privCdsClient;
}
}

View file

@ -56,7 +56,7 @@ export abstract class CDSSocketBase<
this.logger = options.logger;
this.socket = options.socket;
this.socketIterator = this.iterateSocket();
this.socketIterator = this.#iterateSocket();
}
public async close(code: number, reason: string): Promise<void> {
@ -161,7 +161,7 @@ export abstract class CDSSocketBase<
// Private
//
private iterateSocket(): AsyncIterator<Buffer> {
#iterateSocket(): AsyncIterator<Buffer> {
const stream = new Readable({ read: noop, objectMode: true });
this.socket.on('message', ({ type, binaryData }) => {

View file

@ -41,7 +41,7 @@ export abstract class CDSSocketManagerBase<
Socket extends CDSSocketBase,
Options extends CDSSocketManagerBaseOptionsType,
> extends CDSBase<Options> {
private retryAfter?: number;
#retryAfter?: number;
constructor(
private readonly libsignalNet: Net.Net,
@ -55,27 +55,27 @@ export abstract class CDSSocketManagerBase<
): Promise<CDSResponseType> {
const log = this.logger;
if (this.retryAfter !== undefined) {
const delay = Math.max(0, this.retryAfter - Date.now());
if (this.#retryAfter !== undefined) {
const delay = Math.max(0, this.#retryAfter - Date.now());
log.info(`CDSSocketManager: waiting ${delay}ms before retrying`);
await sleep(delay);
}
if (options.useLibsignal) {
return this.requestViaLibsignal(options);
return this.#requestViaLibsignal(options);
}
return this.requestViaNativeSocket(options);
return this.#requestViaNativeSocket(options);
}
private async requestViaNativeSocket(
async #requestViaNativeSocket(
options: CDSRequestOptionsType
): Promise<CDSResponseType> {
const log = this.logger;
const auth = await this.getAuth();
log.info('CDSSocketManager: connecting socket');
const socket = await this.connect(auth).getResult();
const socket = await this.#connect(auth).getResult();
log.info('CDSSocketManager: connected socket');
try {
@ -97,8 +97,8 @@ export abstract class CDSSocketManagerBase<
} catch (error) {
if (error instanceof RateLimitedError) {
if (error.retryAfterSecs > 0) {
this.retryAfter = Math.max(
this.retryAfter ?? Date.now(),
this.#retryAfter = Math.max(
this.#retryAfter ?? Date.now(),
Date.now() + error.retryAfterSecs * durations.SECOND
);
}
@ -110,7 +110,7 @@ export abstract class CDSSocketManagerBase<
}
}
private async requestViaLibsignal(
async #requestViaLibsignal(
options: CDSRequestOptionsType
): Promise<CDSResponseType> {
const log = this.logger;
@ -139,8 +139,8 @@ export abstract class CDSSocketManagerBase<
error.code === LibSignalErrorCode.RateLimitedError
) {
const retryError = error as NetRateLimitedError;
this.retryAfter = Math.max(
this.retryAfter ?? Date.now(),
this.#retryAfter = Math.max(
this.#retryAfter ?? Date.now(),
Date.now() + retryError.retryAfterSecs * durations.SECOND
);
}
@ -148,7 +148,7 @@ export abstract class CDSSocketManagerBase<
}
}
private connect(auth: CDSAuthType): AbortableProcess<Socket> {
#connect(auth: CDSAuthType): AbortableProcess<Socket> {
return connectWebSocket<Socket>({
name: 'CDSSocket',
url: this.getSocketUrl(),

View file

@ -142,7 +142,8 @@ export class User {
}
public getDeviceId(): number | undefined {
const value = this._getDeviceIdFromUuid() || this._getDeviceIdFromNumber();
const value =
this.#_getDeviceIdFromUuid() || this.#_getDeviceIdFromNumber();
if (value === undefined) {
return undefined;
}
@ -202,7 +203,7 @@ export class User {
};
}
private _getDeviceIdFromUuid(): string | undefined {
#_getDeviceIdFromUuid(): string | undefined {
const uuid = this.storage.get('uuid_id');
if (uuid === undefined) {
return undefined;
@ -210,7 +211,7 @@ export class User {
return Helpers.unencodeNumber(uuid)[1];
}
private _getDeviceIdFromNumber(): string | undefined {
#_getDeviceIdFromNumber(): string | undefined {
const numberId = this.storage.get('number_id');
if (numberId === undefined) {
return undefined;