Signal-Desktop/ts/textsecure/UpdateKeysListener.ts

125 lines
3.6 KiB
TypeScript

// Copyright 2017 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as durations from '../util/durations';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
import * as Registration from '../util/registration';
import { ServiceIdKind } from '../types/ServiceId';
import { createLogger } from '../logging/log';
import * as Errors from '../types/errors';
import { HTTPError } from './Errors';
const log = createLogger('UpdateKeysListener');
const UPDATE_INTERVAL = 2 * durations.DAY;
const UPDATE_TIME_STORAGE_KEY = 'nextScheduledUpdateKeyTime';
export type MinimalEventsType = {
on(event: 'timetravel', callback: () => void): void;
};
let initComplete = false;
export class UpdateKeysListener {
public timeout: NodeJS.Timeout | undefined;
protected scheduleUpdateForNow(): void {
const now = Date.now();
void window.textsecure.storage.put(UPDATE_TIME_STORAGE_KEY, now);
}
protected setTimeoutForNextRun(): void {
const now = Date.now();
const time = window.textsecure.storage.get(UPDATE_TIME_STORAGE_KEY, now);
log.info('Next update scheduled for', new Date(time).toISOString());
let waitTime = time - now;
if (waitTime < 0) {
waitTime = 0;
}
clearTimeoutIfNecessary(this.timeout);
this.timeout = setTimeout(() => this.#runWhenOnline(), waitTime);
}
#scheduleNextUpdate(): void {
const now = Date.now();
const nextTime = now + UPDATE_INTERVAL;
void window.textsecure.storage.put(UPDATE_TIME_STORAGE_KEY, nextTime);
}
async #run(): Promise<void> {
log.info('Updating keys...');
try {
const accountManager = window.getAccountManager();
await accountManager.maybeUpdateKeys(ServiceIdKind.ACI);
try {
await accountManager.maybeUpdateKeys(ServiceIdKind.PNI);
} catch (error) {
if (
error instanceof HTTPError &&
(error.code === 422 || error.code === 403)
) {
log.error(`run: Got a ${error.code} uploading PNI keys; unlinking`);
window.Whisper.events.trigger('unlinkAndDisconnect');
} else {
const errorString =
error instanceof HTTPError
? error.code.toString()
: Errors.toLogFormat(error);
log.error(
`run: Failure uploading PNI keys. Not trying again. ${errorString}`
);
}
}
this.#scheduleNextUpdate();
this.setTimeoutForNextRun();
} catch (error) {
const errorString =
error instanceof HTTPError
? error.code.toString()
: Errors.toLogFormat(error);
log.error(`run failure - trying again in five minutes ${errorString}`);
setTimeout(() => this.setTimeoutForNextRun(), 5 * durations.MINUTE);
}
}
#runWhenOnline() {
if (window.textsecure.server?.isOnline()) {
void this.#run();
} else {
log.info('We are offline; will update keys when we are next online');
const listener = () => {
window.Whisper.events.off('online', listener);
this.setTimeoutForNextRun();
};
window.Whisper.events.on('online', listener);
}
}
public static init(events: MinimalEventsType, newVersion: boolean): void {
if (initComplete) {
log.info('Already initialized');
return;
}
initComplete = true;
const listener = new UpdateKeysListener();
if (newVersion) {
listener.scheduleUpdateForNow();
}
listener.setTimeoutForNextRun();
events.on('timetravel', () => {
if (Registration.isDone()) {
listener.setTimeoutForNextRun();
}
});
}
}