@shipeasy/sdk 2.2.0 → 2.4.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/client/index.d.mts +27 -5
- package/dist/client/index.d.ts +27 -5
- package/dist/client/index.js +149 -108
- package/dist/client/index.mjs +150 -109
- package/package.json +1 -1
package/dist/client/index.d.mts
CHANGED
|
@@ -28,11 +28,23 @@ interface EvalResponse {
|
|
|
28
28
|
configs: Record<string, unknown>;
|
|
29
29
|
experiments: Record<string, EvalExpResult>;
|
|
30
30
|
}
|
|
31
|
+
interface AutoCollectGroups {
|
|
32
|
+
vitals: boolean;
|
|
33
|
+
errors: boolean;
|
|
34
|
+
engagement: boolean;
|
|
35
|
+
}
|
|
31
36
|
type FlagsClientBrowserEnv = "dev" | "staging" | "prod";
|
|
32
37
|
interface FlagsClientBrowserOptions {
|
|
33
38
|
sdkKey: string;
|
|
34
39
|
baseUrl?: string;
|
|
35
40
|
autoGuardrails?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Per-group enablement for auto-collected metrics. When set, overrides the
|
|
43
|
+
* blanket `autoGuardrails` flag for the specific groups listed. Any group
|
|
44
|
+
* not present in the object falls back to `autoGuardrails` (defaulting to
|
|
45
|
+
* true when `autoGuardrails` is true).
|
|
46
|
+
*/
|
|
47
|
+
autoGuardrailGroups?: Partial<AutoCollectGroups>;
|
|
36
48
|
/** Which published env to read values from. Defaults to "prod". */
|
|
37
49
|
env?: FlagsClientBrowserEnv;
|
|
38
50
|
}
|
|
@@ -40,6 +52,7 @@ declare class FlagsClientBrowser {
|
|
|
40
52
|
private readonly sdkKey;
|
|
41
53
|
private readonly baseUrl;
|
|
42
54
|
private readonly autoGuardrails;
|
|
55
|
+
private readonly autoGuardrailGroups;
|
|
43
56
|
private readonly env;
|
|
44
57
|
private evalResult;
|
|
45
58
|
private anonId;
|
|
@@ -125,11 +138,20 @@ interface ShipeasyClientConfig {
|
|
|
125
138
|
*/
|
|
126
139
|
autoIdentify?: boolean;
|
|
127
140
|
/**
|
|
128
|
-
* Capture web vitals (LCP, CLS, INP, TTFB, FCP)
|
|
129
|
-
*
|
|
130
|
-
*
|
|
141
|
+
* Capture web vitals (LCP, CLS, INP, TTFB, FCP, navigation timing), JS /
|
|
142
|
+
* network errors, and engagement signals (abandonment) as `__auto_*`
|
|
143
|
+
* metric events. Defaults to `true` — the worker bypasses event-catalog
|
|
144
|
+
* validation for `__auto_*` names so this is safe out of the box.
|
|
145
|
+
*
|
|
146
|
+
* Pass `false` to disable everything, or a per-group object to narrow:
|
|
147
|
+
*
|
|
148
|
+
* ```ts
|
|
149
|
+
* shipeasy({ apiKey, autoCollect: false }); // off
|
|
150
|
+
* shipeasy({ apiKey, autoCollect: { errors: false } }); // vitals + engagement only
|
|
151
|
+
* shipeasy({ apiKey }); // all groups on
|
|
152
|
+
* ```
|
|
131
153
|
*/
|
|
132
|
-
autoCollect?: boolean
|
|
154
|
+
autoCollect?: boolean | Partial<AutoCollectGroups>;
|
|
133
155
|
}
|
|
134
156
|
/**
|
|
135
157
|
* Initialise the ShipEasy client SDK and wire up lazy devtools.
|
|
@@ -237,4 +259,4 @@ interface I18nFacade {
|
|
|
237
259
|
}
|
|
238
260
|
declare const i18n: I18nFacade;
|
|
239
261
|
|
|
240
|
-
export { type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, shipeasy, version };
|
|
262
|
+
export { type AutoCollectGroups, type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, shipeasy, version };
|
package/dist/client/index.d.ts
CHANGED
|
@@ -28,11 +28,23 @@ interface EvalResponse {
|
|
|
28
28
|
configs: Record<string, unknown>;
|
|
29
29
|
experiments: Record<string, EvalExpResult>;
|
|
30
30
|
}
|
|
31
|
+
interface AutoCollectGroups {
|
|
32
|
+
vitals: boolean;
|
|
33
|
+
errors: boolean;
|
|
34
|
+
engagement: boolean;
|
|
35
|
+
}
|
|
31
36
|
type FlagsClientBrowserEnv = "dev" | "staging" | "prod";
|
|
32
37
|
interface FlagsClientBrowserOptions {
|
|
33
38
|
sdkKey: string;
|
|
34
39
|
baseUrl?: string;
|
|
35
40
|
autoGuardrails?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Per-group enablement for auto-collected metrics. When set, overrides the
|
|
43
|
+
* blanket `autoGuardrails` flag for the specific groups listed. Any group
|
|
44
|
+
* not present in the object falls back to `autoGuardrails` (defaulting to
|
|
45
|
+
* true when `autoGuardrails` is true).
|
|
46
|
+
*/
|
|
47
|
+
autoGuardrailGroups?: Partial<AutoCollectGroups>;
|
|
36
48
|
/** Which published env to read values from. Defaults to "prod". */
|
|
37
49
|
env?: FlagsClientBrowserEnv;
|
|
38
50
|
}
|
|
@@ -40,6 +52,7 @@ declare class FlagsClientBrowser {
|
|
|
40
52
|
private readonly sdkKey;
|
|
41
53
|
private readonly baseUrl;
|
|
42
54
|
private readonly autoGuardrails;
|
|
55
|
+
private readonly autoGuardrailGroups;
|
|
43
56
|
private readonly env;
|
|
44
57
|
private evalResult;
|
|
45
58
|
private anonId;
|
|
@@ -125,11 +138,20 @@ interface ShipeasyClientConfig {
|
|
|
125
138
|
*/
|
|
126
139
|
autoIdentify?: boolean;
|
|
127
140
|
/**
|
|
128
|
-
* Capture web vitals (LCP, CLS, INP, TTFB, FCP)
|
|
129
|
-
*
|
|
130
|
-
*
|
|
141
|
+
* Capture web vitals (LCP, CLS, INP, TTFB, FCP, navigation timing), JS /
|
|
142
|
+
* network errors, and engagement signals (abandonment) as `__auto_*`
|
|
143
|
+
* metric events. Defaults to `true` — the worker bypasses event-catalog
|
|
144
|
+
* validation for `__auto_*` names so this is safe out of the box.
|
|
145
|
+
*
|
|
146
|
+
* Pass `false` to disable everything, or a per-group object to narrow:
|
|
147
|
+
*
|
|
148
|
+
* ```ts
|
|
149
|
+
* shipeasy({ apiKey, autoCollect: false }); // off
|
|
150
|
+
* shipeasy({ apiKey, autoCollect: { errors: false } }); // vitals + engagement only
|
|
151
|
+
* shipeasy({ apiKey }); // all groups on
|
|
152
|
+
* ```
|
|
131
153
|
*/
|
|
132
|
-
autoCollect?: boolean
|
|
154
|
+
autoCollect?: boolean | Partial<AutoCollectGroups>;
|
|
133
155
|
}
|
|
134
156
|
/**
|
|
135
157
|
* Initialise the ShipEasy client SDK and wire up lazy devtools.
|
|
@@ -237,4 +259,4 @@ interface I18nFacade {
|
|
|
237
259
|
}
|
|
238
260
|
declare const i18n: I18nFacade;
|
|
239
261
|
|
|
240
|
-
export { type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, shipeasy, version };
|
|
262
|
+
export { type AutoCollectGroups, type BootstrapPayload, type ExperimentResult, FlagsClientBrowser, type FlagsClientBrowserEnv, type FlagsClientBrowserOptions, type I18nFacade, type I18nKey, type I18nRichComponents, type I18nString, type I18nTagRenderer, type I18nVariables, LABEL_MARKER_END, LABEL_MARKER_RE, LABEL_MARKER_SEP, LABEL_MARKER_START, type LabelAttrs, type ShipeasyClientConfig, type ShipeasySdkBridge, type User, _resetShipeasyForTests, attachDevtools, configureShipeasy, encodeLabelMarker, flags, getShipeasyClient, i18n, isDevtoolsRequested, labelAttrs, loadDevtools, readConfigOverride, readExpOverride, readGateOverride, shipeasy, version };
|
package/dist/client/index.js
CHANGED
|
@@ -167,7 +167,7 @@ var EventBuffer = class {
|
|
|
167
167
|
}
|
|
168
168
|
};
|
|
169
169
|
var MAX_ERRORS_PER_SESSION = 5;
|
|
170
|
-
function installAutoGuardrails(buffer, userId, anonId) {
|
|
170
|
+
function installAutoGuardrails(buffer, userId, anonId, groups) {
|
|
171
171
|
if (typeof window === "undefined" || typeof PerformanceObserver === "undefined") return;
|
|
172
172
|
let lcp = null;
|
|
173
173
|
let inp = null;
|
|
@@ -175,100 +175,105 @@ function installAutoGuardrails(buffer, userId, anonId) {
|
|
|
175
175
|
let jsErrorCount = 0;
|
|
176
176
|
let netErrorCount = 0;
|
|
177
177
|
let navTimingFlushed = false;
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
lcpObs.observe({ type: "largest-contentful-paint", buffered: true });
|
|
185
|
-
} catch {
|
|
186
|
-
}
|
|
187
|
-
try {
|
|
188
|
-
const inpObs = new PerformanceObserver((list) => {
|
|
189
|
-
for (const e of list.getEntries()) {
|
|
190
|
-
const dur = e.duration ?? 0;
|
|
191
|
-
if (inp === null || dur > inp) inp = dur;
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
inpObs.observe({
|
|
195
|
-
type: "event",
|
|
196
|
-
buffered: true,
|
|
197
|
-
durationThreshold: 16
|
|
198
|
-
});
|
|
199
|
-
} catch {
|
|
200
|
-
}
|
|
201
|
-
try {
|
|
202
|
-
const clsObs = new PerformanceObserver((list) => {
|
|
203
|
-
for (const e of list.getEntries()) {
|
|
204
|
-
if (e.value > 0.1) clsBad = true;
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
clsObs.observe({ type: "layout-shift", buffered: true });
|
|
208
|
-
} catch {
|
|
209
|
-
}
|
|
210
|
-
const origOnError = window.onerror;
|
|
211
|
-
window.onerror = (msg, source, lineno, _colno, err) => {
|
|
212
|
-
if (jsErrorCount < MAX_ERRORS_PER_SESSION) {
|
|
213
|
-
jsErrorCount += 1;
|
|
214
|
-
buffer.pushMetric("__auto_js_error", userId, anonId, {
|
|
215
|
-
value: 1,
|
|
216
|
-
kind: "exception",
|
|
217
|
-
message: typeof msg === "string" ? msg.slice(0, 200) : String(err ?? "").slice(0, 200),
|
|
218
|
-
source: typeof source === "string" ? source.slice(0, 200) : "",
|
|
219
|
-
line: lineno ?? 0
|
|
178
|
+
if (groups.vitals) {
|
|
179
|
+
try {
|
|
180
|
+
const lcpObs = new PerformanceObserver((list) => {
|
|
181
|
+
const entries = list.getEntries();
|
|
182
|
+
if (entries.length)
|
|
183
|
+
lcp = entries[entries.length - 1].startTime;
|
|
220
184
|
});
|
|
185
|
+
lcpObs.observe({ type: "largest-contentful-paint", buffered: true });
|
|
186
|
+
} catch {
|
|
221
187
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
message: message.slice(0, 200)
|
|
188
|
+
try {
|
|
189
|
+
const inpObs = new PerformanceObserver((list) => {
|
|
190
|
+
for (const e of list.getEntries()) {
|
|
191
|
+
const dur = e.duration ?? 0;
|
|
192
|
+
if (inp === null || dur > inp) inp = dur;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
inpObs.observe({
|
|
196
|
+
type: "event",
|
|
197
|
+
buffered: true,
|
|
198
|
+
durationThreshold: 16
|
|
234
199
|
});
|
|
200
|
+
} catch {
|
|
235
201
|
}
|
|
236
|
-
});
|
|
237
|
-
const origFetch = window.fetch;
|
|
238
|
-
window.fetch = async function(...args) {
|
|
239
|
-
const startedAt = typeof performance !== "undefined" ? performance.now() : 0;
|
|
240
|
-
const url = typeof args[0] === "string" ? args[0] : args[0].toString();
|
|
241
|
-
let res;
|
|
242
202
|
try {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
203
|
+
const clsObs = new PerformanceObserver((list) => {
|
|
204
|
+
for (const e of list.getEntries()) {
|
|
205
|
+
if (e.value > 0.1) clsBad = true;
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
clsObs.observe({ type: "layout-shift", buffered: true });
|
|
209
|
+
} catch {
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (groups.errors) {
|
|
213
|
+
const origOnError = window.onerror;
|
|
214
|
+
window.onerror = (msg, source, lineno, _colno, err) => {
|
|
215
|
+
if (jsErrorCount < MAX_ERRORS_PER_SESSION) {
|
|
216
|
+
jsErrorCount += 1;
|
|
217
|
+
buffer.pushMetric("__auto_js_error", userId, anonId, {
|
|
218
|
+
value: 1,
|
|
219
|
+
kind: "exception",
|
|
220
|
+
message: typeof msg === "string" ? msg.slice(0, 200) : String(err ?? "").slice(0, 200),
|
|
221
|
+
source: typeof source === "string" ? source.slice(0, 200) : "",
|
|
222
|
+
line: lineno ?? 0
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
if (typeof origOnError === "function") return origOnError(msg, source, lineno, _colno, err);
|
|
226
|
+
return false;
|
|
227
|
+
};
|
|
228
|
+
window.addEventListener("unhandledrejection", (e) => {
|
|
229
|
+
if (jsErrorCount < MAX_ERRORS_PER_SESSION) {
|
|
230
|
+
jsErrorCount += 1;
|
|
231
|
+
const reason = e.reason;
|
|
232
|
+
const message = reason instanceof Error ? reason.message : typeof reason === "string" ? reason : String(reason);
|
|
233
|
+
buffer.pushMetric("__auto_js_error", userId, anonId, {
|
|
234
|
+
value: 1,
|
|
235
|
+
kind: "unhandled_rejection",
|
|
236
|
+
message: message.slice(0, 200)
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
const origFetch = window.fetch;
|
|
241
|
+
window.fetch = async function(...args) {
|
|
242
|
+
const startedAt = typeof performance !== "undefined" ? performance.now() : 0;
|
|
243
|
+
const url = typeof args[0] === "string" ? args[0] : args[0].toString();
|
|
244
|
+
let res;
|
|
245
|
+
try {
|
|
246
|
+
res = await origFetch.apply(this, args);
|
|
247
|
+
} catch (err) {
|
|
248
|
+
if (netErrorCount < MAX_ERRORS_PER_SESSION) {
|
|
249
|
+
netErrorCount += 1;
|
|
250
|
+
buffer.pushMetric("__auto_network_error", userId, anonId, {
|
|
251
|
+
value: 1,
|
|
252
|
+
kind: "network",
|
|
253
|
+
status: 0,
|
|
254
|
+
url: url.slice(0, 200)
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
throw err;
|
|
258
|
+
}
|
|
259
|
+
if (res.status >= 500 && netErrorCount < MAX_ERRORS_PER_SESSION) {
|
|
246
260
|
netErrorCount += 1;
|
|
261
|
+
const elapsed = typeof performance !== "undefined" ? performance.now() - startedAt : 0;
|
|
247
262
|
buffer.pushMetric("__auto_network_error", userId, anonId, {
|
|
248
263
|
value: 1,
|
|
249
|
-
kind: "
|
|
250
|
-
status:
|
|
251
|
-
url: url.slice(0, 200)
|
|
264
|
+
kind: "5xx",
|
|
265
|
+
status: res.status,
|
|
266
|
+
url: url.slice(0, 200),
|
|
267
|
+
duration_ms: Math.round(elapsed)
|
|
252
268
|
});
|
|
253
269
|
}
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
netErrorCount += 1;
|
|
258
|
-
const elapsed = typeof performance !== "undefined" ? performance.now() - startedAt : 0;
|
|
259
|
-
buffer.pushMetric("__auto_network_error", userId, anonId, {
|
|
260
|
-
value: 1,
|
|
261
|
-
kind: "5xx",
|
|
262
|
-
status: res.status,
|
|
263
|
-
url: url.slice(0, 200),
|
|
264
|
-
duration_ms: Math.round(elapsed)
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
return res;
|
|
268
|
-
};
|
|
270
|
+
return res;
|
|
271
|
+
};
|
|
272
|
+
}
|
|
269
273
|
const flushNavTiming = () => {
|
|
270
274
|
if (navTimingFlushed) return;
|
|
271
275
|
navTimingFlushed = true;
|
|
276
|
+
if (!groups.vitals) return;
|
|
272
277
|
try {
|
|
273
278
|
const navList = performance.getEntriesByType("navigation");
|
|
274
279
|
const nav = navList[0];
|
|
@@ -301,29 +306,53 @@ function installAutoGuardrails(buffer, userId, anonId) {
|
|
|
301
306
|
} catch {
|
|
302
307
|
}
|
|
303
308
|
};
|
|
304
|
-
if (
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
309
|
+
if (groups.engagement) {
|
|
310
|
+
try {
|
|
311
|
+
buffer.pushMetric("__auto_session_active", userId, anonId, { value: 1 });
|
|
312
|
+
} catch {
|
|
313
|
+
}
|
|
314
|
+
let lastEmit = Date.now();
|
|
315
|
+
const SESSION_GAP_MS = 30 * 60 * 1e3;
|
|
316
|
+
document.addEventListener("visibilitychange", () => {
|
|
317
|
+
if (document.visibilityState !== "visible") return;
|
|
318
|
+
if (Date.now() - lastEmit < SESSION_GAP_MS) return;
|
|
319
|
+
try {
|
|
320
|
+
buffer.pushMetric("__auto_session_active", userId, anonId, { value: 1 });
|
|
321
|
+
lastEmit = Date.now();
|
|
322
|
+
} catch {
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
const needHide = groups.vitals || groups.engagement;
|
|
327
|
+
if (needHide) {
|
|
328
|
+
if (document.readyState === "complete") {
|
|
329
|
+
setTimeout(flushNavTiming, 0);
|
|
330
|
+
} else {
|
|
331
|
+
window.addEventListener(
|
|
332
|
+
"load",
|
|
333
|
+
() => {
|
|
334
|
+
setTimeout(flushNavTiming, 0);
|
|
335
|
+
},
|
|
336
|
+
{ once: true }
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
const flushOnHide = () => {
|
|
340
|
+
flushNavTiming();
|
|
341
|
+
if (groups.vitals) {
|
|
342
|
+
if (lcp !== null) buffer.pushMetric("__auto_lcp", userId, anonId, { value: lcp });
|
|
343
|
+
if (inp !== null) buffer.pushMetric("__auto_inp", userId, anonId, { value: inp });
|
|
344
|
+
if (clsBad) buffer.pushMetric("__auto_cls_binary", userId, anonId, { value: 1 });
|
|
345
|
+
}
|
|
346
|
+
if (groups.engagement) {
|
|
347
|
+
const abandoned = lcp === null ? 1 : 0;
|
|
348
|
+
buffer.pushMetric("__auto_abandoned", userId, anonId, { value: abandoned });
|
|
349
|
+
}
|
|
350
|
+
buffer.flush(true);
|
|
351
|
+
};
|
|
352
|
+
document.addEventListener("visibilitychange", () => {
|
|
353
|
+
if (document.visibilityState === "hidden") flushOnHide();
|
|
354
|
+
});
|
|
314
355
|
}
|
|
315
|
-
const flushOnHide = () => {
|
|
316
|
-
flushNavTiming();
|
|
317
|
-
if (lcp !== null) buffer.pushMetric("__auto_lcp", userId, anonId, { value: lcp });
|
|
318
|
-
if (inp !== null) buffer.pushMetric("__auto_inp", userId, anonId, { value: inp });
|
|
319
|
-
if (clsBad) buffer.pushMetric("__auto_cls_binary", userId, anonId, { value: 1 });
|
|
320
|
-
const abandoned = lcp === null ? 1 : 0;
|
|
321
|
-
buffer.pushMetric("__auto_abandoned", userId, anonId, { value: abandoned });
|
|
322
|
-
buffer.flush(true);
|
|
323
|
-
};
|
|
324
|
-
document.addEventListener("visibilitychange", () => {
|
|
325
|
-
if (document.visibilityState === "hidden") flushOnHide();
|
|
326
|
-
});
|
|
327
356
|
}
|
|
328
357
|
function getOrCreateAnonId() {
|
|
329
358
|
try {
|
|
@@ -391,6 +420,7 @@ var FlagsClientBrowser = class {
|
|
|
391
420
|
sdkKey;
|
|
392
421
|
baseUrl;
|
|
393
422
|
autoGuardrails;
|
|
423
|
+
autoGuardrailGroups;
|
|
394
424
|
env;
|
|
395
425
|
evalResult = null;
|
|
396
426
|
anonId;
|
|
@@ -410,7 +440,13 @@ var FlagsClientBrowser = class {
|
|
|
410
440
|
this.sdkKey = opts.sdkKey;
|
|
411
441
|
this.baseUrl = (opts.baseUrl ?? "https://edge.shipeasy.dev").replace(/\/$/, "");
|
|
412
442
|
this.env = opts.env ?? "prod";
|
|
413
|
-
this.autoGuardrails = opts.autoGuardrails
|
|
443
|
+
this.autoGuardrails = opts.autoGuardrails !== false;
|
|
444
|
+
const g = opts.autoGuardrailGroups ?? {};
|
|
445
|
+
this.autoGuardrailGroups = {
|
|
446
|
+
vitals: g.vitals ?? this.autoGuardrails,
|
|
447
|
+
errors: g.errors ?? this.autoGuardrails,
|
|
448
|
+
engagement: g.engagement ?? this.autoGuardrails
|
|
449
|
+
};
|
|
414
450
|
this.anonId = getOrCreateAnonId();
|
|
415
451
|
this.buffer = new EventBuffer(`${this.baseUrl}/collect`, this.sdkKey);
|
|
416
452
|
void this.buffer.flushPendingAlias();
|
|
@@ -439,9 +475,10 @@ var FlagsClientBrowser = class {
|
|
|
439
475
|
const data = await res.json();
|
|
440
476
|
if (seq !== this.identifySeq) return;
|
|
441
477
|
this.evalResult = data;
|
|
442
|
-
|
|
478
|
+
const anyGroupOn = this.autoGuardrailGroups.vitals || this.autoGuardrailGroups.errors || this.autoGuardrailGroups.engagement;
|
|
479
|
+
if (anyGroupOn && !this.guardrailsInstalled) {
|
|
443
480
|
this.guardrailsInstalled = true;
|
|
444
|
-
installAutoGuardrails(this.buffer, this.userId, this.anonId);
|
|
481
|
+
installAutoGuardrails(this.buffer, this.userId, this.anonId, this.autoGuardrailGroups);
|
|
445
482
|
}
|
|
446
483
|
this.notify();
|
|
447
484
|
}
|
|
@@ -657,10 +694,14 @@ function attachDevtools(client, opts = {}) {
|
|
|
657
694
|
}
|
|
658
695
|
var _client = null;
|
|
659
696
|
function shipeasy(opts) {
|
|
697
|
+
const ac = opts.autoCollect;
|
|
698
|
+
const blanket = ac === false ? false : true;
|
|
699
|
+
const groups = ac && typeof ac === "object" ? ac : void 0;
|
|
660
700
|
const client = configureShipeasy({
|
|
661
701
|
sdkKey: opts.apiKey,
|
|
662
702
|
baseUrl: opts.baseUrl ?? "https://cdn.shipeasy.ai",
|
|
663
|
-
autoGuardrails:
|
|
703
|
+
autoGuardrails: blanket,
|
|
704
|
+
autoGuardrailGroups: groups
|
|
664
705
|
});
|
|
665
706
|
flags.notifyMounted();
|
|
666
707
|
if (opts.autoIdentify !== false) {
|
package/dist/client/index.mjs
CHANGED
|
@@ -124,7 +124,7 @@ var EventBuffer = class {
|
|
|
124
124
|
}
|
|
125
125
|
};
|
|
126
126
|
var MAX_ERRORS_PER_SESSION = 5;
|
|
127
|
-
function installAutoGuardrails(buffer, userId, anonId) {
|
|
127
|
+
function installAutoGuardrails(buffer, userId, anonId, groups) {
|
|
128
128
|
if (typeof window === "undefined" || typeof PerformanceObserver === "undefined") return;
|
|
129
129
|
let lcp = null;
|
|
130
130
|
let inp = null;
|
|
@@ -132,100 +132,105 @@ function installAutoGuardrails(buffer, userId, anonId) {
|
|
|
132
132
|
let jsErrorCount = 0;
|
|
133
133
|
let netErrorCount = 0;
|
|
134
134
|
let navTimingFlushed = false;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
lcpObs.observe({ type: "largest-contentful-paint", buffered: true });
|
|
142
|
-
} catch {
|
|
143
|
-
}
|
|
144
|
-
try {
|
|
145
|
-
const inpObs = new PerformanceObserver((list) => {
|
|
146
|
-
for (const e of list.getEntries()) {
|
|
147
|
-
const dur = e.duration ?? 0;
|
|
148
|
-
if (inp === null || dur > inp) inp = dur;
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
inpObs.observe({
|
|
152
|
-
type: "event",
|
|
153
|
-
buffered: true,
|
|
154
|
-
durationThreshold: 16
|
|
155
|
-
});
|
|
156
|
-
} catch {
|
|
157
|
-
}
|
|
158
|
-
try {
|
|
159
|
-
const clsObs = new PerformanceObserver((list) => {
|
|
160
|
-
for (const e of list.getEntries()) {
|
|
161
|
-
if (e.value > 0.1) clsBad = true;
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
clsObs.observe({ type: "layout-shift", buffered: true });
|
|
165
|
-
} catch {
|
|
166
|
-
}
|
|
167
|
-
const origOnError = window.onerror;
|
|
168
|
-
window.onerror = (msg, source, lineno, _colno, err) => {
|
|
169
|
-
if (jsErrorCount < MAX_ERRORS_PER_SESSION) {
|
|
170
|
-
jsErrorCount += 1;
|
|
171
|
-
buffer.pushMetric("__auto_js_error", userId, anonId, {
|
|
172
|
-
value: 1,
|
|
173
|
-
kind: "exception",
|
|
174
|
-
message: typeof msg === "string" ? msg.slice(0, 200) : String(err ?? "").slice(0, 200),
|
|
175
|
-
source: typeof source === "string" ? source.slice(0, 200) : "",
|
|
176
|
-
line: lineno ?? 0
|
|
135
|
+
if (groups.vitals) {
|
|
136
|
+
try {
|
|
137
|
+
const lcpObs = new PerformanceObserver((list) => {
|
|
138
|
+
const entries = list.getEntries();
|
|
139
|
+
if (entries.length)
|
|
140
|
+
lcp = entries[entries.length - 1].startTime;
|
|
177
141
|
});
|
|
142
|
+
lcpObs.observe({ type: "largest-contentful-paint", buffered: true });
|
|
143
|
+
} catch {
|
|
178
144
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
message: message.slice(0, 200)
|
|
145
|
+
try {
|
|
146
|
+
const inpObs = new PerformanceObserver((list) => {
|
|
147
|
+
for (const e of list.getEntries()) {
|
|
148
|
+
const dur = e.duration ?? 0;
|
|
149
|
+
if (inp === null || dur > inp) inp = dur;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
inpObs.observe({
|
|
153
|
+
type: "event",
|
|
154
|
+
buffered: true,
|
|
155
|
+
durationThreshold: 16
|
|
191
156
|
});
|
|
157
|
+
} catch {
|
|
192
158
|
}
|
|
193
|
-
});
|
|
194
|
-
const origFetch = window.fetch;
|
|
195
|
-
window.fetch = async function(...args) {
|
|
196
|
-
const startedAt = typeof performance !== "undefined" ? performance.now() : 0;
|
|
197
|
-
const url = typeof args[0] === "string" ? args[0] : args[0].toString();
|
|
198
|
-
let res;
|
|
199
159
|
try {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
160
|
+
const clsObs = new PerformanceObserver((list) => {
|
|
161
|
+
for (const e of list.getEntries()) {
|
|
162
|
+
if (e.value > 0.1) clsBad = true;
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
clsObs.observe({ type: "layout-shift", buffered: true });
|
|
166
|
+
} catch {
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (groups.errors) {
|
|
170
|
+
const origOnError = window.onerror;
|
|
171
|
+
window.onerror = (msg, source, lineno, _colno, err) => {
|
|
172
|
+
if (jsErrorCount < MAX_ERRORS_PER_SESSION) {
|
|
173
|
+
jsErrorCount += 1;
|
|
174
|
+
buffer.pushMetric("__auto_js_error", userId, anonId, {
|
|
175
|
+
value: 1,
|
|
176
|
+
kind: "exception",
|
|
177
|
+
message: typeof msg === "string" ? msg.slice(0, 200) : String(err ?? "").slice(0, 200),
|
|
178
|
+
source: typeof source === "string" ? source.slice(0, 200) : "",
|
|
179
|
+
line: lineno ?? 0
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (typeof origOnError === "function") return origOnError(msg, source, lineno, _colno, err);
|
|
183
|
+
return false;
|
|
184
|
+
};
|
|
185
|
+
window.addEventListener("unhandledrejection", (e) => {
|
|
186
|
+
if (jsErrorCount < MAX_ERRORS_PER_SESSION) {
|
|
187
|
+
jsErrorCount += 1;
|
|
188
|
+
const reason = e.reason;
|
|
189
|
+
const message = reason instanceof Error ? reason.message : typeof reason === "string" ? reason : String(reason);
|
|
190
|
+
buffer.pushMetric("__auto_js_error", userId, anonId, {
|
|
191
|
+
value: 1,
|
|
192
|
+
kind: "unhandled_rejection",
|
|
193
|
+
message: message.slice(0, 200)
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
const origFetch = window.fetch;
|
|
198
|
+
window.fetch = async function(...args) {
|
|
199
|
+
const startedAt = typeof performance !== "undefined" ? performance.now() : 0;
|
|
200
|
+
const url = typeof args[0] === "string" ? args[0] : args[0].toString();
|
|
201
|
+
let res;
|
|
202
|
+
try {
|
|
203
|
+
res = await origFetch.apply(this, args);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
if (netErrorCount < MAX_ERRORS_PER_SESSION) {
|
|
206
|
+
netErrorCount += 1;
|
|
207
|
+
buffer.pushMetric("__auto_network_error", userId, anonId, {
|
|
208
|
+
value: 1,
|
|
209
|
+
kind: "network",
|
|
210
|
+
status: 0,
|
|
211
|
+
url: url.slice(0, 200)
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
throw err;
|
|
215
|
+
}
|
|
216
|
+
if (res.status >= 500 && netErrorCount < MAX_ERRORS_PER_SESSION) {
|
|
203
217
|
netErrorCount += 1;
|
|
218
|
+
const elapsed = typeof performance !== "undefined" ? performance.now() - startedAt : 0;
|
|
204
219
|
buffer.pushMetric("__auto_network_error", userId, anonId, {
|
|
205
220
|
value: 1,
|
|
206
|
-
kind: "
|
|
207
|
-
status:
|
|
208
|
-
url: url.slice(0, 200)
|
|
221
|
+
kind: "5xx",
|
|
222
|
+
status: res.status,
|
|
223
|
+
url: url.slice(0, 200),
|
|
224
|
+
duration_ms: Math.round(elapsed)
|
|
209
225
|
});
|
|
210
226
|
}
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
netErrorCount += 1;
|
|
215
|
-
const elapsed = typeof performance !== "undefined" ? performance.now() - startedAt : 0;
|
|
216
|
-
buffer.pushMetric("__auto_network_error", userId, anonId, {
|
|
217
|
-
value: 1,
|
|
218
|
-
kind: "5xx",
|
|
219
|
-
status: res.status,
|
|
220
|
-
url: url.slice(0, 200),
|
|
221
|
-
duration_ms: Math.round(elapsed)
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
return res;
|
|
225
|
-
};
|
|
227
|
+
return res;
|
|
228
|
+
};
|
|
229
|
+
}
|
|
226
230
|
const flushNavTiming = () => {
|
|
227
231
|
if (navTimingFlushed) return;
|
|
228
232
|
navTimingFlushed = true;
|
|
233
|
+
if (!groups.vitals) return;
|
|
229
234
|
try {
|
|
230
235
|
const navList = performance.getEntriesByType("navigation");
|
|
231
236
|
const nav = navList[0];
|
|
@@ -258,29 +263,53 @@ function installAutoGuardrails(buffer, userId, anonId) {
|
|
|
258
263
|
} catch {
|
|
259
264
|
}
|
|
260
265
|
};
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
266
|
+
if (groups.engagement) {
|
|
267
|
+
try {
|
|
268
|
+
buffer.pushMetric("__auto_session_active", userId, anonId, { value: 1 });
|
|
269
|
+
} catch {
|
|
270
|
+
}
|
|
271
|
+
let lastEmit = Date.now();
|
|
272
|
+
const SESSION_GAP_MS = 30 * 60 * 1e3;
|
|
273
|
+
document.addEventListener("visibilitychange", () => {
|
|
274
|
+
if (document.visibilityState !== "visible") return;
|
|
275
|
+
if (Date.now() - lastEmit < SESSION_GAP_MS) return;
|
|
276
|
+
try {
|
|
277
|
+
buffer.pushMetric("__auto_session_active", userId, anonId, { value: 1 });
|
|
278
|
+
lastEmit = Date.now();
|
|
279
|
+
} catch {
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
const needHide = groups.vitals || groups.engagement;
|
|
284
|
+
if (needHide) {
|
|
285
|
+
if (document.readyState === "complete") {
|
|
286
|
+
setTimeout(flushNavTiming, 0);
|
|
287
|
+
} else {
|
|
288
|
+
window.addEventListener(
|
|
289
|
+
"load",
|
|
290
|
+
() => {
|
|
291
|
+
setTimeout(flushNavTiming, 0);
|
|
292
|
+
},
|
|
293
|
+
{ once: true }
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
const flushOnHide = () => {
|
|
297
|
+
flushNavTiming();
|
|
298
|
+
if (groups.vitals) {
|
|
299
|
+
if (lcp !== null) buffer.pushMetric("__auto_lcp", userId, anonId, { value: lcp });
|
|
300
|
+
if (inp !== null) buffer.pushMetric("__auto_inp", userId, anonId, { value: inp });
|
|
301
|
+
if (clsBad) buffer.pushMetric("__auto_cls_binary", userId, anonId, { value: 1 });
|
|
302
|
+
}
|
|
303
|
+
if (groups.engagement) {
|
|
304
|
+
const abandoned = lcp === null ? 1 : 0;
|
|
305
|
+
buffer.pushMetric("__auto_abandoned", userId, anonId, { value: abandoned });
|
|
306
|
+
}
|
|
307
|
+
buffer.flush(true);
|
|
308
|
+
};
|
|
309
|
+
document.addEventListener("visibilitychange", () => {
|
|
310
|
+
if (document.visibilityState === "hidden") flushOnHide();
|
|
311
|
+
});
|
|
312
|
+
}
|
|
284
313
|
}
|
|
285
314
|
function getOrCreateAnonId() {
|
|
286
315
|
try {
|
|
@@ -348,6 +377,7 @@ var FlagsClientBrowser = class {
|
|
|
348
377
|
sdkKey;
|
|
349
378
|
baseUrl;
|
|
350
379
|
autoGuardrails;
|
|
380
|
+
autoGuardrailGroups;
|
|
351
381
|
env;
|
|
352
382
|
evalResult = null;
|
|
353
383
|
anonId;
|
|
@@ -367,7 +397,13 @@ var FlagsClientBrowser = class {
|
|
|
367
397
|
this.sdkKey = opts.sdkKey;
|
|
368
398
|
this.baseUrl = (opts.baseUrl ?? "https://edge.shipeasy.dev").replace(/\/$/, "");
|
|
369
399
|
this.env = opts.env ?? "prod";
|
|
370
|
-
this.autoGuardrails = opts.autoGuardrails
|
|
400
|
+
this.autoGuardrails = opts.autoGuardrails !== false;
|
|
401
|
+
const g = opts.autoGuardrailGroups ?? {};
|
|
402
|
+
this.autoGuardrailGroups = {
|
|
403
|
+
vitals: g.vitals ?? this.autoGuardrails,
|
|
404
|
+
errors: g.errors ?? this.autoGuardrails,
|
|
405
|
+
engagement: g.engagement ?? this.autoGuardrails
|
|
406
|
+
};
|
|
371
407
|
this.anonId = getOrCreateAnonId();
|
|
372
408
|
this.buffer = new EventBuffer(`${this.baseUrl}/collect`, this.sdkKey);
|
|
373
409
|
void this.buffer.flushPendingAlias();
|
|
@@ -396,9 +432,10 @@ var FlagsClientBrowser = class {
|
|
|
396
432
|
const data = await res.json();
|
|
397
433
|
if (seq !== this.identifySeq) return;
|
|
398
434
|
this.evalResult = data;
|
|
399
|
-
|
|
435
|
+
const anyGroupOn = this.autoGuardrailGroups.vitals || this.autoGuardrailGroups.errors || this.autoGuardrailGroups.engagement;
|
|
436
|
+
if (anyGroupOn && !this.guardrailsInstalled) {
|
|
400
437
|
this.guardrailsInstalled = true;
|
|
401
|
-
installAutoGuardrails(this.buffer, this.userId, this.anonId);
|
|
438
|
+
installAutoGuardrails(this.buffer, this.userId, this.anonId, this.autoGuardrailGroups);
|
|
402
439
|
}
|
|
403
440
|
this.notify();
|
|
404
441
|
}
|
|
@@ -614,10 +651,14 @@ function attachDevtools(client, opts = {}) {
|
|
|
614
651
|
}
|
|
615
652
|
var _client = null;
|
|
616
653
|
function shipeasy(opts) {
|
|
654
|
+
const ac = opts.autoCollect;
|
|
655
|
+
const blanket = ac === false ? false : true;
|
|
656
|
+
const groups = ac && typeof ac === "object" ? ac : void 0;
|
|
617
657
|
const client = configureShipeasy({
|
|
618
658
|
sdkKey: opts.apiKey,
|
|
619
659
|
baseUrl: opts.baseUrl ?? "https://cdn.shipeasy.ai",
|
|
620
|
-
autoGuardrails:
|
|
660
|
+
autoGuardrails: blanket,
|
|
661
|
+
autoGuardrailGroups: groups
|
|
621
662
|
});
|
|
622
663
|
flags.notifyMounted();
|
|
623
664
|
if (opts.autoIdentify !== false) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipeasy/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Shipeasy SDK — feature gates, runtime configs, experiments, and metrics for the Shipeasy hosted service.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"homepage": "https://shipeasy.ai",
|