Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | 3x 6x 6x 6x 3x 3x 3x 3x 3x 3x 6x 6x 3x 4x 4x 4x 4x 4x 4x | import { createContext, useContext, useEffect, useState, type ReactNode } from 'react';
/**
* Interface for the crypto module functions exposed by WASM
*/
export interface CryptoModule {
/** Derive a Key ID from a public key: base64url(SHA-256(pubkey)[0:16]) */
derive_kid: (publicKey: Uint8Array) => string;
/** Encode bytes as base64url (RFC 4648) without padding */
encode_base64url: (bytes: Uint8Array) => string;
/** Decode a base64url string to bytes */
decode_base64url: (encoded: string) => Uint8Array;
}
interface CryptoContextValue {
/** The loaded crypto module, or null if still loading */
crypto: CryptoModule | null;
/** Whether the WASM module is currently loading */
isLoading: boolean;
/** Error if WASM failed to load */
error: Error | null;
}
const CryptoContext = createContext<CryptoContextValue>({
crypto: null,
isLoading: true,
error: null,
});
interface CryptoProviderProps {
children: ReactNode;
}
/**
* Provider that loads the tc-crypto WASM module and makes it available
* to child components via the useCrypto hook.
*
* The WASM module is loaded asynchronously on mount. Children will not
* render until the module is loaded (renders null during loading).
*/
export function CryptoProvider({ children }: CryptoProviderProps) {
const [state, setState] = useState<CryptoContextValue>({
crypto: null,
isLoading: true,
error: null,
});
useEffect(() => {
let mounted = true;
async function loadWasm() {
try {
// Dynamic import for code splitting - WASM loads separately from main bundle
const wasm = await import('@/wasm/tc-crypto/tc_crypto.js');
// Initialize the WASM module (required before using exported functions)
await wasm.default();
if (mounted) {
setState({
crypto: {
derive_kid: wasm.derive_kid,
encode_base64url: wasm.encode_base64url,
decode_base64url: wasm.decode_base64url,
},
isLoading: false,
error: null,
});
}
} catch (err) {
if (mounted) {
setState({
crypto: null,
isLoading: false,
error: err instanceof Error ? err : new Error('Failed to load crypto WASM module'),
});
}
}
}
void loadWasm();
return () => {
mounted = false;
};
}, []);
// Don't render children until WASM is loaded
// This ensures crypto is always available when useCryptoRequired is called
if (state.isLoading) {
return null;
}
if (state.error) {
// Let ErrorBoundary handle it or show inline error
throw state.error;
}
return <CryptoContext.Provider value={state}>{children}</CryptoContext.Provider>;
}
/**
* Hook to access the crypto module state.
* Returns { crypto, isLoading, error } - check isLoading/error before using crypto.
*/
export function useCrypto(): CryptoContextValue {
return useContext(CryptoContext);
}
/**
* Hook to access the crypto module directly.
* Throws if the crypto module is not loaded yet.
* Only use this inside components that are descendants of CryptoProvider.
*/
export function useCryptoRequired(): CryptoModule {
const { crypto, isLoading, error } = useContext(CryptoContext);
if (isLoading) {
throw new Error(
'Crypto module is still loading. Ensure this component is inside CryptoProvider.'
);
}
if (error) {
throw error;
}
if (!crypto) {
throw new Error('Crypto module not available. Ensure this component is inside CryptoProvider.');
}
return crypto;
}
|