@@ -2,6 +2,29 @@ import { EventEmitter } from 'node:events'
22import crypto from 'node:crypto'
33import bl from 'bl'
44
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+ */
528function findHandler ( url , arr ) {
629 if ( ! Array . isArray ( arr ) ) {
730 return arr
@@ -17,6 +40,9 @@ function findHandler (url, arr) {
1740 return ret
1841}
1942
43+ /**
44+ * @param {CreateHandlerOptions } options
45+ */
2046function checkType ( options ) {
2147 if ( typeof options !== 'object' ) {
2248 throw new TypeError ( 'must provide an options object' )
@@ -31,7 +57,12 @@ function checkType (options) {
3157 }
3258}
3359
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+ */
3464function create ( initOptions ) {
65+ /** @type {CreateHandlerOptions } */
3566 let options
3667 if ( Array . isArray ( initOptions ) ) {
3768 for ( let i = 0 ; i < initOptions . length ; i ++ ) {
@@ -41,18 +72,30 @@ function create (initOptions) {
4172 checkType ( initOptions )
4273 }
4374
75+ // @ts -ignore - handler is a callable EventEmitter via setPrototypeOf
4476 Object . setPrototypeOf ( handler , EventEmitter . prototype )
77+ // @ts -ignore
4578 EventEmitter . call ( handler )
4679
4780 handler . sign = sign
4881 handler . verify = verify
4982
83+ // @ts -ignore
5084 return handler
5185
86+ /**
87+ * @param {string | Buffer } data
88+ * @returns {string }
89+ */
5290 function sign ( data ) {
5391 return `sha1=${ crypto . createHmac ( 'sha1' , options . secret ) . update ( data ) . digest ( 'hex' ) } `
5492 }
5593
94+ /**
95+ * @param {string } signature
96+ * @param {string | Buffer } data
97+ * @returns {boolean }
98+ */
5699 function verify ( signature , data ) {
57100 const sig = Buffer . from ( signature )
58101 const signed = Buffer . from ( sign ( data ) )
@@ -62,10 +105,16 @@ function create (initOptions) {
62105 return crypto . timingSafeEqual ( sig , signed )
63106 }
64107
108+ /**
109+ * @param {import('node:http').IncomingMessage } req
110+ * @param {import('node:http').ServerResponse } res
111+ * @param {(err?: Error) => void } callback
112+ */
65113 function handler ( req , res , callback ) {
114+ /** @type {string[] | undefined } */
66115 let events
67116
68- options = findHandler ( req . url , initOptions )
117+ options = findHandler ( /** @type { string } */ ( req . url ) , initOptions )
69118
70119 if ( typeof options . events === 'string' && options . events !== '*' ) {
71120 events = [ options . events ]
@@ -77,12 +126,16 @@ function create (initOptions) {
77126 return callback ( )
78127 }
79128
129+ /**
130+ * @param {string } msg
131+ */
80132 function hasError ( msg ) {
81133 res . writeHead ( 400 , { 'content-type' : 'application/json' } )
82134 res . end ( JSON . stringify ( { error : msg } ) )
83135
84136 const err = new Error ( msg )
85137
138+ // @ts -ignore - handler has EventEmitter prototype
86139 handler . emit ( 'error' , err , req )
87140 callback ( err )
88141 }
@@ -103,7 +156,7 @@ function create (initOptions) {
103156 return hasError ( 'No X-Github-Delivery found on request' )
104157 }
105158
106- if ( events && events . indexOf ( event ) === - 1 ) {
159+ if ( events && events . indexOf ( /** @type { string } */ ( event ) ) === - 1 ) {
107160 return hasError ( 'X-Github-Event is not acceptable' )
108161 }
109162
@@ -114,14 +167,14 @@ function create (initOptions) {
114167
115168 let obj
116169
117- if ( ! verify ( sig , data ) ) {
170+ if ( ! verify ( /** @type { string } */ ( sig ) , data ) ) {
118171 return hasError ( 'X-Hub-Signature does not match blob signature' )
119172 }
120173
121174 try {
122175 obj = JSON . parse ( data . toString ( ) )
123176 } catch ( e ) {
124- return hasError ( e )
177+ return hasError ( /** @type { Error } */ ( e ) . message )
125178 }
126179
127180 res . writeHead ( 200 , { 'content-type' : 'application/json' } )
@@ -131,13 +184,15 @@ function create (initOptions) {
131184 event,
132185 id,
133186 payload : obj ,
134- protocol : req . protocol ,
187+ protocol : /** @type { any } */ ( req ) . protocol ,
135188 host : req . headers . host ,
136189 url : req . url ,
137190 path : options . path
138191 }
139192
193+ // @ts -ignore - handler has EventEmitter prototype
140194 handler . emit ( event , emitData )
195+ // @ts -ignore - handler has EventEmitter prototype
141196 handler . emit ( '*' , emitData )
142197 } ) )
143198 }
0 commit comments