@liquidcommercedev/rmn-sdk 1.5.0-beta.10 → 1.5.0-beta.11

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
@@ -67,7 +67,9 @@ exports.RMN_SPOT_EVENT = void 0;
67
67
  RMN_SPOT_EVENT["PURCHASE"] = "PURCHASE";
68
68
  RMN_SPOT_EVENT["ADD_TO_CART"] = "ADD_TO_CART";
69
69
  RMN_SPOT_EVENT["REMOVE_FROM_CART"] = "REMOVE_FROM_CART";
70
+ RMN_SPOT_EVENT["ADD_TO_CART_FROM_DETAILS"] = "ADD_TO_CART_FROM_DETAILS";
70
71
  RMN_SPOT_EVENT["ADD_TO_WISHLIST"] = "ADD_TO_WISHLIST";
72
+ RMN_SPOT_EVENT["EXPAND_PRODUCT"] = "EXPAND_PRODUCT";
71
73
  RMN_SPOT_EVENT["BUY_NOW"] = "BUY_NOW";
72
74
  })(exports.RMN_SPOT_EVENT || (exports.RMN_SPOT_EVENT = {}));
73
75
  exports.RMN_ENV = void 0;
@@ -6545,6 +6547,94 @@ axios.HttpStatusCode = HttpStatusCode;
6545
6547
 
6546
6548
  axios.default = axios;
6547
6549
 
