@frank-auth/react
Version:
Flexible and customizable React UI components for Frank Authentication
311 lines (310 loc) • 8.56 kB
JavaScript
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