@liquidcommercedev/rmn-sdk 1.5.0-beta.8 → 1.5.0-beta.9
Sign up to get free protection for your applications and to get access to all the features.
- 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';
|