1 import { create as createMutex } from '@protontech/mutex-browser';
3 import { createOnceHandler } from '@proton/shared/lib/apiHandlers';
4 import { wait } from '@proton/shared/lib/helpers/promise';
5 import pbkdfWorkerWrapper from '@proton/shared/lib/pow/pbkdfWorkerWrapper';
6 import noop from '@proton/utils/noop';
7 import randomIntFromInterval from '@proton/utils/randomIntFromInterval';
9 import localStorageWithExpiry from './localStorageWithExpiry';
11 const onSolve = (b64Source: string): Promise<string> => {
12 return new Promise((resolve, reject) => {
14 const pbkdfWorker = pbkdfWorkerWrapper();
15 pbkdfWorker.postMessage({ b64Source });
17 pbkdfWorker.onmessage = (event) => {
21 pbkdfWorker.onerror = (error) => {
30 export const createDeviceHandlers = () => {
31 const deviceVerificationHandlers: { [key: string]: ReturnType<typeof createOnceHandler<string, string>> } = {};
33 const deviceVerificationHandler = (
35 challengeType: number,
36 challengePayload: string
37 ): Promise<string> => {
38 if (!deviceVerificationHandlers[UID]) {
39 const mutex = createMutex({ expiry: 15000 });
41 const getMutexLock = async (UID: string) => {
43 await mutex.lock(UID);
45 return mutex.unlock(UID).catch(noop);
48 // If getting the mutex fails, fall back to a random wait
49 await wait(randomIntFromInterval(100, 2000));
51 return Promise.resolve();
56 deviceVerificationHandlers[UID] = createOnceHandler(async (challengePayload: string): Promise<string> => {
57 const unlockMutex = await getMutexLock(UID);
60 const lastCachedDate = localStorageWithExpiry.getData(challengePayload);
61 if (!lastCachedDate) {
62 token = await onSolve(challengePayload);
63 localStorageWithExpiry.storeData(challengePayload, token, 1 * 60 * 1000);
66 token = lastCachedDate;
77 return deviceVerificationHandlers[UID](challengePayload);
80 return deviceVerificationHandler;