hof 20.3.6-beta-gtm → 20.3.6-timeout-beta

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.
@@ -0,0 +1,359 @@
1
+ /*
2
+ * This content is licensed according to the W3C Software License at
3
+ * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
4
+ */
5
+ 'use strict';
6
+
7
+ require('./utils');
8
+
9
+ const aria = window.aria || {};
10
+
11
+ aria.Utils = aria.Utils || {};
12
+
13
+ /**
14
+ * @description
15
+ * Key code constants
16
+ */
17
+ aria.KeyCode = {
18
+ BACKSPACE: 8,
19
+ TAB: 9,
20
+ RETURN: 13,
21
+ SHIFT: 16,
22
+ ESC: 27,
23
+ SPACE: 32,
24
+ PAGE_UP: 33,
25
+ PAGE_DOWN: 34,
26
+ END: 35,
27
+ HOME: 36,
28
+ LEFT: 37,
29
+ UP: 38,
30
+ RIGHT: 39,
31
+ DOWN: 40,
32
+ DELETE: 46
33
+ };
34
+
35
+ (function () {
36
+ // eslint-disable-next-line no-console
37
+ console.log('TEST DIALOG.js', aria);
38
+ /*
39
+ * When util functions move focus around, set this true so the focus listener
40
+ * can ignore the events.
41
+ */
42
+ aria.Utils.IgnoreUtilFocusChanges = false;
43
+
44
+ aria.Utils.dialogOpenClass = 'has-dialog';
45
+
46
+ /**
47
+ * @description Set focus on descendant nodes until the first focusable element is
48
+ * found.
49
+ * @param element
50
+ * DOM node for which to find the first focusable descendant.
51
+ * @returns {boolean}
52
+ * true if a focusable element is found and focus is set.
53
+ */
54
+ aria.Utils.focusFirstDescendant = function (element) {
55
+ for (let i = 0; i < element.childNodes.length; i++) {
56
+ const child = element.childNodes[i];
57
+ if (
58
+ aria.Utils.attemptFocus(child) ||
59
+ aria.Utils.focusFirstDescendant(child)
60
+ ) {
61
+ return true;
62
+ }
63
+ }
64
+ return false;
65
+ }; // end focusFirstDescendant
66
+
67
+ /**
68
+ * @description Find the last descendant node that is focusable.
69
+ * @param element
70
+ * DOM node for which to find the last focusable descendant.
71
+ * @returns {boolean}
72
+ * true if a focusable element is found and focus is set.
73
+ */
74
+ aria.Utils.focusLastDescendant = function (element) {
75
+ for (let i = element.childNodes.length - 1; i >= 0; i--) {
76
+ const child = element.childNodes[i];
77
+ if (
78
+ aria.Utils.attemptFocus(child) ||
79
+ aria.Utils.focusLastDescendant(child)
80
+ ) {
81
+ return true;
82
+ }
83
+ }
84
+ return false;
85
+ }; // end focusLastDescendant
86
+
87
+ /**
88
+ * @description Set Attempt to set focus on the current node.
89
+ * @param element
90
+ * The node to attempt to focus on.
91
+ * @returns {boolean}
92
+ * true if element is focused.
93
+ */
94
+ aria.Utils.attemptFocus = function (element) {
95
+ if (!aria.Utils.isFocusable(element)) {
96
+ return false;
97
+ }
98
+
99
+ aria.Utils.IgnoreUtilFocusChanges = true;
100
+ try {
101
+ element.focus();
102
+ } catch (e) {
103
+ // continue regardless of error
104
+ }
105
+ aria.Utils.IgnoreUtilFocusChanges = false;
106
+ return document.activeElement === element;
107
+ }; // end attemptFocus
108
+
109
+ /* Modals can open modals. Keep track of them with this array. */
110
+ aria.OpenDialogList = aria.OpenDialogList || new Array(0);
111
+
112
+ /**
113
+ * @returns {object} the last opened dialog (the current dialog)
114
+ */
115
+ aria.getCurrentDialog = function () {
116
+ if (aria.OpenDialogList && aria.OpenDialogList.length) {
117
+ return aria.OpenDialogList[aria.OpenDialogList.length - 1];
118
+ }
119
+ return null;
120
+ };
121
+
122
+ aria.closeCurrentDialog = function () {
123
+ const currentDialog = aria.getCurrentDialog();
124
+ if (currentDialog) {
125
+ currentDialog.close();
126
+ return true;
127
+ }
128
+
129
+ return false;
130
+ };
131
+
132
+ aria.handleEscape = function (event) {
133
+ const key = event.which || event.keyCode;
134
+
135
+ if (key === aria.KeyCode.ESC && aria.closeCurrentDialog()) {
136
+ event.stopPropagation();
137
+ }
138
+ };
139
+
140
+ document.addEventListener('keyup', aria.handleEscape);
141
+
142
+ /**
143
+ * @class
144
+ * @description Dialog object providing modal focus management.
145
+ *
146
+ * Assumptions: The element serving as the dialog container is present in the
147
+ * DOM and hidden. The dialog container has role='dialog'.
148
+ * @param dialogId
149
+ * The ID of the element serving as the dialog container.
150
+ * @param focusAfterClosed
151
+ * Either the DOM node or the ID of the DOM node to focus when the
152
+ * dialog closes.
153
+ * @param focusFirst
154
+ * Optional parameter containing either the DOM node or the ID of the
155
+ * DOM node to focus when the dialog opens. If not specified, the
156
+ * first focusable element in the dialog will receive focus.
157
+ */
158
+ aria.Dialog = function (dialogId, focusAfterClosed, focusFirst) {
159
+ this.dialogNode = document.getElementById(dialogId);
160
+ if (this.dialogNode === null) {
161
+ throw new Error('No element found with id="' + dialogId + '".');
162
+ }
163
+
164
+ const validRoles = ['dialog', 'alertdialog'];
165
+ const isDialog = (this.dialogNode.getAttribute('role') || '')
166
+ .trim()
167
+ .split(/\s+/g)
168
+ .some(function (token) {
169
+ return validRoles.some(function (role) {
170
+ return token === role;
171
+ });
172
+ });
173
+ if (!isDialog) {
174
+ throw new Error(
175
+ 'Dialog() requires a DOM element with ARIA role of dialog or alertdialog.'
176
+ );
177
+ }
178
+
179
+ // Wrap in an individual backdrop element if one doesn't exist
180
+ // Native <dialog> elements use the ::backdrop pseudo-element, which
181
+ // works similarly.
182
+ const backdropClass = 'dialog-backdrop';
183
+ if (this.dialogNode.parentNode.classList.contains(backdropClass)) {
184
+ this.backdropNode = this.dialogNode.parentNode;
185
+ } else {
186
+ this.backdropNode = document.createElement('div');
187
+ this.backdropNode.className = backdropClass;
188
+ this.dialogNode.parentNode.insertBefore(
189
+ this.backdropNode,
190
+ this.dialogNode
191
+ );
192
+ this.backdropNode.appendChild(this.dialogNode);
193
+ }
194
+ this.backdropNode.classList.add('active');
195
+
196
+ // Disable scroll on the body element
197
+ document.body.classList.add(aria.Utils.dialogOpenClass);
198
+
199
+ if (typeof focusAfterClosed === 'string') {
200
+ this.focusAfterClosed = document.getElementById(focusAfterClosed);
201
+ } else if (typeof focusAfterClosed === 'object') {
202
+ this.focusAfterClosed = focusAfterClosed;
203
+ } else {
204
+ throw new Error(
205
+ 'the focusAfterClosed parameter is required for the aria.Dialog constructor.'
206
+ );
207
+ }
208
+
209
+ if (typeof focusFirst === 'string') {
210
+ this.focusFirst = document.getElementById(focusFirst);
211
+ } else if (typeof focusFirst === 'object') {
212
+ this.focusFirst = focusFirst;
213
+ } else {
214
+ this.focusFirst = null;
215
+ }
216
+
217
+ // Bracket the dialog node with two invisible, focusable nodes.
218
+ // While this dialog is open, we use these to make sure that focus never
219
+ // leaves the document even if dialogNode is the first or last node.
220
+ const preDiv = document.createElement('div');
221
+ this.preNode = this.dialogNode.parentNode.insertBefore(
222
+ preDiv,
223
+ this.dialogNode
224
+ );
225
+ this.preNode.tabIndex = 0;
226
+ const postDiv = document.createElement('div');
227
+ this.postNode = this.dialogNode.parentNode.insertBefore(
228
+ postDiv,
229
+ this.dialogNode.nextSibling
230
+ );
231
+ this.postNode.tabIndex = 0;
232
+
233
+ // If this modal is opening on top of one that is already open,
234
+ // get rid of the document focus listener of the open dialog.
235
+ if (aria.OpenDialogList.length > 0) {
236
+ aria.getCurrentDialog().removeListeners();
237
+ }
238
+
239
+ this.addListeners();
240
+ aria.OpenDialogList.push(this);
241
+ this.clearDialog();
242
+ this.dialogNode.className = 'default_dialog'; // make visible
243
+
244
+ if (this.focusFirst) {
245
+ this.focusFirst.focus();
246
+ } else {
247
+ aria.Utils.focusFirstDescendant(this.dialogNode);
248
+ }
249
+
250
+ this.lastFocus = document.activeElement;
251
+ }; // end Dialog constructor
252
+
253
+ aria.Dialog.prototype.clearDialog = function () {
254
+ Array.prototype.map.call(
255
+ this.dialogNode.querySelectorAll('input'),
256
+ function (input) {
257
+ input.value = '';
258
+ }
259
+ );
260
+ };
261
+
262
+ /**
263
+ * @description
264
+ * Hides the current top dialog,
265
+ * removes listeners of the top dialog,
266
+ * restore listeners of a parent dialog if one was open under the one that just closed,
267
+ * and sets focus on the element specified for focusAfterClosed.
268
+ */
269
+ aria.Dialog.prototype.close = function () {
270
+ aria.OpenDialogList.pop();
271
+ this.removeListeners();
272
+ aria.Utils.remove(this.preNode);
273
+ aria.Utils.remove(this.postNode);
274
+ this.dialogNode.className = 'hidden';
275
+ this.backdropNode.classList.remove('active');
276
+ this.focusAfterClosed.focus();
277
+
278
+ // If a dialog was open underneath this one, restore its listeners.
279
+ if (aria.OpenDialogList.length > 0) {
280
+ aria.getCurrentDialog().addListeners();
281
+ } else {
282
+ document.body.classList.remove(aria.Utils.dialogOpenClass);
283
+ }
284
+ }; // end close
285
+
286
+ /**
287
+ * @description
288
+ * Hides the current dialog and replaces it with another.
289
+ * @param newDialogId
290
+ * ID of the dialog that will replace the currently open top dialog.
291
+ * @param newFocusAfterClosed
292
+ * Optional ID or DOM node specifying where to place focus when the new dialog closes.
293
+ * If not specified, focus will be placed on the element specified by the dialog being replaced.
294
+ * @param newFocusFirst
295
+ * Optional ID or DOM node specifying where to place focus in the new dialog when it opens.
296
+ * If not specified, the first focusable element will receive focus.
297
+ */
298
+ aria.Dialog.prototype.replace = function (
299
+ newDialogId,
300
+ newFocusAfterClosed,
301
+ newFocusFirst
302
+ ) {
303
+ aria.OpenDialogList.pop();
304
+ this.removeListeners();
305
+ aria.Utils.remove(this.preNode);
306
+ aria.Utils.remove(this.postNode);
307
+ this.dialogNode.className = 'hidden';
308
+ this.backdropNode.classList.remove('active');
309
+
310
+ const focusAfterClosed = newFocusAfterClosed || this.focusAfterClosed;
311
+ aria.Dialog(newDialogId, focusAfterClosed, newFocusFirst);
312
+ }; // end replace
313
+
314
+ aria.Dialog.prototype.addListeners = function () {
315
+ document.addEventListener('focus', this.trapFocus, true);
316
+ }; // end addListeners
317
+
318
+ aria.Dialog.prototype.removeListeners = function () {
319
+ document.removeEventListener('focus', this.trapFocus, true);
320
+ }; // end removeListeners
321
+
322
+ aria.Dialog.prototype.trapFocus = function (event) {
323
+ if (aria.Utils.IgnoreUtilFocusChanges) {
324
+ return;
325
+ }
326
+ const currentDialog = aria.getCurrentDialog();
327
+ if (currentDialog.dialogNode.contains(event.target)) {
328
+ currentDialog.lastFocus = event.target;
329
+ } else {
330
+ aria.Utils.focusFirstDescendant(currentDialog.dialogNode);
331
+ if (currentDialog.lastFocus === document.activeElement) {
332
+ aria.Utils.focusLastDescendant(currentDialog.dialogNode);
333
+ }
334
+ currentDialog.lastFocus = document.activeElement;
335
+ }
336
+ }; // end trapFocus
337
+
338
+ window.openDialog = function (dialogId, focusAfterClosed, focusFirst) {
339
+ aria.Dialog(dialogId, focusAfterClosed, focusFirst);
340
+ };
341
+
342
+ window.closeDialog = function (closeButton) {
343
+ const topDialog = aria.getCurrentDialog();
344
+ if (topDialog.dialogNode.contains(closeButton)) {
345
+ topDialog.close();
346
+ }
347
+ }; // end closeDialog
348
+
349
+ window.replaceDialog = function (
350
+ newDialogId,
351
+ newFocusAfterClosed,
352
+ newFocusFirst
353
+ ) {
354
+ const topDialog = aria.getCurrentDialog();
355
+ if (topDialog.dialogNode.contains(document.activeElement)) {
356
+ topDialog.replace(newDialogId, newFocusAfterClosed, newFocusFirst);
357
+ }
358
+ }; // end replaceDialog
359
+ })();
@@ -0,0 +1,114 @@
1
+ 'use strict';
2
+ /**
3
+ * @namespace aria
4
+ */
5
+
6
+ window.aria = window.aria || {};
7
+
8
+ (function () {
9
+ // eslint-disable-next-line no-console
10
+ // console.log('TEST UTILS.js', aria);
11
+ })();
12
+
13
+ window.aria.Utils = window.aria.Utils || {};
14
+
15
+ // Polyfill src https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
16
+ window.aria.Utils.matches = function (element, selector) {
17
+ if (!Element.prototype.matches) {
18
+ Element.prototype.matches =
19
+ Element.prototype.matchesSelector ||
20
+ Element.prototype.mozMatchesSelector ||
21
+ Element.prototype.msMatchesSelector ||
22
+ Element.prototype.oMatchesSelector ||
23
+ Element.prototype.webkitMatchesSelector ||
24
+ function (s) {
25
+ const matches = element.parentNode.querySelectorAll(s);
26
+ let i = matches.length;
27
+ while (--i >= 0 && matches.item(i) !== this) {
28
+ // empty
29
+ }
30
+ return i > -1;
31
+ };
32
+ }
33
+
34
+ return element.matches(selector);
35
+ };
36
+
37
+ window.aria.Utils.remove = function (item) {
38
+ if (item.remove && typeof item.remove === 'function') {
39
+ return item.remove();
40
+ }
41
+ if (
42
+ item.parentNode &&
43
+ item.parentNode.removeChild &&
44
+ typeof item.parentNode.removeChild === 'function'
45
+ ) {
46
+ return item.parentNode.removeChild(item);
47
+ }
48
+ return false;
49
+ };
50
+
51
+ window.aria.Utils.isFocusable = function (element) {
52
+ if (element.tabIndex < 0) {
53
+ return false;
54
+ }
55
+
56
+ if (element.disabled) {
57
+ return false;
58
+ }
59
+
60
+ switch (element.nodeName) {
61
+ case 'A':
62
+ return !!element.href && element.rel !== 'ignore';
63
+ case 'INPUT':
64
+ return element.type !== 'hidden';
65
+ case 'BUTTON':
66
+ case 'SELECT':
67
+ case 'TEXTAREA':
68
+ return true;
69
+ default:
70
+ return false;
71
+ }
72
+ };
73
+
74
+ window.aria.Utils.getAncestorBySelector = function (element, selector) {
75
+ if (!window.aria.Utils.matches(element, selector + ' ' + element.tagName)) {
76
+ // Element is not inside an element that matches selector
77
+ return null;
78
+ }
79
+
80
+ // Move up the DOM tree until a parent matching the selector is found
81
+ let currentNode = element;
82
+ let ancestor = null;
83
+ while (ancestor === null) {
84
+ if (window.aria.Utils.matches(currentNode.parentNode, selector)) {
85
+ ancestor = currentNode.parentNode;
86
+ } else {
87
+ currentNode = currentNode.parentNode;
88
+ }
89
+ }
90
+
91
+ return ancestor;
92
+ };
93
+
94
+ window.aria.Utils.hasClass = function (element, className) {
95
+ return new RegExp('(\\s|^)' + className + '(\\s|$)').test(element.className);
96
+ };
97
+
98
+ window.aria.Utils.addClass = function (element, className) {
99
+ if (!window.aria.Utils.hasClass(element, className)) {
100
+ element.className += ' ' + className;
101
+ }
102
+ };
103
+
104
+ window.aria.Utils.removeClass = function (element, className) {
105
+ const classRegex = new RegExp('(\\s|^)' + className + '(\\s|$)');
106
+ element.className = element.className.replace(classRegex, ' ').trim();
107
+ };
108
+
109
+ window.aria.Utils.bindMethods = function (object /* , ...methodNames */) {
110
+ const methodNames = Array.prototype.slice.call(arguments, 1);
111
+ methodNames.forEach(function (method) {
112
+ object[method] = object[method].bind(object);
113
+ });
114
+ };
@@ -11,6 +11,7 @@ var validation = toolkit.validation;
11
11
  var GOVUK = require('govuk-frontend');
