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

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