UNPKG

@frank-auth/react

Version:

Flexible and customizable React UI components for Frank Authentication

311 lines (310 loc) 8.56 kB
import { useState as g, useCallback as c, useMemo as E, useEffect as T } from "react"; import { useAuth as $ } from "./use-auth.js"; import { useConfig as j } from "../provider/config-provider.js"; function N() { return typeof window < "u" && "navigator" in window && "credentials" in navigator && "create" in navigator.credentials && "get" in navigator.credentials; } async function J() { if (!N()) return !1; try { return await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(); } catch { return !1; } } function A(s) { const e = s.replace(/-/g, "+").replace(/_/g, "/"), a = e.padEnd(e.length + (4 - e.length % 4) % 4, "="), l = atob(a), f = new ArrayBuffer(l.length), u = new Uint8Array(f); for (let o = 0; o < l.length; o++) u[o] = l.charCodeAt(o); return f; } function w(s) { const e = new Uint8Array(s); let a = ""; for (let l = 0; l < e.byteLength; l++) a += String.fromCharCode(e[l]); return btoa(a).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); } function M(s) { return { ...s, challenge: A(s.challenge), user: { ...s.user, id: A(s.user.id) }, excludeCredentials: s.excludeCredentials?.map((e) => ({ ...e, id: A(e.id) })) }; } function V(s) { return { ...s, challenge: A(s.challenge), allowCredentials: s.allowCredentials?.map((e) => ({ ...e, id: A(e.id) })) }; } function L(s) { const e = s.response, a = { id: s.id, rawId: w(s.rawId), type: s.type, response: { clientDataJSON: w(e.clientDataJSON) } }; return e instanceof AuthenticatorAttestationResponse ? a.response.attestationObject = w(e.attestationObject) : e instanceof AuthenticatorAssertionResponse && (a.response.authenticatorData = w(e.authenticatorData), a.response.signature = w(e.signature), e.userHandle && (a.response.userHandle = w(e.userHandle))), a; } function K() { const { user: s, sdk: e } = $(), { apiUrl: a, publishableKey: l, features: f } = j(), [u, o] = g([]), [m, n] = g(!1), [k, y] = g(null), [W, D] = g(!1), p = E(() => N(), []), b = E(() => f.passkeys, [f.passkeys]), d = c((t) => { const r = { code: t.code || "UNKNOWN_ERROR", message: t.message || "An unknown error occurred", details: t.details, field: t.field }; throw y(r), r; }, []), C = c(async () => { if (!p) return !1; try { const t = await J(); return D(t), t; } catch { return D(!1), !1; } }, [p]), h = c(async () => { if (!(!e.user || !s || !b)) try { n(!0), y(null); const t = await e.user.getPasskeys({ fields: [] }); o(t.data || []); } catch (t) { console.error("Failed to load passkeys:", t), y({ code: "PASSKEYS_LOAD_FAILED", message: "Failed to load passkeys" }); } finally { n(!1); } }, [e.user, s, b]); T(() => { C(), h(); }, [C, h]); const S = c(async (t) => { if (!e.user) throw new Error("User not authenticated"); if (!p) throw new Error("WebAuthn not supported"); if (!b) throw new Error("Passkeys not enabled"); try { n(!0), y(null); const r = await e.auth.beginPasskeyRegistration({ name: t || `Passkey ${u.length + 1}` }), i = M(r.options); return { challenge: r.challenge, options: i, sessionId: r.sessionId }; } catch (r) { return d(r); } finally { n(!1); } }, [e.auth, p, b, u.length, d]), v = c(async (t, r) => { if (!e.user) throw new Error("User not authenticated"); try { n(!0), y(null); const i = L(r), O = { sessionId: t.sessionId, credential: i }, P = await e.auth.finishPasskeyRegistration(O); return await h(), P.passkey; } catch (i) { return d(i); } finally { n(!1); } }, [e.user, h, d]), F = c(async (t) => { const r = await S(t); try { const i = await navigator.credentials.create({ publicKey: r.options }); if (!i) throw new Error("Failed to create credential"); return await v(r, i); } catch (i) { throw i.name === "NotAllowedError" ? new Error("User cancelled the registration process") : i.name === "InvalidStateError" ? new Error("This authenticator is already registered") : new Error(`Registration failed: ${i.message}`); } }, [S, v]), I = c(async () => { if (!e.auth) throw new Error("User not authenticated"); if (!p) throw new Error("WebAuthn not supported"); try { n(!0), y(null); const t = await e.auth.beginPasskeyAuthentication({}), r = V(t.options); return { challenge: t.challenge, options: r, sessionId: t.sessionId }; } catch (t) { return d(t); } finally { n(!1); } }, [e.auth, p, d]), R = c(async (t, r) => { if (!e.user) throw new Error("User not authenticated"); try { n(!0), y(null); const i = L(r), O = { sessionId: t.sessionId, credential: i }, P = await e.auth.finishPasskeyAuthentication(O); return { success: !0, session: P.session, user: P.user }; } catch (i) { return { success: !1, error: i.message }; } finally { n(!1); } }, [e.auth, d]), _ = c(async () => { const t = await I(); try { const r = await navigator.credentials.get({ publicKey: t.options }); if (!r) throw new Error("Failed to get credential"); return await R(t, r); } catch (r) { return r.name === "NotAllowedError" ? { success: !1, error: "User cancelled the authentication process" } : { success: !1, error: `Authentication failed: ${r.message}` }; } }, [I, R]), U = c(async (t, r) => { if (!e.user) throw new Error("User not authenticated"); try { n(!0), y(null); const i = await e.user.updatePasskey(t, r); return await h(), i.passkey; } catch (i) { return d(i); } finally { n(!1); } }, [e.user, h, d]), z = c(async (t) => { if (!e.user) throw new Error("User not authenticated"); try { n(!0), y(null), await e.user.deletePasskey(t), await h(); } catch (r) { d(r); } finally { n(!1); } }, [e.user, h, d]), B = c(async (t, r) => U(t, { name: r }), [U]), q = c(async () => { await h(); }, [h]), x = E(() => u.find((t) => t.isPrimary) || u[0] || null, [u]), H = E(() => u.length, [u]); return { // Passkey state passkeys: u, isSupported: p, isAvailable: W, isLoaded: !!s && b, isLoading: m, error: k, // Passkey registration beginRegistration: S, finishRegistration: v, registerPasskey: F, // Passkey authentication beginAuthentication: I, finishAuthentication: R, authenticateWithPasskey: _, // Passkey management updatePasskey: U, deletePasskey: z, renamePasskey: B, // Passkey information primaryPasskey: x, passkeyCount: H, // Utility methods refreshPasskeys: q, checkSupport: C }; } function X() { const { registerPasskey: s, isSupported: e, isAvailable: a, isLoading: l, error: f } = K(), [u, o] = g("idle"); return { register: c(async (n) => { if (!e || !a) throw o("error"), new Error("Passkeys not supported or available"); try { o("registering"); const k = await s(n); return o("success"), k; } catch (k) { throw o("error"), k; } }, [s, e, a]), state: u, isSupported: e, isAvailable: a, isLoading: l, error: f, canRegister: e && a && !l }; } function Z() { const { authenticateWithPasskey: s, isSupported: e, isAvailable: a, isLoading: l, error: f } = K(), [u, o] = g("idle"); return { authenticate: c(async () => { if (!e || !a) throw o("error"), new Error("Passkeys not supported or available"); try { o("authenticating"); const n = await s(); return n.success ? o("success") : o("error"), n; } catch (n) { throw o("error"), n; } }, [s, e, a]), state: u, isSupported: e, isAvailable: a, isLoading: l, error: f, canAuthenticate: e && a && !l }; } export { Z as usePasskeyAuthentication, X as usePasskeyRegistration, K as usePasskeys }; //# sourceMappingURL=use-passkeys.js.map