@scoperat/tracker 0.3.2 → 0.4.1
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 +9 -8
- package/dist/index.d.ts +30 -0
- package/dist/index.js +194 -46
- package/dist/index.js.map +1 -1
- package/dist/widget/index.js +194 -46
- package/dist/widget/index.js.map +1 -1
- package/dist/widget.global.js +97 -96
- package/dist/widget.global.js.map +1 -1
- package/package.json +29 -17
package/README.md
CHANGED
|
@@ -32,13 +32,14 @@ scoperat.reset();
|
|
|
32
32
|
|
|
33
33
|
Initialize the tracker. Must be called before any other method.
|
|
34
34
|
|
|
35
|
-
| Option | Type | Default | Description
|
|
36
|
-
| --------------- | ------------------------- | ------------------------ |
|
|
37
|
-
| `endpoint` | `string` | `window.location.origin` | API base URL
|
|
38
|
-
| `flushAt` | `number` | `10` | Flush after this many events
|
|
39
|
-
| `flushInterval` | `number` | `3000` | Auto-flush interval in ms
|
|
40
|
-
| `timeout` | `number` | `10000` | Request timeout in ms
|
|
41
|
-
| `
|
|
35
|
+
| Option | Type | Default | Description |
|
|
36
|
+
| --------------- | ------------------------- | ------------------------ | ------------------------------------ |
|
|
37
|
+
| `endpoint` | `string` | `window.location.origin` | API base URL |
|
|
38
|
+
| `flushAt` | `number` | `10` | Flush after this many events |
|
|
39
|
+
| `flushInterval` | `number` | `3000` | Auto-flush interval in ms |
|
|
40
|
+
| `timeout` | `number` | `10000` | Request timeout in ms |
|
|
41
|
+
| `maxQueueSize` | `number` | `1000` | Max buffered events (oldest dropped) |
|
|
42
|
+
| `onError` | `(error, events) => void` | — | Error callback |
|
|
42
43
|
|
|
43
44
|
### `scoperat.identify(userId, traits?)`
|
|
44
45
|
|
|
@@ -65,7 +66,7 @@ Flush remaining events and stop the auto-flush timer. Call before app teardown.
|
|
|
65
66
|
- **Auto-batching** — events are buffered and sent in batches for efficiency
|
|
66
67
|
- **Anonymous IDs** — persistent anonymous ID via `localStorage`, auto-generated on first visit
|
|
67
68
|
- **sendBeacon fallback** — uses `navigator.sendBeacon` on page hide for reliable delivery
|
|
68
|
-
- **Retry on failure** —
|
|
69
|
+
- **Retry on failure** — transient failures (network errors, 5xx, 408/429) are re-queued and retried with exponential backoff; non-retryable 4xx batches are dropped
|
|
69
70
|
- **Zero dependencies** — no runtime dependencies
|
|
70
71
|
- **Tree-shakeable** — marked as side-effect-free for optimal bundling
|
|
71
72
|
- **ESM-only** — ships as pure ES modules
|
package/dist/index.d.ts
CHANGED
|
@@ -37,6 +37,12 @@ interface InitOptions {
|
|
|
37
37
|
flushInterval?: number;
|
|
38
38
|
timeout?: number;
|
|
39
39
|
onError?: (error: Error, events: TrackEvent[]) => void;
|
|
40
|
+
/**
|
|
41
|
+
* Maximum number of events buffered while the ingest API is
|
|
42
|
+
* unreachable. When full, the oldest non-error event is dropped
|
|
43
|
+
* (surfaced through `onError`). Defaults to 1000.
|
|
44
|
+
*/
|
|
45
|
+
maxQueueSize?: number;
|
|
40
46
|
/** Session inactivity timeout in ms. Defaults to 30 minutes. */
|
|
41
47
|
sessionTimeout?: number;
|
|
42
48
|
/** Automatically capture uncaught errors and unhandled rejections. Defaults to false. */
|
|
@@ -108,8 +114,16 @@ declare class TrackerImpl {
|
|
|
108
114
|
private timeout;
|
|
109
115
|
private onError?;
|
|
110
116
|
private buffer;
|
|
117
|
+
private maxQueueSize;
|
|
111
118
|
private timer;
|
|
112
119
|
private visibilityHandler;
|
|
120
|
+
private pageHideHandler;
|
|
121
|
+
private uninstallErrorHandlers;
|
|
122
|
+
/** Consecutive flush failures, drives exponential retry backoff. */
|
|
123
|
+
private flushFailures;
|
|
124
|
+
/** Timestamp before which automatic flushes are suppressed (backoff). */
|
|
125
|
+
private nextFlushAllowedAt;
|
|
126
|
+
private exceptionFlushScheduled;
|
|
113
127
|
private userId;
|
|
114
128
|
private orgId;
|
|
115
129
|
private traits;
|
|
@@ -188,7 +202,22 @@ declare class TrackerImpl {
|
|
|
188
202
|
addBreadcrumb(category: string, message: string, data?: Record<string, unknown>): void;
|
|
189
203
|
track(event: string, properties?: Record<string, unknown>): void;
|
|
190
204
|
track(event: TrackEvent): void;
|
|
205
|
+
/**
|
|
206
|
+
* Push an event onto the buffer, evicting the oldest event when the
|
|
207
|
+
* queue is full. `$exception` events are preferentially retained.
|
|
208
|
+
*/
|
|
209
|
+
private enqueue;
|
|
210
|
+
/** Flush unless we're inside a retry-backoff window. */
|
|
211
|
+
private maybeFlush;
|
|
212
|
+
private scheduleExceptionFlush;
|
|
191
213
|
flush(): Promise<FlushResult>;
|
|
214
|
+
/**
|
|
215
|
+
* Record a failed flush and push out the next automatic flush with
|
|
216
|
+
* exponential backoff (capped) so a down ingest endpoint isn't
|
|
217
|
+
* hammered on every `flushInterval` tick. Explicit `flush()` calls
|
|
218
|
+
* are unaffected.
|
|
219
|
+
*/
|
|
220
|
+
private recordFlushFailure;
|
|
192
221
|
/**
|
|
193
222
|
* Fire-and-forget flush that survives page unload.
|
|
194
223
|
*
|
|
@@ -208,6 +237,7 @@ declare class TrackerImpl {
|
|
|
208
237
|
refreshFlags(): Promise<void>;
|
|
209
238
|
shutdown(): Promise<void>;
|
|
210
239
|
}
|
|
240
|
+
|
|
211
241
|
declare const scoperat: TrackerImpl;
|
|
212
242
|
|
|
213
243
|
export { type EvaluatedFlag, type EventSource, FlagClient, type FlushResult, type IdentifyOptions, type IdentifyTraits, type InitOptions, type TrackEvent, scoperat as default };
|
package/dist/index.js
CHANGED
|
@@ -103,54 +103,107 @@ function getBrowserContext() {
|
|
|
103
103
|
|
|
104
104
|
// src/errors.ts
|
|
105
105
|
var DEDUP_COOLDOWN_MS = 6e4;
|
|
106
|
+
var MAX_DEDUP_ENTRIES = 200;
|
|
107
|
+
var MAX_MESSAGE_LENGTH = 2048;
|
|
108
|
+
var MAX_STACK_LENGTH = 16384;
|
|
109
|
+
var MAX_REASON_JSON_LENGTH = 1024;
|
|
106
110
|
var dedupMap = /* @__PURE__ */ new Map();
|
|
107
|
-
function
|
|
111
|
+
function truncate2(str, max) {
|
|
112
|
+
return str.length > max ? str.slice(0, max) + "\u2026" : str;
|
|
113
|
+
}
|
|
114
|
+
function fingerprint(type, message, stack) {
|
|
108
115
|
const firstFrame = stack?.split("\n")[1]?.trim() ?? "";
|
|
109
|
-
return `${type}:${firstFrame}`;
|
|
116
|
+
return `${type}:${truncate2(message, 100)}:${firstFrame}`;
|
|
110
117
|
}
|
|
111
|
-
function isDuplicate(
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
118
|
+
function isDuplicate(fp) {
|
|
119
|
+
const now = Date.now();
|
|
120
|
+
const lastSent = dedupMap.get(fp);
|
|
121
|
+
if (lastSent && now - lastSent < DEDUP_COOLDOWN_MS) return true;
|
|
122
|
+
dedupMap.set(fp, now);
|
|
123
|
+
pruneDedupMap(now);
|
|
115
124
|
return false;
|
|
116
125
|
}
|
|
126
|
+
function pruneDedupMap(now) {
|
|
127
|
+
if (dedupMap.size <= MAX_DEDUP_ENTRIES) return;
|
|
128
|
+
for (const [key, ts] of dedupMap) {
|
|
129
|
+
if (now - ts >= DEDUP_COOLDOWN_MS) dedupMap.delete(key);
|
|
130
|
+
}
|
|
131
|
+
for (const key of dedupMap.keys()) {
|
|
132
|
+
if (dedupMap.size <= MAX_DEDUP_ENTRIES) break;
|
|
133
|
+
dedupMap.delete(key);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
117
136
|
function sendException(tracker, props, getBreadcrumbs2) {
|
|
118
|
-
const fp =
|
|
137
|
+
const fp = fingerprint(props.type, props.message, props.stack);
|
|
119
138
|
if (isDuplicate(fp)) return;
|
|
120
139
|
tracker.track("$exception", {
|
|
121
140
|
...props,
|
|
141
|
+
message: truncate2(props.message, MAX_MESSAGE_LENGTH),
|
|
142
|
+
stack: props.stack ? truncate2(props.stack, MAX_STACK_LENGTH) : void 0,
|
|
122
143
|
context: { breadcrumbs: getBreadcrumbs2() }
|
|
123
144
|
});
|
|
124
145
|
}
|
|
146
|
+
function describeRejectionReason(reason) {
|
|
147
|
+
if (reason instanceof Error) {
|
|
148
|
+
return {
|
|
149
|
+
type: reason.name || "UnhandledRejection",
|
|
150
|
+
message: reason.message,
|
|
151
|
+
stack: reason.stack
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
if (typeof reason === "object" && reason !== null) {
|
|
155
|
+
let json;
|
|
156
|
+
try {
|
|
157
|
+
json = JSON.stringify(reason) ?? Object.prototype.toString.call(reason);
|
|
158
|
+
} catch {
|
|
159
|
+
json = Object.prototype.toString.call(reason);
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
type: "UnhandledRejection",
|
|
163
|
+
message: `Non-Error promise rejection: ${truncate2(json, MAX_REASON_JSON_LENGTH)}`,
|
|
164
|
+
stack: void 0
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
type: "UnhandledRejection",
|
|
169
|
+
message: String(reason),
|
|
170
|
+
stack: void 0
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
var uninstallHandlers = null;
|
|
125
174
|
function installGlobalHandlers(tracker, getBreadcrumbs2) {
|
|
126
|
-
if (typeof window === "undefined") return
|
|
127
|
-
|
|
128
|
-
|
|
175
|
+
if (typeof window === "undefined") return () => {
|
|
176
|
+
};
|
|
177
|
+
if (uninstallHandlers) return uninstallHandlers;
|
|
178
|
+
const onError = (event) => {
|
|
179
|
+
const error = event.error;
|
|
180
|
+
const message = error?.message || (typeof event.message === "string" && event.message ? event.message : "Unknown error");
|
|
181
|
+
const opaque = !error && /^Script error\.?$/.test(message);
|
|
129
182
|
sendException(
|
|
130
183
|
tracker,
|
|
131
184
|
{
|
|
132
|
-
type:
|
|
133
|
-
message
|
|
134
|
-
stack:
|
|
185
|
+
type: error?.name || "Error",
|
|
186
|
+
message,
|
|
187
|
+
stack: error?.stack,
|
|
135
188
|
handled: false,
|
|
136
189
|
mechanism: "onerror",
|
|
137
190
|
source: "browser",
|
|
138
|
-
filename,
|
|
139
|
-
lineno,
|
|
140
|
-
colno
|
|
191
|
+
filename: event.filename || void 0,
|
|
192
|
+
lineno: event.lineno || void 0,
|
|
193
|
+
colno: event.colno || void 0,
|
|
194
|
+
...opaque ? { opaque: true } : {}
|
|
141
195
|
},
|
|
142
196
|
getBreadcrumbs2
|
|
143
197
|
);
|
|
144
198
|
};
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
199
|
+
const onRejection = (event) => {
|
|
200
|
+
const { type, message, stack } = describeRejectionReason(event.reason);
|
|
148
201
|
sendException(
|
|
149
202
|
tracker,
|
|
150
203
|
{
|
|
151
|
-
type
|
|
152
|
-
message
|
|
153
|
-
stack
|
|
204
|
+
type,
|
|
205
|
+
message,
|
|
206
|
+
stack,
|
|
154
207
|
handled: false,
|
|
155
208
|
mechanism: "onunhandledrejection",
|
|
156
209
|
source: "browser"
|
|
@@ -158,6 +211,14 @@ function installGlobalHandlers(tracker, getBreadcrumbs2) {
|
|
|
158
211
|
getBreadcrumbs2
|
|
159
212
|
);
|
|
160
213
|
};
|
|
214
|
+
window.addEventListener("error", onError);
|
|
215
|
+
window.addEventListener("unhandledrejection", onRejection);
|
|
216
|
+
uninstallHandlers = () => {
|
|
217
|
+
window.removeEventListener("error", onError);
|
|
218
|
+
window.removeEventListener("unhandledrejection", onRejection);
|
|
219
|
+
uninstallHandlers = null;
|
|
220
|
+
};
|
|
221
|
+
return uninstallHandlers;
|
|
161
222
|
}
|
|
162
223
|
|
|
163
224
|
// src/flag-client.ts
|
|
@@ -262,7 +323,9 @@ var DEFAULT_FLUSH_AT = 10;
|
|
|
262
323
|
var DEFAULT_FLUSH_INTERVAL = 3e3;
|
|
263
324
|
var DEFAULT_TIMEOUT = 1e4;
|
|
264
325
|
var DEFAULT_SESSION_TIMEOUT = 30 * 60 * 1e3;
|
|
326
|
+
var DEFAULT_MAX_QUEUE_SIZE = 1e3;
|
|
265
327
|
var MAX_BATCH_SIZE = 100;
|
|
328
|
+
var MAX_RETRY_BACKOFF = 6e4;
|
|
266
329
|
var ANON_ID_KEY = "__scoperat_anon_id";
|
|
267
330
|
var SESSION_ID_KEY = "__scoperat_session_id";
|
|
268
331
|
var LAST_ACTIVITY_KEY = "__scoperat_last_activity";
|
|
@@ -303,8 +366,16 @@ var TrackerImpl = class {
|
|
|
303
366
|
timeout = DEFAULT_TIMEOUT;
|
|
304
367
|
onError;
|
|
305
368
|
buffer = [];
|
|
369
|
+
maxQueueSize = DEFAULT_MAX_QUEUE_SIZE;
|
|
306
370
|
timer = null;
|
|
307
371
|
visibilityHandler = null;
|
|
372
|
+
pageHideHandler = null;
|
|
373
|
+
uninstallErrorHandlers = null;
|
|
374
|
+
/** Consecutive flush failures, drives exponential retry backoff. */
|
|
375
|
+
flushFailures = 0;
|
|
376
|
+
/** Timestamp before which automatic flushes are suppressed (backoff). */
|
|
377
|
+
nextFlushAllowedAt = 0;
|
|
378
|
+
exceptionFlushScheduled = false;
|
|
308
379
|
userId;
|
|
309
380
|
orgId;
|
|
310
381
|
traits = {};
|
|
@@ -322,12 +393,12 @@ var TrackerImpl = class {
|
|
|
322
393
|
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
323
394
|
this.onError = options.onError;
|
|
324
395
|
this.sessionTimeout = options.sessionTimeout ?? DEFAULT_SESSION_TIMEOUT;
|
|
396
|
+
this.maxQueueSize = options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE;
|
|
325
397
|
this.anonymousId = getOrCreateAnonId();
|
|
326
398
|
if (this.timer) clearInterval(this.timer);
|
|
327
399
|
this.timer = setInterval(() => {
|
|
328
400
|
if (this.buffer.length > 0) {
|
|
329
|
-
this.
|
|
330
|
-
});
|
|
401
|
+
this.maybeFlush();
|
|
331
402
|
}
|
|
332
403
|
}, this.flushInterval);
|
|
333
404
|
if (typeof window !== "undefined") {
|
|
@@ -340,9 +411,18 @@ var TrackerImpl = class {
|
|
|
340
411
|
}
|
|
341
412
|
};
|
|
342
413
|
window.addEventListener("visibilitychange", this.visibilityHandler);
|
|
414
|
+
if (this.pageHideHandler) {
|
|
415
|
+
window.removeEventListener("pagehide", this.pageHideHandler);
|
|
416
|
+
}
|
|
417
|
+
this.pageHideHandler = () => {
|
|
418
|
+
if (this.buffer.length > 0) {
|
|
419
|
+
this.flushSync();
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
window.addEventListener("pagehide", this.pageHideHandler);
|
|
343
423
|
}
|
|
344
424
|
if (options.captureErrors) {
|
|
345
|
-
installGlobalHandlers(this, getBreadcrumbs);
|
|
425
|
+
this.uninstallErrorHandlers = installGlobalHandlers(this, getBreadcrumbs);
|
|
346
426
|
startBreadcrumbCollection();
|
|
347
427
|
}
|
|
348
428
|
this._flagClient?.destroy();
|
|
@@ -597,7 +677,7 @@ var TrackerImpl = class {
|
|
|
597
677
|
emitSessionEvent(event, sessionId, reason, occurredAt) {
|
|
598
678
|
if (!this.writeKey) return;
|
|
599
679
|
const traitsContext = Object.keys(this.traits).length > 0 ? { traits: this.traits } : {};
|
|
600
|
-
this.
|
|
680
|
+
this.enqueue({
|
|
601
681
|
event,
|
|
602
682
|
properties: { reason },
|
|
603
683
|
occurred_at: occurredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -643,17 +723,20 @@ var TrackerImpl = class {
|
|
|
643
723
|
}
|
|
644
724
|
captureException(error, context) {
|
|
645
725
|
if (!this.writeKey) return;
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
726
|
+
const { mechanism, ...extra } = context ?? {};
|
|
727
|
+
sendException(
|
|
728
|
+
this,
|
|
729
|
+
{
|
|
730
|
+
type: error.name || "Error",
|
|
731
|
+
message: error.message,
|
|
732
|
+
stack: error.stack,
|
|
733
|
+
handled: true,
|
|
734
|
+
mechanism: typeof mechanism === "string" ? mechanism : "manual",
|
|
735
|
+
source: "browser",
|
|
736
|
+
extra: Object.keys(extra).length > 0 ? extra : void 0
|
|
737
|
+
},
|
|
738
|
+
getBreadcrumbs
|
|
739
|
+
);
|
|
657
740
|
}
|
|
658
741
|
addBreadcrumb(category, message, data) {
|
|
659
742
|
addBreadcrumb({
|
|
@@ -682,12 +765,44 @@ var TrackerImpl = class {
|
|
|
682
765
|
...evt.context
|
|
683
766
|
}
|
|
684
767
|
};
|
|
685
|
-
this.
|
|
686
|
-
if (
|
|
687
|
-
this.
|
|
688
|
-
|
|
768
|
+
this.enqueue(enriched);
|
|
769
|
+
if (enriched.event === "$exception") {
|
|
770
|
+
this.scheduleExceptionFlush();
|
|
771
|
+
} else if (this.buffer.length >= this.flushAt) {
|
|
772
|
+
this.maybeFlush();
|
|
689
773
|
}
|
|
690
774
|
}
|
|
775
|
+
/**
|
|
776
|
+
* Push an event onto the buffer, evicting the oldest event when the
|
|
777
|
+
* queue is full. `$exception` events are preferentially retained.
|
|
778
|
+
*/
|
|
779
|
+
enqueue(evt) {
|
|
780
|
+
if (this.buffer.length >= this.maxQueueSize) {
|
|
781
|
+
const idx = this.buffer.findIndex((e) => e.event !== "$exception");
|
|
782
|
+
const dropped = this.buffer.splice(idx === -1 ? 0 : idx, 1);
|
|
783
|
+
if (dropped.length > 0) {
|
|
784
|
+
this.onError?.(
|
|
785
|
+
new Error("Event buffer full \u2014 dropped oldest event"),
|
|
786
|
+
dropped
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
this.buffer.push(evt);
|
|
791
|
+
}
|
|
792
|
+
/** Flush unless we're inside a retry-backoff window. */
|
|
793
|
+
maybeFlush() {
|
|
794
|
+
if (Date.now() < this.nextFlushAllowedAt) return;
|
|
795
|
+
this.flush().catch(() => {
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
scheduleExceptionFlush() {
|
|
799
|
+
if (this.exceptionFlushScheduled) return;
|
|
800
|
+
this.exceptionFlushScheduled = true;
|
|
801
|
+
setTimeout(() => {
|
|
802
|
+
this.exceptionFlushScheduled = false;
|
|
803
|
+
this.maybeFlush();
|
|
804
|
+
}, 0);
|
|
805
|
+
}
|
|
691
806
|
async flush() {
|
|
692
807
|
if (!this.writeKey || this.buffer.length === 0) {
|
|
693
808
|
return { success: true, ingested: 0 };
|
|
@@ -705,17 +820,41 @@ var TrackerImpl = class {
|
|
|
705
820
|
});
|
|
706
821
|
if (!res.ok) {
|
|
707
822
|
const body = await res.text().catch(() => "");
|
|
708
|
-
|
|
823
|
+
const err = new Error(`API returned ${res.status}: ${body}`);
|
|
824
|
+
const retryable = res.status === 408 || res.status === 429 || res.status >= 500;
|
|
825
|
+
if (retryable) {
|
|
826
|
+
this.buffer.unshift(...batch);
|
|
827
|
+
}
|
|
828
|
+
this.recordFlushFailure();
|
|
829
|
+
this.onError?.(err, batch);
|
|
830
|
+
return { success: false, ingested: 0 };
|
|
709
831
|
}
|
|
832
|
+
this.flushFailures = 0;
|
|
833
|
+
this.nextFlushAllowedAt = 0;
|
|
710
834
|
const json = await res.json();
|
|
711
835
|
return { success: true, ingested: json.ingested };
|
|
712
836
|
} catch (error) {
|
|
713
837
|
this.buffer.unshift(...batch);
|
|
838
|
+
this.recordFlushFailure();
|
|
714
839
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
715
840
|
this.onError?.(err, batch);
|
|
716
841
|
return { success: false, ingested: 0 };
|
|
717
842
|
}
|
|
718
843
|
}
|
|
844
|
+
/**
|
|
845
|
+
* Record a failed flush and push out the next automatic flush with
|
|
846
|
+
* exponential backoff (capped) so a down ingest endpoint isn't
|
|
847
|
+
* hammered on every `flushInterval` tick. Explicit `flush()` calls
|
|
848
|
+
* are unaffected.
|
|
849
|
+
*/
|
|
850
|
+
recordFlushFailure() {
|
|
851
|
+
this.flushFailures += 1;
|
|
852
|
+
const delay = Math.min(
|
|
853
|
+
this.flushInterval * 2 ** this.flushFailures,
|
|
854
|
+
MAX_RETRY_BACKOFF
|
|
855
|
+
);
|
|
856
|
+
this.nextFlushAllowedAt = Date.now() + delay;
|
|
857
|
+
}
|
|
719
858
|
/**
|
|
720
859
|
* Fire-and-forget flush that survives page unload.
|
|
721
860
|
*
|
|
@@ -767,12 +906,21 @@ var TrackerImpl = class {
|
|
|
767
906
|
}
|
|
768
907
|
this._flagClient?.destroy();
|
|
769
908
|
this._flagClient = null;
|
|
770
|
-
if (typeof window !== "undefined"
|
|
771
|
-
|
|
772
|
-
|
|
909
|
+
if (typeof window !== "undefined") {
|
|
910
|
+
if (this.visibilityHandler) {
|
|
911
|
+
window.removeEventListener("visibilitychange", this.visibilityHandler);
|
|
912
|
+
this.visibilityHandler = null;
|
|
913
|
+
}
|
|
914
|
+
if (this.pageHideHandler) {
|
|
915
|
+
window.removeEventListener("pagehide", this.pageHideHandler);
|
|
916
|
+
this.pageHideHandler = null;
|
|
917
|
+
}
|
|
773
918
|
}
|
|
919
|
+
this.uninstallErrorHandlers?.();
|
|
920
|
+
this.uninstallErrorHandlers = null;
|
|
774
921
|
while (this.buffer.length > 0) {
|
|
775
|
-
await this.flush();
|
|
922
|
+
const result = await this.flush();
|
|
923
|
+
if (!result.success) break;
|
|
776
924
|
}
|
|
777
925
|
}
|
|
778
926
|
};
|