@hanzo/event 0.3.25 → 0.3.27
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/LICENSE.md +21 -0
- package/dist/{core-DO8tBUy7.d.cts → core-usULsfUB.d.cts} +16 -4
- package/dist/{core-DO8tBUy7.d.ts → core-usULsfUB.d.ts} +16 -4
- package/dist/index.cjs +48 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.mjs +48 -9
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +50 -14
- 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 +51 -15
- package/dist/react.mjs.map +1 -1
- package/hz.js +19 -20
- package/package.json +10 -10
- package/src/core.ts +105 -21
- package/src/hz.test.ts +14 -3
- package/src/react.tsx +7 -7
- package/src/stream.test.ts +169 -0
- package/src/transport.test.ts +7 -1
- package/src/version.ts +1 -1
- package/src/stack.ts +0 -144
package/src/stack.ts
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
// Pure error parsing: coerce an unknown throwable into {name, message, stack},
|
|
2
|
-
// and parse a browser stack string into structured frames. No I/O, no globals —
|
|
3
|
-
// core.ts wires these to identity and transport.
|
|
4
|
-
//
|
|
5
|
-
// These frames ride the error Event to POST /v1/event so the warehouse stores
|
|
6
|
-
// WHERE a crash happened, not just that one did. Nothing here talks to Sentry:
|
|
7
|
-
// the client has one door and one credential. The frame shape is deliberately the
|
|
8
|
-
// conventional one (function/filename/abs_path/lineno/colno/in_app) because it is
|
|
9
|
-
// what every stack tool already speaks — including a future grouper built over
|
|
10
|
-
// the warehouse.
|
|
11
|
-
|
|
12
|
-
/** Max frames kept — well under the server's cap, plenty to identify a crash. */
|
|
13
|
-
const MAX_FRAMES = 50
|
|
14
|
-
/** Max stack lines examined, and max length of a line worth examining. Guards the
|
|
15
|
-
* frame regexes against a hostile `stack` string (see framesFromStack). */
|
|
16
|
-
const MAX_LINES = 500
|
|
17
|
-
const MAX_LINE_LEN = 2048
|
|
18
|
-
|
|
19
|
-
/** One parsed stack frame. */
|
|
20
|
-
export interface Frame {
|
|
21
|
-
filename?: string
|
|
22
|
-
function?: string
|
|
23
|
-
abs_path?: string
|
|
24
|
-
lineno?: number
|
|
25
|
-
colno?: number
|
|
26
|
-
/** The app's own code, as opposed to vendor/runtime — the useful default filter. */
|
|
27
|
-
in_app?: boolean
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const V8_FRAME = /^\s*at\s+(?:(.+?)\s+\()?(?:(.+?):(\d+):(\d+)|([^)]+))\)?\s*$/
|
|
31
|
-
const MOZ_FRAME = /^\s*(?:(.*?)@)?(.+?):(\d+):(\d+)\s*$/
|
|
32
|
-
|
|
33
|
-
/** inApp marks a frame as the app's own code (vs vendor/runtime). */
|
|
34
|
-
function inApp(file: string): boolean {
|
|
35
|
-
if (!file) return false
|
|
36
|
-
return !(
|
|
37
|
-
file.includes('node_modules') ||
|
|
38
|
-
file.startsWith('webpack-internal') ||
|
|
39
|
-
file.startsWith('webpack://') ||
|
|
40
|
-
file.startsWith('chrome-extension://') ||
|
|
41
|
-
file.startsWith('moz-extension://')
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* framesFromStack parses a browser Error.stack into frames, OLDEST-FIRST (caller
|
|
47
|
-
* → callee, so the crash site is LAST). Handles both V8 ("at fn (file:li:co)")
|
|
48
|
-
* and Firefox/Safari ("fn@file:li:co"). Unparseable lines are skipped.
|
|
49
|
-
*/
|
|
50
|
-
export function framesFromStack(stack: string | undefined): Frame[] {
|
|
51
|
-
if (!stack) return []
|
|
52
|
-
// Both frame regexes use lazy nested quantifiers, which backtrack badly on a
|
|
53
|
-
// long line that never matches. A stack is attacker-influenced (a thrown value
|
|
54
|
-
// can carry any `stack` string), so bound the work: skip absurd lines and stop
|
|
55
|
-
// after MAX_LINES. Only the innermost MAX_FRAMES are kept anyway.
|
|
56
|
-
const lines = stack.split('\n', MAX_LINES)
|
|
57
|
-
const frames: Frame[] = []
|
|
58
|
-
for (const raw of lines) {
|
|
59
|
-
if (raw.length > MAX_LINE_LEN) continue
|
|
60
|
-
const line = raw.trimEnd()
|
|
61
|
-
if (!line) continue
|
|
62
|
-
// Header lines like "TypeError: x is not a function" match neither frame
|
|
63
|
-
// regex (no "at " prefix, no trailing :line:col) and are skipped naturally.
|
|
64
|
-
let fn: string | undefined
|
|
65
|
-
let file = ''
|
|
66
|
-
let lineno = 0
|
|
67
|
-
let colno = 0
|
|
68
|
-
const v = V8_FRAME.exec(line)
|
|
69
|
-
if (v) {
|
|
70
|
-
fn = v[1]
|
|
71
|
-
if (v[2]) {
|
|
72
|
-
file = v[2]
|
|
73
|
-
lineno = Number(v[3]) || 0
|
|
74
|
-
colno = Number(v[4]) || 0
|
|
75
|
-
} else {
|
|
76
|
-
file = (v[5] || '').trim()
|
|
77
|
-
}
|
|
78
|
-
} else {
|
|
79
|
-
const f = MOZ_FRAME.exec(line)
|
|
80
|
-
if (!f) continue
|
|
81
|
-
fn = f[1]
|
|
82
|
-
file = f[2]
|
|
83
|
-
lineno = Number(f[3]) || 0
|
|
84
|
-
colno = Number(f[4]) || 0
|
|
85
|
-
}
|
|
86
|
-
if (!file && !fn) continue
|
|
87
|
-
frames.push({
|
|
88
|
-
function: fn || '<anonymous>',
|
|
89
|
-
filename: file,
|
|
90
|
-
abs_path: file,
|
|
91
|
-
lineno,
|
|
92
|
-
colno,
|
|
93
|
-
in_app: inApp(file),
|
|
94
|
-
})
|
|
95
|
-
}
|
|
96
|
-
// Reverse to oldest-first and cap to the innermost MAX_FRAMES.
|
|
97
|
-
frames.reverse()
|
|
98
|
-
if (frames.length > MAX_FRAMES) return frames.slice(frames.length - MAX_FRAMES)
|
|
99
|
-
return frames
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** read pulls a property off a value that may be hostile — `name`, `message` and
|
|
103
|
-
* `stack` are ordinary getters that a thrown object is free to define as
|
|
104
|
-
* throwing. The thrown value is the least trustworthy input this library
|
|
105
|
-
* handles; losing the whole report to one of them is not acceptable. */
|
|
106
|
-
function read(o: unknown, k: string): unknown {
|
|
107
|
-
try {
|
|
108
|
-
return (o as Record<string, unknown>)[k]
|
|
109
|
-
} catch {
|
|
110
|
-
return undefined
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** str coerces to a string without letting a throwing toString/Symbol.toPrimitive
|
|
115
|
-
* escape. */
|
|
116
|
-
function str(v: unknown): string {
|
|
117
|
-
try {
|
|
118
|
-
return String(v)
|
|
119
|
-
} catch {
|
|
120
|
-
return '[unstringifiable]'
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** normalizeError coerces an unknown throwable into {name, message, stack}.
|
|
125
|
-
* TOTAL: it returns a usable record for ANY input, including an object
|
|
126
|
-
* engineered to throw on property access. */
|
|
127
|
-
export function normalizeError(err: unknown): { name: string; message: string; stack?: string } {
|
|
128
|
-
if (err instanceof Error) {
|
|
129
|
-
const name = read(err, 'name')
|
|
130
|
-
const message = read(err, 'message')
|
|
131
|
-
const stack = read(err, 'stack')
|
|
132
|
-
return {
|
|
133
|
-
name: typeof name === 'string' && name ? name : 'Error',
|
|
134
|
-
message: typeof message === 'string' && message ? message : str(err),
|
|
135
|
-
stack: typeof stack === 'string' ? stack : undefined,
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (typeof err === 'string') return { name: 'Error', message: err }
|
|
139
|
-
try {
|
|
140
|
-
return { name: 'Error', message: JSON.stringify(err) ?? str(err) }
|
|
141
|
-
} catch {
|
|
142
|
-
return { name: 'Error', message: str(err) }
|
|
143
|
-
}
|
|
144
|
-
}
|