bv-ui-core 2.8.2 → 2.9.1

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -38,6 +38,7 @@ module's directory.
38
38
  ## Modules
39
39
 
40
40
  - [body](./lib/body)
41
+ - [bvFetch](./lib/bvFetch/)
41
42
  - [checkHighContrast](./lib/checkHighContrast)
42
43
  - [cookie](./lib/cookie)
43
44
  - [cookieConsent](./lib/cookieConsent)
@@ -0,0 +1,47 @@
1
+ # BvFetch
2
+
3
+ The BvFetch module provides methods to cache duplicate API calls and interact with the cacheStorage
4
+
5
+
6
+ ## The following methods are provided:
7
+
8
+ ## BvFetch Parameters
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
+
11
+ `cacheName (String):` Optional. Specifies the name of the cache to be used. If not provided, the default cache name 'bvCache' will be used.
12
+
13
+ ## bvFetchFunc Method Parameters
14
+ `url (String):` The URL of the API endpoint to fetch data from.
15
+
16
+ `options (Object):` Optional request options such as headers, method, etc., as supported by the Fetch API.
17
+
18
+ ## bvFetchFunc Return Value
19
+ `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.
20
+
21
+ ## flushCache Method Parameters
22
+ This method takes no parameters.
23
+
24
+ ## flushCache Return Value
25
+ `Promise<void>:` A promise indicating the completion of cache flush operation.
26
+
27
+
28
+ ## Usage with of `BvFetch`:
29
+
30
+ ```js
31
+ var BvFetch = require('bv-ui-core/lib/bvFetch')
32
+
33
+ // Initialize BV Fetch instance
34
+ const bvFetch = new BVFetch({
35
+ canBeCached: canBeCached, // optional
36
+ cacheName: "bvCache" // optional, default is "bvCache"
37
+ });
38
+
39
+ // Make API calls using bvFetchFunc method
40
+ bvFetch.bvFetchFunc('https://api.example.com/data')
41
+ .then(response => {
42
+ // Handle response
43
+ })
44
+ .catch(error => {
45
+ // Handle error
46
+ });
47
+ ```
@@ -0,0 +1,128 @@
1
+
2
+ /**
3
+ * @fileOverview
4
+ * Provides api response caching utilties
5
+ */
6
+
7
+ const { fetch } = require('../polyfills/fetch')
8
+
9
+ module.exports = function BvFetch ({ shouldCache, cacheName }) {
10
+ this.shouldCache = shouldCache;
11
+ this.cacheName = cacheName || 'bvCache';
12
+ this.fetchPromises = new Map();
13
+
14
+ /**
15
+ * Generates a unique cache key for the given URL and options.
16
+ * @param {string} url - The URL of the API endpoint.
17
+ * @param {Object} options - Optional request options.
18
+ * @returns {string} The generated cache key.
19
+ */
20
+
21
+ this.generateCacheKey = (url, options) => {
22
+ const optionsString = (Object.keys(options).length > 0) ? JSON.stringify(options) : '';
23
+ const key = url + optionsString;
24
+ return key;
25
+ };
26
+
27
+ /**
28
+ * Fetches data from the API endpoint, caches responses, and handles caching logic.
29
+ * @param {string} url - The URL of the API endpoint.
30
+ * @param {Object} options - Optional request options.
31
+ * @returns {Promise<Response>} A promise resolving to the API response.
32
+ */
33
+
34
+ this.bvFetchFunc = (url, options = {}) => {
35
+ // get the key
36
+ const cacheKey = this.generateCacheKey(url, options);
37
+
38
+ // check if its available in the cache
39
+ return caches.open(this.cacheName)
40
+ .then(currentCache => currentCache.match(cacheKey))
41
+ .then(cachedResponse => {
42
+ if (cachedResponse) {
43
+ const cachedTime = cachedResponse.headers.get('X-Cached-Time');
44
+ const ttl = cachedResponse.headers.get('Cache-Control').match(/max-age=(\d+)/)[1];
45
+ const currentTimestamp = Date.now();
46
+ const cacheAge = (currentTimestamp - cachedTime) / 1000;
47
+
48
+ if (cacheAge < ttl) {
49
+ // Cached response found
50
+ return cachedResponse.clone();
51
+ }
52
+ }
53
+
54
+ // check if there is an ongoing promise
55
+ if (this.fetchPromises.has(cacheKey)) {
56
+ return this.fetchPromises.get(cacheKey).then(res => res.clone());
57
+ }
58
+
59
+ // Make a new call
60
+ const newPromise = fetch(url, options);
61
+
62
+ // Push the newPromise to the fetchPromises Map
63
+ this.fetchPromises.set(cacheKey, newPromise);
64
+
65
+ return newPromise
66
+ .then(response => {
67
+ const clonedResponse = response.clone();
68
+ const errJson = clonedResponse.clone()
69
+ let canBeCached = true;
70
+ return errJson.json().then(json => {
71
+ if (typeof this.shouldCache === 'function') {
72
+ canBeCached = this.shouldCache(json);
73
+ }
74
+ return response
75
+ }).then(res => {
76
+ if (canBeCached) {
77
+ const newHeaders = new Headers();
78
+ clonedResponse.headers.forEach((value, key) => {
79
+ newHeaders.append(key, value);
80
+ });
81
+ newHeaders.append('X-Cached-Time', Date.now());
82
+
83
+ const newResponse = new Response(clonedResponse._bodyBlob, {
84
+ status: clonedResponse.status,
85
+ statusText: clonedResponse.statusText,
86
+ headers: newHeaders
87
+ });
88
+ //Delete promise from promise map once its resolved
89
+ this.fetchPromises.delete(cacheKey);
90
+
91
+ return caches.open(this.cacheName)
92
+ .then(currentCache =>
93
+ currentCache.put(cacheKey, newResponse)
94
+ )
95
+ .then(() => res);
96
+ }
97
+ else {
98
+ //Delete promise from promise map if error exists
99
+ this.fetchPromises.delete(cacheKey);
100
+
101
+ return res
102
+ }
103
+
104
+ });
105
+ })
106
+ })
107
+ .catch(err => {
108
+ // Remove the promise that was pushed earlier
109
+ this.fetchPromises.delete(cacheKey);
110
+ throw err;
111
+ });
112
+ };
113
+
114
+ /**
115
+ * Clears all cache entries stored in the cache storage.
116
+ * @returns {Promise<void>} A promise indicating cache flush completion.
117
+ */
118
+
119
+ this.flushCache = () => {
120
+ return caches.open(this.cacheName).then(cache => {
121
+ return cache.keys().then(keys => {
122
+ const deletionPromises = keys.map(key => cache.delete(key));
123
+ return Promise.all(deletionPromises);
124
+ });
125
+ });
126
+ };
127
+
128
+ }
@@ -152,8 +152,9 @@ module.exports = {
152
152
  done = true;
153
153
  clearTimeout(timeoutHandle);
154
154
  script.onload = script.onreadystatechange = script.onerror = null;
155
- script.parentNode.removeChild(script);
156
-
155
+ if (script && script.parentNode) {
156
+ script.parentNode.removeChild(script);
157
+ }
157
158
  if (!err) {
158
159
  _loadedUrls[url] = true;
159
160
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bv-ui-core",
3
- "version": "2.8.2",
3
+ "version": "2.9.1",
4
4
  "license": "Apache 2.0",
5
5
  "description": "Bazaarvoice UI-related JavaScript",
6
6
  "repository": {
@@ -0,0 +1,156 @@
1
+ //Imports
2
+
3
+ var BvFetch = require('../../../lib/bvFetch');
4
+
5
+ describe('BvFetch', function () {
6
+ let bvFetchInstance;
7
+ let cacheStub;
8
+ let cacheStorage;
9
+
10
+ beforeEach(function () {
11
+ bvFetchInstance = new BvFetch({
12
+ shouldCache: null,
13
+ cacheName: 'testCache'
14
+ });
15
+
16
+ // Define cacheStorage as a Map
17
+ cacheStorage = new Map();
18
+
19
+ // Stubbing caches.open
20
+ cacheStub = sinon.stub(caches, 'open').resolves({
21
+ match: key => {
22
+ const cachedResponse = cacheStorage.get(key);
23
+ return Promise.resolve(cachedResponse);
24
+ },
25
+ put: (key, response) => {
26
+ cacheStorage.set(key, response);
27
+ return Promise.resolve();
28
+ }
29
+ });
30
+
31
+ });
32
+
33
+ afterEach(function () {
34
+ bvFetchInstance = null;
35
+ // Restore the original method after each test
36
+ caches.open.restore();
37
+ });
38
+
39
+ it('should generate correct cache key', function () {
40
+ const url = 'https://jsonplaceholder.typicode.com/todos';
41
+ const options = {};
42
+ const expectedKey = 'https://jsonplaceholder.typicode.com/todos';
43
+ const generatedKey = bvFetchInstance.generateCacheKey(url, options);
44
+ expect(generatedKey).to.equal(expectedKey);
45
+ });
46
+
47
+
48
+ it('should fetch from cache when the response is cached', function (done) {
49
+ const url = 'https://jsonplaceholder.typicode.com/todos';
50
+ const options = {};
51
+
52
+ // Mocking cache response
53
+ const mockResponse = new Response('Mock Data', {
54
+ status: 200,
55
+ statusText: 'OK',
56
+ headers: {
57
+ 'Cache-Control': 'max-age=3600',
58
+ 'X-Cached-Time': Date.now()
59
+ }
60
+ });
61
+
62
+ const cacheKey = bvFetchInstance.generateCacheKey(url, options);
63
+
64
+ // Overriding the stub for this specific test case
65
+ caches.open.resolves({
66
+ match: (key) => {
67
+ expect(key).to.equal(cacheKey);
68
+ Promise.resolve(mockResponse)
69
+ },
70
+ put: (key, response) => {
71
+ cacheStorage.set(key, response);
72
+ return Promise.resolve();
73
+ }
74
+ });
75
+
76
+ bvFetchInstance.bvFetchFunc(url, options)
77
+ .then(response => {
78
+ // Check if response is fetched from cache
79
+ expect(response).to.not.be.null;
80
+
81
+ // Check if response is cached
82
+ const cachedResponse = cacheStorage.get(cacheKey);
83
+ expect(cachedResponse).to.not.be.null;
84
+
85
+ // Check if caches.open was called
86
+ expect(cacheStub.called).to.be.true;
87
+
88
+ done();
89
+ })
90
+ .catch(error => {
91
+ done(error); // Call done with error if any
92
+ })
93
+ });
94
+
95
+
96
+ it('should fetch from network when response is not cached', function (done) {
97
+ const url = 'https://jsonplaceholder.typicode.com/todos';
98
+ const options = {};
99
+
100
+ const cacheKey = bvFetchInstance.generateCacheKey(url, options);
101
+
102
+ caches.open.resolves({
103
+ match: (key) => {
104
+ expect(key).to.equal(cacheKey);
105
+ Promise.resolve(null)
106
+ },
107
+ put: (key, response) => {
108
+ cacheStorage.set(key, response);
109
+ return Promise.resolve();
110
+ }
111
+ });
112
+
113
+
114
+ bvFetchInstance.bvFetchFunc(url, options)
115
+ .then(response => {
116
+ // Check if response is fetched from network
117
+ expect(response).to.not.be.null;
118
+ console.log(response.body)
119
+
120
+ // Check if caches.match was called
121
+ expect(cacheStub.called).to.be.true;
122
+
123
+ done();
124
+ })
125
+ .catch(done);
126
+ });
127
+
128
+ it('should not cache response when there is an error', function (done) {
129
+ const url = 'https://jsonplaceholder.typicode.com/todos';
130
+ const options = {};
131
+
132
+ // Define shouldCache directly in bvFetchInstance
133
+ bvFetchInstance.shouldCache = (res) => {
134
+ return false
135
+ };
136
+
137
+ bvFetchInstance.bvFetchFunc(url, options)
138
+ .then(response => {
139
+ // Check if response is fetched from network
140
+ expect(response).to.not.be.null;
141
+ console.log(response.body)
142
+
143
+ // Check if caches.match was called
144
+ expect(cacheStub.calledOnce).to.be.true;
145
+
146
+ // Check if response is not cached
147
+ const cachedResponse = cacheStorage.get(url);
148
+ expect(cachedResponse).to.be.undefined;
149
+
150
+ done();
151
+ })
152
+ .catch(done);
153
+ });
154
+
155
+
156
+ });