@liquidcommercedev/rmn-sdk 1.5.0-beta.7 → 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;
@@ -15558,11 +15688,13 @@ const CAROUSEL_COMPONENT_STYLE = ({ width, height, fluid }) => `
15558
15688
  position: relative;
15559
15689
  height: 100%;
15560
15690
  width: 100%;
15691
+ display: flex;
15692
+ transition: transform 0.5s ease-in-out;
15561
15693
  }
15562
15694
 
15563
15695
  .slide {
15564
- display: none;
15565
-
15696
+ flex: 0 0 100%;
15697
+ display: flex;
15566
15698
  justify-content: center;
15567
15699
  align-items: center;
15568
15700
  height: 100%;
@@ -15867,9 +15999,9 @@ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined
15867
15999
  renderSlides() {
15868
16000
  const slidesContainer = document.createElement('div');
15869
16001
  slidesContainer.className = 'slides';
15870
- this.slides.forEach((slide, index) => {
16002
+ this.slides.forEach((slide) => {
15871
16003
  const slideElement = document.createElement('div');
15872
- slideElement.className = `slide ${index === this.currentSlide ? 'active' : ''}`;
16004
+ slideElement.className = 'slide';
15873
16005
  if (slide instanceof HTMLElement) {
15874
16006
  slideElement.appendChild(slide);
15875
16007
  }
@@ -15948,10 +16080,9 @@ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined
15948
16080
  updateCarousel() {
15949
16081
  if (!this.slidesContainer)
15950
16082
  return;
15951
- const slides = Array.from(this.slidesContainer.children);
15952
- slides.forEach((slide, index) => {
15953
- slide.classList.toggle('active', index === this.currentSlide);
15954
- });
16083
+ // Calculate the translation distance based on current slide
16084
+ const translateX = -this.currentSlide * 100;
16085
+ this.slidesContainer.style.transform = `translateX(${translateX}%)`;
15955
16086
  this.updateDots();
15956
16087
  }
15957
16088
  updateDots() {
@@ -16375,12 +16506,13 @@ function spotHtmlStringToElement(htmlString) {
16375
16506
  spot.className = 'spot';
16376
16507
  spot.innerHTML = htmlString;
16377
16508
  Object.assign(spot.style, {
16378
- position: 'relative',
16379
16509
  display: 'block',
16380
16510
  width: '100%',
16381
16511
  height: '100%',
16382
16512
  margin: '0',
16383
16513
  padding: '0',
16514
+ containerType: 'inline-size',
16515
+ position: 'relative',
16384
16516
  });
16385
16517
  return spot;
16386
16518
  }
@@ -17250,7 +17382,6 @@ const STYLES$6 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderCo
17250
17382
  box-sizing: border-box;
17251
17383
  color: ${textColor};
17252
17384
  cursor: pointer;
17253
- container-type: inline-size;
17254
17385
  }
17255
17386
 
17256
17387
  .${prefix}__text {
@@ -17266,7 +17397,7 @@ const STYLES$6 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderCo
17266
17397
  .${prefix}__header {
17267
17398
  font-size: 24px;
17268
17399
  margin: 0;
17269
- font-family: "Cormorant";
17400
+ font-family: "Cormorant", system-ui;
17270
17401
  font-style: normal;
17271
17402
  font-weight: 300;
17272
17403
  line-height: normal;
@@ -17385,7 +17516,6 @@ const STYLES$5 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
17385
17516
  height: 100%;
17386
17517
  display: block;
17387
17518
  position: relative;
17388
- container-type: inline-size;
17389
17519
  }
17390
17520
 
17391
17521
  .${prefix}__content {
@@ -17576,15 +17706,20 @@ function rbHomepageHeroThreeTileTemplate(spot, config) {
17576
17706
  const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextColor = textColor, primaryImage, mobilePrimaryImage = primaryImage, }, { prefix }) => `
17577
17707
  <style>
17578
17708
  .${prefix} {
17709
+ width: 100%;
17710
+ height: 100%;
17711
+ background-color: transparent;
17712
+ cursor: pointer;
17713
+ position: relative;
17714
+ }
17715
+
17716
+ .${prefix}__content {
17579
17717
  width: 100%;
17580
17718
  height: 100%;
17581
17719
  display: flex;
17582
17720
  flex-direction: column-reverse;
17583
17721
  background-color: transparent;
17584
17722
  gap: 6px;
17585
- cursor: pointer;
17586
- container-type: inline-size;
17587
- position: relative;
17588
17723
  }
