Use same lock values as mobile clients
[ProtonMail-WebClient.git] / packages / shared / lib / api / helpers / deviceVerificationHandler.ts
blob581f80e17b0227b7638371a341d8cd89958a5769
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) => {
13         try {
14             const pbkdfWorker = pbkdfWorkerWrapper();
15             pbkdfWorker.postMessage({ b64Source });
17             pbkdfWorker.onmessage = (event) => {
18                 resolve(event.data);
19             };
21             pbkdfWorker.onerror = (error) => {
22                 reject(error);
23             };
24         } catch (error) {
25             reject(error);
26         }
27     });
30 export const createDeviceHandlers = () => {
31     const deviceVerificationHandlers: { [key: string]: ReturnType<typeof createOnceHandler<string, string>> } = {};
33     const deviceVerificationHandler = (
34         UID: string,
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) => {
42                 try {
43                     await mutex.lock(UID);
44                     return () => {
45                         return mutex.unlock(UID).catch(noop);
46                     };
47                 } catch (e) {
48                     // If getting the mutex fails, fall back to a random wait
49                     await wait(randomIntFromInterval(100, 2000));
50                     return () => {
51                         return Promise.resolve();
52                     };
53                 }
54             };
56             deviceVerificationHandlers[UID] = createOnceHandler(async (challengePayload: string): Promise<string> => {
57                 const unlockMutex = await getMutexLock(UID);
58                 let token = '';
59                 try {
60                     const lastCachedDate = localStorageWithExpiry.getData(challengePayload);
61                     if (!lastCachedDate) {
62                         token = await onSolve(challengePayload);
63                         localStorageWithExpiry.storeData(challengePayload, token, 1 * 60 * 1000);
64                         await wait(50);
65                     } else {
66                         token = lastCachedDate;
67                     }
68                 } catch (e) {
69                     throw e;
70                 } finally {
71                     await unlockMutex();
72                 }
73                 return token;
74             });
75         }
77         return deviceVerificationHandlers[UID](challengePayload);
78     };
80     return deviceVerificationHandler;