@corbado/observe 1.11.0 → 1.12.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,306 @@
1
+ # @corbado/observe
2
+
3
+ ## 1.12.1
4
+
5
+ ### Patch Changes
6
+
7
+ - a5b4069: Reader metadata: optional `storage` (`ReaderStorage`: area, keys, lifetime, retention) for readers of Observe's own storage. The SDK's local, session and cookie storage readers declare it and no longer list fields, so the manifest extractor lists them in the new `storage` section instead of `sources`. Reader metadata is stripped from builds; no runtime change.
8
+
9
+ ## 1.12.0
10
+
11
+ ### Minor Changes
12
+
13
+ - c4bbb15: Collection manifest fields declare a data `category` (`ReaderCategory`: `personal-identifier`, `personal-data`, `secret`, `passkey`, `device`, `behavior`, `technical`; list in `READER_CATEGORIES`). Every SDK reader field declares one (`check:browser-globals` enforces it); `ReaderMeta.category` is the default for an item's fields. In `@corbado/autocapture`, `f(path, { category })`, `q` / `p` / `h` options, `clearValue({ max, reason, category })`, the element role `category` (applies to `value` / `value.length`, also via the input-tracking handoff), and store field `category` carry it into `describe()`; library fields (WebAuthn, Connect, endpoint timing/status, input tracking, trusted submit, validation) declare theirs. Metadata only: no runtime behavior changes, reader metadata literals stay stripped from shipped builds.
14
+
15
+ **Action required:** declare a `category` on project spec fields with a content-carrying exposure (`format`, `partial`, `computed`, `token`, `pseudonym`, `redacted`, `raw`): `f` / `q` / `p` / `h` options, `clearValue`, element roles that read the value, store fields. The manifest extractor reports them as missing otherwise.
16
+
17
+ ## 1.11.0
18
+
19
+ ### Minor Changes
20
+
21
+ - 1aaa288: Tree-shakeable core: `initCore()` returns a `CorbadoTrackerCore` with only what every integration runs;
22
+ everything else is imported, so a bundle that does not use it does not contain it. `init()` and
23
+ `CorbadoTracker` (which now extends the core) keep their API and wire behaviour.
24
+ - Typed operations as standalone functions that take the tracker, e.g.
25
+ `passkeyLoginFullOperation(tracker, config)`, `totpOperationFull(tracker, config)` (all 21 factories).
26
+ - Rarely used tracker features as functions: `setUser`, `setFlowTags`, `resetSession`, `setExperiment`,
27
+ `clearExperiment`, `clearExperiments`, `logInfo`, `logError` (the existing package-level helpers now
28
+ also accept a tracker as first argument), `conversion`, `authDecisionStarted`, `authDecisionFinished`,
29
+ `setCrossOriginContext`, `getCrossOriginContext`.
30
+ - Option-driven features as plugins (`initCore({ plugins: [...] })`): `cookieStorage()`,
31
+ `crossOriginSession()`, `rawErrors()` (raw-error serializer), `probes()` (SDK probe lane).
32
+ - `getTrackerCore()` returns the default tracker of either kind; `getTracker()` keeps returning only a
33
+ full tracker.
34
+ - Operations and the function forms need a tracker from the same `@corbado/observe` copy (1.11+); mixing
35
+ copies drops optional features such as the raw-error serializer.
36
+
37
+ ## 1.10.1
38
+
39
+ ### Patch Changes
40
+
41
+ - c40eefc: Low events: `captureSeq` is reserved once per run of input changes (and once per further event the run sends) instead of once per keystroke. Before, an input batch carried only its first number while every keystroke used one, so the gap to the next event revealed how many keys were pressed, i.e. the length of a typed password or identifier. Ordering is unchanged: a batch still sorts by its first observed change, also across fields and before the deferred grouped-input flush. Every reserved number is now sent, so a gap only means a lost event.
42
+ - c40eefc: Collection manifest: "Browser and operating system" (`browser-api:device.user-agent`) now declares what actually reaches Corbado, the full User-Agent header the browser sends with every request (`exposure: "raw"`), instead of an OS/browser category. The unused `DeviceCollector.getOsAndBrowserInfo()` that carried the old declaration is removed. No change to what is sent.
43
+ - c40eefc: Remove the DOM password-manager scan of input trackers: the `pa-1`..`pa-6` low events (1Password elements, LastPass attributes and two popover style profiles) are no longer sent. Password manager detection is the extension sampler alone, reported as `Event.passwordManagers` and controlled by `pmd` (server config, `fixedDataConfig.pmd` or `__CBO_DATA_PMD__`). An omitted `fixedDataConfig.pmd` now simply means off; there is no separate scan left that it kept running.
44
+
45
+ ## 1.10.0
46
+
47
+ ### Minor Changes
48
+
49
+ - 21503ae: Opt-in cross-origin session continuity under a shared parent domain: `init({ fixedDataConfig: { pmd, privateMode, crossOriginSession: { cookieDomain: ".example.com" } } })` keeps a small first-party cookie `cbo_xs_<projectId>` on that domain (random session id, last activity, sequence reservation mark, optional integration context of at most 128 cookie-safe characters; `Secure`, `SameSite=Lax`, `Max-Age` = the session inactivity window; the browser sends it to every subdomain of the domain). A tracker on another origin under the same domain adopts that session at init and continues its sequence numbers above the mark, so one journey across subdomains reports into one Observe session; batches report `meta.crossOriginSession: "adopted" | "own"`. Only init, `resetSession()` and `setCrossOriginContext()` take the cookie over from another session. `setCrossOriginContext()` / `getCrossOriginContext()` carry an opaque integration string with the session. As part of `fixedDataConfig` the option also fixes `pmd` and `privateMode`; with it, separate tabs merge into one session even without server-side session continuity. Without the option nothing changes; blocked cookies, a domain that does not match the page, non-HTTPS pages and sandboxed frames turn the feature off without affecting tracking.
50
+
51
+ ## 1.9.1
52
+
53
+ ### Patch Changes
54
+
55
+ - 346a0d8: The SDK config is fetched only when the cached copy is stale instead of at every page load and every 10 minutes. The cache is now one localStorage record, `cbo_sdk_config_cache_<projectId>`, holding the config, the endpoint it was fetched from, and when it was fetched and expires, written in a single write (a failed write keeps the previous record as a whole). The expiry is `max-age` minus `Age` of the response, clamped to 5 minutes..24 hours, 1 hour when the response has no `max-age`. A record for another endpoint, with invalid times or with an expiry beyond 24 hours is stale. The check runs at init and when the page becomes visible again or is restored from the back/forward cache. All tabs of the origin share the record. The previous cache key `cbo_sdk_config_<projectId>` is applied once as stale and removed after the first successful fetch; an older SDK on the same origin then starts from defaults and fetches at init as before. Requests use `cache: "no-cache"`, so the browser revalidates with `If-None-Match` once the server sends an `ETag`, still without a CORS preflight. Failed requests keep the current config and its expiry; after the in-attempt retries, the next trigger tries again at most once per 60 s.
56
+
57
+ ## 1.9.0
58
+
59
+ ### Minor Changes
60
+
61
+ - f2c714a: Browser access moved into readers: all remaining reads of browser globals (SDK storage and `cbo_*` keys, durable outbox locks, event delivery via fetch and sendBeacon, SDK config refresh, page lifecycle and window low events, input low events on handed-over fields, source path, blocked user-agent check, `window.Corbado` loader global) now go through `src/readers/**`, each call with a literal `ReaderMeta`. `ReaderMeta` (internal) gains the optional `title`, `group` and `origin` (`observe-external` | `observe-internal` | `host-config`), filled for every SDK reader. A CI check (`pnpm check:browser-globals`) fails when code outside `src/readers/**` references a browser global. Titles are customer-facing plain language; technical detail is in the new optional `accessNote`. `ReaderField` gains the optional `label` (what the field is, plain language, same rule as titles), declared on every SDK field; the check enforces it. Reader metadata is stripped from the npm and CDN builds (each literal becomes `0`; `dist/analysis` keeps it), and the strip plugin ships as `@corbado/observe/tools/strip-reader-meta.mjs` for downstream release builds. No behaviour change.
62
+ - 921feca: Opaque SDK handoffs: new types `SdkInputHandoff`, `SdkWebAuthnPayload` and `SdkWebAuthnError` (memberless brands, e.g. returned by `@corbado/autocapture` readers) and additive overloads that accept them: the tracker factories `provideIdentifierOperationFull`, `provideDataOperationFull`, `passwordLoginFullOperation`, `passwordEnrollmentFullOperation`, `emailOtpOperationFull`, `smsOtpOperationFull`, `totpOperationFull` and `formOperationFull` take an `SdkInputHandoff` wherever the config takes an input element, `setInputFields` on the OTP, TOTP and form operations takes handoffs, and the passkey steps (`getOptions`, `ceremony`, `postResponse` of passkey login and enrollment, and the CUI steps of provide-identifier and password login) take an `SdkWebAuthnPayload` for `assertionOptions`, `assertionResponse`, `attestationOptions` and `attestationResponse`. The existing signatures are unchanged and listed last, so `Parameters<…>` and existing assignments resolve as before. No runtime change: a handoff is the original element, JSON string or rejection.
63
+
64
+ ## 1.8.0
65
+
66
+ ### Minor Changes
67
+
68
+ - 32cbb64: Add the opt-in `init({ fixedDataConfig })` option, which fixes the data-collection switches on the integration side instead of taking them from the server config or the cached `cbo_sdk_config`. Batches of such a tracker carry `meta.fixedData: true`. `pmd: true` runs password manager detection (extension probes and the DOM scan, `pa-1`..`pa-6`), `pmd: false` runs neither, and an omitted `pmd` turns the extension probes off while the DOM scan keeps running as before. `privateMode: false` skips private-mode detection and its `cbo_pm` cache (default on). Without `fixedDataConfig`, behaviour is unchanged.
69
+
70
+ `init({ sdkConfig: { pmd } })` is deprecated in favour of `fixedDataConfig.pmd`. It keeps working exactly as before when `fixedDataConfig` is not given; when both are given, `fixedDataConfig` wins.
71
+
72
+ Builds can fix the same switches at build time with the esbuild/tsup defines `__CBO_DATA_PMD__` and `__CBO_DATA_PRIVATE_MODE__` (booleans). A define wins over the option and lets the bundler remove the disabled code, including detectincognitojs. The package now declares `sideEffects` (only `dist/cdn/*` has side effects).
73
+
74
+ The package also ships `dist/analysis/**/*.mjs`, an unminified ESM build that keeps one output file per module, for static analysis of what a consumer build collects. Browser reads go through readers that take literal metadata (`ReaderMeta`, exported as internal). `main`, `module` and `types` are unchanged.
75
+
76
+ - 166e06f: Password manager extension detection is on by default. A project can still switch it off in its server-side SDK configuration.
77
+
78
+ ## 1.7.2
79
+
80
+ ### Patch Changes
81
+
82
+ - b62e2c3: WebAuthn payload sanitizers omit user identifiers (creation `user.id`, credential `response.userHandle`) by default. Relying parties often put an email there. Pass `identifierProjectors.userId` to send a projected value instead.
83
+
84
+ ## 1.7.1
85
+
86
+ ### Patch Changes
87
+
88
+ - e6a4779: Social login reports how an attempt began: `socialLoginOperationFull({ entry })` with `"button"` (default), `"popup"` or `"one-tap"`, also accepted on `exchangeCode.start`. A One Tap reports only `exchangeCode`.
89
+
90
+ ## 1.7.0
91
+
92
+ ### Minor Changes
93
+
94
+ - 5d291d5: Track passkey deletion. The new `passkeyDeletionOperationFull()` emits a `passkey-deletion` subflow (no spec type) with the steps `userConfirmation` (the confirmation dialog: start when it opens, finish when the user confirms, `errorTyped({ code: "cancelled" })` when it is dismissed) and `deletion` (the delete request: finish when the passkey was deleted, `error(error)` or `errorTyped({ code })` when it failed).
95
+
96
+ `SubflowType` and `AuthMethodType` gain `"passkey-deletion"`. The script-tag snippet supports the new operation and its steps.
97
+
98
+ ## 1.6.0
99
+
100
+ ### Minor Changes
101
+
102
+ - 9be8da3: Forms can be dated back and report field interaction. `formOperationFull` accepts `startedAt` (epoch ms, at most a minute back) for a form that became visible before the call, and `inputHtmlFields` plus `setInputFields(fields)` to collect low-level interaction signals (focus, typing, paste, autofill; never values) from the form's fields, rebinding them per page without starting a new subflow. `flowStarted` takes an optional `explicitTimestamp` for the same reason, so a backdated form can stay inside its backdated flow.
103
+ - da114e1: Track forms of a process flow such as an onboarding. The new `formOperationFull({ name, fields? })` emits a `form` subflow when the form is shown, with the steps `submit` (the application accepted the form) and `skip` (the user left it forward without submitting, e.g. "Do it later").
104
+ - `name` identifies the form across visits; the backend normalizes it, so `"personalDetails"` and `"personal_details"` are one form. A missing or blank name is logged as a warning and the form is still reported; the backend then records it as a classification error instead of counting it.
105
+ - `fields` takes field names only: entries outside `[A-Za-z0-9._-]` or longer than 40 characters are dropped with a warning, and at most 50 are sent.
106
+
107
+ `SubflowType` gains `"form"`. The script-tag snippet supports the new operation and its `submit`/`skip` steps.
108
+
109
+ ### Patch Changes
110
+
111
+ - d64d7ef: Checkboxes and radio buttons no longer report a prefill when they appear. Their `value` is what the form submits when they are checked (`"on"` by default), not their content, so every bound box used to count as prefilled. Their content is now whether they are checked: an unchecked box is empty, checking it is reported as filling and unchecking as clearing, and a box that is already checked when it appears still reports a prefill.
112
+
113
+ ## 1.5.2
114
+
115
+ ### Patch Changes
116
+
117
+ - 201f06f: A device info snapshot that no event carried (typically the late `privateMode` update after the page's last event) is now sent on its own as the batch's request-level `deviceInfo`, on the ordinary flush timer or with the unload flush — only on pages that tracked events, never persisted or retried. Requires the backend that accepts a device info without events.
118
+
119
+ ## 1.5.1
120
+
121
+ ### Patch Changes
122
+
123
+ - a88b5f0: The `privateMode` detection starts together with the other device info fields instead of after the fast snapshot, so a quick detection (Safari, Firefox) is part of the fast snapshot and needs no update, and Chromium's slower detection has more time before the page goes away. The late deadline for the update is now 6000 ms (was 3000 ms); a detection that fails early is reported in the fast snapshot's `collectionErrors`.
124
+
125
+ ## 1.5.0
126
+
127
+ ### Minor Changes
128
+
129
+ - e816a9c: Device info is collected in two steps: a fast snapshot of every field except `privateMode` within 300 ms, then one update with fields that were still running (up to 3000 ms) and the `privateMode` detection, which now runs at most once per page, only after the fast snapshot, and is cached in localStorage (`cbo_pm`, 7 days). A snapshot no event carried yet rides the first event of the current page in the next outgoing batch, so a burst of events right at page load no longer leaves the session without device info; device info is never sent on its own. Refreshes skip hidden tabs and reuse the page's `privateMode`. The `deviceInfoCollectorTimeoutMs` server config and the `deviceInfoCollectorTimeout` option no longer have an effect; `deviceInfoTiming` reports the fast snapshot, the `privateMode` source and the update's delivery.
130
+
131
+ ### Patch Changes
132
+
133
+ - 6dd6411: Bump `detectincognitojs` to 1.9.0: Firefox no longer reports private mode when IndexedDB fails for reasons other than private browsing, and the library no longer sets a `window.detectIncognito` global on the host page. The `privateMode` cache is keyed by the new detector version.
134
+
135
+ ## 1.4.0
136
+
137
+ ### Minor Changes
138
+
139
+ - 9c1fb94: Add the internal, experimental `tracker.probe()` used by Corbado autocapture bundles: redacted diagnostic items travel in a new optional `probes` array of the events batch. Probes are held in memory only (never the durable outbox), capped at 500 items / 512 KB, sent at most 200 items / 200 KB per request (probe-only batches allowed), keep their capture session across rotation, retry under `config.retry`, and go last at pagehide / destroy in their own request within the remaining unload budget. Nothing changes for integrations that never call it.
140
+
141
+ ### Patch Changes
142
+
143
+ - 4a93061: Add `"naver"` and `"yahoo"` to `SocialLoginProviderType`, so `socialLoginOperationFull()` steps can report Naver and Yahoo as the social `provider`.
144
+
145
+ ## 1.3.0
146
+
147
+ ### Minor Changes
148
+
149
+ - f681198: Add the `integrationVersion` init option: the version of the integration driving the SDK (e.g. an autocapture bundle) is sent as `sdk.integrationVersion` on every batch, so sessions can be cohorted by integration version without a telemetry line. At most 32 characters of `[0-9A-Za-z.+_-]`; other values are ignored.
150
+
151
+ ## 1.2.0
152
+
153
+ ### Minor Changes
154
+
155
+ - b25aedf: Add password manager extension detection. `detectPasswordManagerExtensions()` synchronously detects the 1Password, Bitwarden, Dashlane, Kaspersky, Keeper, LastPass, NordPass and Proton Pass browser extensions without extension IDs, with `signedIn` where the page can observe it. When enabled through the SDK config (off by default), the tracker samples at SDK init, window load and the first focus of each login field and reports the detected managers as `passwordManagers` (names only) on the next tracked event whenever the detected set grows.
156
+
157
+ ## 1.1.6
158
+
159
+ ### Patch Changes
160
+
161
+ - 609ae4b: Add an optional synchronous user identifier projector to WebAuthn serializers and capture. It projects creation `user.id` and response `userHandle`; defaults preserve them. Callback exceptions and unsupported return values omit the user identifier. Credential IDs and encoded attestation/authenticator data remain unchanged. Signatures and PRF results remain excluded. Autocapture requires Observe ^1.1.6 for this contract.
162
+
163
+ ## 1.1.5
164
+
165
+ ### Patch Changes
166
+
167
+ - d0d7d7b: Steps accept the identifier the user submitted: `postResponse.finished({}, { identifier: { value, type } })` (also on start and error). The value is sent as given, so normalize and, if your data policy requires it, hash it first. Observe uses it to find attempts by identifier, including failed logins.
168
+
169
+ ## 1.1.4
170
+
171
+ ### Patch Changes
172
+
173
+ - e30405c: Add `totpOperationFull()` for authenticator-app codes as the `totp` subflow with a `post-response` step, spec types `totp-login` / `totp-enrollment` and OTP input binding. Add `"push"` to `AppConfirmationOperationSpecType` for approvals pushed to an already-enrolled device. Add `"totp"`, `"totp-login"`, `"totp-enrollment"` and `"app-confirmation"` to `AuthMethodType` as decision options.
174
+
175
+ The inline snippet's operation proxy now exposes `setInputFields`, so OTP operations created before the SDK loads can bind late-rendered inputs.
176
+
177
+ ## 1.1.3
178
+
179
+ ### Patch Changes
180
+
181
+ - a89a88c: Add `setFlowTags(tags, options?)` (tracker method and module export) to tag the innermost open flow via a `flow_enriched` event, with optional `flowType`, `expandToChildren` and `expandToFollowing`.
182
+ - e340ca9: Add `ssoLoginOperationFull()` for enterprise SSO (SAML/OIDC) as its own `sso-login` subflow with `get-redirect-url` and `exchange-code` steps. Its config reports the spec type, the organization's email domain and whether the user or the application (`actor: "system"`, e.g. routing by email domain) chose SSO. Add `"sso-login"` to `AuthMethodType` as the decision option for that subflow.
183
+
184
+ ## 1.1.2
185
+
186
+ ### Patch Changes
187
+
188
+ - 1b5a268: Add a `send` step to `smsOtpOperationFull()`, matching `emailOtpOperationFull()`, so a failed code send (e.g. a rate limit) is reported as an error of the SMS OTP attempt.
189
+
190
+ ## 1.1.1
191
+
192
+ ### Patch Changes
193
+
194
+ - a53fc48: Add `"line"`, `"kakao"` and `"singpass"` to `SocialLoginProviderType`, so `socialLoginOperationFull()` steps can report LINE, Kakao and Singpass as the social `provider`.
195
+
196
+ ## 1.1.0
197
+
198
+ ### Minor Changes
199
+
200
+ - 06d85c1: Continue email-link subflows across pages. `emailLinkOperationFull({ transactionId? })` now carries a transaction ID (generated when omitted, readable as `transactionId`) as `crossEnvironmentTransactionIDStart` and remembers the start in this browser for 24 hours. The new `continueEmailLinkOperation({ explicitSpecType?, transactionId? })` is used on the link's target page:
201
+ - In the same browser and session it continues page 1's subflow directly (no second `subflow_started`), so both pages form one email-link subflow.
202
+ - Otherwise it emits a `subflow_started` with `continuation: true` and `crossEnvironmentTransactionIDFinish`, which the backend joins to page 1 through the transaction ID. Without a transaction ID it is deferred until `setTransactionId()`, `flush()` or page hide, and steps called before that are replayed with their original timestamps.
203
+
204
+ `UserReference` gains `crossEnvironmentTransactionIDStart` and `crossEnvironmentTransactionIDFinish`. The script-tag snippet supports the new operation and its `setTransactionId`/`flush` methods.
205
+
206
+ ## 1.0.0
207
+
208
+ ### Major Changes
209
+
210
+ - First stable release (1.0.0).
211
+
212
+ ## 0.16.7
213
+
214
+ ### Patch Changes
215
+
216
+ - 9e78845: Mask query values in `meta.trackingSourcePath` by default. The new `sourcePathQuery` and `sourcePathQueryAllow` reliability config keys control which values are kept: `"allowlist"` (default) keeps every query key and its position but replaces each value with `*` unless its key is allowlisted (case-insensitive, trailing `*` matches a prefix, up to 50 keys; empty by default). `"all"` restores the previous behavior of reporting the full query. Empty values and keys without a value are kept; the path is never changed. Any mode other than `"all"` is treated as `"allowlist"`. Both keys can be set server-side per project or via `init({ sdkConfig })`.
217
+
218
+ **Behavior change:** after updating, query values such as `utm_*` no longer appear in `trackingSourcePath` until the project allowlists their keys or sets `sourcePathQuery` to `"all"`.
219
+
220
+ ## 0.16.6
221
+
222
+ ### Patch Changes
223
+
224
+ - 59cc12d: Return a flow handle from `flowFinished()` as well. Its `enrich(user)` emits `flow_enriched` with `data.targetEventId` set to the `flow_finished` event UUID, so identity learned after completion can be attached from the page that finished the flow, even when that page never saw the flow start. Requires backend support for explicit flow-event targeting. Existing calls that ignore the return value are unaffected.
225
+
226
+ The `flowStarted()` handle now sends `data.targetEventId` instead of the deprecated `data.flowStartedEventId`, which the backend keeps accepting as an alias. Both handles require a backend supporting `targetEventId`.
227
+
228
+ - 2d6cba9: Add data policy support: `init({ dataPolicy })`, `setDataPolicy(code)` and `getDataPolicy()` select a project-scoped data policy code (integer 0..255, 0 = project default) configured in Corbado. Once set, the code is sent as `meta.dataPolicy` on every batch (including an explicit 0), persisted per project in localStorage and kept across reloads, `resetSession()` and `destroy()`; it is only ever overwritten, never cleared. The server records the latest code per session (last write wins). Invalid codes are ignored with a debug log; the SDK never throws. Nothing is sent until a code is set, so existing integrations are unaffected.
229
+
230
+ ## 0.16.5
231
+
232
+ ### Patch Changes
233
+
234
+ - a9151df: Return a flow handle from `flowStarted()` with `enrich(user)` for delayed identity enrichment. The handle emits `flow_enriched` with the original `flowStartedEventId` and session, allowing identity to be attached after completion without targeting a later flow. Requires backend support for explicit start-event targeting. Existing temporal enrichment remains unchanged; the tracker must remain alive until enrichment is enqueued.
235
+
236
+ ## 0.16.4
237
+
238
+ ### Patch Changes
239
+
240
+ - ef1fd8a: Add keySigningOperationFull and keyRegistrationOperationFull helpers without spec types, including deferred snippet support. Track usable signing artifacts and accepted key authority separately from WebAuthn ceremonies.
241
+
242
+ ## 0.16.3
243
+
244
+ ### Patch Changes
245
+
246
+ - 2a55a1c: Gate opt-in raw error diagnostics behind the `rawErrors` SDK configuration flag (default on). Disabled diagnostics skip serialization; remote refreshes apply to subsequent errors without changing normalized error reporting.
247
+
248
+ ## 0.16.2
249
+
250
+ ### Patch Changes
251
+
252
+ - 51b1c87: Add the `same-device` app-confirmation spec for local companion-app authentication such as Okta FastPass. Ceremony remains the completion step; post-response is optional when a separate browser exchange exists.
253
+
254
+ ## 0.16.1
255
+
256
+ ### Patch Changes
257
+
258
+ - 91ed216: Keep Observe npm and CDN bundles minified but unobfuscated so Node can resolve named imports from the CommonJS entry. Require Observe ^0.16.1 from autocapture. Allow matching Observe prereleases in staging autocapture builds.
259
+
260
+ ## 0.16.0
261
+
262
+ ### Minor Changes
263
+
264
+ - a970bbf: Expose WebAuthn credential and options redaction helpers from Observe. Autocapture consumes Observe as a required peer dependency and preserves its existing helper exports for compatibility. Install Observe 0.16.0 or later within the 0.16 release line when upgrading autocapture.
265
+
266
+ ## 0.15.2
267
+
268
+ ### Patch Changes
269
+
270
+ - 8af8bbd: Add the typed password-login error `invalid_identifier_or_password` for enumeration-safe credential rejection.
271
+ - 42b57a8: Add an optional `rawError` diagnostic in the options of normal and typed step errors. It is serialized by the new `serializeRawError` helper with bounded depth, breadth and UTF-8 size, keeps `name`, `message`, `code`, `cause` and own properties of browser and library errors including `DOMException`, omits stacks unless requested, accepts Sentry-style per-call limits (`rawErrorLimits`: depth, breadth, value length, cause chain, stack frames, bytes) clamped to documented bounds, never emits binary content, and is sent alongside the classified error without affecting classification.
272
+ - e0b9197: Add trusted-device check and enrollment operations with optional binding references and typed negative error codes that retain their diagnostics. Support a neutral identifier choice and username identifiers. Purpose distinguishes additional verification from MFA exemption. Require purpose and storage in typed configuration, with non-throwing runtime validation on both operations and omit oversized optional binding metadata with a diagnostic. Support these operations in the asynchronous CDN snippet and tolerate missing runtime typed-error payloads. Typed check errors use `code`, such as `no_eligible_local_key`; normal step responses continue to use `result`, such as `no-eligible-local-key`.
273
+
274
+ ## 0.15.1
275
+
276
+ ### Patch Changes
277
+
278
+ - 7ede3a6: Infer passkey ceremony initiators from the operation or ceremony spec, so integrations no longer need to supply initiator metadata. Manual login/enrollment specs use `passkey-button`; automatic login specs, `passkey-immediate`, and automatic/conditional enrollment use `automatic`. Low events and ceremony step metadata share the inferred value. Explicit overrides remain supported, ambiguous specs remain unknown, and conditional-login collection behavior is unchanged.
279
+
280
+ ## 0.15.0
281
+
282
+ ### Minor Changes
283
+
284
+ - 6f887f2: Add version 1 low-event capture with explicit subflow attribution and per-page capture ordering,
285
+ supporting concurrent input and passkey observations. Improve identifier, password, and single-field
286
+ or split-field OTP capture, including paste detection, prefill, clearing, and three-second idle
287
+ boundaries. Add `setInputFields()` to email and SMS OTP operations so applications can rebind
288
+ replaced input groups without restarting the subflow. Add `capturePrefill()` for application-controlled initialization after observation starts.
289
+
290
+ Add opt-in persistent low-event delivery through `durableLowOutbox`, preserving capture context and
291
+ ordering across retries and reloads. Enable it alongside `durableOutbox` to persist the corresponding
292
+ subflow-start events as well.
293
+
294
+ Deploy backend support for the v1 capture contract and the combined low-event capture-context
295
+ migration before rolling out this SDK version. Version 1 capture is enabled by default;
296
+ `durableLowOutbox` remains disabled by default.
297
+
298
+ ## 0.13.0
299
+
300
+ ### Minor Changes
301
+
302
+ - 0bd42a1: Scope persisted SDK state and cross-tab locks by project ID so multiple projects can run in the
303
+ same application independently. Automatically migrate legacy global state to the first initialized
304
+ project, preserving sessions, sequence counters, pending events, experiment assignments, and device
305
+ identity. Initialize the existing project first when upgrading; migration is a one-time snapshot,
306
+ and later writes from older SDK versions are not imported again.
@@ -59,6 +59,7 @@ var ConfigManager = class {
59
59
  {
60
60
  path: "visibilityState",
61
61
  label: "Whether the page is hidden",
62
+ category: "technical",
62
63
  exposure: "enum",
63
64
  transform: { values: ["hidden", "other"] }
64
65
  }
@@ -78,6 +79,7 @@ var ConfigManager = class {
78
79
  {
79
80
  path: "event.visibilitychange",
80
81
  label: "Page hidden or shown again",
82
+ category: "technical",
81
83
  exposure: "presence",
82
84
  transform: { note: "checks whether Observe settings are due for a refresh when the page is shown" }
83
85
  }
@@ -97,6 +99,7 @@ var ConfigManager = class {
97
99
  {
98
100
  path: "event.persisted",
99
101
  label: "Page restored from the browser's back/forward cache",
102
+ category: "technical",
100
103
  exposure: "presence",
101
104
  transform: { note: "checks whether Observe settings are due for a refresh" }
102
105
  }
@@ -148,27 +151,31 @@ var ConfigManager = class {
148
151
  group: "Event delivery",
149
152
  origin: "observe-internal",
150
153
  fields: [
151
- { path: "response.status", label: "Response code", exposure: "raw" },
154
+ { path: "response.status", label: "Response code", category: "technical", exposure: "raw" },
152
155
  {
153
156
  path: "response.header.retry-after",
154
157
  label: "Whether the server asked to retry later",
158
+ category: "technical",
155
159
  exposure: "presence"
156
160
  },
157
161
  {
158
162
  path: "response.header.cache-control",
159
163
  label: "How long the server allows reusing the settings",
164
+ category: "technical",
160
165
  exposure: "computed",
161
166
  transform: { note: "max-age, reduced to the settings' expiry time" }
162
167
  },
163
168
  {
164
169
  path: "response.header.age",
165
170
  label: "How long the settings were already stored on the way",
171
+ category: "technical",
166
172
  exposure: "computed",
167
173
  transform: { note: "subtracted from max-age for the settings' expiry time" }
168
174
  },
169
175
  {
170
176
  path: "response.body",
171
177
  label: "Observe settings",
178
+ category: "technical",
172
179
  exposure: "raw",
173
180
  transform: { note: "SDK reliability config" }
174
181
  }
@@ -96,12 +96,14 @@ var CrossOriginSession = class _CrossOriginSession {
96
96
  {
97
97
  path: "hostname",
98
98
  label: "Host name of the page",
99
+ category: "technical",
99
100
  exposure: "none",
100
101
  transform: { note: "only compared with the configured parent domain; never sent or stored" }
101
102
  },
102
103
  {
103
104
  path: "https",
104
105
  label: "Whether the page uses a secure connection",
106
+ category: "technical",
105
107
  exposure: "none",
106
108
  transform: { note: "the shared cookie is only used on secure pages; never sent or stored" }
107
109
  }
@@ -127,6 +129,7 @@ var CrossOriginSession = class _CrossOriginSession {
127
129
  {
128
130
  path: "sessionId",
129
131
  label: "Random Observe session id",
132
+ category: "technical",
130
133
  exposure: "token",
131
134
  transform: {
132
135
  pattern: "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
@@ -137,6 +140,7 @@ var CrossOriginSession = class _CrossOriginSession {
137
140
  {
138
141
  path: "lastActiveAt",
139
142
  label: "Time of the last tracked activity",
143
+ category: "technical",
140
144
  exposure: "none",
141
145
  transform: {
142
146
  note: "decides whether the session is still active; not sent to Corbado, but the cookie carries it to every subdomain of the configured domain"
@@ -145,6 +149,7 @@ var CrossOriginSession = class _CrossOriginSession {
145
149
  {
146
150
  path: "seqMark",
147
151
  label: "Event counter position",
152
+ category: "technical",
148
153
  exposure: "none",
149
154
  transform: {
150
155
  note: "the next page continues event numbering above it; not sent to Corbado, but the cookie carries it to every subdomain of the configured domain"
@@ -162,6 +167,7 @@ var CrossOriginSession = class _CrossOriginSession {
162
167
  {
163
168
  path: "context",
164
169
  label: "Optional reference set by your integration",
170
+ category: "technical",
165
171
  exposure: "none",
166
172
  transform: {
167
173
  note: "opaque string of at most 128 cookie-safe characters chosen by the integration; not sent to Corbado, but the cookie carries it to every subdomain of the configured domain"
@@ -11,6 +11,7 @@ function isDocumentHidden() {
11
11
  {
12
12
  path: "visibilityState",
13
13
  label: "Whether the page is hidden",
14
+ category: "technical",
14
15
  exposure: "enum",
15
16
  transform: { values: ["hidden", "other"] }
16
17
  }
@@ -187,6 +187,7 @@ var DeviceCollector = class {
187
187
  {
188
188
  path: "result",
189
189
  label: "Whether Bluetooth is available",
190
+ category: "device",
190
191
  exposure: "enum",
191
192
  transform: { values: [true, false] }
192
193
  }
@@ -206,6 +207,7 @@ var DeviceCollector = class {
206
207
  {
207
208
  path: "result.<capability>",
208
209
  label: "Supported passkey features",
210
+ category: "device",
209
211
  exposure: "enum",
210
212
  transform: { values: ["true", "false"], note: "open set of capability names, kept as reported" }
211
213
  }
@@ -225,6 +227,7 @@ var DeviceCollector = class {
225
227
  {
226
228
  path: "result",
227
229
  label: "Whether the device has a built-in passkey authenticator",
230
+ category: "device",
228
231
  exposure: "enum",
229
232
  transform: { values: [true, false] }
230
233
  }
@@ -244,6 +247,7 @@ var DeviceCollector = class {
244
247
  {
245
248
  path: "result",
246
249
  label: "Whether passkey autofill is available",
250
+ category: "device",
247
251
  exposure: "enum",
248
252
  transform: { values: [true, false] }
249
253
  }
@@ -260,15 +264,16 @@ var DeviceCollector = class {
260
264
  group: "Device information",
261
265
  origin: "observe-external",
262
266
  fields: [
263
- { path: "result.platform", label: "Operating system", exposure: "raw" },
267
+ { path: "result.platform", label: "Operating system", category: "device", exposure: "raw" },
264
268
  {
265
269
  path: "result.mobile",
266
270
  label: "Whether the device is a mobile device",
271
+ category: "device",
267
272
  exposure: "enum",
268
273
  transform: { values: [true, false] }
269
274
  },
270
- { path: "result.platformVersion", label: "Operating system version", exposure: "raw" },
271
- { path: "result.model", label: "Device model", exposure: "raw" }
275
+ { path: "result.platformVersion", label: "Operating system version", category: "device", exposure: "raw" },
276
+ { path: "result.model", label: "Device model", category: "device", exposure: "raw" }
272
277
  ]
273
278
  })
274
279
  ),
@@ -285,6 +290,7 @@ var DeviceCollector = class {
285
290
  {
286
291
  path: "result",
287
292
  label: "Whether the browser is controlled by automation",
293
+ category: "device",
288
294
  exposure: "enum",
289
295
  transform: { values: [true, false] }
290
296
  }
@@ -300,7 +306,9 @@ var DeviceCollector = class {
300
306
  accessNote: "navigator.maxTouchPoints",
301
307
  group: "Device information",
302
308
  origin: "observe-external",
303
- fields: [{ path: "result", label: "Number of touch points of the screen", exposure: "raw" }]
309
+ fields: [
310
+ { path: "result", label: "Number of touch points of the screen", category: "device", exposure: "raw" }
311
+ ]
304
312
  })
305
313
  )
306
314
  ];
@@ -446,6 +454,7 @@ var DeviceCollector = class {
446
454
  {
447
455
  path: "result.isPrivate",
448
456
  label: "Whether private browsing is on",
457
+ category: "device",
449
458
  exposure: "enum",
450
459
  transform: { values: [true, false] }
451
460
  }
@@ -42,6 +42,7 @@ import {
42
42
  detectPasswordManagerExtensions
43
43
  } from "./readers/password-manager-extensions.mjs";
44
44
  import {
45
+ READER_CATEGORIES,
45
46
  READER_EXPOSURES
46
47
  } from "./readers/meta.mjs";
47
48
  import {
@@ -86,6 +87,7 @@ async function destroy() {
86
87
  setDefaultTracker(void 0);
87
88
  }
88
89
  export {
90
+ READER_CATEGORIES,
89
91
  READER_EXPOSURES,
90
92
  SDK_NAME,
91
93
  SDK_VERSION,
@@ -25,12 +25,14 @@ var LowEventGroupedInputTracker = class {
25
25
  {
26
26
  path: "event.paste",
27
27
  label: "Paste into the field",
28
+ category: "behavior",
28
29
  exposure: "presence",
29
30
  transform: { note: "trusted paste events only" }
30
31
  },
31
32
  {
32
33
  path: "value",
33
34
  label: "Length of the pasted text",
35
+ category: "behavior",
34
36
  exposure: "size",
35
37
  transform: { note: "inserted length only; values are compared in memory and discarded" }
36
38
  }
@@ -66,27 +68,40 @@ var LowEventGroupedInputTracker = class {
66
68
  group: "Input interaction",
67
69
  origin: "observe-external",
68
70
  fields: [
69
- { path: "event.focus", label: "Field selected", exposure: "presence" },
71
+ { path: "event.focus", label: "Field selected", category: "behavior", exposure: "presence" },
70
72
  {
71
73
  path: "event.focus.relatedTarget",
72
74
  label: "Whether focus moved between fields of the same group",
75
+ category: "behavior",
73
76
  exposure: "computed",
74
77
  transform: { note: "only whether focus moves within the group" }
75
78
  },
76
- { path: "event.blur", label: "Field left", exposure: "presence" },
77
- { path: "event.pointerdown", label: "Pointer pressed on the field", exposure: "presence" },
78
- { path: "event.pointerup", label: "Pointer released on the field", exposure: "presence" },
79
- { path: "event.click", label: "Click on the field", exposure: "presence" },
80
- { path: "event.input", label: "Typing in the field", exposure: "presence" },
79
+ { path: "event.blur", label: "Field left", category: "behavior", exposure: "presence" },
80
+ {
81
+ path: "event.pointerdown",
82
+ label: "Pointer pressed on the field",
83
+ category: "behavior",
84
+ exposure: "presence"
85
+ },
86
+ {
87
+ path: "event.pointerup",
88
+ label: "Pointer released on the field",
89
+ category: "behavior",
90
+ exposure: "presence"
91
+ },
92
+ { path: "event.click", label: "Click on the field", category: "behavior", exposure: "presence" },
93
+ { path: "event.input", label: "Typing in the field", category: "behavior", exposure: "presence" },
81
94
  {
82
95
  path: "event.input.isComposing",
83
96
  label: "Whether text is being composed with an input method",
97
+ category: "behavior",
84
98
  exposure: "enum",
85
99
  transform: { values: [true, false] }
86
100
  },
87
101
  {
88
102
  path: "event.keyup.key",
89
103
  label: "Escape key pressed",
104
+ category: "behavior",
90
105
  exposure: "enum",
91
106
  transform: { values: ["Escape", "other"] }
92
107
  }
@@ -134,6 +149,7 @@ var LowEventGroupedInputTracker = class {
134
149
  {
135
150
  path: "event.pagehide",
136
151
  label: "Leaving the page",
152
+ category: "technical",
137
153
  exposure: "presence",
138
154
  transform: { note: "flushes pending input low events on unload" }
139
155
  }
@@ -157,6 +173,7 @@ var LowEventGroupedInputTracker = class {
157
173
  {
158
174
  path: "focused",
159
175
  label: "Whether the field is selected",
176
+ category: "behavior",
160
177
  exposure: "enum",
161
178
  transform: { values: [true, false] }
162
179
  }
@@ -191,6 +208,7 @@ var LowEventGroupedInputTracker = class {
191
208
  {
192
209
  path: "value.length",
193
210
  label: "Length of the entered text",
211
+ category: "behavior",
194
212
  exposure: "size",
195
213
  transform: { note: "summed over the fields of a group" }
196
214
  }