Update selected item color in Pass menu
[ProtonMail-WebClient.git] / packages / pass / hooks / usePeriodicOtpCode.ts
blob69066b05fedac8693e3b847eb6f3d937ec2ab598
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>>;
15     payload: OtpRequest;
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`,
46                 type: 'error',
47             });
48         }
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));
57                     }
59                     applyCountdown();
60                 });
61             });
63             applyCountdown();
64         }
65     }, [otpKey]);
67     /* if countdown has reached the 0 limit, trigger
68      * a new OTP Code generation sequence */
69     useEffect(() => {
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 */
75     useEffect(
76         () => () => {
77             setOtp(null);
78             setPercentage(-1);
79             cancelAnimationFrame(requestAnimationRef.current);
80         },
81         [otpKey]
82     );
84     return [otp, percentage];