github-webhook-handler 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [2.1.0](https://github.com/rvagg/github-webhook-handler/compare/v2.0.0...v2.1.0) (2026-01-28)
2
+
3
+ ### Features
4
+
5
+ * add types via jsdoc tsc, remove old handrolled types ([#59](https://github.com/rvagg/github-webhook-handler/issues/59)) ([f8f20d8](https://github.com/rvagg/github-webhook-handler/commit/f8f20d864cf9529b2eebd953099967267b068ba4))
6
+
1
7
  ## [2.0.0](https://github.com/rvagg/github-webhook-handler/compare/v1.0.0...v2.0.0) (2026-01-28)
2
8
 
3
9
  ### ⚠ BREAKING CHANGES
@@ -2,6 +2,29 @@ import { EventEmitter } from 'node:events'
2
2
  import crypto from 'node:crypto'
3
3
  import bl from 'bl'
4
4
 
5
+ /**
6
+ * @typedef {Object} CreateHandlerOptions
7
+ * @property {string} path
8
+ * @property {string} secret
9
+ * @property {string | string[]} [events]
10
+ */
11
+
12
+ /**
13
+ * @typedef {Object} WebhookEvent
14
+ * @property {string} event - The event type (e.g. 'push', 'issues')
15
+ * @property {string} id - The delivery ID from X-Github-Delivery header
16
+ * @property {any} payload - The parsed JSON payload
17
+ * @property {string} [protocol] - The request protocol
18
+ * @property {string} [host] - The request host header
19
+ * @property {string} url - The request URL
20
+ * @property {string} path - The matched handler path
21
+ */
22
+
23
+ /**
24
+ * @param {string} url
25
+ * @param {CreateHandlerOptions | CreateHandlerOptions[]} arr
26
+ * @returns {CreateHandlerOptions}
27
+ */
5
28
  function findHandler (url, arr) {
6
29
  if (!Array.isArray(arr)) {
7
30
  return arr
@@ -17,6 +40,9 @@ function findHandler (url, arr) {
17
40
  return ret
18
41
  }
19
42
 
43
+ /**
44
+ * @param {CreateHandlerOptions} options
45
+ */
20
46
  function checkType (options) {
21
47
  if (typeof options !== 'object') {
22
48
  throw new TypeError('must provide an options object')
@@ -31,7 +57,12 @@ function checkType (options) {
31
57
  }
32
58
  }
33
59
 
60
+ /**
61
+ * @param {CreateHandlerOptions | CreateHandlerOptions[]} initOptions
62
+ * @returns {EventEmitter & {(req: import('node:http').IncomingMessage, res: import('node:http').ServerResponse, callback: (err?: Error) => void): void, sign(data: string | Buffer): string, verify(signature: string, data: string | Buffer): boolean}}
63
+ */
34
64
  function create (initOptions) {
65
+ /** @type {CreateHandlerOptions} */
35
66
  let options
36
67
  if (Array.isArray(initOptions)) {
37
68
  for (let i = 0; i < initOptions.length; i++) {
@@ -41,18 +72,30 @@ function create (initOptions) {
41
72
  checkType(initOptions)
42
73
  }
43
74
 
75
+ // @ts-ignore - handler is a callable EventEmitter via setPrototypeOf
44
76
  Object.setPrototypeOf(handler, EventEmitter.prototype)
77
+ // @ts-ignore
45
78
  EventEmitter.call(handler)
46
79
 
47
80
  handler.sign = sign
48
81
  handler.verify = verify
49
82
 
83
+ // @ts-ignore
50
84
  return handler
51
85
 
86
+ /**
87
+ * @param {string | Buffer} data
88
+ * @returns {string}
89
+ */
52
90
  function sign (data) {
53
91
  return `sha1=${crypto.createHmac('sha1', options.secret).update(data).digest('hex')}`
54
92
  }
55
93
 
94
+ /**
95
+ * @param {string} signature
96
+ * @param {string | Buffer} data
97
+ * @returns {boolean}
98
+ */
56
99
  function verify (signature, data) {
57
100
  const sig = Buffer.from(signature)
58
101
  const signed = Buffer.from(sign(data))
@@ -62,10 +105,16 @@ function create (initOptions) {
62
105
  return crypto.timingSafeEqual(sig, signed)
63
106
  }
64
107
 
108
+ /**
109
+ * @param {import('node:http').IncomingMessage} req
110
+ * @param {import('node:http').ServerResponse} res
111
+ * @param {(err?: Error) => void} callback
112
+ */
65
113
  function handler (req, res, callback) {
114
+ /** @type {string[] | undefined} */
66
115
  let events
67
116
 
68
- options = findHandler(req.url, initOptions)
117
+ options = findHandler(/** @type {string} */ (req.url), initOptions)
69
118
 
70
119
  if (typeof options.events === 'string' && options.events !== '*') {
71
120
  events = [options.events]
@@ -77,12 +126,16 @@ function create (initOptions) {
77
126
  return callback()
78
127
  }
79
128
 
129
+ /**
130
+ * @param {string} msg
131
+ */
80
132
  function hasError (msg) {
81
133
  res.writeHead(400, { 'content-type': 'application/json' })
82
134
  res.end(JSON.stringify({ error: msg }))
83
135
 
84
136
  const err = new Error(msg)
85
137
 
138
+ // @ts-ignore - handler has EventEmitter prototype
86
139
  handler.emit('error', err, req)
87
140
  callback(err)
88
141
  }
@@ -103,7 +156,7 @@ function create (initOptions) {
103
156
  return hasError('No X-Github-Delivery found on request')
104
157
  }
105
158
 
106
- if (events && events.indexOf(event) === -1) {
159
+ if (events && events.indexOf(/** @type {string} */ (event)) === -1) {
107
160
  return hasError('X-Github-Event is not acceptable')
108
161
  }
109
162
 
@@ -114,14 +167,14 @@ function create (initOptions) {
114
167
 
115
168
  let obj
116
169
 
117
- if (!verify(sig, data)) {
170
+ if (!verify(/** @type {string} */ (sig), data)) {
118
171
  return hasError('X-Hub-Signature does not match blob signature')
119
172
  }
120
173
 
121
174
  try {
122
175
  obj = JSON.parse(data.toString())
123
176
  } catch (e) {
124
- return hasError(e)
177
+ return hasError(/** @type {Error} */ (e).message)
125
178
  }
126
179
 
127
180
  res.writeHead(200, { 'content-type': 'application/json' })
@@ -131,13 +184,15 @@ function create (initOptions) {
131
184
  event,
132
185
  id,
133
186
  payload: obj,
134
- protocol: req.protocol,
187
+ protocol: /** @type {any} */ (req).protocol,
135
188
  host: req.headers.host,
136
189
  url: req.url,
137
190
  path: options.path
138
191
  }
139
192
 
193
+ // @ts-ignore - handler has EventEmitter prototype
140
194
  handler.emit(event, emitData)
195
+ // @ts-ignore - handler has EventEmitter prototype
141
196
  handler.emit('*', emitData)
142
197
  }))
143
198
  }
package/package.json CHANGED
@@ -1,17 +1,24 @@
1
1
  {
2
2
  "name": "github-webhook-handler",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Web handler / middleware for processing GitHub Webhooks",
5
5
  "type": "module",
6
6
  "main": "github-webhook-handler.js",
7
- "exports": "./github-webhook-handler.js",
8
- "types": "github-webhook-handler.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./github-webhook-handler.js",
10
+ "types": "./types/github-webhook-handler.d.ts"
11
+ }
12
+ },
13
+ "types": "types/github-webhook-handler.d.ts",
9
14
  "engines": {
10
15
  "node": ">=20"
11
16
  },
12
17
  "scripts": {
13
18
  "lint": "standard",
14
- "build": "true",
19
+ "build": "npm run build:types",
20
+ "build:types": "tsc --build",
21
+ "prepublishOnly": "npm run build",
15
22
  "test:unit": "node --test test.js",
16
23
  "test": "npm run lint && npm run test:unit"
17
24
  },
@@ -35,9 +42,11 @@
35
42
  "@semantic-release/github": "^12.0.2",
36
43
  "@semantic-release/npm": "^13.1.3",
37
44
  "@semantic-release/release-notes-generator": "^14.1.0",
45
+ "@types/node": "^25.0.10",
38
46
  "conventional-changelog-conventionalcommits": "^9.1.0",
39
47
  "semantic-release": "^25.0.2",
40
- "standard": "^17.1.2"
48
+ "standard": "^17.1.2",
49
+ "typescript": "^5.9.3"
41
50
  },
42
51
  "release": {
43
52
  "branches": [
package/tsconfig.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": true,
4
+ "checkJs": true,
5
+ "forceConsistentCasingInFileNames": true,
6
+ "noImplicitReturns": false,
7
+ "noImplicitAny": true,
8
+ "noImplicitThis": true,
9
+ "noFallthroughCasesInSwitch": true,
10
+ "noUnusedLocals": true,
11
+ "noUnusedParameters": true,
12
+ "strictFunctionTypes": false,
13
+ "strictNullChecks": true,
14
+ "strictPropertyInitialization": true,
15
+ "strictBindCallApply": true,
16
+ "strict": true,
17
+ "alwaysStrict": true,
18
+ "esModuleInterop": true,
19
+ "target": "ES2022",
20
+ "module": "NodeNext",
21
+ "moduleResolution": "NodeNext",
22
+ "declaration": true,
23
+ "declarationMap": true,
24
+ "outDir": "types",
25
+ "skipLibCheck": true,
26
+ "stripInternal": true,
27
+ "resolveJsonModule": true,
28
+ "baseUrl": ".",
29
+ "emitDeclarationOnly": true,
30
+ "paths": {
31
+ "github-webhook-handler": ["github-webhook-handler.js"]
32
+ }
33
+ },
34
+ "include": ["github-webhook-handler.js"],
35
+ "exclude": ["node_modules"],
36
+ "compileOnSave": false
37
+ }
@@ -0,0 +1,47 @@
1
+ export default create;
2
+ export type CreateHandlerOptions = {
3
+ path: string;
4
+ secret: string;
5
+ events?: string | string[] | undefined;
6
+ };
7
+ export type WebhookEvent = {
8
+ /**
9
+ * - The event type (e.g. 'push', 'issues')
10
+ */
11
+ event: string;
12
+ /**
13
+ * - The delivery ID from X-Github-Delivery header
14
+ */
15
+ id: string;
16
+ /**
17
+ * - The parsed JSON payload
18
+ */
19
+ payload: any;
20
+ /**
21
+ * - The request protocol
22
+ */
23
+ protocol?: string | undefined;
24
+ /**
25
+ * - The request host header
26
+ */
27
+ host?: string | undefined;
28
+ /**
29
+ * - The request URL
30
+ */
31
+ url: string;
32
+ /**
33
+ * - The matched handler path
34
+ */
35
+ path: string;
36
+ };
37
+ /**
38
+ * @param {CreateHandlerOptions | CreateHandlerOptions[]} initOptions
39
+ * @returns {EventEmitter & {(req: import('node:http').IncomingMessage, res: import('node:http').ServerResponse, callback: (err?: Error) => void): void, sign(data: string | Buffer): string, verify(signature: string, data: string | Buffer): boolean}}
40
+ */
41
+ declare function create(initOptions: CreateHandlerOptions | CreateHandlerOptions[]): EventEmitter & {
42
+ (req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse, callback: (err?: Error) => void): void;
43
+ sign(data: string | Buffer): string;
44
+ verify(signature: string, data: string | Buffer): boolean;
45
+ };
46
+ import { EventEmitter } from 'node:events';
47
+ //# sourceMappingURL=github-webhook-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-webhook-handler.d.ts","sourceRoot":"","sources":["../github-webhook-handler.js"],"names":[],"mappings":";;UAMc,MAAM;YACN,MAAM;;;;;;;WAMN,MAAM;;;;QACN,MAAM;;;;aACN,GAAG;;;;;;;;;;;;SAGH,MAAM;;;;UACN,MAAM;;AAwCpB;;;GAGG;AACH,qCAHW,oBAAoB,GAAG,oBAAoB,EAAE,GAC3C,YAAY,GAAG;IAAC,CAAC,GAAG,EAAE,OAAO,WAAW,EAAE,eAAe,EAAE,GAAG,EAAE,OAAO,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CAAC,CAyIvP;6BAtM4B,aAAa"}
@@ -0,0 +1 @@
1
+ {"root":["../github-webhook-handler.js"],"version":"5.9.3"}
@@ -1,20 +0,0 @@
1
- /// <reference types="node" />
2
-
3
- import { IncomingMessage, ServerResponse } from 'node:http'
4
- import { EventEmitter } from 'node:events'
5
-
6
- interface CreateHandlerOptions {
7
- path: string
8
- secret: string
9
- events?: string | string[]
10
- }
11
-
12
- interface Handler extends EventEmitter {
13
- (req: IncomingMessage, res: ServerResponse, callback: (err?: Error) => void): void
14
- sign(data: string | Buffer): string
15
- verify(signature: string, data: string | Buffer): boolean
16
- }
17
-
18
- declare function createHandler (options: CreateHandlerOptions | CreateHandlerOptions[]): Handler
19
-
20
- export default createHandler