@emit-vision/sdk-js 0.3.0 → 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/dist/index.d.ts +14 -21
- package/dist/index.js +442 -364
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -69,26 +69,23 @@ type EmitVisionOptions = {
|
|
|
69
69
|
/** Disable the SDK after this many consecutive flush failures: the queue is dropped, future captures become no-ops, and a single warning is logged. Default: 20. Set to 0 to disable the cap and retry forever. */
|
|
70
70
|
maxConsecutiveFlushFailures?: number;
|
|
71
71
|
};
|
|
72
|
-
|
|
73
72
|
type RequiredEmitVisionOptions = Required<Pick<EmitVisionOptions, "apiKey" | "endpoint" | "flushIntervalMs" | "batchSize" | "fetchImpl" | "flushOnCapture" | "flagEvalTtlMs" | "retryBackoffInitialMs" | "retryBackoffMaxMs" | "maxConsecutiveFlushFailures">> & Pick<EmitVisionOptions, "environment" | "release" | "sessionId" | "label" | "deployment" | "featureFlags" | "debug"> & {
|
|
74
73
|
autoCapture: Required<AutoCaptureOptions> & {
|
|
75
74
|
flagExposures: boolean;
|
|
76
75
|
pageViews: boolean;
|
|
77
76
|
};
|
|
78
77
|
};
|
|
78
|
+
|
|
79
79
|
declare class EmitVisionClient {
|
|
80
80
|
private readonly options;
|
|
81
|
-
private queue;
|
|
82
81
|
private user;
|
|
83
82
|
private context;
|
|
84
83
|
private deployment;
|
|
85
84
|
private featureFlags;
|
|
86
85
|
private tags;
|
|
87
|
-
private timer;
|
|
88
|
-
private flushScheduled;
|
|
89
|
-
private readonly fetchImpl;
|
|
90
86
|
private readonly flagEval;
|
|
91
|
-
private readonly
|
|
87
|
+
private readonly autoCapture;
|
|
88
|
+
private readonly flusher;
|
|
92
89
|
constructor(options: RequiredEmitVisionOptions);
|
|
93
90
|
captureEvent(name: string, properties?: Record<string, unknown>, options?: CaptureOptions): void;
|
|
94
91
|
captureError(error: unknown, options?: CaptureOptions): void;
|
|
@@ -100,31 +97,27 @@ declare class EmitVisionClient {
|
|
|
100
97
|
getFlag<T extends string | number | boolean | null>(flagKey: string, fallback: T, opts: FlagEvaluationOptions): Promise<T>;
|
|
101
98
|
refreshFlags(opts: FlagEvaluationOptions): Promise<FeatureFlagsContext>;
|
|
102
99
|
capturePageView(path?: string): void;
|
|
100
|
+
captureFeedback(feedback: {
|
|
101
|
+
message: string;
|
|
102
|
+
rating?: number;
|
|
103
|
+
url?: string;
|
|
104
|
+
}, options?: CaptureOptions): void;
|
|
103
105
|
captureExposure(flagKey: string, variantKey: string, variantValue?: string | number | boolean | null, reason?: string, environment?: string): void;
|
|
104
106
|
setTags(tags: Record<string, string>): void;
|
|
105
107
|
flush(): Promise<void>;
|
|
106
|
-
private flushChunk;
|
|
107
|
-
private handleFlushFailure;
|
|
108
|
-
private stopBackgroundWork;
|
|
109
108
|
close(): void;
|
|
110
|
-
private enqueueMetaEvent;
|
|
111
|
-
private enqueue;
|
|
112
|
-
private scheduleFlush;
|
|
113
109
|
private withDefaults;
|
|
114
|
-
private mergeDeployment;
|
|
115
|
-
private mergeFeatureFlags;
|
|
116
|
-
private readonly handleWindowError;
|
|
117
|
-
private readonly handlePopState;
|
|
118
|
-
private patchHistoryMethod;
|
|
119
|
-
private restoreHistoryMethod;
|
|
120
|
-
private readonly handleUnhandledRejection;
|
|
121
|
-
private debug;
|
|
122
110
|
}
|
|
123
111
|
|
|
124
112
|
declare function init(options: EmitVisionOptions): EmitVisionClient;
|
|
125
113
|
declare function captureEvent(name: string, properties?: Record<string, unknown>, options?: CaptureOptions): void;
|
|
126
114
|
declare function captureError(error: unknown, options?: CaptureOptions): void;
|
|
127
115
|
declare function capturePageView(path?: string): void;
|
|
116
|
+
declare function captureFeedback(feedback: {
|
|
117
|
+
message: string;
|
|
118
|
+
rating?: number;
|
|
119
|
+
url?: string;
|
|
120
|
+
}, options?: CaptureOptions): void;
|
|
128
121
|
declare function identify(user: EmitVisionUser): void;
|
|
129
122
|
declare function identify(userId: string, traits?: EmitVisionTraits): void;
|
|
130
123
|
declare function setContext(context: Record<string, unknown>): void;
|
|
@@ -137,4 +130,4 @@ declare function getFlag<T extends string | number | boolean | null>(flagKey: st
|
|
|
137
130
|
declare function refreshFlags(options: FlagEvaluationOptions): Promise<FeatureFlagsContext>;
|
|
138
131
|
declare function captureExposure(flagKey: string, variantKey: string, variantValue?: string | number | boolean | null, reason?: string, environment?: string): void;
|
|
139
132
|
|
|
140
|
-
export { type AutoCaptureOptions, type CaptureOptions, type DeploymentContext, type EmitVisionOptions, type EmitVisionTraits, type EmitVisionUser, type FeatureFlagsContext, type FlagEvaluationOptions, captureError, captureEvent, captureExposure, capturePageView, evaluateFlags, flush, getFlag, identify, init, refreshFlags, setContext, setDeploymentContext, setFeatureFlags, setTags };
|
|
133
|
+
export { type AutoCaptureOptions, type CaptureOptions, type DeploymentContext, type EmitVisionOptions, type EmitVisionTraits, type EmitVisionUser, type FeatureFlagsContext, type FlagEvaluationOptions, captureError, captureEvent, captureExposure, captureFeedback, capturePageView, evaluateFlags, flush, getFlag, identify, init, refreshFlags, setContext, setDeploymentContext, setFeatureFlags, setTags };
|
package/dist/index.js
CHANGED
|
@@ -1,61 +1,3 @@
|
|
|
1
|
-
// src/retry.ts
|
|
2
|
-
var FlushRetryController = class {
|
|
3
|
-
constructor(opts) {
|
|
4
|
-
this.opts = opts;
|
|
5
|
-
}
|
|
6
|
-
opts;
|
|
7
|
-
consecutiveFailures = 0;
|
|
8
|
-
backoffUntil = 0;
|
|
9
|
-
disabled = false;
|
|
10
|
-
isDisabled() {
|
|
11
|
-
return this.disabled;
|
|
12
|
-
}
|
|
13
|
-
isBackingOff(now = Date.now()) {
|
|
14
|
-
return !this.disabled && now < this.backoffUntil;
|
|
15
|
-
}
|
|
16
|
-
msUntilReady(now = Date.now()) {
|
|
17
|
-
if (this.disabled) return Infinity;
|
|
18
|
-
return Math.max(0, this.backoffUntil - now);
|
|
19
|
-
}
|
|
20
|
-
reset() {
|
|
21
|
-
this.consecutiveFailures = 0;
|
|
22
|
-
this.backoffUntil = 0;
|
|
23
|
-
}
|
|
24
|
-
recordFailure(now = Date.now()) {
|
|
25
|
-
this.consecutiveFailures += 1;
|
|
26
|
-
if (this.opts.failureCap > 0 && this.consecutiveFailures >= this.opts.failureCap) {
|
|
27
|
-
const wasDisabled = this.disabled;
|
|
28
|
-
this.disabled = true;
|
|
29
|
-
this.backoffUntil = 0;
|
|
30
|
-
return {
|
|
31
|
-
backoffMs: 0,
|
|
32
|
-
consecutiveFailures: this.consecutiveFailures,
|
|
33
|
-
justDisabled: !wasDisabled
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
if (this.opts.initialMs <= 0) {
|
|
37
|
-
this.backoffUntil = 0;
|
|
38
|
-
return {
|
|
39
|
-
backoffMs: 0,
|
|
40
|
-
consecutiveFailures: this.consecutiveFailures,
|
|
41
|
-
justDisabled: false
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
const exp = Math.min(
|
|
45
|
-
this.opts.initialMs * Math.pow(2, this.consecutiveFailures - 1),
|
|
46
|
-
this.opts.maxMs
|
|
47
|
-
);
|
|
48
|
-
const jitter = exp * 0.2 * (Math.random() * 2 - 1);
|
|
49
|
-
const backoffMs = Math.max(0, Math.floor(exp + jitter));
|
|
50
|
-
this.backoffUntil = now + backoffMs;
|
|
51
|
-
return {
|
|
52
|
-
backoffMs,
|
|
53
|
-
consecutiveFailures: this.consecutiveFailures,
|
|
54
|
-
justDisabled: false
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
1
|
// src/flag-eval.ts
|
|
60
2
|
var FlagEvaluator = class {
|
|
61
3
|
constructor(cfg, onFlagsLoaded, captureExposure2, debug) {
|
|
@@ -215,9 +157,9 @@ function resolveAutoCapture(options) {
|
|
|
215
157
|
};
|
|
216
158
|
}
|
|
217
159
|
|
|
218
|
-
// src/client.ts
|
|
160
|
+
// src/client-helpers.ts
|
|
219
161
|
var SDK_NAME = "emit-vision-js";
|
|
220
|
-
var SDK_VERSION = "0.
|
|
162
|
+
var SDK_VERSION = "0.4.0";
|
|
221
163
|
var MAX_EVENT_BYTES = 64 * 1024;
|
|
222
164
|
var MAX_BATCH_BYTES = 1 * 1024 * 1024;
|
|
223
165
|
function byteLength(s) {
|
|
@@ -248,7 +190,11 @@ function trimEventIfOversized(payload) {
|
|
|
248
190
|
};
|
|
249
191
|
if (payload.type === "error") {
|
|
250
192
|
return {
|
|
251
|
-
payload: {
|
|
193
|
+
payload: {
|
|
194
|
+
...payload,
|
|
195
|
+
...base,
|
|
196
|
+
error: { ...payload.error, stack: void 0 }
|
|
197
|
+
},
|
|
252
198
|
trimmedFields,
|
|
253
199
|
originalBytes
|
|
254
200
|
};
|
|
@@ -270,55 +216,423 @@ function splitBatch(batch) {
|
|
|
270
216
|
const mid = Math.floor(batch.length / 2);
|
|
271
217
|
return [...splitBatch(batch.slice(0, mid)), ...splitBatch(batch.slice(mid))];
|
|
272
218
|
}
|
|
273
|
-
|
|
274
|
-
|
|
219
|
+
|
|
220
|
+
// src/client-internals.ts
|
|
221
|
+
function debugLog(message, opts, payload) {
|
|
222
|
+
if (!opts.debug || typeof console.debug !== "function") return;
|
|
223
|
+
const prefix = opts.label ? `[emit-vision:${opts.label}]` : "[emit-vision]";
|
|
224
|
+
if (payload === void 0) {
|
|
225
|
+
console.debug(prefix, message);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
console.debug(prefix, message, payload);
|
|
229
|
+
}
|
|
230
|
+
function buildMetaEvent(originalName, originalBytes, trimmedFields, opts) {
|
|
231
|
+
return {
|
|
232
|
+
type: "event",
|
|
233
|
+
name: "sdk.event_truncated",
|
|
234
|
+
properties: {
|
|
235
|
+
originalName,
|
|
236
|
+
originalBytes,
|
|
237
|
+
trimmedFields,
|
|
238
|
+
sdkName: SDK_NAME
|
|
239
|
+
},
|
|
240
|
+
environment: opts.environment,
|
|
241
|
+
release: opts.release
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function buildWithDefaults(clientUser, clientContext, clientDeployment, clientFeatureFlags, clientTags, sdkOpts, captureOpts) {
|
|
245
|
+
const {
|
|
246
|
+
context,
|
|
247
|
+
deployment,
|
|
248
|
+
featureFlags,
|
|
249
|
+
tags,
|
|
250
|
+
user,
|
|
251
|
+
environment,
|
|
252
|
+
release,
|
|
253
|
+
sessionId,
|
|
254
|
+
...rest
|
|
255
|
+
} = captureOpts;
|
|
256
|
+
return {
|
|
257
|
+
...rest,
|
|
258
|
+
environment: environment ?? sdkOpts.environment,
|
|
259
|
+
release: release ?? sdkOpts.release,
|
|
260
|
+
sessionId: sessionId ?? sdkOpts.sessionId,
|
|
261
|
+
user: user ?? clientUser,
|
|
262
|
+
context: Object.keys(clientContext).length ? { ...clientContext, ...context } : context,
|
|
263
|
+
deployment: mergeDeploymentFn(clientDeployment, deployment),
|
|
264
|
+
featureFlags: mergeFeatureFlagsFn(clientFeatureFlags, featureFlags),
|
|
265
|
+
tags: Object.keys(clientTags).length ? { ...clientTags, ...tags } : tags
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function mergeDeploymentFn(clientDeployment, overrideDeployment) {
|
|
269
|
+
const merged = { ...clientDeployment ?? {}, ...overrideDeployment ?? {} };
|
|
270
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
271
|
+
}
|
|
272
|
+
function mergeFeatureFlagsFn(clientFlags, overrideFlags) {
|
|
273
|
+
const merged = { ...clientFlags, ...overrideFlags ?? {} };
|
|
274
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
275
|
+
}
|
|
276
|
+
function buildFlushDisabledMessage(label, consecutiveFailures, dropped) {
|
|
277
|
+
return `[emit-vision${label ? `:${label}` : ""}] disabled after ${consecutiveFailures} consecutive flush failures; dropping ${dropped} queued events. Re-init the SDK to resume.`;
|
|
278
|
+
}
|
|
279
|
+
async function executeHttpSend(chunk, endpoint, apiKey, fetchImpl) {
|
|
280
|
+
const response = await fetchImpl(`${endpoint}/v1/batch`, {
|
|
281
|
+
method: "POST",
|
|
282
|
+
headers: { "content-type": "application/json", "x-emit-api-key": apiKey },
|
|
283
|
+
body: buildBatchBody(chunk),
|
|
284
|
+
keepalive: true
|
|
285
|
+
});
|
|
286
|
+
if (!response.ok) {
|
|
287
|
+
throw new Error(`emit-vision flush failed with ${response.status}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/retry.ts
|
|
292
|
+
var FlushRetryController = class {
|
|
293
|
+
constructor(opts) {
|
|
294
|
+
this.opts = opts;
|
|
295
|
+
}
|
|
296
|
+
opts;
|
|
297
|
+
consecutiveFailures = 0;
|
|
298
|
+
backoffUntil = 0;
|
|
299
|
+
disabled = false;
|
|
300
|
+
isDisabled() {
|
|
301
|
+
return this.disabled;
|
|
302
|
+
}
|
|
303
|
+
isBackingOff(now = Date.now()) {
|
|
304
|
+
return !this.disabled && now < this.backoffUntil;
|
|
305
|
+
}
|
|
306
|
+
msUntilReady(now = Date.now()) {
|
|
307
|
+
if (this.disabled) return Infinity;
|
|
308
|
+
return Math.max(0, this.backoffUntil - now);
|
|
309
|
+
}
|
|
310
|
+
reset() {
|
|
311
|
+
this.consecutiveFailures = 0;
|
|
312
|
+
this.backoffUntil = 0;
|
|
313
|
+
}
|
|
314
|
+
recordFailure(now = Date.now()) {
|
|
315
|
+
this.consecutiveFailures += 1;
|
|
316
|
+
if (this.opts.failureCap > 0 && this.consecutiveFailures >= this.opts.failureCap) {
|
|
317
|
+
const wasDisabled = this.disabled;
|
|
318
|
+
this.disabled = true;
|
|
319
|
+
this.backoffUntil = 0;
|
|
320
|
+
return {
|
|
321
|
+
backoffMs: 0,
|
|
322
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
323
|
+
justDisabled: !wasDisabled
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
if (this.opts.initialMs <= 0) {
|
|
327
|
+
this.backoffUntil = 0;
|
|
328
|
+
return {
|
|
329
|
+
backoffMs: 0,
|
|
330
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
331
|
+
justDisabled: false
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
const exp = Math.min(
|
|
335
|
+
this.opts.initialMs * Math.pow(2, this.consecutiveFailures - 1),
|
|
336
|
+
this.opts.maxMs
|
|
337
|
+
);
|
|
338
|
+
const jitter = exp * 0.2 * (Math.random() * 2 - 1);
|
|
339
|
+
const backoffMs = Math.max(0, Math.floor(exp + jitter));
|
|
340
|
+
this.backoffUntil = now + backoffMs;
|
|
341
|
+
return {
|
|
342
|
+
backoffMs,
|
|
343
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
344
|
+
justDisabled: false
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// src/client-flush.ts
|
|
350
|
+
var FlushManager = class {
|
|
351
|
+
constructor(options, fetchImpl) {
|
|
275
352
|
this.options = options;
|
|
276
|
-
this.fetchImpl =
|
|
277
|
-
this.deployment = options.deployment ? { ...options.deployment } : void 0;
|
|
278
|
-
this.featureFlags = options.featureFlags ? { ...options.featureFlags } : {};
|
|
353
|
+
this.fetchImpl = fetchImpl;
|
|
279
354
|
this.retry = new FlushRetryController({
|
|
280
355
|
initialMs: options.retryBackoffInitialMs,
|
|
281
356
|
maxMs: options.retryBackoffMaxMs,
|
|
282
357
|
failureCap: options.maxConsecutiveFlushFailures
|
|
283
358
|
});
|
|
284
|
-
this.flagEval = new FlagEvaluator(
|
|
285
|
-
{
|
|
286
|
-
endpoint: options.endpoint,
|
|
287
|
-
apiKey: options.apiKey,
|
|
288
|
-
ttlMs: options.flagEvalTtlMs,
|
|
289
|
-
flagExposures: options.autoCapture.flagExposures,
|
|
290
|
-
fetchImpl: options.fetchImpl
|
|
291
|
-
},
|
|
292
|
-
(flags) => {
|
|
293
|
-
this.featureFlags = { ...this.featureFlags, ...flags };
|
|
294
|
-
},
|
|
295
|
-
(flagKey, variantKey, variantValue, reason, env) => this.captureExposure(flagKey, variantKey, variantValue, reason, env),
|
|
296
|
-
(msg, data) => this.debug(msg, data)
|
|
297
|
-
);
|
|
298
359
|
this.timer = setInterval(() => {
|
|
299
|
-
if (this.queue.length === 0 || this.retry.isBackingOff())
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
360
|
+
if (this.queue.length === 0 || this.retry.isBackingOff()) return;
|
|
302
361
|
this.flush().catch(
|
|
303
|
-
(err) =>
|
|
362
|
+
(err) => debugLog("background flush error", this.options, { error: err })
|
|
304
363
|
);
|
|
305
364
|
}, options.flushIntervalMs);
|
|
306
|
-
|
|
365
|
+
}
|
|
366
|
+
options;
|
|
367
|
+
fetchImpl;
|
|
368
|
+
queue = [];
|
|
369
|
+
timer;
|
|
370
|
+
flushScheduled = false;
|
|
371
|
+
retry;
|
|
372
|
+
enqueue(payload) {
|
|
373
|
+
if (this.retry.isDisabled()) return;
|
|
374
|
+
const {
|
|
375
|
+
payload: trimmed,
|
|
376
|
+
trimmedFields,
|
|
377
|
+
originalBytes
|
|
378
|
+
} = trimEventIfOversized(payload);
|
|
379
|
+
this.queue.push(trimmed);
|
|
380
|
+
if (trimmedFields !== null) {
|
|
381
|
+
const meta = buildMetaEvent(
|
|
382
|
+
payload.name,
|
|
383
|
+
originalBytes,
|
|
384
|
+
trimmedFields,
|
|
385
|
+
this.options
|
|
386
|
+
);
|
|
387
|
+
this.queue.push(meta);
|
|
388
|
+
}
|
|
389
|
+
debugLog("queued payload", this.options, {
|
|
390
|
+
type: payload.type,
|
|
391
|
+
name: payload.name,
|
|
392
|
+
queueSize: this.queue.length,
|
|
393
|
+
flushOnCapture: this.options.flushOnCapture,
|
|
394
|
+
batchSize: this.options.batchSize
|
|
395
|
+
});
|
|
396
|
+
if (this.options.flushOnCapture) {
|
|
397
|
+
if (!this.retry.isBackingOff()) {
|
|
398
|
+
this.flush().catch(
|
|
399
|
+
(err) => debugLog("capture flush error", this.options, { error: err })
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (this.queue.length >= this.options.batchSize && !this.retry.isBackingOff()) {
|
|
405
|
+
this.flush().catch(
|
|
406
|
+
(err) => debugLog("batch flush error", this.options, { error: err })
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
async flush() {
|
|
411
|
+
if (this.retry.isDisabled()) return;
|
|
412
|
+
const batch = this.queue.splice(0, this.options.batchSize);
|
|
413
|
+
if (batch.length === 0) return;
|
|
414
|
+
debugLog("flushing batch", this.options, { count: batch.length });
|
|
415
|
+
const chunks = splitBatch(batch);
|
|
416
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
417
|
+
const tailCount = chunks.slice(i + 1).reduce((s, c) => s + c.length, 0);
|
|
418
|
+
try {
|
|
419
|
+
await this.flushChunk(chunks[i], tailCount);
|
|
420
|
+
} catch (err) {
|
|
421
|
+
if (!this.retry.isDisabled()) {
|
|
422
|
+
const tail = chunks.slice(i + 1).flat();
|
|
423
|
+
if (tail.length > 0) {
|
|
424
|
+
this.queue.splice(chunks[i].length, 0, ...tail);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
throw err;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (this.options.flushOnCapture && this.queue.length > 0) {
|
|
431
|
+
this.scheduleFlush();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
close() {
|
|
435
|
+
if (this.timer) {
|
|
436
|
+
clearInterval(this.timer);
|
|
437
|
+
this.timer = void 0;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
async flushChunk(chunk, tailCount) {
|
|
441
|
+
try {
|
|
442
|
+
await executeHttpSend(
|
|
443
|
+
chunk,
|
|
444
|
+
this.options.endpoint,
|
|
445
|
+
this.options.apiKey,
|
|
446
|
+
this.fetchImpl
|
|
447
|
+
);
|
|
448
|
+
} catch (err) {
|
|
449
|
+
const m = err instanceof Error ? /flush failed with (\d+)/.exec(err.message) : null;
|
|
450
|
+
this.handleFlushFailure(
|
|
451
|
+
chunk,
|
|
452
|
+
tailCount,
|
|
453
|
+
m ? { reason: "status", status: Number(m[1]) } : { reason: "network", error: err }
|
|
454
|
+
);
|
|
455
|
+
throw err;
|
|
456
|
+
}
|
|
457
|
+
this.retry.reset();
|
|
458
|
+
debugLog("flush complete", this.options, { count: chunk.length });
|
|
459
|
+
}
|
|
460
|
+
handleFlushFailure(batch, tailCount, detail) {
|
|
461
|
+
const result = this.retry.recordFailure();
|
|
462
|
+
if (result.justDisabled) {
|
|
463
|
+
const dropped = this.queue.length + batch.length + tailCount;
|
|
464
|
+
this.queue = [];
|
|
465
|
+
this.close();
|
|
466
|
+
const msg = buildFlushDisabledMessage(
|
|
467
|
+
this.options.label,
|
|
468
|
+
result.consecutiveFailures,
|
|
469
|
+
dropped
|
|
470
|
+
);
|
|
471
|
+
if (typeof console.warn === "function") console.warn(msg);
|
|
472
|
+
debugLog("disabled", this.options, {
|
|
473
|
+
consecutiveFailures: result.consecutiveFailures,
|
|
474
|
+
dropped
|
|
475
|
+
});
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
this.queue.unshift(...batch);
|
|
479
|
+
const base = {
|
|
480
|
+
restoredCount: batch.length,
|
|
481
|
+
consecutiveFailures: result.consecutiveFailures,
|
|
482
|
+
backoffMs: result.backoffMs
|
|
483
|
+
};
|
|
484
|
+
if (detail.reason === "network") {
|
|
485
|
+
debugLog("flush network error", this.options, {
|
|
486
|
+
error: detail.error,
|
|
487
|
+
...base
|
|
488
|
+
});
|
|
489
|
+
} else {
|
|
490
|
+
debugLog("flush failed", this.options, {
|
|
491
|
+
status: detail.status,
|
|
492
|
+
...base
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
scheduleFlush() {
|
|
497
|
+
if (this.flushScheduled) return;
|
|
498
|
+
this.flushScheduled = true;
|
|
499
|
+
queueMicrotask(() => {
|
|
500
|
+
this.flushScheduled = false;
|
|
501
|
+
if (this.queue.length === 0 || this.retry.isBackingOff()) return;
|
|
502
|
+
this.flush().catch(
|
|
503
|
+
(err) => debugLog("scheduled flush error", this.options, { error: err })
|
|
504
|
+
);
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
// src/client-autocapture.ts
|
|
510
|
+
var BrowserAutoCapture = class {
|
|
511
|
+
constructor(onCapture, withDefaults, onPageView, config) {
|
|
512
|
+
this.onCapture = onCapture;
|
|
513
|
+
this.withDefaults = withDefaults;
|
|
514
|
+
this.onPageView = onPageView;
|
|
515
|
+
this.config = config;
|
|
516
|
+
}
|
|
517
|
+
onCapture;
|
|
518
|
+
withDefaults;
|
|
519
|
+
onPageView;
|
|
520
|
+
config;
|
|
521
|
+
attach() {
|
|
522
|
+
if (typeof window === "undefined") return;
|
|
523
|
+
if (this.config.errors) {
|
|
307
524
|
window.addEventListener("error", this.handleWindowError);
|
|
308
525
|
}
|
|
309
|
-
if (
|
|
526
|
+
if (this.config.unhandledRejections) {
|
|
310
527
|
window.addEventListener(
|
|
311
528
|
"unhandledrejection",
|
|
312
529
|
this.handleUnhandledRejection
|
|
313
530
|
);
|
|
314
531
|
}
|
|
315
|
-
if (
|
|
316
|
-
this.
|
|
532
|
+
if (this.config.pageViews) {
|
|
533
|
+
this.onPageView();
|
|
317
534
|
window.addEventListener("popstate", this.handlePopState);
|
|
318
535
|
this.patchHistoryMethod("pushState");
|
|
319
536
|
this.patchHistoryMethod("replaceState");
|
|
320
537
|
}
|
|
321
|
-
|
|
538
|
+
}
|
|
539
|
+
detach() {
|
|
540
|
+
if (typeof window === "undefined") return;
|
|
541
|
+
if (this.config.errors) {
|
|
542
|
+
window.removeEventListener("error", this.handleWindowError);
|
|
543
|
+
}
|
|
544
|
+
if (this.config.unhandledRejections) {
|
|
545
|
+
window.removeEventListener(
|
|
546
|
+
"unhandledrejection",
|
|
547
|
+
this.handleUnhandledRejection
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
if (this.config.pageViews) {
|
|
551
|
+
window.removeEventListener("popstate", this.handlePopState);
|
|
552
|
+
this.restoreHistoryMethod("pushState");
|
|
553
|
+
this.restoreHistoryMethod("replaceState");
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
handleWindowError = (event) => {
|
|
557
|
+
const serialized = serializeError(event.error ?? event.message);
|
|
558
|
+
this.onCapture({
|
|
559
|
+
type: "error",
|
|
560
|
+
name: "error",
|
|
561
|
+
error: { ...serialized, handled: false },
|
|
562
|
+
...this.withDefaults({
|
|
563
|
+
context: {
|
|
564
|
+
source: "window.error",
|
|
565
|
+
url: window.location.href,
|
|
566
|
+
page: window.location.pathname
|
|
567
|
+
}
|
|
568
|
+
})
|
|
569
|
+
});
|
|
570
|
+
};
|
|
571
|
+
handlePopState = () => {
|
|
572
|
+
this.onPageView();
|
|
573
|
+
};
|
|
574
|
+
handleUnhandledRejection = (event) => {
|
|
575
|
+
const serialized = serializeError(event.reason);
|
|
576
|
+
this.onCapture({
|
|
577
|
+
type: "error",
|
|
578
|
+
name: "error",
|
|
579
|
+
error: { ...serialized, handled: false },
|
|
580
|
+
...this.withDefaults({
|
|
581
|
+
context: {
|
|
582
|
+
source: "window.unhandledrejection",
|
|
583
|
+
url: window.location.href,
|
|
584
|
+
page: window.location.pathname
|
|
585
|
+
}
|
|
586
|
+
})
|
|
587
|
+
});
|
|
588
|
+
};
|
|
589
|
+
patchHistoryMethod(method) {
|
|
590
|
+
const original = history[method].bind(history);
|
|
591
|
+
const historyWithCustom = history;
|
|
592
|
+
historyWithCustom[`__ev_orig_${method}`] = original;
|
|
593
|
+
historyWithCustom[method] = (...args) => {
|
|
594
|
+
original(...args);
|
|
595
|
+
this.onPageView();
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
restoreHistoryMethod(method) {
|
|
599
|
+
const historyWithCustom = history;
|
|
600
|
+
const original = historyWithCustom[`__ev_orig_${method}`];
|
|
601
|
+
if (original) {
|
|
602
|
+
historyWithCustom[method] = original;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
// src/client.ts
|
|
608
|
+
var EmitVisionClient = class {
|
|
609
|
+
constructor(options) {
|
|
610
|
+
this.options = options;
|
|
611
|
+
this.deployment = options.deployment ? { ...options.deployment } : void 0;
|
|
612
|
+
this.featureFlags = options.featureFlags ? { ...options.featureFlags } : {};
|
|
613
|
+
this.flusher = new FlushManager(options, options.fetchImpl);
|
|
614
|
+
this.flagEval = new FlagEvaluator(
|
|
615
|
+
{
|
|
616
|
+
endpoint: options.endpoint,
|
|
617
|
+
apiKey: options.apiKey,
|
|
618
|
+
ttlMs: options.flagEvalTtlMs,
|
|
619
|
+
flagExposures: options.autoCapture.flagExposures,
|
|
620
|
+
fetchImpl: options.fetchImpl
|
|
621
|
+
},
|
|
622
|
+
(flags) => {
|
|
623
|
+
this.featureFlags = { ...this.featureFlags, ...flags };
|
|
624
|
+
},
|
|
625
|
+
(flagKey, variantKey, variantValue, reason, env) => this.captureExposure(flagKey, variantKey, variantValue, reason, env),
|
|
626
|
+
(msg, data) => debugLog(msg, this.options, data)
|
|
627
|
+
);
|
|
628
|
+
this.autoCapture = new BrowserAutoCapture(
|
|
629
|
+
(payload) => this.flusher.enqueue(payload),
|
|
630
|
+
(opts) => this.withDefaults(opts),
|
|
631
|
+
() => this.capturePageView(),
|
|
632
|
+
options.autoCapture
|
|
633
|
+
);
|
|
634
|
+
this.autoCapture.attach();
|
|
635
|
+
debugLog("initialized", this.options, {
|
|
322
636
|
endpoint: options.endpoint,
|
|
323
637
|
environment: options.environment,
|
|
324
638
|
release: options.release,
|
|
@@ -329,20 +643,17 @@ var EmitVisionClient = class {
|
|
|
329
643
|
});
|
|
330
644
|
}
|
|
331
645
|
options;
|
|
332
|
-
queue = [];
|
|
333
646
|
user;
|
|
334
647
|
context = {};
|
|
335
648
|
deployment;
|
|
336
649
|
featureFlags = {};
|
|
337
650
|
tags = {};
|
|
338
|
-
timer;
|
|
339
|
-
flushScheduled = false;
|
|
340
|
-
fetchImpl;
|
|
341
651
|
flagEval;
|
|
342
|
-
|
|
652
|
+
autoCapture;
|
|
653
|
+
flusher;
|
|
343
654
|
captureEvent(name, properties, options = {}) {
|
|
344
|
-
|
|
345
|
-
this.enqueue({
|
|
655
|
+
debugLog("queue event", this.options, { name, properties, options });
|
|
656
|
+
this.flusher.enqueue({
|
|
346
657
|
type: "event",
|
|
347
658
|
name,
|
|
348
659
|
properties,
|
|
@@ -351,12 +662,11 @@ var EmitVisionClient = class {
|
|
|
351
662
|
}
|
|
352
663
|
captureError(error, options = {}) {
|
|
353
664
|
const serialized = serializeError(error);
|
|
354
|
-
|
|
665
|
+
debugLog("queue error", this.options, {
|
|
355
666
|
message: serialized.message,
|
|
356
|
-
handled: serialized.handled,
|
|
357
667
|
options
|
|
358
668
|
});
|
|
359
|
-
this.enqueue({
|
|
669
|
+
this.flusher.enqueue({
|
|
360
670
|
type: "error",
|
|
361
671
|
name: "error",
|
|
362
672
|
error: serialized,
|
|
@@ -365,19 +675,19 @@ var EmitVisionClient = class {
|
|
|
365
675
|
}
|
|
366
676
|
identify(userIdOrUser, traits = {}) {
|
|
367
677
|
this.user = typeof userIdOrUser === "string" ? { id: userIdOrUser, ...traits } : { ...userIdOrUser };
|
|
368
|
-
|
|
678
|
+
debugLog("identified user", this.options, this.user);
|
|
369
679
|
}
|
|
370
680
|
setContext(context) {
|
|
371
681
|
this.context = { ...this.context, ...context };
|
|
372
|
-
|
|
682
|
+
debugLog("updated context", this.options, this.context);
|
|
373
683
|
}
|
|
374
684
|
setDeploymentContext(context) {
|
|
375
685
|
this.deployment = Object.keys(context).length > 0 ? { ...context } : void 0;
|
|
376
|
-
|
|
686
|
+
debugLog("updated deployment context", this.options, this.deployment);
|
|
377
687
|
}
|
|
378
688
|
setFeatureFlags(featureFlags) {
|
|
379
689
|
this.featureFlags = { ...featureFlags };
|
|
380
|
-
|
|
690
|
+
debugLog("updated feature flags", this.options, this.featureFlags);
|
|
381
691
|
}
|
|
382
692
|
evaluateFlags(opts) {
|
|
383
693
|
return this.flagEval.evaluateFlags({
|
|
@@ -402,6 +712,14 @@ var EmitVisionClient = class {
|
|
|
402
712
|
if (!page) return;
|
|
403
713
|
this.captureEvent("$page_view", { page });
|
|
404
714
|
}
|
|
715
|
+
captureFeedback(feedback, options = {}) {
|
|
716
|
+
const url = feedback.url ?? (typeof window !== "undefined" ? window.location.href : void 0);
|
|
717
|
+
this.captureEvent(
|
|
718
|
+
"$feedback",
|
|
719
|
+
{ message: feedback.message, ...feedback.rating !== void 0 ? { rating: feedback.rating } : {}, ...url ? { url } : {} },
|
|
720
|
+
options
|
|
721
|
+
);
|
|
722
|
+
}
|
|
405
723
|
captureExposure(flagKey, variantKey, variantValue, reason, environment) {
|
|
406
724
|
this.captureEvent("$flag_exposure", {
|
|
407
725
|
flagKey,
|
|
@@ -413,270 +731,26 @@ var EmitVisionClient = class {
|
|
|
413
731
|
}
|
|
414
732
|
setTags(tags) {
|
|
415
733
|
this.tags = { ...this.tags, ...tags };
|
|
416
|
-
|
|
734
|
+
debugLog("updated tags", this.options, this.tags);
|
|
417
735
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
const batch = this.queue.splice(0, this.options.batchSize);
|
|
423
|
-
if (batch.length === 0) {
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
this.debug("flushing batch", { count: batch.length });
|
|
427
|
-
const chunks = splitBatch(batch);
|
|
428
|
-
for (let i = 0; i < chunks.length; i++) {
|
|
429
|
-
try {
|
|
430
|
-
await this.flushChunk(chunks[i]);
|
|
431
|
-
} catch (err) {
|
|
432
|
-
if (!this.retry.isDisabled()) {
|
|
433
|
-
const tail = chunks.slice(i + 1).flat();
|
|
434
|
-
if (tail.length > 0) {
|
|
435
|
-
this.queue.splice(chunks[i].length, 0, ...tail);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
throw err;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
if (this.options.flushOnCapture && this.queue.length > 0) {
|
|
442
|
-
this.scheduleFlush();
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
async flushChunk(chunk) {
|
|
446
|
-
let response;
|
|
447
|
-
try {
|
|
448
|
-
response = await this.fetchImpl(`${this.options.endpoint}/v1/batch`, {
|
|
449
|
-
method: "POST",
|
|
450
|
-
headers: {
|
|
451
|
-
"content-type": "application/json",
|
|
452
|
-
"x-emit-api-key": this.options.apiKey
|
|
453
|
-
},
|
|
454
|
-
body: buildBatchBody(chunk),
|
|
455
|
-
keepalive: true
|
|
456
|
-
});
|
|
457
|
-
} catch (err) {
|
|
458
|
-
this.handleFlushFailure(chunk, { reason: "network", error: err });
|
|
459
|
-
throw err;
|
|
460
|
-
}
|
|
461
|
-
if (!response.ok) {
|
|
462
|
-
const error = new Error(
|
|
463
|
-
`emit-vision flush failed with ${response.status}`
|
|
464
|
-
);
|
|
465
|
-
this.handleFlushFailure(chunk, {
|
|
466
|
-
reason: "status",
|
|
467
|
-
status: response.status
|
|
468
|
-
});
|
|
469
|
-
throw error;
|
|
470
|
-
}
|
|
471
|
-
this.retry.reset();
|
|
472
|
-
this.debug("flush complete", { count: chunk.length });
|
|
473
|
-
}
|
|
474
|
-
handleFlushFailure(batch, detail) {
|
|
475
|
-
const result = this.retry.recordFailure();
|
|
476
|
-
if (result.justDisabled) {
|
|
477
|
-
const droppedQueued = this.queue.length;
|
|
478
|
-
const droppedBatch = batch.length;
|
|
479
|
-
this.queue = [];
|
|
480
|
-
this.stopBackgroundWork();
|
|
481
|
-
const message = `[emit-vision${this.options.label ? `:${this.options.label}` : ""}] disabled after ${result.consecutiveFailures} consecutive flush failures; dropping ${droppedQueued + droppedBatch} queued events. Re-init the SDK to resume.`;
|
|
482
|
-
if (typeof console.warn === "function") {
|
|
483
|
-
console.warn(message);
|
|
484
|
-
}
|
|
485
|
-
this.debug("disabled", {
|
|
486
|
-
consecutiveFailures: result.consecutiveFailures,
|
|
487
|
-
dropped: droppedQueued + droppedBatch
|
|
488
|
-
});
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
this.queue.unshift(...batch);
|
|
492
|
-
if (detail.reason === "network") {
|
|
493
|
-
this.debug("flush network error", {
|
|
494
|
-
error: detail.error,
|
|
495
|
-
restoredCount: batch.length,
|
|
496
|
-
consecutiveFailures: result.consecutiveFailures,
|
|
497
|
-
backoffMs: result.backoffMs
|
|
498
|
-
});
|
|
499
|
-
} else {
|
|
500
|
-
this.debug("flush failed", {
|
|
501
|
-
status: detail.status,
|
|
502
|
-
restoredCount: batch.length,
|
|
503
|
-
consecutiveFailures: result.consecutiveFailures,
|
|
504
|
-
backoffMs: result.backoffMs
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
stopBackgroundWork() {
|
|
509
|
-
if (this.timer) {
|
|
510
|
-
clearInterval(this.timer);
|
|
511
|
-
this.timer = void 0;
|
|
512
|
-
}
|
|
736
|
+
flush() {
|
|
737
|
+
return this.flusher.flush();
|
|
513
738
|
}
|
|
514
739
|
close() {
|
|
515
|
-
this.
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
this.debug("closed client");
|
|
531
|
-
}
|
|
532
|
-
enqueueMetaEvent(originalName, originalBytes, trimmedFields) {
|
|
533
|
-
this.queue.push({
|
|
534
|
-
type: "event",
|
|
535
|
-
name: "sdk.event_truncated",
|
|
536
|
-
properties: { originalName, originalBytes, trimmedFields, sdkName: SDK_NAME },
|
|
537
|
-
environment: this.options.environment,
|
|
538
|
-
release: this.options.release
|
|
539
|
-
});
|
|
540
|
-
}
|
|
541
|
-
enqueue(payload) {
|
|
542
|
-
if (this.retry.isDisabled()) {
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
const { payload: trimmed, trimmedFields, originalBytes } = trimEventIfOversized(payload);
|
|
546
|
-
this.queue.push(trimmed);
|
|
547
|
-
if (trimmedFields !== null) {
|
|
548
|
-
this.enqueueMetaEvent(payload.name, originalBytes, trimmedFields);
|
|
549
|
-
}
|
|
550
|
-
this.debug("queued payload", {
|
|
551
|
-
type: payload.type,
|
|
552
|
-
name: payload.name,
|
|
553
|
-
queueSize: this.queue.length,
|
|
554
|
-
flushOnCapture: this.options.flushOnCapture,
|
|
555
|
-
batchSize: this.options.batchSize
|
|
556
|
-
});
|
|
557
|
-
if (this.options.flushOnCapture) {
|
|
558
|
-
this.debug("capture flush scheduled", {
|
|
559
|
-
queueSize: this.queue.length
|
|
560
|
-
});
|
|
561
|
-
if (!this.retry.isBackingOff()) {
|
|
562
|
-
this.flush().catch(
|
|
563
|
-
(err) => this.debug("capture flush error", { error: err })
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
if (this.queue.length >= this.options.batchSize && !this.retry.isBackingOff()) {
|
|
569
|
-
this.flush().catch(
|
|
570
|
-
(err) => this.debug("batch flush error", { error: err })
|
|
571
|
-
);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
scheduleFlush() {
|
|
575
|
-
if (this.flushScheduled) {
|
|
576
|
-
return;
|
|
577
|
-
}
|
|
578
|
-
this.flushScheduled = true;
|
|
579
|
-
queueMicrotask(() => {
|
|
580
|
-
this.flushScheduled = false;
|
|
581
|
-
if (this.queue.length === 0 || this.retry.isBackingOff()) {
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
this.flush().catch(
|
|
585
|
-
(err) => this.debug("scheduled flush error", { error: err })
|
|
586
|
-
);
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
withDefaults(options) {
|
|
590
|
-
const {
|
|
591
|
-
context,
|
|
592
|
-
deployment,
|
|
593
|
-
featureFlags,
|
|
594
|
-
tags,
|
|
595
|
-
user,
|
|
596
|
-
environment,
|
|
597
|
-
release,
|
|
598
|
-
sessionId,
|
|
599
|
-
...rest
|
|
600
|
-
} = options;
|
|
601
|
-
return {
|
|
602
|
-
...rest,
|
|
603
|
-
environment: environment ?? this.options.environment,
|
|
604
|
-
release: release ?? this.options.release,
|
|
605
|
-
sessionId: sessionId ?? this.options.sessionId,
|
|
606
|
-
user: user ?? this.user,
|
|
607
|
-
context: Object.keys(this.context).length ? { ...this.context, ...context } : context,
|
|
608
|
-
deployment: this.mergeDeployment(deployment),
|
|
609
|
-
featureFlags: this.mergeFeatureFlags(featureFlags),
|
|
610
|
-
tags: Object.keys(this.tags).length ? { ...this.tags, ...tags } : tags
|
|
611
|
-
};
|
|
612
|
-
}
|
|
613
|
-
mergeDeployment(deployment) {
|
|
614
|
-
const merged = {
|
|
615
|
-
...this.deployment ?? {},
|
|
616
|
-
...deployment ?? {}
|
|
617
|
-
};
|
|
618
|
-
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
619
|
-
}
|
|
620
|
-
mergeFeatureFlags(featureFlags) {
|
|
621
|
-
const merged = {
|
|
622
|
-
...this.featureFlags,
|
|
623
|
-
...featureFlags ?? {}
|
|
624
|
-
};
|
|
625
|
-
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
626
|
-
}
|
|
627
|
-
handleWindowError = (event) => {
|
|
628
|
-
const serialized = serializeError(event.error ?? event.message);
|
|
629
|
-
this.debug("auto-captured window error", {
|
|
630
|
-
message: serialized.message
|
|
631
|
-
});
|
|
632
|
-
this.enqueue({
|
|
633
|
-
type: "error",
|
|
634
|
-
name: "error",
|
|
635
|
-
error: { ...serialized, handled: false },
|
|
636
|
-
...this.withDefaults({ context: { source: "window.error" } })
|
|
637
|
-
});
|
|
638
|
-
};
|
|
639
|
-
handlePopState = () => {
|
|
640
|
-
this.capturePageView();
|
|
641
|
-
};
|
|
642
|
-
patchHistoryMethod(method) {
|
|
643
|
-
const original = history[method].bind(history);
|
|
644
|
-
history[`__ev_orig_${method}`] = original;
|
|
645
|
-
history[method] = (...args) => {
|
|
646
|
-
original(...args);
|
|
647
|
-
this.capturePageView();
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
restoreHistoryMethod(method) {
|
|
651
|
-
const original = history[`__ev_orig_${method}`];
|
|
652
|
-
if (original) {
|
|
653
|
-
history[method] = original;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
handleUnhandledRejection = (event) => {
|
|
657
|
-
const serialized = serializeError(event.reason);
|
|
658
|
-
this.debug("auto-captured rejection", {
|
|
659
|
-
message: serialized.message
|
|
660
|
-
});
|
|
661
|
-
this.enqueue({
|
|
662
|
-
type: "error",
|
|
663
|
-
name: "error",
|
|
664
|
-
error: { ...serialized, handled: false },
|
|
665
|
-
...this.withDefaults({
|
|
666
|
-
context: { source: "window.unhandledrejection" }
|
|
667
|
-
})
|
|
668
|
-
});
|
|
669
|
-
};
|
|
670
|
-
debug(message, payload) {
|
|
671
|
-
if (!this.options.debug || typeof console.debug !== "function") {
|
|
672
|
-
return;
|
|
673
|
-
}
|
|
674
|
-
const prefix = this.options.label ? `[emit-vision:${this.options.label}]` : "[emit-vision]";
|
|
675
|
-
if (payload === void 0) {
|
|
676
|
-
console.debug(prefix, message);
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
console.debug(prefix, message, payload);
|
|
740
|
+
this.flusher.close();
|
|
741
|
+
this.autoCapture.detach();
|
|
742
|
+
debugLog("closed client", this.options);
|
|
743
|
+
}
|
|
744
|
+
withDefaults(opts) {
|
|
745
|
+
return buildWithDefaults(
|
|
746
|
+
this.user,
|
|
747
|
+
this.context,
|
|
748
|
+
this.deployment,
|
|
749
|
+
this.featureFlags,
|
|
750
|
+
this.tags,
|
|
751
|
+
this.options,
|
|
752
|
+
opts
|
|
753
|
+
);
|
|
680
754
|
}
|
|
681
755
|
};
|
|
682
756
|
|
|
@@ -714,6 +788,9 @@ function captureError(error, options) {
|
|
|
714
788
|
function capturePageView(path) {
|
|
715
789
|
requireClient().capturePageView(path);
|
|
716
790
|
}
|
|
791
|
+
function captureFeedback(feedback, options) {
|
|
792
|
+
requireClient().captureFeedback(feedback, options);
|
|
793
|
+
}
|
|
717
794
|
function identify(userIdOrUser, traits) {
|
|
718
795
|
requireClient().identify(userIdOrUser, traits);
|
|
719
796
|
}
|
|
@@ -760,6 +837,7 @@ export {
|
|
|
760
837
|
captureError,
|
|
761
838
|
captureEvent,
|
|
762
839
|
captureExposure,
|
|
840
|
+
captureFeedback,
|
|
763
841
|
capturePageView,
|
|
764
842
|
evaluateFlags,
|
|
765
843
|
flush,
|