govuk_publishing_components 37.6.0 → 37.6.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.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/govuk_publishing_components/components/_contents-list.scss +1 -1
- data/app/views/govuk_publishing_components/components/_contents_list.html.erb +2 -0
- data/app/views/govuk_publishing_components/components/docs/contents_list.yml +1 -0
- data/lib/govuk_publishing_components/version.rb +1 -1
- data/node_modules/govuk-single-consent/README.md +157 -0
- data/node_modules/govuk-single-consent/dist/singleconsent.cjs.js +419 -0
- data/node_modules/govuk-single-consent/dist/singleconsent.esm.js +417 -0
- data/node_modules/govuk-single-consent/dist/singleconsent.iife.js +431 -0
- data/node_modules/govuk-single-consent/dist/singleconsent.iife.min.js +1 -0
- data/node_modules/govuk-single-consent/package.json +56 -0
- metadata +7 -1
@@ -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 };
|