hooklens 1.0.0 → 1.0.1
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 +33 -18
- package/dist/index.js +240 -173
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img src="
|
|
3
|
+
<img src="https://ilia01.github.io/hooklens/logo.svg" alt="HookLens logo" width="88" height="88">
|
|
4
4
|
|
|
5
5
|
# HookLens
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Debug webhook signature failures locally.**
|
|
8
8
|
|
|
9
9
|
Figure out why webhook signature verification failed before your framework hides the evidence.
|
|
10
10
|
|
|
@@ -17,7 +17,14 @@ Figure out why webhook signature verification failed before your framework hides
|
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
-
HookLens is
|
|
20
|
+
HookLens is a local CLI for the annoying part of webhook debugging:
|
|
21
|
+
the delivery reached your app, verification still failed, and your framework already changed the body you needed to inspect.
|
|
22
|
+
|
|
23
|
+
It captures the incoming request before parsing, verifies it locally, stores the event, and lets you replay the exact delivery after you fix your app.
|
|
24
|
+
|
|
25
|
+
<p align="center">
|
|
26
|
+
<img src="https://ilia01.github.io/hooklens/hooklens-demo.gif" alt="HookLens demo showing capture, verification, listing, and replay from the terminal" width="980">
|
|
27
|
+
</p>
|
|
21
28
|
|
|
22
29
|
## Install
|
|
23
30
|
|
|
@@ -27,27 +34,35 @@ HookLens is an open-source CLI for local webhook debugging. It captures the raw
|
|
|
27
34
|
npm install -g hooklens
|
|
28
35
|
```
|
|
29
36
|
|
|
30
|
-
##
|
|
37
|
+
## The loop
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
hooklens listen --verify github --secret ghsecret_xxx
|
|
41
|
+
hooklens list
|
|
42
|
+
hooklens inspect evt_abc123
|
|
43
|
+
hooklens replay evt_abc123 --to http://localhost:3000/webhook
|
|
44
|
+
```
|
|
31
45
|
|
|
32
|
-
|
|
33
|
-
- [Commands](https://ilia01.github.io/hooklens/commands/)
|
|
34
|
-
- [Verification](https://ilia01.github.io/hooklens/verification/)
|
|
35
|
-
- [Forwarding](https://ilia01.github.io/hooklens/forwarding)
|
|
36
|
-
- [Architecture](https://ilia01.github.io/hooklens/architecture)
|
|
46
|
+
Point your provider CLI, tunnel, or webhook source at `http://127.0.0.1:4400`.
|
|
37
47
|
|
|
38
|
-
|
|
48
|
+
Use HookLens when:
|
|
39
49
|
|
|
40
|
-
-
|
|
41
|
-
-
|
|
50
|
+
- the request reached your machine, but signature verification failed
|
|
51
|
+
- your framework parsed or re-serialized the body before verification
|
|
52
|
+
- you need the exact stored request, not a vague error line
|
|
53
|
+
- you want to replay the same event after changing middleware, secrets, or handler logic
|
|
42
54
|
|
|
43
|
-
|
|
55
|
+
It is not a tunnel, a hosted webhook inbox, or a replacement for provider delivery tooling.
|
|
44
56
|
|
|
45
|
-
|
|
46
|
-
- Missing or malformed webhook signature headers
|
|
47
|
-
- Replaying captured events after middleware or secret changes
|
|
48
|
-
- Forwarding webhook traffic to a local target while keeping capture history
|
|
57
|
+
## Read Next
|
|
49
58
|
|
|
50
|
-
|
|
59
|
+
- [Getting Started](https://ilia01.github.io/hooklens/getting-started) for installation and first capture
|
|
60
|
+
- [Commands](https://ilia01.github.io/hooklens/commands/) for the CLI reference
|
|
61
|
+
- [Verification](https://ilia01.github.io/hooklens/verification/) for failure codes and provider behavior
|
|
62
|
+
- [Stripe signature failures](https://ilia01.github.io/hooklens/verification/stripe-signature-failures)
|
|
63
|
+
- [GitHub signature mismatches](https://ilia01.github.io/hooklens/verification/github-signature-mismatch)
|
|
64
|
+
- [Raw body mutation](https://ilia01.github.io/hooklens/verification/raw-body-mutation)
|
|
65
|
+
- [Contributing](./CONTRIBUTING.md)
|
|
51
66
|
|
|
52
67
|
## License
|
|
53
68
|
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,93 @@
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { Command as Command7 } from "commander";
|
|
5
5
|
|
|
6
|
+
// package.json
|
|
7
|
+
var package_default = {
|
|
8
|
+
name: "hooklens",
|
|
9
|
+
version: "1.0.1",
|
|
10
|
+
description: "Debug webhook signature failures locally.",
|
|
11
|
+
type: "module",
|
|
12
|
+
bin: {
|
|
13
|
+
hooklens: "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
scripts: {
|
|
16
|
+
build: "tsup",
|
|
17
|
+
dev: "tsup --watch",
|
|
18
|
+
"docs:build": "vitepress build docs",
|
|
19
|
+
"docs:dev": "vitepress dev docs",
|
|
20
|
+
"docs:preview": "vitepress preview docs",
|
|
21
|
+
start: "node dist/index.js",
|
|
22
|
+
test: "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"test:coverage": "vitest run --coverage",
|
|
25
|
+
lint: "eslint src/ tests/",
|
|
26
|
+
"lint:fix": "eslint src/ tests/ --fix",
|
|
27
|
+
format: 'prettier --write "src/**/*.ts" "tests/**/*.ts"',
|
|
28
|
+
"format:check": 'prettier --check "src/**/*.ts" "tests/**/*.ts"',
|
|
29
|
+
typecheck: "tsc --noEmit",
|
|
30
|
+
prepublishOnly: "npm run build",
|
|
31
|
+
prepare: "husky"
|
|
32
|
+
},
|
|
33
|
+
keywords: [
|
|
34
|
+
"webhook",
|
|
35
|
+
"debug",
|
|
36
|
+
"stripe",
|
|
37
|
+
"stripe-webhooks",
|
|
38
|
+
"github-webhooks",
|
|
39
|
+
"signature",
|
|
40
|
+
"webhook-signature",
|
|
41
|
+
"replay",
|
|
42
|
+
"cli",
|
|
43
|
+
"developer-tools"
|
|
44
|
+
],
|
|
45
|
+
author: "Ilia Goginashvili",
|
|
46
|
+
license: "MIT",
|
|
47
|
+
repository: {
|
|
48
|
+
type: "git",
|
|
49
|
+
url: "https://github.com/Ilia01/hooklens.git"
|
|
50
|
+
},
|
|
51
|
+
bugs: {
|
|
52
|
+
url: "https://github.com/Ilia01/hooklens/issues"
|
|
53
|
+
},
|
|
54
|
+
homepage: "https://ilia01.github.io/hooklens/",
|
|
55
|
+
engines: {
|
|
56
|
+
node: ">=24.0.0"
|
|
57
|
+
},
|
|
58
|
+
files: [
|
|
59
|
+
"dist",
|
|
60
|
+
"README.md",
|
|
61
|
+
"LICENSE"
|
|
62
|
+
],
|
|
63
|
+
devDependencies: {
|
|
64
|
+
"@octokit/webhooks-methods": "^6.0.0",
|
|
65
|
+
"@types/node": "^22.0.0",
|
|
66
|
+
eslint: "^9.0.0",
|
|
67
|
+
husky: "^9.1.7",
|
|
68
|
+
"lint-staged": "^16.4.0",
|
|
69
|
+
prettier: "^3.4.0",
|
|
70
|
+
stripe: "^22.0.0",
|
|
71
|
+
tsup: "^8.0.0",
|
|
72
|
+
typescript: "^5.7.0",
|
|
73
|
+
"typescript-eslint": "^8.0.0",
|
|
74
|
+
vitepress: "^1.6.4",
|
|
75
|
+
vitest: "^3.0.0"
|
|
76
|
+
},
|
|
77
|
+
dependencies: {
|
|
78
|
+
chalk: "^5.4.0",
|
|
79
|
+
commander: "^13.0.0",
|
|
80
|
+
zod: "^4.3.6"
|
|
81
|
+
},
|
|
82
|
+
"lint-staged": {
|
|
83
|
+
"*.{ts,tsx}": [
|
|
84
|
+
"prettier --write",
|
|
85
|
+
"eslint --fix"
|
|
86
|
+
],
|
|
87
|
+
"*.{json,md,yml,yaml}": [
|
|
88
|
+
"prettier --write"
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
6
93
|
// src/errors.ts
|
|
7
94
|
function toError(value) {
|
|
8
95
|
return value instanceof Error ? value : new Error(String(value));
|
|
@@ -14,6 +101,109 @@ function errorMessage(value) {
|
|
|
14
101
|
// src/cli/clear.ts
|
|
15
102
|
import { Command } from "commander";
|
|
16
103
|
|
|
104
|
+
// src/ui/terminal.ts
|
|
105
|
+
import chalk from "chalk";
|
|
106
|
+
function writeLine(stream, line) {
|
|
107
|
+
stream.write(`${line}
|
|
108
|
+
`);
|
|
109
|
+
}
|
|
110
|
+
function verificationLabel(result) {
|
|
111
|
+
if (!result) return chalk.cyan("RECV");
|
|
112
|
+
return result.valid ? chalk.green("PASS") : chalk.red("FAIL");
|
|
113
|
+
}
|
|
114
|
+
function createTerminal(stdout = process.stdout, stderr = process.stderr) {
|
|
115
|
+
return {
|
|
116
|
+
printListenStarted(info) {
|
|
117
|
+
writeLine(
|
|
118
|
+
stdout,
|
|
119
|
+
`${chalk.bold("Listening on")} ${chalk.cyan(`http://127.0.0.1:${info.port}`)}`
|
|
120
|
+
);
|
|
121
|
+
writeLine(stdout, `Verifier: ${info.verifier ?? "none"}`);
|
|
122
|
+
writeLine(stdout, `Forwarding to: ${info.forwardTo ?? "disabled"}`);
|
|
123
|
+
writeLine(stdout, `Storage: ${info.dbPath}`);
|
|
124
|
+
},
|
|
125
|
+
printEventCaptured(event, result) {
|
|
126
|
+
const label = verificationLabel(result);
|
|
127
|
+
const summary = `${label} ${chalk.bold(event.id)} ${event.method} ${event.path}`;
|
|
128
|
+
if (!result) {
|
|
129
|
+
writeLine(stdout, summary);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
writeLine(stdout, `${summary} ${result.message}`);
|
|
133
|
+
},
|
|
134
|
+
printForwardError(eventId, reason) {
|
|
135
|
+
writeLine(stdout, `${chalk.red("FWD")} ${chalk.bold(eventId)} ${reason}`);
|
|
136
|
+
},
|
|
137
|
+
printForwardRetry(eventId, attempt, maxRetries, reason) {
|
|
138
|
+
writeLine(
|
|
139
|
+
stdout,
|
|
140
|
+
`${chalk.yellow("RETRY")} ${chalk.bold(eventId)} attempt ${attempt}/${maxRetries} ${reason}`
|
|
141
|
+
);
|
|
142
|
+
},
|
|
143
|
+
printEventList(events) {
|
|
144
|
+
if (!events.length) {
|
|
145
|
+
writeLine(stdout, chalk.dim("No stored events."));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
for (const event of events) {
|
|
149
|
+
const row = `${chalk.dim(event.timestamp)} ${chalk.cyan(event.method)} ${chalk.bold(event.id)} ${event.path}`;
|
|
150
|
+
writeLine(stdout, row);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
printEventDetail(event) {
|
|
154
|
+
writeLine(stdout, `${chalk.bold("Event:")} ${event.id}`);
|
|
155
|
+
writeLine(stdout, `${chalk.bold("Time:")} ${event.timestamp}`);
|
|
156
|
+
writeLine(stdout, `${chalk.bold("Method:")} ${event.method}`);
|
|
157
|
+
writeLine(stdout, `${chalk.bold("Path:")} ${event.path}`);
|
|
158
|
+
if (event.verification) {
|
|
159
|
+
const v = event.verification;
|
|
160
|
+
const label = v.valid ? chalk.green("PASS") : chalk.red("FAIL");
|
|
161
|
+
writeLine(stdout, "");
|
|
162
|
+
writeLine(stdout, chalk.bold("Verification:"));
|
|
163
|
+
writeLine(stdout, ` Result: ${label}`);
|
|
164
|
+
writeLine(stdout, ` Provider: ${v.provider}`);
|
|
165
|
+
writeLine(stdout, ` Message: ${v.message}`);
|
|
166
|
+
}
|
|
167
|
+
writeLine(stdout, "");
|
|
168
|
+
writeLine(stdout, chalk.bold("Headers:"));
|
|
169
|
+
for (const [key, value] of Object.entries(event.headers)) {
|
|
170
|
+
writeLine(stdout, ` ${key}: ${value}`);
|
|
171
|
+
}
|
|
172
|
+
writeLine(stdout, "");
|
|
173
|
+
writeLine(stdout, chalk.bold("Body:"));
|
|
174
|
+
let bodyText;
|
|
175
|
+
try {
|
|
176
|
+
bodyText = JSON.stringify(JSON.parse(event.body), null, 2);
|
|
177
|
+
} catch {
|
|
178
|
+
bodyText = event.body;
|
|
179
|
+
}
|
|
180
|
+
for (const line of bodyText.split("\n")) {
|
|
181
|
+
writeLine(stdout, ` ${line}`);
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
printReplayResult(result) {
|
|
185
|
+
const summary = `${chalk.bold("Replay response:")} ${chalk.cyan(String(result.status))}`;
|
|
186
|
+
if (!result.body) {
|
|
187
|
+
writeLine(stdout, summary);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
writeLine(stdout, `${summary} ${result.body}`);
|
|
191
|
+
},
|
|
192
|
+
printDeleted(eventId) {
|
|
193
|
+
writeLine(stdout, `Deleted ${chalk.bold(eventId)}`);
|
|
194
|
+
},
|
|
195
|
+
printCleared(count) {
|
|
196
|
+
writeLine(stdout, `Cleared ${chalk.bold(String(count))} events`);
|
|
197
|
+
},
|
|
198
|
+
printListenStopped() {
|
|
199
|
+
writeLine(stdout, chalk.dim("Stopped listening."));
|
|
200
|
+
},
|
|
201
|
+
printError(message) {
|
|
202
|
+
writeLine(stderr, chalk.red(message));
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
17
207
|
// src/storage/index.ts
|
|
18
208
|
import os from "os";
|
|
19
209
|
import fs from "fs";
|
|
@@ -146,107 +336,26 @@ function createStorage(dbPath) {
|
|
|
146
336
|
};
|
|
147
337
|
}
|
|
148
338
|
|
|
149
|
-
// src/
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
function createTerminal(stdout = process.stdout, stderr = process.stderr) {
|
|
160
|
-
return {
|
|
161
|
-
printListenStarted(info) {
|
|
162
|
-
writeLine(
|
|
163
|
-
stdout,
|
|
164
|
-
`${chalk.bold("Listening on")} ${chalk.cyan(`http://127.0.0.1:${info.port}`)}`
|
|
165
|
-
);
|
|
166
|
-
writeLine(stdout, `Verifier: ${info.verifier ?? "none"}`);
|
|
167
|
-
writeLine(stdout, `Forwarding to: ${info.forwardTo ?? "disabled"}`);
|
|
168
|
-
writeLine(stdout, `Storage: ${info.dbPath}`);
|
|
169
|
-
},
|
|
170
|
-
printEventCaptured(event, result) {
|
|
171
|
-
const label = verificationLabel(result);
|
|
172
|
-
const summary = `${label} ${chalk.bold(event.id)} ${event.method} ${event.path}`;
|
|
173
|
-
if (!result) {
|
|
174
|
-
writeLine(stdout, summary);
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
writeLine(stdout, `${summary} ${result.message}`);
|
|
178
|
-
},
|
|
179
|
-
printForwardError(eventId, reason) {
|
|
180
|
-
writeLine(stdout, `${chalk.red("FWD")} ${chalk.bold(eventId)} ${reason}`);
|
|
181
|
-
},
|
|
182
|
-
printForwardRetry(eventId, attempt, maxRetries, reason) {
|
|
183
|
-
writeLine(
|
|
184
|
-
stdout,
|
|
185
|
-
`${chalk.yellow("RETRY")} ${chalk.bold(eventId)} attempt ${attempt}/${maxRetries} ${reason}`
|
|
186
|
-
);
|
|
187
|
-
},
|
|
188
|
-
printEventList(events) {
|
|
189
|
-
if (!events.length) {
|
|
190
|
-
writeLine(stdout, chalk.dim("No stored events."));
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
for (const event of events) {
|
|
194
|
-
const row = `${chalk.dim(event.timestamp)} ${chalk.cyan(event.method)} ${chalk.bold(event.id)} ${event.path}`;
|
|
195
|
-
writeLine(stdout, row);
|
|
196
|
-
}
|
|
197
|
-
},
|
|
198
|
-
printEventDetail(event) {
|
|
199
|
-
writeLine(stdout, `${chalk.bold("Event:")} ${event.id}`);
|
|
200
|
-
writeLine(stdout, `${chalk.bold("Time:")} ${event.timestamp}`);
|
|
201
|
-
writeLine(stdout, `${chalk.bold("Method:")} ${event.method}`);
|
|
202
|
-
writeLine(stdout, `${chalk.bold("Path:")} ${event.path}`);
|
|
203
|
-
if (event.verification) {
|
|
204
|
-
const v = event.verification;
|
|
205
|
-
const label = v.valid ? chalk.green("PASS") : chalk.red("FAIL");
|
|
206
|
-
writeLine(stdout, "");
|
|
207
|
-
writeLine(stdout, chalk.bold("Verification:"));
|
|
208
|
-
writeLine(stdout, ` Result: ${label}`);
|
|
209
|
-
writeLine(stdout, ` Provider: ${v.provider}`);
|
|
210
|
-
writeLine(stdout, ` Message: ${v.message}`);
|
|
211
|
-
}
|
|
212
|
-
writeLine(stdout, "");
|
|
213
|
-
writeLine(stdout, chalk.bold("Headers:"));
|
|
214
|
-
for (const [key, value] of Object.entries(event.headers)) {
|
|
215
|
-
writeLine(stdout, ` ${key}: ${value}`);
|
|
216
|
-
}
|
|
217
|
-
writeLine(stdout, "");
|
|
218
|
-
writeLine(stdout, chalk.bold("Body:"));
|
|
219
|
-
let bodyText;
|
|
220
|
-
try {
|
|
221
|
-
bodyText = JSON.stringify(JSON.parse(event.body), null, 2);
|
|
222
|
-
} catch {
|
|
223
|
-
bodyText = event.body;
|
|
224
|
-
}
|
|
225
|
-
for (const line of bodyText.split("\n")) {
|
|
226
|
-
writeLine(stdout, ` ${line}`);
|
|
227
|
-
}
|
|
228
|
-
},
|
|
229
|
-
printReplayResult(result) {
|
|
230
|
-
const summary = `${chalk.bold("Replay response:")} ${chalk.cyan(String(result.status))}`;
|
|
231
|
-
if (!result.body) {
|
|
232
|
-
writeLine(stdout, summary);
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
writeLine(stdout, `${summary} ${result.body}`);
|
|
236
|
-
},
|
|
237
|
-
printDeleted(eventId) {
|
|
238
|
-
writeLine(stdout, `Deleted ${chalk.bold(eventId)}`);
|
|
239
|
-
},
|
|
240
|
-
printCleared(count) {
|
|
241
|
-
writeLine(stdout, `Cleared ${chalk.bold(String(count))} events`);
|
|
242
|
-
},
|
|
243
|
-
printListenStopped() {
|
|
244
|
-
writeLine(stdout, chalk.dim("Stopped listening."));
|
|
245
|
-
},
|
|
246
|
-
printError(message) {
|
|
247
|
-
writeLine(stderr, chalk.red(message));
|
|
339
|
+
// src/cli/runtime.ts
|
|
340
|
+
async function withDefaultStorage(run) {
|
|
341
|
+
const storage = createStorage(defaultDbPath());
|
|
342
|
+
try {
|
|
343
|
+
return await run(storage);
|
|
344
|
+
} finally {
|
|
345
|
+
try {
|
|
346
|
+
storage.close();
|
|
347
|
+
} catch {
|
|
248
348
|
}
|
|
249
|
-
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
async function runCommandAction(run) {
|
|
352
|
+
const terminal = createTerminal();
|
|
353
|
+
try {
|
|
354
|
+
await run(terminal);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
terminal.printError(errorMessage(error));
|
|
357
|
+
process.exitCode = 1;
|
|
358
|
+
}
|
|
250
359
|
}
|
|
251
360
|
|
|
252
361
|
// src/cli/clear.ts
|
|
@@ -269,13 +378,10 @@ async function runClear(flags, deps = {}) {
|
|
|
269
378
|
return;
|
|
270
379
|
}
|
|
271
380
|
}
|
|
272
|
-
|
|
273
|
-
try {
|
|
381
|
+
return withDefaultStorage((storage) => {
|
|
274
382
|
const count = storage.clear();
|
|
275
383
|
terminal.printCleared(count);
|
|
276
|
-
}
|
|
277
|
-
storage.close();
|
|
278
|
-
}
|
|
384
|
+
});
|
|
279
385
|
}
|
|
280
386
|
var clearCommand = new Command("clear").description("Delete all stored webhook events").option("--yes", "Skip confirmation prompt").addHelpText(
|
|
281
387
|
"after",
|
|
@@ -284,29 +390,20 @@ Examples:
|
|
|
284
390
|
hooklens clear --yes
|
|
285
391
|
hooklens clear`
|
|
286
392
|
).action(async (options) => {
|
|
287
|
-
|
|
288
|
-
try {
|
|
289
|
-
await runClear(options, { terminal });
|
|
290
|
-
} catch (error) {
|
|
291
|
-
terminal.printError(errorMessage(error));
|
|
292
|
-
process.exitCode = 1;
|
|
293
|
-
}
|
|
393
|
+
await runCommandAction((terminal) => runClear(options, { terminal }));
|
|
294
394
|
});
|
|
295
395
|
|
|
296
396
|
// src/cli/delete.ts
|
|
297
397
|
import { Command as Command2 } from "commander";
|
|
298
398
|
async function runDelete(eventId, deps = {}) {
|
|
299
399
|
const terminal = deps.terminal ?? createTerminal();
|
|
300
|
-
|
|
301
|
-
try {
|
|
400
|
+
return withDefaultStorage((storage) => {
|
|
302
401
|
const deleted = storage.delete(eventId);
|
|
303
402
|
if (!deleted) {
|
|
304
403
|
throw new Error(`Event "${eventId}" not found.`);
|
|
305
404
|
}
|
|
306
405
|
terminal.printDeleted(eventId);
|
|
307
|
-
}
|
|
308
|
-
storage.close();
|
|
309
|
-
}
|
|
406
|
+
});
|
|
310
407
|
}
|
|
311
408
|
var deleteCommand = new Command2("delete").description("Delete a stored webhook event").argument("<event-id>", "ID of the event to delete").addHelpText(
|
|
312
409
|
"after",
|
|
@@ -314,13 +411,7 @@ var deleteCommand = new Command2("delete").description("Delete a stored webhook
|
|
|
314
411
|
Examples:
|
|
315
412
|
hooklens delete evt_abc123`
|
|
316
413
|
).action(async (eventId) => {
|
|
317
|
-
|
|
318
|
-
try {
|
|
319
|
-
await runDelete(eventId, { terminal });
|
|
320
|
-
} catch (error) {
|
|
321
|
-
terminal.printError(errorMessage(error));
|
|
322
|
-
process.exitCode = 1;
|
|
323
|
-
}
|
|
414
|
+
await runCommandAction((terminal) => runDelete(eventId, { terminal }));
|
|
324
415
|
});
|
|
325
416
|
|
|
326
417
|
// src/cli/inspect.ts
|
|
@@ -340,8 +431,7 @@ function writeJsonLine(stdout, data) {
|
|
|
340
431
|
// src/cli/inspect.ts
|
|
341
432
|
async function runInspect(eventId, flags, deps = {}) {
|
|
342
433
|
const terminal = deps.terminal ?? createTerminal();
|
|
343
|
-
|
|
344
|
-
try {
|
|
434
|
+
return withDefaultStorage((storage) => {
|
|
345
435
|
const event = storage.load(eventId);
|
|
346
436
|
if (!event) {
|
|
347
437
|
throw new Error(`Event "${eventId}" not found.`);
|
|
@@ -352,9 +442,7 @@ async function runInspect(eventId, flags, deps = {}) {
|
|
|
352
442
|
} else {
|
|
353
443
|
terminal.printEventDetail(event);
|
|
354
444
|
}
|
|
355
|
-
}
|
|
356
|
-
storage.close();
|
|
357
|
-
}
|
|
445
|
+
});
|
|
358
446
|
}
|
|
359
447
|
var inspectCommand = new Command3("inspect").description("View full details of a stored webhook event").argument("<event-id>", "ID of the event to inspect").option("--json", "Output as JSON").addHelpText(
|
|
360
448
|
"after",
|
|
@@ -363,13 +451,7 @@ Examples:
|
|
|
363
451
|
hooklens inspect evt_abc123
|
|
364
452
|
hooklens inspect evt_abc123 --json`
|
|
365
453
|
).action(async (eventId, options) => {
|
|
366
|
-
|
|
367
|
-
try {
|
|
368
|
-
await runInspect(eventId, options, { terminal });
|
|
369
|
-
} catch (error) {
|
|
370
|
-
terminal.printError(errorMessage(error));
|
|
371
|
-
process.exitCode = 1;
|
|
372
|
-
}
|
|
454
|
+
await runCommandAction((terminal) => runInspect(eventId, options, { terminal }));
|
|
373
455
|
});
|
|
374
456
|
|
|
375
457
|
// src/cli/listen.ts
|
|
@@ -908,6 +990,12 @@ function createStripeVerifier(opts) {
|
|
|
908
990
|
};
|
|
909
991
|
}
|
|
910
992
|
|
|
993
|
+
// src/cli/defaults.ts
|
|
994
|
+
var DEFAULT_LISTEN_PORT = 4400;
|
|
995
|
+
var DEFAULT_LIST_LIMIT = 20;
|
|
996
|
+
var DEFAULT_REPLAY_TARGET_URL = "http://localhost:3000/webhook";
|
|
997
|
+
var DEFAULT_RETRY_COUNT = 0;
|
|
998
|
+
|
|
911
999
|
// src/cli/listen.ts
|
|
912
1000
|
function parsePort(port) {
|
|
913
1001
|
const raw = port;
|
|
@@ -918,7 +1006,7 @@ function parsePort(port) {
|
|
|
918
1006
|
return parsed;
|
|
919
1007
|
}
|
|
920
1008
|
function parseRetryCount(retry) {
|
|
921
|
-
if (retry === void 0) return
|
|
1009
|
+
if (retry === void 0) return DEFAULT_RETRY_COUNT;
|
|
922
1010
|
const parsed = typeof retry === "number" ? retry : Number(retry);
|
|
923
1011
|
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 10) {
|
|
924
1012
|
throw new Error(`Invalid retry count "${retry}". Expected an integer between 0 and 10.`);
|
|
@@ -1034,7 +1122,11 @@ async function runListen(flags, deps = {}) {
|
|
|
1034
1122
|
throw error;
|
|
1035
1123
|
}
|
|
1036
1124
|
}
|
|
1037
|
-
var listenCommand = new Command4("listen").description("Start receiving webhooks").option("-p, --port <port>", "Port to listen on",
|
|
1125
|
+
var listenCommand = new Command4("listen").description("Start receiving webhooks").option("-p, --port <port>", "Port to listen on", String(DEFAULT_LISTEN_PORT)).option("--verify <provider>", "Verify signatures (stripe, github)").option("--secret <secret>", "Webhook signing secret").option("--forward-to <url>", "Forward received webhooks to this URL").option(
|
|
1126
|
+
"--retry <count>",
|
|
1127
|
+
"Retry failed forwards with exponential backoff",
|
|
1128
|
+
String(DEFAULT_RETRY_COUNT)
|
|
1129
|
+
).addHelpText(
|
|
1038
1130
|
"after",
|
|
1039
1131
|
`
|
|
1040
1132
|
Examples:
|
|
@@ -1044,13 +1136,7 @@ Examples:
|
|
|
1044
1136
|
hooklens listen --verify github --secret ghsecret_xxx
|
|
1045
1137
|
hooklens listen --forward-to http://localhost:3000/webhook --retry 3`
|
|
1046
1138
|
).action(async (options) => {
|
|
1047
|
-
|
|
1048
|
-
try {
|
|
1049
|
-
await runListen(options, { terminal });
|
|
1050
|
-
} catch (error) {
|
|
1051
|
-
terminal.printError(errorMessage(error));
|
|
1052
|
-
process.exitCode = 1;
|
|
1053
|
-
}
|
|
1139
|
+
await runCommandAction((terminal) => runListen(options, { terminal }));
|
|
1054
1140
|
});
|
|
1055
1141
|
|
|
1056
1142
|
// src/cli/list.ts
|
|
@@ -1064,10 +1150,9 @@ function parseLimit(limit) {
|
|
|
1064
1150
|
return parsed;
|
|
1065
1151
|
}
|
|
1066
1152
|
async function runList(flags, deps = {}) {
|
|
1067
|
-
const limit = parseLimit(flags.limit ??
|
|
1153
|
+
const limit = parseLimit(flags.limit ?? DEFAULT_LIST_LIMIT);
|
|
1068
1154
|
const terminal = deps.terminal ?? createTerminal();
|
|
1069
|
-
|
|
1070
|
-
try {
|
|
1155
|
+
return withDefaultStorage((storage) => {
|
|
1071
1156
|
const events = storage.list(limit);
|
|
1072
1157
|
if (flags.json) {
|
|
1073
1158
|
const out = deps.stdout ?? process.stdout;
|
|
@@ -1082,11 +1167,9 @@ async function runList(flags, deps = {}) {
|
|
|
1082
1167
|
} else {
|
|
1083
1168
|
terminal.printEventList(events);
|
|
1084
1169
|
}
|
|
1085
|
-
}
|
|
1086
|
-
storage.close();
|
|
1087
|
-
}
|
|
1170
|
+
});
|
|
1088
1171
|
}
|
|
1089
|
-
var listCommand = new Command5("list").description("Show received webhook events").option("-n, --limit <count>", "Number of events to show",
|
|
1172
|
+
var listCommand = new Command5("list").description("Show received webhook events").option("-n, --limit <count>", "Number of events to show", String(DEFAULT_LIST_LIMIT)).option("--json", "Output as newline-delimited JSON").addHelpText(
|
|
1090
1173
|
"after",
|
|
1091
1174
|
`
|
|
1092
1175
|
Examples:
|
|
@@ -1094,18 +1177,11 @@ Examples:
|
|
|
1094
1177
|
hooklens list -n 5
|
|
1095
1178
|
hooklens list --json`
|
|
1096
1179
|
).action(async (options) => {
|
|
1097
|
-
|
|
1098
|
-
try {
|
|
1099
|
-
await runList(options, { terminal });
|
|
1100
|
-
} catch (error) {
|
|
1101
|
-
terminal.printError(errorMessage(error));
|
|
1102
|
-
process.exitCode = 1;
|
|
1103
|
-
}
|
|
1180
|
+
await runCommandAction((terminal) => runList(options, { terminal }));
|
|
1104
1181
|
});
|
|
1105
1182
|
|
|
1106
1183
|
// src/cli/replay.ts
|
|
1107
1184
|
import { Command as Command6 } from "commander";
|
|
1108
|
-
var DEFAULT_REPLAY_TARGET_URL = "http://localhost:3000/webhook";
|
|
1109
1185
|
function parseTargetUrl(targetUrl) {
|
|
1110
1186
|
const raw = targetUrl ?? DEFAULT_REPLAY_TARGET_URL;
|
|
1111
1187
|
try {
|
|
@@ -1117,8 +1193,7 @@ function parseTargetUrl(targetUrl) {
|
|
|
1117
1193
|
async function runReplay(eventId, flags, deps = {}) {
|
|
1118
1194
|
const targetUrl = parseTargetUrl(flags.to);
|
|
1119
1195
|
const terminal = deps.terminal ?? createTerminal();
|
|
1120
|
-
|
|
1121
|
-
try {
|
|
1196
|
+
return withDefaultStorage(async (storage) => {
|
|
1122
1197
|
const event = storage.load(eventId);
|
|
1123
1198
|
if (!event) {
|
|
1124
1199
|
throw new Error(`Event "${eventId}" not found.`);
|
|
@@ -1138,9 +1213,7 @@ async function runReplay(eventId, flags, deps = {}) {
|
|
|
1138
1213
|
} catch (error) {
|
|
1139
1214
|
throw new Error(`Failed to replay "${eventId}" to ${targetUrl}: ${errorMessage(error)}`);
|
|
1140
1215
|
}
|
|
1141
|
-
}
|
|
1142
|
-
storage.close();
|
|
1143
|
-
}
|
|
1216
|
+
});
|
|
1144
1217
|
}
|
|
1145
1218
|
var replayCommand = new Command6("replay").description("Replay a stored webhook event").argument("<event-id>", "ID of the event to replay").option("--to <url>", "Target URL to send the event to", DEFAULT_REPLAY_TARGET_URL).option("--json", "Output as JSON").addHelpText(
|
|
1146
1219
|
"after",
|
|
@@ -1150,18 +1223,12 @@ Examples:
|
|
|
1150
1223
|
hooklens replay evt_abc123 --to http://localhost:8080/hook
|
|
1151
1224
|
hooklens replay evt_abc123 --json`
|
|
1152
1225
|
).action(async (eventId, options) => {
|
|
1153
|
-
|
|
1154
|
-
try {
|
|
1155
|
-
await runReplay(eventId, options, { terminal });
|
|
1156
|
-
} catch (error) {
|
|
1157
|
-
terminal.printError(errorMessage(error));
|
|
1158
|
-
process.exitCode = 1;
|
|
1159
|
-
}
|
|
1226
|
+
await runCommandAction((terminal) => runReplay(eventId, options, { terminal }));
|
|
1160
1227
|
});
|
|
1161
1228
|
|
|
1162
1229
|
// src/cli/index.ts
|
|
1163
1230
|
var program = new Command7();
|
|
1164
|
-
program.name("hooklens").description(
|
|
1231
|
+
program.name("hooklens").description(package_default.description).version(package_default.version);
|
|
1165
1232
|
program.addCommand(listenCommand);
|
|
1166
1233
|
program.addCommand(listCommand);
|
|
1167
1234
|
program.addCommand(inspectCommand);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/index.ts","../src/errors.ts","../src/cli/clear.ts","../src/storage/index.ts","../src/types.ts","../src/ui/terminal.ts","../src/cli/delete.ts","../src/cli/inspect.ts","../src/cli/json-output.ts","../src/cli/listen.ts","../src/server/index.ts","../src/verify/github.ts","../src/verify/headers.ts","../src/verify/stripe.ts","../src/cli/list.ts","../src/cli/replay.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { clearCommand } from './clear.js'\nimport { deleteCommand } from './delete.js'\nimport { inspectCommand } from './inspect.js'\nimport { listenCommand } from './listen.js'\nimport { listCommand } from './list.js'\nimport { replayCommand } from './replay.js'\n\nconst program = new Command()\n\nprogram\n .name('hooklens')\n .description('Inspect, verify, and replay webhooks from your terminal')\n .version('0.1.0')\n\nprogram.addCommand(listenCommand)\nprogram.addCommand(listCommand)\nprogram.addCommand(inspectCommand)\nprogram.addCommand(replayCommand)\nprogram.addCommand(deleteCommand)\nprogram.addCommand(clearCommand)\n\ntry {\n await program.parseAsync(process.argv)\n} catch (error) {\n console.error(errorMessage(error))\n process.exitCode = 1\n}\n","export function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\nexport function errorMessage(value: unknown): string {\n return value instanceof Error ? value.message : String(value)\n}\n","import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\n\nexport interface ClearFlags {\n yes?: boolean\n}\n\nexport interface ClearDeps {\n terminal?: TerminalUI\n confirm?: () => Promise<boolean>\n}\n\nasync function defaultConfirm(): Promise<boolean> {\n const { createInterface } = await import('node:readline')\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n\n return new Promise((resolve) => {\n rl.question('Delete all stored events? [y/N] ', (answer) => {\n rl.close()\n resolve(answer.trim().toLowerCase() === 'y')\n })\n })\n}\n\nexport async function runClear(flags: ClearFlags, deps: ClearDeps = {}): Promise<void> {\n const terminal = deps.terminal ?? createTerminal()\n\n if (!flags.yes) {\n const confirm = deps.confirm ?? defaultConfirm\n const confirmed = await confirm()\n\n if (!confirmed) {\n return\n }\n }\n\n const storage = createStorage(defaultDbPath())\n\n try {\n const count = storage.clear()\n terminal.printCleared(count)\n } finally {\n storage.close()\n }\n}\n\nexport const clearCommand = new Command('clear')\n .description('Delete all stored webhook events')\n .option('--yes', 'Skip confirmation prompt')\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens clear --yes\n hooklens clear`,\n )\n .action(async (options) => {\n const terminal = createTerminal()\n\n try {\n await runClear(options, { terminal })\n } catch (error) {\n terminal.printError(errorMessage(error))\n process.exitCode = 1\n }\n })\n","import os from 'node:os'\nimport fs from 'node:fs'\nimport { createRequire } from 'node:module'\nimport path from 'node:path'\nimport type * as sqlite from 'node:sqlite'\nimport {\n eventRowSchema,\n verificationResultSchema,\n webhookEventSchema,\n type EventRow,\n type WebhookEvent,\n} from '../types.js'\n\nconst require = createRequire(import.meta.url)\n\n// tsup/esbuild currently rewrites a static `node:sqlite` import to `sqlite`,\n// which breaks the built CLI. Resolve it at runtime so the core module specifier\n// survives the bundle unchanged.\nconst { DatabaseSync } = require('node:' + 'sqlite') as typeof sqlite\n\nexport function defaultDbPath(): string {\n return path.join(os.homedir(), '.hooklens', 'events.db')\n}\n\nfunction rowToEvent(row: EventRow): WebhookEvent {\n const verification = row.verification\n ? verificationResultSchema.parse(JSON.parse(row.verification))\n : null\n return webhookEventSchema.parse({\n id: row.id,\n timestamp: row.timestamp,\n method: row.method,\n path: row.path,\n headers: JSON.parse(row.headers),\n body: row.body,\n verification,\n })\n}\n\nexport function createStorage(dbPath: string) {\n fs.mkdirSync(path.dirname(dbPath), { recursive: true })\n const db = new DatabaseSync(dbPath)\n\n db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n timestamp TEXT NOT NULL,\n method TEXT NOT NULL,\n path TEXT NOT NULL,\n headers TEXT NOT NULL,\n body TEXT NOT NULL,\n verification TEXT\n )\n `)\n\n // Add verification column to existing databases that lack it.\n try {\n db.exec(`ALTER TABLE events ADD COLUMN verification TEXT`)\n } catch (error) {\n // Re-throw unless the column already exists.\n if (!(error instanceof Error && /duplicate column/i.test(error.message))) {\n throw error\n }\n }\n\n const insertStmt = db.prepare(\n `INSERT OR REPLACE INTO events (id, timestamp, method, path, headers, body, verification)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n\n const getStmt = db.prepare(`SELECT * FROM events WHERE id = ?`)\n const listAllStmt = db.prepare(`SELECT * FROM events ORDER BY timestamp DESC`)\n const listLimitedStmt = db.prepare(`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`)\n const deleteStmt = db.prepare(`DELETE FROM events WHERE id = ?`)\n const clearStmt = db.prepare(`DELETE FROM events`)\n\n return {\n save(event: WebhookEvent): void {\n insertStmt.run(\n event.id,\n event.timestamp,\n event.method,\n event.path,\n JSON.stringify(event.headers),\n event.body,\n event.verification ? JSON.stringify(event.verification) : null,\n )\n },\n\n load(id: string): WebhookEvent | null {\n const raw = getStmt.get(id)\n if (!raw) return null\n const row = eventRowSchema.parse(raw)\n return rowToEvent(row)\n },\n\n list(limit?: number): WebhookEvent[] {\n if (limit !== undefined && (!Number.isInteger(limit) || limit <= 0)) {\n throw new Error(`Invalid limit: must be a positive integer, got ${limit}`)\n }\n const raw = limit === undefined ? listAllStmt.all() : listLimitedStmt.all(limit)\n const rows = raw.map((r) => eventRowSchema.parse(r))\n return rows.map(rowToEvent)\n },\n\n delete(id: string): boolean {\n const result = deleteStmt.run(id)\n return result.changes > 0\n },\n\n clear(): number {\n const result = clearStmt.run()\n return Number(result.changes)\n },\n\n close(): void {\n db.close()\n },\n }\n}\n","import { z } from 'zod'\n\nexport const verificationResultSchema = z.object({\n valid: z.boolean(),\n provider: z.string(),\n message: z.string(),\n code: z.enum([\n 'valid',\n 'missing_header',\n 'malformed_header',\n 'expired_timestamp',\n 'signature_mismatch',\n 'body_mutated',\n ]),\n})\n\nexport type VerificationResult = z.infer<typeof verificationResultSchema>\n\n// A webhook event as it lives in memory and is exposed to the rest of the app.\n// Headers are a parsed object here -- on disk they're stored as a JSON string.\nexport const webhookEventSchema = z.object({\n id: z.string(),\n timestamp: z.string(),\n method: z.string(),\n path: z.string(),\n headers: z.record(z.string(), z.string()),\n body: z.string(),\n verification: verificationResultSchema.nullable().optional(),\n})\n\nexport type WebhookEvent = z.infer<typeof webhookEventSchema>\n\n// The shape of a row read directly from the SQLite events table.\n// headers is a JSON string at this layer; rowToEvent parses it.\nexport const eventRowSchema = z.object({\n id: z.string(),\n timestamp: z.string(),\n method: z.string(),\n path: z.string(),\n headers: z.string(),\n body: z.string(),\n verification: z.string().nullable().optional(),\n})\n\nexport type EventRow = z.infer<typeof eventRowSchema>\n\nexport const replayResultSchema = z.object({\n status: z.number().int(),\n body: z.string(),\n})\n\nexport type ReplayResult = z.infer<typeof replayResultSchema>\n\n/** Provider signature verifier. See CONTRIBUTING.md → Adding a provider. */\nexport interface Verifier {\n readonly provider: string\n verify(event: { headers: Record<string, string>; body: string }): VerificationResult\n}\n","import chalk from 'chalk'\nimport type { ReplayResult, VerificationResult, WebhookEvent } from '../types.js'\n\nexport interface ListenStartedInfo {\n port: number\n dbPath: string\n verifier?: string\n forwardTo?: string\n}\n\nexport interface TerminalUI {\n printListenStarted(info: ListenStartedInfo): void\n printEventCaptured(event: WebhookEvent, result: VerificationResult | null): void\n printForwardError(eventId: string, reason: string): void\n printForwardRetry(eventId: string, attempt: number, maxRetries: number, reason: string): void\n printEventList(events: WebhookEvent[]): void\n printEventDetail(event: WebhookEvent): void\n printReplayResult(result: ReplayResult): void\n printDeleted(eventId: string): void\n printCleared(count: number): void\n printListenStopped(): void\n printError(message: string): void\n}\n\nfunction writeLine(stream: NodeJS.WriteStream, line: string): void {\n stream.write(`${line}\\n`)\n}\n\nfunction verificationLabel(result: VerificationResult | null): string {\n if (!result) return chalk.cyan('RECV')\n return result.valid ? chalk.green('PASS') : chalk.red('FAIL')\n}\n\nexport function createTerminal(\n stdout: NodeJS.WriteStream = process.stdout,\n stderr: NodeJS.WriteStream = process.stderr,\n): TerminalUI {\n return {\n printListenStarted(info) {\n writeLine(\n stdout,\n `${chalk.bold('Listening on')} ${chalk.cyan(`http://127.0.0.1:${info.port}`)}`,\n )\n\n writeLine(stdout, `Verifier: ${info.verifier ?? 'none'}`)\n writeLine(stdout, `Forwarding to: ${info.forwardTo ?? 'disabled'}`)\n writeLine(stdout, `Storage: ${info.dbPath}`)\n },\n\n printEventCaptured(event, result) {\n const label = verificationLabel(result)\n const summary = `${label} ${chalk.bold(event.id)} ${event.method} ${event.path}`\n\n if (!result) {\n writeLine(stdout, summary)\n return\n }\n\n writeLine(stdout, `${summary} ${result.message}`)\n },\n\n printForwardError(eventId, reason) {\n writeLine(stdout, `${chalk.red('FWD')} ${chalk.bold(eventId)} ${reason}`)\n },\n\n printForwardRetry(eventId, attempt, maxRetries, reason) {\n writeLine(\n stdout,\n `${chalk.yellow('RETRY')} ${chalk.bold(eventId)} attempt ${attempt}/${maxRetries} ${reason}`,\n )\n },\n\n printEventList(events) {\n if (!events.length) {\n writeLine(stdout, chalk.dim('No stored events.'))\n return\n }\n\n for (const event of events) {\n const row = `${chalk.dim(event.timestamp)} ${chalk.cyan(event.method)} ${chalk.bold(event.id)} ${event.path}`\n writeLine(stdout, row)\n }\n },\n\n printEventDetail(event) {\n writeLine(stdout, `${chalk.bold('Event:')} ${event.id}`)\n writeLine(stdout, `${chalk.bold('Time:')} ${event.timestamp}`)\n writeLine(stdout, `${chalk.bold('Method:')} ${event.method}`)\n writeLine(stdout, `${chalk.bold('Path:')} ${event.path}`)\n\n if (event.verification) {\n const v = event.verification\n const label = v.valid ? chalk.green('PASS') : chalk.red('FAIL')\n writeLine(stdout, '')\n writeLine(stdout, chalk.bold('Verification:'))\n writeLine(stdout, ` Result: ${label}`)\n writeLine(stdout, ` Provider: ${v.provider}`)\n writeLine(stdout, ` Message: ${v.message}`)\n }\n\n writeLine(stdout, '')\n writeLine(stdout, chalk.bold('Headers:'))\n\n for (const [key, value] of Object.entries(event.headers)) {\n writeLine(stdout, ` ${key}: ${value}`)\n }\n\n writeLine(stdout, '')\n writeLine(stdout, chalk.bold('Body:'))\n\n let bodyText: string\n try {\n bodyText = JSON.stringify(JSON.parse(event.body), null, 2)\n } catch {\n bodyText = event.body\n }\n\n for (const line of bodyText.split('\\n')) {\n writeLine(stdout, ` ${line}`)\n }\n },\n\n printReplayResult(result) {\n const summary = `${chalk.bold('Replay response:')} ${chalk.cyan(String(result.status))}`\n\n if (!result.body) {\n writeLine(stdout, summary)\n return\n }\n\n writeLine(stdout, `${summary} ${result.body}`)\n },\n\n printDeleted(eventId) {\n writeLine(stdout, `Deleted ${chalk.bold(eventId)}`)\n },\n\n printCleared(count) {\n writeLine(stdout, `Cleared ${chalk.bold(String(count))} events`)\n },\n\n printListenStopped() {\n writeLine(stdout, chalk.dim('Stopped listening.'))\n },\n\n printError(message) {\n writeLine(stderr, chalk.red(message))\n },\n }\n}\n","import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\n\nexport interface DeleteDeps {\n terminal?: TerminalUI\n}\n\nexport async function runDelete(eventId: string, deps: DeleteDeps = {}): Promise<void> {\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(defaultDbPath())\n\n try {\n const deleted = storage.delete(eventId)\n\n if (!deleted) {\n throw new Error(`Event \"${eventId}\" not found.`)\n }\n\n terminal.printDeleted(eventId)\n } finally {\n storage.close()\n }\n}\n\nexport const deleteCommand = new Command('delete')\n .description('Delete a stored webhook event')\n .argument('<event-id>', 'ID of the event to delete')\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens delete evt_abc123`,\n )\n .action(async (eventId) => {\n const terminal = createTerminal()\n\n try {\n await runDelete(eventId, { terminal })\n } catch (error) {\n terminal.printError(errorMessage(error))\n process.exitCode = 1\n }\n })\n","import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\nimport { writeJsonLine } from './json-output.js'\n\nexport interface InspectFlags {\n json?: boolean\n}\n\nexport interface InspectDeps {\n terminal?: TerminalUI\n stdout?: NodeJS.WritableStream\n}\n\nexport async function runInspect(\n eventId: string,\n flags: InspectFlags,\n deps: InspectDeps = {},\n): Promise<void> {\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(defaultDbPath())\n\n try {\n const event = storage.load(eventId)\n\n if (!event) {\n throw new Error(`Event \"${eventId}\" not found.`)\n }\n\n if (flags.json) {\n const out = deps.stdout ?? process.stdout\n writeJsonLine(out, event)\n } else {\n terminal.printEventDetail(event)\n }\n } finally {\n storage.close()\n }\n}\n\nexport const inspectCommand = new Command('inspect')\n .description('View full details of a stored webhook event')\n .argument('<event-id>', 'ID of the event to inspect')\n .option('--json', 'Output as JSON')\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens inspect evt_abc123\n hooklens inspect evt_abc123 --json`,\n )\n .action(async (eventId, options) => {\n const terminal = createTerminal()\n\n try {\n await runInspect(eventId, options, { terminal })\n } catch (error) {\n terminal.printError(errorMessage(error))\n process.exitCode = 1\n }\n })\n","export function writeJsonLine(stdout: NodeJS.WritableStream, data: unknown): void {\n const json = JSON.stringify(data)\n if (json === undefined) {\n throw new Error(\n 'Cannot serialize value to JSON – received a non-serializable type (undefined, function, or symbol)',\n )\n }\n stdout.write(json + '\\n')\n}\n","import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { createServer, type Server } from '../server/index.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport type { VerificationResult, Verifier, WebhookEvent } from '../types.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\nimport { createGitHubVerifier } from '../verify/github.js'\nimport { createStripeVerifier } from '../verify/stripe.js'\n\nexport interface ListenFlags {\n port?: string | number\n verify?: string\n secret?: string\n forwardTo?: string\n retry?: string | number\n}\n\nexport interface SignalBus {\n on(event: 'SIGINT' | 'SIGTERM', listener: () => void): void\n off(event: 'SIGINT' | 'SIGTERM', listener: () => void): void\n}\n\nexport interface ListenDeps {\n signals?: SignalBus\n terminal?: TerminalUI\n}\n\nfunction parsePort(port: string | number | undefined): number {\n const raw = port\n const parsed = typeof raw === 'number' ? raw : Number(raw)\n\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65_535) {\n throw new Error(`Invalid port \"${raw}\". Expected an integer between 0 and 65535.`)\n }\n\n return parsed\n}\n\nfunction parseRetryCount(retry: string | number | undefined): number {\n if (retry === undefined) return 0\n const parsed = typeof retry === 'number' ? retry : Number(retry)\n\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 10) {\n throw new Error(`Invalid retry count \"${retry}\". Expected an integer between 0 and 10.`)\n }\n\n return parsed\n}\n\n/** Maps --verify flags to a Verifier. See CONTRIBUTING.md → Adding a provider. */\nexport function buildVerifier(flags: ListenFlags): Verifier | undefined {\n if (!flags.verify) return undefined\n\n switch (flags.verify) {\n case 'stripe': {\n if (!flags.secret) {\n throw new Error('--secret is required when --verify stripe is set')\n }\n return createStripeVerifier({ secret: flags.secret })\n }\n case 'github': {\n if (!flags.secret) {\n throw new Error('--secret is required when --verify github is set')\n }\n return createGitHubVerifier({ secret: flags.secret })\n }\n default:\n throw new Error(`Unknown --verify provider \"${flags.verify}\". Supported: stripe, github`)\n }\n}\n\nasync function stopServer(server: Server | null): Promise<void> {\n if (!server) return\n await server.stop()\n}\n\nfunction printEventCapturedBestEffort(\n terminal: TerminalUI,\n event: WebhookEvent,\n result: VerificationResult | null,\n): void {\n try {\n terminal.printEventCaptured(event, result)\n } catch (error) {\n console.error(`Failed to print captured event: ${errorMessage(error)}`)\n }\n}\n\nexport async function runListen(flags: ListenFlags, deps: ListenDeps = {}): Promise<void> {\n const port = parsePort(flags.port)\n const retryCount = parseRetryCount(flags.retry)\n const verifier = buildVerifier(flags)\n const dbPath = defaultDbPath()\n const signals = deps.signals ?? process\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(dbPath)\n\n let server: Server | null = null\n let cleanedUp = false\n let listenersAttached = false\n\n const cleanup = async (printStopped: boolean): Promise<void> => {\n if (cleanedUp) return\n cleanedUp = true\n if (listenersAttached) {\n signals.off('SIGINT', onSignal)\n signals.off('SIGTERM', onSignal)\n listenersAttached = false\n }\n\n let stopError: unknown = null\n\n try {\n await stopServer(server)\n } catch (error) {\n stopError = error\n } finally {\n storage.close()\n }\n\n if (stopError) {\n throw stopError\n }\n\n if (printStopped) {\n terminal.printListenStopped()\n }\n }\n\n let settle: (() => void) | null = null\n let fail: ((error: unknown) => void) | null = null\n const shutdown = new Promise<void>((resolve, reject) => {\n settle = resolve\n fail = reject\n })\n\n const onSignal = () => {\n void cleanup(true).then(\n () => settle?.(),\n (error) => fail?.(error),\n )\n }\n\n try {\n server = createServer({\n port,\n storage,\n verifier,\n forwardTo: flags.forwardTo,\n retryCount,\n onEvent: (event, result) => printEventCapturedBestEffort(terminal, event, result),\n onForwardError: (event, error) => terminal.printForwardError(event.id, error.message),\n onForwardRetry: (event, attempt, maxRetries, error) =>\n terminal.printForwardRetry(event.id, attempt, maxRetries, error.message),\n })\n\n signals.on('SIGINT', onSignal)\n signals.on('SIGTERM', onSignal)\n listenersAttached = true\n\n await server.start()\n\n if (cleanedUp) {\n return await shutdown\n }\n\n terminal.printListenStarted({\n port: server.port,\n dbPath,\n verifier: verifier?.provider,\n forwardTo: flags.forwardTo,\n })\n\n return await shutdown\n } catch (error) {\n if (cleanedUp) {\n return await shutdown\n }\n\n await cleanup(false)\n throw error\n }\n}\n\nexport const listenCommand = new Command('listen')\n .description('Start receiving webhooks')\n .option('-p, --port <port>', 'Port to listen on', '4400')\n .option('--verify <provider>', 'Verify signatures (stripe, github)')\n .option('--secret <secret>', 'Webhook signing secret')\n .option('--forward-to <url>', 'Forward received webhooks to this URL')\n .option('--retry <count>', 'Retry failed forwards with exponential backoff', '0')\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens listen\n hooklens listen -p 8080 --forward-to http://localhost:3000/webhook\n hooklens listen --verify stripe --secret whsec_xxx\n hooklens listen --verify github --secret ghsecret_xxx\n hooklens listen --forward-to http://localhost:3000/webhook --retry 3`,\n )\n .action(async (options) => {\n const terminal = createTerminal()\n\n try {\n await runListen(options, { terminal })\n } catch (error) {\n terminal.printError(errorMessage(error))\n\n process.exitCode = 1\n }\n })\n","import http from 'node:http'\nimport crypto from 'node:crypto'\nimport { errorMessage, toError } from '../errors.js'\nimport type { ReplayResult, VerificationResult, Verifier, WebhookEvent } from '../types.js'\nimport type { createStorage } from '../storage/index.js'\n\ntype Storage = ReturnType<typeof createStorage>\n\nexport interface ServerOptions {\n port: number\n storage: Storage\n verifier?: Verifier\n forwardTo?: string\n forwardTimeoutMs?: number\n retryCount?: number\n retryBaseDelayMs?: number\n maxBodyBytes?: number\n maxForwardResponseBytes?: number\n onEvent?: (event: WebhookEvent, result: VerificationResult | null) => void\n onForwardError?: (event: WebhookEvent, error: Error) => void\n onForwardRetry?: (event: WebhookEvent, attempt: number, maxRetries: number, error: Error) => void\n}\n\nexport interface Server {\n readonly port: number\n start(): Promise<void>\n stop(): Promise<void>\n}\n\n// Headers we strip before forwarding. This is the RFC 7230 section 6.1\n// hop-by-hop list plus host (fetch sets this from the destination URL) and\n// content-length (fetch recomputes this from the body).\nconst FORWARD_STRIP = new Set([\n 'connection',\n 'keep-alive',\n 'proxy-authenticate',\n 'proxy-authorization',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n 'host',\n 'content-length',\n])\n\nconst DEFAULT_MAX_BODY_BYTES = 1024 * 1024\nconst DEFAULT_FORWARD_TIMEOUT_MS = 5000\nconst DEFAULT_RETRY_BASE_DELAY_MS = 100\nconst RETRY_BACKOFF_MULTIPLIER = 4\n\nfunction forwardedStripSet(headers: Record<string, string>): Set<string> {\n const strip = new Set(FORWARD_STRIP)\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() !== 'connection') continue\n for (const token of value.split(/[,\\s]+/)) {\n const name = token.trim().toLowerCase()\n if (name) strip.add(name)\n }\n }\n return strip\n}\n\nfunction generateEventId(): string {\n return `evt_${crypto.randomBytes(12).toString('base64url')}`\n}\n\nclass PayloadTooLargeError extends Error {\n constructor(readonly maxBytes: number) {\n super(`payload too large: max ${maxBytes} bytes`)\n this.name = 'PayloadTooLargeError'\n }\n}\n\nfunction isPayloadTooLargeError(error: unknown): error is PayloadTooLargeError {\n return error instanceof PayloadTooLargeError\n}\n\nfunction requestSockets(req: http.IncomingMessage): NodeJS.EventEmitter[] {\n const sockets = new Set<NodeJS.EventEmitter>()\n sockets.add(req.socket)\n const proxiedSocket = (req.socket as typeof req.socket & { proxy?: NodeJS.EventEmitter | null })\n .proxy\n if (proxiedSocket) sockets.add(proxiedSocket)\n return [...sockets]\n}\n\nexport function readBody(req: http.IncomingMessage, maxBytes: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let totalBytes = 0\n let settled = false\n const sockets = requestSockets(req)\n\n const cleanup = () => {\n req.off('data', onData)\n req.off('end', onEnd)\n req.off('error', onError)\n for (const socket of sockets) {\n socket.off('close', onSocketClose)\n socket.off('error', onSocketError)\n }\n }\n\n const rejectOnce = (error: Error) => {\n if (settled) return\n settled = true\n cleanup()\n reject(error)\n }\n\n const resolveOnce = (body: string) => {\n if (settled) return\n settled = true\n cleanup()\n resolve(body)\n }\n\n const onData = (chunk: Buffer) => {\n totalBytes += chunk.length\n if (totalBytes > maxBytes) {\n req.resume()\n rejectOnce(new PayloadTooLargeError(maxBytes))\n return\n }\n chunks.push(chunk)\n }\n\n const onEnd = () => resolveOnce(Buffer.concat(chunks, totalBytes).toString('utf8'))\n const onError = (error: Error) => rejectOnce(error)\n const onSocketClose = () => rejectOnce(new Error('socket closed during request body'))\n const onSocketError = (error: Error) => rejectOnce(error)\n\n req.on('data', onData)\n req.on('end', onEnd)\n req.on('error', onError)\n for (const socket of sockets) {\n socket.on('close', onSocketClose)\n socket.on('error', onSocketError)\n }\n })\n}\n\nfunction headersToRecord(headers: http.IncomingHttpHeaders): Record<string, string> {\n const out: Record<string, string> = {}\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue\n out[key] = Array.isArray(value) ? value.join(', ') : value\n }\n return out\n}\n\nexport function headersForForwarding(headers: Record<string, string>): Record<string, string> {\n const out: Record<string, string> = {}\n const strip = forwardedStripSet(headers)\n for (const [key, value] of Object.entries(headers)) {\n if (!strip.has(key.toLowerCase())) out[key] = value\n }\n return out\n}\n\ninterface ParsedEventPath {\n pathname: string\n search: string\n}\n\nfunction forwardPathname(targetPathname: string, incomingPathname: string): string {\n if (targetPathname === '/' || targetPathname === '') return incomingPathname || '/'\n if (incomingPathname === '/' || incomingPathname === '') return targetPathname\n const base = targetPathname.endsWith('/') ? targetPathname.slice(0, -1) : targetPathname\n const incoming = incomingPathname.startsWith('/') ? incomingPathname : `/${incomingPathname}`\n return `${base}${incoming}`\n}\n\nexport function parseEventPath(path: string): ParsedEventPath {\n if (/^[A-Za-z][A-Za-z\\d+.-]*:/.test(path)) {\n const parsed = new URL(path)\n return { pathname: parsed.pathname, search: parsed.search }\n }\n\n const queryIndex = path.indexOf('?')\n if (queryIndex === -1) {\n return { pathname: path, search: '' }\n }\n\n return {\n pathname: path.slice(0, queryIndex),\n search: path.slice(queryIndex),\n }\n}\n\nfunction mergeForwardSearch(targetSearch: string, incomingSearch: string): string {\n const merged = new URLSearchParams(incomingSearch)\n const trusted = new URLSearchParams(targetSearch)\n\n for (const key of new Set(trusted.keys())) {\n merged.delete(key)\n }\n for (const [key, value] of trusted) {\n merged.append(key, value)\n }\n\n const search = merged.toString()\n return search.length > 0 ? `?${search}` : ''\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === 'AbortError'\n}\n\nasync function readResponseBody(\n response: Response,\n maxBytes: number,\n controller: AbortController,\n): Promise<string> {\n const reader = response.body?.getReader()\n if (!reader) return ''\n\n const chunks: Uint8Array[] = []\n let totalBytes = 0\n\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n totalBytes += value.byteLength\n if (totalBytes > maxBytes) {\n await reader.cancel()\n controller.abort()\n throw new Error(`forward response too large: max ${maxBytes} bytes`)\n }\n chunks.push(value)\n }\n } finally {\n reader.releaseLock()\n }\n\n return Buffer.concat(chunks, totalBytes).toString('utf8')\n}\n\nexport async function forwardEvent(\n targetUrl: string,\n event: WebhookEvent,\n timeoutMs = DEFAULT_FORWARD_TIMEOUT_MS,\n maxResponseBytes = DEFAULT_MAX_BODY_BYTES,\n): Promise<ReplayResult> {\n const target = new URL(targetUrl)\n const destination = new URL(target.href)\n const parsedEventPath = parseEventPath(event.path)\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n\n destination.pathname = forwardPathname(destination.pathname, parsedEventPath.pathname)\n destination.search = mergeForwardSearch(destination.search, parsedEventPath.search)\n\n try {\n const hasBody = event.method !== 'GET' && event.method !== 'HEAD'\n const response = await fetch(destination, {\n method: event.method,\n headers: headersForForwarding(event.headers),\n body: hasBody ? event.body : undefined,\n signal: controller.signal,\n })\n\n return {\n status: response.status,\n body: await readResponseBody(response, maxResponseBytes, controller),\n }\n } catch (error) {\n if (isAbortError(error)) {\n throw new Error(`forward timed out after ${timeoutMs}ms`)\n }\n // fetch() wraps the real error (e.g. ECONNREFUSED) inside error.cause.\n // AggregateError (localhost resolving to both IPv6 and IPv4) has an empty\n // message but a useful code property. Fall back through each layer.\n const cause = error instanceof Error ? error.cause : undefined\n const code = cause instanceof Error ? (cause as NodeJS.ErrnoException).code : undefined\n const message = cause instanceof Error && cause.message ? cause.message : code\n throw new Error(message ?? errorMessage(error))\n } finally {\n clearTimeout(timeout)\n }\n}\n\nfunction cancellableDelay(ms: number, req: http.IncomingMessage): Promise<void> {\n if (ms <= 0) return Promise.resolve()\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms)\n const onAbort = () => {\n clearTimeout(timer)\n resolve()\n }\n req.once('close', onAbort)\n timer.unref()\n })\n}\n\nfunction clampRetryCount(value: number | undefined): number {\n const n = value ?? 0\n if (!Number.isFinite(n) || !Number.isInteger(n)) return 0\n return Math.max(0, Math.min(n, 10))\n}\n\nexport function createServer(opts: ServerOptions): Server {\n let boundPort = opts.port\n let httpServer: http.Server | null = null\n let isStarting = false\n const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES\n const maxForwardResponseBytes = opts.maxForwardResponseBytes ?? DEFAULT_MAX_BODY_BYTES\n const forwardTimeoutMs = opts.forwardTimeoutMs ?? DEFAULT_FORWARD_TIMEOUT_MS\n const retryCount = clampRetryCount(opts.retryCount)\n const retryBaseDelayMs = opts.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS\n\n const handleRequest = async (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ): Promise<void> => {\n const body = await readBody(req, maxBodyBytes)\n\n const event: WebhookEvent = {\n id: generateEventId(),\n timestamp: new Date().toISOString(),\n method: req.method ?? 'GET',\n path: req.url ?? '/',\n headers: headersToRecord(req.headers),\n body,\n }\n\n const verification = opts.verifier?.verify({ headers: event.headers, body: event.body }) ?? null\n event.verification = verification\n\n opts.storage.save(event)\n opts.onEvent?.(event, verification)\n\n if (!opts.forwardTo) {\n res.statusCode = 200\n res.end('ok')\n return\n }\n\n let lastError: Error = new Error('forward failed')\n\n for (let attempt = 0; attempt <= retryCount; attempt++) {\n if (attempt > 0) {\n const delayMs = retryBaseDelayMs * Math.pow(RETRY_BACKOFF_MULTIPLIER, attempt - 1)\n await cancellableDelay(delayMs, req)\n try {\n opts.onForwardRetry?.(event, attempt, retryCount, lastError)\n } catch {\n // Don't let a broken callback affect retries.\n }\n }\n\n try {\n const forwarded = await forwardEvent(\n opts.forwardTo,\n event,\n forwardTimeoutMs,\n maxForwardResponseBytes,\n )\n res.statusCode = forwarded.status\n res.end(forwarded.body)\n return\n } catch (error) {\n lastError = toError(error)\n }\n }\n\n try {\n opts.onForwardError?.(event, lastError)\n } catch {\n // Don't let a broken callback turn a 502 into a 500.\n }\n res.statusCode = 502\n res.end(`bad gateway: ${lastError.message}`)\n }\n\n return {\n get port() {\n return boundPort\n },\n\n async start() {\n if (httpServer || isStarting) {\n throw new Error('server already started')\n }\n\n isStarting = true\n const server = http.createServer((req, res) => {\n handleRequest(req, res).catch((err: unknown) => {\n if (isPayloadTooLargeError(err)) {\n res.statusCode = 413\n res.end(err.message)\n return\n }\n res.statusCode = 500\n res.end(errorMessage(err))\n })\n })\n httpServer = server\n\n try {\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error) => {\n server.off('error', onError)\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n reject(err)\n }\n\n server.once('error', onError)\n server.listen(opts.port, '127.0.0.1', () => {\n server.off('error', onError)\n const addr = server.address()\n if (addr && typeof addr !== 'string') {\n boundPort = addr.port\n }\n isStarting = false\n resolve()\n })\n })\n } catch (err) {\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n throw err\n }\n },\n\n async stop() {\n if (!httpServer) return\n const server = httpServer\n await new Promise<void>((resolve, reject) => {\n server.close((err) => {\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n if (err) {\n reject(err)\n return\n }\n resolve()\n })\n })\n },\n }\n}\n","import crypto from 'node:crypto'\nimport type { VerificationResult, Verifier } from '../types.js'\nimport { getHeaderCaseInsensitive, tryCanonicalForm } from './headers.js'\n\nexport interface VerifyGitHubOptions {\n payload: string\n header: string | null | undefined\n secret: string\n}\n\nconst PROVIDER = 'github'\nconst PREFIX = 'sha256='\nconst SHA256_HEX = /^[0-9a-fA-F]{64}$/\n\nfunction computeHmac(secret: string, payload: string): string {\n return crypto.createHmac('sha256', secret).update(payload).digest('hex')\n}\n\nfunction constantTimeMatch(expected: string, actual: string): boolean {\n if (expected.length !== actual.length) return false\n const expectedBuf = Buffer.from(expected, 'utf8')\n const actualBuf = Buffer.from(actual, 'utf8')\n return crypto.timingSafeEqual(expectedBuf, actualBuf)\n}\n\nfunction success(message: string): VerificationResult {\n return { valid: true, provider: PROVIDER, code: 'valid', message }\n}\n\nfunction failure(\n code: Exclude<VerificationResult['code'], 'valid'>,\n message: string,\n): VerificationResult {\n return { valid: false, provider: PROVIDER, code, message }\n}\n\nexport function verifyGitHubSignature(opts: VerifyGitHubOptions): VerificationResult {\n if (!opts.header) {\n return failure(\n 'missing_header',\n 'x-hub-signature-256 header not found. Is this actually from GitHub?',\n )\n }\n\n if (!opts.header.startsWith(PREFIX)) {\n return failure('malformed_header', 'x-hub-signature-256 header must start with sha256=')\n }\n\n const signature = opts.header.slice(PREFIX.length)\n if (signature.length === 0) {\n return failure('malformed_header', 'x-hub-signature-256 header has no signature after sha256=')\n }\n\n if (!SHA256_HEX.test(signature)) {\n return failure('malformed_header', 'x-hub-signature-256 header has invalid sha256 hex digest')\n }\n\n const normalizedSignature = signature.toLowerCase()\n const expected = computeHmac(opts.secret, opts.payload)\n\n if (constantTimeMatch(expected, normalizedSignature)) {\n return success('Signature verified.')\n }\n\n const canonical = tryCanonicalForm(opts.payload)\n if (canonical !== null) {\n const expectedCanonical = computeHmac(opts.secret, canonical)\n if (constantTimeMatch(expectedCanonical, normalizedSignature)) {\n return failure(\n 'body_mutated',\n 'Signature mismatch with correct secret. Body was likely parsed and re-serialized by your framework.',\n )\n }\n }\n\n return failure(\n 'signature_mismatch',\n 'Signature mismatch. Check your webhook secret matches the GitHub settings.',\n )\n}\n\nexport function createGitHubVerifier(opts: { secret: string }): Verifier {\n return {\n provider: PROVIDER,\n verify: (event) =>\n verifyGitHubSignature({\n payload: event.body,\n header: getHeaderCaseInsensitive(event.headers, 'x-hub-signature-256'),\n secret: opts.secret,\n }),\n }\n}\n","export function getHeaderCaseInsensitive(\n headers: Record<string, string>,\n name: string,\n): string | undefined {\n const expected = name.toLowerCase()\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === expected) return value\n }\n return undefined\n}\n\nexport function tryCanonicalForm(payload: string): string | null {\n try {\n const canonical = JSON.stringify(JSON.parse(payload))\n return canonical === payload ? null : canonical\n } catch {\n return null\n }\n}\n","import crypto from 'node:crypto'\nimport type { VerificationResult, Verifier } from '../types.js'\nimport { getHeaderCaseInsensitive, tryCanonicalForm } from './headers.js'\n\nexport interface VerifyStripeOptions {\n payload: string\n header: string | null | undefined\n secret: string\n tolerance?: number\n now?: () => number\n}\n\nconst DEFAULT_TOLERANCE_SECONDS = 300\nconst PROVIDER = 'stripe'\n\ninterface ParsedHeader {\n timestamp: number\n signatures: string[]\n}\n\nfunction parseHeader(header: string): ParsedHeader | null {\n let timestamp: number | null = null\n const signatures: string[] = []\n\n for (const part of header.split(',')) {\n const eqIdx = part.indexOf('=')\n if (eqIdx === -1) return null\n\n const key = part.slice(0, eqIdx)\n const value = part.slice(eqIdx + 1)\n\n if (key === 't') {\n if (!/^\\d+$/.test(value)) return null\n timestamp = Number(value)\n } else if (key === 'v1') {\n if (value.length === 0) return null\n signatures.push(value)\n }\n }\n\n if (timestamp === null || signatures.length === 0) return null\n return { timestamp, signatures }\n}\n\nfunction computeHmac(secret: string, signedPayload: string): string {\n return crypto.createHmac('sha256', secret).update(signedPayload).digest('hex')\n}\n\nfunction constantTimeMatch(expected: string, candidates: string[]): boolean {\n const expectedBuf = Buffer.from(expected, 'utf8')\n for (const candidate of candidates) {\n if (candidate.length !== expected.length) continue\n const candidateBuf = Buffer.from(candidate, 'utf8')\n if (crypto.timingSafeEqual(expectedBuf, candidateBuf)) return true\n }\n return false\n}\n\nfunction success(message: string): VerificationResult {\n return { valid: true, provider: PROVIDER, code: 'valid', message }\n}\n\nfunction failure(\n code: Exclude<VerificationResult['code'], 'valid'>,\n message: string,\n): VerificationResult {\n return { valid: false, provider: PROVIDER, code, message }\n}\n\nexport function verifyStripeSignature(opts: VerifyStripeOptions): VerificationResult {\n if (!opts.header) {\n return failure(\n 'missing_header',\n 'stripe-signature header not found. Is this actually from Stripe?',\n )\n }\n\n const parsed = parseHeader(opts.header)\n if (!parsed) {\n return failure(\n 'malformed_header',\n 'stripe-signature header is malformed. Expected format: t=timestamp,v1=signature',\n )\n }\n\n const tolerance = opts.tolerance ?? DEFAULT_TOLERANCE_SECONDS\n const nowMs = (opts.now ?? Date.now)()\n const ageSeconds = Math.floor(nowMs / 1000) - parsed.timestamp\n\n if (ageSeconds > tolerance) {\n const minutes = Math.floor(ageSeconds / 60)\n return failure(\n 'expired_timestamp',\n `Timestamp is ${minutes} minutes old. Event expired or your clock is drifting.`,\n )\n }\n\n const signedPayload = `${parsed.timestamp}.${opts.payload}`\n const expected = computeHmac(opts.secret, signedPayload)\n\n if (constantTimeMatch(expected, parsed.signatures)) {\n return success('Signature verified.')\n }\n\n const canonical = tryCanonicalForm(opts.payload)\n if (canonical !== null) {\n const expectedCanonical = computeHmac(opts.secret, `${parsed.timestamp}.${canonical}`)\n if (constantTimeMatch(expectedCanonical, parsed.signatures)) {\n return failure(\n 'body_mutated',\n 'Signature mismatch with correct secret. Body was likely parsed and re-serialized by your framework.',\n )\n }\n }\n\n return failure(\n 'signature_mismatch',\n 'Signature mismatch. Check your webhook secret matches the Stripe dashboard.',\n )\n}\n\nexport interface StripeVerifierOptions {\n secret: string\n tolerance?: number\n}\n\nexport function createStripeVerifier(opts: StripeVerifierOptions): Verifier {\n return {\n provider: PROVIDER,\n verify: (event) =>\n verifyStripeSignature({\n payload: event.body,\n header: getHeaderCaseInsensitive(event.headers, 'stripe-signature'),\n secret: opts.secret,\n tolerance: opts.tolerance,\n }),\n }\n}\n","import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\nimport { writeJsonLine } from './json-output.js'\n\nexport interface ListFlags {\n limit?: string | number\n json?: boolean\n}\n\nexport interface ListDeps {\n terminal?: TerminalUI\n stdout?: NodeJS.WritableStream\n}\n\nfunction parseLimit(limit: string | number | undefined): number {\n const raw = limit\n const parsed = typeof raw === 'number' ? raw : Number(raw)\n\n if (!Number.isInteger(parsed) || parsed <= 0) {\n throw new Error(`Invalid limit \"${raw}\". Expected a positive integer.`)\n }\n\n return parsed\n}\n\nexport async function runList(flags: ListFlags, deps: ListDeps = {}): Promise<void> {\n const limit = parseLimit(flags.limit ?? '20')\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(defaultDbPath())\n\n try {\n const events = storage.list(limit)\n\n if (flags.json) {\n const out = deps.stdout ?? process.stdout\n\n for (const event of events) {\n writeJsonLine(out, {\n id: event.id,\n timestamp: event.timestamp,\n method: event.method,\n path: event.path,\n })\n }\n } else {\n terminal.printEventList(events)\n }\n } finally {\n storage.close()\n }\n}\n\nexport const listCommand = new Command('list')\n .description('Show received webhook events')\n .option('-n, --limit <count>', 'Number of events to show', '20')\n .option('--json', 'Output as newline-delimited JSON')\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens list\n hooklens list -n 5\n hooklens list --json`,\n )\n .action(async (options) => {\n const terminal = createTerminal()\n\n try {\n await runList(options, { terminal })\n } catch (error) {\n terminal.printError(errorMessage(error))\n process.exitCode = 1\n }\n })\n","import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { forwardEvent } from '../server/index.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\nimport { writeJsonLine } from './json-output.js'\n\nconst DEFAULT_REPLAY_TARGET_URL = 'http://localhost:3000/webhook'\n\nexport interface ReplayFlags {\n to?: string\n json?: boolean\n}\n\nexport interface ReplayDeps {\n terminal?: TerminalUI\n stdout?: NodeJS.WritableStream\n}\n\nfunction parseTargetUrl(targetUrl: string | undefined): string {\n const raw = targetUrl ?? DEFAULT_REPLAY_TARGET_URL\n\n try {\n return new URL(raw).href\n } catch {\n throw new Error(`Invalid target URL \"${raw}\".`)\n }\n}\n\nexport async function runReplay(\n eventId: string,\n flags: ReplayFlags,\n deps: ReplayDeps = {},\n): Promise<void> {\n const targetUrl = parseTargetUrl(flags.to)\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(defaultDbPath())\n\n try {\n const event = storage.load(eventId)\n\n if (!event) {\n throw new Error(`Event \"${eventId}\" not found.`)\n }\n\n try {\n const result = await forwardEvent(targetUrl, event)\n\n if (flags.json) {\n const out = deps.stdout ?? process.stdout\n writeJsonLine(out, { status: result.status, body: result.body })\n } else {\n const body = result.body.length <= 200 ? result.body : `${result.body.slice(0, 197)}...`\n terminal.printReplayResult({\n status: result.status,\n body,\n })\n }\n } catch (error) {\n throw new Error(`Failed to replay \"${eventId}\" to ${targetUrl}: ${errorMessage(error)}`)\n }\n } finally {\n storage.close()\n }\n}\n\nexport const replayCommand = new Command('replay')\n .description('Replay a stored webhook event')\n .argument('<event-id>', 'ID of the event to replay')\n .option('--to <url>', 'Target URL to send the event to', DEFAULT_REPLAY_TARGET_URL)\n .option('--json', 'Output as JSON')\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens replay evt_abc123\n hooklens replay evt_abc123 --to http://localhost:8080/hook\n hooklens replay evt_abc123 --json`,\n )\n .action(async (eventId, options) => {\n const terminal = createTerminal()\n\n try {\n await runReplay(eventId, options, { terminal })\n } catch (error) {\n terminal.printError(errorMessage(error))\n process.exitCode = 1\n }\n })\n"],"mappings":"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","names":["Command","require","Command","Command","Command","Command","Command","path","crypto","crypto","crypto","PROVIDER","computeHmac","crypto","constantTimeMatch","success","failure","Command","Command","Command","Command","Command","Command"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli/index.ts","../package.json","../src/errors.ts","../src/cli/clear.ts","../src/ui/terminal.ts","../src/storage/index.ts","../src/types.ts","../src/cli/runtime.ts","../src/cli/delete.ts","../src/cli/inspect.ts","../src/cli/json-output.ts","../src/cli/listen.ts","../src/server/index.ts","../src/verify/github.ts","../src/verify/headers.ts","../src/verify/stripe.ts","../src/cli/defaults.ts","../src/cli/list.ts","../src/cli/replay.ts"],"sourcesContent":["import { Command } from 'commander'\nimport packageJson from '../../package.json'\nimport { errorMessage } from '../errors.js'\nimport { clearCommand } from './clear.js'\nimport { deleteCommand } from './delete.js'\nimport { inspectCommand } from './inspect.js'\nimport { listenCommand } from './listen.js'\nimport { listCommand } from './list.js'\nimport { replayCommand } from './replay.js'\n\nconst program = new Command()\n\nprogram.name('hooklens').description(packageJson.description).version(packageJson.version)\n\nprogram.addCommand(listenCommand)\nprogram.addCommand(listCommand)\nprogram.addCommand(inspectCommand)\nprogram.addCommand(replayCommand)\nprogram.addCommand(deleteCommand)\nprogram.addCommand(clearCommand)\n\ntry {\n await program.parseAsync(process.argv)\n} catch (error) {\n console.error(errorMessage(error))\n process.exitCode = 1\n}\n","{\n \"name\": \"hooklens\",\n \"version\": \"1.0.1\",\n \"description\": \"Debug webhook signature failures locally.\",\n \"type\": \"module\",\n \"bin\": {\n \"hooklens\": \"./dist/index.js\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"docs:build\": \"vitepress build docs\",\n \"docs:dev\": \"vitepress dev docs\",\n \"docs:preview\": \"vitepress preview docs\",\n \"start\": \"node dist/index.js\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:coverage\": \"vitest run --coverage\",\n \"lint\": \"eslint src/ tests/\",\n \"lint:fix\": \"eslint src/ tests/ --fix\",\n \"format\": \"prettier --write \\\"src/**/*.ts\\\" \\\"tests/**/*.ts\\\"\",\n \"format:check\": \"prettier --check \\\"src/**/*.ts\\\" \\\"tests/**/*.ts\\\"\",\n \"typecheck\": \"tsc --noEmit\",\n \"prepublishOnly\": \"npm run build\",\n \"prepare\": \"husky\"\n },\n \"keywords\": [\n \"webhook\",\n \"debug\",\n \"stripe\",\n \"stripe-webhooks\",\n \"github-webhooks\",\n \"signature\",\n \"webhook-signature\",\n \"replay\",\n \"cli\",\n \"developer-tools\"\n ],\n \"author\": \"Ilia Goginashvili\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/Ilia01/hooklens.git\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/Ilia01/hooklens/issues\"\n },\n \"homepage\": \"https://ilia01.github.io/hooklens/\",\n \"engines\": {\n \"node\": \">=24.0.0\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"LICENSE\"\n ],\n \"devDependencies\": {\n \"@octokit/webhooks-methods\": \"^6.0.0\",\n \"@types/node\": \"^22.0.0\",\n \"eslint\": \"^9.0.0\",\n \"husky\": \"^9.1.7\",\n \"lint-staged\": \"^16.4.0\",\n \"prettier\": \"^3.4.0\",\n \"stripe\": \"^22.0.0\",\n \"tsup\": \"^8.0.0\",\n \"typescript\": \"^5.7.0\",\n \"typescript-eslint\": \"^8.0.0\",\n \"vitepress\": \"^1.6.4\",\n \"vitest\": \"^3.0.0\"\n },\n \"dependencies\": {\n \"chalk\": \"^5.4.0\",\n \"commander\": \"^13.0.0\",\n \"zod\": \"^4.3.6\"\n },\n \"lint-staged\": {\n \"*.{ts,tsx}\": [\n \"prettier --write\",\n \"eslint --fix\"\n ],\n \"*.{json,md,yml,yaml}\": [\n \"prettier --write\"\n ]\n }\n}\n","export function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\nexport function errorMessage(value: unknown): string {\n return value instanceof Error ? value.message : String(value)\n}\n","import { Command } from 'commander'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\nimport { runCommandAction, withDefaultStorage } from './runtime.js'\n\nexport interface ClearFlags {\n yes?: boolean\n}\n\nexport interface ClearDeps {\n terminal?: TerminalUI\n confirm?: () => Promise<boolean>\n}\n\nasync function defaultConfirm(): Promise<boolean> {\n const { createInterface } = await import('node:readline')\n const rl = createInterface({ input: process.stdin, output: process.stdout })\n\n return new Promise((resolve) => {\n rl.question('Delete all stored events? [y/N] ', (answer) => {\n rl.close()\n resolve(answer.trim().toLowerCase() === 'y')\n })\n })\n}\n\nexport async function runClear(flags: ClearFlags, deps: ClearDeps = {}): Promise<void> {\n const terminal = deps.terminal ?? createTerminal()\n\n if (!flags.yes) {\n const confirm = deps.confirm ?? defaultConfirm\n const confirmed = await confirm()\n\n if (!confirmed) {\n return\n }\n }\n\n return withDefaultStorage((storage) => {\n const count = storage.clear()\n terminal.printCleared(count)\n })\n}\n\nexport const clearCommand = new Command('clear')\n .description('Delete all stored webhook events')\n .option('--yes', 'Skip confirmation prompt')\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens clear --yes\n hooklens clear`,\n )\n .action(async (options) => {\n await runCommandAction((terminal) => runClear(options, { terminal }))\n })\n","import chalk from 'chalk'\nimport type { ReplayResult, VerificationResult, WebhookEvent } from '../types.js'\n\nexport interface ListenStartedInfo {\n port: number\n dbPath: string\n verifier?: string\n forwardTo?: string\n}\n\nexport interface TerminalUI {\n printListenStarted(info: ListenStartedInfo): void\n printEventCaptured(event: WebhookEvent, result: VerificationResult | null): void\n printForwardError(eventId: string, reason: string): void\n printForwardRetry(eventId: string, attempt: number, maxRetries: number, reason: string): void\n printEventList(events: WebhookEvent[]): void\n printEventDetail(event: WebhookEvent): void\n printReplayResult(result: ReplayResult): void\n printDeleted(eventId: string): void\n printCleared(count: number): void\n printListenStopped(): void\n printError(message: string): void\n}\n\nfunction writeLine(stream: NodeJS.WriteStream, line: string): void {\n stream.write(`${line}\\n`)\n}\n\nfunction verificationLabel(result: VerificationResult | null): string {\n if (!result) return chalk.cyan('RECV')\n return result.valid ? chalk.green('PASS') : chalk.red('FAIL')\n}\n\nexport function createTerminal(\n stdout: NodeJS.WriteStream = process.stdout,\n stderr: NodeJS.WriteStream = process.stderr,\n): TerminalUI {\n return {\n printListenStarted(info) {\n writeLine(\n stdout,\n `${chalk.bold('Listening on')} ${chalk.cyan(`http://127.0.0.1:${info.port}`)}`,\n )\n\n writeLine(stdout, `Verifier: ${info.verifier ?? 'none'}`)\n writeLine(stdout, `Forwarding to: ${info.forwardTo ?? 'disabled'}`)\n writeLine(stdout, `Storage: ${info.dbPath}`)\n },\n\n printEventCaptured(event, result) {\n const label = verificationLabel(result)\n const summary = `${label} ${chalk.bold(event.id)} ${event.method} ${event.path}`\n\n if (!result) {\n writeLine(stdout, summary)\n return\n }\n\n writeLine(stdout, `${summary} ${result.message}`)\n },\n\n printForwardError(eventId, reason) {\n writeLine(stdout, `${chalk.red('FWD')} ${chalk.bold(eventId)} ${reason}`)\n },\n\n printForwardRetry(eventId, attempt, maxRetries, reason) {\n writeLine(\n stdout,\n `${chalk.yellow('RETRY')} ${chalk.bold(eventId)} attempt ${attempt}/${maxRetries} ${reason}`,\n )\n },\n\n printEventList(events) {\n if (!events.length) {\n writeLine(stdout, chalk.dim('No stored events.'))\n return\n }\n\n for (const event of events) {\n const row = `${chalk.dim(event.timestamp)} ${chalk.cyan(event.method)} ${chalk.bold(event.id)} ${event.path}`\n writeLine(stdout, row)\n }\n },\n\n printEventDetail(event) {\n writeLine(stdout, `${chalk.bold('Event:')} ${event.id}`)\n writeLine(stdout, `${chalk.bold('Time:')} ${event.timestamp}`)\n writeLine(stdout, `${chalk.bold('Method:')} ${event.method}`)\n writeLine(stdout, `${chalk.bold('Path:')} ${event.path}`)\n\n if (event.verification) {\n const v = event.verification\n const label = v.valid ? chalk.green('PASS') : chalk.red('FAIL')\n writeLine(stdout, '')\n writeLine(stdout, chalk.bold('Verification:'))\n writeLine(stdout, ` Result: ${label}`)\n writeLine(stdout, ` Provider: ${v.provider}`)\n writeLine(stdout, ` Message: ${v.message}`)\n }\n\n writeLine(stdout, '')\n writeLine(stdout, chalk.bold('Headers:'))\n\n for (const [key, value] of Object.entries(event.headers)) {\n writeLine(stdout, ` ${key}: ${value}`)\n }\n\n writeLine(stdout, '')\n writeLine(stdout, chalk.bold('Body:'))\n\n let bodyText: string\n try {\n bodyText = JSON.stringify(JSON.parse(event.body), null, 2)\n } catch {\n bodyText = event.body\n }\n\n for (const line of bodyText.split('\\n')) {\n writeLine(stdout, ` ${line}`)\n }\n },\n\n printReplayResult(result) {\n const summary = `${chalk.bold('Replay response:')} ${chalk.cyan(String(result.status))}`\n\n if (!result.body) {\n writeLine(stdout, summary)\n return\n }\n\n writeLine(stdout, `${summary} ${result.body}`)\n },\n\n printDeleted(eventId) {\n writeLine(stdout, `Deleted ${chalk.bold(eventId)}`)\n },\n\n printCleared(count) {\n writeLine(stdout, `Cleared ${chalk.bold(String(count))} events`)\n },\n\n printListenStopped() {\n writeLine(stdout, chalk.dim('Stopped listening.'))\n },\n\n printError(message) {\n writeLine(stderr, chalk.red(message))\n },\n }\n}\n","import os from 'node:os'\nimport fs from 'node:fs'\nimport { createRequire } from 'node:module'\nimport path from 'node:path'\nimport type * as sqlite from 'node:sqlite'\nimport {\n eventRowSchema,\n verificationResultSchema,\n webhookEventSchema,\n type EventRow,\n type WebhookEvent,\n} from '../types.js'\n\nconst require = createRequire(import.meta.url)\n\n// tsup/esbuild currently rewrites a static `node:sqlite` import to `sqlite`,\n// which breaks the built CLI. Resolve it at runtime so the core module specifier\n// survives the bundle unchanged.\nconst { DatabaseSync } = require('node:' + 'sqlite') as typeof sqlite\n\nexport function defaultDbPath(): string {\n return path.join(os.homedir(), '.hooklens', 'events.db')\n}\n\nfunction rowToEvent(row: EventRow): WebhookEvent {\n const verification = row.verification\n ? verificationResultSchema.parse(JSON.parse(row.verification))\n : null\n return webhookEventSchema.parse({\n id: row.id,\n timestamp: row.timestamp,\n method: row.method,\n path: row.path,\n headers: JSON.parse(row.headers),\n body: row.body,\n verification,\n })\n}\n\nexport function createStorage(dbPath: string) {\n fs.mkdirSync(path.dirname(dbPath), { recursive: true })\n const db = new DatabaseSync(dbPath)\n\n db.exec(`\n CREATE TABLE IF NOT EXISTS events (\n id TEXT PRIMARY KEY,\n timestamp TEXT NOT NULL,\n method TEXT NOT NULL,\n path TEXT NOT NULL,\n headers TEXT NOT NULL,\n body TEXT NOT NULL,\n verification TEXT\n )\n `)\n\n // Add verification column to existing databases that lack it.\n try {\n db.exec(`ALTER TABLE events ADD COLUMN verification TEXT`)\n } catch (error) {\n // Re-throw unless the column already exists.\n if (!(error instanceof Error && /duplicate column/i.test(error.message))) {\n throw error\n }\n }\n\n const insertStmt = db.prepare(\n `INSERT OR REPLACE INTO events (id, timestamp, method, path, headers, body, verification)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n\n const getStmt = db.prepare(`SELECT * FROM events WHERE id = ?`)\n const listAllStmt = db.prepare(`SELECT * FROM events ORDER BY timestamp DESC`)\n const listLimitedStmt = db.prepare(`SELECT * FROM events ORDER BY timestamp DESC LIMIT ?`)\n const deleteStmt = db.prepare(`DELETE FROM events WHERE id = ?`)\n const clearStmt = db.prepare(`DELETE FROM events`)\n\n return {\n save(event: WebhookEvent): void {\n insertStmt.run(\n event.id,\n event.timestamp,\n event.method,\n event.path,\n JSON.stringify(event.headers),\n event.body,\n event.verification ? JSON.stringify(event.verification) : null,\n )\n },\n\n load(id: string): WebhookEvent | null {\n const raw = getStmt.get(id)\n if (!raw) return null\n const row = eventRowSchema.parse(raw)\n return rowToEvent(row)\n },\n\n list(limit?: number): WebhookEvent[] {\n if (limit !== undefined && (!Number.isInteger(limit) || limit <= 0)) {\n throw new Error(`Invalid limit: must be a positive integer, got ${limit}`)\n }\n const raw = limit === undefined ? listAllStmt.all() : listLimitedStmt.all(limit)\n const rows = raw.map((r) => eventRowSchema.parse(r))\n return rows.map(rowToEvent)\n },\n\n delete(id: string): boolean {\n const result = deleteStmt.run(id)\n return result.changes > 0\n },\n\n clear(): number {\n const result = clearStmt.run()\n return Number(result.changes)\n },\n\n close(): void {\n db.close()\n },\n }\n}\n","import { z } from 'zod'\n\nexport const verificationResultSchema = z.object({\n valid: z.boolean(),\n provider: z.string(),\n message: z.string(),\n code: z.enum([\n 'valid',\n 'missing_header',\n 'malformed_header',\n 'expired_timestamp',\n 'signature_mismatch',\n 'body_mutated',\n ]),\n})\n\nexport type VerificationResult = z.infer<typeof verificationResultSchema>\n\n// A webhook event as it lives in memory and is exposed to the rest of the app.\n// Headers are a parsed object here -- on disk they're stored as a JSON string.\nexport const webhookEventSchema = z.object({\n id: z.string(),\n timestamp: z.string(),\n method: z.string(),\n path: z.string(),\n headers: z.record(z.string(), z.string()),\n body: z.string(),\n verification: verificationResultSchema.nullable().optional(),\n})\n\nexport type WebhookEvent = z.infer<typeof webhookEventSchema>\n\n// The shape of a row read directly from the SQLite events table.\n// headers is a JSON string at this layer; rowToEvent parses it.\nexport const eventRowSchema = z.object({\n id: z.string(),\n timestamp: z.string(),\n method: z.string(),\n path: z.string(),\n headers: z.string(),\n body: z.string(),\n verification: z.string().nullable().optional(),\n})\n\nexport type EventRow = z.infer<typeof eventRowSchema>\n\nexport const replayResultSchema = z.object({\n status: z.number().int(),\n body: z.string(),\n})\n\nexport type ReplayResult = z.infer<typeof replayResultSchema>\n\n/** Provider signature verifier. See CONTRIBUTING.md → Adding a provider. */\nexport interface Verifier {\n readonly provider: string\n verify(event: { headers: Record<string, string>; body: string }): VerificationResult\n}\n","import { errorMessage } from '../errors.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\n\ntype Storage = ReturnType<typeof createStorage>\n\nexport async function withDefaultStorage<T>(run: (storage: Storage) => T | Promise<T>): Promise<T> {\n const storage = createStorage(defaultDbPath())\n\n try {\n return await run(storage)\n } finally {\n try {\n storage.close()\n } catch {\n // Swallow close errors to avoid masking the original error from run().\n }\n }\n}\n\nexport async function runCommandAction(\n run: (terminal: TerminalUI) => Promise<void>,\n): Promise<void> {\n const terminal = createTerminal()\n\n try {\n await run(terminal)\n } catch (error) {\n terminal.printError(errorMessage(error))\n process.exitCode = 1\n }\n}\n","import { Command } from 'commander'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\nimport { runCommandAction, withDefaultStorage } from './runtime.js'\n\nexport interface DeleteDeps {\n terminal?: TerminalUI\n}\n\nexport async function runDelete(eventId: string, deps: DeleteDeps = {}): Promise<void> {\n const terminal = deps.terminal ?? createTerminal()\n\n return withDefaultStorage((storage) => {\n const deleted = storage.delete(eventId)\n\n if (!deleted) {\n throw new Error(`Event \"${eventId}\" not found.`)\n }\n\n terminal.printDeleted(eventId)\n })\n}\n\nexport const deleteCommand = new Command('delete')\n .description('Delete a stored webhook event')\n .argument('<event-id>', 'ID of the event to delete')\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens delete evt_abc123`,\n )\n .action(async (eventId) => {\n await runCommandAction((terminal) => runDelete(eventId, { terminal }))\n })\n","import { Command } from 'commander'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\nimport { writeJsonLine } from './json-output.js'\nimport { runCommandAction, withDefaultStorage } from './runtime.js'\n\nexport interface InspectFlags {\n json?: boolean\n}\n\nexport interface InspectDeps {\n terminal?: TerminalUI\n stdout?: NodeJS.WritableStream\n}\n\nexport async function runInspect(\n eventId: string,\n flags: InspectFlags,\n deps: InspectDeps = {},\n): Promise<void> {\n const terminal = deps.terminal ?? createTerminal()\n\n return withDefaultStorage((storage) => {\n const event = storage.load(eventId)\n\n if (!event) {\n throw new Error(`Event \"${eventId}\" not found.`)\n }\n\n if (flags.json) {\n const out = deps.stdout ?? process.stdout\n writeJsonLine(out, event)\n } else {\n terminal.printEventDetail(event)\n }\n })\n}\n\nexport const inspectCommand = new Command('inspect')\n .description('View full details of a stored webhook event')\n .argument('<event-id>', 'ID of the event to inspect')\n .option('--json', 'Output as JSON')\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens inspect evt_abc123\n hooklens inspect evt_abc123 --json`,\n )\n .action(async (eventId, options) => {\n await runCommandAction((terminal) => runInspect(eventId, options, { terminal }))\n })\n","export function writeJsonLine(stdout: NodeJS.WritableStream, data: unknown): void {\n const json = JSON.stringify(data)\n if (json === undefined) {\n throw new Error(\n 'Cannot serialize value to JSON – received a non-serializable type (undefined, function, or symbol)',\n )\n }\n stdout.write(json + '\\n')\n}\n","import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { createServer, type Server } from '../server/index.js'\nimport { createStorage, defaultDbPath } from '../storage/index.js'\nimport type { VerificationResult, Verifier, WebhookEvent } from '../types.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\nimport { createGitHubVerifier } from '../verify/github.js'\nimport { createStripeVerifier } from '../verify/stripe.js'\nimport { DEFAULT_LISTEN_PORT, DEFAULT_RETRY_COUNT } from './defaults.js'\nimport { runCommandAction } from './runtime.js'\n\nexport interface ListenFlags {\n port?: string | number\n verify?: string\n secret?: string\n forwardTo?: string\n retry?: string | number\n}\n\nexport interface SignalBus {\n on(event: 'SIGINT' | 'SIGTERM', listener: () => void): void\n off(event: 'SIGINT' | 'SIGTERM', listener: () => void): void\n}\n\nexport interface ListenDeps {\n signals?: SignalBus\n terminal?: TerminalUI\n}\n\nfunction parsePort(port: string | number | undefined): number {\n const raw = port\n const parsed = typeof raw === 'number' ? raw : Number(raw)\n\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 65_535) {\n throw new Error(`Invalid port \"${raw}\". Expected an integer between 0 and 65535.`)\n }\n\n return parsed\n}\n\nfunction parseRetryCount(retry: string | number | undefined): number {\n if (retry === undefined) return DEFAULT_RETRY_COUNT\n const parsed = typeof retry === 'number' ? retry : Number(retry)\n\n if (!Number.isInteger(parsed) || parsed < 0 || parsed > 10) {\n throw new Error(`Invalid retry count \"${retry}\". Expected an integer between 0 and 10.`)\n }\n\n return parsed\n}\n\n/** Maps --verify flags to a Verifier. See CONTRIBUTING.md → Adding a provider. */\nexport function buildVerifier(flags: ListenFlags): Verifier | undefined {\n if (!flags.verify) return undefined\n\n switch (flags.verify) {\n case 'stripe': {\n if (!flags.secret) {\n throw new Error('--secret is required when --verify stripe is set')\n }\n return createStripeVerifier({ secret: flags.secret })\n }\n case 'github': {\n if (!flags.secret) {\n throw new Error('--secret is required when --verify github is set')\n }\n return createGitHubVerifier({ secret: flags.secret })\n }\n default:\n throw new Error(`Unknown --verify provider \"${flags.verify}\". Supported: stripe, github`)\n }\n}\n\nasync function stopServer(server: Server | null): Promise<void> {\n if (!server) return\n await server.stop()\n}\n\nfunction printEventCapturedBestEffort(\n terminal: TerminalUI,\n event: WebhookEvent,\n result: VerificationResult | null,\n): void {\n try {\n terminal.printEventCaptured(event, result)\n } catch (error) {\n console.error(`Failed to print captured event: ${errorMessage(error)}`)\n }\n}\n\nexport async function runListen(flags: ListenFlags, deps: ListenDeps = {}): Promise<void> {\n const port = parsePort(flags.port)\n const retryCount = parseRetryCount(flags.retry)\n const verifier = buildVerifier(flags)\n const dbPath = defaultDbPath()\n const signals = deps.signals ?? process\n const terminal = deps.terminal ?? createTerminal()\n const storage = createStorage(dbPath)\n\n let server: Server | null = null\n let cleanedUp = false\n let listenersAttached = false\n\n const cleanup = async (printStopped: boolean): Promise<void> => {\n if (cleanedUp) return\n cleanedUp = true\n if (listenersAttached) {\n signals.off('SIGINT', onSignal)\n signals.off('SIGTERM', onSignal)\n listenersAttached = false\n }\n\n let stopError: unknown = null\n\n try {\n await stopServer(server)\n } catch (error) {\n stopError = error\n } finally {\n storage.close()\n }\n\n if (stopError) {\n throw stopError\n }\n\n if (printStopped) {\n terminal.printListenStopped()\n }\n }\n\n let settle: (() => void) | null = null\n let fail: ((error: unknown) => void) | null = null\n const shutdown = new Promise<void>((resolve, reject) => {\n settle = resolve\n fail = reject\n })\n\n const onSignal = () => {\n void cleanup(true).then(\n () => settle?.(),\n (error) => fail?.(error),\n )\n }\n\n try {\n server = createServer({\n port,\n storage,\n verifier,\n forwardTo: flags.forwardTo,\n retryCount,\n onEvent: (event, result) => printEventCapturedBestEffort(terminal, event, result),\n onForwardError: (event, error) => terminal.printForwardError(event.id, error.message),\n onForwardRetry: (event, attempt, maxRetries, error) =>\n terminal.printForwardRetry(event.id, attempt, maxRetries, error.message),\n })\n\n signals.on('SIGINT', onSignal)\n signals.on('SIGTERM', onSignal)\n listenersAttached = true\n\n await server.start()\n\n if (cleanedUp) {\n return await shutdown\n }\n\n terminal.printListenStarted({\n port: server.port,\n dbPath,\n verifier: verifier?.provider,\n forwardTo: flags.forwardTo,\n })\n\n return await shutdown\n } catch (error) {\n if (cleanedUp) {\n return await shutdown\n }\n\n await cleanup(false)\n throw error\n }\n}\n\nexport const listenCommand = new Command('listen')\n .description('Start receiving webhooks')\n .option('-p, --port <port>', 'Port to listen on', String(DEFAULT_LISTEN_PORT))\n .option('--verify <provider>', 'Verify signatures (stripe, github)')\n .option('--secret <secret>', 'Webhook signing secret')\n .option('--forward-to <url>', 'Forward received webhooks to this URL')\n .option(\n '--retry <count>',\n 'Retry failed forwards with exponential backoff',\n String(DEFAULT_RETRY_COUNT),\n )\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens listen\n hooklens listen -p 8080 --forward-to http://localhost:3000/webhook\n hooklens listen --verify stripe --secret whsec_xxx\n hooklens listen --verify github --secret ghsecret_xxx\n hooklens listen --forward-to http://localhost:3000/webhook --retry 3`,\n )\n .action(async (options) => {\n await runCommandAction((terminal) => runListen(options, { terminal }))\n })\n","import http from 'node:http'\nimport crypto from 'node:crypto'\nimport { errorMessage, toError } from '../errors.js'\nimport type { ReplayResult, VerificationResult, Verifier, WebhookEvent } from '../types.js'\nimport type { createStorage } from '../storage/index.js'\n\ntype Storage = ReturnType<typeof createStorage>\n\nexport interface ServerOptions {\n port: number\n storage: Storage\n verifier?: Verifier\n forwardTo?: string\n forwardTimeoutMs?: number\n retryCount?: number\n retryBaseDelayMs?: number\n maxBodyBytes?: number\n maxForwardResponseBytes?: number\n onEvent?: (event: WebhookEvent, result: VerificationResult | null) => void\n onForwardError?: (event: WebhookEvent, error: Error) => void\n onForwardRetry?: (event: WebhookEvent, attempt: number, maxRetries: number, error: Error) => void\n}\n\nexport interface Server {\n readonly port: number\n start(): Promise<void>\n stop(): Promise<void>\n}\n\n// Headers we strip before forwarding. This is the RFC 7230 section 6.1\n// hop-by-hop list plus host (fetch sets this from the destination URL) and\n// content-length (fetch recomputes this from the body).\nconst FORWARD_STRIP = new Set([\n 'connection',\n 'keep-alive',\n 'proxy-authenticate',\n 'proxy-authorization',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n 'host',\n 'content-length',\n])\n\nconst DEFAULT_MAX_BODY_BYTES = 1024 * 1024\nconst DEFAULT_FORWARD_TIMEOUT_MS = 5000\nconst DEFAULT_RETRY_BASE_DELAY_MS = 100\nconst RETRY_BACKOFF_MULTIPLIER = 4\n\nfunction forwardedStripSet(headers: Record<string, string>): Set<string> {\n const strip = new Set(FORWARD_STRIP)\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() !== 'connection') continue\n for (const token of value.split(/[,\\s]+/)) {\n const name = token.trim().toLowerCase()\n if (name) strip.add(name)\n }\n }\n return strip\n}\n\nfunction generateEventId(): string {\n return `evt_${crypto.randomBytes(12).toString('base64url')}`\n}\n\nclass PayloadTooLargeError extends Error {\n constructor(readonly maxBytes: number) {\n super(`payload too large: max ${maxBytes} bytes`)\n this.name = 'PayloadTooLargeError'\n }\n}\n\nfunction isPayloadTooLargeError(error: unknown): error is PayloadTooLargeError {\n return error instanceof PayloadTooLargeError\n}\n\nfunction requestSockets(req: http.IncomingMessage): NodeJS.EventEmitter[] {\n const sockets = new Set<NodeJS.EventEmitter>()\n sockets.add(req.socket)\n const proxiedSocket = (req.socket as typeof req.socket & { proxy?: NodeJS.EventEmitter | null })\n .proxy\n if (proxiedSocket) sockets.add(proxiedSocket)\n return [...sockets]\n}\n\nexport function readBody(req: http.IncomingMessage, maxBytes: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let totalBytes = 0\n let settled = false\n const sockets = requestSockets(req)\n\n const cleanup = () => {\n req.off('data', onData)\n req.off('end', onEnd)\n req.off('error', onError)\n for (const socket of sockets) {\n socket.off('close', onSocketClose)\n socket.off('error', onSocketError)\n }\n }\n\n const rejectOnce = (error: Error) => {\n if (settled) return\n settled = true\n cleanup()\n reject(error)\n }\n\n const resolveOnce = (body: string) => {\n if (settled) return\n settled = true\n cleanup()\n resolve(body)\n }\n\n const onData = (chunk: Buffer) => {\n totalBytes += chunk.length\n if (totalBytes > maxBytes) {\n req.resume()\n rejectOnce(new PayloadTooLargeError(maxBytes))\n return\n }\n chunks.push(chunk)\n }\n\n const onEnd = () => resolveOnce(Buffer.concat(chunks, totalBytes).toString('utf8'))\n const onError = (error: Error) => rejectOnce(error)\n const onSocketClose = () => rejectOnce(new Error('socket closed during request body'))\n const onSocketError = (error: Error) => rejectOnce(error)\n\n req.on('data', onData)\n req.on('end', onEnd)\n req.on('error', onError)\n for (const socket of sockets) {\n socket.on('close', onSocketClose)\n socket.on('error', onSocketError)\n }\n })\n}\n\nfunction headersToRecord(headers: http.IncomingHttpHeaders): Record<string, string> {\n const out: Record<string, string> = {}\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue\n out[key] = Array.isArray(value) ? value.join(', ') : value\n }\n return out\n}\n\nexport function headersForForwarding(headers: Record<string, string>): Record<string, string> {\n const out: Record<string, string> = {}\n const strip = forwardedStripSet(headers)\n for (const [key, value] of Object.entries(headers)) {\n if (!strip.has(key.toLowerCase())) out[key] = value\n }\n return out\n}\n\ninterface ParsedEventPath {\n pathname: string\n search: string\n}\n\nfunction forwardPathname(targetPathname: string, incomingPathname: string): string {\n if (targetPathname === '/' || targetPathname === '') return incomingPathname || '/'\n if (incomingPathname === '/' || incomingPathname === '') return targetPathname\n const base = targetPathname.endsWith('/') ? targetPathname.slice(0, -1) : targetPathname\n const incoming = incomingPathname.startsWith('/') ? incomingPathname : `/${incomingPathname}`\n return `${base}${incoming}`\n}\n\nexport function parseEventPath(path: string): ParsedEventPath {\n if (/^[A-Za-z][A-Za-z\\d+.-]*:/.test(path)) {\n const parsed = new URL(path)\n return { pathname: parsed.pathname, search: parsed.search }\n }\n\n const queryIndex = path.indexOf('?')\n if (queryIndex === -1) {\n return { pathname: path, search: '' }\n }\n\n return {\n pathname: path.slice(0, queryIndex),\n search: path.slice(queryIndex),\n }\n}\n\nfunction mergeForwardSearch(targetSearch: string, incomingSearch: string): string {\n const merged = new URLSearchParams(incomingSearch)\n const trusted = new URLSearchParams(targetSearch)\n\n for (const key of new Set(trusted.keys())) {\n merged.delete(key)\n }\n for (const [key, value] of trusted) {\n merged.append(key, value)\n }\n\n const search = merged.toString()\n return search.length > 0 ? `?${search}` : ''\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof Error && error.name === 'AbortError'\n}\n\nasync function readResponseBody(\n response: Response,\n maxBytes: number,\n controller: AbortController,\n): Promise<string> {\n const reader = response.body?.getReader()\n if (!reader) return ''\n\n const chunks: Uint8Array[] = []\n let totalBytes = 0\n\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n totalBytes += value.byteLength\n if (totalBytes > maxBytes) {\n await reader.cancel()\n controller.abort()\n throw new Error(`forward response too large: max ${maxBytes} bytes`)\n }\n chunks.push(value)\n }\n } finally {\n reader.releaseLock()\n }\n\n return Buffer.concat(chunks, totalBytes).toString('utf8')\n}\n\nexport async function forwardEvent(\n targetUrl: string,\n event: WebhookEvent,\n timeoutMs = DEFAULT_FORWARD_TIMEOUT_MS,\n maxResponseBytes = DEFAULT_MAX_BODY_BYTES,\n): Promise<ReplayResult> {\n const target = new URL(targetUrl)\n const destination = new URL(target.href)\n const parsedEventPath = parseEventPath(event.path)\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), timeoutMs)\n\n destination.pathname = forwardPathname(destination.pathname, parsedEventPath.pathname)\n destination.search = mergeForwardSearch(destination.search, parsedEventPath.search)\n\n try {\n const hasBody = event.method !== 'GET' && event.method !== 'HEAD'\n const response = await fetch(destination, {\n method: event.method,\n headers: headersForForwarding(event.headers),\n body: hasBody ? event.body : undefined,\n signal: controller.signal,\n })\n\n return {\n status: response.status,\n body: await readResponseBody(response, maxResponseBytes, controller),\n }\n } catch (error) {\n if (isAbortError(error)) {\n throw new Error(`forward timed out after ${timeoutMs}ms`)\n }\n // fetch() wraps the real error (e.g. ECONNREFUSED) inside error.cause.\n // AggregateError (localhost resolving to both IPv6 and IPv4) has an empty\n // message but a useful code property. Fall back through each layer.\n const cause = error instanceof Error ? error.cause : undefined\n const code = cause instanceof Error ? (cause as NodeJS.ErrnoException).code : undefined\n const message = cause instanceof Error && cause.message ? cause.message : code\n throw new Error(message ?? errorMessage(error))\n } finally {\n clearTimeout(timeout)\n }\n}\n\nfunction cancellableDelay(ms: number, req: http.IncomingMessage): Promise<void> {\n if (ms <= 0) return Promise.resolve()\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms)\n const onAbort = () => {\n clearTimeout(timer)\n resolve()\n }\n req.once('close', onAbort)\n timer.unref()\n })\n}\n\nfunction clampRetryCount(value: number | undefined): number {\n const n = value ?? 0\n if (!Number.isFinite(n) || !Number.isInteger(n)) return 0\n return Math.max(0, Math.min(n, 10))\n}\n\nexport function createServer(opts: ServerOptions): Server {\n let boundPort = opts.port\n let httpServer: http.Server | null = null\n let isStarting = false\n const maxBodyBytes = opts.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES\n const maxForwardResponseBytes = opts.maxForwardResponseBytes ?? DEFAULT_MAX_BODY_BYTES\n const forwardTimeoutMs = opts.forwardTimeoutMs ?? DEFAULT_FORWARD_TIMEOUT_MS\n const retryCount = clampRetryCount(opts.retryCount)\n const retryBaseDelayMs = opts.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS\n\n const handleRequest = async (\n req: http.IncomingMessage,\n res: http.ServerResponse,\n ): Promise<void> => {\n const body = await readBody(req, maxBodyBytes)\n\n const event: WebhookEvent = {\n id: generateEventId(),\n timestamp: new Date().toISOString(),\n method: req.method ?? 'GET',\n path: req.url ?? '/',\n headers: headersToRecord(req.headers),\n body,\n }\n\n const verification = opts.verifier?.verify({ headers: event.headers, body: event.body }) ?? null\n event.verification = verification\n\n opts.storage.save(event)\n opts.onEvent?.(event, verification)\n\n if (!opts.forwardTo) {\n res.statusCode = 200\n res.end('ok')\n return\n }\n\n let lastError: Error = new Error('forward failed')\n\n for (let attempt = 0; attempt <= retryCount; attempt++) {\n if (attempt > 0) {\n const delayMs = retryBaseDelayMs * Math.pow(RETRY_BACKOFF_MULTIPLIER, attempt - 1)\n await cancellableDelay(delayMs, req)\n try {\n opts.onForwardRetry?.(event, attempt, retryCount, lastError)\n } catch {\n // Don't let a broken callback affect retries.\n }\n }\n\n try {\n const forwarded = await forwardEvent(\n opts.forwardTo,\n event,\n forwardTimeoutMs,\n maxForwardResponseBytes,\n )\n res.statusCode = forwarded.status\n res.end(forwarded.body)\n return\n } catch (error) {\n lastError = toError(error)\n }\n }\n\n try {\n opts.onForwardError?.(event, lastError)\n } catch {\n // Don't let a broken callback turn a 502 into a 500.\n }\n res.statusCode = 502\n res.end(`bad gateway: ${lastError.message}`)\n }\n\n return {\n get port() {\n return boundPort\n },\n\n async start() {\n if (httpServer || isStarting) {\n throw new Error('server already started')\n }\n\n isStarting = true\n const server = http.createServer((req, res) => {\n handleRequest(req, res).catch((err: unknown) => {\n if (isPayloadTooLargeError(err)) {\n res.statusCode = 413\n res.end(err.message)\n return\n }\n res.statusCode = 500\n res.end(errorMessage(err))\n })\n })\n httpServer = server\n\n try {\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error) => {\n server.off('error', onError)\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n reject(err)\n }\n\n server.once('error', onError)\n server.listen(opts.port, '127.0.0.1', () => {\n server.off('error', onError)\n const addr = server.address()\n if (addr && typeof addr !== 'string') {\n boundPort = addr.port\n }\n isStarting = false\n resolve()\n })\n })\n } catch (err) {\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n throw err\n }\n },\n\n async stop() {\n if (!httpServer) return\n const server = httpServer\n await new Promise<void>((resolve, reject) => {\n server.close((err) => {\n if (httpServer === server) httpServer = null\n boundPort = opts.port\n isStarting = false\n if (err) {\n reject(err)\n return\n }\n resolve()\n })\n })\n },\n }\n}\n","import crypto from 'node:crypto'\nimport type { VerificationResult, Verifier } from '../types.js'\nimport { getHeaderCaseInsensitive, tryCanonicalForm } from './headers.js'\n\nexport interface VerifyGitHubOptions {\n payload: string\n header: string | null | undefined\n secret: string\n}\n\nconst PROVIDER = 'github'\nconst PREFIX = 'sha256='\nconst SHA256_HEX = /^[0-9a-fA-F]{64}$/\n\nfunction computeHmac(secret: string, payload: string): string {\n return crypto.createHmac('sha256', secret).update(payload).digest('hex')\n}\n\nfunction constantTimeMatch(expected: string, actual: string): boolean {\n if (expected.length !== actual.length) return false\n const expectedBuf = Buffer.from(expected, 'utf8')\n const actualBuf = Buffer.from(actual, 'utf8')\n return crypto.timingSafeEqual(expectedBuf, actualBuf)\n}\n\nfunction success(message: string): VerificationResult {\n return { valid: true, provider: PROVIDER, code: 'valid', message }\n}\n\nfunction failure(\n code: Exclude<VerificationResult['code'], 'valid'>,\n message: string,\n): VerificationResult {\n return { valid: false, provider: PROVIDER, code, message }\n}\n\nexport function verifyGitHubSignature(opts: VerifyGitHubOptions): VerificationResult {\n if (!opts.header) {\n return failure(\n 'missing_header',\n 'x-hub-signature-256 header not found. Is this actually from GitHub?',\n )\n }\n\n if (!opts.header.startsWith(PREFIX)) {\n return failure('malformed_header', 'x-hub-signature-256 header must start with sha256=')\n }\n\n const signature = opts.header.slice(PREFIX.length)\n if (signature.length === 0) {\n return failure('malformed_header', 'x-hub-signature-256 header has no signature after sha256=')\n }\n\n if (!SHA256_HEX.test(signature)) {\n return failure('malformed_header', 'x-hub-signature-256 header has invalid sha256 hex digest')\n }\n\n const normalizedSignature = signature.toLowerCase()\n const expected = computeHmac(opts.secret, opts.payload)\n\n if (constantTimeMatch(expected, normalizedSignature)) {\n return success('Signature verified.')\n }\n\n const canonical = tryCanonicalForm(opts.payload)\n if (canonical !== null) {\n const expectedCanonical = computeHmac(opts.secret, canonical)\n if (constantTimeMatch(expectedCanonical, normalizedSignature)) {\n return failure(\n 'body_mutated',\n 'Signature mismatch with correct secret. Body was likely parsed and re-serialized by your framework.',\n )\n }\n }\n\n return failure(\n 'signature_mismatch',\n 'Signature mismatch. Check your webhook secret matches the GitHub settings.',\n )\n}\n\nexport function createGitHubVerifier(opts: { secret: string }): Verifier {\n return {\n provider: PROVIDER,\n verify: (event) =>\n verifyGitHubSignature({\n payload: event.body,\n header: getHeaderCaseInsensitive(event.headers, 'x-hub-signature-256'),\n secret: opts.secret,\n }),\n }\n}\n","export function getHeaderCaseInsensitive(\n headers: Record<string, string>,\n name: string,\n): string | undefined {\n const expected = name.toLowerCase()\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === expected) return value\n }\n return undefined\n}\n\nexport function tryCanonicalForm(payload: string): string | null {\n try {\n const canonical = JSON.stringify(JSON.parse(payload))\n return canonical === payload ? null : canonical\n } catch {\n return null\n }\n}\n","import crypto from 'node:crypto'\nimport type { VerificationResult, Verifier } from '../types.js'\nimport { getHeaderCaseInsensitive, tryCanonicalForm } from './headers.js'\n\nexport interface VerifyStripeOptions {\n payload: string\n header: string | null | undefined\n secret: string\n tolerance?: number\n now?: () => number\n}\n\nconst DEFAULT_TOLERANCE_SECONDS = 300\nconst PROVIDER = 'stripe'\n\ninterface ParsedHeader {\n timestamp: number\n signatures: string[]\n}\n\nfunction parseHeader(header: string): ParsedHeader | null {\n let timestamp: number | null = null\n const signatures: string[] = []\n\n for (const part of header.split(',')) {\n const eqIdx = part.indexOf('=')\n if (eqIdx === -1) return null\n\n const key = part.slice(0, eqIdx)\n const value = part.slice(eqIdx + 1)\n\n if (key === 't') {\n if (!/^\\d+$/.test(value)) return null\n timestamp = Number(value)\n } else if (key === 'v1') {\n if (value.length === 0) return null\n signatures.push(value)\n }\n }\n\n if (timestamp === null || signatures.length === 0) return null\n return { timestamp, signatures }\n}\n\nfunction computeHmac(secret: string, signedPayload: string): string {\n return crypto.createHmac('sha256', secret).update(signedPayload).digest('hex')\n}\n\nfunction constantTimeMatch(expected: string, candidates: string[]): boolean {\n const expectedBuf = Buffer.from(expected, 'utf8')\n for (const candidate of candidates) {\n if (candidate.length !== expected.length) continue\n const candidateBuf = Buffer.from(candidate, 'utf8')\n if (crypto.timingSafeEqual(expectedBuf, candidateBuf)) return true\n }\n return false\n}\n\nfunction success(message: string): VerificationResult {\n return { valid: true, provider: PROVIDER, code: 'valid', message }\n}\n\nfunction failure(\n code: Exclude<VerificationResult['code'], 'valid'>,\n message: string,\n): VerificationResult {\n return { valid: false, provider: PROVIDER, code, message }\n}\n\nexport function verifyStripeSignature(opts: VerifyStripeOptions): VerificationResult {\n if (!opts.header) {\n return failure(\n 'missing_header',\n 'stripe-signature header not found. Is this actually from Stripe?',\n )\n }\n\n const parsed = parseHeader(opts.header)\n if (!parsed) {\n return failure(\n 'malformed_header',\n 'stripe-signature header is malformed. Expected format: t=timestamp,v1=signature',\n )\n }\n\n const tolerance = opts.tolerance ?? DEFAULT_TOLERANCE_SECONDS\n const nowMs = (opts.now ?? Date.now)()\n const ageSeconds = Math.floor(nowMs / 1000) - parsed.timestamp\n\n if (ageSeconds > tolerance) {\n const minutes = Math.floor(ageSeconds / 60)\n return failure(\n 'expired_timestamp',\n `Timestamp is ${minutes} minutes old. Event expired or your clock is drifting.`,\n )\n }\n\n const signedPayload = `${parsed.timestamp}.${opts.payload}`\n const expected = computeHmac(opts.secret, signedPayload)\n\n if (constantTimeMatch(expected, parsed.signatures)) {\n return success('Signature verified.')\n }\n\n const canonical = tryCanonicalForm(opts.payload)\n if (canonical !== null) {\n const expectedCanonical = computeHmac(opts.secret, `${parsed.timestamp}.${canonical}`)\n if (constantTimeMatch(expectedCanonical, parsed.signatures)) {\n return failure(\n 'body_mutated',\n 'Signature mismatch with correct secret. Body was likely parsed and re-serialized by your framework.',\n )\n }\n }\n\n return failure(\n 'signature_mismatch',\n 'Signature mismatch. Check your webhook secret matches the Stripe dashboard.',\n )\n}\n\nexport interface StripeVerifierOptions {\n secret: string\n tolerance?: number\n}\n\nexport function createStripeVerifier(opts: StripeVerifierOptions): Verifier {\n return {\n provider: PROVIDER,\n verify: (event) =>\n verifyStripeSignature({\n payload: event.body,\n header: getHeaderCaseInsensitive(event.headers, 'stripe-signature'),\n secret: opts.secret,\n tolerance: opts.tolerance,\n }),\n }\n}\n","export const DEFAULT_LISTEN_PORT = 4400\nexport const DEFAULT_LIST_LIMIT = 20\nexport const DEFAULT_REPLAY_TARGET_URL = 'http://localhost:3000/webhook'\nexport const DEFAULT_RETRY_COUNT = 0\n","import { Command } from 'commander'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\nimport { DEFAULT_LIST_LIMIT } from './defaults.js'\nimport { writeJsonLine } from './json-output.js'\nimport { runCommandAction, withDefaultStorage } from './runtime.js'\n\nexport interface ListFlags {\n limit?: string | number\n json?: boolean\n}\n\nexport interface ListDeps {\n terminal?: TerminalUI\n stdout?: NodeJS.WritableStream\n}\n\nfunction parseLimit(limit: string | number | undefined): number {\n const raw = limit\n const parsed = typeof raw === 'number' ? raw : Number(raw)\n\n if (!Number.isInteger(parsed) || parsed <= 0) {\n throw new Error(`Invalid limit \"${raw}\". Expected a positive integer.`)\n }\n\n return parsed\n}\n\nexport async function runList(flags: ListFlags, deps: ListDeps = {}): Promise<void> {\n const limit = parseLimit(flags.limit ?? DEFAULT_LIST_LIMIT)\n const terminal = deps.terminal ?? createTerminal()\n\n return withDefaultStorage((storage) => {\n const events = storage.list(limit)\n\n if (flags.json) {\n const out = deps.stdout ?? process.stdout\n\n for (const event of events) {\n writeJsonLine(out, {\n id: event.id,\n timestamp: event.timestamp,\n method: event.method,\n path: event.path,\n })\n }\n } else {\n terminal.printEventList(events)\n }\n })\n}\n\nexport const listCommand = new Command('list')\n .description('Show received webhook events')\n .option('-n, --limit <count>', 'Number of events to show', String(DEFAULT_LIST_LIMIT))\n .option('--json', 'Output as newline-delimited JSON')\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens list\n hooklens list -n 5\n hooklens list --json`,\n )\n .action(async (options) => {\n await runCommandAction((terminal) => runList(options, { terminal }))\n })\n","import { Command } from 'commander'\nimport { errorMessage } from '../errors.js'\nimport { forwardEvent } from '../server/index.js'\nimport { createTerminal, type TerminalUI } from '../ui/terminal.js'\nimport { DEFAULT_REPLAY_TARGET_URL } from './defaults.js'\nimport { writeJsonLine } from './json-output.js'\nimport { runCommandAction, withDefaultStorage } from './runtime.js'\n\nexport interface ReplayFlags {\n to?: string\n json?: boolean\n}\n\nexport interface ReplayDeps {\n terminal?: TerminalUI\n stdout?: NodeJS.WritableStream\n}\n\nfunction parseTargetUrl(targetUrl: string | undefined): string {\n const raw = targetUrl ?? DEFAULT_REPLAY_TARGET_URL\n\n try {\n return new URL(raw).href\n } catch {\n throw new Error(`Invalid target URL \"${raw}\".`)\n }\n}\n\nexport async function runReplay(\n eventId: string,\n flags: ReplayFlags,\n deps: ReplayDeps = {},\n): Promise<void> {\n const targetUrl = parseTargetUrl(flags.to)\n const terminal = deps.terminal ?? createTerminal()\n\n return withDefaultStorage(async (storage) => {\n const event = storage.load(eventId)\n\n if (!event) {\n throw new Error(`Event \"${eventId}\" not found.`)\n }\n\n try {\n const result = await forwardEvent(targetUrl, event)\n\n if (flags.json) {\n const out = deps.stdout ?? process.stdout\n writeJsonLine(out, { status: result.status, body: result.body })\n } else {\n const body = result.body.length <= 200 ? result.body : `${result.body.slice(0, 197)}...`\n terminal.printReplayResult({\n status: result.status,\n body,\n })\n }\n } catch (error) {\n throw new Error(`Failed to replay \"${eventId}\" to ${targetUrl}: ${errorMessage(error)}`)\n }\n })\n}\n\nexport const replayCommand = new Command('replay')\n .description('Replay a stored webhook event')\n .argument('<event-id>', 'ID of the event to replay')\n .option('--to <url>', 'Target URL to send the event to', DEFAULT_REPLAY_TARGET_URL)\n .option('--json', 'Output as JSON')\n .addHelpText(\n 'after',\n `\nExamples:\n hooklens replay evt_abc123\n hooklens replay evt_abc123 --to http://localhost:8080/hook\n hooklens replay evt_abc123 --json`,\n )\n .action(async (eventId, options) => {\n await runCommandAction((terminal) => runReplay(eventId, options, { terminal }))\n })\n"],"mappings":"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","names":["Command","require","Command","Command","Command","Command","Command","path","crypto","crypto","crypto","PROVIDER","computeHmac","crypto","constantTimeMatch","success","failure","Command","Command","Command","Command","Command","Command"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hooklens",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Debug webhook signature failures locally.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"hooklens": "./dist/index.js"
|
|
@@ -28,7 +28,10 @@
|
|
|
28
28
|
"webhook",
|
|
29
29
|
"debug",
|
|
30
30
|
"stripe",
|
|
31
|
+
"stripe-webhooks",
|
|
32
|
+
"github-webhooks",
|
|
31
33
|
"signature",
|
|
34
|
+
"webhook-signature",
|
|
32
35
|
"replay",
|
|
33
36
|
"cli",
|
|
34
37
|
"developer-tools"
|
|
@@ -42,7 +45,7 @@
|
|
|
42
45
|
"bugs": {
|
|
43
46
|
"url": "https://github.com/Ilia01/hooklens/issues"
|
|
44
47
|
},
|
|
45
|
-
"homepage": "https://github.
|
|
48
|
+
"homepage": "https://ilia01.github.io/hooklens/",
|
|
46
49
|
"engines": {
|
|
47
50
|
"node": ">=24.0.0"
|
|
48
51
|
},
|