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