@hanzo/event 0.3.19 → 0.3.20

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.
@@ -18,6 +18,61 @@ interface Exception {
18
18
  * true = a caught error the app chose to report. Defaults true. */
19
19
  handled?: boolean;
20
20
  }
21
+ /** One stack frame as Error Tracking renders it. The key names are the product's
22
+ * POST-symbolication vocabulary (`mangled_name`/`source`/`line`/`column`), which
23
+ * is what the issue view reads straight off `$exception_list`. */
24
+ interface ExceptionFrame {
25
+ /** "<hash>/<part>" — stable per code location; the frame's identity. */
26
+ raw_id: string;
27
+ /** The function name as it appears in the shipped bundle. */
28
+ mangled_name: string;
29
+ /** File/URL the frame is in. */
30
+ source: string;
31
+ line: number;
32
+ column: number;
33
+ /** First-party code. Frames without this are hidden by default in the product. */
34
+ in_app: boolean;
35
+ lang: string;
36
+ /** Whether a symbol set mapped this frame back to original source. */
37
+ resolved: boolean;
38
+ resolve_failure?: string;
39
+ resolved_name?: string | null;
40
+ module?: string | null;
41
+ }
42
+ /** One exception in `$exception_list`. */
43
+ interface ExceptionEntry {
44
+ id: string;
45
+ type: string;
46
+ value: string;
47
+ mechanism?: {
48
+ type: 'generic';
49
+ handled: boolean;
50
+ synthetic?: boolean;
51
+ };
52
+ /** `type` MUST be 'resolved' — the renderer draws frames on no other value. */
53
+ stacktrace?: {
54
+ type: 'resolved';
55
+ frames: ExceptionFrame[];
56
+ };
57
+ }
58
+ /** The `$exception_*` property bag Error Tracking reads off a `$exception` event. */
59
+ interface ExceptionProperties {
60
+ $exception_list: ExceptionEntry[];
61
+ /** Issue grouping key. An event without one is dropped by the issue query. */
62
+ $exception_fingerprint: string;
63
+ $exception_fingerprint_record: {
64
+ type: 'manual';
65
+ }[];
66
+ $exception_type: string;
67
+ $exception_message: string;
68
+ $exception_level: SentryLevel;
69
+ $exception_handled: boolean;
70
+ $exception_synthetic: boolean;
71
+ $exception_types: string[];
72
+ $exception_values: string[];
73
+ $exception_sources: string[];
74
+ $exception_functions: string[];
75
+ }
21
76
  /** First-touch marketing attribution, parsed once and persisted. */
