tempest-react-sdk 0.35.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/README.md +72 -70
  2. package/dist/audio/audio-output.cjs +2 -0
  3. package/dist/audio/audio-output.cjs.map +1 -0
  4. package/dist/audio/audio-output.js +17 -0
  5. package/dist/audio/audio-output.js.map +1 -0
  6. package/dist/audio/audio-player.cjs +1 -1
  7. package/dist/audio/audio-player.cjs.map +1 -1
  8. package/dist/audio/audio-player.js +21 -20
  9. package/dist/audio/audio-player.js.map +1 -1
  10. package/dist/audio/audio-recorder.cjs +2 -0
  11. package/dist/audio/audio-recorder.cjs.map +1 -0
  12. package/dist/audio/audio-recorder.js +30 -0
  13. package/dist/audio/audio-recorder.js.map +1 -0
  14. package/dist/audio/duration.cjs +2 -0
  15. package/dist/audio/duration.cjs.map +1 -0
  16. package/dist/audio/duration.js +10 -0
  17. package/dist/audio/duration.js.map +1 -0
  18. package/dist/audio/level-meter.cjs +2 -0
  19. package/dist/audio/level-meter.cjs.map +1 -0
  20. package/dist/audio/level-meter.js +34 -0
  21. package/dist/audio/level-meter.js.map +1 -0
  22. package/dist/audio/media-access.cjs +2 -0
  23. package/dist/audio/media-access.cjs.map +1 -0
  24. package/dist/audio/media-access.js +51 -0
  25. package/dist/audio/media-access.js.map +1 -0
  26. package/dist/audio/use-audio-recorder.cjs +2 -0
  27. package/dist/audio/use-audio-recorder.cjs.map +1 -0
  28. package/dist/audio/use-audio-recorder.js +94 -0
  29. package/dist/audio/use-audio-recorder.js.map +1 -0
  30. package/dist/audio/use-audio.cjs.map +1 -1
  31. package/dist/audio/use-audio.js.map +1 -1
  32. package/dist/audio/use-microphone.cjs +2 -0
  33. package/dist/audio/use-microphone.cjs.map +1 -0
  34. package/dist/audio/use-microphone.js +54 -0
  35. package/dist/audio/use-microphone.js.map +1 -0
  36. package/dist/audio/wav.cjs +2 -0
  37. package/dist/audio/wav.cjs.map +1 -0
  38. package/dist/audio/wav.js +49 -0
  39. package/dist/audio/wav.js.map +1 -0
  40. package/dist/auth/passkey.cjs +2 -0
  41. package/dist/auth/passkey.cjs.map +1 -0
  42. package/dist/auth/passkey.js +171 -0
  43. package/dist/auth/passkey.js.map +1 -0
  44. package/dist/auth/use-passkey.cjs +2 -0
  45. package/dist/auth/use-passkey.cjs.map +1 -0
  46. package/dist/auth/use-passkey.js +120 -0
  47. package/dist/auth/use-passkey.js.map +1 -0
  48. package/dist/br/PixQRCode.cjs +2 -0
  49. package/dist/br/PixQRCode.cjs.map +1 -0
  50. package/dist/br/PixQRCode.js +70 -0
  51. package/dist/br/PixQRCode.js.map +1 -0
  52. package/dist/br/PixQRCode.module.cjs +2 -0
  53. package/dist/br/PixQRCode.module.cjs.map +1 -0
  54. package/dist/br/PixQRCode.module.js +15 -0
  55. package/dist/br/PixQRCode.module.js.map +1 -0
  56. package/dist/br/boleto.cjs +2 -0
  57. package/dist/br/boleto.cjs.map +1 -0
  58. package/dist/br/boleto.js +225 -0
  59. package/dist/br/boleto.js.map +1 -0
  60. package/dist/br/holidays.cjs +2 -0
  61. package/dist/br/holidays.cjs.map +1 -0
  62. package/dist/br/holidays.js +148 -0
  63. package/dist/br/holidays.js.map +1 -0
  64. package/dist/br/nfe.cjs +2 -0
  65. package/dist/br/nfe.cjs.map +1 -0
  66. package/dist/br/nfe.js +104 -0
  67. package/dist/br/nfe.js.map +1 -0
  68. package/dist/br/pix.cjs +2 -0
  69. package/dist/br/pix.cjs.map +1 -0
  70. package/dist/br/pix.js +148 -0
  71. package/dist/br/pix.js.map +1 -0
  72. package/dist/br.cjs +1 -1
  73. package/dist/br.cjs.map +1 -1
  74. package/dist/br.d.ts +878 -0
  75. package/dist/br.js +7 -2
  76. package/dist/br.js.map +1 -1
  77. package/dist/capture/barcode.cjs +2 -0
  78. package/dist/capture/barcode.cjs.map +1 -0
  79. package/dist/capture/barcode.js +59 -0
  80. package/dist/capture/barcode.js.map +1 -0
  81. package/dist/capture/media-recorder.cjs +2 -0
  82. package/dist/capture/media-recorder.cjs.map +1 -0
  83. package/dist/capture/media-recorder.js +64 -0
  84. package/dist/capture/media-recorder.js.map +1 -0
  85. package/dist/capture/use-barcode-scanner.cjs +2 -0
  86. package/dist/capture/use-barcode-scanner.cjs.map +1 -0
  87. package/dist/capture/use-barcode-scanner.js +86 -0
  88. package/dist/capture/use-barcode-scanner.js.map +1 -0
  89. package/dist/capture/use-screen-capture.cjs +2 -0
  90. package/dist/capture/use-screen-capture.cjs.map +1 -0
  91. package/dist/capture/use-screen-capture.js +79 -0
  92. package/dist/capture/use-screen-capture.js.map +1 -0
  93. package/dist/capture/use-speech-recognition.cjs +2 -0
  94. package/dist/capture/use-speech-recognition.cjs.map +1 -0
  95. package/dist/capture/use-speech-recognition.js +118 -0
  96. package/dist/capture/use-speech-recognition.js.map +1 -0
  97. package/dist/capture/use-torch.cjs +2 -0
  98. package/dist/capture/use-torch.cjs.map +1 -0
  99. package/dist/capture/use-torch.js +33 -0
  100. package/dist/capture/use-torch.js.map +1 -0
  101. package/dist/capture/use-video-recorder.cjs +2 -0
  102. package/dist/capture/use-video-recorder.cjs.map +1 -0
  103. package/dist/capture/use-video-recorder.js +90 -0
  104. package/dist/capture/use-video-recorder.js.map +1 -0
  105. package/dist/capture/video-recorder.cjs +2 -0
  106. package/dist/capture/video-recorder.cjs.map +1 -0
  107. package/dist/capture/video-recorder.js +31 -0
  108. package/dist/capture/video-recorder.js.map +1 -0
  109. package/dist/components/AIChat/AIChat.cjs +1 -1
  110. package/dist/components/AIChat/AIChat.cjs.map +1 -1
  111. package/dist/components/AIChat/AIChat.js +93 -97
  112. package/dist/components/AIChat/AIChat.js.map +1 -1
  113. package/dist/components/AIChat/AIChatComposer.cjs +1 -1
  114. package/dist/components/AIChat/AIChatComposer.cjs.map +1 -1
  115. package/dist/components/AIChat/AIChatComposer.js +2 -1
  116. package/dist/components/AIChat/AIChatComposer.js.map +1 -1
  117. package/dist/components/AIChat/AIChatTurn.cjs +2 -2
  118. package/dist/components/AIChat/AIChatTurn.js +6 -6
  119. package/dist/components/AIChat/AIChatTurn.js.map +1 -1
  120. package/dist/components/Alert/Alert.module.cjs.map +1 -1
  121. package/dist/components/Alert/Alert.module.js.map +1 -1
  122. package/dist/components/AudioPlayer/AudioPlayer.cjs +2 -0
  123. package/dist/components/AudioPlayer/AudioPlayer.cjs.map +1 -0
  124. package/dist/components/AudioPlayer/AudioPlayer.js +123 -0
  125. package/dist/components/AudioPlayer/AudioPlayer.js.map +1 -0
  126. package/dist/components/AudioPlayer/AudioPlayer.module.cjs +2 -0
  127. package/dist/components/AudioPlayer/AudioPlayer.module.cjs.map +1 -0
  128. package/dist/components/AudioPlayer/AudioPlayer.module.js +12 -0
  129. package/dist/components/AudioPlayer/AudioPlayer.module.js.map +1 -0
  130. package/dist/components/AudioRecorder/AudioRecorder.cjs +2 -0
  131. package/dist/components/AudioRecorder/AudioRecorder.cjs.map +1 -0
  132. package/dist/components/AudioRecorder/AudioRecorder.js +179 -0
  133. package/dist/components/AudioRecorder/AudioRecorder.js.map +1 -0
  134. package/dist/components/AudioRecorder/AudioRecorder.module.cjs +2 -0
  135. package/dist/components/AudioRecorder/AudioRecorder.module.cjs.map +1 -0
  136. package/dist/components/AudioRecorder/AudioRecorder.module.js +19 -0
  137. package/dist/components/AudioRecorder/AudioRecorder.module.js.map +1 -0
  138. package/dist/components/Badge/Badge.module.cjs.map +1 -1
  139. package/dist/components/Badge/Badge.module.js.map +1 -1
  140. package/dist/components/BarcodeScanner/BarcodeScanner.cjs +2 -0
  141. package/dist/components/BarcodeScanner/BarcodeScanner.cjs.map +1 -0
  142. package/dist/components/BarcodeScanner/BarcodeScanner.js +123 -0
  143. package/dist/components/BarcodeScanner/BarcodeScanner.js.map +1 -0
  144. package/dist/components/BarcodeScanner/BarcodeScanner.module.cjs +2 -0
  145. package/dist/components/BarcodeScanner/BarcodeScanner.module.cjs.map +1 -0
  146. package/dist/components/BarcodeScanner/BarcodeScanner.module.js +22 -0
  147. package/dist/components/BarcodeScanner/BarcodeScanner.module.js.map +1 -0
  148. package/dist/components/BottomNavigation/BottomNavigation.module.cjs.map +1 -1
  149. package/dist/components/BottomNavigation/BottomNavigation.module.js.map +1 -1
  150. package/dist/components/Button/Button.module.cjs.map +1 -1
  151. package/dist/components/Button/Button.module.js.map +1 -1
  152. package/dist/components/Combobox/Combobox.module.cjs.map +1 -1
  153. package/dist/components/Combobox/Combobox.module.js.map +1 -1
  154. package/dist/components/DataTable/DataTable.cjs +1 -1
  155. package/dist/components/DataTable/DataTable.cjs.map +1 -1
  156. package/dist/components/DataTable/DataTable.js +203 -66
  157. package/dist/components/DataTable/DataTable.js.map +1 -1
  158. package/dist/components/DataTable/DataTable.module.cjs +1 -1
  159. package/dist/components/DataTable/DataTable.module.cjs.map +1 -1
  160. package/dist/components/DataTable/DataTable.module.js +9 -3
  161. package/dist/components/DataTable/DataTable.module.js.map +1 -1
  162. package/dist/components/DataTable/EditableCell.cjs +2 -0
  163. package/dist/components/DataTable/EditableCell.cjs.map +1 -0
  164. package/dist/components/DataTable/EditableCell.js +77 -0
  165. package/dist/components/DataTable/EditableCell.js.map +1 -0
  166. package/dist/components/DataTable/edit-labels.cjs +2 -0
  167. package/dist/components/DataTable/edit-labels.cjs.map +1 -0
  168. package/dist/components/DataTable/edit-labels.js +11 -0
  169. package/dist/components/DataTable/edit-labels.js.map +1 -0
  170. package/dist/components/DateRangePicker/DateRangePicker.module.cjs.map +1 -1
  171. package/dist/components/DateRangePicker/DateRangePicker.module.js.map +1 -1
  172. package/dist/components/MultiSelect/MultiSelect.module.cjs.map +1 -1
  173. package/dist/components/MultiSelect/MultiSelect.module.js.map +1 -1
  174. package/dist/components/NavigationRail/NavigationRail.module.cjs.map +1 -1
  175. package/dist/components/NavigationRail/NavigationRail.module.js.map +1 -1
  176. package/dist/components/SyncStatusBadge/SyncStatusBadge.cjs +1 -1
  177. package/dist/components/SyncStatusBadge/SyncStatusBadge.cjs.map +1 -1
  178. package/dist/components/SyncStatusBadge/SyncStatusBadge.js +33 -32
  179. package/dist/components/SyncStatusBadge/SyncStatusBadge.js.map +1 -1
  180. package/dist/components/Toast/ToastProvider.cjs +1 -1
  181. package/dist/components/Toast/ToastProvider.cjs.map +1 -1
  182. package/dist/components/Toast/ToastProvider.js +1 -2
  183. package/dist/components/Toast/ToastProvider.js.map +1 -1
  184. package/dist/hooks/use-announce.cjs +2 -0
  185. package/dist/hooks/use-announce.cjs.map +1 -0
  186. package/dist/hooks/use-announce.js +50 -0
  187. package/dist/hooks/use-announce.js.map +1 -0
  188. package/dist/hooks/use-media-devices.cjs +2 -0
  189. package/dist/hooks/use-media-devices.cjs.map +1 -0
  190. package/dist/hooks/use-media-devices.js +46 -0
  191. package/dist/hooks/use-media-devices.js.map +1 -0
  192. package/dist/hooks/use-media-permission.cjs +2 -0
  193. package/dist/hooks/use-media-permission.cjs.map +1 -0
  194. package/dist/hooks/use-media-permission.js +33 -0
  195. package/dist/hooks/use-media-permission.js.map +1 -0
  196. package/dist/http/resumable-upload.cjs +2 -0
  197. package/dist/http/resumable-upload.cjs.map +1 -0
  198. package/dist/http/resumable-upload.js +249 -0
  199. package/dist/http/resumable-upload.js.map +1 -0
  200. package/dist/styles.css +1 -1
  201. package/dist/tempest-react-sdk.cjs +1 -1
  202. package/dist/tempest-react-sdk.d.ts +2485 -29
  203. package/dist/tempest-react-sdk.js +268 -244
  204. package/dist/vision/core/graph.cjs +2 -0
  205. package/dist/vision/core/graph.cjs.map +1 -0
  206. package/dist/vision/core/graph.js +17 -0
  207. package/dist/vision/core/graph.js.map +1 -0
  208. package/dist/vision/core/session.cjs +1 -1
  209. package/dist/vision/core/session.cjs.map +1 -1
  210. package/dist/vision/core/session.js +19 -9
  211. package/dist/vision/core/session.js.map +1 -1
  212. package/dist/vision/index.cjs +1 -1
  213. package/dist/vision/index.cjs.map +1 -1
  214. package/dist/vision/index.js +14 -13
  215. package/dist/vision/index.js.map +1 -1
  216. package/dist/vision/tasks/classifier.cjs +1 -1
  217. package/dist/vision/tasks/classifier.cjs.map +1 -1
  218. package/dist/vision/tasks/classifier.js +33 -25
  219. package/dist/vision/tasks/classifier.js.map +1 -1
  220. package/dist/vision/tasks/detector.cjs +1 -1
  221. package/dist/vision/tasks/detector.cjs.map +1 -1
  222. package/dist/vision/tasks/detector.js +28 -20
  223. package/dist/vision/tasks/detector.js.map +1 -1
  224. package/dist/vision/tasks/segmenter.cjs +1 -1
  225. package/dist/vision/tasks/segmenter.cjs.map +1 -1
  226. package/dist/vision/tasks/segmenter.js +23 -15
  227. package/dist/vision/tasks/segmenter.js.map +1 -1
  228. package/dist/vision/use-camera-stream.cjs +1 -1
  229. package/dist/vision/use-camera-stream.cjs.map +1 -1
  230. package/dist/vision/use-camera-stream.js +40 -57
  231. package/dist/vision/use-camera-stream.js.map +1 -1
  232. package/dist/vision.cjs +1 -1
  233. package/dist/vision.d.ts +149 -8
  234. package/dist/vision.js +22 -21
  235. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-microphone.js","names":[],"sources":["../../src/audio/use-microphone.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport {\n classifyMediaError,\n isMediaCaptureSupported,\n missingCaptureApiError,\n type MediaAccessError,\n} from \"./media-access\";\n\n/** Lifecycle of a microphone stream. */\nexport type MicrophoneStatus = \"idle\" | \"requesting\" | \"ready\" | \"error\";\n\n/** Options for {@link useMicrophone}. */\nexport interface UseMicrophoneOptions {\n /**\n * Open the stream on mount instead of waiting for `start()`. Default `false`.\n *\n * Left off on purpose. Opening on mount means prompting on mount, and a\n * permission prompt the user did not provoke is the most reliable way to earn a\n * permanent \"Block\" — after which `getUserMedia` rejects without ever prompting\n * again. Wire `start()` to the button that needs the microphone.\n */\n autoStart?: boolean;\n /** Specific microphone, from `useMediaDevices().audioInputs`. */\n deviceId?: string;\n /**\n * Browser voice processing. All three default to `true`, which is what speech\n * wants; turn them off for music, where a gate chewing on a decaying note is\n * worse than the room noise it removes.\n */\n echoCancellation?: boolean;\n noiseSuppression?: boolean;\n autoGainControl?: boolean;\n /** Escape hatch: full constraints, replacing everything above. */\n constraints?: MediaStreamConstraints;\n}\n\n/** Value returned by {@link useMicrophone}. */\nexport interface UseMicrophoneResult {\n status: MicrophoneStatus;\n /** The live stream, or `null`. Feed it to {@link useAudioRecorder} or an analyser. */\n stream: MediaStream | null;\n /** Classified error, or `null` outside the `error` status. */\n error: MediaAccessError | null;\n /** Open the stream. Safe to call when already open — it is a no-op. */\n start: () => void;\n /** Release the microphone. The browser's recording indicator only clears here. */\n stop: () => void;\n}\n\n/**\n * Acquire a microphone `MediaStream`, classified errors included, and release it\n * properly.\n *\n * Releasing matters more than it looks. Every track has to be stopped by hand:\n * dropping the last reference to a `MediaStream` does **not** turn off the\n * microphone, so the browser keeps showing its recording indicator and the OS keeps\n * the device busy — which then makes the *next* `getUserMedia` fail with\n * `NotReadableError` in another tab. The hook stops tracks on `stop()`, on unmount,\n * and before re-opening.\n *\n * @param options - See {@link UseMicrophoneOptions}.\n * @returns The stream, its status, a classified error, and `start`/`stop`.\n *\n * @example\n * const mic = useMicrophone();\n * const recorder = useAudioRecorder(mic.stream);\n * <button onClick={mic.start}>Liberar microfone</button>\n */\nexport function useMicrophone(options: UseMicrophoneOptions = {}): UseMicrophoneResult {\n const {\n autoStart = false,\n deviceId,\n echoCancellation = true,\n noiseSuppression = true,\n autoGainControl = true,\n constraints,\n } = options;\n\n const [status, setStatus] = useState<MicrophoneStatus>(\"idle\");\n const [stream, setStream] = useState<MediaStream | null>(null);\n const [error, setError] = useState<MediaAccessError | null>(null);\n const streamRef = useRef<MediaStream | null>(null);\n const generation = useRef(0);\n\n const release = useCallback((): void => {\n streamRef.current?.getTracks().forEach((track) => track.stop());\n streamRef.current = null;\n }, []);\n\n const stop = useCallback((): void => {\n generation.current += 1;\n release();\n setStream(null);\n setStatus(\"idle\");\n setError(null);\n }, [release]);\n\n const start = useCallback((): void => {\n if (streamRef.current) return;\n const run = (generation.current += 1);\n setStatus(\"requesting\");\n setError(null);\n\n if (!isMediaCaptureSupported()) {\n setError(missingCaptureApiError(\"microphone\"));\n setStatus(\"error\");\n return;\n }\n\n const request: MediaStreamConstraints = constraints ?? {\n audio: {\n echoCancellation,\n noiseSuppression,\n autoGainControl,\n ...(deviceId ? { deviceId: { exact: deviceId } } : {}),\n },\n video: false,\n };\n\n void navigator.mediaDevices\n .getUserMedia(request)\n .then((next) => {\n if (run !== generation.current) {\n next.getTracks().forEach((track) => track.stop());\n return;\n }\n streamRef.current = next;\n setStream(next);\n setStatus(\"ready\");\n })\n .catch((err: unknown) => {\n if (run !== generation.current) return;\n setError(classifyMediaError(err, \"microphone\"));\n setStatus(\"error\");\n });\n }, [constraints, deviceId, echoCancellation, noiseSuppression, autoGainControl]);\n\n useEffect(() => {\n if (autoStart) start();\n // `start` is stable per constraint set; re-running on every identity change\n // would reopen the device on each render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [autoStart]);\n\n useEffect(() => release, [release]);\n\n return { status, stream, error, start, stop };\n}\n"],"mappings":";;;AAqEA,SAAgB,EAAc,IAAgC,CAAC,GAAwB;CACnF,IAAM,EACF,eAAY,IACZ,aACA,sBAAmB,IACnB,sBAAmB,IACnB,qBAAkB,IAClB,mBACA,GAEE,CAAC,GAAQ,KAAa,EAA2B,MAAM,GACvD,CAAC,GAAQ,KAAa,EAA6B,IAAI,GACvD,CAAC,GAAO,KAAY,EAAkC,IAAI,GAC1D,IAAY,EAA2B,IAAI,GAC3C,IAAa,EAAO,CAAC,GAErB,IAAU,QAAwB;EAEpC,AADA,EAAU,SAAS,UAAU,CAAC,CAAC,SAAS,MAAU,EAAM,KAAK,CAAC,GAC9D,EAAU,UAAU;CACxB,GAAG,CAAC,CAAC,GAEC,IAAO,QAAwB;EAKjC,AAJA,EAAW,WAAW,GACtB,EAAQ,GACR,EAAU,IAAI,GACd,EAAU,MAAM,GAChB,EAAS,IAAI;CACjB,GAAG,CAAC,CAAO,CAAC,GAEN,IAAQ,QAAwB;EAClC,IAAI,EAAU,SAAS;EACvB,IAAM,IAAO,EAAW,WAAW;EAInC,IAHA,EAAU,YAAY,GACtB,EAAS,IAAI,GAET,CAAC,EAAwB,GAAG;GAE5B,AADA,EAAS,EAAuB,YAAY,CAAC,GAC7C,EAAU,OAAO;GACjB;EACJ;EAEA,IAAM,IAAkC,KAAe;GACnD,OAAO;IACH;IACA;IACA;IACA,GAAI,IAAW,EAAE,UAAU,EAAE,OAAO,EAAS,EAAE,IAAI,CAAC;GACxD;GACA,OAAO;EACX;EAEA,UAAe,aACV,aAAa,CAAO,CAAC,CACrB,MAAM,MAAS;GACZ,IAAI,MAAQ,EAAW,SAAS;IAC5B,EAAK,UAAU,CAAC,CAAC,SAAS,MAAU,EAAM,KAAK,CAAC;IAChD;GACJ;GAGA,AAFA,EAAU,UAAU,GACpB,EAAU,CAAI,GACd,EAAU,OAAO;EACrB,CAAC,CAAC,CACD,OAAO,MAAiB;GACjB,MAAQ,EAAW,YACvB,EAAS,EAAmB,GAAK,YAAY,CAAC,GAC9C,EAAU,OAAO;EACrB,CAAC;CACT,GAAG;EAAC;EAAa;EAAU;EAAkB;EAAkB;CAAe,CAAC;CAW/E,OATA,QAAgB;EACZ,AAAI,KAAW,EAAM;CAIzB,GAAG,CAAC,CAAS,CAAC,GAEd,QAAgB,GAAS,CAAC,CAAO,CAAC,GAE3B;EAAE;EAAQ;EAAQ;EAAO;EAAO;CAAK;AAChD"}
