@defra/forms-engine-plugin 4.0.38 → 4.0.39
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/server/plugins/engine/pageControllers/StatusPageController.d.ts +1 -0
- package/.server/server/plugins/engine/pageControllers/StatusPageController.js +5 -1
- package/.server/server/plugins/engine/pageControllers/StatusPageController.js.map +1 -1
- package/.server/server/plugins/engine/referenceNumbers.d.ts +7 -0
- package/.server/server/plugins/engine/referenceNumbers.js +30 -5
- package/.server/server/plugins/engine/referenceNumbers.js.map +1 -1
- package/.server/server/plugins/engine/views/confirmation.html +2 -1
- package/package.json +3 -2
- package/src/server/plugins/engine/pageControllers/StatusPageController.ts +5 -1
- package/src/server/plugins/engine/referenceNumbers.test.ts +42 -1
- package/src/server/plugins/engine/referenceNumbers.ts +43 -5
- package/src/server/plugins/engine/views/confirmation.html +2 -1
|
@@ -6,6 +6,7 @@ import { type FormRequest, type FormResponseToolkit } from '~/src/server/routes/
|
|
|
6
6
|
export declare class StatusPageController extends QuestionPageController {
|
|
7
7
|
pageDef: PageStatus;
|
|
8
8
|
allowSaveAndExit: boolean;
|
|
9
|
+
showReferenceNumber: boolean;
|
|
9
10
|
constructor(model: FormModel, pageDef: PageStatus);
|
|
10
11
|
getRelevantPath(): string;
|
|
11
12
|
makeGetRouteHandler(): (request: FormRequest, context: FormContext, h: FormResponseToolkit) => Promise<import("@hapi/hapi").ResponseObject>;
|
|
@@ -2,9 +2,11 @@ import { getCacheService } from "../helpers.js";
|
|
|
2
2
|
import { QuestionPageController } from "./QuestionPageController.js";
|
|
3
3
|
export class StatusPageController extends QuestionPageController {
|
|
4
4
|
allowSaveAndExit = false;
|
|
5
|
+
showReferenceNumber = false;
|
|
5
6
|
constructor(model, pageDef) {
|
|
6
7
|
super(model, pageDef);
|
|
7
8
|
this.viewName = 'confirmation';
|
|
9
|
+
this.showReferenceNumber = model.def.options?.showReferenceNumber ?? false;
|
|
8
10
|
}
|
|
9
11
|
getRelevantPath() {
|
|
10
12
|
return this.getStatusPath();
|
|
@@ -41,7 +43,9 @@ export class StatusPageController extends QuestionPageController {
|
|
|
41
43
|
return h.view(viewName, {
|
|
42
44
|
...viewModel,
|
|
43
45
|
submissionGuidance,
|
|
44
|
-
formName
|
|
46
|
+
formName,
|
|
47
|
+
showReferenceNumber: this.showReferenceNumber,
|
|
48
|
+
referenceNumber: context.referenceNumber
|
|
45
49
|
});
|
|
46
50
|
};
|
|
47
51
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusPageController.js","names":["getCacheService","QuestionPageController","StatusPageController","allowSaveAndExit","constructor","model","pageDef","viewName","getRelevantPath","getStatusPath","makeGetRouteHandler","request","context","h","viewModel","cacheService","server","confirmationState","getConfirmationState","confirmed","proceed","getStartPath","slug","params","formsService","services","getFormMetadata","getFormMetadataById","submissionGuidance","storedFormId","formId","formName","title","undefined","view"],"sources":["../../../../../src/server/plugins/engine/pageControllers/StatusPageController.ts"],"sourcesContent":["import { type PageStatus } from '@defra/forms-model'\n\nimport { getCacheService } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { type FormContext } from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class StatusPageController extends QuestionPageController {\n declare pageDef: PageStatus\n allowSaveAndExit = false\n\n constructor(model: FormModel, pageDef: PageStatus) {\n super(model, pageDef)\n this.viewName = 'confirmation'\n }\n\n getRelevantPath() {\n return this.getStatusPath()\n }\n\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewModel, viewName } = this\n\n const cacheService = getCacheService(request.server)\n const confirmationState = await cacheService.getConfirmationState(request)\n\n // If there's no confirmation state, then\n // redirect the user back to the start of the form\n if (!confirmationState.confirmed) {\n return this.proceed(request, h, this.getStartPath())\n }\n\n const slug = request.params.slug\n const { formsService } = this.model.services\n const { getFormMetadata, getFormMetadataById } = formsService\n\n const { submissionGuidance } = await getFormMetadata(slug)\n\n // Re-read form name if overriding display (for example, in a feedback form)\n const storedFormId = confirmationState.formId\n const formName = storedFormId\n ? (await getFormMetadataById(storedFormId)).title\n : undefined\n\n return h.view(viewName, {\n ...viewModel,\n submissionGuidance,\n formName\n })\n }\n }\n}\n"],"mappings":"AAEA,SAASA,eAAe;AAExB,SAASC,sBAAsB;AAO/B,OAAO,MAAMC,oBAAoB,SAASD,sBAAsB,CAAC;EAE/DE,gBAAgB,GAAG,KAAK;
|
|
1
|
+
{"version":3,"file":"StatusPageController.js","names":["getCacheService","QuestionPageController","StatusPageController","allowSaveAndExit","showReferenceNumber","constructor","model","pageDef","viewName","def","options","getRelevantPath","getStatusPath","makeGetRouteHandler","request","context","h","viewModel","cacheService","server","confirmationState","getConfirmationState","confirmed","proceed","getStartPath","slug","params","formsService","services","getFormMetadata","getFormMetadataById","submissionGuidance","storedFormId","formId","formName","title","undefined","view","referenceNumber"],"sources":["../../../../../src/server/plugins/engine/pageControllers/StatusPageController.ts"],"sourcesContent":["import { type PageStatus } from '@defra/forms-model'\n\nimport { getCacheService } from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { type FormContext } from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class StatusPageController extends QuestionPageController {\n declare pageDef: PageStatus\n allowSaveAndExit = false\n showReferenceNumber = false\n\n constructor(model: FormModel, pageDef: PageStatus) {\n super(model, pageDef)\n this.viewName = 'confirmation'\n this.showReferenceNumber = model.def.options?.showReferenceNumber ?? false\n }\n\n getRelevantPath() {\n return this.getStatusPath()\n }\n\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewModel, viewName } = this\n\n const cacheService = getCacheService(request.server)\n const confirmationState = await cacheService.getConfirmationState(request)\n\n // If there's no confirmation state, then\n // redirect the user back to the start of the form\n if (!confirmationState.confirmed) {\n return this.proceed(request, h, this.getStartPath())\n }\n\n const slug = request.params.slug\n const { formsService } = this.model.services\n const { getFormMetadata, getFormMetadataById } = formsService\n\n const { submissionGuidance } = await getFormMetadata(slug)\n\n // Re-read form name if overriding display (for example, in a feedback form)\n const storedFormId = confirmationState.formId\n const formName = storedFormId\n ? (await getFormMetadataById(storedFormId)).title\n : undefined\n\n return h.view(viewName, {\n ...viewModel,\n submissionGuidance,\n formName,\n showReferenceNumber: this.showReferenceNumber,\n referenceNumber: context.referenceNumber\n })\n }\n }\n}\n"],"mappings":"AAEA,SAASA,eAAe;AAExB,SAASC,sBAAsB;AAO/B,OAAO,MAAMC,oBAAoB,SAASD,sBAAsB,CAAC;EAE/DE,gBAAgB,GAAG,KAAK;EACxBC,mBAAmB,GAAG,KAAK;EAE3BC,WAAWA,CAACC,KAAgB,EAAEC,OAAmB,EAAE;IACjD,KAAK,CAACD,KAAK,EAAEC,OAAO,CAAC;IACrB,IAAI,CAACC,QAAQ,GAAG,cAAc;IAC9B,IAAI,CAACJ,mBAAmB,GAAGE,KAAK,CAACG,GAAG,CAACC,OAAO,EAAEN,mBAAmB,IAAI,KAAK;EAC5E;EAEAO,eAAeA,CAAA,EAAG;IAChB,OAAO,IAAI,CAACC,aAAa,CAAC,CAAC;EAC7B;EAEAC,mBAAmBA,CAAA,EAAG;IACpB,OAAO,OACLC,OAAoB,EACpBC,OAAoB,EACpBC,CAAsB,KACnB;MACH,MAAM;QAAEC,SAAS;QAAET;MAAS,CAAC,GAAG,IAAI;MAEpC,MAAMU,YAAY,GAAGlB,eAAe,CAACc,OAAO,CAACK,MAAM,CAAC;MACpD,MAAMC,iBAAiB,GAAG,MAAMF,YAAY,CAACG,oBAAoB,CAACP,OAAO,CAAC;;MAE1E;MACA;MACA,IAAI,CAACM,iBAAiB,CAACE,SAAS,EAAE;QAChC,OAAO,IAAI,CAACC,OAAO,CAACT,OAAO,EAAEE,CAAC,EAAE,IAAI,CAACQ,YAAY,CAAC,CAAC,CAAC;MACtD;MAEA,MAAMC,IAAI,GAAGX,OAAO,CAACY,MAAM,CAACD,IAAI;MAChC,MAAM;QAAEE;MAAa,CAAC,GAAG,IAAI,CAACrB,KAAK,CAACsB,QAAQ;MAC5C,MAAM;QAAEC,eAAe;QAAEC;MAAoB,CAAC,GAAGH,YAAY;MAE7D,MAAM;QAAEI;MAAmB,CAAC,GAAG,MAAMF,eAAe,CAACJ,IAAI,CAAC;;MAE1D;MACA,MAAMO,YAAY,GAAGZ,iBAAiB,CAACa,MAAM;MAC7C,MAAMC,QAAQ,GAAGF,YAAY,GACzB,CAAC,MAAMF,mBAAmB,CAACE,YAAY,CAAC,EAAEG,KAAK,GAC/CC,SAAS;MAEb,OAAOpB,CAAC,CAACqB,IAAI,CAAC7B,QAAQ,EAAE;QACtB,GAAGS,SAAS;QACZc,kBAAkB;QAClBG,QAAQ;QACR9B,mBAAmB,EAAE,IAAI,CAACA,mBAAmB;QAC7CkC,eAAe,EAAEvB,OAAO,CAACuB;MAC3B,CAAC,CAAC;IACJ,CAAC;EACH;AACF","ignoreList":[]}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* To prevent confusion to users reading the reference number, ambiguous letters and numbers are removed.
|
|
3
|
+
* @param strCodes - array of binary input values
|
|
4
|
+
*/
|
|
5
|
+
export declare function convertToDecAlpha(strCodes: number[]): string;
|
|
1
6
|
/**
|
|
2
7
|
* Generates a reference number in the format of `XXX-XXX-XXX`, or `PREFIX-XXX-XXX` if a prefix is provided.
|
|
3
8
|
* Provides no guarantee on uniqueness.
|
|
9
|
+
* To prevent confusion to users reading the reference number, ambiguous letters and numbers are removed
|
|
10
|
+
* (see https://gunkies.org/wiki/DEC_alphabet )
|
|
4
11
|
*/
|
|
5
12
|
export declare function generateUniqueReference(prefix?: string): string;
|
|
@@ -1,17 +1,42 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { RegExpMatcher, englishDataset, englishRecommendedTransformers } from 'obscenity';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* To prevent confusion to users reading the reference number, ambiguous letters and numbers are removed.
|
|
6
|
+
* @param strCodes - array of binary input values
|
|
7
|
+
*/
|
|
8
|
+
export function convertToDecAlpha(strCodes) {
|
|
9
|
+
const validChars = 'ABCDEFHJKLMNPRSTUVWXYZ23456789';
|
|
10
|
+
const strLen = validChars.length;
|
|
11
|
+
const outArray = [];
|
|
12
|
+
strCodes.forEach(code => {
|
|
13
|
+
const pos = code / 256 * strLen;
|
|
14
|
+
outArray.push(validChars.charAt(pos));
|
|
15
|
+
});
|
|
16
|
+
return outArray.join('');
|
|
17
|
+
}
|
|
2
18
|
|
|
3
19
|
/**
|
|
4
20
|
* Generates a reference number in the format of `XXX-XXX-XXX`, or `PREFIX-XXX-XXX` if a prefix is provided.
|
|
5
21
|
* Provides no guarantee on uniqueness.
|
|
22
|
+
* To prevent confusion to users reading the reference number, ambiguous letters and numbers are removed
|
|
23
|
+
* (see https://gunkies.org/wiki/DEC_alphabet )
|
|
6
24
|
*/
|
|
7
25
|
export function generateUniqueReference(prefix) {
|
|
8
26
|
const segmentLength = 3;
|
|
9
27
|
const segmentCount = prefix ? 2 : 3;
|
|
10
28
|
prefix = prefix ? `${prefix}-` : '';
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
);
|
|
15
|
-
|
|
29
|
+
const profanityMatcher = new RegExpMatcher({
|
|
30
|
+
...englishDataset.build(),
|
|
31
|
+
...englishRecommendedTransformers
|
|
32
|
+
});
|
|
33
|
+
let referenceNumber;
|
|
34
|
+
do {
|
|
35
|
+
const segments = Array.from({
|
|
36
|
+
length: segmentCount
|
|
37
|
+
}, () => convertToDecAlpha([...randomBytes(segmentLength)]).slice(0, segmentLength * 2));
|
|
38
|
+
referenceNumber = `${prefix}${segments.join('-')}`.toUpperCase();
|
|
39
|
+
} while (profanityMatcher.hasMatch(referenceNumber.replaceAll('-', '')));
|
|
40
|
+
return referenceNumber;
|
|
16
41
|
}
|
|
17
42
|
//# sourceMappingURL=referenceNumbers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"referenceNumbers.js","names":["randomBytes","generateUniqueReference","prefix","segmentLength","segmentCount","
|
|
1
|
+
{"version":3,"file":"referenceNumbers.js","names":["randomBytes","RegExpMatcher","englishDataset","englishRecommendedTransformers","convertToDecAlpha","strCodes","validChars","strLen","length","outArray","forEach","code","pos","push","charAt","join","generateUniqueReference","prefix","segmentLength","segmentCount","profanityMatcher","build","referenceNumber","segments","Array","from","slice","toUpperCase","hasMatch","replaceAll"],"sources":["../../../../src/server/plugins/engine/referenceNumbers.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto'\n\nimport {\n RegExpMatcher,\n englishDataset,\n englishRecommendedTransformers\n} from 'obscenity'\n\n/**\n * To prevent confusion to users reading the reference number, ambiguous letters and numbers are removed.\n * @param strCodes - array of binary input values\n */\nexport function convertToDecAlpha(strCodes: number[]) {\n const validChars = 'ABCDEFHJKLMNPRSTUVWXYZ23456789'\n const strLen = validChars.length\n const outArray = [] as string[]\n\n strCodes.forEach((code) => {\n const pos = (code / 256) * strLen\n outArray.push(validChars.charAt(pos))\n })\n\n return outArray.join('')\n}\n\n/**\n * Generates a reference number in the format of `XXX-XXX-XXX`, or `PREFIX-XXX-XXX` if a prefix is provided.\n * Provides no guarantee on uniqueness.\n * To prevent confusion to users reading the reference number, ambiguous letters and numbers are removed\n * (see https://gunkies.org/wiki/DEC_alphabet )\n */\nexport function generateUniqueReference(prefix?: string) {\n const segmentLength = 3\n const segmentCount = prefix ? 2 : 3\n prefix = prefix ? `${prefix}-` : ''\n\n const profanityMatcher = new RegExpMatcher({\n ...englishDataset.build(),\n ...englishRecommendedTransformers\n })\n\n let referenceNumber\n\n do {\n const segments = Array.from({ length: segmentCount }, () =>\n convertToDecAlpha([...randomBytes(segmentLength)]).slice(\n 0,\n segmentLength * 2\n )\n )\n\n referenceNumber = `${prefix}${segments.join('-')}`.toUpperCase()\n } while (profanityMatcher.hasMatch(referenceNumber.replaceAll('-', '')))\n\n return referenceNumber\n}\n"],"mappings":"AAAA,SAASA,WAAW,QAAQ,aAAa;AAEzC,SACEC,aAAa,EACbC,cAAc,EACdC,8BAA8B,QACzB,WAAW;;AAElB;AACA;AACA;AACA;AACA,OAAO,SAASC,iBAAiBA,CAACC,QAAkB,EAAE;EACpD,MAAMC,UAAU,GAAG,gCAAgC;EACnD,MAAMC,MAAM,GAAGD,UAAU,CAACE,MAAM;EAChC,MAAMC,QAAQ,GAAG,EAAc;EAE/BJ,QAAQ,CAACK,OAAO,CAAEC,IAAI,IAAK;IACzB,MAAMC,GAAG,GAAID,IAAI,GAAG,GAAG,GAAIJ,MAAM;IACjCE,QAAQ,CAACI,IAAI,CAACP,UAAU,CAACQ,MAAM,CAACF,GAAG,CAAC,CAAC;EACvC,CAAC,CAAC;EAEF,OAAOH,QAAQ,CAACM,IAAI,CAAC,EAAE,CAAC;AAC1B;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,uBAAuBA,CAACC,MAAe,EAAE;EACvD,MAAMC,aAAa,GAAG,CAAC;EACvB,MAAMC,YAAY,GAAGF,MAAM,GAAG,CAAC,GAAG,CAAC;EACnCA,MAAM,GAAGA,MAAM,GAAG,GAAGA,MAAM,GAAG,GAAG,EAAE;EAEnC,MAAMG,gBAAgB,GAAG,IAAInB,aAAa,CAAC;IACzC,GAAGC,cAAc,CAACmB,KAAK,CAAC,CAAC;IACzB,GAAGlB;EACL,CAAC,CAAC;EAEF,IAAImB,eAAe;EAEnB,GAAG;IACD,MAAMC,QAAQ,GAAGC,KAAK,CAACC,IAAI,CAAC;MAAEjB,MAAM,EAAEW;IAAa,CAAC,EAAE,MACpDf,iBAAiB,CAAC,CAAC,GAAGJ,WAAW,CAACkB,aAAa,CAAC,CAAC,CAAC,CAACQ,KAAK,CACtD,CAAC,EACDR,aAAa,GAAG,CAClB,CACF,CAAC;IAEDI,eAAe,GAAG,GAAGL,MAAM,GAAGM,QAAQ,CAACR,IAAI,CAAC,GAAG,CAAC,EAAE,CAACY,WAAW,CAAC,CAAC;EAClE,CAAC,QAAQP,gBAAgB,CAACQ,QAAQ,CAACN,eAAe,CAACO,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;EAEvE,OAAOP,eAAe;AACxB","ignoreList":[]}
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
<div class="govuk-grid-row">
|
|
9
9
|
<div class="govuk-grid-column-two-thirds">
|
|
10
10
|
{{ govukPanel({
|
|
11
|
-
titleText: pageTitle
|
|
11
|
+
titleText: pageTitle,
|
|
12
|
+
html: "Your reference number<br><strong>" + referenceNumber + "</strong>" if showReferenceNumber
|
|
12
13
|
}) }}
|
|
13
14
|
<h2 class="govuk-heading-m">What happens next</h2>
|
|
14
15
|
<div class="app-prose-scope">
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra/forms-engine-plugin",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.39",
|
|
4
4
|
"description": "Defra forms engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"license": "SEE LICENSE IN LICENSE",
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@defra/forms-model": "^3.0.
|
|
73
|
+
"@defra/forms-model": "^3.0.603",
|
|
74
74
|
"@defra/hapi-tracing": "^1.29.0",
|
|
75
75
|
"@elastic/ecs-pino-format": "^1.5.0",
|
|
76
76
|
"@hapi/boom": "^10.0.1",
|
|
@@ -108,6 +108,7 @@
|
|
|
108
108
|
"lodash": "^4.17.21",
|
|
109
109
|
"marked": "^15.0.12",
|
|
110
110
|
"nunjucks": "^3.2.4",
|
|
111
|
+
"obscenity": "^0.4.5",
|
|
111
112
|
"outdent": "^0.8.0",
|
|
112
113
|
"pino": "^9.14.0",
|
|
113
114
|
"pino-pretty": "^13.1.2",
|
|
@@ -12,10 +12,12 @@ import {
|
|
|
12
12
|
export class StatusPageController extends QuestionPageController {
|
|
13
13
|
declare pageDef: PageStatus
|
|
14
14
|
allowSaveAndExit = false
|
|
15
|
+
showReferenceNumber = false
|
|
15
16
|
|
|
16
17
|
constructor(model: FormModel, pageDef: PageStatus) {
|
|
17
18
|
super(model, pageDef)
|
|
18
19
|
this.viewName = 'confirmation'
|
|
20
|
+
this.showReferenceNumber = model.def.options?.showReferenceNumber ?? false
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
getRelevantPath() {
|
|
@@ -54,7 +56,9 @@ export class StatusPageController extends QuestionPageController {
|
|
|
54
56
|
return h.view(viewName, {
|
|
55
57
|
...viewModel,
|
|
56
58
|
submissionGuidance,
|
|
57
|
-
formName
|
|
59
|
+
formName,
|
|
60
|
+
showReferenceNumber: this.showReferenceNumber,
|
|
61
|
+
referenceNumber: context.referenceNumber
|
|
58
62
|
})
|
|
59
63
|
}
|
|
60
64
|
}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
convertToDecAlpha,
|
|
3
|
+
generateUniqueReference
|
|
4
|
+
} from '~/src/server/plugins/engine/referenceNumbers.js'
|
|
2
5
|
|
|
3
6
|
describe('generateUniqueReference', () => {
|
|
4
7
|
it('should generate a reference number with 3 segments when no prefix is provided', () => {
|
|
@@ -30,4 +33,42 @@ describe('generateUniqueReference', () => {
|
|
|
30
33
|
const referenceNumber2 = generateUniqueReference()
|
|
31
34
|
expect(referenceNumber1).not.toBe(referenceNumber2)
|
|
32
35
|
})
|
|
36
|
+
|
|
37
|
+
describe('convertToDecAlpha', () => {
|
|
38
|
+
it('should generate correct characters in string', () => {
|
|
39
|
+
const allValuesHexPairs = Array.from(Array(256).keys())
|
|
40
|
+
expect(convertToDecAlpha(allValuesHexPairs)).toBe(
|
|
41
|
+
'AAAAAAAAA' +
|
|
42
|
+
'BBBBBBBBB' +
|
|
43
|
+
'CCCCCCCC' +
|
|
44
|
+
'DDDDDDDDD' +
|
|
45
|
+
'EEEEEEEE' +
|
|
46
|
+
'FFFFFFFFF' +
|
|
47
|
+
'HHHHHHHH' +
|
|
48
|
+
'JJJJJJJJJ' +
|
|
49
|
+
'KKKKKKKK' +
|
|
50
|
+
'LLLLLLLLL' +
|
|
51
|
+
'MMMMMMMM' +
|
|
52
|
+
'NNNNNNNNN' +
|
|
53
|
+
'PPPPPPPP' +
|
|
54
|
+
'RRRRRRRRR' +
|
|
55
|
+
'SSSSSSSS' +
|
|
56
|
+
'TTTTTTTTT' +
|
|
57
|
+
'UUUUUUUUU' +
|
|
58
|
+
'VVVVVVVV' +
|
|
59
|
+
'WWWWWWWWW' +
|
|
60
|
+
'XXXXXXXX' +
|
|
61
|
+
'YYYYYYYYY' +
|
|
62
|
+
'ZZZZZZZZ' +
|
|
63
|
+
'222222222' +
|
|
64
|
+
'33333333' +
|
|
65
|
+
'444444444' +
|
|
66
|
+
'55555555' +
|
|
67
|
+
'666666666' +
|
|
68
|
+
'77777777' +
|
|
69
|
+
'888888888' +
|
|
70
|
+
'99999999'
|
|
71
|
+
)
|
|
72
|
+
})
|
|
73
|
+
})
|
|
33
74
|
})
|
|
@@ -1,18 +1,56 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto'
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
RegExpMatcher,
|
|
5
|
+
englishDataset,
|
|
6
|
+
englishRecommendedTransformers
|
|
7
|
+
} from 'obscenity'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* To prevent confusion to users reading the reference number, ambiguous letters and numbers are removed.
|
|
11
|
+
* @param strCodes - array of binary input values
|
|
12
|
+
*/
|
|
13
|
+
export function convertToDecAlpha(strCodes: number[]) {
|
|
14
|
+
const validChars = 'ABCDEFHJKLMNPRSTUVWXYZ23456789'
|
|
15
|
+
const strLen = validChars.length
|
|
16
|
+
const outArray = [] as string[]
|
|
17
|
+
|
|
18
|
+
strCodes.forEach((code) => {
|
|
19
|
+
const pos = (code / 256) * strLen
|
|
20
|
+
outArray.push(validChars.charAt(pos))
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
return outArray.join('')
|
|
24
|
+
}
|
|
25
|
+
|
|
3
26
|
/**
|
|
4
27
|
* Generates a reference number in the format of `XXX-XXX-XXX`, or `PREFIX-XXX-XXX` if a prefix is provided.
|
|
5
28
|
* Provides no guarantee on uniqueness.
|
|
29
|
+
* To prevent confusion to users reading the reference number, ambiguous letters and numbers are removed
|
|
30
|
+
* (see https://gunkies.org/wiki/DEC_alphabet )
|
|
6
31
|
*/
|
|
7
32
|
export function generateUniqueReference(prefix?: string) {
|
|
8
33
|
const segmentLength = 3
|
|
9
34
|
const segmentCount = prefix ? 2 : 3
|
|
10
35
|
prefix = prefix ? `${prefix}-` : ''
|
|
11
36
|
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
)
|
|
37
|
+
const profanityMatcher = new RegExpMatcher({
|
|
38
|
+
...englishDataset.build(),
|
|
39
|
+
...englishRecommendedTransformers
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
let referenceNumber
|
|
43
|
+
|
|
44
|
+
do {
|
|
45
|
+
const segments = Array.from({ length: segmentCount }, () =>
|
|
46
|
+
convertToDecAlpha([...randomBytes(segmentLength)]).slice(
|
|
47
|
+
0,
|
|
48
|
+
segmentLength * 2
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
referenceNumber = `${prefix}${segments.join('-')}`.toUpperCase()
|
|
53
|
+
} while (profanityMatcher.hasMatch(referenceNumber.replaceAll('-', '')))
|
|
16
54
|
|
|
17
|
-
return
|
|
55
|
+
return referenceNumber
|
|
18
56
|
}
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
<div class="govuk-grid-row">
|
|
9
9
|
<div class="govuk-grid-column-two-thirds">
|
|
10
10
|
{{ govukPanel({
|
|
11
|
-
titleText: pageTitle
|
|
11
|
+
titleText: pageTitle,
|
|
12
|
+
html: "Your reference number<br><strong>" + referenceNumber + "</strong>" if showReferenceNumber
|
|
12
13
|
}) }}
|
|
13
14
|
<h2 class="govuk-heading-m">What happens next</h2>
|
|
14
15
|
<div class="app-prose-scope">
|