govuk_publishing_components 37.6.0 → 37.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,417 @@
1
+ const DEFAULT_TIMEOUT = 10000;
2
+ function request(url, options, onSuccess, onError) {
3
+ try {
4
+ var req = new XMLHttpRequest();
5
+ var isTimeout = false;
6
+ options = options || {};
7
+ req.onreadystatechange = function () {
8
+ if (req.readyState === req.DONE) {
9
+ if (req.status >= 200 && req.status < 400) {
10
+ let jsonResponse;
11
+ try {
12
+ jsonResponse = JSON.parse(req.responseText);
13
+ onSuccess(jsonResponse);
14
+ }
15
+ catch (error) {
16
+ return onError(error);
17
+ }
18
+ }
19
+ else if (req.status === 0 && req.timeout > 0) {
20
+ // Possible timeout, waiting for ontimeout event
21
+ // Timeout will throw a status = 0 request
22
+ // onreadystatechange preempts ontimeout
23
+ // And we can't know for sure at this stage if it's a timeout
24
+ setTimeout(function () {
25
+ if (isTimeout) {
26
+ return;
27
+ }
28
+ return onError(new Error('Request to ' + url + ' failed with status: ' + req.status));
29
+ }, 500);
30
+ }
31
+ else {
32
+ return onError(new Error('Request to ' + url + ' failed with status: ' + req.status));
33
+ }
34
+ }
35
+ };
36
+ req.open(options.method || 'GET', url, true);
37
+ if (options.timeout) {
38
+ req.timeout = options.timeout;
39
+ }
40
+ else {
41
+ req.timeout = DEFAULT_TIMEOUT;
42
+ }
43
+ req.ontimeout = function () {
44
+ isTimeout = true;
45
+ return onError(new Error('Request to ' + url + ' timed out'));
46
+ };
47
+ var headers = options.headers || {};
48
+ for (var name in headers) {
49
+ req.setRequestHeader(name, headers[name]);
50
+ }
51
+ req.send(options.body || null);
52
+ }
53
+ catch (error) {
54
+ return onError(error);
55
+ }
56
+ }
57
+ function addUrlParameter(url, name, value) {
58
+ url = parseUrl(url);
59
+ var newParam = name.concat('=', value);
60
+ var modified = false;
61
+ findByKey(name, url.params, function (index) {
62
+ url.params[index] = newParam;
63
+ modified = true;
64
+ });
65
+ if (!modified) {
66
+ url.params.push(newParam);
67
+ }
68
+ return buildUrl(url);
69
+ }
70
+ function removeUrlParameter(url, name) {
71
+ url = parseUrl(url);
72
+ findByKey(name, url.params, function (index) {
73
+ url.params.splice(index--, 1);
74
+ });
75
+ return buildUrl(url);
76
+ }
77
+ function parseUrl(url) {
78
+ var parts = url.split('?');
79
+ return {
80
+ address: parts[0],
81
+ params: (parts[1] || '').split('&').filter(Boolean),
82
+ };
83
+ }
84
+ function buildUrl(parts) {
85
+ return [parts.address, parts.params.join('&')].join('?').replace(/\??$/, '');
86
+ }
87
+ function findByKey(key, keyvals, callback) {
88
+ key += '=';
89
+ for (var index = 0; keyvals.length > index; index++) {
90
+ if (keyvals[index].trim().slice(0, key.length) === key) {
91
+ var value = keyvals[index].trim().slice(key.length);
92
+ if (callback) {
93
+ callback(index, value);
94
+ }
95
+ else {
96
+ return value;
97
+ }
98
+ }
99
+ }
100
+ }
101
+ function isCrossOrigin(link) {
102
+ return (link.protocol !== window.location.protocol ||
103
+ link.hostname !== window.location.hostname ||
104
+ (link.port || '80') !== (window.location.port || '80'));
105
+ }
106
+ function getOriginFromLink(link) {
107
+ var origin = link.protocol.concat('//', link.hostname);
108
+ if (link.port && link.port !== '80' && link.port !== '443') {
109
+ origin = origin.concat(':', link.port);
110
+ }
111
+ return origin;
112
+ }
113
+ function isBrowser() {
114
+ return typeof module === 'undefined';
115
+ }
116
+ function setCookie({ name, value, lifetime }) {
117
+ // const encodedValue = encodeURIComponent(value)
118
+ const encodedValue = value;
119
+ document.cookie = name
120
+ .concat('=', encodedValue)
121
+ .concat('; path=/', '; max-age='.concat(lifetime.toString()), document.location.protocol === 'https:' ? '; Secure' : '');
122
+ }
123
+ function getCookie(name, defaultValue) {
124
+ name += '=';
125
+ const cookies = document.cookie.split(';');
126
+ let cookie = null;
127
+ for (let i = 0; i < cookies.length; i++) {
128
+ let currentCookie = cookies[i].trim();
129
+ if (currentCookie.indexOf(name) === 0) {
130
+ cookie = currentCookie;
131
+ break;
132
+ }
133
+ }
134
+ if (cookie) {
135
+ return decodeURIComponent(cookie.trim().slice(name.length));
136
+ }
137
+ return defaultValue || null;
138
+ }
139
+ function validateConsentObject(response) {
140
+ try {
141
+ if (typeof response !== 'object' || response === null) {
142
+ return false;
143
+ }
144
+ var expectedKeys = ['essential', 'settings', 'usage', 'campaigns'];
145
+ var allKeysPresent = true;
146
+ var responseKeysCount = 0;
147
+ for (var i = 0; i < expectedKeys.length; i++) {
148
+ if (!(expectedKeys[i] in response)) {
149
+ allKeysPresent = false;
150
+ break;
151
+ }
152
+ }
153
+ var allValuesBoolean = true;
154
+ for (var key in response) {
155
+ if (response.hasOwnProperty(key)) {
156
+ responseKeysCount++;
157
+ if (typeof response[key] !== 'boolean') {
158
+ allValuesBoolean = false;
159
+ break;
160
+ }
161
+ }
162
+ }
163
+ var correctNumberOfKeys = responseKeysCount === expectedKeys.length;
164
+ }
165
+ catch (err) {
166
+ return false;
167
+ }
168
+ return allKeysPresent && allValuesBoolean && correctNumberOfKeys;
169
+ }
170
+
171
+ const COOKIE_DAYS = 365;
172
+ class GovConsentConfig {
173
+ constructor(baseUrl) {
174
+ this.uidFromCookie = findByKey(GovConsentConfig.UID_KEY, document.cookie.split(';'));
175
+ this.uidFromUrl = findByKey(GovConsentConfig.UID_KEY, parseUrl(location.href).params);
176
+ this.baseUrl = baseUrl;
177
+ }
178
+ }
179
+ GovConsentConfig.UID_KEY = 'gov_singleconsent_uid';
180
+ GovConsentConfig.CONSENTS_COOKIE_NAME = 'cookies_policy';
181
+ GovConsentConfig.PREFERENCES_SET_COOKIE_NAME = 'cookies_preferences_set';
182
+ GovConsentConfig.COOKIE_LIFETIME = COOKIE_DAYS * 24 * 60 * 60;
183
+
184
+ class ApiV1 {
185
+ constructor(baseUrl) {
186
+ this.version = 'v1';
187
+ this.baseUrl = baseUrl;
188
+ }
189
+ buildUrl(endpoint, pathParam) {
190
+ let url = `${this.baseUrl}/api/${this.version}${endpoint}`;
191
+ if (pathParam) {
192
+ url += `/${pathParam}`;
193
+ }
194
+ return url;
195
+ }
196
+ origins() {
197
+ return this.buildUrl(ApiV1.Routes.origins);
198
+ }
199
+ consents(id) {
200
+ return this.buildUrl(ApiV1.Routes.consents, id || '');
201
+ }
202
+ }
203
+ ApiV1.Routes = {
204
+ origins: '/origins',
205
+ consents: '/consent',
206
+ };
207
+
208
+ class GovSingleConsent {
209
+ constructor(consentsUpdateCallback, baseUrlOrEnv) {
210
+ /**
211
+ Initialises _GovConsent object by performing the following:
212
+ 1. Removes 'uid' from URL.
213
+ 2. Sets 'uid' attribute from cookie or URL.
214
+ 3. Fetches consent status from API if 'uid' exists.
215
+ 4. Notifies event listeners with API response.
216
+
217
+ @arg baseUrl: string - the domain of where the single consent API is. Required.
218
+ @arg consentsUpdateCallback: function(consents, consentsPreferencesSet, error) - callback when the consents are updated
219
+ "consents": this is the consents object. It has the following properties: "essential", "usage", "campaigns", "settings". Each property is a boolean.
220
+ "consentsPreferencesSet": true if the consents have been set for this user and this domain. Typically, only display the cookie banner if this is true.
221
+ "error": if an error occurred, this is the error object. Otherwise, this is null.
222
+ */
223
+ this.cachedConsentsCookie = null;
224
+ this.validateConstructorArguments(baseUrlOrEnv, consentsUpdateCallback);
225
+ const baseUrl = this.resolveBaseUrl(baseUrlOrEnv);
226
+ this._consentsUpdateCallback = consentsUpdateCallback;
227
+ this.config = new GovConsentConfig(baseUrl);
228
+ this.urls = new ApiV1(this.config.baseUrl);
229
+ window.cachedConsentsCookie = null;
230
+ this.hideUIDParameter();
231
+ this.initialiseUIDandConsents();
232
+ }
233
+ validateConstructorArguments(baseUrlOrEnv, consentsUpdateCallback) {
234
+ if (!baseUrlOrEnv) {
235
+ throw new Error('Argument baseUrl is required');
236
+ }
237
+ if (typeof baseUrlOrEnv !== 'string') {
238
+ throw new Error('Argument baseUrl must be a string');
239
+ }
240
+ if (!consentsUpdateCallback) {
241
+ throw new Error('Argument consentsUpdateCallback is required');
242
+ }
243
+ if (typeof consentsUpdateCallback !== 'function') {
244
+ throw new Error('Argument consentsUpdateCallback must be a function');
245
+ }
246
+ }
247
+ resolveBaseUrl(baseUrlOrEnv) {
248
+ if (baseUrlOrEnv === 'staging') {
249
+ return 'https://gds-single-consent-staging.app';
250
+ }
251
+ else if (baseUrlOrEnv === 'production') {
252
+ return 'https://gds-single-consent.app';
253
+ }
254
+ // If not "staging" or "production", assume it's a custom URL
255
+ return baseUrlOrEnv;
256
+ }
257
+ initialiseUIDandConsents() {
258
+ const currentUID = this.getCurrentUID();
259
+ if (this.isNewUID(currentUID)) {
260
+ this.handleNewUID(currentUID);
261
+ }
262
+ if (this.uid) {
263
+ this.fetchAndUpdateConsents();
264
+ }
265
+ else {
266
+ this._consentsUpdateCallback(null, false, null);
267
+ }
268
+ }
269
+ handleNewUID(newUID) {
270
+ this.uid = newUID;
271
+ this.updateLinksEventHandlers(newUID);
272
+ this.setUIDCookie(newUID);
273
+ }
274
+ isNewUID(currentUID) {
275
+ return currentUID && currentUID !== this.uid;
276
+ }
277
+ fetchAndUpdateConsents() {
278
+ const consentsUrl = this.urls.consents(this.uid);
279
+ request(consentsUrl, { timeout: 1000 }, (jsonResponse) => {
280
+ if (!validateConsentObject(jsonResponse.status)) {
281
+ const error = new Error('Invalid consents object returned from the API: ' + JSON.stringify(jsonResponse));
282
+ return this.defaultToRejectAllConsents(error);
283
+ }
284
+ const consents = jsonResponse.status;
285
+ this.updateBrowserConsents(consents);
286
+ this._consentsUpdateCallback(consents, GovSingleConsent.isConsentPreferencesSet(), null);
287
+ }, (error) => this.defaultToRejectAllConsents(error));
288
+ }
289
+ getCurrentUID() {
290
+ // Get the current uid from URL or from the cookie if it exists
291
+ return this.config.uidFromUrl || this.config.uidFromCookie;
292
+ }
293
+ setConsents(consents) {
294
+ if (!consents) {
295
+ throw new Error('consents is required in GovSingleConsent.setConsents()');
296
+ }
297
+ const successCallback = (response) => {
298
+ if (!response.uid) {
299
+ throw new Error('No UID returned from the API');
300
+ }
301
+ if (this.isNewUID(response.uid)) {
302
+ this.handleNewUID(response.uid);
303
+ }
304
+ this.updateBrowserConsents(consents);
305
+ this._consentsUpdateCallback(consents, GovSingleConsent.isConsentPreferencesSet(), null);
306
+ };
307
+ const url = this.urls.consents(this.uid);
308
+ request(url, {
309
+ method: 'POST',
310
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
311
+ body: 'status='.concat(JSON.stringify(consents)),
312
+ }, successCallback, (error) => this.defaultToRejectAllConsents(error));
313
+ }
314
+ defaultToRejectAllConsents(error) {
315
+ this.updateBrowserConsents(GovSingleConsent.REJECT_ALL);
316
+ this._consentsUpdateCallback(GovSingleConsent.REJECT_ALL, GovSingleConsent.isConsentPreferencesSet(), error);
317
+ }
318
+ static getConsents() {
319
+ if (window.cachedConsentsCookie) {
320
+ return window.cachedConsentsCookie;
321
+ }
322
+ const cookieValue = getCookie(GovConsentConfig.CONSENTS_COOKIE_NAME, null);
323
+ if (cookieValue) {
324
+ return JSON.parse(cookieValue);
325
+ }
326
+ return null;
327
+ }
328
+ static hasConsentedToEssential() {
329
+ const consents = GovSingleConsent.getConsents();
330
+ return consents === null || consents === void 0 ? void 0 : consents.essential;
331
+ }
332
+ static hasConsentedToUsage() {
333
+ const consents = GovSingleConsent.getConsents();
334
+ return consents === null || consents === void 0 ? void 0 : consents.usage;
335
+ }
336
+ static hasConsentedToCampaigns() {
337
+ const consents = GovSingleConsent.getConsents();
338
+ return consents === null || consents === void 0 ? void 0 : consents.campaigns;
339
+ }
340
+ static hasConsentedToSettings() {
341
+ const consents = GovSingleConsent.getConsents();
342
+ return consents === null || consents === void 0 ? void 0 : consents.settings;
343
+ }
344
+ static isConsentPreferencesSet() {
345
+ const value = getCookie(GovConsentConfig.PREFERENCES_SET_COOKIE_NAME, null);
346
+ return value === 'true';
347
+ }
348
+ updateLinksEventHandlers(currentUID) {
349
+ request(this.urls.origins(), {},
350
+ // Update links with UID
351
+ (origins) => this.addUIDtoCrossOriginLinks(origins, currentUID), (error) => {
352
+ throw error;
353
+ });
354
+ }
355
+ addUIDtoCrossOriginLinks(origins, uid) {
356
+ /**
357
+ * Adds uid URL parameter to consent sharing links.
358
+ * Only links with known origins are updated.
359
+ */
360
+ const links = document.querySelectorAll('a[href]');
361
+ Array.prototype.forEach.call(links, (link) => {
362
+ if (isCrossOrigin(link) &&
363
+ origins.indexOf(getOriginFromLink(link)) >= 0) {
364
+ link.addEventListener('click', (event) => {
365
+ event.target.href = addUrlParameter(event.target.href, GovConsentConfig.UID_KEY, uid);
366
+ });
367
+ }
368
+ });
369
+ }
370
+ setUIDCookie(uid) {
371
+ setCookie({
372
+ name: GovConsentConfig.UID_KEY,
373
+ value: uid,
374
+ lifetime: GovConsentConfig.COOKIE_LIFETIME,
375
+ });
376
+ }
377
+ updateBrowserConsents(consents) {
378
+ this.setConsentsCookie(consents);
379
+ this.setPreferencesSetCookie(true);
380
+ window.cachedConsentsCookie = consents;
381
+ }
382
+ setConsentsCookie(consents) {
383
+ setCookie({
384
+ name: GovConsentConfig.CONSENTS_COOKIE_NAME,
385
+ value: JSON.stringify(consents),
386
+ lifetime: GovConsentConfig.COOKIE_LIFETIME,
387
+ });
388
+ }
389
+ setPreferencesSetCookie(value) {
390
+ setCookie({
391
+ name: GovConsentConfig.PREFERENCES_SET_COOKIE_NAME,
392
+ value: value.toString(),
393
+ lifetime: GovConsentConfig.COOKIE_LIFETIME,
394
+ });
395
+ }
396
+ hideUIDParameter() {
397
+ history.replaceState(null, null, removeUrlParameter(location.href, GovConsentConfig.UID_KEY));
398
+ }
399
+ }
400
+ GovSingleConsent.ACCEPT_ALL = {
401
+ essential: true,
402
+ usage: true,
403
+ campaigns: true,
404
+ settings: true,
405
+ };
406
+ GovSingleConsent.REJECT_ALL = {
407
+ essential: true,
408
+ usage: false,
409
+ campaigns: false,
410
+ settings: false,
411
+ };
412
+
413
+ if (isBrowser()) {
414
+ window.GovSingleConsent = GovSingleConsent;
415
+ }
416
+
417
+ export { GovSingleConsent };