// Polyfill `crypto.getRandomValues` for React Native — tweetnacl (inside // @boltpay/tokenizer) calls `randomBytes` during postApplePayToken / // postGooglePayToken and throws "no PRNG" on Hermes/JSC without this. // Consumers must install `react-native-get-random-values` as a peer dep so RN // autolinking wires up the native module. import 'react-native-get-random-values'; import TkClient, { type FetchFn, type TkEnvironment } from '@boltpay/tokenizer'; import { useBolt } from './useBolt'; // Tokenizer requests share this timeout. 30s matches the PassKit sheet's own // ceiling, so we fail before iOS starts reporting payment-processing timeouts // to the user. Stalled requests must bound *somewhere* — without this the // Apple Pay sheet stays in "processing" indefinitely while the JS promise awaits. const TOKENIZER_TIMEOUT_MS = 30_000; // Adapts React Native's global fetch to the FetchFn interface TkClient expects, // with a request timeout implemented via AbortController. const rnFetch: FetchFn = async (url, init) => { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), TOKENIZER_TIMEOUT_MS); try { // `init as RequestInit` is safe: @boltpay/tokenizer's IRequestInit is // declared as `{ body?: string; headers?: {[k:string]: string}; method?: string }` // — a strict subset of the DOM RequestInit shape, so this cast cannot // drop fields today. Re-verify against fetch.d.ts on each tokenizer bump; // if IRequestInit ever adds custom keys (retry hints, priority), this // spread would silently discard them. const r = await fetch(url, { ...(init as RequestInit), signal: controller.signal, }); return { status: r.status, statusText: r.statusText, text: () => r.text(), }; } finally { clearTimeout(timeout); } }; // TkClient eagerly fetches the tokenizer public key and generates a tweetnacl // keypair in its constructor. Cache one instance per environment so mounting // both and doesn't duplicate that work. // // If the eager public-key fetch fails (network hiccup at app start), the // rejected publicKey promise is retained on the client forever — every // subsequent post*Token call awaits it and returns an Error with no retry. // Self-heal by evicting the broken client from the cache so the next caller // constructs a fresh one. const clientCache = new Map(); const getTkClient = (environment: TkEnvironment): TkClient => { let client = clientCache.get(environment); if (!client) { client = new TkClient(environment, rnFetch); clientCache.set(environment, client); const created = client; created .isPublicKeySet() .then((ok) => { if (!ok && clientCache.get(environment) === created) { clientCache.delete(environment); } }) .catch(() => { if (clientCache.get(environment) === created) { clientCache.delete(environment); } }); } return client; }; export const useTkClient = (): TkClient => { const { environment } = useBolt(); return getTkClient(environment); };