Migrate to private class properties/methods
This commit is contained in:
parent
7dbe57084b
commit
aa9f53df57
100 changed files with 3795 additions and 3944 deletions
|
|
@ -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'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }) => {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue