@nextlyhq/plugin-form-builder 0.0.2-alpha.66 → 0.0.2-alpha.68
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 -7
- package/dist/admin/index.d.ts +1 -1
- package/dist/admin/index.js +30 -7
- package/dist/admin/index.js.map +1 -1
- package/dist/{chunk-VIGKSRSH.js → chunk-X5WLJLAR.js} +15 -1
- package/dist/chunk-X5WLJLAR.js.map +1 -0
- package/dist/index.d.ts +3 -136
- package/dist/index.js +11 -193
- package/dist/index.js.map +1 -1
- package/dist/{types-hLfLe81K.d.ts → types-Bo4lk9BD.d.ts} +7 -88
- package/package.json +13 -13
- package/dist/chunk-VIGKSRSH.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -30,8 +30,9 @@ import {
|
|
|
30
30
|
parseRedirectReference,
|
|
31
31
|
pickedDocumentField,
|
|
32
32
|
pluralize,
|
|
33
|
-
toTitleCase
|
|
34
|
-
|
|
33
|
+
toTitleCase,
|
|
34
|
+
warnIfWebhooksConfigured
|
|
35
|
+
} from "./chunk-X5WLJLAR.js";
|
|
35
36
|
|
|
36
37
|
// src/plugin.ts
|
|
37
38
|
import { createRequire } from "module";
|
|
@@ -74,16 +75,13 @@ function applyRedirectPattern(pattern, document) {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
// src/collections/access-defaults.ts
|
|
77
|
-
var
|
|
78
|
-
var administrative = ({ roles }) => roles.includes("admin") || roles.includes("super-admin");
|
|
78
|
+
var permitted = ({ permissions, collection, operation }) => permissions.includes(`${collection}:${operation}`);
|
|
79
79
|
function accessWithDefaults(overrides, defaults = {}) {
|
|
80
80
|
return {
|
|
81
|
-
read: overrides?.read ?? defaults.read ??
|
|
82
|
-
create: overrides?.create ?? defaults.create ??
|
|
83
|
-
update: overrides?.update ??
|
|
84
|
-
|
|
85
|
-
// remains the backstop underneath it.
|
|
86
|
-
delete: overrides?.delete ?? administrative
|
|
81
|
+
read: overrides?.read ?? defaults.read ?? permitted,
|
|
82
|
+
create: overrides?.create ?? defaults.create ?? permitted,
|
|
83
|
+
update: overrides?.update ?? permitted,
|
|
84
|
+
delete: overrides?.delete ?? permitted
|
|
87
85
|
};
|
|
88
86
|
}
|
|
89
87
|
|
|
@@ -787,7 +785,8 @@ function submissionsCollection(pluginConfig) {
|
|
|
787
785
|
...overrides.admin
|
|
788
786
|
},
|
|
789
787
|
// Access control with sensible defaults
|
|
790
|
-
// Public can create (submit forms),
|
|
788
|
+
// Public can create (submit forms); reading, updating and deleting need the
|
|
789
|
+
// matching permission on the caller's role (`read-form-submissions`, ...).
|
|
791
790
|
// `create: true` — a visitor submits without an account. Reading, updating
|
|
792
791
|
// and deleting are the shared defaults.
|
|
793
792
|
access: accessWithDefaults(accessOverrides, { create: true }),
|
|
@@ -2979,6 +2978,7 @@ function validateAccess(access, errors) {
|
|
|
2979
2978
|
}
|
|
2980
2979
|
function validateFormConfig(config) {
|
|
2981
2980
|
const errors = [];
|
|
2981
|
+
warnIfWebhooksConfigured(config);
|
|
2982
2982
|
validateSlug(config.slug, errors);
|
|
2983
2983
|
validateFields(config.fields, errors);
|
|
2984
2984
|
validateSettings(config.settings, errors);
|
|
@@ -3057,184 +3057,6 @@ var hidden = (config) => ({
|
|
|
3057
3057
|
type: "hidden"
|
|
3058
3058
|
});
|
|
3059
3059
|
|
|
3060
|
-
// src/handlers/webhooks.ts
|
|
3061
|
-
import { safeFetch, ExternalUrlError } from "nextly";
|
|
3062
|
-
var WEBHOOK_TIMEOUT_MS = 1e4;
|
|
3063
|
-
var SIGNATURE_HEADER = "X-Webhook-Signature";
|
|
3064
|
-
var EVENT_HEADER = "X-Webhook-Event";
|
|
3065
|
-
var DELIVERY_ID_HEADER = "X-Webhook-Delivery-ID";
|
|
3066
|
-
async function generateSignature(payload, secret) {
|
|
3067
|
-
const encoder = new TextEncoder();
|
|
3068
|
-
const keyData = encoder.encode(secret);
|
|
3069
|
-
const messageData = encoder.encode(payload);
|
|
3070
|
-
const cryptoKey = await crypto.subtle.importKey(
|
|
3071
|
-
"raw",
|
|
3072
|
-
keyData,
|
|
3073
|
-
{ name: "HMAC", hash: "SHA-256" },
|
|
3074
|
-
false,
|
|
3075
|
-
["sign"]
|
|
3076
|
-
);
|
|
3077
|
-
const signature = await crypto.subtle.sign("HMAC", cryptoKey, messageData);
|
|
3078
|
-
const hashArray = Array.from(new Uint8Array(signature));
|
|
3079
|
-
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
3080
|
-
return `sha256=${hashHex}`;
|
|
3081
|
-
}
|
|
3082
|
-
async function sendWebhook(webhook, payload) {
|
|
3083
|
-
const startTime = Date.now();
|
|
3084
|
-
const payloadString = JSON.stringify(payload);
|
|
3085
|
-
try {
|
|
3086
|
-
const headers = {
|
|
3087
|
-
"Content-Type": "application/json",
|
|
3088
|
-
[EVENT_HEADER]: payload.event,
|
|
3089
|
-
[DELIVERY_ID_HEADER]: payload.submission.id,
|
|
3090
|
-
...webhook.headers
|
|
3091
|
-
};
|
|
3092
|
-
if (webhook.secret) {
|
|
3093
|
-
headers[SIGNATURE_HEADER] = await generateSignature(
|
|
3094
|
-
payloadString,
|
|
3095
|
-
webhook.secret
|
|
3096
|
-
);
|
|
3097
|
-
}
|
|
3098
|
-
const controller = new AbortController();
|
|
3099
|
-
const timeoutId = setTimeout(() => controller.abort(), WEBHOOK_TIMEOUT_MS);
|
|
3100
|
-
try {
|
|
3101
|
-
const response = await safeFetch(webhook.url, {
|
|
3102
|
-
method: webhook.method || "POST",
|
|
3103
|
-
headers,
|
|
3104
|
-
body: payloadString,
|
|
3105
|
-
signal: controller.signal,
|
|
3106
|
-
allowLocalhost: process.env.NODE_ENV !== "production"
|
|
3107
|
-
});
|
|
3108
|
-
clearTimeout(timeoutId);
|
|
3109
|
-
const durationMs = Date.now() - startTime;
|
|
3110
|
-
if (response.ok) {
|
|
3111
|
-
return {
|
|
3112
|
-
url: webhook.url,
|
|
3113
|
-
success: true,
|
|
3114
|
-
statusCode: response.status,
|
|
3115
|
-
durationMs
|
|
3116
|
-
};
|
|
3117
|
-
}
|
|
3118
|
-
return {
|
|
3119
|
-
url: webhook.url,
|
|
3120
|
-
success: false,
|
|
3121
|
-
statusCode: response.status,
|
|
3122
|
-
error: `HTTP ${response.status}: ${response.statusText}`,
|
|
3123
|
-
durationMs
|
|
3124
|
-
};
|
|
3125
|
-
} finally {
|
|
3126
|
-
clearTimeout(timeoutId);
|
|
3127
|
-
}
|
|
3128
|
-
} catch (error) {
|
|
3129
|
-
const durationMs = Date.now() - startTime;
|
|
3130
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
3131
|
-
return {
|
|
3132
|
-
url: webhook.url,
|
|
3133
|
-
success: false,
|
|
3134
|
-
error: `Timeout after ${WEBHOOK_TIMEOUT_MS}ms`,
|
|
3135
|
-
durationMs
|
|
3136
|
-
};
|
|
3137
|
-
}
|
|
3138
|
-
if (error instanceof ExternalUrlError) {
|
|
3139
|
-
return {
|
|
3140
|
-
url: webhook.url,
|
|
3141
|
-
success: false,
|
|
3142
|
-
error: `Webhook URL rejected for SSRF safety: ${error.message}`,
|
|
3143
|
-
durationMs
|
|
3144
|
-
};
|
|
3145
|
-
}
|
|
3146
|
-
return {
|
|
3147
|
-
url: webhook.url,
|
|
3148
|
-
success: false,
|
|
3149
|
-
error: error instanceof Error ? error.message : String(error),
|
|
3150
|
-
durationMs
|
|
3151
|
-
};
|
|
3152
|
-
}
|
|
3153
|
-
}
|
|
3154
|
-
async function triggerWebhooks(options) {
|
|
3155
|
-
const { event, form, submission } = options;
|
|
3156
|
-
const webhooks = form.webhooks || [];
|
|
3157
|
-
const relevantWebhooks = webhooks.filter((w) => w.events.includes(event));
|
|
3158
|
-
if (relevantWebhooks.length === 0) {
|
|
3159
|
-
return {
|
|
3160
|
-
triggered: 0,
|
|
3161
|
-
successful: 0,
|
|
3162
|
-
failed: 0,
|
|
3163
|
-
results: []
|
|
3164
|
-
};
|
|
3165
|
-
}
|
|
3166
|
-
const deliveryPromises = relevantWebhooks.map(async (webhook) => {
|
|
3167
|
-
const payload = {
|
|
3168
|
-
event,
|
|
3169
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3170
|
-
form: {
|
|
3171
|
-
id: form.id,
|
|
3172
|
-
slug: form.slug,
|
|
3173
|
-
name: form.name
|
|
3174
|
-
},
|
|
3175
|
-
submission: webhook.includeData !== false ? submission : {
|
|
3176
|
-
id: submission.id,
|
|
3177
|
-
submittedAt: submission.submittedAt
|
|
3178
|
-
}
|
|
3179
|
-
};
|
|
3180
|
-
return sendWebhook(webhook, payload);
|
|
3181
|
-
});
|
|
3182
|
-
const results = await Promise.all(deliveryPromises);
|
|
3183
|
-
const successful = results.filter((r) => r.success).length;
|
|
3184
|
-
const failed = results.filter((r) => !r.success).length;
|
|
3185
|
-
return {
|
|
3186
|
-
triggered: relevantWebhooks.length,
|
|
3187
|
-
successful,
|
|
3188
|
-
failed,
|
|
3189
|
-
results
|
|
3190
|
-
};
|
|
3191
|
-
}
|
|
3192
|
-
function fireWebhooks(options, logger) {
|
|
3193
|
-
triggerWebhooks(options).then((result) => {
|
|
3194
|
-
if (result.triggered > 0) {
|
|
3195
|
-
if (result.failed > 0) {
|
|
3196
|
-
logger?.warn?.("Some webhooks failed", {
|
|
3197
|
-
event: options.event,
|
|
3198
|
-
formSlug: options.form.slug,
|
|
3199
|
-
submissionId: options.submission.id,
|
|
3200
|
-
triggered: result.triggered,
|
|
3201
|
-
successful: result.successful,
|
|
3202
|
-
failed: result.failed,
|
|
3203
|
-
errors: result.results.filter((r) => !r.success).map((r) => ({ url: r.url, error: r.error }))
|
|
3204
|
-
});
|
|
3205
|
-
} else {
|
|
3206
|
-
logger?.info?.("Webhooks triggered successfully", {
|
|
3207
|
-
event: options.event,
|
|
3208
|
-
formSlug: options.form.slug,
|
|
3209
|
-
submissionId: options.submission.id,
|
|
3210
|
-
count: result.triggered
|
|
3211
|
-
});
|
|
3212
|
-
}
|
|
3213
|
-
}
|
|
3214
|
-
}).catch((error) => {
|
|
3215
|
-
logger?.error?.("Unexpected error triggering webhooks", {
|
|
3216
|
-
event: options.event,
|
|
3217
|
-
formSlug: options.form.slug,
|
|
3218
|
-
submissionId: options.submission.id,
|
|
3219
|
-
error: error instanceof Error ? error.message : String(error)
|
|
3220
|
-
});
|
|
3221
|
-
});
|
|
3222
|
-
}
|
|
3223
|
-
function isValidWebhookUrl(url2) {
|
|
3224
|
-
try {
|
|
3225
|
-
const parsed = new URL(url2);
|
|
3226
|
-
if (parsed.protocol !== "https:" && !parsed.hostname.match(/^(localhost|127\.0\.0\.1)$/)) {
|
|
3227
|
-
return false;
|
|
3228
|
-
}
|
|
3229
|
-
return true;
|
|
3230
|
-
} catch {
|
|
3231
|
-
return false;
|
|
3232
|
-
}
|
|
3233
|
-
}
|
|
3234
|
-
function getSupportedWebhookEvents() {
|
|
3235
|
-
return ["submission.created", "submission.updated", "submission.deleted"];
|
|
3236
|
-
}
|
|
3237
|
-
|
|
3238
3060
|
// src/index.ts
|
|
3239
3061
|
var formBuilderPlugin = formBuilder().plugin;
|
|
3240
3062
|
export {
|
|
@@ -3256,7 +3078,6 @@ export {
|
|
|
3256
3078
|
exportToCSV,
|
|
3257
3079
|
exportToJSON,
|
|
3258
3080
|
file,
|
|
3259
|
-
fireWebhooks,
|
|
3260
3081
|
formBuilder,
|
|
3261
3082
|
formBuilderPlugin,
|
|
3262
3083
|
formatExportValue,
|
|
@@ -3266,7 +3087,6 @@ export {
|
|
|
3266
3087
|
getFormBuilderConfig,
|
|
3267
3088
|
getFormSubmissionStats,
|
|
3268
3089
|
getSupportedComparisonOperators,
|
|
3269
|
-
getSupportedWebhookEvents,
|
|
3270
3090
|
getValidationErrors,
|
|
3271
3091
|
hidden,
|
|
3272
3092
|
isCheckboxFormField,
|
|
@@ -3279,7 +3099,6 @@ export {
|
|
|
3279
3099
|
isSelectFormField,
|
|
3280
3100
|
isTextFormField,
|
|
3281
3101
|
isValidComparisonOperator,
|
|
3282
|
-
isValidWebhookUrl,
|
|
3283
3102
|
number,
|
|
3284
3103
|
option,
|
|
3285
3104
|
phone,
|
|
@@ -3293,7 +3112,6 @@ export {
|
|
|
3293
3112
|
time,
|
|
3294
3113
|
toTitleCase,
|
|
3295
3114
|
transformFormData,
|
|
3296
|
-
triggerWebhooks,
|
|
3297
3115
|
url,
|
|
3298
3116
|
validateFormConfig,
|
|
3299
3117
|
validateFormData,
|