@ovineko/spa-guard 0.0.1-alpha-1 → 0.0.1-alpha-2

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/README.md CHANGED
@@ -14,13 +14,18 @@ Peer dependencies vary by integration - see sections below for specific requirem
14
14
 
15
15
  - ✅ **Automatic chunk load error detection** - Handles `vite:preloadError`, dynamic imports, and chunk failures across Chrome, Firefox, and Safari
16
16
  - ✅ **Intelligent retry with cache busting** - Uses query parameters with UUID to bypass HTML cache after deployments
17
+ - ✅ **Smart retry cycle reset** - Automatically resets retry cycle when enough time has passed since last reload
17
18
  - ✅ **Configurable retry delays** - Flexible delay arrays (e.g., `[1000, 2000, 5000]`) instead of simple max attempts
19
+ - ✅ **Infinite loop protection** - Prevents rapid retry resets with configurable minimum time between resets
18
20
  - ✅ **Graceful fallback UI** - Shows user-friendly error screen after all retry attempts are exhausted
21
+ - ✅ **Configurable injection target** - Inject fallback UI into any element via CSS selector (default: `body`)
22
+ - ✅ **Error filtering** - Filter out specific errors from logging and reporting via `ignoredErrors` option
19
23
  - ✅ **Deep error serialization** - Captures detailed error information for server-side analysis
20
24
  - ✅ **Smart beacon reporting** - Sends error reports only after retry exhaustion to prevent spam
21
- - ✅ **Dual build system** - Production minified (5KB) and trace verbose (10KB) builds for different environments
25
+ - ✅ **Dual build system** - Production minified (~5.9 KB) and trace minified (~7.3 KB) builds for different environments
22
26
  - ✅ **Global error listeners** - Captures `error`, `unhandledrejection`, and `securitypolicyviolation` events
23
27
  - ✅ **Vite plugin for inline script injection** - Runs before all chunks to catch early errors
28
+ - ✅ **HTML minification** - Automatically minifies fallback HTML to reduce bundle size
24
29
  - ✅ **Fastify server integration** - Ready-to-use plugin for handling error reports
25
30
  - ✅ **React Router v7 integration** - Works seamlessly with React Router error boundaries
26
31
  - ✅ **TypeScript support** - Full type definitions with all exports
