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

Sign up to get free protection for your applications and to get access to all the features.
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
  /**