@searchspring/snap-controller 0.20.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 (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +167 -0
  3. package/dist/cjs/Abstract/AbstractController.d.ts +40 -0
  4. package/dist/cjs/Abstract/AbstractController.d.ts.map +1 -0
  5. package/dist/cjs/Abstract/AbstractController.js +281 -0
  6. package/dist/cjs/Autocomplete/AutocompleteController.d.ts +40 -0
  7. package/dist/cjs/Autocomplete/AutocompleteController.d.ts.map +1 -0
  8. package/dist/cjs/Autocomplete/AutocompleteController.js +687 -0
  9. package/dist/cjs/Finder/FinderController.d.ts +14 -0
  10. package/dist/cjs/Finder/FinderController.d.ts.map +1 -0
  11. package/dist/cjs/Finder/FinderController.js +286 -0
  12. package/dist/cjs/Recommendation/RecommendationController.d.ts +31 -0
  13. package/dist/cjs/Recommendation/RecommendationController.d.ts.map +1 -0
  14. package/dist/cjs/Recommendation/RecommendationController.js +452 -0
  15. package/dist/cjs/Search/SearchController.d.ts +23 -0
  16. package/dist/cjs/Search/SearchController.d.ts.map +1 -0
  17. package/dist/cjs/Search/SearchController.js +429 -0
  18. package/dist/cjs/index.d.ts +7 -0
  19. package/dist/cjs/index.d.ts.map +1 -0
  20. package/dist/cjs/index.js +24 -0
  21. package/dist/cjs/types.d.ts +52 -0
  22. package/dist/cjs/types.d.ts.map +1 -0
  23. package/dist/cjs/types.js +2 -0
  24. package/dist/cjs/utils/getParams.d.ts +2 -0
  25. package/dist/cjs/utils/getParams.d.ts.map +1 -0
  26. package/dist/cjs/utils/getParams.js +72 -0
  27. package/dist/esm/Abstract/AbstractController.d.ts +40 -0
  28. package/dist/esm/Abstract/AbstractController.d.ts.map +1 -0
  29. package/dist/esm/Abstract/AbstractController.js +181 -0
  30. package/dist/esm/Autocomplete/AutocompleteController.d.ts +40 -0
  31. package/dist/esm/Autocomplete/AutocompleteController.d.ts.map +1 -0
  32. package/dist/esm/Autocomplete/AutocompleteController.js +472 -0
  33. package/dist/esm/Finder/FinderController.d.ts +14 -0
  34. package/dist/esm/Finder/FinderController.d.ts.map +1 -0
  35. package/dist/esm/Finder/FinderController.js +164 -0
  36. package/dist/esm/Recommendation/RecommendationController.d.ts +31 -0
  37. package/dist/esm/Recommendation/RecommendationController.d.ts.map +1 -0
  38. package/dist/esm/Recommendation/RecommendationController.js +330 -0
  39. package/dist/esm/Search/SearchController.d.ts +23 -0
  40. package/dist/esm/Search/SearchController.d.ts.map +1 -0
  41. package/dist/esm/Search/SearchController.js +282 -0
  42. package/dist/esm/index.d.ts +7 -0
  43. package/dist/esm/index.d.ts.map +1 -0
  44. package/dist/esm/index.js +6 -0
  45. package/dist/esm/types.d.ts +52 -0
  46. package/dist/esm/types.d.ts.map +1 -0
  47. package/dist/esm/types.js +1 -0
  48. package/dist/esm/utils/getParams.d.ts +2 -0
  49. package/dist/esm/utils/getParams.d.ts.map +1 -0
  50. package/dist/esm/utils/getParams.js +68 -0
  51. package/package.json +40 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RecommendationController.d.ts","sourceRoot":"","sources":["../../../src/Recommendation/RecommendationController.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAGpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACzE,OAAO,KAAK,EAAE,8BAA8B,EAAkC,kBAAkB,EAAa,MAAM,UAAU,CAAC;AAE9H,aAAK,0BAA0B,GAAG;IACjC,OAAO,EAAE;QACR,KAAK,EAAE,CAAC,CAAC,KAAA,EAAE,MAAM,KAAA,KAAK,WAAW,CAAC;QAClC,MAAM,EAAE,CAAC,MAAM,KAAA,KAAK,WAAW,CAAC;QAChC,UAAU,EAAE,CAAC,MAAM,KAAA,KAAK,WAAW,CAAC;KACpC,CAAC;IACF,KAAK,EAAE,CAAC,CAAC,KAAA,KAAK,WAAW,CAAC;IAC1B,UAAU,EAAE,MAAM,WAAW,CAAC;IAC9B,MAAM,EAAE,MAAM,WAAW,CAAC;CAC1B,CAAC;AAUF,qBAAa,wBAAyB,SAAQ,kBAAkB;IACxD,IAAI,SAAoB;IACxB,KAAK,EAAE,mBAAmB,CAAC;IAClC,MAAM,EAAE,8BAA8B,CAAC;IACvC,MAAM;;;;;MAKJ;gBAEU,MAAM,EAAE,8BAA8B,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,kBAAkB;IA6B9I,KAAK,EAAE,0BAA0B,CAoK/B;IAEF,IAAI,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAyBhC;IAED,MAAM,QAAa,QAAQ,IAAI,CAAC,CAqG9B;CACF"}
@@ -0,0 +1,330 @@
1
+ import deepmerge from 'deepmerge';
2
+ import { BeaconType, BeaconCategory } from '@searchspring/snap-tracker';
3
+ import { LogMode } from '@searchspring/snap-logger';
4
+ import { AbstractController } from '../Abstract/AbstractController';
5
+ import { ErrorType } from '@searchspring/snap-store-mobx';
6
+ const defaultConfig = {
7
+ id: 'recommend',
8
+ tag: '',
9
+ batched: true,
10
+ realtime: false,
11
+ globals: {},
12
+ };
13
+ export class RecommendationController extends AbstractController {
14
+ constructor(config, { client, store, urlManager, eventManager, profiler, logger, tracker }) {
15
+ super(config, { client, store, urlManager, eventManager, profiler, logger, tracker });
16
+ this.type = 'recommendation';
17
+ this.events = {
18
+ click: null,
19
+ impression: null,
20
+ render: null,
21
+ product: {},
22
+ };
23
+ this.track = {
24
+ product: {
25
+ click: (e, result) => {
26
+ if (!this.store.profile.tag || !result || !this.events.click)
27
+ return;
28
+ const payload = {
29
+ type: BeaconType.PROFILE_PRODUCT_CLICK,
30
+ category: BeaconCategory.RECOMMENDATIONS,
31
+ context: this.config.globals.siteId ? { website: { trackingCode: this.config.globals.siteId } } : null,
32
+ event: {
33
+ context: {
34
+ action: 'navigate',
35
+ placement: this.store.profile.placement,
36
+ tag: this.store.profile.tag,
37
+ type: 'product-recommendation',
38
+ },
39
+ product: {
40
+ id: result.id,
41
+ mappings: {
42
+ core: result.mappings.core,
43
+ },
44
+ seed: this.config.globals.seed,
45
+ },
46
+ },
47
+ pid: this.events.click.id,
48
+ };
49
+ const event = this.tracker.track.event(payload);
50
+ this.eventManager.fire('track.product.click', { controller: this, event: e, result, trackEvent: event });
51
+ return event;
52
+ },
53
+ impression: (result) => {
54
+ if (!this.store.profile.tag || !result || !this.events.impression || this.events.product[result.id]?.impression)
55
+ return;
56
+ const payload = {
57
+ type: BeaconType.PROFILE_PRODUCT_IMPRESSION,
58
+ category: BeaconCategory.RECOMMENDATIONS,
59
+ context: this.config.globals.siteId ? { website: { trackingCode: this.config.globals.siteId } } : null,
60
+ event: {
61
+ context: {
62
+ placement: this.store.profile.placement,
63
+ tag: this.store.profile.tag,
64
+ type: 'product-recommendation',
65
+ },
66
+ product: {
67
+ id: result.id,
68
+ mappings: {
69
+ core: result.mappings.core,
70
+ },
71
+ seed: this.config.globals.seed,
72
+ },
73
+ },
74
+ pid: this.events.impression.id,
75
+ };
76
+ this.events.product[result.id] = this.events.product[result.id] || {};
77
+ const event = (this.events.product[result.id].impression = this.tracker.track.event(payload));
78
+ this.eventManager.fire('track.product.impression', { controller: this, result, trackEvent: event });
79
+ return event;
80
+ },
81
+ render: (result) => {
82
+ if (!this.store.profile.tag || !result || !this.events.render || this.events.product[result.id]?.render)
83
+ return;
84
+ const payload = {
85
+ type: BeaconType.PROFILE_PRODUCT_RENDER,
86
+ category: BeaconCategory.RECOMMENDATIONS,
87
+ context: this.config.globals.siteId ? { website: { trackingCode: this.config.globals.siteId } } : null,
88
+ event: {
89
+ context: {
90
+ placement: this.store.profile.placement,
91
+ tag: this.store.profile.tag,
92
+ type: 'product-recommendation',
93
+ },
94
+ product: {
95
+ id: result.id,
96
+ mappings: {
97
+ core: result.mappings.core,
98
+ },
99
+ seed: this.config.globals.seed,
100
+ },
101
+ },
102
+ pid: this.events.render.id,
103
+ };
104
+ this.events.product[result.id] = this.events.product[result.id] || {};
105
+ const event = (this.events.product[result.id].render = this.tracker.track.event(payload));
106
+ this.eventManager.fire('track.product.render', { controller: this, result, trackEvent: event });
107
+ return event;
108
+ },
109
+ },
110
+ click: (e) => {
111
+ if (!this.store.profile.tag)
112
+ return;
113
+ const event = this.tracker.track.event({
114
+ type: BeaconType.PROFILE_CLICK,
115
+ category: BeaconCategory.RECOMMENDATIONS,
116
+ context: this.config.globals.siteId ? { website: { trackingCode: this.config.globals.siteId } } : null,
117
+ event: {
118
+ context: {
119
+ action: 'navigate',
120
+ placement: this.store.profile.placement,
121
+ tag: this.store.profile.tag,
122
+ type: 'product-recommendation',
123
+ },
124
+ profile: {
125
+ tag: this.store.profile.tag,
126
+ placement: this.store.profile.placement,
127
+ threshold: this.store.profile.display.threshold,
128
+ templateId: this.store.profile.display.template.uuid,
129
+ },
130
+ },
131
+ });
132
+ this.events.click = event;
133
+ this.eventManager.fire('track.click', { controller: this, event: e, trackEvent: event });
134
+ return event;
135
+ },
136
+ impression: () => {
137
+ if (!this.store.profile.tag || this.events.impression)
138
+ return;
139
+ const event = this.tracker.track.event({
140
+ type: BeaconType.PROFILE_IMPRESSION,
141
+ category: BeaconCategory.RECOMMENDATIONS,
142
+ context: this.config.globals.siteId ? { website: { trackingCode: this.config.globals.siteId } } : null,
143
+ event: {
144
+ context: {
145
+ placement: this.store.profile.placement,
146
+ tag: this.store.profile.tag,
147
+ type: 'product-recommendation',
148
+ },
149
+ profile: {
150
+ tag: this.store.profile.tag,
151
+ placement: this.store.profile.placement,
152
+ threshold: this.store.profile.display.threshold,
153
+ templateId: this.store.profile.display.template.uuid,
154
+ },
155
+ },
156
+ });
157
+ this.events.impression = event;
158
+ this.eventManager.fire('track.impression', { controller: this, trackEvent: event });
159
+ return event;
160
+ },
161
+ render: () => {
162
+ if (!this.store.profile.tag || this.events.render)
163
+ return;
164
+ const event = this.tracker.track.event({
165
+ type: BeaconType.PROFILE_RENDER,
166
+ category: BeaconCategory.RECOMMENDATIONS,
167
+ context: this.config.globals.siteId ? { website: { trackingCode: this.config.globals.siteId } } : null,
168
+ event: {
169
+ context: {
170
+ placement: this.store.profile.placement,
171
+ tag: this.store.profile.tag,
172
+ type: 'product-recommendation',
173
+ },
174
+ profile: {
175
+ tag: this.store.profile.tag,
176
+ placement: this.store.profile.placement,
177
+ threshold: this.store.profile.display.threshold,
178
+ templateId: this.store.profile.display.template.uuid,
179
+ },
180
+ },
181
+ });
182
+ this.events.render = event;
183
+ // track results render
184
+ this.store.results.forEach((result) => this.track.product.render(result));
185
+ this.eventManager.fire('track.render', { controller: this, trackEvent: event });
186
+ return event;
187
+ },
188
+ };
189
+ this.search = async () => {
190
+ if (!this.initialized) {
191
+ await this.init();
192
+ }
193
+ const params = this.params;
194
+ try {
195
+ try {
196
+ await this.eventManager.fire('beforeSearch', {
197
+ controller: this,
198
+ request: params,
199
+ });
200
+ }
201
+ catch (err) {
202
+ if (err?.message == 'cancelled') {
203
+ this.log.warn(`'beforeSearch' middleware cancelled`);
204
+ return;
205
+ }
206
+ else {
207
+ this.log.error(`error in 'beforeSearch' middleware`);
208
+ throw err;
209
+ }
210
+ }
211
+ const searchProfile = this.profiler.create({ type: 'event', name: 'search', context: params }).start();
212
+ const response = await this.client.recommend(params);
213
+ searchProfile.stop();
214
+ this.log.profile(searchProfile);
215
+ const afterSearchProfile = this.profiler.create({ type: 'event', name: 'afterSearch', context: params }).start();
216
+ try {
217
+ await this.eventManager.fire('afterSearch', {
218
+ controller: this,
219
+ request: params,
220
+ response,
221
+ });
222
+ }
223
+ catch (err) {
224
+ if (err?.message == 'cancelled') {
225
+ this.log.warn(`'afterSearch' middleware cancelled`);
226
+ afterSearchProfile.stop();
227
+ return;
228
+ }
229
+ else {
230
+ this.log.error(`error in 'afterSearch' middleware`);
231
+ throw err;
232
+ }
233
+ }
234
+ afterSearchProfile.stop();
235
+ this.log.profile(afterSearchProfile);
236
+ // update the store
237
+ this.store.update(response);
238
+ const afterStoreProfile = this.profiler.create({ type: 'event', name: 'afterStore', context: params }).start();
239
+ try {
240
+ await this.eventManager.fire('afterStore', {
241
+ controller: this,
242
+ request: params,
243
+ response,
244
+ });
245
+ }
246
+ catch (err) {
247
+ if (err?.message == 'cancelled') {
248
+ this.log.warn(`'afterStore' middleware cancelled`);
249
+ afterStoreProfile.stop();
250
+ return;
251
+ }
252
+ else {
253
+ this.log.error(`error in 'afterStore' middleware`);
254
+ throw err;
255
+ }
256
+ }
257
+ afterStoreProfile.stop();
258
+ this.log.profile(afterStoreProfile);
259
+ }
260
+ catch (err) {
261
+ if (err) {
262
+ switch (err) {
263
+ case 429:
264
+ this.store.error = {
265
+ code: 429,
266
+ type: ErrorType.WARNING,
267
+ message: 'Too many requests try again later',
268
+ };
269
+ this.log.warn(this.store.error);
270
+ break;
271
+ case 500:
272
+ this.store.error = {
273
+ code: 500,
274
+ type: ErrorType.ERROR,
275
+ message: 'Invalid Search Request or Service Unavailable',
276
+ };
277
+ this.log.error(this.store.error);
278
+ break;
279
+ default:
280
+ this.log.error(err);
281
+ break;
282
+ }
283
+ this.store.loading = false;
284
+ }
285
+ }
286
+ };
287
+ if (!config.tag) {
288
+ throw new Error(`Invalid config passed to RecommendationController. The "tag" attribute is required.`);
289
+ }
290
+ // deep merge config with defaults
291
+ this.config = deepmerge(defaultConfig, this.config);
292
+ this.store.setConfig(this.config);
293
+ // add 'beforeSearch' middleware
294
+ this.eventManager.on('beforeSearch', async (recommend, next) => {
295
+ recommend.controller.store.loading = true;
296
+ await next();
297
+ });
298
+ // add 'afterStore' middleware
299
+ this.eventManager.on('afterStore', async (recommend, next) => {
300
+ await next();
301
+ recommend.controller.store.loading = false;
302
+ });
303
+ // attach config plugins and event middleware
304
+ this.use(this.config);
305
+ }
306
+ get params() {
307
+ const params = {
308
+ tag: this.config.tag,
309
+ batched: this.config.batched,
310
+ ...this.config.globals,
311
+ branch: this.config.branch || 'production',
312
+ };
313
+ const shopperId = this.tracker.context.shopperId;
314
+ const cart = this.tracker.cookies.cart.get();
315
+ const lastViewed = this.tracker.cookies.viewed.get();
316
+ if (shopperId) {
317
+ params.shopper = shopperId;
318
+ }
319
+ if (cart?.length) {
320
+ params.cart = cart;
321
+ }
322
+ if (lastViewed?.length) {
323
+ params.lastViewed = lastViewed;
324
+ }
325
+ if (this.environment == LogMode.DEVELOPMENT) {
326
+ params.test = true;
327
+ }
328
+ return params;
329
+ }
330
+ }
@@ -0,0 +1,23 @@
1
+ import { AbstractController } from '../Abstract/AbstractController';
2
+ import { StorageStore } from '@searchspring/snap-store-mobx';
3
+ import type { BeaconEvent } from '@searchspring/snap-tracker';
4
+ import type { SearchStore } from '@searchspring/snap-store-mobx';
5
+ import type { SearchControllerConfig, ControllerServices } from '../types';
6
+ import type { SearchRequestModel } from '@searchspring/snapi-types';
7
+ declare type SearchTrackMethods = {
8
+ product: {
9
+ click: (e: any, result: any) => BeaconEvent;
10
+ };
11
+ };
12
+ export declare class SearchController extends AbstractController {
13
+ type: string;
14
+ store: SearchStore;
15
+ config: SearchControllerConfig;
16
+ storage: StorageStore;
17
+ constructor(config: SearchControllerConfig, { client, store, urlManager, eventManager, profiler, logger, tracker }: ControllerServices);
18
+ track: SearchTrackMethods;
19
+ get params(): SearchRequestModel;
20
+ search: () => Promise<void>;
21
+ }
22
+ export {};
23
+ //# sourceMappingURL=SearchController.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SearchController.d.ts","sourceRoot":"","sources":["../../../src/Search/SearchController.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,EAAE,YAAY,EAA0B,MAAM,+BAA+B,CAAC;AAGrF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,KAAK,EAAE,sBAAsB,EAAkD,kBAAkB,EAAa,MAAM,UAAU,CAAC;AACtI,OAAO,KAAK,EAAE,kBAAkB,EAAgD,MAAM,2BAA2B,CAAC;AAmBlH,aAAK,kBAAkB,GAAG;IACzB,OAAO,EAAE;QACR,KAAK,EAAE,CAAC,CAAC,KAAA,EAAE,MAAM,KAAA,KAAK,WAAW,CAAC;KAClC,CAAC;CACF,CAAC;AAEF,qBAAa,gBAAiB,SAAQ,kBAAkB;IAChD,IAAI,SAAY;IAChB,KAAK,EAAE,WAAW,CAAC;IAC1B,MAAM,EAAE,sBAAsB,CAAC;IAC/B,OAAO,EAAE,YAAY,CAAC;gBAEV,MAAM,EAAE,sBAAsB,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,kBAAkB;IAiFtI,KAAK,EAAE,kBAAkB,CA0BvB;IAEF,IAAI,MAAM,IAAI,kBAAkB,CAsC/B;IAED,MAAM,QAAa,QAAQ,IAAI,CAAC,CAuJ9B;CACF"}
@@ -0,0 +1,282 @@
1
+ import deepmerge from 'deepmerge';
2
+ import { AbstractController } from '../Abstract/AbstractController';
3
+ import { StorageStore, StorageType, ErrorType } from '@searchspring/snap-store-mobx';
4
+ import { getSearchParams } from '../utils/getParams';
5
+ const HEIGHT_CHECK_INTERVAL = 50;
6
+ const defaultConfig = {
7
+ id: 'search',
8
+ globals: {},
9
+ settings: {
10
+ redirects: {
11
+ merchandising: true,
12
+ singleResult: true,
13
+ },
14
+ facets: {
15
+ trim: true,
16
+ pinFiltered: true,
17
+ },
18
+ },
19
+ };
20
+ export class SearchController extends AbstractController {
21
+ constructor(config, { client, store, urlManager, eventManager, profiler, logger, tracker }) {
22
+ super(config, { client, store, urlManager, eventManager, profiler, logger, tracker });
23
+ this.type = 'search';
24
+ this.track = {
25
+ product: {
26
+ click: (e, result) => {
27
+ // store scroll position
28
+ if (this.config.settings.infinite) {
29
+ const stringyParams = this.storage.get('lastStringyParams');
30
+ const scrollMap = {};
31
+ scrollMap[stringyParams] = window.scrollY;
32
+ this.storage.set('scrollMap', scrollMap);
33
+ }
34
+ // track
35
+ const { intellisuggestData, intellisuggestSignature } = result.attributes;
36
+ const target = e.target;
37
+ const href = target?.href || result.mappings.core?.url || undefined;
38
+ const event = this.tracker.track.product.click({
39
+ intellisuggestData,
40
+ intellisuggestSignature,
41
+ href,
42
+ });
43
+ this.eventManager.fire('track.product.click', { controller: this, event: e, result, trackEvent: event });
44
+ return event;
45
+ },
46
+ },
47
+ };
48
+ this.search = async () => {
49
+ if (!this.initialized) {
50
+ await this.init();
51
+ }
52
+ const params = this.params;
53
+ try {
54
+ const stringyParams = JSON.stringify(params);
55
+ const prevStringyParams = this.storage.get('lastStringyParams');
56
+ if (stringyParams == prevStringyParams) {
57
+ // no param change - not searching
58
+ return;
59
+ }
60
+ try {
61
+ await this.eventManager.fire('beforeSearch', {
62
+ controller: this,
63
+ request: params,
64
+ });
65
+ }
66
+ catch (err) {
67
+ if (err?.message == 'cancelled') {
68
+ this.log.warn(`'beforeSearch' middleware cancelled`);
69
+ return;
70
+ }
71
+ else {
72
+ this.log.error(`error in 'beforeSearch' middleware`);
73
+ throw err;
74
+ }
75
+ }
76
+ if (this.config.settings.infinite) {
77
+ // TODO: refactor this
78
+ const preventBackfill = this.config.settings.infinite?.backfill && !this.store.results.length && params.pagination?.page > this.config.settings.infinite.backfill;
79
+ const dontBackfill = !this.config.settings.infinite?.backfill && !this.store.results.length && params.pagination?.page > 1;
80
+ if (preventBackfill || dontBackfill) {
81
+ this.storage.set('scrollMap', {});
82
+ this.urlManager.set('page', 1).go();
83
+ return;
84
+ }
85
+ }
86
+ const searchProfile = this.profiler.create({ type: 'event', name: 'search', context: params }).start();
87
+ const [meta, response] = await this.client.search(params);
88
+ if (!response.meta) {
89
+ /**
90
+ * MockClient will overwrite the client search() method and use
91
+ * SearchData to return mock data which already contains meta data
92
+ */
93
+ response.meta = meta;
94
+ }
95
+ // infinite functionality
96
+ // if params.page > 1 and infinite setting exists we should append results
97
+ if (this.config.settings.infinite && params.pagination?.page > 1) {
98
+ // if no results fetch results...
99
+ let previousResults = this.store.data?.results || [];
100
+ if (this.config.settings?.infinite.backfill && !previousResults.length) {
101
+ // figure out how many pages of results to backfill and wait on all responses
102
+ const backfills = [];
103
+ for (let page = 1; page < params.pagination.page; page++) {
104
+ const backfillParams = deepmerge({ ...params }, { pagination: { page } });
105
+ backfills.push(this.client.search(backfillParams));
106
+ }
107
+ const backfillResponses = await Promise.all(backfills);
108
+ backfillResponses.map(([meta, data]) => {
109
+ previousResults = previousResults.concat(data.results);
110
+ });
111
+ }
112
+ response.results = [...previousResults, ...(response.results || [])];
113
+ }
114
+ searchProfile.stop();
115
+ this.log.profile(searchProfile);
116
+ const afterSearchProfile = this.profiler.create({ type: 'event', name: 'afterSearch', context: params }).start();
117
+ try {
118
+ await this.eventManager.fire('afterSearch', {
119
+ controller: this,
120
+ request: params,
121
+ response,
122
+ });
123
+ }
124
+ catch (err) {
125
+ if (err?.message == 'cancelled') {
126
+ this.log.warn(`'afterSearch' middleware cancelled`);
127
+ afterSearchProfile.stop();
128
+ return;
129
+ }
130
+ else {
131
+ this.log.error(`error in 'afterSearch' middleware`);
132
+ throw err;
133
+ }
134
+ }
135
+ afterSearchProfile.stop();
136
+ this.log.profile(afterSearchProfile);
137
+ // update the store
138
+ this.store.update(response);
139
+ const afterStoreProfile = this.profiler.create({ type: 'event', name: 'afterStore', context: params }).start();
140
+ try {
141
+ await this.eventManager.fire('afterStore', {
142
+ controller: this,
143
+ request: params,
144
+ response,
145
+ });
146
+ }
147
+ catch (err) {
148
+ if (err?.message == 'cancelled') {
149
+ this.log.warn(`'afterStore' middleware cancelled`);
150
+ afterStoreProfile.stop();
151
+ return;
152
+ }
153
+ else {
154
+ this.log.error(`error in 'afterStore' middleware`);
155
+ throw err;
156
+ }
157
+ }
158
+ afterStoreProfile.stop();
159
+ this.log.profile(afterStoreProfile);
160
+ }
161
+ catch (err) {
162
+ if (err) {
163
+ switch (err) {
164
+ case 429:
165
+ this.store.error = {
166
+ code: 429,
167
+ type: ErrorType.WARNING,
168
+ message: 'Too many requests try again later',
169
+ };
170
+ this.log.warn(this.store.error);
171
+ break;
172
+ case 500:
173
+ this.store.error = {
174
+ code: 500,
175
+ type: ErrorType.ERROR,
176
+ message: 'Invalid Search Request or Service Unavailable',
177
+ };
178
+ this.log.error(this.store.error);
179
+ break;
180
+ default:
181
+ this.log.error(err);
182
+ break;
183
+ }
184
+ this.store.loading = false;
185
+ }
186
+ }
187
+ };
188
+ // deep merge config with defaults
189
+ this.config = deepmerge(defaultConfig, this.config);
190
+ this.store.setConfig(this.config);
191
+ this.storage = new StorageStore({
192
+ type: StorageType.SESSION,
193
+ key: `ss-controller-${this.config.id}`,
194
+ });
195
+ // set last params to undefined for compare in search
196
+ this.storage.set('lastStringyParams', undefined);
197
+ // add 'beforeSearch' middleware
198
+ this.eventManager.on('beforeSearch', async (search, next) => {
199
+ search.controller.store.loading = true;
200
+ await next();
201
+ });
202
+ // add 'afterSearch' middleware
203
+ this.eventManager.on('afterSearch', async (search, next) => {
204
+ const config = search.controller.config;
205
+ const redirectURL = search.response?.merchandising?.redirect;
206
+ const searchStore = search.controller.store;
207
+ if (redirectURL && config?.settings?.redirects?.merchandising && !search?.response?.filters?.length && !searchStore.loaded) {
208
+ window.location.replace(redirectURL);
209
+ return false;
210
+ }
211
+ if (config?.settings?.redirects?.singleResult &&
212
+ search?.response?.search?.query &&
213
+ search?.response?.pagination?.totalResults === 1 &&
214
+ !search?.response?.filters?.length) {
215
+ window.location.replace(search?.response.results[0].mappings.core.url);
216
+ return false;
217
+ }
218
+ await next();
219
+ });
220
+ this.eventManager.on('afterStore', async (search, next) => {
221
+ await next();
222
+ search.controller.store.loading = false;
223
+ // save last params
224
+ const stringyParams = JSON.stringify(search.request);
225
+ this.storage.set('lastStringyParams', stringyParams);
226
+ if (this.config.settings?.infinite && window.scrollY === 0) {
227
+ // browser didn't jump
228
+ const scrollMap = this.storage.get('scrollMap') || {};
229
+ // interval we ony need to keep checking until the page height > than our stored value
230
+ const scrollToPosition = scrollMap[stringyParams];
231
+ if (scrollToPosition) {
232
+ let checkCount = 0;
233
+ const heightCheck = window.setInterval(() => {
234
+ if (document.documentElement.scrollHeight >= scrollToPosition) {
235
+ window.scrollTo(0, scrollToPosition);
236
+ this.log.debug('scrolling to: ', scrollMap[stringyParams]);
237
+ window.clearInterval(heightCheck);
238
+ }
239
+ if (checkCount > 2000 / HEIGHT_CHECK_INTERVAL) {
240
+ window.clearInterval(heightCheck);
241
+ }
242
+ checkCount++;
243
+ }, HEIGHT_CHECK_INTERVAL);
244
+ }
245
+ }
246
+ });
247
+ // attach config plugins and event middleware
248
+ this.use(this.config);
249
+ }
250
+ get params() {
251
+ const params = deepmerge({ ...getSearchParams(this.urlManager.state) }, this.config.globals);
252
+ // redirect setting
253
+ if (!this.config.settings?.redirects?.merchandising || this.store.loaded) {
254
+ params.search = params.search || {};
255
+ params.search.redirectResponse = 'full';
256
+ }
257
+ params.tracking = params.tracking || {};
258
+ params.tracking.domain = window.location.href;
259
+ const userId = this.tracker.getUserId();
260
+ if (userId) {
261
+ params.tracking.userId = userId;
262
+ }
263
+ if (!this.config.globals?.personalization?.disabled) {
264
+ const cartItems = this.tracker.cookies.cart.get();
265
+ if (cartItems.length) {
266
+ params.personalization = params.personalization || {};
267
+ params.personalization.cart = cartItems.join(',');
268
+ }
269
+ const lastViewedItems = this.tracker.cookies.viewed.get();
270
+ if (lastViewedItems.length) {
271
+ params.personalization = params.personalization || {};
272
+ params.personalization.lastViewed = lastViewedItems.join(',');
273
+ }
274
+ const shopperId = this.tracker.getShopperId();
275
+ if (shopperId) {
276
+ params.personalization = params.personalization || {};
277
+ params.personalization.shopper = shopperId;
278
+ }
279
+ }
280
+ return params;
281
+ }
282
+ }
@@ -0,0 +1,7 @@
1
+ export { FinderController } from './Finder/FinderController';
2
+ export { SearchController } from './Search/SearchController';
3
+ export { AutocompleteController } from './Autocomplete/AutocompleteController';
4
+ export { RecommendationController } from './Recommendation/RecommendationController';
5
+ export { AbstractController } from './Abstract/AbstractController';
6
+ export * from './types';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,EAAE,wBAAwB,EAAE,MAAM,2CAA2C,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAEnE,cAAc,SAAS,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { FinderController } from './Finder/FinderController';
2
+ export { SearchController } from './Search/SearchController';
3
+ export { AutocompleteController } from './Autocomplete/AutocompleteController';
4
+ export { RecommendationController } from './Recommendation/RecommendationController';
5
+ export { AbstractController } from './Abstract/AbstractController';
6
+ export * from './types';