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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -78,574 +78,6 @@ var RMN_ENV;
78
78
  RMN_ENV["PRODUCTION"] = "production";
79
79
  })(RMN_ENV || (RMN_ENV = {}));
80
80
 
81
- const SPOT_EVENTS_EXAMPLE = [
82
- {
83
- event: RMN_SPOT_EVENT.CLICK,
84
- url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwidXIiOm51bGx9&s=hWz37kbxi_u95EVNn2aoQhc5Aas',
85
- },
86
- {
87
- event: RMN_SPOT_EVENT.IMPRESSION,
88
- url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiYmEiOjEsImZxIjowfQ&s=djoysjCimurf-5T11AlNAwwLSS8',
89
- },
90
- {
91
- event: RMN_SPOT_EVENT.PURCHASE,
92
- url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjU5fQ&s=AAPAw-3SfZ0JMzjEGFSwt9L-2S4',
93
- },
94
- {
95
- event: RMN_SPOT_EVENT.ADD_TO_CART,
96
- url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjYwfQ&s=uzQFcjgL7m9XqUG8FvTPVN5YkZY',
97
- },
98
- {
99
- event: RMN_SPOT_EVENT.ADD_TO_WISHLIST,
100
- url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjYzfQ&s=m3ISU_iIy-OFtXrTKpI6cJAEC0k',
101
- },
102
- {
103
- event: RMN_SPOT_EVENT.BUY_NOW,
104
- url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjY5fQ&s=l6MOscQC-q-FkC2Ksd7w6jjySCQ',
105
- },
106
- ];
107
- const RB_SPOTS_SELECTION_EXAMPLE = {
108
- rbHomepageHero: [
109
- {
110
- id: 'abc123',
111
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
112
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
113
- width: 1140,
114
- height: 640,
115
- header: 'Premium Wine Collection',
116
- description: 'Discover our exclusive selection of vintage wines',
117
- ctaText: 'Shop Wines',
118
- textColor: '#ffffff',
119
- ctaTextColor: '#ffffff',
120
- primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Collection',
121
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Wine',
122
- events: SPOT_EVENTS_EXAMPLE,
123
- productIds: [1, 2, 3],
124
- },
125
- {
126
- id: 'jkl012',
127
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
128
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
129
- width: 1140,
130
- height: 640,
131
- header: 'Whiskey Collection',
132
- description: 'Premium whiskeys from around the world',
133
- ctaText: 'Shop Whiskeys',
134
- textColor: '#ffffff',
135
- backgroundColor: '#2c1810',
136
- ctaTextColor: '#2c1810',
137
- primaryImage: 'https://placehold.co/1140x640/png?text=Whiskey',
138
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Whiskey',
139
- events: SPOT_EVENTS_EXAMPLE,
140
- productIds: [10, 11],
141
- },
142
- {
143
- id: 'stu901',
144
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
145
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
146
- width: 1140,
147
- height: 640,
148
- header: 'Summer Cocktails',
149
- description: 'Essential spirits for summer mixing',
150
- ctaText: 'Mix It Up',
151
- textColor: '#ffffff',
152
- backgroundColor: '#4d3a0a',
153
- ctaTextColor: '#4d3a0a',
154
- primaryImage: 'https://placehold.co/1140x640/png?text=Cocktails',
155
- secondaryImage: 'https://placehold.co/1140x640/png?text=Mixers',
156
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Cocktails',
157
- mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Mixers',
158
- events: SPOT_EVENTS_EXAMPLE,
159
- productIds: [16, 17, 18],
160
- },
161
- {
162
- id: 'def456',
163
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
164
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
165
- width: 1140,
166
- height: 640,
167
- header: 'Craft Beer Festival',
168
- description: 'Local breweries and exclusive releases',
169
- ctaText: 'Explore Beers',
170
- textColor: '#ffffff',
171
- ctaTextColor: '#ffffff',
172
- primaryImage: 'https://placehold.co/1140x640/png?text=Beer+Festival',
173
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Beer',
174
- events: SPOT_EVENTS_EXAMPLE,
175
- productIds: [4, 5, 6],
176
- },
177
- {
178
- id: 'mno345',
179
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
180
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
181
- width: 1140,
182
- height: 640,
183
- header: 'Champagne Selection',
184
- description: 'Finest champagnes for every occasion',
185
- ctaText: 'View Champagnes',
186
- textColor: '#ffffff',
187
- backgroundColor: '#1a1a1a',
188
- ctaTextColor: '#1a1a1a',
189
- primaryImage: 'https://placehold.co/1140x640/png?text=Champagne',
190
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Champagne',
191
- events: SPOT_EVENTS_EXAMPLE,
192
- productIds: [12, 13],
193
- },
194
- {
195
- id: 'vwx234',
196
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
197
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
198
- width: 1140,
199
- height: 640,
200
- header: 'Wine Regions',
201
- description: 'Explore wines from top regions',
202
- ctaText: 'Tour Regions',
203
- textColor: '#ffffff',
204
- backgroundColor: '#4d0a0a',
205
- ctaTextColor: '#4d0a0a',
206
- primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Regions',
207
- secondaryImage: 'https://placehold.co/1140x640/png?text=Vineyards',
208
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Regions',
209
- mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Vineyards',
210
- events: SPOT_EVENTS_EXAMPLE,
211
- productIds: [19, 20, 21],
212
- },
213
- {
214
- id: 'ghi789',
215
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
216
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
217
- width: 1140,
218
- height: 640,
219
- header: 'Rare Spirits',
220
- description: 'Limited edition spirits collection',
221
- ctaText: 'View Collection',
222
- textColor: '#ffffff',
223
- ctaTextColor: '#ffffff',
224
- primaryImage: 'https://placehold.co/1140x640/png?text=Rare+Spirits',
225
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Spirits',
226
- events: SPOT_EVENTS_EXAMPLE,
227
- productIds: [7, 8, 9],
228
- },
229
- {
230
- id: 'pqr678',
231
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
232
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
233
- width: 1140,
234
- height: 640,
235
- header: 'Gin Collection',
236
- description: 'Artisanal gins and botanicals',
237
- ctaText: 'Explore Gins',
238
- textColor: '#ffffff',
239
- backgroundColor: '#0a4d4d',
240
- ctaTextColor: '#0a4d4d',
241
- primaryImage: 'https://placehold.co/1140x640/png?text=Gin',
242
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Gin',
243
- events: SPOT_EVENTS_EXAMPLE,
244
- productIds: [14, 15],
245
- },
246
- {
247
- id: 'yz5678',
248
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
249
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
250
- width: 1140,
251
- height: 640,
252
- header: 'Tequila Collection',
253
- description: 'Premium tequilas and mezcals',
254
- ctaText: 'Shop Tequila',
255
- textColor: '#ffffff',
256
- backgroundColor: '#0a4d2b',
257
- ctaTextColor: '#0a4d2b',
258
- primaryImage: 'https://placehold.co/1140x640/png?text=Tequila',
259
- secondaryImage: 'https://placehold.co/1140x640/png?text=Mezcal',
260
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Tequila',
261
- mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Mezcal',
262
- events: SPOT_EVENTS_EXAMPLE,
263
- productIds: [22, 23, 24],
264
- },
265
- ],
266
- rbHomepageHeroFullImage: [
267
- {
268
- id: 'hjk567',
269
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
270
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
271
- width: 1140,
272
- height: 640,
273
- header: 'Holiday Gift Guide',
274
- description: 'Perfect spirits for every occasion',
275
- ctaText: 'Shop Gifts',
276
- textColor: '#ffffff',
277
- ctaTextColor: '#ffffff',
278
- primaryImage: 'https://placehold.co/1140x640/png?text=Gift+Guide',
279
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Gifts',
280
- events: SPOT_EVENTS_EXAMPLE,
281
- productIds: [25, 26],
282
- },
283
- {
284
- id: 'qwe890',
285
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
286
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
287
- width: 1140,
288
- height: 640,
289
- header: 'Summer Wine Festival',
290
- description: 'Refreshing wines for summer',
291
- ctaText: 'Shop Festival',
292
- textColor: '#ffffff',
293
- ctaTextColor: '#ffffff',
294
- primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Festival',
295
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Festival',
296
- events: SPOT_EVENTS_EXAMPLE,
297
- productIds: [27, 28],
298
- },
299
- ],
300
- rbHomepageHeroTwoTile: [
301
- {
302
- id: 'iop987',
303
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
304
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
305
- width: 1140,
306
- height: 640,
307
- header: 'Bourbon Selection',
308
- description: "Kentucky's finest bourbons",
309
- ctaText: 'Shop Bourbon',
310
- textColor: '#ffffff',
311
- backgroundColor: '#2c1810',
312
- ctaTextColor: '#2c1810',
313
- primaryImage: 'https://placehold.co/1140x640/png?text=Bourbon',
314
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Bourbon',
315
- events: SPOT_EVENTS_EXAMPLE,
316
- productIds: [29, 30],
317
- },
318
- {
319
- id: 'lkj012',
320
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
321
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
322
- width: 1140,
323
- height: 640,
324
- header: 'Vodka Collection',
325
- description: 'Premium vodkas from around the world',
326
- ctaText: 'Shop Vodka',
327
- textColor: '#ffffff',
328
- backgroundColor: '#1a1a1a',
329
- ctaTextColor: '#1a1a1a',
330
- primaryImage: 'https://placehold.co/1140x640/png?text=Vodka',
331
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Vodka',
332
- events: SPOT_EVENTS_EXAMPLE,
333
- productIds: [31, 32],
334
- },
335
- ],
336
- rbHomepageHeroThreeTile: [
337
- {
338
- id: 'bnm345',
339
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
340
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
341
- width: 1140,
342
- height: 640,
343
- header: 'Rum Collection',
344
- description: 'Caribbean and craft rums',
345
- ctaText: 'Shop Rum',
346
- textColor: '#ffffff',
347
- backgroundColor: '#4d3a0a',
348
- ctaTextColor: '#4d3a0a',
349
- primaryImage: 'https://placehold.co/1140x640/png?text=Rum',
350
- secondaryImage: 'https://placehold.co/1140x640/png?text=Craft+Rum',
351
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Rum',
352
- mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Craft',
353
- events: SPOT_EVENTS_EXAMPLE,
354
- productIds: [33, 34],
355
- },
356
- {
357
- id: 'fgh678',
358
- spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
359
- variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
360
- width: 1140,
361
- height: 640,
362
- header: 'Scotch Selection',
363
- description: 'Single malts and blends',
364
- ctaText: 'Shop Scotch',
365
- textColor: '#ffffff',
366
- backgroundColor: '#4d0a0a',
367
- ctaTextColor: '#4d0a0a',
368
- primaryImage: 'https://placehold.co/1140x640/png?text=Scotch',
369
- secondaryImage: 'https://placehold.co/1140x640/png?text=Single+Malts',
370
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Scotch',
371
- mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Malts',
372
- events: SPOT_EVENTS_EXAMPLE,
373
- productIds: [35, 36],
374
- },
375
- ],
376
- rbLargeCategoryImageTout: [
377
- {
378
- id: 'cde567',
379
- spot: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
380
- variant: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
381
- width: 468,
382
- height: 410,
383
- header: 'Rare & Limited Edition',
384
- description: 'Discover our collection of hard-to-find and limited release spirits.',
385
- textColor: '#ffffff',
386
- ctaTextColor: '#ffffff',
387
- primaryImage: 'https://placehold.co/468x410/png?text=Rare+Spirits',
388
- mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Rare+Spirits',
389
- ctaText: 'Shop Rare Spirits',
390
- events: SPOT_EVENTS_EXAMPLE,
391
- productIds: [37, 38, 39, 40, 41],
392
- },
393
- {
394
- id: 'efg789',
395
- spot: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
396
- variant: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
397
- width: 468,
398
- height: 410,
399
- header: 'Vintage Champagnes',
400
- description: 'Explore our prestigious collection of aged champagnes.',
401
- textColor: '#ffffff',
402
- ctaTextColor: '#ffffff',
403
- primaryImage: 'https://placehold.co/468x410/png?text=Vintage+Champagne',
404
- mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Champagne',
405
- ctaText: 'View Collection',
406
- events: SPOT_EVENTS_EXAMPLE,
407
- productIds: [42, 43, 44, 45, 46],
408
- },
409
- {
410
- id: 'ghi012',
411
- spot: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
412
- variant: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
413
- width: 468,
414
- height: 410,
415
- header: 'Small Batch Bourbon',
416
- description: 'Hand-selected premium bourbon from craft distilleries.',
417
- textColor: '#ffffff',
418
- ctaTextColor: '#ffffff',
419
- primaryImage: 'https://placehold.co/468x410/png?text=Craft+Bourbon',
420
- mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Bourbon',
421
- ctaText: 'Explore Bourbon',
422
- events: SPOT_EVENTS_EXAMPLE,
423
- productIds: [47, 48, 49, 50, 51],
424
- },
425
- ],
426
- rbSmallDiscoverTout: [
427
- {
428
- id: 'jkl345',
429
- spot: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
430
- variant: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
431
- width: 224,
432
- height: 378,
433
- header: 'Château Margaux 2015 Bordeaux',
434
- textColor: '#ffffff',
435
- primaryImage: 'https://placehold.co/224x378/png?text=Château+Margaux',
436
- mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Château+Margaux',
437
- events: SPOT_EVENTS_EXAMPLE,
438
- productIds: [52, 53, 54, 55, 56],
439
- },
440
- {
441
- id: 'lmn678',
442
- spot: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
443
- variant: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
444
- width: 224,
445
- height: 378,
446
- header: 'Macallan 25 Year',
447
- textColor: '#ffffff',
448
- primaryImage: 'https://placehold.co/224x378/png?text=Macallan+25',
449
- mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Macallan',
450
- events: SPOT_EVENTS_EXAMPLE,
451
- productIds: [57, 58, 59, 60, 61],
452
- },
453
- {
454
- id: 'nop901',
455
- spot: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
456
- variant: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
457
- width: 224,
458
- height: 378,
459
- header: 'Louis XIII Cognac',
460
- textColor: '#ffffff',
461
- primaryImage: 'https://placehold.co/224x378/png?text=Louis+XIII',
462
- mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Louis+XIII',
463
- events: SPOT_EVENTS_EXAMPLE,
464
- productIds: [62, 63, 64, 65, 66],
465
- },
466
- ],
467
- rbSmallCategoryImageTout: [
468
- {
469
- id: 'qrs234',
470
- spot: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
471
- variant: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
472
- width: 224,
473
- height: 410,
474
- header: 'Japanese Sake',
475
- textColor: '#ffffff',
476
- primaryImage: 'https://placehold.co/224x410/png?text=Japanese+Sake',
477
- mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Japanese+Sake',
478
- events: SPOT_EVENTS_EXAMPLE,
479
- productIds: [67, 68, 69, 70, 71],
480
- },
481
- {
482
- id: 'stu567',
483
- spot: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
484
- variant: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
485
- width: 224,
486
- height: 410,
487
- header: 'Craft Vermouth',
488
- textColor: '#ffffff',
489
- primaryImage: 'https://placehold.co/224x410/png?text=Craft+Vermouth',
490
- mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Vermouth',
491
- events: SPOT_EVENTS_EXAMPLE,
492
- productIds: [72, 73, 74, 75, 76],
493
- },
494
- {
495
- id: 'vwx890',
496
- spot: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
497
- variant: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
498
- width: 224,
499
- height: 410,
500
- header: 'Premium Mezcal',
501
- textColor: '#ffffff',
502
- primaryImage: 'https://placehold.co/224x410/png?text=Premium+Mezcal',
503
- mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Mezcal',
504
- events: SPOT_EVENTS_EXAMPLE,
505
- productIds: [77, 78, 79, 80, 81],
506
- },
507
- ],
508
- rbCollectionBannerWithoutTextBlock: [
509
- {
510
- id: 'yz1234',
511
- spot: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
512
- variant: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
513
- width: 887,
514
- height: 344,
515
- primaryImage: 'https://placehold.co/887x344/png?text=Summer+Cocktails',
516
- mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Summer+Cocktails',
517
- events: SPOT_EVENTS_EXAMPLE,
518
- productIds: [82, 83, 84, 85, 86],
519
- },
520
- {
521
- id: 'abc567',
522
- spot: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
523
- variant: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
524
- width: 887,
525
- height: 344,
526
- primaryImage: 'https://placehold.co/887x344/png?text=Holiday+Spirits',
527
- mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Holiday+Spirits',
528
- events: SPOT_EVENTS_EXAMPLE,
529
- productIds: [87, 88, 89, 90, 91],
530
- },
531
- {
532
- id: 'def901',
533
- spot: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
534
- variant: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
535
- width: 887,
536
- height: 344,
537
- primaryImage: 'https://placehold.co/887x344/png?text=Wine+Essentials',
538
- mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Wine+Essentials',
539
- events: SPOT_EVENTS_EXAMPLE,
540
- productIds: [92, 93, 94, 95, 96],
541
- },
542
- ],
543
- rbNavigationBanner: [
544
- {
545
- id: 'ghi234',
546
- spot: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
547
- variant: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
548
- width: 440,
549
- height: 220,
550
- header: 'Explore Tequilas',
551
- textColor: '#ffffff',
552
- primaryImage: 'https://placehold.co/440x220/png?text=Tequila+Collection',
553
- mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Tequila+Collection',
554
- events: SPOT_EVENTS_EXAMPLE,
555
- productIds: [97, 98, 99, 100, 101],
556
- },
557
- {
558
- id: 'jkl678',
559
- spot: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
560
- variant: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
561
- width: 440,
562
- height: 220,
563
- header: 'Craft Gin Selection',
564
- textColor: '#ffffff',
565
- primaryImage: 'https://placehold.co/440x220/png?text=Gin+Selection',
566
- mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Gin+Selection',
567
- events: SPOT_EVENTS_EXAMPLE,
568
- productIds: [102, 103, 104, 105, 106],
569
- },
570
- {
571
- id: 'mno012',
572
- spot: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
573
- variant: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
574
- width: 440,
575
- height: 220,
576
- header: 'Premium Vodka',
577
- textColor: '#ffffff',
578
- primaryImage: 'https://placehold.co/440x220/png?text=Vodka+Premium',
579
- mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Vodka+Premium',
580
- events: SPOT_EVENTS_EXAMPLE,
581
- productIds: [107, 108, 109, 110, 111],
582
- },
583
- ],
584
- };
585
- const IAB_SPOTS_SELECTION_EXAMPLE = {
586
- banner: [],
587
- billboard: [
588
- {
589
- id: 'kol567',
590
- spot: RMN_SPOT_TYPE.BILLBOARD,
591
- variant: `${RMN_SPOT_TYPE.BILLBOARD}V2`,
592
- width: 1140,
593
- height: 640,
594
- header: 'Holiday Gift Guide',
595
- description: 'Perfect spirits for every occasion',
596
- ctaText: 'Shop Gifts',
597
- textColor: '#ffffff',
598
- ctaTextColor: '#ffffff',
599
- primaryImage: 'https://placehold.co/1140x640/png?text=Gift+Guide',
600
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Gifts',
601
- events: SPOT_EVENTS_EXAMPLE,
602
- productIds: [25, 26],
603
- },
604
- {
605
- id: 'hpm390',
606
- spot: RMN_SPOT_TYPE.BILLBOARD,
607
- variant: `${RMN_SPOT_TYPE.BILLBOARD}V2`,
608
- width: 1140,
609
- height: 640,
610
- header: 'Summer Wine Festival',
611
- description: 'Refreshing wines for summer',
612
- ctaText: 'Shop Festival',
613
- textColor: '#ffffff',
614
- ctaTextColor: '#ffffff',
615
- primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Festival',
616
- mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Festival',
617
- events: SPOT_EVENTS_EXAMPLE,
618
- productIds: [27, 28],
619
- },
620
- ],
621
- button2: [],
622
- featurePhoneLargeBanner: [],
623
- featurePhoneMediumBanner: [],
624
- featurePhoneSmallBanner: [],
625
- halfPage: [],
626
- inText: [],
627
- largeLeaderboard: [],
628
- largeRectangle: [],
629
- leaderboard: [],
630
- mediumRectangle: [],
631
- microBar: [],
632
- mobilePhoneInterstitial1: [],
633
- mobilePhoneInterstitial2: [],
634
- mobilePhoneInterstitial3: [],
635
- popUp: [],
636
- portrait: [],
637
- rbProductUpcs: [],
638
- skyscraper: [],
639
- smallRectangle: [],
640
- smallSquare: [],
641
- smartphoneBanner1: [],
642
- smartphoneBanner2: [],
643
- square: [],
644
- verticalBanner: [],
645
- verticalRectangle: [],
646
- wideSkyscraper: [],
647
- };
648
-
649
81
  const REQUEST_CLOUD_PARTNER_SITE = 'X-Liquid-Partner-Site';
650
82
  const REQUEST_CLOUD_PROTECTED_KEY = 'X-Liquid-Protected';
651
83
  const REQUEST_CLOUD_PROTECTED_TIMESTAMP = 'X-Liquid-Timestamp';
@@ -6692,8 +6124,122 @@ function getEventTypeFromRawEvent(event) {
6692
6124
  if (matchesKeywordPattern(words, required, optional)) {
6693
6125
  return eventType;
6694
6126
  }
6695
- }
6696
- return null;
6127
+ }
6128
+ return null;
6129
+ }
6130
+
6131
+ // Configuration object with target field names
6132
+ const extractorConfig = {
6133
+ ids: [
6134
+ // Universal product identifiers
6135
+ 'gtin',
6136
+ 'gtin8',
6137
+ 'gtin12',
6138
+ 'gtin13',
6139
+ 'gtin14',
6140
+ 'mpn',
6141
+ 'sku',
6142
+ 'upc',
6143
+ 'ean',
6144
+ 'isbn',
6145
+ 'isbn10',
6146
+ 'isbn13',
6147
+ 'asin',
6148
+ // Product codes and references
6149
+ 'coupon',
6150
+ 'barcode',
6151
+ 'product_code',
6152
+ 'part_number',
6153
+ 'model_number',
6154
+ 'item_variant',
6155
+ 'item_number',
6156
+ 'article_number',
6157
+ 'reference',
6158
+ 'groupingId',
6159
+ ],
6160
+ price: [
6161
+ 'price',
6162
+ 'unitPrice',
6163
+ 'cost',
6164
+ 'current_price',
6165
+ 'sale_price',
6166
+ 'price_value',
6167
+ 'sale_price_value',
6168
+ 'regular_price',
6169
+ 'discount_price',
6170
+ 'unit_price',
6171
+ 'original_price',
6172
+ 'final_price',
6173
+ 'retail_price',
6174
+ ],
6175
+ };
6176
+ /**
6177
+ * Extracts deep values from an object based on specified target type
6178
+ * @param data - The source data object to extract values from
6179
+ * @param target - The type of values to extract ('ids' or 'price')
6180
+ * @param options - Optional configuration for the extraction process
6181
+ * @returns Array of extracted values or a single value if onlyFirst is true
6182
+ */
6183
+ function extractDeepValues(data, target, options = {}) {
6184
+ const {
6185
+ // eslint-disable-next-line @typescript-eslint/naming-convention
6186
+ onlyFirst = false, shouldIncludeZero = false, } = options;
6187
+ const values = [];
6188
+ const targetProperties = new Set(extractorConfig[target].map((name) => name.toLowerCase()));
6189
+ /**
6190
+ * Checks if a property name matches the target criteria
6191
+ */
6192
+ const isTargetField = (key) => {
6193
+ const normalizedKey = key.toLowerCase();
6194
+ const hasTarget = targetProperties.has(normalizedKey);
6195
+ if (target === 'ids') {
6196
+ return normalizedKey.endsWith('id') || normalizedKey.endsWith('ids') || hasTarget;
6197
+ }
6198
+ return hasTarget;
6199
+ };
6200
+ /**
6201
+ * Validates and normalizes extracted values
6202
+ */
6203
+ const validateValue = (value) => {
6204
+ if (typeof value === 'string') {
6205
+ return value.trim().length > 0;
6206
+ }
6207
+ if (typeof value === 'number') {
6208
+ return !isNaN(value) && (shouldIncludeZero || value !== 0);
6209
+ }
6210
+ return false;
6211
+ };
6212
+ /**
6213
+ * Processes a value and extracts matching fields
6214
+ */
6215
+ const processValue = (value, currentKey) => {
6216
+ // Early exit conditions
6217
+ if (value == null || (onlyFirst && values.length > 0))
6218
+ return;
6219
+ // Process current value if it matches target criteria
6220
+ if (currentKey && isTargetField(currentKey)) {
6221
+ if (Array.isArray(value)) {
6222
+ const validValues = value.filter(validateValue);
6223
+ values.push(...validValues);
6224
+ }
6225
+ else if (validateValue(value)) {
6226
+ values.push(value);
6227
+ }
6228
+ return;
6229
+ }
6230
+ // Recursive processing for nested structures
6231
+ if (Array.isArray(value)) {
6232
+ value.forEach((item) => processValue(item));
6233
+ }
6234
+ else if (typeof value === 'object') {
6235
+ Object.entries(value).forEach(([key, val]) => processValue(val, key));
6236
+ }
6237
+ };
6238
+ processValue(data);
6239
+ // Return based on options
6240
+ if (values.length === 0)
6241
+ return undefined;
6242
+ return onlyFirst ? values[0] : values;
6697
6243
  }
6698
6244
 
6699
6245
  class SingletonManager {
@@ -6861,97 +6407,6 @@ class ObjectHelper {
6861
6407
  }
6862
6408
  }
6863
6409
 
6864
- /**
6865
- * Recursively extracts ID values from a nested data structure.
6866
- * Searches for specified property names and collects their primitive values (strings/numbers).
6867
- * Captures properties ending with 'id' and any additional specified property names.
6868
- *
6869
- * @param data - The data structure to search through (can be nested objects/arrays)
6870
- * @param propertyNames - Array of additional property names to look for (optional)
6871
- * @returns Array of extracted ID values (strings/numbers only)
6872
- *
6873
- * @example
6874
- * const data = {
6875
- * id: [1, 2, 3],
6876
- * nested: { id: 'abc', userId: 123 },
6877
- * items: [{ id: 456, productId: '789', sku: 'ABC123' }]
6878
- * };
6879
- * extractDeepIds(data); // Returns [1, 2, 3, 'abc', 123, 456, '789', 'ABC123']
6880
- */
6881
- function extractDeepIds(data, propertyNames) {
6882
- const ids = [];
6883
- const defaultPropertyNames = [
6884
- // Universal product identifiers
6885
- 'gtin', // Global Trade Item Number
6886
- 'gtin8', // 8-digit GTIN
6887
- 'gtin12', // 12-digit GTIN (UPC)
6888
- 'gtin13', // 13-digit GTIN (EAN)
6889
- 'gtin14', // 14-digit GTIN
6890
- 'mpn', // Manufacturer Part Number
6891
- 'sku', // Stock Keeping Unit
6892
- 'upc', // Universal Product Code
6893
- 'ean', // European Article Number
6894
- 'isbn', // International Standard Book Number
6895
- 'isbn10', // 10-digit ISBN
6896
- 'isbn13', // 13-digit ISBN
6897
- 'asin', // Amazon Standard Identification Number
6898
- // Product codes and references
6899
- 'coupon',
6900
- 'barcode',
6901
- 'product_code',
6902
- 'part_number',
6903
- 'model_number',
6904
- 'item_variant',
6905
- 'item_number',
6906
- 'article_number',
6907
- 'reference',
6908
- 'groupingId',
6909
- ];
6910
- // Convert property names to lowercase for consistent comparison
6911
- const additionalProperties = new Set((defaultPropertyNames).map((name) => name.toLowerCase()));
6912
- /**
6913
- * Checks if a property name is an ID field
6914
- * @param key - The property name to check
6915
- * @returns boolean indicating if the key is an ID field
6916
- */
6917
- const isIdField = (key) => {
6918
- const lowercaseKey = key.toLowerCase();
6919
- return lowercaseKey.endsWith('id') || additionalProperties.has(lowercaseKey);
6920
- };
6921
- /**
6922
- * Processes a value and extracts IDs if it matches criteria
6923
- * @param value - The value to process
6924
- * @param currentKey - The property name of the current value
6925
- */
6926
- const processValue = (value, currentKey) => {
6927
- // Early exit for null/undefined values
6928
- if (value == null)
6929
- return;
6930
- // If current key matches our target properties
6931
- if (currentKey && isIdField(currentKey)) {
6932
- if (Array.isArray(value)) {
6933
- // Filter and push valid array values in one pass
6934
- ids.push(...value.filter((item) => typeof item === 'string' || typeof item === 'number'));
6935
- }
6936
- else if (typeof value === 'string' || typeof value === 'number') {
6937
- ids.push(value);
6938
- }
6939
- return; // Stop processing this branch after handling the ID
6940
- }
6941
- // Recursively process nested structures
6942
- if (Array.isArray(value)) {
6943
- value.forEach((item) => processValue(item));
6944
- }
6945
- else if (typeof value === 'object') {
6946
- // Process all enumerable properties
6947
- for (const [key, val] of Object.entries(value)) {
6948
- processValue(val, key);
6949
- }
6950
- }
6951
- };
6952
- processValue(data);
6953
- return ids;
6954
- }
6955
6410
  // Fallback method using fetch if sendBeacon isn't available
