@liquidcommercedev/rmn-sdk 1.5.0-beta.25 → 1.5.0-beta.27

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.esm.js CHANGED
@@ -6131,120 +6131,6 @@ function getEventTypeFromRawEvent(event) {
6131
6131
  }
6132
6132
 
6133
6133
  // Configuration object with target field names
6134
- const extractorConfig = {
6135
- ids: [
6136
- // Ps: The function handles all the variations of keywords that end with "id" or "ids"
6137
- // Universal product identifiers
6138
- 'gtin',
6139
- 'gtin8',
6140
- 'gtin12',
6141
- 'gtin13',
6142
- 'gtin14',
6143
- 'mpn',
6144
- 'sku',
6145
- 'upc',
6146
- 'ean',
6147
- 'isbn',
6148
- 'isbn10',
6149
- 'isbn13',
6150
- 'asin',
6151
- // Product codes and references
6152
- 'coupon',
6153
- 'barcode',
6154
- 'product_code',
6155
- 'part_number',
6156
- 'model_number',
6157
- 'item_variant',
6158
- 'item_number',
6159
- 'article_number',
6160
- 'reference',
6161
- 'salsifyGrouping',
6162
- 'grouping',
6163
- ],
6164
- price: [
6165
- 'price',
6166
- 'unitPrice',
6167
- 'cost',
6168
- 'current_price',
6169
- 'sale_price',
6170
- 'price_value',
6171
- 'sale_price_value',
6172
- 'regular_price',
6173
- 'discount_price',
6174
- 'unit_price',
6175
- 'original_price',
6176
- 'final_price',
6177
- 'retail_price',
6178
- ],
6179
- };
6180
- /**
6181
- * Extracts deep values from an object based on specified target type
6182
- * @param data - The source data object to extract values from
6183
- * @param target - The type of values to extract ('ids' or 'price')
6184
- * @param options - Optional configuration for the extraction process
6185
- * @returns Array of extracted values or a single value if onlyFirst is true
6186
- */
6187
- function extractDeepValues(data, target, options = {}) {
6188
- const {
6189
- // eslint-disable-next-line @typescript-eslint/naming-convention
6190
- onlyFirst = false, shouldIncludeZero = false, } = options;
6191
- const values = [];
6192
- const targetProperties = new Set(extractorConfig[target].map((name) => name.toLowerCase()));
6193
- /**
6194
- * Checks if a property name matches the target criteria
6195
- */
6196
- const isTargetField = (key) => {
6197
- const normalizedKey = key.toLowerCase();
6198
- const hasTarget = targetProperties.has(normalizedKey);
6199
- if (target === 'ids') {
6200
- return normalizedKey.endsWith('id') || normalizedKey.endsWith('ids') || hasTarget;
6201
- }
6202
- return hasTarget;
6203
- };
6204
- /**
6205
- * Validates and normalizes extracted values
6206
- */
6207
- const validateValue = (value) => {
6208
- if (typeof value === 'string') {
6209
- return value.trim().length > 0;
6210
- }
6211
- if (typeof value === 'number') {
6212
- return !isNaN(value) && (shouldIncludeZero || value !== 0);
6213
- }
6214
- return false;
6215
- };
6216
- /**
6217
- * Processes a value and extracts matching fields
6218
- */
6219
- const processValue = (value, currentKey) => {
6220
- // Early exit conditions
6221
- if (value == null || (onlyFirst && values.length > 0))
6222
- return;
6223
- // Process current value if it matches target criteria
6224
- if (currentKey && isTargetField(currentKey)) {
6225
- if (Array.isArray(value)) {
6226
- const validValues = value.filter(validateValue);
6227
- values.push(...validValues);
6228
- }
6229
- else if (validateValue(value)) {
6230
- values.push(value);
6231
- }
6232
- return;
6233
- }
6234
- // Recursive processing for nested structures
6235
- if (Array.isArray(value)) {
6236
- value.forEach((item) => processValue(item));
6237
- }
6238
- else if (typeof value === 'object') {
6239
- Object.entries(value).forEach(([key, val]) => processValue(val, key));
6240
- }
6241
- };
6242
- processValue(data);
6243
- // Return based on options
6244
- if (values.length === 0)
6245
- return undefined;
6246
- return onlyFirst ? values[0] : values;
6247
- }
6248
6134
  /**
6249
6135
  * Cleans and normalizes an array of product IDs by:
6250
6136
  * 1. Converting all IDs to strings
@@ -16673,8 +16559,8 @@ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined
16673
16559
  // Map clone to original for event handling
16674
16560
  this.cloneToOriginalMap.set(clonedSlide, this.slides[index]);
16675
16561
  let isDragging = false;
16676
- slideElement.addEventListener('mousedown', () => isDragging = false);
16677
- slideElement.addEventListener('mousemove', () => isDragging = true);
16562
+ slideElement.addEventListener('mousedown', () => (isDragging = false));
16563
+ slideElement.addEventListener('mousemove', () => (isDragging = true));
16678
16564
  // Add event delegation to the slide container
16679
16565
  slideElement.addEventListener('click', (e) => {
16680
16566
  if (isDragging) {
@@ -17197,7 +17083,8 @@ if (typeof window !== 'undefined' && typeof window.customElements !== 'undefined
17197
17083
  CarouselElement = CustomCarouselElement;
17198
17084
  }
17199
17085
 
17200
- function SkeletonTemplate({ fluid, width, height }) {
17086
+ function SkeletonTemplate({ fluid, width, height, spotType, }) {
17087
+ const isSmall = spotType === RMN_SPOT_TYPE.RB_IN_TEXT;
17201
17088
  return `
17202
17089
  <style>
17203
17090
  :host {
@@ -17208,42 +17095,20 @@ function SkeletonTemplate({ fluid, width, height }) {
17208
17095
  width: ${fluid ? '100%' : `${width}px`};
17209
17096
  height: ${fluid ? '100%' : `${height}px`};
17210
17097
  background: #ffffff;
17211
- padding: 20px;
17098
+ padding: ${isSmall ? '0' : '1rem'};
17212
17099
  border-radius: 5px;
17213
17100
  }
17214
17101
 
17215
- .content {
17216
- height: 100%;
17217
- display: flex;
17218
- flex-direction: column;
17219
- gap: 20px;
17220
- }
17221
-
17222
- .image-placeholder {
17102
+ .line {
17223
17103
  width: 100%;
17224
17104
  height: 100%;
17105
+ min-height: 15px;
17225
17106
  background: #f0f0f0;
17226
- border-radius: 4px;
17227
- position: relative;
17228
- overflow: hidden;
17229
- }
17230
-
17231
- .lines-container {
17232
- display: flex;
17233
- flex-direction: column;
17234
- justify-content: flex-end;
17235
- }
17236
-
17237
- .line {
17238
- height: 20px;
17239
- background: #f0f0f0;
17240
- border-radius: 4px;
17241
- margin-bottom: 15px;
17107
+ border-radius: 5px;
17242
17108
  position: relative;
17243
17109
  overflow: hidden;
17244
17110
  }
17245
17111
 
17246
- .image-placeholder::after,
17247
17112
  .line::after {
17248
17113
  content: "";
17249
17114
  position: absolute;
@@ -17260,18 +17125,6 @@ function SkeletonTemplate({ fluid, width, height }) {
17260
17125
  animation: shimmer 1.5s infinite;
17261
17126
  }
17262
17127
 
17263
- .line.header {
17264
- width: 25%;
17265
- }
17266
-
17267
- .line.description {
17268
- width: 65%;
17269
- }
17270
-
17271
- .line.button {
17272
- width: 40%;
17273
- }
17274
-
17275
17128
  @keyframes shimmer {
17276
17129
  0% {
17277
17130
  transform: translateX(-100%);
@@ -17282,14 +17135,7 @@ function SkeletonTemplate({ fluid, width, height }) {
17282
17135
  }
17283
17136
  </style>
17284
17137
 
17285
- <div class="content">
17286
- <div class="image-placeholder"></div>
17287
- <div class="lines-container">
17288
- <div class="line header"></div>
17289
- <div class="line description"></div>
17290
- <div class="line button"></div>
17291
- </div>
17292
- </div>
17138
+ <div class="line"></div>
17293
17139
  `;
17294
17140
  }
17295
17141
 
@@ -17468,6 +17314,7 @@ class ElementService {
17468
17314
  const skeleton = document.createElement(SKELETON_ELEMENT_TAG);
17469
17315
  const dimensions = SPOT_DIMENSIONS[params.spotType];
17470
17316
  skeleton.data = {
17317
+ spotType: params.spotType,
17471
17318
  fluid: params.fluid,
17472
17319
  ...dimensions,
17473
17320
  };
@@ -18909,7 +18756,7 @@ function rbHomepageHeroTwoTileTemplate(spot, config) {
18909
18756
  `;
18910
18757
  }
18911
18758
 
18912
- const STYLES$4 = ({ textColor = '#ffffff', backgroundColor = 'transparent' }, { prefix }) => `
18759
+ const STYLES$4 = ({ textColor = '#212121', backgroundColor = 'transparent' }, { prefix }) => `
18913
18760
  <style>
18914
18761
  .${prefix} {
18915
18762
  display: block;
@@ -18926,6 +18773,10 @@ const STYLES$4 = ({ textColor = '#ffffff', backgroundColor = 'transparent' }, {
18926
18773
  font-family: "Source Sans 3", system-ui;
18927
18774
  margin: 0;
18928
18775
  }
18776
+
18777
+ .${prefix}__header:hover {
18778
+ color: #b5914a;
18779
+ }
18929
18780
  </style>
18930
18781
  `;
18931
18782
  function rbInTextTemplate(spot, config) {
@@ -19379,40 +19230,45 @@ class DataLayerMonitor {
19379
19230
  return result;
19380
19231
  }
19381
19232
  for (const pushedEvent of args) {
19382
- const normalizedData = this.cleanEventData(pushedEvent);
19383
- if (normalizedData) {
19384
- this.listener(normalizedData);
19233
+ const eventName = getEventTypeFromRawEvent(pushedEvent.event);
19234
+ if (!eventName) {
19235
+ continue;
19236
+ }
19237
+ const productData = this.extractProductData(pushedEvent);
19238
+ const eventData = {
19239
+ event: eventName,
19240
+ products: productData,
19241
+ };
19242
+ if (productData) {
19243
+ this.listener(eventData);
19385
19244
  }
19386
19245
  }
19387
19246
  return result;
19388
19247
  };
19389
19248
  }
19390
- cleanEventData(data) {
19391
- const eventName = getEventTypeFromRawEvent(data.event);
19392
- if (!eventName) {
19393
- return null;
19394
- }
19395
- const productIds = extractDeepValues(data, 'ids', {
19396
- onlyFirst: false,
19397
- shouldIncludeZero: true,
19398
- });
19399
- if (Array.isArray(productIds) && productIds.length === 0) {
19400
- return null;
19401
- }
19402
- const normalizedData = {
19403
- event: eventName,
19404
- productIds,
19405
- };
19406
- if (eventName === RMN_SPOT_EVENT.PURCHASE) {
19407
- const productPrice = extractDeepValues(data, 'price', {
19408
- onlyFirst: true,
19409
- shouldIncludeZero: true,
19410
- });
19411
- if (productPrice) {
19412
- normalizedData.productPrice = productPrice;
19249
+ extractProductData(event) {
19250
+ var _a, _b, _c, _d, _e, _f, _g, _h;
19251
+ const items = ((_a = event === null || event === void 0 ? void 0 : event.value) === null || _a === void 0 ? void 0 : _a.items) ||
19252
+ ((_b = event === null || event === void 0 ? void 0 : event.ecommerce) === null || _b === void 0 ? void 0 : _b.items) ||
19253
+ ((_d = (_c = event === null || event === void 0 ? void 0 : event.ecommerce) === null || _c === void 0 ? void 0 : _c.detail) === null || _d === void 0 ? void 0 : _d.products) ||
19254
+ ((_f = (_e = event === null || event === void 0 ? void 0 : event.ecommerce) === null || _e === void 0 ? void 0 : _e.checkout) === null || _f === void 0 ? void 0 : _f.products) ||
19255
+ ((_h = (_g = event === null || event === void 0 ? void 0 : event.ecommerce) === null || _g === void 0 ? void 0 : _g.purchase) === null || _h === void 0 ? void 0 : _h.products) ||
19256
+ [];
19257
+ return items.map((item) => {
19258
+ const data = {
19259
+ id: item.item_id || item.id || '',
19260
+ name: item.item_name || item.name || '',
19261
+ brand: item.item_brand || item.brand || '',
19262
+ variant: item.item_variant || item.variant || '',
19263
+ price: Number(item.price) || 0,
19264
+ quantity: Number(item.quantity) || 1,
19265
+ discount: Number(item.discount) || 0,
19266
+ };
19267
+ if (!data.id) {
19268
+ return null;
19413
19269
  }
19414
- }
19415
- return normalizedData;
19270
+ return data;
19271
+ });
19416
19272
  }
19417
19273
  stop() {
19418
19274
  if (this.originalPush) {
@@ -19459,27 +19315,52 @@ class MonitorService {
19459
19315
  this.implementedMonitor.start();
19460
19316
  }
19461
19317
  async matchAndFireEvent(eventData, spots) {
19462
- var _a, _b;
19318
+ var _a;
19463
19319
  if (!spots)
19464
19320
  return;
19465
- const eventProductIds = new Set(cleanProductIds(eventData.productIds));
19466
19321
  for (const spot of Object.values(spots)) {
19467
19322
  if (!spot.productIds.length)
19468
19323
  continue;
19469
- const hasCommonProductIds = cleanProductIds(spot.productIds).find((productId) => eventProductIds.has(productId));
19470
- if (!hasCommonProductIds || !Object.values(RMN_SPOT_EVENT).includes(eventData.event)) {
19471
- continue;
19324
+ for (const data of eventData.products) {
19325
+ if (!Object.values(RMN_SPOT_EVENT).includes(eventData.event)) {
19326
+ continue;
19327
+ }
19328
+ const spotRelatedProductIdsSet = new Set(cleanProductIds(spot.productIds));
19329
+ const [eventProductId] = cleanProductIds([data.id]);
19330
+ const [eventProductBrand] = cleanProductIds([data.brand]);
19331
+ const [eventVariantBrand] = cleanProductIds([data.variant]);
19332
+ const isProductMatch = [eventProductId, eventProductBrand, eventVariantBrand].some((id) => spotRelatedProductIdsSet.has(id));
19333
+ if (!isProductMatch) {
19334
+ continue;
19335
+ }
19336
+ const eventPosition = spot.events.findIndex((event) => event.event === eventData.event);
19337
+ if (eventPosition === -1)
19338
+ continue;
19339
+ const eventUrl = spot.events[eventPosition].url;
19340
+ let additionalQueryParams = '';
19341
+ if (eventData.event === RMN_SPOT_EVENT.PURCHASE) {
19342
+ const gmv = data.price && data.quantity ? data.price * data.quantity : undefined;
19343
+ // gmv = gross merchandise value, it is calculated by multiplying the product price by the product quantity
19344
+ additionalQueryParams = objectToQueryParams({ gmv });
19345
+ }
19346
+ // Fire the event and publish it to the pubsub service
19347
+ await this.fireAndPublishSpotEvent({
19348
+ spotEvent: eventData.event,
19349
+ eventUrl: `${eventUrl}${additionalQueryParams ? `&${additionalQueryParams}` : ''}`,
19350
+ placementId: spot.placementId,
19351
+ spotId: spot.spotId,
19352
+ });
19353
+ // Remove the event url from the spot to prevent duplicate events
19354
+ spot.events[eventPosition].url = '';
19355
+ // Update the spots in the local storage
19356
+ (_a = this.localStorageService) === null || _a === void 0 ? void 0 : _a.setSpot(spot.spotId, {
19357
+ placementId: spot.placementId,
19358
+ spotId: spot.spotId,
19359
+ spotType: spot.spotType,
19360
+ events: spot.events,
19361
+ productIds: spot.productIds,
19362
+ });
19472
19363
  }
19473
- 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 : '';
19474
- const additionalQueryParams = objectToQueryParams({
19475
- override: eventData.productPrice,
19476
- });
19477
- await this.fireAndPublishSpotEvent({
19478
- spotEvent: eventData.event,
19479
- eventUrl: `${eventUrl}${additionalQueryParams ? `&${additionalQueryParams}` : ''}`,
19480
- placementId: spot.placementId,
19481
- spotId: spot.spotId,
19482
- });
19483
19364
  }
19484
19365
  }
19485
19366
  async fireAndPublishSpotEvent({ spotEvent, eventUrl, placementId, spotId, }) {
@@ -1,4 +1,4 @@
1
- type ExtractorTarget = 'ids' | 'price';
1
+ type ExtractorTarget = 'ids' | 'price' | 'quantity';
2
2
  type ExtractedValue = string | number;
3
3
  /**
4
4
  * Extracts deep values from an object based on specified target type
@@ -1,5 +1,6 @@
1
1
  import type { RMN_SPOT_TYPE } from 'enums';
2
2
  export interface ICustomSkeletonElementData {
3
+ spotType: RMN_SPOT_TYPE;
3
4
  fluid: boolean;
4
5
  width: number;
5
6
  height: number;
@@ -1,2 +1,2 @@
1
1
  import type { ICustomSkeletonElementData } from './skeleton.interface';
2
- export declare function SkeletonTemplate({ fluid, width, height }: ICustomSkeletonElementData): string;
2
+ export declare function SkeletonTemplate({ fluid, width, height, spotType, }: ICustomSkeletonElementData): string;
@@ -1,12 +1,13 @@
1
1
  import type { RMN_SPOT_EVENT } from 'enums';
2
2
  export interface IDataLayerEvent {
3
- event: string;
4
- [key: string]: any;
3
+ event: RMN_SPOT_EVENT;
4
+ products: IProductData[];
5
5
  }
6
6
  export interface INormalizedEventData {
7
7
  event: RMN_SPOT_EVENT;
8
8
  productIds: Array<string | number>;
9
9
  productPrice?: number;
10
+ productQuantity?: number;
10
11
  }
11
12
  export interface IFireAndPublishSpotEventParams {
12
13
  spotEvent: RMN_SPOT_EVENT;
@@ -14,3 +15,12 @@ export interface IFireAndPublishSpotEventParams {
14
15
  placementId: string;
15
16
  spotId: string;
16
17
  }
18
+ export interface IProductData {
19
+ id: string | number;
20
+ name: string;
21
+ brand: string;
22
+ variant: string;
23
+ price: number;
24
+ quantity: number;
25
+ discount?: number;
26
+ }
@@ -1,12 +1,12 @@
1
- import type { INormalizedEventData } from '../monitor.interface';
1
+ import type { IDataLayerEvent } from '../monitor.interface';
2
2
  export declare class DataLayerMonitor {
3
3
  private static instance;
4
4
  private readonly originalPush;
5
5
  private listener?;
6
6
  private constructor();
7
7
  static getInstance(): DataLayerMonitor;
8
- setListener(listener: (data: INormalizedEventData) => void): void;
8
+ setListener(listener: (data: IDataLayerEvent) => void): void;
9
9
  start(): void;
10
- private cleanEventData;
10
+ private extractProductData;
11
11
  stop(): void;
12
12
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@liquidcommercedev/rmn-sdk",
3
3
  "description": "LiquidCommerce RMN SDK",
4
4
  "author": "LiquidCommerce Tech",
5
- "version": "1.5.0-beta.25",
5
+ "version": "1.5.0-beta.27",
6
6
  "homepage": "https://docs.liquidcommerce.co/rmn-sdk",
7
7
  "main": "./dist/index.cjs",
8
8
  "module": "./dist/index.esm.js",