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