@hanzo/event 0.3.18 → 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
@@ -74,17 +74,19 @@ function isoWeek(d) {
74
74
  }
75
75
 
76
76
  // src/dsn.ts
77
- var PRODUCT_DSN = Object.freeze({
78
- // hanzo-console — console.hanzo.ai (also served embedded by the cloud binary)
79
- console: "https://1:0c8054dbde157f4f420c56b58660052b2ad782293c4de1d606ef8fbc46a0bf34@api.hanzo.ai/v1/sentry/019fa40b-94ae-7f1d-8f7b-e92f123fad42",
80
- // hanzo-app — hanzo.app
81
- app: "https://1:b3e1173125568c80f91ef4b1fabbbd2d7e22341de02b33ce7e22ef4fc16a196e@api.hanzo.ai/v1/sentry/019f9b1e-57eb-7171-9d92-72c0b85e4b4b",
82
- // hanzo-ai — hanzo.ai (the marketing site; `site` is the product name it declares)
83
- site: "https://1:d9cbfb844958bd7ef2a455600f00fbf237fbd71b75c1504f137773096d6aa53f@api.hanzo.ai/v1/sentry/019f9b1e-5785-7359-ad0b-f75db8e58c99"
77
+ var PRODUCT_PROJECT = Object.freeze({
78
+ console: "019fa40b-94ae-7f1d-8f7b-e92f123fad42",
79
+ // console.hanzo.ai
80
+ app: "019f9b1e-57eb-7171-9d92-72c0b85e4b4b",
81
+ // hanzo.app
82
+ site: "019f9b1e-5785-7359-ad0b-f75db8e58c99"
83
+ // hanzo.ai (marketing; product `site`)
84
84
  });
85
85
  function dsnForProduct(product) {
86
86
  if (!product) return void 0;
87
- return PRODUCT_DSN[product];
87
+ const projectId = PRODUCT_PROJECT[product];
88
+ if (!projectId) return void 0;
89
+ return `https://${HANZO_PUBLISHABLE_KEY}@api.hanzo.ai/v1/sentry/${projectId}`;
88
90
  }
89
91
  var HANZO_PUBLISHABLE_KEY = "pk-live-c88649f1085fb6ad441d8a0072933a9b";
90
92
  function defaultPublishableKey(host) {
@@ -142,6 +144,7 @@ var EVENTS = {
142
144
  DEPLOY_FAILED: "deploy_failed"
143
145
  };
144
146
  var PAGEVIEW = "$pageview";
147
+ var EXCEPTION = "$exception";
145
148
 
146
149
  // src/scrub.ts
147
150
  var REDACTED = "[redacted]";
@@ -371,7 +374,7 @@ function uuidv7Time(id) {
371
374
  }
372
375
 
373
376
  // src/version.ts
374
- var VERSION = "0.3.18";
377
+ var VERSION = "0.3.20";
375
378
 
376
379
  // src/sentry.ts
377
380
  var MAX_FRAMES = 50;
@@ -563,6 +566,94 @@ ${itemHeader}
563
566
  ${payload}
564
567
  `;
565
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
+ }
566
657
 
567
658
  // src/storage.ts
568
659
  var KEY = {
@@ -839,9 +930,20 @@ var Analytics = class {
839
930
  } catch {
840
931
  }
841
932
  try {
933
+ const handled = context?.handled ?? true;
842
934
  const ex = normalizeError2(err);
843
- ex.handled = context?.handled ?? true;
844
- 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
+ });
845
947
  this.flush();
846
948
  } catch {
847
949
  }
@@ -1151,18 +1253,23 @@ var COHORTS = {
1151
1253
  exports.Analytics = Analytics;
1152
1254
  exports.COHORTS = COHORTS;
1153
1255
  exports.EVENTS = EVENTS;
1256
+ exports.EXCEPTION = EXCEPTION;
1154
1257
  exports.FUNNELS = FUNNELS;
1155
1258
  exports.GOALS = GOALS;
1156
1259
  exports.PAGEVIEW = PAGEVIEW;
1157
1260
  exports.PRODUCTS = PRODUCTS;
1158
- exports.PRODUCT_DSN = PRODUCT_DSN;
1261
+ exports.PRODUCT_PROJECT = PRODUCT_PROJECT;
1159
1262
  exports.VERSION = VERSION;
1160
1263
  exports.buildEnvelope = buildEnvelope;
1161
1264
  exports.buildSentryEvent = buildSentryEvent;
1162
1265
  exports.createAnalytics = createAnalytics;
1163
1266
  exports.deriveChannel = deriveChannel;
1267
+ exports.digest = digest;
1164
1268
  exports.dsnForProduct = dsnForProduct;
1165
1269
  exports.eventsOf = eventsOf;
1270
+ exports.exceptionEntry = exceptionEntry;
1271
+ exports.exceptionProperties = exceptionProperties;
1272
+ exports.fingerprint = fingerprint;
1166
1273
  exports.framesFromStack = framesFromStack;
1167
1274
  exports.getCohort = getCohort;
1168
1275
  exports.getFirstTouch = getFirstTouch;