index.ts 1.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041
  1. /**
  2. * UUID minting that works in every JavaScript context this repository ships
  3. * to. `crypto.randomUUID` is a secure-context Web API — a page or worker
  4. * served over plain HTTP on a LAN address has no such method — while
  5. * `crypto.getRandomValues` is unrestricted everywhere (browsers, workers,
  6. * Node ≥ 19). One implementation here replaces per-caller polyfills; the
  7. * `no-restricted-properties` lint rule points `crypto.randomUUID` callers at
  8. * this module.
  9. * @module @deepseek-ai/dsh-util-crypto
  10. */
  11. /** RFC 9562 UUID string, the shape `crypto.randomUUID` declares. */
  12. export type Uuid = `${string}-${string}-${string}-${string}-${string}`
  13. /**
  14. * Encode bytes as canonical base64 without overflowing function argument limits.
  15. * @param data - Bytes to encode.
  16. * @returns base64 text.
  17. */
  18. export function bytesToBase64(data: Uint8Array): string {
  19. let binary = ''
  20. const chunk = 0x8000
  21. for (let offset = 0; offset < data.length; offset += chunk) {
  22. binary += String.fromCharCode(...data.subarray(offset, offset + chunk))
  23. }
  24. return btoa(binary)
  25. }
  26. /**
  27. * Random v4 UUID, minted from `crypto.getRandomValues`.
  28. * @returns the UUID string.
  29. */
  30. export function randomUUID(): Uuid {
  31. const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16))
  32. // RFC 9562 §5.4: version 4 in the high nibble of byte 6, variant 10 in byte 8.
  33. const hex = Array.from(bytes, (byte, index) => {
  34. const pinned = index === 6 ? (byte & 0x0f) | 0x40 : index === 8 ? (byte & 0x3f) | 0x80 : byte
  35. return pinned.toString(16).padStart(2, '0')
  36. }).join('')
  37. return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
  38. }