22
77
  interface Attribution {
23
78
  utm: {
@@ -308,4 +363,4 @@ declare class Analytics {
308
363
  /** createAnalytics builds a client instance. Most apps use one shared instance. */
309
364
  declare function createAnalytics(config: AnalyticsConfig): Analytics;
310
365
 
311
- export { type Attribution as A, type Cohort as C, type Dsn as D, type EventKind as E, type SentryEvent as S, type Transport as T, type WireEvent as W, type CaptureErrorOptions as a, type SentryFrame as b, Analytics as c, type AnalyticsConfig as d, type Exception as e, type SentryLevel as f, createAnalytics as g };
366
+ export { type Attribution as A, type Cohort as C, type Dsn as D, type ExceptionEntry as E, type SentryEvent as S, type Transport as T, type WireEvent as W, type CaptureErrorOptions as a, type SentryFrame as b, type SentryLevel as c, type ExceptionProperties as d, Analytics as e, type AnalyticsConfig as f, type EventKind as g, type Exception as h, type ExceptionFrame as i, createAnalytics as j };
@@ -18,6 +18,61 @@ interface Exception {
18
18
  * true = a caught error the app chose to report. Defaults true. */
19
19
  handled?: boolean;
20
20
  }
21
+ /** One stack frame as Error Tracking renders it. The key names are the product's
22
+ * POST-symbolication vocabulary (`mangled_name`/`source`/`line`/`column`), which
23
+ * is what the issue view reads straight off `$exception_list`. */
24
+ interface ExceptionFrame {
25
+ /** "<hash>/<part>" — stable per code location; the frame's identity. */
26
+ raw_id: string;
27
+ /** The function name as it appears in the shipped bundle. */
28
+ mangled_name: string;
29
+ /** File/URL the frame is in. */
30
+ source: string;
31
+ line: number;
32
+ column: number;
33
+ /** First-party code. Frames without this are hidden by default in the product. */
34
+ in_app: boolean;
35
+ lang: string;
36
+ /** Whether a symbol set mapped this frame back to original source. */
37
+ resolved: boolean;
38
+ resolve_failure?: string;
39
+ resolved_name?: string | null;
40
+ module?: string | null;
41
+ }
42
+ /** One exception in `$exception_list`. */
43
+ interface ExceptionEntry {
44
+ id: string;
45
+ type: string;
46
+ value: string;
47
+ mechanism?: {
48
+ type: 'generic';
49
+ handled: boolean;
50
+ synthetic?: boolean;
51
+ };
52
+ /** `type` MUST be 'resolved' — the renderer draws frames on no other value. */
53
+ stacktrace?: {
54
+ type: 'resolved';
55
+ frames: ExceptionFrame[];
56
+ };
57
+ }
58
+ /** The `$exception_*` property bag Error Tracking reads off a `$exception` event. */
59
+ interface ExceptionProperties {
60
+ $exception_list: ExceptionEntry[];
61
+ /** Issue grouping key. An event without one is dropped by the issue query. */
62
+ $exception_fingerprint: string;
63
+ $exception_fingerprint_record: {
64
+ type: 'manual';
65
+ }[];
66
+ $exception_type: string;
67
+ $exception_message: string;
68
+ $exception_level: SentryLevel;
69
+ $exception_handled: boolean;
70
+ $exception_synthetic: boolean;
71
+ $exception_types: string[];
72
+ $exception_values: string[];
73
+ $exception_sources: string[];
74
+ $exception_functions: string[];
75
+ }
21
76
  /** First-touch marketing attribution, parsed once and persisted. */
22
77
  interface Attribution {
23
78
  utm: {
@@ -308,4 +363,4 @@ declare class Analytics {
308
363
  /** createAnalytics builds a client instance. Most apps use one shared instance. */
309
364
  declare function createAnalytics(config: AnalyticsConfig): Analytics;
310
365
 
311
- export { type Attribution as A, type Cohort as C, type Dsn as D, type EventKind as E, type SentryEvent as S, type Transport as T, type WireEvent as W, type CaptureErrorOptions as a, type SentryFrame as b, Analytics as c, type AnalyticsConfig as d, type Exception as e, type SentryLevel as f, createAnalytics as g };
366
+ export { type Attribution as A, type Cohort as C, type Dsn as D, type ExceptionEntry as E, type SentryEvent as S, type Transport as T, type WireEvent as W, type CaptureErrorOptions as a, type SentryFrame as b, type SentryLevel as c, type ExceptionProperties as d, Analytics as e, type AnalyticsConfig as f, type EventKind as g, type Exception as h, type ExceptionFrame as i, createAnalytics as j };
package/dist/index.cjs CHANGED
@@ -144,6 +144,7 @@ var EVENTS = {
144
144
  DEPLOY_FAILED: "deploy_failed"
145
145
  };
146
146
  var PAGEVIEW = "$pageview";
147
+ var EXCEPTION = "$exception";
147
148
 
148
149
  // src/scrub.ts
149
150
  var REDACTED = "[redacted]";
@@ -373,7 +374,7 @@ function uuidv7Time(id) {
373
374
  }
374
375
 
375
376
  // src/version.ts
376
- var VERSION = "0.3.19";
377
+ var VERSION = "0.3.20";
377
378
 
378
379
  // src/sentry.ts
379
380
  var MAX_FRAMES = 50;
@@ -565,6 +566,94 @@ ${itemHeader}
565
566
  ${payload}
566
567
  `;
567
568
  }
569
+ var MAX_VALUE = 4096;
570
+ function digest(s2) {
571
+ let out = "";
572
+ for (const seed of [2166136261, 16777619, 2654435769, 2246822507]) {
573
+ let h = seed >>> 0;
574
+ for (let i = 0; i < s2.length; i++) {
575
+ h ^= s2.charCodeAt(i);
576
+ h = h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24) >>> 0;
577
+ }
578
+ out += h.toString(16).padStart(8, "0");
579
+ }
580
+ return out;
581
+ }
582
+ function frameOf(f) {
583
+ const source = f.filename ?? "";
584
+ const name = f.function ?? "<anonymous>";
585
+ const line = f.lineno ?? 0;
586
+ const column = f.colno ?? 0;
587
+ return {
588
+ // Stable per code location, so the same frame groups and re-renders under one
589
+ // key. The '/part' suffix is the product's format for one raw frame expanding
590
+ // into several resolved ones; a browser frame is always part 0.
591
+ raw_id: `${digest(`${source}|${name}|${line}|${column}`)}/0`,
592
+ mangled_name: name,
593
+ source,
594
+ line,
595
+ column,
596
+ in_app: f.in_app ?? false,
597
+ lang: "javascript",
598
+ resolved: false,
599
+ resolve_failure: "no symbol set uploaded for this release",
600
+ resolved_name: null,
601
+ module: null
602
+ };
603
+ }
604
+ function truncate2(s2, max) {
605
+ if (s2.length <= max) return s2;
606
+ let end = max;
607
+ const c = s2.charCodeAt(end - 1);
608
+ if (c >= 55296 && c <= 56319) end--;
609
+ return s2.slice(0, end);
610
+ }
611
+ function exceptionEntry(err, opts) {
612
+ const n = normalizeError(err);
613
+ const frames = framesFromStack(n.stack).slice(-50);
614
+ const entry = {
615
+ id: opts.id,
616
+ type: n.name,
617
+ value: truncate2(n.message, MAX_VALUE),
618
+ mechanism: {
619
+ type: "generic",
620
+ handled: opts.handled,
621
+ // An exception the app reported by hand was constructed, not thrown by the
622
+ // runtime at a real call site.
623
+ synthetic: !(err instanceof Error)
624
+ }
625
+ };
626
+ if (frames.length > 0) {
627
+ entry.stacktrace = { type: "resolved", frames: frames.map(frameOf) };
628
+ }
629
+ return entry;
630
+ }
631
+ function fingerprint(entry) {
632
+ const frames = entry.stacktrace?.frames ?? [];
633
+ const pieces = frames.filter((f) => f.in_app).map((f) => `${f.mangled_name}@${f.source}`);
634
+ return digest([entry.type, ...pieces].join("\n"));
635
+ }
636
+ function exceptionProperties(err, opts) {
637
+ const entry = exceptionEntry(err, opts);
638
+ const frames = entry.stacktrace?.frames ?? [];
639
+ const fp = fingerprint(entry);
640
+ return {
641
+ $exception_list: [entry],
642
+ $exception_fingerprint: fp,
643
+ // Records WHY this fingerprint holds. 'manual' is the honest value: the client
644
+ // supplied the key rather than a server grouper deriving one.
645
+ $exception_fingerprint_record: [{ type: "manual" }],
646
+ $exception_type: entry.type,
647
+ $exception_message: entry.value,
648
+ $exception_level: opts.level ?? "error",
649
+ $exception_handled: opts.handled,
650
+ $exception_synthetic: entry.mechanism?.synthetic ?? false,
651
+ $exception_types: [entry.type],
652
+ $exception_values: [entry.value],
653
+ $exception_sources: frames.map((f) => f.source).filter((s2) => !!s2),
654
+ $exception_functions: frames.map((f) => f.resolved_name ?? f.mangled_name).filter((s2) => !!s2)
655
+ };
656
+ }
568
657
 
569
658
  // src/storage.ts
570
659
  var KEY = {
@@ -841,9 +930,20 @@ var Analytics = class {
841
930
  } catch {
842
931
  }
843
932
  try {
933
+ const handled = context?.handled ?? true;
844
934
  const ex = normalizeError2(err);
845
- ex.handled = context?.handled ?? true;
846
- this.enqueue("error", ex.message, { error: ex, properties: context?.properties });
935
+ ex.handled = handled;
936
+ this.enqueue("event", EXCEPTION, {
937
+ error: ex,
938
+ properties: {
939
+ ...context?.properties,
940
+ ...exceptionProperties(err, {
941
+ handled,
942
+ id: hzUuidv7(),
943
+ level: context?.level
944
+ })
945
+ }
946
+ });
847
947
  this.flush();
848
948
  } catch {
849
949
  }
@@ -1153,6 +1253,7 @@ var COHORTS = {
1153
1253
  exports.Analytics = Analytics;
1154
1254
  exports.COHORTS = COHORTS;
1155
1255
  exports.EVENTS = EVENTS;
1256
+ exports.EXCEPTION = EXCEPTION;
1156
1257
  exports.FUNNELS = FUNNELS;
1157
1258
  exports.GOALS = GOALS;
1158
1259
  exports.PAGEVIEW = PAGEVIEW;
@@ -1163,8 +1264,12 @@ exports.buildEnvelope = buildEnvelope;
1163
1264
  exports.buildSentryEvent = buildSentryEvent;
1164
1265
  exports.createAnalytics = createAnalytics;
1165
1266
  exports.deriveChannel = deriveChannel;
1267
+ exports.digest = digest;
1166
1268
  exports.dsnForProduct = dsnForProduct;
1167
1269
  exports.eventsOf = eventsOf;
1270
+ exports.exceptionEntry = exceptionEntry;
1271
+ exports.exceptionProperties = exceptionProperties;
1272
+ exports.fingerprint = fingerprint;
1168
1273
  exports.framesFromStack = framesFromStack;
1169
1274
  exports.getCohort = getCohort;
1170
1275
  exports.getFirstTouch = getFirstTouch;