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 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 { getDebugState, identify, init, page, reset, track } from "./sdk";
2
- export type { PageOptions, TrackOptions, TrackingCollectPayload, TrackingDispatchResult, TrackingEventPayload, TrackingInitOptions, TrackingProperties, TrackingResponse, TrackingScalar, TrackingStateSnapshot, } from "./types";
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";
@@ -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("$page_view"));
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 (!config.autoPageview) {
355
- return;
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("$page_view", {
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("$page_view"));
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 (!config.autoPageview) {
320
- return;
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("$page_view", {
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"];
@@ -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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rsclick-log-sdk-web",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Lightweight Web tracking SDK for rs-click-log.",