hof 20.3.11 → 20.4.0-beta-session-timeout

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. package/.nyc_output/9651d42a-59d8-48e6-949c-655d9fa6db21.json +1 -0
  2. package/.nyc_output/processinfo/9651d42a-59d8-48e6-949c-655d9fa6db21.json +1 -0
  3. package/.nyc_output/processinfo/index.json +1 -1
  4. package/components/date/templates/date.html +1 -1
  5. package/config/hof-defaults.js +10 -5
  6. package/frontend/govuk-template/build/govuk_template.html +17 -18
  7. package/frontend/govuk-template/govuk_template_generated.html +17 -18
  8. package/frontend/template-mixins/partials/forms/checkbox-group.html +1 -1
  9. package/frontend/template-mixins/partials/forms/option-group.html +1 -1
  10. package/frontend/template-mixins/partials/forms/textarea-group.html +3 -1
  11. package/frontend/template-partials/translations/src/en/buttons.json +1 -0
  12. package/frontend/template-partials/views/layout.html +2 -1
  13. package/frontend/template-partials/views/partials/cookie-banner.html +2 -2
  14. package/frontend/template-partials/views/partials/gatag.html +0 -1
  15. package/frontend/template-partials/views/partials/head.html +23 -0
  16. package/frontend/template-partials/views/partials/session-timeout-warning.html +26 -0
  17. package/frontend/themes/gov-uk/client-js/dialog/index.js +358 -0
  18. package/frontend/themes/gov-uk/client-js/dialog/utils.js +114 -0
  19. package/frontend/themes/gov-uk/client-js/index.js +1 -0
  20. package/frontend/themes/gov-uk/styles/govuk.scss +1 -0
  21. package/frontend/themes/gov-uk/styles/session_timeout.scss +138 -0
  22. package/lib/ga-tag.js +33 -7
  23. package/lib/health.js +1 -1
  24. package/lib/sessions.js +0 -2
  25. package/middleware/cookies.js +2 -0
  26. package/middleware/rate-limiter.js +1 -3
  27. package/package.json +5 -5
  28. package/sandbox/apps/sandbox/fields.js +0 -6
  29. package/sandbox/apps/sandbox/translations/src/en/fields.json +5 -5
  30. package/sandbox/apps/sandbox/translations/src/en/pages.json +1 -1
  31. package/sandbox/config.js +1 -2
  32. package/sandbox/public/js/bundle.js +965 -1130
  33. package/sandbox/server.js +1 -4
  34. package/sandbox/yarn.lock +3 -3
  35. package/.nyc_output/b924df42-9cb6-4618-9a07-4f58f67ae28a.json +0 -1
  36. package/.nyc_output/processinfo/b924df42-9cb6-4618-9a07-4f58f67ae28a.json +0 -1
  37. package/sandbox/dump.rdb +0 -0