6550
+ /**
6551
+ * Keyword patterns for each RMN_SPOT_EVENT.
6552
+ * Each event type has required keywords that must be present and optional ones.
6553
+ *
6554
+ * Note: The order of checks matters - more specific patterns must be checked before general ones
6555
+ * to avoid incorrect matches.
6556
+ */
6557
+ const EVENT_KEYWORDS = {
6558
+ [exports.RMN_SPOT_EVENT.ADD_TO_CART_FROM_DETAILS]: {
6559
+ required: ['add', 'cart'],
6560
+ optional: ['details', 'detail', 'single', 'profile', 'page', 'pdp'],
6561
+ },
6562
+ [exports.RMN_SPOT_EVENT.BUY_NOW]: {
6563
+ required: ['buy', 'now'],
6564
+ },
6565
+ [exports.RMN_SPOT_EVENT.EXPAND_PRODUCT]: {
6566
+ required: ['product'],
6567
+ optional: ['details', 'expand', 'modal', 'popup', 'quickview', 'view'],
6568
+ },
6569
+ [exports.RMN_SPOT_EVENT.ADD_TO_WISHLIST]: {
6570
+ required: ['add', 'wishlist'],
6571
+ },
6572
+ [exports.RMN_SPOT_EVENT.REMOVE_FROM_CART]: {
6573
+ required: ['remove', 'cart'],
6574
+ },
6575
+ [exports.RMN_SPOT_EVENT.PURCHASE]: {
6576
+ required: ['purchase'],
6577
+ },
6578
+ [exports.RMN_SPOT_EVENT.ADD_TO_CART]: {
6579
+ required: ['add', 'cart'],
6580
+ },
6581
+ };
6582
+ /**
6583
+ * Normalizes an event name by converting to lowercase, removing special characters,
6584
+ * and splitting into words.
6585
+ */
6586
+ function normalizeEventName(event) {
6587
+ return event
6588
+ .toLowerCase()
6589
+ .replace(/[^a-z0-9\s]+/g, ' ') // Replace special chars with spaces
6590
+ .split(/\s+/) // Split on whitespace
6591
+ .filter(Boolean); // Remove empty strings
6592
+ }
6593
+ /**
6594
+ * Checks if a word matches a keyword, considering word boundaries.
6595
+ * This prevents partial word matches (e.g., "card" shouldn't match "cart").
6596
+ */
6597
+ function wordMatchesKeyword(word, keyword) {
6598
+ // Create a RegExp that matches the keyword as a whole word
6599
+ const keywordRegex = new RegExp(`^${keyword}s?$|${keyword}s?\\W|\\W${keyword}s?$|\\W${keyword}s?\\W`);
6600
+ return keywordRegex.test(` ${word} `); // Add spaces to handle word boundaries
6601
+ }
6602
+ /**
6603
+ * Checks if all required keywords and at least one optional keyword (if specified) are present.
6604
+ */
6605
+ function matchesKeywordPattern(words, required, optional) {
6606
+ // Check if all required keywords are present as whole words
6607
+ const hasAllRequired = required.every((keyword) => words.some((word) => wordMatchesKeyword(word, keyword)));
6608
+ if (!hasAllRequired) {
6609
+ return false;
6610
+ }
6611
+ // If no optional keywords are specified, return true
6612
+ if (!(optional === null || optional === void 0 ? void 0 : optional.length)) {
6613
+ return true;
6614
+ }
6615
+ // If optional keywords exist, check if at least one matches as a whole word
6616
+ return optional.some((keyword) => words.some((word) => wordMatchesKeyword(word, keyword)));
6617
+ }
6618
+ /**
6619
+ * Determines the event type from a raw event string by checking for keyword patterns.
6620
+ *
6621
+ * @param {string} [event] - The raw event string to evaluate
6622
+ * @returns {RMN_SPOT_EVENT | null} - The corresponding RMN_SPOT_EVENT or null if no match
6623
+ */
6624
+ function getEventTypeFromRawEvent(event) {
6625
+ if (!(event === null || event === void 0 ? void 0 : event.trim())) {
6626
+ return null;
6627
+ }
6628
+ const words = normalizeEventName(event);
6629
+ // Use Object.entries to maintain the exact order defined in EVENT_KEYWORDS
6630
+ for (const [eventType, { required, optional }] of Object.entries(EVENT_KEYWORDS)) {
6631
+ if (matchesKeywordPattern(words, required, optional)) {
6632
+ return eventType;
6633
+ }
6634
+ }
6635
+ return null;
6636
+ }
6637
+
6548
6638
  class SingletonManager {
6549
6639
  /**
6550
6640
  * Retrieves an instance of the specified class using the provided instance creator function.
@@ -6703,12 +6793,319 @@ class ObjectHelper {
6703
6793
  *
6704
6794
  * @return {void} - This method does not return any value.
6705
6795
  */
6706
- innerMerge(target, source) {
6707
- for (const key of Object.keys(source)) {
6708
- target[key] = this.mergeValue(target[key], source[key]);
6709
- }
6796
+ innerMerge(target, source) {
6797
+ for (const key of Object.keys(source)) {
6798
+ target[key] = this.mergeValue(target[key], source[key]);
6799
+ }
6800
+ }
6801
+ }
6802
+
6803
+ /**
6804
+ * Recursively extracts ID values from a nested data structure.
6805
+ * Searches for specified property names and collects their primitive values (strings/numbers).
6806
+ *
6807
+ * @param data - The data structure to search through (can be nested objects/arrays)
6808
+ * @param propertyNames - Array of property names to look for
6809
+ * @returns Array of extracted ID values (strings/numbers only)
6810
+ *
6811
+ * @example
6812
+ * const data = {
6813
+ * id: [1, 2, 3],
6814
+ * nested: { id: 'abc' },
6815
+ * items: [{ id: 456 }]
6816
+ * };
6817
+ * extractDeepIds(data); // Returns [1, 2, 3, 'abc', 456]
6818
+ */
6819
+ function extractDeepIds(data, propertyNames) {
6820
+ const ids = [];
6821
+ const defaulPropertyNames = [
6822
+ 'id',
6823
+ 'upc',
6824
+ 'groupingId',
6825
+ 'sku',
6826
+ 'productId',
6827
+ 'item_id',
6828
+ 'isbn',
6829
+ 'asin',
6830
+ 'mpn',
6831
+ 'model_number',
6832
+ 'article_number',
6833
+ 'variant_id',
6834
+ 'item_number',
6835
+ 'catalog_id',
6836
+ 'reference_id',
6837
+ ];
6838
+ // Set for faster property name lookups
6839
+ const propertySet = new Set(defaulPropertyNames);
6840
+ /**
6841
+ * Processes a value and extracts IDs if it matches criteria
6842
+ * @param value - The value to process
6843
+ * @param currentKey - The property name of the current value
6844
+ */
6845
+ const processValue = (value, currentKey) => {
6846
+ // Early exit for null/undefined values
6847
+ if (value == null)
6848
+ return;
6849
+ // If current key matches our target properties
6850
+ if (currentKey && propertySet.has(currentKey)) {
6851
+ if (Array.isArray(value)) {
6852
+ // Filter and push valid array values in one pass
6853
+ ids.push(...value.filter((item) => typeof item === 'string' || typeof item === 'number'));
6854
+ }
6855
+ else if (typeof value === 'string' || typeof value === 'number') {
6856
+ ids.push(value);
6857
+ }
6858
+ return; // Stop processing this branch after handling the ID
6859
+ }
6860
+ // Recursively process nested structures
6861
+ if (Array.isArray(value)) {
6862
+ value.forEach((item) => processValue(item));
6863
+ }
6864
+ else if (typeof value === 'object') {
6865
+ // Process all enumerable properties
6866
+ for (const [key, val] of Object.entries(value)) {
6867
+ processValue(val, key);
6868
+ }
6869
+ }
6870
+ };
6871
+ processValue(data);
6872
+ return ids; // No need to filter nulls as we handle that during collection
6873
+ }
6874
+ // Fallback method using fetch if sendBeacon isn't available
6875
+ async function fallbackEventFire(url) {
6876
+ try {
6877
+ const racePromise = Promise.race([
6878
+ // Promise #1: The fetch request
6879
+ fetch(url, {
6880
+ method: 'POST',
6881
+ keepalive: true,
6882
+ }),
6883
+ // Promise #2: The timeout
6884
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), 2000)),
6885
+ ]);
6886
+ /**
6887
+ * Prevent requests from hanging indefinitely
6888
+ * Improve user experience by failing fast
6889
+ * Handle slow network conditions gracefully
6890
+ * Ensure resources are freed up in a timely manner
6891
+ */
6892
+ const response = await racePromise;
6893
+ return response.ok;
6894
+ }
6895
+ catch (_a) {
6896
+ return false;
6897
+ }
6898
+ }
6899
+ /**
6900
+ * Extracts and decodes a URL from a base64-encoded query parameter.
6901
+ *
6902
+ * @param {string} url - The URL containing the base64-encoded query parameter.
6903
+ * @returns {string | null} - The decoded URL or null if not found or invalid.
6904
+ */
6905
+ function getRedirectUrlFromPayload(url) {
6906
+ try {
6907
+ const base64String = new URL(url).searchParams.get('e');
6908
+ if (!base64String) {
6909
+ return null;
6910
+ }
6911
+ const data = JSON.parse(atob(base64String));
6912
+ return data.ur || null;
6913
+ }
6914
+ catch (_a) {
6915
+ return null;
6916
+ }
6917
+ }
6918
+ /**
6919
+ * Fires an event using the navigator.sendBeacon method or a fallback method if sendBeacon is not available.
6920
+ * If the event is a click event and a redirect URL is found, it redirects the user to that URL.
6921
+ *
6922
+ * @param {IFireEventParams} params - The parameters for firing the event.
6923
+ * @param {RMN_SPOT_EVENT} params.event - The event type.
6924
+ * @param {string} params.eventUrl - The URL to which the event is sent.
6925
+ * @returns {Promise<void>} - A promise that resolves when the event is fired.
6926
+ */
6927
+ async function fireEvent({ event, eventUrl }) {
6928
+ var _a;
6929
+ try {
6930
+ 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));
6931
+ if (!didFireEvent) {
6932
+ return;
6933
+ }
6934
+ if (event === exports.RMN_SPOT_EVENT.CLICK) {
6935
+ const redirectUrl = getRedirectUrlFromPayload(eventUrl);
6936
+ if (redirectUrl) {
6937
+ window.location.href = redirectUrl;
6938
+ }
6939
+ }
6940
+ }
6941
+ catch (_b) {
6942
+ // Handle errors silently
6943
+ }
6944
+ }
6945
+ function calculateScaleFactor(elementScale) {
6946
+ // Step 1: Apply square root for non-linear scaling
6947
+ // This creates a more gradual scaling effect, especially for larger changes
6948
+ // For example:
6949
+ // - elementScale of 0.25 (1/4 size) becomes 0.5
6950
+ // - elementScale of 1 (unchanged) remains 1
6951
+ // - elementScale of 4 (4x size) becomes 2
6952
+ const baseFactor = Math.sqrt(elementScale);
6953
+ // Step 2: Apply additional dampening to further soften the scaling effect
6954
+ // The dampening factor (0.5) can be adjusted:
6955
+ // - Lower values (closer to 0) make scaling more subtle
6956
+ // - Higher values (closer to 1) make scaling more pronounced
6957
+ const dampening = 0.35;
6958
+ // Calculate the scaleFactor:
6959
+ // 1. (baseFactor - 1) represents the change from the original size
6960
+ // 2. Multiply by dampening to reduce the effect
6961
+ // 3. Add 1 to center the scaling around the original size
6962
+ // For example, if baseFactor is 2:
6963
+ // scaleFactor = 1 + (2 - 1) * 0.5 = 1.5
6964
+ const scaleFactor = 1 + (baseFactor - 1) * dampening;
6965
+ // Step 3: Define the allowed range for the scale factor
6966
+ // This ensures that the font size never changes too drastically
6967
+ const minScale = 0.35; // Font will never be smaller than 50% of original
6968
+ const maxScale = 1.5; // Font will never be larger than 150% of original
6969
+ // Step 4: Clamp the scale factor to the defined range
6970
+ // Math.min ensures the value doesn't exceed maxScale
6971
+ // Math.max ensures the value isn't less than minScale
6972
+ return Math.max(minScale, Math.min(maxScale, scaleFactor));
6973
+ }
6974
+
6975
+ class UniqueIdGenerator {
6976
+ /**
6977
+ * Initialize the generator with a node ID
6978
+ * @param nodeId Number between 0-1023 to identify this instance
6979
+ */
6980
+ static initialize(nodeId = Math.floor(Math.random() * 1024)) {
6981
+ if (nodeId < 0 || nodeId >= 1 << this.nodeBits) {
6982
+ throw new Error(`Node ID must be between 0 and ${(1 << this.nodeBits) - 1}`);
6983
+ }
6984
+ this.nodeId = nodeId;
6985
+ }
6986
+ /**
6987
+ * Convert a number to base32 string with specified length
6988
+ */
6989
+ static toBase32(num, length) {
6990
+ let result = '';
6991
+ while (num > 0) {
6992
+ result = this.base32Chars[Number(num % BigInt(32))] + result;
6993
+ // @ts-expect-error - TS doesn't support bigint division
6994
+ num = num / 32n;
6995
+ }
6996
+ return result.padStart(length, '0');
6997
+ }
6998
+ /**
6999
+ * Generate a cryptographically secure random number
7000
+ */
7001
+ static getSecureRandom() {
7002
+ if (typeof crypto !== 'undefined') {
7003
+ const buffer = new Uint32Array(1);
7004
+ crypto.getRandomValues(buffer);
7005
+ return buffer[0];
7006
+ }
7007
+ return Math.floor(Math.random() * 0xffffffff);
7008
+ }
7009
+ /**
7010
+ * Wait until next millisecond
7011
+ */
7012
+ static waitNextMillis(lastTimestamp) {
7013
+ let timestamp = Date.now();
7014
+ while (timestamp <= lastTimestamp) {
7015
+ timestamp = Date.now();
7016
+ }
7017
+ return timestamp;
7018
+ }
7019
+ /**
7020
+ * Generates a highly unique ID with the following format:
7021
+ * TTTTTTTTTTCCCCNNNNNRRRR
7022
+ * T: Timestamp (10 chars)
7023
+ * C: Counter (4 chars)
7024
+ * N: Node ID (5 chars)
7025
+ * R: Random (4 chars)
7026
+ *
7027
+ * Total length: 23 characters, always uppercase alphanumeric
7028
+ */
7029
+ static generate() {
7030
+ if (this.nodeId === undefined) {
7031
+ this.initialize();
7032
+ }
7033
+ let timestamp = Date.now() - this.epoch;
7034
+ // Handle clock moving backwards or same millisecond
7035
+ if (timestamp < this.lastTimestamp) {
7036
+ throw new Error('Clock moved backwards. Refusing to generate ID.');
7037
+ }
7038
+ if (timestamp === this.lastTimestamp) {
7039
+ this.sequence = (this.sequence + 1) & ((1 << this.sequenceBits) - 1);
7040
+ if (this.sequence === 0) {
7041
+ timestamp = this.waitNextMillis(this.lastTimestamp);
7042
+ }
7043
+ }
7044
+ else {
7045
+ this.sequence = 0;
7046
+ }
7047
+ this.lastTimestamp = timestamp;
7048
+ // Generate random component
7049
+ const random = this.getSecureRandom() & 0xffff; // 16 bits of randomness
7050
+ // Combine all components into a BigInt
7051
+ // const id =
7052
+ // (BigInt(timestamp) << BigInt(this.nodeBits + this.sequenceBits + 16)) |
7053
+ // (BigInt(this.nodeId) << BigInt(this.sequenceBits + 16)) |
7054
+ // (BigInt(this.sequence) << BigInt(16)) |
7055
+ // BigInt(random);
7056
+ // Convert to base32 representation
7057
+ const timeComponent = this.toBase32(BigInt(timestamp), 10);
7058
+ const counterComponent = this.toBase32(BigInt(this.sequence), 4);
7059
+ const nodeComponent = this.toBase32(BigInt(this.nodeId), 5);
7060
+ const randomComponent = this.toBase32(BigInt(random), 4);
7061
+ return `${timeComponent}${counterComponent}${nodeComponent}${randomComponent}`;
7062
+ }
7063
+ /**
7064
+ * Validates if a string matches the expected ID format
7065
+ */
7066
+ static isValid(id) {
7067
+ if (!/^[0-9A-HJ-NP-Z]{23}$/.test(id))
7068
+ return false;
7069
+ try {
7070
+ const timeComponent = id.slice(0, 10);
7071
+ const timestamp = this.decodeBase32(timeComponent);
7072
+ const now = Date.now() - this.epoch;
7073
+ return timestamp >= 0 && timestamp <= now;
7074
+ }
7075
+ catch (_a) {
7076
+ return false;
7077
+ }
7078
+ }
7079
+ /**
7080
+ * Decode base32 string to number
7081
+ */
7082
+ static decodeBase32(str) {
7083
+ let result = 0;
7084
+ for (const char of str) {
7085
+ result = result * 32 + this.base32Chars.indexOf(char);
7086
+ }
7087
+ return result;
7088
+ }
7089
+ /**
7090
+ * Extract timestamp from ID
7091
+ */
7092
+ static getTimestamp(id) {
7093
+ if (!this.isValid(id))
7094
+ throw new Error('Invalid ID format');
7095
+ const timeComponent = id.slice(0, 10);
7096
+ const timestamp = this.decodeBase32(timeComponent);
7097
+ return new Date(timestamp + this.epoch);
6710
7098
  }
6711
7099
  }
