diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ad0f299a..0793029b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,6 +24,7 @@ "@nivo/line": "^0.96.0", "base-ex": "^0.8.1", "country-flag-icons": "^1.5.19", + "crypto-js": "^4.2.0", "date-fns": "^4.1.0", "file-replace-loader": "^1.4.2", "i18next": "^25.1.2", @@ -64,6 +65,7 @@ "@eslint/compat": "^1.2.9", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.26.0", + "@types/crypto-js": "^4.2.2", "@types/jest": "^29.5.14", "@types/latlon-geohash": "^2.0.4", "@types/leaflet": "^1.9.17", @@ -4368,6 +4370,13 @@ "@types/filesystem": "*" } }, + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/d3-color": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", @@ -6784,6 +6793,12 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/css-loader": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 03e13e13..4a31fc24 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,6 +24,7 @@ "@eslint/compat": "^1.2.9", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.26.0", + "@types/crypto-js": "^4.2.2", "@types/jest": "^29.5.14", "@types/latlon-geohash": "^2.0.4", "@types/leaflet": "^1.9.17", @@ -71,6 +72,7 @@ "@nivo/line": "^0.96.0", "base-ex": "^0.8.1", "country-flag-icons": "^1.5.19", + "crypto-js": "^4.2.0", "date-fns": "^4.1.0", "file-replace-loader": "^1.4.2", "i18next": "^25.1.2", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 79d0aac7..df0cbdb9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,6 +7,7 @@ import TorConnectionBadge from './components/TorConnection'; import { I18nextProvider } from 'react-i18next'; import i18n from './i18n/Web'; +import * as CryptoJS from 'crypto-js'; import { systemClient } from './services/System'; import ErrorBoundary from './components/ErrorBoundary'; @@ -15,7 +16,64 @@ import { GarageContextProvider } from './contexts/GarageContext'; import { FederationContextProvider } from './contexts/FederationContext'; import NotificationSwitchBadge from './components/NotificationSwitch'; +interface SubtleCrypto { + digest(algorithm: string, data: ArrayBuffer | Uint8Array | string): Promise; +} + +interface Crypto { + getRandomValues(arr: Uint8Array): Uint8Array; + subtle: SubtleCrypto; +} + const App = (): React.JSX.Element => { + // Necesary for OpenPGP JS + const getWebCrypto = (): { subtle: SubtleCrypto } => { + return { + subtle: { + digest: ( + algorithm: string, + data: ArrayBuffer | Uint8Array | string, + ): Promise => { + return new Promise((resolve, reject) => { + if (algorithm === 'SHA-256') { + let message: string; + if (data instanceof Uint8Array) { + message = new TextDecoder().decode(data); + } else if (data instanceof ArrayBuffer) { + message = new TextDecoder().decode(new Uint8Array(data)); + } else { + message = data; + } + + const hash = CryptoJS.SHA256(message).toString(); + const match = hash.match(/.{1,2}/g); + if (!match) { + return reject(new Error('Hash computation failed')); + } + const hashArray = new Uint8Array(match.map((byte) => parseInt(byte, 16))); + resolve(hashArray); + } else { + reject(new Error('Algorithm not supported')); + } + }); + }, + }, + }; + }; + + if (typeof window !== 'undefined') { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (window as any).crypto = { + getRandomValues: (arr: Uint8Array): Uint8Array => { + for (let i = 0; i < arr.length; i++) { + arr[i] = Math.floor(Math.random() * 256); + } + return arr; + }, + subtle: getWebCrypto().subtle, + } as Crypto; + } + const [client] = window.RobosatsSettings.split('-'); return (