6956
6411
  async function fallbackEventFire(url) {
6957
6412
  try {
@@ -6977,22 +6432,49 @@ async function fallbackEventFire(url) {
6977
6432
  return false;
6978
6433
  }
6979
6434
  }
6435
+ /**
6436
+ * Helper function to decode base64 string and parse JSON
6437
+ *
6438
+ * @param {string} base64String - The base64 encoded JSON string
6439
+ * @returns {T | null} - Decoded and parsed object or null if invalid
6440
+ */
6441
+ function decodeBase64Json(base64String) {
6442
+ try {
6443
+ return JSON.parse(atob(base64String));
6444
+ }
6445
+ catch (_a) {
6446
+ return null;
6447
+ }
6448
+ }
6980
6449
  /**
6981
6450
  * Extracts and decodes a URL from a base64-encoded query parameter.
6982
6451
  *
6983
6452
  * @param {string} url - The URL containing the base64-encoded query parameter.
6984
6453
  * @returns {string | null} - The decoded URL or null if not found or invalid.
6454
+ * @throws {Error} - If URL is malformed or payload is invalid.
6985
6455
  */
6986
6456
  function getRedirectUrlFromPayload(url) {
6457
+ if (!url)
6458
+ return null;
6987
6459
  try {
6988
- const base64String = new URL(url).searchParams.get('e');
6989
- if (!base64String) {
6460
+ // Extract initial payload
6461
+ const payload = new URL(url).searchParams.get('p');
6462
+ if (!payload)
6990
6463
  return null;
6991
- }
6992
- const data = JSON.parse(atob(base64String));
6993
- return data.ur || null;
6464
+ // Decode first layer
6465
+ const decodedData = decodeBase64Json(payload);
6466
+ if (!(decodedData === null || decodedData === void 0 ? void 0 : decodedData.u))
6467
+ return null;
6468
+ // Extract URL from nested query
6469
+ const eventPayload = new URLSearchParams(decodedData.u).get('e');
6470
+ if (!eventPayload)
6471
+ return null;
6472
+ // Decode second layer
6473
+ const eventData = decodeBase64Json(eventPayload);
6474
+ return (eventData === null || eventData === void 0 ? void 0 : eventData.ur) || null;
6994
6475
  }
6995
6476
  catch (_a) {
6477
+ console.warn('RmnSdk: Failed to extract redirect URL from payload.');
6996
6478
  return null;
6997
6479
  }
6998
6480
  }
@@ -7052,6 +6534,23 @@ function calculateScaleFactor(elementScale) {
7052
6534
  // Math.max ensures the value isn't less than minScale
7053
6535
  return Math.max(minScale, Math.min(maxScale, scaleFactor));
7054
6536
  }
6537
+ /**
6538
+ * Converts an object to a query string.
6539
+ *
6540
+ * @param {Record<string, string|number|undefined|null>} obj - The object to be converted to a query string.
6541
+ * @returns {string} - The query string.
6542
+ */
6543
+ function objectToQueryParams(obj) {
6544
+ return Object.entries(obj !== null && obj !== void 0 ? obj : {})
6545
+ .map(([key, value]) => {
6546
+ if (value == null) {
6547
+ return '';
6548
+ }
6549
+ return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
6550
+ })
6551
+ .filter(Boolean)
6552
+ .join('&');
6553
+ }
7055
6554
 
7056
6555
  class UniqueIdGenerator {
7057
6556
  /**
@@ -16260,6 +15759,7 @@ class LocalStorageService {
16260
15759
  console.warn('Local storage is not supported in this environment');
16261
15760
  return;
16262
15761
  }
15762
+ this.setUserId();
16263
15763
  this.spots = new Map();
16264
15764
  // Sync local storage with the current state
16265
15765
  this.syncLocalStorage();
@@ -16346,6 +15846,49 @@ class LocalStorageService {
16346
15846
  });
16347
15847
  this.updateLocalStorage();
16348
15848
  }
15849
+ // ======================== Utility functions ======================== //
15850
+ getUserId() {
15851
+ const key = LocalStorageService.localStorageKey;
15852
+ if (!key) {
15853
+ this.setUserId();
15854
+ }
15855
+ return key.replace(`${LocalStorageService.localStorageKeyPrefix}_`, '');
15856
+ }
15857
+ /**
15858
+ * Sets the user ID in the local storage.
15859
+ * If no existing key is found,
15860
+ * it generates a new unique ID and sets it as the local storage key.
15861
+ */
15862
+ setUserId() {
15863
+ const prefix = LocalStorageService.localStorageKeyPrefix;
15864
+ const existingKeys = Object.keys(window.localStorage).filter((key) => key.startsWith(prefix));
15865
+ const setNewKey = () => {
15866
+ const uniqueId = UniqueIdGenerator.generate();
15867
+ const newLocalStorageKey = `${prefix}_${uniqueId}`;
15868
+ window.localStorage.setItem(newLocalStorageKey, '');
15869
+ LocalStorageService.localStorageKey = newLocalStorageKey;
15870
+ };
15871
+ if (existingKeys.length === 0) {
15872
+ setNewKey();
15873
+ }
15874
+ else {
15875
+ const validKey = existingKeys.find((key) => UniqueIdGenerator.isValid(key.replace(`${prefix}_`, '')));
15876
+ // Delete all other keys except the valid one
15877
+ existingKeys.forEach((key) => {
15878
+ if (key !== validKey) {
15879
+ window.localStorage.removeItem(key);
15880
+ }
15881
+ });
15882
+ if (validKey) {
15883
+ // Found a valid key, assign it to localStorageKey
15884
+ LocalStorageService.localStorageKey = validKey;
15885
+ }
15886
+ else {
15887
+ // No valid key found, generate a new key
15888
+ setNewKey();
15889
+ }
15890
+ }
15891
+ }
16349
15892
  mapToObject(map) {
16350
15893
  return Object.fromEntries(map);
16351
15894
  }
@@ -16399,7 +15942,8 @@ class LocalStorageService {
16399
15942
  return atob(data);
16400
15943
  }
16401
15944
  }
16402
- LocalStorageService.localStorageKey = 'lc_rmn';
15945
+ LocalStorageService.localStorageKeyPrefix = 'lc_rmn';
15946
+ LocalStorageService.localStorageKey = '';
16403
15947
  LocalStorageService.spotExpirationTime = 1000 * 60 * 60 * 24 * 7; // 7 days
16404
15948
  LocalStorageService.encryptData = true;
16405
15949
 
@@ -18838,651 +18382,1357 @@ const STYLES$3 = ({ textColor = '#ffffff', ctaTextColor = textColor, ctaBorderCo
18838
18382
  font-size: 28px;
18839
18383
  }
18840
18384
 
18841
- .${prefix}__description {
18842
- font-size: 14px;
18843
- }
18385
+ .${prefix}__description {
18386
+ font-size: 14px;
18387
+ }
18388
+
18389
+ .${prefix}__cta-button {
18390
+ font-size: 13px;
18391
+ }
18392
+ }
18393
+
18394
+ @container (min-width: 1280px) {
18395
+ .${prefix}__cta-button {
18396
+ font-size: 14px;
18397
+ }
18398
+ }
18399
+ </style>
18400
+ `;
18401
+ };
18402
+ function rbLargeCategoryImageToutTemplate(spot, config) {
18403
+ const { prefix = '' } = config;
18404
+ return `
18405
+ ${GFONT_PRECONNECT}
18406
+ ${GFONT_SOURCE_SANS_3}
18407
+ ${GFONT_CORMORANT}
18408
+ ${STYLES$3(spot, config)}
18409
+ <div class="${prefix}">
18410
+ <div class="${prefix}__text">
18411
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
18412
+ ${spot.description ? `<p class="${prefix}__description">${spot.description}</p>` : ''}
18413
+ ${spot.ctaText ? `<span class="${prefix}__cta-button">${spot.ctaText}</span>` : ''}
18414
+ </div>
18415
+ </div>
18416
+ `;
18417
+ }
18418
+
18419
+ const STYLES$2 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = primaryImage }, { prefix, overlay }) => {
18420
+ const linearGradient = generateGradientColor(overlay, 'rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 30%');
18421
+ return `
18422
+ <style>
18423
+ .${prefix} {
18424
+ width: 100%;
18425
+ height: 100%;
18426
+ display: flex;
18427
+ flex-direction: column;
18428
+ justify-content: flex-end;
18429
+ border-radius: 5px;
18430
+ overflow: hidden;
18431
+ cursor: pointer;
18432
+ background-image: linear-gradient(to top, ${linearGradient}), url("${mobilePrimaryImage}");
18433
+ background-size: cover;
18434
+ background-position: center;
18435
+ background-repeat: no-repeat;
18436
+ position: relative;
18437
+ }
18438
+
18439
+ .${prefix}__header {
18440
+ font-size: 16px;
18441
+ color: ${textColor};
18442
+ line-height: 20px;
18443
+ font-weight: 400;
18444
+ font-family: "Source Sans 3", system-ui;
18445
+ font-style: normal;
18446
+ margin: 0;
18447
+ padding: 15px 10%;
18448
+ }
18449
+
18450
+ @container (min-width: 640px) {
18451
+ .${prefix} {
18452
+ background-image: linear-gradient(to top, ${linearGradient}), url("${primaryImage}");
18453
+ }
18454
+ }
18455
+
18456
+ @container (min-width: 768px) {
18457
+ .${prefix}__header {
18458
+ font-size: 22px;
18459
+ }
18460
+ }
18461
+
18462
+ @container (min-width: 1024px) {
18463
+ .${prefix}__header {
18464
+ font-size: 24px;
18465
+ }
18466
+ }
18467
+
18468
+ @container (min-width: 1280px) {
18469
+ .${prefix}__header {
18470
+ font-size: 28px;
18471
+ }
18472
+ }
18473
+ </style>
18474
+ `;
18475
+ };
18476
+ function rbNavigationBannerTemplate(spot, config) {
18477
+ const { prefix = '' } = config;
18478
+ return `
18479
+ ${GFONT_PRECONNECT}
18480
+ ${GFONT_SOURCE_SANS_3}
18481
+ ${STYLES$2(spot, config)}
18482
+ <div class="${prefix}">
18483
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
18484
+ </div>
18485
+ `;
18486
+ }
18487
+
18488
+ const STYLES$1 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = primaryImage }, { prefix, overlay }) => {
18489
+ const linearGradient = generateGradientColor(overlay, 'rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 30%');
18490
+ return `
18491
+ <style>
18492
+ .${prefix} {
18493
+ width: 100%;
18494
+ height: 100%;
18495
+ display: flex;
18496
+ flex-direction: column;
18497
+ justify-content: flex-end;
18498
+ background-image: linear-gradient(to top, ${linearGradient}), url("${mobilePrimaryImage}");
18499
+ background-size: cover;
18500
+ background-repeat: no-repeat;
18501
+ background-position: center;
18502
+ border-radius: 5px;
18503
+ overflow: hidden;
18504
+ cursor: pointer;
18505
+ position: relative;
18506
+ }
18507
+
18508
+ .${prefix}__header {
18509
+ font-size: 12px;
18510
+ color: ${textColor};
18511
+ line-height: 16px;
18512
+ font-family: "Source Sans 3", system-ui;
18513
+ font-style: normal;
18514
+ font-weight: 400;
18515
+ margin: 0;
18516
+ padding: 10px;
18517
+ }
18518
+
18519
+ @container (min-width: 640px) {
18520
+ .${prefix} {
18521
+ background-image: linear-gradient(to top, ${linearGradient}), url("${primaryImage}");
18522
+ }
18523
+ }
18524
+ </style>
18525
+ `;
18526
+ };
18527
+ function rbSmallCategoryImageToutTemplate(spot, config) {
18528
+ const { prefix = '' } = config;
18529
+ return `
18530
+ ${GFONT_PRECONNECT}
18531
+ ${GFONT_CORMORANT}
18532
+ ${STYLES$1(spot, config)}
18533
+ <div class="${prefix}">
18534
+ ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
18535
+ </div>
18536
+ `;
18537
+ }
18538
+
18539
+ const STYLES = ({ textColor = '#000000', backgroundColor = 'transparent', primaryImage, mobilePrimaryImage = primaryImage, }, { prefix }) => `
18540
+ <style>
18541
+ .${prefix} {
18542
+ width: 100%;
18543
+ height: 100%;
18544
+ background-color: ${backgroundColor};
18545
+ cursor: pointer;
18546
+ display: flex;
18547
+ flex-direction: column;
18548
+ border-radius: 5px;
18549
+ }
18550
+
18551
+ .${prefix}__image {
18552
+ width: 100%;
18553
+ height: 100%;
18554
+ background-image: url("${mobilePrimaryImage}");
18555
+ background-size: cover;
18556
+ background-repeat: no-repeat;
18557
+ background-position: center;
18558
+ border-radius: 5px;
18559
+ }
18560
+
18561
+ .${prefix}__text {
18562
+ text-align: left;
18563
+ display: flex;
18564
+ flex-direction: row;
18565
+ align-items: flex-start;
18566
+ justify-content: flex-start;
18567
+ width: 100%;
18568
+ height: fit-content;
18569
+ position: relative;
18570
+ }
18844
18571
 
18845
- .${prefix}__cta-button {
18846
- font-size: 13px;
18847
- }
18848
- }
18572
+ .${prefix}__header {
18573
+ font-size: 12px;
18574
+ color: ${textColor};
18575
+ padding-top: 5px;
18576
+ line-height: 16px;
18577
+ font-family: "Source Sans 3", system-ui;
18578
+ font-style: normal;
18579
+ font-weight: 400;
18580
+ margin: 0;
18581
+ }
18849
18582
 
