@hanzo/event 0.3.19 → 0.3.21
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 +124 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +66 -10
- package/dist/index.d.ts +66 -10
- package/dist/index.mjs +120 -27
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +119 -26
- 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 +119 -26
- package/dist/react.mjs.map +1 -1
- package/hz.js +7 -8
- package/package.json +1 -1
- package/src/core.test.ts +48 -49
- package/src/core.ts +48 -27
- package/src/dsn.test.ts +28 -18
- package/src/dsn.ts +14 -34
- package/src/events.ts +5 -0
- package/src/exception.test.ts +188 -0
- package/src/exception.ts +200 -0
- package/src/hz.test.ts +4 -10
- package/src/index.ts +5 -1
- package/src/types.ts +53 -0
- package/src/version.ts +1 -1
|
@@ -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
|
@@ -82,15 +82,11 @@ var PRODUCT_PROJECT = Object.freeze({
|
|
|
82
82
|
site: "019f9b1e-5785-7359-ad0b-f75db8e58c99"
|
|
83
83
|
// hanzo.ai (marketing; product `site`)
|
|
84
84
|
});
|
|
85
|
-
function dsnForProduct(product) {
|
|
86
|
-
if (!product) return void 0;
|
|
85
|
+
function dsnForProduct(product, key) {
|
|
86
|
+
if (!product || !key) return void 0;
|
|
87
87
|
const projectId = PRODUCT_PROJECT[product];
|
|
88
88
|
if (!projectId) return void 0;
|
|
89
|
-
return `https://${
|
|
90
|
-
}
|
|
91
|
-
var HANZO_PUBLISHABLE_KEY = "pk-live-c88649f1085fb6ad441d8a0072933a9b";
|
|
92
|
-
function defaultPublishableKey(host) {
|
|
93
|
-
return host === void 0 || host === "https://api.hanzo.ai" ? HANZO_PUBLISHABLE_KEY : void 0;
|
|
89
|
+
return `https://${key}@api.hanzo.ai/v1/sentry/${projectId}`;
|
|
94
90
|
}
|
|
95
91
|
|
|
96
92
|
// src/events.ts
|
|
@@ -144,6 +140,7 @@ var EVENTS = {
|
|
|
144
140
|
DEPLOY_FAILED: "deploy_failed"
|
|
145
141
|
};
|
|
146
142
|
var PAGEVIEW = "$pageview";
|
|
143
|
+
var EXCEPTION = "$exception";
|
|
147
144
|
|
|
148
145
|
// src/scrub.ts
|
|
149
146
|
var REDACTED = "[redacted]";
|
|
@@ -373,7 +370,7 @@ function uuidv7Time(id) {
|
|
|
373
370
|
}
|
|
374
371
|
|
|
375
372
|
// src/version.ts
|
|
376
|
-
var VERSION = "0.3.
|
|
373
|
+
var VERSION = "0.3.21";
|
|
377
374
|
|
|
378
375
|
// src/sentry.ts
|
|
379
376
|
var MAX_FRAMES = 50;
|
|
@@ -565,6 +562,94 @@ ${itemHeader}
|
|
|
565
562
|
${payload}
|
|
566
563
|
`;
|
|
567
564
|
}
|
|
565
|
+
var MAX_VALUE = 4096;
|
|
566
|
+
function digest(s2) {
|
|
567
|
+
let out = "";
|
|
568
|
+
for (const seed of [2166136261, 16777619, 2654435769, 2246822507]) {
|
|
569
|
+
let h = seed >>> 0;
|
|
570
|
+
for (let i = 0; i < s2.length; i++) {
|
|
571
|
+
h ^= s2.charCodeAt(i);
|
|
572
|
+
h = h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24) >>> 0;
|
|
573
|
+
}
|
|
574
|
+
out += h.toString(16).padStart(8, "0");
|
|
575
|
+
}
|
|
576
|
+
return out;
|
|
577
|
+
}
|
|
578
|
+
function frameOf(f) {
|
|
579
|
+
const source = f.filename ?? "";
|
|
580
|
+
const name = f.function ?? "<anonymous>";
|
|
581
|
+
const line = f.lineno ?? 0;
|
|
582
|
+
const column = f.colno ?? 0;
|
|
583
|
+
return {
|
|
584
|
+
// Stable per code location, so the same frame groups and re-renders under one
|
|
585
|
+
// key. The '/part' suffix is the product's format for one raw frame expanding
|
|
586
|
+
// into several resolved ones; a browser frame is always part 0.
|
|
587
|
+
raw_id: `${digest(`${source}|${name}|${line}|${column}`)}/0`,
|
|
588
|
+
mangled_name: name,
|
|
589
|
+
source,
|
|
590
|
+
line,
|
|
591
|
+
column,
|
|
592
|
+
in_app: f.in_app ?? false,
|
|
593
|
+
lang: "javascript",
|
|
594
|
+
resolved: false,
|
|
595
|
+
resolve_failure: "no symbol set uploaded for this release",
|
|
596
|
+
resolved_name: null,
|
|
597
|
+
module: null
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
function truncate2(s2, max) {
|
|
601
|
+
if (s2.length <= max) return s2;
|
|
602
|
+
let end = max;
|
|
603
|
+
const c = s2.charCodeAt(end - 1);
|
|
604
|
+
if (c >= 55296 && c <= 56319) end--;
|
|
605
|
+
return s2.slice(0, end);
|
|
606
|
+
}
|
|
607
|
+
function exceptionEntry(err, opts) {
|
|
608
|
+
const n = normalizeError(err);
|
|
609
|
+
const frames = framesFromStack(n.stack).slice(-50);
|
|
610
|
+
const entry = {
|
|
611
|
+
id: opts.id,
|
|
612
|
+
type: n.name,
|
|
613
|
+
value: truncate2(n.message, MAX_VALUE),
|
|
614
|
+
mechanism: {
|
|
615
|
+
type: "generic",
|
|
616
|
+
handled: opts.handled,
|
|
617
|
+
// An exception the app reported by hand was constructed, not thrown by the
|
|
618
|
+
// runtime at a real call site.
|
|
619
|
+
synthetic: !(err instanceof Error)
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
if (frames.length > 0) {
|
|
623
|
+
entry.stacktrace = { type: "resolved", frames: frames.map(frameOf) };
|
|
624
|
+
}
|
|
625
|
+
return entry;
|
|
626
|
+
}
|
|
627
|
+
function fingerprint(entry) {
|
|
628
|
+
const frames = entry.stacktrace?.frames ?? [];
|
|
629
|
+
const pieces = frames.filter((f) => f.in_app).map((f) => `${f.mangled_name}@${f.source}`);
|
|
630
|
+
return digest([entry.type, ...pieces].join("\n"));
|
|
631
|
+
}
|
|
632
|
+
function exceptionProperties(err, opts) {
|
|
633
|
+
const entry = exceptionEntry(err, opts);
|
|
634
|
+
const frames = entry.stacktrace?.frames ?? [];
|
|
635
|
+
const fp = fingerprint(entry);
|
|
636
|
+
return {
|
|
637
|
+
$exception_list: [entry],
|
|
638
|
+
$exception_fingerprint: fp,
|
|
639
|
+
// Records WHY this fingerprint holds. 'manual' is the honest value: the client
|
|
640
|
+
// supplied the key rather than a server grouper deriving one.
|
|
641
|
+
$exception_fingerprint_record: [{ type: "manual" }],
|
|
642
|
+
$exception_type: entry.type,
|
|
643
|
+
$exception_message: entry.value,
|
|
644
|
+
$exception_level: opts.level ?? "error",
|
|
645
|
+
$exception_handled: opts.handled,
|
|
646
|
+
$exception_synthetic: entry.mechanism?.synthetic ?? false,
|
|
647
|
+
$exception_types: [entry.type],
|
|
648
|
+
$exception_values: [entry.value],
|
|
649
|
+
$exception_sources: frames.map((f) => f.source).filter((s2) => !!s2),
|
|
650
|
+
$exception_functions: frames.map((f) => f.resolved_name ?? f.mangled_name).filter((s2) => !!s2)
|
|
651
|
+
};
|
|
652
|
+
}
|
|
568
653
|
|
|
569
654
|
// src/storage.ts
|
|
570
655
|
var KEY = {
|
|
@@ -736,27 +821,24 @@ var Analytics = class {
|
|
|
736
821
|
enabled: true,
|
|
737
822
|
captureErrors: true,
|
|
738
823
|
...config,
|
|
739
|
-
// The publishable key resolves
|
|
740
|
-
//
|
|
741
|
-
// enough. Most specific first:
|
|
824
|
+
// The publishable key resolves from ONE live source, never a literal baked
|
|
825
|
+
// beside the code. Most specific first:
|
|
742
826
|
// 1. an explicit `ingestKey` in config;
|
|
743
827
|
// 2. NEXT_PUBLIC_PUBLISHABLE_KEY, the inlined build-time env the fleet
|
|
744
|
-
// carries end to end
|
|
745
|
-
// build-arg -> the NEXT_PUBLIC_ prefix Next inlines
|
|
746
|
-
// 3. the hanzo org key baked in `dsn.ts`, on the hanzo cloud only.
|
|
828
|
+
// carries end to end — KMS `deploy/PUBLISHABLE_KEY` -> the PUBLISHABLE_KEY
|
|
829
|
+
// build-arg -> the NEXT_PUBLIC_ prefix Next inlines.
|
|
747
830
|
//
|
|
748
|
-
//
|
|
749
|
-
//
|
|
750
|
-
//
|
|
751
|
-
//
|
|
752
|
-
//
|
|
753
|
-
|
|
754
|
-
// its own cloud (a non-default host) gets no default, so the org this
|
|
755
|
-
// attributes to is never the wrong one.
|
|
756
|
-
ingestKey: config.ingestKey ?? readEnv("NEXT_PUBLIC_PUBLISHABLE_KEY") ?? defaultPublishableKey(config.host ?? DEFAULT_HOST)
|
|
831
|
+
// A surface that provides neither has no key, and its anonymous traffic
|
|
832
|
+
// files under `$public` (which drops track/identify/group and answers 200);
|
|
833
|
+
// the fix is to give it the KMS-sourced env, not to hardcode the org key
|
|
834
|
+
// here. The key is a credential-class value: its home is KMS, and the ONE
|
|
835
|
+
// build reads it from there.
|
|
836
|
+
ingestKey: config.ingestKey ?? readEnv("NEXT_PUBLIC_PUBLISHABLE_KEY")
|
|
757
837
|
};
|
|
758
838
|
this.transport = config.transport ?? new DefaultTransport();
|
|
759
|
-
this.dsn = parseDsn(
|
|
839
|
+
this.dsn = parseDsn(
|
|
840
|
+
config.dsn ?? readEnvDsn() ?? dsnForProduct(this.cfg.product, this.cfg.ingestKey)
|
|
841
|
+
);
|
|
760
842
|
}
|
|
761
843
|
/** errorPlaneEnabled reports whether captured exceptions can actually reach the
|
|
762
844
|
* error host. False means a DSN was never configured — the documented
|
|
@@ -841,9 +923,20 @@ var Analytics = class {
|
|
|
841
923
|
} catch {
|
|
842
924
|
}
|
|
843
925
|
try {
|
|
926
|
+
const handled = context?.handled ?? true;
|
|
844
927
|
const ex = normalizeError2(err);
|
|
845
|
-
ex.handled =
|
|
846
|
-
this.enqueue("
|
|
928
|
+
ex.handled = handled;
|
|
929
|
+
this.enqueue("event", EXCEPTION, {
|
|
930
|
+
error: ex,
|
|
931
|
+
properties: {
|
|
932
|
+
...context?.properties,
|
|
933
|
+
...exceptionProperties(err, {
|
|
934
|
+
handled,
|
|
935
|
+
id: hzUuidv7(),
|
|
936
|
+
level: context?.level
|
|
937
|
+
})
|
|
938
|
+
}
|
|
939
|
+
});
|
|
847
940
|
this.flush();
|
|
848
941
|
} catch {
|
|
849
942
|
}
|
|
@@ -1153,6 +1246,7 @@ var COHORTS = {
|
|
|
1153
1246
|
exports.Analytics = Analytics;
|
|
1154
1247
|
exports.COHORTS = COHORTS;
|
|
1155
1248
|
exports.EVENTS = EVENTS;
|
|
1249
|
+
exports.EXCEPTION = EXCEPTION;
|
|
1156
1250
|
exports.FUNNELS = FUNNELS;
|
|
1157
1251
|
exports.GOALS = GOALS;
|
|
1158
1252
|
exports.PAGEVIEW = PAGEVIEW;
|
|
@@ -1163,8 +1257,12 @@ exports.buildEnvelope = buildEnvelope;
|
|
|
1163
1257
|
exports.buildSentryEvent = buildSentryEvent;
|
|
1164
1258
|
exports.createAnalytics = createAnalytics;
|
|
1165
1259
|
exports.deriveChannel = deriveChannel;
|
|
1260
|
+
exports.digest = digest;
|
|
1166
1261
|
exports.dsnForProduct = dsnForProduct;
|
|
1167
1262
|
exports.eventsOf = eventsOf;
|
|
1263
|
+
exports.exceptionEntry = exceptionEntry;
|
|
1264
|
+
exports.exceptionProperties = exceptionProperties;
|
|
1265
|
+
exports.fingerprint = fingerprint;
|
|
1168
1266
|
exports.framesFromStack = framesFromStack;
|
|
1169
1267
|
exports.getCohort = getCohort;
|
|
1170
1268
|
exports.getFirstTouch = getFirstTouch;
|