@ons/design-system 74.0.0-next.14 → 74.0.0-next.15

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.
@@ -74,7 +74,6 @@ export function getConsentCookie() {
74
74
  }
75
75
 
76
76
  export function setConsentCookie(options) {
77
- const domain = getDomain(document.domain);
78
77
  let cookieConsent = getConsentCookie();
79
78
  if (!cookieConsent) {
80
79
  cookieConsent = JSON.parse(JSON.stringify(DEFAULT_COOKIE_CONSENT).replace(/'/g, '"'));
@@ -85,10 +84,6 @@ export function setConsentCookie(options) {
85
84
  for (let cookies in COOKIE_CATEGORIES) {
86
85
  if (COOKIE_CATEGORIES[cookies] === cookieType) {
87
86
  cookie(cookies, null);
88
- if (cookie(cookies)) {
89
- const cookieString = cookies + '=; expires=' + new Date() + '; domain=' + domain + '; path=/';
90
- document.cookie = cookieString;
91
- }
92
87
  }
93
88
  }
94
89
  }
@@ -127,27 +122,25 @@ export function checkConsentCookie(cookieName, cookieValue) {
127
122
  }
128
123
 
129
124
  export function setCookie(name, value, options) {
130
- const domain = getDomain(document.domain);
131
- let setDomain = '';
125
+ if (typeof options === 'undefined') {
126
+ options = {};
127
+ }
132
128
 
133
- if (domain.indexOf('localhost') === -1) {
134
- setDomain = '; domain=' + domain;
129
+ if (options.days && options.days < 0) {
130
+ deleteCookie(name);
131
+ return;
135
132
  }
136
133
 
137
134
  if (checkConsentCookie(name, value)) {
138
- if (typeof options === 'undefined') {
139
- options = {};
140
- }
135
+ const secure = getSecureAttribute();
141
136
 
142
- let cookieString = name + '=' + value + setDomain + '; path=/';
137
+ let cookieString = name + '=' + value + getCookieDomainAttribute() + '; path=/';
143
138
  if (options.days) {
144
139
  const date = new Date();
145
140
  date.setTime(date.getTime() + options.days * 24 * 60 * 60 * 1000);
146
141
  cookieString = cookieString + '; expires=' + date.toGMTString();
147
142
  }
148
- if (document.location.protocol === 'https:') {
149
- cookieString = cookieString + '; Secure';
150
- }
143
+ cookieString = cookieString + secure;
151
144
  document.cookie = cookieString;
152
145
  }
153
146
  }
@@ -167,38 +160,51 @@ export function getCookie(name) {
167
160
  return null;
168
161
  }
169
162
 
170
- function getCookieDomainPolicy() {
163
+ function getCookieDomain() {
171
164
  const banner = document.querySelector('.ons-cookies-banner');
172
- const policy = banner ? banner.getAttribute('data-ons-cookie-domain-policy') : null;
165
+ const configuredDomain = banner ? banner.getAttribute('data-ons-cookie-domain') : null;
173
166
 
174
- // plan: add 'exact-host' policy in future and retire 'legacy' and 'day1'
175
- switch (policy) {
176
- case 'legacy':
177
- case 'day1':
178
- return policy;
179
- default:
180
- return 'legacy';
167
+ if (configuredDomain !== null) {
168
+ return configuredDomain;
169
+ } else {
170
+ return extractDomainFromUrl(document.location.hostname);
181
171
  }
182
172
  }
183
173
 
184
- export function getDomain(domain, cookieHandler = document) {
185
- const cookieDomainPolicy = getCookieDomainPolicy();
174
+ export function extractDomainFromUrl(url) {
175
+ if (url.indexOf('localhost') >= 0 || url.indexOf('127.0.0.1') >= 0) {
176
+ return 'localhost';
177
+ }
178
+
179
+ const pattern = '(\\.co\\.uk|\\.onsdigital\\.uk|\\.gov\\.uk)';
180
+ const tlds = new RegExp(pattern);
186
181
 
187
- if (cookieDomainPolicy === 'legacy' && domain.startsWith('www.')) {
188
- domain = domain.substring(4);
182
+ const isKnownDomain = tlds.test(url);
183
+
184
+ if (isKnownDomain) {
185
+ return url;
189
186
  }
190
187
 
191
- let i = 0,
192
- domainName = domain,
193
- p = domainName.split('.'),
194
- s = '_gd' + new Date().getTime();
195
- while (i < p.length - 1 && cookieHandler.cookie.indexOf(s + '=' + s) == -1) {
196
- // Loop until we find a valid cookie set at the domain or until we've checked all possible domains
197
- domainName = p.slice(i, p.length).join('.');
198
- cookieHandler.cookie = s + '=' + s + ';domain=' + domainName + ';';
188
+ return '';
189
+ }
199
190
 
200
- i++;
191
+ function getCookieDomainAttribute() {
192
+ const domain = getCookieDomain();
193
+
194
+ if (!domain) {
195
+ return '';
201
196
  }
202
- cookieHandler.cookie = s + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT;domain=' + domainName + ';';
203
- return domainName;
197
+
198
+ return '; domain=' + domain;
199
+ }
200
+
201
+ function deleteCookie(name) {
202
+ const expires = new Date(0).toGMTString();
203
+ const secure = getSecureAttribute();
204
+
205
+ document.cookie = name + '=' + getCookieDomainAttribute() + '; path=/; expires=' + expires + secure;
206
+ }
207
+
208
+ function getSecureAttribute() {
209
+ return document.location.protocol === 'https:' ? '; Secure' : '';
204
210
  }
@@ -1,65 +1,122 @@
1
- /** @jest-environment jsdom */
1
+ /**
2
+ * @jest-environment jsdom
3
+ * @jest-environment-options {"url":"https://www.ons.gov.uk/"}
4
+ */
2
5
 
3
- import { getDomain } from './cookies-functions';
6
+ import { extractDomainFromUrl, setConsentCookie, setCookie } from './cookies-functions';
7
+
8
+ let cookieAssignments = [];
4
9
  let mockCookieStore = {};
5
10
 
6
- // Mocking document.cookie which is used for setting domain name in `getDomain()`
7
- export const setMockcookie = {
8
- get cookie() {
9
- return Object.entries(mockCookieStore)
10
- .map(([key, value]) => `${key}=${value}`)
11
- .join('; ');
12
- },
13
- set cookie(value) {
14
- const domainMatch = value.match(/domain=([^;]+)/i);
15
- const domain = domainMatch?.[1]?.trim();
16
-
17
- // Simulate failure to set cookie on this domain
18
- if (domain === 'new-website.ons.gov.uk') return;
19
-
20
- const [key, val] = value.split('=');
21
- mockCookieStore[key] = val.split(';')[0];
22
- },
23
- };
24
-
25
- describe('script: getDomain()', () => {
11
+ function setCookieDomain(domain) {
12
+ const banner = document.createElement('div');
13
+ banner.className = 'ons-cookies-banner';
14
+ banner.setAttribute('data-ons-cookie-domain', domain);
15
+ document.body.appendChild(banner);
16
+ }
17
+
18
+ function getLastCookieAssignment() {
19
+ return cookieAssignments[cookieAssignments.length - 1];
20
+ }
21
+
22
+ describe('script: cookies-functions', () => {
26
23
  beforeEach(() => {
27
- mockCookieStore = {}; // clear the cookie between tests
24
+ cookieAssignments = [];
25
+ mockCookieStore = {};
28
26
  document.body.innerHTML = '';
27
+
28
+ Object.defineProperty(document, 'cookie', {
29
+ get() {
30
+ return Object.entries(mockCookieStore)
31
+ .map(([key, value]) => `${key}=${value}`)
32
+ .join('; ');
33
+ },
34
+ set(value) {
35
+ cookieAssignments.push(value);
36
+
37
+ const [key, val] = value.split('=');
38
+ if (value.includes('expires=Thu, 01 Jan 1970')) {
39
+ delete mockCookieStore[key];
40
+ } else {
41
+ mockCookieStore[key] = val.split(';')[0];
42
+ }
43
+ },
44
+ configurable: true,
45
+ });
29
46
  });
30
47
 
31
- test('should return service-manual.ons.gov.uk as the domain name when cookies can be set at the full subdomain', () => {
32
- const result = getDomain('service-manual.ons.gov.uk', setMockcookie);
33
- expect(result).toBe('service-manual.ons.gov.uk');
48
+ test('uses the Go port domain rule for known hosts', () => {
49
+ expect(extractDomainFromUrl('www.ons.gov.uk')).toBe('www.ons.gov.uk');
50
+ expect(extractDomainFromUrl('eq.census.gov.uk')).toBe('eq.census.gov.uk');
51
+ expect(extractDomainFromUrl('service.onsdigital.uk')).toBe('service.onsdigital.uk');
52
+ expect(extractDomainFromUrl('service.co.uk')).toBe('service.co.uk');
34
53
  });
35
54
 
36
- test('should remove `www` from the domain name www.ons.gov.uk', () => {
37
- const result = getDomain('www.ons.gov.uk', setMockcookie);
38
- expect(result).toBe('ons.gov.uk');
55
+ test('uses the Go port domain rule for local and unrecognised hosts', () => {
56
+ expect(extractDomainFromUrl('localhost')).toBe('localhost');
57
+ expect(extractDomainFromUrl('127.0.0.1')).toBe('localhost');
58
+ expect(extractDomainFromUrl('service.example.com')).toBe('');
39
59
  });
40
60
 
41
- test('removes `www` when cookieDomainPolicy is `legacy`', () => {
42
- const banner = document.createElement('div');
43
- banner.className = 'ons-cookies-banner';
44
- banner.setAttribute('data-ons-cookie-domain-policy', 'legacy');
45
- document.body.appendChild(banner);
61
+ test('sets a domain cookie by default on a known hostname', () => {
62
+ setCookie('ons_cookie_policy', 'test-value', { days: 365 });
46
63
 
47
- const result = getDomain('www.ons.gov.uk', setMockcookie);
48
- expect(result).toBe('ons.gov.uk');
64
+ expect(cookieAssignments).toHaveLength(1);
65
+ expect(getLastCookieAssignment()).toContain('ons_cookie_policy=test-value; domain=www.ons.gov.uk; path=/');
49
66
  });
50
67
 
51
- test('does not remove `www` when cookieDomainPolicy is `day1`', () => {
52
- const banner = document.createElement('div');
53
- banner.className = 'ons-cookies-banner';
54
- banner.setAttribute('data-ons-cookie-domain-policy', 'day1');
55
- document.body.appendChild(banner);
68
+ test('does not expire other scopes before setting banner state', () => {
69
+ setCookie('ons_cookie_message_displayed', 'true', { days: 365 });
56
70
 
57
- const result = getDomain('www.ons.gov.uk', setMockcookie);
58
- expect(result).toBe('www.ons.gov.uk');
71
+ expect(cookieAssignments).toHaveLength(1);
72
+ expect(getLastCookieAssignment()).toContain('ons_cookie_message_displayed=true; domain=www.ons.gov.uk; path=/');
59
73
  });
60
74
 
61
- test('returns `ons.gov.uk` as the domain name when cookies can not be set at subdomain `new-website.ons.gov.uk`', () => {
62
- const result = getDomain('new-website.ons.gov.uk', setMockcookie);
63
- expect(result).toBe('ons.gov.uk');
75
+ test('does not expire other scopes before setting other cookie categories', () => {
76
+ setCookie('_ga', 'test-value', { days: 365 });
77
+
78
+ expect(cookieAssignments).toHaveLength(1);
79
+ expect(getLastCookieAssignment()).toContain('_ga=test-value; domain=www.ons.gov.uk; path=/');
80
+ });
81
+
82
+ test('sets domain cookies when `domain` is provided', () => {
83
+ setCookieDomain('census.gov.uk');
84
+
85
+ setCookie('ons_cookie_policy', 'test-value', { days: 365 });
86
+
87
+ expect(getLastCookieAssignment()).toContain('ons_cookie_policy=test-value; domain=census.gov.uk; path=/');
88
+ });
89
+
90
+ test('sets host-only cookies when `domain` is explicitly false', () => {
91
+ setCookieDomain('');
92
+
93
+ setCookie('ons_cookie_policy', 'test-value', { days: 365 });
94
+
95
+ expect(cookieAssignments).toHaveLength(1);
96
+ expect(getLastCookieAssignment()).toContain('ons_cookie_policy=test-value; path=/');
97
+ expect(getLastCookieAssignment()).not.toMatch(/; domain=/i);
98
+ });
99
+
100
+ test('deletes only the default domain scope', () => {
101
+ setCookie('_ga', null, { days: -1 });
102
+
103
+ expect(cookieAssignments).toEqual(['_ga=; domain=www.ons.gov.uk; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure']);
104
+ });
105
+
106
+ test('deletes only the configured domain scope', () => {
107
+ setCookieDomain('census.gov.uk');
108
+
109
+ setCookie('_ga', null, { days: -1 });
110
+
111
+ expect(cookieAssignments).toEqual(['_ga=; domain=census.gov.uk; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure']);
112
+ });
113
+
114
+ test('deletes category cookies when consent is rejected', () => {
115
+ mockCookieStore.ons_cookie_policy = "{'essential':true,'settings':true,'usage':true,'campaigns':true}";
116
+ mockCookieStore._ga = 'test-value';
117
+
118
+ setConsentCookie({ usage: false });
119
+
120
+ expect(cookieAssignments).toContain('_ga=; domain=www.ons.gov.uk; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure');
64
121
  });
65
122
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ons/design-system",
3
3
  "description": "ONS Design System built CSS, JS, and Nunjucks templates",
4
- "version": "74.0.0-next.14",
4
+ "version": "74.0.0-next.15",
5
5
  "main": "index.js",
6
6
  "license": "MIT",
7
7
  "author": {