@ministryofjustice/hmpps-connect-dps-components 2.1.0 → 3.0.0-beta.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.
Files changed (61) hide show
  1. package/README.md +26 -1
  2. package/dist/allocationService.d.ts +15 -0
  3. package/dist/allocationService.test.d.ts +1 -0
  4. package/dist/caseLoadService.d.ts +14 -2
  5. package/dist/caseLoadService.test.d.ts +1 -0
  6. package/dist/componentsService.d.ts +15 -1
  7. package/dist/componentsService.test.d.ts +1 -0
  8. package/dist/data/allocationsApi/allocationsApiClient.d.ts +10 -0
  9. package/dist/data/componentApi/componentApiClient.d.ts +6 -6
  10. package/dist/data/prisonApi/prisonApiClient.d.ts +6 -6
  11. package/dist/index.cjs +246 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.d.ts +152 -45
  14. package/dist/index.esm.js +242 -0
  15. package/dist/index.esm.js.map +1 -0
  16. package/dist/middleware/getFrontendComponents.d.ts +9 -0
  17. package/dist/types/AllocationJobResponsibility.d.ts +1 -0
  18. package/dist/types/ConnectDpsComponentLogger.d.ts +2 -0
  19. package/dist/types/HeaderFooterSharedData.d.ts +2 -0
  20. package/dist/types/HmppsUser.d.ts +2 -0
  21. package/dist/types/public/middleware/index.d.ts +1 -0
  22. package/dist/types/public/services/index.d.ts +3 -0
  23. package/dist/utils/fallbacks.d.ts +1 -1
  24. package/dist/utils/updateCsp.d.ts +1 -1
  25. package/dist/utils/updateCsp.test.d.ts +1 -0
  26. package/package.json +39 -52
  27. package/dist/caseLoadService.js +0 -52
  28. package/dist/caseLoadService.js.map +0 -1
  29. package/dist/componentsService.js +0 -61
  30. package/dist/componentsService.js.map +0 -1
  31. package/dist/config.d.ts +0 -11
  32. package/dist/config.js +0 -25
  33. package/dist/config.js.map +0 -1
  34. package/dist/data/componentApi/componentApiClient.js +0 -23
  35. package/dist/data/componentApi/componentApiClient.js.map +0 -1
  36. package/dist/data/prisonApi/prisonApiClient.js +0 -24
  37. package/dist/data/prisonApi/prisonApiClient.js.map +0 -1
  38. package/dist/index.js +0 -51
  39. package/dist/index.js.map +0 -1
  40. package/dist/types/AvailableComponent.js +0 -3
  41. package/dist/types/AvailableComponent.js.map +0 -1
  42. package/dist/types/CaseLoad.js +0 -3
  43. package/dist/types/CaseLoad.js.map +0 -1
  44. package/dist/types/CaseLoadOptions.js +0 -3
  45. package/dist/types/CaseLoadOptions.js.map +0 -1
  46. package/dist/types/Component.js +0 -3
  47. package/dist/types/Component.js.map +0 -1
  48. package/dist/types/HeaderFooterSharedData.js +0 -3
  49. package/dist/types/HeaderFooterSharedData.js.map +0 -1
  50. package/dist/types/HmppsUser.js +0 -3
  51. package/dist/types/HmppsUser.js.map +0 -1
  52. package/dist/types/RequestOptions.js +0 -3
  53. package/dist/types/RequestOptions.js.map +0 -1
  54. package/dist/types/Service.js +0 -3
  55. package/dist/types/Service.js.map +0 -1
  56. package/dist/types/TimeoutOptions.js +0 -3
  57. package/dist/types/TimeoutOptions.js.map +0 -1
  58. package/dist/utils/fallbacks.js +0 -36
  59. package/dist/utils/fallbacks.js.map +0 -1
  60. package/dist/utils/updateCsp.js +0 -25
  61. package/dist/utils/updateCsp.js.map +0 -1
package/README.md CHANGED
@@ -159,6 +159,7 @@ This includes:
159
159
  - activeCaseLoad (the active caseload of the user)
160
160
  - caseLoads (all caseloads the user has access to)
161
161
  - services (information on services the user has access to used for global navigation)
162
+ - allocationJobResponsibilities (the allocation policy codes the user has the associated job responsibility for. Allocation policy codes are: `KEY_WORKER`, meaning the user is a key worker and `PERSONAL_OFFICER`, meaning the user is a personal officer.)
162
163
 
163
164
  This can be useful e.g. for when your service needs access to activeCaseLoad information to prevent extra calls to the
164
165
  api and takes advantage of the caching that the micro frontend api does.
@@ -170,7 +171,7 @@ optional middleware which populates:
170
171
  - `res.locals.user.caseLoads` with all case loads the user has access to
171
172
  - `res.locals.user.activeCaseLoad` with the active case load of the user
172
173
  - `res.locals.user.activeCaseLoadId` with the id of the active case load
173
-
174
+
174
175
  It uses the `sharedData` object if it is present and caches in `req.session` so that any subsequent routes that do not
175
176
  use the component middleware can still use the data. If there is no data in the cache, it will fall back to making a
176
177
  call to Prison API to retrieve the data using the user token.
@@ -186,6 +187,30 @@ Again there are a [number of options](./src/index.ts) available depending on you
186
187
  This middleware checks the `res.locals.user.authSource` so ensure that any mock auth data used in tests includes
187
188
  `auth_source: 'nomis'` in the response.
188
189
 
190
+ ### Populating res.locals.user with the shared allocation job responsibilities
191
+
192
+ This library also provides an optional middleware which populates:
193
+ - `res.locals.user.allocationJobResponsibilities` the allocation policy codes the user has the associated job responsibility for. Allocation policy codes are: `KEY_WORKER`, meaning the user is a key worker and `PERSONAL_OFFICER`, meaning the user is a personal officer.
194
+
195
+ Similar to shared case load data, it uses the `sharedData` object if it is present and caches in `req.session` so that any subsequent routes that do not
196
+ use the component middleware can still use the data. If there is no data in the cache, it will fall back to making a
197
+ call to Allocations API to retrieve the data using the user token.
198
+
199
+ To enable this, add the middleware after the component middleware as follows:
200
+
201
+ ```javascript
202
+ app.use(dpsComponents.retrieveAllocationJobResponsibilities({ logger }))
203
+ ```
204
+
205
+ This should go after `dpsComponents.retrieveCaseLoadData` so that `res.locals.user.activeCaseLoadId` will be populated.
206
+ It also requires `ALLOCATIONS_API_URL` to be configured in the environment variables.
207
+
208
+ Again there are a [number of options](./src/index.ts) available depending on your requirements.
209
+
210
+ This middleware checks the `res.locals.user.authSource` so ensure that any mock auth data used in tests includes
211
+ `auth_source: 'nomis'` in the response. It also checks the `res.locals.user.activeCaseLoadId`, which is required for retrieving allocation job responsibilities.
212
+
213
+
189
214
  ### Note
