@searchspring/snap-controller 0.73.7 → 0.74.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/Autocomplete/AutocompleteController.d.ts +0 -1
- package/dist/cjs/Autocomplete/AutocompleteController.d.ts.map +1 -1
- package/dist/cjs/Autocomplete/AutocompleteController.js +91 -66
- package/dist/cjs/Finder/FinderController.d.ts.map +1 -1
- package/dist/cjs/Finder/FinderController.js +3 -0
- package/dist/cjs/Recommendation/RecommendationController.d.ts.map +1 -1
- package/dist/cjs/Recommendation/RecommendationController.js +67 -57
- package/dist/cjs/Search/SearchController.d.ts.map +1 -1
- package/dist/cjs/Search/SearchController.js +112 -64
- package/dist/cjs/utils/isClickWithinProductLink.d.ts.map +1 -1
- package/dist/cjs/utils/isClickWithinProductLink.js +13 -11
- package/dist/esm/Autocomplete/AutocompleteController.d.ts +0 -1
- package/dist/esm/Autocomplete/AutocompleteController.d.ts.map +1 -1
- package/dist/esm/Autocomplete/AutocompleteController.js +68 -26
- package/dist/esm/Finder/FinderController.d.ts.map +1 -1
- package/dist/esm/Finder/FinderController.js +3 -0
- package/dist/esm/Recommendation/RecommendationController.d.ts.map +1 -1
- package/dist/esm/Recommendation/RecommendationController.js +47 -21
- package/dist/esm/Search/SearchController.d.ts.map +1 -1
- package/dist/esm/Search/SearchController.js +76 -20
- package/dist/esm/utils/isClickWithinProductLink.d.ts.map +1 -1
- package/dist/esm/utils/isClickWithinProductLink.js +12 -11
- package/package.json +10 -10
|
@@ -16,6 +16,9 @@ const defaultConfig = {
|
|
|
16
16
|
selector: '',
|
|
17
17
|
action: '',
|
|
18
18
|
globals: {},
|
|
19
|
+
beacon: {
|
|
20
|
+
enabled: true,
|
|
21
|
+
},
|
|
19
22
|
settings: {
|
|
20
23
|
integratedSpellCorrection: false,
|
|
21
24
|
initializeFromUrl: true,
|
|
@@ -43,6 +46,10 @@ export class AutocompleteController extends AbstractController {
|
|
|
43
46
|
this.track = {
|
|
44
47
|
banner: {
|
|
45
48
|
impression: (_banner) => {
|
|
49
|
+
if (!_banner) {
|
|
50
|
+
this.log.warn('No banner provided to track.banner.impression');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
46
53
|
const { responseId, uid } = _banner;
|
|
47
54
|
if (this.events[responseId]?.banner?.[uid]?.impression) {
|
|
48
55
|
return;
|
|
@@ -54,11 +61,15 @@ export class AutocompleteController extends AbstractController {
|
|
|
54
61
|
results: [],
|
|
55
62
|
};
|
|
56
63
|
this.eventManager.fire('track.banner.impression', { controller: this, product: { uid }, trackEvent: data });
|
|
57
|
-
this.tracker.events.autocomplete.impression({ data, siteId: this.config.globals?.siteId });
|
|
64
|
+
this.config.beacon?.enabled && this.tracker.events.autocomplete.impression({ data, siteId: this.config.globals?.siteId });
|
|
58
65
|
this.events[responseId].banner[uid] = this.events[responseId].banner[uid] || {};
|
|
59
66
|
this.events[responseId].banner[uid].impression = true;
|
|
60
67
|
},
|
|
61
68
|
click: (e, banner) => {
|
|
69
|
+
if (!banner) {
|
|
70
|
+
this.log.warn('No banner provided to track.banner.click');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
62
73
|
const { responseId, uid } = banner;
|
|
63
74
|
if (isClickWithinBannerLink(e)) {
|
|
64
75
|
if (this.events?.[responseId]?.banner[uid]?.clickThrough) {
|
|
@@ -73,13 +84,17 @@ export class AutocompleteController extends AbstractController {
|
|
|
73
84
|
}
|
|
74
85
|
},
|
|
75
86
|
clickThrough: (e, { uid, responseId }) => {
|
|
87
|
+
if (!uid) {
|
|
88
|
+
this.log.warn('No banner uid provided to track.banner.clickThrough');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
76
91
|
const banner = { uid };
|
|
77
92
|
const data = {
|
|
78
93
|
responseId,
|
|
79
94
|
banners: [banner],
|
|
80
95
|
};
|
|
81
96
|
this.eventManager.fire('track.banner.clickThrough', { controller: this, event: e, product: { uid }, trackEvent: data });
|
|
82
|
-
this.tracker.events.autocomplete.clickThrough({ data, siteId: this.config.globals?.siteId });
|
|
97
|
+
this.config.beacon?.enabled && this.tracker.events.autocomplete.clickThrough({ data, siteId: this.config.globals?.siteId });
|
|
83
98
|
this.events[responseId].banner[uid] = this.events[responseId].banner[uid] || {};
|
|
84
99
|
this.events[responseId].banner[uid].clickThrough = true;
|
|
85
100
|
setTimeout(() => {
|
|
@@ -89,21 +104,34 @@ export class AutocompleteController extends AbstractController {
|
|
|
89
104
|
},
|
|
90
105
|
product: {
|
|
91
106
|
clickThrough: (e, result) => {
|
|
107
|
+
if (!result) {
|
|
108
|
+
this.log.warn('No result provided to track.product.clickThrough');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
92
111
|
const responseId = result.responseId;
|
|
112
|
+
const type = (['product', 'banner'].includes(result.type) ? result.type : 'product');
|
|
93
113
|
const item = {
|
|
94
|
-
type
|
|
95
|
-
uid: result.id,
|
|
96
|
-
|
|
97
|
-
|
|
114
|
+
type,
|
|
115
|
+
uid: result.id ? '' + result.id : '',
|
|
116
|
+
...(type === 'product'
|
|
117
|
+
? {
|
|
118
|
+
parentId: result.id ? '' + result.id : '',
|
|
119
|
+
sku: result.mappings.core?.sku ? '' + result.mappings.core?.sku : undefined,
|
|
120
|
+
}
|
|
121
|
+
: {}),
|
|
98
122
|
};
|
|
99
123
|
const data = {
|
|
100
124
|
responseId,
|
|
101
125
|
results: [item],
|
|
102
126
|
};
|
|
103
127
|
this.eventManager.fire('track.product.clickThrough', { controller: this, event: e, product: result, trackEvent: data });
|
|
104
|
-
this.tracker.events.autocomplete.clickThrough({ data, siteId: this.config.globals?.siteId });
|
|
128
|
+
this.config.beacon?.enabled && this.tracker.events.autocomplete.clickThrough({ data, siteId: this.config.globals?.siteId });
|
|
105
129
|
},
|
|
106
130
|
click: (e, result) => {
|
|
131
|
+
if (!result) {
|
|
132
|
+
this.log.warn('No result provided to track.product.click');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
107
135
|
const responseId = result.responseId;
|
|
108
136
|
if (result.type === 'banner' && isClickWithinBannerLink(e)) {
|
|
109
137
|
if (this.events?.[responseId]?.product[result.id]?.inlineBannerClickThrough) {
|
|
@@ -129,15 +157,24 @@ export class AutocompleteController extends AbstractController {
|
|
|
129
157
|
}
|
|
130
158
|
},
|
|
131
159
|
impression: (result) => {
|
|
160
|
+
if (!result) {
|
|
161
|
+
this.log.warn('No result provided to track.product.impression');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
132
164
|
const responseId = result.responseId;
|
|
133
165
|
if (this.events?.[responseId]?.product[result.id]?.impression) {
|
|
134
166
|
return;
|
|
135
167
|
}
|
|
168
|
+
const type = (['product', 'banner'].includes(result.type) ? result.type : 'product');
|
|
136
169
|
const item = {
|
|
137
|
-
type
|
|
138
|
-
uid: result.id,
|
|
139
|
-
|
|
140
|
-
|
|
170
|
+
type,
|
|
171
|
+
uid: result.id ? '' + result.id : '',
|
|
172
|
+
...(type === 'product'
|
|
173
|
+
? {
|
|
174
|
+
parentId: result.id ? '' + result.id : '',
|
|
175
|
+
sku: result.mappings.core?.sku ? '' + result.mappings.core?.sku : undefined,
|
|
176
|
+
}
|
|
177
|
+
: {}),
|
|
141
178
|
};
|
|
142
179
|
const data = {
|
|
143
180
|
responseId,
|
|
@@ -145,11 +182,15 @@ export class AutocompleteController extends AbstractController {
|
|
|
145
182
|
banners: [],
|
|
146
183
|
};
|
|
147
184
|
this.eventManager.fire('track.product.impression', { controller: this, product: result, trackEvent: data });
|
|
148
|
-
this.tracker.events.autocomplete.impression({ data, siteId: this.config.globals?.siteId });
|
|
185
|
+
this.config.beacon?.enabled && this.tracker.events.autocomplete.impression({ data, siteId: this.config.globals?.siteId });
|
|
149
186
|
this.events[responseId].product[result.id] = this.events[responseId].product[result.id] || {};
|
|
150
187
|
this.events[responseId].product[result.id].impression = true;
|
|
151
188
|
},
|
|
152
189
|
addToCart: (result) => {
|
|
190
|
+
if (!result) {
|
|
191
|
+
this.log.warn('No result provided to track.product.addToCart');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
153
194
|
const responseId = result.responseId;
|
|
154
195
|
const product = {
|
|
155
196
|
parentId: result.id,
|
|
@@ -163,16 +204,20 @@ export class AutocompleteController extends AbstractController {
|
|
|
163
204
|
results: [product],
|
|
164
205
|
};
|
|
165
206
|
this.eventManager.fire('track.product.addToCart', { controller: this, product: result, trackEvent: data });
|
|
166
|
-
this.tracker.events.autocomplete.addToCart({ data, siteId: this.config.globals?.siteId });
|
|
207
|
+
this.config.beacon?.enabled && this.tracker.events.autocomplete.addToCart({ data, siteId: this.config.globals?.siteId });
|
|
167
208
|
},
|
|
168
209
|
},
|
|
169
210
|
redirect: ({ redirectURL, responseId }) => {
|
|
211
|
+
if (!redirectURL) {
|
|
212
|
+
this.log.warn('No redirectURL provided to track.redirect');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
170
215
|
const data = {
|
|
171
216
|
responseId,
|
|
172
217
|
redirect: redirectURL,
|
|
173
218
|
};
|
|
174
219
|
this.eventManager.fire('track.redirect', { controller: this, redirectURL, trackEvent: data });
|
|
175
|
-
this.tracker.events.autocomplete.redirect({ data, siteId: this.config.globals?.siteId });
|
|
220
|
+
this.config.beacon?.enabled && this.tracker.events.autocomplete.redirect({ data, siteId: this.config.globals?.siteId });
|
|
176
221
|
},
|
|
177
222
|
};
|
|
178
223
|
this.handlers = {
|
|
@@ -425,7 +470,8 @@ export class AutocompleteController extends AbstractController {
|
|
|
425
470
|
this.log.profile(searchProfile);
|
|
426
471
|
const responseId = response.tracking.responseId;
|
|
427
472
|
this.events[responseId] = this.events[responseId] || { product: {}, banner: {} };
|
|
428
|
-
|
|
473
|
+
const previousResponseId = this.store.results[0]?.responseId;
|
|
474
|
+
if (previousResponseId && previousResponseId === responseId) {
|
|
429
475
|
const impressedResultIds = Object.keys(this.events[responseId].product || {}).filter((resultId) => this.events[responseId].product?.[resultId]?.impression);
|
|
430
476
|
this.events[responseId] = {
|
|
431
477
|
product: impressedResultIds.reduce((acc, resultId) => {
|
|
@@ -437,7 +483,6 @@ export class AutocompleteController extends AbstractController {
|
|
|
437
483
|
}
|
|
438
484
|
else {
|
|
439
485
|
this.events[responseId] = { product: {}, banner: {} };
|
|
440
|
-
this.lastSearchQuery = response.search?.query;
|
|
441
486
|
}
|
|
442
487
|
const afterSearchProfile = this.profiler.create({ type: 'event', name: 'afterSearch', context: params }).start();
|
|
443
488
|
try {
|
|
@@ -462,6 +507,8 @@ export class AutocompleteController extends AbstractController {
|
|
|
462
507
|
this.log.profile(afterSearchProfile);
|
|
463
508
|
// update the store
|
|
464
509
|
this.store.update(response);
|
|
510
|
+
const data = { responseId };
|
|
511
|
+
this.config.beacon?.enabled && this.tracker.events.autocomplete.render({ data, siteId: this.config.globals?.siteId });
|
|
465
512
|
const afterStoreProfile = this.profiler.create({ type: 'event', name: 'afterStore', context: params }).start();
|
|
466
513
|
try {
|
|
467
514
|
await this.eventManager.fire('afterStore', {
|
|
@@ -530,7 +577,11 @@ export class AutocompleteController extends AbstractController {
|
|
|
530
577
|
}
|
|
531
578
|
};
|
|
532
579
|
this.addToCart = async (_products) => {
|
|
533
|
-
const products = typeof _products
|
|
580
|
+
const products = typeof _products?.slice == 'function' ? _products.slice() : [_products];
|
|
581
|
+
if (!_products || products.length === 0) {
|
|
582
|
+
this.log.warn('No products provided to autocomplete controller.addToCart');
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
534
585
|
products.forEach((product) => {
|
|
535
586
|
this.track.product.addToCart(product);
|
|
536
587
|
});
|
|
@@ -553,15 +604,6 @@ export class AutocompleteController extends AbstractController {
|
|
|
553
604
|
type: 'session',
|
|
554
605
|
key: `ss-controller-${this.config.id}`,
|
|
555
606
|
});
|
|
556
|
-
this.eventManager.on('afterStore', async (search, next) => {
|
|
557
|
-
await next();
|
|
558
|
-
const controller = search.controller;
|
|
559
|
-
const responseId = search.response.tracking.responseId;
|
|
560
|
-
if (controller.store.loaded && !controller.store.error) {
|
|
561
|
-
const data = { responseId };
|
|
562
|
-
this.tracker.events.autocomplete.render({ data, siteId: this.config.globals?.siteId });
|
|
563
|
-
}
|
|
564
|
-
});
|
|
565
607
|
// add 'afterSearch' middleware
|
|
566
608
|
this.eventManager.on('afterSearch', async (ac, next) => {
|
|
567
609
|
await next();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FinderController.d.ts","sourceRoot":"","sources":["../../../src/Finder/FinderController.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,KAAK,EAAE,sBAAsB,EAAwB,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"FinderController.d.ts","sourceRoot":"","sources":["../../../src/Finder/FinderController.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEpE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAEjE,OAAO,KAAK,EAAE,sBAAsB,EAAwB,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAoBnH,qBAAa,gBAAiB,SAAQ,kBAAkB;IAChD,IAAI,kBAA0B;IAC7B,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,EAAE,sBAAsB,CAAC;gBAGtC,MAAM,EAAE,sBAAsB,EAC9B,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAC1F,OAAO,CAAC,EAAE,gBAAgB;IA+B3B,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CA6BhC;IAED,IAAI,QAAa,QAAQ,IAAI,CAAC,CAkB5B;IAEF,KAAK,QAAO,IAAI,CAId;IAEF,MAAM,QAAa,QAAQ,IAAI,CAAC,CAoI9B;CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecommendationController.d.ts","sourceRoot":"","sources":["../../../src/Recommendation/RecommendationController.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,OAAO,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAW3C,OAAO,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACjF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,KAAK,EAAE,8BAA8B,EAAE,kBAAkB,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"RecommendationController.d.ts","sourceRoot":"","sources":["../../../src/Recommendation/RecommendationController.ts"],"names":[],"mappings":"AAEA,OAAO,EAAa,OAAO,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAW3C,OAAO,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACjF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,KAAK,EAAE,8BAA8B,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAGrG,KAAK,0BAA0B,GAAG;IACjC,OAAO,EAAE;QACR,YAAY,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;QAChE,KAAK,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;QACzD,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;QAC/C,SAAS,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;KACrC,CAAC;CACF,CAAC;AAaF,qBAAa,wBAAyB,SAAQ,kBAAkB;IACxD,IAAI,kBAAkC;IACrC,KAAK,EAAE,mBAAmB,CAAC;IAC3B,MAAM,EAAE,8BAA8B,CAAC;IAE/C,OAAO,CAAC,MAAM,CAUP;gBAGN,MAAM,EAAE,8BAA8B,EACtC,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAC1F,OAAO,CAAC,EAAE,gBAAgB;IAwC3B,KAAK,EAAE,0BAA0B,CA+G/B;IAEF,IAAI,MAAM,IAAI,qBAAqB,CA4BlC;IAED,MAAM,QAAa,QAAQ,IAAI,CAAC,CAgI9B;IAEF,SAAS,cAAqB,OAAO,EAAE,GAAG,OAAO,KAAG,QAAQ,IAAI,CAAC,CAY/D;CACF"}
|
|
@@ -5,6 +5,9 @@ import { ControllerTypes } from '../types';
|
|
|
5
5
|
import { CLICK_DUPLICATION_TIMEOUT, isClickWithinProductLink } from '../utils/isClickWithinProductLink';
|
|
6
6
|
const defaultConfig = {
|
|
7
7
|
id: 'recommend',
|
|
8
|
+
beacon: {
|
|
9
|
+
enabled: true,
|
|
10
|
+
},
|
|
8
11
|
tag: '',
|
|
9
12
|
batched: true,
|
|
10
13
|
realtime: false,
|
|
@@ -18,14 +21,23 @@ export class RecommendationController extends AbstractController {
|
|
|
18
21
|
this.track = {
|
|
19
22
|
product: {
|
|
20
23
|
clickThrough: (e, result) => {
|
|
24
|
+
if (!result) {
|
|
25
|
+
this.log.warn('No result provided to track.product.clickThrough');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
21
28
|
const responseId = result.responseId;
|
|
22
29
|
if (this.events[responseId]?.product[result.id]?.productClickThrough)
|
|
23
30
|
return;
|
|
31
|
+
const type = (['product', 'banner'].includes(result.type) ? result.type : 'product');
|
|
24
32
|
const beaconResult = {
|
|
25
|
-
type
|
|
26
|
-
uid: result.id,
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
type,
|
|
34
|
+
uid: result.id ? '' + result.id : '',
|
|
35
|
+
...(type === 'product'
|
|
36
|
+
? {
|
|
37
|
+
parentId: result.id ? '' + result.id : '',
|
|
38
|
+
sku: result.mappings.core?.sku ? '' + result.mappings.core?.sku : undefined,
|
|
39
|
+
}
|
|
40
|
+
: {}),
|
|
29
41
|
};
|
|
30
42
|
const data = {
|
|
31
43
|
tag: this.store.profile.tag,
|
|
@@ -33,11 +45,15 @@ export class RecommendationController extends AbstractController {
|
|
|
33
45
|
results: [beaconResult],
|
|
34
46
|
};
|
|
35
47
|
this.eventManager.fire('track.product.clickThrough', { controller: this, event: e, product: result, trackEvent: data });
|
|
36
|
-
this.tracker.events.recommendations.clickThrough({ data, siteId: this.config.globals?.siteId });
|
|
48
|
+
this.config.beacon?.enabled && this.tracker.events.recommendations.clickThrough({ data, siteId: this.config.globals?.siteId });
|
|
37
49
|
this.events[responseId].product[result.id] = this.events[responseId].product[result.id] || {};
|
|
38
50
|
this.events[responseId].product[result.id].productClickThrough = true;
|
|
39
51
|
},
|
|
40
52
|
click: (e, result) => {
|
|
53
|
+
if (!result) {
|
|
54
|
+
this.log.warn('No result provided to track.product.click');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
41
57
|
const responseId = result.responseId;
|
|
42
58
|
if (result.type === 'banner') {
|
|
43
59
|
if (this.events[responseId]?.product[result.id]?.inlineBannerClickThrough) {
|
|
@@ -63,15 +79,24 @@ export class RecommendationController extends AbstractController {
|
|
|
63
79
|
}
|
|
64
80
|
},
|
|
65
81
|
impression: (result) => {
|
|
82
|
+
if (!result) {
|
|
83
|
+
this.log.warn('No result provided to track.product.impression');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
66
86
|
const responseId = result.responseId;
|
|
67
87
|
if (this.events[responseId]?.product[result.id]?.impression) {
|
|
68
88
|
return;
|
|
69
89
|
}
|
|
90
|
+
const type = (['product', 'banner'].includes(result.type) ? result.type : 'product');
|
|
70
91
|
const item = {
|
|
71
|
-
type
|
|
72
|
-
uid: result.id,
|
|
73
|
-
|
|
74
|
-
|
|
92
|
+
type,
|
|
93
|
+
uid: result.id ? '' + result.id : '',
|
|
94
|
+
...(type === 'product'
|
|
95
|
+
? {
|
|
96
|
+
parentId: result.id ? '' + result.id : '',
|
|
97
|
+
sku: result.mappings.core?.sku ? '' + result.mappings.core?.sku : undefined,
|
|
98
|
+
}
|
|
99
|
+
: {}),
|
|
75
100
|
};
|
|
76
101
|
const data = {
|
|
77
102
|
tag: this.store.profile.tag,
|
|
@@ -80,11 +105,15 @@ export class RecommendationController extends AbstractController {
|
|
|
80
105
|
banners: [],
|
|
81
106
|
};
|
|
82
107
|
this.eventManager.fire('track.product.impression', { controller: this, product: result, trackEvent: data });
|
|
83
|
-
this.tracker.events.recommendations.impression({ data, siteId: this.config.globals?.siteId });
|
|
108
|
+
this.config.beacon?.enabled && this.tracker.events.recommendations.impression({ data, siteId: this.config.globals?.siteId });
|
|
84
109
|
this.events[responseId].product[result.id] = this.events[responseId].product[result.id] || {};
|
|
85
110
|
this.events[responseId].product[result.id].impression = true;
|
|
86
111
|
},
|
|
87
112
|
addToCart: (result) => {
|
|
113
|
+
if (!result) {
|
|
114
|
+
this.log.warn('No result provided to track.product.addToCart');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
88
117
|
const responseId = result.responseId;
|
|
89
118
|
const product = {
|
|
90
119
|
parentId: result.id,
|
|
@@ -99,7 +128,7 @@ export class RecommendationController extends AbstractController {
|
|
|
99
128
|
results: [product],
|
|
100
129
|
};
|
|
101
130
|
this.eventManager.fire('track.product.addToCart', { controller: this, product: result, trackEvent: data });
|
|
102
|
-
this.tracker.events.recommendations.addToCart({ data, siteId: this.config.globals?.siteId });
|
|
131
|
+
this.config.beacon?.enabled && this.tracker.events.recommendations.addToCart({ data, siteId: this.config.globals?.siteId });
|
|
103
132
|
},
|
|
104
133
|
},
|
|
105
134
|
};
|
|
@@ -155,6 +184,8 @@ export class RecommendationController extends AbstractController {
|
|
|
155
184
|
this.log.profile(afterSearchProfile);
|
|
156
185
|
// update the store
|
|
157
186
|
this.store.update(response);
|
|
187
|
+
const data = { responseId, tag: this.store.profile.tag };
|
|
188
|
+
this.config.beacon?.enabled && this.tracker.events.recommendations.render({ data, siteId: this.config.globals?.siteId });
|
|
158
189
|
const afterStoreProfile = this.profiler.create({ type: 'event', name: 'afterStore', context: params }).start();
|
|
159
190
|
try {
|
|
160
191
|
await this.eventManager.fire('afterStore', {
|
|
@@ -223,7 +254,11 @@ export class RecommendationController extends AbstractController {
|
|
|
223
254
|
}
|
|
224
255
|
};
|
|
225
256
|
this.addToCart = async (_products) => {
|
|
226
|
-
const products = typeof _products
|
|
257
|
+
const products = typeof _products?.slice == 'function' ? _products.slice() : [_products];
|
|
258
|
+
if (!_products || products.length === 0) {
|
|
259
|
+
this.log.warn('No products provided to recommendation controller.addToCart');
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
227
262
|
products.forEach((product) => {
|
|
228
263
|
this.track.product.addToCart(product);
|
|
229
264
|
});
|
|
@@ -246,15 +281,6 @@ export class RecommendationController extends AbstractController {
|
|
|
246
281
|
// deep merge config with defaults
|
|
247
282
|
this.config = deepmerge(defaultConfig, this.config);
|
|
248
283
|
this.store.setConfig(this.config);
|
|
249
|
-
this.eventManager.on('afterStore', async (search, next) => {
|
|
250
|
-
await next();
|
|
251
|
-
const controller = search.controller;
|
|
252
|
-
const responseId = search.response.responseId;
|
|
253
|
-
if (controller.store.loaded && !controller.store.error) {
|
|
254
|
-
const data = { responseId, tag: controller.store.profile.tag };
|
|
255
|
-
this.tracker.events.recommendations.render({ data, siteId: this.config.globals?.siteId });
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
284
|
// add 'afterStore' middleware
|
|
259
285
|
// this.eventManager.on('afterStore', async (recommend: AfterStoreObj, next: Next): Promise<void | boolean> => {
|
|
260
286
|
// await next();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SearchController.d.ts","sourceRoot":"","sources":["../../../src/Search/SearchController.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAa,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAEpG,OAAO,EAAE,eAAe,EAAuB,MAAM,UAAU,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAc,MAAM,+BAA+B,CAAC;AAC9F,OAAO,KAAK,EACX,sBAAsB,EAGtB,kBAAkB,EAClB,gBAAgB,EAIhB,MAAM,UAAU,CAAC;AAElB,OAAO,EACN,KAAK,kBAAkB,EAYvB,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"SearchController.d.ts","sourceRoot":"","sources":["../../../src/Search/SearchController.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAa,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAEpG,OAAO,EAAE,eAAe,EAAuB,MAAM,UAAU,CAAC;AAEhE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAc,MAAM,+BAA+B,CAAC;AAC9F,OAAO,KAAK,EACX,sBAAsB,EAGtB,kBAAkB,EAClB,gBAAgB,EAIhB,MAAM,UAAU,CAAC;AAElB,OAAO,EACN,KAAK,kBAAkB,EAYvB,MAAM,2BAA2B,CAAC;AAyCnC,KAAK,kBAAkB,GAAG;IACzB,MAAM,EAAE;QACP,KAAK,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,mBAAmB,EAAE,0BAA0B,KAAK,IAAI,CAAC;QAChF,YAAY,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,mBAAmB,EAAE,0BAA0B,KAAK,IAAI,CAAC;QACvF,UAAU,EAAE,CAAC,mBAAmB,EAAE,0BAA0B,KAAK,IAAI,CAAC;KACtE,CAAC;IACF,OAAO,EAAE;QACR,YAAY,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;QAChE,KAAK,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;QACzD,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,KAAK,IAAI,CAAC;QAC/C,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;KACtC,CAAC;IACF,QAAQ,EAAE,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC7F,CAAC;AAEF,qBAAa,gBAAiB,SAAQ,kBAAkB;IAChD,IAAI,kBAA0B;IAC7B,KAAK,EAAE,WAAW,CAAC;IACnB,MAAM,EAAE,sBAAsB,CAAC;IACvC,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,CAAC,eAAe,CAAwC;IAC/D,OAAO,CAAC,IAAI,CAEV;IACF,OAAO,CAAC,MAAM,CAgBP;gBAGN,MAAM,EAAE,sBAAsB,EAC9B,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,kBAAkB,EAC1F,OAAO,CAAC,EAAE,gBAAgB;IAiP3B,KAAK,EAAE,kBAAkB,CA+MvB;IAEF,IAAI,MAAM,IAAI,kBAAkB,CA8C/B;IAED,MAAM,QAAa,QAAQ,IAAI,CAAC,CA6N9B;IAEF,SAAS,cAAqB,OAAO,EAAE,GAAG,OAAO,KAAG,QAAQ,IAAI,CAAC,CAY/D;CACF;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,kBAAkB,GAAG,kBAAkB,CAiBxF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,SAAI,GAAG,MAAM,GAAG,SAAS,CAuCvG"}
|
|
@@ -11,6 +11,9 @@ const BACKGROUND_FILTERS_VALUE_FLAGS = [1, 0, '1', '0', 'true', 'false', true, f
|
|
|
11
11
|
const defaultConfig = {
|
|
12
12
|
id: 'search',
|
|
13
13
|
globals: {},
|
|
14
|
+
beacon: {
|
|
15
|
+
enabled: true,
|
|
16
|
+
},
|
|
14
17
|
settings: {
|
|
15
18
|
redirects: {
|
|
16
19
|
merchandising: true,
|
|
@@ -36,6 +39,10 @@ export class SearchController extends AbstractController {
|
|
|
36
39
|
this.track = {
|
|
37
40
|
banner: {
|
|
38
41
|
impression: ({ uid, responseId }) => {
|
|
42
|
+
if (!uid) {
|
|
43
|
+
this.log.warn('No banner provided to track.banner.impression');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
39
46
|
if (this.events[responseId]?.banner[uid]?.impression) {
|
|
40
47
|
return;
|
|
41
48
|
}
|
|
@@ -46,11 +53,15 @@ export class SearchController extends AbstractController {
|
|
|
46
53
|
results: [],
|
|
47
54
|
};
|
|
48
55
|
this.eventManager.fire('track.banner.impression', { controller: this, product: { uid }, trackEvent: data });
|
|
49
|
-
this.tracker.events[this.page.type].impression({ data, siteId: this.config.globals?.siteId });
|
|
56
|
+
this.config.beacon?.enabled && this.tracker.events[this.page.type].impression({ data, siteId: this.config.globals?.siteId });
|
|
50
57
|
this.events[responseId].banner[uid] = this.events[responseId].banner[uid] || {};
|
|
51
58
|
this.events[responseId].banner[uid].impression = true;
|
|
52
59
|
},
|
|
53
60
|
click: (e, banner) => {
|
|
61
|
+
if (!banner) {
|
|
62
|
+
this.log.warn('No banner provided to track.banner.click');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
54
65
|
const { responseId, uid } = banner;
|
|
55
66
|
if (isClickWithinBannerLink(e)) {
|
|
56
67
|
if (this.events?.[responseId]?.banner[uid]?.clickThrough) {
|
|
@@ -65,13 +76,17 @@ export class SearchController extends AbstractController {
|
|
|
65
76
|
}
|
|
66
77
|
},
|
|
67
78
|
clickThrough: (e, { uid, responseId }) => {
|
|
79
|
+
if (!uid) {
|
|
80
|
+
this.log.warn('No banner provided to track.banner.clickThrough');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
68
83
|
const banner = { uid };
|
|
69
84
|
const data = {
|
|
70
85
|
responseId,
|
|
71
86
|
banners: [banner],
|
|
72
87
|
};
|
|
73
88
|
this.eventManager.fire('track.banner.clickThrough', { controller: this, event: e, product: { uid }, trackEvent: data });
|
|
74
|
-
this.tracker.events[this.page.type].clickThrough({ data, siteId: this.config.globals?.siteId });
|
|
89
|
+
this.config.beacon?.enabled && this.tracker.events[this.page.type].clickThrough({ data, siteId: this.config.globals?.siteId });
|
|
75
90
|
this.events[responseId].banner[uid] = this.events[responseId].banner[uid] || {};
|
|
76
91
|
this.events[responseId].banner[uid].clickThrough = true;
|
|
77
92
|
setTimeout(() => {
|
|
@@ -81,6 +96,10 @@ export class SearchController extends AbstractController {
|
|
|
81
96
|
},
|
|
82
97
|
product: {
|
|
83
98
|
clickThrough: (e, result) => {
|
|
99
|
+
if (!result) {
|
|
100
|
+
this.log.warn('No result provided to track.product.clickThrough');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
84
103
|
const responseId = result.responseId;
|
|
85
104
|
const target = e.target;
|
|
86
105
|
const resultHref = result.display?.mappings.core?.url || result.mappings.core?.url || '';
|
|
@@ -108,20 +127,29 @@ export class SearchController extends AbstractController {
|
|
|
108
127
|
}
|
|
109
128
|
// store position data or empty object
|
|
110
129
|
this.storage.set('scrollMap', scrollMap);
|
|
130
|
+
const type = (['product', 'banner'].includes(result.type) ? result.type : 'product');
|
|
111
131
|
const item = {
|
|
112
|
-
type
|
|
113
|
-
uid: result.id,
|
|
114
|
-
|
|
115
|
-
|
|
132
|
+
type,
|
|
133
|
+
uid: result.id ? '' + result.id : '',
|
|
134
|
+
...(type === 'product'
|
|
135
|
+
? {
|
|
136
|
+
parentId: result.id ? '' + result.id : '',
|
|
137
|
+
sku: result.mappings.core?.sku ? '' + result.mappings.core?.sku : undefined,
|
|
138
|
+
}
|
|
139
|
+
: {}),
|
|
116
140
|
};
|
|
117
141
|
const data = {
|
|
118
142
|
responseId,
|
|
119
143
|
results: [item],
|
|
120
144
|
};
|
|
121
145
|
this.eventManager.fire('track.product.clickThrough', { controller: this, event: e, product: result, trackEvent: data });
|
|
122
|
-
this.tracker.events[this.page.type].clickThrough({ data, siteId: this.config.globals?.siteId });
|
|
146
|
+
this.config.beacon?.enabled && this.tracker.events[this.page.type].clickThrough({ data, siteId: this.config.globals?.siteId });
|
|
123
147
|
},
|
|
124
148
|
click: (e, result) => {
|
|
149
|
+
if (!result) {
|
|
150
|
+
this.log.warn('No result provided to track.product.click');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
125
153
|
const responseId = result.responseId;
|
|
126
154
|
if (result.type === 'banner' && isClickWithinBannerLink(e)) {
|
|
127
155
|
if (this.events?.[responseId]?.product[result.id]?.inlineBannerClickThrough) {
|
|
@@ -147,15 +175,24 @@ export class SearchController extends AbstractController {
|
|
|
147
175
|
}
|
|
148
176
|
},
|
|
149
177
|
impression: (result) => {
|
|
178
|
+
if (!result) {
|
|
179
|
+
this.log.warn('No result provided to track.product.impression');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
150
182
|
const responseId = result.responseId;
|
|
151
183
|
if (this.events[responseId]?.product[result.id]?.impression) {
|
|
152
184
|
return;
|
|
153
185
|
}
|
|
186
|
+
const type = (['product', 'banner'].includes(result.type) ? result.type : 'product');
|
|
154
187
|
const item = {
|
|
155
|
-
type
|
|
156
|
-
uid: result.id,
|
|
157
|
-
|
|
158
|
-
|
|
188
|
+
type,
|
|
189
|
+
uid: result.id ? '' + result.id : '',
|
|
190
|
+
...(type === 'product'
|
|
191
|
+
? {
|
|
192
|
+
parentId: result.id ? '' + result.id : '',
|
|
193
|
+
sku: result.mappings.core?.sku ? '' + result.mappings.core?.sku : undefined,
|
|
194
|
+
}
|
|
195
|
+
: {}),
|
|
159
196
|
};
|
|
160
197
|
const data = {
|
|
161
198
|
responseId,
|
|
@@ -163,11 +200,15 @@ export class SearchController extends AbstractController {
|
|
|
163
200
|
banners: [],
|
|
164
201
|
};
|
|
165
202
|
this.eventManager.fire('track.product.impression', { controller: this, product: result, trackEvent: data });
|
|
166
|
-
this.tracker.events[this.page.type].impression({ data, siteId: this.config.globals?.siteId });
|
|
203
|
+
this.config.beacon?.enabled && this.tracker.events[this.page.type].impression({ data, siteId: this.config.globals?.siteId });
|
|
167
204
|
this.events[responseId].product[result.id] = this.events[responseId].product[result.id] || {};
|
|
168
205
|
this.events[responseId].product[result.id].impression = true;
|
|
169
206
|
},
|
|
170
207
|
addToCart: (result) => {
|
|
208
|
+
if (!result) {
|
|
209
|
+
this.log.warn('No result provided to track.product.addToCart');
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
171
212
|
const responseId = result.responseId;
|
|
172
213
|
const product = {
|
|
173
214
|
parentId: result.id,
|
|
@@ -181,16 +222,20 @@ export class SearchController extends AbstractController {
|
|
|
181
222
|
results: [product],
|
|
182
223
|
};
|
|
183
224
|
this.eventManager.fire('track.product.addToCart', { controller: this, product: result, trackEvent: data });
|
|
184
|
-
this.tracker.events[this.page.type].addToCart({ data, siteId: this.config.globals?.siteId });
|
|
225
|
+
this.config.beacon?.enabled && this.tracker.events[this.page.type].addToCart({ data, siteId: this.config.globals?.siteId });
|
|
185
226
|
},
|
|
186
227
|
},
|
|
187
228
|
redirect: ({ redirectURL, responseId }) => {
|
|
229
|
+
if (!redirectURL) {
|
|
230
|
+
this.log.warn('No redirectURL provided to track.redirect');
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
188
233
|
const data = {
|
|
189
234
|
responseId,
|
|
190
235
|
redirect: redirectURL,
|
|
191
236
|
};
|
|
192
237
|
this.eventManager.fire('track.redirect', { controller: this, redirectURL, trackEvent: data });
|
|
193
|
-
this.tracker.events.search.redirect({ data, siteId: this.config.globals?.siteId });
|
|
238
|
+
this.config.beacon?.enabled && this.tracker.events.search.redirect({ data, siteId: this.config.globals?.siteId });
|
|
194
239
|
},
|
|
195
240
|
};
|
|
196
241
|
this.search = async () => {
|
|
@@ -324,6 +369,8 @@ export class SearchController extends AbstractController {
|
|
|
324
369
|
this.previousResults = JSON.parse(JSON.stringify(response.results));
|
|
325
370
|
// update the store
|
|
326
371
|
this.store.update(response);
|
|
372
|
+
const data = { responseId: response.tracking.responseId };
|
|
373
|
+
this.config.beacon?.enabled && this.tracker.events[this.page.type].render({ data, siteId: this.config.globals?.siteId });
|
|
327
374
|
const afterStoreProfile = this.profiler.create({ type: 'event', name: 'afterStore', context: params }).start();
|
|
328
375
|
try {
|
|
329
376
|
await this.eventManager.fire('afterStore', {
|
|
@@ -392,7 +439,11 @@ export class SearchController extends AbstractController {
|
|
|
392
439
|
}
|
|
393
440
|
};
|
|
394
441
|
this.addToCart = async (_products) => {
|
|
395
|
-
const products = typeof _products
|
|
442
|
+
const products = typeof _products?.slice == 'function' ? _products.slice() : [_products];
|
|
443
|
+
if (!_products || products.length === 0) {
|
|
444
|
+
this.log.warn('No products provided to search controller.addToCart');
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
396
447
|
products.forEach((product) => {
|
|
397
448
|
this.track.product.addToCart(product);
|
|
398
449
|
});
|
|
@@ -514,10 +565,7 @@ export class SearchController extends AbstractController {
|
|
|
514
565
|
this.eventManager.on('afterStore', async (search, next) => {
|
|
515
566
|
await next();
|
|
516
567
|
const controller = search.controller;
|
|
517
|
-
const responseId = search.response.tracking.responseId;
|
|
518
568
|
if (controller.store.loaded && !controller.store.error) {
|
|
519
|
-
const data = { responseId };
|
|
520
|
-
this.tracker.events[this.page.type].render({ data, siteId: this.config.globals?.siteId });
|
|
521
569
|
const config = search.controller.config;
|
|
522
570
|
const nonBackgroundFilters = search?.request?.filters?.filter((filter) => !filter.background);
|
|
523
571
|
if (config?.settings?.redirects?.singleResult &&
|
|
@@ -666,8 +714,16 @@ export function generateHrefSelector(element, href, levels = 7) {
|
|
|
666
714
|
let level = 0;
|
|
667
715
|
let elem = element;
|
|
668
716
|
while (elem && level <= levels) {
|
|
669
|
-
|
|
670
|
-
|
|
717
|
+
let innerHrefElem = null;
|
|
718
|
+
try {
|
|
719
|
+
innerHrefElem = elem.querySelector(`[href*="${href}"]`);
|
|
720
|
+
}
|
|
721
|
+
catch (e) {
|
|
722
|
+
try {
|
|
723
|
+
innerHrefElem = elem.querySelector(cssEscape(`[href*="${href}"]`));
|
|
724
|
+
}
|
|
725
|
+
catch { }
|
|
726
|
+
}
|
|
671
727
|
if (innerHrefElem) {
|
|
672
728
|
// innerHrefElem was found! now get selectors up to elem that contained it
|
|
673
729
|
let selector = '';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"isClickWithinProductLink.d.ts","sourceRoot":"","sources":["../../../src/utils/isClickWithinProductLink.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAE7D,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,gCAAgC,KAAK,CAAC;AAEnD,eAAO,MAAM,wBAAwB,MAAO,UAAU,UAAU,OAAO,KAAG,
|
|
1
|
+
{"version":3,"file":"isClickWithinProductLink.d.ts","sourceRoot":"","sources":["../../../src/utils/isClickWithinProductLink.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAE7D,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,gCAAgC,KAAK,CAAC;AAEnD,eAAO,MAAM,wBAAwB,MAAO,UAAU,UAAU,OAAO,KAAG,OAoBzE,CAAC"}
|