@@ -0,0 +1,358 @@
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
+ };
120
+
121
+ aria.closeCurrentDialog = function () {
122
+ const currentDialog = aria.getCurrentDialog();
123
+ if (currentDialog) {
124
+ currentDialog.close();
125
+ return true;
126
+ }
127
+
128
+ return false;
129
+ };
130
+
131
+ aria.handleEscape = function (event) {
132
+ const key = event.which || event.keyCode;
133
+
134
+ if (key === aria.KeyCode.ESC && aria.closeCurrentDialog()) {
135
+ event.stopPropagation();
136
+ }
137
+ };
138
+
139
+ document.addEventListener('keyup', aria.handleEscape);
140
+
141
+ /**
142
+ * @class
143
+ * @description Dialog object providing modal focus management.
144
+ *
145
+ * Assumptions: The element serving as the dialog container is present in the
146
+ * DOM and hidden. The dialog container has role='dialog'.
147
+ * @param dialogId
148
+ * The ID of the element serving as the dialog container.
149
+ * @param focusAfterClosed
150
+ * Either the DOM node or the ID of the DOM node to focus when the
151
+ * dialog closes.
152
+ * @param focusFirst
153
+ * Optional parameter containing either the DOM node or the ID of the
154
+ * DOM node to focus when the dialog opens. If not specified, the
155
+ * first focusable element in the dialog will receive focus.
156
+ */
157
+ aria.Dialog = function (dialogId, focusAfterClosed, focusFirst) {
158
+ this.dialogNode = document.getElementById(dialogId);
159
+ if (this.dialogNode === null) {
160
+ throw new Error('No element found with id="' + dialogId + '".');
161
+ }
162
+
163
+ const validRoles = ['dialog', 'alertdialog'];
164
+ const isDialog = (this.dialogNode.getAttribute('role') || '')
165
+ .trim()
166
+ .split(/\s+/g)
167
+ .some(function (token) {
168
+ return validRoles.some(function (role) {
169
+ return token === role;
170
+ });
171
+ });
172
+ if (!isDialog) {
173
+ throw new Error(
174
+ 'Dialog() requires a DOM element with ARIA role of dialog or alertdialog.'
175
+ );
176
+ }
177
+
178
+ // Wrap in an individual backdrop element if one doesn't exist
179
+ // Native <dialog> elements use the ::backdrop pseudo-element, which
180
+ // works similarly.
181
+ const backdropClass = 'dialog-backdrop';
182
+ if (this.dialogNode.parentNode.classList.contains(backdropClass)) {
183
+ this.backdropNode = this.dialogNode.parentNode;
184
+ } else {
185
+ this.backdropNode = document.createElement('div');
186
+ this.backdropNode.className = backdropClass;
187
+ this.dialogNode.parentNode.insertBefore(
188
+ this.backdropNode,
189
+ this.dialogNode
190
+ );
191
+ this.backdropNode.appendChild(this.dialogNode);
192
+ }
193
+ this.backdropNode.classList.add('active');
194
+
195
+ // Disable scroll on the body element
196
+ document.body.classList.add(aria.Utils.dialogOpenClass);
197
+
198
+ if (typeof focusAfterClosed === 'string') {
199
+ this.focusAfterClosed = document.getElementById(focusAfterClosed);
200
+ } else if (typeof focusAfterClosed === 'object') {
201
+ this.focusAfterClosed = focusAfterClosed;
202
+ } else {
203
+ throw new Error(
204
+ 'the focusAfterClosed parameter is required for the aria.Dialog constructor.'
205
+ );
206
+ }
207
+
208
+ if (typeof focusFirst === 'string') {
209
+ this.focusFirst = document.getElementById(focusFirst);
210
+ } else if (typeof focusFirst === 'object') {
211
+ this.focusFirst = focusFirst;
212
+ } else {
213
+ this.focusFirst = null;
214
+ }
215
+
216
+ // Bracket the dialog node with two invisible, focusable nodes.
217
+ // While this dialog is open, we use these to make sure that focus never
218
+ // leaves the document even if dialogNode is the first or last node.
219
+ const preDiv = document.createElement('div');
220
+ this.preNode = this.dialogNode.parentNode.insertBefore(
221
+ preDiv,
222
+ this.dialogNode
223
+ );
224
+ this.preNode.tabIndex = 0;
225
+ const postDiv = document.createElement('div');
226
+ this.postNode = this.dialogNode.parentNode.insertBefore(
227
+ postDiv,
228
+ this.dialogNode.nextSibling
229
+ );
230
+ this.postNode.tabIndex = 0;
231
+
232
+ // If this modal is opening on top of one that is already open,
233
+ // get rid of the document focus listener of the open dialog.
234
+ if (aria.OpenDialogList.length > 0) {
235
+ aria.getCurrentDialog().removeListeners();
236
+ }
237
+
238
+ this.addListeners();
239
+ aria.OpenDialogList.push(this);
240
+ this.clearDialog();
241
+ this.dialogNode.className = 'default_dialog'; // make visible
242
+
243
+ if (this.focusFirst) {
244
+ this.focusFirst.focus();
245
+ } else {
246
+ aria.Utils.focusFirstDescendant(this.dialogNode);
247
+ }
248
+
249
+ this.lastFocus = document.activeElement;
250
+ }; // end Dialog constructor
251
+
252
+ aria.Dialog.prototype.clearDialog = function () {
253
+ Array.prototype.map.call(
254
+ this.dialogNode.querySelectorAll('input'),
255
+ function (input) {
256
+ input.value = '';
257
+ }
258
+ );
259
+ };
260
+
261
+ /**
262
+ * @description
263
+ * Hides the current top dialog,
264
+ * removes listeners of the top dialog,
265
+ * restore listeners of a parent dialog if one was open under the one that just closed,
266
+ * and sets focus on the element specified for focusAfterClosed.
267
+ */
268
+ aria.Dialog.prototype.close = function () {
269
+ aria.OpenDialogList.pop();
270
+ this.removeListeners();
271
+ aria.Utils.remove(this.preNode);
272
+ aria.Utils.remove(this.postNode);
273
+ this.dialogNode.className = 'hidden';
274
+ this.backdropNode.classList.remove('active');
275
+ this.focusAfterClosed.focus();
276
+
277
+ // If a dialog was open underneath this one, restore its listeners.
278
+ if (aria.OpenDialogList.length > 0) {
279
+ aria.getCurrentDialog().addListeners();
280
+ } else {
281
+ document.body.classList.remove(aria.Utils.dialogOpenClass);
282
+ }
283
+ }; // end close
284
+
285
+ /**
286
+ * @description
287
+ * Hides the current dialog and replaces it with another.
288
+ * @param newDialogId
289
+ * ID of the dialog that will replace the currently open top dialog.
290
+ * @param newFocusAfterClosed
291
+ * Optional ID or DOM node specifying where to place focus when the new dialog closes.
292
+ * If not specified, focus will be placed on the element specified by the dialog being replaced.
293
+ * @param newFocusFirst
294
+ * Optional ID or DOM node specifying where to place focus in the new dialog when it opens.
295
+ * If not specified, the first focusable element will receive focus.
296
+ */
297
+ aria.Dialog.prototype.replace = function (
298
+ newDialogId,
299
+ newFocusAfterClosed,
300
+ newFocusFirst
301
+ ) {
302
+ aria.OpenDialogList.pop();
303
+ this.removeListeners();
304
+ aria.Utils.remove(this.preNode);
305
+ aria.Utils.remove(this.postNode);
306
+ this.dialogNode.className = 'hidden';
307
+ this.backdropNode.classList.remove('active');
308
+
309
+ const focusAfterClosed = newFocusAfterClosed || this.focusAfterClosed;
310
+ new aria.Dialog(newDialogId, focusAfterClosed, newFocusFirst);
311
+ }; // end replace
312
+
313
+ aria.Dialog.prototype.addListeners = function () {
314
+ document.addEventListener('focus', this.trapFocus, true);
315
+ }; // end addListeners
316
+
317
+ aria.Dialog.prototype.removeListeners = function () {
318
+ document.removeEventListener('focus', this.trapFocus, true);
319
+ }; // end removeListeners
320
+
321
+ aria.Dialog.prototype.trapFocus = function (event) {
322
+ if (aria.Utils.IgnoreUtilFocusChanges) {
323
+ return;
324
+ }
325
+ const currentDialog = aria.getCurrentDialog();
326
+ if (currentDialog.dialogNode.contains(event.target)) {
327
+ currentDialog.lastFocus = event.target;
328
+ } else {
329
+ aria.Utils.focusFirstDescendant(currentDialog.dialogNode);
330
+ if (currentDialog.lastFocus === document.activeElement) {
331
+ aria.Utils.focusLastDescendant(currentDialog.dialogNode);
332
+ }
333
+ currentDialog.lastFocus = document.activeElement;
334
+ }
335
+ }; // end trapFocus
336
+
337
+ window.openDialog = function (dialogId, focusAfterClosed, focusFirst) {
338
+ new aria.Dialog(dialogId, focusAfterClosed, focusFirst);
339
+ };
340
+
341
+ window.closeDialog = function (closeButton) {
342
+ const topDialog = aria.getCurrentDialog();
343
+ if (topDialog.dialogNode.contains(closeButton)) {
344
+ topDialog.close();
345
+ }
346
+ }; // end closeDialog
347
+
348
+ window.replaceDialog = function (
349
+ newDialogId,
350
+ newFocusAfterClosed,
351
+ newFocusFirst
352
+ ) {
353
+ const topDialog = aria.getCurrentDialog();
354
+ if (topDialog.dialogNode.contains(document.activeElement)) {
355
+ topDialog.replace(newDialogId, newFocusAfterClosed, newFocusFirst);
356
+ }
357
+ }; // end replaceDialog
358
+ })();
@@ -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');
@@ -26,6 +26,7 @@ $path: "/public/images/" !default;
26
26
  @import "cookie-settings";