7100
+ // Constants for bit manipulation
7101
+ UniqueIdGenerator.epoch = 1577836800000; // 2020-01-01 as epoch
7102
+ UniqueIdGenerator.nodeBits = 10;
7103
+ UniqueIdGenerator.sequenceBits = 12;
7104
+ // Instance variables
7105
+ UniqueIdGenerator.lastTimestamp = -1;
7106
+ UniqueIdGenerator.sequence = 0;
7107
+ // Character set for base32 encoding (excluding similar looking characters)
7108
+ UniqueIdGenerator.base32Chars = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
6712
7109
 
6713
7110
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
6714
7111
 
@@ -15582,301 +15979,100 @@ class BaseApi extends BaseApiAbstract {
15582
15979
  let timestamp = 0;
15583
15980
  if ((response === null || response === void 0 ? void 0 : response.headers) &&
15584
15981
  ((_a = response === null || response === void 0 ? void 0 : response.headers) === null || _a === void 0 ? void 0 : _a.has) &&
15585
- ((_b = response === null || response === void 0 ? void 0 : response.headers) === null || _b === void 0 ? void 0 : _b.has(REQUEST_CLOUD_PROTECTED_KEY)) &&
15586
- ((_c = response === null || response === void 0 ? void 0 : response.headers) === null || _c === void 0 ? void 0 : _c.has(REQUEST_CLOUD_PROTECTED_TIMESTAMP)) &&
15587
- ((_d = response === null || response === void 0 ? void 0 : response.headers) === null || _d === void 0 ? void 0 : _d.get)) {
15588
- isEncrypted = ((_e = response === null || response === void 0 ? void 0 : response.headers) === null || _e === void 0 ? void 0 : _e.get(REQUEST_CLOUD_PROTECTED_KEY)) === 'true';
15589
- timestamp = ((_f = response === null || response === void 0 ? void 0 : response.headers) === null || _f === void 0 ? void 0 : _f.get(REQUEST_CLOUD_PROTECTED_TIMESTAMP))
15590
- ? Number((_g = response === null || response === void 0 ? void 0 : response.headers) === null || _g === void 0 ? void 0 : _g.get(REQUEST_CLOUD_PROTECTED_TIMESTAMP))
15591
- : 0;
15592
- }
15593
- if (responseData &&
15594
- isEncrypted &&
15595
- this.objectHelper.includes(responseData, 't') &&
15596
- timestamp > 0) {
15597
- const { t: encryptedPayload } = responseData;
15598
- const decryptedData = await this.encryptedApi.handleDecryption(encryptedPayload, timestamp);
15599
- if (decryptedData === null || decryptedData === void 0 ? void 0 : decryptedData.payload) {
15600
- delete decryptedData.payload.exp;
15601
- delete decryptedData.payload.iat;
15602
- responseData = decryptedData.payload;
15603
- }
15604
- }
15605
- return { isOk: true, val: responseData, isErr: false };
15606
- }
15607
- /**
15608
- * Creates a URL by concatenating the base URL with the provided path.
15609
- *
15610
- * @param {string | null} path - The path to be appended to the base URL.
15611
- * @return {string} The concatenated URL.
15612
- */
15613
- createURL(path) {
15614
- const formattedPath = path && path[0] !== '/' ? `/${path}` : path;
15615
- return `${this.baseUrl}/api${formattedPath}`;
15616
- }
15617
- }
15618
-
15619
- const AUTH_API_PATH = '/auth';
15620
-
15621
- class AuthService extends BaseApi {
15622
- constructor(apiKey, env) {
15623
- super({
15624
- authenticated: false,
15625
- apiKey,
15626
- token: '',
15627
- env,
15628
- });
15629
- }
15630
- /**
15631
- * Retrieves the singleton instance of the AuthService class.
15632
- *
15633
- * @param {string} apiKey - The API key used for authentication.
15634
- * @param {RMN_ENV} env - The environment enum value.
15635
- * @returns {AuthService} The singleton instance of the AuthService class.
15636
- */
15637
- static getInstance(apiKey, env) {
15638
- return SingletonManager.getInstance('AuthService', () => new AuthService(apiKey, env));
15639
- }
15640
- /**
15641
- * Initializes the authentication process.
15642
- *
15643
- * @returns {Promise<IAuthCredentials>} A Promise that resolves to the authenticated credentials.
15644
- * @throws {Error} If there is an error during authentication or the authentication response is unsuccessful.
15645
- */
15646
- async initialize() {
15647
- const { isOk, isErr, val } = await this.get(AUTH_API_PATH, {
15648
- headers: {
15649
- [SDK_CONFIG.apiHeader]: this.authInfo.apiKey,
15650
- },
15651
- });
15652
- if (isErr) {
15653
- throw new Error(`There was an error during authentication: (${isErr === null || isErr === void 0 ? void 0 : isErr.errorMessage})`);
15654
- }
15655
- if (isOk && (val === null || val === void 0 ? void 0 : val.data.token)) {
15656
- this.authInfo.token = val.data.token;
15657
- this.authInfo.authenticated = true;
15658
- }
15659
- else {
15660
- throw new Error('Auth response was not successful');
15661
- }
15662
- return this.authInfo;
15663
- }
15664
- }
15665
-
15666
- const SPOT_ELEMENT_TAG = 'spot-element';
15667
- const CAROUSEL_ELEMENT_TAG = 'spot-carousel-element';
15668
- const GFONT_PRECONNECT = `
15669
- <link rel="preconnect" href="https://fonts.googleapis.com">
15670
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
15671
- `;
15672
- const GFONT_SOURCE_SANS_3 = `
15673
- <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap">
15674
- `;
15675
- const GFONT_CORMORANT = `
15676
- <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">
15677
- `;
15678
-
15679
- /**
15680
- * Determines the event type from a raw event string.
15681
- *
15682
- * @param {string} [event] - The raw event string to evaluate.
15683
- * @returns {RMN_SPOT_EVENT | null} - The corresponding RMN_SPOT_EVENT or null if no match is found.
15684
- */
15685
- function getEventTypeFromRawEvent(event) {
15686
- if (!event) {
15687
- return null;
15688
- }
15689
- if (event.includes('cart')) {
15690
- if (event.includes('add')) {
15691
- return exports.RMN_SPOT_EVENT.ADD_TO_CART;
15692
- }
15693
- if (event.includes('remove')) {
15694
- return exports.RMN_SPOT_EVENT.REMOVE_FROM_CART;
15695
- }
15696
- }
15697
- if (event.includes('purchase')) {
15698
- return exports.RMN_SPOT_EVENT.PURCHASE;
15699
- }
15700
- // if(event.includes('refund')) {
15701
- // return RMN_SPOT_EVENT.REFUND;
15702
- // }
15703
- if (event.includes('wishlist') && event.includes('add')) {
15704
- return exports.RMN_SPOT_EVENT.ADD_TO_WISHLIST;
15705
- }
15706
- return null;
15707
- }
15708
- /**
15709
- * Recursively extracts ID values from a nested data structure.
15710
- * Searches for specified property names and collects their primitive values (strings/numbers).
15711
- *
15712
- * @param data - The data structure to search through (can be nested objects/arrays)
15713
- * @param propertyNames - Array of property names to look for
15714
- * @returns Array of extracted ID values (strings/numbers only)
15715
- *
15716
- * @example
15717
- * const data = {
15718
- * id: [1, 2, 3],
15719
- * nested: { id: 'abc' },
15720
- * items: [{ id: 456 }]
15721
- * };
15722
- * extractDeepIds(data); // Returns [1, 2, 3, 'abc', 456]
15723
- */
15724
- function extractDeepIds(data, propertyNames) {
15725
- const ids = [];
15726
- const defaulPropertyNames = [
15727
- 'id',
15728
- 'upc',
15729
- 'groupingId',
15730
- 'sku',
15731
- 'productId',
15732
- 'item_id',
15733
- 'isbn',
15734
- 'asin',
15735
- 'mpn',
15736
- 'model_number',
15737
- 'article_number',
15738
- 'variant_id',
15739
- 'item_number',
15740
- 'catalog_id',
15741
- 'reference_id',
15742
- ];
15743
- // Set for faster property name lookups
15744
- const propertySet = new Set(defaulPropertyNames);
15745
- /**
15746
- * Processes a value and extracts IDs if it matches criteria
15747
- * @param value - The value to process
15748
- * @param currentKey - The property name of the current value
15749
- */
15750
- const processValue = (value, currentKey) => {
15751
- // Early exit for null/undefined values
15752
- if (value == null)
15753
- return;
15754
- // If current key matches our target properties
15755
- if (currentKey && propertySet.has(currentKey)) {
15756
- if (Array.isArray(value)) {
15757
- // Filter and push valid array values in one pass
15758
- ids.push(...value.filter((item) => typeof item === 'string' || typeof item === 'number'));
15759
- }
15760
- else if (typeof value === 'string' || typeof value === 'number') {
15761
- ids.push(value);
15762
- }
15763
- return; // Stop processing this branch after handling the ID
15764
- }
15765
- // Recursively process nested structures
15766
- if (Array.isArray(value)) {
15767
- value.forEach((item) => processValue(item));
15768
- }
15769
- else if (typeof value === 'object') {
15770
- // Process all enumerable properties
15771
- for (const [key, val] of Object.entries(value)) {
15772
- processValue(val, key);
15773
- }
15774
- }
15775
- };
15776
- processValue(data);
15777
- return ids; // No need to filter nulls as we handle that during collection
15778
- }
15779
- // Fallback method using fetch if sendBeacon isn't available
15780
- async function fallbackEventFire(url) {
15781
- try {
15782
- const racePromise = Promise.race([
15783
- // Promise #1: The fetch request
15784
- fetch(url, {
15785
- method: 'POST',
15786
- keepalive: true,
15787
- }),
15788
- // Promise #2: The timeout
15789
- new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), 2000)),
15790
- ]);
15791
- /**
15792
- * Prevent requests from hanging indefinitely
15793
- * Improve user experience by failing fast
15794
- * Handle slow network conditions gracefully
15795
- * Ensure resources are freed up in a timely manner
15796
- */
15797
- const response = await racePromise;
15798
- return response.ok;
15799
- }
15800
- catch (_a) {
15801
- return false;
15802
- }
15803
- }
15804
- /**
15805
- * Extracts and decodes a URL from a base64-encoded query parameter.
15806
- *
15807
- * @param {string} url - The URL containing the base64-encoded query parameter.
15808
- * @returns {string | null} - The decoded URL or null if not found or invalid.
15809
- */
15810
- function getRedirectUrlFromPayload(url) {
15811
- try {
15812
- const base64String = new URL(url).searchParams.get('e');
15813
- if (!base64String) {
15814
- return null;
15815
- }
15816
- const data = JSON.parse(atob(base64String));
15817
- return data.ur || null;
15818
- }
15819
- catch (_a) {
15820
- return null;
15821
- }
15822
- }
15823
- /**
15824
- * Fires an event using the navigator.sendBeacon method or a fallback method if sendBeacon is not available.
15825
- * If the event is a click event and a redirect URL is found, it redirects the user to that URL.
15826
- *
15827
- * @param {IFireEventParams} params - The parameters for firing the event.
15828
- * @param {RMN_SPOT_EVENT} params.event - The event type.
15829
- * @param {string} params.eventUrl - The URL to which the event is sent.
15830
- * @returns {Promise<void>} - A promise that resolves when the event is fired.
15831
- */
15832
- async function fireEvent({ event, eventUrl }) {
15833
- var _a;
15834
- try {
15835
- 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));
15836
- if (!didFireEvent) {
15837
- return;
15982
+ ((_b = response === null || response === void 0 ? void 0 : response.headers) === null || _b === void 0 ? void 0 : _b.has(REQUEST_CLOUD_PROTECTED_KEY)) &&
15983
+ ((_c = response === null || response === void 0 ? void 0 : response.headers) === null || _c === void 0 ? void 0 : _c.has(REQUEST_CLOUD_PROTECTED_TIMESTAMP)) &&
15984
+ ((_d = response === null || response === void 0 ? void 0 : response.headers) === null || _d === void 0 ? void 0 : _d.get)) {
15985
+ isEncrypted = ((_e = response === null || response === void 0 ? void 0 : response.headers) === null || _e === void 0 ? void 0 : _e.get(REQUEST_CLOUD_PROTECTED_KEY)) === 'true';
15986
+ timestamp = ((_f = response === null || response === void 0 ? void 0 : response.headers) === null || _f === void 0 ? void 0 : _f.get(REQUEST_CLOUD_PROTECTED_TIMESTAMP))
15987
+ ? Number((_g = response === null || response === void 0 ? void 0 : response.headers) === null || _g === void 0 ? void 0 : _g.get(REQUEST_CLOUD_PROTECTED_TIMESTAMP))
15988
+ : 0;
15838
15989
  }
15839
- if (event === exports.RMN_SPOT_EVENT.CLICK) {
15840
- const redirectUrl = getRedirectUrlFromPayload(eventUrl);
15841
- if (redirectUrl) {
15842
- window.location.href = redirectUrl;
15990
+ if (responseData &&
15991
+ isEncrypted &&
15992
+ this.objectHelper.includes(responseData, 't') &&
15993
+ timestamp > 0) {
15994
+ const { t: encryptedPayload } = responseData;
15995
+ const decryptedData = await this.encryptedApi.handleDecryption(encryptedPayload, timestamp);
15996
+ if (decryptedData === null || decryptedData === void 0 ? void 0 : decryptedData.payload) {
15997
+ delete decryptedData.payload.exp;
15998
+ delete decryptedData.payload.iat;
15999
+ responseData = decryptedData.payload;
15843
16000
  }
15844
16001
  }
16002
+ return { isOk: true, val: responseData, isErr: false };
15845
16003
  }
15846
- catch (_b) {
15847
- // Handle errors silently
16004
+ /**
16005
+ * Creates a URL by concatenating the base URL with the provided path.
16006
+ *
16007
+ * @param {string | null} path - The path to be appended to the base URL.
16008
+ * @return {string} The concatenated URL.
16009
+ */
16010
+ createURL(path) {
16011
+ const formattedPath = path && path[0] !== '/' ? `/${path}` : path;
16012
+ return `${this.baseUrl}/api${formattedPath}`;
15848
16013
  }
15849
16014
  }
15850
- function calculateScaleFactor(elementScale) {
15851
- // Step 1: Apply square root for non-linear scaling
15852
- // This creates a more gradual scaling effect, especially for larger changes
15853
- // For example:
15854
- // - elementScale of 0.25 (1/4 size) becomes 0.5
15855
- // - elementScale of 1 (unchanged) remains 1
15856
- // - elementScale of 4 (4x size) becomes 2
15857
- const baseFactor = Math.sqrt(elementScale);
15858
- // Step 2: Apply additional dampening to further soften the scaling effect
15859
- // The dampening factor (0.5) can be adjusted:
15860
- // - Lower values (closer to 0) make scaling more subtle
15861
- // - Higher values (closer to 1) make scaling more pronounced
15862
- const dampening = 0.35;
15863
- // Calculate the scaleFactor:
15864
- // 1. (baseFactor - 1) represents the change from the original size
15865
- // 2. Multiply by dampening to reduce the effect
15866
- // 3. Add 1 to center the scaling around the original size
15867
- // For example, if baseFactor is 2:
15868
- // scaleFactor = 1 + (2 - 1) * 0.5 = 1.5
15869
- const scaleFactor = 1 + (baseFactor - 1) * dampening;
15870
- // Step 3: Define the allowed range for the scale factor
15871
- // This ensures that the font size never changes too drastically
15872
- const minScale = 0.35; // Font will never be smaller than 50% of original
15873
- const maxScale = 1.5; // Font will never be larger than 150% of original
15874
- // Step 4: Clamp the scale factor to the defined range
15875
- // Math.min ensures the value doesn't exceed maxScale
15876
- // Math.max ensures the value isn't less than minScale
15877
- return Math.max(minScale, Math.min(maxScale, scaleFactor));
16015
+
16016
+ const AUTH_API_PATH = '/auth';
16017
+
16018
+ class AuthService extends BaseApi {
16019
+ constructor(apiKey, env) {
16020
+ super({
16021
+ authenticated: false,
16022
+ apiKey,
16023
+ token: '',
16024
+ env,
16025
+ });
16026
+ }
16027
+ /**
16028
+ * Retrieves the singleton instance of the AuthService class.
16029
+ *
16030
+ * @param {string} apiKey - The API key used for authentication.
16031
+ * @param {RMN_ENV} env - The environment enum value.
16032
+ * @returns {AuthService} The singleton instance of the AuthService class.
16033
+ */
16034
+ static getInstance(apiKey, env) {
16035
+ return SingletonManager.getInstance('AuthService', () => new AuthService(apiKey, env));
16036
+ }
16037
+ /**
16038
+ * Initializes the authentication process.
16039
+ *
16040
+ * @returns {Promise<IAuthCredentials>} A Promise that resolves to the authenticated credentials.
16041
+ * @throws {Error} If there is an error during authentication or the authentication response is unsuccessful.
16042
+ */
16043
+ async initialize() {
16044
+ const { isOk, isErr, val } = await this.get(AUTH_API_PATH, {
16045
+ headers: {
16046
+ [SDK_CONFIG.apiHeader]: this.authInfo.apiKey,
16047
+ },
16048
+ });
16049
+ if (isErr) {
16050
+ throw new Error(`There was an error during authentication: (${isErr === null || isErr === void 0 ? void 0 : isErr.errorMessage})`);
16051
+ }
16052
+ if (isOk && (val === null || val === void 0 ? void 0 : val.data.token)) {
16053
+ this.authInfo.token = val.data.token;
16054
+ this.authInfo.authenticated = true;
16055
+ }
16056
+ else {
16057
+ throw new Error('Auth response was not successful');
16058
+ }
16059
+ return this.authInfo;
16060
+ }
15878
16061
  }
15879
16062
 
16063
+ const SPOT_ELEMENT_TAG = 'spot-element';
16064
+ const CAROUSEL_ELEMENT_TAG = 'spot-carousel-element';
16065
+ const GFONT_PRECONNECT = `
16066
+ <link rel="preconnect" href="https://fonts.googleapis.com">
16067
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
16068
+ `;
16069
+ const GFONT_SOURCE_SANS_3 = `
16070
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap">
16071
+ `;
16072
+ const GFONT_CORMORANT = `
16073
+ <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">
16074
+ `;
16075
+
15880
16076
  class IntersectionObserverService {
15881
16077
  constructor(defaultOptions = {}) {
15882
16078
  this.observers = new Map();
@@ -16867,141 +17063,6 @@ class ElementService {
16867
17063
  }
16868
17064
  }
16869
17065
 
16870
- class UniqueIdGenerator {
16871
- /**
16872
- * Initialize the generator with a node ID
16873
- * @param nodeId Number between 0-1023 to identify this instance
16874
- */
16875
- static initialize(nodeId = Math.floor(Math.random() * 1024)) {
16876
- if (nodeId < 0 || nodeId >= 1 << this.nodeBits) {
16877
- throw new Error(`Node ID must be between 0 and ${(1 << this.nodeBits) - 1}`);
16878
- }
16879
- this.nodeId = nodeId;
16880
- }
16881
- /**
16882
- * Convert a number to base32 string with specified length
16883
- */
16884
- static toBase32(num, length) {
16885
- let result = '';
16886
- while (num > 0) {
16887
- result = this.base32Chars[Number(num % BigInt(32))] + result;
16888
- // @ts-expect-error - TS doesn't support bigint division
16889
- num = num / 32n;
16890
- }
16891
- return result.padStart(length, '0');
16892
- }
16893
- /**
16894
- * Generate a cryptographically secure random number
16895
- */
16896
- static getSecureRandom() {
16897
- if (typeof crypto !== 'undefined') {
16898
- const buffer = new Uint32Array(1);
16899
- crypto.getRandomValues(buffer);
16900
- return buffer[0];
16901
- }
16902
- return Math.floor(Math.random() * 0xffffffff);
16903
- }
16904
- /**
16905
- * Wait until next millisecond
16906
- */
16907
- static waitNextMillis(lastTimestamp) {
16908
- let timestamp = Date.now();
16909
- while (timestamp <= lastTimestamp) {
16910
- timestamp = Date.now();
16911
- }
16912
- return timestamp;
16913
- }
16914
- /**
16915
- * Generates a highly unique ID with the following format:
16916
- * TTTTTTTTTTCCCCNNNNNRRRR
16917
- * T: Timestamp (10 chars)
16918
- * C: Counter (4 chars)
16919
- * N: Node ID (5 chars)
16920
- * R: Random (4 chars)
16921
- *
16922
- * Total length: 23 characters, always uppercase alphanumeric
16923
- */
16924
- static generate() {
16925
- if (this.nodeId === undefined) {
16926
- this.initialize();
16927
- }
16928
- let timestamp = Date.now() - this.epoch;
16929
- // Handle clock moving backwards or same millisecond
16930
- if (timestamp < this.lastTimestamp) {
16931
- throw new Error('Clock moved backwards. Refusing to generate ID.');
16932
- }
16933
- if (timestamp === this.lastTimestamp) {
16934
- this.sequence = (this.sequence + 1) & ((1 << this.sequenceBits) - 1);
16935
- if (this.sequence === 0) {
16936
- timestamp = this.waitNextMillis(this.lastTimestamp);
16937
- }
16938
- }
16939
- else {
16940
- this.sequence = 0;
16941
- }
16942
- this.lastTimestamp = timestamp;
16943
- // Generate random component
16944
- const random = this.getSecureRandom() & 0xffff; // 16 bits of randomness
16945
- // Combine all components into a BigInt
16946
- // const id =
16947
- // (BigInt(timestamp) << BigInt(this.nodeBits + this.sequenceBits + 16)) |
16948
- // (BigInt(this.nodeId) << BigInt(this.sequenceBits + 16)) |
16949
- // (BigInt(this.sequence) << BigInt(16)) |
16950
- // BigInt(random);
16951
- // Convert to base32 representation
16952
- const timeComponent = this.toBase32(BigInt(timestamp), 10);
16953
- const counterComponent = this.toBase32(BigInt(this.sequence), 4);
16954
- const nodeComponent = this.toBase32(BigInt(this.nodeId), 5);
16955
- const randomComponent = this.toBase32(BigInt(random), 4);
16956
- return `${timeComponent}${counterComponent}${nodeComponent}${randomComponent}`;
16957
- }
16958
- /**
16959
- * Validates if a string matches the expected ID format
16960
- */
16961
- static isValid(id) {
16962
- if (!/^[0-9A-HJ-NP-Z]{23}$/.test(id))
16963
- return false;
16964
- try {
16965
- const timeComponent = id.slice(0, 10);
16966
- const timestamp = this.decodeBase32(timeComponent);
16967
- const now = Date.now() - this.epoch;
16968
- return timestamp >= 0 && timestamp <= now;
16969
- }
16970
- catch (_a) {
16971
- return false;
16972
- }
16973
- }
16974
- /**
16975
- * Decode base32 string to number
16976
- */
16977
- static decodeBase32(str) {
16978
- let result = 0;
16979
- for (const char of str) {
16980
- result = result * 32 + this.base32Chars.indexOf(char);
16981
- }
16982
- return result;
16983
- }
16984
- /**
16985
- * Extract timestamp from ID
16986
- */
16987
- static getTimestamp(id) {
16988
- if (!this.isValid(id))
16989
- throw new Error('Invalid ID format');
16990
- const timeComponent = id.slice(0, 10);
16991
- const timestamp = this.decodeBase32(timeComponent);
16992
- return new Date(timestamp + this.epoch);
16993
- }
16994
- }
16995
- // Constants for bit manipulation
16996
- UniqueIdGenerator.epoch = 1577836800000; // 2020-01-01 as epoch
16997
- UniqueIdGenerator.nodeBits = 10;
16998
- UniqueIdGenerator.sequenceBits = 12;
16999
- // Instance variables
17000
- UniqueIdGenerator.lastTimestamp = -1;
17001
- UniqueIdGenerator.sequence = 0;
17002
- // Character set for base32 encoding (excluding similar looking characters)
17003
- UniqueIdGenerator.base32Chars = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
17004
-
17005
17066
  function convertHexToRgba(hex, opacity = 1) {
17006
17067
  // Remove # if present
17007
17068
  const cleanHex = hex.replace('#', '');
@@ -18884,7 +18945,7 @@ class MonitorService {
18884
18945
  this.implementedMonitor.start();
18885
18946
  }
18886
18947
  async matchAndFireEvent(eventData, spots) {
18887
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
18948
+ var _a, _b;
18888
18949
  if (!spots)
18889
18950
  return;
18890
18951
  const eventProductIds = new Set(eventData.productIds);
@@ -18893,47 +18954,13 @@ class MonitorService {
18893
18954
  continue;
18894
18955
  const hasCommonProductIds = spot.productIds.find((productId) => eventProductIds.has(productId));
18895
18956
  if (hasCommonProductIds) {
18896
- switch (eventData.event) {
18897
- case exports.RMN_SPOT_EVENT.ADD_TO_CART:
18898
- await this.fireAndPublishSpotEvent({
18899
- spotEvent: exports.RMN_SPOT_EVENT.ADD_TO_CART,
18900
- 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 : '',
18901
- placementId: spot.placementId,
18902
- spotId: spot.spotId,
18903
- });
18904
- break;
18905
- case exports.RMN_SPOT_EVENT.REMOVE_FROM_CART:
18906
- await this.fireAndPublishSpotEvent({
18907
- spotEvent: exports.RMN_SPOT_EVENT.REMOVE_FROM_CART,
18908
- 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 : '',
18909
- placementId: spot.placementId,
18910
- spotId: spot.spotId,
18911
- });
18912
- break;
18913
- case exports.RMN_SPOT_EVENT.PURCHASE:
18914
- await this.fireAndPublishSpotEvent({
18915
- spotEvent: exports.RMN_SPOT_EVENT.PURCHASE,
18916
- 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 : '',
18917
- placementId: spot.placementId,
18918
- spotId: spot.spotId,
18919
- });
18920
- break;
18921
- case exports.RMN_SPOT_EVENT.ADD_TO_WISHLIST:
18922
- await this.fireAndPublishSpotEvent({
18923
- spotEvent: exports.RMN_SPOT_EVENT.ADD_TO_WISHLIST,
18924
- 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 : '',
18925
- placementId: spot.placementId,
18926
- spotId: spot.spotId,
18927
- });
18928
- break;
18929
- case exports.RMN_SPOT_EVENT.BUY_NOW:
18930
- await this.fireAndPublishSpotEvent({
18931
- spotEvent: exports.RMN_SPOT_EVENT.BUY_NOW,
18932
- 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 : '',
18933
- placementId: spot.placementId,
18934
- spotId: spot.spotId,
18935
- });
18936
- break;
18957
+ if (Object.values(exports.RMN_SPOT_EVENT).includes(eventData.event)) {
18958
+ await this.fireAndPublishSpotEvent({
18959
+ spotEvent: eventData.event,
18960
+ eventUrl: (_b = (_a = spot.events.find((event) => event.event === eventData.event)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
18961
+ placementId: spot.placementId,
18962
+ spotId: spot.spotId,
18963
+ });
18937
18964
  }
18938
18965
  }
18939
18966
  }
@@ -19111,7 +19138,7 @@ class EventService {
19111
19138
  spotId: spot.id,
19112
19139
  spotType: spot.spot,
19113
19140
  events: spot.events,
19114
- productIds: (_c = spot.productIds) !== null && _c !== void 0 ? _c : [],
19141
+ productIds: (_c = spot.productIds) !== null && _c !== void 0 ? _c : [1, 2, 3],
19115
19142
  });
19116
19143
  }
19117
19144
  handleIntersectionObserver(placementId, _spot, spotElement) {