@@ -0,0 +1,2 @@
1
+ function e({channels:e,sampleRate:t}){let n=Math.max(1,e.length),r=e[0]?.length??0,i=r*n*2,a=new ArrayBuffer(44+i),o=new DataView(a),s=(e,t)=>{for(let n=0;n<t.length;n+=1)o.setUint8(e+n,t.charCodeAt(n))},c=t*n*2;s(0,`RIFF`),o.setUint32(4,36+i,!0),s(8,`WAVE`),s(12,`fmt `),o.setUint32(16,16,!0),o.setUint16(20,1,!0),o.setUint16(22,n,!0),o.setUint32(24,t,!0),o.setUint32(28,c,!0),o.setUint16(32,n*2,!0),o.setUint16(34,16,!0),s(36,`data`),o.setUint32(40,i,!0);let l=44;for(let t=0;t<r;t+=1)for(let r=0;r<n;r+=1){let n=e[r]?.[t]??0,i=n<-1?-1:n>1?1:n;o.setInt16(l,i<0?i*32768:i*32767,!0),l+=2}return new Blob([a],{type:`audio/wav`})}function t(e){let t=e.length,n=new Float32Array(t);for(let r=0;r<e.numberOfChannels;r+=1){let i=e.getChannelData(r);for(let e=0;e<t;e+=1)n[e]+=i[e]}if(e.numberOfChannels>1)for(let r=0;r<t;r+=1)n[r]/=e.numberOfChannels;return n}async function n(n,r={}){let{mono:i=!1,sampleRate:a}=r,o=typeof AudioContext<`u`?AudioContext:globalThis.webkitAudioContext;if(!o)throw Error(`Web Audio is not available in this environment.`);let s=new o,c;try{c=await s.decodeAudioData(await n.arrayBuffer())}finally{s.close().catch(()=>void 0)}let l=i?1:c.numberOfChannels,u=a??c.sampleRate;if(u===c.sampleRate)return e({channels:i?[t(c)]:Array.from({length:c.numberOfChannels},(e,t)=>c.getChannelData(t)),sampleRate:c.sampleRate});let d=typeof OfflineAudioContext<`u`?OfflineAudioContext:void 0;if(!d)throw Error(`Resampling requires OfflineAudioContext.`);let f=new d(l,Math.max(1,Math.round(c.duration*u|0)||1),u),p=f.createBufferSource();p.buffer=c,p.connect(f.destination),p.start();let m=await f.startRendering();return e({channels:Array.from({length:m.numberOfChannels},(e,t)=>m.getChannelData(t)),sampleRate:m.sampleRate})}exports.blobToWav=n,exports.encodeWav=e;
2
+ //# sourceMappingURL=wav.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wav.cjs","names":[],"sources":["../../src/audio/wav.ts"],"sourcesContent":["/** Raw PCM ready to be wrapped in a RIFF container. */\nexport interface PcmAudio {\n /** One `Float32Array` per channel, samples in −1…1. */\n channels: readonly Float32Array[];\n sampleRate: number;\n}\n\n/** Options for {@link blobToWav}. */\nexport interface WavOptions {\n /**\n * Mix down to one channel. Default `false`.\n *\n * Worth turning on for speech headed to a server: a microphone recorded in stereo\n * carries two nearly identical channels and doubles the upload for nothing.\n */\n mono?: boolean;\n /**\n * Resample to this rate. Default: keep the source rate.\n *\n * 16000 is the rate most speech-to-text APIs want, and dropping 48 kHz to 16 kHz\n * removes two thirds of the bytes with no audible loss on voice.\n */\n sampleRate?: number;\n}\n\n/** Bytes per sample in the output. 16-bit PCM is what every decoder accepts. */\nconst BYTES_PER_SAMPLE = 2;\n\n/**\n * Wrap PCM in a 16-bit RIFF/WAVE container.\n *\n * The header is the 44-byte canonical form: `RIFF` size, `WAVE`, a 16-byte `fmt `\n * chunk declaring format 1 (uncompressed PCM), then `data`. Samples are interleaved\n * and clamped before scaling, because a value even slightly past ±1 wraps around when\n * truncated to 16 bits and turns a loud passage into a burst of noise.\n *\n * Implemented here rather than pulled from a package: it is a fixed header and a\n * scaling loop, roughly forty lines, and a dependency for it would put its own\n * version bounds on every consumer of this SDK to save writing them.\n *\n * @param audio - Channels and sample rate.\n * @returns A `Blob` of type `audio/wav`.\n */\nexport function encodeWav({ channels, sampleRate }: PcmAudio): Blob {\n const channelCount = Math.max(1, channels.length);\n const frameCount = channels[0]?.length ?? 0;\n const dataBytes = frameCount * channelCount * BYTES_PER_SAMPLE;\n const buffer = new ArrayBuffer(44 + dataBytes);\n const view = new DataView(buffer);\n\n const ascii = (offset: number, text: string): void => {\n for (let index = 0; index < text.length; index += 1) {\n view.setUint8(offset + index, text.charCodeAt(index));\n }\n };\n\n const byteRate = sampleRate * channelCount * BYTES_PER_SAMPLE;\n ascii(0, \"RIFF\");\n view.setUint32(4, 36 + dataBytes, true);\n ascii(8, \"WAVE\");\n ascii(12, \"fmt \");\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, channelCount, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, byteRate, true);\n view.setUint16(32, channelCount * BYTES_PER_SAMPLE, true);\n view.setUint16(34, BYTES_PER_SAMPLE * 8, true);\n ascii(36, \"data\");\n view.setUint32(40, dataBytes, true);\n\n let offset = 44;\n for (let frame = 0; frame < frameCount; frame += 1) {\n for (let channel = 0; channel < channelCount; channel += 1) {\n const sample = channels[channel]?.[frame] ?? 0;\n const clamped = sample < -1 ? -1 : sample > 1 ? 1 : sample;\n // Asymmetric scaling: 16-bit PCM runs −32768…32767.\n view.setInt16(offset, clamped < 0 ? clamped * 0x8000 : clamped * 0x7fff, true);\n offset += BYTES_PER_SAMPLE;\n }\n }\n\n return new Blob([buffer], { type: \"audio/wav\" });\n}\n\n/** Average every channel of a decoded buffer into one. */\nfunction mixToMono(buffer: AudioBuffer): Float32Array {\n const frames = buffer.length;\n const out = new Float32Array(frames);\n for (let channel = 0; channel < buffer.numberOfChannels; channel += 1) {\n const data = buffer.getChannelData(channel);\n for (let frame = 0; frame < frames; frame += 1) out[frame] += data[frame];\n }\n if (buffer.numberOfChannels > 1) {\n for (let frame = 0; frame < frames; frame += 1) out[frame] /= buffer.numberOfChannels;\n }\n return out;\n}\n\n/**\n * Convert a recording to 16-bit WAV, with no dependency.\n *\n * `MediaRecorder` cannot produce WAV — it emits Opus on Chromium and Firefox and AAC\n * on Safari — so a backend that insists on WAV has to be served either by a\n * server-side transcode or by this. It decodes through `AudioContext.decodeAudioData`,\n * which is the browser's own decoder for whatever container the recorder chose, then\n * re-encodes the PCM.\n *\n * !!! The cost is real: WAV is uncompressed, so the same voice note that is 40 KB in\n * Opus is roughly 500 KB here at 48 kHz stereo. `{ mono: true, sampleRate: 16000 }`\n * takes that to about 80 KB and is what a speech-to-text endpoint wants anyway.\n *\n * Resampling uses `OfflineAudioContext`, i.e. the browser's own resampler, rather\n * than a hand-rolled one — this is exactly the \"we want the underlying call, not a\n * wrapper\" case.\n *\n * @param blob - A recording from {@link createAudioRecorder}, or any decodable audio.\n * @param options - See {@link WavOptions}.\n * @returns A `Blob` of type `audio/wav`.\n * @throws When the environment has no Web Audio, or the blob cannot be decoded.\n */\nexport async function blobToWav(blob: Blob, options: WavOptions = {}): Promise<Blob> {\n const { mono = false, sampleRate: target } = options;\n\n const Ctor: typeof AudioContext | undefined =\n typeof AudioContext !== \"undefined\"\n ? AudioContext\n : (globalThis as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctor) throw new Error(\"Web Audio is not available in this environment.\");\n\n const context = new Ctor();\n let decoded: AudioBuffer;\n try {\n decoded = await context.decodeAudioData(await blob.arrayBuffer());\n } finally {\n void context.close().catch(() => undefined);\n }\n\n const wantedChannels = mono ? 1 : decoded.numberOfChannels;\n const wantedRate = target ?? decoded.sampleRate;\n\n if (wantedRate === decoded.sampleRate) {\n const channels = mono\n ? [mixToMono(decoded)]\n : Array.from({ length: decoded.numberOfChannels }, (_, index) =>\n decoded.getChannelData(index),\n );\n return encodeWav({ channels, sampleRate: decoded.sampleRate });\n }\n\n const Offline: typeof OfflineAudioContext | undefined =\n typeof OfflineAudioContext !== \"undefined\" ? OfflineAudioContext : undefined;\n if (!Offline) throw new Error(\"Resampling requires OfflineAudioContext.\");\n\n const frames = Math.max(1, Math.round((decoded.duration * wantedRate) | 0) || 1);\n const offline = new Offline(wantedChannels, frames, wantedRate);\n const source = offline.createBufferSource();\n source.buffer = decoded;\n source.connect(offline.destination);\n source.start();\n const rendered = await offline.startRendering();\n\n return encodeWav({\n channels: Array.from({ length: rendered.numberOfChannels }, (_, index) =>\n rendered.getChannelData(index),\n ),\n sampleRate: rendered.sampleRate,\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ function e({ channels: e, sampleRate: t }) {
2
+ let n = Math.max(1, e.length), r = e[0]?.length ?? 0, i = r * n * 2, a = new ArrayBuffer(44 + i), o = new DataView(a), s = (e, t) => {
3
+ for (let n = 0; n < t.length; n += 1) o.setUint8(e + n, t.charCodeAt(n));
4
+ }, c = t * n * 2;
5
+ s(0, "RIFF"), o.setUint32(4, 36 + i, !0), s(8, "WAVE"), s(12, "fmt "), o.setUint32(16, 16, !0), o.setUint16(20, 1, !0), o.setUint16(22, n, !0), o.setUint32(24, t, !0), o.setUint32(28, c, !0), o.setUint16(32, n * 2, !0), o.setUint16(34, 16, !0), s(36, "data"), o.setUint32(40, i, !0);
6
+ let l = 44;
7
+ for (let t = 0; t < r; t += 1) for (let r = 0; r < n; r += 1) {
8
+ let n = e[r]?.[t] ?? 0, i = n < -1 ? -1 : n > 1 ? 1 : n;
9
+ o.setInt16(l, i < 0 ? i * 32768 : i * 32767, !0), l += 2;
10
+ }
11
+ return new Blob([a], { type: "audio/wav" });
12
+ }
13
+ function t(e) {
14
+ let t = e.length, n = new Float32Array(t);
15
+ for (let r = 0; r < e.numberOfChannels; r += 1) {
16
+ let i = e.getChannelData(r);
17
+ for (let e = 0; e < t; e += 1) n[e] += i[e];
18
+ }
19
+ if (e.numberOfChannels > 1) for (let r = 0; r < t; r += 1) n[r] /= e.numberOfChannels;
20
+ return n;
21
+ }
22
+ async function n(n, r = {}) {
23
+ let { mono: i = !1, sampleRate: a } = r, o = typeof AudioContext < "u" ? AudioContext : globalThis.webkitAudioContext;
24
+ if (!o) throw Error("Web Audio is not available in this environment.");
25
+ let s = new o(), c;
26
+ try {
27
+ c = await s.decodeAudioData(await n.arrayBuffer());
28
+ } finally {
29
+ s.close().catch(() => void 0);
30
+ }
31
+ let l = i ? 1 : c.numberOfChannels, u = a ?? c.sampleRate;
32
+ if (u === c.sampleRate) return e({
33
+ channels: i ? [t(c)] : Array.from({ length: c.numberOfChannels }, (e, t) => c.getChannelData(t)),
34
+ sampleRate: c.sampleRate
35
+ });
36
+ let d = typeof OfflineAudioContext < "u" ? OfflineAudioContext : void 0;
37
+ if (!d) throw Error("Resampling requires OfflineAudioContext.");
38
+ let f = new d(l, Math.max(1, Math.round(c.duration * u | 0) || 1), u), p = f.createBufferSource();
39
+ p.buffer = c, p.connect(f.destination), p.start();
40
+ let m = await f.startRendering();
41
+ return e({
42
+ channels: Array.from({ length: m.numberOfChannels }, (e, t) => m.getChannelData(t)),
43
+ sampleRate: m.sampleRate
44
+ });
45
+ }
46
+ //#endregion
47
+ export { n as blobToWav, e as encodeWav };
48
+
49
+ //# sourceMappingURL=wav.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wav.js","names":[],"sources":["../../src/audio/wav.ts"],"sourcesContent":["/** Raw PCM ready to be wrapped in a RIFF container. */\nexport interface PcmAudio {\n /** One `Float32Array` per channel, samples in −1…1. */\n channels: readonly Float32Array[];\n sampleRate: number;\n}\n\n/** Options for {@link blobToWav}. */\nexport interface WavOptions {\n /**\n * Mix down to one channel. Default `false`.\n *\n * Worth turning on for speech headed to a server: a microphone recorded in stereo\n * carries two nearly identical channels and doubles the upload for nothing.\n */\n mono?: boolean;\n /**\n * Resample to this rate. Default: keep the source rate.\n *\n * 16000 is the rate most speech-to-text APIs want, and dropping 48 kHz to 16 kHz\n * removes two thirds of the bytes with no audible loss on voice.\n */\n sampleRate?: number;\n}\n\n/** Bytes per sample in the output. 16-bit PCM is what every decoder accepts. */\nconst BYTES_PER_SAMPLE = 2;\n\n/**\n * Wrap PCM in a 16-bit RIFF/WAVE container.\n *\n * The header is the 44-byte canonical form: `RIFF` size, `WAVE`, a 16-byte `fmt `\n * chunk declaring format 1 (uncompressed PCM), then `data`. Samples are interleaved\n * and clamped before scaling, because a value even slightly past ±1 wraps around when\n * truncated to 16 bits and turns a loud passage into a burst of noise.\n *\n * Implemented here rather than pulled from a package: it is a fixed header and a\n * scaling loop, roughly forty lines, and a dependency for it would put its own\n * version bounds on every consumer of this SDK to save writing them.\n *\n * @param audio - Channels and sample rate.\n * @returns A `Blob` of type `audio/wav`.\n */\nexport function encodeWav({ channels, sampleRate }: PcmAudio): Blob {\n const channelCount = Math.max(1, channels.length);\n const frameCount = channels[0]?.length ?? 0;\n const dataBytes = frameCount * channelCount * BYTES_PER_SAMPLE;\n const buffer = new ArrayBuffer(44 + dataBytes);\n const view = new DataView(buffer);\n\n const ascii = (offset: number, text: string): void => {\n for (let index = 0; index < text.length; index += 1) {\n view.setUint8(offset + index, text.charCodeAt(index));\n }\n };\n\n const byteRate = sampleRate * channelCount * BYTES_PER_SAMPLE;\n ascii(0, \"RIFF\");\n view.setUint32(4, 36 + dataBytes, true);\n ascii(8, \"WAVE\");\n ascii(12, \"fmt \");\n view.setUint32(16, 16, true);\n view.setUint16(20, 1, true);\n view.setUint16(22, channelCount, true);\n view.setUint32(24, sampleRate, true);\n view.setUint32(28, byteRate, true);\n view.setUint16(32, channelCount * BYTES_PER_SAMPLE, true);\n view.setUint16(34, BYTES_PER_SAMPLE * 8, true);\n ascii(36, \"data\");\n view.setUint32(40, dataBytes, true);\n\n let offset = 44;\n for (let frame = 0; frame < frameCount; frame += 1) {\n for (let channel = 0; channel < channelCount; channel += 1) {\n const sample = channels[channel]?.[frame] ?? 0;\n const clamped = sample < -1 ? -1 : sample > 1 ? 1 : sample;\n // Asymmetric scaling: 16-bit PCM runs −32768…32767.\n view.setInt16(offset, clamped < 0 ? clamped * 0x8000 : clamped * 0x7fff, true);\n offset += BYTES_PER_SAMPLE;\n }\n }\n\n return new Blob([buffer], { type: \"audio/wav\" });\n}\n\n/** Average every channel of a decoded buffer into one. */\nfunction mixToMono(buffer: AudioBuffer): Float32Array {\n const frames = buffer.length;\n const out = new Float32Array(frames);\n for (let channel = 0; channel < buffer.numberOfChannels; channel += 1) {\n const data = buffer.getChannelData(channel);\n for (let frame = 0; frame < frames; frame += 1) out[frame] += data[frame];\n }\n if (buffer.numberOfChannels > 1) {\n for (let frame = 0; frame < frames; frame += 1) out[frame] /= buffer.numberOfChannels;\n }\n return out;\n}\n\n/**\n * Convert a recording to 16-bit WAV, with no dependency.\n *\n * `MediaRecorder` cannot produce WAV — it emits Opus on Chromium and Firefox and AAC\n * on Safari — so a backend that insists on WAV has to be served either by a\n * server-side transcode or by this. It decodes through `AudioContext.decodeAudioData`,\n * which is the browser's own decoder for whatever container the recorder chose, then\n * re-encodes the PCM.\n *\n * !!! The cost is real: WAV is uncompressed, so the same voice note that is 40 KB in\n * Opus is roughly 500 KB here at 48 kHz stereo. `{ mono: true, sampleRate: 16000 }`\n * takes that to about 80 KB and is what a speech-to-text endpoint wants anyway.\n *\n * Resampling uses `OfflineAudioContext`, i.e. the browser's own resampler, rather\n * than a hand-rolled one — this is exactly the \"we want the underlying call, not a\n * wrapper\" case.\n *\n * @param blob - A recording from {@link createAudioRecorder}, or any decodable audio.\n * @param options - See {@link WavOptions}.\n * @returns A `Blob` of type `audio/wav`.\n * @throws When the environment has no Web Audio, or the blob cannot be decoded.\n */\nexport async function blobToWav(blob: Blob, options: WavOptions = {}): Promise<Blob> {\n const { mono = false, sampleRate: target } = options;\n\n const Ctor: typeof AudioContext | undefined =\n typeof AudioContext !== \"undefined\"\n ? AudioContext\n : (globalThis as { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;\n if (!Ctor) throw new Error(\"Web Audio is not available in this environment.\");\n\n const context = new Ctor();\n let decoded: AudioBuffer;\n try {\n decoded = await context.decodeAudioData(await blob.arrayBuffer());\n } finally {\n void context.close().catch(() => undefined);\n }\n\n const wantedChannels = mono ? 1 : decoded.numberOfChannels;\n const wantedRate = target ?? decoded.sampleRate;\n\n if (wantedRate === decoded.sampleRate) {\n const channels = mono\n ? [mixToMono(decoded)]\n : Array.from({ length: decoded.numberOfChannels }, (_, index) =>\n decoded.getChannelData(index),\n );\n return encodeWav({ channels, sampleRate: decoded.sampleRate });\n }\n\n const Offline: typeof OfflineAudioContext | undefined =\n typeof OfflineAudioContext !== \"undefined\" ? OfflineAudioContext : undefined;\n if (!Offline) throw new Error(\"Resampling requires OfflineAudioContext.\");\n\n const frames = Math.max(1, Math.round((decoded.duration * wantedRate) | 0) || 1);\n const offline = new Offline(wantedChannels, frames, wantedRate);\n const source = offline.createBufferSource();\n source.buffer = decoded;\n source.connect(offline.destination);\n source.start();\n const rendered = await offline.startRendering();\n\n return encodeWav({\n channels: Array.from({ length: rendered.numberOfChannels }, (_, index) =>\n rendered.getChannelData(index),\n ),\n sampleRate: rendered.sampleRate,\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ var e=class extends Error{kind;constructor(e,t,n){super(t),this.name=`PasskeyError`,this.kind=e,this.cause=n}},t=[{type:`public-key`,alg:-8},{type:`public-key`,alg:-7},{type:`public-key`,alg:-257}];function n(){return globalThis.PublicKeyCredential}function r(e){let t=e.replace(/-/g,`+`).replace(/_/g,`/`),n=t.padEnd(t.length+(4-t.length%4)%4,`=`),r=atob(n),i=new Uint8Array(r.length);for(let e=0;e<r.length;e+=1)i[e]=r.charCodeAt(e);return i}function i(e){let t=e instanceof Uint8Array?e:new Uint8Array(e),n=``;for(let e of t)n+=String.fromCharCode(e);return btoa(n).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/,``)}function a(){return typeof window<`u`&&typeof navigator<`u`&&navigator.credentials!==void 0&&typeof navigator.credentials.create==`function`&&n()!==void 0}async function o(){let e=n();if(!e?.isUserVerifyingPlatformAuthenticatorAvailable)return!1;try{return await e.isUserVerifyingPlatformAuthenticatorAvailable()}catch{return!1}}async function s(){let e=n();if(!e?.isConditionalMediationAvailable)return!1;try{return await e.isConditionalMediationAvailable()}catch{return!1}}function c(t,n){if(t instanceof e)return t;if(typeof window<`u`&&!window.isSecureContext)return new e(`insecure`,`Passkeys require a secure (HTTPS) connection.`,t);if(t instanceof DOMException)switch(t.name){case`NotAllowedError`:return new e(`cancelled`,`The passkey prompt was dismissed or timed out. The browser does not say which — it must not reveal whether a credential exists.`,t);case`InvalidStateError`:return new e(`already-registered`,`This device already has a passkey for this account. Sign in with it instead of creating another.`,t);case`NotSupportedError`:return new e(`not-supported`,`No authenticator here supports the requested algorithms. Check the server's pubKeyCredParams.`,t);case`SecurityError`:return new e(`rp-mismatch`,`The relying-party id does not match this origin. rp.id must be the page's domain or a registrable parent of it.`,t);case`AbortError`:return new e(`aborted`,`The passkey request was aborted.`,t)}return t instanceof TypeError?new e(`invalid-options`,`The ${n} options the server sent are malformed. Check that challenge, user.id and credential ids are base64url.`,t):new e(`unknown`,t instanceof Error?t.message:`Unexpected error during the passkey ${n} ceremony.`,t)}function l(e){if(e)return e.map(e=>({id:r(e.id),type:e.type,transports:e.transports}))}function u(n={}){let{rpId:u,timeoutMs:d=6e4,credentials:f}=n;function p(){if(f)return f;if(!a())throw new e(`unsupported`,typeof window<`u`&&!window.isSecureContext?`Passkeys require a secure (HTTPS) connection.`:`This browser does not support passkeys (WebAuthn).`);return navigator.credentials}async function m(n,a={}){let o=p(),s={challenge:r(n.challenge),rp:{name:n.rp.name,id:n.rp.id??u},user:{id:r(n.user.id),name:n.user.name,displayName:n.user.displayName},pubKeyCredParams:n.pubKeyCredParams??t,timeout:n.timeout??d,excludeCredentials:l(n.excludeCredentials),authenticatorSelection:n.authenticatorSelection,attestation:n.attestation,extensions:n.extensions},f;try{f=await o.create({publicKey:s,signal:a.signal})}catch(e){throw c(e,`register`)}if(!f)throw new e(`unknown`,`The authenticator returned no credential.`);let m=f,h=m.response,g=h.getPublicKey?.();return{id:m.id,rawId:i(m.rawId),type:`public-key`,authenticatorAttachment:m.authenticatorAttachment??null,response:{clientDataJSON:i(h.clientDataJSON),attestationObject:i(h.attestationObject),transports:h.getTransports?.(),publicKeyAlgorithm:h.getPublicKeyAlgorithm?.(),publicKey:g?i(g):void 0,authenticatorData:h.getAuthenticatorData?i(h.getAuthenticatorData()):void 0},clientExtensionResults:m.getClientExtensionResults()}}async function h(t,n={}){let a=p(),o={challenge:r(t.challenge),rpId:t.rpId??u,timeout:t.timeout??d,allowCredentials:l(t.allowCredentials),userVerification:t.userVerification,extensions:t.extensions},s;try{s=await a.get({publicKey:o,signal:n.signal,mediation:n.mediation})}catch(e){throw c(e,`authenticate`)}if(!s)throw new e(`unknown`,`The authenticator returned no credential.`);let f=s,m=f.response;return{id:f.id,rawId:i(f.rawId),type:`public-key`,authenticatorAttachment:f.authenticatorAttachment??null,response:{clientDataJSON:i(m.clientDataJSON),authenticatorData:i(m.authenticatorData),signature:i(m.signature),userHandle:m.userHandle?i(m.userHandle):null},clientExtensionResults:f.getClientExtensionResults()}}return{register:m,authenticate:h,isSupported:()=>f!==void 0||a(),isPlatformAuthenticatorAvailable:o,isConditionalMediationAvailable:s}}exports.DEFAULT_PUB_KEY_CRED_PARAMS=t,exports.PasskeyError=e,exports.base64UrlToBytes=r,exports.bytesToBase64Url=i,exports.classifyPasskeyError=c,exports.createPasskeyClient=u,exports.isConditionalMediationAvailable=s,exports.isPasskeySupported=a,exports.isPlatformAuthenticatorAvailable=o;
2
+ //# sourceMappingURL=passkey.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passkey.cjs","names":[],"sources":["../../src/auth/passkey.ts"],"sourcesContent":["/**\n * Classified reason a passkey ceremony did not produce a credential.\n *\n * The kinds group the raw `DOMException.name` values the way a UI has to branch\n * on them, which is *not* how the spec groups them:\n *\n * - `\"cancelled\"` covers `NotAllowedError`, which the browser raises both when\n * the user dismissed the sheet and when the ceremony timed out. They are\n * indistinguishable **by design** — telling a site \"the user has no credential\n * for you\" would leak account existence — so a UI must treat them as one thing.\n * - `\"already-registered\"` (`InvalidStateError`) is not really a failure: this\n * device already holds a credential for this user. The correct reaction is\n * \"you are already set up on this device\", never a red error.\n * - `\"rp-mismatch\"` (`SecurityError`) is the single most common integration bug:\n * `rp.id` must equal the page's domain or a registrable parent of it.\n */\nexport type PasskeyErrorKind =\n | \"unsupported\"\n | \"insecure\"\n | \"cancelled\"\n | \"already-registered\"\n | \"not-supported\"\n | \"rp-mismatch\"\n | \"invalid-options\"\n | \"aborted\"\n | \"unknown\";\n\n/** Which ceremony was running, so the message can name it. */\nexport type PasskeyCeremony = \"register\" | \"authenticate\";\n\n/**\n * A passkey failure carrying a stable {@link PasskeyErrorKind} plus an English\n * message safe to show a user.\n *\n * A class rather than the plain `{ kind, message }` object the media classifier\n * returns, because these surface by rejecting a promise: an `Error` subclass keeps\n * stack traces, `instanceof` checks and logging intact, and `kind` is what code\n * branches on.\n */\nexport class PasskeyError extends Error {\n /** Stable, branchable classification. */\n readonly kind: PasskeyErrorKind;\n\n /**\n * Build a classified passkey error.\n *\n * @param kind - The classification a UI branches on.\n * @param message - English, user-safe explanation.\n * @param cause - The original thrown value, when there was one.\n */\n constructor(kind: PasskeyErrorKind, message: string, cause?: unknown) {\n super(message);\n this.name = \"PasskeyError\";\n this.kind = kind;\n this.cause = cause;\n }\n}\n\n/**\n * Minimal subset of `navigator.credentials` the passkey client touches.\n *\n * Declared here — the `<X>Like` pattern the SDK's adapters use — for two reasons:\n * jsdom has no `navigator.credentials` at all, so tests must inject a double; and\n * `mediation: \"conditional\"` is newer than some TypeScript DOM libs, which would\n * otherwise reject the call that makes autofill work.\n */\nexport interface CredentialsContainerLike {\n /** Runs the registration ceremony. */\n create(options: {\n publicKey: PublicKeyCredentialCreationOptions;\n signal?: AbortSignal;\n }): Promise<Credential | null>;\n /** Runs the authentication ceremony. */\n get(options: {\n publicKey: PublicKeyCredentialRequestOptions;\n signal?: AbortSignal;\n mediation?: string;\n }): Promise<Credential | null>;\n}\n\n/** How the browser should surface the authentication ceremony. */\nexport type PasskeyMediation = \"optional\" | \"conditional\" | \"required\" | \"silent\";\n\n/**\n * Server-issued registration options, in the base64url JSON shape every WebAuthn\n * backend speaks (`PublicKeyCredentialCreationOptionsJSON` in the spec).\n *\n * `challenge`, `user.id` and every `excludeCredentials[].id` are **base64url**\n * strings here and `ArrayBuffer`s in the DOM API. Converting them is the plumbing\n * this client owns.\n */\nexport interface PasskeyCreationOptionsJSON {\n /** Base64url server challenge. Single-use; the server must remember it. */\n challenge: string;\n /** Relying party. `id` defaults to the client's `rpId`, then to the origin. */\n rp: { name: string; id?: string };\n /** The account. `id` is base64url of an opaque, stable user handle. */\n user: { id: string; name: string; displayName: string };\n /** Allowed COSE algorithms. Defaults to {@link DEFAULT_PUB_KEY_CRED_PARAMS}. */\n pubKeyCredParams?: { type: \"public-key\"; alg: number }[];\n /** Ceremony timeout in ms. Defaults to the client's `timeoutMs`. */\n timeout?: number;\n /** Credentials this user already has, so the authenticator refuses a duplicate. */\n excludeCredentials?: { id: string; type: \"public-key\"; transports?: string[] }[];\n /** Resident-key / user-verification / attachment requirements. */\n authenticatorSelection?: AuthenticatorSelectionCriteria;\n /** Attestation conveyance. Leave unset (`\"none\"`) unless you verify it. */\n attestation?: AttestationConveyancePreference;\n /** Client extension inputs (`credProps`, `largeBlob`, …). */\n extensions?: AuthenticationExtensionsClientInputs;\n}\n\n/** Server-issued authentication options, base64url JSON. */\nexport interface PasskeyRequestOptionsJSON {\n /** Base64url server challenge. */\n challenge: string;\n /** Relying party id. Defaults to the client's `rpId`, then to the origin. */\n rpId?: string;\n /** Ceremony timeout in ms. Defaults to the client's `timeoutMs`. */\n timeout?: number;\n /** Restrict to these credentials. **Omit it** for usernameless / autofill flows. */\n allowCredentials?: { id: string; type: \"public-key\"; transports?: string[] }[];\n /** Whether the authenticator must verify the user (biometric / PIN). */\n userVerification?: UserVerificationRequirement;\n /** Client extension inputs. */\n extensions?: AuthenticationExtensionsClientInputs;\n}\n\n/** What the client sends to the backend to finish registration. */\nexport interface PasskeyRegistrationJSON {\n /** Base64url credential id. */\n id: string;\n /** Same bytes as `id`; both are sent because servers differ on which they read. */\n rawId: string;\n type: \"public-key\";\n /** `\"platform\"` (this device) or `\"cross-platform\"` (a phone or key). */\n authenticatorAttachment: string | null;\n response: {\n /** Base64url client data — the server re-checks challenge, origin and type. */\n clientDataJSON: string;\n /** Base64url attestation object, holding the new public key. */\n attestationObject: string;\n /** Transports the authenticator advertises, when the browser exposes them. */\n transports?: string[];\n /** COSE algorithm of the new key, when the browser exposes it. */\n publicKeyAlgorithm?: number;\n /** Base64url SPKI public key, when the browser exposes it. */\n publicKey?: string;\n /** Base64url authenticator data, when the browser exposes it. */\n authenticatorData?: string;\n };\n /** Extension outputs — `credProps.rk` tells you whether it is discoverable. */\n clientExtensionResults: AuthenticationExtensionsClientOutputs;\n}\n\n/** What the client sends to the backend to finish authentication. */\nexport interface PasskeyAuthenticationJSON {\n /** Base64url credential id — the server looks up the stored public key by it. */\n id: string;\n /** Same bytes as `id`. */\n rawId: string;\n type: \"public-key\";\n /** `\"platform\"` or `\"cross-platform\"`. */\n authenticatorAttachment: string | null;\n response: {\n /** Base64url client data. */\n clientDataJSON: string;\n /** Base64url authenticator data, carrying the signature counter. */\n authenticatorData: string;\n /** Base64url signature over `authenticatorData || sha256(clientDataJSON)`. */\n signature: string;\n /** Base64url user handle — present on discoverable credentials, else null. */\n userHandle: string | null;\n };\n /** Extension outputs. */\n clientExtensionResults: AuthenticationExtensionsClientOutputs;\n}\n\n/** Per-call knobs for {@link PasskeyClient.register}. */\nexport interface PasskeyRegisterInit {\n /** Cancel the ceremony (closes the browser sheet). */\n signal?: AbortSignal;\n}\n\n/** Per-call knobs for {@link PasskeyClient.authenticate}. */\nexport interface PasskeyAuthenticateInit extends PasskeyRegisterInit {\n /**\n * `\"conditional\"` is the autofill flow: no modal, the browser offers passkeys\n * inside a field marked `autocomplete=\"webauthn\"`. Requires a `signal`, and\n * only one conditional request may be live per page.\n */\n mediation?: PasskeyMediation;\n}\n\n/** Framework-free WebAuthn client. Build one with {@link createPasskeyClient}. */\nexport interface PasskeyClient {\n /** Run the registration ceremony and return the JSON your backend verifies. */\n register(\n options: PasskeyCreationOptionsJSON,\n init?: PasskeyRegisterInit,\n ): Promise<PasskeyRegistrationJSON>;\n /** Run the authentication ceremony and return the JSON your backend verifies. */\n authenticate(\n options: PasskeyRequestOptionsJSON,\n init?: PasskeyAuthenticateInit,\n ): Promise<PasskeyAuthenticationJSON>;\n /**\n * Whether this client can run a ceremony at all — the WebAuthn API exists, or a\n * `credentials` container was injected.\n */\n isSupported(): boolean;\n /** Whether this device has a built-in authenticator (Face ID, Hello, …). */\n isPlatformAuthenticatorAvailable(): Promise<boolean>;\n /** Whether autofill-driven (`\"conditional\"`) requests are available. */\n isConditionalMediationAvailable(): Promise<boolean>;\n}\n\n/** Options for {@link createPasskeyClient}. */\nexport interface CreatePasskeyClientOptions {\n /**\n * Default relying-party id, applied when the server options omit one. Must be\n * the page's domain or a registrable parent of it (`app.acme.com` may use\n * `acme.com`, never the other way round).\n */\n rpId?: string;\n /** Default ceremony timeout in ms. Default `60_000`. */\n timeoutMs?: number;\n /** `navigator.credentials` replacement, for tests. */\n credentials?: CredentialsContainerLike;\n}\n\n/**\n * COSE algorithms offered when the server sends no `pubKeyCredParams`, in\n * preference order.\n *\n * `-8` is Ed25519, which modern authenticators prefer and which produces the\n * smallest signatures. `-7` is ES256, the one algorithm every WebAuthn\n * authenticator supports. `-257` is RS256, needed for TPM-backed Windows Hello.\n * Offering all three is what avoids a `NotSupportedError` on some device you do\n * not own; a server that cannot verify one of them should send its own list.\n */\nexport const DEFAULT_PUB_KEY_CRED_PARAMS: { type: \"public-key\"; alg: number }[] = [\n { type: \"public-key\", alg: -8 },\n { type: \"public-key\", alg: -7 },\n { type: \"public-key\", alg: -257 },\n];\n\n/** Static members of `PublicKeyCredential` that are not in every DOM lib yet. */\ninterface PublicKeyCredentialStatics {\n isUserVerifyingPlatformAuthenticatorAvailable?: () => Promise<boolean>;\n isConditionalMediationAvailable?: () => Promise<boolean>;\n}\n\n/** The browser-supplied extras on an attestation response, all optional. */\ninterface AttestationExtras {\n getTransports?: () => string[];\n getPublicKey?: () => ArrayBuffer | null;\n getPublicKeyAlgorithm?: () => number;\n getAuthenticatorData?: () => ArrayBuffer;\n}\n\nfunction publicKeyCredentialStatics(): PublicKeyCredentialStatics | undefined {\n return (globalThis as { PublicKeyCredential?: PublicKeyCredentialStatics }).PublicKeyCredential;\n}\n\n/**\n * Decode a base64url string into bytes.\n *\n * WebAuthn transports every binary field as base64url (`-`/`_`, no padding)\n * because that is what survives JSON, while the DOM API insists on\n * `ArrayBuffer`. Getting this pair wrong — usually by feeding plain base64 to\n * `atob` and losing the last byte — is the classic broken-WebAuthn bug, which is\n * why the SDK owns it instead of leaving it to each app.\n *\n * @param value - Base64url text, with or without `=` padding.\n * @returns The decoded bytes.\n */\nexport function base64UrlToBytes(value: string): Uint8Array {\n const base64 = value.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), \"=\");\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index += 1) {\n bytes[index] = binary.charCodeAt(index);\n }\n return bytes;\n}\n\n/**\n * Encode bytes as an unpadded base64url string.\n *\n * @param value - Bytes to encode, as a view or a raw buffer.\n * @returns Base64url text, safe to put in JSON and in a URL.\n */\nexport function bytesToBase64Url(value: ArrayBuffer | Uint8Array): string {\n const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/**\n * Whether this browser exposes WebAuthn at all.\n *\n * Note that \"supported\" is not \"usable\": WebAuthn also requires a secure context,\n * and a device may have no authenticator. Use\n * {@link isPlatformAuthenticatorAvailable} before offering a passkey button.\n */\nexport function isPasskeySupported(): boolean {\n return (\n typeof window !== \"undefined\" &&\n typeof navigator !== \"undefined\" &&\n navigator.credentials !== undefined &&\n typeof navigator.credentials.create === \"function\" &&\n publicKeyCredentialStatics() !== undefined\n );\n}\n\n/**\n * Whether the device has a **built-in** authenticator — Face ID, Touch ID,\n * Windows Hello, an Android screen lock.\n *\n * This is the check that decides whether \"Entrar com passkey\" may be shown at\n * all. `isPasskeySupported()` is true on a desktop with no biometrics and no\n * security key, and offering a passkey there sends the user into a sheet that can\n * only be cancelled. A `false` here does not forbid passkeys — a phone can still\n * be used over hybrid/QR — it means the flow needs a second step, so present it\n * as \"usar meu celular\", not as one tap.\n *\n * @returns `false` when the API is missing, so a caller never has to null-check.\n */\nexport async function isPlatformAuthenticatorAvailable(): Promise<boolean> {\n const statics = publicKeyCredentialStatics();\n if (!statics?.isUserVerifyingPlatformAuthenticatorAvailable) return false;\n try {\n return await statics.isUserVerifyingPlatformAuthenticatorAvailable();\n } catch {\n return false;\n }\n}\n\n/**\n * Whether autofill-driven passkeys (`mediation: \"conditional\"`) are available.\n *\n * @returns `false` when the API is missing or throws.\n */\nexport async function isConditionalMediationAvailable(): Promise<boolean> {\n const statics = publicKeyCredentialStatics();\n if (!statics?.isConditionalMediationAvailable) return false;\n try {\n return await statics.isConditionalMediationAvailable();\n } catch {\n return false;\n }\n}\n\n/**\n * Map a thrown WebAuthn failure onto a {@link PasskeyError}.\n *\n * The secure-context check runs first for the same reason it does in the media\n * classifier: over plain HTTP the whole API is absent or refuses, and reporting\n * \"not supported\" sends a developer hunting for a polyfill for something an\n * `https://` URL fixes.\n *\n * @param error - Whatever `navigator.credentials` rejected with.\n * @param ceremony - Which ceremony was running, for the message.\n * @returns The classified error, ready to throw or to show.\n */\nexport function classifyPasskeyError(error: unknown, ceremony: PasskeyCeremony): PasskeyError {\n if (error instanceof PasskeyError) return error;\n\n if (typeof window !== \"undefined\" && !window.isSecureContext) {\n return new PasskeyError(\"insecure\", \"Passkeys require a secure (HTTPS) connection.\", error);\n }\n\n if (error instanceof DOMException) {\n switch (error.name) {\n case \"NotAllowedError\":\n return new PasskeyError(\n \"cancelled\",\n \"The passkey prompt was dismissed or timed out. The browser does not say which — it must not reveal whether a credential exists.\",\n error,\n );\n case \"InvalidStateError\":\n return new PasskeyError(\n \"already-registered\",\n \"This device already has a passkey for this account. Sign in with it instead of creating another.\",\n error,\n );\n case \"NotSupportedError\":\n return new PasskeyError(\n \"not-supported\",\n \"No authenticator here supports the requested algorithms. Check the server's pubKeyCredParams.\",\n error,\n );\n case \"SecurityError\":\n return new PasskeyError(\n \"rp-mismatch\",\n \"The relying-party id does not match this origin. rp.id must be the page's domain or a registrable parent of it.\",\n error,\n );\n case \"AbortError\":\n return new PasskeyError(\"aborted\", \"The passkey request was aborted.\", error);\n }\n }\n\n if (error instanceof TypeError) {\n return new PasskeyError(\n \"invalid-options\",\n `The ${ceremony} options the server sent are malformed. Check that challenge, user.id and credential ids are base64url.`,\n error,\n );\n }\n\n return new PasskeyError(\n \"unknown\",\n error instanceof Error\n ? error.message\n : `Unexpected error during the passkey ${ceremony} ceremony.`,\n error,\n );\n}\n\nfunction toDescriptors(\n list: { id: string; type: \"public-key\"; transports?: string[] }[] | undefined,\n): PublicKeyCredentialDescriptor[] | undefined {\n if (!list) return undefined;\n return list.map((item) => ({\n id: base64UrlToBytes(item.id) as unknown as BufferSource,\n type: item.type,\n transports: item.transports as AuthenticatorTransport[] | undefined,\n }));\n}\n\n/**\n * Build a WebAuthn client: the base64url ↔ `ArrayBuffer` plumbing, the two\n * ceremonies, and one classified error type.\n *\n * ## What your backend must do\n *\n * This is the **client half only**, and a WebAuthn client that documents only its\n * own half is unusable. Four routes are yours to implement:\n *\n * 1. `POST /webauthn/register/begin` → a {@link PasskeyCreationOptionsJSON}. Mint a\n * random `challenge` (≥16 bytes), store it against the session, and list the\n * user's existing credentials in `excludeCredentials`.\n * 2. `POST /webauthn/register/finish` ← a {@link PasskeyRegistrationJSON}. Verify\n * the challenge, `origin` and `type` inside `clientDataJSON`, parse the\n * attestation object, then store the credential id, public key and signature\n * counter.\n * 3. `POST /webauthn/signin/begin` → a {@link PasskeyRequestOptionsJSON}. New\n * challenge. Omit `allowCredentials` for a usernameless or autofill flow.\n * 4. `POST /webauthn/signin/finish` ← a {@link PasskeyAuthenticationJSON}. Look the\n * credential up by `id`, verify the signature over\n * `authenticatorData || sha256(clientDataJSON)`, and reject a signature counter\n * that did not grow (a clone). Only then issue your session token.\n *\n * @param options - Defaults applied when the server options omit them.\n * @returns A client usable from anywhere — React, a plain form, a worker.\n *\n * @example\n * const passkeys = createPasskeyClient({ rpId: \"acme.com\" });\n *\n * const options = await api.post(\"/webauthn/register/begin\");\n * const credential = await passkeys.register(options);\n * await api.post(\"/webauthn/register/finish\", { body: credential });\n */\nexport function createPasskeyClient(options: CreatePasskeyClientOptions = {}): PasskeyClient {\n const { rpId, timeoutMs = 60_000, credentials } = options;\n\n function container(): CredentialsContainerLike {\n if (credentials) return credentials;\n if (!isPasskeySupported()) {\n throw new PasskeyError(\n \"unsupported\",\n typeof window !== \"undefined\" && !window.isSecureContext\n ? \"Passkeys require a secure (HTTPS) connection.\"\n : \"This browser does not support passkeys (WebAuthn).\",\n );\n }\n return navigator.credentials as unknown as CredentialsContainerLike;\n }\n\n async function register(\n json: PasskeyCreationOptionsJSON,\n init: PasskeyRegisterInit = {},\n ): Promise<PasskeyRegistrationJSON> {\n const api = container();\n const publicKey: PublicKeyCredentialCreationOptions = {\n challenge: base64UrlToBytes(json.challenge) as unknown as BufferSource,\n rp: { name: json.rp.name, id: json.rp.id ?? rpId },\n user: {\n id: base64UrlToBytes(json.user.id) as unknown as BufferSource,\n name: json.user.name,\n displayName: json.user.displayName,\n },\n pubKeyCredParams: json.pubKeyCredParams ?? DEFAULT_PUB_KEY_CRED_PARAMS,\n timeout: json.timeout ?? timeoutMs,\n excludeCredentials: toDescriptors(json.excludeCredentials),\n authenticatorSelection: json.authenticatorSelection,\n attestation: json.attestation,\n extensions: json.extensions,\n };\n\n let credential: Credential | null;\n try {\n credential = await api.create({ publicKey, signal: init.signal });\n } catch (error) {\n throw classifyPasskeyError(error, \"register\");\n }\n if (!credential) {\n throw new PasskeyError(\"unknown\", \"The authenticator returned no credential.\");\n }\n\n const typed = credential as PublicKeyCredential;\n const response = typed.response as AuthenticatorAttestationResponse & AttestationExtras;\n const publicKeyBytes = response.getPublicKey?.();\n\n return {\n id: typed.id,\n rawId: bytesToBase64Url(typed.rawId),\n type: \"public-key\",\n authenticatorAttachment: typed.authenticatorAttachment ?? null,\n response: {\n clientDataJSON: bytesToBase64Url(response.clientDataJSON),\n attestationObject: bytesToBase64Url(response.attestationObject),\n transports: response.getTransports?.(),\n publicKeyAlgorithm: response.getPublicKeyAlgorithm?.(),\n publicKey: publicKeyBytes ? bytesToBase64Url(publicKeyBytes) : undefined,\n authenticatorData: response.getAuthenticatorData\n ? bytesToBase64Url(response.getAuthenticatorData())\n : undefined,\n },\n clientExtensionResults: typed.getClientExtensionResults(),\n };\n }\n\n async function authenticate(\n json: PasskeyRequestOptionsJSON,\n init: PasskeyAuthenticateInit = {},\n ): Promise<PasskeyAuthenticationJSON> {\n const api = container();\n const publicKey: PublicKeyCredentialRequestOptions = {\n challenge: base64UrlToBytes(json.challenge) as unknown as BufferSource,\n rpId: json.rpId ?? rpId,\n timeout: json.timeout ?? timeoutMs,\n allowCredentials: toDescriptors(json.allowCredentials),\n userVerification: json.userVerification,\n extensions: json.extensions,\n };\n\n let credential: Credential | null;\n try {\n credential = await api.get({\n publicKey,\n signal: init.signal,\n mediation: init.mediation,\n });\n } catch (error) {\n throw classifyPasskeyError(error, \"authenticate\");\n }\n if (!credential) {\n throw new PasskeyError(\"unknown\", \"The authenticator returned no credential.\");\n }\n\n const typed = credential as PublicKeyCredential;\n const response = typed.response as AuthenticatorAssertionResponse;\n\n return {\n id: typed.id,\n rawId: bytesToBase64Url(typed.rawId),\n type: \"public-key\",\n authenticatorAttachment: typed.authenticatorAttachment ?? null,\n response: {\n clientDataJSON: bytesToBase64Url(response.clientDataJSON),\n authenticatorData: bytesToBase64Url(response.authenticatorData),\n signature: bytesToBase64Url(response.signature),\n userHandle: response.userHandle ? bytesToBase64Url(response.userHandle) : null,\n },\n clientExtensionResults: typed.getClientExtensionResults(),\n };\n }\n\n return {\n register,\n authenticate,\n isSupported: () => credentials !== undefined || isPasskeySupported(),\n isPlatformAuthenticatorAvailable,\n isConditionalMediationAvailable,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,171 @@
1
+ //#region src/auth/passkey.ts
2
+ var e = class extends Error {
3
+ kind;
4
+ constructor(e, t, n) {
5
+ super(t), this.name = "PasskeyError", this.kind = e, this.cause = n;
6
+ }
7
+ }, t = [
8
+ {
9
+ type: "public-key",
10
+ alg: -8
11
+ },
12
+ {
13
+ type: "public-key",
14
+ alg: -7
15
+ },
16
+ {
17
+ type: "public-key",
18
+ alg: -257
19
+ }
20
+ ];
21
+ function n() {
22
+ return globalThis.PublicKeyCredential;
23
+ }
24
+ function r(e) {
25
+ let t = e.replace(/-/g, "+").replace(/_/g, "/"), n = t.padEnd(t.length + (4 - t.length % 4) % 4, "="), r = atob(n), i = new Uint8Array(r.length);
26
+ for (let e = 0; e < r.length; e += 1) i[e] = r.charCodeAt(e);
27
+ return i;
28
+ }
29
+ function i(e) {
30
+ let t = e instanceof Uint8Array ? e : new Uint8Array(e), n = "";
31
+ for (let e of t) n += String.fromCharCode(e);
32
+ return btoa(n).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
33
+ }
34
+ function a() {
35
+ return typeof window < "u" && typeof navigator < "u" && navigator.credentials !== void 0 && typeof navigator.credentials.create == "function" && n() !== void 0;
36
+ }
37
+ async function o() {
38
+ let e = n();
39
+ if (!e?.isUserVerifyingPlatformAuthenticatorAvailable) return !1;
40
+ try {
41
+ return await e.isUserVerifyingPlatformAuthenticatorAvailable();
42
+ } catch {
43
+ return !1;
44
+ }
45
+ }
46
+ async function s() {
47
+ let e = n();
48
+ if (!e?.isConditionalMediationAvailable) return !1;
49
+ try {
50
+ return await e.isConditionalMediationAvailable();
51
+ } catch {
52
+ return !1;
53
+ }
54
+ }
55
+ function c(t, n) {
56
+ if (t instanceof e) return t;
57
+ if (typeof window < "u" && !window.isSecureContext) return new e("insecure", "Passkeys require a secure (HTTPS) connection.", t);
58
+ if (t instanceof DOMException) switch (t.name) {
59
+ case "NotAllowedError": return new e("cancelled", "The passkey prompt was dismissed or timed out. The browser does not say which — it must not reveal whether a credential exists.", t);
60
+ case "InvalidStateError": return new e("already-registered", "This device already has a passkey for this account. Sign in with it instead of creating another.", t);
61
+ case "NotSupportedError": return new e("not-supported", "No authenticator here supports the requested algorithms. Check the server's pubKeyCredParams.", t);
62
+ case "SecurityError": return new e("rp-mismatch", "The relying-party id does not match this origin. rp.id must be the page's domain or a registrable parent of it.", t);
63
+ case "AbortError": return new e("aborted", "The passkey request was aborted.", t);
64
+ }
65
+ return t instanceof TypeError ? new e("invalid-options", `The ${n} options the server sent are malformed. Check that challenge, user.id and credential ids are base64url.`, t) : new e("unknown", t instanceof Error ? t.message : `Unexpected error during the passkey ${n} ceremony.`, t);
66
+ }
67
+ function l(e) {
68
+ if (e) return e.map((e) => ({
69
+ id: r(e.id),
70
+ type: e.type,
71
+ transports: e.transports
72
+ }));
73
+ }
74
+ function u(n = {}) {
75
+ let { rpId: u, timeoutMs: d = 6e4, credentials: f } = n;
76
+ function p() {
77
+ if (f) return f;
78
+ if (!a()) throw new e("unsupported", typeof window < "u" && !window.isSecureContext ? "Passkeys require a secure (HTTPS) connection." : "This browser does not support passkeys (WebAuthn).");
79
+ return navigator.credentials;
80
+ }
81
+ async function m(n, a = {}) {
82
+ let o = p(), s = {
83
+ challenge: r(n.challenge),
84
+ rp: {
85
+ name: n.rp.name,
86
+ id: n.rp.id ?? u
87
+ },
88
+ user: {
89
+ id: r(n.user.id),
90
+ name: n.user.name,
91
+ displayName: n.user.displayName
92
+ },
93
+ pubKeyCredParams: n.pubKeyCredParams ?? t,
94
+ timeout: n.timeout ?? d,
95
+ excludeCredentials: l(n.excludeCredentials),
96
+ authenticatorSelection: n.authenticatorSelection,
97
+ attestation: n.attestation,
98
+ extensions: n.extensions
99
+ }, f;
100
+ try {
101
+ f = await o.create({
102
+ publicKey: s,
103
+ signal: a.signal
104
+ });
105
+ } catch (e) {
106
+ throw c(e, "register");
107
+ }
108
+ if (!f) throw new e("unknown", "The authenticator returned no credential.");
109
+ let m = f, h = m.response, g = h.getPublicKey?.();
110
+ return {
111
+ id: m.id,
112
+ rawId: i(m.rawId),
113
+ type: "public-key",
114
+ authenticatorAttachment: m.authenticatorAttachment ?? null,
115
+ response: {
116
+ clientDataJSON: i(h.clientDataJSON),
117
+ attestationObject: i(h.attestationObject),
118
+ transports: h.getTransports?.(),
119
+ publicKeyAlgorithm: h.getPublicKeyAlgorithm?.(),
120
+ publicKey: g ? i(g) : void 0,
121
+ authenticatorData: h.getAuthenticatorData ? i(h.getAuthenticatorData()) : void 0
122
+ },
123
+ clientExtensionResults: m.getClientExtensionResults()
124
+ };
125
+ }
126
+ async function h(t, n = {}) {
127
+ let a = p(), o = {
128
+ challenge: r(t.challenge),
129
+ rpId: t.rpId ?? u,
130
+ timeout: t.timeout ?? d,
131
+ allowCredentials: l(t.allowCredentials),
132
+ userVerification: t.userVerification,
133
+ extensions: t.extensions
134
+ }, s;
135
+ try {
136
+ s = await a.get({
137
+ publicKey: o,
138
+ signal: n.signal,
139
+ mediation: n.mediation
140
+ });
141
+ } catch (e) {
142
+ throw c(e, "authenticate");
143
+ }
144
+ if (!s) throw new e("unknown", "The authenticator returned no credential.");
145
+ let f = s, m = f.response;
146
+ return {
147
+ id: f.id,
148
+ rawId: i(f.rawId),
149
+ type: "public-key",
150
+ authenticatorAttachment: f.authenticatorAttachment ?? null,
151
+ response: {
152
+ clientDataJSON: i(m.clientDataJSON),
153
+ authenticatorData: i(m.authenticatorData),
154
+ signature: i(m.signature),
155
+ userHandle: m.userHandle ? i(m.userHandle) : null
156
+ },
157
+ clientExtensionResults: f.getClientExtensionResults()
158
+ };
159
+ }
160
+ return {
161
+ register: m,
162
+ authenticate: h,
163
+ isSupported: () => f !== void 0 || a(),
164
+ isPlatformAuthenticatorAvailable: o,
165
+ isConditionalMediationAvailable: s
166
+ };
167
+ }
168
+ //#endregion
169
+ export { t as DEFAULT_PUB_KEY_CRED_PARAMS, e as PasskeyError, r as base64UrlToBytes, i as bytesToBase64Url, c as classifyPasskeyError, u as createPasskeyClient, s as isConditionalMediationAvailable, a as isPasskeySupported, o as isPlatformAuthenticatorAvailable };
170
+
171
+ //# sourceMappingURL=passkey.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passkey.js","names":[],"sources":["../../src/auth/passkey.ts"],"sourcesContent":["/**\n * Classified reason a passkey ceremony did not produce a credential.\n *\n * The kinds group the raw `DOMException.name` values the way a UI has to branch\n * on them, which is *not* how the spec groups them:\n *\n * - `\"cancelled\"` covers `NotAllowedError`, which the browser raises both when\n * the user dismissed the sheet and when the ceremony timed out. They are\n * indistinguishable **by design** — telling a site \"the user has no credential\n * for you\" would leak account existence — so a UI must treat them as one thing.\n * - `\"already-registered\"` (`InvalidStateError`) is not really a failure: this\n * device already holds a credential for this user. The correct reaction is\n * \"you are already set up on this device\", never a red error.\n * - `\"rp-mismatch\"` (`SecurityError`) is the single most common integration bug:\n * `rp.id` must equal the page's domain or a registrable parent of it.\n */\nexport type PasskeyErrorKind =\n | \"unsupported\"\n | \"insecure\"\n | \"cancelled\"\n | \"already-registered\"\n | \"not-supported\"\n | \"rp-mismatch\"\n | \"invalid-options\"\n | \"aborted\"\n | \"unknown\";\n\n/** Which ceremony was running, so the message can name it. */\nexport type PasskeyCeremony = \"register\" | \"authenticate\";\n\n/**\n * A passkey failure carrying a stable {@link PasskeyErrorKind} plus an English\n * message safe to show a user.\n *\n * A class rather than the plain `{ kind, message }` object the media classifier\n * returns, because these surface by rejecting a promise: an `Error` subclass keeps\n * stack traces, `instanceof` checks and logging intact, and `kind` is what code\n * branches on.\n */\nexport class PasskeyError extends Error {\n /** Stable, branchable classification. */\n readonly kind: PasskeyErrorKind;\n\n /**\n * Build a classified passkey error.\n *\n * @param kind - The classification a UI branches on.\n * @param message - English, user-safe explanation.\n * @param cause - The original thrown value, when there was one.\n */\n constructor(kind: PasskeyErrorKind, message: string, cause?: unknown) {\n super(message);\n this.name = \"PasskeyError\";\n this.kind = kind;\n this.cause = cause;\n }\n}\n\n/**\n * Minimal subset of `navigator.credentials` the passkey client touches.\n *\n * Declared here — the `<X>Like` pattern the SDK's adapters use — for two reasons:\n * jsdom has no `navigator.credentials` at all, so tests must inject a double; and\n * `mediation: \"conditional\"` is newer than some TypeScript DOM libs, which would\n * otherwise reject the call that makes autofill work.\n */\nexport interface CredentialsContainerLike {\n /** Runs the registration ceremony. */\n create(options: {\n publicKey: PublicKeyCredentialCreationOptions;\n signal?: AbortSignal;\n }): Promise<Credential | null>;\n /** Runs the authentication ceremony. */\n get(options: {\n publicKey: PublicKeyCredentialRequestOptions;\n signal?: AbortSignal;\n mediation?: string;\n }): Promise<Credential | null>;\n}\n\n/** How the browser should surface the authentication ceremony. */\nexport type PasskeyMediation = \"optional\" | \"conditional\" | \"required\" | \"silent\";\n\n/**\n * Server-issued registration options, in the base64url JSON shape every WebAuthn\n * backend speaks (`PublicKeyCredentialCreationOptionsJSON` in the spec).\n *\n * `challenge`, `user.id` and every `excludeCredentials[].id` are **base64url**\n * strings here and `ArrayBuffer`s in the DOM API. Converting them is the plumbing\n * this client owns.\n */\nexport interface PasskeyCreationOptionsJSON {\n /** Base64url server challenge. Single-use; the server must remember it. */\n challenge: string;\n /** Relying party. `id` defaults to the client's `rpId`, then to the origin. */\n rp: { name: string; id?: string };\n /** The account. `id` is base64url of an opaque, stable user handle. */\n user: { id: string; name: string; displayName: string };\n /** Allowed COSE algorithms. Defaults to {@link DEFAULT_PUB_KEY_CRED_PARAMS}. */\n pubKeyCredParams?: { type: \"public-key\"; alg: number }[];\n /** Ceremony timeout in ms. Defaults to the client's `timeoutMs`. */\n timeout?: number;\n /** Credentials this user already has, so the authenticator refuses a duplicate. */\n excludeCredentials?: { id: string; type: \"public-key\"; transports?: string[] }[];\n /** Resident-key / user-verification / attachment requirements. */\n authenticatorSelection?: AuthenticatorSelectionCriteria;\n /** Attestation conveyance. Leave unset (`\"none\"`) unless you verify it. */\n attestation?: AttestationConveyancePreference;\n /** Client extension inputs (`credProps`, `largeBlob`, …). */\n extensions?: AuthenticationExtensionsClientInputs;\n}\n\n/** Server-issued authentication options, base64url JSON. */\nexport interface PasskeyRequestOptionsJSON {\n /** Base64url server challenge. */\n challenge: string;\n /** Relying party id. Defaults to the client's `rpId`, then to the origin. */\n rpId?: string;\n /** Ceremony timeout in ms. Defaults to the client's `timeoutMs`. */\n timeout?: number;\n /** Restrict to these credentials. **Omit it** for usernameless / autofill flows. */\n allowCredentials?: { id: string; type: \"public-key\"; transports?: string[] }[];\n /** Whether the authenticator must verify the user (biometric / PIN). */\n userVerification?: UserVerificationRequirement;\n /** Client extension inputs. */\n extensions?: AuthenticationExtensionsClientInputs;\n}\n\n/** What the client sends to the backend to finish registration. */\nexport interface PasskeyRegistrationJSON {\n /** Base64url credential id. */\n id: string;\n /** Same bytes as `id`; both are sent because servers differ on which they read. */\n rawId: string;\n type: \"public-key\";\n /** `\"platform\"` (this device) or `\"cross-platform\"` (a phone or key). */\n authenticatorAttachment: string | null;\n response: {\n /** Base64url client data — the server re-checks challenge, origin and type. */\n clientDataJSON: string;\n /** Base64url attestation object, holding the new public key. */\n attestationObject: string;\n /** Transports the authenticator advertises, when the browser exposes them. */\n transports?: string[];\n /** COSE algorithm of the new key, when the browser exposes it. */\n publicKeyAlgorithm?: number;\n /** Base64url SPKI public key, when the browser exposes it. */\n publicKey?: string;\n /** Base64url authenticator data, when the browser exposes it. */\n authenticatorData?: string;\n };\n /** Extension outputs — `credProps.rk` tells you whether it is discoverable. */\n clientExtensionResults: AuthenticationExtensionsClientOutputs;\n}\n\n/** What the client sends to the backend to finish authentication. */\nexport interface PasskeyAuthenticationJSON {\n /** Base64url credential id — the server looks up the stored public key by it. */\n id: string;\n /** Same bytes as `id`. */\n rawId: string;\n type: \"public-key\";\n /** `\"platform\"` or `\"cross-platform\"`. */\n authenticatorAttachment: string | null;\n response: {\n /** Base64url client data. */\n clientDataJSON: string;\n /** Base64url authenticator data, carrying the signature counter. */\n authenticatorData: string;\n /** Base64url signature over `authenticatorData || sha256(clientDataJSON)`. */\n signature: string;\n /** Base64url user handle — present on discoverable credentials, else null. */\n userHandle: string | null;\n };\n /** Extension outputs. */\n clientExtensionResults: AuthenticationExtensionsClientOutputs;\n}\n\n/** Per-call knobs for {@link PasskeyClient.register}. */\nexport interface PasskeyRegisterInit {\n /** Cancel the ceremony (closes the browser sheet). */\n signal?: AbortSignal;\n}\n\n/** Per-call knobs for {@link PasskeyClient.authenticate}. */\nexport interface PasskeyAuthenticateInit extends PasskeyRegisterInit {\n /**\n * `\"conditional\"` is the autofill flow: no modal, the browser offers passkeys\n * inside a field marked `autocomplete=\"webauthn\"`. Requires a `signal`, and\n * only one conditional request may be live per page.\n */\n mediation?: PasskeyMediation;\n}\n\n/** Framework-free WebAuthn client. Build one with {@link createPasskeyClient}. */\nexport interface PasskeyClient {\n /** Run the registration ceremony and return the JSON your backend verifies. */\n register(\n options: PasskeyCreationOptionsJSON,\n init?: PasskeyRegisterInit,\n ): Promise<PasskeyRegistrationJSON>;\n /** Run the authentication ceremony and return the JSON your backend verifies. */\n authenticate(\n options: PasskeyRequestOptionsJSON,\n init?: PasskeyAuthenticateInit,\n ): Promise<PasskeyAuthenticationJSON>;\n /**\n * Whether this client can run a ceremony at all — the WebAuthn API exists, or a\n * `credentials` container was injected.\n */\n isSupported(): boolean;\n /** Whether this device has a built-in authenticator (Face ID, Hello, …). */\n isPlatformAuthenticatorAvailable(): Promise<boolean>;\n /** Whether autofill-driven (`\"conditional\"`) requests are available. */\n isConditionalMediationAvailable(): Promise<boolean>;\n}\n\n/** Options for {@link createPasskeyClient}. */\nexport interface CreatePasskeyClientOptions {\n /**\n * Default relying-party id, applied when the server options omit one. Must be\n * the page's domain or a registrable parent of it (`app.acme.com` may use\n * `acme.com`, never the other way round).\n */\n rpId?: string;\n /** Default ceremony timeout in ms. Default `60_000`. */\n timeoutMs?: number;\n /** `navigator.credentials` replacement, for tests. */\n credentials?: CredentialsContainerLike;\n}\n\n/**\n * COSE algorithms offered when the server sends no `pubKeyCredParams`, in\n * preference order.\n *\n * `-8` is Ed25519, which modern authenticators prefer and which produces the\n * smallest signatures. `-7` is ES256, the one algorithm every WebAuthn\n * authenticator supports. `-257` is RS256, needed for TPM-backed Windows Hello.\n * Offering all three is what avoids a `NotSupportedError` on some device you do\n * not own; a server that cannot verify one of them should send its own list.\n */\nexport const DEFAULT_PUB_KEY_CRED_PARAMS: { type: \"public-key\"; alg: number }[] = [\n { type: \"public-key\", alg: -8 },\n { type: \"public-key\", alg: -7 },\n { type: \"public-key\", alg: -257 },\n];\n\n/** Static members of `PublicKeyCredential` that are not in every DOM lib yet. */\ninterface PublicKeyCredentialStatics {\n isUserVerifyingPlatformAuthenticatorAvailable?: () => Promise<boolean>;\n isConditionalMediationAvailable?: () => Promise<boolean>;\n}\n\n/** The browser-supplied extras on an attestation response, all optional. */\ninterface AttestationExtras {\n getTransports?: () => string[];\n getPublicKey?: () => ArrayBuffer | null;\n getPublicKeyAlgorithm?: () => number;\n getAuthenticatorData?: () => ArrayBuffer;\n}\n\nfunction publicKeyCredentialStatics(): PublicKeyCredentialStatics | undefined {\n return (globalThis as { PublicKeyCredential?: PublicKeyCredentialStatics }).PublicKeyCredential;\n}\n\n/**\n * Decode a base64url string into bytes.\n *\n * WebAuthn transports every binary field as base64url (`-`/`_`, no padding)\n * because that is what survives JSON, while the DOM API insists on\n * `ArrayBuffer`. Getting this pair wrong — usually by feeding plain base64 to\n * `atob` and losing the last byte — is the classic broken-WebAuthn bug, which is\n * why the SDK owns it instead of leaving it to each app.\n *\n * @param value - Base64url text, with or without `=` padding.\n * @returns The decoded bytes.\n */\nexport function base64UrlToBytes(value: string): Uint8Array {\n const base64 = value.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), \"=\");\n const binary = atob(padded);\n const bytes = new Uint8Array(binary.length);\n for (let index = 0; index < binary.length; index += 1) {\n bytes[index] = binary.charCodeAt(index);\n }\n return bytes;\n}\n\n/**\n * Encode bytes as an unpadded base64url string.\n *\n * @param value - Bytes to encode, as a view or a raw buffer.\n * @returns Base64url text, safe to put in JSON and in a URL.\n */\nexport function bytesToBase64Url(value: ArrayBuffer | Uint8Array): string {\n const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/**\n * Whether this browser exposes WebAuthn at all.\n *\n * Note that \"supported\" is not \"usable\": WebAuthn also requires a secure context,\n * and a device may have no authenticator. Use\n * {@link isPlatformAuthenticatorAvailable} before offering a passkey button.\n */\nexport function isPasskeySupported(): boolean {\n return (\n typeof window !== \"undefined\" &&\n typeof navigator !== \"undefined\" &&\n navigator.credentials !== undefined &&\n typeof navigator.credentials.create === \"function\" &&\n publicKeyCredentialStatics() !== undefined\n );\n}\n\n/**\n * Whether the device has a **built-in** authenticator — Face ID, Touch ID,\n * Windows Hello, an Android screen lock.\n *\n * This is the check that decides whether \"Entrar com passkey\" may be shown at\n * all. `isPasskeySupported()` is true on a desktop with no biometrics and no\n * security key, and offering a passkey there sends the user into a sheet that can\n * only be cancelled. A `false` here does not forbid passkeys — a phone can still\n * be used over hybrid/QR — it means the flow needs a second step, so present it\n * as \"usar meu celular\", not as one tap.\n *\n * @returns `false` when the API is missing, so a caller never has to null-check.\n */\nexport async function isPlatformAuthenticatorAvailable(): Promise<boolean> {\n const statics = publicKeyCredentialStatics();\n if (!statics?.isUserVerifyingPlatformAuthenticatorAvailable) return false;\n try {\n return await statics.isUserVerifyingPlatformAuthenticatorAvailable();\n } catch {\n return false;\n }\n}\n\n/**\n * Whether autofill-driven passkeys (`mediation: \"conditional\"`) are available.\n *\n * @returns `false` when the API is missing or throws.\n */\nexport async function isConditionalMediationAvailable(): Promise<boolean> {\n const statics = publicKeyCredentialStatics();\n if (!statics?.isConditionalMediationAvailable) return false;\n try {\n return await statics.isConditionalMediationAvailable();\n } catch {\n return false;\n }\n}\n\n/**\n * Map a thrown WebAuthn failure onto a {@link PasskeyError}.\n *\n * The secure-context check runs first for the same reason it does in the media\n * classifier: over plain HTTP the whole API is absent or refuses, and reporting\n * \"not supported\" sends a developer hunting for a polyfill for something an\n * `https://` URL fixes.\n *\n * @param error - Whatever `navigator.credentials` rejected with.\n * @param ceremony - Which ceremony was running, for the message.\n * @returns The classified error, ready to throw or to show.\n */\nexport function classifyPasskeyError(error: unknown, ceremony: PasskeyCeremony): PasskeyError {\n if (error instanceof PasskeyError) return error;\n\n if (typeof window !== \"undefined\" && !window.isSecureContext) {\n return new PasskeyError(\"insecure\", \"Passkeys require a secure (HTTPS) connection.\", error);\n }\n\n if (error instanceof DOMException) {\n switch (error.name) {\n case \"NotAllowedError\":\n return new PasskeyError(\n \"cancelled\",\n \"The passkey prompt was dismissed or timed out. The browser does not say which — it must not reveal whether a credential exists.\",\n error,\n );\n case \"InvalidStateError\":\n return new PasskeyError(\n \"already-registered\",\n \"This device already has a passkey for this account. Sign in with it instead of creating another.\",\n error,\n );\n case \"NotSupportedError\":\n return new PasskeyError(\n \"not-supported\",\n \"No authenticator here supports the requested algorithms. Check the server's pubKeyCredParams.\",\n error,\n );\n case \"SecurityError\":\n return new PasskeyError(\n \"rp-mismatch\",\n \"The relying-party id does not match this origin. rp.id must be the page's domain or a registrable parent of it.\",\n error,\n );\n case \"AbortError\":\n return new PasskeyError(\"aborted\", \"The passkey request was aborted.\", error);\n }\n }\n\n if (error instanceof TypeError) {\n return new PasskeyError(\n \"invalid-options\",\n `The ${ceremony} options the server sent are malformed. Check that challenge, user.id and credential ids are base64url.`,\n error,\n );\n }\n\n return new PasskeyError(\n \"unknown\",\n error instanceof Error\n ? error.message\n : `Unexpected error during the passkey ${ceremony} ceremony.`,\n error,\n );\n}\n\nfunction toDescriptors(\n list: { id: string; type: \"public-key\"; transports?: string[] }[] | undefined,\n): PublicKeyCredentialDescriptor[] | undefined {\n if (!list) return undefined;\n return list.map((item) => ({\n id: base64UrlToBytes(item.id) as unknown as BufferSource,\n type: item.type,\n transports: item.transports as AuthenticatorTransport[] | undefined,\n }));\n}\n\n/**\n * Build a WebAuthn client: the base64url ↔ `ArrayBuffer` plumbing, the two\n * ceremonies, and one classified error type.\n *\n * ## What your backend must do\n *\n * This is the **client half only**, and a WebAuthn client that documents only its\n * own half is unusable. Four routes are yours to implement:\n *\n * 1. `POST /webauthn/register/begin` → a {@link PasskeyCreationOptionsJSON}. Mint a\n * random `challenge` (≥16 bytes), store it against the session, and list the\n * user's existing credentials in `excludeCredentials`.\n * 2. `POST /webauthn/register/finish` ← a {@link PasskeyRegistrationJSON}. Verify\n * the challenge, `origin` and `type` inside `clientDataJSON`, parse the\n * attestation object, then store the credential id, public key and signature\n * counter.\n * 3. `POST /webauthn/signin/begin` → a {@link PasskeyRequestOptionsJSON}. New\n * challenge. Omit `allowCredentials` for a usernameless or autofill flow.\n * 4. `POST /webauthn/signin/finish` ← a {@link PasskeyAuthenticationJSON}. Look the\n * credential up by `id`, verify the signature over\n * `authenticatorData || sha256(clientDataJSON)`, and reject a signature counter\n * that did not grow (a clone). Only then issue your session token.\n *\n * @param options - Defaults applied when the server options omit them.\n * @returns A client usable from anywhere — React, a plain form, a worker.\n *\n * @example\n * const passkeys = createPasskeyClient({ rpId: \"acme.com\" });\n *\n * const options = await api.post(\"/webauthn/register/begin\");\n * const credential = await passkeys.register(options);\n * await api.post(\"/webauthn/register/finish\", { body: credential });\n */\nexport function createPasskeyClient(options: CreatePasskeyClientOptions = {}): PasskeyClient {\n const { rpId, timeoutMs = 60_000, credentials } = options;\n\n function container(): CredentialsContainerLike {\n if (credentials) return credentials;\n if (!isPasskeySupported()) {\n throw new PasskeyError(\n \"unsupported\",\n typeof window !== \"undefined\" && !window.isSecureContext\n ? \"Passkeys require a secure (HTTPS) connection.\"\n : \"This browser does not support passkeys (WebAuthn).\",\n );\n }\n return navigator.credentials as unknown as CredentialsContainerLike;\n }\n\n async function register(\n json: PasskeyCreationOptionsJSON,\n init: PasskeyRegisterInit = {},\n ): Promise<PasskeyRegistrationJSON> {\n const api = container();\n const publicKey: PublicKeyCredentialCreationOptions = {\n challenge: base64UrlToBytes(json.challenge) as unknown as BufferSource,\n rp: { name: json.rp.name, id: json.rp.id ?? rpId },\n user: {\n id: base64UrlToBytes(json.user.id) as unknown as BufferSource,\n name: json.user.name,\n displayName: json.user.displayName,\n },\n pubKeyCredParams: json.pubKeyCredParams ?? DEFAULT_PUB_KEY_CRED_PARAMS,\n timeout: json.timeout ?? timeoutMs,\n excludeCredentials: toDescriptors(json.excludeCredentials),\n authenticatorSelection: json.authenticatorSelection,\n attestation: json.attestation,\n extensions: json.extensions,\n };\n\n let credential: Credential | null;\n try {\n credential = await api.create({ publicKey, signal: init.signal });\n } catch (error) {\n throw classifyPasskeyError(error, \"register\");\n }\n if (!credential) {\n throw new PasskeyError(\"unknown\", \"The authenticator returned no credential.\");\n }\n\n const typed = credential as PublicKeyCredential;\n const response = typed.response as AuthenticatorAttestationResponse & AttestationExtras;\n const publicKeyBytes = response.getPublicKey?.();\n\n return {\n id: typed.id,\n rawId: bytesToBase64Url(typed.rawId),\n type: \"public-key\",\n authenticatorAttachment: typed.authenticatorAttachment ?? null,\n response: {\n clientDataJSON: bytesToBase64Url(response.clientDataJSON),\n attestationObject: bytesToBase64Url(response.attestationObject),\n transports: response.getTransports?.(),\n publicKeyAlgorithm: response.getPublicKeyAlgorithm?.(),\n publicKey: publicKeyBytes ? bytesToBase64Url(publicKeyBytes) : undefined,\n authenticatorData: response.getAuthenticatorData\n ? bytesToBase64Url(response.getAuthenticatorData())\n : undefined,\n },\n clientExtensionResults: typed.getClientExtensionResults(),\n };\n }\n\n async function authenticate(\n json: PasskeyRequestOptionsJSON,\n init: PasskeyAuthenticateInit = {},\n ): Promise<PasskeyAuthenticationJSON> {\n const api = container();\n const publicKey: PublicKeyCredentialRequestOptions = {\n challenge: base64UrlToBytes(json.challenge) as unknown as BufferSource,\n rpId: json.rpId ?? rpId,\n timeout: json.timeout ?? timeoutMs,\n allowCredentials: toDescriptors(json.allowCredentials),\n userVerification: json.userVerification,\n extensions: json.extensions,\n };\n\n let credential: Credential | null;\n try {\n credential = await api.get({\n publicKey,\n signal: init.signal,\n mediation: init.mediation,\n });\n } catch (error) {\n throw classifyPasskeyError(error, \"authenticate\");\n }\n if (!credential) {\n throw new PasskeyError(\"unknown\", \"The authenticator returned no credential.\");\n }\n\n const typed = credential as PublicKeyCredential;\n const response = typed.response as AuthenticatorAssertionResponse;\n\n return {\n id: typed.id,\n rawId: bytesToBase64Url(typed.rawId),\n type: \"public-key\",\n authenticatorAttachment: typed.authenticatorAttachment ?? null,\n response: {\n clientDataJSON: bytesToBase64Url(response.clientDataJSON),\n authenticatorData: bytesToBase64Url(response.authenticatorData),\n signature: bytesToBase64Url(response.signature),\n userHandle: response.userHandle ? bytesToBase64Url(response.userHandle) : null,\n },\n clientExtensionResults: typed.getClientExtensionResults(),\n };\n }\n\n return {\n register,\n authenticate,\n isSupported: () => credentials !== undefined || isPasskeySupported(),\n isPlatformAuthenticatorAvailable,\n isConditionalMediationAvailable,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../hooks/use-stable-callback.cjs"),t=require("./passkey.cjs");let n=require("react");var r;function i(){return r??=t.createPasskeyClient(),r}function a(e){let t=(0,n.useMemo)(()=>e??i(),[e]),[r]=(0,n.useState)(()=>t.isSupported()),[a,o]=(0,n.useState)(null),[s,c]=(0,n.useState)(null);return(0,n.useEffect)(()=>{let e=!1;return t.isPlatformAuthenticatorAvailable().then(t=>{e||o(t)}),t.isConditionalMediationAvailable().then(t=>{e||c(t)}),()=>{e=!0}},[t]),{supported:r,platformAvailable:a,conditionalAvailable:s}}function o(r){let o=(0,n.useMemo)(()=>r.client??i(),[r.client]),s=a(o),[c,l]=(0,n.useState)(`idle`),[u,d]=(0,n.useState)(null),[f,p]=(0,n.useState)(null),m=(0,n.useRef)(null),h=e.useStableCallback(r.getOptions),g=e.useStableCallback(r.verify),_=e.useStableCallback(e=>r.onSuccess?.(e)),v=e.useStableCallback(e=>r.onError?.(e)),y=(0,n.useCallback)(async()=>{l(`prompting`),d(null);let e=new AbortController;m.current=e;try{let t=await h(),n=await o.register(t,{signal:e.signal});l(`verifying`);let r=await g(n);return p(r),l(`success`),_(r),r}catch(e){let n=t.classifyPasskeyError(e,`register`);return d(n),l(`error`),v(n),null}finally{m.current=null}},[o,h,g,_,v]),b=(0,n.useCallback)(()=>{m.current?.abort()},[]),x=(0,n.useCallback)(()=>{l(`idle`),d(null),p(null)},[]);return{...s,register:y,status:c,error:u,data:f,cancel:b,reset:x}}function s(r){let o=(0,n.useMemo)(()=>r.client??i(),[r.client]),s=a(o),[c,l]=(0,n.useState)(`idle`),[u,d]=(0,n.useState)(null),[f,p]=(0,n.useState)(null),[m,h]=(0,n.useState)(!1),g=(0,n.useRef)(null),_=r.conditional??!1,v=e.useStableCallback(r.getOptions),y=e.useStableCallback(r.verify),b=e.useStableCallback(e=>r.onSuccess?.(e)),x=e.useStableCallback(e=>r.onError?.(e)),S=(0,n.useCallback)(async(e,n=new AbortController)=>{g.current=n,e===`optional`&&(l(`prompting`),d(null));try{let t=await v(),r=await o.authenticate(t,{mediation:e,signal:n.signal});l(`verifying`);let i=await y(r);return p(i),l(`success`),b(i),i}catch(n){let r=t.classifyPasskeyError(n,`authenticate`);return r.kind===`aborted`&&e===`conditional`?null:(d(r),l(`error`),x(r),null)}finally{g.current=null}},[o,v,y,b,x]),C=(0,n.useCallback)(()=>S(`optional`),[S]);(0,n.useEffect)(()=>{if(!_)return;let e=!1,t=null;async function n(){await o.isConditionalMediationAvailable()&&(e||(h(!0),t=new AbortController,await S(`conditional`,t),e||h(!1)))}return n(),()=>{e=!0,t?.abort()}},[_,o,S]);let w=(0,n.useCallback)(()=>{g.current?.abort()},[]),T=(0,n.useCallback)(()=>{l(`idle`),d(null),p(null)},[]);return{...s,signIn:C,status:c,error:u,data:f,conditionalPending:m,cancel:w,reset:T}}exports.usePasskeyCapabilities=a,exports.usePasskeyRegistration=o,exports.usePasskeySignIn=s;
2
+ //# sourceMappingURL=use-passkey.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-passkey.cjs","names":[],"sources":["../../src/auth/use-passkey.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\n\nimport { useStableCallback } from \"../hooks/use-stable-callback\";\nimport {\n classifyPasskeyError,\n createPasskeyClient,\n type PasskeyAuthenticationJSON,\n type PasskeyClient,\n type PasskeyCreationOptionsJSON,\n type PasskeyError,\n type PasskeyRegistrationJSON,\n type PasskeyRequestOptionsJSON,\n} from \"./passkey\";\n\n/**\n * Where a passkey ceremony is.\n *\n * `\"prompting\"` and `\"verifying\"` are split because they need different copy:\n * while prompting, the browser sheet is up and the user must touch a sensor;\n * while verifying, your backend is checking the signature and the user should\n * just wait.\n */\nexport type PasskeyStatus = \"idle\" | \"prompting\" | \"verifying\" | \"success\" | \"error\";\n\n/** Capability probes shared by both passkey hooks. */\nexport interface PasskeyCapabilities {\n /** WebAuthn exists in this browser. Known synchronously. */\n supported: boolean;\n /**\n * This device has a built-in authenticator. `null` until the async probe\n * settles — render nothing passkey-related while it is `null`.\n */\n platformAvailable: boolean | null;\n /** Autofill-driven sign-in is available. `null` until the probe settles. */\n conditionalAvailable: boolean | null;\n}\n\nlet sharedClient: PasskeyClient | undefined;\n\n/**\n * Lazily built default client, shared by every hook that was not given one.\n *\n * Lazy rather than a module-level constant so that importing the hooks never runs\n * a call the bundler cannot prove side-effect-free — that alone would pin the\n * module into every consumer's bundle.\n */\nfunction defaultClient(): PasskeyClient {\n sharedClient ??= createPasskeyClient();\n return sharedClient;\n}\n\n/**\n * Probe what this device can do, once per mount.\n *\n * @param client - Client whose probes to call.\n * @returns The three capability answers, two of them async.\n */\nexport function usePasskeyCapabilities(client?: PasskeyClient): PasskeyCapabilities {\n const resolved = useMemo(() => client ?? defaultClient(), [client]);\n const [supported] = useState<boolean>(() => resolved.isSupported());\n const [platformAvailable, setPlatformAvailable] = useState<boolean | null>(null);\n const [conditionalAvailable, setConditionalAvailable] = useState<boolean | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n void resolved.isPlatformAuthenticatorAvailable().then((value) => {\n if (!cancelled) setPlatformAvailable(value);\n });\n void resolved.isConditionalMediationAvailable().then((value) => {\n if (!cancelled) setConditionalAvailable(value);\n });\n return () => {\n cancelled = true;\n };\n }, [resolved]);\n\n return { supported, platformAvailable, conditionalAvailable };\n}\n\n/** Options for {@link usePasskeyRegistration}. */\nexport interface UsePasskeyRegistrationOptions<TResult> {\n /** Fetch server options — your `POST /webauthn/register/begin`. */\n getOptions: () => Promise<PasskeyCreationOptionsJSON>;\n /** Send the credential to be verified — your `POST /webauthn/register/finish`. */\n verify: (credential: PasskeyRegistrationJSON) => Promise<TResult>;\n /** Client to use. Defaults to a shared `createPasskeyClient()`. */\n client?: PasskeyClient;\n /** Called with whatever `verify` resolved to. */\n onSuccess?: (result: TResult) => void;\n /** Called with the classified failure. */\n onError?: (error: PasskeyError) => void;\n}\n\n/** Value returned by {@link usePasskeyRegistration}. */\nexport interface UsePasskeyRegistrationResult<TResult> extends PasskeyCapabilities {\n /** Run the ceremony. Resolves `null` on failure — read `error` for the reason. */\n register: () => Promise<TResult | null>;\n /** Current phase. */\n status: PasskeyStatus;\n /** Last failure, cleared when a new attempt starts. */\n error: PasskeyError | null;\n /** Whatever `verify` resolved to, on success. */\n data: TResult | null;\n /** Close the browser sheet from your own UI. */\n cancel: () => void;\n /** Back to `\"idle\"`, clearing `error` and `data`. */\n reset: () => void;\n}\n\n/**\n * Register a passkey: fetch options, run `navigator.credentials.create`, hand the\n * credential to your backend.\n *\n * `register()` **resolves** rather than rejects on failure, because every failure\n * here is already state the UI has to render (`error.kind`, `status`). Forcing a\n * `try/catch` around a button handler that also has to set state would only\n * duplicate what the hook holds.\n *\n * @param options - Transport callbacks plus optional client and listeners.\n * @returns Ceremony state, the `register` action, and the capability probes.\n *\n * @example\n * const passkey = usePasskeyRegistration({\n * getOptions: () => api.post(\"/webauthn/register/begin\"),\n * verify: (credential) => api.post(\"/webauthn/register/finish\", { body: credential }),\n * });\n *\n * if (passkey.platformAvailable === false) return null;\n * return (\n * <button onClick={() => void passkey.register()} disabled={passkey.status === \"prompting\"}>\n * Criar passkey\n * </button>\n * );\n */\nexport function usePasskeyRegistration<TResult = unknown>(\n options: UsePasskeyRegistrationOptions<TResult>,\n): UsePasskeyRegistrationResult<TResult> {\n const client = useMemo(() => options.client ?? defaultClient(), [options.client]);\n const capabilities = usePasskeyCapabilities(client);\n const [status, setStatus] = useState<PasskeyStatus>(\"idle\");\n const [error, setError] = useState<PasskeyError | null>(null);\n const [data, setData] = useState<TResult | null>(null);\n const controller = useRef<AbortController | null>(null);\n\n const getOptions = useStableCallback(options.getOptions);\n const verify = useStableCallback(options.verify);\n const onSuccess = useStableCallback((result: TResult) => options.onSuccess?.(result));\n const onError = useStableCallback((failure: PasskeyError) => options.onError?.(failure));\n\n const register = useCallback(async (): Promise<TResult | null> => {\n setStatus(\"prompting\");\n setError(null);\n const abort = new AbortController();\n controller.current = abort;\n try {\n const serverOptions = await getOptions();\n const credential = await client.register(serverOptions, { signal: abort.signal });\n setStatus(\"verifying\");\n const result = await verify(credential);\n setData(result);\n setStatus(\"success\");\n onSuccess(result);\n return result;\n } catch (thrown) {\n const failure = classifyPasskeyError(thrown, \"register\");\n setError(failure);\n setStatus(\"error\");\n onError(failure);\n return null;\n } finally {\n controller.current = null;\n }\n }, [client, getOptions, verify, onSuccess, onError]);\n\n const cancel = useCallback((): void => {\n controller.current?.abort();\n }, []);\n\n const reset = useCallback((): void => {\n setStatus(\"idle\");\n setError(null);\n setData(null);\n }, []);\n\n return { ...capabilities, register, status, error, data, cancel, reset };\n}\n\n/** Options for {@link usePasskeySignIn}. */\nexport interface UsePasskeySignInOptions<TResult> {\n /** Fetch server options — your `POST /webauthn/signin/begin`. */\n getOptions: () => Promise<PasskeyRequestOptionsJSON>;\n /** Send the assertion to be verified — your `POST /webauthn/signin/finish`. */\n verify: (assertion: PasskeyAuthenticationJSON) => Promise<TResult>;\n /** Client to use. Defaults to a shared `createPasskeyClient()`. */\n client?: PasskeyClient;\n /**\n * Arm the autofill flow on mount (`mediation: \"conditional\"`).\n *\n * The request stays pending, invisibly, until the user picks a passkey from\n * the browser's autofill list — so the field it should appear on needs\n * `autocomplete=\"username webauthn\"`. Only one conditional request may be live\n * per page, and it is aborted on unmount.\n */\n conditional?: boolean;\n /** Called with whatever `verify` resolved to. */\n onSuccess?: (result: TResult) => void;\n /** Called with the classified failure. */\n onError?: (error: PasskeyError) => void;\n}\n\n/** Value returned by {@link usePasskeySignIn}. */\nexport interface UsePasskeySignInResult<TResult> extends PasskeyCapabilities {\n /** Run the modal ceremony. Resolves `null` on failure. */\n signIn: () => Promise<TResult | null>;\n /** Current phase. */\n status: PasskeyStatus;\n /** Last failure, cleared when a new attempt starts. */\n error: PasskeyError | null;\n /** Whatever `verify` resolved to, on success. */\n data: TResult | null;\n /** True while an armed conditional (autofill) request is waiting. */\n conditionalPending: boolean;\n /** Close the browser sheet from your own UI. */\n cancel: () => void;\n /** Back to `\"idle\"`, clearing `error` and `data`. */\n reset: () => void;\n}\n\n/**\n * Sign in with a passkey, with or without the autofill flow.\n *\n * Two ways in, and a good login page wires both. `signIn()` is the explicit\n * button. `conditional: true` arms the pleasant one: the browser lists the user's\n * passkeys inside the username field itself, so signing in is one tap and no\n * password was ever typed. The field must carry\n * `autocomplete=\"username webauthn\"` or the list never appears — the hook cannot\n * do that part for you.\n *\n * A conditional request that is aborted (unmount, or the user typing a password\n * instead) is **not** surfaced as an error: nothing failed, the user chose\n * another door.\n *\n * @param options - Transport callbacks, `conditional`, optional client and listeners.\n * @returns Ceremony state, the `signIn` action, and the capability probes.\n *\n * @example\n * const passkey = usePasskeySignIn({\n * conditional: true,\n * getOptions: () => api.post(\"/webauthn/signin/begin\"),\n * verify: (assertion) => api.post(\"/webauthn/signin/finish\", { body: assertion }),\n * onSuccess: (session) => auth.setToken(session.access_token),\n * });\n *\n * <input name=\"email\" autoComplete=\"username webauthn\" />\n * <button onClick={() => void passkey.signIn()}>Entrar com passkey</button>\n */\nexport function usePasskeySignIn<TResult = unknown>(\n options: UsePasskeySignInOptions<TResult>,\n): UsePasskeySignInResult<TResult> {\n const client = useMemo(() => options.client ?? defaultClient(), [options.client]);\n const capabilities = usePasskeyCapabilities(client);\n const [status, setStatus] = useState<PasskeyStatus>(\"idle\");\n const [error, setError] = useState<PasskeyError | null>(null);\n const [data, setData] = useState<TResult | null>(null);\n const [conditionalPending, setConditionalPending] = useState(false);\n const controller = useRef<AbortController | null>(null);\n const conditional = options.conditional ?? false;\n\n const getOptions = useStableCallback(options.getOptions);\n const verify = useStableCallback(options.verify);\n const onSuccess = useStableCallback((result: TResult) => options.onSuccess?.(result));\n const onError = useStableCallback((failure: PasskeyError) => options.onError?.(failure));\n\n const run = useCallback(\n async (\n mediation: \"optional\" | \"conditional\",\n abort: AbortController = new AbortController(),\n ): Promise<TResult | null> => {\n controller.current = abort;\n if (mediation === \"optional\") {\n setStatus(\"prompting\");\n setError(null);\n }\n try {\n const serverOptions = await getOptions();\n const assertion = await client.authenticate(serverOptions, {\n mediation,\n signal: abort.signal,\n });\n setStatus(\"verifying\");\n const result = await verify(assertion);\n setData(result);\n setStatus(\"success\");\n onSuccess(result);\n return result;\n } catch (thrown) {\n const failure = classifyPasskeyError(thrown, \"authenticate\");\n if (failure.kind === \"aborted\" && mediation === \"conditional\") return null;\n setError(failure);\n setStatus(\"error\");\n onError(failure);\n return null;\n } finally {\n controller.current = null;\n }\n },\n [client, getOptions, verify, onSuccess, onError],\n );\n\n const signIn = useCallback(() => run(\"optional\"), [run]);\n\n useEffect(() => {\n if (!conditional) return;\n let cancelled = false;\n let abort: AbortController | null = null;\n\n async function arm(): Promise<void> {\n if (!(await client.isConditionalMediationAvailable())) return;\n if (cancelled) return;\n setConditionalPending(true);\n abort = new AbortController();\n await run(\"conditional\", abort);\n if (!cancelled) setConditionalPending(false);\n }\n\n void arm();\n\n return () => {\n cancelled = true;\n abort?.abort();\n };\n }, [conditional, client, run]);\n\n const cancel = useCallback((): void => {\n controller.current?.abort();\n }, []);\n\n const reset = useCallback((): void => {\n setStatus(\"idle\");\n setError(null);\n setData(null);\n }, []);\n\n return {\n ...capabilities,\n signIn,\n status,\n error,\n data,\n conditionalPending,\n cancel,\n reset,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,120 @@
1
+ import { useStableCallback as e } from "../hooks/use-stable-callback.js";
2
+ import { classifyPasskeyError as t, createPasskeyClient as n } from "./passkey.js";
3
+ import { useCallback as r, useEffect as i, useMemo as a, useRef as o, useState as s } from "react";
4
+ //#region src/auth/use-passkey.ts
5
+ var c;
6
+ function l() {
7
+ return c ??= n(), c;
8
+ }
9
+ function u(e) {
10
+ let t = a(() => e ?? l(), [e]), [n] = s(() => t.isSupported()), [r, o] = s(null), [c, u] = s(null);
11
+ return i(() => {
12
+ let e = !1;
13
+ return t.isPlatformAuthenticatorAvailable().then((t) => {
14
+ e || o(t);
15
+ }), t.isConditionalMediationAvailable().then((t) => {
16
+ e || u(t);
17
+ }), () => {
18
+ e = !0;
19
+ };
20
+ }, [t]), {
21
+ supported: n,
22
+ platformAvailable: r,
23
+ conditionalAvailable: c
24
+ };
25
+ }
26
+ function d(n) {
27
+ let i = a(() => n.client ?? l(), [n.client]), c = u(i), [d, f] = s("idle"), [p, m] = s(null), [h, g] = s(null), _ = o(null), v = e(n.getOptions), y = e(n.verify), b = e((e) => n.onSuccess?.(e)), x = e((e) => n.onError?.(e)), S = r(async () => {
28
+ f("prompting"), m(null);
29
+ let e = new AbortController();
30
+ _.current = e;
31
+ try {
32
+ let t = await v(), n = await i.register(t, { signal: e.signal });
33
+ f("verifying");
34
+ let r = await y(n);
35
+ return g(r), f("success"), b(r), r;
36
+ } catch (e) {
37
+ let n = t(e, "register");
38
+ return m(n), f("error"), x(n), null;
39
+ } finally {
40
+ _.current = null;
41
+ }
42
+ }, [
43
+ i,
44
+ v,
45
+ y,
46
+ b,
47
+ x
48
+ ]), C = r(() => {
49
+ _.current?.abort();
50
+ }, []), w = r(() => {
51
+ f("idle"), m(null), g(null);
52
+ }, []);
53
+ return {
54
+ ...c,
55
+ register: S,
56
+ status: d,
57
+ error: p,
58
+ data: h,
59
+ cancel: C,
60
+ reset: w
61
+ };
62
+ }
63
+ function f(n) {
64
+ let c = a(() => n.client ?? l(), [n.client]), d = u(c), [f, p] = s("idle"), [m, h] = s(null), [g, _] = s(null), [v, y] = s(!1), b = o(null), x = n.conditional ?? !1, S = e(n.getOptions), C = e(n.verify), w = e((e) => n.onSuccess?.(e)), T = e((e) => n.onError?.(e)), E = r(async (e, n = new AbortController()) => {
65
+ b.current = n, e === "optional" && (p("prompting"), h(null));
66
+ try {
67
+ let t = await S(), r = await c.authenticate(t, {
68
+ mediation: e,
69
+ signal: n.signal
70
+ });
71
+ p("verifying");
72
+ let i = await C(r);
73
+ return _(i), p("success"), w(i), i;
74
+ } catch (n) {
75
+ let r = t(n, "authenticate");
76
+ return r.kind === "aborted" && e === "conditional" ? null : (h(r), p("error"), T(r), null);
77
+ } finally {
78
+ b.current = null;
79
+ }
80
+ }, [
81
+ c,
82
+ S,
83
+ C,
84
+ w,
85
+ T
86
+ ]), D = r(() => E("optional"), [E]);
87
+ i(() => {
88
+ if (!x) return;
89
+ let e = !1, t = null;
90
+ async function n() {
91
+ await c.isConditionalMediationAvailable() && (e || (y(!0), t = new AbortController(), await E("conditional", t), e || y(!1)));
92
+ }
93
+ return n(), () => {
94
+ e = !0, t?.abort();
95
+ };
96
+ }, [
97
+ x,
98
+ c,
99
+ E
100
+ ]);
101
+ let O = r(() => {
102
+ b.current?.abort();
103
+ }, []), k = r(() => {
104
+ p("idle"), h(null), _(null);
105
+ }, []);
106
+ return {
107
+ ...d,
108
+ signIn: D,
109
+ status: f,
110
+ error: m,
111
+ data: g,
112
+ conditionalPending: v,
113
+ cancel: O,
114
+ reset: k
115
+ };
116
+ }
117
+ //#endregion
118
+ export { u as usePasskeyCapabilities, d as usePasskeyRegistration, f as usePasskeySignIn };
119
+
120
+ //# sourceMappingURL=use-passkey.js.map