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