@searchspring/snap-controller 0.73.7 → 0.75.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.
Files changed (25) hide show
  1. package/dist/cjs/Autocomplete/AutocompleteController.d.ts +0 -1
  2. package/dist/cjs/Autocomplete/AutocompleteController.d.ts.map +1 -1
  3. package/dist/cjs/Autocomplete/AutocompleteController.js +124 -68
  4. package/dist/cjs/Finder/FinderController.d.ts.map +1 -1
  5. package/dist/cjs/Finder/FinderController.js +3 -0
  6. package/dist/cjs/Recommendation/RecommendationController.d.ts.map +1 -1
  7. package/dist/cjs/Recommendation/RecommendationController.js +84 -58
  8. package/dist/cjs/Search/SearchController.d.ts.map +1 -1
  9. package/dist/cjs/Search/SearchController.js +142 -66
  10. package/dist/cjs/utils/isClickWithinProductLink.d.ts +1 -1
  11. package/dist/cjs/utils/isClickWithinProductLink.d.ts.map +1 -1
  12. package/dist/cjs/utils/isClickWithinProductLink.js +14 -12
  13. package/dist/esm/Autocomplete/AutocompleteController.d.ts +0 -1
  14. package/dist/esm/Autocomplete/AutocompleteController.d.ts.map +1 -1
  15. package/dist/esm/Autocomplete/AutocompleteController.js +101 -28
  16. package/dist/esm/Finder/FinderController.d.ts.map +1 -1
  17. package/dist/esm/Finder/FinderController.js +3 -0
  18. package/dist/esm/Recommendation/RecommendationController.d.ts.map +1 -1
  19. package/dist/esm/Recommendation/RecommendationController.js +64 -22
  20. package/dist/esm/Search/SearchController.d.ts.map +1 -1
  21. package/dist/esm/Search/SearchController.js +106 -22
  22. package/dist/esm/utils/isClickWithinProductLink.d.ts +1 -1
  23. package/dist/esm/utils/isClickWithinProductLink.d.ts.map +1 -1
  24. package/dist/esm/utils/isClickWithinProductLink.js +13 -12
  25. 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,8 +46,16 @@ 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
- if (this.events[responseId]?.banner?.[uid]?.impression) {
54
+ if (!this.events[responseId]) {
55
+ this.log.warn('No responseId found in controller, ensure correct controller is used');
56
+ return;
57
+ }
58
+ else if (this.events?.[responseId]?.banner?.[uid]?.impression) {
48
59
  return;
49
60
  }
50
61
  const banner = { uid };
@@ -54,12 +65,20 @@ export class AutocompleteController extends AbstractController {
54
65
  results: [],
55
66
  };
56
67
  this.eventManager.fire('track.banner.impression', { controller: this, product: { uid }, trackEvent: data });
57
- this.tracker.events.autocomplete.impression({ data, siteId: this.config.globals?.siteId });
68
+ this.config.beacon?.enabled && this.tracker.events.autocomplete.impression({ data, siteId: this.config.globals?.siteId });
58
69
  this.events[responseId].banner[uid] = this.events[responseId].banner[uid] || {};
59
70
  this.events[responseId].banner[uid].impression = true;
60
71
  },
