@autobusal/providers 1.6.7 → 1.6.8
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.
- package/CHANGELOG.md +25 -0
- package/Providers.tsx +35 -100
- package/package.json +1 -1
- package/Recaptcha/Recaptcha.tsx +0 -36
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ All notable changes to `@autobusal/providers` are documented here. This project
|
|
|
4
4
|
adheres to [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.6.8] - 2026-07-31
|
|
8
|
+
|
|
9
|
+
### Removed
|
|
10
|
+
|
|
11
|
+
- **`Recaptcha/Recaptcha.tsx` and its `<Recaptcha>` wrapper around the
|
|
12
|
+
app in `Providers.tsx`.** This mounted `GoogleReCaptchaProvider`
|
|
13
|
+
(`react-google-recaptcha-v3`) as a persistent context provider inside
|
|
14
|
+
the same Suspense tree as `Styles`/`Notifications`/`Setup`; that
|
|
15
|
+
provider's script/badge injection is async and not remount-safe, and
|
|
16
|
+
any remount of it left the DOM in a state that could throw an
|
|
17
|
+
uncaught `removeChild`/`insertBefore` error on the next reconciliation
|
|
18
|
+
anywhere near the root - the actual root cause of the frozen-
|
|
19
|
+
navigation bug 1.6.6/1.6.7 worked around. `@autobusal/hooks` 1.3.1
|
|
20
|
+
moved reCAPTCHA to the same on-demand, no-provider pattern Turnstile
|
|
21
|
+
already used (`useRecaptcha`'s external API is unchanged), so nothing
|
|
22
|
+
needs this wrapper anymore - removing it removes the remount risk at
|
|
23
|
+
the root instead of continuing to recover from it. `useGetSettings()`
|
|
24
|
+
et al are otherwise unaffected: `Styles`/`Notifications`/`Setup` still
|
|
25
|
+
share the outer boundary exactly as before, just without `Recaptcha`
|
|
26
|
+
in the tree.
|
|
27
|
+
- **`useRecoverFromDomCorruption()`'s global `removeChild`/`insertBefore`
|
|
28
|
+
listener**, added in 1.6.7 as a backstop specifically for the
|
|
29
|
+
`GoogleReCaptchaProvider` remount corruption above. With that provider
|
|
30
|
+
gone, its trigger no longer exists.
|
|
31
|
+
|
|
7
32
|
## [1.6.7] - 2026-07-31
|
|
8
33
|
|
|
9
34
|
### Added
|
package/Providers.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { Suspense
|
|
2
|
-
import Recaptcha from './Recaptcha/Recaptcha';
|
|
1
|
+
import { Suspense } from 'react';
|
|
3
2
|
import Queries from './Queries/Queries';
|
|
4
3
|
import Styles from './Styles/Styles';
|
|
5
4
|
import Notifications from './Notifications/Notifications';
|
|
@@ -11,103 +10,39 @@ interface Props {
|
|
|
11
10
|
children: JSX.Element
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (Date.now() - last < 10000) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
sessionStorage.setItem(RECOVERY_KEY, String(Date.now()));
|
|
44
|
-
window.location.reload();
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
window.addEventListener('error', onError);
|
|
48
|
-
|
|
49
|
-
return () => window.removeEventListener('error', onError);
|
|
50
|
-
}, []);
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const Providers = ({ type, loading, children }: Props): JSX.Element => {
|
|
54
|
-
useRecoverFromDomCorruption();
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
// Styles and Setup both call useGetSettings() (a useSuspenseQuery) and
|
|
58
|
-
// sit ABOVE the inner <Suspense> below, so on a cold settings cache their
|
|
59
|
-
// suspension had no boundary to catch it and React threw error #426
|
|
60
|
-
// ("a component suspended while responding to synchronous input"). This
|
|
61
|
-
// outer boundary catches those two suspensions (showing `loading` during
|
|
62
|
-
// the initial settings fetch). The QueryClient in <Queries> is created
|
|
63
|
-
// at module scope, so remounting this subtree while suspended keeps the
|
|
64
|
-
// same client and cache - no refetch loop.
|
|
65
|
-
// Edited: Ferjolt Ozuni - Date: 2026-07-25
|
|
66
|
-
// Recaptcha moved INSIDE Queries: it now reads the label's runtime
|
|
67
|
-
// captcha settings via useGetSettings(), which needs the
|
|
68
|
-
// QueryClientProvider (and suspends into the outer boundary on a cold
|
|
69
|
-
// cache, like Styles/Setup).
|
|
70
|
-
// Edited: Ferjolt Ozuni - Date: 2026-07-31
|
|
71
|
-
// Bug: Setup also calls useGetMenu() (a SEPARATE useSuspenseQuery, its
|
|
72
|
-
// own cache key/promise) in its own render body, still inside this same
|
|
73
|
-
// outer boundary. On a cold menu cache, that suspends AFTER Recaptcha/
|
|
74
|
-
// Styles/Notifications above it have already committed - and a Suspense
|
|
75
|
-
// boundary re-suspending after its content is already committed hides/
|
|
76
|
-
// discards that whole committed subtree and mounts it again once ready.
|
|
77
|
-
// Recaptcha's GoogleReCaptchaProvider isn't remount-safe: its script/
|
|
78
|
-
// badge injection is async and outside React's DOM bookkeeping, so
|
|
79
|
-
// remounting it left a duplicate, half-torn-down
|
|
80
|
-
// <div class="grecaptcha-badge"> behind. That corrupted DOM state then
|
|
81
|
-
// threw an uncaught "Cannot read properties of null (reading
|
|
82
|
-
// 'removeChild')" the next time React reconciled anywhere near the root
|
|
83
|
-
// - which is every route change, since Recaptcha wraps the whole router
|
|
84
|
-
// - silently aborting the commit. The URL would update (the router's
|
|
85
|
-
// history.pushState already ran) but the page itself would never
|
|
86
|
-
// re-render, freezing navigation app-wide with no console-visible error
|
|
87
|
-
// (uncaught DOM exceptions like this don't hit the render-phase-only
|
|
88
|
-
// ErrorBoundary). Giving Setup its own inner boundary means its menu
|
|
89
|
-
// suspension can only hide/retry ITSELF, not the already-committed
|
|
90
|
-
// Recaptcha/Styles/Notifications above it - this closed off the
|
|
91
|
-
// deterministic trigger, and useRecoverFromDomCorruption() above is the
|
|
92
|
-
// backstop for whatever async-timing sliver still gets through.
|
|
93
|
-
<Suspense fallback={ loading }>
|
|
94
|
-
<Queries>
|
|
95
|
-
<Recaptcha>
|
|
96
|
-
<Styles>
|
|
97
|
-
<Notifications>
|
|
13
|
+
const Providers = ({ type, loading, children }: Props): JSX.Element => (
|
|
14
|
+
// Styles and Setup both call useGetSettings() (a useSuspenseQuery) and sit
|
|
15
|
+
// ABOVE the inner <Suspense> below, so on a cold settings cache their
|
|
16
|
+
// suspension had no boundary to catch it and React threw error #426
|
|
17
|
+
// ("a component suspended while responding to synchronous input"). This
|
|
18
|
+
// outer boundary catches those two suspensions (showing `loading` during
|
|
19
|
+
// the initial settings fetch). The QueryClient in <Queries> is created at
|
|
20
|
+
// module scope, so remounting this subtree while suspended keeps the same
|
|
21
|
+
// client and cache - no refetch loop.
|
|
22
|
+
// Edited: Ferjolt Ozuni - Date: 2026-07-31
|
|
23
|
+
// Setup also calls useGetMenu() (a SEPARATE useSuspenseQuery, its own
|
|
24
|
+
// cache key/promise) in its own render body. Kept in its own inner
|
|
25
|
+
// <Suspense> rather than sharing the outer one: a cold menu cache
|
|
26
|
+
// suspending after Styles/Notifications above it have already committed
|
|
27
|
+
// would otherwise hide/discard that already-committed content and remount
|
|
28
|
+
// it once ready - harmless for Styles/Notifications themselves, but a
|
|
29
|
+
// needless remount of anything stateful sitting there is worth avoiding on
|
|
30
|
+
// principle.
|
|
31
|
+
<Suspense fallback={ loading }>
|
|
32
|
+
<Queries>
|
|
33
|
+
<Styles>
|
|
34
|
+
<Notifications>
|
|
35
|
+
<Suspense fallback={ loading }>
|
|
36
|
+
<Setup type={ type }>
|
|
98
37
|
<Suspense fallback={ loading }>
|
|
99
|
-
|
|
100
|
-
<Suspense fallback={ loading }>
|
|
101
|
-
{ children }
|
|
102
|
-
</Suspense>
|
|
103
|
-
</Setup>
|
|
38
|
+
{ children }
|
|
104
39
|
</Suspense>
|
|
105
|
-
</
|
|
106
|
-
</
|
|
107
|
-
</
|
|
108
|
-
</
|
|
109
|
-
</
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
export default Providers;
|
|
40
|
+
</Setup>
|
|
41
|
+
</Suspense>
|
|
42
|
+
</Notifications>
|
|
43
|
+
</Styles>
|
|
44
|
+
</Queries>
|
|
45
|
+
</Suspense>
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
export default Providers;
|
package/package.json
CHANGED
package/Recaptcha/Recaptcha.tsx
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { GoogleReCaptchaProvider } from 'react-google-recaptcha-v3';
|
|
2
|
-
import { useGetSettings } from '../services';
|
|
3
|
-
|
|
4
|
-
interface Props {
|
|
5
|
-
children: JSX.Element
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
// Edited: Ferjolt Ozuni - Date: 2026-07-25
|
|
9
|
-
// The captcha provider + site key are now the label's runtime settings
|
|
10
|
-
// (/api/settings/get, managed at /admin/label?type=captcha) instead of the
|
|
11
|
-
// build-time VITE_RECAPTCHA_KEY - which stays only as a fallback for labels
|
|
12
|
-
// without a captcha config yet. The Google provider script is mounted ONLY
|
|
13
|
-
// when reCAPTCHA is the active provider; Cloudflare Turnstile needs no
|
|
14
|
-
// app-wide provider (its script is loaded on demand by useRecaptcha in
|
|
15
|
-
// @autobusal/hooks). NB: this component now calls useGetSettings(), so it
|
|
16
|
-
// must sit INSIDE <Queries> (see Providers.tsx).
|
|
17
|
-
const Recaptcha = ({ children }: Props): JSX.Element => {
|
|
18
|
-
const { data } = useGetSettings();
|
|
19
|
-
|
|
20
|
-
const provider = data?.captcha?.provider
|
|
21
|
-
?? (import.meta.env.VITE_RECAPTCHA_KEY ? 'recaptcha' : 'none');
|
|
22
|
-
|
|
23
|
-
const siteKey = data?.captcha?.site_key ?? import.meta.env.VITE_RECAPTCHA_KEY;
|
|
24
|
-
|
|
25
|
-
if (provider !== 'recaptcha' || !siteKey) {
|
|
26
|
-
return children;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<GoogleReCaptchaProvider reCaptchaKey={ siteKey }>
|
|
31
|
-
{ children }
|
|
32
|
-
</GoogleReCaptchaProvider>
|
|
33
|
-
);
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export default Recaptcha;
|