@authowl/react 0.21.1 → 0.22.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.
@@ -9,7 +9,7 @@ import {
9
9
  useServerError,
10
10
  useSubmitAction,
11
11
  useT
12
- } from "./chunk-K67DJJHP.js";
12
+ } from "./chunk-PUJV7S6G.js";
13
13
 
14
14
  // src/components/organization/TeamsSection.tsx
15
15
  import * as React4 from "react";
@@ -0,0 +1,68 @@
1
+ "use client";
2
+
3
+ // src/components/captcha-loader.ts
4
+ var SCRIPT_LOAD_TIMEOUT_MS = 1e4;
5
+ var GLOBAL_POLL_INTERVAL_MS = 25;
6
+ var loaders = /* @__PURE__ */ new Map();
7
+ function providerGlobal(adapter) {
8
+ const api = window[adapter.globalName];
9
+ return typeof api?.render === "function" ? api : void 0;
10
+ }
11
+ function loadCaptcha(adapter) {
12
+ const available = providerGlobal(adapter);
13
+ if (available) return Promise.resolve(available);
14
+ const cached = loaders.get(adapter.scriptUrl);
15
+ if (cached) return cached;
16
+ const loading = new Promise((resolve, reject) => {
17
+ const existing = document.querySelector(
18
+ `script[src="${adapter.scriptUrl}"]`
19
+ );
20
+ const script = existing ?? document.createElement("script");
21
+ let poll;
22
+ const cleanup = () => {
23
+ clearTimeout(timeout);
24
+ if (poll !== void 0) clearInterval(poll);
25
+ script.removeEventListener("load", loaded);
26
+ script.removeEventListener("error", failed);
27
+ };
28
+ const resolveWhenPublished = () => {
29
+ const api = providerGlobal(adapter);
30
+ if (!api) return false;
31
+ cleanup();
32
+ resolve(api);
33
+ return true;
34
+ };
35
+ const loaded = () => {
36
+ if (!resolveWhenPublished() && poll === void 0) {
37
+ poll = setInterval(resolveWhenPublished, GLOBAL_POLL_INTERVAL_MS);
38
+ }
39
+ };
40
+ const failed = () => {
41
+ cleanup();
42
+ if (!existing) script.remove();
43
+ reject(new Error(`${adapter.label} failed to load`));
44
+ };
45
+ const timeout = setTimeout(failed, SCRIPT_LOAD_TIMEOUT_MS);
46
+ script.addEventListener("load", loaded, { once: true });
47
+ script.addEventListener("error", failed, { once: true });
48
+ if (existing) {
49
+ loaded();
50
+ } else {
51
+ script.src = adapter.scriptUrl;
52
+ script.async = true;
53
+ script.defer = true;
54
+ const nonce = document.querySelector("script[nonce]")?.nonce;
55
+ if (nonce) script.nonce = nonce;
56
+ document.head.appendChild(script);
57
+ }
58
+ });
59
+ const retryable = loading.catch((error) => {
60
+ loaders.delete(adapter.scriptUrl);
61
+ throw error;
62
+ });
63
+ loaders.set(adapter.scriptUrl, retryable);
64
+ return retryable;
65
+ }
66
+ export {
67
+ loadCaptcha
68
+ };
@@ -0,0 +1,71 @@
1
+ "use client";
2
+
3
+ // src/components/captcha-providers.ts
4
+ function preferredTheme() {
5
+ return typeof window !== "undefined" && window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark" : "light";
6
+ }
7
+ function teardown(api, widgetId) {
8
+ if (api.remove) {
9
+ try {
10
+ api.remove(widgetId);
11
+ return;
12
+ } catch {
13
+ }
14
+ }
15
+ try {
16
+ api.reset?.(widgetId);
17
+ } catch {
18
+ }
19
+ }
20
+ var CAPTCHA_ADAPTERS = {
21
+ turnstile: {
22
+ scriptUrl: "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit",
23
+ globalName: "turnstile",
24
+ label: "Cloudflare Turnstile",
25
+ invisibleRenderOptions: ({ siteKey, theme, action, language }) => ({
26
+ sitekey: siteKey,
27
+ theme,
28
+ action,
29
+ language,
30
+ execution: "execute",
31
+ appearance: "interaction-only",
32
+ size: "flexible"
33
+ }),
34
+ teardown
35
+ },
36
+ hcaptcha: {
37
+ scriptUrl: "https://js.hcaptcha.com/1/api.js?render=explicit",
38
+ globalName: "hcaptcha",
39
+ label: "hCaptcha",
40
+ invisibleRenderOptions: ({ siteKey, theme }) => ({
41
+ sitekey: siteKey,
42
+ // 'auto' is Turnstile vocabulary. hCaptcha and reCAPTCHA take light|dark
43
+ // only and silently fall back to light on anything else, which reads badly
44
+ // in a dark application.
45
+ theme: theme === "auto" ? preferredTheme() : theme,
46
+ size: "invisible"
47
+ }),
48
+ teardown
49
+ },
50
+ "recaptcha-v2": {
51
+ scriptUrl: "https://www.google.com/recaptcha/api.js?render=explicit",
52
+ globalName: "grecaptcha",
53
+ label: "reCAPTCHA",
54
+ invisibleRenderOptions: ({ siteKey, theme }) => ({
55
+ sitekey: siteKey,
56
+ // 'auto' is Turnstile vocabulary. hCaptcha and reCAPTCHA take light|dark
57
+ // only and silently fall back to light on anything else, which reads badly
58
+ // in a dark application.
59
+ theme: theme === "auto" ? preferredTheme() : theme,
60
+ size: "invisible"
61
+ }),
62
+ teardown
63
+ }
64
+ };
65
+ function captchaAdapterFor(provider) {
66
+ return CAPTCHA_ADAPTERS[provider] ?? null;
67
+ }
68
+ export {
69
+ CAPTCHA_ADAPTERS,
70
+ captchaAdapterFor
71
+ };
@@ -381,7 +381,7 @@ function warnPublicConfigFailed(resolved, error) {
381
381
  warnedConfigProjects.add(key);
382
382
  const reason = error instanceof Error ? error.message : String(error);
383
383
  console.warn(
384
- `[AuthOwl] Could not load this project's public config from ${origin} (${reason}). Falling back to a password-only sign-in form, regardless of the methods this project actually enabled. Likely causes: the API is unreachable, \`apiUrl\` is wrong, or the publishable key points at a missing/mismatched project. If password sign-in is not enabled for this project, the form will dead-end at submit - check \`apiUrl\` and \`publishableKey\` on <AuthOwlProvider>. (This warning is dev-only.)`
384
+ `[AuthOwl] Could not load this project's public config from ${origin} (${reason}). Authentication UI is unavailable until the request succeeds. Likely causes: the API is unreachable, \`apiUrl\` is wrong, or the publishable key points at a missing/mismatched project. Check \`apiUrl\` and \`publishableKey\` on <AuthOwlProvider>. (This warning is dev-only.)`
385
385
  );
386
386
  }
