// Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { ipcRenderer } from 'electron'; import { explodePromise } from './util/explodePromise'; import { SECOND } from './util/durations'; import * as log from './logging/log'; import type { IPCResponse as ChallengeResponseType } from './challenge'; type ResolveType = (data: unknown) => void; export type CIType = { deviceName: string; handleEvent: (event: string, data: unknown) => unknown; setProvisioningURL: (url: string) => unknown; solveChallenge: (response: ChallengeResponseType) => unknown; waitForEvent: ( event: string, options: { timeout?: number; ignorePastEvents?: boolean; } ) => unknown; }; export function getCI(deviceName: string): CIType { const eventListeners = new Map>(); const completedEvents = new Map>(); ipcRenderer.on('ci:event', (_, event, data) => { handleEvent(event, data); }); function waitForEvent( event: string, options: { timeout?: number; ignorePastEvents?: boolean; } = {} ) { const timeout = options?.timeout ?? 60 * SECOND; if (!options?.ignorePastEvents) { const pendingCompleted = completedEvents.get(event) || []; const pending = pendingCompleted.shift(); if (pending) { log.info(`CI: resolving pending result for ${event}`, pending); if (pendingCompleted.length === 0) { completedEvents.delete(event); } return pending; } } log.info(`CI: waiting for event ${event}`); const { resolve, reject, promise } = explodePromise(); const timer = setTimeout(() => { reject(new Error('Timed out')); }, timeout); let list = eventListeners.get(event); if (!list) { list = []; eventListeners.set(event, list); } list.push((value: unknown) => { clearTimeout(timer); resolve(value); }); return promise; } function setProvisioningURL(url: string): void { handleEvent('provisioning-url', url); } function handleEvent(event: string, data: unknown): void { const list = eventListeners.get(event) || []; const resolve = list.shift(); if (resolve) { if (list.length === 0) { eventListeners.delete(event); } log.info(`CI: got event ${event} with data`, data); resolve(data); return; } log.info(`CI: postponing event ${event}`); let resultList = completedEvents.get(event); if (!resultList) { resultList = []; completedEvents.set(event, resultList); } resultList.push(data); } function solveChallenge(response: ChallengeResponseType): void { window.Signal.challengeHandler?.onResponse(response); } return { deviceName, handleEvent, setProvisioningURL, solveChallenge, waitForEvent, }; }