async function withRetries( f: Function, { argList, maxTries = 16, onError = () => {} }: any = {} ) { let tries = 0; let lastError = null; while (tries < maxTries) { const argListFull = argList ?? []; for (const arg of argListFull) { try { return await f(arg); } catch (e) { onError(e); lastError = e; } } tries += 1; if (tries >= maxTries) { break; } const sleepDuration = Math.min(5000, tries * 1000); console.log(`retrying in ${sleepDuration}ms`); await new Promise((resolve) => setTimeout(resolve, sleepDuration)); } throw lastError; } const sleep = async (delay: number) => new Promise((resolve) => setTimeout(resolve, delay)); const photosLoop = function sendPhotos(ws: WebSocket, takePhoto: Function) { let running = true; (async () => { while (running) { const start = new Date().getTime(); const blob: any = await takePhoto(); if (blob) { ws.send(blob); } const took = new Date().getTime() - start; const timeout = 100; console.log('took', took); await sleep(took >= timeout ? 0 : timeout - took); } })(); return () => { running = false; }; }; const validateArtifact = async (data: any, sendLogToServer: Function) => { if ( data?.artifactInfo?.kind && data?.artifactInfo?.signature && data?.artifactInfo?.fileId ) { return; } await sendLogToServer('Artifact do not match scheme', data, 'warning'); }; async function createLiveness( servers: string[], jwt = 'jwt', takePhoto: Function, onCommand: Function, onError: Function, onReady: Function, sendLogToServer: Function, attemptNumber: number, setAttemptNumber: Function ) { const createServer = async (address: string) => { const ws = new WebSocket(`${address}/0.5/liveness/${jwt}`); let resolve: Function | null = null; let reject: Function | null = null; const wait = new Promise((res, rej) => { resolve = res; reject = rej; }); let timeout: any = null; ws.onopen = () => { resolve?.(); }; ws.onerror = (e) => { reject?.(e); }; await wait; let stop = () => {}; const platform = 'web-mobile'; const metadata = `{"messageType":"metadata","metadata":{"schemaVersion":"0.5.0","platform":"${platform}","attemptNumber":${attemptNumber}}}`; setAttemptNumber(attemptNumber + 1); ws.send(metadata); ws.send('needArtifacts'); ws.send('giveMeTask'); ws.onclose = (e) => { console.log('facing WS close', e); stop(); clearTimeout(timeout); }; ws.onerror = (e) => { console.error(e); ws.close(); clearTimeout(timeout); sendLogToServer('server_unavailable', e, 'error'); console.log('on error', JSON.stringify(e)); onError('internal'); }; const artifacts: any = {}; ws.onmessage = (event) => { stop(); const data = JSON.parse(event.data); switch (data.messageType) { case 'taskComplete': onCommand(data); timeout = setTimeout(() => { ws.send('giveMeTask'); }, 2000); return; case 'success': data.artifacts = artifacts; onCommand(data); ws?.onclose?.(event); console.log('liveness success'); return; case 'task': onCommand(data); stop = photosLoop(ws, takePhoto); return; case 'warning': onCommand(data, () => ws.send('giveMeTask')); return; case 'failure': console.log('liveness failure'); onCommand(data); ws?.onclose?.(event); return; case 'artifactInfo': validateArtifact(data, sendLogToServer).catch(console.error); artifacts[data.artifactInfo.kind] = { signature: data.artifactInfo?.signature, fileId: data.artifactInfo?.fileId, }; return; default: console.error('Wrong event:', data); break; } }; return () => { console.log('facing WS our close'); ws.close(); }; }; try { await withRetries( async (address: string) => { const stop = await createServer(address); onReady(stop); }, { argList: servers, onError: (e: any) => { console.error(e); sendLogToServer(e.message, e.stack, 'error'); onError('internal'); }, } ); } catch (e) { onError('internal'); sendLogToServer( `server_unavailable can not connect to the servers ${servers.join(', ')}`, null, 'error' ); } } export default createLiveness;