@dwp/govuk-casa 8.0.0-alpha1 → 8.0.0

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.
Files changed (79) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +1 -1
  3. package/dist/assets/css/casa-ie8.css +1 -1
  4. package/dist/assets/css/casa.css +1 -1
  5. package/dist/casa.d.ts +2 -1
  6. package/dist/casa.js +3 -1
  7. package/dist/lib/CasaTemplateLoader.d.ts +11 -3
  8. package/dist/lib/CasaTemplateLoader.js +38 -2
  9. package/dist/lib/JourneyContext.d.ts +51 -8
  10. package/dist/lib/JourneyContext.js +73 -147
  11. package/dist/lib/MutableRouter.d.ts +19 -19
  12. package/dist/lib/MutableRouter.js +30 -23
  13. package/dist/lib/Plan.d.ts +41 -6
  14. package/dist/lib/Plan.js +84 -17
  15. package/dist/lib/ValidationError.d.ts +6 -2
  16. package/dist/lib/ValidationError.js +7 -0
  17. package/dist/lib/ValidatorFactory.d.ts +72 -13
  18. package/dist/lib/ValidatorFactory.js +33 -14
  19. package/dist/lib/configuration-ingestor.d.ts +262 -0
  20. package/dist/lib/configuration-ingestor.js +464 -0
  21. package/dist/lib/configure.d.ts +39 -154
  22. package/dist/lib/configure.js +35 -59
  23. package/dist/lib/dirname.cjs +1 -1
  24. package/dist/lib/dirname.d.cts +2 -0
  25. package/dist/lib/end-session.d.ts +4 -3
  26. package/dist/lib/end-session.js +30 -8
  27. package/dist/lib/field.d.ts +53 -55
  28. package/dist/lib/field.js +96 -54
  29. package/dist/lib/index.d.ts +14 -0
  30. package/dist/lib/index.js +54 -0
  31. package/dist/lib/logger.d.ts +2 -1
  32. package/dist/lib/logger.js +3 -4
  33. package/dist/lib/nunjucks-filters.d.ts +11 -11
  34. package/dist/lib/nunjucks-filters.js +22 -36
  35. package/dist/lib/nunjucks.d.ts +7 -6
  36. package/dist/lib/nunjucks.js +6 -6
  37. package/dist/lib/utils.d.ts +26 -0
  38. package/dist/lib/utils.js +70 -1
  39. package/dist/lib/validators/dateObject.js +1 -1
  40. package/dist/lib/validators/email.js +1 -1
  41. package/dist/lib/validators/inArray.js +1 -1
  42. package/dist/lib/validators/index.js +0 -22
  43. package/dist/lib/validators/postalAddressObject.js +7 -3
  44. package/dist/lib/validators/required.js +1 -1
  45. package/dist/lib/waypoint-url.d.ts +13 -7
  46. package/dist/lib/waypoint-url.js +13 -7
  47. package/dist/middleware/body-parser.d.ts +2 -1
  48. package/dist/middleware/body-parser.js +20 -11
  49. package/dist/middleware/csrf.d.ts +1 -1
  50. package/dist/middleware/csrf.js +2 -2
  51. package/dist/middleware/data.d.ts +1 -2
  52. package/dist/middleware/data.js +19 -15
  53. package/dist/middleware/dirname.cjs +1 -1
  54. package/dist/middleware/dirname.d.cts +2 -0
  55. package/dist/middleware/gather-fields.d.ts +3 -2
  56. package/dist/middleware/gather-fields.js +14 -7
  57. package/dist/middleware/i18n.d.ts +1 -1
  58. package/dist/middleware/i18n.js +16 -11
  59. package/dist/middleware/post.d.ts +3 -1
  60. package/dist/middleware/post.js +24 -9
  61. package/dist/middleware/pre.js +15 -2
  62. package/dist/middleware/progress-journey.js +15 -17
  63. package/dist/middleware/sanitise-fields.js +15 -10
  64. package/dist/middleware/session.d.ts +2 -1
  65. package/dist/middleware/session.js +65 -52
  66. package/dist/middleware/skip-waypoint.js +10 -7
  67. package/dist/middleware/steer-journey.d.ts +3 -2
  68. package/dist/middleware/steer-journey.js +26 -8
  69. package/dist/middleware/validate-fields.js +15 -21
  70. package/dist/mjs/esm-wrapper.js +18 -7
  71. package/dist/routes/ancillary.d.ts +8 -1
  72. package/dist/routes/ancillary.js +7 -1
  73. package/dist/routes/dirname.cjs +1 -1
  74. package/dist/routes/dirname.d.cts +2 -0
  75. package/dist/routes/journey.js +20 -24
  76. package/dist/routes/static.js +10 -9
  77. package/package.json +41 -22
  78. package/views/casa/errors/static.njk +11 -0
  79. package/views/casa/layouts/main.njk +3 -3
