@releval/tracker 1.0.0-bootstrap.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/LICENSE +190 -0
- package/README.md +366 -0
- package/dist/ResultImpressionCollector.d.cts +762 -0
- package/dist/ResultImpressionCollector.d.mts +762 -0
- package/dist/react.cjs +180 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +139 -0
- package/dist/react.d.mts +139 -0
- package/dist/react.mjs +174 -0
- package/dist/react.mjs.map +1 -0
- package/dist/releval-tracker.cjs +8 -0
- package/dist/releval-tracker.d.cts +135 -0
- package/dist/releval-tracker.d.mts +135 -0
- package/dist/releval-tracker.global.js +2 -0
- package/dist/releval-tracker.mjs +3 -0
- package/dist/tracker.cjs +1663 -0
- package/dist/tracker.cjs.map +1 -0
- package/dist/tracker.mjs +1634 -0
- package/dist/tracker.mjs.map +1 -0
- package/package.json +131 -0
|
@@ -0,0 +1,762 @@
|
|
|
1
|
+
//#region src/logging/Logger.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Logging interface for internal tracker diagnostics. Implement this to
|
|
4
|
+
* integrate the tracker's log output with your application's logging system,
|
|
5
|
+
* then pass it as the `logger` option. To fan out to several destinations
|
|
6
|
+
* (e.g. the console AND your own service), pass a logger that wraps both.
|
|
7
|
+
*/
|
|
8
|
+
interface Logger {
|
|
9
|
+
/**
|
|
10
|
+
* Logs a debug message
|
|
11
|
+
* @param msg the message
|
|
12
|
+
* @param data the additional data
|
|
13
|
+
*/
|
|
14
|
+
debug(msg: string, ...data: any[]): void;
|
|
15
|
+
/**
|
|
16
|
+
* Logs an info message
|
|
17
|
+
* @param msg the message
|
|
18
|
+
* @param data the additional data
|
|
19
|
+
*/
|
|
20
|
+
info(msg: string, ...data: any[]): void;
|
|
21
|
+
/**
|
|
22
|
+
* Logs a warn message
|
|
23
|
+
* @param msg the message
|
|
24
|
+
* @param data the additional data
|
|
25
|
+
*/
|
|
26
|
+
warn(msg: string, ...data: any[]): void;
|
|
27
|
+
/**
|
|
28
|
+
* Logs an error message
|
|
29
|
+
* @param msg the message
|
|
30
|
+
* @param data the additional data
|
|
31
|
+
*/
|
|
32
|
+
error(msg: string, ...data: any[]): void;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/attribution/AttributionStore.d.ts
|
|
36
|
+
/**
|
|
37
|
+
* The search context recorded for a result when it was clicked, used to
|
|
38
|
+
* attribute later conversion events (add_to_cart, purchase) for the same
|
|
39
|
+
* object back to the originating query.
|
|
40
|
+
*/
|
|
41
|
+
type AttributionRecord = {
|
|
42
|
+
/** The server-issued query id of the search that produced the result. */
|
|
43
|
+
queryId: string;
|
|
44
|
+
/** The absolute 1-based rank the result was clicked at, if recorded. */
|
|
45
|
+
ordinal?: number;
|
|
46
|
+
/** The query text as the user entered it, if recorded. */
|
|
47
|
+
query?: string;
|
|
48
|
+
};
|
|
49
|
+
//#endregion
|
|
50
|
+
//#region src/types/Event.d.ts
|
|
51
|
+
/**
|
|
52
|
+
* The core UBI event structure that is enriched and emitted to sinks.
|
|
53
|
+
*
|
|
54
|
+
* Only `action_name` is required when dispatching - the dispatcher auto-sets
|
|
55
|
+
* `timestamp` and enrichers populate contextual fields like `application`,
|
|
56
|
+
* `session_id`, `client_id`, and browser/page metadata before the event
|
|
57
|
+
* reaches the sink.
|
|
58
|
+
*
|
|
59
|
+
* @see https://o19s.github.io/ubi/schema/1.3.0/event.schema.json
|
|
60
|
+
*/
|
|
61
|
+
interface Event {
|
|
62
|
+
/**
|
|
63
|
+
* Name of the application integrated with UBI. Distinguishes event sources
|
|
64
|
+
* when multiple search UIs feed into the same backend.
|
|
65
|
+
* @example "primary-search"
|
|
66
|
+
* @example "type-ahead"
|
|
67
|
+
*/
|
|
68
|
+
application?: string;
|
|
69
|
+
/**
|
|
70
|
+
* The name of the action that triggered the event.
|
|
71
|
+
* Common values: `"click"`, `"add_to_cart"`, `"purchase"`, `"impression"`, `"view"`, `"watch"`.
|
|
72
|
+
* Any custom string is also accepted.
|
|
73
|
+
*/
|
|
74
|
+
action_name: string;
|
|
75
|
+
/**
|
|
76
|
+
* The unique identifier of the query that this event is associated with,
|
|
77
|
+
* typically a UUID. Links events back to the originating search.
|
|
78
|
+
* @example "00112233-4455-6677-8899-aabbccddeeff"
|
|
79
|
+
* @example "1234-user-5678"
|
|
80
|
+
*/
|
|
81
|
+
query_id?: string;
|
|
82
|
+
/**
|
|
83
|
+
* The session identifier, used to correlate interactions across page navigations
|
|
84
|
+
* and to track unique visits for both authenticated and anonymous users.
|
|
85
|
+
* Populated automatically by the SessionEnricher.
|
|
86
|
+
* @example "84266fdbd31d4c2c6d0665f7e8380fa3"
|
|
87
|
+
*/
|
|
88
|
+
session_id?: string;
|
|
89
|
+
/**
|
|
90
|
+
* A stable anonymous identifier for the client issuing events.
|
|
91
|
+
* This could be a unique browser, a microservice, or a crawling bot.
|
|
92
|
+
* Populated automatically by the ClientIdEnricher.
|
|
93
|
+
* @example "5e3b2a1c-8b7d-4f2e-a3d4-c9b2e1f3a4b5"
|
|
94
|
+
* @example "quepid-nightly-bot"
|
|
95
|
+
*/
|
|
96
|
+
client_id?: string;
|
|
97
|
+
/**
|
|
98
|
+
* The authenticated user identifier, if available.
|
|
99
|
+
* May be `undefined` for anonymous/unauthenticated users.
|
|
100
|
+
* @example "5e3b2a1c-8b7d-4f2e-a3d4-c9b2e1f3a4b5"
|
|
101
|
+
*/
|
|
102
|
+
user_id?: string;
|
|
103
|
+
/**
|
|
104
|
+
* When the event took place, formatted as an ISO 8601 date-time string.
|
|
105
|
+
* Auto-set by the dispatcher if not provided.
|
|
106
|
+
* @example "2018-11-13T20:20:39+00:00"
|
|
107
|
+
* @example "2018-11-13T20:20:39Z"
|
|
108
|
+
*/
|
|
109
|
+
timestamp?: string;
|
|
110
|
+
/**
|
|
111
|
+
* Groups related `action_name` values into logical categories.
|
|
112
|
+
* @example "QUERY"
|
|
113
|
+
* @example "CONVERSION"
|
|
114
|
+
*/
|
|
115
|
+
message_type?: string;
|
|
116
|
+
/**
|
|
117
|
+
* Optional text message for the log entry. For a `message_type` of `"QUERY"`,
|
|
118
|
+
* this would typically contain the search text.
|
|
119
|
+
*/
|
|
120
|
+
message?: string;
|
|
121
|
+
/** The query as the user entered it, before any normalization or processing. */
|
|
122
|
+
user_query?: string;
|
|
123
|
+
/**
|
|
124
|
+
* Contextual data attached to a tracked event describing what was interacted with
|
|
125
|
+
* and where it appeared on the page. Additional custom properties can be added via
|
|
126
|
+
* the index signature.
|
|
127
|
+
*/
|
|
128
|
+
event_attributes?: EventAttributes;
|
|
129
|
+
/**
|
|
130
|
+
* The public Site identifier issued by Releval when registering a Site.
|
|
131
|
+
* Stamped automatically by the OptionsEnricher from `TrackerOptions.siteId`.
|
|
132
|
+
* A Releval extension to the UBI schema: the server drops events whose
|
|
133
|
+
* `site_id` does not match a registered Site.
|
|
134
|
+
* @example "01J8ZP4Q9K7X2M5N6R8T0V3W1Y"
|
|
135
|
+
*/
|
|
136
|
+
site_id?: string;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Contextual data attached to a tracked event describing what was interacted with
|
|
140
|
+
* and where it appeared on the page. Additional custom properties can be added via
|
|
141
|
+
* the index signature.
|
|
142
|
+
*/
|
|
143
|
+
interface EventAttributes {
|
|
144
|
+
/**
|
|
145
|
+
* Identifies the object (e.g. product, document) that was interacted with.
|
|
146
|
+
* Additional custom properties can be added via the index signature.
|
|
147
|
+
*/
|
|
148
|
+
object?: EventObject;
|
|
149
|
+
/**
|
|
150
|
+
* Describes where an interaction occurred relative to other items on the page.
|
|
151
|
+
* Use `ordinal` for list/grid rank and `x`/`y` for coordinate-based positioning.
|
|
152
|
+
* Additional custom properties can be added via the index signature.
|
|
153
|
+
*/
|
|
154
|
+
position?: EventPosition;
|
|
155
|
+
/**
|
|
156
|
+
* A stable per-event identifier, set once by the dispatcher when the event
|
|
157
|
+
* is created. A Releval extension to the UBI schema. It lives under
|
|
158
|
+
* `event_attributes` because that is where the server preserves unknown
|
|
159
|
+
* fields, so delivery retries (which can duplicate) stay deduplicable in
|
|
160
|
+
* analysis: `LIMIT 1 BY event_attributes.event_id`.
|
|
161
|
+
* @example "01J8ZP4Q9K7X2M5N6R8T0V3W1Y"
|
|
162
|
+
*/
|
|
163
|
+
event_id?: string;
|
|
164
|
+
/**
|
|
165
|
+
* The tracker build that produced the event (`{ version }`), stamped by the
|
|
166
|
+
* dispatcher. A Releval extension, for supportability once several tracker
|
|
167
|
+
* versions are deployed across customer sites.
|
|
168
|
+
*/
|
|
169
|
+
tracker?: any;
|
|
170
|
+
[key: string]: any;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Identifies the object (e.g. product, document) that was interacted with.
|
|
174
|
+
* Additional custom properties can be added via the index signature.
|
|
175
|
+
*/
|
|
176
|
+
interface EventObject {
|
|
177
|
+
/**
|
|
178
|
+
* The identifier that uniquely locates the object within the document corpus.
|
|
179
|
+
* Variants should be incorporated, so for a red t-shirt use the SKU-level identifier.
|
|
180
|
+
* Always a string on the wire: the Releval server binds it strictly and rejects
|
|
181
|
+
* the whole batch when a number arrives.
|
|
182
|
+
* @example "XYZ-12345"
|
|
183
|
+
* @example "ISBN 0-061-96436-0"
|
|
184
|
+
*/
|
|
185
|
+
object_id: string;
|
|
186
|
+
/**
|
|
187
|
+
* The type/namespace of the object identifier.
|
|
188
|
+
* Common values: `"product"`, `"user"`, `"post"`, `"comment"`, `"video"`.
|
|
189
|
+
*/
|
|
190
|
+
object_id_type?: string;
|
|
191
|
+
/**
|
|
192
|
+
* The name of the field that stores the object identifier in the backend data store.
|
|
193
|
+
* If omitted, the search index's default primary identifier is used (e.g. `_id` in OpenSearch).
|
|
194
|
+
*/
|
|
195
|
+
object_id_field?: string;
|
|
196
|
+
/**
|
|
197
|
+
* The internal identifier that the search engine uses to index the object.
|
|
198
|
+
* For example, the `_id` field in OpenSearch indices - pass numeric ids as
|
|
199
|
+
* strings. Always a string on the wire: the Releval server binds it strictly
|
|
200
|
+
* and a number would reject the whole batch, the same hole `object_id` closes.
|
|
201
|
+
* @example "1"
|
|
202
|
+
* @example "123456"
|
|
203
|
+
*/
|
|
204
|
+
internal_id?: string;
|
|
205
|
+
[key: string]: any;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Describes where an interaction occurred relative to other items on the page.
|
|
209
|
+
* Use `ordinal` for list/grid rank and `x`/`y` for coordinate-based positioning.
|
|
210
|
+
* Additional custom properties can be added via the index signature.
|
|
211
|
+
*/
|
|
212
|
+
interface EventPosition {
|
|
213
|
+
/**
|
|
214
|
+
* The absolute, 1-based rank of the item across pagination:
|
|
215
|
+
* `(page - 1) * pageSize + positionOnPage` with `page` 1-based. It must equal
|
|
216
|
+
* index + 1 of the object in the `query_response_hit_ids` the backend sent to
|
|
217
|
+
* track-query for this `query_id`. For grid layouts this is left to right,
|
|
218
|
+
* ignoring wrapping.
|
|
219
|
+
* @example 1
|
|
220
|
+
* @example 21
|
|
221
|
+
*/
|
|
222
|
+
ordinal?: number;
|
|
223
|
+
/**
|
|
224
|
+
* The x coordinate on the screen where the event was triggered.
|
|
225
|
+
* Matches the Releval server's flat `x` field.
|
|
226
|
+
*/
|
|
227
|
+
x?: number;
|
|
228
|
+
/**
|
|
229
|
+
* The y coordinate on the screen where the event was triggered.
|
|
230
|
+
* Matches the Releval server's flat `y` field.
|
|
231
|
+
*/
|
|
232
|
+
y?: number;
|
|
233
|
+
[key: string]: any;
|
|
234
|
+
}
|
|
235
|
+
//#endregion
|
|
236
|
+
//#region src/enrichers/Enricher.d.ts
|
|
237
|
+
/**
|
|
238
|
+
* Mutates a UBI event to add contextual data before it reaches the sink. Built-in enrichers
|
|
239
|
+
* add fields like `application`, `session_id`, `client_id`, and browser metadata. Implement
|
|
240
|
+
* this interface to add custom fields (e.g. A/B test variant, feature flags) and register
|
|
241
|
+
* via `tracker.addEnricher()`.
|
|
242
|
+
*/
|
|
243
|
+
interface Enricher {
|
|
244
|
+
/**
|
|
245
|
+
* Enriches the event.
|
|
246
|
+
* @param event the event to enrich
|
|
247
|
+
*/
|
|
248
|
+
enrich(event: Event): void;
|
|
249
|
+
}
|
|
250
|
+
//#endregion
|
|
251
|
+
//#region src/sinks/Sink.d.ts
|
|
252
|
+
/**
|
|
253
|
+
* A destination that receives enriched UBI events. Built-in sinks send events to an API
|
|
254
|
+
* endpoint (BatchSink) or to the browser console (ConsoleSink). Implement this
|
|
255
|
+
* interface to send events to a custom destination and register via `tracker.addSink()`.
|
|
256
|
+
*/
|
|
257
|
+
interface Sink {
|
|
258
|
+
/**
|
|
259
|
+
* Emit the specified event to the destination.
|
|
260
|
+
* @remarks Implementations should allow errors to propagate. These are logged by the configured tracker logger.
|
|
261
|
+
* @param event The event to emit
|
|
262
|
+
*/
|
|
263
|
+
emit(event: Event): void;
|
|
264
|
+
}
|
|
265
|
+
//#endregion
|
|
266
|
+
//#region src/collectors/resolveResult.d.ts
|
|
267
|
+
/**
|
|
268
|
+
* The result data resolved from a DOM element by the default data-attribute
|
|
269
|
+
* convention shared by the declarative collectors.
|
|
270
|
+
*/
|
|
271
|
+
type ResolvedResultData = {
|
|
272
|
+
/** The identifier of the result object (e.g. product/SKU) - `data-object-id`. */
|
|
273
|
+
objectId: string;
|
|
274
|
+
/**
|
|
275
|
+
* The absolute, 1-based rank of the result across pagination -
|
|
276
|
+
* `data-ordinal`. `undefined` when missing, empty or not a positive
|
|
277
|
+
* integer; the collectors validate before anything is emitted.
|
|
278
|
+
*/
|
|
279
|
+
ordinal?: number;
|
|
280
|
+
/** The backend field the object id maps to - `data-object-id-field`. */
|
|
281
|
+
objectIdField?: string;
|
|
282
|
+
/**
|
|
283
|
+
* Overrides the emitted `action_name` (e.g. `"add_to_cart"`) -
|
|
284
|
+
* `data-action-name`. Clicks default to `"click"`.
|
|
285
|
+
*/
|
|
286
|
+
actionName?: string;
|
|
287
|
+
/**
|
|
288
|
+
* The server-issued `query_id` that produced the result - `data-query-id`
|
|
289
|
+
* on the nearest ancestor carrying it (the element itself counts).
|
|
290
|
+
*/
|
|
291
|
+
queryId?: string;
|
|
292
|
+
/** The query text as the user entered it - `data-query` on that ancestor. */
|
|
293
|
+
query?: string;
|
|
294
|
+
/**
|
|
295
|
+
* Any additional keys - `data-event-*` attributes read by the default
|
|
296
|
+
* resolver, or extra keys returned by a custom one - are persisted as-is
|
|
297
|
+
* under the event's `event_attributes`.
|
|
298
|
+
*/
|
|
299
|
+
[key: string]: unknown;
|
|
300
|
+
};
|
|
301
|
+
/**
|
|
302
|
+
* Reads the documented data-attribute convention off a result element:
|
|
303
|
+
*
|
|
304
|
+
* - `data-object-id`, `data-ordinal`, `data-object-id-field` and (optionally)
|
|
305
|
+
* `data-action-name` on the result element itself;
|
|
306
|
+
* - `data-event-*` on the result element for custom event attributes:
|
|
307
|
+
* `data-event-badge="sale"` becomes `badge: "sale"` (hyphens camelCase,
|
|
308
|
+
* underscores survive - `data-event-sale_price` -> `sale_price`); a value
|
|
309
|
+
* that looks like a JSON object or array is parsed as one
|
|
310
|
+
* (`data-event-filters='{"brand":"acme"}'`), any other value - scalars
|
|
311
|
+
* included - stays a string;
|
|
312
|
+
* - `data-query-id` and `data-query` on the nearest ancestor carrying
|
|
313
|
+
* `data-query-id` (typically the results container, rendered server-side).
|
|
314
|
+
*
|
|
315
|
+
* A missing, empty or non-positive-integer `data-ordinal` yields
|
|
316
|
+
* `undefined`, never 0 - a
|
|
317
|
+
* fabricated rank is indistinguishable from data downstream. The collectors
|
|
318
|
+
* validate what they need and warn (once) when a required field is absent.
|
|
319
|
+
*
|
|
320
|
+
* @param logger receives the one-time malformed-JSON warning; the collectors
|
|
321
|
+
* pass the tracker's logger.
|
|
322
|
+
*/
|
|
323
|
+
declare const readResultData: (element: HTMLElement, logger?: Logger) => ResolvedResultData;
|
|
324
|
+
//#endregion
|
|
325
|
+
//#region src/collectors/ResultClickCollector.d.ts
|
|
326
|
+
/**
|
|
327
|
+
* The result data resolved for a clicked element. `actionName` defaults to
|
|
328
|
+
* `"click"`; a click resolved to any other action (e.g. `"add_to_cart"`) is
|
|
329
|
+
* routed through `trackResultEvent`, where a missing `queryId`/`ordinal` is
|
|
330
|
+
* resolved from the attribution recorded when the result was clicked.
|
|
331
|
+
*/
|
|
332
|
+
type ResolvedResultClick = ResolvedResultData;
|
|
333
|
+
/**
|
|
334
|
+
* Resolves the result data for a clicked element. Return `undefined` to skip
|
|
335
|
+
* the click entirely. The default implementation reads the data-attribute
|
|
336
|
+
* convention described on {@link readResultData}.
|
|
337
|
+
*/
|
|
338
|
+
type ResultClickResolve = (element: HTMLElement, event: MouseEvent) => ResolvedResultClick | undefined;
|
|
339
|
+
/** Options for `Tracker.trackResultClicks`. */
|
|
340
|
+
type TrackResultClicksOptions = {
|
|
341
|
+
/**
|
|
342
|
+
* CSS selector of result elements. Clicks are matched via `closest()`, so a
|
|
343
|
+
* click on any descendant of a matching element counts.
|
|
344
|
+
*/
|
|
345
|
+
selector: string;
|
|
346
|
+
/** The root the single delegated listener attaches to. Defaults to `document`. */
|
|
347
|
+
root?: Document | Element;
|
|
348
|
+
/**
|
|
349
|
+
* Selector for elements whose clicks must NOT be reported as result clicks,
|
|
350
|
+
* checked inside the matched result (e.g. `'[data-add-to-cart]'` for a
|
|
351
|
+
* button nested in the card). Without it a click on the nested button would
|
|
352
|
+
* emit both its own event and a result click, inflating CTR.
|
|
353
|
+
*/
|
|
354
|
+
ignore?: string;
|
|
355
|
+
/** Custom resolver; defaults to the data-attribute convention. */
|
|
356
|
+
resolve?: ResultClickResolve;
|
|
357
|
+
};
|
|
358
|
+
//#endregion
|
|
359
|
+
//#region src/tracker.d.ts
|
|
360
|
+
/**
|
|
361
|
+
* Options shared by every {@link TrackerOptions} variant. Not used directly -
|
|
362
|
+
* see {@link TrackerOptions} for the endpoint pairing rules.
|
|
363
|
+
*/
|
|
364
|
+
type TrackerBaseOptions = {
|
|
365
|
+
/** The name of the application */
|
|
366
|
+
application: string;
|
|
367
|
+
/**
|
|
368
|
+
* The initial user id attached to events (`user_id`). Must be an opaque,
|
|
369
|
+
* pseudonymous identifier - never an email address or name. Change it
|
|
370
|
+
* mid-session (login/logout) with {@link Tracker.setUserId}.
|
|
371
|
+
*/
|
|
372
|
+
userId?: string;
|
|
373
|
+
/** Session inactivity timeout in milliseconds. Defaults to 30 minutes. */
|
|
374
|
+
sessionInactivityTimeoutMs?: number;
|
|
375
|
+
/** Maximum session duration in milliseconds. Defaults to 24 hours. */
|
|
376
|
+
maxSessionDurationMs?: number;
|
|
377
|
+
/**
|
|
378
|
+
* When true, the default console logger also prints debug/info
|
|
379
|
+
* diagnostics: every dispatched event and every delivery outcome. This is
|
|
380
|
+
* the one-line install check against a server that answers 202 to
|
|
381
|
+
* everything. Warnings and errors are printed regardless. Applies to the
|
|
382
|
+
* default `ConsoleLogger` only - a supplied `logger` receives all levels
|
|
383
|
+
* and filters for itself. Defaults to false.
|
|
384
|
+
*/
|
|
385
|
+
debug?: boolean;
|
|
386
|
+
/**
|
|
387
|
+
* A logger to receive internal diagnostics. Replaces the default
|
|
388
|
+
* `ConsoleLogger`, so pass one here to route (or silence) tracker logging
|
|
389
|
+
* instead of writing to the console. To fan out to several destinations,
|
|
390
|
+
* pass a logger that wraps them.
|
|
391
|
+
*/
|
|
392
|
+
logger?: Logger;
|
|
393
|
+
};
|
|
394
|
+
/**
|
|
395
|
+
* Configuration passed to `new Tracker()`. Delivering to Releval requires the
|
|
396
|
+
* pair: `endpointHost` (the base URL browsers use to reach the Releval
|
|
397
|
+
* deployment - not your site's own origin) and `siteId` (the public Site
|
|
398
|
+
* identifier issued by Releval). The server silently drops events whose
|
|
399
|
+
* `site_id` does not match a registered Site, so the type requires the two
|
|
400
|
+
* together. Omit `endpointHost` for development mode: events go to the
|
|
401
|
+
* console - or only to sinks added with `addSink`, once any are added - and
|
|
402
|
+
* nothing leaves the page.
|
|
403
|
+
*/
|
|
404
|
+
type TrackerOptions = TrackerBaseOptions & ({
|
|
405
|
+
/**
|
|
406
|
+
* Base URL browsers use to reach the Releval deployment, e.g.
|
|
407
|
+
* `"https://releval.example.com"` - not your site's own origin. A
|
|
408
|
+
* path prefix for a reverse proxy is allowed; events POST to
|
|
409
|
+
* `<endpointHost>/api/v1/ubi/track-event`.
|
|
410
|
+
*/
|
|
411
|
+
endpointHost: string;
|
|
412
|
+
/**
|
|
413
|
+
* The public Site identifier issued by Releval when registering a
|
|
414
|
+
* Site. Stamped on every event in the request body; identity travels
|
|
415
|
+
* in the payload, not a header, because `navigator.sendBeacon`
|
|
416
|
+
* cannot set custom headers on the unload path.
|
|
417
|
+
*/
|
|
418
|
+
siteId: string;
|
|
419
|
+
} | {
|
|
420
|
+
/** Omit to log events to the console instead of delivering (development mode). */
|
|
421
|
+
endpointHost?: undefined;
|
|
422
|
+
/** Without an endpoint the site id is optional; it is stamped when present. */
|
|
423
|
+
siteId?: string;
|
|
424
|
+
});
|
|
425
|
+
/**
|
|
426
|
+
* A reference to a search result that was interacted with. Used by the
|
|
427
|
+
* high-level `trackResult*` methods to build the UBI `object` + `position`
|
|
428
|
+
* attributes so callers do not hand-roll the event shape.
|
|
429
|
+
*/
|
|
430
|
+
type ResultRef = {
|
|
431
|
+
/** The identifier of the object (e.g. product/SKU) that was interacted with. */
|
|
432
|
+
objectId: string;
|
|
433
|
+
/**
|
|
434
|
+
* The absolute, 1-based rank of the result across pagination -
|
|
435
|
+
* `(page - 1) * pageSize + positionOnPage` with `page` 1-based - not the
|
|
436
|
+
* per-page position. It must equal index + 1 of this object in the
|
|
437
|
+
* `query_response_hit_ids` your backend sent to track-query for this
|
|
438
|
+
* `query_id`: either one query_id spans all pages with absolute ordinals,
|
|
439
|
+
* or each page fetch is its own track-query call with its own query_id.
|
|
440
|
+
*/
|
|
441
|
+
ordinal: number;
|
|
442
|
+
/** The backend field the object id maps to (UBI `object_id_field`). Optional. */
|
|
443
|
+
objectIdField?: string;
|
|
444
|
+
/**
|
|
445
|
+
* Any additional keys are persisted as-is under the event's
|
|
446
|
+
* `event_attributes` (e.g. `badge: "sale"`). Values must be
|
|
447
|
+
* JSON-serialisable. Keys the tracker itself owns - `object`, `position`,
|
|
448
|
+
* `event_id`, `tracker`, `page`, `browser` - are overwritten by it; use
|
|
449
|
+
* {@link Tracker.dispatch} for full control of the event shape.
|
|
450
|
+
*/
|
|
451
|
+
[key: string]: unknown;
|
|
452
|
+
};
|
|
453
|
+
/** Options for {@link Tracker.trackResultEvent}. */
|
|
454
|
+
type TrackResultEventOptions = {
|
|
455
|
+
/** The UBI `action_name` (e.g. `"add_to_cart"`, `"purchase"`, `"view"`). */
|
|
456
|
+
actionName: string;
|
|
457
|
+
/** The identifier of the object (e.g. product/SKU) the event is about. */
|
|
458
|
+
objectId: string;
|
|
459
|
+
/** The backend field the object id maps to (UBI `object_id_field`). Optional. */
|
|
460
|
+
objectIdField?: string;
|
|
461
|
+
/**
|
|
462
|
+
* The absolute, 1-based rank the result was returned at. Optional: when
|
|
463
|
+
* omitted it is resolved from the attribution recorded by
|
|
464
|
+
* {@link Tracker.trackResultClick} for the same `objectId`. When neither is
|
|
465
|
+
* available the event carries no position.
|
|
466
|
+
*/
|
|
467
|
+
ordinal?: number;
|
|
468
|
+
/**
|
|
469
|
+
* The server-issued `query_id` that produced this result. Optional: when
|
|
470
|
+
* omitted it is resolved from the attribution recorded by
|
|
471
|
+
* {@link Tracker.trackResultClick} for the same `objectId`, so a conversion
|
|
472
|
+
* on a later page joins to the originating query without the integrator
|
|
473
|
+
* threading it by hand. When neither is available the event is sent
|
|
474
|
+
* unattributed and a warning is logged once.
|
|
475
|
+
*/
|
|
476
|
+
queryId?: string;
|
|
477
|
+
/** The query text as the user entered it, if known (populates `user_query`). */
|
|
478
|
+
query?: string;
|
|
479
|
+
/**
|
|
480
|
+
* Any additional keys are persisted as-is under the event's
|
|
481
|
+
* `event_attributes` (e.g. `badge: "sale"`). Values must be
|
|
482
|
+
* JSON-serialisable. Keys the tracker itself owns - `object`, `position`,
|
|
483
|
+
* `event_id`, `tracker`, `page`, `browser` - are overwritten by it; use
|
|
484
|
+
* {@link Tracker.dispatch} for full control of the event shape.
|
|
485
|
+
*/
|
|
486
|
+
[key: string]: unknown;
|
|
487
|
+
};
|
|
488
|
+
/** Options for {@link Tracker.trackResultClick}. */
|
|
489
|
+
type TrackResultClickOptions = ResultRef & {
|
|
490
|
+
/** The server-issued `query_id` that produced this result. Required. */
|
|
491
|
+
queryId: string;
|
|
492
|
+
/** The query text as the user entered it, if known (populates `user_query`). */
|
|
493
|
+
query?: string;
|
|
494
|
+
/** Override the `action_name`. Defaults to `"click"`. */
|
|
495
|
+
actionName?: string;
|
|
496
|
+
};
|
|
497
|
+
/** Options for {@link Tracker.trackResultImpression}. */
|
|
498
|
+
type TrackResultImpressionOptions = {
|
|
499
|
+
/** The results that became visible, each with its object id and absolute rank. */
|
|
500
|
+
items: ResultRef[];
|
|
501
|
+
/** The server-issued `query_id` that produced these results. Required. */
|
|
502
|
+
queryId: string;
|
|
503
|
+
/** The query text as the user entered it, if known (populates `user_query`). */
|
|
504
|
+
query?: string;
|
|
505
|
+
};
|
|
506
|
+
/** Options for {@link Tracker.trackSearch}. */
|
|
507
|
+
type TrackSearchOptions = {
|
|
508
|
+
/** The query text as the user entered it. */
|
|
509
|
+
query: string;
|
|
510
|
+
/** The server-issued `query_id` for this search. Required. */
|
|
511
|
+
queryId: string;
|
|
512
|
+
/**
|
|
513
|
+
* Any additional keys are persisted as-is under the event's
|
|
514
|
+
* `event_attributes` (e.g. `badge: "sale"`). Values must be
|
|
515
|
+
* JSON-serialisable. Keys the tracker itself owns - `object`, `position`,
|
|
516
|
+
* `event_id`, `tracker`, `page`, `browser` - are overwritten by it; use
|
|
517
|
+
* {@link Tracker.dispatch} for full control of the event shape.
|
|
518
|
+
*/
|
|
519
|
+
[key: string]: unknown;
|
|
520
|
+
};
|
|
521
|
+
/**
|
|
522
|
+
* The entry point of `@releval/tracker`: collects User Behavior Insights
|
|
523
|
+
* events - searches, result impressions, result clicks and the conversions
|
|
524
|
+
* that follow - in the canonical joinable shape
|
|
525
|
+
* (`query_id` + `object_id` + `ordinal`) and delivers them to a Releval
|
|
526
|
+
* deployment's track-event API.
|
|
527
|
+
*
|
|
528
|
+
* Events flow through a small pipeline: the high-level `track*` methods (or
|
|
529
|
+
* the declarative collectors) build canonical events, enrichers stamp
|
|
530
|
+
* context (application, session, client id, page, browser), and sinks
|
|
531
|
+
* deliver - batched with retry to the endpoint, or to the console in
|
|
532
|
+
* development. Nothing is delivered before {@link Tracker.start}; earlier
|
|
533
|
+
* dispatches are buffered and replayed.
|
|
534
|
+
*
|
|
535
|
+
* @example
|
|
536
|
+
* ```ts
|
|
537
|
+
* const tracker = new Tracker({
|
|
538
|
+
* application: "primary-search",
|
|
539
|
+
* siteId: "YOUR_SITE_ID",
|
|
540
|
+
* endpointHost: "https://releval.example.com",
|
|
541
|
+
* });
|
|
542
|
+
* tracker.start();
|
|
543
|
+
*
|
|
544
|
+
* tracker.trackSearch({ query, queryId }); // ids come from your backend
|
|
545
|
+
* tracker.trackResultClick({ objectId, ordinal, queryId });
|
|
546
|
+
* tracker.trackResultEvent({ actionName: "add_to_cart", objectId });
|
|
547
|
+
* ```
|
|
548
|
+
*
|
|
549
|
+
* The full integration flow is documented at
|
|
550
|
+
* https://releval.co/docs/user-behavior-insights/browser-tracker
|
|
551
|
+
*/
|
|
552
|
+
declare class Tracker {
|
|
553
|
+
private readonly enrichers;
|
|
554
|
+
private readonly options;
|
|
555
|
+
private readonly _sessionManager;
|
|
556
|
+
private started;
|
|
557
|
+
private hasEverStarted;
|
|
558
|
+
private collectors;
|
|
559
|
+
private readonly detaches;
|
|
560
|
+
private sink?;
|
|
561
|
+
private batchSink?;
|
|
562
|
+
private readonly logger;
|
|
563
|
+
private dispatcher?;
|
|
564
|
+
private _clientId?;
|
|
565
|
+
private _userId?;
|
|
566
|
+
private _attribution?;
|
|
567
|
+
private hasWarnedMissingQueryId;
|
|
568
|
+
private hasWarnedUnattributedResult;
|
|
569
|
+
private hasWarnedInvalidOrdinal;
|
|
570
|
+
private readonly preStartBuffer;
|
|
571
|
+
private hasWarnedPreStartOverflow;
|
|
572
|
+
private readonly seenImpressions;
|
|
573
|
+
private readonly extraSinks;
|
|
574
|
+
constructor(options: TrackerOptions);
|
|
575
|
+
private _localStorage?;
|
|
576
|
+
/**
|
|
577
|
+
* The tracker's local storage (with fallbacks when Web Storage is
|
|
578
|
+
* unavailable). Internal: collectors and stores receive it via options.
|
|
579
|
+
*/
|
|
580
|
+
private get localStorage();
|
|
581
|
+
private _sessionStorage?;
|
|
582
|
+
/**
|
|
583
|
+
* The tracker's session storage (with fallbacks when Web Storage is
|
|
584
|
+
* unavailable). Internal: collectors and stores receive it via options.
|
|
585
|
+
*/
|
|
586
|
+
private get sessionStorage();
|
|
587
|
+
/**
|
|
588
|
+
* Gets the session identifier. Automatically rotates on inactivity or max duration.
|
|
589
|
+
*/
|
|
590
|
+
get sessionId(): string;
|
|
591
|
+
/**
|
|
592
|
+
* Gets the stable anonymous client/device ID. Persisted in localStorage across sessions.
|
|
593
|
+
*/
|
|
594
|
+
get clientId(): string;
|
|
595
|
+
/**
|
|
596
|
+
* Sets (or clears, with `undefined`) the user id stamped on events - for a
|
|
597
|
+
* login or logout that happens without a page reload. Applies to events
|
|
598
|
+
* dispatched after the call; events already queued or persisted for retry
|
|
599
|
+
* keep the identity they were stamped with. An explicit `user_id` on a
|
|
600
|
+
* dispatched event still wins. Callable before `start()`. Does not rotate
|
|
601
|
+
* the session or touch the client id. The value must be an opaque,
|
|
602
|
+
* pseudonymous identifier - never an email address or name.
|
|
603
|
+
*/
|
|
604
|
+
setUserId(userId: string | undefined): void;
|
|
605
|
+
/**
|
|
606
|
+
* The object-keyed attribution store, created on first use so construction
|
|
607
|
+
* performs no storage IO for it.
|
|
608
|
+
*/
|
|
609
|
+
private get attribution();
|
|
610
|
+
/**
|
|
611
|
+
* Returns the attribution recorded by {@link Tracker.trackResultClick} for
|
|
612
|
+
* an object in the current session, or undefined. Use it to build custom
|
|
613
|
+
* conversion payloads (e.g. a checkout page resolving several line items).
|
|
614
|
+
*/
|
|
615
|
+
getResultAttribution(objectId: string): AttributionRecord | undefined;
|
|
616
|
+
/**
|
|
617
|
+
* Reports that a search ran, with the server-issued `query_id`. Call this
|
|
618
|
+
* after your search backend returns results.
|
|
619
|
+
*
|
|
620
|
+
* Dispatches a `search` event (`message_type: "QUERY"`) carrying `query_id`
|
|
621
|
+
* and `user_query`. Without it a query that gets no impression and no click
|
|
622
|
+
* leaves no event at all, so abandonment and reformulation cannot be
|
|
623
|
+
* analysed downstream.
|
|
624
|
+
*/
|
|
625
|
+
trackSearch(options: TrackSearchOptions): void;
|
|
626
|
+
/**
|
|
627
|
+
* Dispatches a UBI event for a search result in the canonical shape
|
|
628
|
+
* (`query_id`, `event_attributes.object.object_id`, `event_attributes.position.ordinal`),
|
|
629
|
+
* so callers never hand-build it. Prefer {@link Tracker.trackResultClick} /
|
|
630
|
+
* {@link Tracker.trackResultImpression}; use this directly for conversions
|
|
631
|
+
* (e.g. `actionName: "add_to_cart"` or `"purchase"`), where `queryId` and
|
|
632
|
+
* `ordinal` may be omitted and are resolved from the attribution recorded
|
|
633
|
+
* when the result was clicked - including on a later page. When `queryId`
|
|
634
|
+
* IS supplied and matches the recorded click, a missing `ordinal`/`query`
|
|
635
|
+
* is still borrowed from it, so the row shape does not depend on which
|
|
636
|
+
* page supplied the id.
|
|
637
|
+
*/
|
|
638
|
+
trackResultEvent(options: TrackResultEventOptions): void;
|
|
639
|
+
/**
|
|
640
|
+
* Dispatches a `click` event for a clicked search result, attributed to
|
|
641
|
+
* `queryId` (required at the call site by design), and records the
|
|
642
|
+
* attribution for the object so later conversion events for it - on this
|
|
643
|
+
* page or a later one - resolve the originating query automatically.
|
|
644
|
+
*/
|
|
645
|
+
trackResultClick(options: TrackResultClickOptions): void;
|
|
646
|
+
/**
|
|
647
|
+
* Dispatches an `impression` event for each result that became visible,
|
|
648
|
+
* attributed to `queryId`. Emits one canonical event per item so impressions
|
|
649
|
+
* join to clicks on `object_id`/`ordinal`.
|
|
650
|
+
*
|
|
651
|
+
* Each `(queryId, objectId)` pair is reported ONCE per Tracker instance:
|
|
652
|
+
* re-renders, virtualized-list remounts and re-discovered elements do not
|
|
653
|
+
* inflate the impression count (the CTR denominator), while a new `queryId`
|
|
654
|
+
* re-fires for results returned by consecutive searches. A full page load
|
|
655
|
+
* builds a fresh Tracker and so starts fresh; when revisits matter,
|
|
656
|
+
* deduplicate downstream on distinct `(query_id, object_id)`.
|
|
657
|
+
*/
|
|
658
|
+
trackResultImpression(options: TrackResultImpressionOptions): void;
|
|
659
|
+
/**
|
|
660
|
+
* Adds an enricher that runs on every event before emission (e.g. stamping
|
|
661
|
+
* an A/B variant, store or locale - the split key for any comparison).
|
|
662
|
+
* Returns a disposer that removes it again.
|
|
663
|
+
*/
|
|
664
|
+
addEnricher(enricher: Enricher): () => void;
|
|
665
|
+
/**
|
|
666
|
+
* Registers a collector so it attaches at start() (or immediately when the
|
|
667
|
+
* tracker is already started), and returns a function that detaches and
|
|
668
|
+
* unregisters it again.
|
|
669
|
+
*/
|
|
670
|
+
private registerCollector;
|
|
671
|
+
/**
|
|
672
|
+
* Binds declarative result-click collection to the DOM: one delegated
|
|
673
|
+
* listener reports clicks on elements matching `selector` in the canonical
|
|
674
|
+
* joinable shape, reading `data-object-id` / `data-ordinal` from the result
|
|
675
|
+
* element and `data-query-id` from its nearest ancestor (or a custom
|
|
676
|
+
* `resolve`). A resolved `data-action-name` other than `click` (e.g.
|
|
677
|
+
* `add_to_cart`) is routed through {@link Tracker.trackResultEvent}, so its
|
|
678
|
+
* attribution can resolve from the recorded click.
|
|
679
|
+
*
|
|
680
|
+
* Use `ignore` for interactive descendants of a result (an add-to-cart
|
|
681
|
+
* button inside the card) so their clicks are not double-reported as result
|
|
682
|
+
* clicks.
|
|
683
|
+
*
|
|
684
|
+
* @returns a function that stops this collection again.
|
|
685
|
+
*/
|
|
686
|
+
trackResultClicks(options: TrackResultClicksOptions): () => void;
|
|
687
|
+
/** Routes a resolved declarative click through the high-level API. */
|
|
688
|
+
private emitResolvedResultClick;
|
|
689
|
+
/**
|
|
690
|
+
* Binds declarative result-impression collection to the DOM: each element
|
|
691
|
+
* matching `selector` emits one canonical `impression` event the first time
|
|
692
|
+
* it enters the viewport, attributed via the same data-attribute convention
|
|
693
|
+
* as {@link Tracker.trackResultClicks} (or a custom `resolve`). Elements
|
|
694
|
+
* added or re-rendered after `start()` are discovered automatically.
|
|
695
|
+
*
|
|
696
|
+
* @returns a function that stops this collection again.
|
|
697
|
+
*/
|
|
698
|
+
trackResultImpressions(options: TrackResultImpressionsOptions): () => void;
|
|
699
|
+
/**
|
|
700
|
+
* Adds a sink that receives every emitted event (e.g. mirroring into your
|
|
701
|
+
* own analytics, or a test spy). Returns a disposer that removes it again.
|
|
702
|
+
*/
|
|
703
|
+
addSink(sink: Sink): () => void;
|
|
704
|
+
/**
|
|
705
|
+
* starts the tracker and attaches all collectors.
|
|
706
|
+
*/
|
|
707
|
+
start(): void;
|
|
708
|
+
/**
|
|
709
|
+
* Builds the effective sink from the configured endpoint plus any user sinks.
|
|
710
|
+
* ConsoleSink is used only when there is neither an endpoint nor a user sink
|
|
711
|
+
* (the development default). The result is always an AggregateSink so its
|
|
712
|
+
* reference stays stable across addSink() calls.
|
|
713
|
+
*/
|
|
714
|
+
private buildSink;
|
|
715
|
+
/**
|
|
716
|
+
* Dispatches an event through the tracker pipeline (enrichers → sinks).
|
|
717
|
+
* Use this to send custom events that aren't captured by a collector.
|
|
718
|
+
* Safe to call before start(): the event is buffered (bounded), stamped
|
|
719
|
+
* with the timestamp of the moment it happened, and replayed once start()
|
|
720
|
+
* runs.
|
|
721
|
+
* @param event The event to dispatch.
|
|
722
|
+
*/
|
|
723
|
+
dispatch(event: Event): void;
|
|
724
|
+
/**
|
|
725
|
+
* Forces immediate delivery of any queued events, rather than waiting for the
|
|
726
|
+
* next batch interval. Useful before a critical action or a hard navigation.
|
|
727
|
+
* Resolves once the current batch has been sent (or scheduled for retry).
|
|
728
|
+
* A no-op that resolves immediately when there is no batching sink (events go
|
|
729
|
+
* to the console) or the tracker is stopped.
|
|
730
|
+
*/
|
|
731
|
+
flush(): Promise<void>;
|
|
732
|
+
/**
|
|
733
|
+
* stops the tracker and detaches all collectors.
|
|
734
|
+
*/
|
|
735
|
+
stop(): void;
|
|
736
|
+
}
|
|
737
|
+
//#endregion
|
|
738
|
+
//#region src/collectors/ResultImpressionCollector.d.ts
|
|
739
|
+
/**
|
|
740
|
+
* The result data resolved for an element that became visible: the shared
|
|
741
|
+
* {@link ResolvedResultData} shape. `actionName` is ignored here - an
|
|
742
|
+
* impression is always an `impression`.
|
|
743
|
+
*/
|
|
744
|
+
type ResolvedResultImpression = ResolvedResultData;
|
|
745
|
+
/**
|
|
746
|
+
* Resolves the result data for a visible element. Return `undefined` to skip
|
|
747
|
+
* it. The default implementation reads the data-attribute convention described
|
|
748
|
+
* on {@link readResultData}.
|
|
749
|
+
*/
|
|
750
|
+
type ResultImpressionResolve = (element: HTMLElement) => ResolvedResultImpression | undefined;
|
|
751
|
+
/** Options for `Tracker.trackResultImpressions`. */
|
|
752
|
+
type TrackResultImpressionsOptions = {
|
|
753
|
+
/** CSS selector of result elements to observe for viewport visibility. */
|
|
754
|
+
selector: string;
|
|
755
|
+
/** The root observed for matching elements. Defaults to `document`. */
|
|
756
|
+
root?: Document | Element;
|
|
757
|
+
/** Custom resolver; defaults to the data-attribute convention. */
|
|
758
|
+
resolve?: ResultImpressionResolve;
|
|
759
|
+
};
|
|
760
|
+
//#endregion
|
|
761
|
+
export { AttributionRecord as C, EventPosition as S, Sink as _, TrackResultClickOptions as a, EventAttributes as b, TrackSearchOptions as c, TrackerOptions as d, ResolvedResultClick as f, readResultData as g, ResolvedResultData as h, ResultRef as i, Tracker as l, TrackResultClicksOptions as m, ResultImpressionResolve as n, TrackResultEventOptions as o, ResultClickResolve as p, TrackResultImpressionsOptions as r, TrackResultImpressionOptions as s, ResolvedResultImpression as t, TrackerBaseOptions as u, Enricher as v, Logger as w, EventObject as x, Event as y };
|
|
762
|
+
//# sourceMappingURL=ResultImpressionCollector.d.mts.map
|