@formio/uag 2.0.0-rc.5 → 2.0.0-rc.7
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/bin/uag.js +6 -2
- package/lib/UAGFormInterface.d.ts +12 -0
- package/lib/UAGFormInterface.js +30 -3
- package/lib/UAGProjectInterface.d.ts +9 -1
- package/lib/UAGProjectInterface.js +14 -3
- package/lib/agent/anonymous.js +1 -1
- package/lib/agent/engines.d.ts +10 -0
- package/lib/agent/engines.js +15 -1
- package/lib/agent/errors.d.ts +9 -0
- package/lib/agent/errors.js +20 -0
- package/lib/agent/index.d.ts +1 -1
- package/lib/agent/index.js +2 -1
- package/lib/agent/remote.d.ts +5 -1
- package/lib/agent/remote.js +25 -2
- package/lib/agent/router.d.ts +32 -2
- package/lib/agent/router.js +17 -11
- package/lib/agent/stubs.d.ts +13 -2
- package/lib/agent/stubs.js +18 -5
- package/lib/agent/tools.d.ts +24 -3
- package/lib/agent/tools.js +47 -4
- package/lib/config.d.ts +31 -2
- package/lib/documents/convert.d.ts +38 -0
- package/lib/documents/convert.js +151 -0
- package/lib/documents/converters.d.ts +26 -0
- package/lib/documents/converters.js +139 -0
- package/lib/documents/detect.d.ts +31 -0
- package/lib/documents/detect.js +169 -0
- package/lib/documents/index.d.ts +9 -0
- package/lib/documents/index.js +43 -0
- package/lib/documents/mime.d.ts +39 -0
- package/lib/documents/mime.js +100 -0
- package/lib/documents/plugins.d.ts +18 -0
- package/lib/documents/plugins.js +65 -0
- package/lib/documents/readers.d.ts +19 -0
- package/lib/documents/readers.js +64 -0
- package/lib/documents/routes.d.ts +52 -0
- package/lib/documents/routes.js +99 -0
- package/lib/documents/types.d.ts +126 -0
- package/lib/documents/types.js +2 -0
- package/lib/documents/uagJs.d.ts +17 -0
- package/lib/documents/uagJs.js +157 -0
- package/lib/formflow.d.ts +4 -1
- package/lib/formflow.js +2 -2
- package/lib/index.d.ts +50 -6
- package/lib/index.js +150 -15
- package/lib/markdown.d.ts +1 -0
- package/lib/markdown.js +166 -0
- package/lib/modules/api.d.ts +22 -0
- package/lib/modules/api.js +100 -0
- package/lib/modules/index.d.ts +4 -0
- package/lib/modules/index.js +24 -0
- package/lib/modules/routes.d.ts +14 -0
- package/lib/modules/routes.js +21 -0
- package/lib/modules/types.d.ts +68 -0
- package/lib/modules/types.js +5 -0
- package/lib/pluginFiles.d.ts +16 -0
- package/lib/pluginFiles.js +62 -0
- package/lib/plugins.d.ts +17 -0
- package/lib/plugins.js +93 -0
- package/lib/richText.d.ts +20 -0
- package/lib/richText.js +99 -0
- package/lib/router.d.ts +9 -0
- package/lib/router.js +19 -0
- package/lib/testing.d.ts +74 -0
- package/lib/testing.js +165 -0
- package/lib/tools/index.d.ts +2 -1
- package/lib/tools/index.js +5 -2
- package/lib/tools/readDocument.d.ts +17 -0
- package/lib/tools/readDocument.js +99 -0
- package/lib/tools/submissionUpdate.js +3 -2
- package/lib/tools/utils.d.ts +40 -0
- package/lib/tools/utils.js +16 -1
- package/package.json +10 -5
- package/testing.d.ts +1 -0
- package/testing.js +2 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ConverterRoutes = exports.stepName = void 0;
|
|
4
|
+
const mime_1 = require("./mime");
|
|
5
|
+
const detect_1 = require("./detect");
|
|
6
|
+
/** A converter as `name@version`, as results record routes. */
|
|
7
|
+
const stepName = (converter) => `${converter.name}@${converter.version}`;
|
|
8
|
+
exports.stepName = stepName;
|
|
9
|
+
/**
|
|
10
|
+
* Routes from source types to representations over a set of converters.
|
|
11
|
+
* When several converters can do a step, the component's pin wins, then the
|
|
12
|
+
* operator's `documents.prefer`, then the highest rank, then the name.
|
|
13
|
+
*/
|
|
14
|
+
class ConverterRoutes {
|
|
15
|
+
constructor(converters, prefer = {}) {
|
|
16
|
+
this.converters = converters;
|
|
17
|
+
this.prefer = prefer;
|
|
18
|
+
}
|
|
19
|
+
/** The converter for one step from `source` to `target`, or undefined when none can do it. */
|
|
20
|
+
choose(source, target, pins = {}) {
|
|
21
|
+
const candidates = this.converters.filter((converter) => converter.produces.includes(target) && converter.accepts.some((accept) => accept.type === source));
|
|
22
|
+
if (!candidates.length)
|
|
23
|
+
return undefined;
|
|
24
|
+
for (const preferred of [pins[source], this.prefer[source]]) {
|
|
25
|
+
const found = preferred && candidates.find((converter) => converter.name === preferred);
|
|
26
|
+
if (found)
|
|
27
|
+
return found;
|
|
28
|
+
}
|
|
29
|
+
return [...candidates].sort((a, b) => ((b.rank || 0) - (a.rank || 0)) || a.name.localeCompare(b.name))[0];
|
|
30
|
+
}
|
|
31
|
+
/** The steps from `source` to a PDF: [] for a PDF, one converter otherwise, or null. */
|
|
32
|
+
toPdf(source, pins = {}) {
|
|
33
|
+
if (source === mime_1.MIME.pdf)
|
|
34
|
+
return [];
|
|
35
|
+
const converter = this.choose(source, 'pdf', pins);
|
|
36
|
+
return converter ? [converter] : null;
|
|
37
|
+
}
|
|
38
|
+
/** The steps from `source` to text: one converter, or to a PDF and then its text; null when none. */
|
|
39
|
+
toText(source, pins = {}) {
|
|
40
|
+
const direct = this.choose(source, 'text', pins);
|
|
41
|
+
if (direct)
|
|
42
|
+
return [direct];
|
|
43
|
+
const pdf = this.toPdf(source, pins);
|
|
44
|
+
const fromPdf = pdf && this.choose(mime_1.MIME.pdf, 'text', pins);
|
|
45
|
+
return pdf && fromPdf ? [...pdf, fromPdf] : null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The first of a plugin's input sets every representation of which the
|
|
49
|
+
* file can reach. With both PDF and text, the text is always that same
|
|
50
|
+
* PDF's text, so content made from the PDF is checked against it.
|
|
51
|
+
*/
|
|
52
|
+
route(source, inputs, pins = {}) {
|
|
53
|
+
for (const set of inputs) {
|
|
54
|
+
if (set.includes('pdf')) {
|
|
55
|
+
const pdf = this.toPdf(source, pins);
|
|
56
|
+
if (!pdf)
|
|
57
|
+
continue;
|
|
58
|
+
if (!set.includes('text'))
|
|
59
|
+
return { inputs: set, pdf };
|
|
60
|
+
const fromPdf = this.choose(mime_1.MIME.pdf, 'text', pins);
|
|
61
|
+
if (!fromPdf)
|
|
62
|
+
continue;
|
|
63
|
+
return { inputs: set, pdf, text: [...pdf, fromPdf] };
|
|
64
|
+
}
|
|
65
|
+
if (set.includes('text')) {
|
|
66
|
+
const text = this.toText(source, pins);
|
|
67
|
+
if (text)
|
|
68
|
+
return { inputs: set, text };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
/** Every source type a converter reads, and PDF. */
|
|
74
|
+
sourceTypes() {
|
|
75
|
+
return [...new Set([mime_1.MIME.pdf, ...mime_1.TEXT_TYPES.filter((type) => this.converters.some((c) => c.accepts.some((a) => a.type === type))),
|
|
76
|
+
...this.converters.flatMap((converter) => converter.accepts.map((accept) => accept.type))])];
|
|
77
|
+
}
|
|
78
|
+
/** The source types a plugin with these inputs can take, with their routes: its manifest's `accept`. */
|
|
79
|
+
accept(inputs) {
|
|
80
|
+
const entries = [];
|
|
81
|
+
for (const type of this.sourceTypes()) {
|
|
82
|
+
const route = this.route(type, inputs);
|
|
83
|
+
if (!route)
|
|
84
|
+
continue;
|
|
85
|
+
entries.push({
|
|
86
|
+
type,
|
|
87
|
+
ext: (0, detect_1.extensionsOf)(type, this.converters),
|
|
88
|
+
routes: {
|
|
89
|
+
inputs: route.inputs,
|
|
90
|
+
...(route.pdf && { pdf: route.pdf.map((c) => c.name) }),
|
|
91
|
+
...(route.text && { text: route.text.map((c) => c.name) })
|
|
92
|
+
},
|
|
93
|
+
converters: this.converters.filter((c) => c.accepts.some((a) => a.type === type)).map((c) => c.name)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return entries;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.ConverterRoutes = ConverterRoutes;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { Plugin, PluginContext } from '@formio/appserver';
|
|
2
|
+
import type { GenerateRequest, GenerateResponse } from '@formio/uag-sdk';
|
|
3
|
+
export type Representation = 'pdf' | 'text';
|
|
4
|
+
export type TextFormat = 'plain' | 'markdown' | 'csv';
|
|
5
|
+
/** Plugin type `converter` (apiVersion 1, experimental): a file into a PDF or text. */
|
|
6
|
+
export interface Converter extends Plugin {
|
|
7
|
+
type: 'converter';
|
|
8
|
+
/** MIME types (canonical) and extensions it reads. */
|
|
9
|
+
accepts: Array<{
|
|
10
|
+
type: string;
|
|
11
|
+
ext?: string[];
|
|
12
|
+
}>;
|
|
13
|
+
produces: Representation[];
|
|
14
|
+
/** Default 0; the higher wins when several converters can do a step. */
|
|
15
|
+
rank?: number;
|
|
16
|
+
/** A canonical type for formats the shared detection does not know, or null. */
|
|
17
|
+
detect?(head: Buffer, hint: {
|
|
18
|
+
name: string;
|
|
19
|
+
type: string;
|
|
20
|
+
}): string | null;
|
|
21
|
+
/** Defaults: 25 MB, 120000 ms, 2 at a time. */
|
|
22
|
+
limits?: {
|
|
23
|
+
maxBytes?: number;
|
|
24
|
+
timeoutMs?: number;
|
|
25
|
+
concurrency?: number;
|
|
26
|
+
};
|
|
27
|
+
convert(input: {
|
|
28
|
+
buffer: Buffer;
|
|
29
|
+
type: string;
|
|
30
|
+
name: string;
|
|
31
|
+
}, options: {
|
|
32
|
+
to: Representation;
|
|
33
|
+
settings: Record<string, any>;
|
|
34
|
+
signal: AbortSignal;
|
|
35
|
+
tmpdir: string;
|
|
36
|
+
log: PluginContext['log'];
|
|
37
|
+
}): Promise<ConverterOutput>;
|
|
38
|
+
}
|
|
39
|
+
export type ConverterOutput = {
|
|
40
|
+
pdf: Buffer;
|
|
41
|
+
pages?: number;
|
|
42
|
+
warnings?: string[];
|
|
43
|
+
} | {
|
|
44
|
+
text: string;
|
|
45
|
+
textFormat: TextFormat;
|
|
46
|
+
pages?: Array<{
|
|
47
|
+
page: number;
|
|
48
|
+
text: string;
|
|
49
|
+
}>;
|
|
50
|
+
warnings?: string[];
|
|
51
|
+
};
|
|
52
|
+
export type DocumentFile = {
|
|
53
|
+
name: string;
|
|
54
|
+
type: string;
|
|
55
|
+
size: number;
|
|
56
|
+
sha256: string;
|
|
57
|
+
document: {
|
|
58
|
+
pdf?: Buffer;
|
|
59
|
+
pages?: number;
|
|
60
|
+
text?: string;
|
|
61
|
+
textFormat?: string;
|
|
62
|
+
textPages?: Array<{
|
|
63
|
+
page: number;
|
|
64
|
+
text: string;
|
|
65
|
+
}>;
|
|
66
|
+
};
|
|
67
|
+
/** The converters it went through, as name@version. */
|
|
68
|
+
route: string[];
|
|
69
|
+
original(): Promise<Buffer>;
|
|
70
|
+
};
|
|
71
|
+
export type DocumentContext = {
|
|
72
|
+
files: DocumentFile[];
|
|
73
|
+
/** Markdown. */
|
|
74
|
+
instructions: string;
|
|
75
|
+
/** This component's plugin settings, defaults applied. */
|
|
76
|
+
settings: Record<string, any>;
|
|
77
|
+
/** Operator settings. */
|
|
78
|
+
config: Record<string, any>;
|
|
79
|
+
form: {
|
|
80
|
+
id: string;
|
|
81
|
+
path: string;
|
|
82
|
+
title: string;
|
|
83
|
+
};
|
|
84
|
+
component: {
|
|
85
|
+
key: string;
|
|
86
|
+
path: string;
|
|
87
|
+
label: string;
|
|
88
|
+
};
|
|
89
|
+
/** Submission data sent with the request: context only, untrusted. */
|
|
90
|
+
data: Record<string, any>;
|
|
91
|
+
agent: {
|
|
92
|
+
generate(request: Omit<GenerateRequest, 'signal'>): Promise<GenerateResponse>;
|
|
93
|
+
};
|
|
94
|
+
progress(fraction: number, message?: string): void;
|
|
95
|
+
signal: AbortSignal;
|
|
96
|
+
log: PluginContext['log'];
|
|
97
|
+
};
|
|
98
|
+
export type Issue = {
|
|
99
|
+
kind: string;
|
|
100
|
+
message: string;
|
|
101
|
+
output?: string;
|
|
102
|
+
document?: string;
|
|
103
|
+
page?: number;
|
|
104
|
+
file?: string;
|
|
105
|
+
};
|
|
106
|
+
export type DocumentResult = {
|
|
107
|
+
content: string;
|
|
108
|
+
status: 'ok' | 'flagged';
|
|
109
|
+
issues?: Issue[];
|
|
110
|
+
summary?: string;
|
|
111
|
+
};
|
|
112
|
+
/** Plugin type `document` (apiVersion 1, experimental): files and instructions into content. */
|
|
113
|
+
export interface DocumentPlugin extends Plugin {
|
|
114
|
+
type: 'document';
|
|
115
|
+
/** Acceptable representation sets, in order of preference. */
|
|
116
|
+
inputs: Representation[][];
|
|
117
|
+
/** JSON only; keys are relative to the plugin's settings object. */
|
|
118
|
+
editForm?: any[];
|
|
119
|
+
defaults?: Record<string, any>;
|
|
120
|
+
/** May people edit the content? Default false. */
|
|
121
|
+
editable?: boolean;
|
|
122
|
+
/** Default 'markdown'. */
|
|
123
|
+
output?: 'markdown' | 'text';
|
|
124
|
+
validateSettings?(settings: Record<string, any>): string[];
|
|
125
|
+
process(ctx: DocumentContext): Promise<DocumentResult>;
|
|
126
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import type { PluginRegistry, ServedComponent } from '@formio/appserver';
|
|
3
|
+
/**
|
|
4
|
+
* `@formio/uag-js` from the UAG's own installation, at stable URLs (never a
|
|
5
|
+
* version in the path: the portal remembers its one-time prompt by URL):
|
|
6
|
+
*
|
|
7
|
+
* - `/uag-js/uag.use.js`: the bundle, registering itself with this UAG's URL and the manifest;
|
|
8
|
+
* - `/uag-js/portal.js`: the same in portal mode (the component only), then `?chain=` (https only);
|
|
9
|
+
* - `/uag-js/portal.css`: the stylesheet, after `@import url(<chain>)` when `?chain=` is given;
|
|
10
|
+
* - `/uag-js/uag-js.css`: the stylesheet.
|
|
11
|
+
*
|
|
12
|
+
* The modules' components come after: each script after the registration in
|
|
13
|
+
* `uag.use.js` and `portal.js`, each stylesheet after `uag-js.css` (and in
|
|
14
|
+
* `portal.css`), and each on its own at `/uag-js/components/<type>.js|css`.
|
|
15
|
+
* They are read on every request, as the bundle is, so the ETag follows them.
|
|
16
|
+
*/
|
|
17
|
+
export declare function uagJsRouter(registry: () => PluginRegistry | undefined, components?: () => ServedComponent[]): Router;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.uagJsRouter = uagJsRouter;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const vm_1 = __importDefault(require("vm"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const crypto_1 = require("crypto");
|
|
11
|
+
const express_1 = require("express");
|
|
12
|
+
/** Where @formio/uag-js is installed: as a package, or the UAG's own workspace copy. */
|
|
13
|
+
function uagJsDirectory() {
|
|
14
|
+
try {
|
|
15
|
+
return path_1.default.dirname(require.resolve('@formio/uag-js/package.json'));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
const local = path_1.default.resolve(__dirname, '..', '..', 'packages', 'uag-js');
|
|
19
|
+
return fs_1.default.existsSync(path_1.default.join(local, 'index.js')) ? local : undefined;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const read = (file) => fs_1.default.readFileSync(path_1.default.join(uagJsDirectory() || '', file), 'utf8');
|
|
23
|
+
/** A chained script or stylesheet URL from `?chain=`, when it is an absolute URL of the allowed schemes. */
|
|
24
|
+
function chainOf(req, schemes) {
|
|
25
|
+
const chain = typeof req.query.chain === 'string' ? req.query.chain : '';
|
|
26
|
+
if (!chain)
|
|
27
|
+
return undefined;
|
|
28
|
+
try {
|
|
29
|
+
const url = new URL(chain);
|
|
30
|
+
return schemes.includes(url.protocol) ? url.href : undefined;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The script that registers the bundle once formio.js is on the page: with
|
|
38
|
+
* this UAG's URL (from the script's own src) and the manifest, and in portal
|
|
39
|
+
* mode for the portal script, then loads the chained script over https only.
|
|
40
|
+
*/
|
|
41
|
+
function registration(manifest, options) {
|
|
42
|
+
return `
|
|
43
|
+
;(function () {
|
|
44
|
+
var manifest = ${JSON.stringify(manifest).replace(/</g, '\\u003c')};
|
|
45
|
+
var script = document.currentScript;
|
|
46
|
+
var src = script && script.src ? new URL(script.src, location.href) : null;
|
|
47
|
+
var uagUrl = src ? src.href.slice(0, src.href.indexOf('/uag-js/')) : location.origin;
|
|
48
|
+
function register() {
|
|
49
|
+
var Formio = window.Formio;
|
|
50
|
+
var lib = window.FormioUAG;
|
|
51
|
+
if (!Formio || !lib || typeof Formio.use !== 'function') return false;
|
|
52
|
+
Formio.use(lib.UAG(uagUrl, { ${options.portal ? 'portal: true, ' : ''}manifest: manifest }));
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
if (!register()) {
|
|
56
|
+
var tries = 0;
|
|
57
|
+
var timer = setInterval(function () { if (register() || ++tries > 600) clearInterval(timer); }, 50);
|
|
58
|
+
}
|
|
59
|
+
${options.chain ? `var chain = document.createElement('script');
|
|
60
|
+
chain.src = ${JSON.stringify(options.chain)};
|
|
61
|
+
document.head.appendChild(chain);` : ''}
|
|
62
|
+
})();
|
|
63
|
+
`;
|
|
64
|
+
}
|
|
65
|
+
/** Whether each component script parses as a classic browser script, by its content (checked once per content). */
|
|
66
|
+
const parsed = new Map();
|
|
67
|
+
function parsesAsScript(file, source) {
|
|
68
|
+
const hash = (0, crypto_1.createHash)('sha256').update(source).digest('base64url');
|
|
69
|
+
const known = parsed.get(file);
|
|
70
|
+
if (known?.hash === hash)
|
|
71
|
+
return known.ok;
|
|
72
|
+
let ok = true;
|
|
73
|
+
try {
|
|
74
|
+
// Compiled only, never run. Wrapped as the bundle wraps it, so the check matches what the browser parses.
|
|
75
|
+
new vm_1.default.Script(`try {\n${source}\n} catch (err) {}`, { filename: file });
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
ok = false;
|
|
79
|
+
console.warn(`[formio-uag] The component script ${file} is not a browser script (an ES module, or a syntax error); it is left out of uag.use.js and portal.js.`);
|
|
80
|
+
}
|
|
81
|
+
parsed.set(file, { hash, ok });
|
|
82
|
+
return ok;
|
|
83
|
+
}
|
|
84
|
+
/** Serve one file: public, revalidated every time (no-cache), with an ETag from its content and the manifest. */
|
|
85
|
+
function serve(req, res, type, body) {
|
|
86
|
+
const etag = `"${(0, crypto_1.createHash)('sha256').update(body).digest('base64url')}"`;
|
|
87
|
+
res.set({ 'Content-Type': `${type}; charset=utf-8`, 'Cache-Control': 'no-cache', ETag: etag, 'Access-Control-Allow-Origin': '*' });
|
|
88
|
+
if (req.headers['if-none-match'] === etag)
|
|
89
|
+
return res.status(304).end();
|
|
90
|
+
res.status(200).send(body);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* `@formio/uag-js` from the UAG's own installation, at stable URLs (never a
|
|
94
|
+
* version in the path: the portal remembers its one-time prompt by URL):
|
|
95
|
+
*
|
|
96
|
+
* - `/uag-js/uag.use.js`: the bundle, registering itself with this UAG's URL and the manifest;
|
|
97
|
+
* - `/uag-js/portal.js`: the same in portal mode (the component only), then `?chain=` (https only);
|
|
98
|
+
* - `/uag-js/portal.css`: the stylesheet, after `@import url(<chain>)` when `?chain=` is given;
|
|
99
|
+
* - `/uag-js/uag-js.css`: the stylesheet.
|
|
100
|
+
*
|
|
101
|
+
* The modules' components come after: each script after the registration in
|
|
102
|
+
* `uag.use.js` and `portal.js`, each stylesheet after `uag-js.css` (and in
|
|
103
|
+
* `portal.css`), and each on its own at `/uag-js/components/<type>.js|css`.
|
|
104
|
+
* They are read on every request, as the bundle is, so the ETag follows them.
|
|
105
|
+
*/
|
|
106
|
+
function uagJsRouter(registry, components = () => []) {
|
|
107
|
+
const router = (0, express_1.Router)();
|
|
108
|
+
const manifest = () => {
|
|
109
|
+
const plugins = registry();
|
|
110
|
+
// Every plugin the modules' components may read (document, converter, module, and types
|
|
111
|
+
// modules define): not the UAG's own engines, tools and actions, which /plugins lists.
|
|
112
|
+
return plugins ? plugins.manifest().filter((plugin) => !['engine', 'tool', 'action'].includes(plugin.type)) : [];
|
|
113
|
+
};
|
|
114
|
+
// Each component's script in its own try/catch, so one that fails when it runs does not stop the others.
|
|
115
|
+
// A script that does not parse would break the whole bundle, which try/catch cannot stop: it is left out.
|
|
116
|
+
const scripts = () => components().map((component) => {
|
|
117
|
+
const label = `The ${component.type} component of the ${component.module} module`;
|
|
118
|
+
const source = fs_1.default.readFileSync(component.scriptPath, 'utf8');
|
|
119
|
+
if (!parsesAsScript(component.scriptPath, source)) {
|
|
120
|
+
return `\n;/* ${component.module}: ${component.type} */\nconsole.error(${JSON.stringify(`[uag-js] ${label} is not a browser script (an ES module, or a syntax error), so it was left out.`)});\n`;
|
|
121
|
+
}
|
|
122
|
+
return `\n;/* ${component.module}: ${component.type} */\ntry {\n${source}\n} catch (err) { console.error(${JSON.stringify(`[uag-js] ${label} could not load:`)}, err); }\n`;
|
|
123
|
+
}).join('');
|
|
124
|
+
const styles = () => components().filter((component) => component.stylePath).map((component) => `\n/* ${component.module}: ${component.type} */\n${fs_1.default.readFileSync(component.stylePath, 'utf8')}`).join('');
|
|
125
|
+
const component = (type) => components().find((candidate) => candidate.type === type);
|
|
126
|
+
router.get('/uag-js/components/:file', (req, res) => {
|
|
127
|
+
const match = /^([a-z][a-z0-9]*)\.(js|css)$/.exec(String(req.params.file));
|
|
128
|
+
const found = match ? component(match[1]) : undefined;
|
|
129
|
+
const file = found && (match[2] === 'js' ? found.scriptPath : found.stylePath);
|
|
130
|
+
if (!file)
|
|
131
|
+
return res.status(404).type('text/plain').send('No module of this UAG has that component.');
|
|
132
|
+
serve(req, res, match[2] === 'js' ? 'application/javascript' : 'text/css', fs_1.default.readFileSync(file, 'utf8'));
|
|
133
|
+
});
|
|
134
|
+
const missing = (res) => res.status(404).type('text/plain').send('@formio/uag-js is not installed with this UAG.');
|
|
135
|
+
router.get('/uag-js/uag.use.js', (req, res) => {
|
|
136
|
+
if (!uagJsDirectory())
|
|
137
|
+
return missing(res);
|
|
138
|
+
serve(req, res, 'application/javascript', read('index.js') + registration(manifest(), { portal: false }) + scripts());
|
|
139
|
+
});
|
|
140
|
+
router.get('/uag-js/portal.js', (req, res) => {
|
|
141
|
+
if (!uagJsDirectory())
|
|
142
|
+
return missing(res);
|
|
143
|
+
serve(req, res, 'application/javascript', read('index.js') + registration(manifest(), { portal: true, chain: chainOf(req, ['https:']) }) + scripts());
|
|
144
|
+
});
|
|
145
|
+
router.get('/uag-js/portal.css', (req, res) => {
|
|
146
|
+
if (!uagJsDirectory())
|
|
147
|
+
return missing(res);
|
|
148
|
+
const chain = chainOf(req, ['https:', 'http:']);
|
|
149
|
+
serve(req, res, 'text/css', (chain ? `@import url(${JSON.stringify(chain)});\n` : '') + read('uag-js.css') + styles());
|
|
150
|
+
});
|
|
151
|
+
router.get('/uag-js/uag-js.css', (req, res) => {
|
|
152
|
+
if (!uagJsDirectory())
|
|
153
|
+
return missing(res);
|
|
154
|
+
serve(req, res, 'text/css', read('uag-js.css') + styles());
|
|
155
|
+
});
|
|
156
|
+
return router;
|
|
157
|
+
}
|
package/lib/formflow.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { Formflow } from '@formio/appserver';
|
|
|
4
4
|
* assertSingleCore), under the names the UAG uses. Internal to the UAG:
|
|
5
5
|
* applications reach Formflow as `Formflow` from this package's exports.
|
|
6
6
|
*/
|
|
7
|
-
export declare const EngineConfigError: typeof Formflow.EngineConfigError, EngineRegistry: typeof Formflow.EngineRegistry, FormflowError: typeof Formflow.FormflowError, FormflowService: typeof Formflow.FormflowService, RateLimitError: typeof Formflow.RateLimitError, ScopedTokenError: typeof Formflow.ScopedTokenError, bearerOf: typeof Formflow.bearerOf, engineRegistry: typeof Formflow.engineRegistry, formflow: typeof Formflow.formflow, formflowAuthenticate: typeof Formflow.formflowAuthenticate, identityOf: typeof Formflow.identityOf, issueScopedToken: typeof Formflow.issueScopedToken, isScopedToken: typeof Formflow.isScopedToken, readScopedToken: typeof Formflow.readScopedToken, resultEvent: typeof Formflow.resultEvent, withoutOwnedValues: typeof Formflow.withoutOwnedValues, stubAgentEngine: typeof Formflow.stubAgentEngine, stubDecisionEngine: typeof Formflow.stubDecisionEngine, toolsForScope: typeof Formflow.toolsForScope, EngineError: typeof Formflow.EngineError, formatReport: typeof Formflow.formatReport, flagOn: typeof Formflow.flagOn, flagOff: typeof Formflow.flagOff, ruleWithConstraints: typeof Formflow.ruleWithConstraints;
|
|
7
|
+
export declare const EngineConfigError: typeof Formflow.EngineConfigError, EngineRegistry: typeof Formflow.EngineRegistry, FormflowError: typeof Formflow.FormflowError, FormflowService: typeof Formflow.FormflowService, RateLimitError: typeof Formflow.RateLimitError, ScopedTokenError: typeof Formflow.ScopedTokenError, bearerOf: typeof Formflow.bearerOf, engineRegistry: typeof Formflow.engineRegistry, formflow: typeof Formflow.formflow, formflowAuthenticate: typeof Formflow.formflowAuthenticate, identityOf: typeof Formflow.identityOf, issueScopedToken: typeof Formflow.issueScopedToken, isScopedToken: typeof Formflow.isScopedToken, readScopedToken: typeof Formflow.readScopedToken, resultEvent: typeof Formflow.resultEvent, withoutOwnedValues: typeof Formflow.withoutOwnedValues, callerKey: typeof Formflow.callerKey, stubAgentEngine: typeof Formflow.stubAgentEngine, stubDecisionEngine: typeof Formflow.stubDecisionEngine, toolsForScope: typeof Formflow.toolsForScope, EngineError: typeof Formflow.EngineError, formatReport: typeof Formflow.formatReport, flagOn: typeof Formflow.flagOn, flagOff: typeof Formflow.flagOff, ruleWithConstraints: typeof Formflow.ruleWithConstraints, describeBinary: typeof Formflow.describeBinary, isBinaryComponent: typeof Formflow.isBinaryComponent, fileMeta: typeof Formflow.fileMeta, plainTextReader: Formflow.FormflowFileReader;
|
|
8
8
|
export type AgentEngine = Formflow.AgentEngine;
|
|
9
9
|
export type CallerIdentity = Formflow.CallerIdentity;
|
|
10
10
|
export type DecisionEngine = Formflow.DecisionEngine;
|
|
@@ -25,6 +25,9 @@ export type FormflowRoutingConfig = Formflow.FormflowRoutingConfig;
|
|
|
25
25
|
export type FormflowService = Formflow.FormflowService;
|
|
26
26
|
export type FormflowTask = Formflow.FormflowTask;
|
|
27
27
|
export type FormflowTool = Formflow.FormflowTool;
|
|
28
|
+
export type FormflowFileReader = Formflow.FormflowFileReader;
|
|
29
|
+
export type FormflowFileMeta = Formflow.FormflowFileMeta;
|
|
30
|
+
export type FormflowFileContent = Formflow.FormflowFileContent;
|
|
28
31
|
export type OperationContext = Formflow.OperationContext;
|
|
29
32
|
export type OperationResult = Formflow.OperationResult;
|
|
30
33
|
export type ScopedTokenClaims = Formflow.ScopedTokenClaims;
|
package/lib/formflow.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ruleWithConstraints = exports.flagOff = exports.flagOn = exports.formatReport = exports.EngineError = exports.toolsForScope = exports.stubDecisionEngine = exports.stubAgentEngine = exports.withoutOwnedValues = exports.resultEvent = exports.readScopedToken = exports.isScopedToken = exports.issueScopedToken = exports.identityOf = exports.formflowAuthenticate = exports.formflow = exports.engineRegistry = exports.bearerOf = exports.ScopedTokenError = exports.RateLimitError = exports.FormflowService = exports.FormflowError = exports.EngineRegistry = exports.EngineConfigError = void 0;
|
|
3
|
+
exports.plainTextReader = exports.fileMeta = exports.isBinaryComponent = exports.describeBinary = exports.ruleWithConstraints = exports.flagOff = exports.flagOn = exports.formatReport = exports.EngineError = exports.toolsForScope = exports.stubDecisionEngine = exports.stubAgentEngine = exports.callerKey = exports.withoutOwnedValues = exports.resultEvent = exports.readScopedToken = exports.isScopedToken = exports.issueScopedToken = exports.identityOf = exports.formflowAuthenticate = exports.formflow = exports.engineRegistry = exports.bearerOf = exports.ScopedTokenError = exports.RateLimitError = exports.FormflowService = exports.FormflowError = exports.EngineRegistry = exports.EngineConfigError = void 0;
|
|
4
4
|
exports.callerIdentity = callerIdentity;
|
|
5
5
|
const appserver_1 = require("@formio/appserver");
|
|
6
6
|
/**
|
|
@@ -8,7 +8,7 @@ const appserver_1 = require("@formio/appserver");
|
|
|
8
8
|
* assertSingleCore), under the names the UAG uses. Internal to the UAG:
|
|
9
9
|
* applications reach Formflow as `Formflow` from this package's exports.
|
|
10
10
|
*/
|
|
11
|
-
({ EngineConfigError: exports.EngineConfigError, EngineRegistry: exports.EngineRegistry, FormflowError: exports.FormflowError, FormflowService: exports.FormflowService, RateLimitError: exports.RateLimitError, ScopedTokenError: exports.ScopedTokenError, bearerOf: exports.bearerOf, engineRegistry: exports.engineRegistry, formflow: exports.formflow, formflowAuthenticate: exports.formflowAuthenticate, identityOf: exports.identityOf, issueScopedToken: exports.issueScopedToken, isScopedToken: exports.isScopedToken, readScopedToken: exports.readScopedToken, resultEvent: exports.resultEvent, withoutOwnedValues: exports.withoutOwnedValues, stubAgentEngine: exports.stubAgentEngine, stubDecisionEngine: exports.stubDecisionEngine, toolsForScope: exports.toolsForScope, EngineError: exports.EngineError, formatReport: exports.formatReport, flagOn: exports.flagOn, flagOff: exports.flagOff, ruleWithConstraints: exports.ruleWithConstraints } = appserver_1.Formflow);
|
|
11
|
+
({ EngineConfigError: exports.EngineConfigError, EngineRegistry: exports.EngineRegistry, FormflowError: exports.FormflowError, FormflowService: exports.FormflowService, RateLimitError: exports.RateLimitError, ScopedTokenError: exports.ScopedTokenError, bearerOf: exports.bearerOf, engineRegistry: exports.engineRegistry, formflow: exports.formflow, formflowAuthenticate: exports.formflowAuthenticate, identityOf: exports.identityOf, issueScopedToken: exports.issueScopedToken, isScopedToken: exports.isScopedToken, readScopedToken: exports.readScopedToken, resultEvent: exports.resultEvent, withoutOwnedValues: exports.withoutOwnedValues, callerKey: exports.callerKey, stubAgentEngine: exports.stubAgentEngine, stubDecisionEngine: exports.stubDecisionEngine, toolsForScope: exports.toolsForScope, EngineError: exports.EngineError, formatReport: exports.formatReport, flagOn: exports.flagOn, flagOff: exports.flagOff, ruleWithConstraints: exports.ruleWithConstraints, describeBinary: exports.describeBinary, isBinaryComponent: exports.isBinaryComponent, fileMeta: exports.fileMeta, plainTextReader: exports.plainTextReader } = appserver_1.Formflow);
|
|
12
12
|
/**
|
|
13
13
|
* Who an operation's research acts as: core's `identityOf`, and for a caller
|
|
14
14
|
* signed in with a Form.io token (`x-jwt-token`), that token, so a scoped token
|
package/lib/index.d.ts
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { Server, ServerModule } from '@formio/appserver';
|
|
2
|
+
import type { LoadedModule, ModuleApi, ModuleRoute, ModuleRouteHandlers, ProjectInterface } from '@formio/appserver';
|
|
2
3
|
import { Request, Response, Router } from 'express';
|
|
3
4
|
import { UAGProjectInterface } from './UAGProjectInterface';
|
|
4
5
|
import { UAGFormInterface } from './UAGFormInterface';
|
|
5
6
|
import { UAGConfig } from './config';
|
|
6
7
|
import { UAGDatabase } from './database';
|
|
7
|
-
|
|
8
|
+
import type { UAGModuleApi } from './modules';
|
|
9
|
+
export type UAGModule = ServerModule<UAGModuleApi> & {
|
|
8
10
|
config?: UAGConfig;
|
|
9
11
|
};
|
|
10
12
|
/**
|
|
11
|
-
* Agentic Formflow keeps its license state inside @formio/appserver-core
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* to do instead.
|
|
13
|
+
* Agentic Formflow keeps its license state inside @formio/appserver-core, and
|
|
14
|
+
* modules are built on its module framework. The UAG loads it only through
|
|
15
|
+
* @formio/appserver, so the two always share one copy, which must be 2.26.0 or
|
|
16
|
+
* later. An older @formio/appserver, or an old lockfile, can still resolve an
|
|
17
|
+
* older one, which would fail in confusing ways; say what to do instead.
|
|
16
18
|
*/
|
|
17
19
|
export declare function assertSingleCore(version?: string): void;
|
|
18
20
|
/**
|
|
@@ -27,6 +29,23 @@ export declare class UAGServer extends Server {
|
|
|
27
29
|
database?: UAGDatabase;
|
|
28
30
|
/** The database between connecting and the license check. */
|
|
29
31
|
private pendingDatabase?;
|
|
32
|
+
/**
|
|
33
|
+
* Use a module, or a list of modules (each in turn). A module used twice (by
|
|
34
|
+
* its `name`) is used once. `config.tools` adds up across modules; an MCP
|
|
35
|
+
* tool or engine type that two modules define stops startup, naming both.
|
|
36
|
+
* A route's `rateLimit` is the default for its name, unless the operator set one.
|
|
37
|
+
*/
|
|
38
|
+
use(module: any, source?: string): void;
|
|
39
|
+
/** MCP tools and engine types, which only one module may define. */
|
|
40
|
+
protected moduleClaims(definition: Record<string, any>): string[];
|
|
41
|
+
/**
|
|
42
|
+
* MCP tools add up across modules (lodash would merge them index by index),
|
|
43
|
+
* and a named module's tools get its API as `extra.module`. Its routes'
|
|
44
|
+
* `rateLimit`s become the defaults for their names.
|
|
45
|
+
*/
|
|
46
|
+
protected mergeModuleConfig(config: any, module?: LoadedModule): void;
|
|
47
|
+
/** Route rate-limit defaults, for the names the operator left unset. */
|
|
48
|
+
private routeRateLimits;
|
|
30
49
|
constructor(config?: UAGConfig);
|
|
31
50
|
/**
|
|
32
51
|
* Connect the UAG's own database before the project loads, and check it
|
|
@@ -49,6 +68,8 @@ export declare class UAGServer extends Server {
|
|
|
49
68
|
* route keeps Form.io's plain-text answer.
|
|
50
69
|
*/
|
|
51
70
|
protected sendError(req: Request, res: Response, status: number, message: string, err?: any): void;
|
|
71
|
+
/** Whether a request is for a module's form route: its method, and a path ending in the route's. */
|
|
72
|
+
private isModuleRouteRequest;
|
|
52
73
|
/** Browsers reading Agent API responses need the request id too. */
|
|
53
74
|
get exposedHeaders(): string[];
|
|
54
75
|
/** The loaded UAG project, once the project router has been built. */
|
|
@@ -60,12 +81,35 @@ export declare class UAGServer extends Server {
|
|
|
60
81
|
* on the form's own routes.
|
|
61
82
|
*/
|
|
62
83
|
protected extendRouter(router: Router): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* List the UAG's engines and MCP tools in the plugin registry, beside
|
|
86
|
+
* appserver-core's form actions. They keep loading their own way.
|
|
87
|
+
*/
|
|
88
|
+
protected listPlugins(): Promise<void>;
|
|
89
|
+
/** A form's own route segments, which module routes may not use: appserver's, and the agent operations. */
|
|
90
|
+
protected get reservedFormPaths(): string[];
|
|
91
|
+
/** Module routes may not take the names of the UAG's own rate-limited operations. */
|
|
92
|
+
protected get reservedRouteNames(): string[];
|
|
93
|
+
protected get hostName(): string;
|
|
94
|
+
protected get hostVersion(): string;
|
|
95
|
+
/** The module API: appserver's, with the UAG's license check, engines, converters and helpers. */
|
|
96
|
+
protected createModuleApi(module: LoadedModule): ModuleApi;
|
|
97
|
+
/** Module routes run behind what the agent operations run behind: the caller check, body limit, request id, rate limits and error shape. */
|
|
98
|
+
protected moduleRouteHandlers(project: ProjectInterface, route: ModuleRoute<any>, module: LoadedModule): ModuleRouteHandlers;
|
|
99
|
+
protected moduleErrorBody(req: Request, err: unknown): Record<string, any> & {
|
|
100
|
+
status: number;
|
|
101
|
+
};
|
|
63
102
|
router(): Promise<any>;
|
|
64
103
|
}
|
|
65
104
|
export * from '@formio/appserver';
|
|
66
105
|
export * from './config';
|
|
67
106
|
export * from './agent';
|
|
68
107
|
export * from './tools';
|
|
108
|
+
export * from './documents';
|
|
109
|
+
export * from './modules';
|
|
110
|
+
export { sanitizeMarkdown } from './markdown';
|
|
111
|
+
export { usePluginFiles, pluginsFile, DEFAULT_PLUGINS_FILE } from './pluginFiles';
|
|
69
112
|
export * from './router';
|
|
70
113
|
export * from './template';
|
|
114
|
+
export { sanitizeRichText, isRichText, RICH_TEXT_TAGS, RICH_TEXT_RULE } from './richText';
|
|
71
115
|
export { UAGProjectInterface, UAGFormInterface };
|