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

Sign up to get free protection for your applications and to get access to all the features.
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 };