@emit-vision/sdk-js 0.1.0 → 0.3.0
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 +51 -40
- package/dist/index.js +733 -421
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,462 +1,774 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
+
// src/flag-eval.ts
|
|
60
|
+
var FlagEvaluator = class {
|
|
61
|
+
constructor(cfg, onFlagsLoaded, captureExposure2, debug) {
|
|
62
|
+
this.cfg = cfg;
|
|
63
|
+
this.onFlagsLoaded = onFlagsLoaded;
|
|
64
|
+
this.captureExposure = captureExposure2;
|
|
65
|
+
this.debug = debug;
|
|
66
|
+
}
|
|
67
|
+
cfg;
|
|
68
|
+
onFlagsLoaded;
|
|
69
|
+
captureExposure;
|
|
70
|
+
debug;
|
|
71
|
+
cache = /* @__PURE__ */ new Map();
|
|
72
|
+
async evaluateFlags(opts) {
|
|
73
|
+
const env = opts.environment ?? "";
|
|
74
|
+
const ttl = opts.ttlMs ?? this.cfg.ttlMs;
|
|
75
|
+
const key = `${env}:${opts.evaluationKey}`;
|
|
76
|
+
const cached = this.cache.get(key);
|
|
77
|
+
if (cached && Date.now() < cached.expiresAt) {
|
|
78
|
+
this.debug("flag eval cache hit", { key });
|
|
79
|
+
return cached.flags;
|
|
80
|
+
}
|
|
81
|
+
return this.fetchAndCacheFlags(opts, env, ttl, key);
|
|
82
|
+
}
|
|
83
|
+
async getFlag(flagKey, fallback, opts) {
|
|
84
|
+
try {
|
|
85
|
+
const flags = await this.evaluateFlags(opts);
|
|
86
|
+
const value = flags[flagKey];
|
|
87
|
+
return value !== void 0 ? value : fallback;
|
|
88
|
+
} catch {
|
|
89
|
+
return fallback;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async refreshFlags(opts) {
|
|
93
|
+
const env = opts.environment ?? "";
|
|
94
|
+
const ttl = opts.ttlMs ?? this.cfg.ttlMs;
|
|
95
|
+
const key = `${env}:${opts.evaluationKey}`;
|
|
96
|
+
this.cache.delete(key);
|
|
97
|
+
return this.fetchAndCacheFlags(opts, env, ttl, key);
|
|
98
|
+
}
|
|
99
|
+
async fetchAndCacheFlags(opts, env, ttl, cacheKey) {
|
|
100
|
+
let flags = {};
|
|
101
|
+
let evaluations = {};
|
|
102
|
+
try {
|
|
103
|
+
const body = {
|
|
104
|
+
environment: env,
|
|
105
|
+
userKey: opts.evaluationKey
|
|
106
|
+
};
|
|
107
|
+
if (opts.flagKeys && opts.flagKeys.length > 0) {
|
|
108
|
+
body.flagKeys = opts.flagKeys;
|
|
109
|
+
}
|
|
110
|
+
const response = await this.cfg.fetchImpl(
|
|
111
|
+
`${this.cfg.endpoint}/v1/flags/evaluate`,
|
|
112
|
+
{
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: {
|
|
115
|
+
"content-type": "application/json",
|
|
116
|
+
"x-emit-api-key": this.cfg.apiKey
|
|
117
|
+
},
|
|
118
|
+
body: JSON.stringify(body)
|
|
34
119
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
120
|
+
);
|
|
121
|
+
if (response.ok) {
|
|
122
|
+
const data = await response.json();
|
|
123
|
+
evaluations = data.evaluations;
|
|
124
|
+
flags = Object.fromEntries(
|
|
125
|
+
Object.entries(evaluations).map(([k, v]) => [k, v.variantValue])
|
|
126
|
+
);
|
|
127
|
+
this.debug("flag eval complete", { count: Object.keys(flags).length });
|
|
128
|
+
} else {
|
|
129
|
+
this.debug("flag eval response error", { status: response.status });
|
|
130
|
+
}
|
|
131
|
+
} catch (err) {
|
|
132
|
+
this.debug("flag eval network error", { error: err });
|
|
44
133
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
134
|
+
this.cache.set(cacheKey, { flags, expiresAt: Date.now() + ttl });
|
|
135
|
+
this.onFlagsLoaded(flags);
|
|
136
|
+
if (this.cfg.flagExposures) {
|
|
137
|
+
for (const [flagKey, entry] of Object.entries(evaluations)) {
|
|
138
|
+
this.captureExposure(
|
|
139
|
+
flagKey,
|
|
140
|
+
entry.variantKey,
|
|
141
|
+
entry.variantValue,
|
|
142
|
+
entry.reason,
|
|
143
|
+
env
|
|
144
|
+
);
|
|
145
|
+
}
|
|
53
146
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
147
|
+
return flags;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// src/util.ts
|
|
152
|
+
function parseDsn(dsn) {
|
|
153
|
+
const parsed = new URL(dsn);
|
|
154
|
+
const apiKey = decodeURIComponent(parsed.username);
|
|
155
|
+
if (!apiKey) {
|
|
156
|
+
throw new Error("emit-vision dsn must include the API key before the @");
|
|
157
|
+
}
|
|
158
|
+
const pathname = stripTrailingSlash(parsed.pathname) ?? "";
|
|
159
|
+
const endpointPath = pathname.endsWith("/v1") ? pathname.slice(0, -"/v1".length) : pathname;
|
|
160
|
+
return {
|
|
161
|
+
apiKey,
|
|
162
|
+
endpoint: stripTrailingSlash(`${parsed.origin}${endpointPath}`)
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function stripTrailingSlash(value) {
|
|
166
|
+
if (!value) {
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
170
|
+
}
|
|
171
|
+
function serializeError(error) {
|
|
172
|
+
if (error instanceof Error) {
|
|
173
|
+
return {
|
|
174
|
+
message: error.message,
|
|
175
|
+
name: error.name,
|
|
176
|
+
stack: error.stack,
|
|
177
|
+
handled: true
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
message: typeof error === "string" ? error : JSON.stringify(error),
|
|
182
|
+
handled: true
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function resolveTransport(options) {
|
|
186
|
+
const dsn = options.dsn ? parseDsn(options.dsn) : null;
|
|
187
|
+
const apiKey = options.apiKey ?? dsn?.apiKey;
|
|
188
|
+
if (!apiKey) {
|
|
189
|
+
throw new Error("emit-vision init requires either apiKey or dsn");
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
apiKey,
|
|
193
|
+
endpoint: stripTrailingSlash(options.endpoint ?? dsn?.endpoint)
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
var ANON_ID_KEY = "__ev_anon_id";
|
|
197
|
+
function getOrCreateAnonymousId() {
|
|
198
|
+
try {
|
|
199
|
+
const stored = localStorage.getItem(ANON_ID_KEY);
|
|
200
|
+
if (stored) return stored;
|
|
201
|
+
const id = crypto.randomUUID();
|
|
202
|
+
localStorage.setItem(ANON_ID_KEY, id);
|
|
203
|
+
return id;
|
|
204
|
+
} catch {
|
|
205
|
+
return void 0;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function resolveAutoCapture(options) {
|
|
209
|
+
const legacyDefault = options.autoCaptureErrors === void 0 ? true : options.autoCaptureErrors;
|
|
210
|
+
return {
|
|
211
|
+
errors: options.autoCapture?.errors ?? legacyDefault,
|
|
212
|
+
unhandledRejections: options.autoCapture?.unhandledRejections ?? legacyDefault,
|
|
213
|
+
flagExposures: options.autoCapture?.flagExposures ?? true,
|
|
214
|
+
pageViews: options.autoCapture?.pageViews ?? false
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/client.ts
|
|
219
|
+
var SDK_NAME = "emit-vision-js";
|
|
220
|
+
var SDK_VERSION = "0.3.0";
|
|
221
|
+
var MAX_EVENT_BYTES = 64 * 1024;
|
|
222
|
+
var MAX_BATCH_BYTES = 1 * 1024 * 1024;
|
|
223
|
+
function byteLength(s) {
|
|
224
|
+
return new TextEncoder().encode(s).length;
|
|
225
|
+
}
|
|
226
|
+
function trimEventIfOversized(payload) {
|
|
227
|
+
const json = JSON.stringify(payload);
|
|
228
|
+
const originalBytes = byteLength(json);
|
|
229
|
+
if (originalBytes <= MAX_EVENT_BYTES) {
|
|
230
|
+
return { payload, trimmedFields: null, originalBytes };
|
|
231
|
+
}
|
|
232
|
+
const propBytes = payload.properties ? byteLength(JSON.stringify(payload.properties)) : 0;
|
|
233
|
+
const ctxBytes = payload.context ? byteLength(JSON.stringify(payload.context)) : 0;
|
|
234
|
+
if (typeof console.warn === "function") {
|
|
235
|
+
console.warn(
|
|
236
|
+
`[emit-vision] event truncated \u2014 "${payload.name}" was ${originalBytes} bytes (max ${MAX_EVENT_BYTES}); properties/context trimmed.`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
const trimmedFields = [];
|
|
240
|
+
if (propBytes > 0) trimmedFields.push("properties");
|
|
241
|
+
if (ctxBytes > 0) trimmedFields.push("context");
|
|
242
|
+
if (payload.type === "error" && payload.error.stack) {
|
|
243
|
+
trimmedFields.push("error.stack");
|
|
244
|
+
}
|
|
245
|
+
const base = {
|
|
246
|
+
properties: propBytes > 0 ? { _truncated: true, _originalBytes: propBytes } : void 0,
|
|
247
|
+
context: ctxBytes > 0 ? { _truncated: true, _originalBytes: ctxBytes } : void 0
|
|
248
|
+
};
|
|
249
|
+
if (payload.type === "error") {
|
|
250
|
+
return {
|
|
251
|
+
payload: { ...payload, ...base, error: { ...payload.error, stack: void 0 } },
|
|
252
|
+
trimmedFields,
|
|
253
|
+
originalBytes
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
return { payload: { ...payload, ...base }, trimmedFields, originalBytes };
|
|
257
|
+
}
|
|
258
|
+
function buildBatchBody(chunk) {
|
|
259
|
+
return JSON.stringify({
|
|
260
|
+
events: chunk.map((item) => ({
|
|
261
|
+
...item,
|
|
262
|
+
sdk: { name: SDK_NAME, version: SDK_VERSION }
|
|
263
|
+
}))
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
function splitBatch(batch) {
|
|
267
|
+
if (batch.length <= 1) return [batch];
|
|
268
|
+
const body = buildBatchBody(batch);
|
|
269
|
+
if (byteLength(body) <= MAX_BATCH_BYTES) return [batch];
|
|
270
|
+
const mid = Math.floor(batch.length / 2);
|
|
271
|
+
return [...splitBatch(batch.slice(0, mid)), ...splitBatch(batch.slice(mid))];
|
|
272
|
+
}
|
|
273
|
+
var EmitVisionClient = class {
|
|
274
|
+
constructor(options) {
|
|
275
|
+
this.options = options;
|
|
276
|
+
this.fetchImpl = options.fetchImpl;
|
|
277
|
+
this.deployment = options.deployment ? { ...options.deployment } : void 0;
|
|
278
|
+
this.featureFlags = options.featureFlags ? { ...options.featureFlags } : {};
|
|
279
|
+
this.retry = new FlushRetryController({
|
|
280
|
+
initialMs: options.retryBackoffInitialMs,
|
|
281
|
+
maxMs: options.retryBackoffMaxMs,
|
|
282
|
+
failureCap: options.maxConsecutiveFlushFailures
|
|
283
|
+
});
|
|
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
|
+
this.timer = setInterval(() => {
|
|
299
|
+
if (this.queue.length === 0 || this.retry.isBackingOff()) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
this.flush().catch(
|
|
303
|
+
(err) => this.debug("background flush error", { error: err })
|
|
304
|
+
);
|
|
305
|
+
}, options.flushIntervalMs);
|
|
306
|
+
if (options.autoCapture.errors && typeof window !== "undefined") {
|
|
307
|
+
window.addEventListener("error", this.handleWindowError);
|
|
67
308
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
this.debug("identified user", this.user);
|
|
309
|
+
if (options.autoCapture.unhandledRejections && typeof window !== "undefined") {
|
|
310
|
+
window.addEventListener(
|
|
311
|
+
"unhandledrejection",
|
|
312
|
+
this.handleUnhandledRejection
|
|
313
|
+
);
|
|
74
314
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
315
|
+
if (options.autoCapture.pageViews && typeof window !== "undefined") {
|
|
316
|
+
this.capturePageView();
|
|
317
|
+
window.addEventListener("popstate", this.handlePopState);
|
|
318
|
+
this.patchHistoryMethod("pushState");
|
|
319
|
+
this.patchHistoryMethod("replaceState");
|
|
78
320
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
321
|
+
this.debug("initialized", {
|
|
322
|
+
endpoint: options.endpoint,
|
|
323
|
+
environment: options.environment,
|
|
324
|
+
release: options.release,
|
|
325
|
+
autoCapture: options.autoCapture,
|
|
326
|
+
batchSize: options.batchSize,
|
|
327
|
+
flushIntervalMs: options.flushIntervalMs,
|
|
328
|
+
flushOnCapture: options.flushOnCapture
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
options;
|
|
332
|
+
queue = [];
|
|
333
|
+
user;
|
|
334
|
+
context = {};
|
|
335
|
+
deployment;
|
|
336
|
+
featureFlags = {};
|
|
337
|
+
tags = {};
|
|
338
|
+
timer;
|
|
339
|
+
flushScheduled = false;
|
|
340
|
+
fetchImpl;
|
|
341
|
+
flagEval;
|
|
342
|
+
retry;
|
|
343
|
+
captureEvent(name, properties, options = {}) {
|
|
344
|
+
this.debug("queue event", { name, properties, options });
|
|
345
|
+
this.enqueue({
|
|
346
|
+
type: "event",
|
|
347
|
+
name,
|
|
348
|
+
properties,
|
|
349
|
+
...this.withDefaults(options)
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
captureError(error, options = {}) {
|
|
353
|
+
const serialized = serializeError(error);
|
|
354
|
+
this.debug("queue error", {
|
|
355
|
+
message: serialized.message,
|
|
356
|
+
handled: serialized.handled,
|
|
357
|
+
options
|
|
358
|
+
});
|
|
359
|
+
this.enqueue({
|
|
360
|
+
type: "error",
|
|
361
|
+
name: "error",
|
|
362
|
+
error: serialized,
|
|
363
|
+
...this.withDefaults(options)
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
identify(userIdOrUser, traits = {}) {
|
|
367
|
+
this.user = typeof userIdOrUser === "string" ? { id: userIdOrUser, ...traits } : { ...userIdOrUser };
|
|
368
|
+
this.debug("identified user", this.user);
|
|
369
|
+
}
|
|
370
|
+
setContext(context) {
|
|
371
|
+
this.context = { ...this.context, ...context };
|
|
372
|
+
this.debug("updated context", this.context);
|
|
373
|
+
}
|
|
374
|
+
setDeploymentContext(context) {
|
|
375
|
+
this.deployment = Object.keys(context).length > 0 ? { ...context } : void 0;
|
|
376
|
+
this.debug("updated deployment context", this.deployment);
|
|
377
|
+
}
|
|
378
|
+
setFeatureFlags(featureFlags) {
|
|
379
|
+
this.featureFlags = { ...featureFlags };
|
|
380
|
+
this.debug("updated feature flags", this.featureFlags);
|
|
381
|
+
}
|
|
382
|
+
evaluateFlags(opts) {
|
|
383
|
+
return this.flagEval.evaluateFlags({
|
|
384
|
+
...opts,
|
|
385
|
+
environment: opts.environment ?? this.options.environment
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
getFlag(flagKey, fallback, opts) {
|
|
389
|
+
return this.flagEval.getFlag(flagKey, fallback, {
|
|
390
|
+
...opts,
|
|
391
|
+
environment: opts.environment ?? this.options.environment
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
refreshFlags(opts) {
|
|
395
|
+
return this.flagEval.refreshFlags({
|
|
396
|
+
...opts,
|
|
397
|
+
environment: opts.environment ?? this.options.environment
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
capturePageView(path) {
|
|
401
|
+
const page = path ?? (typeof window !== "undefined" ? window.location.pathname : void 0);
|
|
402
|
+
if (!page) return;
|
|
403
|
+
this.captureEvent("$page_view", { page });
|
|
404
|
+
}
|
|
405
|
+
captureExposure(flagKey, variantKey, variantValue, reason, environment) {
|
|
406
|
+
this.captureEvent("$flag_exposure", {
|
|
407
|
+
flagKey,
|
|
408
|
+
variantKey,
|
|
409
|
+
...variantValue !== void 0 ? { variantValue } : {},
|
|
410
|
+
...reason ? { reason } : {},
|
|
411
|
+
environment: environment ?? this.options.environment
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
setTags(tags) {
|
|
415
|
+
this.tags = { ...this.tags, ...tags };
|
|
416
|
+
this.debug("updated tags", this.tags);
|
|
417
|
+
}
|
|
418
|
+
async flush() {
|
|
419
|
+
if (this.retry.isDisabled()) {
|
|
420
|
+
return;
|
|
83
421
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
422
|
+
const batch = this.queue.splice(0, this.options.batchSize);
|
|
423
|
+
if (batch.length === 0) {
|
|
424
|
+
return;
|
|
87
425
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (cached && Date.now() < cached.expiresAt) {
|
|
100
|
-
this.debug("flag eval cache hit", { key });
|
|
101
|
-
return cached.flags;
|
|
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
|
+
}
|
|
102
437
|
}
|
|
103
|
-
|
|
438
|
+
throw err;
|
|
439
|
+
}
|
|
104
440
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
* Returns `fallback` on network failure or when the flag is not found — never throws.
|
|
108
|
-
*/
|
|
109
|
-
async getFlag(flagKey, fallback, opts) {
|
|
110
|
-
try {
|
|
111
|
-
const flags = await this.evaluateFlags(opts);
|
|
112
|
-
const value = flags[flagKey];
|
|
113
|
-
return (value !== undefined ? value : fallback);
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
return fallback;
|
|
117
|
-
}
|
|
441
|
+
if (this.options.flushOnCapture && this.queue.length > 0) {
|
|
442
|
+
this.scheduleFlush();
|
|
118
443
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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;
|
|
129
460
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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;
|
|
138
470
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (response.ok) {
|
|
159
|
-
const data = (await response.json());
|
|
160
|
-
evaluations = data.evaluations;
|
|
161
|
-
flags = Object.fromEntries(Object.entries(evaluations).map(([k, v]) => [k, v.variantValue]));
|
|
162
|
-
this.debug("flag eval complete", { count: Object.keys(flags).length });
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
this.debug("flag eval response error", { status: response.status });
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
catch (err) {
|
|
169
|
-
this.debug("flag eval network error", { error: err });
|
|
170
|
-
// Return empty flags on network error — never throw to callers
|
|
171
|
-
}
|
|
172
|
-
this.flagCache.set(cacheKey, { flags, expiresAt: Date.now() + ttl });
|
|
173
|
-
// Merge evaluated variants into the existing feature-flag context so that
|
|
174
|
-
// subsequent captureEvent / captureError calls include them automatically.
|
|
175
|
-
this.featureFlags = { ...this.featureFlags, ...flags };
|
|
176
|
-
if (this.options.autoCapture.flagExposures) {
|
|
177
|
-
for (const [flagKey, entry] of Object.entries(evaluations)) {
|
|
178
|
-
this.captureExposure(flagKey, entry.variantKey, entry.variantValue, entry.reason, env);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return flags;
|
|
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;
|
|
182
490
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
+
});
|
|
186
506
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
this.debug("flushing batch", { count: batch.length });
|
|
193
|
-
let response;
|
|
194
|
-
try {
|
|
195
|
-
response = await this.fetchImpl(`${this.options.endpoint}/v1/batch`, {
|
|
196
|
-
method: "POST",
|
|
197
|
-
headers: {
|
|
198
|
-
"content-type": "application/json",
|
|
199
|
-
"x-emit-api-key": this.options.apiKey,
|
|
200
|
-
},
|
|
201
|
-
body: JSON.stringify({
|
|
202
|
-
events: batch.map((item) => ({
|
|
203
|
-
...item,
|
|
204
|
-
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
|
205
|
-
})),
|
|
206
|
-
}),
|
|
207
|
-
keepalive: true,
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
catch (err) {
|
|
211
|
-
this.queue.unshift(...batch);
|
|
212
|
-
this.debug("flush network error", {
|
|
213
|
-
error: err,
|
|
214
|
-
restoredCount: batch.length,
|
|
215
|
-
});
|
|
216
|
-
throw err;
|
|
217
|
-
}
|
|
218
|
-
if (!response.ok) {
|
|
219
|
-
this.queue.unshift(...batch);
|
|
220
|
-
this.debug("flush failed", {
|
|
221
|
-
status: response.status,
|
|
222
|
-
restoredCount: batch.length,
|
|
223
|
-
});
|
|
224
|
-
throw new Error(`emit-vision flush failed with ${response.status}`);
|
|
225
|
-
}
|
|
226
|
-
this.debug("flush complete", { count: batch.length });
|
|
227
|
-
if (this.options.flushOnCapture && this.queue.length > 0) {
|
|
228
|
-
this.scheduleFlush();
|
|
229
|
-
}
|
|
507
|
+
}
|
|
508
|
+
stopBackgroundWork() {
|
|
509
|
+
if (this.timer) {
|
|
510
|
+
clearInterval(this.timer);
|
|
511
|
+
this.timer = void 0;
|
|
230
512
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
if (typeof window !== "undefined" && this.options.autoCapture.errors) {
|
|
237
|
-
window.removeEventListener("error", this.handleWindowError);
|
|
238
|
-
}
|
|
239
|
-
if (typeof window !== "undefined" &&
|
|
240
|
-
this.options.autoCapture.unhandledRejections) {
|
|
241
|
-
window.removeEventListener("unhandledrejection", this.handleUnhandledRejection);
|
|
242
|
-
}
|
|
243
|
-
this.debug("closed client");
|
|
513
|
+
}
|
|
514
|
+
close() {
|
|
515
|
+
this.stopBackgroundWork();
|
|
516
|
+
if (typeof window !== "undefined" && this.options.autoCapture.errors) {
|
|
517
|
+
window.removeEventListener("error", this.handleWindowError);
|
|
244
518
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
queueSize: this.queue.length,
|
|
251
|
-
flushOnCapture: this.options.flushOnCapture,
|
|
252
|
-
batchSize: this.options.batchSize,
|
|
253
|
-
});
|
|
254
|
-
if (this.options.flushOnCapture) {
|
|
255
|
-
this.debug("capture flush scheduled", {
|
|
256
|
-
queueSize: this.queue.length,
|
|
257
|
-
});
|
|
258
|
-
this.flush().catch((err) => this.debug("capture flush error", { error: err }));
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
if (this.queue.length >= this.options.batchSize) {
|
|
262
|
-
this.flush().catch((err) => this.debug("batch flush error", { error: err }));
|
|
263
|
-
}
|
|
519
|
+
if (typeof window !== "undefined" && this.options.autoCapture.unhandledRejections) {
|
|
520
|
+
window.removeEventListener(
|
|
521
|
+
"unhandledrejection",
|
|
522
|
+
this.handleUnhandledRejection
|
|
523
|
+
);
|
|
264
524
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
this.flushScheduled = true;
|
|
270
|
-
queueMicrotask(() => {
|
|
271
|
-
this.flushScheduled = false;
|
|
272
|
-
if (this.queue.length === 0) {
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
this.flush().catch((err) => this.debug("scheduled flush error", { error: err }));
|
|
276
|
-
});
|
|
525
|
+
if (typeof window !== "undefined" && this.options.autoCapture.pageViews) {
|
|
526
|
+
window.removeEventListener("popstate", this.handlePopState);
|
|
527
|
+
this.restoreHistoryMethod("pushState");
|
|
528
|
+
this.restoreHistoryMethod("replaceState");
|
|
277
529
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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;
|
|
293
567
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
};
|
|
299
|
-
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
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
|
+
);
|
|
300
572
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
};
|
|
306
|
-
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
573
|
+
}
|
|
574
|
+
scheduleFlush() {
|
|
575
|
+
if (this.flushScheduled) {
|
|
576
|
+
return;
|
|
307
577
|
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
|
319
611
|
};
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
this.enqueue({
|
|
326
|
-
type: "error",
|
|
327
|
-
name: "error",
|
|
328
|
-
error: { ...serialized, handled: false },
|
|
329
|
-
...this.withDefaults({
|
|
330
|
-
context: { source: "window.unhandledrejection" },
|
|
331
|
-
}),
|
|
332
|
-
});
|
|
612
|
+
}
|
|
613
|
+
mergeDeployment(deployment) {
|
|
614
|
+
const merged = {
|
|
615
|
+
...this.deployment ?? {},
|
|
616
|
+
...deployment ?? {}
|
|
333
617
|
};
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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;
|
|
346
654
|
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
client?.close();
|
|
353
|
-
client = new EmitVisionClient({
|
|
354
|
-
...options,
|
|
355
|
-
flushIntervalMs: options.flushIntervalMs ?? 5000,
|
|
356
|
-
batchSize: options.batchSize ?? 20,
|
|
357
|
-
debug: options.debug ?? false,
|
|
358
|
-
fetchImpl: options.fetchImpl ?? globalThis.fetch.bind(globalThis),
|
|
359
|
-
flushOnCapture: options.flushOnCapture ?? false,
|
|
360
|
-
flagEvalTtlMs: options.flagEvalTtlMs ?? 60_000,
|
|
361
|
-
apiKey: transport.apiKey,
|
|
362
|
-
endpoint: transport.endpoint ?? options.endpoint ?? "http://localhost:4301",
|
|
363
|
-
autoCapture,
|
|
655
|
+
}
|
|
656
|
+
handleUnhandledRejection = (event) => {
|
|
657
|
+
const serialized = serializeError(event.reason);
|
|
658
|
+
this.debug("auto-captured rejection", {
|
|
659
|
+
message: serialized.message
|
|
364
660
|
});
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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);
|
|
680
|
+
}
|
|
681
|
+
};
|
|
682
|
+
|
|
683
|
+
// src/index.ts
|
|
684
|
+
var client;
|
|
685
|
+
function init(options) {
|
|
686
|
+
const transport = resolveTransport(options);
|
|
687
|
+
const autoCapture = resolveAutoCapture(options);
|
|
688
|
+
const resolved = {
|
|
689
|
+
...options,
|
|
690
|
+
flushIntervalMs: options.flushIntervalMs ?? 5e3,
|
|
691
|
+
batchSize: options.batchSize ?? 20,
|
|
692
|
+
debug: options.debug ?? false,
|
|
693
|
+
fetchImpl: options.fetchImpl ?? globalThis.fetch.bind(globalThis),
|
|
694
|
+
flushOnCapture: options.flushOnCapture ?? false,
|
|
695
|
+
flagEvalTtlMs: options.flagEvalTtlMs ?? 6e4,
|
|
696
|
+
retryBackoffInitialMs: options.retryBackoffInitialMs ?? 1e3,
|
|
697
|
+
retryBackoffMaxMs: options.retryBackoffMaxMs ?? 3e4,
|
|
698
|
+
maxConsecutiveFlushFailures: options.maxConsecutiveFlushFailures ?? 20,
|
|
699
|
+
apiKey: transport.apiKey,
|
|
700
|
+
endpoint: transport.endpoint ?? options.endpoint ?? "http://localhost:4301",
|
|
701
|
+
autoCapture,
|
|
702
|
+
sessionId: options.sessionId ?? getOrCreateAnonymousId()
|
|
703
|
+
};
|
|
704
|
+
client?.close();
|
|
705
|
+
client = new EmitVisionClient(resolved);
|
|
706
|
+
return client;
|
|
378
707
|
}
|
|
379
|
-
|
|
380
|
-
|
|
708
|
+
function captureEvent(name, properties, options) {
|
|
709
|
+
requireClient().captureEvent(name, properties, options);
|
|
381
710
|
}
|
|
382
|
-
|
|
383
|
-
|
|
711
|
+
function captureError(error, options) {
|
|
712
|
+
requireClient().captureError(error, options);
|
|
384
713
|
}
|
|
385
|
-
|
|
386
|
-
|
|
714
|
+
function capturePageView(path) {
|
|
715
|
+
requireClient().capturePageView(path);
|
|
387
716
|
}
|
|
388
|
-
|
|
389
|
-
|
|
717
|
+
function identify(userIdOrUser, traits) {
|
|
718
|
+
requireClient().identify(userIdOrUser, traits);
|
|
390
719
|
}
|
|
391
|
-
|
|
392
|
-
|
|
720
|
+
function setContext(context) {
|
|
721
|
+
requireClient().setContext(context);
|
|
393
722
|
}
|
|
394
|
-
|
|
395
|
-
|
|
723
|
+
function setDeploymentContext(context) {
|
|
724
|
+
requireClient().setDeploymentContext(context);
|
|
396
725
|
}
|
|
397
|
-
|
|
398
|
-
|
|
726
|
+
function setFeatureFlags(featureFlags) {
|
|
727
|
+
requireClient().setFeatureFlags(featureFlags);
|
|
399
728
|
}
|
|
400
|
-
|
|
401
|
-
|
|
729
|
+
function setTags(tags) {
|
|
730
|
+
requireClient().setTags(tags);
|
|
402
731
|
}
|
|
403
|
-
function
|
|
404
|
-
|
|
405
|
-
throw new Error("emit-vision SDK has not been initialized");
|
|
406
|
-
}
|
|
407
|
-
return client;
|
|
732
|
+
async function flush() {
|
|
733
|
+
await requireClient().flush();
|
|
408
734
|
}
|
|
409
|
-
function
|
|
410
|
-
|
|
411
|
-
const apiKey = options.apiKey ?? dsn?.apiKey;
|
|
412
|
-
if (!apiKey) {
|
|
413
|
-
throw new Error("emit-vision init requires either apiKey or dsn");
|
|
414
|
-
}
|
|
415
|
-
return {
|
|
416
|
-
apiKey,
|
|
417
|
-
endpoint: stripTrailingSlash(options.endpoint ?? dsn?.endpoint),
|
|
418
|
-
};
|
|
735
|
+
async function evaluateFlags(options) {
|
|
736
|
+
return requireClient().evaluateFlags(options);
|
|
419
737
|
}
|
|
420
|
-
function
|
|
421
|
-
|
|
422
|
-
return {
|
|
423
|
-
errors: options.autoCapture?.errors ?? legacyDefault,
|
|
424
|
-
unhandledRejections: options.autoCapture?.unhandledRejections ?? legacyDefault,
|
|
425
|
-
flagExposures: options.autoCapture?.flagExposures ?? true,
|
|
426
|
-
};
|
|
738
|
+
async function getFlag(flagKey, fallback, options) {
|
|
739
|
+
return requireClient().getFlag(flagKey, fallback, options);
|
|
427
740
|
}
|
|
428
|
-
function
|
|
429
|
-
|
|
430
|
-
const apiKey = decodeURIComponent(parsed.username);
|
|
431
|
-
if (!apiKey) {
|
|
432
|
-
throw new Error("emit-vision dsn must include the API key before the @");
|
|
433
|
-
}
|
|
434
|
-
const pathname = stripTrailingSlash(parsed.pathname) ?? "";
|
|
435
|
-
const endpointPath = pathname.endsWith("/v1")
|
|
436
|
-
? pathname.slice(0, -"/v1".length)
|
|
437
|
-
: pathname;
|
|
438
|
-
return {
|
|
439
|
-
apiKey,
|
|
440
|
-
endpoint: stripTrailingSlash(`${parsed.origin}${endpointPath}`),
|
|
441
|
-
};
|
|
741
|
+
async function refreshFlags(options) {
|
|
742
|
+
return requireClient().refreshFlags(options);
|
|
442
743
|
}
|
|
443
|
-
function
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
744
|
+
function captureExposure(flagKey, variantKey, variantValue, reason, environment) {
|
|
745
|
+
requireClient().captureExposure(
|
|
746
|
+
flagKey,
|
|
747
|
+
variantKey,
|
|
748
|
+
variantValue,
|
|
749
|
+
reason,
|
|
750
|
+
environment
|
|
751
|
+
);
|
|
448
752
|
}
|
|
449
|
-
function
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
stack: error.stack,
|
|
455
|
-
handled: true,
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
return {
|
|
459
|
-
message: typeof error === "string" ? error : JSON.stringify(error),
|
|
460
|
-
handled: true,
|
|
461
|
-
};
|
|
753
|
+
function requireClient() {
|
|
754
|
+
if (!client) {
|
|
755
|
+
throw new Error("emit-vision SDK has not been initialized");
|
|
756
|
+
}
|
|
757
|
+
return client;
|
|
462
758
|
}
|
|
759
|
+
export {
|
|
760
|
+
captureError,
|
|
761
|
+
captureEvent,
|
|
762
|
+
captureExposure,
|
|
763
|
+
capturePageView,
|
|
764
|
+
evaluateFlags,
|
|
765
|
+
flush,
|
|
766
|
+
getFlag,
|
|
767
|
+
identify,
|
|
768
|
+
init,
|
|
769
|
+
refreshFlags,
|
|
770
|
+
setContext,
|
|
771
|
+
setDeploymentContext,
|
|
772
|
+
setFeatureFlags,
|
|
773
|
+
setTags
|
|
774
|
+
};
|