@hanzo/event 0.3.3 → 0.3.5
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/TAXONOMY.md +257 -0
- package/dist/index.cjs +31 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -1
- package/dist/index.d.ts +34 -1
- package/dist/index.mjs +30 -2
- package/dist/index.mjs.map +1 -1
- package/dist/react.cjs +29 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.mjs +29 -1
- package/dist/react.mjs.map +1 -1
- package/package.json +4 -3
- package/src/core.test.ts +44 -4
- package/src/core.ts +21 -3
- package/src/dsn.test.ts +62 -0
- package/src/dsn.ts +45 -0
- package/src/index.ts +1 -0
package/src/dsn.test.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { createAnalytics } from './core'
|
|
4
|
+
import { PRODUCT_DSN, dsnForProduct } from './dsn'
|
|
5
|
+
import { parseDsn } from './sentry'
|
|
6
|
+
|
|
7
|
+
const ENV = 'NEXT_PUBLIC_HANZO_EVENT_DSN'
|
|
8
|
+
const OVERRIDE = 'https://1:aaaa@api.hanzo.ai/v1/sentry/env-project'
|
|
9
|
+
const EXPLICIT = 'https://1:bbbb@api.hanzo.ai/v1/sentry/explicit-project'
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
delete process.env[ENV]
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
describe('the product registry', () => {
|
|
16
|
+
it('resolves a registered product to its DSN', () => {
|
|
17
|
+
expect(dsnForProduct('console')).toBe(PRODUCT_DSN.console)
|
|
18
|
+
expect(dsnForProduct('app')).toBe(PRODUCT_DSN.app)
|
|
19
|
+
expect(dsnForProduct('site')).toBe(PRODUCT_DSN.site)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
it('returns undefined for an unregistered or missing product, rather than guessing', () => {
|
|
23
|
+
expect(dsnForProduct('not-a-product')).toBeUndefined()
|
|
24
|
+
expect(dsnForProduct(undefined)).toBeUndefined()
|
|
25
|
+
expect(dsnForProduct('')).toBeUndefined()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('registers only DSNs that actually parse — a typo here would silently disable a surface', () => {
|
|
29
|
+
for (const [product, dsn] of Object.entries(PRODUCT_DSN)) {
|
|
30
|
+
const parsed = parseDsn(dsn)
|
|
31
|
+
expect(parsed, `${product} DSN must parse`).not.toBeNull()
|
|
32
|
+
expect(parsed!.projectId, `${product} needs a project id`).toBeTruthy()
|
|
33
|
+
expect(parsed!.publicKey, `${product} needs a public key`).toBeTruthy()
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
describe('DSN precedence — most specific source wins', () => {
|
|
39
|
+
it('lights up the error plane from `product` alone, with no dsn and no env', () => {
|
|
40
|
+
const a = createAnalytics({ product: 'console', enabled: false })
|
|
41
|
+
expect(a.errorPlaneEnabled).toBe(true)
|
|
42
|
+
expect(a.errorIngestUrl).toContain(parseDsn(PRODUCT_DSN.console)!.projectId)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('prefers an explicit dsn over both the env and the registry', () => {
|
|
46
|
+
process.env[ENV] = OVERRIDE
|
|
47
|
+
const a = createAnalytics({ product: 'console', dsn: EXPLICIT, enabled: false })
|
|
48
|
+
expect(a.errorIngestUrl).toContain('explicit-project')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('prefers the env override over the registry, so a deploy can repoint a surface', () => {
|
|
52
|
+
process.env[ENV] = OVERRIDE
|
|
53
|
+
const a = createAnalytics({ product: 'console', enabled: false })
|
|
54
|
+
expect(a.errorIngestUrl).toContain('env-project')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('stays inert for an unregistered product — never posts one surface into another project', () => {
|
|
58
|
+
const a = createAnalytics({ product: 'not-a-product', enabled: false })
|
|
59
|
+
expect(a.errorPlaneEnabled).toBe(false)
|
|
60
|
+
expect(a.errorIngestUrl).toBeUndefined()
|
|
61
|
+
})
|
|
62
|
+
})
|
package/src/dsn.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The product → Sentry DSN registry.
|
|
3
|
+
*
|
|
4
|
+
* An app declares WHAT it is (`product: 'console'`); this module knows WHERE its
|
|
5
|
+
* errors go. That split is the whole point: no surface has to learn a DSN, carry
|
|
6
|
+
* a build argument, or grow a config file to report errors — declaring the
|
|
7
|
+
* product it already declares is enough.
|
|
8
|
+
*
|
|
9
|
+
* A Sentry DSN is PUBLIC by construction. It ships inside the client bundle and
|
|
10
|
+
* is readable in devtools on any deployed page, and it grants exactly one
|
|
11
|
+
* capability: submitting new events. It cannot read issues, projects, or any
|
|
12
|
+
* other data. So committing it is not leaking a secret — it is recording a public
|
|
13
|
+
* identifier next to the code that needs it. (Contrast the server-side collector
|
|
14
|
+
* DSN in the `team-analytics-sentry` Secret, which is HMAC-derived and revocable
|
|
15
|
+
* precisely because a server-side credential is NOT public.)
|
|
16
|
+
*
|
|
17
|
+
* Why a literal map instead of deriving `hanzo-${product}`: the projects predate
|
|
18
|
+
* this registry and do not derive cleanly — `site` lives in `hanzo-ai`, not
|
|
19
|
+
* `hanzo-site`. An explicit map is honest about that; a derivation rule plus an
|
|
20
|
+
* exception table is the same data with a trap in it.
|
|
21
|
+
*
|
|
22
|
+
* Projects are org-scoped and named `<org>-<app>`. To add one: create the project
|
|
23
|
+
* (POST /v1/sentry/projects with X-Org-Id), then add its `dsn` here keyed by the
|
|
24
|
+
* product name the app passes to `createAnalytics`.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
/** PRODUCT_DSN maps a `product` to the DSN its errors are submitted to. */
|
|
28
|
+
export const PRODUCT_DSN: Readonly<Record<string, string>> = Object.freeze({
|
|
29
|
+
// hanzo-console — console.hanzo.ai (also served embedded by the cloud binary)
|
|
30
|
+
console:
|
|
31
|
+
'https://1:0c8054dbde157f4f420c56b58660052b2ad782293c4de1d606ef8fbc46a0bf34@api.hanzo.ai/v1/sentry/019fa40b-94ae-7f1d-8f7b-e92f123fad42',
|
|
32
|
+
// hanzo-app — hanzo.app
|
|
33
|
+
app: 'https://1:b3e1173125568c80f91ef4b1fabbbd2d7e22341de02b33ce7e22ef4fc16a196e@api.hanzo.ai/v1/sentry/019f9b1e-57eb-7171-9d92-72c0b85e4b4b',
|
|
34
|
+
// hanzo-ai — hanzo.ai (the marketing site; `site` is the product name it declares)
|
|
35
|
+
site: 'https://1:d9cbfb844958bd7ef2a455600f00fbf237fbd71b75c1504f137773096d6aa53f@api.hanzo.ai/v1/sentry/019f9b1e-5785-7359-ad0b-f75db8e58c99',
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
/** dsnForProduct resolves the registered DSN for a product, or undefined when the
|
|
39
|
+
* product has no project yet — which leaves the error plane inert rather than
|
|
40
|
+
* guessing a destination and silently posting a surface's errors into the wrong
|
|
41
|
+
* project. */
|
|
42
|
+
export function dsnForProduct(product: string | undefined): string | undefined {
|
|
43
|
+
if (!product) return undefined
|
|
44
|
+
return PRODUCT_DSN[product]
|
|
45
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
export { Analytics, createAnalytics, VERSION, getCohort, getFirstTouch } from './core'
|
|
11
11
|
export { parseDsn, buildSentryEvent, buildEnvelope, framesFromStack } from './sentry'
|
|
12
|
+
export { PRODUCT_DSN, dsnForProduct } from './dsn'
|
|
12
13
|
export type { ErrorIdentity } from './sentry'
|
|
13
14
|
export { scrubText, redactSecrets, scrubPII } from './scrub'
|
|
14
15
|
export { EVENTS, PAGEVIEW } from './events'
|