@searchspring/snap-client 0.71.0 → 0.72.1

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/README.md CHANGED
@@ -9,7 +9,7 @@ npm install --save @searchspring/snap-client
9
9
  ```
10
10
 
11
11
  ## Import
12
- ```typescript
12
+ ```js
13
13
  import { Client } from '@searchspring/snap-client';
14
14
  ```
15
15
 
@@ -20,7 +20,7 @@ Globals are API parameters that will be applied to all searches requested by the
20
20
 
21
21
  `siteId` (required)
22
22
 
23
- ```typescript
23
+ ```js
24
24
  const globals = {
25
25
  siteId: 'a1b2c3'
26
26
  };
@@ -30,7 +30,7 @@ Any other keys defined here will be passed to the API request
30
30
 
31
31
  For example, with background filter:
32
32
 
33
- ```typescript
33
+ ```js
34
34
  const globals = {
35
35
  siteId: 'a1b2c3',
36
36
  filters: [{
@@ -45,7 +45,7 @@ const globals = {
45
45
  ## Client Config
46
46
  Optional configuration for each requester. This can be used to specifiy a development origin URL or to configure cache settings per requester.
47
47
 
48
- ```typescript
48
+ ```js
49
49
  type ClientConfig = {
50
50
  mode?: keyof typeof AppMode | AppMode;
51
51
  fetchApi?: WindowOrWorkerGlobalScope['fetch'];
@@ -90,7 +90,7 @@ Each requester in the Snap Client has its own cache settings, which can be confi
90
90
 
91
91
  `entries`: to allow preload the cache. This is primarily used in Email Recommendations.
92
92
 
93
- ```typescript
93
+ ```js
94
94
  const metaResponse = {
95
95
  "facets": {
96
96
  "brand": {
@@ -152,7 +152,7 @@ const results = await client.search({
152
152
  ```
153
153
 
154
154
  ## Standalone usage
155
- ```typescript
155
+ ```js
156
156
  const client = new Client(globals, clientConfig);
157
157
 
158
158
  const [meta, results] = await client.search({
@@ -167,7 +167,7 @@ const [meta, results] = await client.search({
167
167
  ## `search` method
168
168
  Makes a request to the Searchspring Search API and returns a promise.
169
169
 
170
- ```typescript
170
+ ```js
171
171
  const client = new Client(globals, clientConfig);
172
172
 
173
173
  const [meta, results] = await client.search({
@@ -182,7 +182,7 @@ const [meta, results] = await client.search({
182
182
  ## `autocomplete` method
183
183
  Makes a request to the Searchspring Autocomplete API and returns a promise.
184
184
 
185
- ```typescript
185
+ ```js
186
186
  const client = new Client(globals, clientConfig);
187
187
 
188
188
  const [meta, results] = await client.autocomplete({
@@ -201,7 +201,7 @@ const [meta, results] = await client.autocomplete({
201
201
  ## `meta` method
202
202
  Makes a request to the Searchspring Search API to fetch meta properties, it returns a promise. The `search` method utilizes this method.
203
203
 
204
- ```typescript
204
+ ```js
205
205
  const client = new Client(globals, clientConfig);
206
206
  const meta = await client.meta();
207
207
  ```
@@ -209,10 +209,10 @@ const meta = await client.meta();
209
209
  ## `trending` method
210
210
  Makes a request to the Searchspring Trending API and returns a promise.
211
211
 
212
- ```typescript
212
+ ```js
213
213
  const client = new Client(globals, clientConfig);
214
214
  const results = await client.trending({
215
- siteId: 'abc123',
215
+ siteId: 'REPLACE_WITH_YOUR_SITE_ID',
216
216
  limit: 5
217
217
  });
218
218
  ```
@@ -220,7 +220,7 @@ const results = await client.trending({
220
220
  ## `finder` method
221
221
  Makes a request to the Searchspring finder API and returns a promise.
222
222
 
223
- ```typescript
223
+ ```js
224
224
  const client = new Client(globals, clientConfig);
225
225
  const [meta, results] = await client.finder({
226
226
  filters: [{
@@ -235,12 +235,12 @@ const [meta, results] = await client.finder({
235
235
  ## `recommend` method
236
236
  Makes a request to the Searchspring Recommend API and returns a promise.
237
237
 
238
- ```typescript
238
+ ```js
239
239
  const client = new Client(globals, clientConfig);
240
240
  const results = await client.recommend({
241
241
  tag: 'similar',
242
- siteId: 'abc123',
242
+ siteId: 'REPLACE_WITH_YOUR_SITE_ID',
243
243
  products: ['product123'],
244
- shopper: 'snapdev',
244
+ shopper: '[REPLACE WITH LOGGED IN SHOPPER ID]'
245
245
  });
246
246
  ```
@@ -3,7 +3,9 @@ export declare class NetworkCache {
3
3
  protected memoryCache: Cache;
4
4
  protected config: DefaultCacheConfig;
5
5
  constructor(config?: CacheConfig);
6
+ load(): void;
6
7
  get(key: string): Response | void;
8
+ private purgeExpired;
7
9
  set(key: string, value: Response): void;
8
10
  clear(): void;
9
11
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NetworkCache.d.ts","sourceRoot":"","sources":["../../../../src/Client/NetworkCache/NetworkCache.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAWrE,qBAAa,YAAY;IACxB,SAAS,CAAC,WAAW,EAAE,KAAK,CAAM;IAClC,SAAS,CAAC,MAAM,EAAE,kBAAkB,CAAC;gBAEzB,MAAM,CAAC,EAAE,WAAW;IAWzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAkCjC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,IAAI;IA2CvC,KAAK;CAUZ"}
1
+ {"version":3,"file":"NetworkCache.d.ts","sourceRoot":"","sources":["../../../../src/Client/NetworkCache/NetworkCache.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAWrE,qBAAa,YAAY;IACxB,SAAS,CAAC,WAAW,EAAE,KAAK,CAAM;IAClC,SAAS,CAAC,MAAM,EAAE,kBAAkB,CAAC;gBAEzB,MAAM,CAAC,EAAE,WAAW;IAczB,IAAI,IAAI,IAAI;IAcZ,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IA2ExC,OAAO,CAAC,YAAY;IAmBb,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,IAAI;IAuCvC,KAAK;CAUZ"}
@@ -10,6 +10,15 @@ var __assign = (this && this.__assign) || function () {
10
10
  };
11
11
  return __assign.apply(this, arguments);
12
12
  };
13
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
14
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
15
+ if (ar || !(i in from)) {
16
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
17
+ ar[i] = from[i];
18
+ }
19
+ }
20
+ return to.concat(ar || Array.prototype.slice.call(from));
21
+ };
13
22
  var __importDefault = (this && this.__importDefault) || function (mod) {
14
23
  return (mod && mod.__esModule) ? mod : { "default": mod };
15
24
  };
@@ -20,7 +29,7 @@ var CACHE_STORAGE_KEY = 'ss-networkcache';
20
29
  var defaultConfig = {
21
30
  enabled: true,
22
31
  ttl: 300000, // ms
23
- maxSize: 200, // KB
32
+ maxSize: 1000, // KB
24
33
  purgeable: true,
25
34
  };
26
35
  var NetworkCache = /** @class */ (function () {
@@ -29,6 +38,8 @@ var NetworkCache = /** @class */ (function () {
29
38
  var _a;
30
39
  this.memoryCache = {};
31
40
  this.config = (0, deepmerge_1.default)(defaultConfig, config || {});
41
+ this.load();
42
+ // this allows you to pre-populate the cache from the config - primarily used for email recs
32
43
  ((_a = this.config) === null || _a === void 0 ? void 0 : _a.entries) &&
33
44
  Object.keys(this.config.entries).map(function (key) {
34
45
  if (_this.config.entries && _this.config.entries[key]) {
@@ -36,66 +47,134 @@ var NetworkCache = /** @class */ (function () {
36
47
  }
37
48
  });
38
49
  }
50
+ NetworkCache.prototype.load = function () {
51
+ // initialize cache from session storage
52
+ if (typeof window !== 'undefined' && (window === null || window === void 0 ? void 0 : window.sessionStorage)) {
53
+ var stored = window.sessionStorage.getItem(CACHE_STORAGE_KEY);
54
+ var newStored = __assign({}, (stored && JSON.parse(stored)));
55
+ this.memoryCache = newStored || {};
56
+ }
57
+ this.purgeExpired();
58
+ };
39
59
  NetworkCache.prototype.get = function (key) {
60
+ var _a, _b, _c;
40
61
  if (this.config.enabled) {
62
+ this.load();
41
63
  try {
42
- if (this.memoryCache[key]) {
43
- if (Date.now() < this.memoryCache[key].expires) {
44
- return (0, deepmerge_1.default)({}, this.memoryCache[key].value);
64
+ var ignoreKeys_1 = [];
65
+ if (typeof window !== 'undefined') {
66
+ var navigationType = (_c = (_b = (_a = window.performance) === null || _a === void 0 ? void 0 : _a.getEntriesByType('navigation')) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.type;
67
+ if (navigationType === 'back_forward') {
68
+ ignoreKeys_1 = ['lastViewed', 'cart'];
45
69
  }
46
70
  }
47
- if (typeof window !== 'undefined' && (window === null || window === void 0 ? void 0 : window.sessionStorage)) {
48
- var stored = window.sessionStorage.getItem(CACHE_STORAGE_KEY);
49
- var localData = stored && JSON.parse(stored);
50
- if (localData && key && localData[key]) {
71
+ if (Object.keys(this.memoryCache).length && key) {
72
+ var storageKey = key;
73
+ //this only applies to search calls
74
+ if (ignoreKeys_1.length && key.startsWith('/api/search/search.json')) {
75
+ try {
76
+ var url_1 = key.split('{')[0];
77
+ var payload = '{' + key.split('{')[1];
78
+ var searchParams_1 = JSON.parse(payload);
79
+ var foundKey = Object.keys(this.memoryCache).find(function (cachedResponse) {
80
+ try {
81
+ var storedUrl = cachedResponse.split('{')[0];
82
+ if (storedUrl == url_1) {
83
+ var storedPayload = '{' + cachedResponse.split('{')[1];
84
+ var parsedPayload = JSON.parse(storedPayload);
85
+ var allKeys = Array.from(new Set(__spreadArray(__spreadArray([], Object.keys(searchParams_1), true), Object.keys(parsedPayload), true)));
86
+ for (var _i = 0, allKeys_1 = allKeys; _i < allKeys_1.length; _i++) {
87
+ var k = allKeys_1[_i];
88
+ if (ignoreKeys_1.includes(k))
89
+ continue;
90
+ if (JSON.stringify(searchParams_1[k]) !== JSON.stringify(parsedPayload[k])) {
91
+ return false;
92
+ }
93
+ }
94
+ return true;
95
+ }
96
+ else
97
+ return false;
98
+ }
99
+ catch (_a) {
100
+ return false;
101
+ }
102
+ });
103
+ if (foundKey) {
104
+ storageKey = foundKey;
105
+ }
106
+ }
107
+ catch (e) {
108
+ // key is not json
109
+ }
110
+ }
111
+ if (this.memoryCache[storageKey]) {
51
112
  // compare the expiry time of the item with the current time
52
- if (Date.now() >= localData[key].expires) {
113
+ if (Date.now() >= this.memoryCache[storageKey].expires) {
53
114
  // remove item
54
- var newStored = __assign({}, localData);
55
- delete newStored[key];
115
+ var newStored = __assign({}, this.memoryCache);
116
+ delete newStored[storageKey];
56
117
  // update storage
57
118
  window.sessionStorage.setItem(CACHE_STORAGE_KEY, JSON.stringify(newStored));
58
119
  }
59
120
  else {
60
- return localData[key].value;
121
+ return this.memoryCache[storageKey].value;
61
122
  }
62
123
  }
63
124
  }
64
125
  }
65
- catch (_a) {
66
- console.warn('something went wrong getting from cache');
126
+ catch (err) {
127
+ console.warn('something went wrong getting from cache', err);
67
128
  }
68
129
  }
69
130
  };
131
+ NetworkCache.prototype.purgeExpired = function () {
132
+ var _this = this;
133
+ Object.keys(this.memoryCache).forEach(function (cacheKey) {
134
+ //clean up expired cache keys in memory
135
+ if (Date.now() > _this.memoryCache[cacheKey].expires) {
136
+ delete _this.memoryCache[cacheKey];
137
+ }
138
+ });
139
+ // update storage
140
+ try {
141
+ if (typeof window !== 'undefined' && (window === null || window === void 0 ? void 0 : window.sessionStorage)) {
142
+ var stringifiedCache = JSON.stringify(this.memoryCache);
143
+ window.sessionStorage.setItem(CACHE_STORAGE_KEY, stringifiedCache);
144
+ }
145
+ }
146
+ catch (_a) {
147
+ console.warn('failed to store network cache');
148
+ }
149
+ };
70
150
  NetworkCache.prototype.set = function (key, value) {
151
+ var _this = this;
71
152
  if (this.config.enabled) {
153
+ this.load();
72
154
  try {
73
155
  var cacheObject = {
74
156
  value: value,
75
157
  expires: Date.now() + this.config.ttl,
76
158
  purgeable: this.config.purgeable,
77
159
  };
160
+ // purge old items if we are over max size
161
+ var size = new Blob([JSON.stringify(this.memoryCache)], { endings: 'native' }).size / 1024;
162
+ while (size > this.config.maxSize) {
163
+ var oldestKey = Object.keys(this.memoryCache)
164
+ .filter(function (key) { return _this.memoryCache[key].purgeable; })
165
+ .sort(function (a, b) {
166
+ return _this.memoryCache[a].expires - _this.memoryCache[b].expires;
167
+ })[0];
168
+ if (!oldestKey)
169
+ break;
170
+ delete this.memoryCache[oldestKey];
171
+ // recalculate size after removing oldest
172
+ size = new Blob([JSON.stringify(this.memoryCache)], { endings: 'native' }).size / 1024;
173
+ }
174
+ // store cache in memory
78
175
  this.memoryCache[key] = cacheObject;
79
176
  if (typeof window !== 'undefined' && (window === null || window === void 0 ? void 0 : window.sessionStorage)) {
80
- var stored = window.sessionStorage.getItem(CACHE_STORAGE_KEY);
81
- var newStored_1 = __assign({}, (stored && JSON.parse(stored)));
82
- newStored_1[key] = cacheObject;
83
- var size = new Blob([JSON.stringify(newStored_1)], { endings: 'native' }).size / 1024;
84
- while (size > this.config.maxSize) {
85
- var oldestKey = Object.keys(newStored_1)
86
- .filter(function (key) { return newStored_1[key].purgeable; })
87
- .sort(function (a, b) {
88
- return newStored_1[a].expires - newStored_1[b].expires;
89
- })[0];
90
- if (!oldestKey)
91
- break;
92
- delete newStored_1[oldestKey];
93
- // recalculate size after removing oldest
94
- size = new Blob([JSON.stringify(newStored_1)], { endings: 'native' }).size / 1024;
95
- }
96
- if (size < this.config.maxSize) {
97
- window.sessionStorage.setItem(CACHE_STORAGE_KEY, JSON.stringify(newStored_1));
98
- }
177
+ window.sessionStorage.setItem(CACHE_STORAGE_KEY, JSON.stringify(this.memoryCache));
99
178
  }
100
179
  }
101
180
  catch (_a) {
@@ -13,6 +13,7 @@ export interface RequestOpts {
13
13
  headers: HTTPHeaders;
14
14
  query?: HTTPQuery;
15
15
  body?: HTTPBody;
16
+ subDomain?: string;
16
17
  }
17
18
  export declare class API {
18
19
  configuration: ApiConfiguration;
@@ -1 +1 @@
1
- {"version":3,"file":"Abstract.d.ts","sourceRoot":"","sources":["../../../../src/Client/apis/Abstract.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAGrD,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIvE,MAAM,MAAM,IAAI,GAAG,GAAG,CAAC;AACvB,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAC1F,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,GAAG,SAAS,CAAA;CAAE,CAAC;AAClI,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,QAAQ,GAAG,eAAe,CAAC;AAEzD,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;CAChB;AAED,qBAAa,GAAG;IAMI,aAAa,EAAE,gBAAgB;IALlD,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,UAAU,CAAK;IAEhB,KAAK,EAAE,YAAY,CAAC;gBAER,aAAa,EAAE,gBAAgB;IAIlD,SAAS,KAAK,IAAI,IAAI,OAAO,CAE5B;cAEe,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IA8DnF,OAAO,CAAC,iBAAiB;YAoCX,QAAQ;CAKtB;AAED,MAAM,MAAM,QAAQ,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;AAE1D,MAAM,WAAW,0BAA0B;IAC1C,IAAI,CAAC,EAAE,MAAM,OAAO,OAAO,GAAG,OAAO,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,MAAM,CAAC;IACrD,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,OAAO,CAAC,EAAE,cAAc,CAAC;CACzB;AAED,qBAAa,gBAAgB;IAChB,OAAO,CAAC,MAAM;gBAAN,MAAM,GAAE,0BAA+B;IAa3D,IAAI,KAAK,IAAI,WAAW,CAEvB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,QAAQ,IAAI,QAAQ,CAEvB;IAED,IAAI,oBAAoB,IAAI,CAAC,MAAM,EAAE,SAAS,KAAK,MAAM,CAExD;IAED,IAAI,OAAO,IAAI,WAAW,CAEzB;IAED,IAAI,OAAO,CAAC,UAAU,EAAE,WAAW,EAElC;IAED,IAAI,OAAO,IAAI,cAAc,CAE5B;IAED,IAAI,OAAO,CAAC,UAAU,EAAE,cAAc,EAErC;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;CACD;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,SAAK,GAAG,MAAM,CAmBlE"}
1
+ {"version":3,"file":"Abstract.d.ts","sourceRoot":"","sources":["../../../../src/Client/apis/Abstract.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAGrD,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIvE,MAAM,MAAM,IAAI,GAAG,GAAG,CAAC;AACvB,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAC1F,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,GAAG,SAAS,CAAA;CAAE,CAAC;AAClI,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,QAAQ,GAAG,eAAe,CAAC;AAEzD,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,GAAG;IAMI,aAAa,EAAE,gBAAgB;IALlD,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,UAAU,CAAK;IAEhB,KAAK,EAAE,YAAY,CAAC;gBAER,aAAa,EAAE,gBAAgB;IAIlD,SAAS,KAAK,IAAI,IAAI,OAAO,CAE5B;cAEe,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IA8DnF,OAAO,CAAC,iBAAiB;YAoCX,QAAQ;CAKtB;AAED,MAAM,MAAM,QAAQ,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;AAE1D,MAAM,WAAW,0BAA0B;IAC1C,IAAI,CAAC,EAAE,MAAM,OAAO,OAAO,GAAG,OAAO,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,MAAM,CAAC;IACrD,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,OAAO,CAAC,EAAE,cAAc,CAAC;CACzB;AAED,qBAAa,gBAAgB;IAChB,OAAO,CAAC,MAAM;gBAAN,MAAM,GAAE,0BAA+B;IAa3D,IAAI,KAAK,IAAI,WAAW,CAEvB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,QAAQ,IAAI,QAAQ,CAEvB;IAED,IAAI,oBAAoB,IAAI,CAAC,MAAM,EAAE,SAAS,KAAK,MAAM,CAExD;IAED,IAAI,OAAO,IAAI,WAAW,CAEzB;IAED,IAAI,OAAO,CAAC,UAAU,EAAE,WAAW,EAElC;IAED,IAAI,OAAO,IAAI,cAAc,CAE5B;IAED,IAAI,OAAO,CAAC,UAAU,EAAE,cAAc,EAErC;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;CACD;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,SAAK,GAAG,MAAM,CAmBlE"}
@@ -153,7 +153,7 @@ var API = /** @class */ (function () {
153
153
  if (!siteId) {
154
154
  throw new Error("Request failed. Missing \"siteId\" parameter.");
155
155
  }
156
- var siteIdHost = "https://".concat(siteId, ".a.searchspring.io");
156
+ var siteIdHost = "https://".concat(siteId, ".a").concat(context.subDomain ? ".".concat(context.subDomain) : '', ".searchspring.io");
157
157
  var origin = (this.configuration.origin || siteIdHost).replace(/\/$/, '');
158
158
  var url = "".concat(origin, "/").concat(context.path.replace(/^\//, ''));
159
159
  // merging globals to request query
@@ -213,19 +213,19 @@ var RecommendAPI = /** @class */ (function (_super) {
213
213
  };
214
214
  RecommendAPI.prototype.postRecommendations = function (requestParameters) {
215
215
  return __awaiter(this, void 0, void 0, function () {
216
- var headerParameters, siteId, path, response;
216
+ var headerParameters, path, response;
217
217
  return __generator(this, function (_a) {
218
218
  switch (_a.label) {
219
219
  case 0:
220
220
  headerParameters = {};
221
221
  headerParameters['Content-Type'] = 'text/plain';
222
- siteId = requestParameters.siteId;
223
- path = "/boost/".concat(siteId, "/recommend");
222
+ path = "/v1/recommend";
224
223
  return [4 /*yield*/, this.request({
225
224
  path: path,
226
225
  method: 'POST',
227
226
  headers: headerParameters,
228
227
  body: requestParameters,
228
+ subDomain: 'p13n',
229
229
  }, JSON.stringify(requestParameters))];
230
230
  case 1:
231
231
  response = _a.sent();
@@ -3,7 +3,9 @@ export declare class NetworkCache {
3
3
  protected memoryCache: Cache;
4
4
  protected config: DefaultCacheConfig;
5
5
  constructor(config?: CacheConfig);
6
+ load(): void;
6
7
  get(key: string): Response | void;
8
+ private purgeExpired;
7
9
  set(key: string, value: Response): void;
8
10
  clear(): void;
9
11
  }
@@ -1 +1 @@
1
- {"version":3,"file":"NetworkCache.d.ts","sourceRoot":"","sources":["../../../../src/Client/NetworkCache/NetworkCache.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAWrE,qBAAa,YAAY;IACxB,SAAS,CAAC,WAAW,EAAE,KAAK,CAAM;IAClC,SAAS,CAAC,MAAM,EAAE,kBAAkB,CAAC;gBAEzB,MAAM,CAAC,EAAE,WAAW;IAWzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAkCjC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,IAAI;IA2CvC,KAAK;CAUZ"}
1
+ {"version":3,"file":"NetworkCache.d.ts","sourceRoot":"","sources":["../../../../src/Client/NetworkCache/NetworkCache.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAWrE,qBAAa,YAAY;IACxB,SAAS,CAAC,WAAW,EAAE,KAAK,CAAM;IAClC,SAAS,CAAC,MAAM,EAAE,kBAAkB,CAAC;gBAEzB,MAAM,CAAC,EAAE,WAAW;IAczB,IAAI,IAAI,IAAI;IAcZ,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IA2ExC,OAAO,CAAC,YAAY;IAmBb,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,IAAI;IAuCvC,KAAK;CAUZ"}
@@ -3,13 +3,15 @@ const CACHE_STORAGE_KEY = 'ss-networkcache';
3
3
  const defaultConfig = {
4
4
  enabled: true,
5
5
  ttl: 300000, // ms
6
- maxSize: 200, // KB
6
+ maxSize: 1000, // KB
7
7
  purgeable: true,
8
8
  };
9
9
  export class NetworkCache {
10
10
  constructor(config) {
11
11
  this.memoryCache = {};
12
12
  this.config = deepmerge(defaultConfig, config || {});
13
+ this.load();
14
+ // this allows you to pre-populate the cache from the config - primarily used for email recs
13
15
  this.config?.entries &&
14
16
  Object.keys(this.config.entries).map((key) => {
15
17
  if (this.config.entries && this.config.entries[key]) {
@@ -17,70 +19,134 @@ export class NetworkCache {
17
19
  }
18
20
  });
19
21
  }
22
+ load() {
23
+ // initialize cache from session storage
24
+ if (typeof window !== 'undefined' && window?.sessionStorage) {
25
+ const stored = window.sessionStorage.getItem(CACHE_STORAGE_KEY);
26
+ const newStored = {
27
+ ...(stored && JSON.parse(stored)),
28
+ };
29
+ this.memoryCache = newStored || {};
30
+ }
31
+ this.purgeExpired();
32
+ }
20
33
  get(key) {
21
34
  if (this.config.enabled) {
35
+ this.load();
22
36
  try {
23
- if (this.memoryCache[key]) {
24
- if (Date.now() < this.memoryCache[key].expires) {
25
- return deepmerge({}, this.memoryCache[key].value);
37
+ let ignoreKeys = [];
38
+ if (typeof window !== 'undefined') {
39
+ const navigationType = window.performance?.getEntriesByType('navigation')?.[0]?.type;
40
+ if (navigationType === 'back_forward') {
41
+ ignoreKeys = ['lastViewed', 'cart'];
26
42
  }
27
43
  }
28
- if (typeof window !== 'undefined' && window?.sessionStorage) {
29
- const stored = window.sessionStorage.getItem(CACHE_STORAGE_KEY);
30
- const localData = stored && JSON.parse(stored);
31
- if (localData && key && localData[key]) {
44
+ if (Object.keys(this.memoryCache).length && key) {
45
+ let storageKey = key;
46
+ //this only applies to search calls
47
+ if (ignoreKeys.length && key.startsWith('/api/search/search.json')) {
48
+ try {
49
+ const url = key.split('{')[0];
50
+ const payload = '{' + key.split('{')[1];
51
+ const searchParams = JSON.parse(payload);
52
+ const foundKey = Object.keys(this.memoryCache).find((cachedResponse) => {
53
+ try {
54
+ const storedUrl = cachedResponse.split('{')[0];
55
+ if (storedUrl == url) {
56
+ const storedPayload = '{' + cachedResponse.split('{')[1];
57
+ const parsedPayload = JSON.parse(storedPayload);
58
+ const allKeys = Array.from(new Set([...Object.keys(searchParams), ...Object.keys(parsedPayload)]));
59
+ for (const k of allKeys) {
60
+ if (ignoreKeys.includes(k))
61
+ continue;
62
+ if (JSON.stringify(searchParams[k]) !== JSON.stringify(parsedPayload[k])) {
63
+ return false;
64
+ }
65
+ }
66
+ return true;
67
+ }
68
+ else
69
+ return false;
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ });
75
+ if (foundKey) {
76
+ storageKey = foundKey;
77
+ }
78
+ }
79
+ catch (e) {
80
+ // key is not json
81
+ }
82
+ }
83
+ if (this.memoryCache[storageKey]) {
32
84
  // compare the expiry time of the item with the current time
33
- if (Date.now() >= localData[key].expires) {
85
+ if (Date.now() >= this.memoryCache[storageKey].expires) {
34
86
  // remove item
35
87
  const newStored = {
36
- ...localData,
88
+ ...this.memoryCache,
37
89
  };
38
- delete newStored[key];
90
+ delete newStored[storageKey];
39
91
  // update storage
40
92
  window.sessionStorage.setItem(CACHE_STORAGE_KEY, JSON.stringify(newStored));
41
93
  }
42
94
  else {
43
- return localData[key].value;
95
+ return this.memoryCache[storageKey].value;
44
96
  }
45
97
  }
46
98
  }
47
99
  }
48
- catch {
49
- console.warn('something went wrong getting from cache');
100
+ catch (err) {
101
+ console.warn('something went wrong getting from cache', err);
102
+ }
103
+ }
104
+ }
105
+ purgeExpired() {
106
+ Object.keys(this.memoryCache).forEach((cacheKey) => {
107
+ //clean up expired cache keys in memory
108
+ if (Date.now() > this.memoryCache[cacheKey].expires) {
109
+ delete this.memoryCache[cacheKey];
110
+ }
111
+ });
112
+ // update storage
113
+ try {
114
+ if (typeof window !== 'undefined' && window?.sessionStorage) {
115
+ const stringifiedCache = JSON.stringify(this.memoryCache);
116
+ window.sessionStorage.setItem(CACHE_STORAGE_KEY, stringifiedCache);
50
117
  }
51
118
  }
119
+ catch {
120
+ console.warn('failed to store network cache');
121
+ }
52
122
  }
53
123
  set(key, value) {
54
124
  if (this.config.enabled) {
125
+ this.load();
55
126
  try {
56
127
  const cacheObject = {
57
128
  value,
58
129
  expires: Date.now() + this.config.ttl,
59
130
  purgeable: this.config.purgeable,
60
131
  };
132
+ // purge old items if we are over max size
133
+ let size = new Blob([JSON.stringify(this.memoryCache)], { endings: 'native' }).size / 1024;
134
+ while (size > this.config.maxSize) {
135
+ const oldestKey = Object.keys(this.memoryCache)
136
+ .filter((key) => this.memoryCache[key].purgeable)
137
+ .sort((a, b) => {
138
+ return this.memoryCache[a].expires - this.memoryCache[b].expires;
139
+ })[0];
140
+ if (!oldestKey)
141
+ break;
142
+ delete this.memoryCache[oldestKey];
143
+ // recalculate size after removing oldest
144
+ size = new Blob([JSON.stringify(this.memoryCache)], { endings: 'native' }).size / 1024;
145
+ }
146
+ // store cache in memory
61
147
  this.memoryCache[key] = cacheObject;
62
148
  if (typeof window !== 'undefined' && window?.sessionStorage) {
63
- const stored = window.sessionStorage.getItem(CACHE_STORAGE_KEY);
64
- const newStored = {
65
- ...(stored && JSON.parse(stored)),
66
- };
67
- newStored[key] = cacheObject;
68
- let size = new Blob([JSON.stringify(newStored)], { endings: 'native' }).size / 1024;
69
- while (size > this.config.maxSize) {
70
- const oldestKey = Object.keys(newStored)
71
- .filter((key) => newStored[key].purgeable)
72
- .sort((a, b) => {
73
- return newStored[a].expires - newStored[b].expires;
74
- })[0];
75
- if (!oldestKey)
76
- break;
77
- delete newStored[oldestKey];
78
- // recalculate size after removing oldest
79
- size = new Blob([JSON.stringify(newStored)], { endings: 'native' }).size / 1024;
80
- }
81
- if (size < this.config.maxSize) {
82
- window.sessionStorage.setItem(CACHE_STORAGE_KEY, JSON.stringify(newStored));
83
- }
149
+ window.sessionStorage.setItem(CACHE_STORAGE_KEY, JSON.stringify(this.memoryCache));
84
150
  }
85
151
  }
86
152
  catch {
@@ -13,6 +13,7 @@ export interface RequestOpts {
13
13
  headers: HTTPHeaders;
14
14
  query?: HTTPQuery;
15
15
  body?: HTTPBody;
16
+ subDomain?: string;
16
17
  }
17
18
  export declare class API {
18
19
  configuration: ApiConfiguration;
@@ -1 +1 @@
1
- {"version":3,"file":"Abstract.d.ts","sourceRoot":"","sources":["../../../../src/Client/apis/Abstract.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAGrD,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIvE,MAAM,MAAM,IAAI,GAAG,GAAG,CAAC;AACvB,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAC1F,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,GAAG,SAAS,CAAA;CAAE,CAAC;AAClI,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,QAAQ,GAAG,eAAe,CAAC;AAEzD,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;CAChB;AAED,qBAAa,GAAG;IAMI,aAAa,EAAE,gBAAgB;IALlD,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,UAAU,CAAK;IAEhB,KAAK,EAAE,YAAY,CAAC;gBAER,aAAa,EAAE,gBAAgB;IAIlD,SAAS,KAAK,IAAI,IAAI,OAAO,CAE5B;cAEe,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IA8DnF,OAAO,CAAC,iBAAiB;YAoCX,QAAQ;CAKtB;AAED,MAAM,MAAM,QAAQ,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;AAE1D,MAAM,WAAW,0BAA0B;IAC1C,IAAI,CAAC,EAAE,MAAM,OAAO,OAAO,GAAG,OAAO,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,MAAM,CAAC;IACrD,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,OAAO,CAAC,EAAE,cAAc,CAAC;CACzB;AAED,qBAAa,gBAAgB;IAChB,OAAO,CAAC,MAAM;gBAAN,MAAM,GAAE,0BAA+B;IAa3D,IAAI,KAAK,IAAI,WAAW,CAEvB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,QAAQ,IAAI,QAAQ,CAEvB;IAED,IAAI,oBAAoB,IAAI,CAAC,MAAM,EAAE,SAAS,KAAK,MAAM,CAExD;IAED,IAAI,OAAO,IAAI,WAAW,CAEzB;IAED,IAAI,OAAO,CAAC,UAAU,EAAE,WAAW,EAElC;IAED,IAAI,OAAO,IAAI,cAAc,CAE5B;IAED,IAAI,OAAO,CAAC,UAAU,EAAE,cAAc,EAErC;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;CACD;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,SAAK,GAAG,MAAM,CAmBlE"}
1
+ {"version":3,"file":"Abstract.d.ts","sourceRoot":"","sources":["../../../../src/Client/apis/Abstract.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAGrD,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAIvE,MAAM,MAAM,IAAI,GAAG,GAAG,CAAC;AACvB,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAC1F,MAAM,MAAM,SAAS,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,GAAG,SAAS,CAAA;CAAE,CAAC;AAClI,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,QAAQ,GAAG,eAAe,CAAC;AAEzD,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,GAAG;IAMI,aAAa,EAAE,gBAAgB;IALlD,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,UAAU,CAAK;IAEhB,KAAK,EAAE,YAAY,CAAC;gBAER,aAAa,EAAE,gBAAgB;IAIlD,SAAS,KAAK,IAAI,IAAI,OAAO,CAE5B;cAEe,OAAO,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IA8DnF,OAAO,CAAC,iBAAiB;YAoCX,QAAQ;CAKtB;AAED,MAAM,MAAM,QAAQ,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;AAE1D,MAAM,WAAW,0BAA0B;IAC1C,IAAI,CAAC,EAAE,MAAM,OAAO,OAAO,GAAG,OAAO,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,oBAAoB,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,MAAM,CAAC;IACrD,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,OAAO,CAAC,EAAE,cAAc,CAAC;CACzB;AAED,qBAAa,gBAAgB;IAChB,OAAO,CAAC,MAAM;gBAAN,MAAM,GAAE,0BAA+B;IAa3D,IAAI,KAAK,IAAI,WAAW,CAEvB;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,IAAI,QAAQ,IAAI,QAAQ,CAEvB;IAED,IAAI,oBAAoB,IAAI,CAAC,MAAM,EAAE,SAAS,KAAK,MAAM,CAExD;IAED,IAAI,OAAO,IAAI,WAAW,CAEzB;IAED,IAAI,OAAO,CAAC,UAAU,EAAE,WAAW,EAElC;IAED,IAAI,OAAO,IAAI,cAAc,CAE5B;IAED,IAAI,OAAO,CAAC,UAAU,EAAE,cAAc,EAErC;IAED,IAAI,IAAI,IAAI,OAAO,CAElB;CACD;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,SAAK,GAAG,MAAM,CAmBlE"}
@@ -79,7 +79,7 @@ export class API {
79
79
  if (!siteId) {
80
80
  throw new Error(`Request failed. Missing "siteId" parameter.`);
81
81
  }
82
- const siteIdHost = `https://${siteId}.a.searchspring.io`;
82
+ const siteIdHost = `https://${siteId}.a${context.subDomain ? `.${context.subDomain}` : ''}.searchspring.io`;
83
83
  const origin = (this.configuration.origin || siteIdHost).replace(/\/$/, '');
84
84
  let url = `${origin}/${context.path.replace(/^\//, '')}`;
85
85
  // merging globals to request query
@@ -125,13 +125,13 @@ export class RecommendAPI extends API {
125
125
  async postRecommendations(requestParameters) {
126
126
  const headerParameters = {};
127
127
  headerParameters['Content-Type'] = 'text/plain';
128
- const siteId = requestParameters.siteId;
129
- const path = `/boost/${siteId}/recommend`;
128
+ const path = `/v1/recommend`;
130
129
  const response = await this.request({
131
130
  path,
132
131
  method: 'POST',
133
132
  headers: headerParameters,
134
133
  body: requestParameters,
134
+ subDomain: 'p13n',
135
135
  }, JSON.stringify(requestParameters));
136
136
  return response;
137
137
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@searchspring/snap-client",
3
- "version": "0.71.0",
3
+ "version": "0.72.1",
4
4
  "description": "Snap Client",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -20,12 +20,12 @@
20
20
  "test:watch": "jest --watch"
21
21
  },
22
22
  "dependencies": {
23
- "@searchspring/snap-toolbox": "0.71.0",
23
+ "@searchspring/snap-toolbox": "0.72.1",
24
24
  "deepmerge": "4.3.1"
25
25
  },
26
26
  "sideEffects": false,
27
27
  "files": [
28
28
  "dist/**/*"
29
29
  ],
30
- "gitHead": "86eb6dec8c57def5b869157dc00bbb9b6e793619"
30
+ "gitHead": "907fc94dca17369d89e7aa9d60bbe1acdf05a3a7"
31
31
  }