import type { PiiData } from "../types"; const BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const decodeBase64 = (value: string): Uint8Array => { const sanitized = value.replace(/=+$/, ""); let buffer = 0; let bitsCollected = 0; const output: number[] = []; for (const character of sanitized) { const index = BASE64_ALPHABET.indexOf(character); if (index < 0) { continue; } buffer = (buffer << 6) | index; bitsCollected += 6; if (bitsCollected >= 8) { bitsCollected -= 8; output.push((buffer >> bitsCollected) & 0xff); } } return new Uint8Array(output); }; const pemToArrayBuffer = (pem: string): ArrayBuffer => { const normalized = pem .replace(/-----BEGIN PUBLIC KEY-----/g, "") .replace(/-----END PUBLIC KEY-----/g, "") .replace(/\s+/g, ""); const bytes = decodeBase64(normalized); const arrayBuffer = new ArrayBuffer(bytes.byteLength); new Uint8Array(arrayBuffer).set(bytes); return arrayBuffer; }; const arrayBufferToBase64 = (buffer: ArrayBuffer): string => { const bytes = new Uint8Array(buffer); let output = ""; for (let index = 0; index < bytes.length; index += 3) { const byte1 = bytes[index]; const byte2 = bytes[index + 1]; const byte3 = bytes[index + 2]; const chunk = (byte1 << 16) | ((byte2 ?? 0) << 8) | (byte3 ?? 0); output += BASE64_ALPHABET[(chunk >> 18) & 63]; output += BASE64_ALPHABET[(chunk >> 12) & 63]; output += typeof byte2 === "number" ? BASE64_ALPHABET[(chunk >> 6) & 63] : "="; output += typeof byte3 === "number" ? BASE64_ALPHABET[chunk & 63] : "="; } return output; }; export const encryptPii = async ( publicKeyPem: string, payload: PiiData ): Promise => { const subtle = globalThis.crypto?.subtle; if (!subtle) { throw new Error( "No WebCrypto implementation found. Pass a custom piiEncryptor in SDK config for React Native." ); } if (typeof TextEncoder === "undefined") { throw new Error( "TextEncoder is not available. Pass a custom piiEncryptor in SDK config for React Native." ); } const key = await subtle.importKey( "spki", pemToArrayBuffer(publicKeyPem), { name: "RSA-OAEP", hash: "SHA-256", }, false, ["encrypt"] ); const encoded = new TextEncoder().encode(JSON.stringify(payload)); const encrypted = await subtle.encrypt({ name: "RSA-OAEP" }, key, encoded); return arrayBufferToBase64(encrypted); };