18850
- @container (min-width: 1280px) {
18851
- .${prefix}__cta-button {
18852
- font-size: 14px;
18853
- }
18583
+ @container (min-width: 640px) {
18584
+ .${prefix}__image {
18585
+ background-image: url("${primaryImage}");
18854
18586
  }
18855
- </style>
18856
- `;
18857
- };
18858
- function rbLargeCategoryImageToutTemplate(spot, config) {
18587
+ }
18588
+ </style>
18589
+ `;
18590
+ function rbSmallDiscoverToutTemplate(spot, config) {
18859
18591
  const { prefix = '' } = config;
18860
18592
  return `
18861
18593
  ${GFONT_PRECONNECT}
18862
- ${GFONT_SOURCE_SANS_3}
18863
18594
  ${GFONT_CORMORANT}
18864
- ${STYLES$3(spot, config)}
18595
+ ${STYLES(spot, config)}
18865
18596
  <div class="${prefix}">
18597
+ <div class="${prefix}__image"></div>
18866
18598
  <div class="${prefix}__text">
18867
18599
  ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
18868
- ${spot.description ? `<p class="${prefix}__description">${spot.description}</p>` : ''}
18869
- ${spot.ctaText ? `<span class="${prefix}__cta-button">${spot.ctaText}</span>` : ''}
18870
18600
  </div>
18871
18601
  </div>
18872
18602
  `;
18873
18603
  }
18874
18604
 
18875
- const STYLES$2 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = primaryImage }, { prefix, overlay }) => {
18876
- const linearGradient = generateGradientColor(overlay, 'rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 30%');
18877
- return `
18878
- <style>
18879
- .${prefix} {
18880
- width: 100%;
18881
- height: 100%;
18882
- display: flex;
18883
- flex-direction: column;
18884
- justify-content: flex-end;
18885
- border-radius: 5px;
18886
- overflow: hidden;
18887
- cursor: pointer;
18888
- background-image: linear-gradient(to top, ${linearGradient}), url("${mobilePrimaryImage}");
18889
- background-size: cover;
18890
- background-position: center;
18891
- background-repeat: no-repeat;
18892
- position: relative;
18605
+ /**
18606
+ * Returns the HTML element for the given spot.
18607
+ *
18608
+ * @param {ISpot} spot - The spot object.
18609
+ * @param {ISpotTemplateConfig} config - The spot template configuration.
18610
+ *
18611
+ * @return {HTMLElement | null} - The HTML element for the given spot or null if the spot template is not found.
18612
+ */
18613
+ const SPOT_TEMPLATE_HTML_ELEMENT = (spot, config) => {
18614
+ const templates = {
18615
+ // Reserve Bar Spot Templates
18616
+ [RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE]: {
18617
+ rbHomepageHeroThreeTile: rbHomepageHeroThreeTileTemplate,
18618
+ },
18619
+ [RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE]: {
18620
+ rbHomepageHeroTwoTile: rbHomepageHeroTwoTileTemplate,
18621
+ },
18622
+ [RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE]: {
18623
+ rbHomepageHeroFullImage: rbHomepageHeroFullImageTemplate,
18624
+ },
18625
+ [RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT]: {
18626
+ rbLargeCategoryImageTout: rbLargeCategoryImageToutTemplate,
18627
+ },
18628
+ [RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT]: {
18629
+ rbSmallDiscoverTout: rbSmallDiscoverToutTemplate,
18630
+ },
18631
+ [RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT]: {
18632
+ rbSmallCategoryImageTout: rbSmallCategoryImageToutTemplate,
18633
+ },
18634
+ [RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK]: {
18635
+ rbCollectionBannerWithoutTextBlock: rbCollectionBannerWithoutTextBlockTemplate,
18636
+ },
18637
+ [RMN_SPOT_TYPE.RB_NAVIGATION_BANNER]: {
18638
+ rbNavigationBanner: rbNavigationBannerTemplate,
18639
+ },
18640
+ [RMN_SPOT_TYPE.RB_PRODUCT_UPCS]: {
18641
+ rbProductUpcs: () => '', // No template for this spot type, it will be handled by ReserveBar App.
18642
+ },
18643
+ // IAB Standard Spot Templates
18644
+ [RMN_SPOT_TYPE.BILLBOARD]: {
18645
+ billboardV1: billboardV1Template,
18646
+ billboardV2: billboardV2Template,
18647
+ billboardV3: billboardV3Template,
18648
+ },
18649
+ [RMN_SPOT_TYPE.LARGE_RECTANGLE]: {
18650
+ largeRectangleV1: largeRectangleV1Template,
18651
+ },
18652
+ [RMN_SPOT_TYPE.VERTICAL_RECTANGLE]: {
18653
+ verticalRectangleV1: verticalRectangleV1Template,
18654
+ },
18655
+ [RMN_SPOT_TYPE.SQUARE]: {
18656
+ squareV1: squareV1Template,
18657
+ squareV2: squareV2Template,
18658
+ },
18659
+ [RMN_SPOT_TYPE.LARGE_LEADERBOARD]: {
18660
+ largeLeaderboardV1: largeLeaderboardV1Template,
18661
+ largeLeaderboardV2: largeLeaderboardV2Template,
18662
+ },
18663
+ [RMN_SPOT_TYPE.WIDE_SKYSCRAPER]: {
18664
+ wideSkyscraperV1: wideSkyscraperV1Template,
18665
+ },
18666
+ [RMN_SPOT_TYPE.IN_TEXT]: {
18667
+ inTextV1: inTextV1Template,
18668
+ },
18669
+ };
18670
+ const spotVariants = templates[spot.spot];
18671
+ if (!spotVariants) {
18672
+ return null;
18673
+ }
18674
+ const variantTemplate = spotVariants[spot.variant];
18675
+ if (!variantTemplate) {
18676
+ return null;
18677
+ }
18678
+ // Generate a highly unique prefix to avoid conflicts with other elements.
18679
+ const prefix = 's' + UniqueIdGenerator.generate().toLowerCase();
18680
+ const spotHtmlString = variantTemplate(spot, { ...config, prefix });
18681
+ return spotHtmlStringToElement(spotHtmlString);
18682
+ };
18683
+
18684
+ // For the moment, we will only focus on sites that use Google Analytics,
18685
+ // but we will add support for other analytics tools in the future.
18686
+ var AnalyticsTool;
18687
+ (function (AnalyticsTool) {
18688
+ AnalyticsTool["GoogleAnalytics"] = "google-analytics";
18689
+ AnalyticsTool["Other"] = "Other";
18690
+ })(AnalyticsTool || (AnalyticsTool = {}));
18691
+
18692
+ class DataLayerMonitor {
18693
+ constructor() {
18694
+ if (!window.dataLayer) {
18695
+ return;
18696
+ }
18697
+ this.originalPush = window.dataLayer.push;
18698
+ }
18699
+ static getInstance() {
18700
+ if (!DataLayerMonitor.instance) {
18701
+ DataLayerMonitor.instance = new DataLayerMonitor();
18702
+ }
18703
+ return DataLayerMonitor.instance;
18704
+ }
18705
+ setListener(listener) {
18706
+ this.listener = listener;
18707
+ }
18708
+ start() {
18709
+ window.dataLayer.push = (...args) => {
18710
+ const result = this.originalPush.apply(window.dataLayer, args);
18711
+ const pushedEvent = args[0];
18712
+ if (this.listener) {
18713
+ const normalizedData = this.cleanEventData(pushedEvent);
18714
+ if (normalizedData) {
18715
+ this.listener(normalizedData);
18716
+ }
18717
+ }
18718
+ return result;
18719
+ };
18720
+ }
18721
+ cleanEventData(data) {
18722
+ const eventName = getEventTypeFromRawEvent(data.event);
18723
+ if (!eventName) {
18724
+ return null;
18725
+ }
18726
+ const productIds = extractDeepValues(data, 'ids', {
18727
+ onlyFirst: false,
18728
+ shouldIncludeZero: true,
18729
+ });
18730
+ if (Array.isArray(productIds) && productIds.length === 0) {
18731
+ return null;
18732
+ }
18733
+ const normalizedData = {
18734
+ event: eventName,
18735
+ productIds,
18736
+ };
18737
+ if (eventName === RMN_SPOT_EVENT.PURCHASE) {
18738
+ const productPrice = extractDeepValues(data, 'price', {
18739
+ onlyFirst: true,
18740
+ shouldIncludeZero: true,
18741
+ });
18742
+ if (productPrice) {
18743
+ normalizedData.productPrice = productPrice;
18744
+ }
18745
+ }
18746
+ return normalizedData;
18747
+ }
18748
+ stop() {
18749
+ if (this.originalPush) {
18750
+ window.dataLayer.push = this.originalPush;
18751
+ }
18752
+ this.listener = undefined;
18753
+ }
18754
+ }
18755
+
18756
+ // @TODO: Add support for user to push events to our own data layer, if they don't use any analytics tool.
18757
+ // window.rmnDataLayer = window.rmnDataLayer || [];
18758
+ class MonitorService {
18759
+ constructor() {
18760
+ const analyticsTool = this.detectAnalyticsTool();
18761
+ switch (analyticsTool) {
18762
+ case AnalyticsTool.GoogleAnalytics:
18763
+ this.implementedMonitor = DataLayerMonitor.getInstance();
18764
+ break;
18765
+ case AnalyticsTool.Other:
18766
+ default:
18767
+ console.warn('This site uses an unsupported analytics tool.');
18768
+ break;
18769
+ }
18770
+ if (analyticsTool === AnalyticsTool.Other) {
18771
+ return;
18772
+ }
18773
+ this.pubSubService = PubsubService.getInstance();
18774
+ this.localStorageService = LocalStorageService.getInstance();
18775
+ }
18776
+ static getInstance() {
18777
+ if (!MonitorService.instance) {
18778
+ MonitorService.instance = new MonitorService();
18779
+ }
18780
+ return MonitorService.instance;
18781
+ }
18782
+ start() {
18783
+ if (!this.implementedMonitor)
18784
+ return;
18785
+ this.implementedMonitor.setListener(async (eventData) => {
18786
+ var _a;
18787
+ await this.matchAndFireEvent(eventData, (_a = this.localStorageService) === null || _a === void 0 ? void 0 : _a.getSpots());
18788
+ });
18789
+ this.implementedMonitor.start();
18790
+ }
18791
+ async matchAndFireEvent(eventData, spots) {
18792
+ var _a, _b;
18793
+ if (!spots)
18794
+ return;
18795
+ const eventProductIds = new Set(eventData.productIds.map((productId) => String(productId)));
18796
+ for (const spot of Object.values(spots)) {
18797
+ if (!spot.productIds.length)
18798
+ continue;
18799
+ const hasCommonProductIds = spot.productIds.find((productId) => eventProductIds.has(String(productId)));
18800
+ if (!hasCommonProductIds || !Object.values(RMN_SPOT_EVENT).includes(eventData.event)) {
18801
+ continue;
18802
+ }
18803
+ const eventUrl = (_b = (_a = spot.events.find((event) => event.event === eventData.event)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '';
18804
+ const additionalQueryParams = objectToQueryParams({
18805
+ override: eventData.productPrice,
18806
+ });
18807
+ await this.fireAndPublishSpotEvent({
18808
+ spotEvent: eventData.event,
18809
+ eventUrl: `${eventUrl}${additionalQueryParams ? `&${additionalQueryParams}` : ''}`,
18810
+ placementId: spot.placementId,
18811
+ spotId: spot.spotId,
18812
+ });
18813
+ }
18814
+ }
18815
+ async fireAndPublishSpotEvent({ spotEvent, eventUrl, placementId, spotId, }) {
18816
+ await fireEvent({
18817
+ event: spotEvent,
18818
+ eventUrl,
18819
+ });
18820
+ if (!this.pubSubService)
18821
+ return;
18822
+ this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
18823
+ eventType: spotEvent,
18824
+ placementId,
18825
+ spotId,
18826
+ });
18827
+ }
18828
+ detectAnalyticsTool() {
18829
+ let analyticsTool = AnalyticsTool.Other;
18830
+ // Check for Google Analytics
18831
+ if (typeof window.ga !== 'undefined') {
18832
+ analyticsTool = AnalyticsTool.GoogleAnalytics;
18833
+ }
18834
+ // Check for Google Analytics 4
18835
+ if (typeof window.gtag !== 'undefined') {
18836
+ analyticsTool = AnalyticsTool.GoogleAnalytics;
18837
+ }
18838
+ // Check for Google Tag Manager
18839
+ if (typeof window.google_tag_manager !== 'undefined') {
18840
+ analyticsTool = AnalyticsTool.GoogleAnalytics;
18841
+ }
18842
+ // @TODO: Add support for other analytics tools
18843
+ // Check for Heap Analytics
18844
+ // Check for Mixpanel
18845
+ // Check for Woopra
18846
+ // Check for Segment
18847
+ // Check for Amplitude
18848
+ return analyticsTool;
18849
+ }
18850
+ }
18851
+
18852
+ class EventService {
18853
+ constructor() {
18854
+ this.pubSubService = PubsubService.getInstance();
18855
+ this.localStorageService = LocalStorageService.getInstance();
18856
+ this.activeSpots = new Map();
18857
+ this.spotStates = new Map();
18858
+ this.intersectionObserver = new IntersectionObserverService();
18859
+ // Start the user monitor, which will track and check user interactions
18860
+ MonitorService.getInstance().start();
18893
18861
  }
18894
-
18895
- .${prefix}__header {
18896
- font-size: 16px;
18897
- color: ${textColor};
18898
- line-height: 20px;
18899
- font-weight: 400;
18900
- font-family: "Source Sans 3", system-ui;
18901
- font-style: normal;
18902
- margin: 0;
18903
- padding: 15px 10%;
18862
+ static getInstance() {
18863
+ if (!EventService.instance) {
18864
+ EventService.instance = new EventService();
18865
+ }
18866
+ return EventService.instance;
18904
18867
  }
18905
-
18906
- @container (min-width: 640px) {
18907
- .${prefix} {
18908
- background-image: linear-gradient(to top, ${linearGradient}), url("${primaryImage}");
18909
- }
18868
+ subscribe(eventType, callback) {
18869
+ return this.pubSubService.subscribe(eventType, callback);
18910
18870
  }
18911
-
18912
- @container (min-width: 768px) {
18913
- .${prefix}__header {
18914
- font-size: 22px;
18915
- }
18871
+ publish(eventType, data) {
18872
+ this.pubSubService.publish(eventType, data);
18916
18873
  }
18917
-
18918
- @container (min-width: 1024px) {
18919
- .${prefix}__header {
18920
- font-size: 24px;
18921
- }
18874
+ registerSpot(params) {
18875
+ const { placementId, spot, spotElement } = params;
18876
+ this.activeSpots.set(placementId, { spotElement });
18877
+ // Fire impression event
18878
+ this.fireImpressionEvent(placementId, spot);
18879
+ // Handle intersection observer
18880
+ this.handleIntersectionObserver(placementId, spot, spotElement);
18881
+ // Attach click event listener
18882
+ spotElement.addEventListener('click', async () => await this.handleClick(params));
18922
18883
  }
18923
-
18924
- @container (min-width: 1280px) {
18925
- .${prefix}__header {
18926
- font-size: 28px;
18927
- }
18884
+ unregisterSpot(placementId) {
18885
+ const placementIdClean = placementId.replace('#', '');
18886
+ const spotData = this.activeSpots.get(placementIdClean);
18887
+ if (!spotData) {
18888
+ this.handleSpotState(placementIdClean, {
18889
+ state: {
18890
+ error: `Active spot with placementId ${placementIdClean} not found.`,
18891
+ },
18892
+ });
18893
+ return;
18894
+ }
18895
+ this.intersectionObserver.unobserve(spotData.spotElement);
18896
+ this.handleSpotState(placementIdClean, {
18897
+ dom: {
18898
+ spotElement: undefined,
18899
+ visibleOnViewport: false,
18900
+ },
18901
+ state: {
18902
+ unmounted: true,
18903
+ mounted: false,
18904
+ },
18905
+ });
18906
+ this.activeSpots.delete(placementIdClean);
18907
+ const placementElement = document.getElementById(placementIdClean);
18908
+ if (!placementElement) {
18909
+ this.handleSpotState(placementIdClean, {
18910
+ state: {
18911
+ error: `Placement element with id ${placementIdClean} not found.`,
18912
+ },
18913
+ });
18914
+ return;
18915
+ }
18916
+ placementElement.innerHTML = '';
18928
18917
  }
18929
- </style>
18930
- `;
18931
- };
18932
- function rbNavigationBannerTemplate(spot, config) {
18933
- const { prefix = '' } = config;
18934
- return `
18935
- ${GFONT_PRECONNECT}
18936
- ${GFONT_SOURCE_SANS_3}
18937
- ${STYLES$2(spot, config)}
18938
- <div class="${prefix}">
18939
- ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
18940
- </div>
18941
- `;
18942
- }
18943
-
18944
- const STYLES$1 = ({ textColor = '#ffffff', primaryImage, mobilePrimaryImage = primaryImage }, { prefix, overlay }) => {
18945
- const linearGradient = generateGradientColor(overlay, 'rgba(0, 0, 0, 1) 0%, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 30%');
18946
- return `
18947
- <style>
18948
- .${prefix} {
18949
- width: 100%;
18950
- height: 100%;
18951
- display: flex;
18952
- flex-direction: column;
18953
- justify-content: flex-end;
18954
- background-image: linear-gradient(to top, ${linearGradient}), url("${mobilePrimaryImage}");
18955
- background-size: cover;
18956
- background-repeat: no-repeat;
18957
- background-position: center;
18958
- border-radius: 5px;
18959
- overflow: hidden;
18960
- cursor: pointer;
18961
- position: relative;
18962
- }
18963
-
18964
- .${prefix}__header {
18965
- font-size: 12px;
18966
- color: ${textColor};
18967
- line-height: 16px;
18968
- font-family: "Source Sans 3", system-ui;
18969
- font-style: normal;
18970
- font-weight: 400;
18971
- margin: 0;
18972
- padding: 10px;
18973
- }
18974
-
18975
- @container (min-width: 640px) {
18976
- .${prefix} {
18977
- background-image: linear-gradient(to top, ${linearGradient}), url("${primaryImage}");
18918
+ /**
18919
+ * Updates the state of a spot.
18920
+ *
18921
+ * @param {string} placementId - The placement ID of the spot.
18922
+ * @param {Partial<ILifecycleState>} updates - The updates to apply to the spot state.
18923
+ * @param {boolean} publish - Whether to publish the updated state.
18924
+ *
18925
+ * @returns {void}
18926
+ */
18927
+ handleSpotState(placementId, updates, publish = true) {
18928
+ let currentState = this.spotStates.get(placementId);
18929
+ if (!currentState) {
18930
+ currentState = {
18931
+ identifier: {
18932
+ placementId,
18933
+ spotId: '',
18934
+ spotType: '',
18935
+ },
18936
+ dom: {
18937
+ spotElement: undefined,
18938
+ visibleOnViewport: false,
18939
+ },
18940
+ state: {
18941
+ mounted: false,
18942
+ unmounted: false,
18943
+ loading: false,
18944
+ error: undefined,
18945
+ },
18946
+ displayConfig: {
18947
+ isCarousel: false,
18948
+ isCarouselItem: false,
18949
+ isSingleItem: false,
18950
+ },
18951
+ };
18978
18952
  }
18979
- }
18980
- </style>
18981
- `;
18982
- };
18983
- function rbSmallCategoryImageToutTemplate(spot, config) {
18984
- const { prefix = '' } = config;
18985
- return `
18986
- ${GFONT_PRECONNECT}
18987
- ${GFONT_CORMORANT}
18988
- ${STYLES$1(spot, config)}
18989
- <div class="${prefix}">
18990
- ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
18991
- </div>
18992
- `;
18953
+ const merged = this.deepMerge(currentState, updates);
18954
+ this.spotStates.set(placementId, merged);
18955
+ if (publish) {
18956
+ this.pubSubService.publish(RMN_EVENT.LIFECYCLE_STATE, this.spotStates.get(placementId));
18957
+ }
18958
+ }
18959
+ deepMerge(current, updates) {
18960
+ return {
18961
+ identifier: updates.identifier
18962
+ ? { ...current.identifier, ...updates.identifier }
18963
+ : current.identifier,
18964
+ dom: updates.dom ? { ...current.dom, ...updates.dom } : current.dom,
18965
+ state: updates.state ? { ...current.state, ...updates.state } : current.state,
18966
+ displayConfig: updates.displayConfig
18967
+ ? { ...current.displayConfig, ...updates.displayConfig }
18968
+ : current.displayConfig,
18969
+ };
18970
+ }
18971
+ async handleClick({ placementId, spot }) {
18972
+ var _a, _b, _c;
18973
+ this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
18974
+ eventType: RMN_SPOT_EVENT.CLICK,
18975
+ placementId,
18976
+ spotId: spot.id,
18977
+ });
18978
+ await fireEvent({
18979
+ event: RMN_SPOT_EVENT.CLICK,
18980
+ 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 : '',
18981
+ });
18982
+ // Save spot to local storage for event tracking
18983
+ this.localStorageService.setSpot(spot.id, {
18984
+ placementId,
18985
+ spotId: spot.id,
18986
+ spotType: spot.spot,
18987
+ events: spot.events,
18988
+ productIds: (_c = spot.productIds) !== null && _c !== void 0 ? _c : [1, 2, 3],
18989
+ });
18990
+ }
18991
+ handleIntersectionObserver(placementId, _spot, spotElement) {
18992
+ const spotIsVisibleCallback = async () => {
18993
+ this.intersectionObserver.unobserve(spotElement);
18994
+ this.handleSpotState(placementId, {
18995
+ dom: {
18996
+ spotElement,
18997
+ visibleOnViewport: true,
18998
+ },
18999
+ });
19000
+ };
19001
+ this.intersectionObserver.observe(spotElement, spotIsVisibleCallback);
19002
+ }
19003
+ fireImpressionEvent(placementId, spot) {
19004
+ this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
19005
+ eventType: RMN_SPOT_EVENT.IMPRESSION,
19006
+ placementId,
19007
+ spotId: spot.id,
19008
+ });
19009
+ (async () => {
19010
+ var _a, _b;
19011
+ await fireEvent({
19012
+ event: RMN_SPOT_EVENT.IMPRESSION,
19013
+ 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 : '',
19014
+ });
19015
+ })();
19016
+ }
18993
19017
  }
18994
19018
 
18995
- const STYLES = ({ textColor = '#000000', backgroundColor = 'transparent', primaryImage, mobilePrimaryImage = primaryImage, }, { prefix }) => `
18996
- <style>
18997
- .${prefix} {
18998
- width: 100%;
18999
- height: 100%;
19000
- background-color: ${backgroundColor};
19001
- cursor: pointer;
19002
- display: flex;
19003
- flex-direction: column;
19004
- border-radius: 5px;
19005
- }
19019
+ const SELECTION_API_PATH = '/spots/selection';
19006
19020
 
19007
- .${prefix}__image {
19008
- width: 100%;
19009
- height: 100%;
19010
- background-image: url("${mobilePrimaryImage}");
19011
- background-size: cover;
19012
- background-repeat: no-repeat;
19013
- background-position: center;
19014
- border-radius: 5px;
19021
+ class SelectionService extends BaseApi {
19022
+ constructor(auth) {
19023
+ super(auth);
19015
19024
  }
19016
-
19017
- .${prefix}__text {
19018
- text-align: left;
19019
- display: flex;
19020
- flex-direction: row;
19021
- align-items: flex-start;
19022
- justify-content: flex-start;
19023
- width: 100%;
19024
- height: fit-content;
19025
- position: relative;
19025
+ static getInstance(auth) {
19026
+ return SingletonManager.getInstance('SelectionService', () => new SelectionService(auth));
19026
19027
  }
19027
-
19028
- .${prefix}__header {
19029
- font-size: 12px;
19030
- color: ${textColor};
19031
- padding-top: 5px;
19032
- line-height: 16px;
19033
- font-family: "Source Sans 3", system-ui;
19034
- font-style: normal;
19035
- font-weight: 400;
19036
- margin: 0;
19028
+ /**
19029
+ * Makes a selection request on our server based on the provided data.
19030
+ *
19031
+ * @param {ISpotSelectionParams} data - Spots selection parameters.
19032
+ *
19033
+ * @return {Promise<ISpots | { error: string }>} - The spots response object.
19034
+ */
19035
+ async spotSelection(data) {
19036
+ if (data.userId === undefined) {
19037
+ data.userId = this.getUserId();
19038
+ }
19039
+ const { isOk, val, isErr } = await this.post(SELECTION_API_PATH, data, {});
19040
+ if (isErr) {
19041
+ return { error: `There was an error during spot selection: (${isErr === null || isErr === void 0 ? void 0 : isErr.errorMessage})` };
19042
+ }
19043
+ if (isOk && val && val.data && (val === null || val === void 0 ? void 0 : val.refresh.token)) {
19044
+ this.authInfo.authenticated = true;
19045
+ this.authInfo.token = val.refresh.token;
19046
+ return val.data.spots;
19047
+ }
19048
+ return { error: 'Spot selection response was not successful' };
19037
19049
  }
19038
-
19039
- @container (min-width: 640px) {
19040
- .${prefix}__image {
19041
- background-image: url("${primaryImage}");
19042
- }
19050
+ getUserId() {
19051
+ const isWeb = typeof window !== 'undefined';
19052
+ if (isWeb) {
19053
+ const localStorageService = LocalStorageService.getInstance();
19054
+ return localStorageService.getUserId();
19055
+ }
19056
+ return undefined;
19043
19057
  }
19044
- </style>
19045
- `;
19046
- function rbSmallDiscoverToutTemplate(spot, config) {
19047
- const { prefix = '' } = config;
19048
- return `
19049
- ${GFONT_PRECONNECT}
19050
- ${GFONT_CORMORANT}
19051
- ${STYLES(spot, config)}
19052
- <div class="${prefix}">
19053
- <div class="${prefix}__image"></div>
19054
- <div class="${prefix}__text">
19055
- ${spot.header ? `<h2 class="${prefix}__header">${spot.header}</h2>` : ''}
19056
- </div>
19057
- </div>
19058
- `;
19059
19058
  }
