n8n-nodes-emboss 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Emboss
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,14 @@
1
+ # n8n-nodes-emboss
2
+
3
+ An [n8n](https://n8n.io) community node for [Emboss](https://getemboss.ai) — turn flat PDFs into fillable forms and fill them from context.
4
+
5
+ ## Operations
6
+ - **Create Fillable Form** — a flat PDF in, a fillable PDF out.
7
+ - **Fill From PDF + Context** — a PDF plus context (text or a file), the filled PDF out.
8
+ - **Fill Existing Form** — fill a form you already created in Emboss, by ID.
9
+
10
+ ## Credentials
11
+ An Emboss API key (`Authorization: Bearer`). Create one at https://getemboss.ai/dashboard.
12
+
13
+ ## Installation
14
+ Settings → Community Nodes → Install `n8n-nodes-emboss`.
@@ -0,0 +1,9 @@
1
+ import type { IAuthenticateGeneric, ICredentialTestRequest, ICredentialType, INodeProperties } from 'n8n-workflow';
2
+ export declare class EmbossApi implements ICredentialType {
3
+ name: string;
4
+ displayName: string;
5
+ documentationUrl: string;
6
+ properties: INodeProperties[];
7
+ authenticate: IAuthenticateGeneric;
8
+ test: ICredentialTestRequest;
9
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EmbossApi = void 0;
4
+ class EmbossApi {
5
+ constructor() {
6
+ this.name = 'embossApi';
7
+ this.displayName = 'Emboss API';
8
+ this.documentationUrl = 'https://getemboss.ai/dashboard';
9
+ this.properties = [
10
+ {
11
+ displayName: 'API Key',
12
+ name: 'apiKey',
13
+ type: 'string',
14
+ typeOptions: { password: true },
15
+ default: '',
16
+ required: true,
17
+ },
18
+ ];
19
+ this.authenticate = {
20
+ type: 'generic',
21
+ properties: { headers: { Authorization: '=Bearer {{$credentials.apiKey}}' } },
22
+ };
23
+ this.test = {
24
+ request: { baseURL: 'https://api.getemboss.ai', url: '/forms', method: 'GET' },
25
+ };
26
+ }
27
+ }
28
+ exports.EmbossApi = EmbossApi;
@@ -0,0 +1,10 @@
1
+ import type { IExecuteFunctions, ILoadOptionsFunctions, INodeExecutionData, INodeListSearchResult, INodeType, INodeTypeDescription } from 'n8n-workflow';
2
+ export declare class Emboss implements INodeType {
3
+ description: INodeTypeDescription;
4
+ methods: {
5
+ listSearch: {
6
+ getForms(this: ILoadOptionsFunctions): Promise<INodeListSearchResult>;
7
+ };
8
+ };
9
+ execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
10
+ }
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Emboss = void 0;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ const poll_1 = require("./poll");
6
+ const context_1 = require("./context");
7
+ const BASE = 'https://api.getemboss.ai';
8
+ class Emboss {
9
+ constructor() {
10
+ this.description = {
11
+ displayName: 'Emboss',
12
+ name: 'emboss',
13
+ icon: 'file:emboss.svg',
14
+ group: ['transform'],
15
+ version: 1,
16
+ subtitle: '={{$parameter["operation"]}}',
17
+ description: 'Create fillable PDFs and fill forms from context via Emboss',
18
+ defaults: { name: 'Emboss' },
19
+ inputs: ['main'],
20
+ outputs: ['main'],
21
+ credentials: [{ name: 'embossApi', required: true }],
22
+ properties: [
23
+ {
24
+ displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: true,
25
+ options: [
26
+ { name: 'Create Fillable Form', value: 'createForm', description: 'Turn a flat PDF into a fillable form', action: 'Create a fillable form' },
27
+ { name: 'Fill From PDF + Context', value: 'fillFromPdf', description: 'Upload a PDF and context; get the filled PDF', action: 'Fill a form from a PDF and context' },
28
+ { name: 'Fill Existing Form', value: 'fillExisting', description: 'Fill a form already created in Emboss', action: 'Fill an existing form' },
29
+ ],
30
+ default: 'createForm',
31
+ },
32
+ {
33
+ displayName: 'Input PDF Field', name: 'binaryProperty', type: 'string', default: 'data',
34
+ required: true, displayOptions: { show: { operation: ['createForm', 'fillFromPdf'] } },
35
+ description: 'Name of the binary property holding the input PDF',
36
+ },
37
+ {
38
+ displayName: 'Form', name: 'formId', type: 'resourceLocator', default: { mode: 'list', value: '' },
39
+ required: true, displayOptions: { show: { operation: ['fillExisting'] } },
40
+ modes: [
41
+ { displayName: 'From List', name: 'list', type: 'list', typeOptions: { searchListMethod: 'getForms', searchable: true } },
42
+ { displayName: 'By ID', name: 'id', type: 'string', placeholder: 'form-uuid' },
43
+ ],
44
+ },
45
+ {
46
+ displayName: 'Context (Text)', name: 'contextText', type: 'string', default: '',
47
+ typeOptions: { rows: 3 }, displayOptions: { show: { operation: ['fillFromPdf', 'fillExisting'] } },
48
+ description: 'Information to fill the form with',
49
+ },
50
+ {
51
+ displayName: 'Context File Field', name: 'contextBinary', type: 'string', default: '',
52
+ displayOptions: { show: { operation: ['fillFromPdf', 'fillExisting'] } },
53
+ description: 'Optional: name of a binary property holding a context document (PDF, DOCX, CSV, image, text)',
54
+ },
55
+ ],
56
+ };
57
+ this.methods = {
58
+ listSearch: {
59
+ async getForms() {
60
+ const resp = await this.helpers.httpRequestWithAuthentication.call(this, 'embossApi', { url: `${BASE}/forms` });
61
+ const forms = (resp && resp.forms) || [];
62
+ return { results: forms.map((f) => ({ name: f.title || f.id, value: f.id })) };
63
+ },
64
+ },
65
+ };
66
+ }
67
+ async execute() {
68
+ const items = this.getInputData();
69
+ const out = [];
70
+ for (let i = 0; i < items.length; i++) {
71
+ try {
72
+ const operation = this.getNodeParameter('operation', i);
73
+ let resultUrl = '';
74
+ let json = {};
75
+ const readContextFile = async () => {
76
+ const cprop = this.getNodeParameter('contextBinary', i, '');
77
+ if (!cprop)
78
+ return undefined;
79
+ const meta = items[i].binary?.[cprop];
80
+ const cbuf = await this.helpers.getBinaryDataBuffer(i, cprop);
81
+ return { buffer: cbuf, filename: meta?.fileName || 'context', mimeType: meta?.mimeType || 'application/octet-stream' };
82
+ };
83
+ if (operation === 'createForm') {
84
+ const prop = this.getNodeParameter('binaryProperty', i);
85
+ const buf = await this.helpers.getBinaryDataBuffer(i, prop);
86
+ const form = new FormData();
87
+ form.append('file', new Blob([buf], { type: 'application/pdf' }), 'form.pdf');
88
+ const created = await this.helpers.httpRequestWithAuthentication.call(this, 'embossApi', { method: 'POST', url: `${BASE}/forms`, body: form });
89
+ const formId = created.form_id;
90
+ await (0, poll_1.pollUntilReady)(this, `${BASE}/forms/${formId}`);
91
+ resultUrl = `${BASE}/forms/${formId}/fillable`;
92
+ json = { form_id: formId, status: 'ready' };
93
+ }
94
+ else if (operation === 'fillFromPdf') {
95
+ const prop = this.getNodeParameter('binaryProperty', i);
96
+ const fileBuf = await this.helpers.getBinaryDataBuffer(i, prop);
97
+ const text = this.getNodeParameter('contextText', i, '');
98
+ const cf = await readContextFile();
99
+ const form = new FormData();
100
+ form.append('file', new Blob([fileBuf], { type: 'application/pdf' }), 'form.pdf');
101
+ for (const p of (0, context_1.contextParts)(text, cf)) {
102
+ form.append('context', new Blob([p.value], { type: p.contentType }), p.filename);
103
+ }
104
+ const created = await this.helpers.httpRequestWithAuthentication.call(this, 'embossApi', { method: 'POST', url: `${BASE}/forms/with-context`, body: form });
105
+ const ready = await (0, poll_1.pollUntilReady)(this, `${BASE}/forms/with-context/${created.job_id}`);
106
+ resultUrl = `${BASE}/sessions/${ready.session_id}/pdf`;
107
+ json = { session_id: ready.session_id, report: ready.report || {} };
108
+ }
109
+ else if (operation === 'fillExisting') {
110
+ const formId = this.getNodeParameter('formId', i).value;
111
+ const text = this.getNodeParameter('contextText', i, '');
112
+ const cf = await readContextFile();
113
+ const form = new FormData();
114
+ for (const p of (0, context_1.contextParts)(text, cf)) {
115
+ form.append('context', new Blob([p.value], { type: p.contentType }), p.filename);
116
+ }
117
+ const created = await this.helpers.httpRequestWithAuthentication.call(this, 'embossApi', { method: 'POST', url: `${BASE}/forms/${formId}/with-context`, body: form });
118
+ const ready = await (0, poll_1.pollUntilReady)(this, `${BASE}/forms/with-context/${created.job_id}`);
119
+ resultUrl = `${BASE}/sessions/${ready.session_id}/pdf`;
120
+ json = { session_id: ready.session_id, report: ready.report || {} };
121
+ }
122
+ else {
123
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unsupported operation: ${operation}`);
124
+ }
125
+ const pdf = await this.helpers.httpRequestWithAuthentication.call(this, 'embossApi', { url: resultUrl, encoding: 'arraybuffer' });
126
+ const binary = await this.helpers.prepareBinaryData(Buffer.from(pdf), 'emboss.pdf', 'application/pdf');
127
+ out.push({ json, binary: { data: binary }, pairedItem: { item: i } });
128
+ }
129
+ catch (err) {
130
+ if (this.continueOnFail()) {
131
+ out.push({ json: { error: err.message }, pairedItem: { item: i } });
132
+ continue;
133
+ }
134
+ throw err;
135
+ }
136
+ }
137
+ return [out];
138
+ }
139
+ }
140
+ exports.Emboss = Emboss;
@@ -0,0 +1,11 @@
1
+ export interface ContextFile {
2
+ buffer: Buffer;
3
+ filename: string;
4
+ mimeType: string;
5
+ }
6
+ export interface ContextPart {
7
+ value: Buffer;
8
+ filename: string;
9
+ contentType: string;
10
+ }
11
+ export declare function contextParts(contextText?: string, contextFile?: ContextFile): ContextPart[];
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.contextParts = contextParts;
4
+ // Build the `context` multipart file parts. The Emboss endpoints take `context` as a FILE list (a
5
+ // bare text form field 422s), so typed text is wrapped as a context.txt part, and an optional context
6
+ // file carries its real filename + mime so the backend's _resolve_kind accepts it. Both may be present.
7
+ function contextParts(contextText, contextFile) {
8
+ const parts = [];
9
+ if (contextText) {
10
+ parts.push({ value: Buffer.from(contextText, 'utf8'), filename: 'context.txt', contentType: 'text/plain' });
11
+ }
12
+ if (contextFile && contextFile.buffer) {
13
+ parts.push({ value: contextFile.buffer, filename: contextFile.filename || 'context', contentType: contextFile.mimeType || 'application/octet-stream' });
14
+ }
15
+ return parts;
16
+ }
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" width="60" height="60">
2
+ <rect width="60" height="60" rx="12" fill="#ee5a18"/>
3
+ <text x="30" y="44" font-family="Arial, Helvetica, sans-serif" font-size="40" font-weight="700" fill="#ffffff" text-anchor="middle">E</text>
4
+ </svg>
@@ -0,0 +1,6 @@
1
+ import type { IExecuteFunctions } from 'n8n-workflow';
2
+ export interface PollOpts {
3
+ intervalMs?: number;
4
+ maxAttempts?: number;
5
+ }
6
+ export declare function pollUntilReady(ctx: IExecuteFunctions, url: string, opts?: PollOpts): Promise<any>;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pollUntilReady = pollUntilReady;
4
+ const n8n_workflow_1 = require("n8n-workflow");
5
+ // Poll a status URL until the payload reports status 'ready'. Throws a clean NodeOperationError
6
+ // on status 'failed' (surfacing the Emboss error code/message) or after maxAttempts (~5 min default).
7
+ async function pollUntilReady(ctx, url, opts = {}) {
8
+ const intervalMs = opts.intervalMs ?? 3000;
9
+ const maxAttempts = opts.maxAttempts ?? 100; // 3s * 100 ≈ 5 min
10
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
11
+ if (intervalMs > 0)
12
+ await new Promise((r) => setTimeout(r, intervalMs));
13
+ const payload = await ctx.helpers.httpRequestWithAuthentication.call(ctx, 'embossApi', { url });
14
+ if (payload.status === 'failed') {
15
+ const e = payload.error;
16
+ const detail = typeof e === 'string' ? e : (e && (e.message || e.code)) || 'no detail';
17
+ throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), `Emboss fill failed: ${detail}`);
18
+ }
19
+ if (payload.status === 'ready')
20
+ return payload;
21
+ }
22
+ throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), "Emboss job didn't finish within ~5 minutes. Re-run, or check the Emboss dashboard.");
23
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "n8n-nodes-emboss",
3
+ "version": "1.0.0",
4
+ "description": "n8n node for Emboss — create fillable PDFs and fill forms from context.",
5
+ "license": "MIT",
6
+ "author": "Emboss",
7
+ "homepage": "https://getemboss.ai",
8
+ "keywords": [
9
+ "n8n-community-node-package",
10
+ "n8n",
11
+ "emboss",
12
+ "pdf",
13
+ "forms"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc && cp nodes/Emboss/emboss.svg dist/nodes/Emboss/emboss.svg",
17
+ "test": "jest",
18
+ "lint": "eslint nodes credentials --ext .ts"
19
+ },
20
+ "n8n": {
21
+ "n8nNodesApiVersion": 1,
22
+ "credentials": [
23
+ "dist/credentials/EmbossApi.credentials.js"
24
+ ],
25
+ "nodes": [
26
+ "dist/nodes/Emboss/Emboss.node.js"
27
+ ]
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "LICENSE"
32
+ ],
33
+ "devDependencies": {
34
+ "@types/jest": "^29.5.12",
35
+ "@types/node": "^20.12.7",
36
+ "@typescript-eslint/parser": "^7.18.0",
37
+ "eslint": "^8.57.0",
38
+ "eslint-plugin-n8n-nodes-base": "^1.16.0",
39
+ "jest": "^29.7.0",
40
+ "n8n-workflow": "^1.55.0",
41
+ "ts-jest": "^29.1.2",
42
+ "typescript": "^5.4.5"
43
+ }
44
+ }