@crelora/mark 0.2.1 → 0.3.0

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/README.md CHANGED
@@ -52,6 +52,7 @@ Mark.init({
52
52
  cross_domain: { cookie_domain: '.example.com' },
53
53
  site_id: 'uuid-...', // Optional: associate events with a registered site
54
54
  site_host: 'shop.example.com', // Optional: site host for audit/debug
55
+ visitor_id: 'platform_client_id', // Optional: adopt trusted first-party anonymous id when storage is empty
55
56
  autocapture: { pageview: true }, // Optional: auto-emit page_view on load and SPA route changes
56
57
  });
57
58
 
@@ -99,6 +100,7 @@ The Node factory accepts optional custom storage or transport adapters so you ca
99
100
  - `track(eventName, payload?)` – records custom events with arbitrary properties (numbers, strings, arrays, objects). Use `site_id` and `site_host` for per-event site overrides.
100
101
  - `conversion(eventName, payload?)` – records conversion events (same endpoint path as track, with `is_conversion: true` for backend compatibility).
101
102
  - `identify(userId, traits?)` – ties a known user identifier to previous anonymous activity. Recommended traits: `email`, `display_name`, `language`. Custom traits are also supported.
103
+ - `setVisitorId(visitorId)` – adopts or replaces the stored anonymous visitor id with a trusted first-party value (for example a platform client id). Updates storage and cookies immediately on the browser. Use when the id is available after init; theme embeds and pixels should use the same source id.
102
104
  - `setConsent(status)` – enforces consent gating; pass `'granted'` or `'denied'`.
103
105
  - `getVisitorId()` – returns the current pseudonymous visitor ID when available. On the browser, returns `undefined` until consent is granted when `require_consent` is set. Use it to send the ID to your backend (e.g. in a header or body) for server-side attribution when you don't have an authenticated user ID.
104
106
  - `flush()` – flushes queued/persisted delivery items.
@@ -156,12 +158,13 @@ All config options use snake_case. Stored event payloads and database columns ma
156
158
  | `batching` | `{ enabled?: boolean, max_size?: number, flush_interval_ms?: number, endpoint_path?: string }` | Optional batch mode (`/events` by default). |
157
159
  | `require_consent` | `boolean \| 'auto'` | `true` blocks tracking until consent is granted, `'auto'` requires stored granted consent and treats missing consent as denied, default `false` (`'auto'` recommended for production). |
158
160
  | `consent_source` | `{ type: 'tcf', purposes: number[] }` | Optional **IAB TCF v2** integration: the SDK listens for CMP updates and only allows tracking when the listed numeric purpose IDs are consented. Combine with `require_consent` and `setConsent` as your legal team requires. |
159
- | `autocapture` | `{ pageview?: boolean, click?: boolean \| { selector?: string }, form_submit?: boolean, outbound_link?: boolean, scroll_depth?: boolean, web_vitals?: boolean }` | Auto-capture toggles for page views and optional interaction/perf signals. |
160
- | `track_route_changes` | `boolean` | When `autocapture.pageview` is true, also emits on SPA route changes (pushState/replaceState/popstate); defaults to `true`. |
161
+ | `autocapture` | `{ pageview?: boolean, click?: boolean \| { selector?: string }, form_submit?: boolean, outbound_link?: boolean, scroll_depth?: boolean, page_engagement?: boolean \| { min_active_ms?: number, use_beacon?: boolean }, web_vitals?: boolean }` | Auto-capture toggles for page views and optional interaction/perf signals. |
162
+ | `track_route_changes` | `boolean` | When page lifecycle autocapture is enabled (`pageview`, `scroll_depth`, or `page_engagement`), also reacts to SPA route changes (pushState/replaceState/popstate); defaults to `true`. |
161
163
  | `include_page_context` | `boolean` | When true (default), enriches events with `page`, `title`, `referrer`, `site` (full `url` is only sent if you pass it explicitly in payload). |
162
164
  | `cross_domain` | `CrossDomainConfig` | Controls cookie domain, bridge URL, and allowlist for multi-domain attribution. |
163
165
  | `site_id` | `string` | Optional UUID for associating events with a registered site. Included in all event payloads as `site_id`. |
164
166
  | `site_host` | `string` | Optional site host for audit/debug purposes. Included in all event payloads as `site_host`. If not provided, browser SDK uses `window.location.host`. |
167
+ | `visitor_id` | `string` | Optional trusted first-party anonymous id to adopt when no visitor id is stored yet (ignored if storage already has one). Use `setVisitorId()` when the id arrives after init. |
165
168
  | `capture_query_params` | `string[]` | Additional query keys to capture for attribution (merged into the default allowlist). |
166
169
  | `capture_all_query_params` | `boolean` | Capture all query params after denylist/limits. Defaults to `false`. |
167
170
  | `query_param_denylist` | `string[]` | Query keys that are never captured (exact-key matching; default includes sensitive keys like `email`, `token`, `password`). |
