From ef5f58e5a21bce9f48c7888fd5fd6258823f0bda Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 13 Sep 2024 09:19:26 +0530 Subject: [PATCH 1/2] [auth][web] Render shared codes --- web/apps/auth/src/pages/share.tsx | 151 ++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 web/apps/auth/src/pages/share.tsx diff --git a/web/apps/auth/src/pages/share.tsx b/web/apps/auth/src/pages/share.tsx new file mode 100644 index 0000000000..09f83dd8ad --- /dev/null +++ b/web/apps/auth/src/pages/share.tsx @@ -0,0 +1,151 @@ +import { decryptMetadataJSON_New } from '@/base/crypto'; +import React, { useState, useEffect } from 'react'; + +interface SharedCodes { + startTime: number; + step: number; + codes: string; +} + +const Share: React.FC = () => { + const [decryptedData, setDecryptedData] = useState(null); + const [error, setError] = useState(null); + const [timeStatus, setTimeStatus] = useState(-10); + const [currentCode, setCurrentCode] = useState(''); + const [nextCode, setNextCode] = useState(''); + const [progress, setProgress] = useState(0); + + const base64UrlToByteArray = (base64Url: string): Uint8Array => { + const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); + const binaryString = atob(base64); + const len = binaryString.length; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; + }; + + const getCurrentAndNextCode = ( + codes: string[], + startTime: number, + stepDuration: number + ): { currentCode: string; nextCode: string } => { + const currentTime = Date.now(); + const elapsedTime = Math.floor((currentTime - startTime) / 1000); + const index = Math.floor(elapsedTime / stepDuration); + const currentCode = codes[index] || ''; + const nextCode = codes[index + 1] || ''; + return { currentCode, nextCode }; + }; + + const getTimeStatus = ( + currentTime: number, + startTime: number, + codes: string[], + stepDuration: number + ): number => { + if (currentTime < startTime) return -1; + const totalDuration = codes.length * stepDuration * 1000; + if (currentTime > startTime + totalDuration) return 1; + return 0; + }; + + useEffect(() => { + const decryptData = async () => { + const queryParams = new URLSearchParams(window.location.search); + const dataParam = queryParams.get('data'); + const headerParam = queryParams.get('header'); + const keyParam = window.location.hash.substring(1); + if (dataParam && headerParam && keyParam) { + try { + const decryptedCode: SharedCodes = await decryptMetadataJSON_New( + { + encryptedData: base64UrlToByteArray(dataParam), + decryptionHeader: base64UrlToByteArray(headerParam), + }, + base64UrlToByteArray(keyParam) + ) as SharedCodes; + setDecryptedData(decryptedCode); + } catch (error) { + console.error('Failed to decrypt data:', error); + setError('Failed to get the data. Please check the URL and try again.'); + } + } + }; + decryptData(); + }, []); + + useEffect(() => { + if (decryptedData) { + const interval = setInterval(() => { + const currentTime = Date.now(); + const timeStatus = getTimeStatus( + currentTime, + decryptedData.startTime, + decryptedData.codes.split(','), + decryptedData.step + ); + setTimeStatus(timeStatus); + if (timeStatus === 0) { + const { currentCode, nextCode } = getCurrentAndNextCode( + decryptedData.codes.split(','), + decryptedData.startTime, + decryptedData.step + ); + setCurrentCode(currentCode); + setNextCode(nextCode); + + const elapsedTime = (currentTime - decryptedData.startTime) / 1000; + const progress = (elapsedTime % decryptedData.step) / decryptedData.step * 100; + setProgress(progress); + } + }, 1000); + + return () => clearInterval(interval); + } + }, [decryptedData]); + + return ( +
+

Ente.io

+
+ {error &&

{error}

} + {timeStatus === -10 && error === null &&

Decrypting...

} + {timeStatus === -1 &&

Your or the person who shared the code has out of sync time.

} + {timeStatus === 1 && ( +

+ The code has expired. +

+ )} + {timeStatus === 0 && ( +
+
+
0.4 ? "green" : "orange", + transition: 'width 1s linear', + }} + /> +
+
+
{currentCode}
+
+

Next

+

{nextCode}

+
+
+ +
+ )} +
+
+ ); +}; + +export default Share; \ No newline at end of file From accf56351983186312445f0cc3ee915f48810877 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 13 Sep 2024 11:26:17 +0530 Subject: [PATCH 2/2] [auth][web] Update UI for shared codes --- web/apps/auth/src/pages/share.tsx | 364 +++++++++++++++++++----------- 1 file changed, 227 insertions(+), 137 deletions(-) diff --git a/web/apps/auth/src/pages/share.tsx b/web/apps/auth/src/pages/share.tsx index 09f83dd8ad..7b3c42a97b 100644 --- a/web/apps/auth/src/pages/share.tsx +++ b/web/apps/auth/src/pages/share.tsx @@ -1,151 +1,241 @@ -import { decryptMetadataJSON_New } from '@/base/crypto'; -import React, { useState, useEffect } from 'react'; +import { decryptMetadataJSON_New } from "@/base/crypto"; +import React, { useEffect, useMemo, useState } from "react"; -interface SharedCodes { - startTime: number; - step: number; - codes: string; +interface SharedCode { + startTime: number; + step: number; + codes: string; +} + +interface CodeDisplay { + currentCode: string; + nextCode: string; + progress: number; } const Share: React.FC = () => { - const [decryptedData, setDecryptedData] = useState(null); - const [error, setError] = useState(null); - const [timeStatus, setTimeStatus] = useState(-10); - const [currentCode, setCurrentCode] = useState(''); - const [nextCode, setNextCode] = useState(''); - const [progress, setProgress] = useState(0); + const [sharedCode, setSharedCode] = useState(null); + const [error, setError] = useState(null); + const [timeStatus, setTimeStatus] = useState(-10); + const [codeDisplay, setCodeDisplay] = useState({ + currentCode: "", + nextCode: "", + progress: 0, + }); - const base64UrlToByteArray = (base64Url: string): Uint8Array => { - const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); - const binaryString = atob(base64); - const len = binaryString.length; - const bytes = new Uint8Array(len); - for (let i = 0; i < len; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - return bytes; - }; - - const getCurrentAndNextCode = ( - codes: string[], - startTime: number, - stepDuration: number - ): { currentCode: string; nextCode: string } => { - const currentTime = Date.now(); - const elapsedTime = Math.floor((currentTime - startTime) / 1000); - const index = Math.floor(elapsedTime / stepDuration); - const currentCode = codes[index] || ''; - const nextCode = codes[index + 1] || ''; - return { currentCode, nextCode }; - }; - - const getTimeStatus = ( - currentTime: number, - startTime: number, - codes: string[], - stepDuration: number - ): number => { - if (currentTime < startTime) return -1; - const totalDuration = codes.length * stepDuration * 1000; - if (currentTime > startTime + totalDuration) return 1; - return 0; - }; - - useEffect(() => { - const decryptData = async () => { - const queryParams = new URLSearchParams(window.location.search); - const dataParam = queryParams.get('data'); - const headerParam = queryParams.get('header'); - const keyParam = window.location.hash.substring(1); - if (dataParam && headerParam && keyParam) { - try { - const decryptedCode: SharedCodes = await decryptMetadataJSON_New( - { - encryptedData: base64UrlToByteArray(dataParam), - decryptionHeader: base64UrlToByteArray(headerParam), - }, - base64UrlToByteArray(keyParam) - ) as SharedCodes; - setDecryptedData(decryptedCode); - } catch (error) { - console.error('Failed to decrypt data:', error); - setError('Failed to get the data. Please check the URL and try again.'); - } - } + const base64UrlToByteArray = (base64Url: string): Uint8Array => { + const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); + return Uint8Array.from(atob(base64), (c) => c.charCodeAt(0)); }; - decryptData(); - }, []); - useEffect(() => { - if (decryptedData) { - const interval = setInterval(() => { + const formatCode = (code: string): string => + code.replace(/(.{3})/g, "$1 ").trim(); + + const getCodeDisplay = ( + codes: string[], + startTime: number, + stepDuration: number, + ): CodeDisplay => { const currentTime = Date.now(); - const timeStatus = getTimeStatus( - currentTime, - decryptedData.startTime, - decryptedData.codes.split(','), - decryptedData.step - ); - setTimeStatus(timeStatus); - if (timeStatus === 0) { - const { currentCode, nextCode } = getCurrentAndNextCode( - decryptedData.codes.split(','), - decryptedData.startTime, - decryptedData.step - ); - setCurrentCode(currentCode); - setNextCode(nextCode); + const elapsedTime = (currentTime - startTime) / 1000; + const index = Math.floor(elapsedTime / stepDuration); + const progress = ((elapsedTime % stepDuration) / stepDuration) * 100; - const elapsedTime = (currentTime - decryptedData.startTime) / 1000; - const progress = (elapsedTime % decryptedData.step) / decryptedData.step * 100; - setProgress(progress); - } - }, 1000); + return { + currentCode: formatCode(codes[index] || ""), + nextCode: formatCode(codes[index + 1] || ""), + progress, + }; + }; - return () => clearInterval(interval); - } - }, [decryptedData]); + const getTimeStatus = ( + currentTime: number, + startTime: number, + codesLength: number, + stepDuration: number, + ): number => { + if (currentTime < startTime) return -1; + const totalDuration = codesLength * stepDuration * 1000; + if (currentTime > startTime + totalDuration) return 1; + return 0; + }; - return ( -
-

Ente.io

-
- {error &&

{error}

} - {timeStatus === -10 && error === null &&

Decrypting...

} - {timeStatus === -1 &&

Your or the person who shared the code has out of sync time.

} - {timeStatus === 1 && ( -

- The code has expired. -

- )} - {timeStatus === 0 && ( -
-
-
0.4 ? "green" : "orange", - transition: 'width 1s linear', - }} - /> + useEffect(() => { + const decryptCode = async () => { + const urlParams = new URLSearchParams(window.location.search); + const data = urlParams.get("data"); + const header = urlParams.get("header"); + const key = window.location.hash.substring(1); + + if (!(data && header && key)) { + setError("Invalid URL. Please check the URL."); + return; + } + + try { + const decryptedCode = (await decryptMetadataJSON_New( + { + encryptedData: base64UrlToByteArray(data), + decryptionHeader: base64UrlToByteArray(header), + }, + base64UrlToByteArray(key), + )) as SharedCode; + setSharedCode(decryptedCode); + } catch (error) { + console.error("Failed to decrypt data:", error); + setError( + "Failed to get the data. Please check the URL and try again.", + ); + } + }; + decryptCode(); + }, []); + + useEffect(() => { + if (!sharedCode) return; + + const updateCode = () => { + const currentTime = Date.now(); + const codes = sharedCode.codes.split(","); + const status = getTimeStatus( + currentTime, + sharedCode.startTime, + codes.length, + sharedCode.step, + ); + setTimeStatus(status); + + if (status === 0) { + setCodeDisplay( + getCodeDisplay( + codes, + sharedCode.startTime, + sharedCode.step, + ), + ); + } + }; + + const interval = setInterval(updateCode, 100); + return () => clearInterval(interval); + }, [sharedCode]); + + const progressBarColor = useMemo( + () => (100 - codeDisplay.progress > 40 ? "#8E2DE2" : "#FFC107"), + [codeDisplay.progress], + ); + + const Message: React.FC<{ text: string }> = ({ text }) => ( +

{text}

+ ); + + return ( +
+
ente
+ +
+ {error &&

{error}

} + {timeStatus === -10 && !error && ( + + )} + {timeStatus === -1 && ( + + )} + {timeStatus === 1 && } + {timeStatus === 0 && ( +
+
+
+
+
+ {codeDisplay.currentCode} +
+
+

+ {codeDisplay.nextCode === "" + ? "Last code" + : "next"} +

+ {codeDisplay.nextCode !== "" && ( +

+ {codeDisplay.nextCode} +

+ )} +
+
+ )}
-
-
{currentCode}
-
-

Next

-

{nextCode}

-
-
- -
- )} -
-
- ); + + + + +
+ ); }; -export default Share; \ No newline at end of file +export default Share;