@dwp/govuk-casa 8.0.0-alpha2 → 8.0.1
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.
- package/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/dist/assets/css/casa-ie8.css +1 -1
- package/dist/assets/css/casa.css +1 -1
- package/dist/casa.d.ts +2 -1
- package/dist/casa.js +3 -1
- package/dist/lib/CasaTemplateLoader.d.ts +4 -2
- package/dist/lib/CasaTemplateLoader.js +26 -4
- package/dist/lib/JourneyContext.d.ts +38 -6
- package/dist/lib/JourneyContext.js +58 -17
- package/dist/lib/MutableRouter.js +6 -2
- package/dist/lib/Plan.d.ts +37 -4
- package/dist/lib/Plan.js +75 -11
- package/dist/lib/ValidationError.d.ts +6 -2
- package/dist/lib/ValidationError.js +7 -0
- package/dist/lib/ValidatorFactory.d.ts +72 -19
- package/dist/lib/ValidatorFactory.js +33 -20
- package/dist/lib/configuration-ingestor.d.ts +262 -0
- package/dist/lib/configuration-ingestor.js +464 -0
- package/dist/lib/configure.d.ts +26 -140
- package/dist/lib/configure.js +17 -45
- package/dist/lib/dirname.cjs +1 -1
- package/dist/lib/dirname.d.cts +2 -0
- package/dist/lib/end-session.d.ts +2 -1
- package/dist/lib/end-session.js +27 -7
- package/dist/lib/field.d.ts +39 -46
- package/dist/lib/field.js +75 -36
- package/dist/lib/index.d.ts +14 -0
- package/dist/lib/index.js +54 -0
- package/dist/lib/logger.d.ts +2 -1
- package/dist/lib/logger.js +3 -4
- package/dist/lib/nunjucks-filters.js +8 -0
- package/dist/lib/utils.d.ts +18 -2
- package/dist/lib/utils.js +56 -2
- package/dist/lib/validators/inArray.js +1 -1
- package/dist/lib/validators/index.js +0 -22
- package/dist/lib/validators/postalAddressObject.js +6 -2
- package/dist/lib/waypoint-url.d.ts +2 -1
- package/dist/lib/waypoint-url.js +3 -0
- package/dist/middleware/body-parser.d.ts +1 -0
- package/dist/middleware/body-parser.js +18 -9
- package/dist/middleware/data.d.ts +1 -2
- package/dist/middleware/data.js +9 -9
- package/dist/middleware/dirname.cjs +1 -1
- package/dist/middleware/dirname.d.cts +2 -0
- package/dist/middleware/gather-fields.d.ts +2 -1
- package/dist/middleware/gather-fields.js +6 -5
- package/dist/middleware/i18n.js +5 -1
- package/dist/middleware/post.js +6 -6
- package/dist/middleware/progress-journey.js +1 -1
- package/dist/middleware/sanitise-fields.js +9 -9
- package/dist/middleware/session.d.ts +2 -1
- package/dist/middleware/session.js +62 -55
- package/dist/middleware/skip-waypoint.js +2 -2
- package/dist/middleware/steer-journey.d.ts +2 -1
- package/dist/middleware/steer-journey.js +3 -0
- package/dist/middleware/validate-fields.js +7 -6
- package/dist/mjs/esm-wrapper.js +10 -0
- package/dist/routes/ancillary.d.ts +8 -1
- package/dist/routes/ancillary.js +7 -2
- package/dist/routes/dirname.cjs +1 -1
- package/dist/routes/dirname.d.cts +2 -0
- package/dist/routes/journey.js +14 -8
- package/dist/routes/static.js +4 -3
- package/package.json +42 -25
- package/views/casa/layouts/main.njk +2 -2
|
@@ -13,6 +13,24 @@ 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
|
|
@@ -23,7 +41,7 @@ class CasaTemplateLoader extends nunjucks_1.FileSystemLoader {
|
|
|
23
41
|
* Constructor.
|
|
24
42
|
*
|
|
25
43
|
* @param {string[]} searchPaths Template directories
|
|
26
|
-
* @param {
|
|
44
|
+
* @param {FileSystemLoaderOptions} opts Loader options
|
|
27
45
|
*/
|
|
28
46
|
constructor(searchPaths, opts) {
|
|
29
47
|
super(searchPaths, opts);
|
|
@@ -47,9 +65,13 @@ class CasaTemplateLoader extends nunjucks_1.FileSystemLoader {
|
|
|
47
65
|
* @param {string} block Block name, e.g. `bodyStart`
|
|
48
66
|
* @param {BlockModifier} modifier Modifier function
|
|
49
67
|
* @returns {void}
|
|
68
|
+
* @throws {Error} If provided with an unrecognised block
|
|
50
69
|
*/
|
|
51
70
|
modifyBlock(block, modifier) {
|
|
52
|
-
//
|
|
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
|
+
}
|
|
53
75
|
__classPrivateFieldGet(this, _CasaTemplateLoader_blockModifiers, "f").push({
|
|
54
76
|
block,
|
|
55
77
|
modifier,
|
|
@@ -58,9 +80,9 @@ class CasaTemplateLoader extends nunjucks_1.FileSystemLoader {
|
|
|
58
80
|
}
|
|
59
81
|
exports.default = CasaTemplateLoader;
|
|
60
82
|
_CasaTemplateLoader_blockModifiers = new WeakMap(), _CasaTemplateLoader_instances = new WeakSet(), _CasaTemplateLoader_applyBlockModifiers = function _CasaTemplateLoader_applyBlockModifiers(name, source) {
|
|
61
|
-
// TODO: This is open to abuse by plugin authors, e,g
|
|
62
|
-
// `{% block bodyStart %}{% endblock %} <script src="evil.js"></script>`. Problem, or no?
|
|
63
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 */
|
|
64
86
|
const { block, modifier } = __classPrivateFieldGet(this, _CasaTemplateLoader_blockModifiers, "f")[i];
|
|
65
87
|
if (source.src.indexOf(`block ${block}`) > -1) {
|
|
66
88
|
/* eslint-disable-next-line no-param-reassign */
|
|
@@ -1,3 +1,19 @@
|
|
|
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
|
/**
|
|
@@ -133,11 +149,11 @@ export default class JourneyContext {
|
|
|
133
149
|
/**
|
|
134
150
|
* Get data context for a specific a specific page.
|
|
135
151
|
*
|
|
136
|
-
* @param {string |
|
|
152
|
+
* @param {string | Page} page Page waypoint ID, or Page object.
|
|
137
153
|
* @returns {object} Page data.
|
|
138
154
|
* @throws {TypeError} When page is invalid.
|
|
139
155
|
*/
|
|
140
|
-
getDataForPage(page: string |
|
|
156
|
+
getDataForPage(page: string | Page): object;
|
|
141
157
|
getData(): object;
|
|
142
158
|
/**
|
|
143
159
|
* Overwrite the data context with a new object.
|
|
@@ -150,16 +166,16 @@ export default class JourneyContext {
|
|
|
150
166
|
* Write field form data from a page HTML form, into the `data` model.
|
|
151
167
|
*
|
|
152
168
|
* By default this will store the data as-is, keyed against the page's
|
|
153
|
-
* waypoint ID. However, when passing a `
|
|
169
|
+
* waypoint ID. However, when passing a `Page` instance, its
|
|
154
170
|
* `fieldWriter()` method will be called to transform the provided formData
|
|
155
171
|
* before storing in `data`
|
|
156
172
|
*
|
|
157
|
-
* @param {string |
|
|
173
|
+
* @param {string | Page} page Page waypoint ID, or Page object
|
|
158
174
|
* @param {object} webFormData Data to overwrite with
|
|
159
175
|
* @returns {JourneyContext} Chain
|
|
160
176
|
* @throws {TypeError} When page is invalid.
|
|
161
177
|
*/
|
|
162
|
-
setDataForPage(page: string |
|
|
178
|
+
setDataForPage(page: string | Page, webFormData: object): JourneyContext;
|
|
163
179
|
/**
|
|
164
180
|
* Return validation errors for all pages.
|
|
165
181
|
*
|
|
@@ -251,7 +267,7 @@ export default class JourneyContext {
|
|
|
251
267
|
* @param {ContextEvent[]} events Event listeners
|
|
252
268
|
* @returns {JourneyContext} Chain
|
|
253
269
|
*/
|
|
254
|
-
addEventListeners(events:
|
|
270
|
+
addEventListeners(events: ContextEvent[]): JourneyContext;
|
|
255
271
|
applyEventListeners({ event }: {
|
|
256
272
|
event: any;
|
|
257
273
|
}): JourneyContext;
|
|
@@ -263,4 +279,20 @@ export default class JourneyContext {
|
|
|
263
279
|
isDefault(): boolean;
|
|
264
280
|
#private;
|
|
265
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
|
+
};
|
|
266
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:
|
|
@@ -39,7 +40,30 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
39
40
|
const ValidationError_js_1 = __importDefault(require("./ValidationError.js"));
|
|
40
41
|
const logger_js_1 = __importDefault(require("./logger.js"));
|
|
41
42
|
const { cloneDeep, isPlainObject, isObject, has, isEqual, } = lodash_1.default; // CommonJS
|
|
42
|
-
const log = (0, logger_js_1.default)('
|
|
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;
|
|
43
67
|
class JourneyContext {
|
|
44
68
|
/**
|
|
45
69
|
* Constructor.
|
|
@@ -123,16 +147,16 @@ class JourneyContext {
|
|
|
123
147
|
/**
|
|
124
148
|
* Get data context for a specific a specific page.
|
|
125
149
|
*
|
|
126
|
-
* @param {string |
|
|
150
|
+
* @param {string | Page} page Page waypoint ID, or Page object.
|
|
127
151
|
* @returns {object} Page data.
|
|
128
152
|
* @throws {TypeError} When page is invalid.
|
|
129
153
|
*/
|
|
130
154
|
getDataForPage(page) {
|
|
131
155
|
if (typeof page === 'string') {
|
|
132
|
-
return __classPrivateFieldGet(this, _JourneyContext_data, "f")[page];
|
|
156
|
+
return __classPrivateFieldGet(this, _JourneyContext_data, "f")[validateObjectKey(page)];
|
|
133
157
|
}
|
|
134
158
|
if (isPlainObject(page)) {
|
|
135
|
-
return __classPrivateFieldGet(this, _JourneyContext_data, "f")[page.waypoint];
|
|
159
|
+
return __classPrivateFieldGet(this, _JourneyContext_data, "f")[validateObjectKey(page.waypoint)];
|
|
136
160
|
}
|
|
137
161
|
throw new TypeError(`Page must be a string or Page object. Got ${typeof page}`);
|
|
138
162
|
}
|
|
@@ -153,21 +177,21 @@ class JourneyContext {
|
|
|
153
177
|
* Write field form data from a page HTML form, into the `data` model.
|
|
154
178
|
*
|
|
155
179
|
* By default this will store the data as-is, keyed against the page's
|
|
156
|
-
* waypoint ID. However, when passing a `
|
|
180
|
+
* waypoint ID. However, when passing a `Page` instance, its
|
|
157
181
|
* `fieldWriter()` method will be called to transform the provided formData
|
|
158
182
|
* before storing in `data`
|
|
159
183
|
*
|
|
160
|
-
* @param {string |
|
|
184
|
+
* @param {string | Page} page Page waypoint ID, or Page object
|
|
161
185
|
* @param {object} webFormData Data to overwrite with
|
|
162
186
|
* @returns {JourneyContext} Chain
|
|
163
187
|
* @throws {TypeError} When page is invalid.
|
|
164
188
|
*/
|
|
165
189
|
setDataForPage(page, webFormData) {
|
|
166
190
|
if (typeof page === 'string') {
|
|
167
|
-
__classPrivateFieldGet(this, _JourneyContext_data, "f")[page] = webFormData;
|
|
191
|
+
__classPrivateFieldGet(this, _JourneyContext_data, "f")[validateObjectKey(page)] = webFormData;
|
|
168
192
|
}
|
|
169
193
|
else if (isPlainObject(page)) {
|
|
170
|
-
__classPrivateFieldGet(this, _JourneyContext_data, "f")[page.waypoint] = webFormData;
|
|
194
|
+
__classPrivateFieldGet(this, _JourneyContext_data, "f")[validateObjectKey(page.waypoint)] = webFormData;
|
|
171
195
|
}
|
|
172
196
|
else {
|
|
173
197
|
throw new TypeError(`Page must be a string or Page object. Got ${typeof page}`);
|
|
@@ -205,7 +229,7 @@ class JourneyContext {
|
|
|
205
229
|
* @returns {JourneyContext} Chain.
|
|
206
230
|
*/
|
|
207
231
|
clearValidationErrorsForPage(pageId) {
|
|
208
|
-
__classPrivateFieldGet(this, _JourneyContext_validation, "f")[pageId] = null;
|
|
232
|
+
__classPrivateFieldGet(this, _JourneyContext_validation, "f")[validateObjectKey(pageId)] = null;
|
|
209
233
|
return this;
|
|
210
234
|
}
|
|
211
235
|
/**
|
|
@@ -225,7 +249,7 @@ class JourneyContext {
|
|
|
225
249
|
throw new SyntaxError('Field errors must be a ValidationError');
|
|
226
250
|
}
|
|
227
251
|
});
|
|
228
|
-
__classPrivateFieldGet(this, _JourneyContext_validation, "f")[pageId] = errors;
|
|
252
|
+
__classPrivateFieldGet(this, _JourneyContext_validation, "f")[validateObjectKey(pageId)] = errors;
|
|
229
253
|
return this;
|
|
230
254
|
}
|
|
231
255
|
/**
|
|
@@ -236,17 +260,21 @@ class JourneyContext {
|
|
|
236
260
|
* @returns {ValidationError[]} An array of errors
|
|
237
261
|
*/
|
|
238
262
|
getValidationErrorsForPage(pageId) {
|
|
239
|
-
|
|
263
|
+
var _a;
|
|
264
|
+
return (_a = __classPrivateFieldGet(this, _JourneyContext_validation, "f")[validateObjectKey(pageId)]) !== null && _a !== void 0 ? _a : [];
|
|
240
265
|
}
|
|
241
266
|
getValidationErrorsForPageByField(pageId) {
|
|
242
267
|
const errors = this.getValidationErrorsForPage(pageId);
|
|
243
268
|
const obj = Object.create(null);
|
|
269
|
+
// ESLint disabled as `i` is an integer
|
|
270
|
+
/* eslint-disable security/detect-object-injection */
|
|
244
271
|
for (let i = 0, l = errors.length; i < l; i++) {
|
|
245
272
|
if (!obj[errors[i].field]) {
|
|
246
273
|
obj[errors[i].field] = [];
|
|
247
274
|
}
|
|
248
275
|
obj[errors[i].field].push(errors[i]);
|
|
249
276
|
}
|
|
277
|
+
/* eslint-enable security/detect-object-injection */
|
|
250
278
|
return obj;
|
|
251
279
|
}
|
|
252
280
|
/**
|
|
@@ -258,7 +286,7 @@ class JourneyContext {
|
|
|
258
286
|
*/
|
|
259
287
|
hasValidationErrorsForPage(pageId) {
|
|
260
288
|
var _a, _b;
|
|
261
|
-
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;
|
|
262
290
|
}
|
|
263
291
|
/**
|
|
264
292
|
* Set language of the context.
|
|
@@ -277,7 +305,7 @@ class JourneyContext {
|
|
|
277
305
|
* @returns {boolean} True if the page is valid.
|
|
278
306
|
*/
|
|
279
307
|
isPageValid(pageId) {
|
|
280
|
-
return __classPrivateFieldGet(this, _JourneyContext_validation, "f")[pageId] === null;
|
|
308
|
+
return __classPrivateFieldGet(this, _JourneyContext_validation, "f")[validateObjectKey(pageId)] === null;
|
|
281
309
|
}
|
|
282
310
|
/**
|
|
283
311
|
* Remove information about these waypoints.
|
|
@@ -288,10 +316,13 @@ class JourneyContext {
|
|
|
288
316
|
const newData = Object.create(null);
|
|
289
317
|
const newValidation = Object.create(null);
|
|
290
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 */
|
|
291
321
|
for (let i = 0, l = toKeep.length; i < l; i++) {
|
|
292
322
|
newData[toKeep[i]] = __classPrivateFieldGet(this, _JourneyContext_data, "f")[toKeep[i]];
|
|
293
323
|
newValidation[toKeep[i]] = __classPrivateFieldGet(this, _JourneyContext_validation, "f")[toKeep[i]];
|
|
294
324
|
}
|
|
325
|
+
/* eslint-enable security/detect-object-injection */
|
|
295
326
|
__classPrivateFieldSet(this, _JourneyContext_data, Object.assign({}, newData), "f");
|
|
296
327
|
__classPrivateFieldSet(this, _JourneyContext_validation, Object.assign({}, newValidation), "f");
|
|
297
328
|
}
|
|
@@ -304,6 +335,8 @@ class JourneyContext {
|
|
|
304
335
|
*/
|
|
305
336
|
invalidate(waypoints = []) {
|
|
306
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 */
|
|
307
340
|
this.removeValidationStateForPage(waypoints[i]);
|
|
308
341
|
}
|
|
309
342
|
}
|
|
@@ -325,12 +358,16 @@ class JourneyContext {
|
|
|
325
358
|
return this;
|
|
326
359
|
}
|
|
327
360
|
applyEventListeners({ event }) {
|
|
328
|
-
var _a, _b, _c, _d, _e, _f;
|
|
361
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
329
362
|
if (!__classPrivateFieldGet(this, _JourneyContext_eventListeners, "f").length) {
|
|
330
363
|
return this;
|
|
331
364
|
}
|
|
332
365
|
const previousContext = JourneyContext.fromObject(__classPrivateFieldGet(this, _JourneyContext_eventListenerPreState, "f"));
|
|
333
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 */
|
|
334
371
|
for (let i = 0, l = listeners.length; i < l; i++) {
|
|
335
372
|
const { waypoint, field, handler } = listeners[i];
|
|
336
373
|
let logMessage;
|
|
@@ -341,17 +378,18 @@ class JourneyContext {
|
|
|
341
378
|
}
|
|
342
379
|
else if (waypoint && !field) {
|
|
343
380
|
logMessage = `Calling waypoint-specific event handler on "${waypoint}"`;
|
|
344
|
-
runHandler =
|
|
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]);
|
|
345
382
|
}
|
|
346
383
|
else if (waypoint && field) {
|
|
347
384
|
logMessage = `Calling field-specific event handler on "${waypoint} : ${field}"`;
|
|
348
|
-
runHandler = !isEqual((
|
|
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]);
|
|
349
386
|
}
|
|
350
387
|
if (runHandler) {
|
|
351
388
|
log.trace(logMessage);
|
|
352
389
|
handler({ journeyContext: this, previousContext });
|
|
353
390
|
}
|
|
354
391
|
}
|
|
392
|
+
/* eslint-enable security/detect-object-injection */
|
|
355
393
|
return this;
|
|
356
394
|
}
|
|
357
395
|
/* ----------------------------------------------- session context handling */
|
|
@@ -425,6 +463,8 @@ class JourneyContext {
|
|
|
425
463
|
*/
|
|
426
464
|
static getContextById(session, id) {
|
|
427
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 */
|
|
428
468
|
return JourneyContext.fromObject(session.journeyContextList[id]);
|
|
429
469
|
}
|
|
430
470
|
return undefined;
|
|
@@ -518,7 +558,8 @@ class JourneyContext {
|
|
|
518
558
|
}
|
|
519
559
|
static removeContextById(session, id) {
|
|
520
560
|
if (session && has(session.journeyContextList, id)) {
|
|
521
|
-
|
|
561
|
+
// ESLint disabled as `id` has been verified as an "own" property
|
|
562
|
+
/* eslint-disable-next-line security/detect-object-injection, no-param-reassign */
|
|
522
563
|
delete session.journeyContextList[id];
|
|
523
564
|
}
|
|
524
565
|
}
|
|
@@ -48,6 +48,9 @@ class MutableRouter {
|
|
|
48
48
|
return __classPrivateFieldGet(this, _MutableRouter_router, "f");
|
|
49
49
|
}
|
|
50
50
|
__classPrivateFieldGet(this, _MutableRouter_stack, "f").forEach(({ method, args }) => {
|
|
51
|
+
// ESLint disabled as `#router` is dev-controlled, and `seal()` is only
|
|
52
|
+
// run at boot-time before any user interaction
|
|
53
|
+
/* eslint-disable-next-line security/detect-object-injection */
|
|
51
54
|
__classPrivateFieldGet(this, _MutableRouter_router, "f")[method].call(__classPrivateFieldGet(this, _MutableRouter_router, "f"), ...args);
|
|
52
55
|
});
|
|
53
56
|
__classPrivateFieldSet(this, _MutableRouter_sealed, true, "f");
|
|
@@ -115,7 +118,6 @@ class MutableRouter {
|
|
|
115
118
|
__classPrivateFieldGet(this, _MutableRouter_instances, "m", _MutableRouter_prepend).call(this, 'use', path, ...callbacks);
|
|
116
119
|
}
|
|
117
120
|
/* -------------------------------------------------------------- replacers */
|
|
118
|
-
// TODO: How do we handle multiple routes on the same path?
|
|
119
121
|
/**
|
|
120
122
|
* Replace middleware function(s) that were mounted using the `all()` method.
|
|
121
123
|
*
|
|
@@ -264,12 +266,14 @@ _MutableRouter_stack = new WeakMap(), _MutableRouter_router = new WeakMap(), _Mu
|
|
|
264
266
|
if (__classPrivateFieldGet(this, _MutableRouter_sealed, "f")) {
|
|
265
267
|
throw new Error('Cannot alter middleware in a sealed mutable router');
|
|
266
268
|
}
|
|
267
|
-
const
|
|
269
|
+
const finder = (command) => `${command.method}|${command.path}` === `${method}|${path}`;
|
|
270
|
+
const index = __classPrivateFieldGet(this, _MutableRouter_stack, "f").findIndex(finder);
|
|
268
271
|
if (index > -1) {
|
|
269
272
|
__classPrivateFieldGet(this, _MutableRouter_stack, "f").splice(index, 1, {
|
|
270
273
|
method,
|
|
271
274
|
path,
|
|
272
275
|
args: [path, ...callbacks],
|
|
273
276
|
});
|
|
277
|
+
__classPrivateFieldSet(this, _MutableRouter_stack, __classPrivateFieldGet(this, _MutableRouter_stack, "f").filter((command, idx) => idx <= index || !finder(command)), "f");
|
|
274
278
|
}
|
|
275
279
|
};
|
package/dist/lib/Plan.d.ts
CHANGED
|
@@ -1,12 +1,44 @@
|
|
|
1
1
|
export default class Plan {
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Waypoints using the url:// protocol are known as "exit nodes" as they
|
|
4
|
+
* indicate an exit point to another Plan.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} name Waypoint name
|
|
7
|
+
* @returns {boolean} True if the waypoint is a url:// type
|
|
8
|
+
*/
|
|
9
|
+
static isExitNode(name: string): boolean;
|
|
3
10
|
/**
|
|
4
11
|
* Create a Plan.
|
|
5
12
|
*
|
|
6
13
|
* @param {object} opts Options
|
|
14
|
+
* @param {boolean} [opts.validateBeforeRouteCondition=true] Check page validity before conditions
|
|
15
|
+
* @param {Function|string} [opts.arbiter=undefined] Arbitration mechanism
|
|
7
16
|
*/
|
|
8
|
-
constructor(opts?:
|
|
17
|
+
constructor(opts?: {
|
|
18
|
+
validateBeforeRouteCondition?: boolean | undefined;
|
|
19
|
+
arbiter?: string | Function | undefined;
|
|
20
|
+
});
|
|
9
21
|
getOptions(): any;
|
|
22
|
+
/**
|
|
23
|
+
* Retrieve the list of skippable waypoints.
|
|
24
|
+
*
|
|
25
|
+
* @returns {string[]} List of skippable waypoints
|
|
26
|
+
*/
|
|
27
|
+
getSkippables(): string[];
|
|
28
|
+
/**
|
|
29
|
+
* Add one or more skippable waypoints.
|
|
30
|
+
*
|
|
31
|
+
* @param {...string} waypoints Waypoints
|
|
32
|
+
* @returns {Plan}{ Chain}
|
|
33
|
+
*/
|
|
34
|
+
addSkippables(...waypoints: string[]): Plan;
|
|
35
|
+
/**
|
|
36
|
+
* Check if the user can skip the named waypoint.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} waypoint Waypoint
|
|
39
|
+
* @returns {boolean} True if waypoint can be skipped
|
|
40
|
+
*/
|
|
41
|
+
isSkippable(waypoint: string): boolean;
|
|
10
42
|
getWaypoints(): any;
|
|
11
43
|
containsWaypoint(waypoint: any): any;
|
|
12
44
|
getRoutes(): any;
|
|
@@ -114,8 +146,9 @@ export default class Plan {
|
|
|
114
146
|
* Get raw graph data structure. This can be used with other libraries to
|
|
115
147
|
* generate graph visualisations, for example.
|
|
116
148
|
*
|
|
117
|
-
* @returns {
|
|
149
|
+
* @returns {Graph} Graph data structure.
|
|
118
150
|
*/
|
|
119
|
-
getGraphStructure():
|
|
151
|
+
getGraphStructure(): Graph;
|
|
152
|
+
#private;
|
|
120
153
|
}
|
|
121
154
|
import JourneyContext from "./JourneyContext.js";
|
package/dist/lib/Plan.js
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
+
};
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
2
13
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
14
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
15
|
};
|
|
16
|
+
var _Plan_skippableWaypoints;
|
|
5
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
18
|
const graphlib_1 = require("graphlib");
|
|
7
19
|
const JourneyContext_js_1 = __importDefault(require("./JourneyContext.js"));
|
|
8
20
|
const logger_js_1 = __importDefault(require("./logger.js"));
|
|
9
|
-
const log = (0, logger_js_1.default)('
|
|
21
|
+
const log = (0, logger_js_1.default)('lib:plan');
|
|
10
22
|
/**
|
|
11
23
|
* Will check if the source waypoint has specifically passed validation, i.e
|
|
12
24
|
* there is a "null" validation entry for the route source.
|
|
@@ -35,6 +47,9 @@ function validateWaypointId(val) {
|
|
|
35
47
|
if (typeof val !== 'string') {
|
|
36
48
|
throw new TypeError(`Expected waypoint id to be a string, got ${typeof val}`);
|
|
37
49
|
}
|
|
50
|
+
if (val.substr(0, 6) === 'url://' && !val.endsWith('/')) {
|
|
51
|
+
throw new SyntaxError('url:// waypoints must include a trailing /');
|
|
52
|
+
}
|
|
38
53
|
}
|
|
39
54
|
function validateRouteName(val) {
|
|
40
55
|
if (typeof val !== 'string') {
|
|
@@ -43,6 +58,7 @@ function validateRouteName(val) {
|
|
|
43
58
|
else if (!['next', 'prev'].includes(val)) {
|
|
44
59
|
throw new ReferenceError(`Expected route name to be one of next or prev. Got ${val}`);
|
|
45
60
|
}
|
|
61
|
+
return val;
|
|
46
62
|
}
|
|
47
63
|
function validateRouteCondition(val) {
|
|
48
64
|
if (!(val instanceof Function)) {
|
|
@@ -74,15 +90,18 @@ const makeRouteObject = (dgraph, edge) => {
|
|
|
74
90
|
const reExitNodeProtocol = /^[a-z]+:\/\//i;
|
|
75
91
|
const priv = new WeakMap();
|
|
76
92
|
class Plan {
|
|
77
|
-
static isExitNode(name) {
|
|
78
|
-
return reExitNodeProtocol.test(name);
|
|
79
|
-
}
|
|
80
93
|
/**
|
|
81
94
|
* Create a Plan.
|
|
82
95
|
*
|
|
83
96
|
* @param {object} opts Options
|
|
97
|
+
* @param {boolean} [opts.validateBeforeRouteCondition=true] Check page validity before conditions
|
|
98
|
+
* @param {Function|string} [opts.arbiter=undefined] Arbitration mechanism
|
|
84
99
|
*/
|
|
85
100
|
constructor(opts = {}) {
|
|
101
|
+
/**
|
|
102
|
+
* @type {string[]} These waypoints can be skipped
|
|
103
|
+
*/
|
|
104
|
+
_Plan_skippableWaypoints.set(this, void 0);
|
|
86
105
|
// This is our directed, multigraph representation
|
|
87
106
|
const dgraph = new graphlib_1.Graph({
|
|
88
107
|
directed: true,
|
|
@@ -106,10 +125,48 @@ class Plan {
|
|
|
106
125
|
},
|
|
107
126
|
options,
|
|
108
127
|
});
|
|
128
|
+
__classPrivateFieldSet(this, _Plan_skippableWaypoints, [], "f");
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Waypoints using the url:// protocol are known as "exit nodes" as they
|
|
132
|
+
* indicate an exit point to another Plan.
|
|
133
|
+
*
|
|
134
|
+
* @param {string} name Waypoint name
|
|
135
|
+
* @returns {boolean} True if the waypoint is a url:// type
|
|
136
|
+
*/
|
|
137
|
+
static isExitNode(name) {
|
|
138
|
+
return reExitNodeProtocol.test(name);
|
|
109
139
|
}
|
|
110
140
|
getOptions() {
|
|
111
141
|
return priv.get(this).options;
|
|
112
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Retrieve the list of skippable waypoints.
|
|
145
|
+
*
|
|
146
|
+
* @returns {string[]} List of skippable waypoints
|
|
147
|
+
*/
|
|
148
|
+
getSkippables() {
|
|
149
|
+
return __classPrivateFieldGet(this, _Plan_skippableWaypoints, "f");
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Add one or more skippable waypoints.
|
|
153
|
+
*
|
|
154
|
+
* @param {...string} waypoints Waypoints
|
|
155
|
+
* @returns {Plan}{ Chain}
|
|
156
|
+
*/
|
|
157
|
+
addSkippables(...waypoints) {
|
|
158
|
+
__classPrivateFieldSet(this, _Plan_skippableWaypoints, [...__classPrivateFieldGet(this, _Plan_skippableWaypoints, "f"), ...waypoints], "f");
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Check if the user can skip the named waypoint.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} waypoint Waypoint
|
|
165
|
+
* @returns {boolean} True if waypoint can be skipped
|
|
166
|
+
*/
|
|
167
|
+
isSkippable(waypoint) {
|
|
168
|
+
return __classPrivateFieldGet(this, _Plan_skippableWaypoints, "f").indexOf(waypoint) > -1;
|
|
169
|
+
}
|
|
113
170
|
getWaypoints() {
|
|
114
171
|
return priv.get(this).dgraph.nodes();
|
|
115
172
|
}
|
|
@@ -121,7 +178,7 @@ class Plan {
|
|
|
121
178
|
return self.dgraph.edges().map((edge) => makeRouteObject(self.dgraph, edge));
|
|
122
179
|
}
|
|
123
180
|
getRouteCondition(src, tgt, name) {
|
|
124
|
-
return priv.get(this).follows[name][`${src}/${tgt}`];
|
|
181
|
+
return priv.get(this).follows[validateRouteName(name)][`${src}/${tgt}`];
|
|
125
182
|
}
|
|
126
183
|
/**
|
|
127
184
|
* Return all outward routes (out-edges) from the given waypoint, to the
|
|
@@ -149,6 +206,8 @@ class Plan {
|
|
|
149
206
|
addSequence(...waypoints) {
|
|
150
207
|
// Setup simple double routes (next/prev) between all waypoints in this list
|
|
151
208
|
for (let i = 0, l = waypoints.length - 1; i < l; i += 1) {
|
|
209
|
+
// ESLint disabled as `i` is an integer
|
|
210
|
+
/* eslint-disable-next-line security/detect-object-injection */
|
|
152
211
|
this.setRoute(waypoints[i], waypoints[i + 1]);
|
|
153
212
|
}
|
|
154
213
|
}
|
|
@@ -222,12 +281,12 @@ class Plan {
|
|
|
222
281
|
validateRouteCondition(follow);
|
|
223
282
|
}
|
|
224
283
|
// Get routing function name to label edge
|
|
225
|
-
const
|
|
284
|
+
const conditionName = follow && follow.name;
|
|
226
285
|
// Warn if we're overwriting an existing edge on the same name
|
|
227
286
|
if (self.dgraph.hasEdge(src, tgt, name)) {
|
|
228
287
|
log.warn('Setting a route that already exists (%s, %s, %s). Will be overridden', src, tgt, name);
|
|
229
288
|
}
|
|
230
|
-
self.dgraph.setEdge(src, tgt, {
|
|
289
|
+
self.dgraph.setEdge(src, tgt, { conditionName }, name);
|
|
231
290
|
// Determine which follow function to use
|
|
232
291
|
let followFunc;
|
|
233
292
|
if (follow) {
|
|
@@ -253,6 +312,8 @@ class Plan {
|
|
|
253
312
|
else {
|
|
254
313
|
followFunc = defaultPrevFollow;
|
|
255
314
|
}
|
|
315
|
+
// ESLint disabled as `name` has been validated further above
|
|
316
|
+
/* eslint-disable-next-line security/detect-object-injection */
|
|
256
317
|
self.follows[name][`${src}/${tgt}`] = followFunc;
|
|
257
318
|
return this;
|
|
258
319
|
}
|
|
@@ -304,9 +365,7 @@ class Plan {
|
|
|
304
365
|
if (!self.dgraph.hasNode(startWaypoint)) {
|
|
305
366
|
throw new ReferenceError(`Plan does not contain waypoint '${startWaypoint}'`);
|
|
306
367
|
}
|
|
307
|
-
|
|
308
|
-
throw new ReferenceError('Route name must be provided');
|
|
309
|
-
}
|
|
368
|
+
validateRouteName(routeName);
|
|
310
369
|
const history = new Map();
|
|
311
370
|
const traverse = (startWP) => {
|
|
312
371
|
let target = self.dgraph.outEdges(startWP).filter((e) => {
|
|
@@ -315,6 +374,8 @@ class Plan {
|
|
|
315
374
|
}
|
|
316
375
|
const route = makeRouteObject(self.dgraph, e);
|
|
317
376
|
try {
|
|
377
|
+
// ESLint disabled as `routeName` has been validated further above
|
|
378
|
+
/* eslint-disable-next-line security/detect-object-injection */
|
|
318
379
|
return self.follows[routeName][`${e.v}/${e.w}`](route, context);
|
|
319
380
|
}
|
|
320
381
|
catch (ex) {
|
|
@@ -357,6 +418,8 @@ class Plan {
|
|
|
357
418
|
const results = new Array(totalTrav + 1);
|
|
358
419
|
results[0] = route;
|
|
359
420
|
for (let i = 0; i < totalTrav; i++) {
|
|
421
|
+
// ESLint disabled as `i` is an integer
|
|
422
|
+
/* eslint-disable-next-line security/detect-object-injection */
|
|
360
423
|
results[i + 1] = traversed[i];
|
|
361
424
|
}
|
|
362
425
|
return results;
|
|
@@ -376,10 +439,11 @@ class Plan {
|
|
|
376
439
|
* Get raw graph data structure. This can be used with other libraries to
|
|
377
440
|
* generate graph visualisations, for example.
|
|
378
441
|
*
|
|
379
|
-
* @returns {
|
|
442
|
+
* @returns {Graph} Graph data structure.
|
|
380
443
|
*/
|
|
381
444
|
getGraphStructure() {
|
|
382
445
|
return priv.get(this).dgraph;
|
|
383
446
|
}
|
|
384
447
|
}
|
|
385
448
|
exports.default = Plan;
|
|
449
|
+
_Plan_skippableWaypoints = new WeakMap();
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('./ValidatorFactory').ValidateContext} ValidateContext
|
|
3
|
+
*/
|
|
1
4
|
export default class ValidationError {
|
|
2
5
|
/**
|
|
3
6
|
* Make a ValidationError instance from a primitive object (or a function that
|
|
@@ -61,10 +64,11 @@ export default class ValidationError {
|
|
|
61
64
|
* @param {ValidateContext} context See structure above
|
|
62
65
|
* @returns {ValidationError} Chain
|
|
63
66
|
*/
|
|
64
|
-
withContext(context:
|
|
67
|
+
withContext(context: ValidateContext): ValidationError;
|
|
65
68
|
variables: any;
|
|
66
|
-
field:
|
|
69
|
+
field: string | undefined;
|
|
67
70
|
fieldHref: string | undefined;
|
|
68
71
|
focusSuffix: any;
|
|
69
72
|
validator: any;
|
|
70
73
|
}
|
|
74
|
+
export type ValidateContext = import('./ValidatorFactory').ValidateContext;
|