rsclick-log-sdk-web 0.2.1 → 0.2.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/browser.d.ts +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.global.js +204 -7
- package/dist/index.js +206 -8
- package/dist/predefined-events.d.ts +35 -0
- package/dist/sdk-core.d.ts +3 -1
- package/dist/sdk-error.d.ts +18 -0
- package/dist/sdk.d.ts +9 -1
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/dist/browser.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
declare const TrackingAnalyticsSDK: {
|
|
2
|
+
captureError: (error: unknown, options?: import("./types").CaptureErrorOptions) => Promise<import("./types").TrackingDispatchResult>;
|
|
2
3
|
init: (options: import("./types").TrackingInitOptions) => Promise<void>;
|
|
3
4
|
track: (eventName: string, options?: import("./types").TrackOptions) => Promise<import("./types").TrackingDispatchResult>;
|
|
4
5
|
identify: (userId: string | null) => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export
|
|
1
|
+
export { PREDEFINED_TRACKING_EVENTS, type PredefinedTrackingEventName, } from "./predefined-events";
|
|
2
|
+
export { captureError, getDebugState, identify, init, page, reset, track } from "./sdk";
|
|
3
|
+
export type { CaptureErrorOptions, PageOptions, TrackOptions, TrackingCollectPayload, TrackingDispatchResult, TrackingEventPayload, TrackingInitOptions, TrackingProperties, TrackingResponse, TrackingScalar, TrackingStateSnapshot, } from "./types";
|
package/dist/index.global.js
CHANGED
|
@@ -33,6 +33,41 @@
|
|
|
33
33
|
TrackingAnalyticsSDK: () => TrackingAnalyticsSDK
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
+
// src/predefined-events.ts
|
|
37
|
+
var PREDEFINED_TRACKING_EVENTS = {
|
|
38
|
+
pageView: {
|
|
39
|
+
eventName: "$page_view",
|
|
40
|
+
displayName: "页面浏览"
|
|
41
|
+
},
|
|
42
|
+
elementClick: {
|
|
43
|
+
eventName: "$element_click",
|
|
44
|
+
displayName: "元素点击"
|
|
45
|
+
},
|
|
46
|
+
formSubmit: {
|
|
47
|
+
eventName: "$form_submit",
|
|
48
|
+
displayName: "表单提交"
|
|
49
|
+
},
|
|
50
|
+
search: {
|
|
51
|
+
eventName: "$search",
|
|
52
|
+
displayName: "发起搜索"
|
|
53
|
+
},
|
|
54
|
+
exposure: {
|
|
55
|
+
eventName: "$exposure",
|
|
56
|
+
displayName: "内容曝光"
|
|
57
|
+
},
|
|
58
|
+
fileDownload: {
|
|
59
|
+
eventName: "$file_download",
|
|
60
|
+
displayName: "文件下载"
|
|
61
|
+
},
|
|
62
|
+
share: {
|
|
63
|
+
eventName: "$share",
|
|
64
|
+
displayName: "分享"
|
|
65
|
+
},
|
|
66
|
+
error: {
|
|
67
|
+
eventName: "$error",
|
|
68
|
+
displayName: "异常错误"
|
|
69
|
+
}
|
|
70
|
+
};
|
|
36
71
|
// src/sdk-core.ts
|
|
37
72
|
var DEFAULT_ENDPOINT = "/track/collect";
|
|
38
73
|
var DEFAULT_SESSION_TIMEOUT_MINUTES = 30;
|
|
@@ -52,6 +87,7 @@
|
|
|
52
87
|
writeKey,
|
|
53
88
|
endpoint: normalizeOptionalString(options.endpoint) ?? DEFAULT_ENDPOINT,
|
|
54
89
|
autoPageview: options.autoPageview ?? true,
|
|
90
|
+
autoErrorCapture: options.autoErrorCapture ?? false,
|
|
55
91
|
sessionTimeoutMs: Math.max(options.sessionTimeoutMinutes ?? DEFAULT_SESSION_TIMEOUT_MINUTES, 1) * 60000,
|
|
56
92
|
storagePrefix: normalizeOptionalString(options.storagePrefix) ?? DEFAULT_STORAGE_PREFIX
|
|
57
93
|
};
|
|
@@ -261,6 +297,157 @@
|
|
|
261
297
|
return await response.json();
|
|
262
298
|
};
|
|
263
299
|
|
|
300
|
+
// src/sdk-error.ts
|
|
301
|
+
var MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
302
|
+
var MAX_ERROR_STACK_LENGTH = 2000;
|
|
303
|
+
var installGlobalErrorListeners = (browser, client) => {
|
|
304
|
+
const addEventListener = browser.addEventListener?.bind(globalThis);
|
|
305
|
+
const removeEventListener = browser.removeEventListener?.bind(globalThis);
|
|
306
|
+
if (!addEventListener || !removeEventListener) {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
const runtime = {
|
|
310
|
+
lastFingerprint: null,
|
|
311
|
+
lastCapturedAt: 0
|
|
312
|
+
};
|
|
313
|
+
const onError = (event) => {
|
|
314
|
+
captureErrorEvent(client, runtime, toWindowErrorTrackOptions(event));
|
|
315
|
+
};
|
|
316
|
+
const onUnhandledRejection = (event) => {
|
|
317
|
+
captureErrorEvent(client, runtime, toUnhandledRejectionTrackOptions(event));
|
|
318
|
+
};
|
|
319
|
+
addEventListener("error", onError);
|
|
320
|
+
addEventListener("unhandledrejection", onUnhandledRejection);
|
|
321
|
+
return () => {
|
|
322
|
+
removeEventListener("error", onError);
|
|
323
|
+
removeEventListener("unhandledrejection", onUnhandledRejection);
|
|
324
|
+
};
|
|
325
|
+
};
|
|
326
|
+
var captureErrorEvent = async (client, runtime, options) => {
|
|
327
|
+
const fingerprint = buildFingerprint(options.properties);
|
|
328
|
+
const now = Date.now();
|
|
329
|
+
if (fingerprint && runtime.lastFingerprint === fingerprint && now - runtime.lastCapturedAt < 1000) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
runtime.lastFingerprint = fingerprint;
|
|
333
|
+
runtime.lastCapturedAt = now;
|
|
334
|
+
try {
|
|
335
|
+
await client.track(PREDEFINED_TRACKING_EVENTS.error.eventName, options);
|
|
336
|
+
} catch {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
var toWindowErrorTrackOptions = (event) => {
|
|
341
|
+
const record = asRecord(event);
|
|
342
|
+
const nestedError = record.error;
|
|
343
|
+
const error = nestedError instanceof Error ? nestedError : null;
|
|
344
|
+
const message = firstNonEmptyString(asString(record.message), error?.message, "Script error");
|
|
345
|
+
const properties = {
|
|
346
|
+
error_kind: "window_error",
|
|
347
|
+
message: truncate(message, MAX_ERROR_MESSAGE_LENGTH),
|
|
348
|
+
filename: normalizeOptionalString(asString(record.filename)),
|
|
349
|
+
lineno: asNumber(record.lineno),
|
|
350
|
+
colno: asNumber(record.colno),
|
|
351
|
+
stack: truncate(normalizeOptionalString(error?.stack) ?? message, MAX_ERROR_STACK_LENGTH)
|
|
352
|
+
};
|
|
353
|
+
return {
|
|
354
|
+
properties: compactProperties(properties)
|
|
355
|
+
};
|
|
356
|
+
};
|
|
357
|
+
var toUnhandledRejectionTrackOptions = (event) => {
|
|
358
|
+
const record = asRecord(event);
|
|
359
|
+
const reason = record.reason;
|
|
360
|
+
const normalized = normalizeUnknownError(reason);
|
|
361
|
+
const properties = {
|
|
362
|
+
error_kind: "unhandledrejection",
|
|
363
|
+
message: truncate(normalized.message, MAX_ERROR_MESSAGE_LENGTH),
|
|
364
|
+
error_name: normalized.name,
|
|
365
|
+
stack: truncate(normalized.stack ?? normalized.message, MAX_ERROR_STACK_LENGTH)
|
|
366
|
+
};
|
|
367
|
+
return {
|
|
368
|
+
properties: compactProperties(properties)
|
|
369
|
+
};
|
|
370
|
+
};
|
|
371
|
+
var toManualErrorTrackOptions = (error, options = {}) => {
|
|
372
|
+
const normalized = normalizeUnknownError(error);
|
|
373
|
+
const source = normalizeOptionalString(options.source) ?? "manual";
|
|
374
|
+
const properties = {
|
|
375
|
+
error_kind: "captured_error",
|
|
376
|
+
error_name: normalized.name,
|
|
377
|
+
message: truncate(normalized.message, MAX_ERROR_MESSAGE_LENGTH),
|
|
378
|
+
stack: truncate(normalized.stack ?? normalized.message, MAX_ERROR_STACK_LENGTH),
|
|
379
|
+
source,
|
|
380
|
+
...options.properties
|
|
381
|
+
};
|
|
382
|
+
return {
|
|
383
|
+
eventTime: options.eventTime,
|
|
384
|
+
page: options.page,
|
|
385
|
+
properties: compactProperties(properties)
|
|
386
|
+
};
|
|
387
|
+
};
|
|
388
|
+
var normalizeUnknownError = (input) => {
|
|
389
|
+
if (input instanceof Error) {
|
|
390
|
+
return {
|
|
391
|
+
name: normalizeOptionalString(input.name),
|
|
392
|
+
message: input.message || input.name || "Unknown error",
|
|
393
|
+
stack: normalizeOptionalString(input.stack)
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
const record = asRecord(input);
|
|
397
|
+
const name = normalizeOptionalString(asString(record.name));
|
|
398
|
+
const message = firstNonEmptyString(asString(record.message), stringifyUnknown(input), "Unknown error") ?? "Unknown error";
|
|
399
|
+
return {
|
|
400
|
+
name,
|
|
401
|
+
message,
|
|
402
|
+
stack: normalizeOptionalString(asString(record.stack))
|
|
403
|
+
};
|
|
404
|
+
};
|
|
405
|
+
var buildFingerprint = (properties) => {
|
|
406
|
+
if (!properties) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
return JSON.stringify(Object.entries(properties).sort(([left], [right]) => left.localeCompare(right)));
|
|
410
|
+
};
|
|
411
|
+
var compactProperties = (properties) => Object.entries(properties).reduce((accumulator, [key, value]) => {
|
|
412
|
+
if (typeof value === "string") {
|
|
413
|
+
const normalized = normalizeOptionalString(value);
|
|
414
|
+
if (normalized) {
|
|
415
|
+
accumulator[key] = normalized;
|
|
416
|
+
}
|
|
417
|
+
return accumulator;
|
|
418
|
+
}
|
|
419
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
420
|
+
accumulator[key] = value;
|
|
421
|
+
}
|
|
422
|
+
return accumulator;
|
|
423
|
+
}, {});
|
|
424
|
+
var asRecord = (value) => typeof value === "object" && value !== null ? value : {};
|
|
425
|
+
var asString = (value) => typeof value === "string" ? value : null;
|
|
426
|
+
var asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
427
|
+
var firstNonEmptyString = (...values) => {
|
|
428
|
+
for (const value of values) {
|
|
429
|
+
const normalized = normalizeOptionalString(value);
|
|
430
|
+
if (normalized) {
|
|
431
|
+
return normalized;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return null;
|
|
435
|
+
};
|
|
436
|
+
var truncate = (value, maxLength) => value && value.length > maxLength ? value.slice(0, maxLength) : value;
|
|
437
|
+
var stringifyUnknown = (value) => {
|
|
438
|
+
if (typeof value === "string") {
|
|
439
|
+
return value;
|
|
440
|
+
}
|
|
441
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
442
|
+
return String(value);
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
return JSON.stringify(value);
|
|
446
|
+
} catch {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
|
|
264
451
|
// src/sdk-client.ts
|
|
265
452
|
var createTrackingClient = (options) => {
|
|
266
453
|
const browser = resolveBrowser();
|
|
@@ -268,6 +455,7 @@
|
|
|
268
455
|
const storage = createScopedStorage(browser, config.storagePrefix);
|
|
269
456
|
let state = loadState(browser, storage);
|
|
270
457
|
let routeListenerCleanup = null;
|
|
458
|
+
let errorListenerCleanup = null;
|
|
271
459
|
const routeRuntime = {
|
|
272
460
|
timer: null,
|
|
273
461
|
lastAutoPageUrl: normalizeOptionalString(browser.location?.href)
|
|
@@ -307,7 +495,7 @@
|
|
|
307
495
|
return null;
|
|
308
496
|
}
|
|
309
497
|
routeRuntime.lastAutoPageUrl = currentUrl;
|
|
310
|
-
return dispatchCollect(browser, config.endpoint, createCollectPayload(
|
|
498
|
+
return dispatchCollect(browser, config.endpoint, createCollectPayload(PREDEFINED_TRACKING_EVENTS.pageView.eventName));
|
|
311
499
|
};
|
|
312
500
|
const installRouteListeners = () => {
|
|
313
501
|
const addEventListener = browser.addEventListener?.bind(globalThis);
|
|
@@ -351,14 +539,19 @@
|
|
|
351
539
|
};
|
|
352
540
|
return {
|
|
353
541
|
init: async () => {
|
|
354
|
-
if (
|
|
355
|
-
|
|
542
|
+
if (config.autoPageview) {
|
|
543
|
+
await dispatchCollect(browser, config.endpoint, createCollectPayload(PREDEFINED_TRACKING_EVENTS.pageView.eventName));
|
|
544
|
+
routeRuntime.lastAutoPageUrl = normalizeOptionalString(browser.location?.href);
|
|
545
|
+
routeListenerCleanup = installRouteListeners();
|
|
546
|
+
}
|
|
547
|
+
if (config.autoErrorCapture) {
|
|
548
|
+
errorListenerCleanup = installGlobalErrorListeners(browser, {
|
|
549
|
+
track: async (eventName, options2 = {}) => dispatchCollect(browser, config.endpoint, createCollectPayload(eventName, options2))
|
|
550
|
+
});
|
|
356
551
|
}
|
|
357
|
-
await dispatchCollect(browser, config.endpoint, createCollectPayload("$page_view"));
|
|
358
|
-
routeRuntime.lastAutoPageUrl = normalizeOptionalString(browser.location?.href);
|
|
359
|
-
routeListenerCleanup = installRouteListeners();
|
|
360
552
|
},
|
|
361
553
|
track: async (eventName, options2 = {}) => dispatchCollect(browser, config.endpoint, createCollectPayload(eventName, options2)),
|
|
554
|
+
captureError: async (error, options2 = {}) => dispatchCollect(browser, config.endpoint, createCollectPayload(PREDEFINED_TRACKING_EVENTS.error.eventName, toManualErrorTrackOptions(error, options2))),
|
|
362
555
|
identify: (userId) => {
|
|
363
556
|
state = {
|
|
364
557
|
...state,
|
|
@@ -366,7 +559,7 @@
|
|
|
366
559
|
};
|
|
367
560
|
persistState(storage, state);
|
|
368
561
|
},
|
|
369
|
-
page: async (options2 = {}) => dispatchCollect(browser, config.endpoint, createCollectPayload(
|
|
562
|
+
page: async (options2 = {}) => dispatchCollect(browser, config.endpoint, createCollectPayload(PREDEFINED_TRACKING_EVENTS.pageView.eventName, {
|
|
370
563
|
properties: options2.properties,
|
|
371
564
|
page: options2
|
|
372
565
|
})),
|
|
@@ -380,6 +573,8 @@
|
|
|
380
573
|
destroy: () => {
|
|
381
574
|
routeListenerCleanup?.();
|
|
382
575
|
routeListenerCleanup = null;
|
|
576
|
+
errorListenerCleanup?.();
|
|
577
|
+
errorListenerCleanup = null;
|
|
383
578
|
}
|
|
384
579
|
};
|
|
385
580
|
};
|
|
@@ -392,6 +587,7 @@
|
|
|
392
587
|
await trackingClient.init();
|
|
393
588
|
};
|
|
394
589
|
var track = async (eventName, options = {}) => getClient().track(eventName, options);
|
|
590
|
+
var captureError = async (error, options = {}) => getClient().captureError(error, options);
|
|
395
591
|
var identify = (userId) => {
|
|
396
592
|
getClient().identify(userId);
|
|
397
593
|
};
|
|
@@ -406,6 +602,7 @@
|
|
|
406
602
|
};
|
|
407
603
|
// src/browser.ts
|
|
408
604
|
var TrackingAnalyticsSDK = {
|
|
605
|
+
captureError,
|
|
409
606
|
init,
|
|
410
607
|
track,
|
|
411
608
|
identify,
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
// src/predefined-events.ts
|
|
2
|
+
var PREDEFINED_TRACKING_EVENTS = {
|
|
3
|
+
pageView: {
|
|
4
|
+
eventName: "$page_view",
|
|
5
|
+
displayName: "页面浏览"
|
|
6
|
+
},
|
|
7
|
+
elementClick: {
|
|
8
|
+
eventName: "$element_click",
|
|
9
|
+
displayName: "元素点击"
|
|
10
|
+
},
|
|
11
|
+
formSubmit: {
|
|
12
|
+
eventName: "$form_submit",
|
|
13
|
+
displayName: "表单提交"
|
|
14
|
+
},
|
|
15
|
+
search: {
|
|
16
|
+
eventName: "$search",
|
|
17
|
+
displayName: "发起搜索"
|
|
18
|
+
},
|
|
19
|
+
exposure: {
|
|
20
|
+
eventName: "$exposure",
|
|
21
|
+
displayName: "内容曝光"
|
|
22
|
+
},
|
|
23
|
+
fileDownload: {
|
|
24
|
+
eventName: "$file_download",
|
|
25
|
+
displayName: "文件下载"
|
|
26
|
+
},
|
|
27
|
+
share: {
|
|
28
|
+
eventName: "$share",
|
|
29
|
+
displayName: "分享"
|
|
30
|
+
},
|
|
31
|
+
error: {
|
|
32
|
+
eventName: "$error",
|
|
33
|
+
displayName: "异常错误"
|
|
34
|
+
}
|
|
35
|
+
};
|
|
1
36
|
// src/sdk-core.ts
|
|
2
37
|
var DEFAULT_ENDPOINT = "/track/collect";
|
|
3
38
|
var DEFAULT_SESSION_TIMEOUT_MINUTES = 30;
|
|
@@ -17,6 +52,7 @@ var normalizeConfig = (options) => {
|
|
|
17
52
|
writeKey,
|
|
18
53
|
endpoint: normalizeOptionalString(options.endpoint) ?? DEFAULT_ENDPOINT,
|
|
19
54
|
autoPageview: options.autoPageview ?? true,
|
|
55
|
+
autoErrorCapture: options.autoErrorCapture ?? false,
|
|
20
56
|
sessionTimeoutMs: Math.max(options.sessionTimeoutMinutes ?? DEFAULT_SESSION_TIMEOUT_MINUTES, 1) * 60000,
|
|
21
57
|
storagePrefix: normalizeOptionalString(options.storagePrefix) ?? DEFAULT_STORAGE_PREFIX
|
|
22
58
|
};
|
|
@@ -226,6 +262,157 @@ var postWithFetch = async (browser, endpoint, body) => {
|
|
|
226
262
|
return await response.json();
|
|
227
263
|
};
|
|
228
264
|
|
|
265
|
+
// src/sdk-error.ts
|
|
266
|
+
var MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
267
|
+
var MAX_ERROR_STACK_LENGTH = 2000;
|
|
268
|
+
var installGlobalErrorListeners = (browser, client) => {
|
|
269
|
+
const addEventListener = browser.addEventListener?.bind(globalThis);
|
|
270
|
+
const removeEventListener = browser.removeEventListener?.bind(globalThis);
|
|
271
|
+
if (!addEventListener || !removeEventListener) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const runtime = {
|
|
275
|
+
lastFingerprint: null,
|
|
276
|
+
lastCapturedAt: 0
|
|
277
|
+
};
|
|
278
|
+
const onError = (event) => {
|
|
279
|
+
captureErrorEvent(client, runtime, toWindowErrorTrackOptions(event));
|
|
280
|
+
};
|
|
281
|
+
const onUnhandledRejection = (event) => {
|
|
282
|
+
captureErrorEvent(client, runtime, toUnhandledRejectionTrackOptions(event));
|
|
283
|
+
};
|
|
284
|
+
addEventListener("error", onError);
|
|
285
|
+
addEventListener("unhandledrejection", onUnhandledRejection);
|
|
286
|
+
return () => {
|
|
287
|
+
removeEventListener("error", onError);
|
|
288
|
+
removeEventListener("unhandledrejection", onUnhandledRejection);
|
|
289
|
+
};
|
|
290
|
+
};
|
|
291
|
+
var captureErrorEvent = async (client, runtime, options) => {
|
|
292
|
+
const fingerprint = buildFingerprint(options.properties);
|
|
293
|
+
const now = Date.now();
|
|
294
|
+
if (fingerprint && runtime.lastFingerprint === fingerprint && now - runtime.lastCapturedAt < 1000) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
runtime.lastFingerprint = fingerprint;
|
|
298
|
+
runtime.lastCapturedAt = now;
|
|
299
|
+
try {
|
|
300
|
+
await client.track(PREDEFINED_TRACKING_EVENTS.error.eventName, options);
|
|
301
|
+
} catch {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
var toWindowErrorTrackOptions = (event) => {
|
|
306
|
+
const record = asRecord(event);
|
|
307
|
+
const nestedError = record.error;
|
|
308
|
+
const error = nestedError instanceof Error ? nestedError : null;
|
|
309
|
+
const message = firstNonEmptyString(asString(record.message), error?.message, "Script error");
|
|
310
|
+
const properties = {
|
|
311
|
+
error_kind: "window_error",
|
|
312
|
+
message: truncate(message, MAX_ERROR_MESSAGE_LENGTH),
|
|
313
|
+
filename: normalizeOptionalString(asString(record.filename)),
|
|
314
|
+
lineno: asNumber(record.lineno),
|
|
315
|
+
colno: asNumber(record.colno),
|
|
316
|
+
stack: truncate(normalizeOptionalString(error?.stack) ?? message, MAX_ERROR_STACK_LENGTH)
|
|
317
|
+
};
|
|
318
|
+
return {
|
|
319
|
+
properties: compactProperties(properties)
|
|
320
|
+
};
|
|
321
|
+
};
|
|
322
|
+
var toUnhandledRejectionTrackOptions = (event) => {
|
|
323
|
+
const record = asRecord(event);
|
|
324
|
+
const reason = record.reason;
|
|
325
|
+
const normalized = normalizeUnknownError(reason);
|
|
326
|
+
const properties = {
|
|
327
|
+
error_kind: "unhandledrejection",
|
|
328
|
+
message: truncate(normalized.message, MAX_ERROR_MESSAGE_LENGTH),
|
|
329
|
+
error_name: normalized.name,
|
|
330
|
+
stack: truncate(normalized.stack ?? normalized.message, MAX_ERROR_STACK_LENGTH)
|
|
331
|
+
};
|
|
332
|
+
return {
|
|
333
|
+
properties: compactProperties(properties)
|
|
334
|
+
};
|
|
335
|
+
};
|
|
336
|
+
var toManualErrorTrackOptions = (error, options = {}) => {
|
|
337
|
+
const normalized = normalizeUnknownError(error);
|
|
338
|
+
const source = normalizeOptionalString(options.source) ?? "manual";
|
|
339
|
+
const properties = {
|
|
340
|
+
error_kind: "captured_error",
|
|
341
|
+
error_name: normalized.name,
|
|
342
|
+
message: truncate(normalized.message, MAX_ERROR_MESSAGE_LENGTH),
|
|
343
|
+
stack: truncate(normalized.stack ?? normalized.message, MAX_ERROR_STACK_LENGTH),
|
|
344
|
+
source,
|
|
345
|
+
...options.properties
|
|
346
|
+
};
|
|
347
|
+
return {
|
|
348
|
+
eventTime: options.eventTime,
|
|
349
|
+
page: options.page,
|
|
350
|
+
properties: compactProperties(properties)
|
|
351
|
+
};
|
|
352
|
+
};
|
|
353
|
+
var normalizeUnknownError = (input) => {
|
|
354
|
+
if (input instanceof Error) {
|
|
355
|
+
return {
|
|
356
|
+
name: normalizeOptionalString(input.name),
|
|
357
|
+
message: input.message || input.name || "Unknown error",
|
|
358
|
+
stack: normalizeOptionalString(input.stack)
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
const record = asRecord(input);
|
|
362
|
+
const name = normalizeOptionalString(asString(record.name));
|
|
363
|
+
const message = firstNonEmptyString(asString(record.message), stringifyUnknown(input), "Unknown error") ?? "Unknown error";
|
|
364
|
+
return {
|
|
365
|
+
name,
|
|
366
|
+
message,
|
|
367
|
+
stack: normalizeOptionalString(asString(record.stack))
|
|
368
|
+
};
|
|
369
|
+
};
|
|
370
|
+
var buildFingerprint = (properties) => {
|
|
371
|
+
if (!properties) {
|
|
372
|
+
return null;
|
|
373
|
+
}
|
|
374
|
+
return JSON.stringify(Object.entries(properties).sort(([left], [right]) => left.localeCompare(right)));
|
|
375
|
+
};
|
|
376
|
+
var compactProperties = (properties) => Object.entries(properties).reduce((accumulator, [key, value]) => {
|
|
377
|
+
if (typeof value === "string") {
|
|
378
|
+
const normalized = normalizeOptionalString(value);
|
|
379
|
+
if (normalized) {
|
|
380
|
+
accumulator[key] = normalized;
|
|
381
|
+
}
|
|
382
|
+
return accumulator;
|
|
383
|
+
}
|
|
384
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
385
|
+
accumulator[key] = value;
|
|
386
|
+
}
|
|
387
|
+
return accumulator;
|
|
388
|
+
}, {});
|
|
389
|
+
var asRecord = (value) => typeof value === "object" && value !== null ? value : {};
|
|
390
|
+
var asString = (value) => typeof value === "string" ? value : null;
|
|
391
|
+
var asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
392
|
+
var firstNonEmptyString = (...values) => {
|
|
393
|
+
for (const value of values) {
|
|
394
|
+
const normalized = normalizeOptionalString(value);
|
|
395
|
+
if (normalized) {
|
|
396
|
+
return normalized;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return null;
|
|
400
|
+
};
|
|
401
|
+
var truncate = (value, maxLength) => value && value.length > maxLength ? value.slice(0, maxLength) : value;
|
|
402
|
+
var stringifyUnknown = (value) => {
|
|
403
|
+
if (typeof value === "string") {
|
|
404
|
+
return value;
|
|
405
|
+
}
|
|
406
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
407
|
+
return String(value);
|
|
408
|
+
}
|
|
409
|
+
try {
|
|
410
|
+
return JSON.stringify(value);
|
|
411
|
+
} catch {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
229
416
|
// src/sdk-client.ts
|
|
230
417
|
var createTrackingClient = (options) => {
|
|
231
418
|
const browser = resolveBrowser();
|
|
@@ -233,6 +420,7 @@ var createTrackingClient = (options) => {
|
|
|
233
420
|
const storage = createScopedStorage(browser, config.storagePrefix);
|
|
234
421
|
let state = loadState(browser, storage);
|
|
235
422
|
let routeListenerCleanup = null;
|
|
423
|
+
let errorListenerCleanup = null;
|
|
236
424
|
const routeRuntime = {
|
|
237
425
|
timer: null,
|
|
238
426
|
lastAutoPageUrl: normalizeOptionalString(browser.location?.href)
|
|
@@ -272,7 +460,7 @@ var createTrackingClient = (options) => {
|
|
|
272
460
|
return null;
|
|
273
461
|
}
|
|
274
462
|
routeRuntime.lastAutoPageUrl = currentUrl;
|
|
275
|
-
return dispatchCollect(browser, config.endpoint, createCollectPayload(
|
|
463
|
+
return dispatchCollect(browser, config.endpoint, createCollectPayload(PREDEFINED_TRACKING_EVENTS.pageView.eventName));
|
|
276
464
|
};
|
|
277
465
|
const installRouteListeners = () => {
|
|
278
466
|
const addEventListener = browser.addEventListener?.bind(globalThis);
|
|
@@ -316,14 +504,19 @@ var createTrackingClient = (options) => {
|
|
|
316
504
|
};
|
|
317
505
|
return {
|
|
318
506
|
init: async () => {
|
|
319
|
-
if (
|
|
320
|
-
|
|
507
|
+
if (config.autoPageview) {
|
|
508
|
+
await dispatchCollect(browser, config.endpoint, createCollectPayload(PREDEFINED_TRACKING_EVENTS.pageView.eventName));
|
|
509
|
+
routeRuntime.lastAutoPageUrl = normalizeOptionalString(browser.location?.href);
|
|
510
|
+
routeListenerCleanup = installRouteListeners();
|
|
511
|
+
}
|
|
512
|
+
if (config.autoErrorCapture) {
|
|
513
|
+
errorListenerCleanup = installGlobalErrorListeners(browser, {
|
|
514
|
+
track: async (eventName, options2 = {}) => dispatchCollect(browser, config.endpoint, createCollectPayload(eventName, options2))
|
|
515
|
+
});
|
|
321
516
|
}
|
|
322
|
-
await dispatchCollect(browser, config.endpoint, createCollectPayload("$page_view"));
|
|
323
|
-
routeRuntime.lastAutoPageUrl = normalizeOptionalString(browser.location?.href);
|
|
324
|
-
routeListenerCleanup = installRouteListeners();
|
|
325
517
|
},
|
|
326
518
|
track: async (eventName, options2 = {}) => dispatchCollect(browser, config.endpoint, createCollectPayload(eventName, options2)),
|
|
519
|
+
captureError: async (error, options2 = {}) => dispatchCollect(browser, config.endpoint, createCollectPayload(PREDEFINED_TRACKING_EVENTS.error.eventName, toManualErrorTrackOptions(error, options2))),
|
|
327
520
|
identify: (userId) => {
|
|
328
521
|
state = {
|
|
329
522
|
...state,
|
|
@@ -331,7 +524,7 @@ var createTrackingClient = (options) => {
|
|
|
331
524
|
};
|
|
332
525
|
persistState(storage, state);
|
|
333
526
|
},
|
|
334
|
-
page: async (options2 = {}) => dispatchCollect(browser, config.endpoint, createCollectPayload(
|
|
527
|
+
page: async (options2 = {}) => dispatchCollect(browser, config.endpoint, createCollectPayload(PREDEFINED_TRACKING_EVENTS.pageView.eventName, {
|
|
335
528
|
properties: options2.properties,
|
|
336
529
|
page: options2
|
|
337
530
|
})),
|
|
@@ -345,6 +538,8 @@ var createTrackingClient = (options) => {
|
|
|
345
538
|
destroy: () => {
|
|
346
539
|
routeListenerCleanup?.();
|
|
347
540
|
routeListenerCleanup = null;
|
|
541
|
+
errorListenerCleanup?.();
|
|
542
|
+
errorListenerCleanup = null;
|
|
348
543
|
}
|
|
349
544
|
};
|
|
350
545
|
};
|
|
@@ -357,6 +552,7 @@ var init = async (options) => {
|
|
|
357
552
|
await trackingClient.init();
|
|
358
553
|
};
|
|
359
554
|
var track = async (eventName, options = {}) => getClient().track(eventName, options);
|
|
555
|
+
var captureError = async (error, options = {}) => getClient().captureError(error, options);
|
|
360
556
|
var identify = (userId) => {
|
|
361
557
|
getClient().identify(userId);
|
|
362
558
|
};
|
|
@@ -375,5 +571,7 @@ export {
|
|
|
375
571
|
page,
|
|
376
572
|
init,
|
|
377
573
|
identify,
|
|
378
|
-
getDebugState
|
|
574
|
+
getDebugState,
|
|
575
|
+
captureError,
|
|
576
|
+
PREDEFINED_TRACKING_EVENTS
|
|
379
577
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export declare const PREDEFINED_TRACKING_EVENTS: {
|
|
2
|
+
readonly pageView: {
|
|
3
|
+
readonly eventName: "$page_view";
|
|
4
|
+
readonly displayName: "页面浏览";
|
|
5
|
+
};
|
|
6
|
+
readonly elementClick: {
|
|
7
|
+
readonly eventName: "$element_click";
|
|
8
|
+
readonly displayName: "元素点击";
|
|
9
|
+
};
|
|
10
|
+
readonly formSubmit: {
|
|
11
|
+
readonly eventName: "$form_submit";
|
|
12
|
+
readonly displayName: "表单提交";
|
|
13
|
+
};
|
|
14
|
+
readonly search: {
|
|
15
|
+
readonly eventName: "$search";
|
|
16
|
+
readonly displayName: "发起搜索";
|
|
17
|
+
};
|
|
18
|
+
readonly exposure: {
|
|
19
|
+
readonly eventName: "$exposure";
|
|
20
|
+
readonly displayName: "内容曝光";
|
|
21
|
+
};
|
|
22
|
+
readonly fileDownload: {
|
|
23
|
+
readonly eventName: "$file_download";
|
|
24
|
+
readonly displayName: "文件下载";
|
|
25
|
+
};
|
|
26
|
+
readonly share: {
|
|
27
|
+
readonly eventName: "$share";
|
|
28
|
+
readonly displayName: "分享";
|
|
29
|
+
};
|
|
30
|
+
readonly error: {
|
|
31
|
+
readonly eventName: "$error";
|
|
32
|
+
readonly displayName: "异常错误";
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
export type PredefinedTrackingEventName = (typeof PREDEFINED_TRACKING_EVENTS)[keyof typeof PREDEFINED_TRACKING_EVENTS]["eventName"];
|
package/dist/sdk-core.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PageOptions, TrackOptions, TrackingDispatchResult, TrackingInitOptions, TrackingStateSnapshot } from "./types";
|
|
1
|
+
import type { CaptureErrorOptions, PageOptions, TrackOptions, TrackingDispatchResult, TrackingInitOptions, TrackingStateSnapshot } from "./types";
|
|
2
2
|
export declare const DEFAULT_ENDPOINT = "/track/collect";
|
|
3
3
|
export declare const DEFAULT_SESSION_TIMEOUT_MINUTES = 30;
|
|
4
4
|
export declare const DEFAULT_STORAGE_PREFIX = "rs_tracking_sdk";
|
|
@@ -52,12 +52,14 @@ export interface NormalizedConfig {
|
|
|
52
52
|
writeKey: string;
|
|
53
53
|
endpoint: string;
|
|
54
54
|
autoPageview: boolean;
|
|
55
|
+
autoErrorCapture: boolean;
|
|
55
56
|
sessionTimeoutMs: number;
|
|
56
57
|
storagePrefix: string;
|
|
57
58
|
}
|
|
58
59
|
export interface TrackingClient {
|
|
59
60
|
init: () => Promise<void>;
|
|
60
61
|
track: (eventName: string, options?: TrackOptions) => Promise<TrackingDispatchResult>;
|
|
62
|
+
captureError: (error: unknown, options?: CaptureErrorOptions) => Promise<TrackingDispatchResult>;
|
|
61
63
|
identify: (userId: string | null) => void;
|
|
62
64
|
page: (options?: PageOptions) => Promise<TrackingDispatchResult>;
|
|
63
65
|
reset: () => TrackingStateSnapshot;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CaptureErrorOptions, TrackOptions } from "./types";
|
|
2
|
+
import { type BrowserLike, type TrackingClient } from "./sdk-core";
|
|
3
|
+
/**
|
|
4
|
+
* 安装全局错误监听,将浏览器未处理异常统一转成 `$error` 事件。
|
|
5
|
+
*
|
|
6
|
+
* @param browser - 浏览器能力对象
|
|
7
|
+
* @param client - 当前追踪客户端
|
|
8
|
+
* @returns 返回卸载监听的方法;环境能力不足时返回 null
|
|
9
|
+
*/
|
|
10
|
+
export declare const installGlobalErrorListeners: (browser: BrowserLike, client: Pick<TrackingClient, "track">) => (() => void) | null;
|
|
11
|
+
/**
|
|
12
|
+
* 将任意错误对象转换为 `$error` 事件参数,供框架级手动上报复用。
|
|
13
|
+
*
|
|
14
|
+
* @param error - 任意错误对象
|
|
15
|
+
* @param options - 额外上下文与自定义属性
|
|
16
|
+
* @returns 返回 SDK 可直接上报的 track 参数
|
|
17
|
+
*/
|
|
18
|
+
export declare const toManualErrorTrackOptions: (error: unknown, options?: CaptureErrorOptions) => TrackOptions;
|
package/dist/sdk.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PageOptions, TrackOptions, TrackingDispatchResult, TrackingInitOptions, TrackingStateSnapshot } from "./types";
|
|
1
|
+
import type { CaptureErrorOptions, PageOptions, TrackOptions, TrackingDispatchResult, TrackingInitOptions, TrackingStateSnapshot } from "./types";
|
|
2
2
|
/**
|
|
3
3
|
* 初始化 SDK,并按默认配置发送一次 `$page_view`。
|
|
4
4
|
*
|
|
@@ -14,6 +14,14 @@ export declare const init: (options: TrackingInitOptions) => Promise<void>;
|
|
|
14
14
|
* @returns 返回本次上报的传输结果
|
|
15
15
|
*/
|
|
16
16
|
export declare const track: (eventName: string, options?: TrackOptions) => Promise<TrackingDispatchResult>;
|
|
17
|
+
/**
|
|
18
|
+
* 手动捕获错误并按 `$error` 事件结构上报。
|
|
19
|
+
*
|
|
20
|
+
* @param error - 任意错误对象
|
|
21
|
+
* @param options - 额外页面上下文与业务属性
|
|
22
|
+
* @returns 返回本次上报的传输结果
|
|
23
|
+
*/
|
|
24
|
+
export declare const captureError: (error: unknown, options?: CaptureErrorOptions) => Promise<TrackingDispatchResult>;
|
|
17
25
|
/**
|
|
18
26
|
* 识别当前登录用户,仅影响后续事件。
|
|
19
27
|
*
|
package/dist/types.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export interface TrackingInitOptions {
|
|
|
4
4
|
writeKey: string;
|
|
5
5
|
endpoint: string;
|
|
6
6
|
autoPageview?: boolean;
|
|
7
|
+
autoErrorCapture?: boolean;
|
|
7
8
|
sessionTimeoutMinutes?: number;
|
|
8
9
|
storagePrefix?: string;
|
|
9
10
|
}
|
|
@@ -19,6 +20,9 @@ export interface TrackOptions {
|
|
|
19
20
|
properties?: TrackingProperties;
|
|
20
21
|
page?: PageOptions;
|
|
21
22
|
}
|
|
23
|
+
export interface CaptureErrorOptions extends TrackOptions {
|
|
24
|
+
source?: string;
|
|
25
|
+
}
|
|
22
26
|
export interface TrackingEventPayload {
|
|
23
27
|
event_name: string;
|
|
24
28
|
event_time?: number;
|