@hanzo/event 0.3.33 → 0.3.34
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/index.cjs +80 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -2
- package/dist/index.d.ts +76 -2
- package/dist/index.mjs +76 -11
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +75 -10
- package/dist/react.cjs.map +1 -1
- package/dist/react.mjs +75 -10
- package/dist/react.mjs.map +1 -1
- package/package.json +2 -2
- package/src/anon.d.ts +1 -1
- package/src/anon.js +29 -9
- package/src/core.ts +16 -6
- package/src/index.ts +7 -0
- package/src/org.test.ts +113 -0
- package/src/org.ts +129 -0
- package/src/storage.test.ts +1 -1
- package/src/version.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/event",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.34",
|
|
4
4
|
"description": "Hanzo Event — the ONE telemetry client. Emits pageview/event/identify/group to the Hanzo Cloud event stream (POST /v1/event), AND reports errors to Sentry as real Sentry envelopes — the error plane needs a DSN, without one nothing reaches Sentry. First-touch attribution, beacon-on-unload, auto error capture, client-side secret/PII scrubbing, a shared event + goal vocabulary. Subsumes @sentry.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"build": "tsup",
|
|
82
82
|
"dev": "tsup --watch",
|
|
83
83
|
"test": "vitest run",
|
|
84
|
-
"typecheck": "
|
|
84
|
+
"typecheck": "tsgo --noEmit -p tsconfig.json",
|
|
85
85
|
"clean": "rm -rf dist"
|
|
86
86
|
}
|
|
87
87
|
}
|
package/src/anon.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export declare function hzUuidv7(now?: number): string
|
|
|
8
8
|
/**
|
|
9
9
|
* The stable anonymous id for this browser, or '' during SSR.
|
|
10
10
|
*
|
|
11
|
-
* cookie · localStorage `
|
|
11
|
+
* cookie · localStorage `iam-anon-id` · adopted keys · in-memory · mint —
|
|
12
12
|
* every existing id is adopted, and only a browser holding none is given a new one.
|
|
13
13
|
*/
|
|
14
14
|
export declare function hzAnonId(): string
|
package/src/anon.js
CHANGED
|
@@ -22,14 +22,21 @@
|
|
|
22
22
|
|
|
23
23
|
/* ── BEGIN hz anon chain — copied VERBATIM into hanzoai/cloud ──────────────── */
|
|
24
24
|
|
|
25
|
-
/** The ONE anonymous-id key, on every surface and in every distribution.
|
|
26
|
-
|
|
25
|
+
/** The ONE anonymous-id key, on every surface and in every distribution.
|
|
26
|
+
*
|
|
27
|
+
* It names what it is, not who ships it: this value is visible to anyone who opens
|
|
28
|
+
* their own cookie jar, and a vendor's name has no business there. Identity is IAM's
|
|
29
|
+
* concern whether or not the visitor has said who they are. */
|
|
30
|
+
var HZ_ANON_KEY = 'iam-anon-id'
|
|
27
31
|
|
|
28
|
-
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
|
|
32
|
+
/** Keys an id may already be living under. READ, never written.
|
|
33
|
+
*
|
|
34
|
+
* Each was canonical once — `hz_anon_id` most recently, and `hz_id` before it, when a
|
|
35
|
+
* no-build tag wrote a second identity space and one page could hold two people.
|
|
36
|
+
* Adopting an id already in the wild is what makes this a rename rather than an
|
|
37
|
+
* erasure: minting over one detaches a returning visitor from their own history.
|
|
38
|
+
* They are never written, so they leave with the cookies that hold them. */
|
|
39
|
+
var HZ_ANON_ADOPT = ['hz_anon_id', 'hz_id']
|
|
33
40
|
|
|
34
41
|
/** The registrable domain the cookie is scoped to, so docs, cloud, console,
|
|
35
42
|
* studio, pay, id and www all read the ONE id. localStorage cannot do this: it is
|
|
@@ -165,7 +172,7 @@ function hzAnonWrite(name, value) {
|
|
|
165
172
|
* Resolution is strictly ADDITIVE — every id that already exists is ADOPTED, and
|
|
166
173
|
* only a browser holding none of them is given a new one:
|
|
167
174
|
*
|
|
168
|
-
* cookie · localStorage
|
|
175
|
+
* cookie · localStorage iam-anon-id · adopted keys · in-memory · mint
|
|
169
176
|
*
|
|
170
177
|
* Minting over an id resets a returning visitor and detaches them from their own
|
|
171
178
|
* history, so the order is the migration: the cookie is the shared home, the two
|
|
@@ -175,13 +182,26 @@ function hzAnonWrite(name, value) {
|
|
|
175
182
|
* localStorage keeps being written, so a rollback finds everyone where it left
|
|
176
183
|
* them, and a browser that refuses cookies still holds one id per origin.
|
|
177
184
|
*/
|
|
185
|
+
/**
|
|
186
|
+
* hzAnonAdopted returns an id already living under a key that used to be canonical,
|
|
187
|
+
* from the cookie jar first — that is the copy shared across subdomains, so a visitor
|
|
188
|
+
* who arrived on one host is the same person on the next.
|
|
189
|
+
*/
|
|
190
|
+
function hzAnonAdopted(s) {
|
|
191
|
+
for (var i = 0; i < HZ_ANON_ADOPT.length; i++) {
|
|
192
|
+
var id = hzAnonCookie(HZ_ANON_ADOPT[i]) || hzAnonItem(s, HZ_ANON_ADOPT[i])
|
|
193
|
+
if (id) return id
|
|
194
|
+
}
|
|
195
|
+
return ''
|
|
196
|
+
}
|
|
197
|
+
|
|
178
198
|
function hzAnonId() {
|
|
179
199
|
if (typeof window === 'undefined') return '' // SSR / prerender: no browser to identify
|
|
180
200
|
var s = hzAnonStore()
|
|
181
201
|
var id =
|
|
182
202
|
hzAnonCookie(HZ_ANON_KEY) ||
|
|
183
203
|
hzAnonItem(s, HZ_ANON_KEY) ||
|
|
184
|
-
|
|
204
|
+
hzAnonAdopted(s) ||
|
|
185
205
|
hzAnonMemo ||
|
|
186
206
|
hzUuidv7()
|
|
187
207
|
hzAnonMemo = id
|
package/src/core.ts
CHANGED
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
deriveChannel,
|
|
47
47
|
} from './attribution'
|
|
48
48
|
import { dsnForProduct } from './dsn'
|
|
49
|
+
import { keyForPage } from './org'
|
|
49
50
|
import { EXCEPTION, PAGEVIEW } from './events'
|
|
50
51
|
import { exceptionProperties } from './exception'
|
|
51
52
|
import { scrubText } from './scrub'
|
|
@@ -283,12 +284,21 @@ export class Analytics {
|
|
|
283
284
|
// carries end to end — KMS `deploy/PUBLISHABLE_KEY` -> the PUBLISHABLE_KEY
|
|
284
285
|
// build-arg -> the NEXT_PUBLIC_ prefix Next inlines.
|
|
285
286
|
//
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
|
|
287
|
+
// 3. the org that owns the HOST this page is served from (`org.ts`).
|
|
288
|
+
//
|
|
289
|
+
// Step 3 fires only when a surface configured NOTHING, so it never overrides
|
|
290
|
+
// the KMS chain above — it replaces going dark with reporting to the right
|
|
291
|
+
// brand. That is what a static export needs: `output: export` inlines no env,
|
|
292
|
+
// so before this the only way to report was to commit a key literal, and the
|
|
293
|
+
// fleet grew one copy per site of a value with a single source.
|
|
294
|
+
//
|
|
295
|
+
// A surface no brand claims still has no key, and the door REFUSES an
|
|
296
|
+
// unattributed event (401 `ingest_key_required`) — the reserved `$public`
|
|
297
|
+
// tenant that once caught keyless beacons is retired, and anonymous ingest is
|
|
298
|
+
// refused at every door on every brand host. So there is no quiet fallback to
|
|
299
|
+
// rely on: an event lands in the org a credential names, or it does not land.
|
|
300
|
+
ingestKey:
|
|
301
|
+
config.ingestKey ?? readEnv('NEXT_PUBLIC_PUBLISHABLE_KEY') ?? keyForPage(),
|
|
292
302
|
}
|
|
293
303
|
this.transport = config.transport ?? new DefaultTransport()
|
|
294
304
|
// Error plane, most specific source first: an explicit DSN wins, then the
|
package/src/index.ts
CHANGED
|
@@ -44,3 +44,10 @@ export type {
|
|
|
44
44
|
Transport,
|
|
45
45
|
WireEvent,
|
|
46
46
|
} from './types'
|
|
47
|
+
|
|
48
|
+
/** Which org owns a host's telemetry — the resolution that lets a surface report
|
|
49
|
+
* correctly while configuring nothing. `keyFor` takes an optional keyring so a
|
|
50
|
+
* runtime that receives one (hanzo.id serves every brand from one image) resolves
|
|
51
|
+
* through this same function rather than a second copy of it. */
|
|
52
|
+
export { ORG_DOMAIN, ORG_KEY, orgOf, keyFor, keyForPage } from './org'
|
|
53
|
+
export type { Keyring } from './org'
|
package/src/org.test.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { createAnalytics } from './core'
|
|
4
|
+
import { ORG_KEY, keyFor, keyForPage, orgOf } from './org'
|
|
5
|
+
|
|
6
|
+
const ENV = 'NEXT_PUBLIC_PUBLISHABLE_KEY'
|
|
7
|
+
|
|
8
|
+
/** Stand on a host the way a browser does, for the duration of one assertion. */
|
|
9
|
+
function onHost<T>(hostname: string, run: () => T): T {
|
|
10
|
+
const had = 'location' in globalThis
|
|
11
|
+
const prev = (globalThis as { location?: unknown }).location
|
|
12
|
+
Object.defineProperty(globalThis, 'location', {
|
|
13
|
+
value: { hostname },
|
|
14
|
+
configurable: true,
|
|
15
|
+
writable: true,
|
|
16
|
+
})
|
|
17
|
+
try {
|
|
18
|
+
return run()
|
|
19
|
+
} finally {
|
|
20
|
+
if (had) Object.defineProperty(globalThis, 'location', { value: prev, configurable: true, writable: true })
|
|
21
|
+
else delete (globalThis as { location?: unknown }).location
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
delete process.env[ENV]
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('the org that owns a host', () => {
|
|
30
|
+
it('claims a brand domain and every subdomain of it', () => {
|
|
31
|
+
expect(orgOf('hanzo.ai')).toBe('hanzo')
|
|
32
|
+
expect(orgOf('lux.network')).toBe('lux')
|
|
33
|
+
expect(orgOf('zoo.ngo')).toBe('zoo')
|
|
34
|
+
// an alias inherits by naming its brand's domain — no entry of its own
|
|
35
|
+
expect(orgOf('explore.lux.network')).toBe('lux')
|
|
36
|
+
expect(orgOf('id.zoo.network')).toBe('zoo')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('reads a hostname the way a domain compares', () => {
|
|
40
|
+
expect(orgOf('LUX.NETWORK')).toBe('lux')
|
|
41
|
+
expect(orgOf('lux.network:3000')).toBe('lux')
|
|
42
|
+
expect(orgOf('lux.network.')).toBe('lux')
|
|
43
|
+
expect(orgOf(' hanzo.ai ')).toBe('hanzo')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('claims nothing it does not own, rather than defaulting to Hanzo', () => {
|
|
47
|
+
// The defect this exists to prevent: an unrecognised host filed under a brand
|
|
48
|
+
// that is not its own is silent, reads as working, and surfaces a week later
|
|
49
|
+
// in someone else's warehouse.
|
|
50
|
+
for (const host of ['osage.id', 'pars.id', 'id.bootno.de', 'example.com', '']) {
|
|
51
|
+
expect(orgOf(host)).toBeUndefined()
|
|
52
|
+
expect(keyFor(host)).toBeUndefined()
|
|
53
|
+
}
|
|
54
|
+
// and it must not be fooled by a suffix that merely ENDS with a brand domain
|
|
55
|
+
expect(orgOf('notlux.network')).toBeUndefined()
|
|
56
|
+
expect(orgOf('evil-hanzo.ai')).toBeUndefined()
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe('the key a host resolves to', () => {
|
|
61
|
+
it('gives each brand its OWN key', () => {
|
|
62
|
+
const hanzo = keyFor('hanzo.ai')
|
|
63
|
+
const lux = keyFor('lux.network')
|
|
64
|
+
const zoo = keyFor('zoo.ngo')
|
|
65
|
+
for (const k of [hanzo, lux, zoo]) expect(k).toMatch(/^pk-/)
|
|
66
|
+
// The bug on record: one build-time key filed every brand's visitors into
|
|
67
|
+
// Hanzo's project. Three brands must resolve to three DIFFERENT keys.
|
|
68
|
+
expect(new Set([hanzo, lux, zoo]).size).toBe(3)
|
|
69
|
+
expect(lux).not.toBe(hanzo)
|
|
70
|
+
expect(zoo).not.toBe(hanzo)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('refuses anything that is not publishable, failing closed', () => {
|
|
74
|
+
expect(keyFor('hanzo.ai', { hanzo: 'sk-live-secret' })).toBeUndefined()
|
|
75
|
+
expect(keyFor('hanzo.ai', { hanzo: '' })).toBeUndefined()
|
|
76
|
+
expect(keyFor('hanzo.ai', {})).toBeUndefined()
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('resolves a runtime keyring through the same function, not a second copy', () => {
|
|
80
|
+
// hanzo.id serves every brand from ONE image and gets its keyring at runtime.
|
|
81
|
+
expect(keyFor('id.lux.network', { lux: 'pk-runtime-lux' })).toBe('pk-runtime-lux')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('is undefined off a browser, where there is no host to read', () => {
|
|
85
|
+
expect(keyForPage()).toBeUndefined()
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe('a surface that configures nothing', () => {
|
|
90
|
+
it('still reports, to its own brand', () => {
|
|
91
|
+
const lux = onHost('lux.network', () => createAnalytics({ product: 'site' }))
|
|
92
|
+
expect((lux as unknown as { cfg: { ingestKey?: string } }).cfg.ingestKey).toBe(ORG_KEY.lux)
|
|
93
|
+
|
|
94
|
+
const zoo = onHost('zoo.ngo', () => createAnalytics({ product: 'site' }))
|
|
95
|
+
expect((zoo as unknown as { cfg: { ingestKey?: string } }).cfg.ingestKey).toBe(ORG_KEY.zoo)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('never overrides what the surface or the build already resolved', () => {
|
|
99
|
+
const explicit = onHost('lux.network', () =>
|
|
100
|
+
createAnalytics({ product: 'site', ingestKey: 'pk-explicit' })
|
|
101
|
+
)
|
|
102
|
+
expect((explicit as unknown as { cfg: { ingestKey?: string } }).cfg.ingestKey).toBe('pk-explicit')
|
|
103
|
+
|
|
104
|
+
process.env[ENV] = 'pk-from-the-build'
|
|
105
|
+
const env = onHost('lux.network', () => createAnalytics({ product: 'site' }))
|
|
106
|
+
expect((env as unknown as { cfg: { ingestKey?: string } }).cfg.ingestKey).toBe('pk-from-the-build')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('has no key on a host no brand claims, and invents none', () => {
|
|
110
|
+
const none = onHost('example.com', () => createAnalytics({ product: 'site' }))
|
|
111
|
+
expect((none as unknown as { cfg: { ingestKey?: string } }).cfg.ingestKey).toBeUndefined()
|
|
112
|
+
})
|
|
113
|
+
})
|
package/src/org.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which org owns a host's telemetry.
|
|
3
|
+
*
|
|
4
|
+
* A surface should not have to LEARN its key. It already knows the domain it is
|
|
5
|
+
* served from, and a domain belongs to exactly one brand, so the key is derivable
|
|
6
|
+
* and asking a surface to carry one is asking it to restate something already
|
|
7
|
+
* true. This module answers `orgOf(location.hostname)` and hands back that org's
|
|
8
|
+
* publishable key, which is what lets a static export report correctly while
|
|
9
|
+
* configuring nothing.
|
|
10
|
+
*
|
|
11
|
+
* This is the SAME question `hanzo.id` answers at runtime for the identity hosts
|
|
12
|
+
* (`pkgs/shared/src/ingest.ts`): one image serves every brand, so the key cannot
|
|
13
|
+
* be a property of the build. It was, and the bill is on record — Lux's, Zoo's,
|
|
14
|
+
* Osage's and Pars' visitors were all filed in HANZO's project, because one
|
|
15
|
+
* build-time key was inlined for every brand. That is the white-label boundary
|
|
16
|
+
* crossed in the direction that shows up latest. The marketing sites carry the
|
|
17
|
+
* identical defect in its other form: each commits its OWN key literal, so the
|
|
18
|
+
* fleet holds N copies of a value with one source, and a site added tomorrow
|
|
19
|
+
* reports nothing until somebody remembers to paste one in.
|
|
20
|
+
*
|
|
21
|
+
* A `pk-` is PUBLISHABLE: it authorizes a write into one org and mints no reading
|
|
22
|
+
* principal. It is readable in devtools on every deployed page that sends an
|
|
23
|
+
* event, so stating it here exposes nothing that serving the page did not. What
|
|
24
|
+
* it does buy is that correct attribution becomes the DEFAULT rather than
|
|
25
|
+
* something each surface opts into.
|
|
26
|
+
*
|
|
27
|
+
* Keyed by ORG, never by host: a brand's facts repeat on every alias it owns
|
|
28
|
+
* (`lux.id`, `id.lux.network`), and a key stated per host would have to be
|
|
29
|
+
* repeated too — so the day someone adds an alias and forgets the key, that host
|
|
30
|
+
* silently stops reporting. Stated once per org, a new alias inherits its brand's
|
|
31
|
+
* key by naming its brand's domain, which is all an alias ever says.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** One publishable key per org. */
|
|
35
|
+
export type Keyring = Readonly<Record<string, string>>
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Each org's registrable domains. A host matches a domain when it IS that domain
|
|
39
|
+
* or a subdomain of it, so `explore.lux.network` needs no entry of its own.
|
|
40
|
+
*
|
|
41
|
+
* An org absent from this table resolves to undefined, and that is deliberate:
|
|
42
|
+
* `osage`, `pars` and `bootnode` have no project of their own yet, and reporting
|
|
43
|
+
* NOTHING is the honest answer. Filing them under a brand that is not theirs is
|
|
44
|
+
* the defect this module exists to prevent — it is silent, it reads as working,
|
|
45
|
+
* and it is only visible later in someone else's warehouse.
|
|
46
|
+
*/
|
|
47
|
+
export const ORG_DOMAIN: Readonly<Record<string, readonly string[]>> = Object.freeze({
|
|
48
|
+
hanzo: Object.freeze(['hanzo.ai', 'hanzo.app', 'hanzo.chat', 'hanzo.id', 'hanzo.bot', 'hanzo.sh']),
|
|
49
|
+
lux: Object.freeze([
|
|
50
|
+
'lux.network',
|
|
51
|
+
'lux.exchange',
|
|
52
|
+
'lux.market',
|
|
53
|
+
'lux.finance',
|
|
54
|
+
'lux.financial',
|
|
55
|
+
'lux.credit',
|
|
56
|
+
'lux.fund',
|
|
57
|
+
'lux.id',
|
|
58
|
+
]),
|
|
59
|
+
zoo: Object.freeze(['zoo.ngo', 'zoo.network', 'zoolabs.id']),
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Each org's publishable key — the same values `universe`'s `SPA_INGEST_KEYRING`
|
|
64
|
+
* serves to the identity hosts, which are each brand's own insights team token.
|
|
65
|
+
* Add an org here the day its project exists, never before.
|
|
66
|
+
*/
|
|
67
|
+
export const ORG_KEY: Keyring = Object.freeze({
|
|
68
|
+
hanzo: 'pk-rM_CdaF2MQckGCrla113SrR1oH4zvqN8xh2I95Z9tY8',
|
|
69
|
+
lux: 'pk-gUZp6ZVfhJzSwK-rb4oLbVkpCnMBx5uSCpxf_5yEhQk',
|
|
70
|
+
zoo: 'pk-3TKpKnERV9AQSsBUERWkZejC1O1mUxc1jRzsP3MPbs4',
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
/** `pk-` is publishable; `sk-` is not, and there is no third thing. Checked at the
|
|
74
|
+
* one point a key becomes something a page will send, so a mistyped entry fails
|
|
75
|
+
* closed rather than putting a secret in every visitor's tab. */
|
|
76
|
+
const PUBLISHABLE = 'pk-'
|
|
77
|
+
|
|
78
|
+
/** Strip the port and case a hostname the way a domain compares. */
|
|
79
|
+
const normalize = (host: string): string =>
|
|
80
|
+
host.trim().toLowerCase().replace(/\.$/, '').split(':')[0] ?? ''
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The org that owns a host, or undefined when no brand claims it.
|
|
84
|
+
*
|
|
85
|
+
* Longest match wins, so a brand owning both a domain and a subdomain of another
|
|
86
|
+
* brand's cannot be decided by table order. Pure and total.
|
|
87
|
+
*/
|
|
88
|
+
export function orgOf(host: string): string | undefined {
|
|
89
|
+
const h = normalize(host)
|
|
90
|
+
if (!h) return undefined
|
|
91
|
+
let best: string | undefined
|
|
92
|
+
let bestLen = 0
|
|
93
|
+
for (const [org, domains] of Object.entries(ORG_DOMAIN)) {
|
|
94
|
+
for (const d of domains) {
|
|
95
|
+
if ((h === d || h.endsWith(`.${d}`)) && d.length > bestLen) {
|
|
96
|
+
best = org
|
|
97
|
+
bestLen = d.length
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return best
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The publishable key for a host, or undefined when there is not exactly one to
|
|
106
|
+
* give.
|
|
107
|
+
*
|
|
108
|
+
* Deliberately WITHOUT a fallback: returning Hanzo's key for an unrecognised host
|
|
109
|
+
* is precisely the defect described above. Undefined is the honest answer and the
|
|
110
|
+
* caller reports nothing.
|
|
111
|
+
*
|
|
112
|
+
* `keyring` is a parameter so the identity runtime — which receives its keyring
|
|
113
|
+
* from `/config.json` because one image serves every brand — resolves through
|
|
114
|
+
* this SAME function rather than a second copy of it.
|
|
115
|
+
*/
|
|
116
|
+
export function keyFor(host: string, keyring: Keyring = ORG_KEY): string | undefined {
|
|
117
|
+
const org = orgOf(host)
|
|
118
|
+
if (!org) return undefined
|
|
119
|
+
const key = keyring[org]
|
|
120
|
+
if (typeof key !== 'string') return undefined
|
|
121
|
+
const trimmed = key.trim()
|
|
122
|
+
return trimmed.startsWith(PUBLISHABLE) ? trimmed : undefined
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** The key for the page this code is running on; undefined off a browser. */
|
|
126
|
+
export function keyForPage(keyring: Keyring = ORG_KEY): string | undefined {
|
|
127
|
+
if (typeof location === 'undefined') return undefined
|
|
128
|
+
return keyFor(location.hostname, keyring)
|
|
129
|
+
}
|
package/src/storage.test.ts
CHANGED
package/src/version.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// The library version, stamped on every event (`libraryVersion`) and on the
|
|
2
2
|
// Sentry `sdk` block. It lives alone so `sentry.ts` can read it without importing
|
|
3
3
|
// `core.ts` — core imports sentry, so the reverse would be an import cycle.
|
|
4
|
-
export const VERSION = '0.3.
|
|
4
|
+
export const VERSION = '0.3.34'
|