@nexly/core 0.7.0 → 0.8.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.
@@ -1,9 +1,26 @@
1
1
  /**
2
- * Collects browser and environment metadata for the ingest `context` object (snake_case keys).
3
- * Includes `visitor_id` and `session_id` from session management.
4
- * Safe to call only in a browser; returns `{}` on the server or if APIs throw.
2
+ * Browser metadata collection, split into session-stable and per-event fields.
3
+ *
4
+ * - `collectSessionMeta()` stable within a session (UA, screen, language, etc.), sent once.
5
+ * - `collectEventMeta()` — per-event volatile fields (url, pathname, referrer), sent with every event.
6
+ * - `collectBrowserMeta()` — legacy: merges both for backward compatibility.
5
7
  *
6
8
  * Does not include geo (country/region/city) — that requires server-side IP lookup.
7
9
  */
10
+ /**
11
+ * Session-stable metadata: user agent, screen, language, timezone, etc.
12
+ * Collected once per session and sent as `session_context` on the wire.
13
+ */
14
+ export declare function collectSessionMeta(): Record<string, unknown>;
15
+ /**
16
+ * Per-event volatile metadata: only fields that actually change between events.
17
+ * Includes `visitor_id` and `session_id` for server-side extraction,
18
+ * plus `url` and `pathname` to track SPA navigation.
19
+ */
20
+ export declare function collectEventMeta(): Record<string, unknown>;
21
+ /**
22
+ * Legacy: full browser metadata (session + event fields merged).
23
+ * Kept for backward compatibility with `Nexly.event()` and custom events.
24
+ */
8
25
  export declare function collectBrowserMeta(): Record<string, unknown>;
9
26
  //# sourceMappingURL=browser-meta.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser-meta.d.ts","sourceRoot":"","sources":["../src/browser-meta.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmG5D"}
1
+ {"version":3,"file":"browser-meta.d.ts","sourceRoot":"","sources":["../src/browser-meta.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkBH;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAiF5D;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAc1D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE5D"}
@@ -1,7 +1,9 @@
1
1
  /**
2
- * Collects browser and environment metadata for the ingest `context` object (snake_case keys).
3
- * Includes `visitor_id` and `session_id` from session management.
4
- * Safe to call only in a browser; returns `{}` on the server or if APIs throw.
2
+ * Browser metadata collection, split into session-stable and per-event fields.
3
+ *
4
+ * - `collectSessionMeta()` stable within a session (UA, screen, language, etc.), sent once.
5
+ * - `collectEventMeta()` — per-event volatile fields (url, pathname, referrer), sent with every event.
6
+ * - `collectBrowserMeta()` — legacy: merges both for backward compatibility.
5
7
  *
6
8
  * Does not include geo (country/region/city) — that requires server-side IP lookup.
7
9
  */
@@ -14,19 +16,18 @@ function safe(fn) {
14
16
  return undefined;
15
17
  }
16
18
  }
