@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 +306 -0
- package/dist/analysis/config-manager.mjs +8 -1
- package/dist/analysis/cross-origin-session.mjs +6 -0
- package/dist/analysis/device-info-manager.mjs +1 -0
- package/dist/analysis/device.mjs +13 -4
- package/dist/analysis/index.mjs +2 -0
- package/dist/analysis/low-events/grouped-input-tracker.mjs +24 -6
- package/dist/analysis/low-events/input-tracker.mjs +24 -7
- package/dist/analysis/low-events/shared-window-observer.mjs +13 -3
- package/dist/analysis/queue.mjs +4 -0
- package/dist/analysis/readers/meta.mjs +10 -0
- package/dist/analysis/storage/index.mjs +21 -30
- package/dist/analysis/store/event-store.mjs +1 -0
- package/dist/analysis/tracker-core.mjs +5 -1
- package/dist/analysis/transports/fetch.mjs +11 -2
- package/dist/analysis/utils/email-link-operation-full.mjs +1 -0
- package/dist/analysis/utils/password-manager-sampler.mjs +9 -1
- package/dist/analysis/version.mjs +1 -1
- package/dist/cdn/script.global.js +1 -1
- package/dist/index.d.mts +45 -3
- package/dist/index.d.ts +45 -3
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/package.json +3 -2
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"
|
package/dist/analysis/device.mjs
CHANGED
|
@@ -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: [
|
|
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
|
}
|
package/dist/analysis/index.mjs
CHANGED
|
@@ -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
|
-
{
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
}
|