import * as React from "react"; import * as Comlink from "comlink"; type CryptoContextType = { decrypt: (secret: string, ciphertext: string) => Promise; encrypt: (secret: string, plaintext: string) => Promise; getSharedSecret: ( ownPrivateKey: string, ownPublicKey: string, otherPublicKey: string, ) => Promise; }; type ApiRef = { encrypt: (secret: string, plaintext: string) => Promise; decrypt: (secret: string, ciphertext: string) => Promise; getSharedSecret: ( ownPrivateKey: string, ownPublicKey: string, otherPublicKey: string, ) => Promise; }; export const context = React.createContext( undefined, ); /** * Provides cryptographic actions backed by a worker without coupling to UI state. * @param props Component props with children. * @returns Crypto context provider JSX. */ export default function Provider(props: { children: React.ReactNode }) { const apiRef = React.useRef(null); const value = React.useMemo( () => createCryptoActions(() => apiRef.current), [], ); React.useEffect(() => { const worker = new Worker(new URL("./worker.ts", import.meta.url), { type: "module", }); apiRef.current = Comlink.wrap(worker); return () => { apiRef.current = null; worker.terminate(); }; }, []); return {props.children}; } /** * Creates crypto action functions that safely delegate to the worker API. * @param getApiRef Function that returns worker API reference when initialized. * @returns Typed crypto action functions. */ export function createCryptoActions( getApiRef: () => ApiRef | null, ): CryptoContextType { /** * Encrypts plaintext by delegating to the crypto worker API. * @param secret Hex-encoded shared secret. * @param plaintext Plaintext to encrypt. * @returns Encrypted ciphertext. */ const encrypt = async ( secret: string, plaintext: string, ): Promise => { const apiRef = getApiRef(); if (!apiRef) throw new Error("API not initialized"); return await apiRef.encrypt(secret, plaintext); }; /** * Decrypts ciphertext by delegating to the crypto worker API. * @param secret Hex-encoded shared secret. * @param ciphertext Ciphertext to decrypt. * @returns Decrypted plaintext. */ const decrypt = async ( secret: string, ciphertext: string, ): Promise => { const apiRef = getApiRef(); if (!apiRef) throw new Error("API not initialized"); return await apiRef.decrypt(secret, ciphertext); }; /** * Derives a shared secret from local and peer key material via the worker API. * @param ownPrivateKey Local private key. * @param ownPublicKey Local public key. * @param otherPublicKey Peer public key. * @returns Hex-encoded shared secret. */ const getSharedSecret = async ( ownPrivateKey: string, ownPublicKey: string, otherPublicKey: string, ): Promise => { const apiRef = getApiRef(); if (!apiRef) throw new Error("API not initialized"); return await apiRef.getSharedSecret( ownPrivateKey, ownPublicKey, otherPublicKey, ); }; return { encrypt, decrypt, getSharedSecret }; } /** * Returns the crypto actions from the nearest provider. * Throws when used outside of the crypto provider tree. */ export function useCrypto(): CryptoContextType { const ctx = React.useContext(context); if (!ctx) { throw new Error("useCrypto must be used within a CryptoProvider"); } return ctx; }