@liquidcommercedev/rmn-sdk 1.4.6-beta.4 → 1.4.6-beta.5

Sign up to get free protection for your applications and to get access to all the features.
package/dist/index.cjs CHANGED
@@ -56,6 +56,9 @@ exports.RMN_FILTER_PROPERTIES = void 0;
56
56
  })(exports.RMN_FILTER_PROPERTIES || (exports.RMN_FILTER_PROPERTIES = {}));
57
57
  exports.RMN_SPOT_EVENT = void 0;
58
58
  (function (RMN_SPOT_EVENT) {
59
+ RMN_SPOT_EVENT["MOUNTED"] = "MOUNTED";
60
+ RMN_SPOT_EVENT["UNMOUNTED"] = "UNMOUNTED";
61
+ RMN_SPOT_EVENT["RESIZED"] = "RESIZED";
59
62
  RMN_SPOT_EVENT["IMPRESSION"] = "IMPRESSION";
60
63
  RMN_SPOT_EVENT["CLICK"] = "CLICK";
61
64
  RMN_SPOT_EVENT["PURCHASE"] = "PURCHASE";
@@ -15164,6 +15167,46 @@ const GFONT_CORMORANT = `
15164
15167
  <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">
15165
15168
  `;
15166
15169
 
15170
+ class IntersectionObserverService {
15171
+ constructor(defaultOptions = {}) {
15172
+ this.observers = new Map();
15173
+ this.defaultOptions = {
15174
+ root: null,
15175
+ rootMargin: '0px',
15176
+ threshold: 0.5,
15177
+ ...defaultOptions,
15178
+ };
15179
+ }
15180
+ observe(element, callback, options = {}) {
15181
+ const mergedOptions = { ...this.defaultOptions, ...options };
15182
+ const ioCallback = (entries) => {
15183
+ entries.forEach((entry) => {
15184
+ if (entry.isIntersecting) {
15185
+ callback(entry);
15186
+ }
15187
+ });
15188
+ };
15189
+ const observer = new IntersectionObserver(ioCallback, mergedOptions);
15190
+ this.observers.set(element, observer);
15191
+ observer.observe(element);
15192
+ }
15193
+ unobserve(element) {
15194
+ const observer = this.observers.get(element);
15195
+ if (observer) {
15196
+ observer.unobserve(element);
15197
+ observer.disconnect();
15198
+ this.observers.delete(element);
15199
+ }
15200
+ }
15201
+ unobserveAll() {
15202
+ this.observers.forEach((observer, element) => {
15203
+ observer.unobserve(element);
15204
+ observer.disconnect();
15205
+ });
15206
+ this.observers.clear();
15207
+ }
15208
+ }
15209
+
15167
15210
  class ResizeObserverService {
15168
15211
  constructor({ element, maxSize, minScale }) {
15169
15212
  this.element = element;
@@ -15469,8 +15512,9 @@ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined
15469
15512
  this.addEventListener('spotSizeChanged', this.handleCarouselSizeChanged.bind(this));
15470
15513
  }
15471
15514
  }
15472
- handleCarouselSizeChanged(event) {
15473
- console.info('Carousel Size Changed', event);
15515
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
15516
+ handleCarouselSizeChanged(_event) {
15517
+ // console.info('Carousel Size Changed', event);
15474
15518
  }
15475
15519
  render() {
15476
15520
  var _a;
@@ -15696,7 +15740,7 @@ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined
15696
15740
  * #########################################################
15697
15741
  */
15698
15742
  handleSpotSizeChanged(event) {
15699
- console.info('Spot Size Changed', event);
15743
+ // console.info('Spot Size Changed', event);
15700
15744
  // Adjust text elements font size based on the scale factor
15701
15745
  this.adjustFontSize(event.detail.scale);
15702
15746
  }
@@ -15708,8 +15752,8 @@ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined
15708
15752
  elements === null || elements === void 0 ? void 0 : elements.forEach((element) => {
15709
15753
  if (element instanceof HTMLElement) {
15710
15754
  if (!this.originalFontSizes.has(element)) {
15711
- const orignalSize = parseFloat(window.getComputedStyle(element).fontSize);
15712
- this.originalFontSizes.set(element, orignalSize);
15755
+ const originalSize = parseFloat(window.getComputedStyle(element).fontSize);
15756
+ this.originalFontSizes.set(element, originalSize);
15713
15757
  }
15714
15758
  const originalSize = this.originalFontSizes.get(element);
15715
15759
  const newFontSize = originalSize * scaleFactor;
@@ -17540,11 +17584,12 @@ function rbSmallDiscoverToutTemplate(spot, config) {
17540
17584
  }
17541
17585
 
17542
17586
  /**
17543
- * Creates the spot html string based on the provided spot data.
17587
+ * Returns the HTML element for the given spot.
17544
17588
  *
17545
- * @param {ISpot} spot - The spot data.
17589
+ * @param {ISpot} spot - The spot object.
17590
+ * @param {ISpotTemplateConfig} config - The spot template configuration.
17546
17591
  *
17547
- * @return {string} - The spot html string.
17592
+ * @return {HTMLElement | null} - The HTML element for the given spot or null if the spot template is not found.
17548
17593
  */
17549
17594
  const SPOT_TEMPLATE_HTML_ELEMENT = (spot, config) => {
17550
17595
  const templates = {
@@ -17617,6 +17662,228 @@ const SPOT_TEMPLATE_HTML_ELEMENT = (spot, config) => {
17617
17662
  return spotHtmlStringToElement(spotHtmlString);
17618
17663
  };
17619
17664
 
17665
+ /**
17666
+ * PubSub class
17667
+ * Manages event subscriptions and publications
17668
+ * @template IEventMap A record type defining the structure of events and their data
17669
+ */
17670
+ class PubSub {
17671
+ constructor() {
17672
+ /**
17673
+ * Object to store subscribers for each event type
17674
+ */
17675
+ this.subscribers = {};
17676
+ }
17677
+ /**
17678
+ * Subscribe to an event
17679
+ * @param eventType - The type of event to subscribe to
17680
+ * @param callback - The function to be called when the event is published
17681
+ * @returns A function to unsubscribe from the event
17682
+ *
17683
+ * @Example:
17684
+ * const unsubscribe = pubSub.subscribe('userLogin', (data) => {
17685
+ * console.log(`User ${data.username} logged in`);
17686
+ * });
17687
+ */
17688
+ subscribe(eventType, callback) {
17689
+ if (!this.subscribers[eventType]) {
17690
+ this.subscribers[eventType] = [];
17691
+ }
17692
+ this.subscribers[eventType].push(callback);
17693
+ // Return an unsubscribe function
17694
+ return () => {
17695
+ this.subscribers[eventType] = this.subscribers[eventType].filter((cb) => cb !== callback);
17696
+ };
17697
+ }
17698
+ /**
17699
+ * Publish an event
17700
+ * @param eventType - The type of event to publish
17701
+ * @param data - The data to be passed to the event subscribers
17702
+ *
17703
+ * @Example:
17704
+ * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
17705
+ */
17706
+ publish(eventType, data) {
17707
+ if (!this.subscribers[eventType]) {
17708
+ return;
17709
+ }
17710
+ this.subscribers[eventType].forEach((callback) => callback(data));
17711
+ }
17712
+ }
17713
+ /**
17714
+ * Usage Example:
17715
+ *
17716
+ * interface IEventMap {
17717
+ * userLogin: { username: string; timestamp: number };
17718
+ * pageView: { url: string; timestamp: number };
17719
+ * }
17720
+ *
17721
+ * const pubSub = new PubSub<IEventMap>();
17722
+ *
17723
+ * // Subscribe to events
17724
+ * const unsubscribeLogin = pubSub.subscribe('userLogin', (data) => {
17725
+ * console.log(`User ${data.username} logged in at ${new Date(data.timestamp)}`);
17726
+ * });
17727
+ *
17728
+ * pubSub.subscribe('pageView', (data) => {
17729
+ * console.log(`Page ${data.url} viewed at ${new Date(data.timestamp)}`);
17730
+ * });
17731
+ *
17732
+ * // Publish events
17733
+ * pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
17734
+ * pubSub.publish('pageView', { url: '/home', timestamp: Date.now() });
17735
+ *
17736
+ * // Unsubscribe from an event
17737
+ * unsubscribeLogin();
17738
+ */
17739
+
17740
+ class EventService {
17741
+ constructor() {
17742
+ this.pubSub = new PubSub();
17743
+ this.activeSpots = new Map();
17744
+ this.intersectionObserver = new IntersectionObserverService();
17745
+ }
17746
+ static getInstance() {
17747
+ if (!EventService.instance) {
17748
+ EventService.instance = new EventService();
17749
+ }
17750
+ return EventService.instance;
17751
+ }
17752
+ registerSpot({ placementId, element, spot }) {
17753
+ this.activeSpots.set(spot.id, {
17754
+ placementId,
17755
+ element,
17756
+ impressionTracked: false,
17757
+ });
17758
+ // Handle resize observer
17759
+ // this.handleResizeObserver(placementId, spot, element);
17760
+ // Handle intersection observer
17761
+ this.handleIntersectionObserver(placementId, spot, element);
17762
+ // Attach click event listener
17763
+ element.addEventListener('click', async () => {
17764
+ var _a, _b;
17765
+ this.pubSub.publish(exports.RMN_SPOT_EVENT.CLICK, {
17766
+ placementId,
17767
+ spotId: spot.id,
17768
+ element,
17769
+ });
17770
+ // Fire click event
17771
+ await this.fireEvent({
17772
+ event: exports.RMN_SPOT_EVENT.CLICK,
17773
+ 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 : '',
17774
+ });
17775
+ });
17776
+ // Publish spot created event
17777
+ this.pubSub.publish(exports.RMN_SPOT_EVENT.MOUNTED, {
17778
+ placementId,
17779
+ spotId: spot.id,
17780
+ spotType: spot.spot,
17781
+ spotVariant: spot.variant,
17782
+ element,
17783
+ });
17784
+ }
17785
+ unregisterSpot(spotId) {
17786
+ const spotData = this.activeSpots.get(spotId);
17787
+ if (spotData) {
17788
+ this.intersectionObserver.unobserve(spotData.element);
17789
+ // this.resizeObserver?.disconnect();
17790
+ this.pubSub.publish(exports.RMN_SPOT_EVENT.UNMOUNTED, {
17791
+ placementId: spotData.placementId,
17792
+ spotId,
17793
+ });
17794
+ this.activeSpots.delete(spotId);
17795
+ }
17796
+ }
17797
+ // private handleResizeObserver(placementId: string, spot: ISpot, element: HTMLElement): void {
17798
+ // // this.resizeObserver = new ResizeObserverService({
17799
+ // // element,
17800
+ // // maxSize: {
17801
+ // // width: spot.width,
17802
+ // // height: spot.height,
17803
+ // // },
17804
+ // // minScale: 0.25,
17805
+ // // });
17806
+ //
17807
+ // const sizeChangedCb = (event: ISizeChangedEvent) => {
17808
+ // // Publish spot resized event
17809
+ // this.pubSub.publish(RMN_SPOT_EVENT.RESIZED, {
17810
+ // placementId,
17811
+ // spotId: spot.id,
17812
+ // scale: event.detail.scale,
17813
+ // previousDimensions: {
17814
+ // width: event.detail.width,
17815
+ // height: event.detail.height,
17816
+ // },
17817
+ // currentDimensions: {
17818
+ // width: event.detail.newWidth,
17819
+ // height: event.detail.newHeight,
17820
+ // },
17821
+ // });
17822
+ // };
17823
+ //
17824
+ // element.addEventListener('spotSizeChanged', sizeChangedCb as EventListener);
17825
+ // }
17826
+ handleIntersectionObserver(placementId, spot, element) {
17827
+ const spotIsVisibleCb = async () => {
17828
+ var _a, _b;
17829
+ this.pubSub.publish(exports.RMN_SPOT_EVENT.IMPRESSION, {
17830
+ placementId,
17831
+ spotId: spot.id,
17832
+ element,
17833
+ });
17834
+ this.intersectionObserver.unobserve(element);
17835
+ this.activeSpots.set(spot.id, {
17836
+ placementId,
17837
+ element,
17838
+ impressionTracked: true,
17839
+ });
17840
+ // Fire impression event
17841
+ await this.fireEvent({
17842
+ event: exports.RMN_SPOT_EVENT.IMPRESSION,
17843
+ 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 : '',
17844
+ });
17845
+ };
17846
+ this.intersectionObserver.observe(element, spotIsVisibleCb);
17847
+ }
17848
+ subscribe(eventType, callback) {
17849
+ return this.pubSub.subscribe(eventType, callback);
17850
+ }
17851
+ publish(eventType, data) {
17852
+ this.pubSub.publish(eventType, data);
17853
+ }
17854
+ /**
17855
+ * Fires an event using the navigator.sendBeacon method and redirects the user if the event is a click event.
17856
+ *
17857
+ * @param {IFireEventParams} params - The parameters for firing the event.
17858
+ * @param {RMN_SPOT_EVENT} params.event - The event type.
17859
+ * @param {string} params.eventUrl - The URL to which the event is sent.
17860
+ * @returns {Promise<void>} - A promise that resolves when the event is fired.
17861
+ */
17862
+ async fireEvent({ event, eventUrl }) {
17863
+ const didFireEvent = navigator.sendBeacon(eventUrl);
17864
+ if (didFireEvent && event === exports.RMN_SPOT_EVENT.CLICK) {
17865
+ window.location.href = this.getRedirectUrlFromPayload(eventUrl);
17866
+ }
17867
+ }
17868
+ /**
17869
+ * Extracts and decodes a URL from a base64-encoded query parameter.
17870
+ *
17871
+ * @param {string} url - The URL containing the base64-encoded query parameter.
17872
+ * @returns {string} - The decoded URL or an empty string if decoding fails.
17873
+ */
17874
+ getRedirectUrlFromPayload(url) {
17875
+ var _a, _b;
17876
+ const base64String = (_a = new URL(url).searchParams.get('e')) !== null && _a !== void 0 ? _a : '';
17877
+ try {
17878
+ const data = JSON.parse(atob(base64String));
17879
+ return (_b = data.ur) !== null && _b !== void 0 ? _b : '';
17880
+ }
17881
+ catch (_c) {
17882
+ return '';
17883
+ }
17884
+ }
17885
+ }
17886
+
17620
17887
  const SELECTION_API_PATH = '/spots/selection';
17621
17888
 
17622
17889
  class SelectionService extends BaseApi {
@@ -17651,6 +17918,7 @@ class LiquidCommerceRmnClient {
17651
17918
  constructor(auth) {
17652
17919
  this.selectionService = SelectionService.getInstance(auth);
17653
17920
  this.elementService = ElementService.getInstance();
17921
+ this.eventService = EventService.getInstance();
17654
17922
  }
17655
17923
  /**
17656
17924
  * Makes a selection request on our server based on the provided data.
@@ -17701,6 +17969,14 @@ class LiquidCommerceRmnClient {
17701
17969
  }
17702
17970
  }
17703
17971
  }
17972
+ /**
17973
+ * Returns the event manager instance.
17974
+ *
17975
+ * @return {EventService} - The event manager instance.
17976
+ */
17977
+ eventManager() {
17978
+ return this.eventService;
17979
+ }
17704
17980
  /**
17705
17981
  * Makes a selection request on our server based on the provided data.
17706
17982
  *
@@ -17741,6 +18017,11 @@ class LiquidCommerceRmnClient {
17741
18017
  console.warn(`RmnSdk: Failed to inject carousel spot element. Could not create element for type "${spot.spot}".`);
17742
18018
  return;
17743
18019
  }
18020
+ this.eventSpotElement({
18021
+ spot,
18022
+ placementId: placement.id,
18023
+ element: content,
18024
+ });
17744
18025
  carouselSlides.push(content);
17745
18026
  }
17746
18027
  const { maxWidth, maxHeight } = spots.reduce((max, spot) => ({
@@ -17792,8 +18073,20 @@ class LiquidCommerceRmnClient {
17792
18073
  console.warn(`RmnSdk: Failed to inject spot element. Could not create element for type "${injectItem.spotType}".`);
17793
18074
  return;
17794
18075
  }
18076
+ this.eventSpotElement({
18077
+ spot,
18078
+ placementId: injectItem.placementId,
18079
+ element: spotElement,
18080
+ });
17795
18081
  placement.replaceChildren(spotElement);
17796
18082
  }
18083
+ eventSpotElement({ spot, placementId, element, }) {
18084
+ this.eventService.registerSpot({
18085
+ placementId,
18086
+ element,
18087
+ spot,
18088
+ });
18089
+ }
17797
18090
  /**
17798
18091
  * Prevents duplicate placement ids in the inject data.
17799
18092
  *