17
- export function collectBrowserMeta() {
19
+ function filterEmpty(obj) {
20
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined && v !== '' && !(Array.isArray(v) && v.length === 0)));
21
+ }
22
+ /**
23
+ * Session-stable metadata: user agent, screen, language, timezone, etc.
24
+ * Collected once per session and sent as `session_context` on the wire.
25
+ */
26
+ export function collectSessionMeta() {
18
27
  if (typeof window === 'undefined' || typeof navigator === 'undefined') {
19
28
  return {};
20
29
  }
21
30
  const out = {};
22
- out.visitor_id = safe(() => getVisitorId());
23
- out.session_id = safe(() => getSessionId());
24
- out.url = safe(() => window.location.href);
25
- out.pathname = safe(() => window.location.pathname);
26
- const ref = safe(() => document.referrer);
27
- if (ref) {
28
- out.referrer = ref;
29
- }
30
31
  out.language = safe(() => navigator.language);
31
32
  const langs = safe(() => navigator.languages);
32
33
  if (langs?.length) {
@@ -41,10 +42,14 @@ export function collectBrowserMeta() {
41
42
  out.viewport_width = safe(() => window.innerWidth);
42
43
  out.viewport_height = safe(() => window.innerHeight);
43
44
  out.device_pixel_ratio = safe(() => window.devicePixelRatio);
45
+ const ref = safe(() => document.referrer);
46
+ if (ref) {
47
+ out.referrer = ref;
48
+ }
49
+ out.online = safe(() => navigator.onLine);
44
50
  out.user_agent = safe(() => navigator.userAgent);
45
51
  out.navigator_platform = safe(() => navigator.platform);
46
52
  out.cookie_enabled = safe(() => navigator.cookieEnabled);
47
- out.online = safe(() => navigator.onLine);
48
53
  out.hardware_concurrency = safe(() => navigator.hardwareConcurrency);
49
54
  out.max_touch_points = safe(() => navigator.maxTouchPoints);
50
55
  const conn = safe(() => navigator.connection);
@@ -87,15 +92,28 @@ export function collectBrowserMeta() {
87
92
  out.prefers_reduced_motion = reduced;
88
93
  }
89
94
  }
90
- if (typeof document !== 'undefined' && document.documentElement) {
91
- const docW = safe(() => document.documentElement.clientWidth);
92
- const docH = safe(() => document.documentElement.clientHeight);
93
- if (docW !== undefined) {
94
- out.document_client_width = docW;
95
- }
96
- if (docH !== undefined) {
97
- out.document_client_height = docH;
98
- }
95
+ return filterEmpty(out);
96
+ }
97
+ /**
98
+ * Per-event volatile metadata: only fields that actually change between events.
99
+ * Includes `visitor_id` and `session_id` for server-side extraction,
100
+ * plus `url` and `pathname` to track SPA navigation.
101
+ */
102
+ export function collectEventMeta() {
103
+ if (typeof window === 'undefined' || typeof navigator === 'undefined') {
104
+ return {};
99
105
  }
100
- return Object.fromEntries(Object.entries(out).filter(([, v]) => v !== undefined && v !== '' && !(Array.isArray(v) && v.length === 0)));
106
+ const out = {};
107
+ out.visitor_id = safe(() => getVisitorId());
108
+ out.session_id = safe(() => getSessionId());
109
+ out.url = safe(() => window.location.href);
110
+ out.pathname = safe(() => window.location.pathname);
111
+ return filterEmpty(out);
112
+ }
113
+ /**
114
+ * Legacy: full browser metadata (session + event fields merged).
115
+ * Kept for backward compatibility with `Nexly.event()` and custom events.
116
+ */
117
+ export function collectBrowserMeta() {
118
+ return { ...collectSessionMeta(), ...collectEventMeta() };
101
119
  }
package/dist/collect.d.ts CHANGED
@@ -5,8 +5,9 @@ export type IngestCredentials = {
5
5
  };
6
6
  /**
7
7
  * Event envelope:
8
- * - `context` — built-in traffic / device / locale (referrer, url, utm_*, os, device, ). Not owner-registered.
8
+ * - `context` — per-event volatile metadata (url, pathname, viewport, etc.).
9
9
  * - `data` — owner-defined custom properties (later: only keys registered for the app).
10
+ * - `session_context` — session-stable metadata (UA, screen, language), sent once per session.
10
11
  */
11
12
  export type TrackEventInput = {
12
13
  credentials: IngestCredentials;
@@ -14,6 +15,7 @@ export type TrackEventInput = {
14
15
  eventType: string;
15
16
  context?: Record<string, unknown>;
16
17
  data?: Record<string, unknown>;
18
+ sessionContext?: Record<string, unknown>;
17
19
  };
18
20
  /** Full send: transport URL + event envelope. */
19
21
  export type SendBeaconCollectInput = TrackEventInput & {
@@ -26,6 +28,7 @@ export type CollectCredentials = {
26
28
  /**
27
29
  * Builds the JSON body for POST /v1/collect (snake_case wire format).
28
30
  * Duplicates `context.path` to top-level `path` when present.
31
+ * Includes `session_context` when provided (first event of a session).
29
32
  */
30
33
  export declare function buildCollectPayload(input: TrackEventInput): Record<string, unknown>;
31
34
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../src/collect.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED;;;;GAIG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,EAAE,iBAAiB,CAAA;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAC/B,CAAA;AAED,iDAAiD;AACjD,MAAM,MAAM,sBAAsB,GAAG,eAAe,GAAG;IACrD,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,6GAA6G;AAC7G,MAAM,MAAM,kBAAkB,GAAG;IAC/B,UAAU,EAAE,MAAM,CAAA;CACnB,GAAG,iBAAiB,CAAA;AAMrB;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAiBnF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CASxE"}
1
+ {"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../src/collect.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;CACjB,CAAA;AAED;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,WAAW,EAAE,iBAAiB,CAAA;IAC9B,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACzC,CAAA;AAED,iDAAiD;AACjD,MAAM,MAAM,sBAAsB,GAAG,eAAe,GAAG;IACrD,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA;AAED,6GAA6G;AAC7G,MAAM,MAAM,kBAAkB,GAAG;IAC/B,UAAU,EAAE,MAAM,CAAA;CACnB,GAAG,iBAAiB,CAAA;AAMrB;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqBnF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,GAAG,OAAO,CASxE"}
package/dist/collect.js CHANGED
@@ -4,6 +4,7 @@ function isPlainObject(v) {
4
4
  /**
5
5
  * Builds the JSON body for POST /v1/collect (snake_case wire format).
6
6
  * Duplicates `context.path` to top-level `path` when present.
7
+ * Includes `session_context` when provided (first event of a session).
7
8
  */
8
9
  export function buildCollectPayload(input) {
9
10
  const context = isPlainObject(input.context) ? input.context : {};
@@ -21,6 +22,10 @@ export function buildCollectPayload(input) {
21
22
  if (path) {
22
23
  body.path = path;
23
24
  }
25
+ const sc = isPlainObject(input.sessionContext) ? input.sessionContext : null;
26
+ if (sc && Object.keys(sc).length > 0) {
27
+ body.session_context = sc;
28
+ }
24
29
  return body;
25
30
  }
26
31
  /**
@@ -1,8 +1,12 @@
1
1
  import { type CollectCredentials } from '../collect.js';
2
2
  type StopFn = () => void;
3
3
  /**
4
- * Attaches engagement listeners (scroll, click, input focus, visibility, heartbeat).
4
+ * Attaches engagement listeners (scroll, click, input focus, visibility, keepalive heartbeat).
5
5
  * Returns a cleanup function that removes all listeners and timers.
6
+ *
7
+ * Duration is tracked client-side: a 1s tick increments `activeSeconds` only while visible.
8
+ * A lightweight keepalive heartbeat fires every 60s for the realtime dashboard.
9
+ * Summary events are sent on visibility hidden and beforeunload.
6
10
  */
7
11
  export declare function startEngagementTracking(creds: CollectCredentials): StopFn;
8
12
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"engagement.d.ts","sourceRoot":"","sources":["../../src/events/engagement.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAqB1E,KAAK,MAAM,GAAG,MAAM,IAAI,CAAA;AAExB;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CA2HzE"}
1
+ {"version":3,"file":"engagement.d.ts","sourceRoot":"","sources":["../../src/events/engagement.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAyB1E,KAAK,MAAM,GAAG,MAAM,IAAI,CAAA;AAExB;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,kBAAkB,GAAG,MAAM,CAmIzE"}
@@ -1,24 +1,27 @@
1
- import { collectBrowserMeta } from '../browser-meta.js';
1
+ import { collectEventMeta, collectSessionMeta } from '../browser-meta.js';
2
2
  import { sendBeaconCollect } from '../collect.js';
3
3
  const CLICK_THROTTLE_MS = 500;
4
- const HEARTBEAT_INTERVAL_MS = 30_000;
4
+ const KEEPALIVE_INTERVAL_MS = 60_000;
5
5
  const SCROLL_DEBOUNCE_MS = 300;
6
- function makeContext() {
7
- return collectBrowserMeta();
8
- }
9
- function sendEngagement(creds, eventName, eventType, data) {
6
+ const ACTIVE_TICK_MS = 1_000;
7
+ function sendEngagement(creds, eventName, eventType, data, sessionContext) {
10
8
  sendBeaconCollect({
11
9
  collectUrl: creds.collectUrl,
12
10
  credentials: { appId: creds.appId, apiToken: creds.apiToken },
13
11
  eventName,
14
12
  eventType,
15
- context: makeContext(),
13
+ context: collectEventMeta(),
16
14
  data,
15
+ sessionContext,
17
16
  });
18
17
  }
19
18
  /**
20
- * Attaches engagement listeners (scroll, click, input focus, visibility, heartbeat).
19
+ * Attaches engagement listeners (scroll, click, input focus, visibility, keepalive heartbeat).
21
20
  * Returns a cleanup function that removes all listeners and timers.
21
+ *
22
+ * Duration is tracked client-side: a 1s tick increments `activeSeconds` only while visible.
23
+ * A lightweight keepalive heartbeat fires every 60s for the realtime dashboard.
24
+ * Summary events are sent on visibility hidden and beforeunload.
22
25
  */
23
26
  export function startEngagementTracking(creds) {
24
27
  if (typeof window === 'undefined' || typeof document === 'undefined') {
@@ -26,6 +29,20 @@ export function startEngagementTracking(creds) {
26
29
  }
27
30
  const ac = new AbortController();
28
31
  const { signal } = ac;
32
+ let sessionContextSent = false;
33
+ function getSessionContextOnce() {
34
+ if (sessionContextSent)
35
+ return undefined;
36
+ sessionContextSent = true;
37
+ return collectSessionMeta();
38
+ }
39
+ // ---- client-side active duration tracking ----
40
+ let activeSeconds = 0;
41
+ const activeTickId = setInterval(() => {
42
+ if (document.visibilityState === 'visible') {
43
+ activeSeconds++;
44
+ }
45
+ }, ACTIVE_TICK_MS);
29
46
  // ---- scroll depth ----
30
47
  let maxScrollPercent = 0;
31
48
  let scrollTimer = null;
@@ -91,50 +108,36 @@ export function startEngagementTracking(creds) {
91
108
  });
92
109
  }
93
110
  document.addEventListener('focusin', onFocusIn, { signal });
94
- // ---- visibility / visit duration ----
95
- let visibleSince = document.visibilityState === 'visible' ? Date.now() : 0;
96
- let accumulatedVisibleMs = 0;
111
+ // ---- visibility / session summary ----
97
112
  function onVisibilityChange() {
98
113
  if (document.visibilityState === 'hidden') {
99
- if (visibleSince > 0) {
100
- accumulatedVisibleMs += Date.now() - visibleSince;
101
- visibleSince = 0;
102
- }
103
114
  flushScrollDepth();
104
- const secs = Math.round(accumulatedVisibleMs / 1000);
105
- if (secs > 0) {
106
- sendEngagement(creds, 'visibility_change', 'engagement', { visible_seconds: secs, state: 'hidden' });
115
+ if (activeSeconds > 0) {
116
+ sendEngagement(creds, 'session_ping', 'lifecycle', { active_seconds: activeSeconds });
107
117
  }
108
118
  }
109
- else {
110
- visibleSince = Date.now();
111
- }
112
119
  }
113
120
  document.addEventListener('visibilitychange', onVisibilityChange, { signal });
114
121
  function onBeforeUnload() {
115
- if (visibleSince > 0) {
116
- accumulatedVisibleMs += Date.now() - visibleSince;
117
- visibleSince = 0;
118
- }
119
122
  flushScrollDepth();
120
- const secs = Math.round(accumulatedVisibleMs / 1000);
121
- sendEngagement(creds, 'visibility_change', 'engagement', { visible_seconds: secs, state: 'unload' });
123
+ sendEngagement(creds, 'session_end', 'lifecycle', { active_seconds: activeSeconds });
122
124
  }
123
125
  window.addEventListener('beforeunload', onBeforeUnload, { signal });
124
- // ---- heartbeat ----
125
- const startTime = Date.now();
126
- function sendHeartbeat() {
126
+ // ---- keepalive heartbeat (60s, lightweight, for realtime dashboard) ----
127
+ function sendKeepalive() {
127
128
  if (document.visibilityState !== 'visible')
128
129
  return;
129
- const elapsed = Math.round((Date.now() - startTime) / 1000);
130
- sendEngagement(creds, 'heartbeat', 'engagement', { elapsed_seconds: elapsed });
130
+ sendEngagement(creds, 'heartbeat', 'lifecycle', { active_seconds: activeSeconds }, getSessionContextOnce());
131
131
  }
132
- const heartbeatId = setInterval(sendHeartbeat, HEARTBEAT_INTERVAL_MS);
132
+ // Send first heartbeat immediately to register the session context
133
+ sendKeepalive();
134
+ const keepaliveId = setInterval(sendKeepalive, KEEPALIVE_INTERVAL_MS);
133
135
  // ---- cleanup ----
134
136
  return () => {
135
137
  ac.abort();
136
138
  if (scrollTimer)
137
139
  clearTimeout(scrollTimer);
138
- clearInterval(heartbeatId);
140
+ clearInterval(activeTickId);
141
+ clearInterval(keepaliveId);
139
142
  };
140
143
  }
@@ -2,7 +2,8 @@ import { type CollectCredentials } from '../collect.js';
2
2
  /** Current document path, or `/` outside a browser. */
3
3
  export declare function getDefaultPathname(): string;
4
4
  /**
5
- * Page view: fills `context` with {@link collectBrowserMeta} and sets `path` (overrides pathname from the location snapshot).
5
+ * Page view: sends slim `context` (per-event meta) plus `session_context` (session-stable meta).
6
+ * The backend upserts the session row from `session_context` on every pageview.
6
7
  */
7
8
  export declare function sendPageViewBeacon(creds: CollectCredentials, path?: string): boolean;
8
9
  //# sourceMappingURL=pageview.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"pageview.d.ts","sourceRoot":"","sources":["../../src/events/pageview.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAE1E,uDAAuD;AACvD,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,EAAE,IAAI,GAAE,MAA6B,GAAG,OAAO,CAY1G"}
1
+ {"version":3,"file":"pageview.d.ts","sourceRoot":"","sources":["../../src/events/pageview.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAA;AAE1E,uDAAuD;AACvD,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,EAAE,IAAI,GAAE,MAA6B,GAAG,OAAO,CAa1G"}
@@ -1,11 +1,12 @@
1
- import { collectBrowserMeta } from '../browser-meta.js';
1
+ import { collectEventMeta, collectSessionMeta } from '../browser-meta.js';
2
2
  import { sendBeaconCollect } from '../collect.js';
3
3
  /** Current document path, or `/` outside a browser. */
4
4
  export function getDefaultPathname() {
5
5
  return typeof window !== 'undefined' ? window.location.pathname : '/';
6
6
  }
7
7
  /**
8
- * Page view: fills `context` with {@link collectBrowserMeta} and sets `path` (overrides pathname from the location snapshot).
8
+ * Page view: sends slim `context` (per-event meta) plus `session_context` (session-stable meta).
9
+ * The backend upserts the session row from `session_context` on every pageview.
9
10
  */
10
11
  export function sendPageViewBeacon(creds, path = getDefaultPathname()) {
11
12
  return sendBeaconCollect({
@@ -14,9 +15,10 @@ export function sendPageViewBeacon(creds, path = getDefaultPathname()) {
14
15
  eventName: 'pageview',
15
16
  eventType: 'pageview',
16
17
  context: {
17
- ...collectBrowserMeta(),
18
+ ...collectEventMeta(),
18
19
  path,
19
20
  },
20
21
  data: {},
22
+ sessionContext: collectSessionMeta(),
21
23
  });
22
24
  }
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Browser event ingest client (public API).
3
3
  */
4
4
  export { Nexly, type NexlyEventInput, type NexlyEventType, type NexlyInit } from './nexly.js';
5
- export { collectBrowserMeta } from './browser-meta.js';
5
+ export { collectBrowserMeta, collectSessionMeta, collectEventMeta } from './browser-meta.js';
6
6
  export { getSessionId, getVisitorId } from './session.js';
7
7
  export { buildCollectPayload, sendBeaconCollect, type CollectCredentials, type IngestCredentials, type SendBeaconCollectInput, type TrackEventInput, } from './collect.js';
8
8
  export { getDefaultPathname, sendPageViewBeacon, startEngagementTracking } from './events/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,KAAK,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AACtD,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,GACrB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,KAAK,EAAE,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAC7F,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AAC5F,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,eAAe,GACrB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAA"}
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Browser event ingest client (public API).
3
3
  */
4
4
  export { Nexly } from './nexly.js';
5
- export { collectBrowserMeta } from './browser-meta.js';
5
+ export { collectBrowserMeta, collectSessionMeta, collectEventMeta } from './browser-meta.js';
6
6
  export { getSessionId, getVisitorId } from './session.js';
7
7
  export { buildCollectPayload, sendBeaconCollect, } from './collect.js';
8
8
  export { getDefaultPathname, sendPageViewBeacon, startEngagementTracking } from './events/index.js';
package/dist/nexly.d.ts CHANGED
@@ -37,7 +37,8 @@ export declare class Nexly {
37
37
  */
38
38
  pageview(path?: string): boolean;
39
39
  /**
40
- * Sends a custom event. Default context is browser meta + path; pass `context` to merge or override fields.
40
+ * Sends a custom event. Default context is per-event meta + path; pass `context` to merge or override fields.
41
+ * Includes session_context so the backend can upsert session metadata.
41
42
  */
42
43
  event(input: NexlyEventInput): boolean;
43
44
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"nexly.d.ts","sourceRoot":"","sources":["../src/nexly.ts"],"names":[],"mappings":"AAMA,kGAAkG;AAClG,MAAM,MAAM,SAAS,GAAG;IACtB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,gEAAgE;AAChE,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,YAAY,GAAG,QAAQ,CAAA;AAEjE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,cAAc,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,oGAAoG;IACpG,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC,CAAA;AAED;;GAEG;AACH,qBAAa,KAAK;IAChB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAqB;IAE5C;;;OAGG;IACH,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,KAAK;IAKtC,uFAAuF;IACvF,MAAM,CAAC,WAAW,IAAI,KAAK,GAAG,IAAI;IAIlC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;gBAER,IAAI,EAAE,SAAS;IAM3B,OAAO,KAAK,WAAW,GAMtB;IAED,OAAO,CAAC,cAAc;IAOtB;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO;IAIhC;;OAEG;IACH,KAAK,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO;IActC;;OAEG;IACH,eAAe,IAAI,MAAM,IAAI;CAG9B"}
1
+ {"version":3,"file":"nexly.d.ts","sourceRoot":"","sources":["../src/nexly.ts"],"names":[],"mappings":"AAMA,kGAAkG;AAClG,MAAM,MAAM,SAAS,GAAG;IACtB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,gEAAgE;AAChE,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,YAAY,GAAG,QAAQ,CAAA;AAEjE,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,cAAc,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC9B,oGAAoG;IACpG,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC,CAAA;AAED;;GAEG;AACH,qBAAa,KAAK;IAChB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAqB;IAE5C;;;OAGG;IACH,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,KAAK;IAKtC,uFAAuF;IACvF,MAAM,CAAC,WAAW,IAAI,KAAK,GAAG,IAAI;IAIlC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;gBAER,IAAI,EAAE,SAAS;IAM3B,OAAO,KAAK,WAAW,GAMtB;IAED,OAAO,CAAC,cAAc;IAOtB;;OAEG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO;IAIhC;;;OAGG;IACH,KAAK,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO;IAetC;;OAEG;IACH,eAAe,IAAI,MAAM,IAAI;CAG9B"}
package/dist/nexly.js CHANGED
@@ -1,4 +1,4 @@
1
- import { collectBrowserMeta } from './browser-meta.js';
1
+ import { collectEventMeta, collectSessionMeta } from './browser-meta.js';
2
2
  import { sendBeaconCollect } from './collect.js';
3
3
  import { getDefaultPathname, sendPageViewBeacon, startEngagementTracking } from './events/index.js';
4
4
  const DEFAULT_COLLECT_URL = 'https://gate.nexly.to/v1/collect';
@@ -36,7 +36,7 @@ export class Nexly {
36
36
  }
37
37
  defaultContext() {
38
38
  return {
39
- ...collectBrowserMeta(),
39
+ ...collectEventMeta(),
40
40
  path: getDefaultPathname(),
41
41
  };
42
42
  }
@@ -47,7 +47,8 @@ export class Nexly {
47
47
  return sendPageViewBeacon(this.credentials, path ?? getDefaultPathname());
48
48
  }
49
49
  /**
50
- * Sends a custom event. Default context is browser meta + path; pass `context` to merge or override fields.
50
+ * Sends a custom event. Default context is per-event meta + path; pass `context` to merge or override fields.
51
+ * Includes session_context so the backend can upsert session metadata.
51
52
  */
52
53
  event(input) {
53
54
  const context = input.context
@@ -60,6 +61,7 @@ export class Nexly {
60
61
  eventType: input.type,
61
62
  context,
62
63
  data: input.data ?? {},
64
+ sessionContext: collectSessionMeta(),
63
65
  });
64
66
  }
65
67
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexly/core",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Nexly browser ingest core (sendBeacon, Nexly client, context, engagement)",
5
5
  "license": "MIT",
6
6
  "keywords": [