27
27
  @import "check_your_answers";
28
28
  @import "pdf";
29
+ @import "session_timeout";
29
30
 
30
31
  // Modules
31
32
  @import "modules/validation";
@@ -0,0 +1,138 @@
1
+ //autocomplete styling
2
+ .tt-menu {
3
+ background-color: #fff;
4
+ border: 1px solid #ccc;
5
+ width: 65%;
6
+ }
7
+
8
+ .tt-suggestion {
9
+ padding: 0.5em;
10
+
11
+ &:hover,
12
+ &.tt-cursor {
13
+ color: #fff;
14
+ background-color: #0097cf;
15
+ }
16
+
17
+ &:hover {
18
+ cursor: pointer;
19
+ }
20
+ }
21
+
22
+ .twitter-typeahead {
23
+ width: 100%;
24
+ }
25
+
26
+ .hidden {
27
+ display: none;
28
+ }
29
+
30
+ [role="dialog"] {
31
+ box-sizing: border-box;
32
+ padding: 15px;
33
+ border: 1px solid #000;
34
+ background-color: #fff;
35
+ min-height: 100vh;
36
+ }
37
+
38
+ @media screen and (min-width: 640px) {
39
+ [role="dialog"] {
40
+ position: absolute;
41
+ top: 25vh;
42
+ left: 50vw; /* move to the middle of the screen (assumes relative parent is the body/viewport) */
43
+ transform: translateX(
44
+ -50%
45
+ ); /* move backwards 50% of this element's width */
46
+ min-width: calc(640px - (15px * 2)); /* == breakpoint - left+right margin */
47
+ min-height: auto;
48
+ box-shadow: 0 19px 38px rgb(0 0 0 / 12%), 0 15px 12px rgb(0 0 0 / 22%);
49
+ }
50
+ }
51
+
52
+ .dialog_form {
53
+ margin: 15px;
54
+ }
55
+
56
+ .dialog_form .label_text {
57
+ box-sizing: border-box;
58
+ padding-right: 0.5em;
59
+ display: inline-block;
60
+ font-size: 16px;
61
+ font-weight: bold;
62
+ width: 30%;
63
+ text-align: right;
64
+ }
65
+
66
+ .dialog_form .label_info {
67
+ box-sizing: border-box;
68
+ padding-right: 0.5em;
69
+ font-size: 12px;
70
+ width: 30%;
71
+ text-align: right;
72
+ display: inline-block;
73
+ }
74
+
75
+ .dialog_form_item {
76
+ margin: 10px 0;
77
+ font-size: 0;
78
+ }
79
+
80
+ .dialog_form_item .wide_input {
81
+ box-sizing: border-box;
82
+ max-width: 70%;
83
+ width: 27em;
84
+ }
85
+
86
+ .dialog_form_actions {
87
+ text-align: right;
88
+ padding: 0 20px 20px;
89
+ }
90
+
91
+ .dialog_close_button {
92
+ float: right;
93
+ position: absolute;
94
+ top: 10px;
95
+ left: 92%;
96
+ height: 25px;
97
+ }
98
+
99
+ .dialog_close_button img {
100
+ border: 0;
101
+ }
102
+
103
+ .dialog_desc {
104
+ padding: 10px 20px;
105
+ }
106
+
107
+ /* native <dialog> element uses the ::backdrop pseudo-element */
108
+
109
+ /* dialog::backdrop, */
110
+ .dialog-backdrop {
111
+ display: none;
112
+ position: fixed;
113
+ overflow-y: auto;
114
+ top: 0;
115
+ right: 0;
116
+ bottom: 0;
117
+ left: 0;
118
+ z-index: 1;
119
+ }
120
+
121
+ @media screen and (min-width: 640px) {
122
+ .dialog-backdrop {
123
+ background: rgb(0 0 0 / 30%);
124
+ }
125
+ }
126
+
127
+ .dialog-backdrop.active {
128
+ display: block;
129
+ }
130
+
131
+ .no-scroll {
132
+ overflow-y: auto !important;
133
+ }
134
+
135
+ /* this is added to the body when a dialog is open */
136
+ .has-dialog {
137
+ overflow: hidden;
138
+ }
package/lib/ga-tag.js CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  const _ = require('lodash');
4
4
 
