@dwp/govuk-casa 7.0.6 → 8.0.0-alpha1

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 (219) hide show
  1. package/README.md +22 -17
  2. package/dist/{casa → assets}/css/casa-ie8.css +1 -1
  3. package/dist/assets/css/casa.css +1 -0
  4. package/dist/casa.d.ts +10 -0
  5. package/dist/casa.js +44 -0
  6. package/dist/lib/CasaTemplateLoader.d.ts +19 -0
  7. package/dist/lib/CasaTemplateLoader.js +57 -0
  8. package/dist/lib/JourneyContext.d.ts +255 -0
  9. package/dist/lib/JourneyContext.js +681 -0
  10. package/dist/lib/MutableRouter.d.ts +155 -0
  11. package/dist/lib/MutableRouter.js +272 -0
  12. package/dist/lib/Plan.d.ts +119 -0
  13. package/dist/lib/Plan.js +382 -0
  14. package/dist/lib/ValidationError.d.ts +70 -0
  15. package/dist/lib/ValidationError.js +156 -0
  16. package/dist/lib/ValidatorFactory.d.ts +24 -0
  17. package/dist/lib/ValidatorFactory.js +87 -0
  18. package/dist/lib/configure.d.ts +205 -0
  19. package/dist/lib/configure.js +215 -0
  20. package/dist/lib/dirname.cjs +1 -0
  21. package/dist/lib/end-session.d.ts +12 -0
  22. package/dist/lib/end-session.js +24 -0
  23. package/dist/lib/field.d.ts +79 -0
  24. package/dist/lib/field.js +223 -0
  25. package/dist/lib/logger.d.ts +8 -0
  26. package/dist/lib/logger.js +19 -0
  27. package/dist/lib/nunjucks-filters.d.ts +26 -0
  28. package/dist/lib/nunjucks-filters.js +112 -0
  29. package/dist/lib/nunjucks.d.ts +22 -0
  30. package/dist/lib/nunjucks.js +49 -0
  31. package/dist/lib/utils.d.ts +22 -0
  32. package/dist/lib/utils.js +44 -0
  33. package/dist/lib/validators/dateObject.d.ts +4 -0
  34. package/dist/lib/validators/dateObject.js +135 -0
  35. package/dist/lib/validators/email.d.ts +4 -0
  36. package/dist/lib/validators/email.js +46 -0
  37. package/dist/lib/validators/inArray.d.ts +4 -0
  38. package/dist/lib/validators/inArray.js +60 -0
  39. package/dist/lib/validators/index.d.ts +21 -0
  40. package/dist/lib/validators/index.js +47 -0
  41. package/dist/lib/validators/nino.d.ts +4 -0
  42. package/dist/lib/validators/nino.js +46 -0
  43. package/dist/lib/validators/postalAddressObject.d.ts +4 -0
  44. package/dist/lib/validators/postalAddressObject.js +123 -0
  45. package/dist/lib/validators/regex.d.ts +4 -0
  46. package/dist/lib/validators/regex.js +40 -0
  47. package/dist/lib/validators/required.d.ts +4 -0
  48. package/dist/lib/validators/required.js +56 -0
  49. package/dist/lib/validators/strlen.d.ts +4 -0
  50. package/dist/lib/validators/strlen.js +51 -0
  51. package/dist/lib/validators/wordCount.d.ts +5 -0
  52. package/dist/lib/validators/wordCount.js +54 -0
  53. package/dist/lib/waypoint-url.d.ts +17 -0
  54. package/dist/lib/waypoint-url.js +46 -0
  55. package/dist/middleware/body-parser.d.ts +1 -0
  56. package/dist/middleware/body-parser.js +24 -0
  57. package/dist/middleware/csrf.d.ts +1 -0
  58. package/dist/middleware/csrf.js +31 -0
  59. package/dist/middleware/data.d.ts +6 -0
  60. package/dist/middleware/data.js +53 -0
  61. package/dist/middleware/dirname.cjs +1 -0
  62. package/dist/middleware/gather-fields.d.ts +5 -0
  63. package/dist/middleware/gather-fields.js +39 -0
  64. package/dist/middleware/i18n.d.ts +4 -0
  65. package/dist/middleware/i18n.js +87 -0
  66. package/dist/middleware/post.d.ts +1 -0
  67. package/dist/middleware/post.js +42 -0
  68. package/dist/middleware/pre.d.ts +3 -0
  69. package/dist/middleware/pre.js +38 -0
  70. package/dist/middleware/progress-journey.d.ts +6 -0
  71. package/dist/middleware/progress-journey.js +82 -0
  72. package/dist/middleware/sanitise-fields.d.ts +5 -0
  73. package/dist/middleware/sanitise-fields.js +48 -0
  74. package/dist/middleware/session.d.ts +10 -0
  75. package/dist/middleware/session.js +115 -0
  76. package/dist/middleware/skip-waypoint.d.ts +5 -0
  77. package/dist/middleware/skip-waypoint.js +40 -0
  78. package/dist/middleware/steer-journey.d.ts +6 -0
  79. package/dist/middleware/steer-journey.js +44 -0
  80. package/dist/middleware/validate-fields.d.ts +7 -0
  81. package/dist/middleware/validate-fields.js +76 -0
  82. package/dist/mjs/esm-wrapper.js +10 -0
  83. package/dist/mjs/package.json +3 -0
  84. package/dist/package.json +3 -0
  85. package/dist/routes/ancillary.d.ts +4 -0
  86. package/dist/routes/ancillary.js +19 -0
  87. package/dist/routes/dirname.cjs +1 -0
  88. package/dist/routes/journey.d.ts +8 -0
  89. package/dist/routes/journey.js +130 -0
  90. package/dist/routes/static.d.ts +26 -0
  91. package/dist/routes/static.js +67 -0
  92. package/package.json +45 -86
  93. package/views/casa/components/checkboxes/template.njk +4 -1
  94. package/views/casa/components/date-input/template.njk +3 -3
  95. package/views/casa/components/journey-form/README.md +3 -1
  96. package/views/casa/components/journey-form/template.njk +1 -1
  97. package/views/casa/components/postal-address-object/template.njk +5 -5
  98. package/views/casa/components/radios/template.njk +1 -1
  99. package/views/casa/layouts/journey.njk +26 -9
  100. package/views/casa/layouts/main.njk +6 -19
  101. package/views/casa/partials/scripts.njk +8 -3
  102. package/views/casa/partials/styles.njk +2 -2
  103. package/casa.js +0 -208
  104. package/definitions/review-page.js +0 -60
  105. package/dist/casa/css/casa.css +0 -1
  106. package/dist/casa/js/casa.js +0 -1
  107. package/index.d.ts +0 -121
  108. package/lib/ConfigIngestor.js +0 -588
  109. package/lib/GatherModifier.js +0 -14
  110. package/lib/I18n.js +0 -160
  111. package/lib/JourneyContext.d.ts +0 -97
  112. package/lib/JourneyContext.js +0 -552
  113. package/lib/JourneyMap.js +0 -233
  114. package/lib/JourneyRoad.js +0 -330
  115. package/lib/Logger.js +0 -59
  116. package/lib/PageDictionary.d.ts +0 -11
  117. package/lib/PageDirectory.js +0 -77
  118. package/lib/Plan.js +0 -423
  119. package/lib/RoadConverter.js +0 -153
  120. package/lib/UserJourney.js +0 -8
  121. package/lib/Util.js +0 -227
  122. package/lib/Validation.js +0 -20
  123. package/lib/bootstrap/end-session.js +0 -44
  124. package/lib/bootstrap/load-definitions.js +0 -64
  125. package/lib/commonBodyParser.js +0 -15
  126. package/lib/enums.js +0 -6
  127. package/lib/gather-modifiers/index.js +0 -7
  128. package/lib/gather-modifiers/trimPostalAddressObject.js +0 -75
  129. package/lib/gather-modifiers/trimWhitespace.js +0 -16
  130. package/lib/utils/createGetRequest.d.ts +0 -5
  131. package/lib/utils/createGetRequest.js +0 -59
  132. package/lib/utils/index.js +0 -11
  133. package/lib/utils/parseRequest.d.ts +0 -5
  134. package/lib/utils/parseRequest.js +0 -72
  135. package/lib/utils/sanitise.js +0 -74
  136. package/lib/utils/validate.js +0 -32
  137. package/lib/validation/ArrayObjectField.js +0 -49
  138. package/lib/validation/ObjectField.js +0 -53
  139. package/lib/validation/SimpleField.d.ts +0 -11
  140. package/lib/validation/SimpleField.js +0 -46
  141. package/lib/validation/ValidationError.d.ts +0 -14
  142. package/lib/validation/ValidationError.js +0 -170
  143. package/lib/validation/ValidatorFactory.d.ts +0 -32
  144. package/lib/validation/ValidatorFactory.js +0 -91
  145. package/lib/validation/index.js +0 -22
  146. package/lib/validation/processor/flattenErrorArray.js +0 -24
  147. package/lib/validation/processor/queue.js +0 -214
  148. package/lib/validation/processor.js +0 -84
  149. package/lib/validation/rules/README.md +0 -3
  150. package/lib/validation/rules/ValidationRules.d.ts +0 -22
  151. package/lib/validation/rules/dateObject.js +0 -156
  152. package/lib/validation/rules/email.js +0 -44
  153. package/lib/validation/rules/inArray.js +0 -61
  154. package/lib/validation/rules/index.js +0 -23
  155. package/lib/validation/rules/nino.js +0 -48
  156. package/lib/validation/rules/optional.js +0 -14
  157. package/lib/validation/rules/postalAddressObject.js +0 -142
  158. package/lib/validation/rules/regex.js +0 -39
  159. package/lib/validation/rules/required.js +0 -57
  160. package/lib/validation/rules/strlen.js +0 -57
  161. package/lib/validation/rules/wordCount.js +0 -61
  162. package/lib/view-filters/formatDateObject.js +0 -35
  163. package/lib/view-filters/includes.js +0 -10
  164. package/lib/view-filters/index.js +0 -23
  165. package/lib/view-filters/mergeObjectsDeep.js +0 -21
  166. package/lib/view-filters/renderAsAttributes.js +0 -33
  167. package/middleware/errors/404.js +0 -12
  168. package/middleware/errors/catch-all.js +0 -27
  169. package/middleware/errors/index.js +0 -9
  170. package/middleware/headers/config-defaults.js +0 -57
  171. package/middleware/headers/headers.js +0 -40
  172. package/middleware/headers/index.js +0 -9
  173. package/middleware/i18n/i18n.js +0 -56
  174. package/middleware/i18n/index.js +0 -16
  175. package/middleware/index.js +0 -55
  176. package/middleware/mount/index.js +0 -9
  177. package/middleware/mount/mount.js +0 -10
  178. package/middleware/nunjucks/environment.js +0 -57
  179. package/middleware/nunjucks/index.js +0 -8
  180. package/middleware/page/csrf.js +0 -37
  181. package/middleware/page/edit-mode.js +0 -52
  182. package/middleware/page/gather.js +0 -75
  183. package/middleware/page/index.js +0 -103
  184. package/middleware/page/journey-continue.js +0 -157
  185. package/middleware/page/journey-rails.js +0 -102
  186. package/middleware/page/prepare-request.js +0 -77
  187. package/middleware/page/render.js +0 -75
  188. package/middleware/page/skip.js +0 -72
  189. package/middleware/page/utils.js +0 -206
  190. package/middleware/page/validate.js +0 -67
  191. package/middleware/session/expiry.js +0 -95
  192. package/middleware/session/genid.js +0 -18
  193. package/middleware/session/index.js +0 -18
  194. package/middleware/session/init.js +0 -25
  195. package/middleware/session/seed.js +0 -50
  196. package/middleware/session/timeout.js +0 -5
  197. package/middleware/static/asset-versions.js +0 -23
  198. package/middleware/static/index.js +0 -104
  199. package/middleware/static/prepare-assets.js +0 -51
  200. package/middleware/static/serve-assets.js +0 -58
  201. package/middleware/variables/index.js +0 -12
  202. package/middleware/variables/variables.js +0 -35
  203. package/src/browserconfig.xml +0 -5
  204. package/src/js/casa.js +0 -132
  205. package/src/scss/_casaElements.scss +0 -11
  206. package/src/scss/_casaGovukTemplateJinjaPolyfill.scss +0 -39
  207. package/src/scss/_casaMountUrl.scss +0 -8
  208. package/src/scss/casa-ie8.scss +0 -3
  209. package/src/scss/casa.scss +0 -14
  210. package/test/unit/templates/README.md +0 -5
  211. package/test/utils/BaseTestWaypoint.js +0 -106
  212. package/test/utils/concatWaypoints.js +0 -26
  213. package/test/utils/index.js +0 -6
  214. package/test/utils/testTraversal.js +0 -90
  215. package/views/casa/partials/cookie_message.njk +0 -3
  216. package/views/casa/partials/phase_banner_alpha.njk +0 -8
  217. package/views/casa/partials/phase_banner_beta.njk +0 -8
  218. package/views/casa/review/page-block.njk +0 -8
  219. package/views/casa/review/review.njk +0 -47