@@ -13,18 +13,48 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
13
13
  var _CasaTemplateLoader_instances, _CasaTemplateLoader_blockModifiers, _CasaTemplateLoader_applyBlockModifiers;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  const nunjucks_1 = require("nunjucks");
16
+ /**
17
+ * @typedef {import('nunjucks').FileSystemLoaderOptions} FileSystemLoaderOptions
18
+ */
19
+ const VALID_BLOCKS = [
20
+ 'beforeContent',
21
+ 'bodyEnd',
22
+ 'bodyStart',
23
+ 'casaPageTitle',
24
+ 'content',
25
+ 'footer',
26
+ 'head',
27
+ 'header',
28
+ 'headIcons',
29
+ 'journey_form',
30
+ 'main',
31
+ 'pageTitle',
32
+ 'skipLink',
33
+ ];
16
34
  /**
17
35
  * @callback BlockModifier
18
36
  * @param {string} templateName Path to the template being modified
19
37
  * @returns {string} The modified template source
20
38
  */
21
39
  class CasaTemplateLoader extends nunjucks_1.FileSystemLoader {
40
+ /**
41
+ * Constructor.
42
+ *
43
+ * @param {string[]} searchPaths Template directories
44
+ * @param {FileSystemLoaderOptions} opts Loader options
45
+ */
22
46
  constructor(searchPaths, opts) {
23
47
  super(searchPaths, opts);
24
48
  _CasaTemplateLoader_instances.add(this);
25
49
  _CasaTemplateLoader_blockModifiers.set(this, void 0);
26
50
  __classPrivateFieldSet(this, _CasaTemplateLoader_blockModifiers, [], "f");
27
51
  }
52
+ /**
53
+ * Extract the source from the given template file.
54
+ *
55
+ * @param {string} name Source file path
56
+ * @returns {string} Source contents of template
57
+ */
28
58
  getSource(name) {
29
59
  const source = super.getSource(name);
30
60
  return source ? __classPrivateFieldGet(this, _CasaTemplateLoader_instances, "m", _CasaTemplateLoader_applyBlockModifiers).call(this, name, source) : source;
@@ -35,9 +65,13 @@ class CasaTemplateLoader extends nunjucks_1.FileSystemLoader {
35
65
  * @param {string} block Block name, e.g. `bodyStart`
36
66
  * @param {BlockModifier} modifier Modifier function
37
67
  * @returns {void}
68
+ * @throws {Error} If provided with an unrecognised block
38
69
  */
39
70
  modifyBlock(block, modifier) {
40
- // TODO: Limit to only known block so the user can't do general string replacements
71
+ // Limit to only known block so the user can't do general string replacements
72
+ if (!VALID_BLOCKS.includes(block)) {
73
+ throw new Error(`Block "${String(block)}" is not a recognised template block.`);
74
+ }
41
75
  __classPrivateFieldGet(this, _CasaTemplateLoader_blockModifiers, "f").push({
42
76
  block,
43
77
  modifier,
@@ -46,10 +80,12 @@ class CasaTemplateLoader extends nunjucks_1.FileSystemLoader {
46
80
  }
47
81
  exports.default = CasaTemplateLoader;
48
82
  _CasaTemplateLoader_blockModifiers = new WeakMap(), _CasaTemplateLoader_instances = new WeakSet(), _CasaTemplateLoader_applyBlockModifiers = function _CasaTemplateLoader_applyBlockModifiers(name, source) {
49
- // TODO: This is open to abuse by plugin authors, e,g `{% block bodyStart %}{% endblock %} <script src="evil.js"></script>`. Problem, or no?
50
83
  for (let i = 0, l = __classPrivateFieldGet(this, _CasaTemplateLoader_blockModifiers, "f").length; i < l; i++) {
84
+ // ESLint disabled as `i` is an integer
85
+ /* eslint-disable-next-line security/detect-object-injection */
51
86
  const { block, modifier } = __classPrivateFieldGet(this, _CasaTemplateLoader_blockModifiers, "f")[i];
52
87
  if (source.src.indexOf(`block ${block}`) > -1) {
88
+ /* eslint-disable-next-line no-param-reassign */
53
89
  source.src = source.src.replace(`block ${block} %}`, `block ${block} %}${modifier(name)}`);
54
90
  }
55
91
  }
@@ -1,12 +1,37 @@
1
+ /**
2
+ * @typedef {import('./configuration-ingestor').Page} Page
3
+ */
4
+ /**
5
+ * @callback ContextEventHandler
6
+ * @param {JourneyContext} journeyContext Context including changes
7
+ * @param {JourneyContext} previousContext Context prior to changes
8
+ * @returns {void}
9
+ */
10
+ /**
11
+ * @typedef {object} ContextEvent
12
+ * @property {string} waypoint Waypoint to watch for changes
13
+ * @property {string} [field] Field to watch for changes
14
+ * @property {ContextEventHandler} handler Handler to invoke when change happens
15
+ */
16
+ export function validateObjectKey(key?: string): string;
1
17
  export default class JourneyContext {
2
18
  static DEFAULT_CONTEXT_ID: string;
3
19
  /**
4
20
  * Create a new JourneyContext using the plain object.
5
21
  *
6
22
  * @param {object} obj Object.
23
+ * @param {object} obj.data Data
24
+ * @param {object} obj.validation Validation state
25
+ * @param {object} obj.nav Navigation meta
26
+ * @param {object} obj.identity Identity meta
7
27
  * @returns {JourneyContext} Instance.
8
28
  */
9
- static fromObject({ data, validation, nav, identity, }?: object): JourneyContext;
29
+ static fromObject({ data, validation, nav, identity, }?: {
30
+ data: object;
31
+ validation: object;
32
+ nav: object;
33
+ identity: object;
34
+ }): JourneyContext;
10
35
  /**
11
36
  * Construct a new JourneyContext instance frmo another instance.
12
37
  *
@@ -124,11 +149,11 @@ export default class JourneyContext {
124
149
  /**
125
150
  * Get data context for a specific a specific page.
126
151
  *
127
- * @param {string | PageMeta} page Page waypoint ID, or Page object.
152
+ * @param {string | Page} page Page waypoint ID, or Page object.
128
153
  * @returns {object} Page data.
129
154
  * @throws {TypeError} When page is invalid.
130
155
  */
131
- getDataForPage(page: string | any): object;
156
+ getDataForPage(page: string | Page): object;
132
157
  getData(): object;
133
158
  /**
134
159
  * Overwrite the data context with a new object.
@@ -141,16 +166,16 @@ export default class JourneyContext {
141
166
  * Write field form data from a page HTML form, into the `data` model.
142
167
  *
143
168
  * By default this will store the data as-is, keyed against the page's
144
- * waypoint ID. However, when passing a `PageMeta` instance, its
169
+ * waypoint ID. However, when passing a `Page` instance, its
145
170
  * `fieldWriter()` method will be called to transform the provided formData
146
171
  * before storing in `data`
147
172
  *
148
- * @param {string | PageMeta} page Page waypoint ID, or PageMeta object
173
+ * @param {string | Page} page Page waypoint ID, or Page object
149
174
  * @param {object} webFormData Data to overwrite with
150
175
  * @returns {JourneyContext} Chain
151
176
  * @throws {TypeError} When page is invalid.
152
177
  */
153
- setDataForPage(page: string | any, webFormData: object): JourneyContext;
178
+ setDataForPage(page: string | Page, webFormData: object): JourneyContext;
154
179
  /**
155
180
  * Return validation errors for all pages.
156
181
  *
@@ -227,6 +252,7 @@ export default class JourneyContext {
227
252
  * force the user to revisit some waypoints.
228
253
  *
229
254
  * @param {string[]} waypoints Waypoints to be invalidated
255
+ * @returns {void}
230
256
  */
231
257
  invalidate(waypoints?: string[]): void;
232
258
  /**
@@ -239,11 +265,12 @@ export default class JourneyContext {
239
265
  * times during a request, so the context will be constantly changing.
240
266
  *
241
267
  * @param {ContextEvent[]} events Event listeners
268
+ * @returns {JourneyContext} Chain
242
269
  */
243
- addEventListeners(events: any[]): JourneyContext;
270
+ addEventListeners(events: ContextEvent[]): JourneyContext;
244
271
  applyEventListeners({ event }: {
245
272
  event: any;
246
- }): JourneyContext | undefined;
273
+ }): JourneyContext;
247
274
  /**
248
275
  * Convenience method to determine if this is the default context.
249
276
  *
@@ -252,4 +279,20 @@ export default class JourneyContext {
252
279
  isDefault(): boolean;
253
280
  #private;
254
281
  }
282
+ export type Page = import('./configuration-ingestor').Page;
283
+ export type ContextEventHandler = (journeyContext: JourneyContext, previousContext: JourneyContext) => void;
284
+ export type ContextEvent = {
285
+ /**
286
+ * Waypoint to watch for changes
287
+ */
288
+ waypoint: string;
289
+ /**
290
+ * Field to watch for changes
291
+ */
292
+ field?: string | undefined;
293
+ /**
294
+ * Handler to invoke when change happens
295
+ */
296
+ handler: ContextEventHandler;
297
+ };
255
298
  import ValidationError from "./ValidationError.js";
@@ -26,6 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  };
27
27
  var _JourneyContext_data, _JourneyContext_validation, _JourneyContext_nav, _JourneyContext_identity, _JourneyContext_eventListeners, _JourneyContext_eventListenerPreState;
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.validateObjectKey = void 0;
29
30
  /**
30
31
  * Represents the state of a user's journey through the Plan. It contains
31
32
  * information about:
@@ -35,37 +36,34 @@ Object.defineProperty(exports, "__esModule", { value: true });
35
36
  * - Navigation information about how the user got where they are.
36
37
  */
37
38
  const uuid_1 = require("uuid");
39
+ const lodash_1 = __importDefault(require("lodash"));
38
40
  const ValidationError_js_1 = __importDefault(require("./ValidationError.js"));
39
41
  const logger_js_1 = __importDefault(require("./logger.js"));
40
- const lodash_1 = __importDefault(require("lodash"));
41
- const { cloneDeep, isPlainObject, isObject, has, isEqual } = lodash_1.default; // CommonJS
42
- const log = (0, logger_js_1.default)('class:journey-context');
43
- // const buildBreadcrumb = (mountUrl, done = Object.create(null)) => {
44
- // console.log('buildnav', mountUrl, waypoint, source, navUrls, done);
45
- // // 1. there are url:// waypoint in the mount list - [a, b, url://other/, d] and [x, y, z] -> [a, b, x, y z, d]
46
- // // 2. there are no url:// in there - [a, b, c, d]
47
- // // 3. other mount lists have url:///mountUrl/ in them - [a, b, c, d] and [x, y, url://abcd/, z] -> [x, y, a, b, c, d, z]
48
- // // 4. in and out - [a, b, url://c/, d] and [x, z, url://a/] -> [a, b, x, z, d] <-- need recursion protection here
49
- // // [x, z, a, b, x, z, d] ... changes depend on order called ://///
50
- // if (done[mountUrl]) {
51
- // return done[mountUrl];
52
- // }
53
- // done[mountUrl] = [];
54
- // const mountCrumbs = [];
55
- // for (let mount in navUrls) {
56
- // mountCrumbs =
57
- // }
58
- // const breadcrumb = source.map(wp => {
59
- // if (Plan.isExitNode(wp)) {
60
- // } else {
61
- // return waypointUrl({ mountUrl, waypoint: wp });
62
- // }
63
- // });
64
- // const waypointIndex = source?.mountUrl?.indexOf(waypoint);
65
- // if (waypointIndex == -1) {
66
- // return [];
67
- // }
68
- // };
42
+ const { cloneDeep, isPlainObject, isObject, has, isEqual, } = lodash_1.default; // CommonJS
43
+ const log = (0, logger_js_1.default)('lib:journey-context');
44
+ /**
45
+ * @typedef {import('./configuration-ingestor').Page} Page
46
+ */
47
+ /**
48
+ * @callback ContextEventHandler
49
+ * @param {JourneyContext} journeyContext Context including changes
50
+ * @param {JourneyContext} previousContext Context prior to changes
51
+ * @returns {void}
52
+ */
53
+ /**
54
+ * @typedef {object} ContextEvent
55
+ * @property {string} waypoint Waypoint to watch for changes
56
+ * @property {string} [field] Field to watch for changes
57
+ * @property {ContextEventHandler} handler Handler to invoke when change happens
58
+ */
59
+ function validateObjectKey(key = '') {
60
+ const keyLower = String.prototype.toLowerCase.call(key);
61
+ if (keyLower === 'prototype' || keyLower === '__proto__' || keyLower === 'constructor') {
62
+ throw new SyntaxError(`Invalid object key used, ${key}`);
63
+ }
64
+ return String(key);
65
+ }
66
+ exports.validateObjectKey = validateObjectKey;
69
67
  class JourneyContext {
70
68
  /**
71
69
  * Constructor.
@@ -122,6 +120,10 @@ class JourneyContext {
122
120
  * Create a new JourneyContext using the plain object.
123
121
  *
124
122
  * @param {object} obj Object.
123
+ * @param {object} obj.data Data
124
+ * @param {object} obj.validation Validation state
125
+ * @param {object} obj.nav Navigation meta
126
+ * @param {object} obj.identity Identity meta
125
127
  * @returns {JourneyContext} Instance.
126
128
  */
127
129
  static fromObject({ data = Object.create(null), validation = Object.create(null), nav = Object.create(null), identity = Object.create(null), } = {}) {
@@ -145,20 +147,18 @@ class JourneyContext {
145
147
  /**
146
148
  * Get data context for a specific a specific page.
147
149
  *
148
- * @param {string | PageMeta} page Page waypoint ID, or Page object.
150
+ * @param {string | Page} page Page waypoint ID, or Page object.
149
151
  * @returns {object} Page data.
150
152
  * @throws {TypeError} When page is invalid.
151
153
  */
152
154
  getDataForPage(page) {
153
155
  if (typeof page === 'string') {
154
- return __classPrivateFieldGet(this, _JourneyContext_data, "f")[page];
156
+ return __classPrivateFieldGet(this, _JourneyContext_data, "f")[validateObjectKey(page)];
155
157
  }
156
- else if (isPlainObject(page)) {
157
- return __classPrivateFieldGet(this, _JourneyContext_data, "f")[page.waypoint];
158
- }
159
- else {
160
- throw new TypeError(`Page must be a string or Page object. Got ${typeof page}`);
158
+ if (isPlainObject(page)) {
159
+ return __classPrivateFieldGet(this, _JourneyContext_data, "f")[validateObjectKey(page.waypoint)];
161
160
  }
161
+ throw new TypeError(`Page must be a string or Page object. Got ${typeof page}`);
162
162
  }
163
163
  getData() {
164
164
  return __classPrivateFieldGet(this, _JourneyContext_data, "f");
@@ -177,21 +177,21 @@ class JourneyContext {
177
177
  * Write field form data from a page HTML form, into the `data` model.
178
178
  *
179
179
  * By default this will store the data as-is, keyed against the page's
180
- * waypoint ID. However, when passing a `PageMeta` instance, its
180
+ * waypoint ID. However, when passing a `Page` instance, its
181
181
  * `fieldWriter()` method will be called to transform the provided formData
182
182
  * before storing in `data`
183
183
  *
184
- * @param {string | PageMeta} page Page waypoint ID, or PageMeta object
184
+ * @param {string | Page} page Page waypoint ID, or Page object
185
185
  * @param {object} webFormData Data to overwrite with
186
186
  * @returns {JourneyContext} Chain
187
187
  * @throws {TypeError} When page is invalid.
188
188
  */
189
189
  setDataForPage(page, webFormData) {
190
190
  if (typeof page === 'string') {
191
- __classPrivateFieldGet(this, _JourneyContext_data, "f")[page] = webFormData;
191
+ __classPrivateFieldGet(this, _JourneyContext_data, "f")[validateObjectKey(page)] = webFormData;
192
192
  }
193
193
  else if (isPlainObject(page)) {
194
- __classPrivateFieldGet(this, _JourneyContext_data, "f")[page.waypoint] = webFormData;
194
+ __classPrivateFieldGet(this, _JourneyContext_data, "f")[validateObjectKey(page.waypoint)] = webFormData;
195
195
  }
196
196
  else {
197
197
  throw new TypeError(`Page must be a string or Page object. Got ${typeof page}`);
@@ -229,7 +229,7 @@ class JourneyContext {
229
229
  * @returns {JourneyContext} Chain.
230
230
  */
231
231
  clearValidationErrorsForPage(pageId) {
232
- __classPrivateFieldGet(this, _JourneyContext_validation, "f")[pageId] = null;
232
+ __classPrivateFieldGet(this, _JourneyContext_validation, "f")[validateObjectKey(pageId)] = null;
233
233
  return this;
234
234
  }
235
235
  /**
@@ -249,7 +249,7 @@ class JourneyContext {
249
249
  throw new SyntaxError('Field errors must be a ValidationError');
250
250
  }
251
251
  });
252
- __classPrivateFieldGet(this, _JourneyContext_validation, "f")[pageId] = errors;
252
+ __classPrivateFieldGet(this, _JourneyContext_validation, "f")[validateObjectKey(pageId)] = errors;
253
253
  return this;
254
254
  }
255
255
  /**
@@ -260,17 +260,21 @@ class JourneyContext {
260
260
  * @returns {ValidationError[]} An array of errors
261
261
  */
262
262
  getValidationErrorsForPage(pageId) {
263
- return __classPrivateFieldGet(this, _JourneyContext_validation, "f")[pageId] || [];
263
+ var _a;
264
+ return (_a = __classPrivateFieldGet(this, _JourneyContext_validation, "f")[validateObjectKey(pageId)]) !== null && _a !== void 0 ? _a : [];
264
265
  }
265
266
  getValidationErrorsForPageByField(pageId) {
266
267
  const errors = this.getValidationErrorsForPage(pageId);
267
268
  const obj = Object.create(null);
269
+ // ESLint disabled as `i` is an integer
270
+ /* eslint-disable security/detect-object-injection */
268
271
  for (let i = 0, l = errors.length; i < l; i++) {
269
272
  if (!obj[errors[i].field]) {
270
273
  obj[errors[i].field] = [];
271
274
  }
272
275
  obj[errors[i].field].push(errors[i]);
273
276
  }
277
+ /* eslint-enable security/detect-object-injection */
274
278
  return obj;
275
279
  }
276
280
  /**
@@ -282,7 +286,7 @@ class JourneyContext {
282
286
  */
283
287
  hasValidationErrorsForPage(pageId) {
284
288
  var _a, _b;
285
- return ((_b = (_a = __classPrivateFieldGet(this, _JourneyContext_validation, "f")) === null || _a === void 0 ? void 0 : _a[pageId]) === null || _b === void 0 ? void 0 : _b.length) > 0;
289
+ return ((_b = (_a = __classPrivateFieldGet(this, _JourneyContext_validation, "f")) === null || _a === void 0 ? void 0 : _a[validateObjectKey(pageId)]) === null || _b === void 0 ? void 0 : _b.length) > 0;
286
290
  }
287
291
  /**
288
292
  * Set language of the context.
@@ -294,101 +298,6 @@ class JourneyContext {
294
298
  __classPrivateFieldGet(this, _JourneyContext_nav, "f").language = language;
295
299
  return this;
296
300
  }
297
- // /**
298
- // * Store the list of visited waypoints against the given mountUrl. This will
299
- // * help build a picture of the user's navigation across multiple sub-apps.
300
- // *
301
- // * @param {string} mountUrl Mount URL prefixing all given waypoints
302
- // * @param {string[]} waypoints Waypoints visited (in this order)
303
- // * @param {string} [routeName=next] Name of the route followed to get this list of waypoints
304
- // * @returns
305
- // */
306
- // setNavigationUrls(mountUrl, waypoints = [], routeName = 'next') {
307
- // this.#nav.urls = this.#nav?.urls ?? Object.create(null);
308
- // this.#nav.urls[mountUrl] = this.#nav.urls[mountUrl] ?? Object.create(null);
309
- // this.#nav.urls[mountUrl][routeName] = waypoints;
310
- // return this;
311
- // }
312
- // /**
313
- // * Combines all nav information to build a linear list of URLs that the
314
- // * user has visited from the point specificed.
315
- // *
316
- // * @param {string} mountUrl Mount URL
317
- // * @param {string} [routeName=next] Route name (optional, default next)
318
- // * @returns {string[]} List of URL, all prefixed with mountUrl
319
- // */
320
- // buildNavigationBreadcrumb(mountUrl, routeName = 'next') {
321
- // // THIS IS WHERE I'M AT. Need to properly sort this so it injects waypoints into other mount points correctly.
322
- // // There is a big dependency on when this is called - the mountUrl will be different every time,, which changes the point from which we begin trying to build this.
323
- // let toUrl = (mntUrl) => (u) => {
324
- // if (routeName === 'prev' && Plan.isExitNode(u)) {
325
- // return waypointUrl({ mountUrl: u.substr(6), waypoint: `url://${mntUrl}`, routeName });
326
- // } else {
327
- // return waypointUrl({ mountUrl: mntUrl, waypoint: u, routeName });
328
- // }
329
- // };
330
- // let nav = (this.#nav?.urls?.[mountUrl]?.[routeName] ?? []).map(toUrl(mountUrl));
331
- // // No URLs? There might be some references to this mountUrl in the other
332
- // // mount points
333
- // const mountUrls = Object.keys(this.#nav?.urls ?? Object.create(null));
334
- // for (let i = 0, l = mountUrls.length; i < l; i ++) {
335
- // const murl = mountUrls[i];
336
- // const index = this.#nav.urls[murl][routeName].indexOf(`url://${mountUrl}`);
337
- // if (index > -1) {
338
- // console.log('before', nav)
339
- // console.log('insert', this.#nav.urls[murl][routeName].slice(index).map(toUrl(murl)))
340
- // // nav = [
341
- // // ...nav,
342
- // // ...this.#nav.urls[murl][routeName].slice(index).map(toUrl(murl)),
343
- // // ];
344
- // nav = [
345
- // ...this.#nav.urls[murl][routeName].slice(index).map(toUrl(murl)),
346
- // ...nav,
347
- // ];
348
- // }
349
- // }
350
- // return nav;
351
- // // return this.#nav.urls[mountUrl].map(wp => waypointUrl({ mountUrl, waypoint: wp, routeName }));
352
- // // // const breadcrumb = buildBreadcrumb({ mountUrl, waypoint }, this.#nav.urls[mountUrl] ?? [], this.#nav.urls);
353
- // // let nav = [];
354
- // // // First, build URLs for the specified mount path
355
- // // nav = this.#nav.urls[mountUrl].map(wp => waypointUrl({ mountUrl, waypoint: wp, routeName }));
356
- // // // Second, find any refs to mountUrl in the other mount paths
357
- // // for (let mp in this.#nav.urls) {
358
- // // if (mp === mountUrl) continue;
359
- // // this.buildNavigationBreadcrumb(mp, routeName);
360
- // // }
361
- // // return breadcrum;
362
- // // // TODO: The solution below does not take account plans that have multiple references to sub-apps, such as:
363
- // // // a -> b -> url:///c/ -> d -> e -> url:///f/
364
- // // // We'll need a recursive function to explode all url references and inject waypoints
365
- // // //
366
- // // // Also, once we've added the waypoint for a mountUrl, we want to check elsewhere for url://// to _that_ mountUrl, and so on. At some point we need to know where to stop. Be careful of recursion
367
- // // let nav = [];
368
- // // // First, get all the waypoints listed up to the requested one under the given mountUrl
369
- // // let mountUrls = Object.keys(this.#nav?.urls ?? Object.create(null));
370
- // // if (mountUrls.indexOf(mountUrl) > -1 && this.#nav.urls[mountUrl].indexOf(waypoint) > -1) {
371
- // // nav = [
372
- // // ...nav,
373
- // // ...this.#nav.urls[mountUrl].slice(0, this.#nav.urls[mountUrl].indexOf(waypoint) + 1).map(w => waypointUrl({ mountUrl, waypoint: w })),
374
- // // ];
375
- // // }
376
- // // // Next, look for any sub-app references to the requested mountUrl elsewhere
377
- // // // in the nav context, and add all the waypoints listed there.
378
- // // const url = `url://${mountUrl}`;
379
- // // for (let i = 0, l = mountUrls.length; i < l; i ++) {
380
- // // const index = this.#nav.urls[mountUrls[i]].indexOf(url);
381
- // // if (index > -1) {
382
- // // nav = [
383
- // // ...this.#nav.urls[mountUrls[i]].slice(0, index).map(w => waypointUrl({ mountUrl: mountUrls[i], waypoint: w })),
384
- // // ...nav,
385
- // // ...this.#nav.urls[mountUrls[i]].slice(index).map(w => waypointUrl({ mountUrl: mountUrls[i], waypoint: w })), // TODO this will fail if url:// waypoints are there - need that recurive function building
386
- // // ];
387
- // // break;
388
- // // }
389
- // // }
390
- // // return nav;
391
- // }
392
301
  /**
393
302
  * Convenience function to test if page is valid.
394
303
  *
@@ -396,7 +305,7 @@ class JourneyContext {
396
305
  * @returns {boolean} True if the page is valid.
397
306
  */
398
307
  isPageValid(pageId) {
399
- return __classPrivateFieldGet(this, _JourneyContext_validation, "f")[pageId] === null;
308
+ return __classPrivateFieldGet(this, _JourneyContext_validation, "f")[validateObjectKey(pageId)] === null;
400
309
  }
401
310
  /**
402
311
  * Remove information about these waypoints.
@@ -406,11 +315,14 @@ class JourneyContext {
406
315
  purge(waypoints = []) {
407
316
  const newData = Object.create(null);
408
317
  const newValidation = Object.create(null);
409
- const toKeep = Object.keys(this.data).filter(w => !waypoints.includes(w));
318
+ const toKeep = Object.keys(this.data).filter((w) => !waypoints.includes(w));
319
+ // ESLint disabled as `i` is an integer
320
+ /* eslint-disable security/detect-object-injection */
410
321
  for (let i = 0, l = toKeep.length; i < l; i++) {
411
322
  newData[toKeep[i]] = __classPrivateFieldGet(this, _JourneyContext_data, "f")[toKeep[i]];
412
323
  newValidation[toKeep[i]] = __classPrivateFieldGet(this, _JourneyContext_validation, "f")[toKeep[i]];
413
324
  }
325
+ /* eslint-enable security/detect-object-injection */
414
326
  __classPrivateFieldSet(this, _JourneyContext_data, Object.assign({}, newData), "f");
415
327
  __classPrivateFieldSet(this, _JourneyContext_validation, Object.assign({}, newValidation), "f");
416
328
  }
@@ -419,9 +331,12 @@ class JourneyContext {
419
331
  * force the user to revisit some waypoints.
420
332
  *
421
333
  * @param {string[]} waypoints Waypoints to be invalidated
334
+ * @returns {void}
422
335
  */
423
336
  invalidate(waypoints = []) {
424
337
  for (let i = 0, l = waypoints.length; i < l; i++) {
338
+ // ESLint disabled as `i` is an integer
339
+ /* eslint-disable-next-line security/detect-object-injection */
425
340
  this.removeValidationStateForPage(waypoints[i]);
426
341
  }
427
342
  }
@@ -435,6 +350,7 @@ class JourneyContext {
435
350
  * times during a request, so the context will be constantly changing.
436
351
  *
437
352
  * @param {ContextEvent[]} events Event listeners
353
+ * @returns {JourneyContext} Chain
438
354
  */
439
355
  addEventListeners(events) {
440
356
  __classPrivateFieldSet(this, _JourneyContext_eventListeners, events, "f");
@@ -442,33 +358,39 @@ class JourneyContext {
442
358
  return this;
443
359
  }
444
360
  applyEventListeners({ event }) {
445
- var _a, _b, _c, _d, _e, _f;
361
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
446
362
  if (!__classPrivateFieldGet(this, _JourneyContext_eventListeners, "f").length) {
447
363
  return this;
448
364
  }
449
365
  const previousContext = JourneyContext.fromObject(__classPrivateFieldGet(this, _JourneyContext_eventListenerPreState, "f"));
450
- const listeners = __classPrivateFieldGet(this, _JourneyContext_eventListeners, "f").filter(l => l.event === event);
366
+ const listeners = __classPrivateFieldGet(this, _JourneyContext_eventListeners, "f").filter((l) => l.event === event);
367
+ // ESLint disabled as `listeners[i]` uses an integer key, and the other keys
368
+ // are derived from the list of `listeners`, which are not manipulated at
369
+ // runtime (only set by dev in code).
370
+ /* eslint-disable security/detect-object-injection */
451
371
  for (let i = 0, l = listeners.length; i < l; i++) {
452
372
  const { waypoint, field, handler } = listeners[i];
453
373
  let logMessage;
454
374
  let runHandler = false;
455
375
  if (!waypoint && !field) {
456
- logMessage = `Calling generic event handler`;
376
+ logMessage = 'Calling generic event handler';
457
377
  runHandler = true;
458
378
  }
459
379
  else if (waypoint && !field) {
460
380
  logMessage = `Calling waypoint-specific event handler on "${waypoint}"`;
461
- runHandler = !isEqual((_a = this.data) === null || _a === void 0 ? void 0 : _a[waypoint], (_b = previousContext.data) === null || _b === void 0 ? void 0 : _b[waypoint]);
381
+ runHandler = ((_a = previousContext.data) === null || _a === void 0 ? void 0 : _a[waypoint]) !== undefined && !isEqual((_b = this.data) === null || _b === void 0 ? void 0 : _b[waypoint], (_c = previousContext.data) === null || _c === void 0 ? void 0 : _c[waypoint]);
462
382
  }
463
383
  else if (waypoint && field) {
464
384
  logMessage = `Calling field-specific event handler on "${waypoint} : ${field}"`;
465
- runHandler = !isEqual((_d = (_c = this.data) === null || _c === void 0 ? void 0 : _c[waypoint]) === null || _d === void 0 ? void 0 : _d[field], (_f = (_e = previousContext.data) === null || _e === void 0 ? void 0 : _e[waypoint]) === null || _f === void 0 ? void 0 : _f[field]);
385
+ runHandler = ((_e = (_d = previousContext.data) === null || _d === void 0 ? void 0 : _d[waypoint]) === null || _e === void 0 ? void 0 : _e[field]) !== undefined && !isEqual((_g = (_f = this.data) === null || _f === void 0 ? void 0 : _f[waypoint]) === null || _g === void 0 ? void 0 : _g[field], (_j = (_h = previousContext.data) === null || _h === void 0 ? void 0 : _h[waypoint]) === null || _j === void 0 ? void 0 : _j[field]);
466
386
  }
467
387
  if (runHandler) {
468
388
  log.trace(logMessage);
469
389
  handler({ journeyContext: this, previousContext });
470
390
  }
471
391
  }
392
+ /* eslint-enable security/detect-object-injection */
393
+ return this;
472
394
  }
473
395
  /* ----------------------------------------------- session context handling */
474
396
  /**
@@ -503,6 +425,7 @@ class JourneyContext {
503
425
  static initContextStore(session) {
504
426
  if (!has(session, 'journeyContextList')) {
505
427
  log.trace('Initialising session with a default journey context list');
428
+ /* eslint-disable-next-line no-param-reassign */
506
429
  session.journeyContextList = Object.create(null);
507
430
  const defaultContext = new JourneyContext();
508
431
  defaultContext.identity.id = JourneyContext.DEFAULT_CONTEXT_ID;
@@ -540,6 +463,8 @@ class JourneyContext {
540
463
  */
541
464
  static getContextById(session, id) {
542
465
  if (has(session === null || session === void 0 ? void 0 : session.journeyContextList, id)) {
466
+ // ESLint disabled as `id` has been verified as an "own" property
467
+ /* eslint-disable-next-line security/detect-object-injection */
543
468
  return JourneyContext.fromObject(session.journeyContextList[id]);
544
469
  }
545
470
  return undefined;
@@ -633,7 +558,8 @@ class JourneyContext {
633
558
  }
634
559
  static removeContextById(session, id) {
635
560
  if (session && has(session.journeyContextList, id)) {
636
- /* eslint-disable-next-line no-param-reassign */
561
+ // ESLint disabled as `id` has been verified as an "own" property
562
+ /* eslint-disable-next-line security/detect-object-injection, no-param-reassign */
637
563
  delete session.journeyContextList[id];
638
564
  }
639
565
  }