@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.
- package/dist/{core-D4PaPlOB.d.cts → core-CHLpJy51.d.cts} +56 -1
- package/dist/{core-D4PaPlOB.d.ts → core-CHLpJy51.d.ts} +56 -1
- package/dist/index.cjs +108 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +57 -4
- package/dist/index.d.ts +57 -4
- package/dist/index.mjs +104 -4
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +103 -3
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.mjs +103 -3
- package/dist/react.mjs.map +1 -1
- package/hz.js +1 -1
- package/package.json +10 -10
- package/src/core.test.ts +45 -12
- package/src/core.ts +30 -3
- package/src/events.ts +5 -0
- package/src/exception.test.ts +188 -0
- package/src/exception.ts +200 -0
- package/src/index.ts +5 -1
- package/src/types.ts +53 -0
- package/src/version.ts +1 -1
- package/LICENSE.md +0 -21
|
@@ -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
|
|
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
|
|
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.
|
|
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 =
|
|
846
|
-
this.enqueue("
|
|
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;
|