@@ -194,7 +197,21 @@ When you don't have an authenticated user ID, you can pass the SDK's visitor ID
194
197
 
195
198
  **Browser:** Call `Mark.getVisitorId()` after init. If you use `require_consent: true` or `'auto'`, it returns `undefined` until consent is granted. Once available, send it in a header or request body to your API and use it as the `visitor_id` when calling the Node SDK or your ingestion.
196
199
 
197
- **Node:** Call `mark.getVisitorId()` to read the visitor ID from the storage you passed to `createNodeMark` (e.g. from `storageDefaults.visitor_id`). Use it to associate server events with the same visitor.
200
+ **Adopting an external anonymous id:** When your platform already provides a stable first-party anonymous key (for example a Shopify client id), pass it at init or call `Mark.setVisitorId()` once it is available:
201
+
202
+ ```ts
203
+ Mark.init({
204
+ key: 'pk_xxxxx',
205
+ visitor_id: platformClientId, // only adopted when storage is empty
206
+ });
207
+
208
+ // Or when the id arrives asynchronously:
209
+ Mark.setVisitorId(platformClientId);
210
+ ```
211
+
212
+ Use this for trusted first-party ids only — not email addresses, order ids, or values from unvalidated URL parameters. `identify()` sets `user_id` (known person), not `visitor_id` (anonymous device/browser key). After `Mark.reset()`, the SDK rotates to a fresh generated visitor id.
213
+
214
+ **Node:** Call `mark.getVisitorId()` to read the visitor ID from the storage you passed to `createNodeMark` (e.g. from `storageDefaults.visitor_id` or `Mark.init`-equivalent config `visitor_id`). Use `mark.setVisitorId()` to update the id for the current server-side client instance.
198
215
 
199
216
  The visitor ID is a pseudonymous, SDK-scoped identifier. Do not use it as a cross-site or long-term user identity; use it only for joining browser and server events within your attribution flow.
200
217
 
@@ -233,7 +250,8 @@ All of the following are **opt-in** under `autocapture`. They respect the same c
233
250
  | `click` | `data-mark-event` value, or `click` | Listens for clicks. **Default:** only elements matching `[data-mark-event]`; set `click: { selector: '.my-tracked' }` to use a custom selector. Sends `element_id`, `element_classes`, truncated `text`, and `href` when applicable. |
234
251
  | `form_submit` | `form_submit` | Listens for `submit`; sends `form_id`, `form_name`, `action`. |
235
252
  | `outbound_link` | `outbound_link_click` | On click, if the link target is another origin, sends `href`. Uses `preferBeacon: true` so the event is more likely to fire before navigation. |
236
- | `scroll_depth` | `scroll_depth` | Fires at **25%, 50%, 75%, and 100%** of vertical scroll depth (once each per page load). Payload includes `percent`. |
253
+ | `scroll_depth` | `scroll_depth` | Fires at **25%, 50%, 75%, and 100%** of vertical scroll depth (once each per **logical page**; resets on SPA route changes when route tracking is enabled). Payload includes `percent`. |
254
+ | `page_engagement` | `page_engagement` | One summary per **logical page** when the user navigates away or unloads (not on tab background alone). Emits if accumulated **visible** active time is at least `min_active_ms` (default **10000**). Tab hide/show pauses the clock. Payload includes `page`, `active_time_ms`, `total_time_ms`, and `max_scroll_percent`. Uses `sendBeacon` on exit by default. |
237
255
  | `web_vitals` | `web_vital` | Lazy-loads the `web-vitals` dependency and reports **LCP, CLS, INP, and TTFB** with `metric`, `value`, optional `rating`, and `metric_id`. If the module fails to load, only `debug: true` logs a warning. |
238
256
 
239
257
  Example:
@@ -249,11 +267,16 @@ Mark.init({
249
267
  form_submit: true,
250
268
  outbound_link: true,
251
269
  scroll_depth: true,
270
+ page_engagement: true,
252
271
  web_vitals: true,
253
272
  },
254
273
  });
255
274
  ```
256
275
 
276
+ ## Session fields on events
277
+
278
+ Every tracked event includes `session_id` when a session exists. Events also include `session_started_at` (ISO timestamp) and `session_elapsed_ms` (milliseconds since that session started). The same session fields are included on `identify` payloads after the first tracked activity in a session. Session rotation uses `session_timeout_ms` (default 30 minutes of inactivity) and UTC day boundaries; the event that triggers rotation keeps the **previous** session identifiers. Session duration for reporting is typically derived server-side from the event stream (`max(occurred_at) - session_started_at` per `session_id`).
279
+
257
280
  ## Flagging internal traffic
258
281
 
259
282
  Use the `is_internal` marker to keep QA sessions, staff testing, or automated smoke tests out of customer-facing reports without dropping them on the client. The SDK supports it in two complementary ways: