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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ };