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 CHANGED
@@ -1,10 +1,10 @@
1
1
  <div align="center">
2
2
 
3
- <img src="./docs/public/logo.svg" alt="HookLens logo" width="88" height="88">
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
- **Inspect, verify, and replay webhooks from your terminal.**
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 an open-source CLI for local webhook debugging. It captures the raw request before your framework mutates it, verifies the signature, stores the event locally, and lets you replay or forward it while you fix your app.
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
- ## Quick links
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
- - [Getting Started](https://ilia01.github.io/hooklens/getting-started)
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
- ## Current provider support
48
+ Use HookLens when:
39
49
 
40
- - Stripe
41
- - GitHub
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
- ## What it helps with
55
+ It is not a tunnel, a hosted webhook inbox, or a replacement for provider delivery tooling.
44
56
 
45
- - Raw body mutation before signature verification
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
- Detailed command usage, verification behavior, forwarding notes, and contributor guidance live in the docs and [CONTRIBUTING.md](./CONTRIBUTING.md).
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/ui/terminal.ts
150
- import chalk from "chalk";
151
- function writeLine(stream, line) {
152
- stream.write(`${line}
153
- `);
154
- }
155
- function verificationLabel(result) {
156
- if (!result) return chalk.cyan("RECV");
157
- return result.valid ? chalk.green("PASS") : chalk.red("FAIL");
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
- const storage = createStorage(defaultDbPath());
273
- try {
381
+ return withDefaultStorage((storage) => {
274
382
  const count = storage.clear();
275
383
  terminal.printCleared(count);
276
- } finally {
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
- const terminal = createTerminal();
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
- const storage = createStorage(defaultDbPath());
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
- } finally {
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
- const terminal = createTerminal();
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
- const storage = createStorage(defaultDbPath());
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
- } finally {
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
- const terminal = createTerminal();
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 0;
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", "4400").option("--verify <provider>", "Verify signatures (stripe, github)").option("--secret <secret>", "Webhook signing secret").option("--forward-to <url>", "Forward received webhooks to this URL").option("--retry <count>", "Retry failed forwards with exponential backoff", "0").addHelpText(
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
- const terminal = createTerminal();
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 ?? "20");
1153
+ const limit = parseLimit(flags.limit ?? DEFAULT_LIST_LIMIT);
1068
1154
  const terminal = deps.terminal ?? createTerminal();
1069
- const storage = createStorage(defaultDbPath());
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
- } finally {
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", "20").option("--json", "Output as newline-delimited JSON").addHelpText(
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
- const terminal = createTerminal();
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
- const storage = createStorage(defaultDbPath());
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
- } finally {
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
- const terminal = createTerminal();
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("Inspect, verify, and replay webhooks from your terminal").version("0.1.0");
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.0",
4
- "description": "Inspect, verify, and replay webhooks from your terminal",
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.com/Ilia01/hooklens#readme",
48
+ "homepage": "https://ilia01.github.io/hooklens/",
46
49
  "engines": {
47
50
  "node": ">=24.0.0"
48
51
  },