@liquidcommercedev/rmn-sdk 1.5.0-beta.8 → 1.5.0-beta.9
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 +351 -256
- package/dist/index.esm.js +352 -257
- package/dist/types/common/helpers/utils.helper.d.ts +50 -0
- package/dist/types/enums.d.ts +4 -1
- package/dist/types/modules/event/event.interface.d.ts +6 -36
- package/dist/types/modules/event/event.service.d.ts +5 -23
- package/dist/types/modules/event/index.d.ts +0 -2
- package/dist/types/modules/{event/helpers → helper-service}/index.d.ts +1 -1
- package/dist/types/modules/{event/helpers → helper-service}/localstorage.service.d.ts +2 -2
- package/dist/types/modules/{event/pubsub.d.ts → helper-service/pubsub.service.d.ts} +4 -4
- package/dist/types/modules/monitor/index.d.ts +2 -0
- package/dist/types/modules/monitor/monitor.enums.d.ts +4 -0
- package/dist/types/modules/monitor/monitor.interface.d.ts +12 -0
- package/dist/types/modules/monitor/monitor.service.d.ts +12 -0
- package/dist/types/modules/{event/helpers → monitor/monitors}/datalayer.monitor.d.ts +1 -10
- package/dist/types/types.d.ts +4 -4
- package/package.json +1 -1
- package/umd/liquidcommerce-rmn-sdk.min.js +1 -1
- package/dist/types/modules/element/component/utils.d.ts +0 -1
- package/dist/types/modules/event/helpers/utils.d.ts +0 -24
- package/dist/types/modules/event/user.monitor.d.ts +0 -14
- /package/dist/types/modules/{event/helpers → helper-service}/intersection.service.d.ts +0 -0
- /package/dist/types/modules/{event/helpers → helper-service}/resize.service.d.ts +0 -0
package/dist/index.cjs
CHANGED
@@ -55,9 +55,13 @@ exports.RMN_FILTER_PROPERTIES = void 0;
|
|
55
55
|
RMN_FILTER_PROPERTIES["PUBLISHERS"] = "publishers";
|
56
56
|
RMN_FILTER_PROPERTIES["SECTION"] = "section";
|
57
57
|
})(exports.RMN_FILTER_PROPERTIES || (exports.RMN_FILTER_PROPERTIES = {}));
|
58
|
+
exports.RMN_EVENT = void 0;
|
59
|
+
(function (RMN_EVENT) {
|
60
|
+
RMN_EVENT["LIFECYCLE_STATE"] = "LIFECYCLE_STATE";
|
61
|
+
RMN_EVENT["SPOT_EVENT"] = "SPOT_EVENT";
|
62
|
+
})(exports.RMN_EVENT || (exports.RMN_EVENT = {}));
|
58
63
|
exports.RMN_SPOT_EVENT = void 0;
|
59
64
|
(function (RMN_SPOT_EVENT) {
|
60
|
-
RMN_SPOT_EVENT["LIFECYCLE_STATE"] = "LIFECYCLE_STATE";
|
61
65
|
RMN_SPOT_EVENT["IMPRESSION"] = "IMPRESSION";
|
62
66
|
RMN_SPOT_EVENT["CLICK"] = "CLICK";
|
63
67
|
RMN_SPOT_EVENT["PURCHASE"] = "PURCHASE";
|
@@ -15167,6 +15171,12 @@ const GFONT_CORMORANT = `
|
|
15167
15171
|
<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">
|
15168
15172
|
`;
|
15169
15173
|
|
15174
|
+
/**
|
15175
|
+
* Determines the event type from a raw event string.
|
15176
|
+
*
|
15177
|
+
* @param {string} [event] - The raw event string to evaluate.
|
15178
|
+
* @returns {RMN_SPOT_EVENT | null} - The corresponding RMN_SPOT_EVENT or null if no match is found.
|
15179
|
+
*/
|
15170
15180
|
function getEventTypeFromRawEvent(event) {
|
15171
15181
|
if (!event) {
|
15172
15182
|
return null;
|
@@ -15195,7 +15205,7 @@ function getEventTypeFromRawEvent(event) {
|
|
15195
15205
|
* Searches for specified property names and collects their primitive values (strings/numbers).
|
15196
15206
|
*
|
15197
15207
|
* @param data - The data structure to search through (can be nested objects/arrays)
|
15198
|
-
* @param propertyNames - Array of property names to look for
|
15208
|
+
* @param propertyNames - Array of property names to look for
|
15199
15209
|
* @returns Array of extracted ID values (strings/numbers only)
|
15200
15210
|
*
|
15201
15211
|
* @example
|
@@ -15206,10 +15216,27 @@ function getEventTypeFromRawEvent(event) {
|
|
15206
15216
|
* };
|
15207
15217
|
* extractDeepIds(data); // Returns [1, 2, 3, 'abc', 456]
|
15208
15218
|
*/
|
15209
|
-
function extractDeepIds(data, propertyNames
|
15219
|
+
function extractDeepIds(data, propertyNames) {
|
15210
15220
|
const ids = [];
|
15221
|
+
const defaulPropertyNames = [
|
15222
|
+
'id',
|
15223
|
+
'upc',
|
15224
|
+
'groupingId',
|
15225
|
+
'sku',
|
15226
|
+
'productId',
|
15227
|
+
'item_id',
|
15228
|
+
'isbn',
|
15229
|
+
'asin',
|
15230
|
+
'mpn',
|
15231
|
+
'model_number',
|
15232
|
+
'article_number',
|
15233
|
+
'variant_id',
|
15234
|
+
'item_number',
|
15235
|
+
'catalog_id',
|
15236
|
+
'reference_id',
|
15237
|
+
];
|
15211
15238
|
// Set for faster property name lookups
|
15212
|
-
const propertySet = new Set(
|
15239
|
+
const propertySet = new Set(defaulPropertyNames);
|
15213
15240
|
/**
|
15214
15241
|
* Processes a value and extracts IDs if it matches criteria
|
15215
15242
|
* @param value - The value to process
|
@@ -15244,54 +15271,106 @@ function extractDeepIds(data, propertyNames = ['id', 'upc', 'groupingId', 'sku',
|
|
15244
15271
|
processValue(data);
|
15245
15272
|
return ids; // No need to filter nulls as we handle that during collection
|
15246
15273
|
}
|
15247
|
-
|
15248
|
-
|
15249
|
-
|
15250
|
-
|
15251
|
-
|
15252
|
-
|
15253
|
-
|
15274
|
+
// Fallback method using fetch if sendBeacon isn't available
|
15275
|
+
async function fallbackEventFire(url) {
|
15276
|
+
try {
|
15277
|
+
const racePromise = Promise.race([
|
15278
|
+
// Promise #1: The fetch request
|
15279
|
+
fetch(url, {
|
15280
|
+
method: 'POST',
|
15281
|
+
keepalive: true,
|
15282
|
+
}),
|
15283
|
+
// Promise #2: The timeout
|
15284
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), 2000)),
|
15285
|
+
]);
|
15286
|
+
/**
|
15287
|
+
* Prevent requests from hanging indefinitely
|
15288
|
+
* Improve user experience by failing fast
|
15289
|
+
* Handle slow network conditions gracefully
|
15290
|
+
* Ensure resources are freed up in a timely manner
|
15291
|
+
*/
|
15292
|
+
const response = await racePromise;
|
15293
|
+
return response.ok;
|
15254
15294
|
}
|
15255
|
-
|
15256
|
-
|
15257
|
-
|
15295
|
+
catch (_a) {
|
15296
|
+
return false;
|
15297
|
+
}
|
15298
|
+
}
|
15299
|
+
/**
|
15300
|
+
* Extracts and decodes a URL from a base64-encoded query parameter.
|
15301
|
+
*
|
15302
|
+
* @param {string} url - The URL containing the base64-encoded query parameter.
|
15303
|
+
* @returns {string | null} - The decoded URL or null if not found or invalid.
|
15304
|
+
*/
|
15305
|
+
function getRedirectUrlFromPayload(url) {
|
15306
|
+
try {
|
15307
|
+
const base64String = new URL(url).searchParams.get('e');
|
15308
|
+
if (!base64String) {
|
15309
|
+
return null;
|
15258
15310
|
}
|
15259
|
-
|
15311
|
+
const data = JSON.parse(atob(base64String));
|
15312
|
+
return data.ur || null;
|
15260
15313
|
}
|
15261
|
-
|
15262
|
-
|
15314
|
+
catch (_a) {
|
15315
|
+
return null;
|
15263
15316
|
}
|
15264
|
-
|
15265
|
-
|
15266
|
-
|
15267
|
-
|
15268
|
-
|
15269
|
-
|
15270
|
-
|
15271
|
-
|
15272
|
-
|
15317
|
+
}
|
15318
|
+
/**
|
15319
|
+
* Fires an event using the navigator.sendBeacon method or a fallback method if sendBeacon is not available.
|
15320
|
+
* If the event is a click event and a redirect URL is found, it redirects the user to that URL.
|
15321
|
+
*
|
15322
|
+
* @param {IFireEventParams} params - The parameters for firing the event.
|
15323
|
+
* @param {RMN_SPOT_EVENT} params.event - The event type.
|
15324
|
+
* @param {string} params.eventUrl - The URL to which the event is sent.
|
15325
|
+
* @returns {Promise<void>} - A promise that resolves when the event is fired.
|
15326
|
+
*/
|
15327
|
+
async function fireEvent({ event, eventUrl }) {
|
15328
|
+
var _a;
|
15329
|
+
try {
|
15330
|
+
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));
|
15331
|
+
if (!didFireEvent) {
|
15332
|
+
return;
|
15333
|
+
}
|
15334
|
+
if (event === exports.RMN_SPOT_EVENT.CLICK) {
|
15335
|
+
const redirectUrl = getRedirectUrlFromPayload(eventUrl);
|
15336
|
+
if (redirectUrl) {
|
15337
|
+
window.location.href = redirectUrl;
|
15273
15338
|
}
|
15274
|
-
return result;
|
15275
|
-
};
|
15276
|
-
}
|
15277
|
-
cleanEventData(data) {
|
15278
|
-
const eventName = getEventTypeFromRawEvent(data.event);
|
15279
|
-
if (!eventName) {
|
15280
|
-
return null;
|
15281
15339
|
}
|
15282
|
-
const productIds = extractDeepIds(data.value);
|
15283
|
-
return {
|
15284
|
-
event: eventName,
|
15285
|
-
productIds,
|
15286
|
-
};
|
15287
15340
|
}
|
15288
|
-
|
15289
|
-
|
15290
|
-
window.dataLayer.push = this.originalPush;
|
15291
|
-
}
|
15292
|
-
this.listener = undefined;
|
15341
|
+
catch (_b) {
|
15342
|
+
// Handle errors silently
|
15293
15343
|
}
|
15294
15344
|
}
|
15345
|
+
function calculateScaleFactor(elementScale) {
|
15346
|
+
// Step 1: Apply square root for non-linear scaling
|
15347
|
+
// This creates a more gradual scaling effect, especially for larger changes
|
15348
|
+
// For example:
|
15349
|
+
// - elementScale of 0.25 (1/4 size) becomes 0.5
|
15350
|
+
// - elementScale of 1 (unchanged) remains 1
|
15351
|
+
// - elementScale of 4 (4x size) becomes 2
|
15352
|
+
const baseFactor = Math.sqrt(elementScale);
|
15353
|
+
// Step 2: Apply additional dampening to further soften the scaling effect
|
15354
|
+
// The dampening factor (0.5) can be adjusted:
|
15355
|
+
// - Lower values (closer to 0) make scaling more subtle
|
15356
|
+
// - Higher values (closer to 1) make scaling more pronounced
|
15357
|
+
const dampening = 0.35;
|
15358
|
+
// Calculate the scaleFactor:
|
15359
|
+
// 1. (baseFactor - 1) represents the change from the original size
|
15360
|
+
// 2. Multiply by dampening to reduce the effect
|
15361
|
+
// 3. Add 1 to center the scaling around the original size
|
15362
|
+
// For example, if baseFactor is 2:
|
15363
|
+
// scaleFactor = 1 + (2 - 1) * 0.5 = 1.5
|
15364
|
+
const scaleFactor = 1 + (baseFactor - 1) * dampening;
|
15365
|
+
// Step 3: Define the allowed range for the scale factor
|
15366
|
+
// This ensures that the font size never changes too drastically
|
15367
|
+
const minScale = 0.35; // Font will never be smaller than 50% of original
|
15368
|
+
const maxScale = 1.5; // Font will never be larger than 150% of original
|
15369
|
+
// Step 4: Clamp the scale factor to the defined range
|
15370
|
+
// Math.min ensures the value doesn't exceed maxScale
|
15371
|
+
// Math.max ensures the value isn't less than minScale
|
15372
|
+
return Math.max(minScale, Math.min(maxScale, scaleFactor));
|
15373
|
+
}
|
15295
15374
|
|
15296
15375
|
class IntersectionObserverService {
|
15297
15376
|
constructor(defaultOptions = {}) {
|
@@ -15341,7 +15420,7 @@ var ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX;
|
|
15341
15420
|
ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX["PRODUCT_IDS"] = 3] = "PRODUCT_IDS";
|
15342
15421
|
ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX[ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX["CREATED_AT"] = 4] = "CREATED_AT";
|
15343
15422
|
})(ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX || (ENUM_LOCAL_STORAGE_SPOT_ARRAY_INDEX = {}));
|
15344
|
-
class
|
15423
|
+
class LocalStorageService {
|
15345
15424
|
constructor() {
|
15346
15425
|
if (typeof window.localStorage === 'undefined') {
|
15347
15426
|
console.warn('Local storage is not supported in this environment');
|
@@ -15354,13 +15433,13 @@ class LocalStorage {
|
|
15354
15433
|
this.removeExpiredSpots();
|
15355
15434
|
}
|
15356
15435
|
static getInstance() {
|
15357
|
-
if (!
|
15358
|
-
|
15436
|
+
if (!LocalStorageService.instance) {
|
15437
|
+
LocalStorageService.instance = new LocalStorageService();
|
15359
15438
|
}
|
15360
|
-
return
|
15439
|
+
return LocalStorageService.instance;
|
15361
15440
|
}
|
15362
15441
|
syncLocalStorage() {
|
15363
|
-
const localStorageData = window.localStorage.getItem(
|
15442
|
+
const localStorageData = window.localStorage.getItem(LocalStorageService.localStorageKey);
|
15364
15443
|
// TODO: Encrypt the data before storing it in the local storage
|
15365
15444
|
if (localStorageData) {
|
15366
15445
|
try {
|
@@ -15410,17 +15489,17 @@ class LocalStorage {
|
|
15410
15489
|
for (const [key, value] of Object.entries(data)) {
|
15411
15490
|
dataArray[key] = this.objectToArray(value);
|
15412
15491
|
}
|
15413
|
-
window.localStorage.setItem(
|
15492
|
+
window.localStorage.setItem(LocalStorageService.localStorageKey, JSON.stringify(dataArray));
|
15414
15493
|
}
|
15415
15494
|
clearLocalStorage() {
|
15416
|
-
window.localStorage.removeItem(
|
15495
|
+
window.localStorage.removeItem(LocalStorageService.localStorageKey);
|
15417
15496
|
}
|
15418
15497
|
removeExpiredSpots() {
|
15419
15498
|
var _a;
|
15420
15499
|
const currentTime = Date.now();
|
15421
15500
|
(_a = this.spots) === null || _a === void 0 ? void 0 : _a.forEach((spot, spotId) => {
|
15422
15501
|
var _a, _b;
|
15423
|
-
if (currentTime - ((_a = spot.createdAt) !== null && _a !== void 0 ? _a : 0) >
|
15502
|
+
if (currentTime - ((_a = spot.createdAt) !== null && _a !== void 0 ? _a : 0) > LocalStorageService.spotExpirationTime) {
|
15424
15503
|
(_b = this.spots) === null || _b === void 0 ? void 0 : _b.delete(spotId);
|
15425
15504
|
}
|
15426
15505
|
});
|
@@ -15445,8 +15524,89 @@ class LocalStorage {
|
|
15445
15524
|
};
|
15446
15525
|
}
|
15447
15526
|
}
|
15448
|
-
|
15449
|
-
|
15527
|
+
LocalStorageService.localStorageKey = 'lc_rmn';
|
15528
|
+
LocalStorageService.spotExpirationTime = 1000 * 60 * 60 * 24 * 7; // 7 days
|
15529
|
+
|
15530
|
+
/**
|
15531
|
+
* PubsubService class
|
15532
|
+
* Manages event subscriptions and publications
|
15533
|
+
* @template IRmnEventMap A record type defining the structure of events and their data
|
15534
|
+
*/
|
15535
|
+
class PubsubService {
|
15536
|
+
constructor() {
|
15537
|
+
/**
|
15538
|
+
* Object to store subscribers for each event type
|
15539
|
+
*/
|
15540
|
+
this.subscribers = {};
|
15541
|
+
}
|
15542
|
+
static getInstance() {
|
15543
|
+
if (!PubsubService.instance) {
|
15544
|
+
PubsubService.instance = new PubsubService();
|
15545
|
+
}
|
15546
|
+
return PubsubService.instance;
|
15547
|
+
}
|
15548
|
+
/**
|
15549
|
+
* Subscribe to an event
|
15550
|
+
* @param eventType - The type of event to subscribe to
|
15551
|
+
* @param callback - The function to be called when the event is published
|
15552
|
+
* @returns A function to unsubscribe from the event
|
15553
|
+
*
|
15554
|
+
* @Example:
|
15555
|
+
* const unsubscribe = pubSub.subscribe('userLogin', (data) => {
|
15556
|
+
* console.log(`User ${data.username} logged in`);
|
15557
|
+
* });
|
15558
|
+
*/
|
15559
|
+
subscribe(eventType, callback) {
|
15560
|
+
if (!this.subscribers[eventType]) {
|
15561
|
+
this.subscribers[eventType] = [];
|
15562
|
+
}
|
15563
|
+
this.subscribers[eventType].push(callback);
|
15564
|
+
// Return an unsubscribe function
|
15565
|
+
return () => {
|
15566
|
+
this.subscribers[eventType] = this.subscribers[eventType].filter((cb) => cb !== callback);
|
15567
|
+
};
|
15568
|
+
}
|
15569
|
+
/**
|
15570
|
+
* Publish an event
|
15571
|
+
* @param eventType - The type of event to publish
|
15572
|
+
* @param data - The data to be passed to the event subscribers
|
15573
|
+
*
|
15574
|
+
* @Example:
|
15575
|
+
* pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
|
15576
|
+
*/
|
15577
|
+
publish(eventType, data) {
|
15578
|
+
if (!this.subscribers[eventType]) {
|
15579
|
+
return;
|
15580
|
+
}
|
15581
|
+
this.subscribers[eventType].forEach((callback) => callback(data));
|
15582
|
+
}
|
15583
|
+
}
|
15584
|
+
/**
|
15585
|
+
* Usage Example:
|
15586
|
+
*
|
15587
|
+
* interface IRmnEventMap {
|
15588
|
+
* userLogin: { username: string; timestamp: number };
|
15589
|
+
* pageView: { url: string; timestamp: number };
|
15590
|
+
* }
|
15591
|
+
*
|
15592
|
+
* const pubSub = new PubsubService<IRmnEventMap>();
|
15593
|
+
*
|
15594
|
+
* // Subscribe to events
|
15595
|
+
* const unsubscribeLogin = pubSub.subscribe('userLogin', (data) => {
|
15596
|
+
* console.log(`User ${data.username} logged in at ${new Date(data.timestamp)}`);
|
15597
|
+
* });
|
15598
|
+
*
|
15599
|
+
* pubSub.subscribe('pageView', (data) => {
|
15600
|
+
* console.log(`Page ${data.url} viewed at ${new Date(data.timestamp)}`);
|
15601
|
+
* });
|
15602
|
+
*
|
15603
|
+
* // Publish events
|
15604
|
+
* pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
|
15605
|
+
* pubSub.publish('pageView', { url: '/home', timestamp: Date.now() });
|
15606
|
+
*
|
15607
|
+
* // Unsubscribe from an event
|
15608
|
+
* unsubscribeLogin();
|
15609
|
+
*/
|
15450
15610
|
|
15451
15611
|
class ResizeObserverService {
|
15452
15612
|
constructor({ element, maxSize, minScale }) {
|
@@ -15516,36 +15676,6 @@ class ResizeObserverService {
|
|
15516
15676
|
}
|
15517
15677
|
}
|
15518
15678
|
|
15519
|
-
function calculateScaleFactor(elementScale) {
|
15520
|
-
// Step 1: Apply square root for non-linear scaling
|
15521
|
-
// This creates a more gradual scaling effect, especially for larger changes
|
15522
|
-
// For example:
|
15523
|
-
// - elementScale of 0.25 (1/4 size) becomes 0.5
|
15524
|
-
// - elementScale of 1 (unchanged) remains 1
|
15525
|
-
// - elementScale of 4 (4x size) becomes 2
|
15526
|
-
const baseFactor = Math.sqrt(elementScale);
|
15527
|
-
// Step 2: Apply additional dampening to further soften the scaling effect
|
15528
|
-
// The dampening factor (0.5) can be adjusted:
|
15529
|
-
// - Lower values (closer to 0) make scaling more subtle
|
15530
|
-
// - Higher values (closer to 1) make scaling more pronounced
|
15531
|
-
const dampening = 0.35;
|
15532
|
-
// Calculate the scaleFactor:
|
15533
|
-
// 1. (baseFactor - 1) represents the change from the original size
|
15534
|
-
// 2. Multiply by dampening to reduce the effect
|
15535
|
-
// 3. Add 1 to center the scaling around the original size
|
15536
|
-
// For example, if baseFactor is 2:
|
15537
|
-
// scaleFactor = 1 + (2 - 1) * 0.5 = 1.5
|
15538
|
-
const scaleFactor = 1 + (baseFactor - 1) * dampening;
|
15539
|
-
// Step 3: Define the allowed range for the scale factor
|
15540
|
-
// This ensures that the font size never changes too drastically
|
15541
|
-
const minScale = 0.35; // Font will never be smaller than 50% of original
|
15542
|
-
const maxScale = 1.5; // Font will never be larger than 150% of original
|
15543
|
-
// Step 4: Clamp the scale factor to the defined range
|
15544
|
-
// Math.min ensures the value doesn't exceed maxScale
|
15545
|
-
// Math.max ensures the value isn't less than minScale
|
15546
|
-
return Math.max(minScale, Math.min(maxScale, scaleFactor));
|
15547
|
-
}
|
15548
|
-
|
15549
15679
|
const CAROUSEL_COMPONENT_STYLE = ({ width, height, fluid }) => `
|
15550
15680
|
:host {
|
15551
15681
|
position: relative;
|
@@ -18131,97 +18261,65 @@ const SPOT_TEMPLATE_HTML_ELEMENT = (spot, config) => {
|
|
18131
18261
|
return spotHtmlStringToElement(spotHtmlString);
|
18132
18262
|
};
|
18133
18263
|
|
18134
|
-
|
18135
|
-
|
18136
|
-
|
18137
|
-
|
18138
|
-
|
18139
|
-
|
18264
|
+
// For the moment, we will only focus on sites that use Google Analytics,
|
18265
|
+
// but we will add support for other analytics tools in the future.
|
18266
|
+
var AnalyticsTool;
|
18267
|
+
(function (AnalyticsTool) {
|
18268
|
+
AnalyticsTool["GoogleAnalytics"] = "google-analytics";
|
18269
|
+
AnalyticsTool["Other"] = "Other";
|
18270
|
+
})(AnalyticsTool || (AnalyticsTool = {}));
|
18271
|
+
|
18272
|
+
class DataLayerMonitor {
|
18140
18273
|
constructor() {
|
18141
|
-
|
18142
|
-
|
18143
|
-
|
18144
|
-
this.
|
18274
|
+
if (!window.dataLayer) {
|
18275
|
+
return;
|
18276
|
+
}
|
18277
|
+
this.originalPush = window.dataLayer.push;
|
18145
18278
|
}
|
18146
18279
|
static getInstance() {
|
18147
|
-
if (!
|
18148
|
-
|
18280
|
+
if (!DataLayerMonitor.instance) {
|
18281
|
+
DataLayerMonitor.instance = new DataLayerMonitor();
|
18149
18282
|
}
|
18150
|
-
return
|
18283
|
+
return DataLayerMonitor.instance;
|
18151
18284
|
}
|
18152
|
-
|
18153
|
-
|
18154
|
-
|
18155
|
-
|
18156
|
-
|
18157
|
-
|
18158
|
-
|
18159
|
-
|
18160
|
-
|
18161
|
-
|
18162
|
-
|
18163
|
-
|
18164
|
-
|
18165
|
-
|
18285
|
+
setListener(listener) {
|
18286
|
+
this.listener = listener;
|
18287
|
+
}
|
18288
|
+
start() {
|
18289
|
+
window.dataLayer.push = (...args) => {
|
18290
|
+
const result = this.originalPush.apply(window.dataLayer, args);
|
18291
|
+
const pushedEvent = args[0];
|
18292
|
+
if (this.listener) {
|
18293
|
+
const normalizedData = this.cleanEventData(pushedEvent);
|
18294
|
+
if (normalizedData) {
|
18295
|
+
this.listener(normalizedData);
|
18296
|
+
}
|
18297
|
+
}
|
18298
|
+
return result;
|
18299
|
+
};
|
18300
|
+
}
|
18301
|
+
cleanEventData(data) {
|
18302
|
+
const eventName = getEventTypeFromRawEvent(data.event);
|
18303
|
+
if (!eventName) {
|
18304
|
+
return null;
|
18166
18305
|
}
|
18167
|
-
|
18168
|
-
|
18169
|
-
|
18170
|
-
|
18306
|
+
const productIds = extractDeepIds(data.value);
|
18307
|
+
return {
|
18308
|
+
event: eventName,
|
18309
|
+
productIds,
|
18171
18310
|
};
|
18172
18311
|
}
|
18173
|
-
|
18174
|
-
|
18175
|
-
|
18176
|
-
* @param data - The data to be passed to the event subscribers
|
18177
|
-
*
|
18178
|
-
* @Example:
|
18179
|
-
* pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
|
18180
|
-
*/
|
18181
|
-
publish(eventType, data) {
|
18182
|
-
if (!this.subscribers[eventType]) {
|
18183
|
-
return;
|
18312
|
+
stop() {
|
18313
|
+
if (this.originalPush) {
|
18314
|
+
window.dataLayer.push = this.originalPush;
|
18184
18315
|
}
|
18185
|
-
this.
|
18316
|
+
this.listener = undefined;
|
18186
18317
|
}
|
18187
18318
|
}
|
18188
|
-
/**
|
18189
|
-
* Usage Example:
|
18190
|
-
*
|
18191
|
-
* interface IRmnEventMap {
|
18192
|
-
* userLogin: { username: string; timestamp: number };
|
18193
|
-
* pageView: { url: string; timestamp: number };
|
18194
|
-
* }
|
18195
|
-
*
|
18196
|
-
* const pubSub = new PubSub<IRmnEventMap>();
|
18197
|
-
*
|
18198
|
-
* // Subscribe to events
|
18199
|
-
* const unsubscribeLogin = pubSub.subscribe('userLogin', (data) => {
|
18200
|
-
* console.log(`User ${data.username} logged in at ${new Date(data.timestamp)}`);
|
18201
|
-
* });
|
18202
|
-
*
|
18203
|
-
* pubSub.subscribe('pageView', (data) => {
|
18204
|
-
* console.log(`Page ${data.url} viewed at ${new Date(data.timestamp)}`);
|
18205
|
-
* });
|
18206
|
-
*
|
18207
|
-
* // Publish events
|
18208
|
-
* pubSub.publish('userLogin', { username: 'john_doe', timestamp: Date.now() });
|
18209
|
-
* pubSub.publish('pageView', { url: '/home', timestamp: Date.now() });
|
18210
|
-
*
|
18211
|
-
* // Unsubscribe from an event
|
18212
|
-
* unsubscribeLogin();
|
18213
|
-
*/
|
18214
18319
|
|
18215
18320
|
// @TODO: Add support for user to push events to our own data layer, if they don't use any analytics tool.
|
18216
18321
|
// window.rmnDataLayer = window.rmnDataLayer || [];
|
18217
|
-
|
18218
|
-
// but we will add support for other analytics tools in the future.
|
18219
|
-
var AnalyticsTool;
|
18220
|
-
(function (AnalyticsTool) {
|
18221
|
-
AnalyticsTool["GoogleAnalytics"] = "google-analytics";
|
18222
|
-
AnalyticsTool["Other"] = "Other";
|
18223
|
-
})(AnalyticsTool || (AnalyticsTool = {}));
|
18224
|
-
class UserMonitor {
|
18322
|
+
class MonitorService {
|
18225
18323
|
constructor() {
|
18226
18324
|
const analyticsTool = this.detectAnalyticsTool();
|
18227
18325
|
switch (analyticsTool) {
|
@@ -18236,29 +18334,97 @@ class UserMonitor {
|
|
18236
18334
|
if (analyticsTool === AnalyticsTool.Other) {
|
18237
18335
|
return;
|
18238
18336
|
}
|
18239
|
-
this.
|
18337
|
+
this.pubSubService = PubsubService.getInstance();
|
18338
|
+
this.localStorageService = LocalStorageService.getInstance();
|
18240
18339
|
}
|
18241
18340
|
static getInstance() {
|
18242
|
-
if (!
|
18243
|
-
|
18341
|
+
if (!MonitorService.instance) {
|
18342
|
+
MonitorService.instance = new MonitorService();
|
18244
18343
|
}
|
18245
|
-
return
|
18344
|
+
return MonitorService.instance;
|
18246
18345
|
}
|
18247
18346
|
start() {
|
18248
18347
|
if (!this.implementedMonitor)
|
18249
18348
|
return;
|
18250
|
-
this.implementedMonitor.setListener((eventData) => {
|
18349
|
+
this.implementedMonitor.setListener(async (eventData) => {
|
18251
18350
|
var _a;
|
18252
|
-
this.matchAndFireEvent(eventData, (_a = this.
|
18351
|
+
await this.matchAndFireEvent(eventData, (_a = this.localStorageService) === null || _a === void 0 ? void 0 : _a.getSpots());
|
18253
18352
|
});
|
18254
18353
|
this.implementedMonitor.start();
|
18255
18354
|
}
|
18256
|
-
matchAndFireEvent(
|
18257
|
-
|
18258
|
-
|
18259
|
-
|
18260
|
-
|
18261
|
-
|
18355
|
+
async matchAndFireEvent(eventData, spots) {
|
18356
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
18357
|
+
if (!spots)
|
18358
|
+
return;
|
18359
|
+
const eventProductIds = new Set(eventData.productIds);
|
18360
|
+
for (const spot of Object.values(spots)) {
|
18361
|
+
if (!spot.productIds.length)
|
18362
|
+
continue;
|
18363
|
+
const hasCommonProductIds = spot.productIds.find((productId) => eventProductIds.has(productId));
|
18364
|
+
if (hasCommonProductIds) {
|
18365
|
+
switch (eventData.event) {
|
18366
|
+
case exports.RMN_SPOT_EVENT.ADD_TO_CART:
|
18367
|
+
await this.fireAndPublishSpotEvent({
|
18368
|
+
spotEvent: exports.RMN_SPOT_EVENT.ADD_TO_CART,
|
18369
|
+
eventUrl: (_b = (_a = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.ADD_TO_CART)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
|
18370
|
+
placementId: '',
|
18371
|
+
spotId: spot.spotId,
|
18372
|
+
spotElement: undefined,
|
18373
|
+
});
|
18374
|
+
break;
|
18375
|
+
case exports.RMN_SPOT_EVENT.REMOVE_FROM_CART:
|
18376
|
+
await this.fireAndPublishSpotEvent({
|
18377
|
+
spotEvent: exports.RMN_SPOT_EVENT.REMOVE_FROM_CART,
|
18378
|
+
eventUrl: (_d = (_c = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.REMOVE_FROM_CART)) === null || _c === void 0 ? void 0 : _c.url) !== null && _d !== void 0 ? _d : '',
|
18379
|
+
placementId: '',
|
18380
|
+
spotId: spot.spotId,
|
18381
|
+
spotElement: undefined,
|
18382
|
+
});
|
18383
|
+
break;
|
18384
|
+
case exports.RMN_SPOT_EVENT.PURCHASE:
|
18385
|
+
await this.fireAndPublishSpotEvent({
|
18386
|
+
spotEvent: exports.RMN_SPOT_EVENT.PURCHASE,
|
18387
|
+
eventUrl: (_f = (_e = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.PURCHASE)) === null || _e === void 0 ? void 0 : _e.url) !== null && _f !== void 0 ? _f : '',
|
18388
|
+
placementId: '',
|
18389
|
+
spotId: spot.spotId,
|
18390
|
+
spotElement: undefined,
|
18391
|
+
});
|
18392
|
+
break;
|
18393
|
+
case exports.RMN_SPOT_EVENT.ADD_TO_WISHLIST:
|
18394
|
+
await this.fireAndPublishSpotEvent({
|
18395
|
+
spotEvent: exports.RMN_SPOT_EVENT.ADD_TO_WISHLIST,
|
18396
|
+
eventUrl: (_h = (_g = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.ADD_TO_WISHLIST)) === null || _g === void 0 ? void 0 : _g.url) !== null && _h !== void 0 ? _h : '',
|
18397
|
+
placementId: '',
|
18398
|
+
spotId: spot.spotId,
|
18399
|
+
spotElement: undefined,
|
18400
|
+
});
|
18401
|
+
break;
|
18402
|
+
case exports.RMN_SPOT_EVENT.BUY_NOW:
|
18403
|
+
await this.fireAndPublishSpotEvent({
|
18404
|
+
spotEvent: exports.RMN_SPOT_EVENT.BUY_NOW,
|
18405
|
+
eventUrl: (_k = (_j = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.BUY_NOW)) === null || _j === void 0 ? void 0 : _j.url) !== null && _k !== void 0 ? _k : '',
|
18406
|
+
placementId: '',
|
18407
|
+
spotId: spot.spotId,
|
18408
|
+
spotElement: undefined,
|
18409
|
+
});
|
18410
|
+
break;
|
18411
|
+
}
|
18412
|
+
}
|
18413
|
+
}
|
18414
|
+
}
|
18415
|
+
async fireAndPublishSpotEvent({ spotEvent, eventUrl, placementId, spotId, spotElement, }) {
|
18416
|
+
await fireEvent({
|
18417
|
+
event: spotEvent,
|
18418
|
+
eventUrl,
|
18419
|
+
});
|
18420
|
+
if (!this.pubSubService)
|
18421
|
+
return;
|
18422
|
+
this.pubSubService.publish(exports.RMN_EVENT.SPOT_EVENT, {
|
18423
|
+
eventType: spotEvent,
|
18424
|
+
placementId,
|
18425
|
+
spotId,
|
18426
|
+
spotElement,
|
18427
|
+
});
|
18262
18428
|
}
|
18263
18429
|
detectAnalyticsTool() {
|
18264
18430
|
let analyticsTool = AnalyticsTool.Other;
|
@@ -18286,13 +18452,13 @@ class UserMonitor {
|
|
18286
18452
|
|
18287
18453
|
class EventService {
|
18288
18454
|
constructor() {
|
18289
|
-
this.
|
18290
|
-
this.
|
18455
|
+
this.pubSubService = PubsubService.getInstance();
|
18456
|
+
this.localStorageService = LocalStorageService.getInstance();
|
18291
18457
|
this.activeSpots = new Map();
|
18292
18458
|
this.spotStates = new Map();
|
18293
18459
|
this.intersectionObserver = new IntersectionObserverService();
|
18294
18460
|
// Start the user monitor, which will track and check user interactions
|
18295
|
-
|
18461
|
+
MonitorService.getInstance().start();
|
18296
18462
|
}
|
18297
18463
|
static getInstance() {
|
18298
18464
|
if (!EventService.instance) {
|
@@ -18301,10 +18467,10 @@ class EventService {
|
|
18301
18467
|
return EventService.instance;
|
18302
18468
|
}
|
18303
18469
|
subscribe(eventType, callback) {
|
18304
|
-
return this.
|
18470
|
+
return this.pubSubService.subscribe(eventType, callback);
|
18305
18471
|
}
|
18306
18472
|
publish(eventType, data) {
|
18307
|
-
this.
|
18473
|
+
this.pubSubService.publish(eventType, data);
|
18308
18474
|
}
|
18309
18475
|
registerSpot(params) {
|
18310
18476
|
const { placementId, spot, spotElement } = params;
|
@@ -18388,7 +18554,7 @@ class EventService {
|
|
18388
18554
|
const merged = this.deepMerge(currentState, updates);
|
18389
18555
|
this.spotStates.set(placementId, merged);
|
18390
18556
|
if (publish) {
|
18391
|
-
this.
|
18557
|
+
this.pubSubService.publish(exports.RMN_EVENT.LIFECYCLE_STATE, this.spotStates.get(placementId));
|
18392
18558
|
}
|
18393
18559
|
}
|
18394
18560
|
deepMerge(current, updates) {
|
@@ -18419,19 +18585,18 @@ class EventService {
|
|
18419
18585
|
}
|
18420
18586
|
async handleClick({ placementId, spot, spotElement, }) {
|
18421
18587
|
var _a, _b, _c;
|
18422
|
-
|
18423
|
-
|
18588
|
+
this.pubSubService.publish(exports.RMN_EVENT.SPOT_EVENT, {
|
18589
|
+
eventType: exports.RMN_SPOT_EVENT.CLICK,
|
18424
18590
|
placementId,
|
18425
18591
|
spotId: spot.id,
|
18426
18592
|
spotElement,
|
18427
18593
|
});
|
18428
|
-
|
18429
|
-
await this.fireEvent({
|
18594
|
+
await fireEvent({
|
18430
18595
|
event: exports.RMN_SPOT_EVENT.CLICK,
|
18431
18596
|
eventUrl: (_b = (_a = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.CLICK)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
|
18432
18597
|
});
|
18433
18598
|
// Save spot to local storage for event tracking
|
18434
|
-
this.
|
18599
|
+
this.localStorageService.setSpot(spot.id, {
|
18435
18600
|
spotId: spot.id,
|
18436
18601
|
spotType: spot.spot,
|
18437
18602
|
events: spot.events,
|
@@ -18451,90 +18616,20 @@ class EventService {
|
|
18451
18616
|
this.intersectionObserver.observe(spotElement, spotIsVisibleCallback);
|
18452
18617
|
}
|
18453
18618
|
fireImpressionEvent(placementId, spot, spotElement) {
|
18454
|
-
this.
|
18619
|
+
this.pubSubService.publish(exports.RMN_EVENT.SPOT_EVENT, {
|
18620
|
+
eventType: exports.RMN_SPOT_EVENT.IMPRESSION,
|
18455
18621
|
placementId,
|
18456
18622
|
spotId: spot.id,
|
18457
18623
|
spotElement,
|
18458
18624
|
});
|
18459
18625
|
(async () => {
|
18460
18626
|
var _a, _b;
|
18461
|
-
await
|
18627
|
+
await fireEvent({
|
18462
18628
|
event: exports.RMN_SPOT_EVENT.IMPRESSION,
|
18463
18629
|
eventUrl: (_b = (_a = spot.events.find((event) => event.event === exports.RMN_SPOT_EVENT.IMPRESSION)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
|
18464
18630
|
});
|
18465
18631
|
})();
|
18466
18632
|
}
|
18467
|
-
/**
|
18468
|
-
* Fires an event using the navigator.sendBeacon method or a fallback method if sendBeacon is not available.
|
18469
|
-
* If the event is a click event and a redirect URL is found, it redirects the user to that URL.
|
18470
|
-
*
|
18471
|
-
* @param {IFireEventParams} params - The parameters for firing the event.
|
18472
|
-
* @param {RMN_SPOT_EVENT} params.event - The event type.
|
18473
|
-
* @param {string} params.eventUrl - The URL to which the event is sent.
|
18474
|
-
* @returns {Promise<void>} - A promise that resolves when the event is fired.
|
18475
|
-
*/
|
18476
|
-
async fireEvent({ event, eventUrl }) {
|
18477
|
-
var _a;
|
18478
|
-
try {
|
18479
|
-
const didFireEvent = ((_a = navigator === null || navigator === void 0 ? void 0 : navigator.sendBeacon) === null || _a === void 0 ? void 0 : _a.call(navigator, eventUrl)) || (await this.fallbackEventFire(eventUrl));
|
18480
|
-
if (!didFireEvent) {
|
18481
|
-
return;
|
18482
|
-
}
|
18483
|
-
if (event === exports.RMN_SPOT_EVENT.CLICK) {
|
18484
|
-
const redirectUrl = this.getRedirectUrlFromPayload(eventUrl);
|
18485
|
-
if (redirectUrl) {
|
18486
|
-
window.location.href = redirectUrl;
|
18487
|
-
}
|
18488
|
-
}
|
18489
|
-
}
|
18490
|
-
catch (_b) {
|
18491
|
-
// Handle errors silently
|
18492
|
-
}
|
18493
|
-
}
|
18494
|
-
// Fallback method using fetch if sendBeacon isn't available
|
18495
|
-
async fallbackEventFire(url) {
|
18496
|
-
try {
|
18497
|
-
const racePromise = Promise.race([
|
18498
|
-
// Promise #1: The fetch request
|
18499
|
-
fetch(url, {
|
18500
|
-
method: 'POST',
|
18501
|
-
keepalive: true,
|
18502
|
-
}),
|
18503
|
-
// Promise #2: The timeout
|
18504
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error('Request timeout')), 2000)),
|
18505
|
-
]);
|
18506
|
-
/**
|
18507
|
-
* Prevent requests from hanging indefinitely
|
18508
|
-
* Improve user experience by failing fast
|
18509
|
-
* Handle slow network conditions gracefully
|
18510
|
-
* Ensure resources are freed up in a timely manner
|
18511
|
-
*/
|
18512
|
-
const response = await racePromise;
|
18513
|
-
return response.ok;
|
18514
|
-
}
|
18515
|
-
catch (_a) {
|
18516
|
-
return false;
|
18517
|
-
}
|
18518
|
-
}
|
18519
|
-
/**
|
18520
|
-
* Extracts and decodes a URL from a base64-encoded query parameter.
|
18521
|
-
*
|
18522
|
-
* @param {string} url - The URL containing the base64-encoded query parameter.
|
18523
|
-
* @returns {string | null} - The decoded URL or null if not found or invalid.
|
18524
|
-
*/
|
18525
|
-
getRedirectUrlFromPayload(url) {
|
18526
|
-
try {
|
18527
|
-
const base64String = new URL(url).searchParams.get('e');
|
18528
|
-
if (!base64String) {
|
18529
|
-
return null;
|
18530
|
-
}
|
18531
|
-
const data = JSON.parse(atob(base64String));
|
18532
|
-
return data.ur || null;
|
18533
|
-
}
|
18534
|
-
catch (_a) {
|
18535
|
-
return null;
|
18536
|
-
}
|
18537
|
-
}
|
18538
18633
|
}
|
18539
18634
|
|
18540
18635
|
const SELECTION_API_PATH = '/spots/selection';
|