@snowplow/signals-browser-plugin 0.0.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.
@@ -0,0 +1,649 @@
1
+ /*!
2
+ * Snowplow Signals Interventions SDK v0.0.1 (https://github.com/snowplow-incubator/signals-browser-plugin)
3
+ * Copyright 2025 Snowplow Analytics Ltd
4
+ * Licensed under BSD-3-Clause
5
+ */
6
+
7
+ import { __spreadArray } from 'tslib';
8
+ import { buildSelfDescribingEvent, resolveDynamicContext } from '@snowplow/tracker-core';
9
+
10
+ var LogLevel;
11
+ (function (LogLevel) {
12
+ LogLevel["DEBUG"] = "debug";
13
+ LogLevel["ERROR"] = "error";
14
+ LogLevel["INFO"] = "info";
15
+ LogLevel["WARN"] = "warn";
16
+ })(LogLevel || (LogLevel = {}));
17
+ var name;
18
+ var LOG;
19
+ /**
20
+ * Wrap the passed logger so that it's clear which plugin/tracker instance log messages refer to
21
+ * @param level Log level for the logged message
22
+ * @param trackerId Tracker ID logging the event
23
+ * @param args Log message and other arguments
24
+ */
25
+ var logger = function (level, trackerId) {
26
+ var args = [];
27
+ for (var _i = 2; _i < arguments.length; _i++) {
28
+ args[_i - 2] = arguments[_i];
29
+ }
30
+ if (!name || !LOG) {
31
+ return;
32
+ }
33
+ var prefix = "[".concat(name, ":").concat(trackerId, "] ");
34
+ if (typeof args[0] === 'string') {
35
+ var msg = prefix + args.shift();
36
+ LOG[level].apply(LOG, __spreadArray([msg], args, false));
37
+ }
38
+ else {
39
+ LOG[level].apply(LOG, __spreadArray([prefix], args, false));
40
+ }
41
+ };
42
+ var setLogger = function (plugin, log) {
43
+ name = plugin;
44
+ LOG = log;
45
+ };
46
+
47
+ var ENTITY_ALIASES = ['co', 'cx', 'context', 'entity', 'entities'];
48
+ var EVENT_ALIASES = ['ue', 'ue_pr', 'ue_px', 'unstruct', 'unstruct_event', 'self_describing', 'sde'];
49
+ var EVENT_SCHEMAS = {
50
+ se: 'iglu:com.google.analytics/event/jsonschema/1-0-0',
51
+ ev: 'iglu:com.google.analytics/event/jsonschema/1-0-0',
52
+ ad: 'iglu:com.snowplowanalytics.snowplow/ad_impression/jsonschema/1-0-0',
53
+ tr: 'iglu:com.snowplowanalytics.snowplow/transaction/jsonschema/1-0-0',
54
+ ti: 'iglu:com.snowplowanalytics.snowplow/transaction_item/1-0-0',
55
+ pv: 'iglu:com.snowplowanalytics.snowplow/page_view/jsonschema/1-0-0',
56
+ pp: 'iglu:com.snowplowanalytics.snowplow/page_ping/jsonschema/1-0-0',
57
+ ue: '', // here to pass an `in` check below; needs to be extracted from SDJ payload
58
+ };
59
+ var objWithKey = function (obj, key) {
60
+ return typeof obj === 'object' && obj != null && key in obj;
61
+ };
62
+ var extractUrlFrom = function (part, field) {
63
+ return function (event) {
64
+ try {
65
+ return new URL(event[field])[part].toString() || undefined;
66
+ }
67
+ catch (_) {
68
+ return;
69
+ }
70
+ };
71
+ };
72
+ var extractUrlParamFrom = function (param, field) {
73
+ return function (event) {
74
+ var _a;
75
+ try {
76
+ return (_a = new URL(event[field]).searchParams.get(param)) !== null && _a !== void 0 ? _a : undefined;
77
+ }
78
+ catch (_) {
79
+ return;
80
+ }
81
+ };
82
+ };
83
+ var extractParamPartFrom = function (param, field, index, sep) {
84
+ if (sep === void 0) { sep = '.'; }
85
+ return function (event) {
86
+ var _a;
87
+ try {
88
+ var full = (_a = new URL(event[field]).searchParams.get(param)) !== null && _a !== void 0 ? _a : undefined;
89
+ return full ? full.split(sep)[index] : undefined;
90
+ }
91
+ catch (_) {
92
+ return;
93
+ }
94
+ };
95
+ };
96
+ var extractDimensionValueFrom = function (dim, field) {
97
+ return function (event) {
98
+ try {
99
+ return event[field].split('x')[dim];
100
+ }
101
+ catch (_) {
102
+ return;
103
+ }
104
+ };
105
+ };
106
+ /**
107
+ * `Payload` instances access data via their tracker protocol field names, rather than the more human-friendly ones users are familiar with from enriched data.
108
+ * This maps the enriched fields back to their TP equivalents; allowing for field extraction where necessary.
109
+ */
110
+ var selectorMap = {
111
+ app_id: 'aid',
112
+ platform: 'p',
113
+ dvce_created_tstamp: 'dtm',
114
+ event: function (_a) {
115
+ var _b;
116
+ var e = _a.e;
117
+ return ((_b = {
118
+ se: 'struct',
119
+ ev: 'struct',
120
+ ue: 'unstruct',
121
+ ad: 'ad_impression',
122
+ tr: 'transaction',
123
+ ti: 'transaction_item',
124
+ pv: 'page_view',
125
+ pp: 'page_ping',
126
+ }[e]) !== null && _b !== void 0 ? _b : e);
127
+ },
128
+ event_id: 'eid',
129
+ txn_id: 'tid',
130
+ name_tracker: 'tna',
131
+ v_tracker: 'tv',
132
+ user_id: 'uid',
133
+ user_ipaddress: 'ip',
134
+ user_fingerprint: 'fp',
135
+ domain_userid: 'duid',
136
+ domain_sessionidx: 'vid',
137
+ network_userid: 'nuid',
138
+ page_url: 'url',
139
+ page_title: 'page',
140
+ page_referrer: 'refr',
141
+ page_urlscheme: extractUrlFrom('protocol', 'url'),
142
+ page_urlhost: extractUrlFrom('hostname', 'url'),
143
+ page_urlport: extractUrlFrom('port', 'url'),
144
+ page_urlpath: extractUrlFrom('pathname', 'url'),
145
+ page_urlquery: extractUrlFrom('search', 'url'),
146
+ page_urlfragment: extractUrlFrom('hash', 'url'),
147
+ refr_urlscheme: extractUrlFrom('protocol', 'refr'),
148
+ refr_urlhost: extractUrlFrom('hostname', 'refr'),
149
+ refr_urlport: extractUrlFrom('port', 'refr'),
150
+ refr_urlpath: extractUrlFrom('pathname', 'refr'),
151
+ refr_urlquery: extractUrlFrom('search', 'refr'),
152
+ refr_urlfragment: extractUrlFrom('hash', 'refr'),
153
+ mkt_medium: extractUrlParamFrom('utm_medium', 'url'),
154
+ mkt_source: extractUrlParamFrom('utm_source', 'url'),
155
+ mkt_term: extractUrlParamFrom('utm_term', 'url'),
156
+ mkt_content: extractUrlParamFrom('utm_content', 'url'),
157
+ mkt_campaign: extractUrlParamFrom('utm_campaign', 'url'),
158
+ se_category: 'se_ca',
159
+ se_action: 'se_ac',
160
+ se_label: 'se_la',
161
+ se_property: 'se_pr',
162
+ se_value: 'se_va',
163
+ tr_orderid: 'tr_id',
164
+ tr_affiliation: 'tr_af',
165
+ tr_total: 'tr_tt',
166
+ tr_tax: 'tr_tx',
167
+ tr_shipping: 'tr_sh',
168
+ tr_city: 'tr_ci',
169
+ tr_state: 'tr_st',
170
+ tr_country: 'tr_co',
171
+ ti_orderid: 'ti_id',
172
+ ti_sku: 'ti_sk',
173
+ ti_name: 'ti_na',
174
+ ti_category: 'ti_ca',
175
+ ti_price: 'ti_pr',
176
+ ti_quantity: 'ti_qu',
177
+ pp_xoffset_min: 'pp_mix',
178
+ pp_xoffset_max: 'pp_max',
179
+ pp_yoffset_min: 'pp_miy',
180
+ pp_yoffset_max: 'pp_may',
181
+ useragent: function (_a) {
182
+ var ua = _a.ua;
183
+ return ua !== null && ua !== void 0 ? ua : navigator.userAgent;
184
+ }, // often elided and extracted from HTTP header
185
+ br_lang: 'lang',
186
+ br_features_pdf: 'f_pdf',
187
+ br_features_flash: 'f_fla',
188
+ br_features_java: 'f_java',
189
+ br_features_director: 'f_dir',
190
+ br_features_quicktime: 'f_qt',
191
+ br_features_realplayer: 'f_realp',
192
+ br_features_windowsmedia: 'f_wma',
193
+ br_features_gears: 'f_gears',
194
+ br_features_silverlight: 'f_ag',
195
+ br_cookies: 'cookie',
196
+ br_colordepth: 'cd',
197
+ br_viewwidth: extractDimensionValueFrom(0, 'vp'),
198
+ br_viewheight: extractDimensionValueFrom(1, 'vp'),
199
+ os_timezone: 'tz',
200
+ dvce_screenwidth: extractDimensionValueFrom(0, 'res'),
201
+ dvce_screenheight: extractDimensionValueFrom(1, 'res'),
202
+ doc_charset: 'cs',
203
+ doc_width: extractDimensionValueFrom(0, 'ds'),
204
+ doc_height: extractDimensionValueFrom(1, 'ds'),
205
+ tr_currency: 'tr_cu',
206
+ ti_currency: 'ti_cu',
207
+ mkt_clickid: extractUrlParamFrom('gclid', 'url'),
208
+ dvce_sent_tstamp: 'stm',
209
+ refr_domain_userid: extractParamPartFrom('_sp', 'url', 0),
210
+ refr_device_tstamp: extractParamPartFrom('_sp', 'url', 1),
211
+ domain_sessionid: 'sid',
212
+ event_vendor: function (evt) { var _a; return (_a = getEventSelf(evt)) === null || _a === void 0 ? void 0 : _a.vendor; },
213
+ event_name: function (evt) { var _a; return (_a = getEventSelf(evt)) === null || _a === void 0 ? void 0 : _a.name; },
214
+ event_format: function (evt) { var _a; return (_a = getEventSelf(evt)) === null || _a === void 0 ? void 0 : _a.format; },
215
+ event_version: function (evt) { var _a; return (_a = getEventSelf(evt)) === null || _a === void 0 ? void 0 : _a.version; },
216
+ true_tstamp: 'ttm',
217
+ };
218
+ var parseJsonPointer = function (pointer) {
219
+ if (!pointer) {
220
+ return;
221
+ }
222
+ if (pointer[0] !== '/') {
223
+ pointer = "/".concat(pointer); // for atomic-level fields, this will allow regular names since JSON Pointer isn't super intuitive
224
+ }
225
+ return pointer
226
+ .split('/')
227
+ .map(function (segment) { return segment.replace(/~1/g, '/').replace(/~0/g, '~'); })
228
+ .slice(1);
229
+ };
230
+ var parseEncodedFields = function (evt, fields) {
231
+ return fields.map(function (field) {
232
+ if (objWithKey(evt, field)) {
233
+ var val = evt[field];
234
+ if (typeof val === 'string') {
235
+ try {
236
+ return JSON.parse(val);
237
+ }
238
+ catch (_) {
239
+ try {
240
+ return JSON.parse(atob(val.replace(/[_-]/g, function (m) { return (m == '-' ? '+' : '/'); })));
241
+ }
242
+ catch (_) { }
243
+ }
244
+ }
245
+ }
246
+ });
247
+ };
248
+ /**
249
+ * Build a lookup map of `vendor: name[]` for entities to lookup more easily
250
+ */
251
+ var summarizeEntities = function (evt) {
252
+ var fields = parseEncodedFields(evt, ['co', 'cx']);
253
+ return fields.reduce(function (acc, ctx) {
254
+ if (objWithKey(ctx, 'data') && Array.isArray(ctx.data)) {
255
+ ctx.data.forEach(function (entity) {
256
+ var _a, _b;
257
+ if (objWithKey(entity, 'schema') && objWithKey(entity, 'data')) {
258
+ if (typeof entity.schema === 'string' && entity.schema.indexOf('iglu:') === 0) {
259
+ var parts = entity.schema.slice(5).split('/');
260
+ if (parts.length !== 4) {
261
+ return;
262
+ }
263
+ var vendor = (acc[parts[0]] = (_a = acc[parts[0]]) !== null && _a !== void 0 ? _a : {});
264
+ vendor[parts[1]] = (_b = vendor[parts[1]]) !== null && _b !== void 0 ? _b : [];
265
+ vendor[parts[1]].push(entity.data);
266
+ }
267
+ }
268
+ });
269
+ }
270
+ return acc;
271
+ }, {});
272
+ };
273
+ /**
274
+ * Build a lookup map of `vendor: name` for SDE payloads to lookup more easily
275
+ */
276
+ var summarizeEvent = function (evt) {
277
+ var fields = parseEncodedFields(evt, ['ue_pr', 'ue_px']);
278
+ return fields.reduce(function (acc, ctx) {
279
+ var _a;
280
+ if (objWithKey(ctx, 'data')) {
281
+ if (objWithKey(ctx.data, 'schema') && objWithKey(ctx.data, 'data')) {
282
+ var event_1 = ctx.data;
283
+ if (typeof event_1.schema === 'string' && event_1.schema.indexOf('iglu:') === 0) {
284
+ var parts = event_1.schema.slice(5).split('/');
285
+ if (parts.length !== 4) {
286
+ return;
287
+ }
288
+ var vendor = (acc[parts[0]] = (_a = acc[parts[0]]) !== null && _a !== void 0 ? _a : {});
289
+ vendor[parts[1]] = event_1.data;
290
+ }
291
+ }
292
+ }
293
+ return acc;
294
+ }, {});
295
+ };
296
+ var getEventSelf = function (evt) {
297
+ var _a;
298
+ var schema = undefined;
299
+ if (!objWithKey(evt, 'e') || typeof evt.e !== 'string' || !(evt.e in EVENT_SCHEMAS)) {
300
+ return;
301
+ }
302
+ if (evt.e === 'ue') {
303
+ var fields = parseEncodedFields(evt, ['ue_pr', 'ue_px']);
304
+ var ue = (_a = fields[0]) !== null && _a !== void 0 ? _a : fields[1];
305
+ if (objWithKey(ue, 'data') && objWithKey(ue.data, 'schema') && typeof ue.data.schema === 'string') {
306
+ schema = ue.data.schema;
307
+ }
308
+ }
309
+ else {
310
+ schema = EVENT_SCHEMAS[evt.e];
311
+ }
312
+ if (typeof schema === 'string' && schema.indexOf('iglu:') === 0) {
313
+ var _b = schema.slice(5).split('/'), vendor = _b[0], name_1 = _b[1], format = _b[2], version = _b[3];
314
+ return { vendor: vendor, name: name_1, format: format, version: version };
315
+ }
316
+ };
317
+ var derefJsonPointer = function (pointerString, obj) {
318
+ var _a, _b;
319
+ var pointer = parseJsonPointer(pointerString);
320
+ if (pointer == null) {
321
+ return obj; // return whole document
322
+ }
323
+ if (typeof obj !== 'object' || obj === null) {
324
+ return; // no segments will work
325
+ }
326
+ // map enriched fieldname <> TP field
327
+ if (pointer[0] in selectorMap) {
328
+ var mappedSelector = selectorMap[pointer[0]];
329
+ if (typeof mappedSelector === 'string' && objWithKey(obj, mappedSelector)) {
330
+ return obj[mappedSelector];
331
+ }
332
+ else if (typeof mappedSelector === 'function') {
333
+ return mappedSelector(obj);
334
+ }
335
+ }
336
+ // descend into nested SDJ payloads
337
+ var cursor = obj;
338
+ if (ENTITY_ALIASES.indexOf(pointer[0]) !== -1) {
339
+ cursor = (_a = {}, _a[pointer[0]] = summarizeEntities(cursor), _a);
340
+ }
341
+ if (EVENT_ALIASES.indexOf(pointer[0]) !== -1) {
342
+ cursor = (_b = {}, _b[pointer[0]] = summarizeEvent(cursor), _b);
343
+ }
344
+ for (var _i = 0, pointer_1 = pointer; _i < pointer_1.length; _i++) {
345
+ var segment = pointer_1[_i];
346
+ if (objWithKey(cursor, segment)) {
347
+ cursor = cursor[segment];
348
+ }
349
+ else if (Array.isArray(cursor) && cursor.length === 1 && objWithKey(cursor[0], segment)) {
350
+ cursor = cursor[0][segment];
351
+ }
352
+ else
353
+ return;
354
+ }
355
+ return cursor;
356
+ };
357
+ /**
358
+ * Given a list of rules to extract entity IDs and a Snowplow event payload, return any extracted ID results.
359
+ * @param targets Map of entity names to rule definitions for how to find ID values
360
+ * @param pb Snowplow event
361
+ * @returns Resulting IDs extracted from the event
362
+ */
363
+ function extractEntityValues(targets, pb) {
364
+ var extracted = {};
365
+ Object.entries(targets).forEach(function (_a) {
366
+ var entityName = _a[0], pointers = _a[1];
367
+ pointers = Array.isArray(pointers) ? pointers : [pointers];
368
+ for (var _i = 0, pointers_1 = pointers; _i < pointers_1.length; _i++) {
369
+ var pointer = pointers_1[_i];
370
+ if (pointer === '') {
371
+ continue;
372
+ }
373
+ var candidateId = derefJsonPointer(pointer, pb);
374
+ if (candidateId != null) {
375
+ extracted[entityName] = String(candidateId);
376
+ return;
377
+ }
378
+ }
379
+ });
380
+ return extracted;
381
+ }
382
+
383
+ var DEFAULT_PULL_API_PATH = '/api/v1/interventions';
384
+ var DEFAULT_ENTITY_TARGETS = {
385
+ domain_userid: '/domain_userid',
386
+ domain_sessionid: '/domain_sessionid',
387
+ //pageview_id: '/co/com.snowplowanalytics.snowplow/web_page/id', // not a default entity seed
388
+ //tab_id: '/co/com.snowplowanalytics.snowplow/browser_context/tabId', // not a default entity seed
389
+ };
390
+ var DEFAULT_CONNECTION_TIMEOUT_MS = 2500;
391
+ /**
392
+ * Default `Fetcher` implementation; uses SSEs, auto updates based on observed entity IDs defined with JSON Pointers
393
+ */
394
+ var InterventionFetcher = /** @class */ (function () {
395
+ function InterventionFetcher(tracker, dispatch) {
396
+ this.tracker = tracker;
397
+ this.dispatch = dispatch;
398
+ this.entityValues = {};
399
+ this.aborter = new AbortController();
400
+ this.timeoutMs = DEFAULT_CONNECTION_TIMEOUT_MS;
401
+ this.newEndpoint = false;
402
+ this.currentParams = '';
403
+ this.entitySelectors = DEFAULT_ENTITY_TARGETS;
404
+ // reasonable defaults even without any events observed
405
+ var info = tracker.getDomainUserInfo();
406
+ this.update({
407
+ duid: info[1] || undefined,
408
+ sid: info[6] || undefined,
409
+ });
410
+ }
411
+ InterventionFetcher.prototype.configure = function (_a) {
412
+ var endpoint = _a.endpoint, _b = _a.apiPath, apiPath = _b === void 0 ? DEFAULT_PULL_API_PATH : _b, _c = _a.entityTargets, entityTargets = _c === void 0 ? DEFAULT_ENTITY_TARGETS : _c, _d = _a.entityIds, entityIds = _d === void 0 ? {} : _d, _e = _a.connectionTimeoutMs, connectionTimeoutMs = _e === void 0 ? DEFAULT_CONNECTION_TIMEOUT_MS : _e;
413
+ this.entitySelectors = Object.assign({}, this.entitySelectors, entityTargets);
414
+ this.timeoutMs = connectionTimeoutMs;
415
+ var prevEndpoint = this.endpoint;
416
+ this.endpoint = "".concat(/\/\//.test(endpoint) ? endpoint : 'https://' + endpoint).concat(apiPath);
417
+ this.newEndpoint = prevEndpoint != this.endpoint;
418
+ this.update(undefined, entityIds);
419
+ };
420
+ InterventionFetcher.prototype.update = function (payload, explicitEntities) {
421
+ if (explicitEntities === void 0) { explicitEntities = {}; }
422
+ if (payload) {
423
+ Object.assign(this.entityValues, extractEntityValues(this.entitySelectors, payload));
424
+ }
425
+ Object.assign(this.entityValues, explicitEntities);
426
+ var newParams = new URLSearchParams(this.entityValues).toString();
427
+ if (this.endpoint && (newParams != this.currentParams || this.newEndpoint)) {
428
+ logger(LogLevel.DEBUG, this.tracker.id, 'Entity IDs updated', this.entityValues);
429
+ this.currentParams = newParams;
430
+ this.requestInterventions();
431
+ }
432
+ };
433
+ /**
434
+ * Set up a new SSE connection and start dispatching any received events
435
+ */
436
+ InterventionFetcher.prototype.requestInterventions = function () {
437
+ var _this = this;
438
+ this.aborter.abort(); // abort any previous connection
439
+ var aborter = (this.aborter = new AbortController()); // new controller to reset aborted state
440
+ if (!this.endpoint) {
441
+ logger(LogLevel.ERROR, this.tracker.id, 'Requested interventions from undefined endpoint');
442
+ return;
443
+ }
444
+ var url = "".concat(this.endpoint, "?").concat(this.currentParams);
445
+ var stream = new EventSource(url);
446
+ aborter.signal.addEventListener('abort', stream.close.bind(stream), {
447
+ once: true,
448
+ });
449
+ var timeout = setTimeout(aborter.abort.bind(aborter), this.timeoutMs);
450
+ stream.addEventListener('open', function () { return clearTimeout(timeout); }, {
451
+ once: true,
452
+ });
453
+ stream.addEventListener('error', function (ev) {
454
+ return logger(LogLevel.ERROR, _this.tracker.id, 'Error fetching interventions:', ev);
455
+ });
456
+ stream.addEventListener('message', function (ev) {
457
+ _this.dispatch(JSON.parse(ev.data), _this.tracker);
458
+ });
459
+ };
460
+ /**
461
+ * Factory to match signature of public interface and create the default fetcher instance
462
+ * @param tracker Tracker activating the plugin
463
+ * @param dispatch Callback to pass interventions to
464
+ * @returns `InterventionFetcher` instance
465
+ */
466
+ InterventionFetcher.create = function (tracker, dispatch) { return new InterventionFetcher(tracker, dispatch); };
467
+ return InterventionFetcher;
468
+ }());
469
+
470
+ var Events = {
471
+ INTERVENTION_RECEIVED: 'iglu:com.snowplowanalytics.signals/intervention_receive/jsonschema/1-0-0',
472
+ INTERVENTION_HANDLE: 'iglu:com.snowplowanalytics.signals/intervention_handle/jsonschema/1-0-0',
473
+ INTERVENTION_HANDLE_ERROR: 'iglu:com.snowplowanalytics.signals/intervention_handle_error/jsonschema/1-0-0',
474
+ };
475
+ var Entities = {
476
+ INTERVENTION: 'iglu:com.snowplowanalytics.signals/intervention_instance/jsonschema/1-0-0',
477
+ };
478
+ var MEASUREMENT_EVENTS = {
479
+ delivery: Events.INTERVENTION_RECEIVED,
480
+ dispatch_accept: Events.INTERVENTION_HANDLE,
481
+ dispatch_error: Events.INTERVENTION_HANDLE_ERROR,
482
+ };
483
+
484
+ var DEFAULT_MEASUREMENT_SETTINGS = {
485
+ delivery: true,
486
+ dispatch_accept: true,
487
+ dispatch_error: true,
488
+ };
489
+ /* Per-tracker state */
490
+ var instances = {}; // entities will likely vary by tracker, so each set will need its own fetcher
491
+ var handlerRegistry = {}; // handlers can be added/removed for individual trackers
492
+ var measurementSettings = {}; // currently global and only specified per-plugin instance but may be required in future
493
+ /**
494
+ * Create an instance of the Signals Interventions plugin.
495
+ * @param configuration Configuration for the plugin
496
+ * @returns Configured plugin instance
497
+ */
498
+ function SignalsInterventionsPlugin(_a) {
499
+ var _b = _a === void 0 ? {
500
+ measurement: DEFAULT_MEASUREMENT_SETTINGS,
501
+ handlers: {},
502
+ } : _a, fetcher = _b.fetcher, _c = _b.measurement, measurement = _c === void 0 ? DEFAULT_MEASUREMENT_SETTINGS : _c, _d = _b.handlers, handlers = _d === void 0 ? {} : _d;
503
+ return {
504
+ activateBrowserPlugin: function (tracker) {
505
+ var _a;
506
+ logger(LogLevel.INFO, tracker.id, 'Activating plugin for tracker');
507
+ instances[tracker.id] = fetcher ? fetcher(tracker, dispatch) : InterventionFetcher.create(tracker, dispatch);
508
+ measurementSettings[tracker.id] = Object.assign({}, DEFAULT_MEASUREMENT_SETTINGS, measurement);
509
+ handlerRegistry[tracker.id] = Object.assign((_a = handlerRegistry[tracker.id]) !== null && _a !== void 0 ? _a : {}, handlers);
510
+ },
511
+ afterTrack: function (payload) {
512
+ var trackerName = payload['tna'];
513
+ if (typeof trackerName === 'string' && trackerName in instances) {
514
+ instances[trackerName].update(payload);
515
+ }
516
+ },
517
+ logger: function (LOG) {
518
+ setLogger(SignalsInterventionsPlugin.name, LOG);
519
+ },
520
+ };
521
+ }
522
+ /**
523
+ * Tracks an event upon receipt/handling of an intervention unless configured not to.
524
+ * @param settings Measurement settings defining whether an event should fire or not, and configuring custom context
525
+ * @param tracker The tracker to track the event with
526
+ * @param measurement The type of event to track
527
+ * @param intervention The intervention payload in question
528
+ * @param payload An event-specific payload, if required according to `measurement`
529
+ */
530
+ var measure = function (settings, tracker, measurement, intervention, payload) {
531
+ var _a;
532
+ var filter = settings[measurement];
533
+ if (filter && (typeof filter !== 'function' || filter(intervention))) {
534
+ var entities = [
535
+ {
536
+ schema: Entities.INTERVENTION,
537
+ data: {
538
+ intervention_id: intervention.intervention_id,
539
+ name: intervention.name,
540
+ version: intervention.version,
541
+ entity: intervention.target_entity,
542
+ attributes: intervention.attributes,
543
+ },
544
+ },
545
+ ];
546
+ tracker.core.track(buildSelfDescribingEvent({
547
+ event: {
548
+ schema: MEASUREMENT_EVENTS[measurement],
549
+ data: payload,
550
+ },
551
+ }), resolveDynamicContext(__spreadArray(__spreadArray([], entities, true), ((_a = settings.context) !== null && _a !== void 0 ? _a : []), true), measurement, intervention, payload));
552
+ }
553
+ };
554
+ /**
555
+ * Called by the `Fetcher` to track receipt of an Intervention and distribute to any handlers
556
+ * @param intervention The intervention payload that was fetched
557
+ * @param tracker The tracker associated with the plugin
558
+ */
559
+ var dispatch = function (intervention, tracker) {
560
+ var _a, _b;
561
+ var measurement = (_a = measurementSettings[tracker.id]) !== null && _a !== void 0 ? _a : DEFAULT_MEASUREMENT_SETTINGS;
562
+ var handlers = (_b = handlerRegistry[tracker.id]) !== null && _b !== void 0 ? _b : {};
563
+ var handlerIds = Object.keys(handlers);
564
+ if (!handlerIds.length) {
565
+ return logger(LogLevel.WARN, tracker.id, 'No handlers registered for intervention', intervention);
566
+ }
567
+ logger(LogLevel.INFO, tracker.id, 'Attempting dispatch for intervention', intervention, handlerIds);
568
+ measure(measurement, tracker, 'delivery', intervention, {
569
+ handlers: handlerIds,
570
+ });
571
+ for (var _i = 0, _c = Object.entries(handlers); _i < _c.length; _i++) {
572
+ var _d = _c[_i], handlerId = _d[0], handler = _d[1];
573
+ setTimeout(function (handlerId, handler, intervention, tracker) {
574
+ var success = function () {
575
+ logger(LogLevel.INFO, tracker.id, 'Intervention handled', handlerId, intervention);
576
+ measure(measurement, tracker, 'dispatch_accept', intervention, {
577
+ handler: handlerId,
578
+ });
579
+ };
580
+ var failure = function (err) {
581
+ logger(LogLevel.ERROR, tracker.id, 'Handler failed processing intervention', err, handlerId, intervention);
582
+ measure(measurement, tracker, 'dispatch_error', intervention, {
583
+ handler: handlerId,
584
+ error: err ? String(err) : undefined,
585
+ });
586
+ };
587
+ try {
588
+ var result = handler(intervention, tracker);
589
+ if (result instanceof Promise) {
590
+ result.then(success, failure);
591
+ }
592
+ else if (objWithKey(result, 'then') && typeof result.then === 'function') {
593
+ result.then(success, failure);
594
+ }
595
+ else
596
+ success();
597
+ }
598
+ catch (e) {
599
+ failure(e);
600
+ }
601
+ }, 0, handlerId, handler, intervention, tracker);
602
+ }
603
+ };
604
+ /**
605
+ * Configure the endpoint information for the plugin's `Fetcher` to subscribe to events from
606
+ * @param config Configuration about the endpoint and entity IDs/definitions to configure
607
+ * @param trackers List of tracker IDs that have activated the plugin to configure a `Fetcher` for
608
+ */
609
+ function subscribeToInterventions(config, trackers) {
610
+ if (trackers === void 0) { trackers = Object.keys(instances); }
611
+ for (var _i = 0, trackers_1 = trackers; _i < trackers_1.length; _i++) {
612
+ var trackerId = trackers_1[_i];
613
+ if (trackerId in instances) {
614
+ instances[trackerId].configure(config);
615
+ }
616
+ }
617
+ }
618
+ /**
619
+ * Start calling the given handlers when new interventions are received
620
+ * @param handlers Map of handler IDs to handler functions to call with new interventions
621
+ * @param trackers List of tracker IDs that have activated the plugin to add these handlers to
622
+ */
623
+ function addInterventionHandlers(handlers, trackers) {
624
+ var _a;
625
+ if (trackers === void 0) { trackers = Object.keys(instances); }
626
+ for (var _i = 0, trackers_2 = trackers; _i < trackers_2.length; _i++) {
627
+ var trackerId = trackers_2[_i];
628
+ handlerRegistry[trackerId] = Object.assign((_a = handlerRegistry[trackerId]) !== null && _a !== void 0 ? _a : {}, handlers);
629
+ }
630
+ }
631
+ /**
632
+ * Stop calling handlers with the given IDs with new interventions
633
+ * @param handlerIds One or more handler IDs to remove
634
+ * @param trackers List of trackers to remove the handlers for; defaults to all trackers that have activated the plugin.
635
+ */
636
+ function removeInterventionHandlers(handlerIds, trackers) {
637
+ if (trackers === void 0) { trackers = Object.keys(instances); }
638
+ var toRemove = Array.isArray(handlerIds) ? handlerIds : [handlerIds];
639
+ for (var _i = 0, toRemove_1 = toRemove; _i < toRemove_1.length; _i++) {
640
+ var handlerId = toRemove_1[_i];
641
+ for (var _a = 0, trackers_3 = trackers; _a < trackers_3.length; _a++) {
642
+ var trackerId = trackers_3[_a];
643
+ delete handlerRegistry[trackerId][handlerId];
644
+ }
645
+ }
646
+ }
647
+
648
+ export { SignalsInterventionsPlugin, addInterventionHandlers, removeInterventionHandlers, subscribeToInterventions };
649
+ //# sourceMappingURL=index.module.js.map