61
72
  click: (e, banner) => {
73
+ if (!banner) {
74
+ this.log.warn('No banner provided to track.banner.click');
75
+ return;
76
+ }
62
77
  const { responseId, uid } = banner;
78
+ if (!this.events[responseId]) {
79
+ this.log.warn('No responseId found in controller, ensure correct controller is used');
80
+ return;
81
+ }
63
82
  if (isClickWithinBannerLink(e)) {
64
83
  if (this.events?.[responseId]?.banner[uid]?.clickThrough) {
65
84
  return;
@@ -73,13 +92,21 @@ export class AutocompleteController extends AbstractController {
73
92
  }
74
93
  },
75
94
  clickThrough: (e, { uid, responseId }) => {
95
+ if (!uid) {
96
+ this.log.warn('No banner uid provided to track.banner.clickThrough');
97
+ return;
98
+ }
99
+ if (!this.events[responseId]) {
100
+ this.log.warn('No responseId found in controller, ensure correct controller is used');
101
+ return;
102
+ }
76
103
  const banner = { uid };
77
104
  const data = {
78
105
  responseId,
79
106
  banners: [banner],
80
107
  };
81
108
  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 });
109
+ this.config.beacon?.enabled && this.tracker.events.autocomplete.clickThrough({ data, siteId: this.config.globals?.siteId });
83
110
  this.events[responseId].banner[uid] = this.events[responseId].banner[uid] || {};
84
111
  this.events[responseId].banner[uid].clickThrough = true;
85
112
  setTimeout(() => {
@@ -89,22 +116,43 @@ export class AutocompleteController extends AbstractController {
89
116
  },
90
117
  product: {
91
118
  clickThrough: (e, result) => {
119
+ if (!result) {
120
+ this.log.warn('No result provided to track.product.clickThrough');
121
+ return;
122
+ }
92
123
  const responseId = result.responseId;
124
+ if (!this.events[responseId]) {
125
+ this.log.warn('No responseId found in controller, ensure correct controller is used');
126
+ return;
127
+ }
128
+ const type = (['product', 'banner'].includes(result.type) ? result.type : 'product');
93
129
  const item = {
94
- type: result.type,
95
- uid: result.id,
96
- parentId: result.id,
97
- sku: result.mappings.core?.sku,
130
+ type,
131
+ uid: result.id ? '' + result.id : '',
132
+ ...(type === 'product'
133
+ ? {
134
+ parentId: result.id ? '' + result.id : '',
135
+ sku: result.mappings.core?.sku ? '' + result.mappings.core?.sku : undefined,
136
+ }
137
+ : {}),
98
138
  };
99
139
  const data = {
100
140
  responseId,
101
141
  results: [item],
102
142
  };
103
143
  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 });
144
+ this.config.beacon?.enabled && this.tracker.events.autocomplete.clickThrough({ data, siteId: this.config.globals?.siteId });
105
145
  },
106
146
  click: (e, result) => {
147
+ if (!result) {
148
+ this.log.warn('No result provided to track.product.click');
149
+ return;
150
+ }
107
151
  const responseId = result.responseId;
152
+ if (!this.events[responseId]) {
153
+ this.log.warn('No responseId found in controller, ensure correct controller is used');
154
+ return;
155
+ }
108
156
  if (result.type === 'banner' && isClickWithinBannerLink(e)) {
109
157
  if (this.events?.[responseId]?.product[result.id]?.inlineBannerClickThrough) {
110
158
  return;
@@ -129,15 +177,28 @@ export class AutocompleteController extends AbstractController {
129
177
  }
130
178
  },
131
179
  impression: (result) => {
180
+ if (!result) {
181
+ this.log.warn('No result provided to track.product.impression');
182
+ return;
183
+ }
132
184
  const responseId = result.responseId;
133
- if (this.events?.[responseId]?.product[result.id]?.impression) {
185
+ if (!this.events[responseId]) {
186
+ this.log.warn('No responseId found in controller, ensure correct controller is used');
187
+ return;
188
+ }
189
+ else if (this.events?.[responseId]?.product[result.id]?.impression) {
134
190
  return;
135
191
  }
192
+ const type = (['product', 'banner'].includes(result.type) ? result.type : 'product');
136
193
  const item = {
137
- type: result.type,
138
- uid: result.id,
139
- parentId: result.id,
140
- sku: result.mappings.core?.sku,
194
+ type,
195
+ uid: result.id ? '' + result.id : '',
196
+ ...(type === 'product'
197
+ ? {
198
+ parentId: result.id ? '' + result.id : '',
199
+ sku: result.mappings.core?.sku ? '' + result.mappings.core?.sku : undefined,
200
+ }
201
+ : {}),
141
202
  };
142
203
  const data = {
143
204
  responseId,
@@ -145,12 +206,20 @@ export class AutocompleteController extends AbstractController {
145
206
  banners: [],
146
207
  };
147
208
  this.eventManager.fire('track.product.impression', { controller: this, product: result, trackEvent: data });
148
- this.tracker.events.autocomplete.impression({ data, siteId: this.config.globals?.siteId });
209
+ this.config.beacon?.enabled && this.tracker.events.autocomplete.impression({ data, siteId: this.config.globals?.siteId });
149
210
  this.events[responseId].product[result.id] = this.events[responseId].product[result.id] || {};
150
211
  this.events[responseId].product[result.id].impression = true;
151
212
  },
152
213
  addToCart: (result) => {
214
+ if (!result) {
215
+ this.log.warn('No result provided to track.product.addToCart');
216
+ return;
217
+ }
153
218
  const responseId = result.responseId;
219
+ if (!this.events[responseId]) {
220
+ this.log.warn('No responseId found in controller, ensure correct controller is used');
221
+ return;
222
+ }
154
223
  const product = {
155
224
  parentId: result.id,
156
225
  uid: result.id,
@@ -163,16 +232,20 @@ export class AutocompleteController extends AbstractController {
163
232
  results: [product],
164
233
  };
165
234
  this.eventManager.fire('track.product.addToCart', { controller: this, product: result, trackEvent: data });
166
- this.tracker.events.autocomplete.addToCart({ data, siteId: this.config.globals?.siteId });
235
+ this.config.beacon?.enabled && this.tracker.events.autocomplete.addToCart({ data, siteId: this.config.globals?.siteId });
167
236
  },
168
237
  },
169
238
  redirect: ({ redirectURL, responseId }) => {
239
+ if (!redirectURL) {
240
+ this.log.warn('No redirectURL provided to track.redirect');
241
+ return;
242
+ }
170
243
  const data = {
171
244
  responseId,
172
245
  redirect: redirectURL,
173
246
  };
174
247
  this.eventManager.fire('track.redirect', { controller: this, redirectURL, trackEvent: data });
175
- this.tracker.events.autocomplete.redirect({ data, siteId: this.config.globals?.siteId });
248
+ this.config.beacon?.enabled && this.tracker.events.autocomplete.redirect({ data, siteId: this.config.globals?.siteId });
176
249
  },
177
250
  };
178
251
  this.handlers = {
@@ -425,7 +498,9 @@ export class AutocompleteController extends AbstractController {
425
498
  this.log.profile(searchProfile);
426
499
  const responseId = response.tracking.responseId;
427
500
  this.events[responseId] = this.events[responseId] || { product: {}, banner: {} };
428
- if (response.search?.query === this.lastSearchQuery) {
501
+ const previousResponseId = this.store.results[0]?.responseId;
502
+ const repeatedSearch = previousResponseId && previousResponseId === responseId;
503
+ if (repeatedSearch) {
429
504
  const impressedResultIds = Object.keys(this.events[responseId].product || {}).filter((resultId) => this.events[responseId].product?.[resultId]?.impression);
430
505
  this.events[responseId] = {
431
506
  product: impressedResultIds.reduce((acc, resultId) => {
@@ -437,7 +512,6 @@ export class AutocompleteController extends AbstractController {
437
512
  }
438
513
  else {
439
514
  this.events[responseId] = { product: {}, banner: {} };
440
- this.lastSearchQuery = response.search?.query;
441
515
  }
442
516
  const afterSearchProfile = this.profiler.create({ type: 'event', name: 'afterSearch', context: params }).start();
443
517
  try {
@@ -462,6 +536,10 @@ export class AutocompleteController extends AbstractController {
462
536
  this.log.profile(afterSearchProfile);
463
537
  // update the store
464
538
  this.store.update(response);
539
+ if (!repeatedSearch) {
540
+ const data = { responseId };
541
+ this.config.beacon?.enabled && this.tracker.events.autocomplete.render({ data, siteId: this.config.globals?.siteId });
542
+ }
465
543
  const afterStoreProfile = this.profiler.create({ type: 'event', name: 'afterStore', context: params }).start();
466
544
  try {
467
545
  await this.eventManager.fire('afterStore', {
@@ -530,7 +608,11 @@ export class AutocompleteController extends AbstractController {
530
608
  }
531
609
  };
532
610
  this.addToCart = async (_products) => {
533
- const products = typeof _products.slice == 'function' ? _products.slice() : [_products];
611
+ const products = typeof _products?.slice == 'function' ? _products.slice() : [_products];
612
+ if (!_products || products.length === 0) {
613
+ this.log.warn('No products provided to autocomplete controller.addToCart');
614
+ return;
615
+ }
534
616
  products.forEach((product) => {
535
617
  this.track.product.addToCart(product);
536
618
  });
@@ -553,15 +635,6 @@ export class AutocompleteController extends AbstractController {
553
635
  type: 'session',
554
636
  key: `ss-controller-${this.config.id}`,
555
637
  });
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
638
  // add 'afterSearch' middleware
566
639
  this.eventManager.on('afterSearch', async (ac, next) => {
567
640
  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;AAiBnH,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
+ {"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"}
@@ -5,6 +5,9 @@ import { getSearchParams } from '../utils/getParams';
5
5
  import { ControllerTypes } from '../types';
6
6
  const defaultConfig = {
7
7
  id: 'finder',
8
+ beacon: {
9
+ enabled: true,
10
+ },
8
11
  globals: {
9
12
  pagination: {
10
13
  pageSize: 0,
@@ -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,EAAiB,MAAM,UAAU,CAAC;AAIpH,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;AAUF,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;IAkD3B,KAAK,EAAE,0BAA0B,CAqF/B;IAEF,IAAI,MAAM,IAAI,qBAAqB,CA4BlC;IAED,MAAM,QAAa,QAAQ,IAAI,CAAC,CA6H9B;IAEF,SAAS,cAAqB,OAAO,EAAE,GAAG,OAAO,KAAG,QAAQ,IAAI,CAAC,CAQ/D;CACF"}
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,CA0I/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,27 @@ 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;
29
+ if (!this.events[responseId]) {
30
+ this.log.warn('No responseId found in controller, ensure correct controller is used');
31
+ return;
32
+ }
22
33
  if (this.events[responseId]?.product[result.id]?.productClickThrough)
23
34
  return;
35
+ const type = (['product', 'banner'].includes(result.type) ? result.type : 'product');
24
36
  const beaconResult = {
25
- type: result.type,
26
- uid: result.id,
27
- parentId: result.id,
28
- sku: result.mappings.core?.sku,
37
+ type,
38
+ uid: result.id ? '' + result.id : '',
39
+ ...(type === 'product'
40
+ ? {
41
+ parentId: result.id ? '' + result.id : '',
42
+ sku: result.mappings.core?.sku ? '' + result.mappings.core?.sku : undefined,
43
+ }
44
+ : {}),
29
45
  };
30
46
  const data = {
31
47
  tag: this.store.profile.tag,
@@ -33,12 +49,20 @@ export class RecommendationController extends AbstractController {
33
49
  results: [beaconResult],
34
50
  };
35
51
  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 });
52
+ this.config.beacon?.enabled && this.tracker.events.recommendations.clickThrough({ data, siteId: this.config.globals?.siteId });
37
53
  this.events[responseId].product[result.id] = this.events[responseId].product[result.id] || {};
38
54
  this.events[responseId].product[result.id].productClickThrough = true;
39
55
  },
40
56
  click: (e, result) => {
57
+ if (!result) {
58
+ this.log.warn('No result provided to track.product.click');
59
+ return;
60
+ }
41
61
  const responseId = result.responseId;
62
+ if (!this.events[responseId]) {
63
+ this.log.warn('No responseId found in controller, ensure correct controller is used');
64
+ return;
65
+ }
42
66
  if (result.type === 'banner') {
43
67
  if (this.events[responseId]?.product[result.id]?.inlineBannerClickThrough) {
44
68
  return;
@@ -63,15 +87,28 @@ export class RecommendationController extends AbstractController {
63
87
  }
64
88
  },
65
89
  impression: (result) => {
90
+ if (!result) {
91
+ this.log.warn('No result provided to track.product.impression');
92
+ return;
93
+ }
66
94
  const responseId = result.responseId;
67
- if (this.events[responseId]?.product[result.id]?.impression) {
95
+ if (!this.events[responseId]) {
96
+ this.log.warn('No responseId found in controller, ensure correct controller is used');
97
+ return;
98
+ }
99
+ else if (this.events[responseId]?.product[result.id]?.impression) {
68
100
  return;
69
101
  }
102
+ const type = (['product', 'banner'].includes(result.type) ? result.type : 'product');
70
103
  const item = {
71
- type: result.type,
72
- uid: result.id,
73
- parentId: result.id,
74
- sku: result.mappings.core?.sku,
104
+ type,
105
+ uid: result.id ? '' + result.id : '',
106
+ ...(type === 'product'
107
+ ? {
108
+ parentId: result.id ? '' + result.id : '',
109
+ sku: result.mappings.core?.sku ? '' + result.mappings.core?.sku : undefined,
110
+ }
111
+ : {}),
75
112
  };
76
113
  const data = {
77
114
  tag: this.store.profile.tag,
@@ -80,12 +117,20 @@ export class RecommendationController extends AbstractController {
80
117
  banners: [],
81
118
  };
82
119
  this.eventManager.fire('track.product.impression', { controller: this, product: result, trackEvent: data });
83
- this.tracker.events.recommendations.impression({ data, siteId: this.config.globals?.siteId });
120
+ this.config.beacon?.enabled && this.tracker.events.recommendations.impression({ data, siteId: this.config.globals?.siteId });
84
121
  this.events[responseId].product[result.id] = this.events[responseId].product[result.id] || {};
85
122
  this.events[responseId].product[result.id].impression = true;
86
123
  },
87
124
  addToCart: (result) => {
125
+ if (!result) {
126
+ this.log.warn('No result provided to track.product.addToCart');
127
+ return;
128
+ }
88
129
  const responseId = result.responseId;
130
+ if (!this.events[responseId]) {
131
+ this.log.warn('No responseId found in controller, ensure correct controller is used');
132
+ return;
133
+ }
89
134
  const product = {
90
135
  parentId: result.id,
91
136
  uid: result.id,
@@ -99,7 +144,7 @@ export class RecommendationController extends AbstractController {
99
144
  results: [product],
100
145
  };
101
146
  this.eventManager.fire('track.product.addToCart', { controller: this, product: result, trackEvent: data });
102
- this.tracker.events.recommendations.addToCart({ data, siteId: this.config.globals?.siteId });
147
+ this.config.beacon?.enabled && this.tracker.events.recommendations.addToCart({ data, siteId: this.config.globals?.siteId });
103
148
  },
104
149
  },
105
150
  };
@@ -155,6 +200,8 @@ export class RecommendationController extends AbstractController {
155
200
  this.log.profile(afterSearchProfile);
156
201
  // update the store
157
202
  this.store.update(response);
203
+ const data = { responseId, tag: this.store.profile.tag };
204
+ this.config.beacon?.enabled && this.tracker.events.recommendations.render({ data, siteId: this.config.globals?.siteId });
158
205
  const afterStoreProfile = this.profiler.create({ type: 'event', name: 'afterStore', context: params }).start();
159
206
  try {
160
207
  await this.eventManager.fire('afterStore', {
@@ -223,7 +270,11 @@ export class RecommendationController extends AbstractController {
223
270
  }
224
271
  };
225
272
  this.addToCart = async (_products) => {
226
- const products = typeof _products.slice == 'function' ? _products.slice() : [_products];
273
+ const products = typeof _products?.slice == 'function' ? _products.slice() : [_products];
274
+ if (!_products || products.length === 0) {
275
+ this.log.warn('No products provided to recommendation controller.addToCart');
276
+ return;
277
+ }
227
278
  products.forEach((product) => {
228
279
  this.track.product.addToCart(product);
229
280
  });
@@ -246,15 +297,6 @@ export class RecommendationController extends AbstractController {
246
297
  // deep merge config with defaults
247
298
  this.config = deepmerge(defaultConfig, this.config);
248
299
  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
300
  // add 'afterStore' middleware
259
301
  // this.eventManager.on('afterStore', async (recommend: AfterStoreObj, next: Next): Promise<void | boolean> => {
260
302
  // 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;AAsCnC,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;IAoP3B,KAAK,EAAE,kBAAkB,CAqKvB;IAEF,IAAI,MAAM,IAAI,kBAAkB,CA8C/B;IAED,MAAM,QAAa,QAAQ,IAAI,CAAC,CA0N9B;IAEF,SAAS,cAAqB,OAAO,EAAE,GAAG,OAAO,KAAG,QAAQ,IAAI,CAAC,CAQ/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,CAgCvG"}
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,CA4PvB;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"}