@@ -41,18 +46,22 @@ export default defineConfig({
41
46
  spaGuardVitePlugin({
42
47
  // Production configuration
43
48
  reloadDelays: [1000, 2000, 5000], // 3 attempts with increasing delays
44
- fallbackHtml: `
45
- <div style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif">
46
- <div style="text-align:center">
47
- <h1>Something went wrong</h1>
48
- <p>Please refresh the page to continue.</p>
49
- <button onclick="location.reload()">Refresh Page</button>
49
+ fallback: {
50
+ html: `
51
+ <div style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif">
52
+ <div style="text-align:center">
53
+ <h1>Something went wrong</h1>
54
+ <p>Please refresh the page to continue.</p>
55
+ <button onclick="location.reload()">Refresh Page</button>
56
+ </div>
50
57
  </div>
51
- </div>
52
- `,
58
+ `,
59
+ selector: "body", // CSS selector where to inject fallback UI (default: "body")
60
+ },
53
61
  reportBeacon: {
54
62
  endpoint: "/api/beacon",
55
63
  },
64
+ ignoredErrors: [], // Filter out specific error messages from reporting
56
65
  useRetryId: true, // Use query parameters for cache busting (default: true)
57
66
  }),
58
67
  react(),
@@ -131,6 +140,48 @@ reloadDelays: [1000, 2000, 5000]; // Default
131
140
  - **Attempt 3**: Wait 5000ms (5s) → reload with `?spaGuardRetryAttempt=3`
132
141
  - **Exhausted**: Show fallback UI, send beacon to server
133
142
 
143
+ ### Smart Retry Cycle Reset
144
+
145
+ When `enableRetryReset: true` (default), spa-guard automatically resets the retry cycle if enough time has passed since the last reload. This prevents retry attempts from accumulating unnecessarily.
146
+
147
+ **How it works:**
148
+
149
+ 1. User is on `?spaGuardRetryAttempt=2` after waiting 2 seconds for a reload
150
+ 2. User successfully uses the app for 5 seconds (longer than the retry delay)
151
+ 3. A new error occurs
152
+ 4. Instead of continuing to attempt 3, spa-guard:
153
+ - Clears retry parameters from URL (without page reload)
154
+ - Starts a fresh retry cycle from attempt 0
155
+ - Generates a new retry ID
156
+
157
+ **Benefits:**
158
+
159
+ - Clean URLs when retries are successful
160
+ - Each error context gets its own independent retry cycle
161
+ - Better user experience for long-running sessions
162
+
163
+ **Infinite loop protection:**
164
+
165
+ The `minTimeBetweenResets` option (default: 5000ms) prevents infinite reset loops by ensuring a reset can only happen if the previous reset was at least this many milliseconds ago.
166
+
167
+ **Example:**
168
+
169
+ ```typescript
170
+ spaGuardVitePlugin({
171
+ enableRetryReset: true, // Enable smart reset (default)
172
+ minTimeBetweenResets: 5000, // Minimum 5s between resets (default)
173
+ reloadDelays: [1000, 2000, 5000],
174
+ });
175
+ ```
176
+
177
+ **Disable if needed:**
178
+
179
+ ```typescript
180
+ spaGuardVitePlugin({
181
+ enableRetryReset: false, // Keep old behavior - retry params persist
182
+ });
183
+ ```
184
+
134
185
  ### Secure Random ID Generation
135
186
 
136
187
  Three-tier fallback chain for maximum compatibility:
@@ -152,14 +203,22 @@ spaGuardVitePlugin({
152
203
  // Retry configuration
153
204
  reloadDelays: [1000, 2000, 5000], // Array of delays in milliseconds
154
205
  useRetryId: true, // Use query parameters for cache busting (default: true)
206
+ enableRetryReset: true, // Auto-reset retry cycle when enough time passes (default: true)
207
+ minTimeBetweenResets: 5000, // Min time between retry resets in ms (default: 5000)
155
208
 
156
209
  // Fallback UI configuration
157
- fallbackHtml: `
158
- <div style="...">
159
- <h1>Something went wrong</h1>
160
- <button onclick="location.reload()">Refresh</button>
161
- </div>
162
- `,
210
+ fallback: {
211
+ html: `
212
+ <div style="...">
213
+ <h1>Something went wrong</h1>
214
+ <button onclick="location.reload()">Refresh</button>
215
+ </div>
216
+ `,
217
+ selector: "body", // CSS selector for injection target (default: "body")
218
+ },
219
+
220
+ // Error filtering
221
+ ignoredErrors: [], // Array of error message substrings to ignore
163
222
 
164
223
  // Beacon reporting
165
224
  reportBeacon: {
@@ -185,7 +244,16 @@ spaGuardVitePlugin({
185
244
  interface Options {
186
245
  reloadDelays?: number[]; // Array of retry delays in ms (default: [1000, 2000, 5000])
187
246
  useRetryId?: boolean; // Use query params for cache busting (default: true)
188
- fallbackHtml?: string; // Custom error UI HTML (default: basic error screen)
247
+ enableRetryReset?: boolean; // Auto-reset retry cycle when enough time passes (default: true)
248
+ minTimeBetweenResets?: number; // Min time between retry resets to prevent loops (default: 5000)
249
+
250
+ fallback?: {
251
+ html?: string; // Custom error UI HTML (default: basic error screen)
252
+ selector?: string; // CSS selector for injection target (default: "body")
253
+ };
254
+
255
+ ignoredErrors?: string[]; // Error message substrings to filter out (default: [])
256
+
189
257
  reportBeacon?: {
190
258
  endpoint?: string; // Server endpoint for beacon reports
191
259
  };
@@ -202,15 +270,21 @@ interface VitePluginOptions extends Options {
202
270
  {
203
271
  reloadDelays: [1000, 2000, 5000],
204
272
  useRetryId: true,
205
- fallbackHtml: `
206
- <div style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif">
207
- <div style="text-align:center">
208
- <h1>Something went wrong</h1>
209
- <p>Please refresh the page to continue.</p>
210
- <button onclick="location.reload()">Refresh Page</button>
273
+ enableRetryReset: true,
274
+ minTimeBetweenResets: 5000,
275
+ fallback: {
276
+ html: `
277
+ <div style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif">
278
+ <div style="text-align:center">
279
+ <h1>Something went wrong</h1>
280
+ <p>Please refresh the page to continue.</p>
281
+ <button onclick="location.reload()">Refresh Page</button>
282
+ </div>
211
283
  </div>
212
- </div>
213
- `,
284
+ `,
285
+ selector: "body",
286
+ },
287
+ ignoredErrors: [],
214
288
  }
215
289
  ```
216
290
 
@@ -479,12 +553,21 @@ interface FastifySPAGuardOptions {
479
553
 
480
554
  ```typescript
481
555
  interface Options {
482
- fallbackHtml?: string; // Custom error UI HTML
483
556
  reloadDelays?: number[]; // Retry delays in ms (default: [1000, 2000, 5000])
557
+ useRetryId?: boolean; // Use query params for cache busting (default: true)
558
+ enableRetryReset?: boolean; // Auto-reset retry cycle when enough time passes (default: true)
559
+ minTimeBetweenResets?: number; // Min time between retry resets in ms (default: 5000)
560
+
561
+ fallback?: {
562
+ html?: string; // Custom error UI HTML
563
+ selector?: string; // CSS selector for injection target (default: "body")
564
+ };
565
+
566
+ ignoredErrors?: string[]; // Error message substrings to filter out (default: [])
567
+
484
568
  reportBeacon?: {
485
569
  endpoint?: string; // Error reporting endpoint
486
570
  };
487
- useRetryId?: boolean; // Use query params for cache busting (default: true)
488
571
  }
489
572
  ```
490
573
 
@@ -528,15 +611,46 @@ From `@ovineko/spa-guard/schema/parse`:
528
611
 
529
612
  - `parseBeacon(data)` - Parse and validate beacon data
530
613
 
614
+ ### Runtime Exports
615
+
616
+ From `@ovineko/spa-guard/runtime`:
617
+
618
+ - `getState()` - Get current spa-guard state (currentAttempt, isFallbackShown, isWaiting, lastRetryResetTime, lastResetRetryId)
619
+ - `subscribeToState(callback)` - Subscribe to state changes, returns unsubscribe function
620
+ - `SpaGuardState` - TypeScript type for state object
621
+
622
+ **Example:**
623
+
624
+ ```typescript
625
+ import { getState, subscribeToState } from "@ovineko/spa-guard/runtime";
626
+
627
+ // Get current state
628
+ const state = getState();
629
+ console.log("Current attempt:", state.currentAttempt);
630
+ console.log("Is fallback shown:", state.isFallbackShown);
631
+ console.log("Is waiting for retry:", state.isWaiting);
632
+ console.log("Last retry reset time:", state.lastRetryResetTime);
633
+ console.log("Last reset retry ID:", state.lastResetRetryId);
634
+
635
+ // Subscribe to state changes
636
+ const unsubscribe = subscribeToState((state) => {
637
+ console.log("State changed:", state);
638
+ });
639
+
640
+ // Later: unsubscribe
641
+ unsubscribe();
642
+ ```
643
+
531
644
  ## Module Exports
532
645
 
533
- spa-guard provides 8 export entry points:
646
+ spa-guard provides 9 export entry points:
534
647
 
535
648
  | Export | Description | Peer Dependencies |
536
649
  | ------------------------ | -------------------------------------------- | -------------------------------------- |
537
650
  | `.` | Core functionality (events, listen, options) | None |
538
651
  | `./schema` | BeaconSchema type definitions | `typebox@^1` |
539
652
  | `./schema/parse` | Beacon parsing utilities | `typebox@^1` |
653
+ | `./runtime` | Runtime state management and subscriptions | None |
540
654
  | `./react` | React integration (placeholder) | `react@^19` |
541
655
  | `./react-router` | React Router integration | `react@^19`, `react-router@^7` |
542
656
  | `./fastify` | Fastify server plugin | None |
@@ -561,8 +675,8 @@ import { fastifySPAGuard } from "@ovineko/spa-guard/fastify";
561
675
 
562
676
  ## Build Sizes
563
677
 
564
- - **Production:** `dist-inline/index.js` ~5KB minified (Terser)
565
- - **Trace:** `dist-inline-trace/index.js` ~10KB unminified (debug)
678
+ - **Production:** `dist-inline/index.js` ~5.9 KB minified (Terser)
679
+ - **Trace:** `dist-inline-trace/index.js` ~7.3 KB minified (Terser)
566
680
  - **Main library:** `dist/` varies by export
567
681
 
568
682
  ## Advanced Usage
@@ -582,58 +696,98 @@ spaGuardVitePlugin({
582
696
 
583
697
  ### Custom Fallback UI
584
698
 
585
- Provide fully custom HTML for error screen (injected into `document.body`):
699
+ Provide fully custom HTML for error screen:
586
700
 
587
701
  ```typescript
588
702
  spaGuardVitePlugin({
589
- fallbackHtml: `
590
- <style>
591
- .spa-guard-error {
592
- font-family: system-ui, -apple-system, sans-serif;
593
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
594
- color: white;
595
- display: flex;
596
- align-items: center;
597
- justify-content: center;
598
- height: 100vh;
599
- margin: 0;
600
- padding: 20px;
601
- box-sizing: border-box;
602
- }
603
- .spa-guard-container {
604
- max-width: 500px;
605
- text-align: center;
606
- background: rgba(255, 255, 255, 0.1);
607
- backdrop-filter: blur(10px);
608
- padding: 40px;
609
- border-radius: 20px;
610
- }
611
- .spa-guard-button {
612
- background: white;
613
- color: #667eea;
614
- border: none;
615
- padding: 12px 24px;
616
- font-size: 16px;
617
- font-weight: 600;
618
- border-radius: 8px;
619
- cursor: pointer;
620
- margin-top: 20px;
621
- }
622
- .spa-guard-button:hover {
623
- transform: scale(1.05);
624
- }
625
- </style>
626
- <div class="spa-guard-error">
627
- <div class="spa-guard-container">
628
- <h1>⚠️ Application Error</h1>
629
- <p>We're experiencing technical difficulties. Please try reloading the page.</p>
630
- <button class="spa-guard-button" onclick="location.reload()">Reload Application</button>
703
+ fallback: {
704
+ html: `
705
+ <style>
706
+ .spa-guard-error {
707
+ font-family: system-ui, -apple-system, sans-serif;
708
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
709
+ color: white;
710
+ display: flex;
711
+ align-items: center;
712
+ justify-content: center;
713
+ height: 100vh;
714
+ margin: 0;
715
+ padding: 20px;
716
+ box-sizing: border-box;
717
+ }
718
+ .spa-guard-container {
719
+ max-width: 500px;
720
+ text-align: center;
721
+ background: rgba(255, 255, 255, 0.1);
722
+ backdrop-filter: blur(10px);
723
+ padding: 40px;
724
+ border-radius: 20px;
725
+ }
726
+ .spa-guard-button {
727
+ background: white;
728
+ color: #667eea;
729
+ border: none;
730
+ padding: 12px 24px;
731
+ font-size: 16px;
732
+ font-weight: 600;
733
+ border-radius: 8px;
734
+ cursor: pointer;
735
+ margin-top: 20px;
736
+ }
737
+ .spa-guard-button:hover {
738
+ transform: scale(1.05);
739
+ }
740
+ </style>
741
+ <div class="spa-guard-error">
742
+ <div class="spa-guard-container">
743
+ <h1>⚠️ Application Error</h1>
744
+ <p>We're experiencing technical difficulties. Please try reloading the page.</p>
745
+ <button class="spa-guard-button" onclick="location.reload()">Reload Application</button>
746
+ </div>
631
747
  </div>
632
- </div>
633
- `,
748
+ `,
749
+ selector: "body", // Inject into document.body
750
+ },
751
+ });
752
+ ```
753
+
754
+ ### Custom Injection Target
755
+
756
+ Inject fallback UI into a specific element instead of `<body>`:
757
+
758
+ ```typescript
759
+ spaGuardVitePlugin({
760
+ fallback: {
761
+ html: `<div>Error occurred. <button onclick="location.reload()">Retry</button></div>`,
762
+ selector: "#app", // Inject into <div id="app">
763
+ },
634
764
  });
635
765
  ```
636
766
 
767
+ ### Error Filtering
768
+
769
+ Filter out specific errors from being logged or reported:
770
+
771
+ ```typescript
772
+ spaGuardVitePlugin({
773
+ ignoredErrors: [
774
+ "ResizeObserver loop", // Ignore benign ResizeObserver errors
775
+ "Non-Error promise rejection", // Ignore specific promise rejections
776
+ "Script error", // Ignore generic script errors from third-party scripts
777
+ ],
778
+ reportBeacon: {
779
+ endpoint: "/api/beacon",
780
+ },
781
+ });
782
+ ```
783
+
784
+ **How it works:**
785
+
786
+ - Errors containing any of the `ignoredErrors` substrings will not be logged to console
787
+ - Beacons for filtered errors will not be sent to the server
788
+ - Useful for filtering out known benign errors or third-party script noise
789
+ - Case-sensitive substring matching
790
+
637
791
  ### Custom Retry Strategy
638
792
 
639
793
  Adjust retry delays for different environments:
@@ -0,0 +1,17 @@
1
+ import {
2
+ name
3
+ } from "./chunk-RP52SPBK.js";
4
+
5
+ // src/common/constants.ts
6
+ var optionsWindowKey = "__SPA_GUARD_OPTIONS__";
7
+ var eventSubscribersWindowKey = /* @__PURE__ */ Symbol.for(`${name}:event-subscribers`);
8
+ var internalConfigWindowKey = /* @__PURE__ */ Symbol.for(`${name}:internal-config`);
9
+ var RETRY_ID_PARAM = "spaGuardRetryId";
10
+ var RETRY_ATTEMPT_PARAM = "spaGuardRetryAttempt";
11
+
12
+ export {
13
+ optionsWindowKey,
14
+ eventSubscribersWindowKey,
15
+ RETRY_ID_PARAM,
16
+ RETRY_ATTEMPT_PARAM
17
+ };
@@ -0,0 +1,17 @@
1
+ // src/schema/index.ts
2
+ import { Type } from "typebox";
3
+ var beaconSchema = Type.Object(
4
+ {
5
+ errorMessage: Type.Optional(Type.String()),
6
+ eventMessage: Type.Optional(Type.String()),
7
+ eventName: Type.Optional(Type.String()),
8
+ retryAttempt: Type.Optional(Type.Number()),
9
+ retryId: Type.Optional(Type.String()),
10
+ serialized: Type.Optional(Type.String())
11
+ },
12
+ { additionalProperties: false }
13
+ );
14
+
15
+ export {
16
+ beaconSchema
17
+ };
@@ -0,0 +1,19 @@
1
+ import {
2
+ beaconSchema
3
+ } from "./chunk-7HWVSDJZ.js";
4
+
5
+ // src/schema/parse.ts
6
+ import { Value } from "typebox/value";
7
+ var parseBeacon = (value) => {
8
+ const cleaned = Value.Clean(beaconSchema, value);
9
+ const isValid = Value.Check(beaconSchema, cleaned);
10
+ if (!isValid) {
11
+ const errors = [...Value.Errors(beaconSchema, cleaned)];
12
+ throw new Error(`Beacon validation failed: ${JSON.stringify(errors)}`);
13
+ }
14
+ return cleaned;
15
+ };
16
+
17
+ export {
18
+ parseBeacon
19
+ };
@@ -1,17 +1,10 @@
1
+ import {
2
+ optionsWindowKey
3
+ } from "./chunk-66I6YML2.js";
1
4
  import {
2
5
  __export
3
6
  } from "./chunk-MLKGABMK.js";
4
7
 
5
- // package.json
6
- var name = "@ovineko/spa-guard";
7
-
8
- // src/common/constants.ts
9
- var optionsWindowKey = "__SPA_GUARD_OPTIONS__";
10
- var eventSubscribersWindowKey = /* @__PURE__ */ Symbol.for(`${name}:event-subscribers`);
11
- var internalConfigWindowKey = /* @__PURE__ */ Symbol.for(`${name}:internal-config`);
12
- var RETRY_ID_PARAM = "spaGuardRetryId";
13
- var RETRY_ATTEMPT_PARAM = "spaGuardRetryAttempt";
14
-
15
8
  // src/common/options.ts
16
9
  var options_exports = {};
17
10
  __export(options_exports, {
@@ -20,11 +13,17 @@ __export(options_exports, {
20
13
  });
21
14
 
22
15
  // src/common/fallbackHtml.generated.ts
23
- var defaultFallbackHtml = `<div style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif"><div style="text-align:center"><h1>Something went wrong</h1><p>Please refresh the page to continue.</p><button onclick="location.reload()">Refresh Page</button></div></div>`;
16
+ var defaultFallbackHtml = `<div style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif"><div style="text-align:center"><h1>Something went wrong</h1><p>Please refresh the page to continue.</p><button onclick="location.reload()">Refresh Page</button><p style="margin-top:20px;font-size:12px;color:#999">Error ID: <span class="spa-guard-retry-id"></span></p></div></div>`;
24
17
 
25
18
  // src/common/options.ts
26
19
  var defaultOptions = {
27
- fallbackHtml: defaultFallbackHtml,
20
+ enableRetryReset: true,
21
+ fallback: {
22
+ html: defaultFallbackHtml,
23
+ selector: "body"
24
+ },
25
+ ignoredErrors: [],
26
+ minTimeBetweenResets: 5e3,
28
27
  reloadDelays: [1e3, 2e3, 5e3],
29
28
  useRetryId: true
30
29
  };
@@ -33,6 +32,10 @@ var getOptions = () => {
33
32
  return {
34
33
  ...defaultOptions,
35
34
  ...windowOptions,
35
+ fallback: {
36
+ ...defaultOptions.fallback,
37
+ ...windowOptions?.fallback
38
+ },
36
39
  reportBeacon: {
37
40
  ...defaultOptions.reportBeacon,
38
41
  ...windowOptions?.reportBeacon
@@ -41,11 +44,6 @@ var getOptions = () => {
41
44
  };
42
45
 
43
46
  export {
44
- name,
45
- optionsWindowKey,
46
- eventSubscribersWindowKey,
47
- RETRY_ID_PARAM,
48
- RETRY_ATTEMPT_PARAM,
49
47
  getOptions,
50
48
  options_exports
51
49
  };
@@ -0,0 +1,104 @@
1
+ import {
2
+ getLastRetryResetInfo,
3
+ getRetryStateFromUrl,
4
+ subscribe
5
+ } from "./chunk-Z3Y4C3HZ.js";
6
+
7
+ // src/runtime/state.ts
8
+ var getInitialStateFromUrl = () => {
9
+ const resetInfo = getLastRetryResetInfo();
10
+ if (globalThis.window === void 0) {
11
+ return {
12
+ currentAttempt: 0,
13
+ isFallbackShown: false,
14
+ isWaiting: false,
15
+ lastResetRetryId: resetInfo?.previousRetryId,
16
+ lastRetryResetTime: resetInfo?.timestamp
17
+ };
18
+ }
19
+ const retryState = getRetryStateFromUrl();
20
+ if (!retryState) {
21
+ return {
22
+ currentAttempt: 0,
23
+ isFallbackShown: false,
24
+ isWaiting: false,
25
+ lastResetRetryId: resetInfo?.previousRetryId,
26
+ lastRetryResetTime: resetInfo?.timestamp
27
+ };
28
+ }
29
+ if (retryState.retryAttempt === -1) {
30
+ return {
31
+ currentAttempt: 0,
32
+ isFallbackShown: true,
33
+ isWaiting: false,
34
+ lastResetRetryId: resetInfo?.previousRetryId,
35
+ lastRetryResetTime: resetInfo?.timestamp
36
+ };
37
+ }
38
+ return {
39
+ currentAttempt: retryState.retryAttempt,
40
+ isFallbackShown: false,
41
+ isWaiting: false,
42
+ lastResetRetryId: resetInfo?.previousRetryId,
43
+ lastRetryResetTime: resetInfo?.timestamp
44
+ };
45
+ };
46
+ var currentState = getInitialStateFromUrl();
47
+ var stateSubscribers = /* @__PURE__ */ new Set();
48
+ var updateState = (nextState) => {
49
+ currentState = nextState;
50
+ stateSubscribers.forEach((cb) => cb(currentState));
51
+ };
52
+ subscribe((event) => {
53
+ switch (event.name) {
54
+ case "fallback-ui-shown": {
55
+ updateState({
56
+ ...currentState,
57
+ isFallbackShown: true
58
+ });
59
+ break;
60
+ }
61
+ case "retry-attempt": {
62
+ updateState({
63
+ ...currentState,
64
+ currentAttempt: event.attempt,
65
+ isFallbackShown: false,
66
+ isWaiting: true
67
+ });
68
+ break;
69
+ }
70
+ case "retry-exhausted": {
71
+ updateState({
72
+ ...currentState,
73
+ currentAttempt: event.finalAttempt,
74
+ isFallbackShown: false,
75
+ isWaiting: false
76
+ });
77
+ break;
78
+ }
79
+ case "retry-reset": {
80
+ updateState({
81
+ ...currentState,
82
+ currentAttempt: 0,
83
+ isFallbackShown: false,
84
+ isWaiting: false,
85
+ lastResetRetryId: event.previousRetryId,
86
+ lastRetryResetTime: Date.now()
87
+ });
88
+ break;
89
+ }
90
+ }
91
+ });
92
+ var getState = () => currentState;
93
+ var subscribeToState = (cb) => {
94
+ cb(currentState);
95
+ stateSubscribers.add(cb);
96
+ return () => {
97
+ stateSubscribers.delete(cb);
98
+ };
99
+ };
100
+
101
+ export {
102
+ getState,
103
+ subscribeToState
104
+ };
@@ -0,0 +1,6 @@
1
+ // package.json
2
+ var name = "@ovineko/spa-guard";
3
+
4
+ export {
5
+ name
6
+ };