{"version":3,"file":"crypto-runtime.mjs","names":[],"sources":["../../../../../../encryption/src/crypto-runtime.ts"],"sourcesContent":["import { UnsupportedRuntimeError } from \"./errors\";\n\n/**\n * Minimal structural views over the WebCrypto surface this package touches.\n *\n * They exist so the package type-checks without requiring `lib.dom` (browser)\n * or `@types/node` (server) in the consumer's tsconfig — the real `CryptoKey`\n * and `SubtleCrypto` are structurally assignable to these.\n */\nexport type CryptoKeyLike = {\n  readonly type: string;\n  readonly extractable: boolean;\n};\n\nexport type SubtleCryptoLike = {\n  importKey(\n    format: \"raw\",\n    keyData: Uint8Array,\n    algorithm: any,\n    extractable: boolean,\n    usages: string[]\n  ): Promise<any>;\n  deriveKey(\n    algorithm: any,\n    baseKey: any,\n    derivedKeyAlgorithm: any,\n    extractable: boolean,\n    usages: string[]\n  ): Promise<any>;\n  encrypt(algorithm: any, key: any, data: Uint8Array): Promise<ArrayBuffer>;\n  decrypt(algorithm: any, key: any, data: Uint8Array): Promise<ArrayBuffer>;\n};\n\ntype CryptoLike = {\n  subtle?: SubtleCryptoLike;\n  getRandomValues?<T extends Uint8Array>(array: T): T;\n};\n\nconst RUNTIME_HINT =\n  \"AES-GCM requires WebCrypto (globalThis.crypto.subtle), available in browsers over HTTPS/localhost and in Node.js 18+.\";\n\nfunction getCrypto(): CryptoLike {\n  const runtimeCrypto = (globalThis as any)?.crypto as CryptoLike | undefined;\n\n  if (!runtimeCrypto) {\n    throw new UnsupportedRuntimeError(\n      `No Web Crypto API found in this runtime. ${RUNTIME_HINT}`\n    );\n  }\n\n  return runtimeCrypto;\n}\n\n/**\n * Get the runtime's `crypto.subtle`, or throw a clear error.\n *\n * Browsers only expose `subtle` in a secure context, which is why the error\n * mentions HTTPS/localhost — that is by far the most common cause.\n */\nexport function getSubtle(): SubtleCryptoLike {\n  const { subtle } = getCrypto();\n\n  if (!subtle) {\n    throw new UnsupportedRuntimeError(\n      `crypto.subtle is not available in this runtime (an insecure browser context, or a runtime older than Node.js 18). ${RUNTIME_HINT}`\n    );\n  }\n\n  return subtle;\n}\n\n/**\n * Fill a buffer of the given length from the platform CSPRNG.\n *\n * Never falls back to `Math.random` — if there is no CSPRNG we refuse to\n * produce a salt or a nonce at all.\n */\nexport function randomBytes(length: number): Uint8Array {\n  const runtimeCrypto = getCrypto();\n\n  if (typeof runtimeCrypto.getRandomValues !== \"function\") {\n    throw new UnsupportedRuntimeError(\n      `crypto.getRandomValues is not available in this runtime; refusing to generate a salt/nonce from a non-cryptographic source. ${RUNTIME_HINT}`\n    );\n  }\n\n  return runtimeCrypto.getRandomValues(new Uint8Array(length));\n}\n\nconst textEncoder = new TextEncoder();\nconst textDecoder = new TextDecoder(\"utf-8\", { fatal: true });\n\nexport function utf8ToBytes(text: string): Uint8Array {\n  return textEncoder.encode(text);\n}\n\nexport function bytesToUtf8(bytes: Uint8Array): string {\n  return textDecoder.decode(bytes);\n}\n\nexport function concatBytes(...chunks: Uint8Array[]): Uint8Array {\n  const total = chunks.reduce((length, chunk) => length + chunk.length, 0);\n  const output = new Uint8Array(total);\n\n  let offset = 0;\n  for (const chunk of chunks) {\n    output.set(chunk, offset);\n    offset += chunk.length;\n  }\n\n  return output;\n}\n\n// `btoa` chokes on very large argument lists, so the binary string is built in\n// chunks — a 10k-character payload is a realistic input for this package.\nconst BASE64_CHUNK_SIZE = 0x8000;\n\nexport function toBase64(bytes: Uint8Array): string {\n  let binary = \"\";\n\n  for (let index = 0; index < bytes.length; index += BASE64_CHUNK_SIZE) {\n    binary += String.fromCharCode(\n      ...bytes.subarray(index, index + BASE64_CHUNK_SIZE)\n    );\n  }\n\n  return btoa(binary);\n}\n\n/**\n * Decode base64 into bytes, returning `null` instead of throwing when the\n * input is not valid base64 — callers treat \"not decodable\" as \"not one of\n * our envelopes\" rather than as a hard failure.\n */\nexport function tryFromBase64(value: string): Uint8Array | null {\n  if (typeof value !== \"string\" || value.length === 0) return null;\n\n  // `atob` tolerates some whitespace but not arbitrary characters; reject\n  // anything outside the base64 alphabet up front so the shape check is ours.\n  if (!/^[A-Za-z0-9+/]+={0,2}$/.test(value)) return null;\n\n  let binary: string;\n\n  try {\n    binary = atob(value);\n  } catch {\n    return null;\n  }\n\n  const bytes = new Uint8Array(binary.length);\n\n  for (let index = 0; index < binary.length; index++) {\n    bytes[index] = binary.charCodeAt(index);\n  }\n\n  return bytes;\n}\n"],"mappings":";;;AAsCA,MAAM,eACJ;AAEF,SAAS,YAAwB;CAC/B,MAAM,gBAAiB,YAAoB;CAE3C,IAAI,CAAC,eACH,MAAM,IAAI,wBACR,4CAA4C,cAC9C;CAGF,OAAO;AACT;;;;;;;AAQA,SAAgB,YAA8B;CAC5C,MAAM,EAAE,WAAW,UAAU;CAE7B,IAAI,CAAC,QACH,MAAM,IAAI,wBACR,qHAAqH,cACvH;CAGF,OAAO;AACT;;;;;;;AAQA,SAAgB,YAAY,QAA4B;CACtD,MAAM,gBAAgB,UAAU;CAEhC,IAAI,OAAO,cAAc,oBAAoB,YAC3C,MAAM,IAAI,wBACR,+HAA+H,cACjI;CAGF,OAAO,cAAc,gBAAgB,IAAI,WAAW,MAAM,CAAC;AAC7D;AAEA,MAAM,cAAc,IAAI,YAAY;AACpC,MAAM,cAAc,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC;AAE5D,SAAgB,YAAY,MAA0B;CACpD,OAAO,YAAY,OAAO,IAAI;AAChC;AAEA,SAAgB,YAAY,OAA2B;CACrD,OAAO,YAAY,OAAO,KAAK;AACjC;AAEA,SAAgB,YAAY,GAAG,QAAkC;CAC/D,MAAM,QAAQ,OAAO,QAAQ,QAAQ,UAAU,SAAS,MAAM,QAAQ,CAAC;CACvE,MAAM,SAAS,IAAI,WAAW,KAAK;CAEnC,IAAI,SAAS;CACb,KAAK,MAAM,SAAS,QAAQ;EAC1B,OAAO,IAAI,OAAO,MAAM;EACxB,UAAU,MAAM;CAClB;CAEA,OAAO;AACT;AAIA,MAAM,oBAAoB;AAE1B,SAAgB,SAAS,OAA2B;CAClD,IAAI,SAAS;CAEb,KAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS,mBACjD,UAAU,OAAO,aACf,GAAG,MAAM,SAAS,OAAO,QAAQ,iBAAiB,CACpD;CAGF,OAAO,KAAK,MAAM;AACpB;;;;;;AAOA,SAAgB,cAAc,OAAkC;CAC9D,IAAI,OAAO,UAAU,YAAY,MAAM,WAAW,GAAG,OAAO;CAI5D,IAAI,CAAC,yBAAyB,KAAK,KAAK,GAAG,OAAO;CAElD,IAAI;CAEJ,IAAI;EACF,SAAS,KAAK,KAAK;CACrB,QAAQ;EACN,OAAO;CACT;CAEA,MAAM,QAAQ,IAAI,WAAW,OAAO,MAAM;CAE1C,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SACzC,MAAM,SAAS,OAAO,WAAW,KAAK;CAGxC,OAAO;AACT"}