@better-webhook/cli 3.4.4 → 3.5.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 +35 -0
- package/dist/dashboard/assets/index-CxrRCNTh.css +1 -0
- package/dist/dashboard/assets/index-Dlqdzwyc.js +23 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/index.cjs +330 -15
- package/dist/index.js +330 -15
- package/package.json +4 -3
- package/dist/commands/capture.d.ts +0 -2
- package/dist/commands/capture.js +0 -30
- package/dist/commands/captures.d.ts +0 -2
- package/dist/commands/captures.js +0 -217
- package/dist/commands/dashboard.d.ts +0 -2
- package/dist/commands/dashboard.js +0 -65
- package/dist/commands/index.d.ts +0 -6
- package/dist/commands/index.js +0 -6
- package/dist/commands/replay.d.ts +0 -2
- package/dist/commands/replay.js +0 -140
- package/dist/commands/run.d.ts +0 -2
- package/dist/commands/run.js +0 -181
- package/dist/commands/templates.d.ts +0 -2
- package/dist/commands/templates.js +0 -285
- package/dist/core/capture-server.d.ts +0 -31
- package/dist/core/capture-server.js +0 -298
- package/dist/core/dashboard-api.d.ts +0 -8
- package/dist/core/dashboard-api.js +0 -271
- package/dist/core/dashboard-server.d.ts +0 -20
- package/dist/core/dashboard-server.js +0 -124
- package/dist/core/executor.d.ts +0 -11
- package/dist/core/executor.js +0 -130
- package/dist/core/index.d.ts +0 -5
- package/dist/core/index.js +0 -5
- package/dist/core/replay-engine.d.ts +0 -18
- package/dist/core/replay-engine.js +0 -208
- package/dist/core/signature.d.ts +0 -24
- package/dist/core/signature.js +0 -199
- package/dist/core/template-manager.d.ts +0 -24
- package/dist/core/template-manager.js +0 -246
- package/dist/dashboard/assets/index-BSfTbn4Y.js +0 -23
- package/dist/dashboard/assets/index-zDTVdss_.css +0 -1
- package/dist/types/index.d.ts +0 -299
- package/dist/types/index.js +0 -86
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
import express from "express";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { TemplateManager } from "./template-manager.js";
|
|
4
|
-
import { ReplayEngine } from "./replay-engine.js";
|
|
5
|
-
import { executeTemplate } from "./executor.js";
|
|
6
|
-
import { HeaderEntrySchema, HttpMethodSchema, } from "../types/index.js";
|
|
7
|
-
function jsonError(res, status, message) {
|
|
8
|
-
return res.status(status).json({ error: message });
|
|
9
|
-
}
|
|
10
|
-
function getSecretEnvVarName(provider) {
|
|
11
|
-
const envVarMap = {
|
|
12
|
-
github: "GITHUB_WEBHOOK_SECRET",
|
|
13
|
-
stripe: "STRIPE_WEBHOOK_SECRET",
|
|
14
|
-
shopify: "SHOPIFY_WEBHOOK_SECRET",
|
|
15
|
-
twilio: "TWILIO_WEBHOOK_SECRET",
|
|
16
|
-
ragie: "RAGIE_WEBHOOK_SECRET",
|
|
17
|
-
slack: "SLACK_WEBHOOK_SECRET",
|
|
18
|
-
linear: "LINEAR_WEBHOOK_SECRET",
|
|
19
|
-
clerk: "CLERK_WEBHOOK_SECRET",
|
|
20
|
-
sendgrid: "SENDGRID_WEBHOOK_SECRET",
|
|
21
|
-
discord: "DISCORD_WEBHOOK_SECRET",
|
|
22
|
-
custom: "WEBHOOK_SECRET",
|
|
23
|
-
};
|
|
24
|
-
return envVarMap[provider] || "WEBHOOK_SECRET";
|
|
25
|
-
}
|
|
26
|
-
const ReplayBodySchema = z.object({
|
|
27
|
-
captureId: z.string().min(1),
|
|
28
|
-
targetUrl: z.string().min(1),
|
|
29
|
-
method: HttpMethodSchema.optional(),
|
|
30
|
-
headers: z.array(HeaderEntrySchema).optional(),
|
|
31
|
-
});
|
|
32
|
-
const TemplateDownloadBodySchema = z.object({
|
|
33
|
-
id: z.string().min(1),
|
|
34
|
-
});
|
|
35
|
-
const RunTemplateBodySchema = z.object({
|
|
36
|
-
templateId: z.string().min(1),
|
|
37
|
-
url: z.string().min(1),
|
|
38
|
-
secret: z.string().optional(),
|
|
39
|
-
headers: z.array(HeaderEntrySchema).optional(),
|
|
40
|
-
});
|
|
41
|
-
export function createDashboardApiRouter(options = {}) {
|
|
42
|
-
const router = express.Router();
|
|
43
|
-
const replayEngine = new ReplayEngine(options.capturesDir);
|
|
44
|
-
const templateManager = new TemplateManager(options.templatesBaseDir);
|
|
45
|
-
const broadcast = options.broadcast;
|
|
46
|
-
const broadcastCaptures = () => {
|
|
47
|
-
if (!broadcast)
|
|
48
|
-
return;
|
|
49
|
-
const captures = replayEngine.listCaptures(200);
|
|
50
|
-
broadcast({
|
|
51
|
-
type: "captures_updated",
|
|
52
|
-
payload: { captures, count: captures.length },
|
|
53
|
-
});
|
|
54
|
-
};
|
|
55
|
-
const broadcastTemplates = async () => {
|
|
56
|
-
if (!broadcast)
|
|
57
|
-
return;
|
|
58
|
-
const local = templateManager.listLocalTemplates();
|
|
59
|
-
let remote = [];
|
|
60
|
-
try {
|
|
61
|
-
const index = await templateManager.fetchRemoteIndex(false);
|
|
62
|
-
const localIds = new Set(local.map((t) => t.id));
|
|
63
|
-
remote = index.templates.map((metadata) => ({
|
|
64
|
-
metadata,
|
|
65
|
-
isDownloaded: localIds.has(metadata.id),
|
|
66
|
-
}));
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
remote = [];
|
|
70
|
-
}
|
|
71
|
-
broadcast({
|
|
72
|
-
type: "templates_updated",
|
|
73
|
-
payload: { local, remote },
|
|
74
|
-
});
|
|
75
|
-
};
|
|
76
|
-
router.get("/captures", (req, res) => {
|
|
77
|
-
const limitRaw = typeof req.query.limit === "string" ? req.query.limit : "";
|
|
78
|
-
const providerRaw = typeof req.query.provider === "string" ? req.query.provider : "";
|
|
79
|
-
const qRaw = typeof req.query.q === "string" ? req.query.q : "";
|
|
80
|
-
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : 50;
|
|
81
|
-
if (!Number.isFinite(limit) || limit <= 0 || limit > 5000) {
|
|
82
|
-
return jsonError(res, 400, "Invalid limit");
|
|
83
|
-
}
|
|
84
|
-
const q = qRaw.trim();
|
|
85
|
-
const provider = providerRaw.trim();
|
|
86
|
-
let captures = q
|
|
87
|
-
? replayEngine.searchCaptures(q)
|
|
88
|
-
: replayEngine.listCaptures(Math.max(limit, 1000));
|
|
89
|
-
if (provider) {
|
|
90
|
-
captures = captures.filter((c) => (c.capture.provider || "").toLowerCase() === provider.toLowerCase());
|
|
91
|
-
}
|
|
92
|
-
captures = captures.slice(0, limit);
|
|
93
|
-
return res.json({ captures, count: captures.length });
|
|
94
|
-
});
|
|
95
|
-
router.get("/captures/:id", (req, res) => {
|
|
96
|
-
const id = req.params.id;
|
|
97
|
-
if (!id) {
|
|
98
|
-
return jsonError(res, 400, "Missing capture id");
|
|
99
|
-
}
|
|
100
|
-
const captureFile = replayEngine.getCapture(id);
|
|
101
|
-
if (!captureFile) {
|
|
102
|
-
return jsonError(res, 404, "Capture not found");
|
|
103
|
-
}
|
|
104
|
-
return res.json(captureFile);
|
|
105
|
-
});
|
|
106
|
-
router.delete("/captures/:id", (req, res) => {
|
|
107
|
-
const id = req.params.id;
|
|
108
|
-
if (!id) {
|
|
109
|
-
return jsonError(res, 400, "Missing capture id");
|
|
110
|
-
}
|
|
111
|
-
const deleted = replayEngine.deleteCapture(id);
|
|
112
|
-
if (!deleted) {
|
|
113
|
-
return jsonError(res, 404, "Capture not found");
|
|
114
|
-
}
|
|
115
|
-
broadcastCaptures();
|
|
116
|
-
return res.json({ success: true });
|
|
117
|
-
});
|
|
118
|
-
router.delete("/captures", (_req, res) => {
|
|
119
|
-
const deleted = replayEngine.deleteAllCaptures();
|
|
120
|
-
broadcastCaptures();
|
|
121
|
-
return res.json({ success: true, deleted });
|
|
122
|
-
});
|
|
123
|
-
router.post("/replay", express.json({ limit: "5mb" }), async (req, res) => {
|
|
124
|
-
const parsed = ReplayBodySchema.safeParse(req.body);
|
|
125
|
-
if (!parsed.success) {
|
|
126
|
-
return jsonError(res, 400, parsed.error.issues[0]?.message || "Invalid body");
|
|
127
|
-
}
|
|
128
|
-
const { captureId, targetUrl, method, headers } = parsed.data;
|
|
129
|
-
try {
|
|
130
|
-
new URL(targetUrl);
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
return jsonError(res, 400, "Invalid targetUrl");
|
|
134
|
-
}
|
|
135
|
-
try {
|
|
136
|
-
const result = await replayEngine.replay(captureId, {
|
|
137
|
-
targetUrl,
|
|
138
|
-
method,
|
|
139
|
-
headers,
|
|
140
|
-
});
|
|
141
|
-
broadcast?.({
|
|
142
|
-
type: "replay_result",
|
|
143
|
-
payload: { captureId, targetUrl, result },
|
|
144
|
-
});
|
|
145
|
-
return res.json(result);
|
|
146
|
-
}
|
|
147
|
-
catch (error) {
|
|
148
|
-
return jsonError(res, 400, error?.message || "Replay failed");
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
router.get("/templates/local", (_req, res) => {
|
|
152
|
-
const local = templateManager.listLocalTemplates();
|
|
153
|
-
return res.json({ templates: local, count: local.length });
|
|
154
|
-
});
|
|
155
|
-
router.get("/templates/remote", async (req, res) => {
|
|
156
|
-
const refresh = typeof req.query.refresh === "string"
|
|
157
|
-
? req.query.refresh === "1" ||
|
|
158
|
-
req.query.refresh.toLowerCase() === "true"
|
|
159
|
-
: false;
|
|
160
|
-
try {
|
|
161
|
-
const index = await templateManager.fetchRemoteIndex(refresh);
|
|
162
|
-
const localIds = new Set(templateManager.listLocalTemplates().map((t) => t.id));
|
|
163
|
-
const remote = index.templates.map((metadata) => ({
|
|
164
|
-
metadata,
|
|
165
|
-
isDownloaded: localIds.has(metadata.id),
|
|
166
|
-
}));
|
|
167
|
-
return res.json({ templates: remote, count: remote.length });
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
return jsonError(res, 500, error?.message || "Failed to fetch remote templates");
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
router.post("/templates/download", express.json({ limit: "2mb" }), async (req, res) => {
|
|
174
|
-
const parsed = TemplateDownloadBodySchema.safeParse(req.body);
|
|
175
|
-
if (!parsed.success) {
|
|
176
|
-
return jsonError(res, 400, parsed.error.issues[0]?.message || "Invalid body");
|
|
177
|
-
}
|
|
178
|
-
try {
|
|
179
|
-
const template = await templateManager.downloadTemplate(parsed.data.id);
|
|
180
|
-
void broadcastTemplates();
|
|
181
|
-
return res.json({ success: true, template });
|
|
182
|
-
}
|
|
183
|
-
catch (error) {
|
|
184
|
-
return jsonError(res, 400, error?.message || "Download failed");
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
router.post("/templates/download-all", express.json({ limit: "1mb" }), async (_req, res) => {
|
|
188
|
-
try {
|
|
189
|
-
const index = await templateManager.fetchRemoteIndex(true);
|
|
190
|
-
const localIds = new Set(templateManager.listLocalTemplates().map((t) => t.id));
|
|
191
|
-
const toDownload = index.templates.filter((t) => !localIds.has(t.id));
|
|
192
|
-
const downloaded = [];
|
|
193
|
-
const failed = [];
|
|
194
|
-
for (const t of toDownload) {
|
|
195
|
-
try {
|
|
196
|
-
await templateManager.downloadTemplate(t.id);
|
|
197
|
-
downloaded.push(t.id);
|
|
198
|
-
}
|
|
199
|
-
catch (e) {
|
|
200
|
-
failed.push({ id: t.id, error: e?.message || "Failed" });
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return res.json({
|
|
204
|
-
success: true,
|
|
205
|
-
total: index.templates.length,
|
|
206
|
-
downloaded,
|
|
207
|
-
failed,
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
catch (error) {
|
|
211
|
-
return jsonError(res, 500, error?.message || "Download-all failed");
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
router.post("/run", express.json({ limit: "10mb" }), async (req, res) => {
|
|
215
|
-
const parsed = RunTemplateBodySchema.safeParse(req.body);
|
|
216
|
-
if (!parsed.success) {
|
|
217
|
-
return jsonError(res, 400, parsed.error.issues[0]?.message || "Invalid body");
|
|
218
|
-
}
|
|
219
|
-
let { templateId, url, secret, headers } = parsed.data;
|
|
220
|
-
try {
|
|
221
|
-
new URL(url);
|
|
222
|
-
}
|
|
223
|
-
catch {
|
|
224
|
-
return jsonError(res, 400, "Invalid url");
|
|
225
|
-
}
|
|
226
|
-
if (templateId.startsWith("remote:")) {
|
|
227
|
-
templateId = templateId.slice("remote:".length);
|
|
228
|
-
try {
|
|
229
|
-
await templateManager.downloadTemplate(templateId);
|
|
230
|
-
}
|
|
231
|
-
catch (error) {
|
|
232
|
-
return jsonError(res, 400, error?.message || "Failed to download template");
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
let localTemplate = templateManager.getLocalTemplate(templateId);
|
|
236
|
-
if (!localTemplate) {
|
|
237
|
-
try {
|
|
238
|
-
await templateManager.downloadTemplate(templateId);
|
|
239
|
-
localTemplate = templateManager.getLocalTemplate(templateId);
|
|
240
|
-
}
|
|
241
|
-
catch {
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
if (!localTemplate) {
|
|
245
|
-
return jsonError(res, 404, "Template not found");
|
|
246
|
-
}
|
|
247
|
-
if (!secret && localTemplate.metadata.provider) {
|
|
248
|
-
const envVarName = getSecretEnvVarName(localTemplate.metadata.provider);
|
|
249
|
-
secret = process.env[envVarName];
|
|
250
|
-
}
|
|
251
|
-
const safeHeaders = headers?.length
|
|
252
|
-
? headers
|
|
253
|
-
: undefined;
|
|
254
|
-
try {
|
|
255
|
-
const result = await executeTemplate(localTemplate.template, {
|
|
256
|
-
url,
|
|
257
|
-
secret,
|
|
258
|
-
headers: safeHeaders,
|
|
259
|
-
});
|
|
260
|
-
broadcast?.({
|
|
261
|
-
type: "replay_result",
|
|
262
|
-
payload: { templateId, url, result },
|
|
263
|
-
});
|
|
264
|
-
return res.json(result);
|
|
265
|
-
}
|
|
266
|
-
catch (error) {
|
|
267
|
-
return jsonError(res, 400, error?.message || "Run failed");
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
return router;
|
|
271
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import express from "express";
|
|
2
|
-
import { type Server } from "http";
|
|
3
|
-
import { type DashboardApiOptions } from "./dashboard-api.js";
|
|
4
|
-
import { CaptureServer } from "./capture-server.js";
|
|
5
|
-
export interface DashboardServerOptions extends DashboardApiOptions {
|
|
6
|
-
host?: string;
|
|
7
|
-
port?: number;
|
|
8
|
-
captureHost?: string;
|
|
9
|
-
capturePort?: number;
|
|
10
|
-
startCapture?: boolean;
|
|
11
|
-
}
|
|
12
|
-
export declare function startDashboardServer(options?: DashboardServerOptions): Promise<{
|
|
13
|
-
app: express.Express;
|
|
14
|
-
server: Server;
|
|
15
|
-
url: string;
|
|
16
|
-
capture?: {
|
|
17
|
-
server: CaptureServer;
|
|
18
|
-
url: string;
|
|
19
|
-
};
|
|
20
|
-
}>;
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import express from "express";
|
|
2
|
-
import { createServer } from "http";
|
|
3
|
-
import { WebSocketServer } from "ws";
|
|
4
|
-
import path from "path";
|
|
5
|
-
import { existsSync } from "fs";
|
|
6
|
-
import { fileURLToPath } from "url";
|
|
7
|
-
import { createDashboardApiRouter, } from "./dashboard-api.js";
|
|
8
|
-
import { CaptureServer } from "./capture-server.js";
|
|
9
|
-
import { ReplayEngine } from "./replay-engine.js";
|
|
10
|
-
import { TemplateManager } from "./template-manager.js";
|
|
11
|
-
function resolveDashboardDistDir(runtimeDir) {
|
|
12
|
-
const candidates = [
|
|
13
|
-
path.resolve(runtimeDir, "dashboard"),
|
|
14
|
-
path.resolve(runtimeDir, "..", "dashboard"),
|
|
15
|
-
path.resolve(runtimeDir, "..", "..", "dist", "dashboard"),
|
|
16
|
-
path.resolve(runtimeDir, "..", "..", "..", "dashboard", "dist"),
|
|
17
|
-
];
|
|
18
|
-
for (const distDir of candidates) {
|
|
19
|
-
const indexHtml = path.join(distDir, "index.html");
|
|
20
|
-
if (existsSync(indexHtml)) {
|
|
21
|
-
return { distDir, indexHtml };
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
const details = candidates.map((p) => `- ${p}`).join("\n");
|
|
25
|
-
throw new Error(`Dashboard UI build output not found.\n` +
|
|
26
|
-
`Looked in:\n${details}\n\n` +
|
|
27
|
-
`Build it with:\n` +
|
|
28
|
-
`- pnpm --filter @better-webhook/dashboard build\n` +
|
|
29
|
-
`- pnpm --filter @better-webhook/cli build\n`);
|
|
30
|
-
}
|
|
31
|
-
export async function startDashboardServer(options = {}) {
|
|
32
|
-
const app = express();
|
|
33
|
-
app.get("/health", (_req, res) => {
|
|
34
|
-
res.json({ ok: true });
|
|
35
|
-
});
|
|
36
|
-
const clients = new Set();
|
|
37
|
-
const broadcast = (message) => {
|
|
38
|
-
const data = JSON.stringify(message);
|
|
39
|
-
for (const client of clients) {
|
|
40
|
-
if (client.readyState === 1) {
|
|
41
|
-
client.send(data);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
app.use("/api", createDashboardApiRouter({
|
|
46
|
-
capturesDir: options.capturesDir,
|
|
47
|
-
templatesBaseDir: options.templatesBaseDir,
|
|
48
|
-
broadcast,
|
|
49
|
-
}));
|
|
50
|
-
const host = options.host || "localhost";
|
|
51
|
-
const port = options.port ?? 4000;
|
|
52
|
-
const runtimeDir = typeof __dirname !== "undefined"
|
|
53
|
-
?
|
|
54
|
-
__dirname
|
|
55
|
-
: path.dirname(fileURLToPath(import.meta.url));
|
|
56
|
-
const { distDir: dashboardDistDir, indexHtml: dashboardIndexHtml } = resolveDashboardDistDir(runtimeDir);
|
|
57
|
-
app.use(express.static(dashboardDistDir));
|
|
58
|
-
app.get("*", (req, res, next) => {
|
|
59
|
-
if (req.path.startsWith("/api") || req.path === "/health")
|
|
60
|
-
return next();
|
|
61
|
-
res.sendFile(dashboardIndexHtml, (err) => {
|
|
62
|
-
if (err)
|
|
63
|
-
next();
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
const server = createServer(app);
|
|
67
|
-
const wss = new WebSocketServer({ server, path: "/ws" });
|
|
68
|
-
wss.on("connection", async (ws) => {
|
|
69
|
-
clients.add(ws);
|
|
70
|
-
ws.on("close", () => clients.delete(ws));
|
|
71
|
-
ws.on("error", () => clients.delete(ws));
|
|
72
|
-
const replayEngine = new ReplayEngine(options.capturesDir);
|
|
73
|
-
const templateManager = new TemplateManager(options.templatesBaseDir);
|
|
74
|
-
const captures = replayEngine.listCaptures(200);
|
|
75
|
-
ws.send(JSON.stringify({
|
|
76
|
-
type: "captures_updated",
|
|
77
|
-
payload: { captures, count: captures.length },
|
|
78
|
-
}));
|
|
79
|
-
const local = templateManager.listLocalTemplates();
|
|
80
|
-
let remote = [];
|
|
81
|
-
try {
|
|
82
|
-
const index = await templateManager.fetchRemoteIndex(true);
|
|
83
|
-
const localIds = new Set(local.map((t) => t.id));
|
|
84
|
-
remote = index.templates.map((metadata) => ({
|
|
85
|
-
metadata,
|
|
86
|
-
isDownloaded: localIds.has(metadata.id),
|
|
87
|
-
}));
|
|
88
|
-
}
|
|
89
|
-
catch {
|
|
90
|
-
remote = [];
|
|
91
|
-
}
|
|
92
|
-
ws.send(JSON.stringify({
|
|
93
|
-
type: "templates_updated",
|
|
94
|
-
payload: { local, remote },
|
|
95
|
-
}));
|
|
96
|
-
});
|
|
97
|
-
await new Promise((resolve, reject) => {
|
|
98
|
-
server.listen(port, host, () => resolve());
|
|
99
|
-
server.on("error", reject);
|
|
100
|
-
});
|
|
101
|
-
const url = `http://${host}:${port}`;
|
|
102
|
-
let capture;
|
|
103
|
-
const shouldStartCapture = options.startCapture !== false;
|
|
104
|
-
if (shouldStartCapture) {
|
|
105
|
-
const captureHost = options.captureHost || "0.0.0.0";
|
|
106
|
-
const capturePort = options.capturePort ?? 3001;
|
|
107
|
-
const captureServer = new CaptureServer({
|
|
108
|
-
capturesDir: options.capturesDir,
|
|
109
|
-
enableWebSocket: false,
|
|
110
|
-
onCapture: ({ file, capture }) => {
|
|
111
|
-
broadcast({
|
|
112
|
-
type: "capture",
|
|
113
|
-
payload: { file, capture },
|
|
114
|
-
});
|
|
115
|
-
},
|
|
116
|
-
});
|
|
117
|
-
const actualPort = await captureServer.start(capturePort, captureHost);
|
|
118
|
-
capture = {
|
|
119
|
-
server: captureServer,
|
|
120
|
-
url: `http://${captureHost === "0.0.0.0" ? "localhost" : captureHost}:${actualPort}`,
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
return { app, server, url, capture };
|
|
124
|
-
}
|
package/dist/core/executor.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { WebhookExecutionOptions, WebhookExecutionResult, HeaderEntry, WebhookTemplate } from "../types/index.js";
|
|
2
|
-
export declare function executeWebhook(options: WebhookExecutionOptions): Promise<WebhookExecutionResult>;
|
|
3
|
-
export declare function executeTemplate(template: WebhookTemplate, options?: {
|
|
4
|
-
url?: string;
|
|
5
|
-
secret?: string;
|
|
6
|
-
headers?: HeaderEntry[];
|
|
7
|
-
}): Promise<WebhookExecutionResult>;
|
|
8
|
-
export declare class ExecutionError extends Error {
|
|
9
|
-
duration: number;
|
|
10
|
-
constructor(message: string, duration: number);
|
|
11
|
-
}
|
package/dist/core/executor.js
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { request } from "undici";
|
|
2
|
-
import { generateSignature, getProviderHeaders } from "./signature.js";
|
|
3
|
-
export async function executeWebhook(options) {
|
|
4
|
-
const startTime = Date.now();
|
|
5
|
-
let bodyStr;
|
|
6
|
-
if (options.body !== undefined) {
|
|
7
|
-
bodyStr =
|
|
8
|
-
typeof options.body === "string"
|
|
9
|
-
? options.body
|
|
10
|
-
: JSON.stringify(options.body);
|
|
11
|
-
}
|
|
12
|
-
const headers = {};
|
|
13
|
-
if (options.provider) {
|
|
14
|
-
const providerHeaders = getProviderHeaders(options.provider);
|
|
15
|
-
for (const h of providerHeaders) {
|
|
16
|
-
headers[h.key] = h.value;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
if (options.headers) {
|
|
20
|
-
for (const h of options.headers) {
|
|
21
|
-
headers[h.key] = h.value;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
if (options.secret && options.provider && bodyStr) {
|
|
25
|
-
const sig = generateSignature(options.provider, bodyStr, options.secret, {
|
|
26
|
-
url: options.url,
|
|
27
|
-
});
|
|
28
|
-
if (sig) {
|
|
29
|
-
headers[sig.header] = sig.value;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
if (!headers["Content-Type"] && !headers["content-type"]) {
|
|
33
|
-
headers["Content-Type"] = "application/json";
|
|
34
|
-
}
|
|
35
|
-
try {
|
|
36
|
-
const response = await request(options.url, {
|
|
37
|
-
method: options.method || "POST",
|
|
38
|
-
headers,
|
|
39
|
-
body: bodyStr,
|
|
40
|
-
headersTimeout: options.timeout || 30000,
|
|
41
|
-
bodyTimeout: options.timeout || 30000,
|
|
42
|
-
});
|
|
43
|
-
const bodyText = await response.body.text();
|
|
44
|
-
const duration = Date.now() - startTime;
|
|
45
|
-
const responseHeaders = {};
|
|
46
|
-
for (const [key, value] of Object.entries(response.headers)) {
|
|
47
|
-
if (value !== undefined) {
|
|
48
|
-
responseHeaders[key] = value;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
let json;
|
|
52
|
-
try {
|
|
53
|
-
json = JSON.parse(bodyText);
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
}
|
|
57
|
-
return {
|
|
58
|
-
status: response.statusCode,
|
|
59
|
-
statusText: getStatusText(response.statusCode),
|
|
60
|
-
headers: responseHeaders,
|
|
61
|
-
body: json ?? bodyText,
|
|
62
|
-
bodyText,
|
|
63
|
-
json,
|
|
64
|
-
duration,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
const duration = Date.now() - startTime;
|
|
69
|
-
throw new ExecutionError(error.message, duration);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
export async function executeTemplate(template, options = {}) {
|
|
73
|
-
const targetUrl = options.url || template.url;
|
|
74
|
-
if (!targetUrl) {
|
|
75
|
-
throw new Error("No target URL specified. Use --url or set url in template.");
|
|
76
|
-
}
|
|
77
|
-
const mergedHeaders = [...(template.headers || [])];
|
|
78
|
-
if (options.headers) {
|
|
79
|
-
for (const h of options.headers) {
|
|
80
|
-
const existingIdx = mergedHeaders.findIndex((mh) => mh.key.toLowerCase() === h.key.toLowerCase());
|
|
81
|
-
if (existingIdx >= 0) {
|
|
82
|
-
mergedHeaders[existingIdx] = h;
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
mergedHeaders.push(h);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return executeWebhook({
|
|
90
|
-
url: targetUrl,
|
|
91
|
-
method: template.method,
|
|
92
|
-
headers: mergedHeaders,
|
|
93
|
-
body: template.body,
|
|
94
|
-
secret: options.secret,
|
|
95
|
-
provider: template.provider,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
export class ExecutionError extends Error {
|
|
99
|
-
duration;
|
|
100
|
-
constructor(message, duration) {
|
|
101
|
-
super(message);
|
|
102
|
-
this.name = "ExecutionError";
|
|
103
|
-
this.duration = duration;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
function getStatusText(code) {
|
|
107
|
-
const statusTexts = {
|
|
108
|
-
200: "OK",
|
|
109
|
-
201: "Created",
|
|
110
|
-
202: "Accepted",
|
|
111
|
-
204: "No Content",
|
|
112
|
-
301: "Moved Permanently",
|
|
113
|
-
302: "Found",
|
|
114
|
-
304: "Not Modified",
|
|
115
|
-
400: "Bad Request",
|
|
116
|
-
401: "Unauthorized",
|
|
117
|
-
403: "Forbidden",
|
|
118
|
-
404: "Not Found",
|
|
119
|
-
405: "Method Not Allowed",
|
|
120
|
-
408: "Request Timeout",
|
|
121
|
-
409: "Conflict",
|
|
122
|
-
422: "Unprocessable Entity",
|
|
123
|
-
429: "Too Many Requests",
|
|
124
|
-
500: "Internal Server Error",
|
|
125
|
-
502: "Bad Gateway",
|
|
126
|
-
503: "Service Unavailable",
|
|
127
|
-
504: "Gateway Timeout",
|
|
128
|
-
};
|
|
129
|
-
return statusTexts[code] || "Unknown";
|
|
130
|
-
}
|
package/dist/core/index.d.ts
DELETED
package/dist/core/index.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { CaptureFile, ReplayOptions, WebhookExecutionResult, WebhookTemplate } from "../types/index.js";
|
|
2
|
-
export declare class ReplayEngine {
|
|
3
|
-
private capturesDir;
|
|
4
|
-
constructor(capturesDir?: string);
|
|
5
|
-
getCapturesDir(): string;
|
|
6
|
-
listCaptures(limit?: number): CaptureFile[];
|
|
7
|
-
getCapture(captureId: string): CaptureFile | null;
|
|
8
|
-
replay(captureId: string, options: ReplayOptions): Promise<WebhookExecutionResult>;
|
|
9
|
-
captureToTemplate(captureId: string, options?: {
|
|
10
|
-
url?: string;
|
|
11
|
-
}): WebhookTemplate;
|
|
12
|
-
getCaptureSummary(captureId: string): string;
|
|
13
|
-
searchCaptures(query: string): CaptureFile[];
|
|
14
|
-
getCapturesByProvider(provider: string): CaptureFile[];
|
|
15
|
-
deleteCapture(captureId: string): boolean;
|
|
16
|
-
deleteAllCaptures(): number;
|
|
17
|
-
}
|
|
18
|
-
export declare function getReplayEngine(capturesDir?: string): ReplayEngine;
|