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

Sign up to get free protection for your applications and to get access to all the features.
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,6 +19722,14 @@ 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
@@ -19752,7 +19861,8 @@ class LiquidCommerceRmnClient {
19752
19861
  * If it is near, make the spot selection request.
19753
19862
  */
19754
19863
  this.intersectionObserver.observe(placement, spotPlacementIsNearCallback, {
19755
- rootMargin: '500px',
19864
+ rootMargin: '1000px',
19865
+ threshold: 0,
19756
19866
  });
19757
19867
  }
19758
19868
  }
@@ -19928,7 +20038,6 @@ class LiquidCommerceRmnClient {
19928
20038
  */
19929
20039
  async injectSpotSelectionRequest(params) {
19930
20040
  const { inject, filter, config } = params;
19931
- const localStorageService = LocalStorageService.getInstance();
19932
20041
  const spots = inject.map((item) => ({
19933
20042
  placementId: item.placementId,
19934
20043
  spot: item.spotType,
@@ -19936,7 +20045,6 @@ class LiquidCommerceRmnClient {
19936
20045
  ...item === null || item === void 0 ? void 0 : item.filter,
19937
20046
  }));
19938
20047
  const request = {
19939
- userId: localStorageService.getUserId(),
19940
20048
  url: config === null || config === void 0 ? void 0 : config.url,
19941
20049
  filter,
19942
20050
  spots,
@@ -19955,6 +20063,7 @@ class LiquidCommerceRmnClient {
19955
20063
  async function RmnClient(apiKey, config) {
19956
20064
  const authService = AuthService.getInstance(apiKey, config.env);
19957
20065
  const credentials = await authService.initialize();
20066
+ setUserId();
19958
20067
  return new LiquidCommerceRmnClient(credentials);
19959
20068
  }
19960
20069
  /**