@hanzo/event 0.3.44 → 0.3.49
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/README.md +11 -50
- package/dist/core.d.ts +6 -36
- package/dist/core.d.ts.map +1 -1
- package/dist/index.cjs +242 -414
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +241 -411
- package/dist/index.mjs.map +1 -1
- package/dist/items.d.ts +22 -0
- package/dist/items.d.ts.map +1 -0
- package/dist/org.d.ts.map +1 -1
- package/dist/react.cjs +181 -415
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +8 -0
- package/dist/react.d.ts +8 -0
- package/dist/react.d.ts.map +1 -1
- package/dist/react.mjs +183 -418
- package/dist/react.mjs.map +1 -1
- package/dist/tags.d.ts +14 -1
- package/dist/tags.d.ts.map +1 -1
- package/dist/throwable.d.ts +15 -0
- package/dist/throwable.d.ts.map +1 -0
- package/dist/types.d.ts +19 -74
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core.test.ts +28 -300
- package/src/core.ts +36 -185
- package/src/exception.ts +1 -1
- package/src/index.ts +4 -6
- package/src/items.test.ts +22 -0
- package/src/items.ts +44 -0
- package/src/org.ts +4 -0
- package/src/react.tsx +22 -0
- package/src/stream.test.ts +0 -7
- package/src/tags.test.ts +18 -0
- package/src/tags.ts +38 -5
- package/src/throwable.test.ts +72 -0
- package/src/throwable.ts +126 -0
- package/src/types.ts +15 -75
- package/src/uid.test.ts +1 -1
- package/dist/dsn.d.ts +0 -43
- package/dist/dsn.d.ts.map +0 -1
- package/dist/sentry.d.ts +0 -62
- package/dist/sentry.d.ts.map +0 -1
- package/src/dsn.test.ts +0 -75
- package/src/dsn.ts +0 -56
- package/src/sentry.test.ts +0 -312
- package/src/sentry.ts +0 -311
package/dist/index.cjs
CHANGED
|
@@ -75,22 +75,6 @@ function isoWeek(d) {
|
|
|
75
75
|
return `${date.getUTCFullYear()}-W${String(week).padStart(2, "0")}`;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
// src/dsn.ts
|
|
79
|
-
var PRODUCT_PROJECT = Object.freeze({
|
|
80
|
-
console: "019fa40b-94ae-7f1d-8f7b-e92f123fad42",
|
|
81
|
-
// console.hanzo.ai
|
|
82
|
-
app: "019f9b1e-57eb-7171-9d92-72c0b85e4b4b",
|
|
83
|
-
// hanzo.app
|
|
84
|
-
site: "019f9b1e-5785-7359-ad0b-f75db8e58c99"
|
|
85
|
-
// hanzo.ai (marketing; product `site`)
|
|
86
|
-
});
|
|
87
|
-
function dsnForProduct(product, key) {
|
|
88
|
-
if (!product || !key) return void 0;
|
|
89
|
-
const projectId = PRODUCT_PROJECT[product];
|
|
90
|
-
if (!projectId) return void 0;
|
|
91
|
-
return `https://${key}@api.hanzo.ai/v1/event/${projectId}`;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
78
|
// src/org.ts
|
|
95
79
|
var ORG_DOMAIN = Object.freeze({
|
|
96
80
|
// A brand's domains are not a pattern — `hanzoskills.com` is Hanzo's and does
|
|
@@ -99,11 +83,15 @@ var ORG_DOMAIN = Object.freeze({
|
|
|
99
83
|
// separate fact, which is the whole reason this is a table.
|
|
100
84
|
hanzo: Object.freeze([
|
|
101
85
|
"hanzo.ai",
|
|
86
|
+
"hanzo.agency",
|
|
102
87
|
"hanzo.app",
|
|
103
88
|
"hanzo.bot",
|
|
104
89
|
"hanzo.chat",
|
|
105
90
|
"hanzo.codes",
|
|
106
91
|
"hanzo.id",
|
|
92
|
+
"hanzo.industries",
|
|
93
|
+
"hanzo.market",
|
|
94
|
+
"hanzo.network",
|
|
107
95
|
"hanzo.sh",
|
|
108
96
|
"hanzo.team",
|
|
109
97
|
"hanzo.ventures",
|
|
@@ -283,6 +271,93 @@ function scrubText(s, capturePII = false) {
|
|
|
283
271
|
return s;
|
|
284
272
|
}
|
|
285
273
|
|
|
274
|
+
// src/throwable.ts
|
|
275
|
+
var MAX_FRAMES = 50;
|
|
276
|
+
var MAX_LINES = 500;
|
|
277
|
+
var MAX_LINE_LEN = 2048;
|
|
278
|
+
var V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+)|([^)]+))\)?\s*$/;
|
|
279
|
+
var MOZ_FRAME = /^\s*(?:(.*?)@)?(.+?):(\d+):(\d+)\s*$/;
|
|
280
|
+
function inApp(file) {
|
|
281
|
+
if (!file) return false;
|
|
282
|
+
return !(file.includes("node_modules") || file.startsWith("webpack-internal") || file.startsWith("webpack://") || file.startsWith("chrome-extension://") || file.startsWith("moz-extension://"));
|
|
283
|
+
}
|
|
284
|
+
function framesFromStack(stack) {
|
|
285
|
+
if (!stack) return [];
|
|
286
|
+
const lines = stack.split("\n", MAX_LINES);
|
|
287
|
+
const frames = [];
|
|
288
|
+
for (const raw of lines) {
|
|
289
|
+
if (raw.length > MAX_LINE_LEN) continue;
|
|
290
|
+
const line = raw.trimEnd();
|
|
291
|
+
if (!line) continue;
|
|
292
|
+
let fn;
|
|
293
|
+
let file = "";
|
|
294
|
+
let lineno = 0;
|
|
295
|
+
let colno = 0;
|
|
296
|
+
const v = V8_FRAME.exec(line);
|
|
297
|
+
if (v) {
|
|
298
|
+
fn = v[1];
|
|
299
|
+
if (v[2]) {
|
|
300
|
+
file = v[2];
|
|
301
|
+
lineno = Number(v[3]) || 0;
|
|
302
|
+
colno = Number(v[4]) || 0;
|
|
303
|
+
} else {
|
|
304
|
+
file = (v[5] || "").trim();
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
const f = MOZ_FRAME.exec(line);
|
|
308
|
+
if (!f) continue;
|
|
309
|
+
fn = f[1];
|
|
310
|
+
file = f[2];
|
|
311
|
+
lineno = Number(f[3]) || 0;
|
|
312
|
+
colno = Number(f[4]) || 0;
|
|
313
|
+
}
|
|
314
|
+
if (!file && !fn) continue;
|
|
315
|
+
frames.push({
|
|
316
|
+
function: fn || "<anonymous>",
|
|
317
|
+
filename: file,
|
|
318
|
+
abs_path: file,
|
|
319
|
+
lineno,
|
|
320
|
+
colno,
|
|
321
|
+
in_app: inApp(file)
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
frames.reverse();
|
|
325
|
+
if (frames.length > MAX_FRAMES) return frames.slice(frames.length - MAX_FRAMES);
|
|
326
|
+
return frames;
|
|
327
|
+
}
|
|
328
|
+
function normalizeError(err) {
|
|
329
|
+
if (err instanceof Error) {
|
|
330
|
+
const name = read(err, "name");
|
|
331
|
+
const message = read(err, "message");
|
|
332
|
+
const stack = read(err, "stack");
|
|
333
|
+
return {
|
|
334
|
+
name: typeof name === "string" && name ? name : "Error",
|
|
335
|
+
message: typeof message === "string" && message ? message : str(err),
|
|
336
|
+
stack: typeof stack === "string" ? stack : void 0
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
if (typeof err === "string") return { name: "Error", message: err };
|
|
340
|
+
try {
|
|
341
|
+
return { name: "Error", message: JSON.stringify(err) ?? str(err) };
|
|
342
|
+
} catch {
|
|
343
|
+
return { name: "Error", message: str(err) };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
function read(o, k) {
|
|
347
|
+
try {
|
|
348
|
+
return o[k];
|
|
349
|
+
} catch {
|
|
350
|
+
return void 0;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
function str(v) {
|
|
354
|
+
try {
|
|
355
|
+
return String(v);
|
|
356
|
+
} catch {
|
|
357
|
+
return "[unstringifiable]";
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
286
361
|
// src/anon.js
|
|
287
362
|
var HZ_ANON_KEY = "iam-anon-id";
|
|
288
363
|
var HZ_ANON_ADOPT = ["hz_anon_id", "hz_id"];
|
|
@@ -406,292 +481,6 @@ function uuidv7Time(id) {
|
|
|
406
481
|
return parseInt(id.slice(0, 8) + id.slice(9, 13), 16);
|
|
407
482
|
}
|
|
408
483
|
|
|
409
|
-
// package.json
|
|
410
|
-
var version = "0.3.44";
|
|
411
|
-
|
|
412
|
-
// src/version.ts
|
|
413
|
-
var VERSION = version;
|
|
414
|
-
|
|
415
|
-
// src/sentry.ts
|
|
416
|
-
var MAX_FRAMES = 50;
|
|
417
|
-
var MAX_LINES = 500;
|
|
418
|
-
var MAX_LINE_LEN = 2048;
|
|
419
|
-
var MAX_TAG_LEN = 1024;
|
|
420
|
-
var MAX_TAGS = 50;
|
|
421
|
-
function eventId() {
|
|
422
|
-
return hzUuidv7().replace(/-/g, "");
|
|
423
|
-
}
|
|
424
|
-
function byteLen(s) {
|
|
425
|
-
if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(s).length;
|
|
426
|
-
if (typeof Buffer !== "undefined") return Buffer.byteLength(s, "utf8");
|
|
427
|
-
return s.length;
|
|
428
|
-
}
|
|
429
|
-
function parseDsn(dsn) {
|
|
430
|
-
if (!dsn) return null;
|
|
431
|
-
const m = /^(https?):\/\/([^@]+)@([^/]+)(\/.*)?$/.exec(dsn.trim());
|
|
432
|
-
if (!m) return null;
|
|
433
|
-
const scheme = m[1];
|
|
434
|
-
const publicKey = m[2];
|
|
435
|
-
const host = m[3];
|
|
436
|
-
const path = m[4] ?? "";
|
|
437
|
-
if (!publicKey || !host) return null;
|
|
438
|
-
const segs = path.split("/").filter(Boolean);
|
|
439
|
-
const projectId = segs.length > 0 ? segs[segs.length - 1] : "";
|
|
440
|
-
if (!projectId) return null;
|
|
441
|
-
const origin = `${scheme}://${host}`;
|
|
442
|
-
const base = `${origin}/${segs.map(encodeURIComponent).join("/")}`;
|
|
443
|
-
const ingestUrl = `${base}/envelope/?sentry_key=${encodeURIComponent(publicKey)}`;
|
|
444
|
-
return { publicKey, origin, projectId, base, ingestUrl };
|
|
445
|
-
}
|
|
446
|
-
var V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+)|([^)]+))\)?\s*$/;
|
|
447
|
-
var MOZ_FRAME = /^\s*(?:(.*?)@)?(.+?):(\d+):(\d+)\s*$/;
|
|
448
|
-
function inApp(file) {
|
|
449
|
-
if (!file) return false;
|
|
450
|
-
return !(file.includes("node_modules") || file.startsWith("webpack-internal") || file.startsWith("webpack://") || file.startsWith("chrome-extension://") || file.startsWith("moz-extension://"));
|
|
451
|
-
}
|
|
452
|
-
function framesFromStack(stack) {
|
|
453
|
-
if (!stack) return [];
|
|
454
|
-
const lines = stack.split("\n", MAX_LINES);
|
|
455
|
-
const frames = [];
|
|
456
|
-
for (const raw of lines) {
|
|
457
|
-
if (raw.length > MAX_LINE_LEN) continue;
|
|
458
|
-
const line = raw.trimEnd();
|
|
459
|
-
if (!line) continue;
|
|
460
|
-
let fn;
|
|
461
|
-
let file = "";
|
|
462
|
-
let lineno = 0;
|
|
463
|
-
let colno = 0;
|
|
464
|
-
const v = V8_FRAME.exec(line);
|
|
465
|
-
if (v) {
|
|
466
|
-
fn = v[1];
|
|
467
|
-
if (v[2]) {
|
|
468
|
-
file = v[2];
|
|
469
|
-
lineno = Number(v[3]) || 0;
|
|
470
|
-
colno = Number(v[4]) || 0;
|
|
471
|
-
} else {
|
|
472
|
-
file = (v[5] || "").trim();
|
|
473
|
-
}
|
|
474
|
-
} else {
|
|
475
|
-
const f = MOZ_FRAME.exec(line);
|
|
476
|
-
if (!f) continue;
|
|
477
|
-
fn = f[1];
|
|
478
|
-
file = f[2];
|
|
479
|
-
lineno = Number(f[3]) || 0;
|
|
480
|
-
colno = Number(f[4]) || 0;
|
|
481
|
-
}
|
|
482
|
-
if (!file && !fn) continue;
|
|
483
|
-
frames.push({
|
|
484
|
-
function: fn || "<anonymous>",
|
|
485
|
-
filename: file,
|
|
486
|
-
abs_path: file,
|
|
487
|
-
lineno,
|
|
488
|
-
colno,
|
|
489
|
-
in_app: inApp(file)
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
frames.reverse();
|
|
493
|
-
if (frames.length > MAX_FRAMES) return frames.slice(frames.length - MAX_FRAMES);
|
|
494
|
-
return frames;
|
|
495
|
-
}
|
|
496
|
-
function normalizeError(err) {
|
|
497
|
-
if (err instanceof Error) {
|
|
498
|
-
const name = read(err, "name");
|
|
499
|
-
const message = read(err, "message");
|
|
500
|
-
const stack = read(err, "stack");
|
|
501
|
-
return {
|
|
502
|
-
name: typeof name === "string" && name ? name : "Error",
|
|
503
|
-
message: typeof message === "string" && message ? message : str(err),
|
|
504
|
-
stack: typeof stack === "string" ? stack : void 0
|
|
505
|
-
};
|
|
506
|
-
}
|
|
507
|
-
if (typeof err === "string") return { name: "Error", message: err };
|
|
508
|
-
try {
|
|
509
|
-
return { name: "Error", message: JSON.stringify(err) ?? str(err) };
|
|
510
|
-
} catch {
|
|
511
|
-
return { name: "Error", message: str(err) };
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
function read(o, k) {
|
|
515
|
-
try {
|
|
516
|
-
return o[k];
|
|
517
|
-
} catch {
|
|
518
|
-
return void 0;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
function str(v) {
|
|
522
|
-
try {
|
|
523
|
-
return String(v);
|
|
524
|
-
} catch {
|
|
525
|
-
return "[unstringifiable]";
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
function coerceTag(v) {
|
|
529
|
-
const s = typeof v === "string" ? v : (() => {
|
|
530
|
-
try {
|
|
531
|
-
return JSON.stringify(v) ?? String(v);
|
|
532
|
-
} catch {
|
|
533
|
-
try {
|
|
534
|
-
return String(v);
|
|
535
|
-
} catch {
|
|
536
|
-
return "[unstringifiable]";
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
})();
|
|
540
|
-
return s.length > MAX_TAG_LEN ? s.slice(0, MAX_TAG_LEN) : s;
|
|
541
|
-
}
|
|
542
|
-
function buildSentryEvent(input) {
|
|
543
|
-
const { error, options: options2 = {}, identity, capturePII = false } = input;
|
|
544
|
-
const norm = normalizeError(error);
|
|
545
|
-
const handled = options2.handled !== false;
|
|
546
|
-
const level = options2.level ?? (handled ? "error" : "fatal");
|
|
547
|
-
const tags2 = { handled: String(handled) };
|
|
548
|
-
if (identity.product) tags2.product = identity.product;
|
|
549
|
-
if (identity.sessionId) tags2.session = identity.sessionId;
|
|
550
|
-
try {
|
|
551
|
-
const props = options2.properties ?? {};
|
|
552
|
-
let n = 0;
|
|
553
|
-
for (const k of Object.keys(props)) {
|
|
554
|
-
if (n >= MAX_TAGS) break;
|
|
555
|
-
try {
|
|
556
|
-
const val = props[k];
|
|
557
|
-
if (val === void 0 || val === null) continue;
|
|
558
|
-
tags2[k] = scrubText(coerceTag(val), capturePII);
|
|
559
|
-
n++;
|
|
560
|
-
} catch {
|
|
561
|
-
continue;
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
} catch {
|
|
565
|
-
}
|
|
566
|
-
const event = {
|
|
567
|
-
event_id: input.id ?? eventId(),
|
|
568
|
-
timestamp: (input.now ?? Date.now()) / 1e3,
|
|
569
|
-
platform: "javascript",
|
|
570
|
-
level,
|
|
571
|
-
logger: identity.product,
|
|
572
|
-
environment: identity.environment,
|
|
573
|
-
release: identity.release,
|
|
574
|
-
exception: {
|
|
575
|
-
values: [
|
|
576
|
-
{
|
|
577
|
-
type: norm.name,
|
|
578
|
-
value: scrubText(norm.message, capturePII),
|
|
579
|
-
stacktrace: { frames: framesFromStack(norm.stack) }
|
|
580
|
-
}
|
|
581
|
-
]
|
|
582
|
-
},
|
|
583
|
-
tags: tags2,
|
|
584
|
-
sdk: { name: "@hanzo/event", version: VERSION }
|
|
585
|
-
};
|
|
586
|
-
if (identity.userId) event.user = { id: identity.userId };
|
|
587
|
-
return event;
|
|
588
|
-
}
|
|
589
|
-
function buildEnvelope(event, dsn, sentAt) {
|
|
590
|
-
const payload = JSON.stringify(event);
|
|
591
|
-
const header = JSON.stringify({
|
|
592
|
-
event_id: event.event_id,
|
|
593
|
-
dsn: dsn.base,
|
|
594
|
-
sent_at: sentAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
595
|
-
});
|
|
596
|
-
const itemHeader = JSON.stringify({
|
|
597
|
-
type: "event",
|
|
598
|
-
content_type: "application/json",
|
|
599
|
-
length: byteLen(payload)
|
|
600
|
-
});
|
|
601
|
-
return `${header}
|
|
602
|
-
${itemHeader}
|
|
603
|
-
${payload}
|
|
604
|
-
`;
|
|
605
|
-
}
|
|
606
|
-
var MAX_VALUE = 4096;
|
|
607
|
-
function digest(s) {
|
|
608
|
-
let out = "";
|
|
609
|
-
for (const seed of [2166136261, 16777619, 2654435769, 2246822507]) {
|
|
610
|
-
let h = seed >>> 0;
|
|
611
|
-
for (let i = 0; i < s.length; i++) {
|
|
612
|
-
h ^= s.charCodeAt(i);
|
|
613
|
-
h = h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24) >>> 0;
|
|
614
|
-
}
|
|
615
|
-
out += h.toString(16).padStart(8, "0");
|
|
616
|
-
}
|
|
617
|
-
return out;
|
|
618
|
-
}
|
|
619
|
-
function frameOf(f) {
|
|
620
|
-
const source = f.filename ?? "";
|
|
621
|
-
const name = f.function ?? "<anonymous>";
|
|
622
|
-
const line = f.lineno ?? 0;
|
|
623
|
-
const column = f.colno ?? 0;
|
|
624
|
-
return {
|
|
625
|
-
// Stable per code location, so the same frame groups and re-renders under one
|
|
626
|
-
// key. The '/part' suffix is the product's format for one raw frame expanding
|
|
627
|
-
// into several resolved ones; a browser frame is always part 0.
|
|
628
|
-
raw_id: `${digest(`${source}|${name}|${line}|${column}`)}/0`,
|
|
629
|
-
mangled_name: name,
|
|
630
|
-
source,
|
|
631
|
-
line,
|
|
632
|
-
column,
|
|
633
|
-
in_app: f.in_app ?? false,
|
|
634
|
-
lang: "javascript",
|
|
635
|
-
resolved: false,
|
|
636
|
-
resolve_failure: "no symbol set uploaded for this release",
|
|
637
|
-
resolved_name: null,
|
|
638
|
-
module: null
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
function truncate2(s, max) {
|
|
642
|
-
if (s.length <= max) return s;
|
|
643
|
-
let end = max;
|
|
644
|
-
const c = s.charCodeAt(end - 1);
|
|
645
|
-
if (c >= 55296 && c <= 56319) end--;
|
|
646
|
-
return s.slice(0, end);
|
|
647
|
-
}
|
|
648
|
-
function exceptionEntry(err, opts) {
|
|
649
|
-
const n = normalizeError(err);
|
|
650
|
-
const frames = framesFromStack(n.stack).slice(-50);
|
|
651
|
-
const entry = {
|
|
652
|
-
id: opts.id,
|
|
653
|
-
type: n.name,
|
|
654
|
-
value: truncate2(n.message, MAX_VALUE),
|
|
655
|
-
mechanism: {
|
|
656
|
-
type: "generic",
|
|
657
|
-
handled: opts.handled,
|
|
658
|
-
// An exception the app reported by hand was constructed, not thrown by the
|
|
659
|
-
// runtime at a real call site.
|
|
660
|
-
synthetic: !(err instanceof Error)
|
|
661
|
-
}
|
|
662
|
-
};
|
|
663
|
-
if (frames.length > 0) {
|
|
664
|
-
entry.stacktrace = { type: "resolved", frames: frames.map(frameOf) };
|
|
665
|
-
}
|
|
666
|
-
return entry;
|
|
667
|
-
}
|
|
668
|
-
function fingerprint(entry) {
|
|
669
|
-
const frames = entry.stacktrace?.frames ?? [];
|
|
670
|
-
const pieces = frames.filter((f) => f.in_app).map((f) => `${f.mangled_name}@${f.source}`);
|
|
671
|
-
return digest([entry.type, ...pieces].join("\n"));
|
|
672
|
-
}
|
|
673
|
-
function exceptionProperties(err, opts) {
|
|
674
|
-
const entry = exceptionEntry(err, opts);
|
|
675
|
-
const frames = entry.stacktrace?.frames ?? [];
|
|
676
|
-
const fp = fingerprint(entry);
|
|
677
|
-
return {
|
|
678
|
-
$exception_list: [entry],
|
|
679
|
-
$exception_fingerprint: fp,
|
|
680
|
-
// Records WHY this fingerprint holds. 'manual' is the honest value: the client
|
|
681
|
-
// supplied the key rather than a server grouper deriving one.
|
|
682
|
-
$exception_fingerprint_record: [{ type: "manual" }],
|
|
683
|
-
$exception_type: entry.type,
|
|
684
|
-
$exception_message: entry.value,
|
|
685
|
-
$exception_level: opts.level ?? "error",
|
|
686
|
-
$exception_handled: opts.handled,
|
|
687
|
-
$exception_synthetic: entry.mechanism?.synthetic ?? false,
|
|
688
|
-
$exception_types: [entry.type],
|
|
689
|
-
$exception_values: [entry.value],
|
|
690
|
-
$exception_sources: frames.map((f) => f.source).filter((s) => !!s),
|
|
691
|
-
$exception_functions: frames.map((f) => f.resolved_name ?? f.mangled_name).filter((s) => !!s)
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
|
|
695
484
|
// src/storage.ts
|
|
696
485
|
var KEY = {
|
|
697
486
|
session: "hz_session",
|
|
@@ -843,20 +632,16 @@ function stripLink(href) {
|
|
|
843
632
|
return hit ? u.pathname + u.search + u.hash : void 0;
|
|
844
633
|
}
|
|
845
634
|
|
|
635
|
+
// package.json
|
|
636
|
+
var version = "0.3.49";
|
|
637
|
+
|
|
638
|
+
// src/version.ts
|
|
639
|
+
var VERSION = version;
|
|
640
|
+
|
|
846
641
|
// src/core.ts
|
|
847
642
|
var EVENT_PATH = "/v1/event";
|
|
848
643
|
var DEFAULT_HOST = "https://api.hanzo.ai";
|
|
849
|
-
var ENVELOPE_CONTENT_TYPE = "application/x-sentry-envelope";
|
|
850
644
|
var BEACON_CONTENT_TYPE = "text/plain";
|
|
851
|
-
function readEnvDsn() {
|
|
852
|
-
try {
|
|
853
|
-
if (typeof process !== "undefined" && process.env) {
|
|
854
|
-
return process.env.NEXT_PUBLIC_HANZO_EVENT_DSN || process.env.HANZO_EVENT_DSN || void 0;
|
|
855
|
-
}
|
|
856
|
-
} catch {
|
|
857
|
-
}
|
|
858
|
-
return void 0;
|
|
859
|
-
}
|
|
860
645
|
function readEnv(name) {
|
|
861
646
|
try {
|
|
862
647
|
if (typeof process !== "undefined" && process.env) return process.env[name] || void 0;
|
|
@@ -869,7 +654,8 @@ function appendQuery(url, key, value) {
|
|
|
869
654
|
}
|
|
870
655
|
function normalizeError2(err) {
|
|
871
656
|
const n = normalizeError(err);
|
|
872
|
-
|
|
657
|
+
const frames = framesFromStack(n.stack).slice(-50).map((f) => ({ function: f.function, file: f.filename, line: f.lineno, column: f.colno }));
|
|
658
|
+
return { type: n.name, message: n.message, stack: n.stack, frames };
|
|
873
659
|
}
|
|
874
660
|
var isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
|
|
875
661
|
function serializeBatch(batch) {
|
|
@@ -965,35 +751,13 @@ var Analytics = class {
|
|
|
965
751
|
ingestKey: config.ingestKey ?? readEnv("NEXT_PUBLIC_PUBLISHABLE_KEY") ?? keyForPage()
|
|
966
752
|
};
|
|
967
753
|
this.transport = config.transport ?? new DefaultTransport();
|
|
968
|
-
this.dsn = parseDsn(
|
|
969
|
-
config.dsn ?? readEnvDsn() ?? dsnForProduct(this.cfg.product, this.cfg.ingestKey)
|
|
970
|
-
);
|
|
971
754
|
}
|
|
972
755
|
/** adopt gives this client a credential it does not have. The key belongs to
|
|
973
756
|
* the stream, not to whichever caller happened to ask for the handle first,
|
|
974
|
-
* so a later caller carrying one hands it over.
|
|
975
|
-
* the key, so it comes up here too when it was inert for want of one.
|
|
976
|
-
* Present fields are never overwritten: the first caller's key stays. */
|
|
757
|
+
* so a later caller carrying one hands it over. Present fields are never overwritten: the first caller's key stays. */
|
|
977
758
|
adopt(config) {
|
|
978
759
|
if (config.ingestKey && !this.cfg.ingestKey) this.cfg.ingestKey = config.ingestKey;
|
|
979
760
|
if (config.getToken && !this.cfg.getToken) this.cfg.getToken = config.getToken;
|
|
980
|
-
if (!this.dsn) {
|
|
981
|
-
this.dsn = parseDsn(
|
|
982
|
-
config.dsn ?? readEnvDsn() ?? dsnForProduct(this.cfg.product, this.cfg.ingestKey)
|
|
983
|
-
);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
/** errorPlaneEnabled reports whether captured exceptions can actually reach the
|
|
987
|
-
* error host. False means a DSN was never configured — the documented
|
|
988
|
-
* fail-safe. Exposed so an app (or a test) can assert its wiring instead of
|
|
989
|
-
* discovering months later that nothing was ever reported. */
|
|
990
|
-
get errorPlaneEnabled() {
|
|
991
|
-
return this.dsn !== null;
|
|
992
|
-
}
|
|
993
|
-
/** errorIngestUrl is the fully-derived envelope endpoint, or undefined when the
|
|
994
|
-
* plane is inert. Diagnostics only. */
|
|
995
|
-
get errorIngestUrl() {
|
|
996
|
-
return this.dsn?.ingestUrl;
|
|
997
761
|
}
|
|
998
762
|
/** init is idempotent and browser-only for its side effects: capture first-touch
|
|
999
763
|
* attribution, hydrate cohort, register the unload flush, and (unless opted out)
|
|
@@ -1081,45 +845,28 @@ var Analytics = class {
|
|
|
1081
845
|
this.enqueue("event", event, { properties, ...commerce });
|
|
1082
846
|
}
|
|
1083
847
|
/** captureError reports a caught error, an unhandled rejection, a React render
|
|
1084
|
-
* error, or a manual report
|
|
1085
|
-
*
|
|
1086
|
-
*
|
|
1087
|
-
*
|
|
1088
|
-
*
|
|
1089
|
-
* - the EVENT STREAM — a `type:'error'` row in the cloud event warehouse, so
|
|
1090
|
-
* an error stays correlated with the session's pageviews for product
|
|
1091
|
-
* analysis (readable via GET /v1/errors).
|
|
1092
|
-
*
|
|
1093
|
-
* Both carry the SAME session and subject id, so an error and the pageview
|
|
1094
|
-
* before it join up. Never throws back into the app; errors are higher-signal
|
|
1095
|
-
* than pageviews, so both planes flush promptly (a crash may unload the page
|
|
1096
|
-
* moments later). */
|
|
848
|
+
* error, or a manual report as ONE event of type 'error' on the one stream.
|
|
849
|
+
* It carries the exception (type, message, frames, stack, handled) and the
|
|
850
|
+
* release, environment, site, product and level that group and scope it, and
|
|
851
|
+
* flushes promptly (a crash may unload the page moments later). Never throws
|
|
852
|
+
* back into the app. */
|
|
1097
853
|
captureError(err, context) {
|
|
1098
854
|
if (this.reentrant) return;
|
|
1099
855
|
this.reentrant = true;
|
|
1100
856
|
try {
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
handled,
|
|
1115
|
-
id: hzUuidv7(),
|
|
1116
|
-
level: context?.level
|
|
1117
|
-
})
|
|
1118
|
-
}
|
|
1119
|
-
});
|
|
1120
|
-
this.flush();
|
|
1121
|
-
} catch {
|
|
1122
|
-
}
|
|
857
|
+
const handled = context?.handled ?? true;
|
|
858
|
+
const ex = normalizeError2(err);
|
|
859
|
+
ex.handled = handled;
|
|
860
|
+
this.enqueue("error", void 0, {
|
|
861
|
+
error: ex,
|
|
862
|
+
level: context?.level ?? (handled ? "error" : "fatal"),
|
|
863
|
+
release: this.cfg.release ?? readEnv("NEXT_PUBLIC_HANZO_RELEASE"),
|
|
864
|
+
environment: this.cfg.environment ?? readEnv("NODE_ENV"),
|
|
865
|
+
site: isBrowser() ? window.location.hostname : void 0,
|
|
866
|
+
properties: context?.properties
|
|
867
|
+
});
|
|
868
|
+
this.flush();
|
|
869
|
+
} catch {
|
|
1123
870
|
} finally {
|
|
1124
871
|
this.reentrant = false;
|
|
1125
872
|
}
|
|
@@ -1162,39 +909,6 @@ var Analytics = class {
|
|
|
1162
909
|
});
|
|
1163
910
|
}
|
|
1164
911
|
// ── internals ────────────────────────────────────────────────────────────
|
|
1165
|
-
/** sendError frames one exception as a Sentry envelope and posts it to the DSN's
|
|
1166
|
-
* ingest URL. The DSN's own key rides ?sentry_key= (the credential channel the
|
|
1167
|
-
* server trusts, and the only one a headerless beacon can carry), so NO bearer
|
|
1168
|
-
* or publishable key is attached here — the two planes authenticate
|
|
1169
|
-
* independently. Errors are sent one envelope per event, immediately: batching
|
|
1170
|
-
* a crash report is how you lose it. */
|
|
1171
|
-
sendError(err, options2) {
|
|
1172
|
-
if (!this.cfg.enabled || !this.dsn) return;
|
|
1173
|
-
const event = buildSentryEvent({
|
|
1174
|
-
error: err,
|
|
1175
|
-
options: options2,
|
|
1176
|
-
identity: this.errorIdentity(),
|
|
1177
|
-
capturePII: this.cfg.capturePII ?? false
|
|
1178
|
-
});
|
|
1179
|
-
const body = buildEnvelope(event, this.dsn);
|
|
1180
|
-
if (this.cfg.debug) console.debug("[event] error \u2192", this.dsn.ingestUrl, event.event_id);
|
|
1181
|
-
this.transport.send(this.dsn.ingestUrl, body, {
|
|
1182
|
-
beacon: false,
|
|
1183
|
-
contentType: ENVELOPE_CONTENT_TYPE,
|
|
1184
|
-
debug: this.cfg.debug
|
|
1185
|
-
});
|
|
1186
|
-
}
|
|
1187
|
-
/** errorIdentity is the SAME identity the event stream stamps — the OIDC subject
|
|
1188
|
-
* once identify() has run, else the anon id. Never email/PII. */
|
|
1189
|
-
errorIdentity() {
|
|
1190
|
-
return {
|
|
1191
|
-
userId: this.personId ?? anonId(),
|
|
1192
|
-
sessionId: sessionId(),
|
|
1193
|
-
product: this.cfg.product,
|
|
1194
|
-
release: this.cfg.release ?? readEnv("NEXT_PUBLIC_HANZO_RELEASE"),
|
|
1195
|
-
environment: this.cfg.environment ?? readEnv("NODE_ENV")
|
|
1196
|
-
};
|
|
1197
|
-
}
|
|
1198
912
|
enqueue(kind, event, extra) {
|
|
1199
913
|
if (!this.cfg.enabled) return;
|
|
1200
914
|
if (!this.started) this.init();
|
|
@@ -1283,6 +997,94 @@ function createAnalytics(config) {
|
|
|
1283
997
|
clients.set(key, fresh);
|
|
1284
998
|
return fresh;
|
|
1285
999
|
}
|
|
1000
|
+
var MAX_VALUE = 4096;
|
|
1001
|
+
function digest(s) {
|
|
1002
|
+
let out = "";
|
|
1003
|
+
for (const seed of [2166136261, 16777619, 2654435769, 2246822507]) {
|
|
1004
|
+
let h = seed >>> 0;
|
|
1005
|
+
for (let i = 0; i < s.length; i++) {
|
|
1006
|
+
h ^= s.charCodeAt(i);
|
|
1007
|
+
h = h + (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24) >>> 0;
|
|
1008
|
+
}
|
|
1009
|
+
out += h.toString(16).padStart(8, "0");
|
|
1010
|
+
}
|
|
1011
|
+
return out;
|
|
1012
|
+
}
|
|
1013
|
+
function frameOf(f) {
|
|
1014
|
+
const source = f.filename ?? "";
|
|
1015
|
+
const name = f.function ?? "<anonymous>";
|
|
1016
|
+
const line = f.lineno ?? 0;
|
|
1017
|
+
const column = f.colno ?? 0;
|
|
1018
|
+
return {
|
|
1019
|
+
// Stable per code location, so the same frame groups and re-renders under one
|
|
1020
|
+
// key. The '/part' suffix is the product's format for one raw frame expanding
|
|
1021
|
+
// into several resolved ones; a browser frame is always part 0.
|
|
1022
|
+
raw_id: `${digest(`${source}|${name}|${line}|${column}`)}/0`,
|
|
1023
|
+
mangled_name: name,
|
|
1024
|
+
source,
|
|
1025
|
+
line,
|
|
1026
|
+
column,
|
|
1027
|
+
in_app: f.in_app ?? false,
|
|
1028
|
+
lang: "javascript",
|
|
1029
|
+
resolved: false,
|
|
1030
|
+
resolve_failure: "no symbol set uploaded for this release",
|
|
1031
|
+
resolved_name: null,
|
|
1032
|
+
module: null
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
function truncate2(s, max) {
|
|
1036
|
+
if (s.length <= max) return s;
|
|
1037
|
+
let end = max;
|
|
1038
|
+
const c = s.charCodeAt(end - 1);
|
|
1039
|
+
if (c >= 55296 && c <= 56319) end--;
|
|
1040
|
+
return s.slice(0, end);
|
|
1041
|
+
}
|
|
1042
|
+
function exceptionEntry(err, opts) {
|
|
1043
|
+
const n = normalizeError(err);
|
|
1044
|
+
const frames = framesFromStack(n.stack).slice(-50);
|
|
1045
|
+
const entry = {
|
|
1046
|
+
id: opts.id,
|
|
1047
|
+
type: n.name,
|
|
1048
|
+
value: truncate2(n.message, MAX_VALUE),
|
|
1049
|
+
mechanism: {
|
|
1050
|
+
type: "generic",
|
|
1051
|
+
handled: opts.handled,
|
|
1052
|
+
// An exception the app reported by hand was constructed, not thrown by the
|
|
1053
|
+
// runtime at a real call site.
|
|
1054
|
+
synthetic: !(err instanceof Error)
|
|
1055
|
+
}
|
|
1056
|
+
};
|
|
1057
|
+
if (frames.length > 0) {
|
|
1058
|
+
entry.stacktrace = { type: "resolved", frames: frames.map(frameOf) };
|
|
1059
|
+
}
|
|
1060
|
+
return entry;
|
|
1061
|
+
}
|
|
1062
|
+
function fingerprint(entry) {
|
|
1063
|
+
const frames = entry.stacktrace?.frames ?? [];
|
|
1064
|
+
const pieces = frames.filter((f) => f.in_app).map((f) => `${f.mangled_name}@${f.source}`);
|
|
1065
|
+
return digest([entry.type, ...pieces].join("\n"));
|
|
1066
|
+
}
|
|
1067
|
+
function exceptionProperties(err, opts) {
|
|
1068
|
+
const entry = exceptionEntry(err, opts);
|
|
1069
|
+
const frames = entry.stacktrace?.frames ?? [];
|
|
1070
|
+
const fp = fingerprint(entry);
|
|
1071
|
+
return {
|
|
1072
|
+
$exception_list: [entry],
|
|
1073
|
+
$exception_fingerprint: fp,
|
|
1074
|
+
// Records WHY this fingerprint holds. 'manual' is the honest value: the client
|
|
1075
|
+
// supplied the key rather than a server grouper deriving one.
|
|
1076
|
+
$exception_fingerprint_record: [{ type: "manual" }],
|
|
1077
|
+
$exception_type: entry.type,
|
|
1078
|
+
$exception_message: entry.value,
|
|
1079
|
+
$exception_level: opts.level ?? "error",
|
|
1080
|
+
$exception_handled: opts.handled,
|
|
1081
|
+
$exception_synthetic: entry.mechanism?.synthetic ?? false,
|
|
1082
|
+
$exception_types: [entry.type],
|
|
1083
|
+
$exception_values: [entry.value],
|
|
1084
|
+
$exception_sources: frames.map((f) => f.source).filter((s) => !!s),
|
|
1085
|
+
$exception_functions: frames.map((f) => f.resolved_name ?? f.mangled_name).filter((s) => !!s)
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1286
1088
|
|
|
1287
1089
|
// src/cookie.ts
|
|
1288
1090
|
var DAY = 86400;
|
|
@@ -1390,6 +1192,17 @@ function touch(c) {
|
|
|
1390
1192
|
}
|
|
1391
1193
|
return out;
|
|
1392
1194
|
}
|
|
1195
|
+
var SITE_KEY = Object.freeze({
|
|
1196
|
+
"hanzo.ai": "pk-CmfLA2K6kvsPflrS9DSkt06H_kSoQB_21sjedt6VJdc",
|
|
1197
|
+
"www.hanzo.ai": "pk-CmfLA2K6kvsPflrS9DSkt06H_kSoQB_21sjedt6VJdc",
|
|
1198
|
+
"hanzo.app": "pk-wlnXN2a9_vmCm60yTFtQ629Q8TyuaxBNZbY1RWT72gQ",
|
|
1199
|
+
"hanzo.team": "pk-NCzD2FiHpZv8KUpkCX4olT1LJOJsMxBC_Z8NkiQsOFQ",
|
|
1200
|
+
"pay.hanzo.ai": "pk-eX6kv7JZNoiYn1WkeJH3tT_8OvkVYScmCXnLMwxTKf8",
|
|
1201
|
+
"platform.hanzo.ai": "pk-My1RpZLEUnTj8vAdPbWKYdDUuhxZJ4dVjHcwjZN4rZ8",
|
|
1202
|
+
"docs.hanzo.ai": "pk-jukhtjMT2ymoeBDAeFjINQWlBv-v9sNn1TPztiCrrwk",
|
|
1203
|
+
"hanzo.bot": "pk-W5d7Mn7ZukT7igyscIy6Pqe8JpA0Ge604Yn4xNR4JCU",
|
|
1204
|
+
"cloud.hanzo.ai": "pk-RAfEGHPoNdCEU9fnA_cPd_Xo9Tci44rlYQV9xuJ1Ob0"
|
|
1205
|
+
});
|
|
1393
1206
|
var page = () => window;
|
|
1394
1207
|
var tags = [];
|
|
1395
1208
|
var configured = false;
|
|
@@ -1567,8 +1380,10 @@ function start(o = {}) {
|
|
|
1567
1380
|
configured = true;
|
|
1568
1381
|
const base = (o.base ?? "https://api.hanzo.ai").replace(/\/$/, "");
|
|
1569
1382
|
const q = new URLSearchParams();
|
|
1570
|
-
|
|
1571
|
-
|
|
1383
|
+
const host = o.host ?? window.location.hostname;
|
|
1384
|
+
const key = o.key ?? SITE_KEY[host];
|
|
1385
|
+
if (key) q.set("key", key);
|
|
1386
|
+
q.set("host", host);
|
|
1572
1387
|
const answer = (list) => {
|
|
1573
1388
|
tags = list;
|
|
1574
1389
|
configAnswered = true;
|
|
@@ -1680,16 +1495,17 @@ function mirror(name, params, id, to = reach()) {
|
|
|
1680
1495
|
}
|
|
1681
1496
|
}
|
|
1682
1497
|
}
|
|
1683
|
-
function track(stream, name, params = {}) {
|
|
1498
|
+
function track(stream, name, params = {}, only) {
|
|
1684
1499
|
if (typeof window === "undefined") return;
|
|
1685
1500
|
if (!leaving && !settled()) {
|
|
1686
|
-
held.push(() => track(stream, name, params));
|
|
1501
|
+
held.push(() => track(stream, name, params, only));
|
|
1687
1502
|
return;
|
|
1688
1503
|
}
|
|
1689
1504
|
const c = read2();
|
|
1690
|
-
const event_id = crypto.randomUUID();
|
|
1505
|
+
const event_id = typeof params.event_id === "string" && params.event_id ? params.event_id : crypto.randomUUID();
|
|
1691
1506
|
const to = reach();
|
|
1692
1507
|
mirror(name, params, event_id, to);
|
|
1508
|
+
if (only === "tags") return;
|
|
1693
1509
|
stream?.capture(name, {
|
|
1694
1510
|
...params,
|
|
1695
1511
|
...touch(c),
|
|
@@ -1705,6 +1521,20 @@ function visit() {
|
|
|
1705
1521
|
return { ...touch(c), consent: render(c), tags: reach(), userAgent: navigator.userAgent, url: window.location.href };
|
|
1706
1522
|
}
|
|
1707
1523
|
|
|
1524
|
+
// src/items.ts
|
|
1525
|
+
function commerceItem(p) {
|
|
1526
|
+
const annual = /^(year|yearly|annual)$/i.test(p.interval);
|
|
1527
|
+
return {
|
|
1528
|
+
item_id: p.id,
|
|
1529
|
+
...p.name ? { item_name: p.name } : {},
|
|
1530
|
+
item_category: p.team ? "team" : "individual",
|
|
1531
|
+
item_variant: annual ? "annual" : "monthly",
|
|
1532
|
+
price: p.price ?? 0,
|
|
1533
|
+
quantity: p.quantity ?? 1
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
var worth = (i) => i.price * i.quantity;
|
|
1537
|
+
|
|
1708
1538
|
// src/goals.ts
|
|
1709
1539
|
var GOALS = {
|
|
1710
1540
|
// Signup: the conversion is signup_completed, along the site signup funnel.
|
|
@@ -1798,17 +1628,15 @@ exports.GOALS = GOALS;
|
|
|
1798
1628
|
exports.LINK_PARAMS = LINK_PARAMS;
|
|
1799
1629
|
exports.ORG_DOMAIN = ORG_DOMAIN;
|
|
1800
1630
|
exports.ORG_KEY = ORG_KEY;
|
|
1801
|
-
exports.
|
|
1631
|
+
exports.SITE_KEY = SITE_KEY;
|
|
1802
1632
|
exports.VERSION = VERSION;
|
|
1803
1633
|
exports.acceptAll = acceptAll;
|
|
1804
1634
|
exports.asks = asks;
|
|
1805
|
-
exports.buildEnvelope = buildEnvelope;
|
|
1806
|
-
exports.buildSentryEvent = buildSentryEvent;
|
|
1807
1635
|
exports.captureClick = capture;
|
|
1636
|
+
exports.commerceItem = commerceItem;
|
|
1808
1637
|
exports.createAnalytics = createAnalytics;
|
|
1809
1638
|
exports.deriveChannel = deriveChannel;
|
|
1810
1639
|
exports.digest = digest;
|
|
1811
|
-
exports.dsnForProduct = dsnForProduct;
|
|
1812
1640
|
exports.exceptionEntry = exceptionEntry;
|
|
1813
1641
|
exports.exceptionProperties = exceptionProperties;
|
|
1814
1642
|
exports.fingerprint = fingerprint;
|
|
@@ -1825,7 +1653,6 @@ exports.linkUrl = linkUrl;
|
|
|
1825
1653
|
exports.mirror = mirror;
|
|
1826
1654
|
exports.orgOf = orgOf;
|
|
1827
1655
|
exports.parseAttribution = parseAttribution;
|
|
1828
|
-
exports.parseDsn = parseDsn;
|
|
1829
1656
|
exports.reach = reach;
|
|
1830
1657
|
exports.readConsent = read2;
|
|
1831
1658
|
exports.readLink = readLink;
|
|
@@ -1846,5 +1673,6 @@ exports.track = track;
|
|
|
1846
1673
|
exports.uuidv7 = hzUuidv7;
|
|
1847
1674
|
exports.uuidv7Time = uuidv7Time;
|
|
1848
1675
|
exports.visit = visit;
|
|
1676
|
+
exports.worth = worth;
|
|
1849
1677
|
//# sourceMappingURL=index.cjs.map
|
|
1850
1678
|
//# sourceMappingURL=index.cjs.map
|