import { sha256 } from '@noble/hashes/sha256' type DigestAlgorithm = string | { readonly name?: unknown } type DigestInput = ArrayBuffer | ArrayBufferView interface PkceSubtleCrypto { digest?: (algorithm: DigestAlgorithm, data: DigestInput) => Promise } export interface PkceCryptoTarget { getRandomValues?: (array: T) => T subtle?: PkceSubtleCrypto } function normalizeAlgorithm(algorithm: DigestAlgorithm): string { const name = typeof algorithm === 'string' ? algorithm : algorithm.name return typeof name === 'string' ? name.trim().toUpperCase().replaceAll('_', '-') : '' } function inputBytes(data: DigestInput): Uint8Array { if (data instanceof ArrayBuffer) return new Uint8Array(data) return new Uint8Array(data.buffer, data.byteOffset, data.byteLength) } /** * Supabase Auth requires WebCrypto SHA-256 to generate an S256 PKCE challenge. * React Native's native random-values shim supplies CSPRNG bytes but not * `crypto.subtle`; without this narrow digest implementation auth-js silently * downgrades to `code_challenge_method=plain`. */ export function installPkceS256(target: PkceCryptoTarget | undefined): void { if (!target || typeof target.getRandomValues !== 'function') { throw new Error('pkce_native_csprng_unavailable') } if (typeof target.subtle?.digest === 'function') return const digest = async (algorithm: DigestAlgorithm, data: DigestInput): Promise => { if (normalizeAlgorithm(algorithm) !== 'SHA-256') { throw new Error('pkce_digest_algorithm_unsupported') } if (!(data instanceof ArrayBuffer) && !ArrayBuffer.isView(data)) { throw new Error('pkce_digest_input_invalid') } return new Uint8Array(sha256(inputBytes(data))).buffer } Object.defineProperty(target, 'subtle', { value: Object.freeze({ digest }), enumerable: true, configurable: false, writable: false, }) } installPkceS256(globalThis.crypto as unknown as PkceCryptoTarget | undefined)