bv-ui-core 2.9.6 → 2.9.8

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.
@@ -9,6 +9,8 @@ The BvFetch module provides methods to cache duplicate API calls and interact wi
9
9
  `shouldCache (Function):` A function that takes the API response JSON as input and returns a boolean indicating whether to cache the response or not. This allows you to implement custom logic based on the response content. If caching is desired, the function should return true; otherwise, false.
10
10
  `cacheName (String):` Optional. Specifies the name of the cache to be used. If not provided, the default cache name 'bvCache' will be used.
11
11
  `cacheLimit (Integer)`: Optional. Specifies the cache size limit for the cache storage. Its value should be in MB. Default value is 10 MB.
12
+ `excludeHeaders (Array[String])`: Includes the list of headers that are excluded from the cacheKey
13
+
12
14
 
13
15
  ## bvFetchFunc Method Parameters
14
16
  `url (String):` The URL of the API endpoint to fetch data from.
@@ -17,17 +19,22 @@ The BvFetch module provides methods to cache duplicate API calls and interact wi
17
19
  ## bvFetchFunc Return Value
18
20
  `Promise<Response>:` A promise that resolves to the API response. If the response is cached, it returns the cached response. Otherwise, it fetches data from the API endpoint, caches the response according to the caching logic, and returns the fetched response.
19
21
 
20
- ## generateCacheKey Method Parameters:
21
- `url (String):` The URL of the API endpoint.
22
- `options (Object):` Optional request options.
23
- ## generateCacheKey Return Value:
24
- `string:` The generated cache key.
22
+ ## generateCacheKey Method
23
+
24
+ Generates a cache key as a Request object for a given URL and options, filtering out any headers specified in `excludeHeaders`.
25
+
26
+ **Parameters:**
27
+ - `url (String)`: The URL of the API endpoint.
28
+ - `options (Object)`: *(Optional)* Request options (e.g., headers, method, etc.).
29
+
30
+ **Returns:**
31
+ `Request`: The generated Request object to be used as a cache key.
25
32
 
26
- ## retrieveCachedUrls Method
27
- Retrieves cached URLs from the cache storage associated with the provided cache name.
28
- ## retrieveCachedUrls Parameters
33
+ ## retrievecachedRequests Method
34
+ Retrieves cached Requests from the cache storage associated with the provided cache name.
35
+ ## retrievecachedRequests Parameters
29
36
  This method takes no parameters.
30
- ## retrieveCachedUrls Return Value
37
+ ## retrievecachedRequests Return Value
31
38
  `void:` This method does not return anything.
32
39
 
33
40
  ## fetchDataAndCache Method
@@ -77,8 +84,10 @@ var BvFetch = require('bv-ui-core/lib/bvFetch')
77
84
 
78
85
  // Initialize BV Fetch instance
79
86
  const bvFetch = new BVFetch({
80
- canBeCached: canBeCached, // optional
81
- cacheName: "bvCache" // optional, default is "bvCache"
87
+ shouldCache: function(responseJson) => { /* return true or false */ }, // optional, callback function to check if the response is cachable
88
+ cacheName: "bvCache" // optional, default is "bvCache"
89
+ cacheLimit: 10, // optional but default is 10
90
+ excludeHeaders: [], // optional, list of headers to be excluded from cache
82
91
  });
83
92
 
84
93
  // Make API calls using bvFetchFunc method
@@ -5,46 +5,122 @@
5
5
 
6
6
  const { fetch } = require('../polyfills/fetch')
7
7
 