17589
17724
 
17590
17725
  .${prefix}__image {
@@ -17615,7 +17750,7 @@ const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
17615
17750
  font-size: 18px;
17616
17751
  margin: 0;
17617
17752
  color: inherit;
17618
- font-family: "Cormorant";
17753
+ font-family: "Cormorant", system-ui;
17619
17754
  font-style: normal;
17620
17755
  font-weight: 700;
17621
17756
  line-height: normal;
@@ -17657,7 +17792,7 @@ const STYLES$4 = ({ textColor = '#212121', backgroundColor = '#e8e6de', ctaTextC
17657
17792
  }
17658
17793
 
17659
17794
  @container (min-width: 768px) {
17660
- .${prefix} {
17795
+ .${prefix}__content {
17661
17796
  flex-direction: row;
17662
17797
  }
17663
17798
  .${prefix}__image {
@@ -17712,12 +17847,14 @@ function rbHomepageHeroTwoTileTemplate(spot, config) {
17712
17847
  ${GFONT_CORMORANT}
17713
17848
  ${STYLES$4(spot, config)}
17714
17849
  <div class="${prefix}">
17715
- <div class="${prefix}__text">
17716
- ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
17717
- ${spot.description ? `<p class="${prefix}__description">${spot.description}</p>` : ''}
17718
- ${spot.ctaText ? `<span class="${prefix}__cta-button">${spot.ctaText}</span>` : ''}
17719
- </div>
17720
- <div class="${prefix}__image"></div>
17850
+ <div class="${prefix}__content">
17851
+ <div class="${prefix}__text">
17852
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
17853
+ ${spot.description ? `<p class="${prefix}__description">${spot.description}</p>` : ''}
17854
+ ${spot.ctaText ? `<span class="${prefix}__cta-button">${spot.ctaText}</span>` : ''}
17855
+ </div>
17856
+ <div class="${prefix}__image"></div>
17857
+ </div>
17721
17858
  </div>
17722
17859
  `;
17723
17860
  }
@@ -17740,12 +17877,10 @@ const STYLES$3 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderCo
17740
17877
  overflow: hidden;
17741
17878
  cursor: pointer;
17742
17879
  color: ${textColor};
17743
- container-type: inline-size;
17744
17880
  }
17745
17881
 
17746
17882
  .${prefix}__text {
17747
17883
  padding: 20px;
17748
- width: 70%;
17749
17884
  display: flex;
17750
17885
  flex-direction: column;
17751
17886
  justify-content: center;
@@ -17757,7 +17892,7 @@ const STYLES$3 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderCo
17757
17892
  color: inherit;
17758
17893
  margin: 0;
17759
17894
  font-size: 20px;
17760
- font-family: "Cormorant";
17895
+ font-family: "Cormorant", system-ui;
17761
17896
  font-style: normal;
17762
17897
  font-weight: 300;
17763
17898
  line-height: normal;
@@ -17876,7 +18011,6 @@ const STYLES$2 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = pr
17876
18011
  background-size: cover;
17877
18012
  background-position: center;
17878
18013
  background-repeat: no-repeat;
17879
- container-type: inline-size;
17880
18014
  position: relative;
17881
18015
  }
17882
18016
 
@@ -17946,12 +18080,7 @@ const STYLES$1 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = pr
17946
18080
  border-radius: 5px;
17947
18081
  overflow: hidden;
17948
18082
  cursor: pointer;
17949
- container-type: inline-size;
17950
- }
17951
-
17952
- .${prefix}__text {
17953
- padding: 10px;
17954
- width: 70%;
18083
+ position: relative;
17955
18084
  }
17956
18085
 
17957
18086
  .${prefix}__header {
@@ -17962,6 +18091,7 @@ const STYLES$1 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = pr
17962
18091
  font-style: normal;
17963
18092
  font-weight: 400;
17964
18093
  margin: 0;
18094
+ padding: 10px;
17965
18095
  }
17966
18096
 
17967
18097
  @container (min-width: 640px) {
@@ -17979,9 +18109,7 @@ function rbSmallCategoryImageToutTemplate(spot, config) {
17979
18109
  ${GFONT_CORMORANT}
17980
18110
  ${STYLES$1(spot, config)}
17981
18111
  <div class="${prefix}">
17982
- <div class="${prefix}__text">
17983
- ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
17984
- </div>
18112
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
17985
18113
  </div>
17986
18114
  `;
17987
18115
  }
@@ -17996,7 +18124,6 @@ const STYLES = ({ textColor = '#000000', backgroundColor = 'transparent', primar
17996
18124
  display: flex;
17997
18125
  flex-direction: column;
17998
18126
  border-radius: 5px;
17999
- container-type: inline-size;
18000
18127
  }
18001
18128
 
18002
18129
  .${prefix}__image {
@@ -18132,97 +18259,65 @@ const SPOT_TEMPLATE_HTML_ELEMENT = (spot, config) => {
18132
18259
  return spotHtmlStringToElement(spotHtmlString);
18133
18260
  };
18134
18261
 
18135
- /**
18136
- * PubSub class
18137
- * Manages event subscriptions and publications
18138
- * @template IRmnEventMap A record type defining the structure of events and their data
18139
- */
18140
- 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 {
18141
18271
  constructor() {
18142
- /**
18143
- * Object to store subscribers for each event type
18144
- */
18145
- this.subscribers = {};
18272
+ if (!window.dataLayer) {
18273
+ return;
18274
+ }
18275
+ this.originalPush = window.dataLayer.push;
18146
18276
  }
18147
18277
  static getInstance() {
18148
- if (!PubSub.instance) {
18149
- PubSub.instance = new PubSub();
18278
+ if (!DataLayerMonitor.instance) {
18279
+ DataLayerMonitor.instance = new DataLayerMonitor();
18150
18280
  }
18151
- return PubSub.instance;
18281
+ return DataLayerMonitor.instance;
18152
18282
  }
18153
- /**
18154
- * Subscribe to an event
18155
- * @param eventType - The type of event to subscribe to
18156
- * @param callback - The function to be called when the event is published
18157
- * @returns A function to unsubscribe from the event
18158
- *
18159
- * @Example:
18160
- * const unsubscribe = pubSub.subscribe('userLogin', (data) => {
18161
- * console.log(`User ${data.username} logged in`);
18162
- * });
18163
- */
18164
- subscribe(eventType, callback) {
18165
- if (!this.subscribers[eventType]) {
18166
- 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;
18167
18303
  }
18168
- this.subscribers[eventType].push(callback);
18169
- // Return an unsubscribe function
18170
- return () => {
18171
- this.subscribers[eventType] = this.subscribers[eventType].filter((cb) => cb !== callback);
18304
+ const productIds = extractDeepIds(data.value);
18305
+ return {
18306
+ event: eventName,
18307
+ productIds,
18172
18308
  };
18173
18309
  }
18174
- /**
18175
- * Publish an event
18176
- * @param eventType - The type of event to publish
18177
- * @param data - The data to be passed to the event subscribers
18178
- *
18179
- * @Example:
18180
- * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
18181
- */
18182
- publish(eventType, data) {
18183
- if (!this.subscribers[eventType]) {
18184
- return;
18310
+ stop() {
18311
+ if (this.originalPush) {
18312
+ window.dataLayer.push = this.originalPush;
18185
18313
  }
18186
- this.subscribers[eventType].forEach((callback) => callback(data));
18314
+ this.listener = undefined;
18187
18315
  }
18188
18316
  }
18189
- /**
18190
- * Usage Example:
18191
- *
18192
- * interface IRmnEventMap {
18193
- * userLogin: { username: string; timestamp: number };
18194
- * pageView: { url: string; timestamp: number };
18195
- * }
18196
- *
18197
- * const pubSub = new PubSub<IRmnEventMap>();
18198
- *
18199
- * // Subscribe to events
18200
- * const unsubscribeLogin = pubSub.subscribe('userLogin', (data) => {
18201
- * console.log(`User ${data.username} logged in at ${new Date(data.timestamp)}`);
18202
- * });
18203
- *
18204
- * pubSub.subscribe('pageView', (data) => {
18205
- * console.log(`Page ${data.url} viewed at ${new Date(data.timestamp)}`);
18206
- * });
18207
- *
18208
- * // Publish events
18209
- * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
18210
- * pubSub.publish('pageView', { url: '/home', timestamp: Date.now() });
18211
- *
18212
- * // Unsubscribe from an event
18213
- * unsubscribeLogin();
18214
- */
18215
18317
 
18216
18318
  // @TODO: Add support for user to push events to our own data layer, if they don't use any analytics tool.
18217
18319
  // window.rmnDataLayer = window.rmnDataLayer || [];
18218
- // For the moment, we will only focus on sites that use Google Analytics,
18219
- // but we will add support for other analytics tools in the future.
18220
- var AnalyticsTool;
18221
- (function (AnalyticsTool) {
18222
- AnalyticsTool["GoogleAnalytics"] = "google-analytics";
18223
- AnalyticsTool["Other"] = "Other";
18224
- })(AnalyticsTool || (AnalyticsTool = {}));
18225
- class UserMonitor {
18320
+ class MonitorService {
18226
18321
  constructor() {
18227
18322
  const analyticsTool = this.detectAnalyticsTool();
18228
18323
  switch (analyticsTool) {
@@ -18237,29 +18332,97 @@ class UserMonitor {
18237
18332
  if (analyticsTool === AnalyticsTool.Other) {
18238
18333
  return;
18239
18334
  }
18240
- this.localStorage = LocalStorage.getInstance();
18335
+ this.pubSubService = PubsubService.getInstance();
18336
+ this.localStorageService = LocalStorageService.getInstance();
18241
18337
  }
18242
18338
  static getInstance() {
18243
- if (!UserMonitor.instance) {
18244
- UserMonitor.instance = new UserMonitor();
18339
+ if (!MonitorService.instance) {
18340
+ MonitorService.instance = new MonitorService();
18245
18341
  }
18246
- return UserMonitor.instance;
18342
+ return MonitorService.instance;
18247
18343
  }
18248
18344
  start() {
18249
18345
  if (!this.implementedMonitor)
18250
18346
  return;
18251
- this.implementedMonitor.setListener((eventData) => {
18347
+ this.implementedMonitor.setListener(async (eventData) => {
18252
18348
  var _a;
18253
- 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());
18254
18350
  });
18255
18351
  this.implementedMonitor.start();
18256
18352
  }
18257
- matchAndFireEvent(
18258
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
18259
- _eventData,
18260
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
18261
- _spots) {
18262
- // 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
+ });
18263
18426
  }
18264
18427
  detectAnalyticsTool() {
18265
18428
  let analyticsTool = AnalyticsTool.Other;
@@ -18287,13 +18450,13 @@ class UserMonitor {
18287
18450
 
18288
18451
  class EventService {
18289
18452
  constructor() {
18290
- this.pubSub = PubSub.getInstance();
18291
- this.localStorage = LocalStorage.getInstance();
18453
+ this.pubSubService = PubsubService.getInstance();
18454
+ this.localStorageService = LocalStorageService.getInstance();
18292
18455
  this.activeSpots = new Map();
18293
18456
  this.spotStates = new Map();
18294
18457
  this.intersectionObserver = new IntersectionObserverService();
18295
18458
  // Start the user monitor, which will track and check user interactions
18296
- UserMonitor.getInstance().start();
18459
+ MonitorService.getInstance().start();
18297
18460
  }
18298
18461
  static getInstance() {
18299
18462
  if (!EventService.instance) {
@@ -18302,10 +18465,10 @@ class EventService {
18302
18465
  return EventService.instance;
18303
18466
  }
18304
18467
  subscribe(eventType, callback) {
18305
- return this.pubSub.subscribe(eventType, callback);
18468
+ return this.pubSubService.subscribe(eventType, callback);
18306
18469
  }
18307
18470
  publish(eventType, data) {
18308
- this.pubSub.publish(eventType, data);
18471
+ this.pubSubService.publish(eventType, data);
18309
18472
  }
18310
18473
  registerSpot(params) {
18311
18474
  const { placementId, spot, spotElement } = params;
@@ -18389,7 +18552,7 @@ class EventService {
18389
18552
  const merged = this.deepMerge(currentState, updates);
18390
18553
  this.spotStates.set(placementId, merged);
18391
18554
  if (publish) {
18392
- this.pubSub.publish(RMN_SPOT_EVENT.LIFECYCLE_STATE, this.spotStates.get(placementId));
18555
+ this.pubSubService.publish(RMN_EVENT.LIFECYCLE_STATE, this.spotStates.get(placementId));
18393
18556
  }
18394
18557
  }
18395
18558
  deepMerge(current, updates) {
@@ -18420,19 +18583,18 @@ class EventService {
18420
18583
  }
18421
18584
  async handleClick({ placementId, spot, spotElement, }) {
18422
18585
  var _a, _b, _c;
18423
- // Publish click event
18424
- this.pubSub.publish(RMN_SPOT_EVENT.CLICK, {
18586
+ this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
18587
+ eventType: RMN_SPOT_EVENT.CLICK,
18425
18588
  placementId,
18426
18589
  spotId: spot.id,
18427
18590
  spotElement,
18428
18591
  });
18429
- // Fire click event
18430
- await this.fireEvent({
18592
+ await fireEvent({
18431
18593
  event: RMN_SPOT_EVENT.CLICK,
18432
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 : '',
18433
18595
  });
18434
18596
  // Save spot to local storage for event tracking
18435
- this.localStorage.setSpot(spot.id, {
18597
+ this.localStorageService.setSpot(spot.id, {
18436
18598
  spotId: spot.id,
18437
18599
  spotType: spot.spot,
18438
18600
  events: spot.events,
@@ -18452,90 +18614,20 @@ class EventService {
18452
18614
  this.intersectionObserver.observe(spotElement, spotIsVisibleCallback);
18453
18615
  }
18454
18616
  fireImpressionEvent(placementId, spot, spotElement) {
18455
- this.pubSub.publish(RMN_SPOT_EVENT.IMPRESSION, {
18617
+ this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
18618
+ eventType: RMN_SPOT_EVENT.IMPRESSION,
18456
18619
  placementId,
18457
18620
  spotId: spot.id,
18458
18621
  spotElement,
18459
18622
  });
18460
18623
  (async () => {
18461
18624
  var _a, _b;
18462
- await this.fireEvent({
18625
+ await fireEvent({
18463
18626
  event: RMN_SPOT_EVENT.IMPRESSION,
18464
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 : '',
18465
18628
  });
18466
18629
  })();
18467
18630
  }
18468
- /**
18469
- * Fires an event using the navigator.sendBeacon method or a fallback method if sendBeacon is not available.
18470
- * If the event is a click event and a redirect URL is found, it redirects the user to that URL.
18471
- *
18472
- * @param {IFireEventParams} params - The parameters for firing the event.
18473
- * @param {RMN_SPOT_EVENT} params.event - The event type.
18474
- * @param {string} params.eventUrl - The URL to which the event is sent.
18475
- * @returns {Promise<void>} - A promise that resolves when the event is fired.
18476
- */
18477
- async fireEvent({ event, eventUrl }) {
18478
- var _a;
18479
- try {
18480
- 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));
18481
- if (!didFireEvent) {
18482
- return;
18483
- }
18484
- if (event === RMN_SPOT_EVENT.CLICK) {
18485
- const redirectUrl = this.getRedirectUrlFromPayload(eventUrl);
18486
- if (redirectUrl) {
18487
- window.location.href = redirectUrl;
18488
- }
18489
- }
18490
- }
18491
- catch (_b) {
18492
- // Handle errors silently
18493
- }
18494
- }
18495
- // Fallback method using fetch if sendBeacon isn't available
18496
- async fallbackEventFire(url) {
18497
- try {
18498
- const racePromise = Promise.race([
18499
- // Promise #1: The fetch request
18500
- fetch(url, {
18501
- method: 'POST',
18502
- keepalive: true,
18503
- }),
18504
- // Promise #2: The timeout
18505
- new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), 2000)),
18506
- ]);
18507
- /**
18508
- * Prevent requests from hanging indefinitely
18509
- * Improve user experience by failing fast
18510
- * Handle slow network conditions gracefully
18511
- * Ensure resources are freed up in a timely manner
18512
- */
18513
- const response = await racePromise;
18514
- return response.ok;
18515
- }
18516
- catch (_a) {
18517
- return false;
18518
- }
18519
- }
18520
- /**
18521
- * Extracts and decodes a URL from a base64-encoded query parameter.
18522
- *
18523
- * @param {string} url - The URL containing the base64-encoded query parameter.
18524
- * @returns {string | null} - The decoded URL or null if not found or invalid.
18525
- */
18526
- getRedirectUrlFromPayload(url) {
18527
- try {
18528
- const base64String = new URL(url).searchParams.get('e');
18529
- if (!base64String) {
18530
- return null;
18531
- }
18532
- const data = JSON.parse(atob(base64String));
18533
- return data.ur || null;
18534
- }
18535
- catch (_a) {
18536
- return null;
18537
- }
18538
- }
18539
18631
  }
18540
18632
 
18541
18633
  const SELECTION_API_PATH = '/spots/selection';
@@ -19185,4 +19277,4 @@ function RmnCreateSpotElement(spot, config) {
19185
19277
  });
19186
19278
  }
19187
19279
 
19188
- 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 };