@@ -0,0 +1,681 @@
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
+ };
13
+ var __rest = (this && this.__rest) || function (s, e) {
14
+ var t = {};
15
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
16
+ t[p] = s[p];
17
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
18
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
19
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
20
+ t[p[i]] = s[p[i]];
21
+ }
22
+ return t;
23
+ };
24
+ var __importDefault = (this && this.__importDefault) || function (mod) {
25
+ return (mod && mod.__esModule) ? mod : { "default": mod };
26
+ };
27
+ var _JourneyContext_data, _JourneyContext_validation, _JourneyContext_nav, _JourneyContext_identity, _JourneyContext_eventListeners, _JourneyContext_eventListenerPreState;
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ /**
30
+ * Represents the state of a user's journey through the Plan. It contains
31
+ * information about:
32
+ *
33
+ * - Data gathered during the journey
34
+ * - Validation errors on that data
35
+ * - Navigation information about how the user got where they are.
36
+ */
37
+ const uuid_1 = require("uuid");
38
+ const ValidationError_js_1 = __importDefault(require("./ValidationError.js"));
39
+ 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
+ // };
69
+ class JourneyContext {
70
+ /**
71
+ * Constructor.
72
+ *
73
+ * `data` is the "single source of truth" for all data gathered during the
74
+ * user's journey. This is referred to as the "canonical data model".
75
+ * Page-specific "views" of this data are generated at runtime in order to
76
+ * populate/validate specific form fields.
77
+ *
78
+ * `validation` holds the results of form field validation carried out when
79
+ * page forms are POSTed. These results are mapped directly to per-page,
80
+ * per-field.
81
+ *
82
+ * `nav` holds information about the current navigation state. Currently this
83
+ * comprises of the language in which the user is navigating the service.
84
+ *
85
+ * `identity` holds information that helps uniquely identify this context
86
+ * among a group of contexts stored in the session.
87
+ *
88
+ * @param {object} data Entire journey data.
89
+ * @param {object} validation Page errors (indexed by waypoint id).
90
+ * @param {object} nav Navigation context.
91
+ * @param {object} identity Some metadata for identifying this context among others.
92
+ */
93
+ constructor(data = {}, validation = {}, nav = {}, identity = {}) {
94
+ // Private properties
95
+ _JourneyContext_data.set(this, void 0);
96
+ _JourneyContext_validation.set(this, void 0);
97
+ _JourneyContext_nav.set(this, void 0);
98
+ _JourneyContext_identity.set(this, void 0);
99
+ _JourneyContext_eventListeners.set(this, void 0);
100
+ _JourneyContext_eventListenerPreState.set(this, void 0);
101
+ __classPrivateFieldSet(this, _JourneyContext_data, data, "f");
102
+ __classPrivateFieldSet(this, _JourneyContext_validation, validation, "f");
103
+ __classPrivateFieldSet(this, _JourneyContext_nav, nav, "f");
104
+ __classPrivateFieldSet(this, _JourneyContext_identity, identity, "f");
105
+ __classPrivateFieldSet(this, _JourneyContext_eventListeners, [], "f");
106
+ __classPrivateFieldSet(this, _JourneyContext_eventListenerPreState, null, "f");
107
+ }
108
+ /**
109
+ * Clone into an object that can be stringified.
110
+ *
111
+ * @returns {object} Plain object.
112
+ */
113
+ toObject() {
114
+ return Object.assign(Object.create(null), {
115
+ data: cloneDeep(__classPrivateFieldGet(this, _JourneyContext_data, "f")),
116
+ validation: cloneDeep(__classPrivateFieldGet(this, _JourneyContext_validation, "f")),
117
+ nav: cloneDeep(__classPrivateFieldGet(this, _JourneyContext_nav, "f")),
118
+ identity: cloneDeep(__classPrivateFieldGet(this, _JourneyContext_identity, "f")),
119
+ });
120
+ }
121
+ /**
122
+ * Create a new JourneyContext using the plain object.
123
+ *
124
+ * @param {object} obj Object.
125
+ * @returns {JourneyContext} Instance.
126
+ */
127
+ static fromObject({ data = Object.create(null), validation = Object.create(null), nav = Object.create(null), identity = Object.create(null), } = {}) {
128
+ return new JourneyContext(data, validation, nav, identity);
129
+ }
130
+ get data() {
131
+ return __classPrivateFieldGet(this, _JourneyContext_data, "f");
132
+ }
133
+ set data(value) {
134
+ __classPrivateFieldSet(this, _JourneyContext_data, value, "f");
135
+ }
136
+ get validation() {
137
+ return __classPrivateFieldGet(this, _JourneyContext_validation, "f");
138
+ }
139
+ get nav() {
140
+ return __classPrivateFieldGet(this, _JourneyContext_nav, "f");
141
+ }
142
+ get identity() {
143
+ return __classPrivateFieldGet(this, _JourneyContext_identity, "f");
144
+ }
145
+ /**
146
+ * Get data context for a specific a specific page.
147
+ *
148
+ * @param {string | PageMeta} page Page waypoint ID, or Page object.
149
+ * @returns {object} Page data.
150
+ * @throws {TypeError} When page is invalid.
151
+ */
152
+ getDataForPage(page) {
153
+ if (typeof page === 'string') {
154
+ return __classPrivateFieldGet(this, _JourneyContext_data, "f")[page];
155
+ }
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}`);
161
+ }
162
+ }
163
+ getData() {
164
+ return __classPrivateFieldGet(this, _JourneyContext_data, "f");
165
+ }
166
+ /**
167
+ * Overwrite the data context with a new object.
168
+ *
169
+ * @param {object} data Data that will overwrite all existing data.
170
+ * @returns {JourneyContext} Chain.
171
+ */
172
+ setData(data) {
173
+ __classPrivateFieldSet(this, _JourneyContext_data, data, "f");
174
+ return this;
175
+ }
176
+ /**
177
+ * Write field form data from a page HTML form, into the `data` model.
178
+ *
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
181
+ * `fieldWriter()` method will be called to transform the provided formData
182
+ * before storing in `data`
183
+ *
184
+ * @param {string | PageMeta} page Page waypoint ID, or PageMeta object
185
+ * @param {object} webFormData Data to overwrite with
186
+ * @returns {JourneyContext} Chain
187
+ * @throws {TypeError} When page is invalid.
188
+ */
189
+ setDataForPage(page, webFormData) {
190
+ if (typeof page === 'string') {
191
+ __classPrivateFieldGet(this, _JourneyContext_data, "f")[page] = webFormData;
192
+ }
193
+ else if (isPlainObject(page)) {
194
+ __classPrivateFieldGet(this, _JourneyContext_data, "f")[page.waypoint] = webFormData;
195
+ }
196
+ else {
197
+ throw new TypeError(`Page must be a string or Page object. Got ${typeof page}`);
198
+ }
199
+ return this;
200
+ }
201
+ /**
202
+ * Return validation errors for all pages.
203
+ *
204
+ * @returns {object} All page validation errors.
205
+ */
206
+ getValidationErrors() {
207
+ return __classPrivateFieldGet(this, _JourneyContext_validation, "f");
208
+ }
209
+ /**
210
+ * Removes any validation state for the given page. Clearing validation state
211
+ * completely will, by default, prevent onward traversal from this page. See
212
+ * the traversal logic in Plan class.
213
+ *
214
+ * @param {string} pageId Page ID.
215
+ * @returns {JourneyContext} Chain.
216
+ */
217
+ removeValidationStateForPage(pageId) {
218
+ const _a = __classPrivateFieldGet(this, _JourneyContext_validation, "f"), _b = pageId, dummy = _a[_b], remaining = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
219
+ __classPrivateFieldSet(this, _JourneyContext_validation, Object.assign({}, remaining), "f");
220
+ return this;
221
+ }
222
+ /**
223
+ * Clear any validation errors for the given page. This effectively declares
224
+ * that this page has been successfully validated, and so can be traversed. If
225
+ * you want to remove any knowledge of validation success/failure, use
226
+ * `removeValidationStateForPage()` instead.
227
+ *
228
+ * @param {string} pageId Page ID.
229
+ * @returns {JourneyContext} Chain.
230
+ */
231
+ clearValidationErrorsForPage(pageId) {
232
+ __classPrivateFieldGet(this, _JourneyContext_validation, "f")[pageId] = null;
233
+ return this;
234
+ }
235
+ /**
236
+ * Set validation errors for a page.
237
+ *
238
+ * @param {string} pageId Page ID.
239
+ * @param {ValidationError[]} errors Errors
240
+ * @returns {JourneyContext} Chain.
241
+ * @throws {SyntaxError} When errors are invalid.
242
+ */
243
+ setValidationErrorsForPage(pageId, errors = []) {
244
+ if (!Array.isArray(errors)) {
245
+ throw new SyntaxError(`Errors must be an Array. Received ${Object.prototype.toString.call(errors)}`);
246
+ }
247
+ errors.forEach((error) => {
248
+ if (!(error instanceof ValidationError_js_1.default)) {
249
+ throw new SyntaxError('Field errors must be a ValidationError');
250
+ }
251
+ });
252
+ __classPrivateFieldGet(this, _JourneyContext_validation, "f")[pageId] = errors;
253
+ return this;
254
+ }
255
+ /**
256
+ * Return the validation errors associated with the page's currently held data
257
+ * context (if any).
258
+ *
259
+ * @param {string} pageId Page ID.
260
+ * @returns {ValidationError[]} An array of errors
261
+ */
262
+ getValidationErrorsForPage(pageId) {
263
+ return __classPrivateFieldGet(this, _JourneyContext_validation, "f")[pageId] || [];
264
+ }
265
+ getValidationErrorsForPageByField(pageId) {
266
+ const errors = this.getValidationErrorsForPage(pageId);
267
+ const obj = Object.create(null);
268
+ for (let i = 0, l = errors.length; i < l; i++) {
269
+ if (!obj[errors[i].field]) {
270
+ obj[errors[i].field] = [];
271
+ }
272
+ obj[errors[i].field].push(errors[i]);
273
+ }
274
+ return obj;
275
+ }
276
+ /**
277
+ * Determine whether the specified page has any errors in its validation
278
+ * context.
279
+ *
280
+ * @param {string} pageId Page ID.
281
+ * @returns {boolean} Result.
282
+ */
283
+ hasValidationErrorsForPage(pageId) {
284
+ 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;
286
+ }
287
+ /**
288
+ * Set language of the context.
289
+ *
290
+ * @param {string} language Language to set (ISO 639-1 2-letter code).
291
+ * @returns {JourneyContext} Chain.
292
+ */
293
+ setNavigationLanguage(language = 'en') {
294
+ __classPrivateFieldGet(this, _JourneyContext_nav, "f").language = language;
295
+ return this;
296
+ }
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
+ /**
393
+ * Convenience function to test if page is valid.
394
+ *
395
+ * @param {string} pageId Page ID.
396
+ * @returns {boolean} True if the page is valid.
397
+ */
398
+ isPageValid(pageId) {
399
+ return __classPrivateFieldGet(this, _JourneyContext_validation, "f")[pageId] === null;
400
+ }
401
+ /**
402
+ * Remove information about these waypoints.
403
+ *
404
+ * @param {string[]} waypoints Waypoints to be removed
405
+ */
406
+ purge(waypoints = []) {
407
+ const newData = Object.create(null);
408
+ const newValidation = Object.create(null);
409
+ const toKeep = Object.keys(this.data).filter(w => !waypoints.includes(w));
410
+ for (let i = 0, l = toKeep.length; i < l; i++) {
411
+ newData[toKeep[i]] = __classPrivateFieldGet(this, _JourneyContext_data, "f")[toKeep[i]];
412
+ newValidation[toKeep[i]] = __classPrivateFieldGet(this, _JourneyContext_validation, "f")[toKeep[i]];
413
+ }
414
+ __classPrivateFieldSet(this, _JourneyContext_data, Object.assign({}, newData), "f");
415
+ __classPrivateFieldSet(this, _JourneyContext_validation, Object.assign({}, newValidation), "f");
416
+ }
417
+ /**
418
+ * Remove validation state from these waypoints. This is useful to quickly
419
+ * force the user to revisit some waypoints.
420
+ *
421
+ * @param {string[]} waypoints Waypoints to be invalidated
422
+ */
423
+ invalidate(waypoints = []) {
424
+ for (let i = 0, l = waypoints.length; i < l; i++) {
425
+ this.removeValidationStateForPage(waypoints[i]);
426
+ }
427
+ }
428
+ /**
429
+ * Event listeners are transient. They are not stored in session, and generally
430
+ * only apply for the current request.
431
+ *
432
+ * They also only act on a fixed snapshot of this context's state, which is
433
+ * taken at the point of attaching the listeners (in the "data" middleware).
434
+ * This is important because JourneyContext.putContext()` could be called many
435
+ * times during a request, so the context will be constantly changing.
436
+ *
437
+ * @param {ContextEvent[]} events Event listeners
438
+ */
439
+ addEventListeners(events) {
440
+ __classPrivateFieldSet(this, _JourneyContext_eventListeners, events, "f");
441
+ __classPrivateFieldSet(this, _JourneyContext_eventListenerPreState, this.toObject(), "f");
442
+ return this;
443
+ }
444
+ applyEventListeners({ event }) {
445
+ var _a, _b, _c, _d, _e, _f;
446
+ if (!__classPrivateFieldGet(this, _JourneyContext_eventListeners, "f").length) {
447
+ return this;
448
+ }
449
+ const previousContext = JourneyContext.fromObject(__classPrivateFieldGet(this, _JourneyContext_eventListenerPreState, "f"));
450
+ const listeners = __classPrivateFieldGet(this, _JourneyContext_eventListeners, "f").filter(l => l.event === event);
451
+ for (let i = 0, l = listeners.length; i < l; i++) {
452
+ const { waypoint, field, handler } = listeners[i];
453
+ let logMessage;
454
+ let runHandler = false;
455
+ if (!waypoint && !field) {
456
+ logMessage = `Calling generic event handler`;
457
+ runHandler = true;
458
+ }
459
+ else if (waypoint && !field) {
460
+ 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]);
462
+ }
463
+ else if (waypoint && field) {
464
+ 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]);
466
+ }
467
+ if (runHandler) {
468
+ log.trace(logMessage);
469
+ handler({ journeyContext: this, previousContext });
470
+ }
471
+ }
472
+ }
473
+ /* ----------------------------------------------- session context handling */
474
+ /**
475
+ * Construct a new JourneyContext instance frmo another instance.
476
+ *
477
+ * @param {JourneyContext} context Context to copy from
478
+ * @returns {JourneyContext} Constructed JourneyContext instance
479
+ * @throws {TypeError} When context is not a valid type
480
+ */
481
+ static fromContext(context) {
482
+ if (!(context instanceof JourneyContext)) {
483
+ throw new TypeError('Source context must be a JourneyContext');
484
+ }
485
+ const newContextObj = context.toObject();
486
+ newContextObj.identity.id = (0, uuid_1.v4)();
487
+ return JourneyContext.fromObject(newContextObj);
488
+ }
489
+ /**
490
+ * Convenience method to determine if this is the default context.
491
+ *
492
+ * @returns {boolean} True if this is the "default" journey context
493
+ */
494
+ isDefault() {
495
+ return __classPrivateFieldGet(this, _JourneyContext_identity, "f").id === JourneyContext.DEFAULT_CONTEXT_ID;
496
+ }
497
+ /**
498
+ * Initialise session with an empty entry for the "default" context.
499
+ *
500
+ * @param {object} session Request session
501
+ * @returns {void}
502
+ */
503
+ static initContextStore(session) {
504
+ if (!has(session, 'journeyContextList')) {
505
+ log.trace('Initialising session with a default journey context list');
506
+ session.journeyContextList = Object.create(null);
507
+ const defaultContext = new JourneyContext();
508
+ defaultContext.identity.id = JourneyContext.DEFAULT_CONTEXT_ID;
509
+ JourneyContext.putContext(session, defaultContext);
510
+ }
511
+ }
512
+ /**
513
+ * Validate the format of a context ID, i.e. "default" or a uuid
514
+ * eg 00000000-0000-0000-0000-000000000000
515
+ * eg 123e4567-e89b-12d3-a456-426614174000
516
+ *
517
+ * @param {string} id Context ID
518
+ * @returns {string} Original ID if it's valid
519
+ * @throws {TypeError} When id is not a valid type
520
+ * @throws {SyntaxError} When id is not a valid uuid format
521
+ */
522
+ static validateContextId(id) {
523
+ if (id === JourneyContext.DEFAULT_CONTEXT_ID) {
524
+ return JourneyContext.DEFAULT_CONTEXT_ID;
525
+ }
526
+ if (typeof id !== 'string') {
527
+ throw new TypeError('Context ID must be a string');
528
+ }
529
+ else if (!(0, uuid_1.validate)(id)) {
530
+ throw new SyntaxError('Context ID is not in the correct uuid format');
531
+ }
532
+ return id;
533
+ }
534
+ /**
535
+ * Lookup context from session using the ID.
536
+ *
537
+ * @param {object} session Request session
538
+ * @param {string} id Context ID
539
+ * @returns {JourneyContext} The discovered JourneyContext instance
540
+ */
541
+ static getContextById(session, id) {
542
+ if (has(session === null || session === void 0 ? void 0 : session.journeyContextList, id)) {
543
+ return JourneyContext.fromObject(session.journeyContextList[id]);
544
+ }
545
+ return undefined;
546
+ }
547
+ /**
548
+ * Lookup context from session using the name.
549
+ *
550
+ * @param {object} session Request session
551
+ * @param {string} name Context name
552
+ * @returns {JourneyContext} The discovered JourneyContext instance
553
+ */
554
+ static getContextByName(session, name) {
555
+ if (session) {
556
+ const context = Object.values(session.journeyContextList).find((c) => (c.identity.name === name));
557
+ if (context) {
558
+ return JourneyContext.fromObject(context);
559
+ }
560
+ }
561
+ return undefined;
562
+ }
563
+ /**
564
+ * Lookup contexts from session using the tag.
565
+ *
566
+ * @param {object} session Request session
567
+ * @param {string} tag Context tag
568
+ * @returns {Array<JourneyContext>} The discovered JourneyContext instance
569
+ */
570
+ static getContextsByTag(session, tag) {
571
+ if (session) {
572
+ return Object.values(session.journeyContextList).filter((c) => { var _a; return ((_a = c.identity.tags) === null || _a === void 0 ? void 0 : _a.includes(tag)); }).map((c) => (JourneyContext.fromObject(c)));
573
+ }
574
+ return undefined;
575
+ }
576
+ /**
577
+ * Return all contexts currently stored in the session.
578
+ *
579
+ * @param {object} session Request session
580
+ * @returns {Array} Array of contexts
581
+ */
582
+ static getContexts(session) {
583
+ if (has(session, 'journeyContextList')) {
584
+ return Object.values(session.journeyContextList).map((contextObj) => (JourneyContext.fromObject(contextObj)));
585
+ }
586
+ return [];
587
+ }
588
+ /**
589
+ * Put context back into the session store.
590
+ *
591
+ * @param {object} session Request session
592
+ * @param {JourneyContext} context Context
593
+ * @returns {void}
594
+ * @throws {TypeError} When isession is not a valid type, or context has no ID
595
+ */
596
+ static putContext(session, context) {
597
+ if (!isObject(session)) {
598
+ throw new TypeError('Session must be an object');
599
+ }
600
+ else if (!(context instanceof JourneyContext)) {
601
+ throw new TypeError('Context must be a valid JourneyContext');
602
+ }
603
+ else if (context.identity.id === undefined) {
604
+ throw new TypeError('Context must have an ID before storing in session');
605
+ }
606
+ // Initialise the session if necessary
607
+ if (!has(session, 'journeyContextList')) {
608
+ JourneyContext.initContextStore(session);
609
+ }
610
+ // Apply context events
611
+ context.applyEventListeners({
612
+ event: 'waypoint-change',
613
+ previousContextObject: session.journeyContextList[context.identity.id],
614
+ });
615
+ context.applyEventListeners({
616
+ event: 'context-change',
617
+ previousContextObject: session.journeyContextList[context.identity.id],
618
+ });
619
+ /* eslint-disable-next-line no-param-reassign */
620
+ session.journeyContextList[context.identity.id] = context.toObject();
621
+ }
622
+ /**
623
+ * Remove a context from the session store.
624
+ *
625
+ * @param {object} session Request session
626
+ * @param {JourneyContext} context Context
627
+ * @returns {void}
628
+ */
629
+ static removeContext(session, context) {
630
+ if (context instanceof JourneyContext) {
631
+ JourneyContext.removeContextById(session, context.identity.id);
632
+ }
633
+ }
634
+ static removeContextById(session, id) {
635
+ if (session && has(session.journeyContextList, id)) {
636
+ /* eslint-disable-next-line no-param-reassign */
637
+ delete session.journeyContextList[id];
638
+ }
639
+ }
640
+ static removeContextByName(session, name) {
641
+ JourneyContext.removeContext(session, JourneyContext.getContextByName(session, name));
642
+ }
643
+ static removeContextsByTag(session, tag) {
644
+ JourneyContext.getContextsByTag(session, tag).forEach((c) => JourneyContext.removeContext(session, c));
645
+ }
646
+ static removeContexts(session) {
647
+ JourneyContext.getContexts(session).forEach((c) => JourneyContext.removeContext(session, c));
648
+ }
649
+ static extractContextFromRequest(req) {
650
+ JourneyContext.initContextStore(req.session);
651
+ let contextId;
652
+ if (has(req.query, 'contextid')) {
653
+ log.trace('Context ID found in req.query.contextid');
654
+ contextId = String(req.query.contextid);
655
+ }
656
+ else if (has(req === null || req === void 0 ? void 0 : req.body, 'contextid')) {
657
+ log.trace('Context ID found in req.body.contextid');
658
+ contextId = String(req.body.contextid);
659
+ }
660
+ else {
661
+ log.trace('Context ID not specified or not found; will attempt to use default');
662
+ contextId = JourneyContext.DEFAULT_CONTEXT_ID;
663
+ }
664
+ try {
665
+ contextId = JourneyContext.validateContextId(contextId);
666
+ const context = JourneyContext.getContextById(req.session, contextId);
667
+ if (!context) {
668
+ throw (new Error(`Could not find a context with id, ${contextId}`));
669
+ }
670
+ return context;
671
+ }
672
+ catch (err) {
673
+ log.debug(err.message);
674
+ log.trace('Falling back to default context');
675
+ return JourneyContext.getContextById(req.session, JourneyContext.DEFAULT_CONTEXT_ID);
676
+ }
677
+ }
678
+ }
679
+ exports.default = JourneyContext;
680
+ _JourneyContext_data = new WeakMap(), _JourneyContext_validation = new WeakMap(), _JourneyContext_nav = new WeakMap(), _JourneyContext_identity = new WeakMap(), _JourneyContext_eventListeners = new WeakMap(), _JourneyContext_eventListenerPreState = new WeakMap();
681
+ JourneyContext.DEFAULT_CONTEXT_ID = 'default';