5
+ const convertToGTMPage = text => {
6
+ // Remove leading and trailing slashes
7
+ let str = text.replace(/^\/|\/$/g, '');
8
+ // Replace hyphens with spaces and capitalize each word
9
+ str = str.replace(/-+/g, ' ').replace(/(^|\s)\S/g, function (match) {
10
+ return match.toUpperCase();
11
+ });
12
+ return str;
13
+ };
14
+
5
15
  const pageView = (path, pageMap) => pageMap.get(path) || path;
6
16
 
7
17
  const createUris = routes => {
@@ -42,19 +52,35 @@ const setupPageMap = routes => {
42
52
  module.exports = (app, config) => {
43
53
  const gaTagId = config.gaTagId;
44
54
  const ga4TagId = config.ga4TagId;
55
+ const gtm = config.gtm;
45
56
  const gaCrossDomainTrackingTagId = config.gaCrossDomainTrackingTagId;
46
57
  const routes = config.routes;
47
58
 
48
- if (gaTagId || ga4TagId) {
59
+ if (gaTagId || ga4TagId || gtm.tagId) {
49
60
  const pageMap = setupPageMap(routes);
50
61
 
51
62
  app.use((req, res, next) => {
52
- res.locals.gaAllowDebug = config.env === 'development';
53
- res.locals.gaTagId = gaTagId;
54
- res.locals.ga4TagId = ga4TagId;
55
- res.locals.gaCrossDomainTrackingTagId = gaCrossDomainTrackingTagId;
56
- res.locals['ga-id'] = gaTagId;
57
- res.locals['ga-page'] = pageView(req.path, pageMap);
63
+ const page = pageView(req.path, pageMap);
64
+
65
+ // Preparing common res.locals properties
66
+ const properties = {
67
+ gaAllowDebug: config.env === 'development',
68
+ gaTagId: gaTagId,
69
+ ga4TagId: ga4TagId,
70
+ gaCrossDomainTrackingTagId: gaCrossDomainTrackingTagId,
71
+ 'ga-id': gaTagId,
72
+ 'ga-page': page
73
+ };
74
+
75
+ // Adding extra properties if a GTM TAG is available
76
+ if (gtm.tagId) {
77
+ gtm.config.pageName = gtm.composePageName(page, convertToGTMPage);
78
+ Object.assign(properties, {
79
+ gtmConfig: JSON.stringify(gtm.config),
80
+ gtmTagId: gtm.tagId
81
+ });
82
+ }
83
+ res.locals = Object.assign(res.locals, properties);
58
84
  next();
59
85
  });
60
86
  }
package/lib/health.js CHANGED
@@ -15,7 +15,7 @@ module.exports = redis => {
15
15
 
16
16
  router.get('/readiness', healthCheck({
17
17
  test: () => {
18
- if (!redis.isOpen && !redis.isReady) {
18
+ if (!redis.connected && !redis.ready) {
19
19
  return new Error('Session store unhealthy');
20
20
  }
21
21
  return 0;
package/lib/sessions.js CHANGED
@@ -51,8 +51,6 @@ module.exports = (app, config) => {
51
51
  });
52
52
  }
53
53
 
54
- client.connect();
55
-
56
54
  const store = new RedisStore({
57
55
  client: client,
58
56
  ttl: config.session.ttl,