// 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 { UUIDKind } from '../types/UUID'; import * as log from '../logging/log'; const ROTATION_INTERVAL = 2 * durations.DAY; export type MinimalEventsType = { on(event: 'timetravel', callback: () => void): void; }; let initComplete = false; export class RotateSignedPreKeyListener { public timeout: NodeJS.Timeout | undefined; protected scheduleRotationForNow(): void { const now = Date.now(); void window.textsecure.storage.put('nextSignedKeyRotationTime', now); } protected setTimeoutForNextRun(): void { const now = Date.now(); const time = window.textsecure.storage.get( 'nextSignedKeyRotationTime', now ); log.info( 'Next signed key rotation scheduled for', new Date(time).toISOString() ); let waitTime = time - now; if (waitTime < 0) { waitTime = 0; } clearTimeoutIfNecessary(this.timeout); this.timeout = setTimeout(() => this.runWhenOnline(), waitTime); } private scheduleNextRotation(): void { const now = Date.now(); const nextTime = now + ROTATION_INTERVAL; void window.textsecure.storage.put('nextSignedKeyRotationTime', nextTime); } private async run(): Promise { log.info('Rotating signed prekey...'); try { const accountManager = window.getAccountManager(); await Promise.all([ accountManager.rotateSignedPreKey(UUIDKind.ACI), accountManager.rotateSignedPreKey(UUIDKind.PNI), ]); // We try to update this whenever we remove a preKey; this is a fail-safe to ensure // we're always in good shape await Promise.all([ accountManager.refreshPreKeys(UUIDKind.ACI), accountManager.refreshPreKeys(UUIDKind.PNI), ]); this.scheduleNextRotation(); this.setTimeoutForNextRun(); } catch (error) { log.error('rotateSignedPrekey() failed. Trying again in five minutes'); setTimeout(() => this.setTimeoutForNextRun(), 5 * durations.MINUTE); } } private runWhenOnline() { if (window.navigator.onLine) { void this.run(); } else { log.info('We are offline; keys will be rotated when we are next online'); const listener = () => { window.removeEventListener('online', listener); this.setTimeoutForNextRun(); }; window.addEventListener('online', listener); } } public static init(events: MinimalEventsType, newVersion: boolean): void { if (initComplete) { window.SignalContext.log.info( 'Rotate signed prekey listener: Already initialized' ); return; } initComplete = true; const listener = new RotateSignedPreKeyListener(); if (newVersion) { listener.scheduleRotationForNow(); } listener.setTimeoutForNextRun(); events.on('timetravel', () => { if (Registration.isDone()) { listener.setTimeoutForNextRun(); } }); } }