1 import { useCallback, useEffect, useRef, useState } from 'react';
3 import { c } from 'ttag';
5 import { useNotifications } from '@proton/components';
6 import type { MaybeNull, MaybePromise, OtpCode, OtpRequest } from '@proton/pass/types';
8 import { useEnsureMounted } from './useEnsureMounted';
10 export type UsePeriodOtpCodeOptions = {
11 /** defines how you want to resolve the OTP Code. In the case of the
12 * extension we can leverage worker messaging. For the web-app, this
13 * will use the OTP generation utilities in-place. */
14 generate: (payload: OtpRequest) => MaybePromise<MaybeNull<OtpCode>>;
18 /** Frame-skipping rate to achieve no more than
19 * 24fps for performance reasons. */
20 const REFRESH_RATE = 1000 / 24;
22 export const usePeriodicOtpCode = ({ generate, payload }: UsePeriodOtpCodeOptions): [MaybeNull<OtpCode>, number] => {
23 const otpKey = payload.type === 'item' ? `${payload.item.shareId}-${payload.item.itemId}` : payload.totpUri;
25 const [otp, setOtp] = useState<MaybeNull<OtpCode>>(null);
26 const [percentage, setPercentage] = useState<number>(-1);
27 const updatedAt = useRef<number>(0);
29 const requestAnimationRef = useRef<number>(-1);
30 const ensureMounted = useEnsureMounted();
32 const { createNotification } = useNotifications();
34 /* Only trigger the countdown if we have a valid
35 * OTP code with a valid period - else do nothing */
36 const doRequestOtpCodeGeneration = useCallback(async () => {
37 cancelAnimationFrame(requestAnimationRef.current);
38 updatedAt.current = 0;
40 const otpCode = await generate(payload);
41 ensureMounted(setOtp)(otpCode);
43 if (otpCode === null) {
44 return createNotification({
45 text: c('Error').t`Unable to generate an OTP code for this item`,
50 if (otpCode !== null && otpCode.period && otpCode.period > 0) {
51 const applyCountdown = ensureMounted(() => {
52 requestAnimationRef.current = requestAnimationFrame((timestamp) => {
53 if (timestamp - updatedAt.current >= REFRESH_RATE) {
54 updatedAt.current = timestamp;
55 const ms = otpCode.expiry * 1000 - Date.now();
56 setPercentage(ms / (otpCode.period * 1000));
67 /* if countdown has reached the 0 limit, trigger
68 * a new OTP Code generation sequence */
70 if (percentage < 0) void doRequestOtpCodeGeneration();
71 }, [percentage, doRequestOtpCodeGeneration]);
73 /* if any of the props change : clear the request
74 * animation frame request and re-init state */
79 cancelAnimationFrame(requestAnimationRef.current);
84 return [otp, percentage];