@hanzo/event 0.3.36 → 0.3.37
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/README.md +8 -8
- package/TAXONOMY.md +1 -1
- package/dist/attribution.d.ts +14 -0
- package/dist/attribution.d.ts.map +1 -0
- package/dist/core.d.ts +102 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/dsn.d.ts +43 -0
- package/dist/dsn.d.ts.map +1 -0
- package/dist/events.d.ts +21 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/exception.d.ts +50 -0
- package/dist/exception.d.ts.map +1 -0
- package/dist/funnels.d.ts +14 -0
- package/dist/funnels.d.ts.map +1 -0
- package/dist/goals.d.ts +24 -0
- package/dist/goals.d.ts.map +1 -0
- package/dist/index.cjs +7 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -292
- package/dist/index.d.ts +22 -292
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +7 -7
- package/dist/index.mjs.map +1 -1
- package/dist/org.d.ts +74 -0
- package/dist/org.d.ts.map +1 -0
- package/dist/react.cjs +7 -7
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +13 -14
- package/dist/react.d.ts +13 -14
- package/dist/react.d.ts.map +1 -0
- package/dist/react.mjs +7 -7
- package/dist/react.mjs.map +1 -1
- package/dist/scrub.d.ts +20 -0
- package/dist/scrub.d.ts.map +1 -0
- package/dist/sentry.d.ts +62 -0
- package/dist/sentry.d.ts.map +1 -0
- package/dist/stack.d.ts +25 -0
- package/dist/stack.d.ts.map +1 -0
- package/dist/storage.d.ts +32 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/{core-CIhI2R7S.d.cts → types.d.ts} +21 -119
- package/dist/types.d.ts.map +1 -0
- package/dist/uid.d.ts +10 -0
- package/dist/uid.d.ts.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/package.json +11 -11
- package/src/anon.d.ts +1 -1
- package/src/anon.js +3 -3
- package/src/anon.test.ts +3 -3
- package/src/core.test.ts +6 -6
- package/src/core.ts +11 -11
- package/src/events.ts +1 -1
- package/src/exception.ts +1 -1
- package/src/stack.ts +1 -1
- package/src/types.ts +3 -3
- package/src/uid.ts +2 -2
- package/src/version.ts +1 -1
- package/dist/core-CIhI2R7S.d.ts +0 -380
package/dist/index.d.cts
CHANGED
|
@@ -1,292 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
export {
|
|
3
|
-
|
|
4
|
-
export {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* framesFromStack parses a browser Error.stack into Sentry frames, OLDEST-FIRST
|
|
24
|
-
* (Sentry orders caller->callee; the crash site is last — matching the server's
|
|
25
|
-
* pickCrashFrame). Handles both V8 ("at fn (file:li:co)") and
|
|
26
|
-
* Firefox/Safari ("fn@file:li:co"). Unparseable lines are skipped.
|
|
27
|
-
*/
|
|
28
|
-
declare function framesFromStack(stack: string | undefined): SentryFrame[];
|
|
29
|
-
/** Identity carried onto every error event — the SAME ids analytics uses. */
|
|
30
|
-
interface ErrorIdentity {
|
|
31
|
-
/** OIDC sub (post-identify) or anon id. NEVER email/PII. */
|
|
32
|
-
userId?: string;
|
|
33
|
-
sessionId?: string;
|
|
34
|
-
product?: string;
|
|
35
|
-
release?: string;
|
|
36
|
-
environment?: string;
|
|
37
|
-
}
|
|
38
|
-
interface BuildEventInput {
|
|
39
|
-
error: unknown;
|
|
40
|
-
options?: CaptureErrorOptions;
|
|
41
|
-
identity: ErrorIdentity;
|
|
42
|
-
capturePII?: boolean;
|
|
43
|
-
/** Injectable for deterministic tests. */
|
|
44
|
-
now?: number;
|
|
45
|
-
id?: string;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* buildSentryEvent turns a throwable + identity into a Sentry `event`. The message
|
|
49
|
-
* (the leak surface) is scrubbed client-side; the user is ONLY the stable subject
|
|
50
|
-
* id — never email/username/ip. Level defaults to error, or fatal for uncaught.
|
|
51
|
-
*/
|
|
52
|
-
declare function buildSentryEvent(input: BuildEventInput): SentryEvent;
|
|
53
|
-
/**
|
|
54
|
-
* buildEnvelope frames a Sentry event into a newline-delimited envelope:
|
|
55
|
-
*
|
|
56
|
-
* {"event_id","dsn","sent_at"}\n
|
|
57
|
-
* {"type":"event","content_type":"application/json","length":N}\n
|
|
58
|
-
* <event json>\n
|
|
59
|
-
*
|
|
60
|
-
* The item is length-delimited (N = UTF-8 byte length) — the framing the server's
|
|
61
|
-
* parseEnvelope reads first (falling back to newline-delimited otherwise).
|
|
62
|
-
*/
|
|
63
|
-
declare function buildEnvelope(event: SentryEvent, dsn: Dsn, sentAt?: string): string;
|
|
64
|
-
|
|
65
|
-
// Types for anon.js, which is hand-written ES5 rather than TypeScript because
|
|
66
|
-
// The door's hosted tag inlines it VERBATIM and has no compiler.
|
|
67
|
-
// The declarations are here so the bundled client still imports it typed.
|
|
68
|
-
|
|
69
|
-
/** Mints a time-ordered UUIDv7 (RFC 9562 §5.7) for `now` in epoch milliseconds. */
|
|
70
|
-
declare function hzUuidv7(now?: number): string
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* uuidv7 mints a time-ordered UUIDv7 for `now` (epoch milliseconds).
|
|
74
|
-
*
|
|
75
|
-
* Two ids minted in the same millisecond sort arbitrarily between themselves; ids
|
|
76
|
-
* from different milliseconds sort by time, lexically and numerically alike.
|
|
77
|
-
*/
|
|
78
|
-
|
|
79
|
-
/** The millisecond timestamp a v7 id was minted at — the inverse of uuidv7. */
|
|
80
|
-
declare function uuidv7Time(id: string): number;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* The product → Sentinel DSN registry.
|
|
84
|
-
*
|
|
85
|
-
* An app declares WHAT it is (`product: 'console'`); this module knows WHERE its
|
|
86
|
-
* errors go. That split is the whole point: no surface has to learn a DSN, carry
|
|
87
|
-
* a build argument, or grow a config file to report errors — declaring the
|
|
88
|
-
* product it already declares is enough.
|
|
89
|
-
*
|
|
90
|
-
* A Sentinel DSN is PUBLIC by construction. It ships inside the client bundle and
|
|
91
|
-
* is readable in devtools on any deployed page, and it grants exactly one
|
|
92
|
-
* capability: submitting new events. It cannot read issues, projects, or any
|
|
93
|
-
* other data. So committing it is not leaking a secret — it is recording a public
|
|
94
|
-
* identifier next to the code that needs it. (Contrast the server-side collector
|
|
95
|
-
* DSN in the `team-analytics-sentry` Secret, which is HMAC-derived and revocable
|
|
96
|
-
* precisely because a server-side credential is NOT public.)
|
|
97
|
-
*
|
|
98
|
-
* Why a literal map instead of deriving `hanzo-${product}`: the projects predate
|
|
99
|
-
* this registry and do not derive cleanly — `site` lives in `hanzo-ai`, not
|
|
100
|
-
* `hanzo-site`. An explicit map is honest about that; a derivation rule plus an
|
|
101
|
-
* exception table is the same data with a trap in it.
|
|
102
|
-
*
|
|
103
|
-
* Projects are org-scoped and named `<org>-<app>`. To add one: create the project
|
|
104
|
-
* (POST /v1/sentinel/projects with X-Org-Id), then add its `dsn` here keyed by the
|
|
105
|
-
* product name the app passes to `createAnalytics`.
|
|
106
|
-
*/
|
|
107
|
-
/** PRODUCT_PROJECT maps a `product` to its Sentinel project id. The DSN's KEY is no
|
|
108
|
-
* longer a per-project secret — it is the ONE org publishable key (below), so a
|
|
109
|
-
* surface's errors ride the SAME key its events do. The id only names WHICH
|
|
110
|
-
* project the errors group under, and cloud auto-provisions that project on first
|
|
111
|
-
* keyed ingest, so a new id needs nothing minted. `site` lives in the `hanzo-ai`
|
|
112
|
-
* project — an explicit map, because the projects predate this and do not derive
|
|
113
|
-
* cleanly from the product name. */
|
|
114
|
-
declare const PRODUCT_PROJECT: Readonly<Record<string, string>>;
|
|
115
|
-
/** dsnForProduct builds the product's Sentinel DSN from the caller's resolved
|
|
116
|
-
* publishable `key` and the product's project — the SAME key the event stream
|
|
117
|
-
* carries, at the product's envelope endpoint. Returns undefined when there is no
|
|
118
|
-
* key or no project for the product, leaving the error plane inert rather than
|
|
119
|
-
* posting into the wrong one. The key is NOT baked here: it is the value the
|
|
120
|
-
* surface resolved (an explicit `ingestKey`, or the KMS-sourced
|
|
121
|
-
* NEXT_PUBLIC_PUBLISHABLE_KEY the build inlines) — the ONE live source, never a
|
|
122
|
-
* literal committed beside the code. */
|
|
123
|
-
declare function dsnForProduct(product: string | undefined, key: string | undefined): string | undefined;
|
|
124
|
-
|
|
125
|
-
/** redactSecrets removes known secret shapes. Always applied. */
|
|
126
|
-
declare function redactSecrets(s: string): string;
|
|
127
|
-
/** scrubPII masks emails and IPs. Applied unless PII capture is enabled. */
|
|
128
|
-
declare function scrubPII(s: string): string;
|
|
129
|
-
/** scrubText applies the redaction policy to a free-text field. Input is capped
|
|
130
|
-
* first: unbounded text is a denial-of-service surface, not just a size problem. */
|
|
131
|
-
declare function scrubText(s: string | undefined, capturePII?: boolean): string;
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* digest is a stable 32-hex-char (128-bit) content hash, computed synchronously.
|
|
135
|
-
*
|
|
136
|
-
* Grouping keys are needed on the capture path, which is synchronous and may be
|
|
137
|
-
* running inside an unload handler — SubtleCrypto is async and unavailable on
|
|
138
|
-
* insecure origins, so it cannot be used here. This is FNV-1a run over four seeds
|
|
139
|
-
* and concatenated. It is a GROUPING key, never a security boundary: it is not
|
|
140
|
-
* collision-resistant against a chosen-input adversary, and nothing authorizes or
|
|
141
|
-
* authenticates on it. The product treats the value as opaque.
|
|
142
|
-
*/
|
|
143
|
-
declare function digest(s: string): string;
|
|
144
|
-
/**
|
|
145
|
-
* exceptionEntry builds the single `$exception_list` entry for a throwable.
|
|
146
|
-
*
|
|
147
|
-
* One entry, not a chain: `Error.cause` chaining is a distinct fact with its own
|
|
148
|
-
* ordering rules, and emitting it wrongly is worse than not emitting it.
|
|
149
|
-
*/
|
|
150
|
-
declare function exceptionEntry(err: unknown, opts: {
|
|
151
|
-
handled: boolean;
|
|
152
|
-
id: string;
|
|
153
|
-
}): ExceptionEntry;
|
|
154
|
-
/**
|
|
155
|
-
* fingerprint is the issue grouping key.
|
|
156
|
-
*
|
|
157
|
-
* Keyed on exception type plus each in-app frame's function and source — the same
|
|
158
|
-
* pieces the server-side grouper records ("Exception Type", "Resolved function
|
|
159
|
-
* name", "Source file name"). Deliberately NOT the message: `Loading chunk 3324
|
|
160
|
-
* failed` and `Loading chunk 998 failed` are one bug, and grouping on message is
|
|
161
|
-
* precisely the mistake that made every distinct error string its own event name.
|
|
162
|
-
*
|
|
163
|
-
* Falls back to the type alone when no in-app frame survived, which keeps
|
|
164
|
-
* stackless errors (`Script error.`, cross-origin) in one issue instead of
|
|
165
|
-
* scattering them.
|
|
166
|
-
*/
|
|
167
|
-
declare function fingerprint(entry: ExceptionEntry): string;
|
|
168
|
-
/**
|
|
169
|
-
* exceptionProperties builds the full `$exception_*` property bag for one captured
|
|
170
|
-
* throwable — everything Error Tracking reads off the event.
|
|
171
|
-
*
|
|
172
|
-
* The denormalized arrays are ordered like `frames`: last element is the throw
|
|
173
|
-
* site, which is the element the issue list indexes at -1 for its source/function
|
|
174
|
-
* columns.
|
|
175
|
-
*/
|
|
176
|
-
declare function exceptionProperties(err: unknown, opts: {
|
|
177
|
-
handled: boolean;
|
|
178
|
-
id: string;
|
|
179
|
-
level?: SentryLevel;
|
|
180
|
-
}): ExceptionProperties;
|
|
181
|
-
|
|
182
|
-
interface GoalDef {
|
|
183
|
-
/** Human label shown in Insights. */
|
|
184
|
-
label: string;
|
|
185
|
-
/** The event whose occurrence counts as the goal conversion. */
|
|
186
|
-
event: string;
|
|
187
|
-
/** The funnel leading to the goal — an id into FUNNELS (see funnels.ts). */
|
|
188
|
-
funnelId?: FunnelId;
|
|
189
|
-
/** The ordered event names of `funnelId`, derived — never hand-written. */
|
|
190
|
-
funnel?: string[];
|
|
191
|
-
/** Optional property equality filter that qualifies the conversion. */
|
|
192
|
-
filter?: {
|
|
193
|
-
property: string;
|
|
194
|
-
equals: string;
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
declare const GOALS: Record<'signup' | 'sale' | 'upgradeIntent' | 'activation', GoalDef>;
|
|
198
|
-
interface CohortDef {
|
|
199
|
-
/** The hanzo.events column the cohort dimension maps to. */
|
|
200
|
-
field: string;
|
|
201
|
-
label: string;
|
|
202
|
-
}
|
|
203
|
-
declare const COHORTS: Record<'signupWeek' | 'channel' | 'refCode', CohortDef>;
|
|
204
|
-
|
|
205
|
-
/** parseAttribution reads UTM params + ref/refCode from a query string and pairs
|
|
206
|
-
* them with the referrer. `search` is a location.search value ("?utm_source=…"). */
|
|
207
|
-
declare function parseAttribution(search: string, referrer: string): Attribution;
|
|
208
|
-
/** deriveChannel classifies the visit: paid | referral | social | organic | direct. */
|
|
209
|
-
declare function deriveChannel(a: Attribution): string;
|
|
210
|
-
/** hostOf extracts a bare lowercase host from a URL; "" when unparseable. */
|
|
211
|
-
declare function hostOf(raw?: string): string;
|
|
212
|
-
/** hasAttribution reports whether anything was captured (so we don't persist an
|
|
213
|
-
* empty first-touch that would shadow a later real one). */
|
|
214
|
-
declare function hasAttribution(a: Attribution): boolean;
|
|
215
|
-
/** isoWeek returns the ISO-8601 week label, e.g. "2026-W28". */
|
|
216
|
-
declare function isoWeek(d: Date): string;
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Which org owns a host's telemetry.
|
|
220
|
-
*
|
|
221
|
-
* A surface should not have to LEARN its key. It already knows the domain it is
|
|
222
|
-
* served from, and a domain belongs to exactly one brand, so the key is derivable
|
|
223
|
-
* and asking a surface to carry one is asking it to restate something already
|
|
224
|
-
* true. This module answers `orgOf(location.hostname)` and hands back that org's
|
|
225
|
-
* publishable key, which is what lets a static export report correctly while
|
|
226
|
-
* configuring nothing.
|
|
227
|
-
*
|
|
228
|
-
* This is the SAME question `hanzo.id` answers at runtime for the identity hosts
|
|
229
|
-
* (`pkgs/shared/src/ingest.ts`): one image serves every brand, so the key cannot
|
|
230
|
-
* be a property of the build. It was, and the bill is on record — Lux's, Zoo's,
|
|
231
|
-
* Osage's and Pars' visitors were all filed in HANZO's project, because one
|
|
232
|
-
* build-time key was inlined for every brand. That is the white-label boundary
|
|
233
|
-
* crossed in the direction that shows up latest. The marketing sites carry the
|
|
234
|
-
* identical defect in its other form: each commits its OWN key literal, so the
|
|
235
|
-
* fleet holds N copies of a value with one source, and a site added tomorrow
|
|
236
|
-
* reports nothing until somebody remembers to paste one in.
|
|
237
|
-
*
|
|
238
|
-
* A `pk-` is PUBLISHABLE: it authorizes a write into one org and mints no reading
|
|
239
|
-
* principal. It is readable in devtools on every deployed page that sends an
|
|
240
|
-
* event, so stating it here exposes nothing that serving the page did not. What
|
|
241
|
-
* it does buy is that correct attribution becomes the DEFAULT rather than
|
|
242
|
-
* something each surface opts into.
|
|
243
|
-
*
|
|
244
|
-
* Keyed by ORG, never by host: a brand's facts repeat on every alias it owns
|
|
245
|
-
* (`lux.id`, `id.lux.network`), and a key stated per host would have to be
|
|
246
|
-
* repeated too — so the day someone adds an alias and forgets the key, that host
|
|
247
|
-
* silently stops reporting. Stated once per org, a new alias inherits its brand's
|
|
248
|
-
* key by naming its brand's domain, which is all an alias ever says.
|
|
249
|
-
*/
|
|
250
|
-
/** One publishable key per org. */
|
|
251
|
-
type Keyring = Readonly<Record<string, string>>;
|
|
252
|
-
/**
|
|
253
|
-
* Each org's registrable domains. A host matches a domain when it IS that domain
|
|
254
|
-
* or a subdomain of it, so `explore.lux.network` needs no entry of its own.
|
|
255
|
-
*
|
|
256
|
-
* An org absent from this table resolves to undefined, and that is deliberate:
|
|
257
|
-
* `osage`, `pars` and `bootnode` have no project of their own yet, and reporting
|
|
258
|
-
* NOTHING is the honest answer. Filing them under a brand that is not theirs is
|
|
259
|
-
* the defect this module exists to prevent — it is silent, it reads as working,
|
|
260
|
-
* and it is only visible later in someone else's warehouse.
|
|
261
|
-
*/
|
|
262
|
-
declare const ORG_DOMAIN: Readonly<Record<string, readonly string[]>>;
|
|
263
|
-
/**
|
|
264
|
-
* Each org's publishable key — the same values `universe`'s `SPA_INGEST_KEYRING`
|
|
265
|
-
* serves to the identity hosts, which are each brand's own insights team token.
|
|
266
|
-
* Add an org here the day its project exists, never before.
|
|
267
|
-
*/
|
|
268
|
-
declare const ORG_KEY: Keyring;
|
|
269
|
-
/**
|
|
270
|
-
* The org that owns a host, or undefined when no brand claims it.
|
|
271
|
-
*
|
|
272
|
-
* Longest match wins, so a brand owning both a domain and a subdomain of another
|
|
273
|
-
* brand's cannot be decided by table order. Pure and total.
|
|
274
|
-
*/
|
|
275
|
-
declare function orgOf(host: string): string | undefined;
|
|
276
|
-
/**
|
|
277
|
-
* The publishable key for a host, or undefined when there is not exactly one to
|
|
278
|
-
* give.
|
|
279
|
-
*
|
|
280
|
-
* Deliberately WITHOUT a fallback: returning Hanzo's key for an unrecognised host
|
|
281
|
-
* is precisely the defect described above. Undefined is the honest answer and the
|
|
282
|
-
* caller reports nothing.
|
|
283
|
-
*
|
|
284
|
-
* `keyring` is a parameter so the identity runtime — which receives its keyring
|
|
285
|
-
* from `/config.json` because one image serves every brand — resolves through
|
|
286
|
-
* this SAME function rather than a second copy of it.
|
|
287
|
-
*/
|
|
288
|
-
declare function keyFor(host: string, keyring?: Keyring): string | undefined;
|
|
289
|
-
/** The key for the page this code is running on; undefined off a browser. */
|
|
290
|
-
declare function keyForPage(keyring?: Keyring): string | undefined;
|
|
291
|
-
|
|
292
|
-
export { Attribution, COHORTS, CaptureErrorOptions, Cohort, type CohortDef, Dsn, type ErrorIdentity, ExceptionEntry, ExceptionProperties, GOALS, type GoalDef, type Keyring, ORG_DOMAIN, ORG_KEY, PRODUCT_PROJECT, SentryEvent, SentryFrame, SentryLevel, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, digest, dsnForProduct, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, keyFor, keyForPage, orgOf, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
|
|
1
|
+
export { Analytics, createAnalytics, VERSION, getCohort, getFirstTouch } from './core';
|
|
2
|
+
export { parseDsn, buildSentryEvent, buildEnvelope, framesFromStack } from './sentry';
|
|
3
|
+
export { uuidv7, uuidv7Time } from './uid';
|
|
4
|
+
export { PRODUCT_PROJECT, dsnForProduct } from './dsn';
|
|
5
|
+
export type { ErrorIdentity } from './sentry';
|
|
6
|
+
export { scrubText, redactSecrets, scrubPII } from './scrub';
|
|
7
|
+
export { EVENTS, EXCEPTION, PAGEVIEW } from './events';
|
|
8
|
+
export { exceptionEntry, exceptionProperties, fingerprint, digest } from './exception';
|
|
9
|
+
export type { EventName } from './events';
|
|
10
|
+
export { GOALS, COHORTS } from './goals';
|
|
11
|
+
export type { GoalDef, CohortDef } from './goals';
|
|
12
|
+
export { FUNNELS, PRODUCTS, eventsOf } from './funnels';
|
|
13
|
+
export type { FunnelDef, FunnelStep, FunnelId, ProductId } from './funnels';
|
|
14
|
+
export { parseAttribution, deriveChannel, hasAttribution, hostOf, isoWeek, } from './attribution';
|
|
15
|
+
export type { AnalyticsConfig, Attribution, CaptureErrorOptions, Cohort, Dsn, EventKind, Exception, ExceptionEntry, ExceptionFrame, ExceptionProperties, SentryEvent, SentryFrame, SentryLevel, Transport, WireEvent, } from './types';
|
|
16
|
+
/** Which org owns a host's telemetry — the resolution that lets a surface report
|
|
17
|
+
* correctly while configuring nothing. `keyFor` takes an optional keyring so a
|
|
18
|
+
* runtime that receives one (hanzo.id serves every brand from one image) resolves
|
|
19
|
+
* through this same function rather than a second copy of it. */
|
|
20
|
+
export { ORG_DOMAIN, ORG_KEY, orgOf, keyFor, keyForPage } from './org';
|
|
21
|
+
export type { Keyring } from './org';
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,292 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
export {
|
|
3
|
-
|
|
4
|
-
export {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* framesFromStack parses a browser Error.stack into Sentry frames, OLDEST-FIRST
|
|
24
|
-
* (Sentry orders caller->callee; the crash site is last — matching the server's
|
|
25
|
-
* pickCrashFrame). Handles both V8 ("at fn (file:li:co)") and
|
|
26
|
-
* Firefox/Safari ("fn@file:li:co"). Unparseable lines are skipped.
|
|
27
|
-
*/
|
|
28
|
-
declare function framesFromStack(stack: string | undefined): SentryFrame[];
|
|
29
|
-
/** Identity carried onto every error event — the SAME ids analytics uses. */
|
|
30
|
-
interface ErrorIdentity {
|
|
31
|
-
/** OIDC sub (post-identify) or anon id. NEVER email/PII. */
|
|
32
|
-
userId?: string;
|
|
33
|
-
sessionId?: string;
|
|
34
|
-
product?: string;
|
|
35
|
-
release?: string;
|
|
36
|
-
environment?: string;
|
|
37
|
-
}
|
|
38
|
-
interface BuildEventInput {
|
|
39
|
-
error: unknown;
|
|
40
|
-
options?: CaptureErrorOptions;
|
|
41
|
-
identity: ErrorIdentity;
|
|
42
|
-
capturePII?: boolean;
|
|
43
|
-
/** Injectable for deterministic tests. */
|
|
44
|
-
now?: number;
|
|
45
|
-
id?: string;
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* buildSentryEvent turns a throwable + identity into a Sentry `event`. The message
|
|
49
|
-
* (the leak surface) is scrubbed client-side; the user is ONLY the stable subject
|
|
50
|
-
* id — never email/username/ip. Level defaults to error, or fatal for uncaught.
|
|
51
|
-
*/
|
|
52
|
-
declare function buildSentryEvent(input: BuildEventInput): SentryEvent;
|
|
53
|
-
/**
|
|
54
|
-
* buildEnvelope frames a Sentry event into a newline-delimited envelope:
|
|
55
|
-
*
|
|
56
|
-
* {"event_id","dsn","sent_at"}\n
|
|
57
|
-
* {"type":"event","content_type":"application/json","length":N}\n
|
|
58
|
-
* <event json>\n
|
|
59
|
-
*
|
|
60
|
-
* The item is length-delimited (N = UTF-8 byte length) — the framing the server's
|
|
61
|
-
* parseEnvelope reads first (falling back to newline-delimited otherwise).
|
|
62
|
-
*/
|
|
63
|
-
declare function buildEnvelope(event: SentryEvent, dsn: Dsn, sentAt?: string): string;
|
|
64
|
-
|
|
65
|
-
// Types for anon.js, which is hand-written ES5 rather than TypeScript because
|
|
66
|
-
// The door's hosted tag inlines it VERBATIM and has no compiler.
|
|
67
|
-
// The declarations are here so the bundled client still imports it typed.
|
|
68
|
-
|
|
69
|
-
/** Mints a time-ordered UUIDv7 (RFC 9562 §5.7) for `now` in epoch milliseconds. */
|
|
70
|
-
declare function hzUuidv7(now?: number): string
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* uuidv7 mints a time-ordered UUIDv7 for `now` (epoch milliseconds).
|
|
74
|
-
*
|
|
75
|
-
* Two ids minted in the same millisecond sort arbitrarily between themselves; ids
|
|
76
|
-
* from different milliseconds sort by time, lexically and numerically alike.
|
|
77
|
-
*/
|
|
78
|
-
|
|
79
|
-
/** The millisecond timestamp a v7 id was minted at — the inverse of uuidv7. */
|
|
80
|
-
declare function uuidv7Time(id: string): number;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* The product → Sentinel DSN registry.
|
|
84
|
-
*
|
|
85
|
-
* An app declares WHAT it is (`product: 'console'`); this module knows WHERE its
|
|
86
|
-
* errors go. That split is the whole point: no surface has to learn a DSN, carry
|
|
87
|
-
* a build argument, or grow a config file to report errors — declaring the
|
|
88
|
-
* product it already declares is enough.
|
|
89
|
-
*
|
|
90
|
-
* A Sentinel DSN is PUBLIC by construction. It ships inside the client bundle and
|
|
91
|
-
* is readable in devtools on any deployed page, and it grants exactly one
|
|
92
|
-
* capability: submitting new events. It cannot read issues, projects, or any
|
|
93
|
-
* other data. So committing it is not leaking a secret — it is recording a public
|
|
94
|
-
* identifier next to the code that needs it. (Contrast the server-side collector
|
|
95
|
-
* DSN in the `team-analytics-sentry` Secret, which is HMAC-derived and revocable
|
|
96
|
-
* precisely because a server-side credential is NOT public.)
|
|
97
|
-
*
|
|
98
|
-
* Why a literal map instead of deriving `hanzo-${product}`: the projects predate
|
|
99
|
-
* this registry and do not derive cleanly — `site` lives in `hanzo-ai`, not
|
|
100
|
-
* `hanzo-site`. An explicit map is honest about that; a derivation rule plus an
|
|
101
|
-
* exception table is the same data with a trap in it.
|
|
102
|
-
*
|
|
103
|
-
* Projects are org-scoped and named `<org>-<app>`. To add one: create the project
|
|
104
|
-
* (POST /v1/sentinel/projects with X-Org-Id), then add its `dsn` here keyed by the
|
|
105
|
-
* product name the app passes to `createAnalytics`.
|
|
106
|
-
*/
|
|
107
|
-
/** PRODUCT_PROJECT maps a `product` to its Sentinel project id. The DSN's KEY is no
|
|
108
|
-
* longer a per-project secret — it is the ONE org publishable key (below), so a
|
|
109
|
-
* surface's errors ride the SAME key its events do. The id only names WHICH
|
|
110
|
-
* project the errors group under, and cloud auto-provisions that project on first
|
|
111
|
-
* keyed ingest, so a new id needs nothing minted. `site` lives in the `hanzo-ai`
|
|
112
|
-
* project — an explicit map, because the projects predate this and do not derive
|
|
113
|
-
* cleanly from the product name. */
|
|
114
|
-
declare const PRODUCT_PROJECT: Readonly<Record<string, string>>;
|
|
115
|
-
/** dsnForProduct builds the product's Sentinel DSN from the caller's resolved
|
|
116
|
-
* publishable `key` and the product's project — the SAME key the event stream
|
|
117
|
-
* carries, at the product's envelope endpoint. Returns undefined when there is no
|
|
118
|
-
* key or no project for the product, leaving the error plane inert rather than
|
|
119
|
-
* posting into the wrong one. The key is NOT baked here: it is the value the
|
|
120
|
-
* surface resolved (an explicit `ingestKey`, or the KMS-sourced
|
|
121
|
-
* NEXT_PUBLIC_PUBLISHABLE_KEY the build inlines) — the ONE live source, never a
|
|
122
|
-
* literal committed beside the code. */
|
|
123
|
-
declare function dsnForProduct(product: string | undefined, key: string | undefined): string | undefined;
|
|
124
|
-
|
|
125
|
-
/** redactSecrets removes known secret shapes. Always applied. */
|
|
126
|
-
declare function redactSecrets(s: string): string;
|
|
127
|
-
/** scrubPII masks emails and IPs. Applied unless PII capture is enabled. */
|
|
128
|
-
declare function scrubPII(s: string): string;
|
|
129
|
-
/** scrubText applies the redaction policy to a free-text field. Input is capped
|
|
130
|
-
* first: unbounded text is a denial-of-service surface, not just a size problem. */
|
|
131
|
-
declare function scrubText(s: string | undefined, capturePII?: boolean): string;
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* digest is a stable 32-hex-char (128-bit) content hash, computed synchronously.
|
|
135
|
-
*
|
|
136
|
-
* Grouping keys are needed on the capture path, which is synchronous and may be
|
|
137
|
-
* running inside an unload handler — SubtleCrypto is async and unavailable on
|
|
138
|
-
* insecure origins, so it cannot be used here. This is FNV-1a run over four seeds
|
|
139
|
-
* and concatenated. It is a GROUPING key, never a security boundary: it is not
|
|
140
|
-
* collision-resistant against a chosen-input adversary, and nothing authorizes or
|
|
141
|
-
* authenticates on it. The product treats the value as opaque.
|
|
142
|
-
*/
|
|
143
|
-
declare function digest(s: string): string;
|
|
144
|
-
/**
|
|
145
|
-
* exceptionEntry builds the single `$exception_list` entry for a throwable.
|
|
146
|
-
*
|
|
147
|
-
* One entry, not a chain: `Error.cause` chaining is a distinct fact with its own
|
|
148
|
-
* ordering rules, and emitting it wrongly is worse than not emitting it.
|
|
149
|
-
*/
|
|
150
|
-
declare function exceptionEntry(err: unknown, opts: {
|
|
151
|
-
handled: boolean;
|
|
152
|
-
id: string;
|
|
153
|
-
}): ExceptionEntry;
|
|
154
|
-
/**
|
|
155
|
-
* fingerprint is the issue grouping key.
|
|
156
|
-
*
|
|
157
|
-
* Keyed on exception type plus each in-app frame's function and source — the same
|
|
158
|
-
* pieces the server-side grouper records ("Exception Type", "Resolved function
|
|
159
|
-
* name", "Source file name"). Deliberately NOT the message: `Loading chunk 3324
|
|
160
|
-
* failed` and `Loading chunk 998 failed` are one bug, and grouping on message is
|
|
161
|
-
* precisely the mistake that made every distinct error string its own event name.
|
|
162
|
-
*
|
|
163
|
-
* Falls back to the type alone when no in-app frame survived, which keeps
|
|
164
|
-
* stackless errors (`Script error.`, cross-origin) in one issue instead of
|
|
165
|
-
* scattering them.
|
|
166
|
-
*/
|
|
167
|
-
declare function fingerprint(entry: ExceptionEntry): string;
|
|
168
|
-
/**
|
|
169
|
-
* exceptionProperties builds the full `$exception_*` property bag for one captured
|
|
170
|
-
* throwable — everything Error Tracking reads off the event.
|
|
171
|
-
*
|
|
172
|
-
* The denormalized arrays are ordered like `frames`: last element is the throw
|
|
173
|
-
* site, which is the element the issue list indexes at -1 for its source/function
|
|
174
|
-
* columns.
|
|
175
|
-
*/
|
|
176
|
-
declare function exceptionProperties(err: unknown, opts: {
|
|
177
|
-
handled: boolean;
|
|
178
|
-
id: string;
|
|
179
|
-
level?: SentryLevel;
|
|
180
|
-
}): ExceptionProperties;
|
|
181
|
-
|
|
182
|
-
interface GoalDef {
|
|
183
|
-
/** Human label shown in Insights. */
|
|
184
|
-
label: string;
|
|
185
|
-
/** The event whose occurrence counts as the goal conversion. */
|
|
186
|
-
event: string;
|
|
187
|
-
/** The funnel leading to the goal — an id into FUNNELS (see funnels.ts). */
|
|
188
|
-
funnelId?: FunnelId;
|
|
189
|
-
/** The ordered event names of `funnelId`, derived — never hand-written. */
|
|
190
|
-
funnel?: string[];
|
|
191
|
-
/** Optional property equality filter that qualifies the conversion. */
|
|
192
|
-
filter?: {
|
|
193
|
-
property: string;
|
|
194
|
-
equals: string;
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
declare const GOALS: Record<'signup' | 'sale' | 'upgradeIntent' | 'activation', GoalDef>;
|
|
198
|
-
interface CohortDef {
|
|
199
|
-
/** The hanzo.events column the cohort dimension maps to. */
|
|
200
|
-
field: string;
|
|
201
|
-
label: string;
|
|
202
|
-
}
|
|
203
|
-
declare const COHORTS: Record<'signupWeek' | 'channel' | 'refCode', CohortDef>;
|
|
204
|
-
|
|
205
|
-
/** parseAttribution reads UTM params + ref/refCode from a query string and pairs
|
|
206
|
-
* them with the referrer. `search` is a location.search value ("?utm_source=…"). */
|
|
207
|
-
declare function parseAttribution(search: string, referrer: string): Attribution;
|
|
208
|
-
/** deriveChannel classifies the visit: paid | referral | social | organic | direct. */
|
|
209
|
-
declare function deriveChannel(a: Attribution): string;
|
|
210
|
-
/** hostOf extracts a bare lowercase host from a URL; "" when unparseable. */
|
|
211
|
-
declare function hostOf(raw?: string): string;
|
|
212
|
-
/** hasAttribution reports whether anything was captured (so we don't persist an
|
|
213
|
-
* empty first-touch that would shadow a later real one). */
|
|
214
|
-
declare function hasAttribution(a: Attribution): boolean;
|
|
215
|
-
/** isoWeek returns the ISO-8601 week label, e.g. "2026-W28". */
|
|
216
|
-
declare function isoWeek(d: Date): string;
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Which org owns a host's telemetry.
|
|
220
|
-
*
|
|
221
|
-
* A surface should not have to LEARN its key. It already knows the domain it is
|
|
222
|
-
* served from, and a domain belongs to exactly one brand, so the key is derivable
|
|
223
|
-
* and asking a surface to carry one is asking it to restate something already
|
|
224
|
-
* true. This module answers `orgOf(location.hostname)` and hands back that org's
|
|
225
|
-
* publishable key, which is what lets a static export report correctly while
|
|
226
|
-
* configuring nothing.
|
|
227
|
-
*
|
|
228
|
-
* This is the SAME question `hanzo.id` answers at runtime for the identity hosts
|
|
229
|
-
* (`pkgs/shared/src/ingest.ts`): one image serves every brand, so the key cannot
|
|
230
|
-
* be a property of the build. It was, and the bill is on record — Lux's, Zoo's,
|
|
231
|
-
* Osage's and Pars' visitors were all filed in HANZO's project, because one
|
|
232
|
-
* build-time key was inlined for every brand. That is the white-label boundary
|
|
233
|
-
* crossed in the direction that shows up latest. The marketing sites carry the
|
|
234
|
-
* identical defect in its other form: each commits its OWN key literal, so the
|
|
235
|
-
* fleet holds N copies of a value with one source, and a site added tomorrow
|
|
236
|
-
* reports nothing until somebody remembers to paste one in.
|
|
237
|
-
*
|
|
238
|
-
* A `pk-` is PUBLISHABLE: it authorizes a write into one org and mints no reading
|
|
239
|
-
* principal. It is readable in devtools on every deployed page that sends an
|
|
240
|
-
* event, so stating it here exposes nothing that serving the page did not. What
|
|
241
|
-
* it does buy is that correct attribution becomes the DEFAULT rather than
|
|
242
|
-
* something each surface opts into.
|
|
243
|
-
*
|
|
244
|
-
* Keyed by ORG, never by host: a brand's facts repeat on every alias it owns
|
|
245
|
-
* (`lux.id`, `id.lux.network`), and a key stated per host would have to be
|
|
246
|
-
* repeated too — so the day someone adds an alias and forgets the key, that host
|
|
247
|
-
* silently stops reporting. Stated once per org, a new alias inherits its brand's
|
|
248
|
-
* key by naming its brand's domain, which is all an alias ever says.
|
|
249
|
-
*/
|
|
250
|
-
/** One publishable key per org. */
|
|
251
|
-
type Keyring = Readonly<Record<string, string>>;
|
|
252
|
-
/**
|
|
253
|
-
* Each org's registrable domains. A host matches a domain when it IS that domain
|
|
254
|
-
* or a subdomain of it, so `explore.lux.network` needs no entry of its own.
|
|
255
|
-
*
|
|
256
|
-
* An org absent from this table resolves to undefined, and that is deliberate:
|
|
257
|
-
* `osage`, `pars` and `bootnode` have no project of their own yet, and reporting
|
|
258
|
-
* NOTHING is the honest answer. Filing them under a brand that is not theirs is
|
|
259
|
-
* the defect this module exists to prevent — it is silent, it reads as working,
|
|
260
|
-
* and it is only visible later in someone else's warehouse.
|
|
261
|
-
*/
|
|
262
|
-
declare const ORG_DOMAIN: Readonly<Record<string, readonly string[]>>;
|
|
263
|
-
/**
|
|
264
|
-
* Each org's publishable key — the same values `universe`'s `SPA_INGEST_KEYRING`
|
|
265
|
-
* serves to the identity hosts, which are each brand's own insights team token.
|
|
266
|
-
* Add an org here the day its project exists, never before.
|
|
267
|
-
*/
|
|
268
|
-
declare const ORG_KEY: Keyring;
|
|
269
|
-
/**
|
|
270
|
-
* The org that owns a host, or undefined when no brand claims it.
|
|
271
|
-
*
|
|
272
|
-
* Longest match wins, so a brand owning both a domain and a subdomain of another
|
|
273
|
-
* brand's cannot be decided by table order. Pure and total.
|
|
274
|
-
*/
|
|
275
|
-
declare function orgOf(host: string): string | undefined;
|
|
276
|
-
/**
|
|
277
|
-
* The publishable key for a host, or undefined when there is not exactly one to
|
|
278
|
-
* give.
|
|
279
|
-
*
|
|
280
|
-
* Deliberately WITHOUT a fallback: returning Hanzo's key for an unrecognised host
|
|
281
|
-
* is precisely the defect described above. Undefined is the honest answer and the
|
|
282
|
-
* caller reports nothing.
|
|
283
|
-
*
|
|
284
|
-
* `keyring` is a parameter so the identity runtime — which receives its keyring
|
|
285
|
-
* from `/config.json` because one image serves every brand — resolves through
|
|
286
|
-
* this SAME function rather than a second copy of it.
|
|
287
|
-
*/
|
|
288
|
-
declare function keyFor(host: string, keyring?: Keyring): string | undefined;
|
|
289
|
-
/** The key for the page this code is running on; undefined off a browser. */
|
|
290
|
-
declare function keyForPage(keyring?: Keyring): string | undefined;
|
|
291
|
-
|
|
292
|
-
export { Attribution, COHORTS, CaptureErrorOptions, Cohort, type CohortDef, Dsn, type ErrorIdentity, ExceptionEntry, ExceptionProperties, GOALS, type GoalDef, type Keyring, ORG_DOMAIN, ORG_KEY, PRODUCT_PROJECT, SentryEvent, SentryFrame, SentryLevel, VERSION, buildEnvelope, buildSentryEvent, deriveChannel, digest, dsnForProduct, exceptionEntry, exceptionProperties, fingerprint, framesFromStack, getCohort, getFirstTouch, hasAttribution, hostOf, isoWeek, keyFor, keyForPage, orgOf, parseAttribution, parseDsn, redactSecrets, scrubPII, scrubText, hzUuidv7 as uuidv7, uuidv7Time };
|
|
1
|
+
export { Analytics, createAnalytics, VERSION, getCohort, getFirstTouch } from './core';
|
|
2
|
+
export { parseDsn, buildSentryEvent, buildEnvelope, framesFromStack } from './sentry';
|
|
3
|
+
export { uuidv7, uuidv7Time } from './uid';
|
|
4
|
+
export { PRODUCT_PROJECT, dsnForProduct } from './dsn';
|
|
5
|
+
export type { ErrorIdentity } from './sentry';
|
|
6
|
+
export { scrubText, redactSecrets, scrubPII } from './scrub';
|
|
7
|
+
export { EVENTS, EXCEPTION, PAGEVIEW } from './events';
|
|
8
|
+
export { exceptionEntry, exceptionProperties, fingerprint, digest } from './exception';
|
|
9
|
+
export type { EventName } from './events';
|
|
10
|
+
export { GOALS, COHORTS } from './goals';
|
|
11
|
+
export type { GoalDef, CohortDef } from './goals';
|
|
12
|
+
export { FUNNELS, PRODUCTS, eventsOf } from './funnels';
|
|
13
|
+
export type { FunnelDef, FunnelStep, FunnelId, ProductId } from './funnels';
|
|
14
|
+
export { parseAttribution, deriveChannel, hasAttribution, hostOf, isoWeek, } from './attribution';
|
|
15
|
+
export type { AnalyticsConfig, Attribution, CaptureErrorOptions, Cohort, Dsn, EventKind, Exception, ExceptionEntry, ExceptionFrame, ExceptionProperties, SentryEvent, SentryFrame, SentryLevel, Transport, WireEvent, } from './types';
|
|
16
|
+
/** Which org owns a host's telemetry — the resolution that lets a surface report
|
|
17
|
+
* correctly while configuring nothing. `keyFor` takes an optional keyring so a
|
|
18
|
+
* runtime that receives one (hanzo.id serves every brand from one image) resolves
|
|
19
|
+
* through this same function rather than a second copy of it. */
|
|
20
|
+
export { ORG_DOMAIN, ORG_KEY, orgOf, keyFor, keyForPage } from './org';
|
|
21
|
+
export type { Keyring } from './org';
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA;AACtF,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AACrF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAC1C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AACtD,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAC5D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AACtD,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACtF,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AACzC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACxC,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACjD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACvD,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAC3E,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,MAAM,EACN,OAAO,GACR,MAAM,eAAe,CAAA;AACtB,YAAY,EACV,eAAe,EACf,WAAW,EACX,mBAAmB,EACnB,MAAM,EACN,GAAG,EACH,SAAS,EACT,SAAS,EACT,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,WAAW,EACX,WAAW,EACX,WAAW,EACX,SAAS,EACT,SAAS,GACV,MAAM,SAAS,CAAA;AAEhB;;;kEAGkE;AAClE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AACtE,YAAY,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA"}
|