387
387
  var Context = React4.createContext(null);
@@ -418,6 +418,8 @@ function AuthOwlProvider({
418
418
  const client = React4.useMemo(() => createAuthOwlClient(resolved), [resolved]);
419
419
  const [config, setConfig] = React4.useState(null);
420
420
  const [configState, setConfigState] = React4.useState("loading");
421
+ const [configAttempt, setConfigAttempt] = React4.useState(0);
422
+ const retryPublicConfig = React4.useCallback(() => setConfigAttempt((attempt) => attempt + 1), []);
421
423
  React4.useEffect(() => {
422
424
  let active = true;
423
425
  setConfigState("loading");
@@ -434,7 +436,7 @@ function AuthOwlProvider({
434
436
  return () => {
435
437
  active = false;
436
438
  };
437
- }, [resolved]);
439
+ }, [resolved, configAttempt]);
438
440
  const merged = resolveAppearance(appearance, config);
439
441
  const [autoLocale, setAutoLocale] = React4.useState(null);
440
442
  React4.useEffect(() => {
@@ -445,8 +447,8 @@ function AuthOwlProvider({
445
447
  captureInvitationClaim();
446
448
  }, []);
447
449
  const ctxValue = React4.useMemo(
448
- () => ({ client, appearance, config, configState, locale }),
449
- [client, appearance, config, configState, locale]
450
+ () => ({ client, appearance, config, configState, retryPublicConfig, locale }),
451
+ [client, appearance, config, configState, retryPublicConfig, locale]
450
452
  );
451
453
  return /* @__PURE__ */ jsx4(Context.Provider, { value: ctxValue, children: /* @__PURE__ */ jsxs3(
452
454
  "div",
@@ -489,11 +491,12 @@ function useAccount() {
489
491
  return useAuthClient().account;
490
492
  }
491
493
  function usePublicConfig() {
492
- const { config, configState } = useAuthOwlContext();
494
+ const { config, configState, retryPublicConfig } = useAuthOwlContext();
493
495
  return {
494
496
  config,
495
497
  isLoading: configState === "loading",
496
- isError: configState === "error"
498
+ isError: configState === "error",
499
+ retry: retryPublicConfig
497
500
  };
498
501
  }
499
502
  function useSession() {