@liquidcommercedev/rmn-sdk 1.5.0-beta.8 → 1.5.0-beta.9

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/dist/index.esm.js CHANGED
@@ -53,9 +53,13 @@ var RMN_FILTER_PROPERTIES;
53
53
  RMN_FILTER_PROPERTIES["PUBLISHERS"] = "publishers";
54
54
  RMN_FILTER_PROPERTIES["SECTION"] = "section";
55
55
  })(RMN_FILTER_PROPERTIES || (RMN_FILTER_PROPERTIES = {}));
56
+ var RMN_EVENT;
57
+ (function (RMN_EVENT) {
58
+ RMN_EVENT["LIFECYCLE_STATE"] = "LIFECYCLE_STATE";
59
+ RMN_EVENT["SPOT_EVENT"] = "SPOT_EVENT";
60
+ })(RMN_EVENT || (RMN_EVENT = {}));
56
61
  var RMN_SPOT_EVENT;
57
62
  (function (RMN_SPOT_EVENT) {
58
- RMN_SPOT_EVENT["LIFECYCLE_STATE"] = "LIFECYCLE_STATE";
59
63
  RMN_SPOT_EVENT["IMPRESSION"] = "IMPRESSION";
60
64
  RMN_SPOT_EVENT["CLICK"] = "CLICK";
61
65
  RMN_SPOT_EVENT["PURCHASE"] = "PURCHASE";
@@ -15165,6 +15169,12 @@ const GFONT_CORMORANT = `
15165
15169
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant:ital,wght@0,300..700;1,300..700&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap">
15166
15170
  `;
15167
15171
 
15172
+ /**
15173
+ * Determines the event type from a raw event string.
15174
+ *
15175
+ * @param {string} [event] - The raw event string to evaluate.
15176
+ * @returns {RMN_SPOT_EVENT | null} - The corresponding RMN_SPOT_EVENT or null if no match is found.
15177
+ */
15168
15178
  function getEventTypeFromRawEvent(event) {
15169
15179
  if (!event) {
15170
15180
  return null;
@@ -15193,7 +15203,7 @@ function getEventTypeFromRawEvent(event) {
15193
15203
  * Searches for specified property names and collects their primitive values (strings/numbers).
15194
15204
  *
15195
15205
  * @param data - The data structure to search through (can be nested objects/arrays)
15196
- * @param propertyNames - Array of property names to look for (defaults to ['id', 'upc', 'groupingId', 'sku', 'productId'])
15206
+ * @param propertyNames - Array of property names to look for
15197
15207
  * @returns Array of extracted ID values (strings/numbers only)
15198
15208
  *
15199
15209
  * @example
@@ -15204,10 +15214,27 @@ function getEventTypeFromRawEvent(event) {
15204
15214
  * };
15205
15215
  * extractDeepIds(data); // Returns [1, 2, 3, 'abc', 456]
15206
15216
  */
15207
- function extractDeepIds(data, propertyNames = ['id', 'upc', 'groupingId', 'sku', 'productId']) {
15217
+ function extractDeepIds(data, propertyNames) {
15208
15218
  const ids = [];
15219
+ const defaulPropertyNames = [
15220
+ 'id',
15221
+ 'upc',
15222
+ 'groupingId',
15223
+ 'sku',
15224
+ 'productId',
15225
+ 'item_id',
15226
+ 'isbn',
15227
+ 'asin',
15228
+ 'mpn',
15229
+ 'model_number',
15230
+ 'article_number',
15231
+ 'variant_id',
15232
+ 'item_number',
15233
+ 'catalog_id',
15234
+ 'reference_id',
15235
+ ];
15209
15236
  // Set for faster property name lookups
15210
- const propertySet = new Set(propertyNames);
15237
+ const propertySet = new Set(defaulPropertyNames);
15211
15238
  /**
15212
15239
  * Processes a value and extracts IDs if it matches criteria
15213
15240
  * @param value - The value to process
@@ -15242,54 +15269,106 @@ function extractDeepIds(data, propertyNames = ['id', 'upc', 'groupingId', 'sku',
15242
15269
  processValue(data);
15243
15270
  return ids; // No need to filter nulls as we handle that during collection
15244
15271
  }
15245
-
15246
- class DataLayerMonitor {
15247
- constructor() {
15248
- if (!window.dataLayer) {
15249
- return;
15250
- }
15251
- this.originalPush = window.dataLayer.push;
15272
+ // Fallback method using fetch if sendBeacon isn't available
15273
+ async function fallbackEventFire(url) {
15274
+ try {
15275
+ const racePromise = Promise.race([
15276
+ // Promise #1: The fetch request
15277
+ fetch(url, {
15278
+ method: 'POST',
15279
+ keepalive: true,
15280
+ }),
15281
+ // Promise #2: The timeout
15282
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), 2000)),
15283
+ ]);
15284
+ /**
15285
+ * Prevent requests from hanging indefinitely
15286
+ * Improve user experience by failing fast
15287
+ * Handle slow network conditions gracefully
15288
+ * Ensure resources are freed up in a timely manner
15289
+ */
15290
+ const response = await racePromise;
15291
+ return response.ok;
15252
15292
  }
15253
- static getInstance() {
15254
- if (!DataLayerMonitor.instance) {
15255
- DataLayerMonitor.instance = new DataLayerMonitor();
15293
+ catch (_a) {
15294
+ return false;
15295
+ }
15296
+ }
15297
+ /**
15298
+ * Extracts and decodes a URL from a base64-encoded query parameter.
15299
+ *
15300
+ * @param {string} url - The URL containing the base64-encoded query parameter.
15301
+ * @returns {string | null} - The decoded URL or null if not found or invalid.
15302
+ */
15303
+ function getRedirectUrlFromPayload(url) {
15304
+ try {
15305
+ const base64String = new URL(url).searchParams.get('e');
15306
+ if (!base64String) {
15307
+ return null;
15256
15308
  }
15257
- return DataLayerMonitor.instance;
15309
+ const data = JSON.parse(atob(base64String));
15310
+ return data.ur || null;
15258
15311
  }
15259
- setListener(listener) {
15260
- this.listener = listener;
15312
+ catch (_a) {
15313
+ return null;
15261
15314
  }
15262
- start() {
15263
- window.dataLayer.push = (...args) => {
15264
- const result = this.originalPush.apply(window.dataLayer, args);
15265
- const pushedEvent = args[0];
15266
- if (this.listener) {
15267
- const normalizedData = this.cleanEventData(pushedEvent);
15268
- if (normalizedData) {
15269
- this.listener(normalizedData);
15270
- }
15315
+ }
15316
+ /**
15317
+ * Fires an event using the navigator.sendBeacon method or a fallback method if sendBeacon is not available.
15318
+ * If the event is a click event and a redirect URL is found, it redirects the user to that URL.
15319
+ *
15320
+ * @param {IFireEventParams} params - The parameters for firing the event.
15321
+ * @param {RMN_SPOT_EVENT} params.event - The event type.
15322
+ * @param {string} params.eventUrl - The URL to which the event is sent.
15323
+ * @returns {Promise<void>} - A promise that resolves when the event is fired.
15324
+ */
15325
+ async function fireEvent({ event, eventUrl }) {
15326
+ var _a;
15327
+ try {
15328
+ const didFireEvent = ((_a = navigator === null || navigator === void 0 ? void 0 : navigator.sendBeacon) === null || _a === void 0 ? void 0 : _a.call(navigator, eventUrl)) || (await fallbackEventFire(eventUrl));
15329
+ if (!didFireEvent) {
15330
+ return;
15331
+ }
15332
+ if (event === RMN_SPOT_EVENT.CLICK) {
15333
+ const redirectUrl = getRedirectUrlFromPayload(eventUrl);
15334
+ if (redirectUrl) {
15335
+ window.location.href = redirectUrl;
15271
15336
  }
15272
- return result;
15273
- };
15274
- }
15275
- cleanEventData(data) {
15276
- const eventName = getEventTypeFromRawEvent(data.event);
15277
- if (!eventName) {
15278
- return null;
15279
15337
  }
15280
- const productIds = extractDeepIds(data.value);
15281
- return {
15282
- event: eventName,
15283
- productIds,
15284
- };
15285
15338
  }
15286
- stop() {
15287
- if (this.originalPush) {
15288
- window.dataLayer.push = this.originalPush;
15289
- }
15290
- this.listener = undefined;
15339
+ catch (_b) {
15340
+ // Handle errors silently
15291
15341
  }
15292
15342
  }
15343
+ function calculateScaleFactor(elementScale) {
15344
+ // Step 1: Apply square root for non-linear scaling
15345
+ // This creates a more gradual scaling effect, especially for larger changes
15346
+ // For example:
15347
+ // - elementScale of 0.25 (1/4 size) becomes 0.5
15348
+ // - elementScale of 1 (unchanged) remains 1
15349
+ // - elementScale of 4 (4x size) becomes 2
15350
+ const baseFactor = Math.sqrt(elementScale);
15351
+ // Step 2: Apply additional dampening to further soften the scaling effect
15352
+ // The dampening factor (0.5) can be adjusted:
15353
+ // - Lower values (closer to 0) make scaling more subtle
15354
+ // - Higher values (closer to 1) make scaling more pronounced
15355
+ const dampening = 0.35;
15356
+ // Calculate the scaleFactor:
15357
+ // 1. (baseFactor - 1) represents the change from the original size
15358
+ // 2. Multiply by dampening to reduce the effect
15359
+ // 3. Add 1 to center the scaling around the original size
15360
+ // For example, if baseFactor is 2:
15361
+ // scaleFactor = 1 + (2 - 1) * 0.5 = 1.5
15362
+ const scaleFactor = 1 + (baseFactor - 1) * dampening;
15363
+ // Step 3: Define the allowed range for the scale factor
15364
+ // This ensures that the font size never changes too drastically
15365
+ const minScale = 0.35; // Font will never be smaller than 50% of original
15366
+ const maxScale = 1.5; // Font will never be larger than 150% of original
15367
+ // Step 4: Clamp the scale factor to the defined range
15368
+ // Math.min ensures the value doesn't exceed maxScale
15369
+ // Math.max ensures the value isn't less than minScale
15370
+ return Math.max(minScale, Math.min(maxScale, scaleFactor));
15371
+ }
15293
15372
 
15294
15373
  class IntersectionObserverService {
15295
15374
  constructor(defaultOptions = {}) {
@@ -15339,7 +15418,7 @@ var ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX;
15339
15418
  ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX["PRODUCT_IDS"] = 3] = "PRODUCT_IDS";
15340
15419
  ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX["CREATED_AT"] = 4] = "CREATED_AT";
15341
15420
  })(ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX || (ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX = {}));
15342
- class LocalStorage {
15421
+ class LocalStorageService {
15343
15422
  constructor() {
15344
15423
  if (typeof window.localStorage === 'undefined') {
15345
15424
  console.warn('Local storage is not supported in this environment');
@@ -15352,13 +15431,13 @@ class LocalStorage {
15352
15431
  this.removeExpiredSpots();
15353
15432
  }
15354
15433
  static getInstance() {
15355
- if (!LocalStorage.instance) {
15356
- LocalStorage.instance = new LocalStorage();
15434
+ if (!LocalStorageService.instance) {
15435
+ LocalStorageService.instance = new LocalStorageService();
15357
15436
  }
15358
- return LocalStorage.instance;
15437
+ return LocalStorageService.instance;
15359
15438
  }
15360
15439
  syncLocalStorage() {
15361
- const localStorageData = window.localStorage.getItem(LocalStorage.localStorageKey);
15440
+ const localStorageData = window.localStorage.getItem(LocalStorageService.localStorageKey);
15362
15441
  // TODO: Encrypt the data before storing it in the local storage
15363
15442
  if (localStorageData) {
15364
15443
  try {
@@ -15408,17 +15487,17 @@ class LocalStorage {
15408
15487
  for (const [key, value] of Object.entries(data)) {
15409
15488
  dataArray[key] = this.objectToArray(value);
15410
15489
  }
15411
- window.localStorage.setItem(LocalStorage.localStorageKey, JSON.stringify(dataArray));
15490
+ window.localStorage.setItem(LocalStorageService.localStorageKey, JSON.stringify(dataArray));
15412
15491
  }
15413
15492
  clearLocalStorage() {
15414
- window.localStorage.removeItem(LocalStorage.localStorageKey);
15493
+ window.localStorage.removeItem(LocalStorageService.localStorageKey);
15415
15494
  }
15416
15495
  removeExpiredSpots() {
15417
15496
  var _a;
15418
15497
  const currentTime = Date.now();
15419
15498
  (_a = this.spots) === null || _a === void 0 ? void 0 : _a.forEach((spot, spotId) => {
15420
15499
  var _a, _b;
15421
- if (currentTime - ((_a = spot.createdAt) !== null && _a !== void 0 ? _a : 0) > LocalStorage.spotExpirationTime) {
15500
+ if (currentTime - ((_a = spot.createdAt) !== null && _a !== void 0 ? _a : 0) > LocalStorageService.spotExpirationTime) {
15422
15501
  (_b = this.spots) === null || _b === void 0 ? void 0 : _b.delete(spotId);
15423
15502
  }
15424
15503
  });
@@ -15443,8 +15522,89 @@ class LocalStorage {
15443
15522
  };
15444
15523
  }
15445
15524
  }
15446
- LocalStorage.localStorageKey = 'lc_rmn';
15447
- LocalStorage.spotExpirationTime = 1000 * 60 * 60 * 24 * 7; // 7 days
15525
+ LocalStorageService.localStorageKey = 'lc_rmn';
15526
+ LocalStorageService.spotExpirationTime = 1000 * 60 * 60 * 24 * 7; // 7 days
15527
+
15528
+ /**
15529
+ * PubsubService class
15530
+ * Manages event subscriptions and publications
15531
+ * @template IRmnEventMap A record type defining the structure of events and their data
15532
+ */
15533
+ class PubsubService {
15534
+ constructor() {
15535
+ /**
15536
+ * Object to store subscribers for each event type
15537
+ */
15538
+ this.subscribers = {};
15539
+ }
15540
+ static getInstance() {
15541
+ if (!PubsubService.instance) {
15542
+ PubsubService.instance = new PubsubService();
15543
+ }
15544
+ return PubsubService.instance;
15545
+ }
15546
+ /**
15547
+ * Subscribe to an event
15548
+ * @param eventType - The type of event to subscribe to
15549
+ * @param callback - The function to be called when the event is published
15550
+ * @returns A function to unsubscribe from the event
15551
+ *
15552
+ * @Example:
15553
+ * const unsubscribe = pubSub.subscribe('userLogin', (data) => {
15554
+ * console.log(`User ${data.username} logged in`);
15555
+ * });
15556
+ */
15557
+ subscribe(eventType, callback) {
15558
+ if (!this.subscribers[eventType]) {
15559
+ this.subscribers[eventType] = [];
15560
+ }
15561
+ this.subscribers[eventType].push(callback);
15562
+ // Return an unsubscribe function
15563
+ return () => {
15564
+ this.subscribers[eventType] = this.subscribers[eventType].filter((cb) => cb !== callback);
15565
+ };
15566
+ }
15567
+ /**
15568
+ * Publish an event
15569
+ * @param eventType - The type of event to publish
15570
+ * @param data - The data to be passed to the event subscribers
15571
+ *
15572
+ * @Example:
15573
+ * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
15574
+ */
15575
+ publish(eventType, data) {
15576
+ if (!this.subscribers[eventType]) {
15577
+ return;
15578
+ }
15579
+ this.subscribers[eventType].forEach((callback) => callback(data));
15580
+ }
15581
+ }
15582
+ /**
15583
+ * Usage Example:
15584
+ *
15585
+ * interface IRmnEventMap {
15586
+ * userLogin: { username: string; timestamp: number };
15587
+ * pageView: { url: string; timestamp: number };
15588
+ * }
15589
+ *
15590
+ * const pubSub = new PubsubService<IRmnEventMap>();
15591
+ *
15592
+ * // Subscribe to events
15593
+ * const unsubscribeLogin = pubSub.subscribe('userLogin', (data) => {
15594
+ * console.log(`User ${data.username} logged in at ${new Date(data.timestamp)}`);
15595
+ * });
15596
+ *
15597
+ * pubSub.subscribe('pageView', (data) => {
15598
+ * console.log(`Page ${data.url} viewed at ${new Date(data.timestamp)}`);
15599
+ * });
15600
+ *
15601
+ * // Publish events
15602
+ * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
15603
+ * pubSub.publish('pageView', { url: '/home', timestamp: Date.now() });
15604
+ *
15605
+ * // Unsubscribe from an event
15606
+ * unsubscribeLogin();
15607
+ */
15448
15608
 
15449
15609
  class ResizeObserverService {
15450
15610
  constructor({ element, maxSize, minScale }) {
@@ -15514,36 +15674,6 @@ class ResizeObserverService {
15514
15674
  }
15515
15675
  }
15516
15676
 
15517
- function calculateScaleFactor(elementScale) {
15518
- // Step 1: Apply square root for non-linear scaling
15519
- // This creates a more gradual scaling effect, especially for larger changes
15520
- // For example:
15521
- // - elementScale of 0.25 (1/4 size) becomes 0.5
15522
- // - elementScale of 1 (unchanged) remains 1
15523
- // - elementScale of 4 (4x size) becomes 2
15524
- const baseFactor = Math.sqrt(elementScale);
15525
- // Step 2: Apply additional dampening to further soften the scaling effect
15526
- // The dampening factor (0.5) can be adjusted:
15527
- // - Lower values (closer to 0) make scaling more subtle
15528
- // - Higher values (closer to 1) make scaling more pronounced
15529
- const dampening = 0.35;
15530
- // Calculate the scaleFactor:
15531
- // 1. (baseFactor - 1) represents the change from the original size
15532
- // 2. Multiply by dampening to reduce the effect
15533
- // 3. Add 1 to center the scaling around the original size
15534
- // For example, if baseFactor is 2:
15535
- // scaleFactor = 1 + (2 - 1) * 0.5 = 1.5
15536
- const scaleFactor = 1 + (baseFactor - 1) * dampening;
15537
- // Step 3: Define the allowed range for the scale factor
15538
- // This ensures that the font size never changes too drastically
15539
- const minScale = 0.35; // Font will never be smaller than 50% of original
15540
- const maxScale = 1.5; // Font will never be larger than 150% of original
15541
- // Step 4: Clamp the scale factor to the defined range
15542
- // Math.min ensures the value doesn't exceed maxScale
15543
- // Math.max ensures the value isn't less than minScale
15544
- return Math.max(minScale, Math.min(maxScale, scaleFactor));
15545
- }
15546
-
15547
15677
  const CAROUSEL_COMPONENT_STYLE = ({ width, height, fluid }) => `
15548
15678
  :host {
15549
15679
  position: relative;
@@ -18129,97 +18259,65 @@ const SPOT_TEMPLATE_HTML_ELEMENT = (spot, config) => {
18129
18259
  return spotHtmlStringToElement(spotHtmlString);
18130
18260
  };
18131
18261
 
18132
- /**
18133
- * PubSub class
18134
- * Manages event subscriptions and publications
18135
- * @template IRmnEventMap A record type defining the structure of events and their data
18136
- */
18137
- class PubSub {
18262
+ // For the moment, we will only focus on sites that use Google Analytics,
18263
+ // but we will add support for other analytics tools in the future.
18264
+ var AnalyticsTool;
18265
+ (function (AnalyticsTool) {
18266
+ AnalyticsTool["GoogleAnalytics"] = "google-analytics";
18267
+ AnalyticsTool["Other"] = "Other";
18268
+ })(AnalyticsTool || (AnalyticsTool = {}));
18269
+
18270
+ class DataLayerMonitor {
18138
18271
  constructor() {
18139
- /**
18140
- * Object to store subscribers for each event type
18141
- */
18142
- this.subscribers = {};
18272
+ if (!window.dataLayer) {
18273
+ return;
18274
+ }
18275
+ this.originalPush = window.dataLayer.push;
18143
18276
  }
18144
18277
  static getInstance() {
18145
- if (!PubSub.instance) {
18146
- PubSub.instance = new PubSub();
18278
+ if (!DataLayerMonitor.instance) {
18279
+ DataLayerMonitor.instance = new DataLayerMonitor();
18147
18280
  }
18148
- return PubSub.instance;
18281
+ return DataLayerMonitor.instance;
18149
18282
  }
18150
- /**
18151
- * Subscribe to an event
18152
- * @param eventType - The type of event to subscribe to
18153
- * @param callback - The function to be called when the event is published
18154
- * @returns A function to unsubscribe from the event
18155
- *
18156
- * @Example:
18157
- * const unsubscribe = pubSub.subscribe('userLogin', (data) => {
18158
- * console.log(`User ${data.username} logged in`);
18159
- * });
18160
- */
18161
- subscribe(eventType, callback) {
18162
- if (!this.subscribers[eventType]) {
18163
- this.subscribers[eventType] = [];
18283
+ setListener(listener) {
18284
+ this.listener = listener;
18285
+ }
18286
+ start() {
18287
+ window.dataLayer.push = (...args) => {
18288
+ const result = this.originalPush.apply(window.dataLayer, args);
18289
+ const pushedEvent = args[0];
18290
+ if (this.listener) {
18291
+ const normalizedData = this.cleanEventData(pushedEvent);
18292
+ if (normalizedData) {
18293
+ this.listener(normalizedData);
18294
+ }
18295
+ }
18296
+ return result;
18297
+ };
18298
+ }
18299
+ cleanEventData(data) {
18300
+ const eventName = getEventTypeFromRawEvent(data.event);
18301
+ if (!eventName) {
18302
+ return null;
18164
18303
  }
18165
- this.subscribers[eventType].push(callback);
18166
- // Return an unsubscribe function
18167
- return () => {
18168
- this.subscribers[eventType] = this.subscribers[eventType].filter((cb) => cb !== callback);
18304
+ const productIds = extractDeepIds(data.value);
18305
+ return {
18306
+ event: eventName,
18307
+ productIds,
18169
18308
  };
18170
18309
  }
18171
- /**
18172
- * Publish an event
18173
- * @param eventType - The type of event to publish
18174
- * @param data - The data to be passed to the event subscribers
18175
- *
18176
- * @Example:
18177
- * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
18178
- */
18179
- publish(eventType, data) {
18180
- if (!this.subscribers[eventType]) {
18181
- return;
18310
+ stop() {
18311
+ if (this.originalPush) {
18312
+ window.dataLayer.push = this.originalPush;
18182
18313
  }
18183
- this.subscribers[eventType].forEach((callback) => callback(data));
18314
+ this.listener = undefined;
18184
18315
  }
18185
18316
  }
18186
- /**
18187
- * Usage Example:
18188
- *
18189
- * interface IRmnEventMap {
18190
- * userLogin: { username: string; timestamp: number };
18191
- * pageView: { url: string; timestamp: number };
18192
- * }
18193
- *
18194
- * const pubSub = new PubSub<IRmnEventMap>();
18195
- *
18196
- * // Subscribe to events
18197
- * const unsubscribeLogin = pubSub.subscribe('userLogin', (data) => {
18198
- * console.log(`User ${data.username} logged in at ${new Date(data.timestamp)}`);
18199
- * });
18200
- *
18201
- * pubSub.subscribe('pageView', (data) => {
18202
- * console.log(`Page ${data.url} viewed at ${new Date(data.timestamp)}`);
18203
- * });
18204
- *
18205
- * // Publish events
18206
- * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
18207
- * pubSub.publish('pageView', { url: '/home', timestamp: Date.now() });
18208
- *
18209
- * // Unsubscribe from an event
18210
- * unsubscribeLogin();
18211
- */
18212
18317
 
18213
18318
  // @TODO: Add support for user to push events to our own data layer, if they don't use any analytics tool.
18214
18319
  // window.rmnDataLayer = window.rmnDataLayer || [];
18215
- // For the moment, we will only focus on sites that use Google Analytics,
18216
- // but we will add support for other analytics tools in the future.
18217
- var AnalyticsTool;
18218
- (function (AnalyticsTool) {
18219
- AnalyticsTool["GoogleAnalytics"] = "google-analytics";
18220
- AnalyticsTool["Other"] = "Other";
18221
- })(AnalyticsTool || (AnalyticsTool = {}));
18222
- class UserMonitor {
18320
+ class MonitorService {
18223
18321
  constructor() {
18224
18322
  const analyticsTool = this.detectAnalyticsTool();
18225
18323
  switch (analyticsTool) {
@@ -18234,29 +18332,97 @@ class UserMonitor {
18234
18332
  if (analyticsTool === AnalyticsTool.Other) {
18235
18333
  return;
18236
18334
  }
18237
- this.localStorage = LocalStorage.getInstance();
18335
+ this.pubSubService = PubsubService.getInstance();
18336
+ this.localStorageService = LocalStorageService.getInstance();
18238
18337
  }
18239
18338
  static getInstance() {
18240
- if (!UserMonitor.instance) {
18241
- UserMonitor.instance = new UserMonitor();
18339
+ if (!MonitorService.instance) {
18340
+ MonitorService.instance = new MonitorService();
18242
18341
  }
18243
- return UserMonitor.instance;
18342
+ return MonitorService.instance;
18244
18343
  }
18245
18344
  start() {
18246
18345
  if (!this.implementedMonitor)
18247
18346
  return;
18248
- this.implementedMonitor.setListener((eventData) => {
18347
+ this.implementedMonitor.setListener(async (eventData) => {
18249
18348
  var _a;
18250
- this.matchAndFireEvent(eventData, (_a = this.localStorage) === null || _a === void 0 ? void 0 : _a.getSpots());
18349
+ await this.matchAndFireEvent(eventData, (_a = this.localStorageService) === null || _a === void 0 ? void 0 : _a.getSpots());
18251
18350
  });
18252
18351
  this.implementedMonitor.start();
18253
18352
  }
18254
- matchAndFireEvent(
18255
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
18256
- _eventData,
18257
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
18258
- _spots) {
18259
- // console.info({ eventData, spots });
18353
+ async matchAndFireEvent(eventData, spots) {
18354
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
18355
+ if (!spots)
18356
+ return;
18357
+ const eventProductIds = new Set(eventData.productIds);
18358
+ for (const spot of Object.values(spots)) {
18359
+ if (!spot.productIds.length)
18360
+ continue;
18361
+ const hasCommonProductIds = spot.productIds.find((productId) => eventProductIds.has(productId));
18362
+ if (hasCommonProductIds) {
18363
+ switch (eventData.event) {
18364
+ case RMN_SPOT_EVENT.ADD_TO_CART:
18365
+ await this.fireAndPublishSpotEvent({
18366
+ spotEvent: RMN_SPOT_EVENT.ADD_TO_CART,
18367
+ eventUrl: (_b = (_a = spot.events.find((event) => event.event === RMN_SPOT_EVENT.ADD_TO_CART)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
18368
+ placementId: '',
18369
+ spotId: spot.spotId,
18370
+ spotElement: undefined,
18371
+ });
18372
+ break;
18373
+ case RMN_SPOT_EVENT.REMOVE_FROM_CART:
18374
+ await this.fireAndPublishSpotEvent({
18375
+ spotEvent: RMN_SPOT_EVENT.REMOVE_FROM_CART,
18376
+ eventUrl: (_d = (_c = spot.events.find((event) => event.event === RMN_SPOT_EVENT.REMOVE_FROM_CART)) === null || _c === void 0 ? void 0 : _c.url) !== null && _d !== void 0 ? _d : '',
18377
+ placementId: '',
18378
+ spotId: spot.spotId,
18379
+ spotElement: undefined,
18380
+ });
18381
+ break;
18382
+ case RMN_SPOT_EVENT.PURCHASE:
18383
+ await this.fireAndPublishSpotEvent({
18384
+ spotEvent: RMN_SPOT_EVENT.PURCHASE,
18385
+ eventUrl: (_f = (_e = spot.events.find((event) => event.event === RMN_SPOT_EVENT.PURCHASE)) === null || _e === void 0 ? void 0 : _e.url) !== null && _f !== void 0 ? _f : '',
18386
+ placementId: '',
18387
+ spotId: spot.spotId,
18388
+ spotElement: undefined,
18389
+ });
18390
+ break;
18391
+ case RMN_SPOT_EVENT.ADD_TO_WISHLIST:
18392
+ await this.fireAndPublishSpotEvent({
18393
+ spotEvent: RMN_SPOT_EVENT.ADD_TO_WISHLIST,
18394
+ eventUrl: (_h = (_g = spot.events.find((event) => event.event === RMN_SPOT_EVENT.ADD_TO_WISHLIST)) === null || _g === void 0 ? void 0 : _g.url) !== null && _h !== void 0 ? _h : '',
18395
+ placementId: '',
18396
+ spotId: spot.spotId,
18397
+ spotElement: undefined,
18398
+ });
18399
+ break;
18400
+ case RMN_SPOT_EVENT.BUY_NOW:
18401
+ await this.fireAndPublishSpotEvent({
18402
+ spotEvent: RMN_SPOT_EVENT.BUY_NOW,
18403
+ eventUrl: (_k = (_j = spot.events.find((event) => event.event === RMN_SPOT_EVENT.BUY_NOW)) === null || _j === void 0 ? void 0 : _j.url) !== null && _k !== void 0 ? _k : '',
18404
+ placementId: '',
18405
+ spotId: spot.spotId,
18406
+ spotElement: undefined,
18407
+ });
18408
+ break;
18409
+ }
18410
+ }
18411
+ }
18412
+ }
18413
+ async fireAndPublishSpotEvent({ spotEvent, eventUrl, placementId, spotId, spotElement, }) {
18414
+ await fireEvent({
18415
+ event: spotEvent,
18416
+ eventUrl,
18417
+ });
18418
+ if (!this.pubSubService)
18419
+ return;
18420
+ this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
18421
+ eventType: spotEvent,
18422
+ placementId,
18423
+ spotId,
18424
+ spotElement,
18425
+ });
18260
18426
  }
18261
18427
  detectAnalyticsTool() {
18262
18428
  let analyticsTool = AnalyticsTool.Other;
@@ -18284,13 +18450,13 @@ class UserMonitor {
18284
18450
 
18285
18451
  class EventService {
18286
18452
  constructor() {
18287
- this.pubSub = PubSub.getInstance();
18288
- this.localStorage = LocalStorage.getInstance();
18453
+ this.pubSubService = PubsubService.getInstance();
18454
+ this.localStorageService = LocalStorageService.getInstance();
18289
18455
  this.activeSpots = new Map();
18290
18456
  this.spotStates = new Map();
18291
18457
  this.intersectionObserver = new IntersectionObserverService();
18292
18458
  // Start the user monitor, which will track and check user interactions
18293
- UserMonitor.getInstance().start();
18459
+ MonitorService.getInstance().start();
18294
18460
  }
18295
18461
  static getInstance() {
18296
18462
  if (!EventService.instance) {
@@ -18299,10 +18465,10 @@ class EventService {
18299
18465
  return EventService.instance;
18300
18466
  }
18301
18467
  subscribe(eventType, callback) {
18302
- return this.pubSub.subscribe(eventType, callback);
18468
+ return this.pubSubService.subscribe(eventType, callback);
18303
18469
  }
18304
18470
  publish(eventType, data) {
18305
- this.pubSub.publish(eventType, data);
18471
+ this.pubSubService.publish(eventType, data);
18306
18472
  }
18307
18473
  registerSpot(params) {
18308
18474
  const { placementId, spot, spotElement } = params;
@@ -18386,7 +18552,7 @@ class EventService {
18386
18552
  const merged = this.deepMerge(currentState, updates);
18387
18553
  this.spotStates.set(placementId, merged);
18388
18554
  if (publish) {
18389
- this.pubSub.publish(RMN_SPOT_EVENT.LIFECYCLE_STATE, this.spotStates.get(placementId));
18555
+ this.pubSubService.publish(RMN_EVENT.LIFECYCLE_STATE, this.spotStates.get(placementId));
18390
18556
  }
18391
18557
  }
18392
18558
  deepMerge(current, updates) {
@@ -18417,19 +18583,18 @@ class EventService {
18417
18583
  }
18418
18584
  async handleClick({ placementId, spot, spotElement, }) {
18419
18585
  var _a, _b, _c;
18420
- // Publish click event
18421
- this.pubSub.publish(RMN_SPOT_EVENT.CLICK, {
18586
+ this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
18587
+ eventType: RMN_SPOT_EVENT.CLICK,
18422
18588
  placementId,
18423
18589
  spotId: spot.id,
18424
18590
  spotElement,
18425
18591
  });
18426
- // Fire click event
18427
- await this.fireEvent({
18592
+ await fireEvent({
18428
18593
  event: RMN_SPOT_EVENT.CLICK,
18429
18594
  eventUrl: (_b = (_a = spot.events.find((event) => event.event === RMN_SPOT_EVENT.CLICK)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
18430
18595
  });
18431
18596
  // Save spot to local storage for event tracking
18432
- this.localStorage.setSpot(spot.id, {
18597
+ this.localStorageService.setSpot(spot.id, {
18433
18598
  spotId: spot.id,
18434
18599
  spotType: spot.spot,
18435
18600
  events: spot.events,
@@ -18449,90 +18614,20 @@ class EventService {
18449
18614
  this.intersectionObserver.observe(spotElement, spotIsVisibleCallback);
18450
18615
  }
18451
18616
  fireImpressionEvent(placementId, spot, spotElement) {
18452
- this.pubSub.publish(RMN_SPOT_EVENT.IMPRESSION, {
18617
+ this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
18618
+ eventType: RMN_SPOT_EVENT.IMPRESSION,
18453
18619
  placementId,
18454
18620
  spotId: spot.id,
18455
18621
  spotElement,
18456
18622
  });
18457
18623
  (async () => {
18458
18624
  var _a, _b;
18459
- await this.fireEvent({
18625
+ await fireEvent({
18460
18626
  event: RMN_SPOT_EVENT.IMPRESSION,
18461
18627
  eventUrl: (_b = (_a = spot.events.find((event) => event.event === RMN_SPOT_EVENT.IMPRESSION)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
18462
18628
  });
18463
18629
  })();
18464
18630
  }
18465
- /**
18466
- * Fires an event using the navigator.sendBeacon method or a fallback method if sendBeacon is not available.
18467
- * If the event is a click event and a redirect URL is found, it redirects the user to that URL.
18468
- *
18469
- * @param {IFireEventParams} params - The parameters for firing the event.
18470
- * @param {RMN_SPOT_EVENT} params.event - The event type.
18471
- * @param {string} params.eventUrl - The URL to which the event is sent.
18472
- * @returns {Promise<void>} - A promise that resolves when the event is fired.
18473
- */
18474
- async fireEvent({ event, eventUrl }) {
18475
- var _a;
18476
- try {
18477
- const didFireEvent = ((_a = navigator === null || navigator === void 0 ? void 0 : navigator.sendBeacon) === null || _a === void 0 ? void 0 : _a.call(navigator, eventUrl)) || (await this.fallbackEventFire(eventUrl));
18478
- if (!didFireEvent) {
18479
- return;
18480
- }
18481
- if (event === RMN_SPOT_EVENT.CLICK) {
18482
- const redirectUrl = this.getRedirectUrlFromPayload(eventUrl);
18483
- if (redirectUrl) {
18484
- window.location.href = redirectUrl;
18485
- }
18486
- }
18487
- }
18488
- catch (_b) {
18489
- // Handle errors silently
18490
- }
18491
- }
18492
- // Fallback method using fetch if sendBeacon isn't available
18493
- async fallbackEventFire(url) {
18494
- try {
18495
- const racePromise = Promise.race([
18496
- // Promise #1: The fetch request
18497
- fetch(url, {
18498
- method: 'POST',
18499
- keepalive: true,
18500
- }),
18501
- // Promise #2: The timeout
18502
- new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), 2000)),
18503
- ]);
18504
- /**
18505
- * Prevent requests from hanging indefinitely
18506
- * Improve user experience by failing fast
18507
- * Handle slow network conditions gracefully
18508
- * Ensure resources are freed up in a timely manner
18509
- */
18510
- const response = await racePromise;
18511
- return response.ok;
18512
- }
18513
- catch (_a) {
18514
- return false;
18515
- }
18516
- }
18517
- /**
18518
- * Extracts and decodes a URL from a base64-encoded query parameter.
18519
- *
18520
- * @param {string} url - The URL containing the base64-encoded query parameter.
18521
- * @returns {string | null} - The decoded URL or null if not found or invalid.
18522
- */
18523
- getRedirectUrlFromPayload(url) {
18524
- try {
18525
- const base64String = new URL(url).searchParams.get('e');
18526
- if (!base64String) {
18527
- return null;
18528
- }
18529
- const data = JSON.parse(atob(base64String));
18530
- return data.ur || null;
18531
- }
18532
- catch (_a) {
18533
- return null;
18534
- }
18535
- }
18536
18631
  }
18537
18632
 
18538
18633
  const SELECTION_API_PATH = '/spots/selection';
@@ -19182,4 +19277,4 @@ function RmnCreateSpotElement(spot, config) {
19182
19277
  });
19183
19278
  }
19184
19279
 
19185
- export { LiquidCommerceRmnClient, RMN_ENV, RMN_FILTER_PROPERTIES, RMN_SPOT_EVENT, RMN_SPOT_TYPE, RmnClient, RmnCreateSpotElement, RmnEventManager };
19280
+ export { LiquidCommerceRmnClient, RMN_ENV, RMN_EVENT, RMN_FILTER_PROPERTIES, RMN_SPOT_EVENT, RMN_SPOT_TYPE, RmnClient, RmnCreateSpotElement, RmnEventManager };