@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 +228 -74
- package/dist/chunk-66I6YML2.js +17 -0
- package/dist/chunk-7HWVSDJZ.js +17 -0
- package/dist/chunk-AB733L4R.js +19 -0
- package/dist/{chunk-VWV6L4Q2.js → chunk-FQCDQYOP.js} +15 -17
- package/dist/chunk-KHXP4ZNT.js +104 -0
- package/dist/chunk-RP52SPBK.js +6 -0
- package/dist/chunk-Z3Y4C3HZ.js +193 -0
- package/dist/common/events/index.d.ts +1 -1
- package/dist/common/events/types.d.ts +28 -5
- package/dist/common/fallbackHtml.generated.d.ts +1 -1
- package/dist/common/index.js +290 -178
- package/dist/common/lastReloadTime.d.ts +17 -0
- package/dist/common/listen/internal.d.ts +1 -1
- package/dist/common/options.d.ts +31 -1
- package/dist/common/retryState.d.ts +12 -0
- package/dist/common/shouldIgnore.d.ts +9 -0
- package/dist/fastify/index.d.ts +14 -27
- package/dist/fastify/index.js +41 -25
- package/dist/inline-trace/index.d.ts +1 -0
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +30 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +12 -0
- package/dist/runtime/state.d.ts +20 -0
- package/dist/schema/index.d.ts +3 -1
- package/dist/schema/index.js +1 -1
- package/dist/schema/parse.d.ts +2 -6
- package/dist/schema/parse.js +2 -2
- package/dist/vite-plugin/index.js +27 -4
- package/dist-inline/index.js +1 -1
- package/dist-inline-trace/index.js +1 -360
- package/package.json +30 -1
- package/dist/chunk-BL4EG6R7.js +0 -14
- package/dist/chunk-XV2YCVOR.js +0 -12
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 (
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
<div style="
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
<
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
<
|
|
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
|
-
|
|
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
|
|
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` ~
|
|
565
|
-
- **Trace:** `dist-inline-trace/index.js` ~
|
|
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
|
|
699
|
+
Provide fully custom HTML for error screen:
|
|
586
700
|
|
|
587
701
|
```typescript
|
|
588
702
|
spaGuardVitePlugin({
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
<div class="spa-guard-
|
|
628
|
-
<
|
|
629
|
-
|
|
630
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
};
|