@hanzo/event 0.3.20 → 0.3.22

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/hz.js CHANGED
@@ -55,7 +55,7 @@
55
55
  }
56
56
 
57
57
  var LIB = 'hz.js'
58
- var VERSION = '0.3.20'
58
+ var VERSION = '0.3.21'
59
59
  var host = (s.getAttribute('data-host') || 'https://api.hanzo.ai').replace(/\/+$/, '')
60
60
  var product = s.getAttribute('data-product') || location.hostname
61
61
  var capture = s.getAttribute('data-capture') !== '0'
@@ -67,13 +67,12 @@
67
67
  // nothing here reads the response. Through 0.3.11 this file had no way to
68
68
  // present a key at all, so every keyed static surface looked wired, measured
69
69
  // fine in the browser, and filed nothing.
70
- // data-publishable-key is the name; a static tag has no lockstep build to
71
- // migrate it, so data-ingest-key stays readable as the retiring spelling. With
72
- // neither, a tag on the hanzo cloud falls to the baked hanzo org key (the same
73
- // default the npm client bakes in dsn.ts), so a bare tag still emits attributed
74
- // rather than dropping to $public. A custom data-host gets no default.
75
- var key = s.getAttribute('data-publishable-key') || s.getAttribute('data-ingest-key') ||
76
- (host === 'https://api.hanzo.ai' ? 'pk-live-c88649f1085fb6ad441d8a0072933a9b' : '')
70
+ // data-publishable-key is the name; data-ingest-key is the retiring spelling.
71
+ // The value is the surface's own KMS-sourced key, stamped into the tag by the
72
+ // deploy — never a literal baked in here. With neither attribute the tag is
73
+ // keyless and its writes file under $public; the fix is to stamp the key, not
74
+ // to hardcode the org's credential into this file.
75
+ var key = s.getAttribute('data-publishable-key') || s.getAttribute('data-ingest-key') || ''
77
76
 
78
77
  // ── the shared anonymous-identity chain ───────────────────────────────────
79
78
  // COPIED VERBATIM from @hanzo/event's src/anon.js, markers and all, for the same
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanzo/event",
3
- "version": "0.3.20",
3
+ "version": "0.3.22",
4
4
  "description": "Hanzo Event \u2014 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 \u2014 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,5 +81,8 @@
81
81
  "tsup": "^8.5.1",
82
82
  "typescript": "^5.9.3",
83
83
  "vitest": "^4.1.0"
84
+ },
85
+ "dependencies": {
86
+ "@hanzo/events": "^0.2.0"
84
87
  }
85
88
  }
package/src/core.test.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { describe, it, expect, beforeEach } from 'vitest'
2
2
  import { Analytics, VERSION } from './core'
3
- import { HANZO_PUBLISHABLE_KEY } from './dsn'
4
3
  import { EVENTS, PAGEVIEW } from './events'
5
4
  import type { Transport, WireEvent } from './types'
6
5
  import pkg from '../package.json' with { type: 'json' }
@@ -384,48 +383,15 @@ describe('Analytics capture', () => {
384
383
  }
385
384
  })
386
385
 