19060
19059
 
19061
- /**
19062
- * Returns the HTML element for the given spot.
19063
- *
19064
- * @param {ISpot} spot - The spot object.
19065
- * @param {ISpotTemplateConfig} config - The spot template configuration.
19066
- *
19067
- * @return {HTMLElement | null} - The HTML element for the given spot or null if the spot template is not found.
19068
- */
19069
- const SPOT_TEMPLATE_HTML_ELEMENT = (spot, config) => {
19070
- const templates = {
19071
- // Reserve Bar Spot Templates
19072
- [RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE]: {
19073
- rbHomepageHeroThreeTile: rbHomepageHeroThreeTileTemplate,
19060
+ const SPOT_EVENTS_EXAMPLE = [
19061
+ {
19062
+ event: RMN_SPOT_EVENT.CLICK,
19063
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwidXIiOm51bGx9&s=hWz37kbxi_u95EVNn2aoQhc5Aas',
19064
+ },
19065
+ {
19066
+ event: RMN_SPOT_EVENT.IMPRESSION,
19067
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiYmEiOjEsImZxIjowfQ&s=djoysjCimurf-5T11AlNAwwLSS8',
19068
+ },
19069
+ {
19070
+ event: RMN_SPOT_EVENT.PURCHASE,
19071
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjU5fQ&s=AAPAw-3SfZ0JMzjEGFSwt9L-2S4',
19072
+ },
19073
+ {
19074
+ event: RMN_SPOT_EVENT.ADD_TO_CART,
19075
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjYwfQ&s=uzQFcjgL7m9XqUG8FvTPVN5YkZY',
19076
+ },
19077
+ {
19078
+ event: RMN_SPOT_EVENT.ADD_TO_WISHLIST,
19079
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjYzfQ&s=m3ISU_iIy-OFtXrTKpI6cJAEC0k',
19080
+ },
19081
+ {
19082
+ event: RMN_SPOT_EVENT.BUY_NOW,
19083
+ url: 'https://dev.rmn.liquidcommerce.cloud/api/spots/event?e=eyJ2IjoiMS4xMiIsImF2IjozMDY1NzgzLCJhdCI6MTYzLCJidCI6MCwiY20iOjQ0MDE5MjQxOCwiY2giOjYzMTg0LCJjayI6e30sImNyIjo0ODE4NTUzNzUsImRpIjoiOWMxNGFhMGI3NWY4NDMxNTllMTAwYWQzNzA1NzQyYzMiLCJkaiI6MCwiaWkiOiIxZjU0MGM5NmQ1N2M0YmZjODFlZjRkNjhkMzFjNDVkOSIsImRtIjozLCJmYyI6NjU2NjgyNTQ5LCJmbCI6NjQzOTMxODIxLCJpcCI6IjM1LjIyMy4xOTguOTUiLCJudyI6MTE1MDAsInBjIjo1MDAwLCJvcCI6NTAwMCwibXAiOjUwMDAsImVjIjowLCJnbSI6MCwiZXAiOm51bGwsInByIjoyNDkzMTYsInJ0IjoxLCJycyI6NTAwLCJzYSI6IjU1Iiwic2IiOiJpLTA0MDI0ODg4ZDlkMWRjZWQ3Iiwic3AiOjI3MjI3Miwic3QiOjEyODcyOTYsInRyIjp0cnVlLCJ1ayI6IjNhZWRhMWMxLTZhYjItNDUwNy04Mzg5LTEwZTJmNDMxYjM5MSIsInRzIjoxNzI5MzU5MDU0OTI3LCJiZiI6dHJ1ZSwicG4iOiJyYlByb2R1Y3RVcGNzIiwiZ2MiOnRydWUsImdDIjp0cnVlLCJncyI6Im5vbmUiLCJkYyI6MSwidHoiOiJBbWVyaWNhL05ld19Zb3JrIiwiZXQiOjY5fQ&s=l6MOscQC-q-FkC2Ksd7w6jjySCQ',
19084
+ },
19085
+ ];
19086
+ ({
19087
+ rbHomepageHero: [
19088
+ {
19089
+ id: 'abc123',
19090
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19091
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19092
+ width: 1140,
19093
+ height: 640,
19094
+ header: 'Premium Wine Collection',
19095
+ description: 'Discover our exclusive selection of vintage wines',
19096
+ ctaText: 'Shop Wines',
19097
+ textColor: '#ffffff',
19098
+ ctaTextColor: '#ffffff',
19099
+ primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Collection',
19100
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Wine',
19101
+ events: SPOT_EVENTS_EXAMPLE,
19102
+ productIds: [1, 2, 3],
19103
+ },
19104
+ {
19105
+ id: 'jkl012',
19106
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19107
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19108
+ width: 1140,
19109
+ height: 640,
19110
+ header: 'Whiskey Collection',
19111
+ description: 'Premium whiskeys from around the world',
19112
+ ctaText: 'Shop Whiskeys',
19113
+ textColor: '#ffffff',
19114
+ backgroundColor: '#2c1810',
19115
+ ctaTextColor: '#2c1810',
19116
+ primaryImage: 'https://placehold.co/1140x640/png?text=Whiskey',
19117
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Whiskey',
19118
+ events: SPOT_EVENTS_EXAMPLE,
19119
+ productIds: [10, 11],
19120
+ },
19121
+ {
19122
+ id: 'stu901',
19123
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19124
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19125
+ width: 1140,
19126
+ height: 640,
19127
+ header: 'Summer Cocktails',
19128
+ description: 'Essential spirits for summer mixing',
19129
+ ctaText: 'Mix It Up',
19130
+ textColor: '#ffffff',
19131
+ backgroundColor: '#4d3a0a',
19132
+ ctaTextColor: '#4d3a0a',
19133
+ primaryImage: 'https://placehold.co/1140x640/png?text=Cocktails',
19134
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Mixers',
19135
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Cocktails',
19136
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Mixers',
19137
+ events: SPOT_EVENTS_EXAMPLE,
19138
+ productIds: [16, 17, 18],
19139
+ },
19140
+ {
19141
+ id: 'def456',
19142
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19143
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19144
+ width: 1140,
19145
+ height: 640,
19146
+ header: 'Craft Beer Festival',
19147
+ description: 'Local breweries and exclusive releases',
19148
+ ctaText: 'Explore Beers',
19149
+ textColor: '#ffffff',
19150
+ ctaTextColor: '#ffffff',
19151
+ primaryImage: 'https://placehold.co/1140x640/png?text=Beer+Festival',
19152
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Beer',
19153
+ events: SPOT_EVENTS_EXAMPLE,
19154
+ productIds: [4, 5, 6],
19155
+ },
19156
+ {
19157
+ id: 'mno345',
19158
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19159
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19160
+ width: 1140,
19161
+ height: 640,
19162
+ header: 'Champagne Selection',
19163
+ description: 'Finest champagnes for every occasion',
19164
+ ctaText: 'View Champagnes',
19165
+ textColor: '#ffffff',
19166
+ backgroundColor: '#1a1a1a',
19167
+ ctaTextColor: '#1a1a1a',
19168
+ primaryImage: 'https://placehold.co/1140x640/png?text=Champagne',
19169
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Champagne',
19170
+ events: SPOT_EVENTS_EXAMPLE,
19171
+ productIds: [12, 13],
19172
+ },
19173
+ {
19174
+ id: 'vwx234',
19175
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19176
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19177
+ width: 1140,
19178
+ height: 640,
19179
+ header: 'Wine Regions',
19180
+ description: 'Explore wines from top regions',
19181
+ ctaText: 'Tour Regions',
19182
+ textColor: '#ffffff',
19183
+ backgroundColor: '#4d0a0a',
19184
+ ctaTextColor: '#4d0a0a',
19185
+ primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Regions',
19186
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Vineyards',
19187
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Regions',
19188
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Vineyards',
19189
+ events: SPOT_EVENTS_EXAMPLE,
19190
+ productIds: [19, 20, 21],
19074
19191
  },
19075
- [RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE]: {
19076
- rbHomepageHeroTwoTile: rbHomepageHeroTwoTileTemplate,
19192
+ {
19193
+ id: 'ghi789',
19194
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19195
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19196
+ width: 1140,
19197
+ height: 640,
19198
+ header: 'Rare Spirits',
19199
+ description: 'Limited edition spirits collection',
19200
+ ctaText: 'View Collection',
19201
+ textColor: '#ffffff',
19202
+ ctaTextColor: '#ffffff',
19203
+ primaryImage: 'https://placehold.co/1140x640/png?text=Rare+Spirits',
19204
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Spirits',
19205
+ events: SPOT_EVENTS_EXAMPLE,
19206
+ productIds: [7, 8, 9],
19077
19207
  },
19078
- [RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE]: {
19079
- rbHomepageHeroFullImage: rbHomepageHeroFullImageTemplate,
19208
+ {
19209
+ id: 'pqr678',
19210
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19211
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19212
+ width: 1140,
19213
+ height: 640,
19214
+ header: 'Gin Collection',
19215
+ description: 'Artisanal gins and botanicals',
19216
+ ctaText: 'Explore Gins',
19217
+ textColor: '#ffffff',
19218
+ backgroundColor: '#0a4d4d',
19219
+ ctaTextColor: '#0a4d4d',
19220
+ primaryImage: 'https://placehold.co/1140x640/png?text=Gin',
19221
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Gin',
19222
+ events: SPOT_EVENTS_EXAMPLE,
19223
+ productIds: [14, 15],
19080
19224
  },
19081
- [RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT]: {
19082
- rbLargeCategoryImageTout: rbLargeCategoryImageToutTemplate,
19225
+ {
19226
+ id: 'yz5678',
19227
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19228
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19229
+ width: 1140,
19230
+ height: 640,
19231
+ header: 'Tequila Collection',
19232
+ description: 'Premium tequilas and mezcals',
19233
+ ctaText: 'Shop Tequila',
19234
+ textColor: '#ffffff',
19235
+ backgroundColor: '#0a4d2b',
19236
+ ctaTextColor: '#0a4d2b',
19237
+ primaryImage: 'https://placehold.co/1140x640/png?text=Tequila',
19238
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Mezcal',
19239
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Tequila',
19240
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Mezcal',
19241
+ events: SPOT_EVENTS_EXAMPLE,
19242
+ productIds: [22, 23, 24],
19083
19243
  },
19084
- [RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT]: {
19085
- rbSmallDiscoverTout: rbSmallDiscoverToutTemplate,
19244
+ ],
19245
+ rbHomepageHeroFullImage: [
19246
+ {
19247
+ id: 'hjk567',
19248
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19249
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19250
+ width: 1140,
19251
+ height: 640,
19252
+ header: 'Holiday Gift Guide',
19253
+ description: 'Perfect spirits for every occasion',
19254
+ ctaText: 'Shop Gifts',
19255
+ textColor: '#ffffff',
19256
+ ctaTextColor: '#ffffff',
19257
+ primaryImage: 'https://placehold.co/1140x640/png?text=Gift+Guide',
19258
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Gifts',
19259
+ events: SPOT_EVENTS_EXAMPLE,
19260
+ productIds: [25, 26],
19261
+ },
19262
+ {
19263
+ id: 'qwe890',
19264
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19265
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_FULL_IMAGE,
19266
+ width: 1140,
19267
+ height: 640,
19268
+ header: 'Summer Wine Festival',
19269
+ description: 'Refreshing wines for summer',
19270
+ ctaText: 'Shop Festival',
19271
+ textColor: '#ffffff',
19272
+ ctaTextColor: '#ffffff',
19273
+ primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Festival',
19274
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Festival',
19275
+ events: SPOT_EVENTS_EXAMPLE,
19276
+ productIds: [27, 28],
19277
+ },
19278
+ ],
19279
+ rbHomepageHeroTwoTile: [
19280
+ {
19281
+ id: 'iop987',
19282
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19283
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19284
+ width: 1140,
19285
+ height: 640,
19286
+ header: 'Bourbon Selection',
19287
+ description: "Kentucky's finest bourbons",
19288
+ ctaText: 'Shop Bourbon',
19289
+ textColor: '#ffffff',
19290
+ backgroundColor: '#2c1810',
19291
+ ctaTextColor: '#2c1810',
19292
+ primaryImage: 'https://placehold.co/1140x640/png?text=Bourbon',
19293
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Bourbon',
19294
+ events: SPOT_EVENTS_EXAMPLE,
19295
+ productIds: [29, 30],
19296
+ },
19297
+ {
19298
+ id: 'lkj012',
19299
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19300
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_TWO_TILE,
19301
+ width: 1140,
19302
+ height: 640,
19303
+ header: 'Vodka Collection',
19304
+ description: 'Premium vodkas from around the world',
19305
+ ctaText: 'Shop Vodka',
19306
+ textColor: '#ffffff',
19307
+ backgroundColor: '#1a1a1a',
19308
+ ctaTextColor: '#1a1a1a',
19309
+ primaryImage: 'https://placehold.co/1140x640/png?text=Vodka',
19310
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Vodka',
19311
+ events: SPOT_EVENTS_EXAMPLE,
19312
+ productIds: [31, 32],
19313
+ },
19314
+ ],
19315
+ rbHomepageHeroThreeTile: [
19316
+ {
19317
+ id: 'bnm345',
19318
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19319
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19320
+ width: 1140,
19321
+ height: 640,
19322
+ header: 'Rum Collection',
19323
+ description: 'Caribbean and craft rums',
19324
+ ctaText: 'Shop Rum',
19325
+ textColor: '#ffffff',
19326
+ backgroundColor: '#4d3a0a',
19327
+ ctaTextColor: '#4d3a0a',
19328
+ primaryImage: 'https://placehold.co/1140x640/png?text=Rum',
19329
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Craft+Rum',
19330
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Rum',
19331
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Craft',
19332
+ events: SPOT_EVENTS_EXAMPLE,
19333
+ productIds: [33, 34],
19334
+ },
19335
+ {
19336
+ id: 'fgh678',
19337
+ spot: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19338
+ variant: RMN_SPOT_TYPE.RB_HOMEPAGE_HERO_THREE_TILE,
19339
+ width: 1140,
19340
+ height: 640,
19341
+ header: 'Scotch Selection',
19342
+ description: 'Single malts and blends',
19343
+ ctaText: 'Shop Scotch',
19344
+ textColor: '#ffffff',
19345
+ backgroundColor: '#4d0a0a',
19346
+ ctaTextColor: '#4d0a0a',
19347
+ primaryImage: 'https://placehold.co/1140x640/png?text=Scotch',
19348
+ secondaryImage: 'https://placehold.co/1140x640/png?text=Single+Malts',
19349
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Scotch',
19350
+ mobileSecondaryImage: 'https://placehold.co/640x320/png?text=Mobile+Malts',
19351
+ events: SPOT_EVENTS_EXAMPLE,
19352
+ productIds: [35, 36],
19353
+ },
19354
+ ],
19355
+ rbLargeCategoryImageTout: [
19356
+ {
19357
+ id: 'cde567',
19358
+ spot: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19359
+ variant: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19360
+ width: 468,
19361
+ height: 410,
19362
+ header: 'Rare & Limited Edition',
19363
+ description: 'Discover our collection of hard-to-find and limited release spirits.',
19364
+ textColor: '#ffffff',
19365
+ ctaTextColor: '#ffffff',
19366
+ primaryImage: 'https://placehold.co/468x410/png?text=Rare+Spirits',
19367
+ mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Rare+Spirits',
19368
+ ctaText: 'Shop Rare Spirits',
19369
+ events: SPOT_EVENTS_EXAMPLE,
19370
+ productIds: [37, 38, 39, 40, 41],
19371
+ },
19372
+ {
19373
+ id: 'efg789',
19374
+ spot: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19375
+ variant: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19376
+ width: 468,
19377
+ height: 410,
19378
+ header: 'Vintage Champagnes',
19379
+ description: 'Explore our prestigious collection of aged champagnes.',
19380
+ textColor: '#ffffff',
19381
+ ctaTextColor: '#ffffff',
19382
+ primaryImage: 'https://placehold.co/468x410/png?text=Vintage+Champagne',
19383
+ mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Champagne',
19384
+ ctaText: 'View Collection',
19385
+ events: SPOT_EVENTS_EXAMPLE,
19386
+ productIds: [42, 43, 44, 45, 46],
19387
+ },
19388
+ {
19389
+ id: 'ghi012',
19390
+ spot: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19391
+ variant: RMN_SPOT_TYPE.RB_LARGE_CATEGORY_IMAGE_TOUT,
19392
+ width: 468,
19393
+ height: 410,
19394
+ header: 'Small Batch Bourbon',
19395
+ description: 'Hand-selected premium bourbon from craft distilleries.',
19396
+ textColor: '#ffffff',
19397
+ ctaTextColor: '#ffffff',
19398
+ primaryImage: 'https://placehold.co/468x410/png?text=Craft+Bourbon',
19399
+ mobilePrimaryImage: 'https://placehold.co/468x410/png?text=Mobile+Bourbon',
19400
+ ctaText: 'Explore Bourbon',
19401
+ events: SPOT_EVENTS_EXAMPLE,
19402
+ productIds: [47, 48, 49, 50, 51],
19403
+ },
19404
+ ],
19405
+ rbSmallDiscoverTout: [
19406
+ {
19407
+ id: 'jkl345',
19408
+ spot: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19409
+ variant: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19410
+ width: 224,
19411
+ height: 378,
19412
+ header: 'Château Margaux 2015 Bordeaux',
19413
+ textColor: '#ffffff',
19414
+ primaryImage: 'https://placehold.co/224x378/png?text=Château+Margaux',
19415
+ mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Château+Margaux',
19416
+ events: SPOT_EVENTS_EXAMPLE,
19417
+ productIds: [52, 53, 54, 55, 56],
19418
+ },
19419
+ {
19420
+ id: 'lmn678',
19421
+ spot: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19422
+ variant: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19423
+ width: 224,
19424
+ height: 378,
19425
+ header: 'Macallan 25 Year',
19426
+ textColor: '#ffffff',
19427
+ primaryImage: 'https://placehold.co/224x378/png?text=Macallan+25',
19428
+ mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Macallan',
19429
+ events: SPOT_EVENTS_EXAMPLE,
19430
+ productIds: [57, 58, 59, 60, 61],
19431
+ },
19432
+ {
19433
+ id: 'nop901',
19434
+ spot: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19435
+ variant: RMN_SPOT_TYPE.RB_SMALL_DISCOVER_TOUT,
19436
+ width: 224,
19437
+ height: 378,
19438
+ header: 'Louis XIII Cognac',
19439
+ textColor: '#ffffff',
19440
+ primaryImage: 'https://placehold.co/224x378/png?text=Louis+XIII',
19441
+ mobilePrimaryImage: 'https://placehold.co/224x378/png?text=Mobile+Louis+XIII',
19442
+ events: SPOT_EVENTS_EXAMPLE,
19443
+ productIds: [62, 63, 64, 65, 66],
19086
19444
  },
19087
- [RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT]: {
19088
- rbSmallCategoryImageTout: rbSmallCategoryImageToutTemplate,
19445
+ ],
19446
+ rbSmallCategoryImageTout: [
19447
+ {
19448
+ id: 'qrs234',
19449
+ spot: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19450
+ variant: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19451
+ width: 224,
19452
+ height: 410,
19453
+ header: 'Japanese Sake',
19454
+ textColor: '#ffffff',
19455
+ primaryImage: 'https://placehold.co/224x410/png?text=Japanese+Sake',
19456
+ mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Japanese+Sake',
19457
+ events: SPOT_EVENTS_EXAMPLE,
19458
+ productIds: [67, 68, 69, 70, 71],
19089
19459
  },
19090
- [RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK]: {
19091
- rbCollectionBannerWithoutTextBlock: rbCollectionBannerWithoutTextBlockTemplate,
19460
+ {
19461
+ id: 'stu567',
19462
+ spot: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19463
+ variant: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19464
+ width: 224,
19465
+ height: 410,
19466
+ header: 'Craft Vermouth',
19467
+ textColor: '#ffffff',
19468
+ primaryImage: 'https://placehold.co/224x410/png?text=Craft+Vermouth',
19469
+ mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Vermouth',
19470
+ events: SPOT_EVENTS_EXAMPLE,
19471
+ productIds: [72, 73, 74, 75, 76],
19092
19472
  },
19093
- [RMN_SPOT_TYPE.RB_NAVIGATION_BANNER]: {
19094
- rbNavigationBanner: rbNavigationBannerTemplate,
19473
+ {
19474
+ id: 'vwx890',
19475
+ spot: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19476
+ variant: RMN_SPOT_TYPE.RB_SMALL_CATEGORY_IMAGE_TOUT,
19477
+ width: 224,
19478
+ height: 410,
19479
+ header: 'Premium Mezcal',
19480
+ textColor: '#ffffff',
19481
+ primaryImage: 'https://placehold.co/224x410/png?text=Premium+Mezcal',
19482
+ mobilePrimaryImage: 'https://placehold.co/224x410/png?text=Mobile+Mezcal',
19483
+ events: SPOT_EVENTS_EXAMPLE,
19484
+ productIds: [77, 78, 79, 80, 81],
19095
19485
  },
19096
- [RMN_SPOT_TYPE.RB_PRODUCT_UPCS]: {
19097
- rbProductUpcs: () => '', // No template for this spot type, it will be handled by ReserveBar App.
19486
+ ],
19487
+ rbCollectionBannerWithoutTextBlock: [
19488
+ {
19489
+ id: 'yz1234',
19490
+ spot: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
19491
+ variant: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
19492
+ width: 887,
19493
+ height: 344,
19494
+ primaryImage: 'https://placehold.co/887x344/png?text=Summer+Cocktails',
19495
+ mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Summer+Cocktails',
19496
+ events: SPOT_EVENTS_EXAMPLE,
19497
+ productIds: [82, 83, 84, 85, 86],
19098
19498
  },
19099
- // IAB Standard Spot Templates
19100
- [RMN_SPOT_TYPE.BILLBOARD]: {
19101
- billboardV1: billboardV1Template,
19102
- billboardV2: billboardV2Template,
19103
- billboardV3: billboardV3Template,
19499
+ {
19500
+ id: 'abc567',
19501
+ spot: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
19502
+ variant: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
19503
+ width: 887,
19504
+ height: 344,
19505
+ primaryImage: 'https://placehold.co/887x344/png?text=Holiday+Spirits',
19506
+ mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Holiday+Spirits',
19507
+ events: SPOT_EVENTS_EXAMPLE,
19508
+ productIds: [87, 88, 89, 90, 91],
19104
19509
  },
19105
- [RMN_SPOT_TYPE.LARGE_RECTANGLE]: {
19106
- largeRectangleV1: largeRectangleV1Template,
19510
+ {
19511
+ id: 'def901',
19512
+ spot: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
19513
+ variant: RMN_SPOT_TYPE.RB_COLLECTION_BANNER_WITHOUT_TEXT_BLOCK,
19514
+ width: 887,
19515
+ height: 344,
19516
+ primaryImage: 'https://placehold.co/887x344/png?text=Wine+Essentials',
19517
+ mobilePrimaryImage: 'https://placehold.co/887x344/png?text=Mobile+Wine+Essentials',
19518
+ events: SPOT_EVENTS_EXAMPLE,
19519
+ productIds: [92, 93, 94, 95, 96],
19107
19520
  },
19108
- [RMN_SPOT_TYPE.VERTICAL_RECTANGLE]: {
19109
- verticalRectangleV1: verticalRectangleV1Template,
19521
+ ],
19522
+ rbNavigationBanner: [
19523
+ {
19524
+ id: 'ghi234',
19525
+ spot: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
19526
+ variant: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
19527
+ width: 440,
19528
+ height: 220,
19529
+ header: 'Explore Tequilas',
19530
+ textColor: '#ffffff',
19531
+ primaryImage: 'https://placehold.co/440x220/png?text=Tequila+Collection',
19532
+ mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Tequila+Collection',
19533
+ events: SPOT_EVENTS_EXAMPLE,
19534
+ productIds: [97, 98, 99, 100, 101],
19110
19535
  },
19111
- [RMN_SPOT_TYPE.SQUARE]: {
19112
- squareV1: squareV1Template,
19113
- squareV2: squareV2Template,
19536
+ {
19537
+ id: 'jkl678',
19538
+ spot: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
19539
+ variant: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
19540
+ width: 440,
19541
+ height: 220,
19542
+ header: 'Craft Gin Selection',
19543
+ textColor: '#ffffff',
19544
+ primaryImage: 'https://placehold.co/440x220/png?text=Gin+Selection',
19545
+ mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Gin+Selection',
19546
+ events: SPOT_EVENTS_EXAMPLE,
19547
+ productIds: [102, 103, 104, 105, 106],
19114
19548
  },
19115
- [RMN_SPOT_TYPE.LARGE_LEADERBOARD]: {
19116
- largeLeaderboardV1: largeLeaderboardV1Template,
19117
- largeLeaderboardV2: largeLeaderboardV2Template,
19549
+ {
19550
+ id: 'mno012',
19551
+ spot: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
19552
+ variant: RMN_SPOT_TYPE.RB_NAVIGATION_BANNER,
19553
+ width: 440,
19554
+ height: 220,
19555
+ header: 'Premium Vodka',
19556
+ textColor: '#ffffff',
19557
+ primaryImage: 'https://placehold.co/440x220/png?text=Vodka+Premium',
19558
+ mobilePrimaryImage: 'https://placehold.co/440x220/png?text=Mobile+Vodka+Premium',
19559
+ events: SPOT_EVENTS_EXAMPLE,
19560
+ productIds: [107, 108, 109, 110, 111],
19118
19561
  },
19119
- [RMN_SPOT_TYPE.WIDE_SKYSCRAPER]: {
19120
- wideSkyscraperV1: wideSkyscraperV1Template,
19562
+ ],
19563
+ });
19564
+ ({
19565
+ banner: [],
19566
+ billboard: [
19567
+ {
19568
+ id: 'kol567',
19569
+ spot: RMN_SPOT_TYPE.BILLBOARD,
19570
+ variant: `${RMN_SPOT_TYPE.BILLBOARD}V2`,
19571
+ width: 1140,
19572
+ height: 640,
19573
+ header: 'Holiday Gift Guide',
19574
+ description: 'Perfect spirits for every occasion',
19575
+ ctaText: 'Shop Gifts',
19576
+ textColor: '#ffffff',
19577
+ ctaTextColor: '#ffffff',
19578
+ primaryImage: 'https://placehold.co/1140x640/png?text=Gift+Guide',
19579
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Gifts',
19580
+ events: SPOT_EVENTS_EXAMPLE,
19581
+ productIds: [25, 26],
19121
19582
  },
19122
- [RMN_SPOT_TYPE.IN_TEXT]: {
19123
- inTextV1: inTextV1Template,
19583
+ {
19584
+ id: 'hpm390',
19585
+ spot: RMN_SPOT_TYPE.BILLBOARD,
19586
+ variant: `${RMN_SPOT_TYPE.BILLBOARD}V2`,
19587
+ width: 1140,
19588
+ height: 640,
19589
+ header: 'Summer Wine Festival',
19590
+ description: 'Refreshing wines for summer',
19591
+ ctaText: 'Shop Festival',
19592
+ textColor: '#ffffff',
19593
+ ctaTextColor: '#ffffff',
19594
+ primaryImage: 'https://placehold.co/1140x640/png?text=Wine+Festival',
19595
+ mobilePrimaryImage: 'https://placehold.co/640x640/png?text=Mobile+Festival',
19596
+ events: SPOT_EVENTS_EXAMPLE,
19597
+ productIds: [27, 28],
19124
19598
  },
19125
- };
19126
- const spotVariants = templates[spot.spot];
19127
- if (!spotVariants) {
19128
- return null;
19129
- }
19130
- const variantTemplate = spotVariants[spot.variant];
19131
- if (!variantTemplate) {
19132
- return null;
19133
- }
19134
- // Generate a highly unique prefix to avoid conflicts with other elements.
19135
- const prefix = 's' + UniqueIdGenerator.generate().toLowerCase();
19136
- const spotHtmlString = variantTemplate(spot, { ...config, prefix });
19137
- return spotHtmlStringToElement(spotHtmlString);
19138
- };
19139
-
19140
- // For the moment, we will only focus on sites that use Google Analytics,
19141
- // but we will add support for other analytics tools in the future.
19142
- var AnalyticsTool;
19143
- (function (AnalyticsTool) {
19144
- AnalyticsTool["GoogleAnalytics"] = "google-analytics";
19145
- AnalyticsTool["Other"] = "Other";
19146
- })(AnalyticsTool || (AnalyticsTool = {}));
19147
-
19148
- class DataLayerMonitor {
19149
- constructor() {
19150
- if (!window.dataLayer) {
19151
- return;
19152
- }
19153
- this.originalPush = window.dataLayer.push;
19154
- }
19155
- static getInstance() {
19156
- if (!DataLayerMonitor.instance) {
19157
- DataLayerMonitor.instance = new DataLayerMonitor();
19158
- }
19159
- return DataLayerMonitor.instance;
19160
- }
19161
- setListener(listener) {
19162
- this.listener = listener;
19163
- }
19164
- start() {
19165
- window.dataLayer.push = (...args) => {
19166
- const result = this.originalPush.apply(window.dataLayer, args);
19167
- const pushedEvent = args[0];
19168
- if (this.listener) {
19169
- const normalizedData = this.cleanEventData(pushedEvent);
19170
- if (normalizedData) {
19171
- this.listener(normalizedData);
19172
- }
19173
- }
19174
- return result;
19175
- };
19176
- }
19177
- cleanEventData(data) {
19178
- const eventName = getEventTypeFromRawEvent(data.event);
19179
- if (!eventName) {
19180
- return null;
19181
- }
19182
- const productIds = extractDeepIds(data);
19183
- return {
19184
- event: eventName,
19185
- productIds,
19186
- };
19187
- }
19188
- stop() {
19189
- if (this.originalPush) {
19190
- window.dataLayer.push = this.originalPush;
19191
- }
19192
- this.listener = undefined;
19193
- }
19194
- }
19599
+ ],
19600
+ button2: [],
19601
+ featurePhoneLargeBanner: [],
19602
+ featurePhoneMediumBanner: [],
19603
+ featurePhoneSmallBanner: [],
19604
+ halfPage: [],
19605
+ inText: [],
19606
+ largeLeaderboard: [],
19607
+ largeRectangle: [],
19608
+ leaderboard: [],
19609
+ mediumRectangle: [],
19610
+ microBar: [],
19611
+ mobilePhoneInterstitial1: [],
19612
+ mobilePhoneInterstitial2: [],
19613
+ mobilePhoneInterstitial3: [],
19614
+ popUp: [],
19615
+ portrait: [],
19616
+ rbProductUpcs: [],
19617
+ skyscraper: [],
19618
+ smallRectangle: [],
19619
+ smallSquare: [],
19620
+ smartphoneBanner1: [],
19621
+ smartphoneBanner2: [],
19622
+ square: [],
19623
+ verticalBanner: [],
19624
+ verticalRectangle: [],
19625
+ wideSkyscraper: [],
19626
+ });
19195
19627
 
19196
- // @TODO: Add support for user to push events to our own data layer, if they don't use any analytics tool.
19197
- // window.rmnDataLayer = window.rmnDataLayer || [];
19198
- class MonitorService {
19199
- constructor() {
19200
- const analyticsTool = this.detectAnalyticsTool();
19201
- switch (analyticsTool) {
19202
- case AnalyticsTool.GoogleAnalytics:
19203
- this.implementedMonitor = DataLayerMonitor.getInstance();
19204
- break;
19205
- case AnalyticsTool.Other:
19206
- default:
19207
- console.warn('This site uses an unsupported analytics tool.');
19208
- break;
19209
- }
19210
- if (analyticsTool === AnalyticsTool.Other) {
19211
- return;
19212
- }
19213
- this.pubSubService = PubsubService.getInstance();
19214
- this.localStorageService = LocalStorageService.getInstance();
19215
- }
19216
- static getInstance() {
19217
- if (!MonitorService.instance) {
19218
- MonitorService.instance = new MonitorService();
19219
- }
19220
- return MonitorService.instance;
19221
- }
19222
- start() {
19223
- if (!this.implementedMonitor)
19224
- return;
19225
- this.implementedMonitor.setListener(async (eventData) => {
19226
- var _a;
19227
- await this.matchAndFireEvent(eventData, (_a = this.localStorageService) === null || _a === void 0 ? void 0 : _a.getSpots());
19228
- });
19229
- this.implementedMonitor.start();
19230
- }
19231
- async matchAndFireEvent(eventData, spots) {
19232
- var _a, _b;
19233
- if (!spots)
19234
- return;
19235
- const eventProductIds = new Set(eventData.productIds.map((productId) => String(productId)));
19236
- for (const spot of Object.values(spots)) {
19237
- if (!spot.productIds.length)
19238
- continue;
19239
- const hasCommonProductIds = spot.productIds.find((productId) => eventProductIds.has(String(productId)));
19240
- if (hasCommonProductIds) {
19241
- if (Object.values(RMN_SPOT_EVENT).includes(eventData.event)) {
19242
- await this.fireAndPublishSpotEvent({
19243
- spotEvent: eventData.event,
19244
- eventUrl: (_b = (_a = spot.events.find((event) => event.event === eventData.event)) === null || _a === void 0 ? void 0 : _a.url) !== null && _b !== void 0 ? _b : '',
19245
- placementId: spot.placementId,
19246
- spotId: spot.spotId,
19247
- });
19248
- }
19249
- }
19250
- }
19251
- }
19252
- async fireAndPublishSpotEvent({ spotEvent, eventUrl, placementId, spotId, }) {
19253
- await fireEvent({
19254
- event: spotEvent,
19255
- eventUrl,
19256
- });
19257
- if (!this.pubSubService)
19258
- return;
19259
- this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
19260
- eventType: spotEvent,
19261
- placementId,
19262
- spotId,
19263
- });
19264
- }
19265
- detectAnalyticsTool() {
19266
- let analyticsTool = AnalyticsTool.Other;
19267
- // Check for Google Analytics
19268
- if (typeof window.ga !== 'undefined') {
19269
- analyticsTool = AnalyticsTool.GoogleAnalytics;
19270
- }
19271
- // Check for Google Analytics 4
19272
- if (typeof window.gtag !== 'undefined') {
19273
- analyticsTool = AnalyticsTool.GoogleAnalytics;
19274
- }
19275
- // Check for Google Tag Manager
19276
- if (typeof window.google_tag_manager !== 'undefined') {
19277
- analyticsTool = AnalyticsTool.GoogleAnalytics;
19278
- }
19279
- // @TODO: Add support for other analytics tools
19280
- // Check for Heap Analytics
19281
- // Check for Mixpanel
19282
- // Check for Woopra
19283
- // Check for Segment
19284
- // Check for Amplitude
19285
- return analyticsTool;
19628
+ /**
19629
+ * Checks if the current environment is a browser environment.
19630
+ *
19631
+ * @return {boolean} - Whether the current environment is a browser environment.
19632
+ */
19633
+ function isBrowserEnvironment() {
19634
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
19635
+ console.warn('LiquidCommerce Rmn Sdk: Methods which create elements are only available in browser environments.');
19636
+ return false;
19286
19637
  }
19638
+ return true;
19287
19639
  }
19288
-
19289
- class EventService {
19290
- constructor() {
19291
- this.pubSubService = PubsubService.getInstance();
19292
- this.localStorageService = LocalStorageService.getInstance();
19293
- this.activeSpots = new Map();
19294
- this.spotStates = new Map();
19295
- this.intersectionObserver = new IntersectionObserverService();
19296
- // Start the user monitor, which will track and check user interactions
19297
- MonitorService.getInstance().start();
19298
- }
19299
- static getInstance() {
19300
- if (!EventService.instance) {
19301
- EventService.instance = new EventService();
19302
- }
19303
- return EventService.instance;
19304
- }
19305
- subscribe(eventType, callback) {
19306
- return this.pubSubService.subscribe(eventType, callback);
19307
- }
19308
- publish(eventType, data) {
19309
- this.pubSubService.publish(eventType, data);
19310
- }
19311
- registerSpot(params) {
19312
- const { placementId, spot, spotElement } = params;
19313
- this.activeSpots.set(placementId, { spotElement });
19314
- // Fire impression event
19315
- this.fireImpressionEvent(placementId, spot);
19316
- // Handle intersection observer
19317
- this.handleIntersectionObserver(placementId, spot, spotElement);
19318
- // Attach click event listener
19319
- spotElement.addEventListener('click', async () => await this.handleClick(params));
19320
- }
19321
- unregisterSpot(placementId) {
19322
- const placementIdClean = placementId.replace('#', '');
19323
- const spotData = this.activeSpots.get(placementIdClean);
19324
- if (!spotData) {
19325
- this.handleSpotState(placementIdClean, {
19326
- state: {
19327
- error: `Active spot with placementId ${placementIdClean} not found.`,
19328
- },
19329
- });
19330
- return;
19331
- }
19332
- this.intersectionObserver.unobserve(spotData.spotElement);
19333
- this.handleSpotState(placementIdClean, {
19334
- dom: {
19335
- spotElement: undefined,
19336
- visibleOnViewport: false,
19337
- },
19338
- state: {
19339
- unmounted: true,
19340
- mounted: false,
19341
- },
19342
- });
19343
- this.activeSpots.delete(placementIdClean);
19344
- const placementElement = document.getElementById(placementIdClean);
19345
- if (!placementElement) {
19346
- this.handleSpotState(placementIdClean, {
19640
+ /**
19641
+ * Validates the inject data by preventing duplicate placement ids and non-existent spot types.
19642
+ *
19643
+ * @param {IInjectSpotElement[]} inject - The inject data.
19644
+ * @return {IInjectSpotElement[]} - The validated inject data.
19645
+ */
19646
+ function validateInjectData(inject) {
19647
+ const eventService = EventService.getInstance();
19648
+ const placementIds = new Set();
19649
+ const validSpotTypes = new Set(Object.values(RMN_SPOT_TYPE));
19650
+ const validatedInject = [];
19651
+ for (const item of inject) {
19652
+ const placementId = item.placementId.replace('#', '');
19653
+ // Check for duplicate placement ids
19654
+ if (placementIds.has(placementId)) {
19655
+ eventService.handleSpotState(placementId, {
19347
19656
  state: {
19348
- error: `Placement element with id ${placementIdClean} not found.`,
19657
+ error: `Duplicate placement id (${placementId}) found. Please provide a unique placement id for each spot element.`,
19349
19658
  },
19350
19659
  });
19351
- return;
19352
- }
19353
- placementElement.innerHTML = '';
19354
- }
19355
- /**
19356
- * Updates the state of a spot.
19357
- *
19358
- * @param {string} placementId - The placement ID of the spot.
19359
- * @param {Partial<ILifecycleState>} updates - The updates to apply to the spot state.
19360
- * @param {boolean} publish - Whether to publish the updated state.
19361
- *
19362
- * @returns {void}
19363
- */
19364
- handleSpotState(placementId, updates, publish = true) {
19365
- let currentState = this.spotStates.get(placementId);
19366
- if (!currentState) {
19367
- currentState = {
19368
- identifier: {
19369
- placementId,
19370
- spotId: '',
19371
- spotType: '',
19372
- },
19373
- dom: {
19374
- spotElement: undefined,
19375
- visibleOnViewport: false,
19376
- },
19377
- state: {
19378
- mounted: false,
19379
- unmounted: false,
19380
- loading: false,
19381
- error: undefined,
19382
- },
19383
- displayConfig: {
19384
- isCarousel: false,
19385
- isCarouselItem: false,
19386
- isSingleItem: false,
19387
- },
19388
- };
19389
- }
19390
- const merged = this.deepMerge(currentState, updates);
19391
- this.spotStates.set(placementId, merged);
19392
- if (publish) {
19393
- this.pubSubService.publish(RMN_EVENT.LIFECYCLE_STATE, this.spotStates.get(placementId));
19394
- }
19395
- }
19396
- deepMerge(current, updates) {
19397
- return {
19398
- identifier: updates.identifier
19399
- ? { ...current.identifier, ...updates.identifier }
19400
- : current.identifier,
19401
- dom: updates.dom ? { ...current.dom, ...updates.dom } : current.dom,
19402
- state: updates.state ? { ...current.state, ...updates.state } : current.state,
19403
- displayConfig: updates.displayConfig
19404
- ? { ...current.displayConfig, ...updates.displayConfig }
19405
- : current.displayConfig,
19406
- };
19407
- }
19408
- async handleClick({ placementId, spot }) {
19409
- var _a, _b, _c;
19410
- this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
19411
- eventType: RMN_SPOT_EVENT.CLICK,
19412
- placementId,
19413
- spotId: spot.id,
19414
- });
19415
- await fireEvent({
19416
- event: RMN_SPOT_EVENT.CLICK,
19417
- 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 : '',
19418
- });
19419
- // Save spot to local storage for event tracking
19420
- this.localStorageService.setSpot(spot.id, {
19421
- placementId,
19422
- spotId: spot.id,
19423
- spotType: spot.spot,
19424
- events: spot.events,
19425
- productIds: (_c = spot.productIds) !== null && _c !== void 0 ? _c : [1, 2, 3],
19426
- });
19427
- }
19428
- handleIntersectionObserver(placementId, _spot, spotElement) {
19429
- const spotIsVisibleCallback = async () => {
19430
- this.intersectionObserver.unobserve(spotElement);
19431
- this.handleSpotState(placementId, {
19432
- dom: {
19433
- spotElement,
19434
- visibleOnViewport: true,
19660
+ continue;
19661
+ }
19662
+ // Check for non-existent spot types
19663
+ if (!validSpotTypes.has(item.spotType)) {
19664
+ eventService.handleSpotState(placementId, {
19665
+ state: {
19666
+ error: `Invalid spot type (${item.spotType}) found. Please provide a valid spot type for each spot element.`,
19435
19667
  },
19436
19668
  });
19437
- };
19438
- this.intersectionObserver.observe(spotElement, spotIsVisibleCallback);
19439
- }
19440
- fireImpressionEvent(placementId, spot) {
19441
- this.pubSubService.publish(RMN_EVENT.SPOT_EVENT, {
19442
- eventType: RMN_SPOT_EVENT.IMPRESSION,
19443
- placementId,
19444
- spotId: spot.id,
19445
- });
19446
- (async () => {
19447
- var _a, _b;
19448
- await fireEvent({
19449
- event: RMN_SPOT_EVENT.IMPRESSION,
19450
- 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 : '',
19451
- });
19452
- })();
19669
+ continue;
19670
+ }
19671
+ placementIds.add(placementId);
19672
+ validatedInject.push(item);
19453
19673
  }
19674
+ return validatedInject;
19454
19675
  }
19455
-
19456
- const SELECTION_API_PATH = '/spots/selection';
19457
-
19458
- class SelectionService extends BaseApi {
19459
- constructor(auth) {
19460
- super(auth);
19676
+ /**
19677
+ * Clears the placement element by removing all its children.
19678
+ *
19679
+ * @param {string} placementId - The placement id.
19680
+ *
19681
+ * @return {void}
19682
+ */
19683
+ function clearPlacement(placementId) {
19684
+ var _a;
19685
+ (_a = document.getElementById(placementId)) === null || _a === void 0 ? void 0 : _a.replaceChildren();
19686
+ }
19687
+ /**
19688
+ * Prepares the spot placement for rendering by taking care of its styling.
19689
+ *
19690
+ * @param {HTMLElement} placement - The placement element.
19691
+ *
19692
+ * @return {void}
19693
+ */
19694
+ function prepareSpotPlacement(placement) {
19695
+ placement.removeAttribute('style');
19696
+ placement.removeAttribute('class');
19697
+ const styles = {
19698
+ width: '100%',
19699
+ height: '100%',
19700
+ display: 'flex',
19701
+ justifyContent: 'center',
19702
+ };
19703
+ Object.assign(placement.style, styles);
19704
+ }
19705
+ /**
19706
+ * Waits for the DOM to be ready before continuing.
19707
+ *
19708
+ * @return {Promise<void>} - A promise that resolves when the DOM is ready.
19709
+ */
19710
+ async function waitForDOM() {
19711
+ if (!isBrowserEnvironment()) {
19712
+ return;
19461
19713
  }
19462
- static getInstance(auth) {
19463
- return SingletonManager.getInstance('SelectionService', () => new SelectionService(auth));
19714
+ if (document.readyState === 'complete' || document.readyState === 'interactive') {
19715
+ return;
19464
19716
  }
19465
- /**
19466
- * Makes a selection request on our server based on the provided data.
19467
- *
19468
- * @param {ISpotSelectionParams} data - Spots selection parameters.
19469
- *
19470
- * @return {Promise<ISpots | { error: string }>} - The spots response object.
19471
- */
19472
- async spotSelection(data) {
19473
- const { isOk, val, isErr } = await this.post(SELECTION_API_PATH, data, {});
19474
- if (isErr) {
19475
- return { error: `There was an error during spot selection: (${isErr === null || isErr === void 0 ? void 0 : isErr.errorMessage})` };
19476
- }
19477
- if (isOk && val && val.data && (val === null || val === void 0 ? void 0 : val.refresh.token)) {
19478
- this.authInfo.authenticated = true;
19479
- this.authInfo.token = val.refresh.token;
19480
- return val.data.spots;
19481
- }
19482
- return { error: 'Spot selection response was not successful' };
19717
+ return new Promise((resolve) => {
19718
+ document.addEventListener('DOMContentLoaded', () => {
19719
+ resolve();
19720
+ });
19721
+ });
19722
+ }
19723
+ // Sets the id for the user who is browsing the website
19724
+ // This id is used to identify the user and provide personalized content
19725
+ function setUserId() {
19726
+ if (isBrowserEnvironment()) {
19727
+ const localStorageService = LocalStorageService.getInstance();
19728
+ localStorageService.setUserId();
19483
19729
  }
19484
19730
  }
19485
19731
 
19732
+ /**
19733
+ * LiquidCommerce Rmn Client
19734
+ * @class
19735
+ */
19486
19736
  class LiquidCommerceRmnClient {
19487
19737
  constructor(auth) {
19488
19738
  this.selectionService = SelectionService.getInstance(auth);
@@ -19511,14 +19761,11 @@ class LiquidCommerceRmnClient {
19511
19761
  */
19512
19762
  async injectSpotElement(params) {
19513
19763
  var _a;
19514
- if (typeof window === 'undefined' || typeof document === 'undefined') {
19515
- console.warn('LiquidCommerce Rmn Sdk: injectSpotElement method is only available in the browser environment.');
19516
- return;
19517
- }
19764
+ // Wait for the DOM to be ready before continuing, to avoid issues with the spot placements
19765
+ await waitForDOM();
19518
19766
  const config = params.config;
19519
- let inject = params.inject;
19520
19767
  // Handle no spots error state
19521
- if (!inject.length) {
19768
+ if (!params.inject.length) {
19522
19769
  this.eventService.handleSpotState('all', {
19523
19770
  state: {
19524
19771
  error: 'No spot elements provided for injection.',
@@ -19526,49 +19773,32 @@ class LiquidCommerceRmnClient {
19526
19773
  });
19527
19774
  return;
19528
19775
  }
19529
- // Identify the spot elements
19776
+ // Validate inject data
19777
+ const inject = validateInjectData(params.inject);
19530
19778
  for (const item of inject) {
19779
+ // Identify the spot element
19531
19780
  this.eventService.handleSpotState(item.placementId, {
19532
19781
  identifier: {
19533
19782
  placementId: item.placementId,
19534
19783
  spotType: item.spotType,
19535
19784
  },
19536
19785
  }, false);
19537
- }
19538
- // Prevent duplicate placement ids
19539
- const hasDuplicatePlacementIds = this.preventDuplicateSpotPlacementIds(inject);
19540
- if (!hasDuplicatePlacementIds) {
19541
- return;
19542
- }
19543
- // Prevent non-existent spot types
19544
- inject = this.preventNonExistentSpotTypes(inject);
19545
- // Add Intersection Observer to the spot elements to track visibility
19546
- // This is useful for lazy loading the spot elements
19547
- for (const item of inject) {
19548
- const placementId = item.placementId.replace('#', '');
19549
- const placement = document.getElementById(placementId);
19786
+ const placement = document.getElementById(item.placementId);
19787
+ // Handle placement not found error state
19550
19788
  if (!placement) {
19551
- // Handle placement not found error state
19552
19789
  this.eventService.handleSpotState(item.placementId, {
19553
19790
  state: {
19554
- error: `Placement not found for id "${placementId}".`,
19791
+ error: `Placement not found for id "${item.placementId}".`,
19555
19792
  },
19556
19793
  });
19557
19794
  continue;
19558
19795
  }
19559
- // Take over placement styles
19560
- placement.removeAttribute('style');
19561
- placement.removeAttribute('class');
19562
- Object.assign(placement.style, {
19563
- width: '100%',
19564
- height: '100%',
19565
- display: 'flex',
19566
- justifyContent: 'center',
19567
- });
19796
+ prepareSpotPlacement(placement);
19568
19797
  const skeletonElement = this.elementService.createSkeletonElement({
19569
19798
  fluid: (_a = config === null || config === void 0 ? void 0 : config.fluid) !== null && _a !== void 0 ? _a : false,
19570
19799
  spotType: item.spotType,
19571
19800
  });
19801
+ // Handle skeleton loader error state
19572
19802
  if (!skeletonElement) {
19573
19803
  this.eventService.handleSpotState(item.placementId, {
19574
19804
  state: {
@@ -19580,19 +19810,15 @@ class LiquidCommerceRmnClient {
19580
19810
  continue;
19581
19811
  }
19582
19812
  placement.replaceChildren(skeletonElement);
19583
- const spotPlacementIsNear = async () => {
19813
+ const spotPlacementIsNearCallback = async () => {
19584
19814
  var _a;
19585
- // Set the spot element to loading state
19586
- this.eventService.handleSpotState(item.placementId, {
19587
- state: {
19588
- loading: true,
19589
- },
19590
- });
19591
- // Stop observing the placement
19815
+ // Stop observing the placement, as we only need to do this once
19592
19816
  this.intersectionObserver.unobserve(placement);
19817
+ // Set the spot element to loading state
19818
+ this.eventService.handleSpotState(item.placementId, { state: { loading: true } });
19593
19819
  // Make the spot selection request
19594
- const response = await this.spotSelectionRequest({ ...params, inject: [item] });
19595
- // const response = await this.useSpotSelectionExample(inject);
19820
+ const response = await this.injectSpotSelectionRequest({ ...params, inject: [item] });
19821
+ // const response = await useSpotSelectionExample(inject);
19596
19822
  // Handle request error state
19597
19823
  if (typeof response === 'object' && 'error' in response) {
19598
19824
  this.eventService.handleSpotState(item.placementId, {
@@ -19602,13 +19828,13 @@ class LiquidCommerceRmnClient {
19602
19828
  loading: false,
19603
19829
  },
19604
19830
  });
19605
- this.clearPlacement(item.placementId);
19831
+ clearPlacement(item.placementId);
19606
19832
  return;
19607
19833
  }
19608
19834
  const itemConfig = (_a = item.config) !== null && _a !== void 0 ? _a : config;
19609
19835
  const spots = response[item.placementId];
19836
+ // Handle no spots found error state
19610
19837
  if (!(spots === null || spots === void 0 ? void 0 : spots.length)) {
19611
- // Handle no spots found error state
19612
19838
  this.eventService.handleSpotState(item.placementId, {
19613
19839
  state: {
19614
19840
  error: `No spots found for type "${item.spotType}".`,
@@ -19616,22 +19842,106 @@ class LiquidCommerceRmnClient {
19616
19842
  loading: false,
19617
19843
  },
19618
19844
  });
19619
- this.clearPlacement(item.placementId);
19845
+ clearPlacement(item.placementId);
19620
19846
  return;
19621
19847
  }
19622
19848
  // Handle single spot
19623
19849
  if (spots.length === 1) {
19624
- this.injectOneSpotElement(item, placement, spots[0], itemConfig);
19850
+ this.injectOneSpotElement(placement, spots[0], itemConfig);
19625
19851
  }
19626
19852
  // Handle multiple spots (carousel)
19627
19853
  if (spots.length > 1) {
19628
19854
  this.injectCarouselSpotElement(placement, spots, itemConfig);
19629
19855
  }
19630
19856
  };
19631
- this.intersectionObserver.observe(placement, spotPlacementIsNear, { rootMargin: '500px' });
19857
+ /**
19858
+ * Observe the placement element to check if it is near the viewport.
19859
+ * If it is near, make the spot selection request.
19860
+ */
19861
+ this.intersectionObserver.observe(placement, spotPlacementIsNearCallback, {
19862
+ rootMargin: '1000px',
19863
+ threshold: 0,
19864
+ });
19865
+ }
19866
+ }
19867
+ /**
19868
+ * Injects a single spot element into the provided placement.
19869
+ *
19870
+ * @param {HTMLElement} placement - The placement element.
19871
+ * @param {ISpot} spot - The spot data.
19872
+ * @param {IInjectSpotElementConfig} config - The configuration object.
19873
+ *
19874
+ * @return {void}
19875
+ */
19876
+ injectOneSpotElement(placement, spot, config) {
19877
+ var _a;
19878
+ const placementId = placement.id;
19879
+ const spotType = spot.spot;
19880
+ this.eventService.handleSpotState(placementId, {
19881
+ identifier: {
19882
+ placementId,
19883
+ spotType,
19884
+ spotId: spot.id,
19885
+ },
19886
+ displayConfig: {
19887
+ isSingleItem: true,
19888
+ isCarousel: false,
19889
+ isCarouselItem: false,
19890
+ },
19891
+ });
19892
+ const spotData = this.elementService.overrideSpotColors(spot, config === null || config === void 0 ? void 0 : config.colors);
19893
+ const content = SPOT_TEMPLATE_HTML_ELEMENT(spotData, { overlay: config === null || config === void 0 ? void 0 : config.overlay });
19894
+ if (!content) {
19895
+ this.eventService.handleSpotState(placementId, {
19896
+ state: {
19897
+ error: `Failed to inject spot element. Could not create element for type "${spotType}".`,
19898
+ mounted: false,
19899
+ loading: false,
19900
+ },
19901
+ });
19902
+ clearPlacement(placementId);
19903
+ return;
19904
+ }
19905
+ // Create the spot element
19906
+ const spotElement = this.elementService.createSpotElement({
19907
+ content,
19908
+ config: {
19909
+ fluid: config === null || config === void 0 ? void 0 : config.fluid,
19910
+ spot: spot.spot,
19911
+ width: spot.width,
19912
+ height: spot.height,
19913
+ minScale: (_a = config === null || config === void 0 ? void 0 : config.minScale) !== null && _a !== void 0 ? _a : 0.25, // Scale down to 25% of the original size
19914
+ },
19915
+ });
19916
+ if (!spotElement) {
19917
+ this.eventService.handleSpotState(placementId, {
19918
+ state: {
19919
+ error: `Failed to inject spot element. Could not create element for type "${spotType}".`,
19920
+ mounted: false,
19921
+ loading: false,
19922
+ },
19923
+ });
19924
+ clearPlacement(placementId);
19925
+ return;
19632
19926
  }
19927
+ this.eventService.registerSpot({
19928
+ spot: spotData,
19929
+ placementId,
19930
+ spotElement,
19931
+ });
19932
+ placement.replaceChildren(spotElement);
19933
+ this.eventService.handleSpotState(placementId, {
19934
+ dom: {
19935
+ spotElement,
19936
+ visibleOnViewport: false,
19937
+ },
19938
+ state: {
19939
+ mounted: true,
19940
+ loading: false,
19941
+ error: undefined,
19942
+ },
19943
+ });
19633
19944
  }
19634
- /** ========================= HELPER METHODS ========================= **/
19635
19945
  /**
19636
19946
  * Injects a carousel element with the provided spots into the placement.
19637
19947
  *
@@ -19643,11 +19953,12 @@ class LiquidCommerceRmnClient {
19643
19953
  */
19644
19954
  injectCarouselSpotElement(placement, spots, config) {
19645
19955
  var _a;
19956
+ const placementId = placement.id;
19646
19957
  const carouselSlides = [];
19647
19958
  for (const spotItem of spots) {
19648
- this.eventService.handleSpotState(placement.id, {
19959
+ this.eventService.handleSpotState(placementId, {
19649
19960
  identifier: {
19650
- placementId: placement.id,
19961
+ placementId,
19651
19962
  spotType: spotItem.spot,
19652
19963
  spotId: spotItem.id,
19653
19964
  },
@@ -19660,7 +19971,7 @@ class LiquidCommerceRmnClient {
19660
19971
  const spot = this.elementService.overrideSpotColors(spotItem, config === null || config === void 0 ? void 0 : config.colors);
19661
19972
  const content = SPOT_TEMPLATE_HTML_ELEMENT(spot, { overlay: config === null || config === void 0 ? void 0 : config.overlay });
19662
19973
  if (!content) {
19663
- this.eventService.handleSpotState(placement.id, {
19974
+ this.eventService.handleSpotState(placementId, {
19664
19975
  state: {
19665
19976
  error: `Failed to inject carousel spot item element. Could not create element for type "${spot.spot}".`,
19666
19977
  mounted: false,
@@ -19671,7 +19982,7 @@ class LiquidCommerceRmnClient {
19671
19982
  }
19672
19983
  this.eventService.registerSpot({
19673
19984
  spot,
19674
- placementId: placement.id,
19985
+ placementId,
19675
19986
  spotElement: content,
19676
19987
  });
19677
19988
  carouselSlides.push(content);
@@ -19693,18 +20004,18 @@ class LiquidCommerceRmnClient {
19693
20004
  },
19694
20005
  });
19695
20006
  if (!carouselElement) {
19696
- this.eventService.handleSpotState(placement.id, {
20007
+ this.eventService.handleSpotState(placementId, {
19697
20008
  state: {
19698
20009
  error: `Failed to inject spot carousel element. Could not create spot carousel element.`,
19699
20010
  mounted: false,
19700
20011
  loading: false,
19701
20012
  },
19702
20013
  });
19703
- this.clearPlacement(placement.id);
20014
+ clearPlacement(placementId);
19704
20015
  return;
19705
20016
  }
19706
20017
  placement.replaceChildren(carouselElement);
19707
- this.eventService.handleSpotState(placement.id, {
20018
+ this.eventService.handleSpotState(placementId, {
19708
20019
  dom: {
19709
20020
  spotElement: carouselElement,
19710
20021
  visibleOnViewport: false,
@@ -19716,94 +20027,6 @@ class LiquidCommerceRmnClient {
19716
20027
  },
19717
20028
  });
19718
20029
  }
19719
- /**
19720
- * Injects a single spot element into the provided placement.
19721
- *
19722
- * @param {IInjectSpotElement} injectItem - The inject item data.
19723
- * @param {HTMLElement} placement - The placement element.
19724
- * @param {ISpot} spot - The spot data.
19725
- * @param {IInjectSpotElementConfig} config - The configuration object.
19726
- *
19727
- * @return {void}
19728
- */
19729
- injectOneSpotElement(injectItem, placement, spot, config) {
19730
- var _a;
19731
- this.eventService.handleSpotState(injectItem.placementId, {
19732
- identifier: {
19733
- placementId: injectItem.placementId,
19734
- spotType: injectItem.spotType,
19735
- spotId: spot.id,
19736
- },
19737
- displayConfig: {
19738
- isSingleItem: true,
19739
- isCarousel: false,
19740
- isCarouselItem: false,
19741
- },
19742
- });
19743
- const spotData = this.elementService.overrideSpotColors(spot, config === null || config === void 0 ? void 0 : config.colors);
19744
- const content = SPOT_TEMPLATE_HTML_ELEMENT(spotData, { overlay: config === null || config === void 0 ? void 0 : config.overlay });
19745
- if (!content) {
19746
- this.eventService.handleSpotState(injectItem.placementId, {
19747
- state: {
19748
- error: `Failed to inject spot element. Could not create element for type "${injectItem.spotType}".`,
19749
- mounted: false,
19750
- loading: false,
19751
- },
19752
- });
19753
- this.clearPlacement(injectItem.placementId);
19754
- return;
19755
- }
19756
- // Create the spot element
19757
- const spotElement = this.elementService.createSpotElement({
19758
- content,
19759
- config: {
19760
- fluid: config === null || config === void 0 ? void 0 : config.fluid,
19761
- spot: spot.spot,
19762
- width: spot.width,
19763
- height: spot.height,
19764
- minScale: (_a = config === null || config === void 0 ? void 0 : config.minScale) !== null && _a !== void 0 ? _a : 0.25, // Scale down to 25% of the original size
19765
- },
19766
- });
19767
- if (!spotElement) {
19768
- this.eventService.handleSpotState(injectItem.placementId, {
19769
- state: {
19770
- error: `Failed to inject spot element. Could not create element for type "${injectItem.spotType}".`,
19771
- mounted: false,
19772
- loading: false,
19773
- },
19774
- });
19775
- this.clearPlacement(injectItem.placementId);
19776
- return;
19777
- }
19778
- this.eventService.registerSpot({
19779
- spot: spotData,
19780
- placementId: injectItem.placementId,
19781
- spotElement,
19782
- });
19783
- placement.replaceChildren(spotElement);
19784
- this.eventService.handleSpotState(injectItem.placementId, {
19785
- dom: {
19786
- spotElement,
19787
- visibleOnViewport: false,
19788
- },
19789
- state: {
19790
- mounted: true,
19791
- loading: false,
19792
- error: undefined,
19793
- },
19794
- });
19795
- }
19796
- /**
19797
- * Clears the placement element by removing all its children.
19798
- *
19799
- * @param {string} placementId - The placement id.
19800
- *
19801
- * @return {void}
19802
- */
19803
- clearPlacement(placementId) {
19804
- var _a;
19805
- (_a = document.getElementById(placementId)) === null || _a === void 0 ? void 0 : _a.replaceChildren();
19806
- }
19807
20030
  /**
19808
20031
  * Makes a selection request on our server based on the provided data.
19809
20032
  *
@@ -19811,77 +20034,21 @@ class LiquidCommerceRmnClient {
19811
20034
  *
19812
20035
  * @return {Promise<ISpots | {error: string}>} - The spots response object.
19813
20036
  */
19814
- async spotSelectionRequest(params) {
20037
+ async injectSpotSelectionRequest(params) {
19815
20038
  const { inject, filter, config } = params;
20039
+ const spots = inject.map((item) => ({
20040
+ placementId: item.placementId,
20041
+ spot: item.spotType,
20042
+ count: item === null || item === void 0 ? void 0 : item.count,
20043
+ ...item === null || item === void 0 ? void 0 : item.filter,
20044
+ }));
19816
20045
  const request = {
19817
20046
  url: config === null || config === void 0 ? void 0 : config.url,
19818
20047
  filter,
19819
- spots: inject.map((item) => ({
19820
- placementId: item.placementId,
19821
- spot: item.spotType,
19822
- count: item === null || item === void 0 ? void 0 : item.count,
19823
- ...item === null || item === void 0 ? void 0 : item.filter,
19824
- })),
20048
+ spots,
19825
20049
  };
19826
20050
  return this.spotSelection(request);
19827
20051
  }
19828
- /**
19829
- * Prevents duplicate placement ids in the inject data.
19830
- *
19831
- * @param {IInjectSpotElement[]} inject - The inject data.
19832
- *
19833
- * @throws {Error} - If a duplicate placement id is found.
19834
- *
19835
- * @return {void}
19836
- */
19837
- preventDuplicateSpotPlacementIds(inject) {
19838
- const placementIds = new Set();
19839
- for (const item of inject) {
19840
- if (placementIds.has(item.placementId)) {
19841
- this.eventService.handleSpotState(item.placementId, {
19842
- state: {
19843
- error: `Duplicate placement id (${item.placementId}) found. Please provide a unique placement id for each spot element.`,
19844
- },
19845
- });
19846
- return false;
19847
- }
19848
- placementIds.add(item.placementId);
19849
- }
19850
- return true;
19851
- }
19852
- /**
19853
- * Prevents non-existent spot types in the inject data.
19854
- *
19855
- * @param {IInjectSpotElement[]} inject - The inject data.
19856
- * @return {IInjectSpotElement[]} - The filtered inject data.
19857
- */
19858
- preventNonExistentSpotTypes(inject) {
19859
- const newInject = [];
19860
- for (const item of inject) {
19861
- if (!Object.values(RMN_SPOT_TYPE).includes(item.spotType)) {
19862
- this.eventService.handleSpotState(item.placementId, {
19863
- state: {
19864
- error: `Invalid spot type (${item.spotType}) found. Please provide a valid spot type for each spot element.`,
19865
- },
19866
- });
19867
- continue;
19868
- }
19869
- newInject.push(item);
19870
- }
19871
- return newInject;
19872
- }
19873
- // Use spot selection example data for private testing
19874
- useSpotSelectionExample(inject) {
19875
- const examples = { ...RB_SPOTS_SELECTION_EXAMPLE, ...IAB_SPOTS_SELECTION_EXAMPLE };
19876
- const data = {};
19877
- inject.map((item) => {
19878
- var _a, _b, _c;
19879
- data[item.placementId] = (_c = (_a = examples[item.spotType]) === null || _a === void 0 ? void 0 : _a.slice(0, (_b = item.count) !== null && _b !== void 0 ? _b : 1)) !== null && _c !== void 0 ? _c : [];
19880
- });
19881
- return new Promise((resolve) => {
19882
- resolve(data);
19883
- });
19884
- }
19885
20052
  }
19886
20053
  /**
19887
20054
  * Creates a new instance of the RmnClient.
@@ -19894,6 +20061,7 @@ class LiquidCommerceRmnClient {
19894
20061
  async function RmnClient(apiKey, config) {
19895
20062
  const authService = AuthService.getInstance(apiKey, config.env);
19896
20063
  const credentials = await authService.initialize();
20064
+ setUserId();
19897
20065
  return new LiquidCommerceRmnClient(credentials);
19898
20066
  }
19899
20067
  /**