n8n-nodes-formbase 0.2.0 → 0.3.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/README.md +7 -0
- package/dist/credentials/FormbaseOAuth2Api.credentials.js +4 -1
- package/dist/credentials/FormbaseOAuth2Api.credentials.js.map +1 -1
- package/dist/nodes/Formbase/FormbaseTrigger.node.js +32 -8
- package/dist/nodes/Formbase/FormbaseTrigger.node.js.map +1 -1
- package/dist/nodes/Formbase/FormbaseTrigger.node.json +18 -0
- package/dist/nodes/Formbase/FormbaseWebhookSignature.d.ts +3 -0
- package/dist/nodes/Formbase/FormbaseWebhookSignature.js +46 -0
- package/dist/nodes/Formbase/FormbaseWebhookSignature.js.map +1 -0
- package/dist/nodes/Formbase/formbase-logo.dark.svg +6 -0
- package/dist/nodes/Formbase/formbase-logo.svg +6 -0
- package/dist/package.json +3 -2
- package/examples/formbase-submission.json +77 -0
- package/package.json +3 -2
- package/dist/nodes/Formbase/formbase.dark.svg +0 -4
- package/dist/nodes/Formbase/formbase.svg +0 -4
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@ Community node for [n8n](https://n8n.io) that starts workflows when a [formbase]
|
|
|
8
8
|
- Trigger on abandoned submissions when partial submission tracking is enabled.
|
|
9
9
|
- Load forms dynamically from every workspace available to the credential.
|
|
10
10
|
- Register and remove formbase webhook subscriptions with the n8n workflow lifecycle.
|
|
11
|
+
- Verify every webhook with HMAC-SHA256 and reject stale or forged requests.
|
|
11
12
|
- Connect through workspace-scoped OAuth 2.1 with PKCE and automatic refresh-token rotation.
|
|
12
13
|
|
|
13
14
|
## Install
|
|
@@ -42,6 +43,12 @@ Self-hosted n8n must use configured HTTPS public URL for OAuth callback. Loopbac
|
|
|
42
43
|
|
|
43
44
|
n8n webhook URL must be publicly reachable over HTTPS. For reverse-proxy or tunnel deployments, configure n8n's `WEBHOOK_URL` so generated webhook URLs use public origin.
|
|
44
45
|
|
|
46
|
+
n8n generates a separate 256-bit signing secret for each registration. Incoming requests must contain a valid `X-formbase-Signature` header with a timestamp no more than five minutes old. Missing, stale, or invalid signatures receive `401 Unauthorized` and do not start the workflow.
|
|
47
|
+
|
|
48
|
+
## Example workflow
|
|
49
|
+
|
|
50
|
+
Import [`examples/formbase-submission.json`](examples/formbase-submission.json), connect formbase credential, select form, then activate workflow. Example maps event ID, submission ID, respondent email, and form name into stable output fields.
|
|
51
|
+
|
|
45
52
|
## Output
|
|
46
53
|
|
|
47
54
|
Each webhook produces one n8n item containing formbase payload:
|
|
@@ -7,7 +7,10 @@ class FormbaseOAuth2Api {
|
|
|
7
7
|
this.name = constants_1.FORMBASE_OAUTH2_CREDENTIAL_NAME;
|
|
8
8
|
this.extends = ['oAuth2Api'];
|
|
9
9
|
this.displayName = 'formbase OAuth2 API';
|
|
10
|
-
this.icon = {
|
|
10
|
+
this.icon = {
|
|
11
|
+
light: 'file:../nodes/Formbase/formbase-logo.svg',
|
|
12
|
+
dark: 'file:../nodes/Formbase/formbase-logo.dark.svg',
|
|
13
|
+
};
|
|
11
14
|
this.documentationUrl = 'https://github.com/formbaseso/n8n-nodes-formbase#configure-credentials';
|
|
12
15
|
this.properties = [
|
|
13
16
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormbaseOAuth2Api.credentials.js","sourceRoot":"","sources":["../../credentials/FormbaseOAuth2Api.credentials.ts"],"names":[],"mappings":";;;AAEA,2DAAwG;AAExG,MAAa,iBAAiB;IAA9B;QACE,SAAI,GAAG,2CAA+B,CAAA;QAEtC,YAAO,GAAG,CAAC,WAAW,CAAC,CAAA;QAGvB,gBAAW,GAAG,qBAAqB,CAAA;QAEnC,SAAI,GAAS,
|
|
1
|
+
{"version":3,"file":"FormbaseOAuth2Api.credentials.js","sourceRoot":"","sources":["../../credentials/FormbaseOAuth2Api.credentials.ts"],"names":[],"mappings":";;;AAEA,2DAAwG;AAExG,MAAa,iBAAiB;IAA9B;QACE,SAAI,GAAG,2CAA+B,CAAA;QAEtC,YAAO,GAAG,CAAC,WAAW,CAAC,CAAA;QAGvB,gBAAW,GAAG,qBAAqB,CAAA;QAEnC,SAAI,GAAS;YACX,KAAK,EAAE,0CAA0C;YACjD,IAAI,EAAE,+CAA+C;SACtD,CAAA;QAED,qBAAgB,GAAG,wEAAwE,CAAA;QAE3F,eAAU,GAAsB;YAC9B;gBACE,WAAW,EAAE,iCAAiC;gBAC9C,IAAI,EAAE,8BAA8B;gBACpC,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,IAAI;aACd;YACD;gBACE,WAAW,EAAE,YAAY;gBACzB,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,qCAAyB;gBAClC,QAAQ,EAAE,IAAI;aACf;SACF,CAAA;QAED,SAAI,GAA2B;YAC7B,OAAO,EAAE;gBACP,GAAG,EAAE,6BAA6B;gBAClC,MAAM,EAAE,MAAM;gBACd,IAAI,EAAE;oBACJ,MAAM,EAAE,QAAQ;oBAChB,MAAM,EAAE,EAAE;iBACX;aACF;SACF,CAAA;IACH,CAAC;CAAA;AAzCD,8CAyCC"}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.FormbaseTrigger = void 0;
|
|
4
4
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
5
|
const constants_1 = require("./constants");
|
|
6
|
+
const FormbaseWebhookSignature_1 = require("./FormbaseWebhookSignature");
|
|
6
7
|
const GenericFunctions_1 = require("./GenericFunctions");
|
|
7
8
|
const FORMS_PAGE_SIZE = 100;
|
|
8
9
|
async function listWorkspaceForms(context, workspace) {
|
|
@@ -27,17 +28,20 @@ async function listWorkspaceForms(context, workspace) {
|
|
|
27
28
|
function findWebhookSubscription(subscriptions, targetUrl, eventType) {
|
|
28
29
|
return subscriptions.find((subscription) => subscription.targetUrl === targetUrl && subscription.provider === 'n8n' && subscription.eventType === eventType);
|
|
29
30
|
}
|
|
31
|
+
function clearWebhookRegistration(webhookData) {
|
|
32
|
+
delete webhookData.subscriptionId;
|
|
33
|
+
delete webhookData.webhookSecret;
|
|
34
|
+
}
|
|
30
35
|
class FormbaseTrigger {
|
|
31
36
|
constructor() {
|
|
32
37
|
this.description = {
|
|
33
38
|
displayName: 'formbase Trigger',
|
|
34
39
|
name: 'formbaseTrigger',
|
|
35
|
-
icon: { light: 'file:formbase.svg', dark: 'file:formbase.dark.svg' },
|
|
40
|
+
icon: { light: 'file:formbase-logo.svg', dark: 'file:formbase-logo.dark.svg' },
|
|
36
41
|
group: ['trigger'],
|
|
37
42
|
version: 1,
|
|
38
|
-
subtitle: '={{$parameter["event"]}}',
|
|
43
|
+
subtitle: '={{ $parameter["event"] === "submission_created" ? "On submission created" : "On submission abandoned" }}',
|
|
39
44
|
description: 'Starts the workflow when a formbase form receives a submission',
|
|
40
|
-
usableAsTool: true,
|
|
41
45
|
defaults: {
|
|
42
46
|
name: 'formbase Trigger',
|
|
43
47
|
},
|
|
@@ -85,10 +89,14 @@ class FormbaseTrigger {
|
|
|
85
89
|
{
|
|
86
90
|
name: 'Submission Abandoned',
|
|
87
91
|
value: constants_1.FORMBASE_WEBHOOK_EVENTS.submissionAbandoned,
|
|
92
|
+
action: 'On submission abandoned',
|
|
93
|
+
description: 'Runs when a respondent leaves the selected form before submitting it; requires partial submission tracking',
|
|
88
94
|
},
|
|
89
95
|
{
|
|
90
96
|
name: 'Submission Created',
|
|
91
97
|
value: constants_1.FORMBASE_WEBHOOK_EVENTS.submissionCreated,
|
|
98
|
+
action: 'On submission created',
|
|
99
|
+
description: 'Runs when a respondent submits the selected form',
|
|
92
100
|
},
|
|
93
101
|
],
|
|
94
102
|
default: 'submission_created',
|
|
@@ -127,10 +135,17 @@ class FormbaseTrigger {
|
|
|
127
135
|
formId,
|
|
128
136
|
}));
|
|
129
137
|
const match = findWebhookSubscription(result.items, webhookUrl, eventType);
|
|
130
|
-
if (match) {
|
|
131
|
-
webhookData
|
|
132
|
-
return
|
|
138
|
+
if (!match) {
|
|
139
|
+
clearWebhookRegistration(webhookData);
|
|
140
|
+
return false;
|
|
133
141
|
}
|
|
142
|
+
const registrationMatchesStaticData = webhookData.subscriptionId === match.subscriptionId && typeof webhookData.webhookSecret === 'string';
|
|
143
|
+
if (registrationMatchesStaticData)
|
|
144
|
+
return true;
|
|
145
|
+
await GenericFunctions_1.formbaseApiRequest.call(this, 'webhooks.delete', {
|
|
146
|
+
subscriptionId: match.subscriptionId,
|
|
147
|
+
});
|
|
148
|
+
clearWebhookRegistration(webhookData);
|
|
134
149
|
return false;
|
|
135
150
|
},
|
|
136
151
|
async create() {
|
|
@@ -139,21 +154,26 @@ class FormbaseTrigger {
|
|
|
139
154
|
return false;
|
|
140
155
|
const formId = this.getNodeParameter('formId');
|
|
141
156
|
const eventType = this.getNodeParameter('event');
|
|
157
|
+
const webhookSecret = (0, FormbaseWebhookSignature_1.createFormbaseWebhookSecret)();
|
|
142
158
|
const created = (await GenericFunctions_1.formbaseApiRequest.call(this, 'webhooks.create', {
|
|
143
159
|
formId,
|
|
144
160
|
targetUrl: webhookUrl,
|
|
145
161
|
provider: 'n8n',
|
|
146
162
|
eventType,
|
|
163
|
+
signingSecret: webhookSecret,
|
|
147
164
|
}));
|
|
148
165
|
const webhookData = this.getWorkflowStaticData('node');
|
|
149
166
|
webhookData.subscriptionId = created.subscriptionId;
|
|
167
|
+
webhookData.webhookSecret = webhookSecret;
|
|
150
168
|
return true;
|
|
151
169
|
},
|
|
152
170
|
async delete() {
|
|
153
171
|
const webhookData = this.getWorkflowStaticData('node');
|
|
154
172
|
const subscriptionId = webhookData.subscriptionId;
|
|
155
|
-
if (typeof subscriptionId !== 'string')
|
|
173
|
+
if (typeof subscriptionId !== 'string') {
|
|
174
|
+
clearWebhookRegistration(webhookData);
|
|
156
175
|
return true;
|
|
176
|
+
}
|
|
157
177
|
try {
|
|
158
178
|
await GenericFunctions_1.formbaseApiRequest.call(this, 'webhooks.delete', {
|
|
159
179
|
subscriptionId,
|
|
@@ -164,13 +184,17 @@ class FormbaseTrigger {
|
|
|
164
184
|
return false;
|
|
165
185
|
}
|
|
166
186
|
}
|
|
167
|
-
|
|
187
|
+
clearWebhookRegistration(webhookData);
|
|
168
188
|
return true;
|
|
169
189
|
},
|
|
170
190
|
},
|
|
171
191
|
};
|
|
172
192
|
}
|
|
173
193
|
async webhook() {
|
|
194
|
+
if (!(0, FormbaseWebhookSignature_1.verifyFormbaseWebhookSignature)(this)) {
|
|
195
|
+
this.getResponseObject().status(401).send('Unauthorized').end();
|
|
196
|
+
return { noWebhookResponse: true };
|
|
197
|
+
}
|
|
174
198
|
const body = this.getBodyData();
|
|
175
199
|
return {
|
|
176
200
|
workflowData: [this.helpers.returnJsonArray(body)],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormbaseTrigger.node.js","sourceRoot":"","sources":["../../../nodes/Formbase/FormbaseTrigger.node.ts"],"names":[],"mappings":";;;AAUA,+CAAgE;AAEhE,2CAAiH;AACjH,yDAAuD;AA8BvD,MAAM,eAAe,GAAG,GAAG,CAAA;AAE3B,KAAK,UAAU,kBAAkB,CAAC,OAA8B,EAAE,SAA2B;IAC3F,MAAM,KAAK,GAAkB,EAAE,CAAA;IAC/B,IAAI,MAA0B,CAAA;IAE9B,GAAG,CAAC;QACF,MAAM,MAAM,GAAG,CAAC,MAAM,qCAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE;YACnE,WAAW,EAAE,SAAS,CAAC,EAAE;YACzB,KAAK,EAAE,eAAe;YACtB,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9B,CAAC,CAA8B,CAAA;QAEhC,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;QAC3B,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,MAAK;QAC1B,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YACvB,MAAM,IAAI,2BAAY,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,4CAA4C,EAAE,CAAC,CAAA;QACtG,CAAC;QACD,MAAM,GAAG,MAAM,CAAC,UAAU,CAAA;IAC5B,CAAC,QAAQ,MAAM,EAAC;IAEhB,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAS,uBAAuB,CAC9B,aAAoC,EACpC,SAAiB,EACjB,SAA+B;IAE/B,OAAO,aAAa,CAAC,IAAI,CACvB,CAAC,YAAY,EAAE,EAAE,CACf,YAAY,CAAC,SAAS,KAAK,SAAS,IAAI,YAAY,CAAC,QAAQ,KAAK,KAAK,IAAI,YAAY,CAAC,SAAS,KAAK,SAAS,CAClH,CAAA;AACH,CAAC;AAED,MAAa,eAAe;IAA5B;QACE,gBAAW,GAAyB;YAClC,WAAW,EAAE,kBAAkB;YAC/B,IAAI,EAAE,iBAAiB;YACvB,IAAI,EAAE,EAAE,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"FormbaseTrigger.node.js","sourceRoot":"","sources":["../../../nodes/Formbase/FormbaseTrigger.node.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"node": "n8n-nodes-formbase",
|
|
3
|
+
"nodeVersion": "1.0",
|
|
4
|
+
"codexVersion": "1.0",
|
|
5
|
+
"categories": ["Marketing & Content", "Productivity"],
|
|
6
|
+
"resources": {
|
|
7
|
+
"credentialDocumentation": [
|
|
8
|
+
{
|
|
9
|
+
"url": "https://github.com/formbaseso/n8n-nodes-formbase#configure-credentials"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"primaryDocumentation": [
|
|
13
|
+
{
|
|
14
|
+
"url": "https://github.com/formbaseso/n8n-nodes-formbase"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createFormbaseWebhookSecret = createFormbaseWebhookSecret;
|
|
4
|
+
exports.verifyFormbaseWebhookSignature = verifyFormbaseWebhookSignature;
|
|
5
|
+
const node_crypto_1 = require("node:crypto");
|
|
6
|
+
const SIGNATURE_HEADER_PATTERN = /^t=(\d+),sha256=([a-f0-9]{64})$/;
|
|
7
|
+
const SIGNATURE_MAX_AGE_SECONDS = 5 * 60;
|
|
8
|
+
function getRawBody(request) {
|
|
9
|
+
if (!('rawBody' in request))
|
|
10
|
+
return undefined;
|
|
11
|
+
const rawBody = request.rawBody;
|
|
12
|
+
if (Buffer.isBuffer(rawBody))
|
|
13
|
+
return rawBody;
|
|
14
|
+
if (typeof rawBody === 'string')
|
|
15
|
+
return Buffer.from(rawBody);
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
function createFormbaseWebhookSecret() {
|
|
19
|
+
return `whsec_${(0, node_crypto_1.randomBytes)(32).toString('hex')}`;
|
|
20
|
+
}
|
|
21
|
+
function verifyFormbaseWebhookSignature(context) {
|
|
22
|
+
const webhookData = context.getWorkflowStaticData('node');
|
|
23
|
+
const secret = webhookData.webhookSecret;
|
|
24
|
+
if (typeof secret !== 'string' || secret.length === 0)
|
|
25
|
+
return false;
|
|
26
|
+
const signatureHeader = context.getHeaderData()['x-formbase-signature'];
|
|
27
|
+
if (typeof signatureHeader !== 'string')
|
|
28
|
+
return false;
|
|
29
|
+
const match = SIGNATURE_HEADER_PATTERN.exec(signatureHeader);
|
|
30
|
+
if (!match)
|
|
31
|
+
return false;
|
|
32
|
+
const [, timestamp, signatureHex] = match;
|
|
33
|
+
const timestampSeconds = Number(timestamp);
|
|
34
|
+
if (!Number.isSafeInteger(timestampSeconds))
|
|
35
|
+
return false;
|
|
36
|
+
const currentTimestampSeconds = Math.floor(Date.now() / 1000);
|
|
37
|
+
if (Math.abs(currentTimestampSeconds - timestampSeconds) > SIGNATURE_MAX_AGE_SECONDS)
|
|
38
|
+
return false;
|
|
39
|
+
const rawBody = getRawBody(context.getRequestObject());
|
|
40
|
+
if (!rawBody)
|
|
41
|
+
return false;
|
|
42
|
+
const expectedSignature = (0, node_crypto_1.createHmac)('sha256', secret).update(timestamp).update('.').update(rawBody).digest();
|
|
43
|
+
const receivedSignature = Buffer.from(signatureHex, 'hex');
|
|
44
|
+
return expectedSignature.length === receivedSignature.length && (0, node_crypto_1.timingSafeEqual)(expectedSignature, receivedSignature);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=FormbaseWebhookSignature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FormbaseWebhookSignature.js","sourceRoot":"","sources":["../../../nodes/Formbase/FormbaseWebhookSignature.ts"],"names":[],"mappings":";;AAgBA,kEAEC;AAED,wEAyBC;AA7CD,6CAAsE;AAItE,MAAM,wBAAwB,GAAG,iCAAiC,CAAA;AAClE,MAAM,yBAAyB,GAAG,CAAC,GAAG,EAAE,CAAA;AAExC,SAAS,UAAU,CAAC,OAA0D;IAC5E,IAAI,CAAC,CAAC,SAAS,IAAI,OAAO,CAAC;QAAE,OAAO,SAAS,CAAA;IAE7C,MAAM,OAAO,GAAY,OAAO,CAAC,OAAO,CAAA;IACxC,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAA;IAC5C,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC5D,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,SAAgB,2BAA2B;IACzC,OAAO,SAAS,IAAA,yBAAW,EAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAA;AACnD,CAAC;AAED,SAAgB,8BAA8B,CAAC,OAA0B;IACvE,MAAM,WAAW,GAAG,OAAO,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAA;IACzD,MAAM,MAAM,GAAG,WAAW,CAAC,aAAa,CAAA;IACxC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAEnE,MAAM,eAAe,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,sBAAsB,CAAC,CAAA;IACvE,IAAI,OAAO,eAAe,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAErD,MAAM,KAAK,GAAG,wBAAwB,CAAC,IAAI,CAAC,eAAe,CAAC,CAAA;IAC5D,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAA;IAExB,MAAM,CAAC,EAAE,SAAS,EAAE,YAAY,CAAC,GAAG,KAAK,CAAA;IACzC,MAAM,gBAAgB,GAAG,MAAM,CAAC,SAAS,CAAC,CAAA;IAC1C,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC;QAAE,OAAO,KAAK,CAAA;IAEzD,MAAM,uBAAuB,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAA;IAC7D,IAAI,IAAI,CAAC,GAAG,CAAC,uBAAuB,GAAG,gBAAgB,CAAC,GAAG,yBAAyB;QAAE,OAAO,KAAK,CAAA;IAElG,MAAM,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAA;IACtD,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IAE1B,MAAM,iBAAiB,GAAG,IAAA,wBAAU,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAA;IAC7G,MAAM,iBAAiB,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,CAAA;IAE1D,OAAO,iBAAiB,CAAC,MAAM,KAAK,iBAAiB,CAAC,MAAM,IAAI,IAAA,6BAAe,EAAC,iBAAiB,EAAE,iBAAiB,CAAC,CAAA;AACvH,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="32" height="32" rx="10" fill="#FFFFFF" fill-opacity="0.04"/>
|
|
3
|
+
<rect x="8" y="8" width="6" height="16" rx="2.5" fill="#F7F7F7"/>
|
|
4
|
+
<rect x="16" y="8" width="8" height="6" rx="2.5" fill="#F7F7F7" fill-opacity="0.8"/>
|
|
5
|
+
<rect x="16" y="16" width="6" height="5" rx="2.5" fill="#F7F7F7" fill-opacity="0.7"/>
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="32" height="32" rx="10" fill="#000000" fill-opacity="0.04"/>
|
|
3
|
+
<rect x="8" y="8" width="6" height="16" rx="2.5" fill="#2F2F2F"/>
|
|
4
|
+
<rect x="16" y="8" width="8" height="6" rx="2.5" fill="#2F2F2F" fill-opacity="0.8"/>
|
|
5
|
+
<rect x="16" y="16" width="6" height="5" rx="2.5" fill="#2F2F2F" fill-opacity="0.7"/>
|
|
6
|
+
</svg>
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-formbase",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "n8n community node for formbase — trigger workflows from form submissions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"prepublishOnly": "npm test && npm run build && npm run lint"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
|
-
"dist"
|
|
35
|
+
"dist",
|
|
36
|
+
"examples"
|
|
36
37
|
],
|
|
37
38
|
"engines": {
|
|
38
39
|
"node": ">=22"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Handle formbase submission",
|
|
3
|
+
"nodes": [
|
|
4
|
+
{
|
|
5
|
+
"parameters": {
|
|
6
|
+
"formId": "",
|
|
7
|
+
"event": "submission_created"
|
|
8
|
+
},
|
|
9
|
+
"type": "n8n-nodes-formbase.formbaseTrigger",
|
|
10
|
+
"typeVersion": 1,
|
|
11
|
+
"position": [0, 0],
|
|
12
|
+
"id": "16e911f0-6385-4acc-8286-1afb15eb035c",
|
|
13
|
+
"name": "formbase Trigger"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"parameters": {
|
|
17
|
+
"assignments": {
|
|
18
|
+
"assignments": [
|
|
19
|
+
{
|
|
20
|
+
"id": "cdcb57d7-377e-45e2-8da9-63e52f4690b4",
|
|
21
|
+
"name": "eventId",
|
|
22
|
+
"value": "={{ $json.eventId }}",
|
|
23
|
+
"type": "string"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "b27b42be-fc5d-491f-a93b-02a7fa4e63f8",
|
|
27
|
+
"name": "submissionId",
|
|
28
|
+
"value": "={{ $json.submission.id }}",
|
|
29
|
+
"type": "string"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "2b171786-cb32-4e73-96e5-d60f39060fe1",
|
|
33
|
+
"name": "respondentEmail",
|
|
34
|
+
"value": "={{ $json.submission.respondentEmail }}",
|
|
35
|
+
"type": "string"
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "a6366cdf-c3b9-4bdc-be3c-723d2bfab7a4",
|
|
39
|
+
"name": "formName",
|
|
40
|
+
"value": "={{ $json.form.name }}",
|
|
41
|
+
"type": "string"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"options": {}
|
|
46
|
+
},
|
|
47
|
+
"type": "n8n-nodes-base.set",
|
|
48
|
+
"typeVersion": 3.4,
|
|
49
|
+
"position": [240, 0],
|
|
50
|
+
"id": "27abc7e3-21e9-4654-9078-b12b60420f6d",
|
|
51
|
+
"name": "Prepare submission"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"pinData": {},
|
|
55
|
+
"connections": {
|
|
56
|
+
"formbase Trigger": {
|
|
57
|
+
"main": [
|
|
58
|
+
[
|
|
59
|
+
{
|
|
60
|
+
"node": "Prepare submission",
|
|
61
|
+
"type": "main",
|
|
62
|
+
"index": 0
|
|
63
|
+
}
|
|
64
|
+
]
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"active": false,
|
|
69
|
+
"settings": {
|
|
70
|
+
"executionOrder": "v1"
|
|
71
|
+
},
|
|
72
|
+
"versionId": "0f0cc0ea-543e-43ac-bf55-9256b1ee5f85",
|
|
73
|
+
"meta": {
|
|
74
|
+
"templateCredsSetupCompleted": false
|
|
75
|
+
},
|
|
76
|
+
"tags": []
|
|
77
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-formbase",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "n8n community node for formbase — trigger workflows from form submissions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"prepublishOnly": "npm test && npm run build && npm run lint"
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
|
-
"dist"
|
|
35
|
+
"dist",
|
|
36
|
+
"examples"
|
|
36
37
|
],
|
|
37
38
|
"engines": {
|
|
38
39
|
"node": ">=22"
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" width="60" height="60">
|
|
2
|
-
<circle cx="30" cy="30" r="28" fill="#818CF8"/>
|
|
3
|
-
<text x="30" y="40" font-family="Inter, system-ui, sans-serif" font-size="32" font-weight="700" text-anchor="middle" fill="#ffffff">F</text>
|
|
4
|
-
</svg>
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 60" width="60" height="60">
|
|
2
|
-
<circle cx="30" cy="30" r="28" fill="#6366F1"/>
|
|
3
|
-
<text x="30" y="40" font-family="Inter, system-ui, sans-serif" font-size="32" font-weight="700" text-anchor="middle" fill="#ffffff">F</text>
|
|
4
|
-
</svg>
|