@emit-vision/sdk-js 0.4.0 → 0.4.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/dist/index.d.ts +77 -1
- package/dist/index.js +232 -5
- package/package.json +14 -3
package/dist/index.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ type CaptureOptions = {
|
|
|
38
38
|
release?: string;
|
|
39
39
|
sessionId?: string;
|
|
40
40
|
user?: EmitVisionUser;
|
|
41
|
+
groupId?: string;
|
|
41
42
|
context?: Record<string, unknown>;
|
|
42
43
|
deployment?: DeploymentContext;
|
|
43
44
|
featureFlags?: FeatureFlagsContext;
|
|
@@ -60,6 +61,10 @@ type EmitVisionOptions = {
|
|
|
60
61
|
flushIntervalMs?: number;
|
|
61
62
|
batchSize?: number;
|
|
62
63
|
fetchImpl?: typeof fetch;
|
|
64
|
+
/** Project ID — passed through to the DevTools debug channel so the extension can build dashboard deep links. Not required for event capture. */
|
|
65
|
+
projectId?: string;
|
|
66
|
+
/** Initial group ID to associate with all events. Can be overridden per-event or updated via `group()`. */
|
|
67
|
+
groupId?: string;
|
|
63
68
|
/** TTL in milliseconds for in-memory flag evaluation cache. Default: 60 000 (1 minute). */
|
|
64
69
|
flagEvalTtlMs?: number;
|
|
65
70
|
/** Initial delay (ms) before retrying after a flush failure. Default: 1000. Doubles each consecutive failure up to {@link retryBackoffMaxMs}. Set to 0 to retry immediately on every tick. */
|
|
@@ -68,17 +73,72 @@ type EmitVisionOptions = {
|
|
|
68
73
|
retryBackoffMaxMs?: number;
|
|
69
74
|
/** 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
75
|
maxConsecutiveFlushFailures?: number;
|
|
76
|
+
/** When true, events are queued in memory but not sent until `consent()` is called. Default: false. */
|
|
77
|
+
consentRequired?: boolean;
|
|
78
|
+
/** Set to true when `consentRequired` is true but consent is already known at init time (e.g. read from a cookie server-side). Skips the consent gate. Default: false. */
|
|
79
|
+
consentGiven?: boolean;
|
|
71
80
|
};
|
|
72
81
|
type RequiredEmitVisionOptions = Required<Pick<EmitVisionOptions, "apiKey" | "endpoint" | "flushIntervalMs" | "batchSize" | "fetchImpl" | "flushOnCapture" | "flagEvalTtlMs" | "retryBackoffInitialMs" | "retryBackoffMaxMs" | "maxConsecutiveFlushFailures">> & Pick<EmitVisionOptions, "environment" | "release" | "sessionId" | "label" | "deployment" | "featureFlags" | "debug"> & {
|
|
73
82
|
autoCapture: Required<AutoCaptureOptions> & {
|
|
74
83
|
flagExposures: boolean;
|
|
75
84
|
pageViews: boolean;
|
|
76
85
|
};
|
|
86
|
+
projectId?: string;
|
|
87
|
+
groupId?: string;
|
|
88
|
+
consentRequired?: boolean;
|
|
89
|
+
consentGiven?: boolean;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
type DevToolsMessage = {
|
|
93
|
+
type: "init";
|
|
94
|
+
payload: {
|
|
95
|
+
sdkVersion: string;
|
|
96
|
+
endpoint: string;
|
|
97
|
+
environment?: string;
|
|
98
|
+
release?: string;
|
|
99
|
+
sessionId: string;
|
|
100
|
+
autoCapture: AutoCaptureOptions;
|
|
101
|
+
projectId?: string;
|
|
102
|
+
consentRequired?: boolean;
|
|
103
|
+
};
|
|
104
|
+
} | {
|
|
105
|
+
type: "event";
|
|
106
|
+
payload: {
|
|
107
|
+
name: string;
|
|
108
|
+
properties?: Record<string, unknown>;
|
|
109
|
+
eventId?: string;
|
|
110
|
+
timestamp?: string;
|
|
111
|
+
environment?: string;
|
|
112
|
+
sessionId?: string;
|
|
113
|
+
};
|
|
114
|
+
} | {
|
|
115
|
+
type: "identify";
|
|
116
|
+
payload: {
|
|
117
|
+
userId?: string;
|
|
118
|
+
traits?: Record<string, unknown>;
|
|
119
|
+
};
|
|
120
|
+
} | {
|
|
121
|
+
type: "flush";
|
|
122
|
+
payload: {
|
|
123
|
+
count: number;
|
|
124
|
+
};
|
|
125
|
+
} | {
|
|
126
|
+
type: "error";
|
|
127
|
+
payload: {
|
|
128
|
+
message: string;
|
|
129
|
+
};
|
|
130
|
+
} | {
|
|
131
|
+
type: "group";
|
|
132
|
+
payload: {
|
|
133
|
+
groupId: string;
|
|
134
|
+
traits?: Record<string, unknown>;
|
|
135
|
+
};
|
|
77
136
|
};
|
|
78
137
|
|
|
79
138
|
declare class EmitVisionClient {
|
|
80
139
|
private readonly options;
|
|
81
140
|
private user;
|
|
141
|
+
private groupId;
|
|
82
142
|
private context;
|
|
83
143
|
private deployment;
|
|
84
144
|
private featureFlags;
|
|
@@ -86,10 +146,13 @@ declare class EmitVisionClient {
|
|
|
86
146
|
private readonly flagEval;
|
|
87
147
|
private readonly autoCapture;
|
|
88
148
|
private readonly flusher;
|
|
149
|
+
private readonly persistOnHide;
|
|
150
|
+
private readonly persistOnHidden;
|
|
89
151
|
constructor(options: RequiredEmitVisionOptions);
|
|
90
152
|
captureEvent(name: string, properties?: Record<string, unknown>, options?: CaptureOptions): void;
|
|
91
153
|
captureError(error: unknown, options?: CaptureOptions): void;
|
|
92
154
|
identify(userIdOrUser: string | EmitVisionUser, traits?: EmitVisionTraits): void;
|
|
155
|
+
group(groupId: string, traits?: Record<string, unknown>): void;
|
|
93
156
|
setContext(context: Record<string, unknown>): void;
|
|
94
157
|
setDeploymentContext(context: DeploymentContext): void;
|
|
95
158
|
setFeatureFlags(featureFlags: FeatureFlagsContext): void;
|
|
@@ -97,8 +160,14 @@ declare class EmitVisionClient {
|
|
|
97
160
|
getFlag<T extends string | number | boolean | null>(flagKey: string, fallback: T, opts: FlagEvaluationOptions): Promise<T>;
|
|
98
161
|
refreshFlags(opts: FlagEvaluationOptions): Promise<FeatureFlagsContext>;
|
|
99
162
|
capturePageView(path?: string): void;
|
|
163
|
+
captureFeedback(feedback: {
|
|
164
|
+
message: string;
|
|
165
|
+
rating?: number;
|
|
166
|
+
url?: string;
|
|
167
|
+
}, options?: CaptureOptions): void;
|
|
100
168
|
captureExposure(flagKey: string, variantKey: string, variantValue?: string | number | boolean | null, reason?: string, environment?: string): void;
|
|
101
169
|
setTags(tags: Record<string, string>): void;
|
|
170
|
+
consent(): Promise<void>;
|
|
102
171
|
flush(): Promise<void>;
|
|
103
172
|
close(): void;
|
|
104
173
|
private withDefaults;
|
|
@@ -108,6 +177,11 @@ declare function init(options: EmitVisionOptions): EmitVisionClient;
|
|
|
108
177
|
declare function captureEvent(name: string, properties?: Record<string, unknown>, options?: CaptureOptions): void;
|
|
109
178
|
declare function captureError(error: unknown, options?: CaptureOptions): void;
|
|
110
179
|
declare function capturePageView(path?: string): void;
|
|
180
|
+
declare function captureFeedback(feedback: {
|
|
181
|
+
message: string;
|
|
182
|
+
rating?: number;
|
|
183
|
+
url?: string;
|
|
184
|
+
}, options?: CaptureOptions): void;
|
|
111
185
|
declare function identify(user: EmitVisionUser): void;
|
|
112
186
|
declare function identify(userId: string, traits?: EmitVisionTraits): void;
|
|
113
187
|
declare function setContext(context: Record<string, unknown>): void;
|
|
@@ -115,9 +189,11 @@ declare function setDeploymentContext(context: DeploymentContext): void;
|
|
|
115
189
|
declare function setFeatureFlags(featureFlags: FeatureFlagsContext): void;
|
|
116
190
|
declare function setTags(tags: Record<string, string>): void;
|
|
117
191
|
declare function flush(): Promise<void>;
|
|
192
|
+
declare function consent(): Promise<void>;
|
|
193
|
+
declare function group(groupId: string, traits?: Record<string, unknown>): void;
|
|
118
194
|
declare function evaluateFlags(options: FlagEvaluationOptions): Promise<FeatureFlagsContext>;
|
|
119
195
|
declare function getFlag<T extends string | number | boolean | null>(flagKey: string, fallback: T, options: FlagEvaluationOptions): Promise<T>;
|
|
120
196
|
declare function refreshFlags(options: FlagEvaluationOptions): Promise<FeatureFlagsContext>;
|
|
121
197
|
declare function captureExposure(flagKey: string, variantKey: string, variantValue?: string | number | boolean | null, reason?: string, environment?: string): void;
|
|
122
198
|
|
|
123
|
-
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 };
|
|
199
|
+
export { type AutoCaptureOptions, type CaptureOptions, type DeploymentContext, type DevToolsMessage, type EmitVisionOptions, type EmitVisionTraits, type EmitVisionUser, type FeatureFlagsContext, type FlagEvaluationOptions, captureError, captureEvent, captureExposure, captureFeedback, capturePageView, consent, evaluateFlags, flush, getFlag, group, identify, init, refreshFlags, setContext, setDeploymentContext, setFeatureFlags, setTags };
|
package/dist/index.js
CHANGED
|
@@ -276,6 +276,21 @@ function mergeFeatureFlagsFn(clientFlags, overrideFlags) {
|
|
|
276
276
|
function buildFlushDisabledMessage(label, consecutiveFailures, dropped) {
|
|
277
277
|
return `[emit-vision${label ? `:${label}` : ""}] disabled after ${consecutiveFailures} consecutive flush failures; dropping ${dropped} queued events. Re-init the SDK to resume.`;
|
|
278
278
|
}
|
|
279
|
+
function buildAuthDisabledMessage(label, status, dropped) {
|
|
280
|
+
return `[emit-vision${label ? `:${label}` : ""}] SDK disabled: received ${AUTH_FAILURE_CAP} consecutive ${status} responses from the ingest API. This usually means your DSN or API key is invalid \u2014 check the dsn or apiKey option passed to init(). Dropping ${dropped} queued events.`;
|
|
281
|
+
}
|
|
282
|
+
var AUTH_FAILURE_CAP = 3;
|
|
283
|
+
var FlushHttpError = class extends Error {
|
|
284
|
+
constructor(status, retryAfterSeconds, serverMessage) {
|
|
285
|
+
super(`emit-vision flush failed with ${status}`);
|
|
286
|
+
this.status = status;
|
|
287
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
288
|
+
this.serverMessage = serverMessage;
|
|
289
|
+
}
|
|
290
|
+
status;
|
|
291
|
+
retryAfterSeconds;
|
|
292
|
+
serverMessage;
|
|
293
|
+
};
|
|
279
294
|
async function executeHttpSend(chunk, endpoint, apiKey, fetchImpl) {
|
|
280
295
|
const response = await fetchImpl(`${endpoint}/v1/batch`, {
|
|
281
296
|
method: "POST",
|
|
@@ -284,7 +299,18 @@ async function executeHttpSend(chunk, endpoint, apiKey, fetchImpl) {
|
|
|
284
299
|
keepalive: true
|
|
285
300
|
});
|
|
286
301
|
if (!response.ok) {
|
|
287
|
-
|
|
302
|
+
const retryAfter = Number(response.headers?.get?.("retry-after"));
|
|
303
|
+
let serverMessage;
|
|
304
|
+
try {
|
|
305
|
+
const body = await response.json();
|
|
306
|
+
if (body && typeof body.message === "string") serverMessage = body.message;
|
|
307
|
+
} catch {
|
|
308
|
+
}
|
|
309
|
+
throw new FlushHttpError(
|
|
310
|
+
response.status,
|
|
311
|
+
Number.isFinite(retryAfter) && retryAfter > 0 ? retryAfter : void 0,
|
|
312
|
+
serverMessage
|
|
313
|
+
);
|
|
288
314
|
}
|
|
289
315
|
}
|
|
290
316
|
|
|
@@ -307,10 +333,20 @@ var FlushRetryController = class {
|
|
|
307
333
|
if (this.disabled) return Infinity;
|
|
308
334
|
return Math.max(0, this.backoffUntil - now);
|
|
309
335
|
}
|
|
336
|
+
forceDisable() {
|
|
337
|
+
this.disabled = true;
|
|
338
|
+
this.backoffUntil = 0;
|
|
339
|
+
}
|
|
310
340
|
reset() {
|
|
311
341
|
this.consecutiveFailures = 0;
|
|
312
342
|
this.backoffUntil = 0;
|
|
313
343
|
}
|
|
344
|
+
/** Push the next retry out to honor a server-sent Retry-After hint. Capped at 5 minutes; never shortens an existing backoff. */
|
|
345
|
+
applyRetryAfter(seconds, now = Date.now()) {
|
|
346
|
+
if (this.disabled || !Number.isFinite(seconds) || seconds <= 0) return;
|
|
347
|
+
const cappedMs = Math.min(seconds, 300) * 1e3;
|
|
348
|
+
this.backoffUntil = Math.max(this.backoffUntil, now + cappedMs);
|
|
349
|
+
}
|
|
314
350
|
recordFailure(now = Date.now()) {
|
|
315
351
|
this.consecutiveFailures += 1;
|
|
316
352
|
if (this.opts.failureCap > 0 && this.consecutiveFailures >= this.opts.failureCap) {
|
|
@@ -346,11 +382,77 @@ var FlushRetryController = class {
|
|
|
346
382
|
}
|
|
347
383
|
};
|
|
348
384
|
|
|
385
|
+
// src/persistence.ts
|
|
386
|
+
var MAX_PERSISTED_EVENTS = 200;
|
|
387
|
+
var MAX_PERSISTED_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
388
|
+
function persistenceKey(apiKey, projectId) {
|
|
389
|
+
return `emit-vision:queue:${projectId ?? "-"}:${apiKey.slice(0, 16)}`;
|
|
390
|
+
}
|
|
391
|
+
function getStorage() {
|
|
392
|
+
try {
|
|
393
|
+
if (typeof localStorage === "undefined") return null;
|
|
394
|
+
return localStorage;
|
|
395
|
+
} catch {
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function persistQueue(key, queue) {
|
|
400
|
+
const storage = getStorage();
|
|
401
|
+
if (!storage || queue.length === 0) return;
|
|
402
|
+
const record = {
|
|
403
|
+
savedAt: Date.now(),
|
|
404
|
+
events: queue.slice(0, MAX_PERSISTED_EVENTS)
|
|
405
|
+
};
|
|
406
|
+
try {
|
|
407
|
+
storage.setItem(key, JSON.stringify(record));
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function restoreQueue(key, now = Date.now()) {
|
|
412
|
+
const storage = getStorage();
|
|
413
|
+
if (!storage) return [];
|
|
414
|
+
try {
|
|
415
|
+
const raw = storage.getItem(key);
|
|
416
|
+
storage.removeItem(key);
|
|
417
|
+
if (!raw) return [];
|
|
418
|
+
const parsed = JSON.parse(raw);
|
|
419
|
+
if (typeof parsed.savedAt !== "number" || !Array.isArray(parsed.events)) {
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
422
|
+
if (now - parsed.savedAt > MAX_PERSISTED_AGE_MS) return [];
|
|
423
|
+
return parsed.events.slice(0, MAX_PERSISTED_EVENTS);
|
|
424
|
+
} catch {
|
|
425
|
+
return [];
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
function clearPersistedQueue(key) {
|
|
429
|
+
const storage = getStorage();
|
|
430
|
+
if (!storage) return;
|
|
431
|
+
try {
|
|
432
|
+
storage.removeItem(key);
|
|
433
|
+
} catch {
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/devtools-channel.ts
|
|
438
|
+
function dispatchDevToolsEvent(type, payload) {
|
|
439
|
+
if (typeof window === "undefined" || typeof window.dispatchEvent !== "function") {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
window.dispatchEvent(
|
|
443
|
+
new CustomEvent("__emitVisionDevTools", {
|
|
444
|
+
detail: { type, payload, timestamp: Date.now() }
|
|
445
|
+
})
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
349
449
|
// src/client-flush.ts
|
|
350
450
|
var FlushManager = class {
|
|
351
451
|
constructor(options, fetchImpl) {
|
|
352
452
|
this.options = options;
|
|
353
453
|
this.fetchImpl = fetchImpl;
|
|
454
|
+
this.consentGiven = !options.consentRequired || (options.consentGiven ?? false);
|
|
455
|
+
this.storageKey = persistenceKey(options.apiKey, options.projectId);
|
|
354
456
|
this.retry = new FlushRetryController({
|
|
355
457
|
initialMs: options.retryBackoffInitialMs,
|
|
356
458
|
maxMs: options.retryBackoffMaxMs,
|
|
@@ -369,6 +471,9 @@ var FlushManager = class {
|
|
|
369
471
|
timer;
|
|
370
472
|
flushScheduled = false;
|
|
371
473
|
retry;
|
|
474
|
+
storageKey;
|
|
475
|
+
consentGiven;
|
|
476
|
+
authFailureCount = 0;
|
|
372
477
|
enqueue(payload) {
|
|
373
478
|
if (this.retry.isDisabled()) return;
|
|
374
479
|
const {
|
|
@@ -407,8 +512,27 @@ var FlushManager = class {
|
|
|
407
512
|
);
|
|
408
513
|
}
|
|
409
514
|
}
|
|
515
|
+
giveConsent() {
|
|
516
|
+
this.consentGiven = true;
|
|
517
|
+
return this.flush();
|
|
518
|
+
}
|
|
519
|
+
/** Restore any queue persisted by a previous page load, prepending it so older events flush first. */
|
|
520
|
+
restorePersisted() {
|
|
521
|
+
const restored = restoreQueue(this.storageKey);
|
|
522
|
+
if (restored.length === 0) return;
|
|
523
|
+
this.queue.unshift(...restored);
|
|
524
|
+
debugLog("restored persisted queue", this.options, {
|
|
525
|
+
count: restored.length
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
/** Spill the current queue to localStorage (pagehide / tab hidden). No-op without consent. */
|
|
529
|
+
persistNow() {
|
|
530
|
+
if (!this.consentGiven || this.retry.isDisabled()) return;
|
|
531
|
+
persistQueue(this.storageKey, this.queue);
|
|
532
|
+
}
|
|
410
533
|
async flush() {
|
|
411
534
|
if (this.retry.isDisabled()) return;
|
|
535
|
+
if (!this.consentGiven) return;
|
|
412
536
|
const batch = this.queue.splice(0, this.options.batchSize);
|
|
413
537
|
if (batch.length === 0) return;
|
|
414
538
|
debugLog("flushing batch", this.options, { count: batch.length });
|
|
@@ -446,19 +570,50 @@ var FlushManager = class {
|
|
|
446
570
|
this.fetchImpl
|
|
447
571
|
);
|
|
448
572
|
} catch (err) {
|
|
449
|
-
const m = err instanceof Error ? /flush failed with (\d+)/.exec(err.message) : null;
|
|
450
573
|
this.handleFlushFailure(
|
|
451
574
|
chunk,
|
|
452
575
|
tailCount,
|
|
453
|
-
|
|
576
|
+
err instanceof FlushHttpError ? {
|
|
577
|
+
reason: "status",
|
|
578
|
+
status: err.status,
|
|
579
|
+
retryAfterSeconds: err.retryAfterSeconds,
|
|
580
|
+
serverMessage: err.serverMessage
|
|
581
|
+
} : { reason: "network", error: err }
|
|
454
582
|
);
|
|
455
583
|
throw err;
|
|
456
584
|
}
|
|
457
585
|
this.retry.reset();
|
|
586
|
+
this.authFailureCount = 0;
|
|
587
|
+
clearPersistedQueue(this.storageKey);
|
|
458
588
|
debugLog("flush complete", this.options, { count: chunk.length });
|
|
589
|
+
dispatchDevToolsEvent("flush", { count: chunk.length });
|
|
459
590
|
}
|
|
460
591
|
handleFlushFailure(batch, tailCount, detail) {
|
|
592
|
+
if (detail.reason === "status" && (detail.status === 401 || detail.status === 403)) {
|
|
593
|
+
this.authFailureCount += 1;
|
|
594
|
+
if (this.authFailureCount >= AUTH_FAILURE_CAP) {
|
|
595
|
+
const dropped = this.queue.length + batch.length + tailCount;
|
|
596
|
+
this.queue = [];
|
|
597
|
+
this.close();
|
|
598
|
+
this.retry.forceDisable();
|
|
599
|
+
const msg = buildAuthDisabledMessage(
|
|
600
|
+
this.options.label,
|
|
601
|
+
detail.status,
|
|
602
|
+
dropped
|
|
603
|
+
);
|
|
604
|
+
if (typeof console.warn === "function") console.warn(msg);
|
|
605
|
+
debugLog("disabled (auth)", this.options, {
|
|
606
|
+
status: detail.status,
|
|
607
|
+
authFailures: this.authFailureCount,
|
|
608
|
+
dropped
|
|
609
|
+
});
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
461
613
|
const result = this.retry.recordFailure();
|
|
614
|
+
if (detail.reason === "status" && (detail.status === 429 || detail.status === 503) && detail.retryAfterSeconds !== void 0) {
|
|
615
|
+
this.retry.applyRetryAfter(detail.retryAfterSeconds);
|
|
616
|
+
}
|
|
462
617
|
if (result.justDisabled) {
|
|
463
618
|
const dropped = this.queue.length + batch.length + tailCount;
|
|
464
619
|
this.queue = [];
|
|
@@ -476,6 +631,7 @@ var FlushManager = class {
|
|
|
476
631
|
return;
|
|
477
632
|
}
|
|
478
633
|
this.queue.unshift(...batch);
|
|
634
|
+
this.persistNow();
|
|
479
635
|
const base = {
|
|
480
636
|
restoredCount: batch.length,
|
|
481
637
|
consecutiveFailures: result.consecutiveFailures,
|
|
@@ -489,6 +645,7 @@ var FlushManager = class {
|
|
|
489
645
|
} else {
|
|
490
646
|
debugLog("flush failed", this.options, {
|
|
491
647
|
status: detail.status,
|
|
648
|
+
serverMessage: detail.serverMessage,
|
|
492
649
|
...base
|
|
493
650
|
});
|
|
494
651
|
}
|
|
@@ -608,9 +765,17 @@ var BrowserAutoCapture = class {
|
|
|
608
765
|
var EmitVisionClient = class {
|
|
609
766
|
constructor(options) {
|
|
610
767
|
this.options = options;
|
|
768
|
+
this.groupId = options.groupId;
|
|
611
769
|
this.deployment = options.deployment ? { ...options.deployment } : void 0;
|
|
612
770
|
this.featureFlags = options.featureFlags ? { ...options.featureFlags } : {};
|
|
613
771
|
this.flusher = new FlushManager(options, options.fetchImpl);
|
|
772
|
+
this.flusher.restorePersisted();
|
|
773
|
+
if (typeof window !== "undefined") {
|
|
774
|
+
window.addEventListener("pagehide", this.persistOnHide);
|
|
775
|
+
}
|
|
776
|
+
if (typeof document !== "undefined") {
|
|
777
|
+
document.addEventListener("visibilitychange", this.persistOnHidden);
|
|
778
|
+
}
|
|
614
779
|
this.flagEval = new FlagEvaluator(
|
|
615
780
|
{
|
|
616
781
|
endpoint: options.endpoint,
|
|
@@ -641,9 +806,20 @@ var EmitVisionClient = class {
|
|
|
641
806
|
flushIntervalMs: options.flushIntervalMs,
|
|
642
807
|
flushOnCapture: options.flushOnCapture
|
|
643
808
|
});
|
|
809
|
+
dispatchDevToolsEvent("init", {
|
|
810
|
+
sdkVersion: SDK_VERSION,
|
|
811
|
+
endpoint: options.endpoint,
|
|
812
|
+
environment: options.environment,
|
|
813
|
+
release: options.release,
|
|
814
|
+
sessionId: options.sessionId ?? "",
|
|
815
|
+
autoCapture: options.autoCapture,
|
|
816
|
+
projectId: options.projectId,
|
|
817
|
+
consentRequired: options.consentRequired
|
|
818
|
+
});
|
|
644
819
|
}
|
|
645
820
|
options;
|
|
646
821
|
user;
|
|
822
|
+
groupId;
|
|
647
823
|
context = {};
|
|
648
824
|
deployment;
|
|
649
825
|
featureFlags = {};
|
|
@@ -651,13 +827,28 @@ var EmitVisionClient = class {
|
|
|
651
827
|
flagEval;
|
|
652
828
|
autoCapture;
|
|
653
829
|
flusher;
|
|
830
|
+
persistOnHide = () => this.flusher.persistNow();
|
|
831
|
+
persistOnHidden = () => {
|
|
832
|
+
if (typeof document !== "undefined" && document.visibilityState === "hidden") {
|
|
833
|
+
this.flusher.persistNow();
|
|
834
|
+
}
|
|
835
|
+
};
|
|
654
836
|
captureEvent(name, properties, options = {}) {
|
|
837
|
+
const eventId = options.eventId ?? (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : void 0);
|
|
655
838
|
debugLog("queue event", this.options, { name, properties, options });
|
|
656
839
|
this.flusher.enqueue({
|
|
657
840
|
type: "event",
|
|
658
841
|
name,
|
|
659
842
|
properties,
|
|
660
|
-
...this.withDefaults(options)
|
|
843
|
+
...this.withDefaults({ ...options, eventId })
|
|
844
|
+
});
|
|
845
|
+
dispatchDevToolsEvent("event", {
|
|
846
|
+
name,
|
|
847
|
+
properties,
|
|
848
|
+
eventId,
|
|
849
|
+
timestamp: options.timestamp,
|
|
850
|
+
environment: options.environment,
|
|
851
|
+
sessionId: options.sessionId
|
|
661
852
|
});
|
|
662
853
|
}
|
|
663
854
|
captureError(error, options = {}) {
|
|
@@ -676,6 +867,13 @@ var EmitVisionClient = class {
|
|
|
676
867
|
identify(userIdOrUser, traits = {}) {
|
|
677
868
|
this.user = typeof userIdOrUser === "string" ? { id: userIdOrUser, ...traits } : { ...userIdOrUser };
|
|
678
869
|
debugLog("identified user", this.options, this.user);
|
|
870
|
+
const { id, ...rest } = this.user;
|
|
871
|
+
dispatchDevToolsEvent("identify", { userId: id, traits: rest });
|
|
872
|
+
}
|
|
873
|
+
group(groupId, traits) {
|
|
874
|
+
this.groupId = groupId;
|
|
875
|
+
debugLog("set group", this.options, { groupId, traits });
|
|
876
|
+
dispatchDevToolsEvent("group", { groupId, traits });
|
|
679
877
|
}
|
|
680
878
|
setContext(context) {
|
|
681
879
|
this.context = { ...this.context, ...context };
|
|
@@ -712,6 +910,14 @@ var EmitVisionClient = class {
|
|
|
712
910
|
if (!page) return;
|
|
713
911
|
this.captureEvent("$page_view", { page });
|
|
714
912
|
}
|
|
913
|
+
captureFeedback(feedback, options = {}) {
|
|
914
|
+
const url = feedback.url ?? (typeof window !== "undefined" ? window.location.href : void 0);
|
|
915
|
+
this.captureEvent(
|
|
916
|
+
"$feedback",
|
|
917
|
+
{ message: feedback.message, ...feedback.rating !== void 0 ? { rating: feedback.rating } : {}, ...url ? { url } : {} },
|
|
918
|
+
options
|
|
919
|
+
);
|
|
920
|
+
}
|
|
715
921
|
captureExposure(flagKey, variantKey, variantValue, reason, environment) {
|
|
716
922
|
this.captureEvent("$flag_exposure", {
|
|
717
923
|
flagKey,
|
|
@@ -725,10 +931,19 @@ var EmitVisionClient = class {
|
|
|
725
931
|
this.tags = { ...this.tags, ...tags };
|
|
726
932
|
debugLog("updated tags", this.options, this.tags);
|
|
727
933
|
}
|
|
934
|
+
consent() {
|
|
935
|
+
return this.flusher.giveConsent();
|
|
936
|
+
}
|
|
728
937
|
flush() {
|
|
729
938
|
return this.flusher.flush();
|
|
730
939
|
}
|
|
731
940
|
close() {
|
|
941
|
+
if (typeof window !== "undefined") {
|
|
942
|
+
window.removeEventListener("pagehide", this.persistOnHide);
|
|
943
|
+
}
|
|
944
|
+
if (typeof document !== "undefined") {
|
|
945
|
+
document.removeEventListener("visibilitychange", this.persistOnHidden);
|
|
946
|
+
}
|
|
732
947
|
this.flusher.close();
|
|
733
948
|
this.autoCapture.detach();
|
|
734
949
|
debugLog("closed client", this.options);
|
|
@@ -741,7 +956,7 @@ var EmitVisionClient = class {
|
|
|
741
956
|
this.featureFlags,
|
|
742
957
|
this.tags,
|
|
743
958
|
this.options,
|
|
744
|
-
opts
|
|
959
|
+
{ groupId: this.groupId, ...opts }
|
|
745
960
|
);
|
|
746
961
|
}
|
|
747
962
|
};
|
|
@@ -780,6 +995,9 @@ function captureError(error, options) {
|
|
|
780
995
|
function capturePageView(path) {
|
|
781
996
|
requireClient().capturePageView(path);
|
|
782
997
|
}
|
|
998
|
+
function captureFeedback(feedback, options) {
|
|
999
|
+
requireClient().captureFeedback(feedback, options);
|
|
1000
|
+
}
|
|
783
1001
|
function identify(userIdOrUser, traits) {
|
|
784
1002
|
requireClient().identify(userIdOrUser, traits);
|
|
785
1003
|
}
|
|
@@ -798,6 +1016,12 @@ function setTags(tags) {
|
|
|
798
1016
|
async function flush() {
|
|
799
1017
|
await requireClient().flush();
|
|
800
1018
|
}
|
|
1019
|
+
async function consent() {
|
|
1020
|
+
await requireClient().consent();
|
|
1021
|
+
}
|
|
1022
|
+
function group(groupId, traits) {
|
|
1023
|
+
requireClient().group(groupId, traits);
|
|
1024
|
+
}
|
|
801
1025
|
async function evaluateFlags(options) {
|
|
802
1026
|
return requireClient().evaluateFlags(options);
|
|
803
1027
|
}
|
|
@@ -826,10 +1050,13 @@ export {
|
|
|
826
1050
|
captureError,
|
|
827
1051
|
captureEvent,
|
|
828
1052
|
captureExposure,
|
|
1053
|
+
captureFeedback,
|
|
829
1054
|
capturePageView,
|
|
1055
|
+
consent,
|
|
830
1056
|
evaluateFlags,
|
|
831
1057
|
flush,
|
|
832
1058
|
getFlag,
|
|
1059
|
+
group,
|
|
833
1060
|
identify,
|
|
834
1061
|
init,
|
|
835
1062
|
refreshFlags,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emit-vision/sdk-js",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Browser SDK for self-hosted emit-vision analytics and error tracking.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,8 +33,19 @@
|
|
|
33
33
|
"default": "./dist/index.js"
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
|
+
"size-limit": [
|
|
37
|
+
{
|
|
38
|
+
"name": "@emit-vision/sdk-js (gzip)",
|
|
39
|
+
"path": "dist/index.js",
|
|
40
|
+
"limit": "9 kB"
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@size-limit/file": "^11.1.6",
|
|
45
|
+
"size-limit": "^11.1.6"
|
|
46
|
+
},
|
|
36
47
|
"scripts": {
|
|
37
48
|
"build": "tsup --config tsup.config.ts",
|
|
38
|
-
"
|
|
49
|
+
"size": "size-limit"
|
|
39
50
|
}
|
|
40
|
-
}
|
|
51
|
+
}
|