8
- module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
8
+ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit, excludeHeaders = [] }) {
9
9
  this.shouldCache = shouldCache;
10
10
  this.cacheName = cacheName || 'bvCache';
11
11
  this.cacheLimit = cacheLimit * 1024 * 1024 || 10 * 1024 * 1024;
12
12
  this.fetchPromises = new Map();
13
- this.cachedUrls = new Set();
13
+ this.cachedRequests = new Set();
14
+ this.excludeHeaders = excludeHeaders;
14
15
 
15
16
  /**
16
- * Generates a unique cache key for the given URL and options.
17
- * @param {string} url - The URL of the API endpoint.
18
- * @param {Object} options - Optional request options.
19
- * @returns {string} The generated cache key.
17
+ * Checks if a request is present in a set of cached URLs.
18
+ *
19
+ * @param {Set} cachedRequests - A set of cached request objects.
20
+ * @param {Object} cacheKey - The request object to check for in the cachedRequests set.
21
+ * @param {string} cacheKey.url - The URL of the request.
22
+ * @param {Headers} cacheKey.headers - The headers of the request.
23
+ * @returns {boolean} - Returns true if the request is found in the cachedRequests set, otherwise false.
20
24
  */
25
+ function isRequestInSet (cachedRequests, cacheKey) {
26
+ try {
27
+ // Convert the Set to an array and check if any request matches
28
+ return [...cachedRequests].some((cachedRequest) => {
29
+ // Compare URLs
30
+ if (cachedRequest.url !== cacheKey.url) {
31
+ return false;
32
+ }
33
+
34
+ // Compare headers
35
+ const cachedHeaders = [...cachedRequest.headers.entries()];
36
+ const keyHeaders = [...cacheKey.headers.entries()];
37
+
38
+ if (cachedHeaders.length !== keyHeaders.length) {
39
+ return false; // Different number of headers
40
+ }
41
+ // Check if all headers match
42
+ return cachedHeaders.every(([key, value]) => {
43
+ return cacheKey.headers.get(key) === value;
44
+ });
45
+ });
46
+ }
47
+ catch (error) {
48
+ console.warn('Error checking in if request is in cache: ', error);
49
+ return false;
50
+ }
51
+ }
52
+
21
53
 
54
+ /**
55
+ * Creates a new Request object with the given URL and options.
56
+ *
57
+ * @param {string} url - The URL to which the request is sent.
58
+ * @param {Object} options - The options to apply to the request.
59
+ * @returns {Request} The created Request object.
60
+ */
61
+
62
+ /**
63
+ * Generates a cache key as a Request object for the given URL and options.
64
+ * Filters out headers specified in `excludeHeaders` from the options before creating the Request.
65
+ *
66
+ * @param {string} url - The URL of the API endpoint.
67
+ * @param {Object} options - Optional request options (e.g., headers, method, etc.).
68
+ * @returns {Request} The generated Request object to be used as a cache key.
69
+ */
22
70
  this.generateCacheKey = (url, options) => {
23
- const optionsString = (Object.keys(options).length > 0) ? JSON.stringify(options) : '';
24
- const key = url + optionsString;
71
+ const filteredOptions = Object.assign({}, options);
72
+ this.excludeHeaders.forEach(header => {
73
+ if (filteredOptions.headers && filteredOptions.headers[header]) {
74
+ delete filteredOptions.headers[header];
75
+ }
76
+ }
77
+ );
78
+ const key = new Request(url, filteredOptions);
25
79
  return key;
26
80
  };
27
81
 
82
+ /**
83
+ * Serializes a request object into a JSON string for use as a cache key.
84
+ * The serialization includes the URL, method, and sorted headers of the request.
85
+ * @param {Request} request - The request object to serialize.
86
+ * @returns {string} A JSON string representing the serialized request.
87
+ */
88
+ this.serializeRequestKey = (request) => {
89
+ const url = request.url;
90
+ const method = request.method || 'GET';
91
+ // Convert headers to a sorted array of [key, value] pairs for consistency
92
+ const headersArr = Array.from(request.headers.entries()).sort(([a], [b]) => a.localeCompare(b));
93
+ return JSON.stringify([url, method, { headers: headersArr }]);
94
+ }
95
+
28
96
  /**
29
- * Retrieves cached URLs from the cache storage associated with the provided cache name.
97
+ * Retrieves cached Requests from the cache storage associated with the provided cache name.
30
98
  * @returns {void}
31
99
  */
32
100
 
33
- this.retrieveCachedUrls = () => {
101
+ this.retrievecachedRequests = () => {
34
102
  // Open the Cache Storage
35
103
  caches.open(this.cacheName).then(cache => {
36
104
  // Get all cache keys
37
105
  cache.keys().then(keys => {
38
106
  keys.forEach(request => {
39
- this.cachedUrls.add(request.url);
107
+ const headers = {};
108
+ request.headers.forEach((value, key) => {
109
+ if (!this.excludeHeaders.includes(key)) {
110
+ headers[key] = value;
111
+ }
112
+ });
113
+
114
+ // Generate the cache key with headers and URL
115
+ const cacheKey = this.generateCacheKey(request.url, { headers });
116
+ this.cachedRequests.add(cacheKey); // Add to the set
40
117
  });
41
118
  });
42
119
  });
43
-
44
120
  }
45
121
 
46
- //callretrieveCachedUrls function to set the cache URL set with the cached URLS
47
- this.retrieveCachedUrls();
122
+ //callretrievecachedRequests function to set the cache URL set with the cached URLS
123
+ this.retrievecachedRequests();
48
124
 
49
125
  /**
50
126
  * Fetches data from the specified URL, caches the response, and returns the response.
@@ -75,6 +151,11 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
75
151
  this.cacheData = (response, cacheKey) => {
76
152
  const errJson = response.clone();
77
153
  let canBeCached = true;
154
+ if (!response.ok) {
155
+ // If the response is not ok, we don't cache it
156
+ canBeCached = false;
157
+ return;
158
+ }
78
159
  // Check for error in response obj
79
160
  errJson.json().then(json => {
80
161
  if (typeof this.shouldCache === 'function') {
@@ -106,8 +187,8 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
106
187
  // Cache the response
107
188
  caches.open(this.cacheName).then(cache => {
108
189
  cache.put(cacheKey, newResponse);
109
- //add key to cachedUrls set
110
- this.cachedUrls.add(cacheKey);
190
+ //add key to cachedRequests set
191
+ this.cachedRequests.add(cacheKey);
111
192
  });
112
193
  });
113
194
  }
@@ -122,8 +203,8 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
122
203
  * @throws {Error} Throws an error if there's any problem fetching from cache.
123
204
  */
124
205
  this.fetchFromCache = (cacheKey) => {
125
- // Check if the URL is in the set of cached URLs
126
- if (!this.cachedUrls.has(cacheKey)) {
206
+ // Check if the URL is in the set of cached requests set
207
+ if (!isRequestInSet(this.cachedRequests, cacheKey)) {
127
208
  return Promise.resolve(null);
128
209
  }
129
210
 
@@ -133,7 +214,7 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
133
214
  return cache.match(cacheKey)
134
215
  .then((cachedResponse) => {
135
216
  if (!cachedResponse) {
136
- this.cachedUrls.delete(cacheKey)
217
+ this.cachedRequests.delete(cacheKey)
137
218
  return Promise.resolve(null);
138
219
  }
139
220
  const cachedTime = cachedResponse.headers.get('X-Bazaarvoice-Cached-Time');
@@ -162,11 +243,11 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
162
243
 
163
244
  const cacheKey = this.generateCacheKey(url, options);
164
245
  // If an ongoing fetch promise exists for the URL, return it
165
- if (this.fetchPromises.has(cacheKey)) {
166
- return this.fetchPromises.get(cacheKey).then(res => res.clone());
246
+ if (this.fetchPromises.has(this.serializeRequestKey(cacheKey))) {
247
+ return this.fetchPromises.get(this.serializeRequestKey(cacheKey)).then(res => res.clone());
167
248
  }
168
249
 
169
- // Check if response is available in cache
250
+ // Check if response is available in cache
170
251
  const newPromise = this.fetchFromCache(cacheKey)
171
252
  .then((cachedResponse) => {
172
253
  // If response found in cache, return it
@@ -178,14 +259,14 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
178
259
  });
179
260
 
180
261
  // Store the ongoing fetch promise
181
- this.fetchPromises.set(cacheKey, newPromise);
262
+ this.fetchPromises.set(this.serializeRequestKey(cacheKey), newPromise);
182
263
 
183
264
  //initiate cache cleanUp
184
265
  this.debounceCleanupExpiredCache();
185
266
 
186
267
  // When fetch completes or fails, remove the promise from the store
187
268
  newPromise.finally(() => {
188
- this.fetchPromises.delete(cacheKey);
269
+ this.fetchPromises.delete(this.serializeRequestKey(cacheKey));
189
270
  });
190
271
 
191
272
  return newPromise.then(res => res.clone());
@@ -218,7 +299,7 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
218
299
  const cacheAge = (currentTimestamp - cachedTime) / 1000;
219
300
  if (cacheAge >= ttl) {
220
301
  cache.delete(key);
221
- this.cachedUrls.delete(key);
302
+ this.cachedRequests.delete(key);
222
303
  }
223
304
  });
224
305
  });
@@ -266,7 +347,7 @@ module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit }) {
266
347
  cacheEntries.forEach(entry => {
267
348
  if (currentSize > this.cacheLimit) {
268
349
  cache.delete(entry.key);
269
- this.cachedUrls.delete(entry.key);
350
+ this.cachedRequests.delete(entry.key);
270
351
  currentSize -= entry.size;
271
352
  }
272
353
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bv-ui-core",
3
- "version": "2.9.6",
3
+ "version": "2.9.8",
4
4
  "license": "Apache 2.0",
5
5
  "description": "Bazaarvoice UI-related JavaScript",
6
6
  "repository": {
@@ -38,16 +38,27 @@ describe('BvFetch', function () {
38
38
 
39
39
  it('should generate correct cache key', function () {
40
40
  const url = 'https://jsonplaceholder.typicode.com/todos';
41
- const options = {};
42
- const expectedKey = 'https://jsonplaceholder.typicode.com/todos';
41
+ const options = {
42
+ method: 'GET',
43
+ headers: {
44
+ 'X-Header-1': 'Value 1',
45
+ }
46
+ };
47
+ const expectedKey = new Request(url, options)
43
48
  const generatedKey = bvFetchInstance.generateCacheKey(url, options);
44
- expect(generatedKey).to.equal(expectedKey);
49
+ expect(generatedKey.url).to.equal(expectedKey.url);
50
+ expect(generatedKey.headers).to.deep.equal(expectedKey.headers);
45
51
  });
46
52
 
47
53
 
48
54
  it('should fetch from cache when the response is cached', function (done) {
49
55
  const url = 'https://jsonplaceholder.typicode.com/todos';
50
- const options = {};
56
+ const options = {
57
+ method: 'GET',
58
+ headers: {
59
+ 'X-Header-1': 'Value 1',
60
+ }
61
+ };
51
62
 
52
63
  // Mocking cache response
53
64
  const mockResponse = new Response('Mock Data', {
@@ -64,7 +75,8 @@ describe('BvFetch', function () {
64
75
  // Overriding the stub for this specific test case
65
76
  caches.open.resolves({
66
77
  match: (key) => {
67
- expect(key).to.equal(cacheKey);
78
+ expect(key.url).to.equal(cacheKey.url);
79
+ expect(key.headers).to.deep.equal(cacheKey.headers);
68
80
  return Promise.resolve(mockResponse)
69
81
  },
70
82
  put: (key, response) => {
@@ -74,7 +86,7 @@ describe('BvFetch', function () {
74
86
  });
75
87
 
76
88
  // Simulate that the response is cached
77
- bvFetchInstance.cachedUrls.add(cacheKey);
89
+ bvFetchInstance.cachedRequests.add(cacheKey);
78
90
 
79
91
  // Call the function under test
80
92
  bvFetchInstance.bvFetchFunc(url, options)