bv-ui-core 0.1.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/.eslintrc +40 -0
- package/.travis.yml +5 -0
- package/CONTRIBUTING.md +98 -0
- package/LICENSE.md +13 -0
- package/OWNERS +2 -0
- package/README.md +63 -0
- package/catalog-info.yaml +30 -0
- package/karma.conf.js +130 -0
- package/lib/README.md +61 -0
- package/lib/api/.babelrc +3 -0
- package/lib/api/.eslintrc +9 -0
- package/lib/api/README.md +134 -0
- package/lib/api/index.js +15 -0
- package/lib/api/statistics.js +148 -0
- package/lib/application/README.md +96 -0
- package/lib/application/index.js +171 -0
- package/lib/body/README.md +13 -0
- package/lib/body/index.js +19 -0
- package/lib/bvFetch/README.md +101 -0
- package/lib/bvFetch/index.js +383 -0
- package/lib/checkHighContrast/README.md +26 -0
- package/lib/checkHighContrast/index.js +36 -0
- package/lib/cookie/README.md +29 -0
- package/lib/cookie/index.js +111 -0
- package/lib/cookieConsent/README.md +41 -0
- package/lib/cookieConsent/index.js +227 -0
- package/lib/cssLoadChecker/README.md +19 -0
- package/lib/cssLoadChecker/index.js +72 -0
- package/lib/date.now/README.md +11 -0
- package/lib/date.now/index.js +40 -0
- package/lib/domainPolice/README.md +66 -0
- package/lib/domainPolice/index.js +95 -0
- package/lib/evented/README.md +42 -0
- package/lib/evented/index.js +135 -0
- package/lib/getOriginalConstructor/README.md +16 -0
- package/lib/getOriginalConstructor/index.js +52 -0
- package/lib/global/README.md +10 -0
- package/lib/global/index.js +48 -0
- package/lib/ie/README.md +13 -0
- package/lib/ie/index.js +25 -0
- package/lib/loader/README.md +73 -0
- package/lib/loader/index.js +358 -0
- package/lib/logger/README.md +82 -0
- package/lib/logger/index.js +180 -0
- package/lib/messageFormat/README.md +10 -0
- package/lib/messageFormat/index.js +42 -0
- package/lib/namespacer/README.md +57 -0
- package/lib/namespacer/index.js +98 -0
- package/lib/performance/README.md +35 -0
- package/lib/performance/entries.js +5 -0
- package/lib/performance/getEntries.js +42 -0
- package/lib/performance/getEntriesByName.js +61 -0
- package/lib/performance/getEntriesByType.js +55 -0
- package/lib/performance/index.js +11 -0
- package/lib/performance/mark.js +97 -0
- package/lib/performance/measure.js +106 -0
- package/lib/performance/now.js +63 -0
- package/lib/pixelsDisplayed/README.md +19 -0
- package/lib/pixelsDisplayed/index.js +113 -0
- package/lib/polyfills/README.md +39 -0
- package/lib/polyfills/fetch/Headers.js +3 -0
- package/lib/polyfills/fetch/Request.js +3 -0
- package/lib/polyfills/fetch/Response.js +3 -0
- package/lib/polyfills/fetch/fetch.js +3 -0
- package/lib/polyfills/fetch/index.js +57 -0
- package/lib/polyfills/promise.js +265 -0
- package/lib/queryShadowDom/README.md +50 -0
- package/lib/queryShadowDom/index.js +93 -0
- package/lib/region/README.md +64 -0
- package/lib/region/europe/index.js +94 -0
- package/lib/region/europe/territories.js +40 -0
- package/lib/region/index.js +123 -0
- package/lib/staticAssetLoader/README.md +116 -0
- package/lib/staticAssetLoader/index.js +248 -0
- package/lib/waitForBody/README.md +21 -0
- package/lib/waitForBody/index.js +36 -0
- package/mkdocs.yaml +5 -0
- package/package.json +55 -0
- package/test/.eslintrc +5 -0
- package/test/fixtures/lib.loader.document-is-wrong-in-ie8.js +5 -0
- package/test/fixtures/lib.loader.loadscript-attribute.js +3 -0
- package/test/fixtures/lib.loader.loadscript.js +3 -0
- package/test/fixtures/lib.loader.loadstylesheet.css +3 -0
- package/test/unit/api/.eslintrc +6 -0
- package/test/unit/api/index.spec.js +37 -0
- package/test/unit/api/response.json +67 -0
- package/test/unit/api/statistics.spec.js +218 -0
- package/test/unit/application/index.spec.js +313 -0
- package/test/unit/body/index.spec.js +15 -0
- package/test/unit/bvFetch/index.spec.js +246 -0
- package/test/unit/checkHighContrast/index.spec.js +16 -0
- package/test/unit/cookie/index.spec.js +191 -0
- package/test/unit/cookieConsent/index.spec.js +119 -0
- package/test/unit/cssLoadChecker/index.spec.js +75 -0
- package/test/unit/date.now/index.spec.js +21 -0
- package/test/unit/domainPolice/index.spec.js +141 -0
- package/test/unit/evented/index.spec.js +59 -0
- package/test/unit/getOriginalConstructor/index.spec.js +60 -0
- package/test/unit/global/index.spec.js +84 -0
- package/test/unit/ie/index.spec.js +14 -0
- package/test/unit/loader/index.spec.js +644 -0
- package/test/unit/logger/index.spec.js +141 -0
- package/test/unit/messageFormat/index.spec.js +31 -0
- package/test/unit/mochaInit.js +8 -0
- package/test/unit/namespacer/index.spec.js +100 -0
- package/test/unit/performance/getEntries.spec.js +30 -0
- package/test/unit/performance/getEntriesByName.spec.js +88 -0
- package/test/unit/performance/getEntriesByType.spec.js +48 -0
- package/test/unit/performance/index.spec.js +35 -0
- package/test/unit/performance/mark.spec.js +40 -0
- package/test/unit/performance/measure.spec.js +50 -0
- package/test/unit/performance/now.spec.js +21 -0
- package/test/unit/pixelsDisplayed/index.spec.js +271 -0
- package/test/unit/polyfills/fetch/Headers.js +12 -0
- package/test/unit/polyfills/fetch/Request.js +12 -0
- package/test/unit/polyfills/fetch/Response.js +12 -0
- package/test/unit/polyfills/fetch/fetch.js +12 -0
- package/test/unit/polyfills/fetch/index.js +61 -0
- package/test/unit/polyfills/promise.spec.js +48 -0
- package/test/unit/region/europe/index.spec.js +70 -0
- package/test/unit/region/index.spec.js +113 -0
- package/test/unit/staticAssetLoader/index.spec.js +215 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileOverview
|
|
3
|
+
* Provides api response caching utilties
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { fetch } = require('../polyfills/fetch')
|
|
7
|
+
|
|
8
|
+
module.exports = function BvFetch ({ shouldCache, cacheName, cacheLimit, excludeHeaders = [] }) {
|
|
9
|
+
this.shouldCache = shouldCache;
|
|
10
|
+
this.cacheName = cacheName || 'bvCache';
|
|
11
|
+
this.cacheLimit = cacheLimit * 1024 * 1024 || 10 * 1024 * 1024;
|
|
12
|
+
this.fetchPromises = new Map();
|
|
13
|
+
this.cachedRequests = new Set();
|
|
14
|
+
this.excludeHeaders = excludeHeaders;
|
|
15
|
+
|
|
16
|
+
/**
|
|
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.
|
|
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
|
+
|
|
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
|
+
*/
|
|
70
|
+
this.generateCacheKey = (url, options) => {
|
|
71
|
+
const filteredOptions = Object.assign({}, options);
|
|
72
|
+
if (filteredOptions.headers) {
|
|
73
|
+
const newHeaders = new Headers();
|
|
74
|
+
for (const [key, value] of Object.entries(filteredOptions.headers)) {
|
|
75
|
+
if (!this.excludeHeaders.includes(key)) {
|
|
76
|
+
newHeaders.set(key, value);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
filteredOptions.headers = newHeaders;
|
|
80
|
+
}
|
|
81
|
+
const key = new Request(url, filteredOptions);
|
|
82
|
+
return key;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Serializes a request object into a JSON string for use as a cache key.
|
|
87
|
+
* The serialization includes the URL, method, and sorted headers of the request.
|
|
88
|
+
* @param {Request} request - The request object to serialize.
|
|
89
|
+
* @returns {string} A JSON string representing the serialized request.
|
|
90
|
+
*/
|
|
91
|
+
this.serializeRequestKey = (request) => {
|
|
92
|
+
const url = request.url;
|
|
93
|
+
const method = request.method || 'GET';
|
|
94
|
+
// Convert headers to a sorted array of [key, value] pairs for consistency
|
|
95
|
+
const headersArr = Array.from(request.headers.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
96
|
+
return JSON.stringify([url, method, { headers: headersArr }]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Retrieves cached Requests from the cache storage associated with the provided cache name.
|
|
101
|
+
* @returns {void}
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
this.retrievecachedRequests = () => {
|
|
105
|
+
// Open the Cache Storage
|
|
106
|
+
caches.open(this.cacheName).then(cache => {
|
|
107
|
+
// Get all cache keys
|
|
108
|
+
cache.keys().then(keys => {
|
|
109
|
+
keys.forEach(request => {
|
|
110
|
+
const headers = {};
|
|
111
|
+
request.headers.forEach((value, key) => {
|
|
112
|
+
if (!this.excludeHeaders.includes(key)) {
|
|
113
|
+
headers[key] = value;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Generate the cache key with headers and URL
|
|
118
|
+
const cacheKey = this.generateCacheKey(request.url, { headers });
|
|
119
|
+
this.cachedRequests.add(cacheKey); // Add to the set
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
//callretrievecachedRequests function to set the cache URL set with the cached URLS
|
|
126
|
+
this.retrievecachedRequests();
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Fetches data from the specified URL, caches the response, and returns the response.
|
|
130
|
+
* @param {string} url - The URL from which to fetch data.
|
|
131
|
+
* @param {string} cacheKey - The cache key associated with the fetched data.
|
|
132
|
+
* @returns {Promise<Response>} A Promise that resolves with the fetched response.
|
|
133
|
+
* @throws {Error} Throws an error if there's any problem fetching the data.
|
|
134
|
+
*/
|
|
135
|
+
this.fetchDataAndCache = (url, options = {}, cacheKey) => {
|
|
136
|
+
return fetch(url,options)
|
|
137
|
+
.then((response) => {
|
|
138
|
+
// initiate caching of response and return the response
|
|
139
|
+
this.cacheData(response, cacheKey);
|
|
140
|
+
return response.clone();
|
|
141
|
+
})
|
|
142
|
+
.catch(function (error) {
|
|
143
|
+
throw new Error('Error fetching data: ' + error);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Caches the provided response with the specified cache key if it meets the criteria for caching.
|
|
149
|
+
* @param {Response} response - The response object to be cached.
|
|
150
|
+
* @param {string} cacheKey - The cache key associated with the response.
|
|
151
|
+
* @returns {void}
|
|
152
|
+
*/
|
|
153
|
+
|
|
154
|
+
this.cacheData = (response, cacheKey) => {
|
|
155
|
+
const errJson = response.clone();
|
|
156
|
+
let canBeCached = true;
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
// If the response is not ok, we don't cache it
|
|
159
|
+
canBeCached = false;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (!response.headers.has('Cache-Control')) {
|
|
163
|
+
canBeCached = false;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Check for error in response obj
|
|
167
|
+
errJson.json().then(json => {
|
|
168
|
+
if (typeof this.shouldCache === 'function') {
|
|
169
|
+
canBeCached = this.shouldCache(json);
|
|
170
|
+
}
|
|
171
|
+
}).catch(() => {
|
|
172
|
+
canBeCached = false;
|
|
173
|
+
}).then(() => {
|
|
174
|
+
if (canBeCached) {
|
|
175
|
+
const clonedResponse = response.clone();
|
|
176
|
+
const sizeCheck = response.clone();
|
|
177
|
+
const newHeaders = new Headers();
|
|
178
|
+
clonedResponse.headers.forEach((value, key) => {
|
|
179
|
+
newHeaders.append(key, value);
|
|
180
|
+
});
|
|
181
|
+
newHeaders.append('X-Bazaarvoice-Cached-Time', Date.now())
|
|
182
|
+
// Get response text to calculate its size
|
|
183
|
+
sizeCheck.text().then(text => {
|
|
184
|
+
// Calculate size of response text in bytes
|
|
185
|
+
const sizeInBytes = new Blob([text]).size;
|
|
186
|
+
|
|
187
|
+
// Append response size to headers
|
|
188
|
+
newHeaders.append('X-Bazaarvoice-Response-Size', sizeInBytes);
|
|
189
|
+
|
|
190
|
+
// Create new Response object with modified headers
|
|
191
|
+
const newResponse = new Response(clonedResponse._bodyBlob || clonedResponse.body, {
|
|
192
|
+
status: clonedResponse.status,
|
|
193
|
+
statusText: clonedResponse.statusText,
|
|
194
|
+
headers: newHeaders
|
|
195
|
+
});
|
|
196
|
+
// Cache the response
|
|
197
|
+
caches.open(this.cacheName).then(cache => {
|
|
198
|
+
cache.put(cacheKey, newResponse);
|
|
199
|
+
//add key to cachedRequests set
|
|
200
|
+
this.cachedRequests.add(cacheKey);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Function to fetch data from cache.
|
|
209
|
+
* @param {string} cacheKey - The cache key to fetch data from the cache.
|
|
210
|
+
* @returns {Promise<Response|null>} A Promise that resolves with a Response object if the data is found in cache,
|
|
211
|
+
* or null if the data is not cached or expired.
|
|
212
|
+
* @throws {Error} Throws an error if there's any problem fetching from cache.
|
|
213
|
+
*/
|
|
214
|
+
this.fetchFromCache = (cacheKey) => {
|
|
215
|
+
// Check if the URL is in the set of cached requests set
|
|
216
|
+
if (!isRequestInSet(this.cachedRequests, cacheKey)) {
|
|
217
|
+
return Promise.resolve(null);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Open the cache and try to match the URL
|
|
221
|
+
return caches.open(this.cacheName)
|
|
222
|
+
.then((cache) => {
|
|
223
|
+
return cache.match(cacheKey)
|
|
224
|
+
.then((cachedResponse) => {
|
|
225
|
+
if (!cachedResponse) {
|
|
226
|
+
this.cachedRequests.delete(cacheKey)
|
|
227
|
+
return Promise.resolve(null);
|
|
228
|
+
}
|
|
229
|
+
const cachedTime = cachedResponse.headers.get('X-Bazaarvoice-Cached-Time');
|
|
230
|
+
const ttl = cachedResponse.headers.get('Cache-Control').match(/max-age=(\d+)/)[1];
|
|
231
|
+
const currentTimestamp = Date.now();
|
|
232
|
+
const cacheAge = (currentTimestamp - cachedTime) / 1000;
|
|
233
|
+
if (cacheAge < ttl) {
|
|
234
|
+
// Cached response found
|
|
235
|
+
return cachedResponse.clone();
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
})
|
|
239
|
+
.catch((error) => {
|
|
240
|
+
throw new Error('Error fetching from cache: ' + error);
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Fetches data from the API endpoint, caches responses, and handles caching logic.
|
|
246
|
+
* @param {string} url - The URL of the API endpoint.
|
|
247
|
+
* @param {Object} options - Optional request options.
|
|
248
|
+
* @returns {Promise<Response>} A promise resolving to the API response.
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
this.bvFetchFunc = (url, options = {}) => {
|
|
252
|
+
|
|
253
|
+
const cacheKey = this.generateCacheKey(url, options);
|
|
254
|
+
// If an ongoing fetch promise exists for the URL, return it
|
|
255
|
+
if (this.fetchPromises.has(this.serializeRequestKey(cacheKey))) {
|
|
256
|
+
return this.fetchPromises.get(this.serializeRequestKey(cacheKey)).then(res => res.clone());
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Check if response is available in cache
|
|
260
|
+
const newPromise = this.fetchFromCache(cacheKey)
|
|
261
|
+
.then((cachedResponse) => {
|
|
262
|
+
// If response found in cache, return it
|
|
263
|
+
if (cachedResponse) {
|
|
264
|
+
return cachedResponse;
|
|
265
|
+
}
|
|
266
|
+
// If response not found in cache, fetch from API and cache it
|
|
267
|
+
return this.fetchDataAndCache(url, options, cacheKey);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Store the ongoing fetch promise
|
|
271
|
+
this.fetchPromises.set(this.serializeRequestKey(cacheKey), newPromise);
|
|
272
|
+
|
|
273
|
+
//initiate cache cleanUp
|
|
274
|
+
this.debounceCleanupExpiredCache();
|
|
275
|
+
|
|
276
|
+
// When fetch completes or fails, remove the promise from the store
|
|
277
|
+
newPromise.finally(() => {
|
|
278
|
+
this.fetchPromises.delete(this.serializeRequestKey(cacheKey));
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
return newPromise.then(res => res.clone());
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Clears all cache entries stored in the cache storage.
|
|
287
|
+
* @returns {Promise<void>} A promise indicating cache flush completion.
|
|
288
|
+
*/
|
|
289
|
+
|
|
290
|
+
this.flushCache = () => {
|
|
291
|
+
return caches.open(this.cacheName).then(cache => {
|
|
292
|
+
return cache.keys().then(keys => {
|
|
293
|
+
const deletionPromises = keys.map(key => cache.delete(key));
|
|
294
|
+
return Promise.all(deletionPromises);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
this.manageCache = () => {
|
|
300
|
+
// Delete expired cache entries
|
|
301
|
+
caches.open(this.cacheName).then(cache => {
|
|
302
|
+
cache.keys().then(keys => {
|
|
303
|
+
keys.forEach(key => {
|
|
304
|
+
cache.match(key).then(response => {
|
|
305
|
+
const cachedTime = response.headers.get('X-Bazaarvoice-Cached-Time');
|
|
306
|
+
const ttl = response.headers.get('Cache-Control').match(/max-age=(\d+)/)[1];
|
|
307
|
+
const currentTimestamp = Date.now();
|
|
308
|
+
const cacheAge = (currentTimestamp - cachedTime) / 1000;
|
|
309
|
+
if (cacheAge >= ttl) {
|
|
310
|
+
cache.delete(key);
|
|
311
|
+
this.cachedRequests.delete(key);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Calculate total size of cached responses
|
|
319
|
+
let totalSize = 0;
|
|
320
|
+
caches.open(this.cacheName).then(cache => {
|
|
321
|
+
cache.keys().then(keys => {
|
|
322
|
+
// Create an array of promises for cache match operations
|
|
323
|
+
const matchPromises = keys.map(key =>
|
|
324
|
+
cache.match(key).then(response => {
|
|
325
|
+
const sizeHeader = response.headers.get('X-Bazaarvoice-Response-Size');
|
|
326
|
+
return parseInt(sizeHeader, 10);
|
|
327
|
+
})
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
// wait for all match promises to resolve
|
|
331
|
+
return Promise.all(matchPromises)
|
|
332
|
+
.then(sizes => sizes.reduce((acc, size) => acc + size, 0));
|
|
333
|
+
}).then(size => {
|
|
334
|
+
totalSize = size;
|
|
335
|
+
// If total size exceeds 10 MB, delete old cache entries
|
|
336
|
+
if (totalSize > this.cacheLimit) {
|
|
337
|
+
|
|
338
|
+
// create an array of cached responses
|
|
339
|
+
const cacheEntries = [];
|
|
340
|
+
return cache.keys().then(keys => {
|
|
341
|
+
const cachesResEntries = keys.map(key =>
|
|
342
|
+
cache.match(key).then(response => {
|
|
343
|
+
const sizeHeader = response.headers.get('X-Bazaarvoice-Response-Size');
|
|
344
|
+
const lastAccessedTime = response.headers.get('X-Bazaarvoice-Cached-Time');
|
|
345
|
+
cacheEntries.push({ key, size: parseInt(sizeHeader, 10), lastAccessedTime });
|
|
346
|
+
})
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
return Promise.all(cachesResEntries)
|
|
350
|
+
.then(() => {
|
|
351
|
+
// Sort cache entries by last accessed time in ascending order
|
|
352
|
+
cacheEntries.sort((a, b) => a.lastAccessedTime - b.lastAccessedTime);
|
|
353
|
+
|
|
354
|
+
// Delete older cache entries until total size is under 10 MB
|
|
355
|
+
let currentSize = totalSize;
|
|
356
|
+
cacheEntries.forEach(entry => {
|
|
357
|
+
if (currentSize > this.cacheLimit) {
|
|
358
|
+
cache.delete(entry.key);
|
|
359
|
+
this.cachedRequests.delete(entry.key);
|
|
360
|
+
currentSize -= entry.size;
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
function debounce (func, delay) {
|
|
372
|
+
let timer;
|
|
373
|
+
return function () {
|
|
374
|
+
clearTimeout(timer);
|
|
375
|
+
timer = setTimeout(() => {
|
|
376
|
+
func.apply(this, arguments);
|
|
377
|
+
}, delay);
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
this.debounceCleanupExpiredCache = debounce(this.manageCache, 8000);
|
|
382
|
+
|
|
383
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# checkHighContrast
|
|
2
|
+
|
|
3
|
+
This is a small method to do the somewhat complicated job of figuring out
|
|
4
|
+
whether a browser is operating in high contrast mode. The end goal is to be
|
|
5
|
+
able to style the page differently so these users.
|
|
6
|
+
|
|
7
|
+
# Limitations
|
|
8
|
+
|
|
9
|
+
This works on all Windows browsers but only Firefox on Mac. The way this works
|
|
10
|
+
is by detecting when the browser's computed styles are different than the
|
|
11
|
+
given ones. In Windows, the high contrast theme coerces the browsers to ignore
|
|
12
|
+
a website's given styles in favor of its own. In OSX, the system level high
|
|
13
|
+
contrast settings will alter the entire display at a lower level so browsers
|
|
14
|
+
won't be aware of it and therefore neither will we. Firefox has options in
|
|
15
|
+
the content pane of preferences to ignore website's styles and will behave
|
|
16
|
+
similarly to Windows at that point. The likelihood of Apple changing their
|
|
17
|
+
approach anytime soon is probably small.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
```javascript
|
|
21
|
+
var checkHighContrast = require('checkHighContrast');
|
|
22
|
+
|
|
23
|
+
if( checkHighContrast(body) ) {
|
|
24
|
+
// ... setup alternative styles here ...
|
|
25
|
+
}
|
|
26
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileOverview Utility for determining if browser is in highcontrast mode
|
|
3
|
+
* Originally from Firebird's util directory.
|
|
4
|
+
*
|
|
5
|
+
* High contrast mode overwrites a website's choice of colors with a user's
|
|
6
|
+
* or OS's. This naturally can play havoc with the visual display of information.
|
|
7
|
+
* Detecting and using alternative styles can improve a user's experience.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
function checkHighContrast (body) {
|
|
11
|
+
var div = document.createElement('div');
|
|
12
|
+
div.style['background-color'] = 'blue';
|
|
13
|
+
body.appendChild(div);
|
|
14
|
+
// Some IE's do not have access to getComputedStyle and need to use
|
|
15
|
+
// elem.currentStyle instead. The results are not the same but either
|
|
16
|
+
// option should reveal whether the browser is interfering.
|
|
17
|
+
var styleAccessor = (window.getComputedStyle && window.getComputedStyle(div)) || div.currentStyle;
|
|
18
|
+
var color = styleAccessor['background-color'];
|
|
19
|
+
|
|
20
|
+
body.removeChild(div);
|
|
21
|
+
|
|
22
|
+
// We may get back the color in an rgb format, we should consider that acceptable
|
|
23
|
+
if (color === 'rgb(0, 0, 255)') {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (color === 'blue') {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// At this point, the color isn't one of the two possible valid results for
|
|
32
|
+
// blue, so we're assuming that high-contrast mode is altering our visual.
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = checkHighContrast;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# cookie
|
|
2
|
+
|
|
3
|
+
The cookie module provides methods for interacting with browser cookies.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
var cookie = require('bv-ui-core/lib/cookie');
|
|
7
|
+
|
|
8
|
+
cookie.create('RememberMe', '1', 365);
|
|
9
|
+
console.log(cookie.read('RememberMe')); // '1'
|
|
10
|
+
cookie.remove('RememberMe');
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## The following methods are provided:
|
|
14
|
+
|
|
15
|
+
- `read(name)`: Read the cookie with the given name.
|
|
16
|
+
- `create(name, value, [days, domain, secure])`: Write a cookie with the given
|
|
17
|
+
name and value. Set the expiration in days using the `days` argument; set
|
|
18
|
+
the domain using the `domain` argument; indicate that the cookie is secure
|
|
19
|
+
by passing `true` as the `secure` argument. Note that if one optional
|
|
20
|
+
argument is provided, all previous arguments must be provided as well.
|
|
21
|
+
- `remove(name)`: Delete the cookie with the given name.
|
|
22
|
+
|
|
23
|
+
## Usage with `cookieConsent`:
|
|
24
|
+
|
|
25
|
+
- Call `cookieConsent.initConsent` before calling `cookie.create`.
|
|
26
|
+
- Cookies will get created only if the consent for that cookie is true.
|
|
27
|
+
- Each cookie will automatically subscribe to `enable` and `disable` consent events for that cookie.
|
|
28
|
+
- Cookie will get set when consent is `true`.
|
|
29
|
+
- Cookie will get removed when consent is `false`.
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileOverview
|
|
3
|
+
* Provides cookie utilities.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Imports.
|
|
7
|
+
var global = require('../global');
|
|
8
|
+
var cookieConsent = require('../cookieConsent');
|
|
9
|
+
// Store to hold cookie values
|
|
10
|
+
var store = {};
|
|
11
|
+
/**
|
|
12
|
+
* Create a cookie.
|
|
13
|
+
*
|
|
14
|
+
* @param {String} name The cookie name.
|
|
15
|
+
* @param {String} value The cookie value.
|
|
16
|
+
* @param {Number} days The cookie lifespan in days.
|
|
17
|
+
* @param {String} [domain] The domain for the cookie.
|
|
18
|
+
* @param {Boolean} [secure] Whether this is a secure cookie.
|
|
19
|
+
* @param {String} [sameSite='Lax'] The SameSite attribute ('Strict', 'Lax', or 'None'). Defaults to 'Lax'.
|
|
20
|
+
*/
|
|
21
|
+
function createCookie (name, value, days, domain, secure, sameSite = 'Lax') {
|
|
22
|
+
var date = new Date();
|
|
23
|
+
|
|
24
|
+
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
25
|
+
var expires = days ? ';expires=' + date.toGMTString() : '';
|
|
26
|
+
|
|
27
|
+
// Determine if the cookie should be secure.
|
|
28
|
+
// Force the Secure flag if the page is loaded over HTTPS, OR if the explicit `secure` argument is true.
|
|
29
|
+
// This ensures that all cookies (including BVBRANDID) set by this library are secure in production environments.
|
|
30
|
+
var isSecure = secure || (global.location && global.location.protocol === 'https:');
|
|
31
|
+
|
|
32
|
+
// SameSite=None requires the Secure flag, so we fallback to 'Lax' if not secure.
|
|
33
|
+
var sameSiteValue = (sameSite === 'None' && !isSecure) ? 'Lax' : sameSite;
|
|
34
|
+
|
|
35
|
+
var c = encodeURIComponent(name) + '=' +
|
|
36
|
+
encodeURIComponent(value) +
|
|
37
|
+
expires +
|
|
38
|
+
';path=/' +
|
|
39
|
+
(domain ? (';domain=' + domain) : '') +
|
|
40
|
+
(isSecure ? ';secure' : '') +
|
|
41
|
+
';SameSite=' + sameSiteValue;
|
|
42
|
+
|
|
43
|
+
global.document.cookie = c;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Obtain the value of a cookie.
|
|
47
|
+
*
|
|
48
|
+
* @param {String} name The cookie name.
|
|
49
|
+
* @return {String} The cookie value or null if no such cookie.
|
|
50
|
+
*/
|
|
51
|
+
function readCookie (name) {
|
|
52
|
+
var nameEQ = encodeURIComponent(name) + '=';
|
|
53
|
+
var ca = global.document.cookie.split(';');
|
|
54
|
+
var i;
|
|
55
|
+
|
|
56
|
+
for (i = 0; i < ca.length; i++) {
|
|
57
|
+
var c = ca[i];
|
|
58
|
+
|
|
59
|
+
while (c.charAt(0) === ' ') {
|
|
60
|
+
c = c.substring(1, c.length);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (c.indexOf(nameEQ) === 0) {
|
|
64
|
+
return decodeURIComponent(c.substring(nameEQ.length, c.length));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Remove a cookie.
|
|
72
|
+
*
|
|
73
|
+
* @param {String} name The cookie name.
|
|
74
|
+
*/
|
|
75
|
+
function removeCookie (name, domain) {
|
|
76
|
+
delete store[name];
|
|
77
|
+
if (domain) {
|
|
78
|
+
createCookie(name, '', -1, domain);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
createCookie(name, '', -1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
create: function (name, value, days, domain, secure, sameSite) {
|
|
87
|
+
store[name] = value;
|
|
88
|
+
var consentPresent = cookieConsent.getConsent(name);
|
|
89
|
+
if (consentPresent) {
|
|
90
|
+
createCookie(name, value, days, domain, secure, sameSite);
|
|
91
|
+
}
|
|
92
|
+
cookieConsent.subscribe(name, 'add', function (consent) {
|
|
93
|
+
if (consent) {
|
|
94
|
+
createCookie(name, value, days, domain, secure, sameSite);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
removeCookie(name, domain);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
cookieConsent.subscribe(name, 'enable', function () {
|
|
102
|
+
createCookie(name, value, days, domain, secure, sameSite);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
cookieConsent.subscribe(name, 'disable', function () {
|
|
106
|
+
removeCookie(name, domain);
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
read: readCookie,
|
|
110
|
+
remove: removeCookie
|
|
111
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# cookieConsent
|
|
2
|
+
|
|
3
|
+
The cookieConsent module provides methods for controlling cookie consents.
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
var cookieConsent = require('bv-ui-core/lib/cookieConsent');
|
|
7
|
+
// Initialize
|
|
8
|
+
/**
|
|
9
|
+
* 2nd parameter is to disable/enable consent mechanism.
|
|
10
|
+
* Passing true will disable the consent mechanism.
|
|
11
|
+
*/
|
|
12
|
+
cookieConsent.initConsent({
|
|
13
|
+
cookie1: false,
|
|
14
|
+
cookie2: true
|
|
15
|
+
}, false);
|
|
16
|
+
|
|
17
|
+
// Set cookie consent
|
|
18
|
+
cookieConsent.setConsent({ cookie1: true });
|
|
19
|
+
|
|
20
|
+
// Get cookie consent
|
|
21
|
+
cookieConsent.getConsent('cookie1');
|
|
22
|
+
|
|
23
|
+
// Subscribe to consent 'add' event. Triggers when a new cookie consent is added
|
|
24
|
+
cookieConsent.subscribe('cookie3', 'add', function (data) {});
|
|
25
|
+
|
|
26
|
+
// Subscribe to consent 'enable' event. Triggers when a cookie consent is set to true
|
|
27
|
+
cookieConsent.subscribe('cookie3', 'enable', function (data) {});
|
|
28
|
+
|
|
29
|
+
// Valid events that can be subscribed to are 'add', 'enable', 'disable', and 'change'
|
|
30
|
+
// Subscribe to consent 'disable' event. Triggers when a cookie consent is set to false
|
|
31
|
+
var event = cookieConsent.subscribe('cookie3', 'disable', function (data) {});
|
|
32
|
+
|
|
33
|
+
// Subscribe to the store change event. The latest consent store object is passed as parameter to the callback function
|
|
34
|
+
var event = subscribeToConsentStore(function (store){});
|
|
35
|
+
|
|
36
|
+
// Unsubscribe events
|
|
37
|
+
event.unsubscribe();
|
|
38
|
+
|
|
39
|
+
// Get cookie consent disabled/enabled value.
|
|
40
|
+
cookieConsent.getConsentDisabled();
|
|
41
|
+
```
|