UNPKG

@frank-auth/react

Version:

Flexible and customizable React UI components for Frank Authentication

236 lines (235 loc) 6.51 kB
import { useState as k, useMemo as _, useEffect as D, useCallback as s } from "react"; import { useAuth as F } from "./use-auth.js"; import { useConfig as x } from "../provider/config-provider.js"; const L = { // Default expiration time (15 minutes) DEFAULT_EXPIRES_IN: 15 * 60, // Minimum time between sends (60 seconds) RESEND_COOLDOWN: 60, // Email validation regex EMAIL_REGEX: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, // Default templates TEMPLATES: { SIGN_IN: "magic-link-sign-in", SIGN_UP: "magic-link-sign-up", VERIFY_EMAIL: "magic-link-verify-email", PASSWORD_RESET: "magic-link-password-reset" }, // URL parameter names URL_PARAMS: { TOKEN: "token", EMAIL: "email", TYPE: "type", REDIRECT: "redirect_to" } }; function p() { const { activeOrganization: a, reload: i, userType: w, sdk: t } = F(), { apiUrl: h, publishableKey: l, features: o, linksPath: u, frontendUrl: S } = x(), [f, n] = k(!1), [c, g] = k(null), [R, E] = k(null), [d, O] = k(null), I = _(() => o.magicLink, [o.magicLink]), m = _(() => d ? (Date.now() - d.getTime()) / 1e3 >= L.RESEND_COOLDOWN : !1, [d]), y = _(() => { if (!d || m) return 0; const e = (Date.now() - d.getTime()) / 1e3; return Math.max(0, L.RESEND_COOLDOWN - e); }, [d, m]); D(() => { if (!d || m) return; const e = setInterval(() => { }, 1e3); return () => clearInterval(e); }, [d, m]), s((e) => { const r = { code: e.code || "UNKNOWN_ERROR", message: e.message || "An unknown error occurred", details: e.details, field: e.field }; throw g(r), r; }, []); const A = s((e) => L.EMAIL_REGEX.test(e), []), v = s((e) => { const r = e || window.location.href; try { return new URL(r).searchParams.get(L.URL_PARAMS.TOKEN); } catch { return null; } }, []), T = s(async (e, r) => { if (!I) throw new Error("Magic links not available"); if (!A(e)) throw new Error("Invalid email address"); try { n(!0), g(null); const M = { email: e, redirectUrl: r?.redirectUrl || `${S ?? window.location.origin}${u?.magicLink}`, // organizationId: options?.organizationId || activeOrganization?.id, // customData: options?.customData, expiresIn: r?.expiresIn || L.DEFAULT_EXPIRES_IN // locale: options?.locale, }, U = await t.auth.sendMagicLink(M); return E(e), O(/* @__PURE__ */ new Date()), { success: U.success, email: e, message: U.message, expiresAt: new Date(Date.now() + M.expiresIn * 1e3) }; } catch (M) { return { success: !1, email: e, expiresAt: /* @__PURE__ */ new Date(), error: M.message || "Failed to send magic link" }; } finally { n(!1); } }, [t.auth, I, A, a]), N = s(async (e) => { if (!I) throw new Error("Magic links not available"); if (!e) throw new Error("Verification token is required"); try { n(!0), g(null); const r = await t.auth.verifyMagicLink(e); return r.session ? (await i(), { success: !0, user: r.user, session: r.session, requiresAdditionalVerification: r.mfaRequired, mfaToken: r.mfaToken }) : { success: !1, error: r.error || "Magic link verification failed" }; } catch (r) { return { success: !1, error: r.message || "Magic link verification failed" }; } finally { n(!1); } }, [t.auth, I, i]), P = s(async (e) => { const r = v(e); return r ? N(r) : { success: !1, error: "No verification token found in URL" }; }, [v, N]), V = s(async () => { if (!R) throw new Error("No previous magic link to resend"); if (!m) throw new Error(`Please wait ${Math.ceil(y)} seconds before resending`); return T(R); }, [R, m, y, T]), b = s(() => { g(null), E(null), O(null); }, []); return { // Magic link state isLoading: f, error: c, lastSentEmail: R, lastSentAt: d, canResend: m, timeUntilResend: y, // Magic link operations sendMagicLink: T, verifyMagicLink: N, resendMagicLink: V, // Magic link verification verifyFromUrl: P, extractTokenFromUrl: v, // Utility methods isValidEmail: A, clearState: b }; } function W() { const { sendMagicLink: a, isLoading: i, error: w, lastSentEmail: t, canResend: h, resendMagicLink: l, isValidEmail: o, clearState: u } = p(), [S, f] = k("idle"), n = s(async (g, R) => { if (!o(g)) throw new Error("Please enter a valid email address"); try { const E = await a(g, { redirectUrl: R || "/dashboard", template: L.TEMPLATES.SIGN_IN }); return E.success && f("email_sent"), E; } catch (E) { throw f("idle"), E; } }, [a, o]), c = s(() => { f("idle"), u(); }, [u]); return { signIn: n, resend: l, reset: c, state: S, sentTo: t, canResend: h, isLoading: i, error: w, isValidEmail: o }; } function K() { const { verifyFromUrl: a, verifyMagicLink: i, extractTokenFromUrl: w, isLoading: t, error: h } = p(), [l, o] = k("idle"), [u, S] = k(null); D(() => { const n = w(); n && l === "idle" && f(n); }, [w, l]); const f = s(async (n) => { try { o("verifying"); const c = n ? await i(n) : await a(); return S(c), o(c.success ? "success" : "error"), c; } catch (c) { throw o("error"), S({ success: !1, error: c.message }), c; } }, [i, a]); return { verify: f, state: l, result: u, isVerifying: l === "verifying" || t, isSuccess: l === "success", isError: l === "error", error: h || u?.error, requiresMFA: u?.requiresAdditionalVerification, mfaToken: u?.mfaToken }; } function $() { const { sendMagicLink: a, isValidEmail: i } = p(); return { sendResetLink: s(async (t) => { if (!i(t)) throw new Error("Please enter a valid email address"); return a(t, { template: L.TEMPLATES.PASSWORD_RESET, redirectUrl: `${window.location.origin}/auth/reset-password` }); }, [a, i]), isValidEmail: i }; } export { L as MAGIC_LINK_CONFIG, p as useMagicLink, $ as useMagicLinkPasswordReset, W as useMagicLinkSignIn, K as useMagicLinkVerification }; //# sourceMappingURL=use-magic-link.js.map