12
12
  GOVUK.initAll();
13
13
  window.GOVUK = GOVUK;
14
+ var dialog = require('./dialog');
14
15
  var skipToMain = require('./skip-to-main');
15
16
  var cookie = require('./govuk-cookies');
16
17
  var cookieSettings = require('./cookieSettings');
package/lib/ga-tag.js CHANGED
@@ -52,12 +52,11 @@ const setupPageMap = routes => {
52
52
  module.exports = (app, config) => {
53
53
  const gaTagId = config.gaTagId;
54
54
  const ga4TagId = config.ga4TagId;
55
- const gtmTagId = config.gtmTagId;
56
- const environmentType = config.environmentType ? config.environmentType : 'dev';
55
+ const gtm = config.gtm;
57
56
  const gaCrossDomainTrackingTagId = config.gaCrossDomainTrackingTagId;
58
57
  const routes = config.routes;
59
58
 
60
- if (gaTagId || ga4TagId || gtmTagId) {
59
+ if (gaTagId || ga4TagId || gtm.tagId) {
61
60
  const pageMap = setupPageMap(routes);
62
61
 
63
62
  app.use((req, res, next) => {
@@ -74,47 +73,17 @@ module.exports = (app, config) => {
74
73
  };
75
74
 
76
75
  // Adding extra properties if a GTM TAG is available
77
- if (gtmTagId) {
76
+ if (gtm.tagId) {
77
+ gtm.config.pageName = gtm.composePageName(page, convertToGTMPage);
78
78
  Object.assign(properties, {
79
- gtmTagId: gtmTagId,
80
- 'gtm-page': convertToGTMPage(page),
81
- eventName: 'pageLoad',
82
- pageName: `ETA | Customer Contact Webform | ${convertToGTMPage(page)}`,
83
- applicationType: 'ETA | Customer Contact',
84
- environmentType: environmentType
79
+ gtmConfig: JSON.stringify(gtm.config),
80
+ gtmTagId: gtm.tagId
85
81
  });
86
82
  }
87
-
88
83
  res.locals = Object.assign(res.locals, properties);
89
84
  next();
90
85
  });
91
86
  }
92
87
 
93
- /*
94
- if (gaTagId || ga4TagId || gtmTagId) {
95
- const pageMap = setupPageMap(routes);
96
-
97
- app.use((req, res, next) => {
98
- const page = pageView(req.path, pageMap);
99
- res.locals.gaAllowDebug = config.env === 'development';
100
-
101
- if (gtmTagId) {
102
- res.locals.gtmTagId = gtmTagId;
103
- res.locals['gtm-page'] = convertToGTMPage(page);
104
- res.locals.eventName = "pageLoad"
105
- res.locals.pageName = `ETA | Customer Contact Webform | ${res.locals['gtm-page']}`
106
- res.locals.applicationType = "ETA | Customer Contact"
107
- res.locals.environmentType = environmentType;
108
- }
109
- res.locals.gaTagId = gaTagId;
110
- res.locals.ga4TagId = ga4TagId;
111
- res.locals.gaCrossDomainTrackingTagId = gaCrossDomainTrackingTagId;
112
- res.locals['ga-id'] = gaTagId;
113
- res.locals['ga-page'] = page;
114
- next();
115
- });
116
- }
117
- */
118
-
119
88
  return app;
120
89
  };
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ module.exports = (options, timers) => {
4
+ return (req, res, next) => {
5
+ // eslint-disable-next-line no-console
6
+ console.log(req.url, 'SESSION TIMOUT:', options.session);
7
+ if (timers.length >= 2) {
8
+ // eslint-disable-next-line no-console
9
+ console.log(timers);
10
+ for (const t of timers) {
11
+ clearTimeout(t);
12
+ }
13
+ }
14
+ timers.push(setTimeout(() => {
15
+ // eslint-disable-next-line no-console,no-alert
16
+ console.log(req.originalUrl);
17
+ const err = new Error('Session will expire soon');
18
+ err.code = 'SESSION_TIMEOUT_WARNING';
19
+ return next(err);
20
+ }, 1000 * 10));
21
+ next();
22
+ };
23
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hof",
3
3
  "description": "A bootstrap for HOF projects",
4
- "version": "20.3.6-beta-gtm",
4
+ "version": "20.3.6-timeout-beta",
5
5
  "license": "MIT",
6
6
  "main": "index.js",
7
7
  "author": "HomeOffice",
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ module.exports = SuperClass => class extends SuperClass {
4
+ getValues(req, res, next) {
5
+ // eslint-disable-next-line no-console
6
+ console.log(req.sessionModel);
7
+ next();
8
+ }
9
+ };