190
215
 
191
216
  In the event of a failure to retrieve the components, the package will populate the html fields with fallback components.
@@ -0,0 +1,15 @@
1
+ import { type RequestHandler } from 'express';
2
+ import { ApiConfig, AuthenticationClient } from '@ministryofjustice/hmpps-rest-client';
3
+ import AllocationsApiClient from './data/allocationsApi/allocationsApiClient';
4
+ import { ConnectDpsComponentLogger } from './types/ConnectDpsComponentLogger';
5
+ export default class AllocationService {
6
+ private readonly logger;
7
+ private readonly allocationsApiClient;
8
+ constructor(logger: ConnectDpsComponentLogger, allocationsApiClient: AllocationsApiClient);
9
+ static create({ logger, allocationsApiConfig, authenticationClient, }: {
10
+ logger?: ConnectDpsComponentLogger;
11
+ allocationsApiConfig: ApiConfig;
12
+ authenticationClient: AuthenticationClient;
13
+ }): AllocationService;
14
+ retrieveAllocationJobResponsibilities(): RequestHandler;
15
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,15 @@
1
1
  import { type RequestHandler } from 'express';
2
- import CaseLoadOptions from './types/CaseLoadOptions';
3
- export default function retrieveCaseLoadData(caseLoadOptions?: CaseLoadOptions): RequestHandler;
2
+ import { ApiConfig, AuthenticationClient } from '@ministryofjustice/hmpps-rest-client';
3
+ import PrisonApiClient from './data/prisonApi/prisonApiClient';
4
+ import { ConnectDpsComponentLogger } from './types/ConnectDpsComponentLogger';
5
+ export default class CaseLoadService {
6
+ private readonly logger;
7
+ private readonly prisonApiClient;
8
+ constructor(logger: ConnectDpsComponentLogger, prisonApiClient: PrisonApiClient);
9
+ static create({ logger, prisonApiConfig, authenticationClient, }: {
10
+ logger?: ConnectDpsComponentLogger;
11
+ prisonApiConfig: ApiConfig;
12
+ authenticationClient: AuthenticationClient;
13
+ }): CaseLoadService;
14
+ retrieveCaseLoadData(): RequestHandler;
15
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,3 +1,17 @@
1
1
  import { type RequestHandler } from 'express';
2
+ import { ApiConfig, AuthenticationClient } from '@ministryofjustice/hmpps-rest-client';
3
+ import ComponentApiClient from './data/componentApi/componentApiClient';
2
4
  import RequestOptions from './types/RequestOptions';
3
- export default function getFrontendComponents(requestOptions?: RequestOptions): RequestHandler;
5
+ import { ConnectDpsComponentLogger } from './types/ConnectDpsComponentLogger';
6
+ export default class ComponentsService {
7
+ private readonly logger;
8
+ private readonly componentApiConfig;
9
+ private readonly componentApiClient;
10
+ constructor(logger: ConnectDpsComponentLogger, componentApiConfig: ApiConfig, componentApiClient: ComponentApiClient);
11
+ static create({ logger, componentApiConfig, authenticationClient, }: {
12
+ logger?: ConnectDpsComponentLogger;
13
+ componentApiConfig: ApiConfig;
14
+ authenticationClient: AuthenticationClient;
15
+ }): ComponentsService;
16
+ getFrontendComponents(requestOptions: RequestOptions): RequestHandler;
17
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ import { ApiConfig, AuthenticationClient, RestClient } from '@ministryofjustice/hmpps-rest-client';
2
+ import { PrisonUser } from '../../types/HmppsUser';
3
+ import { AllocationJobResponsibility } from '../../types/AllocationJobResponsibility';
4
+ import { ConnectDpsComponentLogger } from '../../types/ConnectDpsComponentLogger';
5
+ export default class AllocationsApiClient extends RestClient {
6
+ constructor(logger: ConnectDpsComponentLogger, config: ApiConfig, authenticationClient: AuthenticationClient);
7
+ getStaffAllocationPolicies(user: PrisonUser): Promise<{
8
+ policies: AllocationJobResponsibility[];
9
+ }>;
10
+ }
@@ -1,12 +1,12 @@
1
- import type bunyan from 'bunyan';
1
+ import { ApiConfig, AuthenticationClient, RestClient } from '@ministryofjustice/hmpps-rest-client';
2
2
  import AvailableComponent from '../../types/AvailableComponent';
3
3
  import Component from '../../types/Component';
4
- import TimeoutOptions from '../../types/TimeoutOptions';
4
+ import { ConnectDpsComponentLogger } from '../../types/ConnectDpsComponentLogger';
5
5
  import { ComponentsSharedData } from '../../types/HeaderFooterSharedData';
6
6
  export type ComponentsApiResponse<T extends AvailableComponent[]> = Record<T[number], Component> & {
7
7
  meta: ComponentsSharedData[T[number]];
8
8
  };
9
- declare const _default: {
10
- getComponents<T extends AvailableComponent[]>(userToken: string, timeoutOptions: TimeoutOptions, log: bunyan | typeof console): Promise<ComponentsApiResponse<T>>;
11
- };
12
- export default _default;
9
+ export default class ComponentApiClient extends RestClient {
10
+ constructor(logger: ConnectDpsComponentLogger, config: ApiConfig, authenticationClient: AuthenticationClient);
11
+ getComponents<T extends AvailableComponent[]>(userToken: string): Promise<ComponentsApiResponse<T>>;
12
+ }
@@ -1,7 +1,7 @@
1
- import type bunyan from 'bunyan';
2
- import TimeoutOptions from '../../types/TimeoutOptions';
1
+ import { ApiConfig, AuthenticationClient, RestClient } from '@ministryofjustice/hmpps-rest-client';
3
2
  import CaseLoad from '../../types/CaseLoad';
4
- declare const _default: {
5
- getUserCaseLoads(userToken: string, timeoutOptions: TimeoutOptions, log: bunyan | typeof console): Promise<CaseLoad[]>;
6
- };
7
- export default _default;
3
+ import { ConnectDpsComponentLogger } from '../../types/ConnectDpsComponentLogger';
4
+ export default class PrisonApiClient extends RestClient {
5
+ constructor(logger: ConnectDpsComponentLogger, config: ApiConfig, authenticationClient: AuthenticationClient);
6
+ getUserCaseLoads(userToken: string): Promise<CaseLoad[]>;
7
+ }
package/dist/index.cjs ADDED
@@ -0,0 +1,246 @@
1
+ 'use strict';
2
+
3
+ var hmppsRestClient = require('@ministryofjustice/hmpps-rest-client');
4
+ var nunjucks = require('nunjucks');
5
+
6
+ class ComponentApiClient extends hmppsRestClient.RestClient {
7
+ constructor(logger, config, authenticationClient) {
8
+ super('Component API Client', config, logger, authenticationClient);
9
+ }
10
+ async getComponents(userToken) {
11
+ return this.get({
12
+ path: `/components`,
13
+ query: 'component=header&component=footer',
14
+ headers: { 'x-user-token': userToken },
15
+ }, hmppsRestClient.asSystem());
16
+ }
17
+ }
18
+
19
+ function getFallbackHeader(user, requestOptions) {
20
+ const { dpsUrl, environmentName, authUrl, supportUrl } = requestOptions;
21
+ return nunjucks.render('dpsComponents/header-bar.njk', {
22
+ isPrisonUser: !user || user.authSource === 'nomis',
23
+ user,
24
+ dpsUrl,
25
+ environmentName,
26
+ authUrl,
27
+ supportUrl,
28
+ name: initialiseName(user?.displayName),
29
+ });
30
+ }
31
+ function getFallbackFooter(user, requestOptions) {
32
+ const { supportUrl, authUrl } = requestOptions;
33
+ return nunjucks.render('dpsComponents/footer.njk', {
34
+ isPrisonUser: !user || user.authSource === 'nomis',
35
+ supportUrl,
36
+ authUrl,
37
+ });
38
+ }
39
+ function initialiseName(fullName) {
40
+ if (!fullName)
41
+ return null;
42
+ const array = fullName.split(' ');
43
+ return `${array[0][0]}. ${array.reverse()[0]}`;
44
+ }
45
+
46
+ function updateCsp(feComponentsUrl, res) {
47
+ const csp = res.getHeaders()['content-security-policy'];
48
+ const allDirectives = csp?.split(';') ?? [];
49
+ const directivesToUpdate = ['script-src', 'style-src', 'img-src', 'font-src'];
50
+ const updatedCspDirectives = allDirectives.map(directive => {
51
+ // if directive is not in cspToUpdate or includes fe components url already return as is
52
+ if (directive.includes(feComponentsUrl) || !directivesToUpdate.some(p => directive.includes(`${p} `)))
53
+ return directive;
54
+ // if directive is in cspToUpdate and does not have fe components url, add in
55
+ return `${directive} ${feComponentsUrl}`;
56
+ });
57
+ const requiredAndNotPresent = directivesToUpdate
58
+ .filter(p => !updatedCspDirectives.find(directive => directive.includes(`${p} `)))
59
+ .map(p => `${p} 'self' ${feComponentsUrl}`);
60
+ res.set('content-security-policy', [...updatedCspDirectives, ...requiredAndNotPresent].join(';'));
61
+ }
62
+
63
+ const defaultOptions = {
64
+ logger: console,
65
+ timeoutOptions: { response: 2500, deadline: 2500 },
66
+ includeSharedData: false,
67
+ useFallbacksByDefault: false,
68
+ };
69
+ class ComponentsService {
70
+ logger;
71
+ componentApiConfig;
72
+ componentApiClient;
73
+ constructor(logger, componentApiConfig, componentApiClient) {
74
+ this.logger = logger;
75
+ this.componentApiConfig = componentApiConfig;
76
+ this.componentApiClient = componentApiClient;
77
+ }
78
+ static create({ logger = console, componentApiConfig, authenticationClient, }) {
79
+ return new ComponentsService(logger, componentApiConfig, new ComponentApiClient(logger, componentApiConfig, authenticationClient));
80
+ }
81
+ getFrontendComponents(requestOptions) {
82
+ const requestOptionsWithDefaults = {
83
+ ...defaultOptions,
84
+ ...requestOptions,
85
+ };
86
+ const { includeSharedData, useFallbacksByDefault } = {
87
+ ...defaultOptions,
88
+ ...requestOptions,
89
+ };
90
+ return async (_req, res, next) => {
91
+ const useFallbacks = (user) => {
92
+ res.locals.feComponents = {
93
+ header: getFallbackHeader(user, requestOptionsWithDefaults),
94
+ footer: getFallbackFooter(user, requestOptionsWithDefaults),
95
+ cssIncludes: [],
96
+ jsIncludes: [],
97
+ };
98
+ };
99
+ if (!res.locals.user) {
100
+ this.logger.info('Using fallback frontend components when no user in context');
101
+ useFallbacks(null);
102
+ return next();
103
+ }
104
+ if (useFallbacksByDefault) {
105
+ this.logger.info('Using fallback frontend components by default');
106
+ useFallbacks(res.locals.user);
107
+ return next();
108
+ }
109
+ try {
110
+ const { header, footer, meta } = await this.componentApiClient.getComponents(res.locals.user.token);
111
+ res.locals.feComponents = {
112
+ header: header.html,
113
+ footer: footer.html,
114
+ cssIncludes: [...header.css, ...footer.css],
115
+ jsIncludes: [...header.javascript, ...footer.javascript],
116
+ };
117
+ if (includeSharedData) {
118
+ res.locals.feComponents.sharedData = meta;
119
+ }
120
+ updateCsp(this.componentApiConfig.url, res);
121
+ return next();
122
+ }
123
+ catch (_error) {
124
+ this.logger.error('Failed to retrieve front end components, using fallbacks');
125
+ useFallbacks(res.locals.user);
126
+ return next();
127
+ }
128
+ };
129
+ }
130
+ }
131
+
132
+ class PrisonApiClient extends hmppsRestClient.RestClient {
133
+ constructor(logger, config, authenticationClient) {
134
+ super('Prison API Client', config, logger, authenticationClient);
135
+ }
136
+ async getUserCaseLoads(userToken) {
137
+ return this.get({
138
+ path: '/api/users/me/caseloads',
139
+ query: { allCaseloads: true },
140
+ }, hmppsRestClient.asUser(userToken));
141
+ }
142
+ }
143
+
144
+ class CaseLoadService {
145
+ logger;
146
+ prisonApiClient;
147
+ constructor(logger, prisonApiClient) {
148
+ this.logger = logger;
149
+ this.prisonApiClient = prisonApiClient;
150
+ }
151
+ static create({ logger = console, prisonApiConfig, authenticationClient, }) {
152
+ return new CaseLoadService(logger, new PrisonApiClient(logger, prisonApiConfig, authenticationClient));
153
+ }
154
+ retrieveCaseLoadData() {
155
+ return async (req, res, next) => {
156
+ if (!req.session)
157
+ throw new Error('User session required in order to cache case loads');
158
+ if (res.locals.user && res.locals.user.token && res.locals.user.authSource === 'nomis') {
159
+ try {
160
+ // Update cache with values from res.feComponents.sharedData if present
161
+ if (res.locals.feComponents && res.locals.feComponents.sharedData) {
162
+ req.session.caseLoads = res.locals.feComponents.sharedData.caseLoads;
163
+ req.session.activeCaseLoad = res.locals.feComponents.sharedData.activeCaseLoad;
164
+ req.session.activeCaseLoadId = res.locals.feComponents.sharedData.activeCaseLoad?.caseLoadId;
165
+ }
166
+ // If cache is empty, fetch data from Prison API
167
+ if (!req.session.caseLoads) {
168
+ this.logger.info(`Falling back to Prison API to retrieve case loads for: ${res.locals.user.username}`);
169
+ const userCaseLoads = await this.prisonApiClient.getUserCaseLoads(res.locals.user.token);
170
+ const caseLoads = userCaseLoads.filter(caseload => caseload.type !== 'APP');
171
+ const activeCaseLoad = caseLoads.find((caseLoad) => caseLoad.currentlyActive);
172
+ req.session.caseLoads = caseLoads;
173
+ req.session.activeCaseLoad = activeCaseLoad;
174
+ req.session.activeCaseLoadId = activeCaseLoad?.caseLoadId;
175
+ }
176
+ // Populate res.locals.user with values from cache
177
+ res.locals.user.caseLoads = req.session.caseLoads;
178
+ res.locals.user.activeCaseLoad = req.session.activeCaseLoad;
179
+ res.locals.user.activeCaseLoadId = req.session.activeCaseLoadId;
180
+ }
181
+ catch (error) {
182
+ this.logger.error(error, `Failed to retrieve case loads for: ${res.locals.user.username}`);
183
+ return next(error);
184
+ }
185
+ }
186
+ return next();
187
+ };
188
+ }
189
+ }
190
+
191
+ class AllocationsApiClient extends hmppsRestClient.RestClient {
192
+ constructor(logger, config, authenticationClient) {
193
+ super('Allocations API Client', config, logger, authenticationClient);
194
+ }
195
+ async getStaffAllocationPolicies(user) {
196
+ return this.get({
197
+ path: `/prisons/${user.activeCaseLoadId}/staff/${user.userId}/job-classifications`,
198
+ }, hmppsRestClient.asSystem());
199
+ }
200
+ }
201
+
202
+ class AllocationService {
203
+ logger;
204
+ allocationsApiClient;
205
+ constructor(logger, allocationsApiClient) {
206
+ this.logger = logger;
207
+ this.allocationsApiClient = allocationsApiClient;
208
+ }
209
+ static create({ logger = console, allocationsApiConfig, authenticationClient, }) {
210
+ return new AllocationService(logger, new AllocationsApiClient(logger, allocationsApiConfig, authenticationClient));
211
+ }
212
+ retrieveAllocationJobResponsibilities() {
213
+ return async (req, res, next) => {
214
+ if (!req.session)
215
+ throw new Error('User session required in order to cache allocation job responsibilities');
216
+ if (!res.locals.user.token)
217
+ throw new Error('Caseload details needs to be populated before retrieving allocation job responsibilities. Please run retrieveCaseLoadData before retrieveAllocationJobResponsibilities.');
218
+ if (res.locals.user && res.locals.user.authSource === 'nomis') {
219
+ try {
220
+ // Update cache with values from res.feComponents.sharedData if present
221
+ if (res.locals.feComponents && res.locals.feComponents.sharedData) {
222
+ req.session.allocationJobResponsibilities = res.locals.feComponents.sharedData.allocationJobResponsibilities;
223
+ }
224
+ // If cache is empty, fetch data from Prison API
225
+ if (!req.session.allocationJobResponsibilities) {
226
+ this.logger.info(`Falling back to Allocations API to retrieve job responsibilities for: ${res.locals.user.username}`);
227
+ const allocationPolicies = await this.allocationsApiClient.getStaffAllocationPolicies(res.locals.user);
228
+ req.session.allocationJobResponsibilities = allocationPolicies.policies;
229
+ }
230
+ // Populate res.locals.user with values from cache
231
+ res.locals.user.allocationJobResponsibilities = req.session.allocationJobResponsibilities;
232
+ }
233
+ catch (error) {
234
+ this.logger.error(error, `Failed to retrieve allocation job responsibilities for: ${res.locals.user.username}`);
235
+ return next(error);
236
+ }
237
+ }
238
+ return next();
239
+ };
240
+ }
241
+ }
242
+
243
+ exports.AllocationService = AllocationService;
244
+ exports.CaseLoadService = CaseLoadService;
245
+ exports.ComponentsService = ComponentsService;
246
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","sources":["../src/data/componentApi/componentApiClient.ts","../src/utils/fallbacks.ts","../src/utils/updateCsp.ts","../src/componentsService.ts","../src/data/prisonApi/prisonApiClient.ts","../src/caseLoadService.ts","../src/data/allocationsApi/allocationsApiClient.ts","../src/allocationService.ts"],"sourcesContent":["import { ApiConfig, asSystem, AuthenticationClient, RestClient } from '@ministryofjustice/hmpps-rest-client'\nimport AvailableComponent from '../../types/AvailableComponent'\nimport Component from '../../types/Component'\nimport { ConnectDpsComponentLogger } from '../../types/ConnectDpsComponentLogger'\nimport { ComponentsSharedData } from '../../types/HeaderFooterSharedData'\n\nexport type ComponentsApiResponse<T extends AvailableComponent[]> = Record<T[number], Component> & {\n meta: ComponentsSharedData[T[number]] // TODO: rename 'meta' in the API response\n}\n\nexport default class ComponentApiClient extends RestClient {\n constructor(logger: ConnectDpsComponentLogger, config: ApiConfig, authenticationClient: AuthenticationClient) {\n super('Component API Client', config, logger, authenticationClient)\n }\n\n async getComponents<T extends AvailableComponent[]>(userToken: string): Promise<ComponentsApiResponse<T>> {\n return this.get<ComponentsApiResponse<T>>(\n {\n path: `/components`,\n query: 'component=header&component=footer',\n headers: { 'x-user-token': userToken },\n },\n asSystem(),\n )\n }\n}\n","import nunjucks from 'nunjucks'\nimport RequestOptions from '../types/RequestOptions'\nimport { HmppsUser } from '../types/HmppsUser'\n\nexport function getFallbackHeader(user: HmppsUser | null, requestOptions: RequestOptions): string {\n const { dpsUrl, environmentName, authUrl, supportUrl } = requestOptions\n\n return nunjucks.render('dpsComponents/header-bar.njk', {\n isPrisonUser: !user || user.authSource === 'nomis',\n user,\n dpsUrl,\n environmentName,\n authUrl,\n supportUrl,\n name: initialiseName(user?.displayName),\n })\n}\n\nexport function getFallbackFooter(user: HmppsUser | null, requestOptions: RequestOptions): string {\n const { supportUrl, authUrl } = requestOptions\n return nunjucks.render('dpsComponents/footer.njk', {\n isPrisonUser: !user || user.authSource === 'nomis',\n supportUrl,\n authUrl,\n })\n}\n\nfunction initialiseName(fullName?: string): string | null {\n if (!fullName) return null\n\n const array = fullName.split(' ')\n return `${array[0][0]}. ${array.reverse()[0]}`\n}\n","import { type Response } from 'express'\n\nexport default function updateCsp(feComponentsUrl: string, res: Response) {\n const csp = res.getHeaders()['content-security-policy']\n const allDirectives = csp?.split(';') ?? []\n const directivesToUpdate = ['script-src', 'style-src', 'img-src', 'font-src']\n\n const updatedCspDirectives = allDirectives.map(directive => {\n // if directive is not in cspToUpdate or includes fe components url already return as is\n if (directive.includes(feComponentsUrl as string) || !directivesToUpdate.some(p => directive.includes(`${p} `)))\n return directive\n\n // if directive is in cspToUpdate and does not have fe components url, add in\n return `${directive} ${feComponentsUrl}`\n })\n\n const requiredAndNotPresent = directivesToUpdate\n .filter(p => !updatedCspDirectives.find(directive => directive.includes(`${p} `)))\n .map(p => `${p} 'self' ${feComponentsUrl}`)\n\n res.set('content-security-policy', [...updatedCspDirectives, ...requiredAndNotPresent].join(';'))\n}\n","import { type NextFunction, type Request, type Response, type RequestHandler } from 'express'\nimport { ApiConfig, AuthenticationClient } from '@ministryofjustice/hmpps-rest-client'\nimport ComponentApiClient from './data/componentApi/componentApiClient'\nimport { getFallbackFooter, getFallbackHeader } from './utils/fallbacks'\nimport RequestOptions from './types/RequestOptions'\nimport updateCsp from './utils/updateCsp'\nimport { HmppsUser } from './types/HmppsUser'\nimport { ConnectDpsComponentLogger } from './types/ConnectDpsComponentLogger'\n\nconst defaultOptions: Partial<RequestOptions> = {\n logger: console,\n timeoutOptions: { response: 2500, deadline: 2500 },\n includeSharedData: false,\n useFallbacksByDefault: false,\n}\n\nexport default class ComponentsService {\n constructor(\n private readonly logger: ConnectDpsComponentLogger,\n private readonly componentApiConfig: ApiConfig,\n private readonly componentApiClient: ComponentApiClient,\n ) {}\n\n static create({\n logger = console,\n componentApiConfig,\n authenticationClient,\n }: {\n logger?: ConnectDpsComponentLogger\n componentApiConfig: ApiConfig\n authenticationClient: AuthenticationClient\n }) {\n return new ComponentsService(\n logger,\n componentApiConfig,\n new ComponentApiClient(logger, componentApiConfig, authenticationClient),\n )\n }\n\n getFrontendComponents(requestOptions: RequestOptions): RequestHandler {\n const requestOptionsWithDefaults = {\n ...defaultOptions,\n ...requestOptions,\n }\n const { includeSharedData, useFallbacksByDefault } = {\n ...defaultOptions,\n ...requestOptions,\n }\n\n return async (_req: Request, res: Response, next: NextFunction) => {\n const useFallbacks = (user: HmppsUser | null) => {\n res.locals.feComponents = {\n header: getFallbackHeader(user, requestOptionsWithDefaults),\n footer: getFallbackFooter(user, requestOptionsWithDefaults),\n cssIncludes: [],\n jsIncludes: [],\n }\n }\n\n if (!res.locals.user) {\n this.logger.info('Using fallback frontend components when no user in context')\n useFallbacks(null)\n return next()\n }\n\n if (useFallbacksByDefault) {\n this.logger.info('Using fallback frontend components by default')\n useFallbacks(res.locals.user)\n return next()\n }\n\n try {\n const { header, footer, meta } = await this.componentApiClient.getComponents(res.locals.user.token as string)\n\n res.locals.feComponents = {\n header: header.html,\n footer: footer.html,\n cssIncludes: [...header.css, ...footer.css],\n jsIncludes: [...header.javascript, ...footer.javascript],\n }\n\n if (includeSharedData) {\n res.locals.feComponents.sharedData = meta\n }\n\n updateCsp(this.componentApiConfig.url, res)\n\n return next()\n } catch (_error) {\n this.logger.error('Failed to retrieve front end components, using fallbacks')\n useFallbacks(res.locals.user)\n return next()\n }\n }\n }\n}\n","import { ApiConfig, asUser, AuthenticationClient, RestClient } from '@ministryofjustice/hmpps-rest-client'\nimport CaseLoad from '../../types/CaseLoad'\nimport { ConnectDpsComponentLogger } from '../../types/ConnectDpsComponentLogger'\n\nexport default class PrisonApiClient extends RestClient {\n constructor(logger: ConnectDpsComponentLogger, config: ApiConfig, authenticationClient: AuthenticationClient) {\n super('Prison API Client', config, logger, authenticationClient)\n }\n\n async getUserCaseLoads(userToken: string): Promise<CaseLoad[]> {\n return this.get<CaseLoad[]>(\n {\n path: '/api/users/me/caseloads',\n query: { allCaseloads: true },\n },\n asUser(userToken),\n )\n }\n}\n","import { type RequestHandler } from 'express'\nimport { ApiConfig, AuthenticationClient } from '@ministryofjustice/hmpps-rest-client'\nimport CaseLoad from './types/CaseLoad'\nimport PrisonApiClient from './data/prisonApi/prisonApiClient'\nimport { ConnectDpsComponentLogger } from './types/ConnectDpsComponentLogger'\n\nexport default class CaseLoadService {\n constructor(\n private readonly logger: ConnectDpsComponentLogger,\n private readonly prisonApiClient: PrisonApiClient,\n ) {}\n\n static create({\n logger = console,\n prisonApiConfig,\n authenticationClient,\n }: {\n logger?: ConnectDpsComponentLogger\n prisonApiConfig: ApiConfig\n authenticationClient: AuthenticationClient\n }) {\n return new CaseLoadService(logger, new PrisonApiClient(logger, prisonApiConfig, authenticationClient))\n }\n\n retrieveCaseLoadData(): RequestHandler {\n return async (req, res, next) => {\n if (!req.session) throw new Error('User session required in order to cache case loads')\n\n if (res.locals.user && res.locals.user.token && res.locals.user.authSource === 'nomis') {\n try {\n // Update cache with values from res.feComponents.sharedData if present\n if (res.locals.feComponents && res.locals.feComponents.sharedData) {\n req.session.caseLoads = res.locals.feComponents.sharedData.caseLoads\n req.session.activeCaseLoad = res.locals.feComponents.sharedData.activeCaseLoad\n req.session.activeCaseLoadId = res.locals.feComponents.sharedData.activeCaseLoad?.caseLoadId\n }\n\n // If cache is empty, fetch data from Prison API\n if (!req.session.caseLoads) {\n this.logger.info(`Falling back to Prison API to retrieve case loads for: ${res.locals.user.username}`)\n const userCaseLoads = await this.prisonApiClient.getUserCaseLoads(res.locals.user.token)\n const caseLoads = userCaseLoads.filter(caseload => caseload.type !== 'APP')\n const activeCaseLoad = caseLoads.find((caseLoad: CaseLoad) => caseLoad.currentlyActive)\n\n req.session.caseLoads = caseLoads\n req.session.activeCaseLoad = activeCaseLoad\n req.session.activeCaseLoadId = activeCaseLoad?.caseLoadId\n }\n\n // Populate res.locals.user with values from cache\n res.locals.user.caseLoads = req.session.caseLoads\n res.locals.user.activeCaseLoad = req.session.activeCaseLoad\n res.locals.user.activeCaseLoadId = req.session.activeCaseLoadId\n } catch (error) {\n this.logger.error(error, `Failed to retrieve case loads for: ${res.locals.user.username}`)\n return next(error)\n }\n }\n\n return next()\n }\n }\n}\n","import { ApiConfig, asSystem, AuthenticationClient, RestClient } from '@ministryofjustice/hmpps-rest-client'\nimport { PrisonUser } from '../../types/HmppsUser'\nimport { AllocationJobResponsibility } from '../../types/AllocationJobResponsibility'\nimport { ConnectDpsComponentLogger } from '../../types/ConnectDpsComponentLogger'\n\nexport default class AllocationsApiClient extends RestClient {\n constructor(logger: ConnectDpsComponentLogger, config: ApiConfig, authenticationClient: AuthenticationClient) {\n super('Allocations API Client', config, logger, authenticationClient)\n }\n\n async getStaffAllocationPolicies(user: PrisonUser): Promise<{ policies: AllocationJobResponsibility[] }> {\n return this.get<{ policies: AllocationJobResponsibility[] }>(\n {\n path: `/prisons/${user.activeCaseLoadId}/staff/${user.userId}/job-classifications`,\n },\n asSystem(),\n )\n }\n}\n","import { type RequestHandler } from 'express'\nimport { ApiConfig, AuthenticationClient } from '@ministryofjustice/hmpps-rest-client'\nimport AllocationsApiClient from './data/allocationsApi/allocationsApiClient'\nimport { ConnectDpsComponentLogger } from './types/ConnectDpsComponentLogger'\n\nexport default class AllocationService {\n constructor(\n private readonly logger: ConnectDpsComponentLogger,\n private readonly allocationsApiClient: AllocationsApiClient,\n ) {}\n\n static create({\n logger = console,\n allocationsApiConfig,\n authenticationClient,\n }: {\n logger?: ConnectDpsComponentLogger\n allocationsApiConfig: ApiConfig\n authenticationClient: AuthenticationClient\n }) {\n return new AllocationService(logger, new AllocationsApiClient(logger, allocationsApiConfig, authenticationClient))\n }\n\n public retrieveAllocationJobResponsibilities(): RequestHandler {\n return async (req, res, next) => {\n if (!req.session) throw new Error('User session required in order to cache allocation job responsibilities')\n if (!res.locals.user.token)\n throw new Error(\n 'Caseload details needs to be populated before retrieving allocation job responsibilities. Please run retrieveCaseLoadData before retrieveAllocationJobResponsibilities.',\n )\n\n if (res.locals.user && res.locals.user.authSource === 'nomis') {\n try {\n // Update cache with values from res.feComponents.sharedData if present\n if (res.locals.feComponents && res.locals.feComponents.sharedData) {\n req.session.allocationJobResponsibilities = res.locals.feComponents.sharedData.allocationJobResponsibilities\n }\n\n // If cache is empty, fetch data from Prison API\n if (!req.session.allocationJobResponsibilities) {\n this.logger.info(\n `Falling back to Allocations API to retrieve job responsibilities for: ${res.locals.user.username}`,\n )\n const allocationPolicies = await this.allocationsApiClient.getStaffAllocationPolicies(res.locals.user)\n req.session.allocationJobResponsibilities = allocationPolicies.policies\n }\n\n // Populate res.locals.user with values from cache\n res.locals.user.allocationJobResponsibilities = req.session.allocationJobResponsibilities\n } catch (error) {\n this.logger.error(\n error,\n `Failed to retrieve allocation job responsibilities for: ${res.locals.user.username}`,\n )\n return next(error)\n }\n }\n\n return next()\n }\n }\n}\n"],"names":["RestClient","asSystem","asUser"],"mappings":";;;;;AAUc,MAAO,kBAAmB,SAAQA,0BAAU,CAAA;AACxD,IAAA,WAAA,CAAY,MAAiC,EAAE,MAAiB,EAAE,oBAA0C,EAAA;QAC1G,KAAK,CAAC,sBAAsB,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,CAAC;IACrE;IAEA,MAAM,aAAa,CAAiC,SAAiB,EAAA;QACnE,OAAO,IAAI,CAAC,GAAG,CACb;AACE,YAAA,IAAI,EAAE,CAAA,WAAA,CAAa;AACnB,YAAA,KAAK,EAAE,mCAAmC;AAC1C,YAAA,OAAO,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE;SACvC,EACDC,wBAAQ,EAAE,CACX;IACH;AACD;;ACrBK,SAAU,iBAAiB,CAAC,IAAsB,EAAE,cAA8B,EAAA;IACtF,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,cAAc;AAEvE,IAAA,OAAO,QAAQ,CAAC,MAAM,CAAC,8BAA8B,EAAE;QACrD,YAAY,EAAE,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,KAAK,OAAO;QAClD,IAAI;QACJ,MAAM;QACN,eAAe;QACf,OAAO;QACP,UAAU;AACV,QAAA,IAAI,EAAE,cAAc,CAAC,IAAI,EAAE,WAAW,CAAC;AACxC,KAAA,CAAC;AACJ;AAEM,SAAU,iBAAiB,CAAC,IAAsB,EAAE,cAA8B,EAAA;AACtF,IAAA,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,cAAc;AAC9C,IAAA,OAAO,QAAQ,CAAC,MAAM,CAAC,0BAA0B,EAAE;QACjD,YAAY,EAAE,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,KAAK,OAAO;QAClD,UAAU;QACV,OAAO;AACR,KAAA,CAAC;AACJ;AAEA,SAAS,cAAc,CAAC,QAAiB,EAAA;AACvC,IAAA,IAAI,CAAC,QAAQ;AAAE,QAAA,OAAO,IAAI;IAE1B,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC;AACjC,IAAA,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA,EAAA,EAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE;AAChD;;AC9Bc,SAAU,SAAS,CAAC,eAAuB,EAAE,GAAa,EAAA;IACtE,MAAM,GAAG,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC,yBAAyB,CAAC;IACvD,MAAM,aAAa,GAAG,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE;IAC3C,MAAM,kBAAkB,GAAG,CAAC,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,CAAC;IAE7E,MAAM,oBAAoB,GAAG,aAAa,CAAC,GAAG,CAAC,SAAS,IAAG;;QAEzD,IAAI,SAAS,CAAC,QAAQ,CAAC,eAAyB,CAAC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAA,EAAG,CAAC,CAAA,CAAA,CAAG,CAAC,CAAC;AAC7G,YAAA,OAAO,SAAS;;AAGlB,QAAA,OAAO,CAAA,EAAG,SAAS,CAAA,CAAA,EAAI,eAAe,EAAE;AAC1C,IAAA,CAAC,CAAC;IAEF,MAAM,qBAAqB,GAAG;SAC3B,MAAM,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA,CAAA,CAAG,CAAC,CAAC;SAChF,GAAG,CAAC,CAAC,IAAI,CAAA,EAAG,CAAC,CAAA,QAAA,EAAW,eAAe,CAAA,CAAE,CAAC;AAE7C,IAAA,GAAG,CAAC,GAAG,CAAC,yBAAyB,EAAE,CAAC,GAAG,oBAAoB,EAAE,GAAG,qBAAqB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACnG;;ACZA,MAAM,cAAc,GAA4B;AAC9C,IAAA,MAAM,EAAE,OAAO;IACf,cAAc,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE;AAClD,IAAA,iBAAiB,EAAE,KAAK;AACxB,IAAA,qBAAqB,EAAE,KAAK;CAC7B;AAEa,MAAO,iBAAiB,CAAA;AAEjB,IAAA,MAAA;AACA,IAAA,kBAAA;AACA,IAAA,kBAAA;AAHnB,IAAA,WAAA,CACmB,MAAiC,EACjC,kBAA6B,EAC7B,kBAAsC,EAAA;QAFtC,IAAA,CAAA,MAAM,GAAN,MAAM;QACN,IAAA,CAAA,kBAAkB,GAAlB,kBAAkB;QAClB,IAAA,CAAA,kBAAkB,GAAlB,kBAAkB;IAClC;IAEH,OAAO,MAAM,CAAC,EACZ,MAAM,GAAG,OAAO,EAChB,kBAAkB,EAClB,oBAAoB,GAKrB,EAAA;AACC,QAAA,OAAO,IAAI,iBAAiB,CAC1B,MAAM,EACN,kBAAkB,EAClB,IAAI,kBAAkB,CAAC,MAAM,EAAE,kBAAkB,EAAE,oBAAoB,CAAC,CACzE;IACH;AAEA,IAAA,qBAAqB,CAAC,cAA8B,EAAA;AAClD,QAAA,MAAM,0BAA0B,GAAG;AACjC,YAAA,GAAG,cAAc;AACjB,YAAA,GAAG,cAAc;SAClB;AACD,QAAA,MAAM,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,GAAG;AACnD,YAAA,GAAG,cAAc;AACjB,YAAA,GAAG,cAAc;SAClB;QAED,OAAO,OAAO,IAAa,EAAE,GAAa,EAAE,IAAkB,KAAI;AAChE,YAAA,MAAM,YAAY,GAAG,CAAC,IAAsB,KAAI;AAC9C,gBAAA,GAAG,CAAC,MAAM,CAAC,YAAY,GAAG;AACxB,oBAAA,MAAM,EAAE,iBAAiB,CAAC,IAAI,EAAE,0BAA0B,CAAC;AAC3D,oBAAA,MAAM,EAAE,iBAAiB,CAAC,IAAI,EAAE,0BAA0B,CAAC;AAC3D,oBAAA,WAAW,EAAE,EAAE;AACf,oBAAA,UAAU,EAAE,EAAE;iBACf;AACH,YAAA,CAAC;AAED,YAAA,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE;AACpB,gBAAA,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,4DAA4D,CAAC;gBAC9E,YAAY,CAAC,IAAI,CAAC;gBAClB,OAAO,IAAI,EAAE;YACf;YAEA,IAAI,qBAAqB,EAAE;AACzB,gBAAA,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,+CAA+C,CAAC;AACjE,gBAAA,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;gBAC7B,OAAO,IAAI,EAAE;YACf;AAEA,YAAA,IAAI;gBACF,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC;AAE7G,gBAAA,GAAG,CAAC,MAAM,CAAC,YAAY,GAAG;oBACxB,MAAM,EAAE,MAAM,CAAC,IAAI;oBACnB,MAAM,EAAE,MAAM,CAAC,IAAI;oBACnB,WAAW,EAAE,CAAC,GAAG,MAAM,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC;oBAC3C,UAAU,EAAE,CAAC,GAAG,MAAM,CAAC,UAAU,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC;iBACzD;gBAED,IAAI,iBAAiB,EAAE;oBACrB,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,GAAG,IAAI;gBAC3C;gBAEA,SAAS,CAAC,IAAI,CAAC,kBAAkB,CAAC,GAAG,EAAE,GAAG,CAAC;gBAE3C,OAAO,IAAI,EAAE;YACf;YAAE,OAAO,MAAM,EAAE;AACf,gBAAA,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,0DAA0D,CAAC;AAC7E,gBAAA,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;gBAC7B,OAAO,IAAI,EAAE;YACf;AACF,QAAA,CAAC;IACH;AACD;;AC3Fa,MAAO,eAAgB,SAAQD,0BAAU,CAAA;AACrD,IAAA,WAAA,CAAY,MAAiC,EAAE,MAAiB,EAAE,oBAA0C,EAAA;QAC1G,KAAK,CAAC,mBAAmB,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,CAAC;IAClE;IAEA,MAAM,gBAAgB,CAAC,SAAiB,EAAA;QACtC,OAAO,IAAI,CAAC,GAAG,CACb;AACE,YAAA,IAAI,EAAE,yBAAyB;AAC/B,YAAA,KAAK,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE;AAC9B,SAAA,EACDE,sBAAM,CAAC,SAAS,CAAC,CAClB;IACH;AACD;;ACZa,MAAO,eAAe,CAAA;AAEf,IAAA,MAAA;AACA,IAAA,eAAA;IAFnB,WAAA,CACmB,MAAiC,EACjC,eAAgC,EAAA;QADhC,IAAA,CAAA,MAAM,GAAN,MAAM;QACN,IAAA,CAAA,eAAe,GAAf,eAAe;IAC/B;IAEH,OAAO,MAAM,CAAC,EACZ,MAAM,GAAG,OAAO,EAChB,eAAe,EACf,oBAAoB,GAKrB,EAAA;AACC,QAAA,OAAO,IAAI,eAAe,CAAC,MAAM,EAAE,IAAI,eAAe,CAAC,MAAM,EAAE,eAAe,EAAE,oBAAoB,CAAC,CAAC;IACxG;IAEA,oBAAoB,GAAA;QAClB,OAAO,OAAO,GAAG,EAAE,GAAG,EAAE,IAAI,KAAI;YAC9B,IAAI,CAAC,GAAG,CAAC,OAAO;AAAE,gBAAA,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC;YAEvF,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,KAAK,OAAO,EAAE;AACtF,gBAAA,IAAI;;AAEF,oBAAA,IAAI,GAAG,CAAC,MAAM,CAAC,YAAY,IAAI,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE;AACjE,wBAAA,GAAG,CAAC,OAAO,CAAC,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS;AACpE,wBAAA,GAAG,CAAC,OAAO,CAAC,cAAc,GAAG,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,cAAc;AAC9E,wBAAA,GAAG,CAAC,OAAO,CAAC,gBAAgB,GAAG,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,cAAc,EAAE,UAAU;oBAC9F;;AAGA,oBAAA,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE;AAC1B,wBAAA,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA,uDAAA,EAA0D,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAA,CAAE,CAAC;AACtG,wBAAA,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;AACxF,wBAAA,MAAM,SAAS,GAAG,aAAa,CAAC,MAAM,CAAC,QAAQ,IAAI,QAAQ,CAAC,IAAI,KAAK,KAAK,CAAC;AAC3E,wBAAA,MAAM,cAAc,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,QAAkB,KAAK,QAAQ,CAAC,eAAe,CAAC;AAEvF,wBAAA,GAAG,CAAC,OAAO,CAAC,SAAS,GAAG,SAAS;AACjC,wBAAA,GAAG,CAAC,OAAO,CAAC,cAAc,GAAG,cAAc;wBAC3C,GAAG,CAAC,OAAO,CAAC,gBAAgB,GAAG,cAAc,EAAE,UAAU;oBAC3D;;AAGA,oBAAA,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS;AACjD,oBAAA,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc;AAC3D,oBAAA,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,GAAG,GAAG,CAAC,OAAO,CAAC,gBAAgB;gBACjE;gBAAE,OAAO,KAAK,EAAE;AACd,oBAAA,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA,mCAAA,EAAsC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAA,CAAE,CAAC;AAC1F,oBAAA,OAAO,IAAI,CAAC,KAAK,CAAC;gBACpB;YACF;YAEA,OAAO,IAAI,EAAE;AACf,QAAA,CAAC;IACH;AACD;;ACzDa,MAAO,oBAAqB,SAAQF,0BAAU,CAAA;AAC1D,IAAA,WAAA,CAAY,MAAiC,EAAE,MAAiB,EAAE,oBAA0C,EAAA;QAC1G,KAAK,CAAC,wBAAwB,EAAE,MAAM,EAAE,MAAM,EAAE,oBAAoB,CAAC;IACvE;IAEA,MAAM,0BAA0B,CAAC,IAAgB,EAAA;QAC/C,OAAO,IAAI,CAAC,GAAG,CACb;YACE,IAAI,EAAE,YAAY,IAAI,CAAC,gBAAgB,CAAA,OAAA,EAAU,IAAI,CAAC,MAAM,CAAA,oBAAA,CAAsB;SACnF,EACDC,wBAAQ,EAAE,CACX;IACH;AACD;;ACba,MAAO,iBAAiB,CAAA;AAEjB,IAAA,MAAA;AACA,IAAA,oBAAA;IAFnB,WAAA,CACmB,MAAiC,EACjC,oBAA0C,EAAA;QAD1C,IAAA,CAAA,MAAM,GAAN,MAAM;QACN,IAAA,CAAA,oBAAoB,GAApB,oBAAoB;IACpC;IAEH,OAAO,MAAM,CAAC,EACZ,MAAM,GAAG,OAAO,EAChB,oBAAoB,EACpB,oBAAoB,GAKrB,EAAA;AACC,QAAA,OAAO,IAAI,iBAAiB,CAAC,MAAM,EAAE,IAAI,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,EAAE,oBAAoB,CAAC,CAAC;IACpH;IAEO,qCAAqC,GAAA;QAC1C,OAAO,OAAO,GAAG,EAAE,GAAG,EAAE,IAAI,KAAI;YAC9B,IAAI,CAAC,GAAG,CAAC,OAAO;AAAE,gBAAA,MAAM,IAAI,KAAK,CAAC,yEAAyE,CAAC;AAC5G,YAAA,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK;AACxB,gBAAA,MAAM,IAAI,KAAK,CACb,yKAAyK,CAC1K;AAEH,YAAA,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,KAAK,OAAO,EAAE;AAC7D,gBAAA,IAAI;;AAEF,oBAAA,IAAI,GAAG,CAAC,MAAM,CAAC,YAAY,IAAI,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE;AACjE,wBAAA,GAAG,CAAC,OAAO,CAAC,6BAA6B,GAAG,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,6BAA6B;oBAC9G;;AAGA,oBAAA,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,6BAA6B,EAAE;AAC9C,wBAAA,IAAI,CAAC,MAAM,CAAC,IAAI,CACd,CAAA,sEAAA,EAAyE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAA,CAAE,CACpG;AACD,wBAAA,MAAM,kBAAkB,GAAG,MAAM,IAAI,CAAC,oBAAoB,CAAC,0BAA0B,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;wBACtG,GAAG,CAAC,OAAO,CAAC,6BAA6B,GAAG,kBAAkB,CAAC,QAAQ;oBACzE;;AAGA,oBAAA,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,GAAG,GAAG,CAAC,OAAO,CAAC,6BAA6B;gBAC3F;gBAAE,OAAO,KAAK,EAAE;AACd,oBAAA,IAAI,CAAC,MAAM,CAAC,KAAK,CACf,KAAK,EACL,CAAA,wDAAA,EAA2D,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAA,CAAE,CACtF;AACD,oBAAA,OAAO,IAAI,CAAC,KAAK,CAAC;gBACpB;YACF;YAEA,OAAO,IAAI,EAAE;AACf,QAAA,CAAC;IACH;AACD;;;;;;"}