@liquidcommercedev/rmn-sdk 1.5.0-beta.16 → 1.5.0-beta.18

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
@@ -6130,6 +6130,120 @@ function getEventTypeFromRawEvent(event) {
6130
6130
  return null;
6131
6131
  }
6132
6132
 
6133
+ // Configuration object with target field names
6134
+ const extractorConfig = {
6135
+ ids: [
6136
+ // Universal product identifiers
6137
+ 'gtin',
6138
+ 'gtin8',
6139
+ 'gtin12',
6140
+ 'gtin13',
6141
+ 'gtin14',
6142
+ 'mpn',
6143
+ 'sku',
6144
+ 'upc',
6145
+ 'ean',
6146
+ 'isbn',
6147
+ 'isbn10',
6148
+ 'isbn13',
6149
+ 'asin',
6150
+ // Product codes and references
6151
+ 'coupon',
6152
+ 'barcode',
6153
+ 'product_code',
6154
+ 'part_number',
6155
+ 'model_number',
6156
+ 'item_variant',
6157
+ 'item_number',
6158
+ 'article_number',
6159
+ 'reference',
6160
+ 'groupingId',
6161
+ ],
6162
+ price: [
6163
+ 'price',
6164
+ 'unitPrice',
6165
+ 'cost',
6166
+ 'current_price',
6167
+ 'sale_price',
6168
+ 'price_value',
6169
+ 'sale_price_value',
6170
+ 'regular_price',
6171
+ 'discount_price',
6172
+ 'unit_price',
6173
+ 'original_price',
6174
+ 'final_price',
6175
+ 'retail_price',
6176
+ ],
6177
+ };
6178
+ /**
6179
+ * Extracts deep values from an object based on specified target type
6180
+ * @param data - The source data object to extract values from
6181
+ * @param target - The type of values to extract ('ids' or 'price')
6182
+ * @param options - Optional configuration for the extraction process
6183
+ * @returns Array of extracted values or a single value if onlyFirst is true
6184
+ */
6185
+ function extractDeepValues(data, target, options = {}) {
6186
+ const {
6187
+ // eslint-disable-next-line @typescript-eslint/naming-convention
6188
+ onlyFirst = false, shouldIncludeZero = false, } = options;
6189
+ const values = [];
6190
+ const targetProperties = new Set(extractorConfig[target].map((name) => name.toLowerCase()));
6191
+ /**
6192
+ * Checks if a property name matches the target criteria
6193
+ */
6194
+ const isTargetField = (key) => {
6195
+ const normalizedKey = key.toLowerCase();
6196
+ const hasTarget = targetProperties.has(normalizedKey);
6197
+ if (target === 'ids') {
6198
+ return normalizedKey.endsWith('id') || normalizedKey.endsWith('ids') || hasTarget;
6199
+ }
6200
+ return hasTarget;
6201
+ };
6202
+ /**
6203
+ * Validates and normalizes extracted values
6204
+ */
6205
+ const validateValue = (value) => {
6206
+ if (typeof value === 'string') {
6207
+ return value.trim().length > 0;
6208
+ }
6209
+ if (typeof value === 'number') {
6210
+ return !isNaN(value) && (shouldIncludeZero || value !== 0);
6211
+ }
6212
+ return false;
6213
+ };
6214
+ /**
6215
+ * Processes a value and extracts matching fields
6216
+ */
6217
+ const processValue = (value, currentKey) => {
6218
+ // Early exit conditions
6219
+ if (value == null || (onlyFirst && values.length > 0))
6220
+ return;
6221
+ // Process current value if it matches target criteria
6222
+ if (currentKey && isTargetField(currentKey)) {
6223
+ if (Array.isArray(value)) {
6224
+ const validValues = value.filter(validateValue);
6225
+ values.push(...validValues);
6226
+ }
6227
+ else if (validateValue(value)) {
6228
+ values.push(value);
6229
+ }
6230
+ return;
6231
+ }
6232
+ // Recursive processing for nested structures
6233
+ if (Array.isArray(value)) {
6234
+ value.forEach((item) => processValue(item));
6235
+ }
6236
+ else if (typeof value === 'object') {
6237
+ Object.entries(value).forEach(([key, val]) => processValue(val, key));
6238
+ }
6239
+ };
6240
+ processValue(data);
6241
+ // Return based on options
6242
+ if (values.length === 0)
6243
+ return undefined;
6244
+ return onlyFirst ? values[0] : values;
6245
+ }
6246
+
6133
6247
  class SingletonManager {
6134
6248
  /**
6135
6249
  * Retrieves an instance of the specified class using the provided instance creator function.
@@ -6295,97 +6409,6 @@ class ObjectHelper {
6295
6409
  }
6296
6410
  }
6297
6411
 
6298
- /**
6299
- * Recursively extracts ID values from a nested data structure.
6300
- * Searches for specified property names and collects their primitive values (strings/numbers).
6301
- * Captures properties ending with 'id' and any additional specified property names.
6302
- *
6303
- * @param data - The data structure to search through (can be nested objects/arrays)
6304
- * @param propertyNames - Array of additional property names to look for (optional)
6305
- * @returns Array of extracted ID values (strings/numbers only)
6306
- *
6307
- * @example
6308
- * const data = {
6309
- * id: [1, 2, 3],
6310
- * nested: { id: 'abc', userId: 123 },
6311
- * items: [{ id: 456, productId: '789', sku: 'ABC123' }]
6312
- * };
6313
- * extractDeepIds(data); // Returns [1, 2, 3, 'abc', 123, 456, '789', 'ABC123']
6314
- */
6315
- function extractDeepIds(data, propertyNames) {
6316
- const ids = [];
6317
- const defaultPropertyNames = [
6318
- // Universal product identifiers
6319
- 'gtin', // Global Trade Item Number
6320
- 'gtin8', // 8-digit GTIN
6321
- 'gtin12', // 12-digit GTIN (UPC)
6322
- 'gtin13', // 13-digit GTIN (EAN)
6323
- 'gtin14', // 14-digit GTIN
6324
- 'mpn', // Manufacturer Part Number
6325
- 'sku', // Stock Keeping Unit
6326
- 'upc', // Universal Product Code
6327
- 'ean', // European Article Number
6328
- 'isbn', // International Standard Book Number
6329
- 'isbn10', // 10-digit ISBN
6330
- 'isbn13', // 13-digit ISBN
6331
- 'asin', // Amazon Standard Identification Number
6332
- // Product codes and references
6333
- 'coupon',
6334
- 'barcode',
6335
- 'product_code',
6336
- 'part_number',
6337
- 'model_number',
6338
- 'item_variant',
6339
- 'item_number',
6340
- 'article_number',
6341
- 'reference',
6342
- 'groupingId',
6343
- ];
6344
- // Convert property names to lowercase for consistent comparison
6345
- const additionalProperties = new Set((defaultPropertyNames).map((name) => name.toLowerCase()));
6346
- /**
6347
- * Checks if a property name is an ID field
6348
- * @param key - The property name to check
6349
- * @returns boolean indicating if the key is an ID field
6350
- */
6351
- const isIdField = (key) => {
6352
- const lowercaseKey = key.toLowerCase();
6353
- return lowercaseKey.endsWith('id') || additionalProperties.has(lowercaseKey);
6354
- };
6355
- /**
6356
- * Processes a value and extracts IDs if it matches criteria
6357
- * @param value - The value to process
6358
- * @param currentKey - The property name of the current value
6359
- */
6360
- const processValue = (value, currentKey) => {
6361
- // Early exit for null/undefined values
6362
- if (value == null)
6363
- return;
6364
- // If current key matches our target properties
6365
- if (currentKey && isIdField(currentKey)) {
6366
- if (Array.isArray(value)) {
6367
- // Filter and push valid array values in one pass
6368
- ids.push(...value.filter((item) => typeof item === 'string' || typeof item === 'number'));
6369
- }
6370
- else if (typeof value === 'string' || typeof value === 'number') {
6371
- ids.push(value);
6372
- }
6373
- return; // Stop processing this branch after handling the ID
6374
- }
6375
- // Recursively process nested structures
6376
- if (Array.isArray(value)) {
6377
- value.forEach((item) => processValue(item));
6378
- }
6379
- else if (typeof value === 'object') {
6380
- // Process all enumerable properties
6381
- for (const [key, val] of Object.entries(value)) {
6382
- processValue(val, key);
6383
- }
6384
- }
6385
- };
6386
- processValue(data);
6387
- return ids;
6388
- }
6389
6412
  // Fallback method using fetch if sendBeacon isn't available
6390
6413
  async function fallbackEventFire(url) {
6391
6414
  try {
@@ -6411,22 +6434,49 @@ async function fallbackEventFire(url) {
6411
6434
  return false;
6412
6435
  }
6413
6436
  }
6437
+ /**
6438
+ * Helper function to decode base64 string and parse JSON
6439
+ *
6440
+ * @param {string} base64String - The base64 encoded JSON string
6441
+ * @returns {T | null} - Decoded and parsed object or null if invalid
6442
+ */
6443
+ function decodeBase64Json(base64String) {
6444
+ try {
6445
+ return JSON.parse(atob(base64String));
6446
+ }
6447
+ catch (_a) {
6448
+ return null;
6449
+ }
6450
+ }
6414
6451
  /**
6415
6452
  * Extracts and decodes a URL from a base64-encoded query parameter.
6416
6453
  *
6417
6454
  * @param {string} url - The URL containing the base64-encoded query parameter.
6418
6455
  * @returns {string | null} - The decoded URL or null if not found or invalid.
6456
+ * @throws {Error} - If URL is malformed or payload is invalid.
6419
6457
  */
6420
6458
  function getRedirectUrlFromPayload(url) {
6459
+ if (!url)
6460
+ return null;
6421
6461
  try {
6422
- const base64String = new URL(url).searchParams.get('e');
6423
- if (!base64String) {
6462
+ // Extract initial payload
6463
+ const payload = new URL(url).searchParams.get('p');
6464
+ if (!payload)
6424
6465
  return null;
6425
- }
6426
- const data = JSON.parse(atob(base64String));
6427
- return data.ur || null;
6466
+ // Decode first layer
6467
+ const decodedData = decodeBase64Json(payload);
6468
+ if (!(decodedData === null || decodedData === void 0 ? void 0 : decodedData.u))
6469
+ return null;
6470
+ // Extract URL from nested query
6471
+ const eventPayload = new URLSearchParams(decodedData.u).get('e');
6472
+ if (!eventPayload)
6473
+ return null;
6474
+ // Decode second layer
6475
+ const eventData = decodeBase64Json(eventPayload);
6476
+ return (eventData === null || eventData === void 0 ? void 0 : eventData.ur) || null;
6428
6477
  }
6429
6478
  catch (_a) {
6479
+ console.warn('RmnSdk: Failed to extract redirect URL from payload.');
6430
6480
  return null;
6431
6481
  }
6432
6482
  }
@@ -6486,6 +6536,23 @@ function calculateScaleFactor(elementScale) {
6486
6536
  // Math.max ensures the value isn't less than minScale
6487
6537
  return Math.max(minScale, Math.min(maxScale, scaleFactor));
6488
6538
  }
6539
+ /**
6540
+ * Converts an object to a query string.
6541
+ *
6542
+ * @param {Record<string, string|number|undefined|null>} obj - The object to be converted to a query string.
6543
+ * @returns {string} - The query string.
6544
+ */
6545
+ function objectToQueryParams(obj) {
6546
+ return Object.entries(obj !== null && obj !== void 0 ? obj : {})
6547
+ .map(([key, value]) => {
6548
+ if (value == null) {
6549
+ return '';
6550
+ }
6551
+ return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
6552
+ })
6553
+ .filter(Boolean)
6554
+ .join('&');
6555
+ }
6489
6556
 
6490
6557
  class UniqueIdGenerator {
6491
6558
  /**
@@ -15783,7 +15850,11 @@ class LocalStorageService {
15783
15850
  }
15784
15851
  // ======================== Utility functions ======================== //
15785
15852
  getUserId() {
15786
- return LocalStorageService.localStorageKey.replace(`${LocalStorageService.localStorageKeyPrefix}_`, '');
15853
+ const key = LocalStorageService.localStorageKey;
15854
+ if (!key) {
15855
+ this.setUserId();
15856
+ }
15857
+ return key.replace(`${LocalStorageService.localStorageKeyPrefix}_`, '');
15787
15858
  }
15788
15859
  /**
15789
15860
  * Sets the user ID in the local storage.
@@ -18654,11 +18725,27 @@ class DataLayerMonitor {
18654
18725
  if (!eventName) {
18655
18726
  return null;
18656
18727
  }
18657
- const productIds = extractDeepIds(data);
18658
- return {
18728
+ const productIds = extractDeepValues(data, 'ids', {
18729
+ onlyFirst: false,
18730
+ shouldIncludeZero: true,
18731
+ });
18732
+ if (Array.isArray(productIds) && productIds.length === 0) {
18733
+ return null;
18734
+ }
18735
+ const normalizedData = {
18659
18736
  event: eventName,
18660
18737
  productIds,
18661
18738
  };
18739
+ if (eventName === exports.RMN_SPOT_EVENT.PURCHASE) {
18740
+ const productPrice = extractDeepValues(data, 'price', {
18741
+ onlyFirst: true,
18742
+ shouldIncludeZero: true,
18743
+ });
18744
+ if (productPrice) {
18745
+ normalizedData.productPrice = productPrice;
18746
+ }
18747
+ }
18748
+ return normalizedData;
18662
18749
  }
18663
18750
  stop() {
18664
18751
  if (this.originalPush) {
@@ -18712,16 +18799,19 @@ class MonitorService {
18712
18799
  if (!spot.productIds.length)
18713
18800
  continue;
18714
18801
  const hasCommonProductIds = spot.productIds.find((productId) => eventProductIds.has(String(productId)));
18715
- if (hasCommonProductIds) {
18716
- if (Object.values(exports.RMN_SPOT_EVENT).includes(eventData.event)) {
18717
- await this.fireAndPublishSpotEvent({
18718
- spotEvent: eventData.event,
18719
- eventUrl: (_b = (_a = spot.events.find((event) => event.event === eventData.event)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
18720
- placementId: spot.placementId,
18721
- spotId: spot.spotId,
18722
- });
18723
- }
18802
+ if (!hasCommonProductIds || !Object.values(exports.RMN_SPOT_EVENT).includes(eventData.event)) {
18803
+ continue;
18724
18804
  }
18805
+ const eventUrl = (_b = (_a = spot.events.find((event) => event.event === eventData.event)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '';
18806
+ const additionalQueryParams = objectToQueryParams({
18807
+ override: eventData.productPrice,
18808
+ });
18809
+ await this.fireAndPublishSpotEvent({
18810
+ spotEvent: eventData.event,
18811
+ eventUrl: `${eventUrl}${additionalQueryParams ? `&${additionalQueryParams}` : ''}`,
18812
+ placementId: spot.placementId,
18813
+ spotId: spot.spotId,
18814
+ });
18725
18815
  }
18726
18816
  }
18727
18817
  async fireAndPublishSpotEvent({ spotEvent, eventUrl, placementId, spotId, }) {
@@ -18945,6 +19035,9 @@ class SelectionService extends BaseApi {
18945
19035
  * @return {Promise<ISpots | { error: string }>} - The spots response object.
18946
19036
  */
18947
19037
  async spotSelection(data) {
19038
+ if (data.userId === undefined) {
19039
+ data.userId = this.getUserId();
19040
+ }
18948
19041
  const { isOk, val, isErr } = await this.post(SELECTION_API_PATH, data, {});
18949
19042
  if (isErr) {
18950
19043
  return { error: `There was an error during spot selection: (${isErr === null || isErr === void 0 ? void 0 : isErr.errorMessage})` };
@@ -18956,6 +19049,14 @@ class SelectionService extends BaseApi {
18956
19049
  }
18957
19050
  return { error: 'Spot selection response was not successful' };
18958
19051
  }
19052
+ getUserId() {
19053
+ const isWeb = typeof window !== 'undefined';
19054
+ if (isWeb) {
19055
+ const localStorageService = LocalStorageService.getInstance();
19056
+ return localStorageService.getUserId();
19057
+ }
19058
+ return undefined;
19059
+ }
18959
19060
  }
18960
19061
 
18961
19062
  const SPOT_EVENTS_EXAMPLE = [
@@ -19621,9 +19722,18 @@ async function waitForDOM() {
19621
19722
  });
19622
19723
  });
19623
19724
  }