387
- it('stays keyless when neither config nor env names a key, off the hanzo cloud', () => {
388
- // mk() uses host:'' a same-origin app, deliberately NOT defaulted.
389
- const a = mk()
390
- a.capture('x')
391
- a.flush(true)
392
- expect(tx.sent[0].ingestKey).toBeUndefined()
393
- })
394
-
395
- it('bakes the hanzo publishable key as the default ON the hanzo cloud', () => {
396
- // publishable_key for all: a hanzo surface that passes no key and inlines no
397
- // env still emits attributed, the same way declaring `product` is enough for
398
- // the DSN. No wiring, no $public, no silent drop of track/identify/group.
386
+ it('stays keyless when neither config nor env names a key no baked literal', () => {
387
+ // The key comes from ONE live source (config or the KMS-sourced env); nothing
388
+ // is hardcoded, so a surface that provides neither is honestly keyless.
399
389
  const a = mk({ host: 'https://api.hanzo.ai' })
400
390
  a.capture('x')
401
391
  a.flush(true)
402
- expect(tx.sent[0].ingestKey).toBe(HANZO_PUBLISHABLE_KEY)
403
- })
404
-
405
- it('does NOT bake the default for a white-label (non-hanzo) host', () => {
406
- const a = mk({ host: 'https://api.zoo.ngo' })
407
- a.capture('x')
408
- a.flush(true)
409
392
  expect(tx.sent[0].ingestKey).toBeUndefined()
410
393
  })
411
394
 
412
- it('lets an explicit key and the build env each override the baked default', () => {
413
- const explicit = mk({ host: 'https://api.hanzo.ai', ingestKey: 'pk-explicit' })
414
- explicit.capture('x')
415
- explicit.flush(true)
416
- expect(tx.sent[0].ingestKey).toBe('pk-explicit')
417
-
418
- process.env.NEXT_PUBLIC_PUBLISHABLE_KEY = 'pk-from-env'
419
- try {
420
- const env = mk({ host: 'https://api.hanzo.ai' }) // mk() reassigns `tx`
421
- env.capture('y')
422
- env.flush(true)
423
- expect(tx.sent[0].ingestKey).toBe('pk-from-env')
424
- } finally {
425
- delete process.env.NEXT_PUBLIC_PUBLISHABLE_KEY
426
- }
427
- })
428
-
429
395
  it('a signed-in bearer WINS over a key from the build env', () => {
430
396
  // The leak this closes: one console bundle is served to several brands, and a
431
397
  // pk- names ONE org. If an env-sourced key displaced the bearer, every
package/src/core.ts CHANGED
@@ -43,7 +43,7 @@ import {
43
43
  hasAttribution,
44
44
  deriveChannel,
45
45
  } from './attribution'
46
- import { dsnForProduct, defaultPublishableKey } from './dsn'
46
+ import { dsnForProduct } from './dsn'
47
47
  import { EXCEPTION, PAGEVIEW } from './events'
48
48
  import { exceptionProperties } from './exception'
49
49
  import { scrubText } from './scrub'
@@ -239,35 +239,29 @@ export class Analytics {
239
239
  enabled: true,
240
240
  captureErrors: true,
241
241
  ...config,
242
- // The publishable key resolves the SAME way the DSN below does, and now
243
- // ends the SAME way too — in a baked default, so declaring nothing is
244
- // enough. Most specific first:
242
+ // The publishable key resolves from ONE live source, never a literal baked
243
+ // beside the code. Most specific first:
245
244
  // 1. an explicit `ingestKey` in config;
246
245
  // 2. NEXT_PUBLIC_PUBLISHABLE_KEY, the inlined build-time env the fleet
247
- // carries end to end (KMS `deploy/PUBLISHABLE_KEY` -> the PUBLISHABLE_KEY
248
- // build-arg -> the NEXT_PUBLIC_ prefix Next inlines);
249
- // 3. the hanzo org key baked in `dsn.ts`, on the hanzo cloud only.
246
+ // carries end to end KMS `deploy/PUBLISHABLE_KEY` -> the PUBLISHABLE_KEY
247
+ // build-arg -> the NEXT_PUBLIC_ prefix Next inlines.
250
248
  //
251
- // (3) is why this is `publishable_key for all`: a hanzo surface that passes
252
- // no key in code and inlines no env still emits attributed, exactly as
253
- // declaring `product` is enough for the DSN. Without it that surface sent
254
- // its beacons to `$public` which drops every track/identify/group and
255
- // still answers 200 silent in the page and invisible until you read the
256
- // warehouse and find the host missing entirely. A white-label surface on
257
- // its own cloud (a non-default host) gets no default, so the org this
258
- // attributes to is never the wrong one.
259
- ingestKey:
260
- config.ingestKey ??
261
- readEnv('NEXT_PUBLIC_PUBLISHABLE_KEY') ??
262
- defaultPublishableKey(config.host ?? DEFAULT_HOST),
249
+ // A surface that provides neither has no key, and its anonymous traffic
250
+ // files under `$public` (which drops track/identify/group and answers 200);
251
+ // the fix is to give it the KMS-sourced env, not to hardcode the org key
252
+ // here. The key is a credential-class value: its home is KMS, and the ONE
253
+ // build reads it from there.
254
+ ingestKey: config.ingestKey ?? readEnv('NEXT_PUBLIC_PUBLISHABLE_KEY'),
263
255
  }
264
256
  this.transport = config.transport ?? new DefaultTransport()
265
257
  // Error plane, most specific source first: an explicit DSN wins, then the
266
- // inlined build-time env (a per-deploy override), then the product registry
267
- // so declaring `product` is enough to report errors and no surface needs
268
- // build-argument plumbing. Malformed or absent => null => inert, never
269
- // throwing into the host app.
270
- this.dsn = parseDsn(config.dsn ?? readEnvDsn() ?? dsnForProduct(this.cfg.product))
258
+ // inlined build-time env (a per-deploy override), then the product registry
259
+ // whose DSN carries the SAME resolved key (not a baked one), so declaring
260
+ // `product` + providing the key is enough to report errors. Malformed or
261
+ // absent => null => inert, never throwing into the host app.
262
+ this.dsn = parseDsn(
263
+ config.dsn ?? readEnvDsn() ?? dsnForProduct(this.cfg.product, this.cfg.ingestKey)
264
+ )
271
265
  }
272
266
 
273
267
  /** errorPlaneEnabled reports whether captured exceptions can actually reach the
package/src/dsn.test.ts CHANGED
@@ -1,64 +1,74 @@
1
1
  import { describe, it, expect, afterEach } from 'vitest'
2
2
 
3
3
  import { createAnalytics } from './core'
4
- import { PRODUCT_PROJECT, dsnForProduct, HANZO_PUBLISHABLE_KEY } from './dsn'
4
+ import { PRODUCT_PROJECT, dsnForProduct } from './dsn'
5
5
  import { parseDsn } from './sentry'
6
6
 
7
7
  const ENV = 'NEXT_PUBLIC_HANZO_EVENT_DSN'
8
8
  const OVERRIDE = 'https://1:aaaa@api.hanzo.ai/v1/sentry/env-project'
9
9
  const EXPLICIT = 'https://1:bbbb@api.hanzo.ai/v1/sentry/explicit-project'
10
+ // A stand-in for the surface's own resolved key — the DSN carries whatever key
11
+ // the caller resolved (config or the KMS-sourced env), never a literal.
12
+ const KEY = 'pk-test-resolved-key'
10
13
 
11
14
  afterEach(() => {
12
15
  delete process.env[ENV]
13
16
  })
14
17
 
15
18
  describe('the product registry', () => {
16
- it('builds a product DSN the org publishable key at its project envelope', () => {
17
- expect(dsnForProduct('console')).toBe(
18
- `https://${HANZO_PUBLISHABLE_KEY}@api.hanzo.ai/v1/sentry/${PRODUCT_PROJECT.console}`
19
+ it('builds a product DSN from the CALLER key + the project — nothing baked', () => {
20
+ expect(dsnForProduct('console', KEY)).toBe(
21
+ `https://${KEY}@api.hanzo.ai/v1/sentry/${PRODUCT_PROJECT.console}`
19
22
  )
20
- expect(dsnForProduct('site')).toBe(
21
- `https://${HANZO_PUBLISHABLE_KEY}@api.hanzo.ai/v1/sentry/${PRODUCT_PROJECT.site}`
23
+ expect(dsnForProduct('site', KEY)).toBe(
24
+ `https://${KEY}@api.hanzo.ai/v1/sentry/${PRODUCT_PROJECT.site}`
22
25
  )
23
26
  })
24
27
 
25
- it('returns undefined for an unregistered or missing product, rather than guessing', () => {
26
- expect(dsnForProduct('not-a-product')).toBeUndefined()
27
- expect(dsnForProduct(undefined)).toBeUndefined()
28
- expect(dsnForProduct('')).toBeUndefined()
28
+ it('returns undefined without a key or without a project, rather than guessing', () => {
29
+ expect(dsnForProduct('console', undefined)).toBeUndefined() // no key -> inert
30
+ expect(dsnForProduct('not-a-product', KEY)).toBeUndefined()
31
+ expect(dsnForProduct(undefined, KEY)).toBeUndefined()
32
+ expect(dsnForProduct('', KEY)).toBeUndefined()
29
33
  })
30
34
 
31
- it('every product DSN parses to the org key + its own project id', () => {
35
+ it('every product DSN parses to the caller key + its own project id', () => {
32
36
  for (const [product, projectId] of Object.entries(PRODUCT_PROJECT)) {
33
- const parsed = parseDsn(dsnForProduct(product))
37
+ const parsed = parseDsn(dsnForProduct(product, KEY))
34
38
  expect(parsed, `${product} DSN must parse`).not.toBeNull()
35
39
  expect(parsed!.projectId, `${product} project id`).toBe(projectId)
36
- expect(parsed!.publicKey, `${product} carries the org key`).toBe(HANZO_PUBLISHABLE_KEY)
40
+ expect(parsed!.publicKey, `${product} carries the resolved key`).toBe(KEY)
37
41
  }
38
42
  })
39
43
  })
40
44
 
41
45
  describe('DSN precedence — most specific source wins', () => {
42
- it('lights up the error plane from `product` alone, with no dsn and no env', () => {
43
- const a = createAnalytics({ product: 'console', enabled: false })
46
+ it('lights up the error plane from `product` + a resolved key, with no explicit dsn', () => {
47
+ const a = createAnalytics({ product: 'console', ingestKey: KEY, enabled: false })
44
48
  expect(a.errorPlaneEnabled).toBe(true)
45
49
  expect(a.errorIngestUrl).toContain(PRODUCT_PROJECT.console)
46
50
  })
47
51
 
48
52
  it('prefers an explicit dsn over both the env and the registry', () => {
49
53
  process.env[ENV] = OVERRIDE
50
- const a = createAnalytics({ product: 'console', dsn: EXPLICIT, enabled: false })
54
+ const a = createAnalytics({ product: 'console', ingestKey: KEY, dsn: EXPLICIT, enabled: false })
51
55
  expect(a.errorIngestUrl).toContain('explicit-project')
52
56
  })
53
57
 
54
58
  it('prefers the env override over the registry, so a deploy can repoint a surface', () => {
55
59
  process.env[ENV] = OVERRIDE
56
- const a = createAnalytics({ product: 'console', enabled: false })
60
+ const a = createAnalytics({ product: 'console', ingestKey: KEY, enabled: false })
57
61
  expect(a.errorIngestUrl).toContain('env-project')
58
62
  })
59
63
 
60
64
  it('stays inert for an unregistered product — never posts one surface into another project', () => {
61
- const a = createAnalytics({ product: 'not-a-product', enabled: false })
65
+ const a = createAnalytics({ product: 'not-a-product', ingestKey: KEY, enabled: false })
66
+ expect(a.errorPlaneEnabled).toBe(false)
67
+ expect(a.errorIngestUrl).toBeUndefined()
68
+ })
69
+
70
+ it('stays inert with a product but no key — the error plane needs the live key', () => {
71
+ const a = createAnalytics({ product: 'console', enabled: false })
62
72
  expect(a.errorPlaneEnabled).toBe(false)
63
73
  expect(a.errorIngestUrl).toBeUndefined()
64
74
  })
package/src/dsn.ts CHANGED
@@ -37,40 +37,20 @@ export const PRODUCT_PROJECT: Readonly<Record<string, string>> = Object.freeze({
37
37
  site: '019f9b1e-5785-7359-ad0b-f75db8e58c99', // hanzo.ai (marketing; product `site`)
38
38
  })
39
39
 
40
- /** dsnForProduct builds the product's Sentry DSN the ONE org publishable key at
41
- * its project's envelope endpoint or undefined when the product has no project
42
- * yet, which leaves the error plane inert rather than posting into the wrong one.
43
- * Same key as the event stream: cloud resolves it to the org and attributes the
44
- * errors there. */
45
- export function dsnForProduct(product: string | undefined): string | undefined {
46
- if (!product) return undefined
40
+ /** dsnForProduct builds the product's Sentry DSN from the caller's resolved
41
+ * publishable `key` and the product's projectthe SAME key the event stream
42
+ * carries, at the product's envelope endpoint. Returns undefined when there is no
43
+ * key or no project for the product, leaving the error plane inert rather than
44
+ * posting into the wrong one. The key is NOT baked here: it is the value the
45
+ * surface resolved (an explicit `ingestKey`, or the KMS-sourced
46
+ * NEXT_PUBLIC_PUBLISHABLE_KEY the build inlines) the ONE live source, never a
47
+ * literal committed beside the code. */
48
+ export function dsnForProduct(
49
+ product: string | undefined,
50
+ key: string | undefined
51
+ ): string | undefined {
52
+ if (!product || !key) return undefined
47
53
  const projectId = PRODUCT_PROJECT[product]
48
54
  if (!projectId) return undefined
49
- return `https://${HANZO_PUBLISHABLE_KEY}@api.hanzo.ai/v1/sentry/${projectId}`
50
- }
51
-
52
- /** The hanzo org's publishable ingest key. Like the DSNs above it is PUBLIC by
53
- * construction: write-only — it attributes a write and mints no reading
54
- * principal — so it ships in the bundle and is readable in devtools, exactly as
55
- * hanzo.id already inlines it. It is the value KMS holds at `deploy/PUBLISHABLE_KEY`
56
- * and every fleet Dockerfile passes as the `PUBLISHABLE_KEY` build-arg; baking it
57
- * as the default is the SAME move `dsnForProduct` makes for errors — a hanzo
58
- * surface on the hanzo cloud emits attributed with zero wiring, and no build can
59
- * ship unkeyed (the failure that files anonymous traffic under `$public`, which
60
- * drops every track/identify/group and answers 200). */
61
- export const HANZO_PUBLISHABLE_KEY =
62
- 'pk-live-c88649f1085fb6ad441d8a0072933a9b'
63
-
64
- /** defaultPublishableKey resolves the baked hanzo key, but ONLY for the explicit
65
- * hanzo cloud host (api.hanzo.ai) — the host the public, anonymous-traffic
66
- * surfaces use. It is deliberately NOT applied to a same-origin `''` host: that
67
- * is the shape a cookie/session app uses (it attributes signed-in users through
68
- * the session, so it needs no anonymous key), AND it is the one host a
69
- * white-label surface shares with hanzo, so defaulting it could attribute the
70
- * wrong org. A custom host gets undefined. An explicit `ingestKey`, or an
71
- * inlined NEXT_PUBLIC_PUBLISHABLE_KEY, still wins over this. */
72
- export function defaultPublishableKey(host: string | undefined): string | undefined {
73
- return host === undefined || host === 'https://api.hanzo.ai'
74
- ? HANZO_PUBLISHABLE_KEY
75
- : undefined
55
+ return `https://${key}@api.hanzo.ai/v1/sentry/${projectId}`
76
56
  }
package/src/events.ts CHANGED
@@ -1,76 +1,21 @@
1
- // The ONE product-analytics vocabulary. Every Hanzo surface emits these exact
2
- // names so funnels, goals, and cohorts line up across console/chat/app/site/admin.
3
- // Pageviews use the reserved "$pageview" name (emitted by analytics.pageview()),
4
- // matching the server read lens.
5
- //
6
- // Naming is a closed convention, not a style preference (see TAXONOMY.md):
7
- // • snake_case, `<object>_<verb-in-past-tense>` signup_completed, plan_clicked.
8
- // • Names are CONSTANTS, never interpolated. A dimension (framework, model,
9
- // plan, source) is a PROPERTY, never part of the name. `deploy_succeeded`
10
- // with {framework:'static'} — never `deploy_static_succeeded`.
11
- // • One name per user-visible moment, shared by every surface. The surface is
12
- // already on the wire as `product`, so a name is never product-prefixed.
13
-
14
- export const EVENTS = {
15
- // Signup funnel: view -> submit -> verify -> completed -> first action.
16
- SIGNUP_VIEWED: 'signup_viewed',
17
- SIGNUP_SUBMITTED: 'signup_submitted',
18
- SIGNUP_VERIFIED: 'signup_verified',
19
- SIGNUP_COMPLETED: 'signup_completed',
20
- /** A RETURNING user authenticated the non-signup half of the IAM callback.
21
- * Keeping it distinct is what stops returning logins from inflating signups. */
22
- LOGIN_COMPLETED: 'login_completed',
23
- /** Activation: the first moment of real value. ONE event for every product —
24
- * the product-specific moment is the `action` property (api_call, app_live,
25
- * chat_reply), never a new event name. */
26
- FIRST_ACTION: 'first_action',
27
-
28
- // Waitlist + referral.
29
- WAITLIST_JOINED: 'waitlist_joined',
30
- WAITLIST_SHARED: 'waitlist_shared',
31
- REFERRAL_USED: 'referral_used',
32
- REFERRAL_CLAIMED: 'referral_claimed',
33
-
34
- // Upgrade-intent + purchase.
35
- PRICING_VIEWED: 'pricing_viewed',
36
- PLAN_CLICKED: 'plan_clicked',
37
- CHECKOUT_STARTED: 'checkout_started',
38
- ORDER_COMPLETED: 'order_completed',
39
-
40
- // Feature usage — generic + the common key surfaces across products.
41
- FEATURE_USED: 'feature_used',
42
- API_KEY_CREATED: 'api_key_created',
43
- APP_CREATED: 'app_created',
44
- PROJECT_CREATED: 'project_created',
45
- AGENT_CREATED: 'agent_created',
46
- CHAT_STARTED: 'chat_started',
47
- CHAT_MESSAGE_SENT: 'chat_message_sent',
48
- /** The user switched model/endpoint — the single strongest quality signal a
49
- * chat surface emits (a switch usually follows a bad answer). */
50
- MODEL_SWITCHED: 'model_switched',
51
- TASK_STARTED: 'task_started',
52
- TASK_COMPLETED: 'task_completed',
53
-
54
- // Build → ship. `build_*` is a MODEL producing an artifact; `deploy_*` is that
55
- // artifact going live. Intent (build_started) is never the same event as the
56
- // artifact existing (app_created) — conflating them makes the funnel lie.
57
- BUILD_STARTED: 'build_started',
58
- /** A model finished producing an artifact (an app build, a chat reply, an agent
59
- * run). Carries `durationMs` — the outcome event owns its own duration, so no
60
- * paired start event is needed. */
61
- GENERATION_COMPLETED: 'generation_completed',
62
- GENERATION_FAILED: 'generation_failed',
63
- DEPLOY_STARTED: 'deploy_started',
64
- DEPLOY_SUCCEEDED: 'deploy_succeeded',
65
- DEPLOY_FAILED: 'deploy_failed',
66
- } as const
67
-
68
- export type EventName = (typeof EVENTS)[keyof typeof EVENTS]
69
-
70
- /** The reserved event name a pageview is stored under (server + read lens). */
71
- export const PAGEVIEW = '$pageview'
72
-
73
- /** The reserved name every captured exception is emitted under. Error Tracking
74
- * reads exactly this name; an exception emitted under its own message instead
75
- * makes every distinct message a permanent entry in the event taxonomy. */
76
- export const EXCEPTION = '$exception'
1
+ /**
2
+ * The vocabulary, re-exported from where it is now defined.
3
+ *
4
+ * These names used to be declared here. They moved to @hanzo/events — the
5
+ * plural package is the catalog (many events, what each means, what each
6
+ * carries), this one is the client (one call sends one event). Splitting them
7
+ * that way is what lets the ingest door read the same catalog: the door is Go
8
+ * and cannot import TypeScript, so @hanzo/events also ships the data as JSON,
9
+ * and there is still exactly one definition.
10
+ *
11
+ * Nothing about the API moved. Every existing import keeps working, the names
12
+ * and their values are unchanged, and `pkgs/events/test/catalog.mjs` fails the
13
+ * build if a name and its meaning ever disagree.
14
+ *
15
+ * The naming convention lives with the names, in @hanzo/events and TAXONOMY.md:
16
+ * snake_case `<object>_<verb-past>`, a dimension is a PROPERTY and never part
17
+ * of the name, one name per user-visible moment shared by every surface.
18
+ */
19
+
20
+ export { EVENTS, PAGEVIEW, EXCEPTION } from '@hanzo/events'
21
+ export type { EventName } from '@hanzo/events'
package/src/hz.test.ts CHANGED
@@ -247,18 +247,12 @@ describe('hz.js', () => {
247
247
  expect(post.url).toBe('https://api.hanzo.ai/v1/event?ingest_key=pk-abc123')
248
248
  })
249
249
 
250
- it('falls to the baked hanzo key when no key is declared on the hanzo cloud', () => {
251
- // publishable_key for all: a bare tag on the hanzo cloud still emits
252
- // attributed, rather than dropping unkeyed to $public.
250
+ it('sends no credential when no key is declared no baked literal', () => {
251
+ // The key is the surface's own, stamped into the tag by its deploy from KMS;
252
+ // a bare tag carries nothing, so it is honestly keyless rather than borrowing
253
+ // a hardcoded org credential.
253
254
  run.api!.flush()
254
255
  const post = run.posts.at(-1)!
255
- expect(post.headers.authorization).toBe('Bearer pk-live-c88649f1085fb6ad441d8a0072933a9b')
256
- })
257
-
258
- it('sends no credential when no key is declared and the host is not the hanzo cloud', () => {
259
- const r = runSnippet({ attrs: { 'data-host': 'https://api.zoo.ngo' } })
260
- r.api!.flush()
261
- const post = r.posts.at(-1)!
262
256
  expect(post.headers.authorization).toBeUndefined()
263
257
  expect(post.url).not.toContain('ingest_key')
264
258
  })
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.20'
4
+ export const VERSION = '0.3.21'