@defra/forms-engine-plugin 4.5.1 → 4.5.2
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/.server/config/index.js +1 -1
- package/.server/config/index.js.map +1 -1
- package/.server/server/plugins/engine/components/FileUploadField.js +2 -0
- package/.server/server/plugins/engine/components/FileUploadField.js.map +1 -1
- package/.server/server/plugins/engine/helpers.d.ts +2 -8
- package/.server/server/plugins/engine/helpers.js +0 -23
- package/.server/server/plugins/engine/helpers.js.map +1 -1
- package/.server/server/plugins/engine/routes/questions.js +2 -1
- package/.server/server/plugins/engine/routes/questions.js.map +1 -1
- package/.server/server/plugins/nunjucks/context.js +1 -2
- package/.server/server/plugins/nunjucks/context.js.map +1 -1
- package/.server/server/plugins/nunjucks/context.test.js +0 -36
- package/.server/server/plugins/nunjucks/context.test.js.map +1 -1
- package/package.json +3 -3
- package/src/config/index.ts +1 -1
- package/src/server/plugins/engine/components/FileUploadField.test.ts +21 -0
- package/src/server/plugins/engine/components/FileUploadField.ts +1 -0
- package/src/server/plugins/engine/helpers.test.ts +0 -74
- package/src/server/plugins/engine/helpers.ts +0 -27
- package/src/server/plugins/engine/routes/questions.ts +1 -1
- package/src/server/plugins/nunjucks/context.js +1 -3
- package/src/server/plugins/nunjucks/context.test.js +0 -37
- package/src/server/routes/dummy-api.test.ts +3 -1
package/.server/config/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["resolve","convict","isProduction","process","env","NODE_ENV","isDev","isTest","oneMinute","oneHour","config","appDir","format","String","default","import","meta","dirname","publicDir","port","doc","cdpEnvironment","enforceCsrf","Boolean","isDevelopment","serviceName","serviceVersion","nullable","feedbackLink","phaseTag","sessionTimeout","Number","confirmationSessionTimeout","sessionCookiePassword","sensitive","redis","host","username","password","keyPrefix","tracing","header","notifyTemplateId","notifyAPIKey","designerUrl","submissionUrl","uploaderUrl","uploaderBucketName","log","enabled","level","redact","Array","safelist","stagingPrefix","submissionEmailAddress","ordnanceSurveyApiKey","undefined","validate","allowed"],"sources":["../../src/config/index.ts"],"sourcesContent":["import { resolve } from 'node:path'\n\nimport convict, { type SchemaObj } from 'convict'\nimport { type LevelWithSilent } from 'pino'\n\nimport 'dotenv/config'\n\nconst isProduction = process.env.NODE_ENV === 'production'\nconst isDev = process.env.NODE_ENV !== 'production'\nconst isTest = process.env.NODE_ENV === 'test'\n\nconst oneMinute = 1000 * 60\nconst oneHour = oneMinute * 60\n\nexport const config = convict({\n appDir: {\n format: String,\n default: resolve(import.meta.dirname, '../server')\n },\n publicDir: {\n format: String,\n default: isTest\n ? resolve(import.meta.dirname, '../../test/fixtures')\n : resolve(import.meta.dirname, '../../.public')\n },\n\n /**\n * Server\n */\n port: {\n format: 'port',\n default: 3009,\n env: 'PORT'\n },\n env: {\n doc: 'The application environment.',\n format: ['production', 'development', 'test'],\n default: 'development',\n env: 'NODE_ENV'\n },\n cdpEnvironment: {\n doc: 'The CDP environment the app is currently in, with the addition of \"local\"',\n format: [\n 'local',\n 'infra-dev',\n 'management',\n 'dev',\n 'test',\n 'perf-test',\n 'ext-test',\n 'prod'\n ],\n default: 'local',\n env: 'ENVIRONMENT'\n },\n enforceCsrf: {\n format: Boolean,\n default: isProduction,\n env: 'ENFORCE_CSRF'\n },\n\n /**\n * Helper flags\n */\n isProduction: {\n doc: 'If this application running in the production environment',\n format: Boolean,\n default: isProduction\n },\n isDevelopment: {\n doc: 'If this application running in the development environment',\n format: Boolean,\n default: isDev\n },\n isTest: {\n doc: 'If this application running in the test environment',\n format: Boolean,\n default: isTest\n },\n\n /**\n * Service\n */\n serviceName: {\n doc: 'Applications Service Name',\n format: String,\n default: 'Digital Express Toolkit'\n },\n serviceVersion: {\n doc: 'The service version, this variable is injected into your docker container in CDP environments',\n format: String,\n nullable: true,\n default: '',\n env: 'SERVICE_VERSION'\n } as SchemaObj<string>,\n feedbackLink: {\n doc: 'Used in your phase banner. Can be a URL or more commonly mailto mailto:feedback@department.gov.uk',\n format: String,\n default: '',\n env: 'FEEDBACK_LINK'\n } as SchemaObj<string>,\n phaseTag: {\n format: String,\n default: 'beta', // Accepts \"alpha\" |\"beta\" | \"\"\n env: 'PHASE_TAG'\n },\n\n /**\n * Session storage\n * Redis integration is optional, but recommended for production environments.\n */\n sessionTimeout: {\n format: Number,\n default: oneHour * 24, // 1 day\n env: 'SESSION_TIMEOUT'\n },\n confirmationSessionTimeout: {\n format: Number,\n default: oneMinute * 20,\n env: 'CONFIRMATION_SESSION_TIMEOUT'\n },\n sessionCookiePassword: {\n format: String,\n default: '',\n sensitive: true,\n env: 'SESSION_COOKIE_PASSWORD'\n } as SchemaObj<string>,\n redis: {\n host: {\n doc: 'Redis cache host',\n format: String,\n default: '',\n env: 'REDIS_HOST'\n } as SchemaObj<string>,\n username: {\n doc: 'Redis cache username',\n format: String,\n default: '',\n env: 'REDIS_USERNAME'\n } as SchemaObj<string>,\n password: {\n doc: 'Redis cache password',\n format: '*',\n default: '',\n sensitive: true,\n env: 'REDIS_PASSWORD'\n } as SchemaObj<string>,\n keyPrefix: {\n doc: 'Redis cache key prefix name used to isolate the cached results across multiple clients',\n format: String,\n default: '',\n env: 'REDIS_KEY_PREFIX'\n } as SchemaObj<string>\n },\n tracing: {\n header: {\n doc: 'Tracing header name',\n format: String,\n default: 'x-cdp-request-id',\n env: 'TRACING_HEADER'\n } as SchemaObj<string>\n },\n\n /**\n * Email outputs\n * Email outputs will use notify to send an email to a single inbox.\n */\n notifyTemplateId: {\n format: String,\n default: '',\n env: 'NOTIFY_TEMPLATE_ID'\n } as SchemaObj<string>,\n notifyAPIKey: {\n format: String,\n default: '',\n env: 'NOTIFY_API_KEY'\n } as SchemaObj<string>,\n\n /**\n * API integrations\n */\n designerUrl: {\n format: String,\n default: 'http://localhost:3000',\n env: 'DESIGNER_URL'\n } as SchemaObj<string>,\n\n submissionUrl: {\n format: String,\n default: 'http://localhost:3002',\n env: 'SUBMISSION_URL'\n } as SchemaObj<string>,\n\n uploaderUrl: {\n format: String,\n default: 'http://localhost:7337',\n env: 'UPLOADER_URL'\n } as SchemaObj<string>,\n\n uploaderBucketName: {\n format: String,\n default: 'files',\n env: 'UPLOADER_BUCKET_NAME'\n },\n\n /**\n * Logging\n */\n log: {\n enabled: {\n doc: 'Is logging enabled',\n format: Boolean,\n default: !isTest,\n env: 'LOG_ENABLED'\n },\n level: {\n doc: 'Logging level',\n format: ['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent'],\n default: 'info',\n env: 'LOG_LEVEL'\n } as SchemaObj<LevelWithSilent>,\n format: {\n doc: 'Format to output logs in.',\n format: ['ecs', 'pino-pretty'],\n default: isProduction ? 'ecs' : 'pino-pretty',\n env: 'LOG_FORMAT'\n } as SchemaObj<'ecs' | 'pino-pretty'>,\n redact: {\n doc: 'Log paths to redact',\n format: Array,\n default: isProduction\n ? ['req.headers.authorization', 'req.headers.cookie', 'res.headers']\n : ['req', 'res', 'responseTime']\n }\n },\n\n safelist: {\n format: Array,\n default: ['61bca17e-fe74-40e0-9c15-a901ad120eca.mock.pstmn.io'],\n env: 'SAFELIST'\n },\n\n stagingPrefix: {\n doc: 'Prefix for staging files in S3',\n format: String,\n default: 'staging',\n env: 'STAGING_PREFIX'\n },\n\n submissionEmailAddress: {\n doc: 'Email address to send the form to (local devtool only)',\n format: String,\n default: '',\n env: 'SUBMISSION_EMAIL_ADDRESS'\n } as SchemaObj<string>,\n\n ordnanceSurveyApiKey: {\n doc: 'The ordnance survey api key used by the postcode lookup and maps plugin',\n format: String,\n nullable: true,\n default: undefined,\n env: 'ORDNANCE_SURVEY_API_KEY'\n } as SchemaObj<string | undefined>\n})\n\nconfig.validate({ allowed: 'strict' })\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"index.js","names":["resolve","convict","isProduction","process","env","NODE_ENV","isDev","isTest","oneMinute","oneHour","config","appDir","format","String","default","import","meta","dirname","publicDir","port","doc","cdpEnvironment","enforceCsrf","Boolean","isDevelopment","serviceName","serviceVersion","nullable","feedbackLink","phaseTag","sessionTimeout","Number","confirmationSessionTimeout","sessionCookiePassword","sensitive","redis","host","username","password","keyPrefix","tracing","header","notifyTemplateId","notifyAPIKey","designerUrl","submissionUrl","uploaderUrl","uploaderBucketName","log","enabled","level","redact","Array","safelist","stagingPrefix","submissionEmailAddress","ordnanceSurveyApiKey","undefined","validate","allowed"],"sources":["../../src/config/index.ts"],"sourcesContent":["import { resolve } from 'node:path'\n\nimport convict, { type SchemaObj } from 'convict'\nimport { type LevelWithSilent } from 'pino'\n\nimport 'dotenv/config'\n\nconst isProduction = process.env.NODE_ENV === 'production'\nconst isDev = process.env.NODE_ENV !== 'production'\nconst isTest = process.env.NODE_ENV === 'test'\n\nconst oneMinute = 1000 * 60\nconst oneHour = oneMinute * 60\n\nexport const config = convict({\n appDir: {\n format: String,\n default: resolve(import.meta.dirname, '../server')\n },\n publicDir: {\n format: String,\n default: isTest\n ? resolve(import.meta.dirname, '../../test/fixtures')\n : resolve(import.meta.dirname, '../../.public')\n },\n\n /**\n * Server\n */\n port: {\n format: 'port',\n default: 3009,\n env: 'PORT'\n },\n env: {\n doc: 'The application environment.',\n format: ['production', 'development', 'test'],\n default: 'development',\n env: 'NODE_ENV'\n },\n cdpEnvironment: {\n doc: 'The CDP environment the app is currently in, with the addition of \"local\"',\n format: [\n 'local',\n 'infra-dev',\n 'management',\n 'dev',\n 'test',\n 'perf-test',\n 'ext-test',\n 'prod'\n ],\n default: 'local',\n env: 'ENVIRONMENT'\n },\n enforceCsrf: {\n format: Boolean,\n default: true,\n env: 'ENFORCE_CSRF'\n },\n\n /**\n * Helper flags\n */\n isProduction: {\n doc: 'If this application running in the production environment',\n format: Boolean,\n default: isProduction\n },\n isDevelopment: {\n doc: 'If this application running in the development environment',\n format: Boolean,\n default: isDev\n },\n isTest: {\n doc: 'If this application running in the test environment',\n format: Boolean,\n default: isTest\n },\n\n /**\n * Service\n */\n serviceName: {\n doc: 'Applications Service Name',\n format: String,\n default: 'Digital Express Toolkit'\n },\n serviceVersion: {\n doc: 'The service version, this variable is injected into your docker container in CDP environments',\n format: String,\n nullable: true,\n default: '',\n env: 'SERVICE_VERSION'\n } as SchemaObj<string>,\n feedbackLink: {\n doc: 'Used in your phase banner. Can be a URL or more commonly mailto mailto:feedback@department.gov.uk',\n format: String,\n default: '',\n env: 'FEEDBACK_LINK'\n } as SchemaObj<string>,\n phaseTag: {\n format: String,\n default: 'beta', // Accepts \"alpha\" |\"beta\" | \"\"\n env: 'PHASE_TAG'\n },\n\n /**\n * Session storage\n * Redis integration is optional, but recommended for production environments.\n */\n sessionTimeout: {\n format: Number,\n default: oneHour * 24, // 1 day\n env: 'SESSION_TIMEOUT'\n },\n confirmationSessionTimeout: {\n format: Number,\n default: oneMinute * 20,\n env: 'CONFIRMATION_SESSION_TIMEOUT'\n },\n sessionCookiePassword: {\n format: String,\n default: '',\n sensitive: true,\n env: 'SESSION_COOKIE_PASSWORD'\n } as SchemaObj<string>,\n redis: {\n host: {\n doc: 'Redis cache host',\n format: String,\n default: '',\n env: 'REDIS_HOST'\n } as SchemaObj<string>,\n username: {\n doc: 'Redis cache username',\n format: String,\n default: '',\n env: 'REDIS_USERNAME'\n } as SchemaObj<string>,\n password: {\n doc: 'Redis cache password',\n format: '*',\n default: '',\n sensitive: true,\n env: 'REDIS_PASSWORD'\n } as SchemaObj<string>,\n keyPrefix: {\n doc: 'Redis cache key prefix name used to isolate the cached results across multiple clients',\n format: String,\n default: '',\n env: 'REDIS_KEY_PREFIX'\n } as SchemaObj<string>\n },\n tracing: {\n header: {\n doc: 'Tracing header name',\n format: String,\n default: 'x-cdp-request-id',\n env: 'TRACING_HEADER'\n } as SchemaObj<string>\n },\n\n /**\n * Email outputs\n * Email outputs will use notify to send an email to a single inbox.\n */\n notifyTemplateId: {\n format: String,\n default: '',\n env: 'NOTIFY_TEMPLATE_ID'\n } as SchemaObj<string>,\n notifyAPIKey: {\n format: String,\n default: '',\n env: 'NOTIFY_API_KEY'\n } as SchemaObj<string>,\n\n /**\n * API integrations\n */\n designerUrl: {\n format: String,\n default: 'http://localhost:3000',\n env: 'DESIGNER_URL'\n } as SchemaObj<string>,\n\n submissionUrl: {\n format: String,\n default: 'http://localhost:3002',\n env: 'SUBMISSION_URL'\n } as SchemaObj<string>,\n\n uploaderUrl: {\n format: String,\n default: 'http://localhost:7337',\n env: 'UPLOADER_URL'\n } as SchemaObj<string>,\n\n uploaderBucketName: {\n format: String,\n default: 'files',\n env: 'UPLOADER_BUCKET_NAME'\n },\n\n /**\n * Logging\n */\n log: {\n enabled: {\n doc: 'Is logging enabled',\n format: Boolean,\n default: !isTest,\n env: 'LOG_ENABLED'\n },\n level: {\n doc: 'Logging level',\n format: ['fatal', 'error', 'warn', 'info', 'debug', 'trace', 'silent'],\n default: 'info',\n env: 'LOG_LEVEL'\n } as SchemaObj<LevelWithSilent>,\n format: {\n doc: 'Format to output logs in.',\n format: ['ecs', 'pino-pretty'],\n default: isProduction ? 'ecs' : 'pino-pretty',\n env: 'LOG_FORMAT'\n } as SchemaObj<'ecs' | 'pino-pretty'>,\n redact: {\n doc: 'Log paths to redact',\n format: Array,\n default: isProduction\n ? ['req.headers.authorization', 'req.headers.cookie', 'res.headers']\n : ['req', 'res', 'responseTime']\n }\n },\n\n safelist: {\n format: Array,\n default: ['61bca17e-fe74-40e0-9c15-a901ad120eca.mock.pstmn.io'],\n env: 'SAFELIST'\n },\n\n stagingPrefix: {\n doc: 'Prefix for staging files in S3',\n format: String,\n default: 'staging',\n env: 'STAGING_PREFIX'\n },\n\n submissionEmailAddress: {\n doc: 'Email address to send the form to (local devtool only)',\n format: String,\n default: '',\n env: 'SUBMISSION_EMAIL_ADDRESS'\n } as SchemaObj<string>,\n\n ordnanceSurveyApiKey: {\n doc: 'The ordnance survey api key used by the postcode lookup and maps plugin',\n format: String,\n nullable: true,\n default: undefined,\n env: 'ORDNANCE_SURVEY_API_KEY'\n } as SchemaObj<string | undefined>\n})\n\nconfig.validate({ allowed: 'strict' })\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -242,6 +242,8 @@ export class FileUploadField extends FormComponent {
|
|
|
242
242
|
} catch (error) {
|
|
243
243
|
if (Boom.isBoom(error) && (error.output.statusCode === 403 ||
|
|
244
244
|
// Forbidden - retrieval key invalid
|
|
245
|
+
error.output.statusCode === 404 ||
|
|
246
|
+
// Not Found - file not found
|
|
245
247
|
error.output.statusCode === 410) // Gone - file expired (took to long to submit, etc)
|
|
246
248
|
) {
|
|
247
249
|
// Failed to persist files. We can't recover from this, the only real way we can recover the submissions is
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileUploadField.js","names":["Boom","joi","FormComponent","isUploadState","InvalidComponentStateError","messageTemplate","FileStatus","UploadStatus","render","uploadIdSchema","string","uuid","required","fileSchema","object","fileId","filename","contentLength","number","tempFileSchema","append","fileStatus","valid","complete","rejected","pending","errorMessage","optional","formFileSchema","metadataSchema","keys","retrievalKey","email","tempStatusSchema","uploadStatus","ready","metadata","form","file","array","items","single","numberOfRejectedFiles","formStatusSchema","itemSchema","uploadId","tempItemSchema","status","formItemSchema","FileUploadField","constructor","def","props","options","schema","formSchema","label","length","max","min","stateSchema","default","allow","getFormValueFromState","state","name","getFormValue","value","isValue","undefined","getDisplayStringFromFormValue","files","unit","getDisplayStringFromState","getContextValueFromFormValue","map","getContextValueFromState","getViewModel","payload","errors","query","page","isForceAccess","viewModel","attributes","id","filtered","filter","count","rows","item","index","tag","classes","text","valueHtml","view","context","params","trim","keyHtml","path","href","getHref","push","visuallyHiddenText","key","html","actions","accept","allowsMultiple","summaryList","multiple","upload","getAllPossibleErrors","onSubmit","request","notificationEmail","Error","app","model","services","formSubmissionService","values","initiatedRetrievalKey","persistFiles","error","isBoom","output","statusCode","baseErrors","type","template","selectRequired","advancedSettingsErrors"],"sources":["../../../../../src/server/plugins/engine/components/FileUploadField.ts"],"sourcesContent":["import {\n type FileUploadFieldComponent,\n type FormMetadata\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport joi, { type ArraySchema } from 'joi'\n\nimport {\n FormComponent,\n isUploadState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { InvalidComponentStateError } from '~/src/server/plugins/engine/pageControllers/errors.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n FileStatus,\n UploadStatus,\n type ErrorMessageTemplateList,\n type FileState,\n type FileUpload,\n type FileUploadMetadata,\n type FormContext,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionError,\n type FormSubmissionState,\n type SummaryList,\n type SummaryListAction,\n type SummaryListRow,\n type UploadState,\n type UploadStatusFileResponse,\n type UploadStatusResponse\n} from '~/src/server/plugins/engine/types.js'\nimport { render } from '~/src/server/plugins/nunjucks/index.js'\nimport {\n type FormQuery,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nexport const uploadIdSchema = joi.string().uuid().required()\n\nexport const fileSchema = joi\n .object<FileUpload>({\n fileId: joi.string().uuid().required(),\n filename: joi.string().required(),\n contentLength: joi.number().required()\n })\n .required()\n\nexport const tempFileSchema = fileSchema.append({\n fileStatus: joi\n .string()\n .valid(FileStatus.complete, FileStatus.rejected, FileStatus.pending)\n .required(),\n errorMessage: joi.string().optional()\n})\n\nexport const formFileSchema = fileSchema.append({\n fileStatus: joi.string().valid(FileStatus.complete).required()\n})\n\nexport const metadataSchema = joi\n .object<FileUploadMetadata>()\n .keys({\n retrievalKey: joi.string().email().required()\n })\n .required()\n\nexport const tempStatusSchema = joi\n .object<UploadStatusFileResponse>({\n uploadStatus: joi\n .string()\n .valid(UploadStatus.ready, UploadStatus.pending)\n .required(),\n metadata: metadataSchema,\n form: joi\n .object()\n .required()\n .keys({\n file: joi.array().items(tempFileSchema).single().required()\n }),\n numberOfRejectedFiles: joi.number().optional()\n })\n .required()\n\nexport const formStatusSchema = joi\n .object<UploadStatusResponse>({\n uploadStatus: joi.string().valid(UploadStatus.ready).required(),\n metadata: metadataSchema,\n form: joi.object().required().keys({\n file: formFileSchema\n }),\n numberOfRejectedFiles: joi.number().valid(0).required()\n })\n .required()\n\nexport const itemSchema = joi.object<FileState>({\n uploadId: uploadIdSchema\n})\n\nexport const tempItemSchema = itemSchema.append({\n status: tempStatusSchema\n})\n\nexport const formItemSchema = itemSchema.append({\n status: formStatusSchema\n})\n\nexport class FileUploadField extends FormComponent {\n declare options: FileUploadFieldComponent['options']\n declare schema: FileUploadFieldComponent['schema']\n declare formSchema: ArraySchema<FileState>\n declare stateSchema: ArraySchema<FileState>\n\n constructor(\n def: FileUploadFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { options, schema } = def\n\n let formSchema = joi\n .array<FileState>()\n .label(this.label)\n .single()\n .required()\n\n if (options.required === false) {\n formSchema = formSchema.optional()\n }\n\n if (typeof schema.length !== 'number') {\n if (typeof schema.max === 'number') {\n formSchema = formSchema.max(schema.max)\n }\n\n if (typeof schema.min === 'number') {\n formSchema = formSchema.min(schema.min)\n } else if (options.required !== false) {\n formSchema = formSchema.min(1)\n }\n } else {\n formSchema = formSchema.length(schema.length)\n }\n\n this.formSchema = formSchema.items(formItemSchema)\n this.stateSchema = formSchema\n .items(formItemSchema)\n .default(null)\n .allow(null)\n\n this.options = options\n this.schema = schema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const { name } = this\n return this.getFormValue(state[name])\n }\n\n getFormValue(value?: FormStateValue | FormState) {\n return this.isValue(value) ? value : undefined\n }\n\n getDisplayStringFromFormValue(files: FileState[] | undefined): string {\n if (!files?.length) {\n return ''\n }\n\n const unit = files.length === 1 ? 'file' : 'files'\n return `Uploaded ${files.length} ${unit}`\n }\n\n getDisplayStringFromState(state: FormSubmissionState) {\n const files = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(files)\n }\n\n getContextValueFromFormValue(\n files: UploadState | undefined\n ): string[] | null {\n return files?.map(({ status }) => status.form.file.fileId) ?? null\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const files = this.getFormValueFromState(state)\n return this.getContextValueFromFormValue(files)\n }\n\n getViewModel(\n payload: FormPayload,\n errors?: FormSubmissionError[],\n query: FormQuery = {}\n ) {\n const { options, page, schema } = this\n\n // Allow preview URL direct access\n const isForceAccess = 'force' in query\n\n const viewModel = super.getViewModel(payload, errors)\n const { attributes, id, value } = viewModel\n\n const files = this.getFormValue(value) ?? []\n const filtered = files.filter(\n (file) => file.status.form.file.fileStatus === FileStatus.complete\n )\n const count = filtered.length\n\n const rows: SummaryListRow[] = filtered.map((item, index) => {\n const { status } = item\n const { form } = status\n const { file } = form\n\n const tag = { classes: 'govuk-tag--green', text: 'Uploaded' }\n\n const valueHtml = render\n .view('components/fileuploadfield-value.html', {\n context: { params: { tag } }\n })\n .trim()\n\n const keyHtml = render\n .view('components/fileuploadfield-key.html', {\n context: {\n params: {\n name: file.filename,\n errorMessage: errors && file.errorMessage\n }\n }\n })\n .trim()\n\n const items: SummaryListAction[] = []\n\n // Remove summary list actions from previews\n if (!isForceAccess) {\n const path = `/${file.fileId}/confirm-delete`\n const href = page?.getHref(`${page.path}${path}`) ?? '#'\n\n items.push({\n href,\n text: 'Remove',\n classes: 'govuk-link--no-visited-state',\n attributes: { id: `${id}__${index}` },\n visuallyHiddenText: file.filename\n })\n }\n\n return {\n key: {\n html: keyHtml\n },\n value: {\n html: valueHtml\n },\n actions: {\n items\n }\n } satisfies SummaryListRow\n })\n\n // Set up the `accept` attribute\n if ('accept' in options && options.accept) {\n attributes.accept = options.accept\n }\n\n // Allow multiple file selection when schema permits more than 1 file\n const allowsMultiple = schema.max !== 1 && schema.length !== 1\n\n const summaryList: SummaryList = {\n classes: 'govuk-summary-list--long-key',\n rows\n }\n\n return {\n ...viewModel,\n\n // File input can't have a initial value\n value: '',\n\n // Override the component name we send to CDP\n name: 'file',\n\n // Enable multi-file selection in the file picker\n ...(allowsMultiple && { multiple: true }),\n\n upload: {\n count,\n summaryList\n }\n }\n }\n\n isValue(value?: FormStateValue | FormState): value is UploadState {\n return isUploadState(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return FileUploadField.getAllPossibleErrors()\n }\n\n async onSubmit(\n request: FormRequestPayload,\n metadata: FormMetadata,\n context: FormContext\n ) {\n const notificationEmail = metadata.notificationEmail\n\n if (!notificationEmail) {\n // this should not happen because notificationEmail is checked further up\n // the chain in SummaryPageController before submitForm is called.\n throw new Error('Unexpected missing notificationEmail in metadata')\n }\n\n if (!request.app.model?.services.formSubmissionService) {\n throw new Error('No form submission service available in app model')\n }\n\n const { formSubmissionService } = request.app.model.services\n const values = this.getFormValueFromState(context.state) ?? []\n\n const files = values.map((value) => ({\n fileId: value.status.form.file.fileId,\n initiatedRetrievalKey: value.status.metadata.retrievalKey\n }))\n\n if (!files.length) {\n return\n }\n\n try {\n await formSubmissionService.persistFiles(files, notificationEmail)\n } catch (error) {\n if (\n Boom.isBoom(error) &&\n (error.output.statusCode === 403 || // Forbidden - retrieval key invalid\n error.output.statusCode === 410) // Gone - file expired (took to long to submit, etc)\n ) {\n // Failed to persist files. We can't recover from this, the only real way we can recover the submissions is\n // by resetting the problematic components and letting the user re-try.\n // Scenarios: file missing from S3, invalid retrieval key (timing problem), etc.\n throw new InvalidComponentStateError(\n this,\n 'There was a problem with your uploaded files. Re-upload them before submitting the form again.'\n )\n }\n\n throw error\n }\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n { type: 'selectRequired', template: messageTemplate.selectRequired },\n {\n type: 'filesMimes',\n template: 'The selected file must be a {{#limit}}'\n },\n {\n type: 'filesSize',\n template: 'The selected file must be smaller than 100MB'\n },\n { type: 'filesEmpty', template: 'The selected file is empty' },\n { type: 'filesVirus', template: 'The selected file contains a virus' },\n {\n type: 'filesPartial',\n template: 'The selected file has not fully uploaded'\n },\n {\n type: 'filesError',\n template: 'The selected file could not be uploaded – try again'\n }\n ],\n advancedSettingsErrors: [\n {\n type: 'filesMin',\n template: 'You must upload {{#limit}} files or more'\n },\n {\n type: 'filesMax',\n template: 'You can only upload {{#limit}} files or less'\n },\n {\n type: 'filesExact',\n template: 'You must upload exactly {{#limit}} files'\n }\n ]\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"FileUploadField.js","names":["Boom","joi","FormComponent","isUploadState","InvalidComponentStateError","messageTemplate","FileStatus","UploadStatus","render","uploadIdSchema","string","uuid","required","fileSchema","object","fileId","filename","contentLength","number","tempFileSchema","append","fileStatus","valid","complete","rejected","pending","errorMessage","optional","formFileSchema","metadataSchema","keys","retrievalKey","email","tempStatusSchema","uploadStatus","ready","metadata","form","file","array","items","single","numberOfRejectedFiles","formStatusSchema","itemSchema","uploadId","tempItemSchema","status","formItemSchema","FileUploadField","constructor","def","props","options","schema","formSchema","label","length","max","min","stateSchema","default","allow","getFormValueFromState","state","name","getFormValue","value","isValue","undefined","getDisplayStringFromFormValue","files","unit","getDisplayStringFromState","getContextValueFromFormValue","map","getContextValueFromState","getViewModel","payload","errors","query","page","isForceAccess","viewModel","attributes","id","filtered","filter","count","rows","item","index","tag","classes","text","valueHtml","view","context","params","trim","keyHtml","path","href","getHref","push","visuallyHiddenText","key","html","actions","accept","allowsMultiple","summaryList","multiple","upload","getAllPossibleErrors","onSubmit","request","notificationEmail","Error","app","model","services","formSubmissionService","values","initiatedRetrievalKey","persistFiles","error","isBoom","output","statusCode","baseErrors","type","template","selectRequired","advancedSettingsErrors"],"sources":["../../../../../src/server/plugins/engine/components/FileUploadField.ts"],"sourcesContent":["import {\n type FileUploadFieldComponent,\n type FormMetadata\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport joi, { type ArraySchema } from 'joi'\n\nimport {\n FormComponent,\n isUploadState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { InvalidComponentStateError } from '~/src/server/plugins/engine/pageControllers/errors.js'\nimport { messageTemplate } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n FileStatus,\n UploadStatus,\n type ErrorMessageTemplateList,\n type FileState,\n type FileUpload,\n type FileUploadMetadata,\n type FormContext,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionError,\n type FormSubmissionState,\n type SummaryList,\n type SummaryListAction,\n type SummaryListRow,\n type UploadState,\n type UploadStatusFileResponse,\n type UploadStatusResponse\n} from '~/src/server/plugins/engine/types.js'\nimport { render } from '~/src/server/plugins/nunjucks/index.js'\nimport {\n type FormQuery,\n type FormRequestPayload\n} from '~/src/server/routes/types.js'\n\nexport const uploadIdSchema = joi.string().uuid().required()\n\nexport const fileSchema = joi\n .object<FileUpload>({\n fileId: joi.string().uuid().required(),\n filename: joi.string().required(),\n contentLength: joi.number().required()\n })\n .required()\n\nexport const tempFileSchema = fileSchema.append({\n fileStatus: joi\n .string()\n .valid(FileStatus.complete, FileStatus.rejected, FileStatus.pending)\n .required(),\n errorMessage: joi.string().optional()\n})\n\nexport const formFileSchema = fileSchema.append({\n fileStatus: joi.string().valid(FileStatus.complete).required()\n})\n\nexport const metadataSchema = joi\n .object<FileUploadMetadata>()\n .keys({\n retrievalKey: joi.string().email().required()\n })\n .required()\n\nexport const tempStatusSchema = joi\n .object<UploadStatusFileResponse>({\n uploadStatus: joi\n .string()\n .valid(UploadStatus.ready, UploadStatus.pending)\n .required(),\n metadata: metadataSchema,\n form: joi\n .object()\n .required()\n .keys({\n file: joi.array().items(tempFileSchema).single().required()\n }),\n numberOfRejectedFiles: joi.number().optional()\n })\n .required()\n\nexport const formStatusSchema = joi\n .object<UploadStatusResponse>({\n uploadStatus: joi.string().valid(UploadStatus.ready).required(),\n metadata: metadataSchema,\n form: joi.object().required().keys({\n file: formFileSchema\n }),\n numberOfRejectedFiles: joi.number().valid(0).required()\n })\n .required()\n\nexport const itemSchema = joi.object<FileState>({\n uploadId: uploadIdSchema\n})\n\nexport const tempItemSchema = itemSchema.append({\n status: tempStatusSchema\n})\n\nexport const formItemSchema = itemSchema.append({\n status: formStatusSchema\n})\n\nexport class FileUploadField extends FormComponent {\n declare options: FileUploadFieldComponent['options']\n declare schema: FileUploadFieldComponent['schema']\n declare formSchema: ArraySchema<FileState>\n declare stateSchema: ArraySchema<FileState>\n\n constructor(\n def: FileUploadFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n const { options, schema } = def\n\n let formSchema = joi\n .array<FileState>()\n .label(this.label)\n .single()\n .required()\n\n if (options.required === false) {\n formSchema = formSchema.optional()\n }\n\n if (typeof schema.length !== 'number') {\n if (typeof schema.max === 'number') {\n formSchema = formSchema.max(schema.max)\n }\n\n if (typeof schema.min === 'number') {\n formSchema = formSchema.min(schema.min)\n } else if (options.required !== false) {\n formSchema = formSchema.min(1)\n }\n } else {\n formSchema = formSchema.length(schema.length)\n }\n\n this.formSchema = formSchema.items(formItemSchema)\n this.stateSchema = formSchema\n .items(formItemSchema)\n .default(null)\n .allow(null)\n\n this.options = options\n this.schema = schema\n }\n\n getFormValueFromState(state: FormSubmissionState) {\n const { name } = this\n return this.getFormValue(state[name])\n }\n\n getFormValue(value?: FormStateValue | FormState) {\n return this.isValue(value) ? value : undefined\n }\n\n getDisplayStringFromFormValue(files: FileState[] | undefined): string {\n if (!files?.length) {\n return ''\n }\n\n const unit = files.length === 1 ? 'file' : 'files'\n return `Uploaded ${files.length} ${unit}`\n }\n\n getDisplayStringFromState(state: FormSubmissionState) {\n const files = this.getFormValueFromState(state)\n\n return this.getDisplayStringFromFormValue(files)\n }\n\n getContextValueFromFormValue(\n files: UploadState | undefined\n ): string[] | null {\n return files?.map(({ status }) => status.form.file.fileId) ?? null\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n const files = this.getFormValueFromState(state)\n return this.getContextValueFromFormValue(files)\n }\n\n getViewModel(\n payload: FormPayload,\n errors?: FormSubmissionError[],\n query: FormQuery = {}\n ) {\n const { options, page, schema } = this\n\n // Allow preview URL direct access\n const isForceAccess = 'force' in query\n\n const viewModel = super.getViewModel(payload, errors)\n const { attributes, id, value } = viewModel\n\n const files = this.getFormValue(value) ?? []\n const filtered = files.filter(\n (file) => file.status.form.file.fileStatus === FileStatus.complete\n )\n const count = filtered.length\n\n const rows: SummaryListRow[] = filtered.map((item, index) => {\n const { status } = item\n const { form } = status\n const { file } = form\n\n const tag = { classes: 'govuk-tag--green', text: 'Uploaded' }\n\n const valueHtml = render\n .view('components/fileuploadfield-value.html', {\n context: { params: { tag } }\n })\n .trim()\n\n const keyHtml = render\n .view('components/fileuploadfield-key.html', {\n context: {\n params: {\n name: file.filename,\n errorMessage: errors && file.errorMessage\n }\n }\n })\n .trim()\n\n const items: SummaryListAction[] = []\n\n // Remove summary list actions from previews\n if (!isForceAccess) {\n const path = `/${file.fileId}/confirm-delete`\n const href = page?.getHref(`${page.path}${path}`) ?? '#'\n\n items.push({\n href,\n text: 'Remove',\n classes: 'govuk-link--no-visited-state',\n attributes: { id: `${id}__${index}` },\n visuallyHiddenText: file.filename\n })\n }\n\n return {\n key: {\n html: keyHtml\n },\n value: {\n html: valueHtml\n },\n actions: {\n items\n }\n } satisfies SummaryListRow\n })\n\n // Set up the `accept` attribute\n if ('accept' in options && options.accept) {\n attributes.accept = options.accept\n }\n\n // Allow multiple file selection when schema permits more than 1 file\n const allowsMultiple = schema.max !== 1 && schema.length !== 1\n\n const summaryList: SummaryList = {\n classes: 'govuk-summary-list--long-key',\n rows\n }\n\n return {\n ...viewModel,\n\n // File input can't have a initial value\n value: '',\n\n // Override the component name we send to CDP\n name: 'file',\n\n // Enable multi-file selection in the file picker\n ...(allowsMultiple && { multiple: true }),\n\n upload: {\n count,\n summaryList\n }\n }\n }\n\n isValue(value?: FormStateValue | FormState): value is UploadState {\n return isUploadState(value)\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return FileUploadField.getAllPossibleErrors()\n }\n\n async onSubmit(\n request: FormRequestPayload,\n metadata: FormMetadata,\n context: FormContext\n ) {\n const notificationEmail = metadata.notificationEmail\n\n if (!notificationEmail) {\n // this should not happen because notificationEmail is checked further up\n // the chain in SummaryPageController before submitForm is called.\n throw new Error('Unexpected missing notificationEmail in metadata')\n }\n\n if (!request.app.model?.services.formSubmissionService) {\n throw new Error('No form submission service available in app model')\n }\n\n const { formSubmissionService } = request.app.model.services\n const values = this.getFormValueFromState(context.state) ?? []\n\n const files = values.map((value) => ({\n fileId: value.status.form.file.fileId,\n initiatedRetrievalKey: value.status.metadata.retrievalKey\n }))\n\n if (!files.length) {\n return\n }\n\n try {\n await formSubmissionService.persistFiles(files, notificationEmail)\n } catch (error) {\n if (\n Boom.isBoom(error) &&\n (error.output.statusCode === 403 || // Forbidden - retrieval key invalid\n error.output.statusCode === 404 || // Not Found - file not found\n error.output.statusCode === 410) // Gone - file expired (took to long to submit, etc)\n ) {\n // Failed to persist files. We can't recover from this, the only real way we can recover the submissions is\n // by resetting the problematic components and letting the user re-try.\n // Scenarios: file missing from S3, invalid retrieval key (timing problem), etc.\n throw new InvalidComponentStateError(\n this,\n 'There was a problem with your uploaded files. Re-upload them before submitting the form again.'\n )\n }\n\n throw error\n }\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n { type: 'selectRequired', template: messageTemplate.selectRequired },\n {\n type: 'filesMimes',\n template: 'The selected file must be a {{#limit}}'\n },\n {\n type: 'filesSize',\n template: 'The selected file must be smaller than 100MB'\n },\n { type: 'filesEmpty', template: 'The selected file is empty' },\n { type: 'filesVirus', template: 'The selected file contains a virus' },\n {\n type: 'filesPartial',\n template: 'The selected file has not fully uploaded'\n },\n {\n type: 'filesError',\n template: 'The selected file could not be uploaded – try again'\n }\n ],\n advancedSettingsErrors: [\n {\n type: 'filesMin',\n template: 'You must upload {{#limit}} files or more'\n },\n {\n type: 'filesMax',\n template: 'You can only upload {{#limit}} files or less'\n },\n {\n type: 'filesExact',\n template: 'You must upload exactly {{#limit}} files'\n }\n ]\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -4,7 +4,7 @@ import { type ValidationErrorItem } from 'joi';
|
|
|
4
4
|
import { Liquid } from 'liquidjs';
|
|
5
5
|
import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js';
|
|
6
6
|
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js';
|
|
7
|
-
import { type
|
|
7
|
+
import { type FormContext, type FormContextRequest, type FormSubmissionError } from '~/src/server/plugins/engine/types.js';
|
|
8
8
|
import { FormStatus, type FormParams, type FormQuery, type FormResponseToolkit } from '~/src/server/routes/types.js';
|
|
9
9
|
export declare const engine: Liquid;
|
|
10
10
|
export interface GlobalScope {
|
|
@@ -50,12 +50,6 @@ export declare function createError(componentName: string, message: string): {
|
|
|
50
50
|
name: string;
|
|
51
51
|
text: string;
|
|
52
52
|
};
|
|
53
|
-
/**
|
|
54
|
-
* A small helper to safely generate a crumb token.
|
|
55
|
-
* Checks that the crumb plugin is available, that crumb
|
|
56
|
-
* is not disabled on the current route, and that cookies/state are present.
|
|
57
|
-
*/
|
|
58
|
-
export declare function safeGenerateCrumb(request: AnyFormRequest | null): string | undefined;
|
|
59
53
|
/**
|
|
60
54
|
* Calculates an exponential backoff delay (in milliseconds) based on the current retry depth,
|
|
61
55
|
* using a base delay of 2000ms (2 seconds) and doubling for each additional depth, while capping the delay at 25,000ms (25 seconds).
|
|
@@ -69,7 +63,7 @@ export declare function getSaveAndExitHelpers(server: Server): import("~/src/ser
|
|
|
69
63
|
export declare function getPluginOptions(server: Server): {
|
|
70
64
|
baseLayoutPath: string;
|
|
71
65
|
cacheService: import("../../services/cacheService.js").CacheService;
|
|
72
|
-
viewContext?: (request: AnyFormRequest | null) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
66
|
+
viewContext?: (request: import("~/src/server/plugins/engine/types.js").AnyFormRequest | null) => Record<string, unknown> | Promise<Record<string, unknown>>;
|
|
73
67
|
saveAndExit?: import("~/src/server/plugins/engine/types.js").PluginOptions["saveAndExit"];
|
|
74
68
|
baseUrl: string;
|
|
75
69
|
services: import("~/src/server/plugins/engine/types.js").PluginOptions["services"];
|
|
@@ -222,29 +222,6 @@ export function createError(componentName, message) {
|
|
|
222
222
|
};
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
-
/**
|
|
226
|
-
* A small helper to safely generate a crumb token.
|
|
227
|
-
* Checks that the crumb plugin is available, that crumb
|
|
228
|
-
* is not disabled on the current route, and that cookies/state are present.
|
|
229
|
-
*/
|
|
230
|
-
export function safeGenerateCrumb(request) {
|
|
231
|
-
// no request or no .state
|
|
232
|
-
if (!request?.state) {
|
|
233
|
-
return undefined;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// crumb plugin or its generate method doesn't exist
|
|
237
|
-
if (!request.server.plugins.crumb.generate) {
|
|
238
|
-
return undefined;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// crumb is explicitly disabled for this route
|
|
242
|
-
if (request.route.settings.plugins?.crumb === false) {
|
|
243
|
-
return undefined;
|
|
244
|
-
}
|
|
245
|
-
return request.server.plugins.crumb.generate(request);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
225
|
/**
|
|
249
226
|
* Calculates an exponential backoff delay (in milliseconds) based on the current retry depth,
|
|
250
227
|
* using a base delay of 2000ms (2 seconds) and doubling for each additional depth, while capping the delay at 25,000ms (25 seconds).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","names":["ControllerPath","Engine","getErrorMessage","hasComponents","isFormType","Boom","format","parseISO","StatusCodes","Liquid","createLogger","FORM_VERSION_METADATA_KEY","getAnswer","stripParam","FormAction","FormStatus","logger","engine","outputEscape","jsTruthy","ownPropertyOnly","registerFilter","template","globals","context","evaluated","evaluateTemplate","path","pageDef","pages","get","query","page","pageMap","undefined","getPageHref","name","componentDef","components","component","componentMap","isFormComponent","answer","relevantState","proceed","request","h","nextUrl","method","payload","returnUrl","isReturnAllowed","action","Continue","Validate","nextQuery","response","isPathRelative","redirect","redirectPath","code","SEE_OTHER","MOVED_TEMPORARILY","encodeUrl","link","URL","toString","err","error","pathOrQuery","queryOnly","Error","getHref","isRelative","params","Object","entries","filter","url","value","searchParams","set","pathname","search","href","startsWith","normalisePath","trim","replace","getPage","model","findPage","notFound","findPath","find","getStartPath","V2","startPath","def","at","Start","startPage","checkFormStatus","isPreview","state","Live","Draft","checkEmailAddressForLiveFormSubmission","emailAddress","internal","getErrors","details","length","map","getError","detail","message","key","text","createError","componentName","safeGenerateCrumb","server","plugins","crumb","generate","route","settings","getExponentialBackoffDelay","depth","BASE_DELAY_MS","CAP_DELAY_MS","delay","Math","min","pageDefMap","componentDefMap","parseAndRenderSync","getCacheService","getPluginOptions","cacheService","getSaveAndExitHelpers","saveAndExit","handleLegacyRedirect","targetUrl","permanent","takeover","getFormVersion","definition","metadata","setPageTitles","forEach","title","firstFormComponent","type"],"sources":["../../../../src/server/plugins/engine/helpers.ts"],"sourcesContent":["import {\n ControllerPath,\n Engine,\n getErrorMessage,\n hasComponents,\n isFormType,\n type ComponentDef,\n type FormDefinition,\n type Page\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit, type Server } from '@hapi/hapi'\nimport { format, parseISO } from 'date-fns'\nimport { StatusCodes } from 'http-status-codes'\nimport { type Schema, type ValidationErrorItem } from 'joi'\nimport { Liquid } from 'liquidjs'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { FORM_VERSION_METADATA_KEY } from '~/src/server/constants.js'\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { stripParam } from '~/src/server/plugins/engine/pageControllers/helpers/state.js'\nimport {\n type AnyFormRequest,\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError\n} from '~/src/server/plugins/engine/types.js'\nimport {\n FormAction,\n FormStatus,\n type FormParams,\n type FormQuery,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nconst logger = createLogger()\n\nexport const engine = new Liquid({\n outputEscape: 'escape',\n jsTruthy: true,\n ownPropertyOnly: false\n})\n\nexport interface GlobalScope {\n context: FormContext\n pages: Map<string, Page>\n components: Map<string, ComponentDef>\n}\n\nengine.registerFilter('evaluate', function (template?: string) {\n if (typeof template !== 'string') {\n return template\n }\n\n const globals = this.context.globals as GlobalScope\n const evaluated = evaluateTemplate(template, globals.context)\n\n return evaluated\n})\n\nengine.registerFilter('page', function (path?: string) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const pageDef = globals.pages.get(path)\n\n return pageDef\n})\n\nengine.registerFilter('href', function (path: string, query?: FormQuery) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const page = globals.context.pageMap.get(path)\n\n if (page === undefined) {\n return\n }\n\n return getPageHref(page, query)\n})\n\nengine.registerFilter('field', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const componentDef = globals.components.get(name)\n\n return componentDef\n})\n\nengine.registerFilter('answer', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const component = globals.context.componentMap.get(name)\n\n if (!component?.isFormComponent) {\n return\n }\n\n const answer = getAnswer(component as Field, globals.context.relevantState)\n\n return answer\n})\n\nexport function proceed(\n request: Pick<FormContextRequest, 'method' | 'payload' | 'query'>,\n h: FormResponseToolkit,\n nextUrl: string\n) {\n const { method, payload, query } = request\n const { returnUrl } = query\n\n const isReturnAllowed =\n payload && 'action' in payload\n ? payload.action === FormAction.Continue ||\n payload.action === FormAction.Validate\n : false\n\n // On POST, strip all query params to prevent them persisting across pages.\n // On GET, forward params (minus returnUrl) so pre-population query params\n // survive dispatch redirects (e.g. ?formId= reaching the start page).\n const nextQuery =\n method === 'get' ? stripParam(query, 'returnUrl') : undefined\n\n // Redirect to return location (optional)\n const response =\n isReturnAllowed && isPathRelative(returnUrl)\n ? h.redirect(returnUrl)\n : h.redirect(redirectPath(nextUrl, nextQuery))\n\n // Redirect POST to GET to avoid resubmission\n return method === 'post'\n ? response.code(StatusCodes.SEE_OTHER)\n : response.code(StatusCodes.MOVED_TEMPORARILY)\n}\n\n/**\n * Encodes a URL, returning undefined if the process fails.\n */\nexport function encodeUrl(link?: string) {\n if (link) {\n try {\n return new URL(link).toString() // escape the search params without breaking the ? and & reserved characters in rfc2368\n } catch (err) {\n logger.error(\n err,\n `[urlEncodingFailed] Failed to encode URL: ${link} - ${getErrorMessage(err)}`\n )\n throw err\n }\n }\n}\n\n/**\n * Get page href\n */\nexport function getPageHref(\n page: PageControllerClass,\n query?: FormQuery\n): string\n\n/**\n * Get page href by path\n */\nexport function getPageHref(\n page: PageControllerClass,\n path: string,\n query?: FormQuery\n): string\n\nexport function getPageHref(\n page: PageControllerClass,\n pathOrQuery?: string | FormQuery,\n queryOnly: FormQuery = {}\n) {\n const path = typeof pathOrQuery === 'string' ? pathOrQuery : page.path\n const query = typeof pathOrQuery === 'object' ? pathOrQuery : queryOnly\n\n if (!isPathRelative(path)) {\n throw Error(`Only relative URLs are allowed: ${path}`)\n }\n\n // Return path with page href as base\n return redirectPath(page.getHref(path), query)\n}\n\n/**\n * Get redirect path with optional query params\n */\nexport function redirectPath(nextUrl: string, query: FormQuery = {}) {\n const isRelative = isPathRelative(nextUrl)\n\n // Filter string query params only\n const params = Object.entries(query).filter(\n (query): query is [string, string] => typeof query[1] === 'string'\n )\n\n // Build URL with relative path support\n const url = isRelative\n ? new URL(nextUrl, 'http://example.com')\n : new URL(nextUrl)\n\n // Append query params\n for (const [name, value] of params) {\n url.searchParams.set(name, value)\n }\n\n if (isRelative) {\n return `${url.pathname}${url.search}`\n }\n\n return url.href\n}\n\nexport function isPathRelative(path?: string) {\n return (path ?? '').startsWith('/')\n}\n\nexport function normalisePath(path = '') {\n return path\n .trim() // Trim empty spaces\n .replace(/^\\//, '') // Remove leading slash\n .replace(/\\/$/, '') // Remove trailing slash\n}\n\nexport function getPage(\n model: FormModel | undefined,\n request: FormContextRequest\n) {\n const { params } = request\n\n const page = findPage(model, `/${params.path}`)\n\n if (!page) {\n throw Boom.notFound(`No page found for /${params.path}`)\n }\n\n return page\n}\n\nexport function findPage(model: FormModel | undefined, path?: string) {\n const findPath = `/${normalisePath(path)}`\n return model?.pages.find(({ path }) => path === findPath)\n}\n\nexport function getStartPath(model?: FormModel) {\n if (model?.engine === Engine.V2) {\n const startPath = normalisePath(model.def.pages.at(0)?.path)\n return startPath ? `/${startPath}` : ControllerPath.Start\n }\n\n const startPath = normalisePath(model?.def.startPage)\n return startPath ? `/${startPath}` : ControllerPath.Start\n}\n\nexport function checkFormStatus(params?: FormParams) {\n const isPreview = !!params?.state\n\n let state = FormStatus.Live\n\n if (isPreview && params.state === FormStatus.Draft) {\n state = FormStatus.Draft\n }\n\n return {\n isPreview,\n state\n }\n}\n\nexport function checkEmailAddressForLiveFormSubmission(\n emailAddress: string | undefined,\n isPreview: boolean\n) {\n if (!emailAddress && !isPreview) {\n throw Boom.internal(\n 'An email address is required to complete the form submission'\n )\n }\n}\n\n/**\n * Parses the errors from {@link Schema.validate} so they can be rendered by govuk-frontend templates\n * @param [details] - provided by {@link Schema.validate}\n */\nexport function getErrors(\n details?: ValidationErrorItem[]\n): FormSubmissionError[] | undefined {\n if (!details?.length) {\n return\n }\n\n return details.map(getError)\n}\n\nexport function getError(detail: ValidationErrorItem): FormSubmissionError {\n const { context, message, path } = detail\n\n const name = context?.key ?? ''\n const href = `#${name}`\n\n const text = message.replace(\n /\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z)/,\n (text) => format(parseISO(text), 'd MMMM yyyy')\n )\n\n return {\n path,\n href,\n name,\n text,\n context\n }\n}\n\nexport function createError(componentName: string, message: string) {\n return {\n href: `#${componentName}`,\n name: componentName,\n text: message\n }\n}\n\n/**\n * A small helper to safely generate a crumb token.\n * Checks that the crumb plugin is available, that crumb\n * is not disabled on the current route, and that cookies/state are present.\n */\nexport function safeGenerateCrumb(\n request: AnyFormRequest | null\n): string | undefined {\n // no request or no .state\n if (!request?.state) {\n return undefined\n }\n\n // crumb plugin or its generate method doesn't exist\n if (!request.server.plugins.crumb.generate) {\n return undefined\n }\n\n // crumb is explicitly disabled for this route\n if (request.route.settings.plugins?.crumb === false) {\n return undefined\n }\n\n return request.server.plugins.crumb.generate(request)\n}\n\n/**\n * Calculates an exponential backoff delay (in milliseconds) based on the current retry depth,\n * using a base delay of 2000ms (2 seconds) and doubling for each additional depth, while capping the delay at 25,000ms (25 seconds).\n * @param depth - The current retry depth (1, 2, 3, …)\n * @returns The calculated delay in milliseconds.\n */\nexport function getExponentialBackoffDelay(depth: number): number {\n const BASE_DELAY_MS = 2000 // 2 seconds initial delay\n const CAP_DELAY_MS = 25000 // cap each delay to 25 seconds\n const delay = BASE_DELAY_MS * 2 ** (depth - 1)\n return Math.min(delay, CAP_DELAY_MS)\n}\n\nexport function evaluateTemplate(\n template: string,\n context: FormContext\n): string {\n const globals: GlobalScope = {\n context,\n pages: context.pageDefMap,\n components: context.componentDefMap\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return engine.parseAndRenderSync(template, context.relevantState, {\n globals\n })\n}\n\nexport function getCacheService(server: Server) {\n return getPluginOptions(server).cacheService\n}\n\nexport function getSaveAndExitHelpers(server: Server) {\n return getPluginOptions(server).saveAndExit\n}\n\nexport function getPluginOptions(server: Server) {\n return server.plugins['forms-engine-plugin']\n}\n\n/**\n * Handles logging and issuing a permanent redirect for legacy routes.\n * @param h - The Hapi response toolkit.\n * @param targetUrl - The URL to redirect to.\n * @returns The Hapi response object configured for permanent redirect.\n */\nexport function handleLegacyRedirect(h: ResponseToolkit, targetUrl: string) {\n return h.redirect(targetUrl).permanent().takeover()\n}\n\n/**\n * If the page doesn't have a title, set it from the title of the first form component\n * @param def - the form definition\n */\nexport interface FormVersionMetadata {\n versionNumber: number\n createdAt: Date\n}\n\n/**\n * Extracts form version metadata from a form definition\n */\nexport function getFormVersion(\n definition: Pick<FormDefinition, 'metadata'>\n): FormVersionMetadata | undefined {\n return definition.metadata?.[FORM_VERSION_METADATA_KEY] as\n | FormVersionMetadata\n | undefined\n}\n\nexport function setPageTitles(def: FormDefinition) {\n def.pages.forEach((page) => {\n if (!page.title) {\n if (hasComponents(page)) {\n // Set the page title from the first form component\n const firstFormComponent = page.components.find((component) =>\n isFormType(component.type)\n )\n\n page.title = firstFormComponent?.title ?? ''\n }\n }\n })\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"helpers.js","names":["ControllerPath","Engine","getErrorMessage","hasComponents","isFormType","Boom","format","parseISO","StatusCodes","Liquid","createLogger","FORM_VERSION_METADATA_KEY","getAnswer","stripParam","FormAction","FormStatus","logger","engine","outputEscape","jsTruthy","ownPropertyOnly","registerFilter","template","globals","context","evaluated","evaluateTemplate","path","pageDef","pages","get","query","page","pageMap","undefined","getPageHref","name","componentDef","components","component","componentMap","isFormComponent","answer","relevantState","proceed","request","h","nextUrl","method","payload","returnUrl","isReturnAllowed","action","Continue","Validate","nextQuery","response","isPathRelative","redirect","redirectPath","code","SEE_OTHER","MOVED_TEMPORARILY","encodeUrl","link","URL","toString","err","error","pathOrQuery","queryOnly","Error","getHref","isRelative","params","Object","entries","filter","url","value","searchParams","set","pathname","search","href","startsWith","normalisePath","trim","replace","getPage","model","findPage","notFound","findPath","find","getStartPath","V2","startPath","def","at","Start","startPage","checkFormStatus","isPreview","state","Live","Draft","checkEmailAddressForLiveFormSubmission","emailAddress","internal","getErrors","details","length","map","getError","detail","message","key","text","createError","componentName","getExponentialBackoffDelay","depth","BASE_DELAY_MS","CAP_DELAY_MS","delay","Math","min","pageDefMap","componentDefMap","parseAndRenderSync","getCacheService","server","getPluginOptions","cacheService","getSaveAndExitHelpers","saveAndExit","plugins","handleLegacyRedirect","targetUrl","permanent","takeover","getFormVersion","definition","metadata","setPageTitles","forEach","title","firstFormComponent","type"],"sources":["../../../../src/server/plugins/engine/helpers.ts"],"sourcesContent":["import {\n ControllerPath,\n Engine,\n getErrorMessage,\n hasComponents,\n isFormType,\n type ComponentDef,\n type FormDefinition,\n type Page\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type ResponseToolkit, type Server } from '@hapi/hapi'\nimport { format, parseISO } from 'date-fns'\nimport { StatusCodes } from 'http-status-codes'\nimport { type Schema, type ValidationErrorItem } from 'joi'\nimport { Liquid } from 'liquidjs'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { FORM_VERSION_METADATA_KEY } from '~/src/server/constants.js'\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { stripParam } from '~/src/server/plugins/engine/pageControllers/helpers/state.js'\nimport {\n type FormContext,\n type FormContextRequest,\n type FormSubmissionError\n} from '~/src/server/plugins/engine/types.js'\nimport {\n FormAction,\n FormStatus,\n type FormParams,\n type FormQuery,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nconst logger = createLogger()\n\nexport const engine = new Liquid({\n outputEscape: 'escape',\n jsTruthy: true,\n ownPropertyOnly: false\n})\n\nexport interface GlobalScope {\n context: FormContext\n pages: Map<string, Page>\n components: Map<string, ComponentDef>\n}\n\nengine.registerFilter('evaluate', function (template?: string) {\n if (typeof template !== 'string') {\n return template\n }\n\n const globals = this.context.globals as GlobalScope\n const evaluated = evaluateTemplate(template, globals.context)\n\n return evaluated\n})\n\nengine.registerFilter('page', function (path?: string) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const pageDef = globals.pages.get(path)\n\n return pageDef\n})\n\nengine.registerFilter('href', function (path: string, query?: FormQuery) {\n if (typeof path !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const page = globals.context.pageMap.get(path)\n\n if (page === undefined) {\n return\n }\n\n return getPageHref(page, query)\n})\n\nengine.registerFilter('field', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const componentDef = globals.components.get(name)\n\n return componentDef\n})\n\nengine.registerFilter('answer', function (name: string) {\n if (typeof name !== 'string') {\n return\n }\n\n const globals = this.context.globals as GlobalScope\n const component = globals.context.componentMap.get(name)\n\n if (!component?.isFormComponent) {\n return\n }\n\n const answer = getAnswer(component as Field, globals.context.relevantState)\n\n return answer\n})\n\nexport function proceed(\n request: Pick<FormContextRequest, 'method' | 'payload' | 'query'>,\n h: FormResponseToolkit,\n nextUrl: string\n) {\n const { method, payload, query } = request\n const { returnUrl } = query\n\n const isReturnAllowed =\n payload && 'action' in payload\n ? payload.action === FormAction.Continue ||\n payload.action === FormAction.Validate\n : false\n\n // On POST, strip all query params to prevent them persisting across pages.\n // On GET, forward params (minus returnUrl) so pre-population query params\n // survive dispatch redirects (e.g. ?formId= reaching the start page).\n const nextQuery =\n method === 'get' ? stripParam(query, 'returnUrl') : undefined\n\n // Redirect to return location (optional)\n const response =\n isReturnAllowed && isPathRelative(returnUrl)\n ? h.redirect(returnUrl)\n : h.redirect(redirectPath(nextUrl, nextQuery))\n\n // Redirect POST to GET to avoid resubmission\n return method === 'post'\n ? response.code(StatusCodes.SEE_OTHER)\n : response.code(StatusCodes.MOVED_TEMPORARILY)\n}\n\n/**\n * Encodes a URL, returning undefined if the process fails.\n */\nexport function encodeUrl(link?: string) {\n if (link) {\n try {\n return new URL(link).toString() // escape the search params without breaking the ? and & reserved characters in rfc2368\n } catch (err) {\n logger.error(\n err,\n `[urlEncodingFailed] Failed to encode URL: ${link} - ${getErrorMessage(err)}`\n )\n throw err\n }\n }\n}\n\n/**\n * Get page href\n */\nexport function getPageHref(\n page: PageControllerClass,\n query?: FormQuery\n): string\n\n/**\n * Get page href by path\n */\nexport function getPageHref(\n page: PageControllerClass,\n path: string,\n query?: FormQuery\n): string\n\nexport function getPageHref(\n page: PageControllerClass,\n pathOrQuery?: string | FormQuery,\n queryOnly: FormQuery = {}\n) {\n const path = typeof pathOrQuery === 'string' ? pathOrQuery : page.path\n const query = typeof pathOrQuery === 'object' ? pathOrQuery : queryOnly\n\n if (!isPathRelative(path)) {\n throw Error(`Only relative URLs are allowed: ${path}`)\n }\n\n // Return path with page href as base\n return redirectPath(page.getHref(path), query)\n}\n\n/**\n * Get redirect path with optional query params\n */\nexport function redirectPath(nextUrl: string, query: FormQuery = {}) {\n const isRelative = isPathRelative(nextUrl)\n\n // Filter string query params only\n const params = Object.entries(query).filter(\n (query): query is [string, string] => typeof query[1] === 'string'\n )\n\n // Build URL with relative path support\n const url = isRelative\n ? new URL(nextUrl, 'http://example.com')\n : new URL(nextUrl)\n\n // Append query params\n for (const [name, value] of params) {\n url.searchParams.set(name, value)\n }\n\n if (isRelative) {\n return `${url.pathname}${url.search}`\n }\n\n return url.href\n}\n\nexport function isPathRelative(path?: string) {\n return (path ?? '').startsWith('/')\n}\n\nexport function normalisePath(path = '') {\n return path\n .trim() // Trim empty spaces\n .replace(/^\\//, '') // Remove leading slash\n .replace(/\\/$/, '') // Remove trailing slash\n}\n\nexport function getPage(\n model: FormModel | undefined,\n request: FormContextRequest\n) {\n const { params } = request\n\n const page = findPage(model, `/${params.path}`)\n\n if (!page) {\n throw Boom.notFound(`No page found for /${params.path}`)\n }\n\n return page\n}\n\nexport function findPage(model: FormModel | undefined, path?: string) {\n const findPath = `/${normalisePath(path)}`\n return model?.pages.find(({ path }) => path === findPath)\n}\n\nexport function getStartPath(model?: FormModel) {\n if (model?.engine === Engine.V2) {\n const startPath = normalisePath(model.def.pages.at(0)?.path)\n return startPath ? `/${startPath}` : ControllerPath.Start\n }\n\n const startPath = normalisePath(model?.def.startPage)\n return startPath ? `/${startPath}` : ControllerPath.Start\n}\n\nexport function checkFormStatus(params?: FormParams) {\n const isPreview = !!params?.state\n\n let state = FormStatus.Live\n\n if (isPreview && params.state === FormStatus.Draft) {\n state = FormStatus.Draft\n }\n\n return {\n isPreview,\n state\n }\n}\n\nexport function checkEmailAddressForLiveFormSubmission(\n emailAddress: string | undefined,\n isPreview: boolean\n) {\n if (!emailAddress && !isPreview) {\n throw Boom.internal(\n 'An email address is required to complete the form submission'\n )\n }\n}\n\n/**\n * Parses the errors from {@link Schema.validate} so they can be rendered by govuk-frontend templates\n * @param [details] - provided by {@link Schema.validate}\n */\nexport function getErrors(\n details?: ValidationErrorItem[]\n): FormSubmissionError[] | undefined {\n if (!details?.length) {\n return\n }\n\n return details.map(getError)\n}\n\nexport function getError(detail: ValidationErrorItem): FormSubmissionError {\n const { context, message, path } = detail\n\n const name = context?.key ?? ''\n const href = `#${name}`\n\n const text = message.replace(\n /\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z)/,\n (text) => format(parseISO(text), 'd MMMM yyyy')\n )\n\n return {\n path,\n href,\n name,\n text,\n context\n }\n}\n\nexport function createError(componentName: string, message: string) {\n return {\n href: `#${componentName}`,\n name: componentName,\n text: message\n }\n}\n\n/**\n * Calculates an exponential backoff delay (in milliseconds) based on the current retry depth,\n * using a base delay of 2000ms (2 seconds) and doubling for each additional depth, while capping the delay at 25,000ms (25 seconds).\n * @param depth - The current retry depth (1, 2, 3, …)\n * @returns The calculated delay in milliseconds.\n */\nexport function getExponentialBackoffDelay(depth: number): number {\n const BASE_DELAY_MS = 2000 // 2 seconds initial delay\n const CAP_DELAY_MS = 25000 // cap each delay to 25 seconds\n const delay = BASE_DELAY_MS * 2 ** (depth - 1)\n return Math.min(delay, CAP_DELAY_MS)\n}\n\nexport function evaluateTemplate(\n template: string,\n context: FormContext\n): string {\n const globals: GlobalScope = {\n context,\n pages: context.pageDefMap,\n components: context.componentDefMap\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n return engine.parseAndRenderSync(template, context.relevantState, {\n globals\n })\n}\n\nexport function getCacheService(server: Server) {\n return getPluginOptions(server).cacheService\n}\n\nexport function getSaveAndExitHelpers(server: Server) {\n return getPluginOptions(server).saveAndExit\n}\n\nexport function getPluginOptions(server: Server) {\n return server.plugins['forms-engine-plugin']\n}\n\n/**\n * Handles logging and issuing a permanent redirect for legacy routes.\n * @param h - The Hapi response toolkit.\n * @param targetUrl - The URL to redirect to.\n * @returns The Hapi response object configured for permanent redirect.\n */\nexport function handleLegacyRedirect(h: ResponseToolkit, targetUrl: string) {\n return h.redirect(targetUrl).permanent().takeover()\n}\n\n/**\n * If the page doesn't have a title, set it from the title of the first form component\n * @param def - the form definition\n */\nexport interface FormVersionMetadata {\n versionNumber: number\n createdAt: Date\n}\n\n/**\n * Extracts form version metadata from a form definition\n */\nexport function getFormVersion(\n definition: Pick<FormDefinition, 'metadata'>\n): FormVersionMetadata | undefined {\n return definition.metadata?.[FORM_VERSION_METADATA_KEY] as\n | FormVersionMetadata\n | undefined\n}\n\nexport function setPageTitles(def: FormDefinition) {\n def.pages.forEach((page) => {\n if (!page.title) {\n if (hasComponents(page)) {\n // Set the page title from the first form component\n const firstFormComponent = page.components.find((component) =>\n isFormType(component.type)\n )\n\n page.title = firstFormComponent?.title ?? ''\n }\n }\n })\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -24,7 +24,8 @@ async function handleHttpEvent(request, page, context, event, model, preparePage
|
|
|
24
24
|
// @ts-expect-error - function signature will be refactored in the next iteration of the formatter
|
|
25
25
|
const payload = format(context, items, model, undefined, undefined);
|
|
26
26
|
const opts = {
|
|
27
|
-
payload
|
|
27
|
+
payload,
|
|
28
|
+
timeout: 5000
|
|
28
29
|
};
|
|
29
30
|
if (preparePageEventRequestOptions) {
|
|
30
31
|
preparePageEventRequestOptions(opts, event, page, context);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"questions.js","names":["hasFormComponents","slugSchema","Boom","Joi","normalisePath","proceed","redirectPath","SummaryViewModel","format","getFormSubmissionData","dispatchHandler","redirectOrMakeHandler","actionSchema","crumbSchema","itemIdSchema","pathSchema","stateSchema","httpService","handleHttpEvent","request","page","context","event","model","preparePageEventRequestOptions","options","url","viewModel","items","details","payload","undefined","opts","response","postJson","Object","assign","data","makeGetHandler","onRequest","getHandler","h","params","path","events","app","notFound","onLoad","type","makeGetRouteHandler","makePostHandler","postHandler","query","pageDef","isForceAccess","href","makePostRouteHandler","onSave","isSuccessful","statusCode","isBoom","getRoutes","getRouteOptions","postRouteOptions","method","handler","validate","object","keys","slug","state","itemId","optional","crumb","action","unknown","required"],"sources":["../../../../../src/server/plugins/engine/routes/questions.ts"],"sourcesContent":["import { hasFormComponents, slugSchema, type Event } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type RouteOptions,\n type ServerRoute\n} from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport {\n normalisePath,\n proceed,\n redirectPath\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport { format } from '~/src/server/plugins/engine/outputFormatters/machine/v1.js'\nimport { getFormSubmissionData } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n dispatchHandler,\n redirectOrMakeHandler\n} from '~/src/server/plugins/engine/routes/index.js'\nimport {\n type AnyFormRequest,\n type FormContext,\n type OnRequestCallback,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n itemIdSchema,\n pathSchema,\n stateSchema\n} from '~/src/server/schemas/index.js'\nimport * as httpService from '~/src/server/services/httpService.js'\n\nasync function handleHttpEvent(\n request: AnyFormRequest,\n page: PageControllerClass,\n context: FormContext,\n event: Event,\n model: FormModel,\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n) {\n const { options } = event\n const { url } = options\n\n // TODO: Update structured data POST payload with when helper\n // is updated to removing the dependency on `SummaryViewModel` etc.\n const viewModel = new SummaryViewModel(request, page, context)\n const items = getFormSubmissionData(viewModel.context, viewModel.details)\n\n // @ts-expect-error - function signature will be refactored in the next iteration of the formatter\n const payload = format(context, items, model, undefined, undefined)\n const opts = { payload }\n\n if (preparePageEventRequestOptions) {\n preparePageEventRequestOptions(opts, event, page, context)\n }\n\n const { payload: response } = await httpService.postJson(url, opts)\n\n Object.assign(context.data, response)\n}\n\nexport function makeGetHandler(\n preparePageEventRequestOptions?: PreparePageEventRequestOptions,\n onRequest?: OnRequestCallback\n) {\n return function getHandler(request: FormRequest, h: FormResponseToolkit) {\n const { params } = request\n\n if (normalisePath(params.path) === '') {\n return dispatchHandler(request, h)\n }\n\n return redirectOrMakeHandler(\n request,\n h,\n onRequest,\n async (page, context) => {\n // Check for a page onLoad HTTP event and if one exists,\n // call it and assign the response to the context data\n const { events } = page\n const { model } = request.app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n if (events?.onLoad?.type === 'http') {\n await handleHttpEvent(\n request,\n page,\n context,\n events.onLoad,\n model,\n preparePageEventRequestOptions\n )\n }\n\n return page.makeGetRouteHandler()(request, context, h)\n }\n )\n }\n}\n\nexport function makePostHandler(\n preparePageEventRequestOptions?: PreparePageEventRequestOptions,\n onRequest?: OnRequestCallback\n) {\n return function postHandler(\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const { query } = request\n\n return redirectOrMakeHandler(\n request,\n h,\n onRequest,\n async (page, context) => {\n const { pageDef } = page\n const { isForceAccess } = context\n const { model } = request.app\n const { events } = page\n\n // Redirect to GET for preview URL direct access\n if (isForceAccess && !hasFormComponents(pageDef)) {\n return proceed(request, h, redirectPath(page.href, query))\n }\n\n if (!model) {\n throw Boom.notFound(`No model found for /${request.params.path}`)\n }\n\n const response = await page.makePostRouteHandler()(request, context, h)\n\n if (events?.onSave?.type === 'http' && isSuccessful(response)) {\n await handleHttpEvent(\n request,\n page,\n context,\n events.onSave,\n model,\n preparePageEventRequestOptions\n )\n }\n\n return response\n }\n )\n }\n}\n\nfunction isSuccessful(response: ResponseObject): boolean {\n const { statusCode } = response\n\n return !Boom.isBoom(response) && statusCode >= 200 && statusCode < 400\n}\n\nexport function getRoutes(\n getRouteOptions: RouteOptions<FormRequestRefs>,\n postRouteOptions: RouteOptions<FormRequestPayloadRefs>,\n preparePageEventRequestOptions?: PreparePageEventRequestOptions,\n onRequest?: OnRequestCallback\n): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[] {\n return [\n {\n method: 'get',\n path: '/{slug}',\n handler: makeGetHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}',\n handler: dispatchHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/{slug}/{path}/{itemId?}',\n handler: makeGetHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: makeGetHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'post',\n path: '/{slug}/{path}/{itemId?}',\n handler: makePostHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n },\n {\n method: 'post',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: makePostHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n }\n ]\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"questions.js","names":["hasFormComponents","slugSchema","Boom","Joi","normalisePath","proceed","redirectPath","SummaryViewModel","format","getFormSubmissionData","dispatchHandler","redirectOrMakeHandler","actionSchema","crumbSchema","itemIdSchema","pathSchema","stateSchema","httpService","handleHttpEvent","request","page","context","event","model","preparePageEventRequestOptions","options","url","viewModel","items","details","payload","undefined","opts","timeout","response","postJson","Object","assign","data","makeGetHandler","onRequest","getHandler","h","params","path","events","app","notFound","onLoad","type","makeGetRouteHandler","makePostHandler","postHandler","query","pageDef","isForceAccess","href","makePostRouteHandler","onSave","isSuccessful","statusCode","isBoom","getRoutes","getRouteOptions","postRouteOptions","method","handler","validate","object","keys","slug","state","itemId","optional","crumb","action","unknown","required"],"sources":["../../../../../src/server/plugins/engine/routes/questions.ts"],"sourcesContent":["import { hasFormComponents, slugSchema, type Event } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type RouteOptions,\n type ServerRoute\n} from '@hapi/hapi'\nimport Joi from 'joi'\n\nimport {\n normalisePath,\n proceed,\n redirectPath\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport { format } from '~/src/server/plugins/engine/outputFormatters/machine/v1.js'\nimport { getFormSubmissionData } from '~/src/server/plugins/engine/pageControllers/SummaryPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n dispatchHandler,\n redirectOrMakeHandler\n} from '~/src/server/plugins/engine/routes/index.js'\nimport {\n type AnyFormRequest,\n type FormContext,\n type OnRequestCallback,\n type PreparePageEventRequestOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n itemIdSchema,\n pathSchema,\n stateSchema\n} from '~/src/server/schemas/index.js'\nimport * as httpService from '~/src/server/services/httpService.js'\n\nasync function handleHttpEvent(\n request: AnyFormRequest,\n page: PageControllerClass,\n context: FormContext,\n event: Event,\n model: FormModel,\n preparePageEventRequestOptions?: PreparePageEventRequestOptions\n) {\n const { options } = event\n const { url } = options\n\n // TODO: Update structured data POST payload with when helper\n // is updated to removing the dependency on `SummaryViewModel` etc.\n const viewModel = new SummaryViewModel(request, page, context)\n const items = getFormSubmissionData(viewModel.context, viewModel.details)\n\n // @ts-expect-error - function signature will be refactored in the next iteration of the formatter\n const payload = format(context, items, model, undefined, undefined)\n const opts: httpService.RequestOptions = { payload, timeout: 5000 }\n\n if (preparePageEventRequestOptions) {\n preparePageEventRequestOptions(opts, event, page, context)\n }\n\n const { payload: response } = await httpService.postJson(url, opts)\n\n Object.assign(context.data, response)\n}\n\nexport function makeGetHandler(\n preparePageEventRequestOptions?: PreparePageEventRequestOptions,\n onRequest?: OnRequestCallback\n) {\n return function getHandler(request: FormRequest, h: FormResponseToolkit) {\n const { params } = request\n\n if (normalisePath(params.path) === '') {\n return dispatchHandler(request, h)\n }\n\n return redirectOrMakeHandler(\n request,\n h,\n onRequest,\n async (page, context) => {\n // Check for a page onLoad HTTP event and if one exists,\n // call it and assign the response to the context data\n const { events } = page\n const { model } = request.app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n if (events?.onLoad?.type === 'http') {\n await handleHttpEvent(\n request,\n page,\n context,\n events.onLoad,\n model,\n preparePageEventRequestOptions\n )\n }\n\n return page.makeGetRouteHandler()(request, context, h)\n }\n )\n }\n}\n\nexport function makePostHandler(\n preparePageEventRequestOptions?: PreparePageEventRequestOptions,\n onRequest?: OnRequestCallback\n) {\n return function postHandler(\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const { query } = request\n\n return redirectOrMakeHandler(\n request,\n h,\n onRequest,\n async (page, context) => {\n const { pageDef } = page\n const { isForceAccess } = context\n const { model } = request.app\n const { events } = page\n\n // Redirect to GET for preview URL direct access\n if (isForceAccess && !hasFormComponents(pageDef)) {\n return proceed(request, h, redirectPath(page.href, query))\n }\n\n if (!model) {\n throw Boom.notFound(`No model found for /${request.params.path}`)\n }\n\n const response = await page.makePostRouteHandler()(request, context, h)\n\n if (events?.onSave?.type === 'http' && isSuccessful(response)) {\n await handleHttpEvent(\n request,\n page,\n context,\n events.onSave,\n model,\n preparePageEventRequestOptions\n )\n }\n\n return response\n }\n )\n }\n}\n\nfunction isSuccessful(response: ResponseObject): boolean {\n const { statusCode } = response\n\n return !Boom.isBoom(response) && statusCode >= 200 && statusCode < 400\n}\n\nexport function getRoutes(\n getRouteOptions: RouteOptions<FormRequestRefs>,\n postRouteOptions: RouteOptions<FormRequestPayloadRefs>,\n preparePageEventRequestOptions?: PreparePageEventRequestOptions,\n onRequest?: OnRequestCallback\n): (ServerRoute<FormRequestRefs> | ServerRoute<FormRequestPayloadRefs>)[] {\n return [\n {\n method: 'get',\n path: '/{slug}',\n handler: makeGetHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}',\n handler: dispatchHandler,\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema\n })\n }\n }\n },\n {\n method: 'get',\n path: '/{slug}/{path}/{itemId?}',\n handler: makeGetHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'get',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: makeGetHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...getRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n })\n }\n }\n },\n {\n method: 'post',\n path: '/{slug}/{path}/{itemId?}',\n handler: makePostHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n },\n {\n method: 'post',\n path: '/preview/{state}/{slug}/{path}/{itemId?}',\n handler: makePostHandler(preparePageEventRequestOptions, onRequest),\n options: {\n ...postRouteOptions,\n validate: {\n params: Joi.object().keys({\n state: stateSchema,\n slug: slugSchema,\n path: pathSchema,\n itemId: itemIdSchema.optional()\n }),\n payload: Joi.object()\n .keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n .unknown(true)\n .required()\n }\n }\n }\n ]\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -4,7 +4,7 @@ import Boom from '@hapi/boom';
|
|
|
4
4
|
import { StatusCodes } from 'http-status-codes';
|
|
5
5
|
import { config } from "../../../config/index.js";
|
|
6
6
|
import { createLogger } from "../../common/helpers/logging/logger.js";
|
|
7
|
-
import { checkFormStatus, encodeUrl
|
|
7
|
+
import { checkFormStatus, encodeUrl } from "../engine/helpers.js";
|
|
8
8
|
const logger = createLogger();
|
|
9
9
|
|
|
10
10
|
/** @type {Record<string, string> | undefined} */
|
|
@@ -43,7 +43,6 @@ export async function context(request) {
|
|
|
43
43
|
// take consumers props first so we can override it
|
|
44
44
|
...consumerViewContext,
|
|
45
45
|
baseLayoutPath: pluginStorage.baseLayoutPath,
|
|
46
|
-
crumb: safeGenerateCrumb(request),
|
|
47
46
|
currentPath: `${request.path}${request.url.search}`,
|
|
48
47
|
previewMode: isPreviewMode ? formState : undefined,
|
|
49
48
|
slug: isResponseOK ? params?.slug : undefined
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":["readFileSync","basename","join","Boom","StatusCodes","config","createLogger","checkFormStatus","encodeUrl","
|
|
1
|
+
{"version":3,"file":"context.js","names":["readFileSync","basename","join","Boom","StatusCodes","config","createLogger","checkFormStatus","encodeUrl","logger","webpackManifest","context","request","params","response","isPreview","isPreviewMode","state","formState","isResponseOK","isBoom","statusCode","OK","pluginStorage","server","plugins","consumerViewContext","Error","baseLayoutPath","viewContext","ctx","currentPath","path","url","search","previewMode","undefined","slug","devtoolContext","_request","manifestPath","get","JSON","parse","info","cdpEnvironment","designerUrl","feedbackLink","phaseTag","serviceName","serviceVersion","assetPath","getDxtAssetPath","asset"],"sources":["../../../../src/server/plugins/nunjucks/context.js"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { basename, join } from 'node:path'\n\nimport Boom from '@hapi/boom'\nimport { StatusCodes } from 'http-status-codes'\n\nimport { config } from '~/src/config/index.js'\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport {\n checkFormStatus,\n encodeUrl\n} from '~/src/server/plugins/engine/helpers.js'\n\nconst logger = createLogger()\n\n/** @type {Record<string, string> | undefined} */\nlet webpackManifest\n\n/**\n * @param {AnyFormRequest | null} request\n */\nexport async function context(request) {\n const { params, response } = request ?? {}\n\n const { isPreview: isPreviewMode, state: formState } = checkFormStatus(params)\n\n // Only add the slug in to the context if the response is OK.\n // Footer meta links are not rendered when the slug is missing.\n const isResponseOK =\n !Boom.isBoom(response) && response?.statusCode === StatusCodes.OK\n\n const pluginStorage = request?.server.plugins['forms-engine-plugin']\n\n let consumerViewContext = {}\n\n if (!pluginStorage) {\n throw Error('context called before plugin registered')\n }\n\n if (!pluginStorage.baseLayoutPath) {\n throw Error('Missing baseLayoutPath in plugin.options.nunjucks')\n }\n\n if (typeof pluginStorage.viewContext === 'function') {\n consumerViewContext = await pluginStorage.viewContext(request)\n }\n\n /** @type {ViewContext} */\n const ctx = {\n // take consumers props first so we can override it\n ...consumerViewContext,\n baseLayoutPath: pluginStorage.baseLayoutPath,\n currentPath: `${request.path}${request.url.search}`,\n previewMode: isPreviewMode ? formState : undefined,\n slug: isResponseOK ? params?.slug : undefined\n }\n\n return ctx\n}\n\n/**\n * Returns the context for the devtool. Consumers won't have access to this.\n * @param {AnyFormRequest | null} _request\n * @returns {Record<string, unknown> & { assetPath: string, getDxtAssetPath: (asset: string) => string }}\n */\nexport function devtoolContext(_request) {\n const manifestPath = join(config.get('publicDir'), 'assets-manifest.json')\n\n if (!webpackManifest) {\n try {\n // eslint-disable-next-line -- Allow JSON type 'any'\n webpackManifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))\n } catch {\n logger.info(\n `[webpackManifestMissing] Webpack ${basename(manifestPath)} not found - running without asset manifest`\n )\n }\n }\n\n return {\n config: {\n cdpEnvironment: config.get('cdpEnvironment'),\n designerUrl: config.get('designerUrl'),\n feedbackLink: encodeUrl(config.get('feedbackLink')),\n phaseTag: config.get('phaseTag'),\n serviceName: config.get('serviceName'),\n serviceVersion: config.get('serviceVersion')\n },\n assetPath: '/assets',\n getDxtAssetPath: (asset = '') => {\n return `/${webpackManifest?.[asset] ?? asset}`\n }\n }\n}\n\n/**\n * @import { ViewContext } from '~/src/server/plugins/nunjucks/types.js'\n * @import { AnyFormRequest } from '~/src/server/plugins/engine/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -92,42 +92,6 @@ describe('Nunjucks context', () => {
|
|
|
92
92
|
expect(crumb).toBeUndefined();
|
|
93
93
|
expect(malformedRequest.server.plugins.crumb.generate).not.toHaveBeenCalled();
|
|
94
94
|
});
|
|
95
|
-
it('should generate crumb when state exists', async () => {
|
|
96
|
-
const mockCrumb = 'generated-crumb-value';
|
|
97
|
-
const validRequest = /** @type {FormRequest} */
|
|
98
|
-
/** @type {unknown} */{
|
|
99
|
-
server: {
|
|
100
|
-
plugins: {
|
|
101
|
-
crumb: {
|
|
102
|
-
generate: jest.fn().mockReturnValue(mockCrumb)
|
|
103
|
-
},
|
|
104
|
-
'forms-engine-plugin': {
|
|
105
|
-
baseLayoutPath: 'randomValue'
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
plugins: {},
|
|
110
|
-
route: {
|
|
111
|
-
settings: {
|
|
112
|
-
plugins: {}
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
path: '/test',
|
|
116
|
-
url: {
|
|
117
|
-
search: ''
|
|
118
|
-
},
|
|
119
|
-
state: {},
|
|
120
|
-
yar: {
|
|
121
|
-
flash: jest.fn().mockReturnValue([]),
|
|
122
|
-
commit: jest.fn()
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
const {
|
|
126
|
-
crumb
|
|
127
|
-
} = await context(validRequest);
|
|
128
|
-
expect(crumb).toBe(mockCrumb);
|
|
129
|
-
expect(validRequest.server.plugins.crumb.generate).toHaveBeenCalledWith(validRequest);
|
|
130
|
-
});
|
|
131
95
|
});
|
|
132
96
|
});
|
|
133
97
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.test.js","names":["tmpdir","context","devtoolContext","describe","beforeEach","jest","resetModules","it","assetPath","expect","toBe","getDxtAssetPath","isolateModulesAsync","config","set","rejects","toThrow","malformedRequest","server","plugins","crumb","generate","fn","baseLayoutPath","route","settings","path","url","search","yar","flash","mockReturnValue","commit","toBeUndefined","not","toHaveBeenCalled"
|
|
1
|
+
{"version":3,"file":"context.test.js","names":["tmpdir","context","devtoolContext","describe","beforeEach","jest","resetModules","it","assetPath","expect","toBe","getDxtAssetPath","isolateModulesAsync","config","set","rejects","toThrow","malformedRequest","server","plugins","crumb","generate","fn","baseLayoutPath","route","settings","path","url","search","yar","flash","mockReturnValue","commit","toBeUndefined","not","toHaveBeenCalled"],"sources":["../../../../src/server/plugins/nunjucks/context.test.js"],"sourcesContent":["import { tmpdir } from 'node:os'\n\nimport {\n context,\n devtoolContext\n} from '~/src/server/plugins/nunjucks/context.js'\n\ndescribe('Nunjucks context', () => {\n beforeEach(() => jest.resetModules())\n\n describe('Asset path', () => {\n it(\"should include 'assetPath' for GOV.UK Frontend icons\", () => {\n const { assetPath } = devtoolContext(null)\n expect(assetPath).toBe('/assets')\n })\n })\n\n describe('Asset helper', () => {\n it(\"should locate 'assets-manifest.json' assets\", () => {\n const { getDxtAssetPath } = devtoolContext(null)\n\n expect(getDxtAssetPath('example.scss')).toBe(\n '/stylesheets/example.xxxxxxx.min.css'\n )\n\n expect(getDxtAssetPath('example.mjs')).toBe(\n '/javascripts/example.xxxxxxx.min.js'\n )\n })\n\n it(\"should return path when 'assets-manifest.json' is missing\", async () => {\n await jest.isolateModulesAsync(async () => {\n const { config } = await import('~/src/config/index.js')\n\n // Import when isolated to avoid cache\n const { devtoolContext } =\n await import('~/src/server/plugins/nunjucks/context.js')\n\n // Update config for missing manifest\n config.set('publicDir', tmpdir())\n const { getDxtAssetPath } = devtoolContext(null)\n\n // Uses original paths when missing\n expect(getDxtAssetPath('example.scss')).toBe('/example.scss')\n expect(getDxtAssetPath('example.mjs')).toBe('/example.mjs')\n })\n })\n\n it('should return path to unknown assets', () => {\n const { getDxtAssetPath } = devtoolContext(null)\n\n expect(getDxtAssetPath('')).toBe('/')\n expect(getDxtAssetPath('example.jpg')).toBe('/example.jpg')\n expect(getDxtAssetPath('example.gif')).toBe('/example.gif')\n })\n })\n\n describe('Config', () => {\n it('should include environment, phase tag and service info', async () => {\n await expect(context(null)).rejects.toThrow(\n 'context called before plugin registered'\n )\n })\n })\n\n describe('Crumb', () => {\n it('should handle malformed requests with missing state', async () => {\n // While state should always exist in a valid Hapi request (it holds cookies),\n // we've seen malformed requests in production where it's missing\n const malformedRequest = /** @type {FormRequest} */ (\n /** @type {unknown} */ ({\n server: {\n plugins: {\n crumb: {\n generate: jest.fn()\n },\n 'forms-engine-plugin': {\n baseLayoutPath: 'randomValue'\n }\n }\n },\n plugins: {},\n route: {\n settings: {\n plugins: {}\n }\n },\n path: '/test',\n url: { search: '' },\n yar: {\n flash: jest.fn().mockReturnValue([]),\n commit: jest.fn()\n }\n // state intentionally omitted to test real malformed requests\n })\n )\n\n const { crumb } = await context(malformedRequest)\n expect(crumb).toBeUndefined()\n expect(\n malformedRequest.server.plugins.crumb.generate\n ).not.toHaveBeenCalled()\n })\n })\n})\n\n/**\n * @import { FormRequest } from '~/src/server/routes/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "4.5.
|
|
3
|
+
"version": "4.5.2",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"blipp": "^4.0.2",
|
|
110
110
|
"btoa": "^1.2.1",
|
|
111
111
|
"chokidar": "3.6.0",
|
|
112
|
-
"convict": "^6.2.
|
|
112
|
+
"convict": "^6.2.5",
|
|
113
113
|
"date-fns": "^4.1.0",
|
|
114
114
|
"dotenv": "^17.2.3",
|
|
115
115
|
"expr-eval-fork": "^3.0.0",
|
|
@@ -140,7 +140,7 @@
|
|
|
140
140
|
"@babel/plugin-syntax-import-attributes": "^7.27.1",
|
|
141
141
|
"@babel/preset-env": "^7.28.5",
|
|
142
142
|
"@babel/preset-typescript": "^7.28.5",
|
|
143
|
-
"@defra/docusaurus-theme-govuk": "^0.0.
|
|
143
|
+
"@defra/docusaurus-theme-govuk": "^0.0.13-alpha",
|
|
144
144
|
"@docusaurus/core": "^3.9.2",
|
|
145
145
|
"@docusaurus/plugin-content-docs": "^3.9.2",
|
|
146
146
|
"@easyops-cn/docusaurus-search-local": "^0.55.0",
|
package/src/config/index.ts
CHANGED
|
@@ -1029,6 +1029,27 @@ describe('FileUploadField', () => {
|
|
|
1029
1029
|
)
|
|
1030
1030
|
})
|
|
1031
1031
|
|
|
1032
|
+
it('should throw InvalidComponentStateError when persistFiles throws 404 Not Found', async () => {
|
|
1033
|
+
const notFoundError = Boom.notFound('File not found')
|
|
1034
|
+
mockPersistFiles.mockRejectedValue(notFoundError)
|
|
1035
|
+
|
|
1036
|
+
await expect(
|
|
1037
|
+
fileUploadField.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
1038
|
+
).rejects.toThrow(InvalidComponentStateError)
|
|
1039
|
+
|
|
1040
|
+
const error = await fileUploadField
|
|
1041
|
+
.onSubmit(mockRequest, mockMetadata, mockContext)
|
|
1042
|
+
.catch((e: unknown) => e)
|
|
1043
|
+
|
|
1044
|
+
expect(error).toBeInstanceOf(InvalidComponentStateError)
|
|
1045
|
+
expect((error as InvalidComponentStateError).component).toBe(
|
|
1046
|
+
fileUploadField
|
|
1047
|
+
)
|
|
1048
|
+
expect((error as InvalidComponentStateError).userMessage).toBe(
|
|
1049
|
+
'There was a problem with your uploaded files. Re-upload them before submitting the form again.'
|
|
1050
|
+
)
|
|
1051
|
+
})
|
|
1052
|
+
|
|
1032
1053
|
it('should re-throw other Boom errors without wrapping', async () => {
|
|
1033
1054
|
const serverError = Boom.internal('Internal server error')
|
|
1034
1055
|
mockPersistFiles.mockRejectedValue(serverError)
|
|
@@ -339,6 +339,7 @@ export class FileUploadField extends FormComponent {
|
|
|
339
339
|
if (
|
|
340
340
|
Boom.isBoom(error) &&
|
|
341
341
|
(error.output.statusCode === 403 || // Forbidden - retrieval key invalid
|
|
342
|
+
error.output.statusCode === 404 || // Not Found - file not found
|
|
342
343
|
error.output.statusCode === 410) // Gone - file expired (took to long to submit, etc)
|
|
343
344
|
) {
|
|
344
345
|
// Failed to persist files. We can't recover from this, the only real way we can recover the submissions is
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
getExponentialBackoffDelay,
|
|
19
19
|
getPageHref,
|
|
20
20
|
proceed,
|
|
21
|
-
safeGenerateCrumb,
|
|
22
21
|
setPageTitles,
|
|
23
22
|
type GlobalScope
|
|
24
23
|
} from '~/src/server/plugins/engine/helpers.js'
|
|
@@ -36,7 +35,6 @@ import {
|
|
|
36
35
|
import {
|
|
37
36
|
FormAction,
|
|
38
37
|
FormStatus,
|
|
39
|
-
type FormRequest,
|
|
40
38
|
type FormResponseToolkit
|
|
41
39
|
} from '~/src/server/routes/types.js'
|
|
42
40
|
import definition from '~/test/form/definitions/basic.js'
|
|
@@ -493,78 +491,6 @@ describe('Helpers', () => {
|
|
|
493
491
|
})
|
|
494
492
|
})
|
|
495
493
|
|
|
496
|
-
describe('safeGenerateCrumb', () => {
|
|
497
|
-
it('should return undefined when request.state is missing (malformed request)', () => {
|
|
498
|
-
const malformedRequest = {
|
|
499
|
-
server: {
|
|
500
|
-
plugins: {
|
|
501
|
-
crumb: {
|
|
502
|
-
generate: jest.fn()
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
},
|
|
506
|
-
plugins: {},
|
|
507
|
-
route: { settings: { plugins: {} } },
|
|
508
|
-
path: '/test',
|
|
509
|
-
url: { search: '' }
|
|
510
|
-
// state intentionally omitted
|
|
511
|
-
} as unknown as FormRequest
|
|
512
|
-
|
|
513
|
-
const crumbToken = safeGenerateCrumb(malformedRequest)
|
|
514
|
-
expect(crumbToken).toBeUndefined()
|
|
515
|
-
expect(
|
|
516
|
-
malformedRequest.server.plugins.crumb.generate
|
|
517
|
-
).not.toHaveBeenCalled()
|
|
518
|
-
})
|
|
519
|
-
|
|
520
|
-
it('should return undefined if crumb is disabled in route settings', () => {
|
|
521
|
-
const requestWithDisabledCrumb = {
|
|
522
|
-
server: {
|
|
523
|
-
plugins: {
|
|
524
|
-
crumb: {
|
|
525
|
-
generate: jest.fn().mockReturnValue('test-token')
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
},
|
|
529
|
-
plugins: {},
|
|
530
|
-
route: { settings: { plugins: { crumb: false } } },
|
|
531
|
-
path: '/test',
|
|
532
|
-
url: { search: '' },
|
|
533
|
-
state: {}
|
|
534
|
-
} as unknown as FormRequest
|
|
535
|
-
|
|
536
|
-
const crumbToken = safeGenerateCrumb(requestWithDisabledCrumb)
|
|
537
|
-
expect(crumbToken).toBeUndefined()
|
|
538
|
-
expect(
|
|
539
|
-
requestWithDisabledCrumb.server.plugins.crumb.generate
|
|
540
|
-
).not.toHaveBeenCalled()
|
|
541
|
-
})
|
|
542
|
-
|
|
543
|
-
it('should generate crumb when state exists and crumb plugin is available', () => {
|
|
544
|
-
const mockCrumb = 'generated-crumb-value'
|
|
545
|
-
const validRequest = {
|
|
546
|
-
server: {
|
|
547
|
-
plugins: {
|
|
548
|
-
crumb: {
|
|
549
|
-
generate: jest.fn().mockReturnValue(mockCrumb)
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
},
|
|
553
|
-
plugins: {},
|
|
554
|
-
route: { settings: { plugins: {} } },
|
|
555
|
-
path: '/test',
|
|
556
|
-
url: { search: '' },
|
|
557
|
-
state: {}
|
|
558
|
-
} as unknown as FormRequest
|
|
559
|
-
|
|
560
|
-
const crumbToken = safeGenerateCrumb(validRequest)
|
|
561
|
-
expect(crumbToken).toBe(mockCrumb)
|
|
562
|
-
expect(validRequest.server.plugins.crumb.generate).toHaveBeenCalledWith(
|
|
563
|
-
validRequest
|
|
564
|
-
)
|
|
565
|
-
})
|
|
566
|
-
})
|
|
567
|
-
|
|
568
494
|
describe('getExponentialBackoffDelay', () => {
|
|
569
495
|
it.each([
|
|
570
496
|
{ depth: 1, expected: 2000 },
|
|
@@ -25,7 +25,6 @@ import { type FormModel } from '~/src/server/plugins/engine/models/FormModel.js'
|
|
|
25
25
|
import { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'
|
|
26
26
|
import { stripParam } from '~/src/server/plugins/engine/pageControllers/helpers/state.js'
|
|
27
27
|
import {
|
|
28
|
-
type AnyFormRequest,
|
|
29
28
|
type FormContext,
|
|
30
29
|
type FormContextRequest,
|
|
31
30
|
type FormSubmissionError
|
|
@@ -336,32 +335,6 @@ export function createError(componentName: string, message: string) {
|
|
|
336
335
|
}
|
|
337
336
|
}
|
|
338
337
|
|
|
339
|
-
/**
|
|
340
|
-
* A small helper to safely generate a crumb token.
|
|
341
|
-
* Checks that the crumb plugin is available, that crumb
|
|
342
|
-
* is not disabled on the current route, and that cookies/state are present.
|
|
343
|
-
*/
|
|
344
|
-
export function safeGenerateCrumb(
|
|
345
|
-
request: AnyFormRequest | null
|
|
346
|
-
): string | undefined {
|
|
347
|
-
// no request or no .state
|
|
348
|
-
if (!request?.state) {
|
|
349
|
-
return undefined
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// crumb plugin or its generate method doesn't exist
|
|
353
|
-
if (!request.server.plugins.crumb.generate) {
|
|
354
|
-
return undefined
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// crumb is explicitly disabled for this route
|
|
358
|
-
if (request.route.settings.plugins?.crumb === false) {
|
|
359
|
-
return undefined
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
return request.server.plugins.crumb.generate(request)
|
|
363
|
-
}
|
|
364
|
-
|
|
365
338
|
/**
|
|
366
339
|
* Calculates an exponential backoff delay (in milliseconds) based on the current retry depth,
|
|
367
340
|
* using a base delay of 2000ms (2 seconds) and doubling for each additional depth, while capping the delay at 25,000ms (25 seconds).
|
|
@@ -63,7 +63,7 @@ async function handleHttpEvent(
|
|
|
63
63
|
|
|
64
64
|
// @ts-expect-error - function signature will be refactored in the next iteration of the formatter
|
|
65
65
|
const payload = format(context, items, model, undefined, undefined)
|
|
66
|
-
const opts = { payload }
|
|
66
|
+
const opts: httpService.RequestOptions = { payload, timeout: 5000 }
|
|
67
67
|
|
|
68
68
|
if (preparePageEventRequestOptions) {
|
|
69
69
|
preparePageEventRequestOptions(opts, event, page, context)
|
|
@@ -8,8 +8,7 @@ import { config } from '~/src/config/index.js'
|
|
|
8
8
|
import { createLogger } from '~/src/server/common/helpers/logging/logger.js'
|
|
9
9
|
import {
|
|
10
10
|
checkFormStatus,
|
|
11
|
-
encodeUrl
|
|
12
|
-
safeGenerateCrumb
|
|
11
|
+
encodeUrl
|
|
13
12
|
} from '~/src/server/plugins/engine/helpers.js'
|
|
14
13
|
|
|
15
14
|
const logger = createLogger()
|
|
@@ -51,7 +50,6 @@ export async function context(request) {
|
|
|
51
50
|
// take consumers props first so we can override it
|
|
52
51
|
...consumerViewContext,
|
|
53
52
|
baseLayoutPath: pluginStorage.baseLayoutPath,
|
|
54
|
-
crumb: safeGenerateCrumb(request),
|
|
55
53
|
currentPath: `${request.path}${request.url.search}`,
|
|
56
54
|
previewMode: isPreviewMode ? formState : undefined,
|
|
57
55
|
slug: isResponseOK ? params?.slug : undefined
|
|
@@ -101,43 +101,6 @@ describe('Nunjucks context', () => {
|
|
|
101
101
|
malformedRequest.server.plugins.crumb.generate
|
|
102
102
|
).not.toHaveBeenCalled()
|
|
103
103
|
})
|
|
104
|
-
|
|
105
|
-
it('should generate crumb when state exists', async () => {
|
|
106
|
-
const mockCrumb = 'generated-crumb-value'
|
|
107
|
-
const validRequest = /** @type {FormRequest} */ (
|
|
108
|
-
/** @type {unknown} */ ({
|
|
109
|
-
server: {
|
|
110
|
-
plugins: {
|
|
111
|
-
crumb: {
|
|
112
|
-
generate: jest.fn().mockReturnValue(mockCrumb)
|
|
113
|
-
},
|
|
114
|
-
'forms-engine-plugin': {
|
|
115
|
-
baseLayoutPath: 'randomValue'
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
},
|
|
119
|
-
plugins: {},
|
|
120
|
-
route: {
|
|
121
|
-
settings: {
|
|
122
|
-
plugins: {}
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
path: '/test',
|
|
126
|
-
url: { search: '' },
|
|
127
|
-
state: {},
|
|
128
|
-
yar: {
|
|
129
|
-
flash: jest.fn().mockReturnValue([]),
|
|
130
|
-
commit: jest.fn()
|
|
131
|
-
}
|
|
132
|
-
})
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
const { crumb } = await context(validRequest)
|
|
136
|
-
expect(crumb).toBe(mockCrumb)
|
|
137
|
-
expect(validRequest.server.plugins.crumb.generate).toHaveBeenCalledWith(
|
|
138
|
-
validRequest
|
|
139
|
-
)
|
|
140
|
-
})
|
|
141
104
|
})
|
|
142
105
|
})
|
|
143
106
|
|