19725
+ // Sets the id for the user who is browsing the website
19726
+ // This id is used to identify the user and provide personalized content
19727
+ function setUserId() {
19728
+ if (isBrowserEnvironment()) {
19729
+ const localStorageService = LocalStorageService.getInstance();
19730
+ localStorageService.setUserId();
19731
+ }
19732
+ }
19624
19733
 
19625
19734
  /**
19626
19735
  * LiquidCommerce Rmn Client
19736
+ *
19627
19737
  * @class
19628
19738
  */
19629
19739
  class LiquidCommerceRmnClient {
@@ -19752,7 +19862,8 @@ class LiquidCommerceRmnClient {
19752
19862
  * If it is near, make the spot selection request.
19753
19863
  */
19754
19864
  this.intersectionObserver.observe(placement, spotPlacementIsNearCallback, {
19755
- rootMargin: '500px',
19865
+ rootMargin: '1000px',
19866
+ threshold: 0,
19756
19867
  });
19757
19868
  }
19758
19869
  }
@@ -19928,7 +20039,6 @@ class LiquidCommerceRmnClient {
19928
20039
  */
19929
20040
  async injectSpotSelectionRequest(params) {
19930
20041
  const { inject, filter, config } = params;
19931
- const localStorageService = LocalStorageService.getInstance();
19932
20042
  const spots = inject.map((item) => ({
19933
20043
  placementId: item.placementId,
19934
20044
  spot: item.spotType,
@@ -19936,7 +20046,6 @@ class LiquidCommerceRmnClient {
19936
20046
  ...item === null || item === void 0 ? void 0 : item.filter,
19937
20047
  }));
19938
20048
  const request = {
19939
- userId: localStorageService.getUserId(),
19940
20049
  url: config === null || config === void 0 ? void 0 : config.url,
19941
20050
  filter,
19942
20051
  spots,
@@ -19955,6 +20064,7 @@ class LiquidCommerceRmnClient {
19955
20064
  async function RmnClient(apiKey, config) {
19956
20065
  const authService = AuthService.getInstance(apiKey, config.env);
19957
20066
  const credentials = await authService.initialize();
20067
+ setUserId();
19958
20068
  return new LiquidCommerceRmnClient(credentials);
19959
20069
  }
19960
20070
  /**