@octokit/webhooks
Advanced tools
Comparing version 6.3.2 to 7.0.0
@@ -1,14 +0,14 @@ | ||
module.exports = createEventHandler | ||
module.exports = createEventHandler; | ||
const on = require('./on') | ||
const receive = require('./receive') | ||
const removeListener = require('./remove-listener') | ||
const on = require("./on"); | ||
const receive = require("./receive"); | ||
const removeListener = require("./remove-listener"); | ||
function createEventHandler (options) { | ||
function createEventHandler(options) { | ||
const state = { | ||
hooks: {} | ||
} | ||
}; | ||
if (options && options.transform) { | ||
state.transform = options.transform | ||
state.transform = options.transform; | ||
} | ||
@@ -20,3 +20,3 @@ | ||
receive: receive.bind(null, state) | ||
} | ||
}; | ||
} |
@@ -1,20 +0,24 @@ | ||
module.exports = receiverOn | ||
module.exports = receiverOn; | ||
const webhookNames = require('../lib/webhook-names.json') | ||
const webhookNames = require("../lib/webhook-names.json"); | ||
function receiverOn (state, webhookNameOrNames, handler) { | ||
function receiverOn(state, webhookNameOrNames, handler) { | ||
if (Array.isArray(webhookNameOrNames)) { | ||
webhookNameOrNames.forEach(webhookName => receiverOn(state, webhookName, handler)) | ||
return | ||
webhookNameOrNames.forEach(webhookName => | ||
receiverOn(state, webhookName, handler) | ||
); | ||
return; | ||
} | ||
if (webhookNames.indexOf(webhookNameOrNames) === -1) { | ||
console.warn(`"${webhookNameOrNames}" is not a known webhook name (https://developer.github.com/v3/activity/events/types/)`) | ||
console.warn( | ||
`"${webhookNameOrNames}" is not a known webhook name (https://developer.github.com/v3/activity/events/types/)` | ||
); | ||
} | ||
if (!state.hooks[webhookNameOrNames]) { | ||
state.hooks[webhookNameOrNames] = [] | ||
state.hooks[webhookNameOrNames] = []; | ||
} | ||
state.hooks[webhookNameOrNames].push(handler) | ||
state.hooks[webhookNameOrNames].push(handler); | ||
} |
@@ -1,63 +0,68 @@ | ||
'use strict' | ||
"use strict"; | ||
module.exports = receiverHandle | ||
module.exports = receiverHandle; | ||
const wrapErrorHandler = require('./wrap-error-handler') | ||
const wrapErrorHandler = require("./wrap-error-handler"); | ||
// main handler function | ||
function receiverHandle (state, event) { | ||
const errorHandlers = state.hooks.error || [] | ||
function receiverHandle(state, event) { | ||
const errorHandlers = state.hooks.error || []; | ||
if (event instanceof Error) { | ||
errorHandlers.forEach(handler => wrapErrorHandler(handler, event)) | ||
errorHandlers.forEach(handler => wrapErrorHandler(handler, event)); | ||
return Promise.reject(event) | ||
return Promise.reject(event); | ||
} | ||
if (!event || !event.name) { | ||
throw new Error('Event name not passed') | ||
throw new Error("Event name not passed"); | ||
} | ||
if (!event.payload) { | ||
throw new Error('Event payload not passed') | ||
throw new Error("Event payload not passed"); | ||
} | ||
// flatten arrays of event listeners and remove undefined values | ||
const hooks = [].concat( | ||
state.hooks[`${event.name}.${event.payload.action}`], | ||
state.hooks[event.name], | ||
state.hooks['*'] | ||
).filter(Boolean) | ||
const hooks = [] | ||
.concat( | ||
state.hooks[`${event.name}.${event.payload.action}`], | ||
state.hooks[event.name], | ||
state.hooks["*"] | ||
) | ||
.filter(Boolean); | ||
if (hooks.length === 0) { | ||
return Promise.resolve() | ||
return Promise.resolve(); | ||
} | ||
const errors = [] | ||
const errors = []; | ||
const promises = hooks.map(handler => { | ||
let promise = Promise.resolve(event) | ||
let promise = Promise.resolve(event); | ||
if (state.transform) { | ||
promise = promise.then(state.transform) | ||
promise = promise.then(state.transform); | ||
} | ||
return promise.then((event) => { | ||
return handler(event) | ||
}) | ||
return promise | ||
.then(event => { | ||
return handler(event); | ||
}) | ||
.catch(error => errors.push(Object.assign(error, { event }))) | ||
}) | ||
.catch(error => errors.push(Object.assign(error, { event }))); | ||
}); | ||
return Promise.all(promises).then(() => { | ||
if (errors.length === 0) { | ||
return | ||
return; | ||
} | ||
errorHandlers.forEach(handler => errors.forEach(wrapErrorHandler.bind(null, handler))) | ||
errorHandlers.forEach(handler => | ||
errors.forEach(wrapErrorHandler.bind(null, handler)) | ||
); | ||
const error = new Error('Webhook handler error') | ||
error.errors = errors | ||
const error = new Error("Webhook handler error"); | ||
error.errors = errors; | ||
throw error | ||
}) | ||
throw error; | ||
}); | ||
} |
@@ -1,13 +0,15 @@ | ||
'use strict' | ||
"use strict"; | ||
module.exports = receiverListener | ||
module.exports = receiverListener; | ||
function receiverListener (state, webhookNameOrNames, handler) { | ||
function receiverListener(state, webhookNameOrNames, handler) { | ||
if (Array.isArray(webhookNameOrNames)) { | ||
webhookNameOrNames.forEach(webhookName => receiverListener(state, webhookName, handler)) | ||
return | ||
webhookNameOrNames.forEach(webhookName => | ||
receiverListener(state, webhookName, handler) | ||
); | ||
return; | ||
} | ||
if (!state.hooks[webhookNameOrNames]) { | ||
return | ||
return; | ||
} | ||
@@ -19,6 +21,6 @@ | ||
if (state.hooks[webhookNameOrNames][i] === handler) { | ||
state.hooks[webhookNameOrNames].splice(i, 1) | ||
return | ||
state.hooks[webhookNameOrNames].splice(i, 1); | ||
return; | ||
} | ||
} | ||
} |
@@ -1,4 +0,4 @@ | ||
'use strict' | ||
"use strict"; | ||
module.exports = wrapErrorHandler | ||
module.exports = wrapErrorHandler; | ||
@@ -8,10 +8,10 @@ // Errors thrown or rejected Promises in "error" event handlers are not handled | ||
// "Fatal: Error occured" message to stdout | ||
function wrapErrorHandler (handler, error) { | ||
let returnValue | ||
function wrapErrorHandler(handler, error) { | ||
let returnValue; | ||
try { | ||
returnValue = handler(error) | ||
returnValue = handler(error); | ||
} catch (error) { | ||
console.log('FATAL: Error occured in "error" event handler') | ||
console.log(error) | ||
console.log('FATAL: Error occured in "error" event handler'); | ||
console.log(error); | ||
} | ||
@@ -21,6 +21,6 @@ | ||
returnValue.catch(error => { | ||
console.log('FATAL: Error occured in "error" event handler') | ||
console.log(error) | ||
}) | ||
console.log('FATAL: Error occured in "error" event handler'); | ||
console.log(error); | ||
}); | ||
} | ||
} |
22
index.js
@@ -1,12 +0,12 @@ | ||
module.exports = createWebhooksApi | ||
module.exports = createWebhooksApi; | ||
const createEventHandler = require('./event-handler') | ||
const middleware = require('./middleware/middleware') | ||
const sign = require('./sign') | ||
const verify = require('./verify') | ||
const verifyAndReceive = require('./middleware/verify-and-receive') | ||
const createEventHandler = require("./event-handler"); | ||
const middleware = require("./middleware/middleware"); | ||
const sign = require("./sign"); | ||
const verify = require("./verify"); | ||
const verifyAndReceive = require("./middleware/verify-and-receive"); | ||
function createWebhooksApi (options) { | ||
function createWebhooksApi(options) { | ||
if (!options || !options.secret) { | ||
throw new Error('options.secret required') | ||
throw new Error("options.secret required"); | ||
} | ||
@@ -16,5 +16,5 @@ | ||
eventHandler: createEventHandler(options), | ||
path: options.path || '/', | ||
path: options.path || "/", | ||
secret: options.secret | ||
} | ||
}; | ||
@@ -29,3 +29,3 @@ return { | ||
verifyAndReceive: verifyAndReceive.bind(null, state) | ||
} | ||
}; | ||
} |
@@ -88,2 +88,5 @@ [ | ||
"organization.renamed", | ||
"package", | ||
"package.published", | ||
"package.updated", | ||
"page_build", | ||
@@ -133,5 +136,2 @@ "ping", | ||
"push", | ||
"registry_package", | ||
"registry_package.published", | ||
"registry_package.updated", | ||
"release", | ||
@@ -138,0 +138,0 @@ "release.created", |
{ | ||
"name": "@octokit/webhooks", | ||
"version": "6.3.2", | ||
"version": "7.0.0", | ||
"publishConfig": { | ||
@@ -18,3 +18,3 @@ "access": "public" | ||
"@gimenete/type-writer": "^0.1.3", | ||
"@octokit/webhooks-definitions": "1.5.1", | ||
"@octokit/webhooks-definitions": "2.0.0", | ||
"axios": "^0.19.0", | ||
@@ -24,9 +24,8 @@ "cheerio": "^1.0.0-rc.2", | ||
"get-port": "^5.0.0", | ||
"pascal-case": "^2.0.1", | ||
"pascal-case": "^3.0.0", | ||
"pify": "^4.0.0", | ||
"prettier": "^1.16.1", | ||
"prettier": "^1.19.1", | ||
"proxyquire": "^2.0.0", | ||
"semantic-release": "^15.13.24", | ||
"simple-mock": "^0.8.0", | ||
"standard": "^14.0.2", | ||
"table-builder": "^2.1.1", | ||
@@ -39,3 +38,5 @@ "tap": "^14.0.0" | ||
"generate-known-webhooks": "node scripts/update-known-events.js", | ||
"pretest": "standard", | ||
"lint": "prettier --check '{event-handler,lib.middleware,scripts,sign,test,verify}/**/*.{js,json}' index.js index.d.ts README.md package.json", | ||
"lint:fix": "prettier --write '{event-handler,lib.middleware,scripts,sign,test,verify}/**/*.{js,json}' index.js index.d.ts README.md package.json", | ||
"pretest": "npm run -s lint", | ||
"test": "tap --100 --coverage 'test/**/*-test.js'", | ||
@@ -42,0 +43,0 @@ "semantic-release": "semantic-release", |
108
README.md
@@ -23,12 +23,14 @@ # @octokit/webhooks | ||
// install with: npm install @octokit/webhooks | ||
const WebhooksApi = require('@octokit/webhooks') | ||
const WebhooksApi = require("@octokit/webhooks"); | ||
const webhooks = new WebhooksApi({ | ||
secret: 'mysecret' | ||
}) | ||
secret: "mysecret" | ||
}); | ||
webhooks.on('*', ({id, name, payload}) => { | ||
console.log(name, 'event received') | ||
}) | ||
webhooks.on("*", ({ id, name, payload }) => { | ||
console.log(name, "event received"); | ||
}); | ||
require('http').createServer(webhooks.middleware).listen(3000) | ||
require("http") | ||
.createServer(webhooks.middleware) | ||
.listen(3000); | ||
// can now receive webhook events at port 3000 | ||
@@ -47,13 +49,15 @@ ``` | ||
```js | ||
const webhookProxyUrl = 'https://smee.io/IrqK0nopGAOc847' // replace with your own Webhook Proxy URL | ||
const source = new EventSource(webhookProxyUrl) | ||
source.onmessage = (event) => { | ||
const webhookEvent = JSON.parse(event.data) | ||
webhooks.verifyAndReceive({ | ||
id: webhookEvent['x-request-id'], | ||
name: webhookEvent['x-github-event'], | ||
signature: webhookEvent['x-hub-signature'], | ||
payload: webhookEvent.body | ||
}).catch(console.error) | ||
} | ||
const webhookProxyUrl = "https://smee.io/IrqK0nopGAOc847"; // replace with your own Webhook Proxy URL | ||
const source = new EventSource(webhookProxyUrl); | ||
source.onmessage = event => { | ||
const webhookEvent = JSON.parse(event.data); | ||
webhooks | ||
.verifyAndReceive({ | ||
id: webhookEvent["x-request-id"], | ||
name: webhookEvent["x-github-event"], | ||
signature: webhookEvent["x-hub-signature"], | ||
payload: webhookEvent.body | ||
}) | ||
.catch(console.error); | ||
}; | ||
``` | ||
@@ -69,10 +73,10 @@ | ||
4. [webhooks.verifyAndReceive()](#webhooksverifyandreceive) | ||
4. [webhooks.receive()](#webhooksreceive) | ||
5. [webhooks.on()](#webhookson) | ||
6. [webhooks.removeListener()](#webhooksremoveListener) | ||
7. [webhooks.middleware()](#webhooksmiddleware) | ||
8. [Webhook events](#webhook-events) | ||
9. [Special events](#special-events) | ||
1. [`*` wildcard event](#-wildcard-event) | ||
1. [`error` event](#error-event) | ||
5. [webhooks.receive()](#webhooksreceive) | ||
6. [webhooks.on()](#webhookson) | ||
7. [webhooks.removeListener()](#webhooksremoveListener) | ||
8. [webhooks.middleware()](#webhooksmiddleware) | ||
9. [Webhook events](#webhook-events) | ||
10. [Special events](#special-events) | ||
1. [`*` wildcard event](#-wildcard-event) | ||
1. [`error` event](#error-event) | ||
@@ -129,3 +133,3 @@ ### Constructor | ||
```js | ||
webhooks.sign(eventPayload) | ||
webhooks.sign(eventPayload); | ||
``` | ||
@@ -157,3 +161,3 @@ | ||
```js | ||
webhooks.verify(eventPayload, signature) | ||
webhooks.verify(eventPayload, signature); | ||
``` | ||
@@ -199,3 +203,3 @@ | ||
```js | ||
webhooks.verifyAndReceive({id, name, payload, signature}) | ||
webhooks.verifyAndReceive({ id, name, payload, signature }); | ||
``` | ||
@@ -271,15 +275,17 @@ | ||
```js | ||
const WebhooksApi = require('@octokit/webhooks') | ||
const WebhooksApi = require("@octokit/webhooks"); | ||
const webhooks = new WebhooksApi({ | ||
secret: 'mysecret' | ||
}) | ||
eventHandler.on('error', handleSignatureVerificationError) | ||
secret: "mysecret" | ||
}); | ||
eventHandler.on("error", handleSignatureVerificationError); | ||
// put this inside your webhooks route handler | ||
eventHandler.verifyAndReceive({ | ||
id: request.headers['x-github-delivery'], | ||
name: request.headers['x-github-event'], | ||
payload: request.body, | ||
signature: request.headers['x-hub-signature'] | ||
}).catch(handleErrorsFromHooks) | ||
eventHandler | ||
.verifyAndReceive({ | ||
id: request.headers["x-github-delivery"], | ||
name: request.headers["x-github-event"], | ||
payload: request.body, | ||
signature: request.headers["x-hub-signature"] | ||
}) | ||
.catch(handleErrorsFromHooks); | ||
``` | ||
@@ -290,3 +296,3 @@ | ||
```js | ||
webhooks.receive({id, name, payload}) | ||
webhooks.receive({ id, name, payload }); | ||
``` | ||
@@ -346,4 +352,4 @@ | ||
```js | ||
webhooks.on(eventName, handler) | ||
webhooks.on(eventNames, handler) | ||
webhooks.on(eventName, handler); | ||
webhooks.on(eventNames, handler); | ||
``` | ||
@@ -403,4 +409,4 @@ | ||
```js | ||
webhooks.removeListener(eventName, handler) | ||
webhooks.removeListener(eventNames, handler) | ||
webhooks.removeListener(eventName, handler); | ||
webhooks.removeListener(eventNames, handler); | ||
``` | ||
@@ -539,2 +545,3 @@ | ||
<tr ><td class="name-td td_text"><a href="https://developer.github.com/v3/activity/events/types/#orgblockevent"><code>org_block</code></a></td><td class="actions-td td_text"><code>blocked</code><br><code>unblocked</code></td></tr> | ||
<tr ><td class="name-td td_text"><a href="https://developer.github.com/v3/activity/events/types/#packageevent"><code>package</code></a></td><td class="actions-td td_text"><code>published</code><br><code>updated</code></td></tr> | ||
<tr ><td class="name-td td_text"><a href="https://developer.github.com/v3/activity/events/types/#pagebuildevent"><code>page_build</code></a></td><td class="actions-td td_num"></td></tr> | ||
@@ -549,3 +556,2 @@ <tr ><td class="name-td td_text"><a href="https://developer.github.com/v3/activity/events/types/#projectcardevent"><code>project_card</code></a></td><td class="actions-td td_text"><code>converted</code><br><code>created</code><br><code>deleted</code><br><code>edited</code><br><code>moved</code></td></tr> | ||
<tr ><td class="name-td td_text"><a href="https://developer.github.com/v3/activity/events/types/#pushevent"><code>push</code></a></td><td class="actions-td td_num"></td></tr> | ||
<tr ><td class="name-td td_text"><a href="https://developer.github.com/v3/activity/events/types/#registrypackageevent"><code>registry_package</code></a></td><td class="actions-td td_text"><code>published</code><br><code>updated</code></td></tr> | ||
<tr ><td class="name-td td_text"><a href="https://developer.github.com/v3/activity/events/types/#releaseevent"><code>release</code></a></td><td class="actions-td td_text"><code>created</code><br><code>deleted</code><br><code>edited</code><br><code>prereleased</code><br><code>published</code><br><code>unpublished</code></td></tr> | ||
@@ -573,5 +579,5 @@ <tr ><td class="name-td td_text"><a href="https://developer.github.com/v3/activity/events/types/#repositorydispatchevent"><code>repository_dispatch</code></a></td><td class="actions-td td_num"></td></tr> | ||
```js | ||
webhooks.on('*', (event) => { | ||
console.log(`"${event.name}" event received"`) | ||
}) | ||
webhooks.on("*", event => { | ||
console.log(`"${event.name}" event received"`); | ||
}); | ||
``` | ||
@@ -588,5 +594,5 @@ | ||
```js | ||
webhooks.on('error', (error) => { | ||
console.log(`Error occured in "${error.event.name} handler: ${error.stack}"`) | ||
}) | ||
webhooks.on("error", error => { | ||
console.log(`Error occured in "${error.event.name} handler: ${error.stack}"`); | ||
}); | ||
``` | ||
@@ -593,0 +599,0 @@ |
@@ -1,26 +0,27 @@ | ||
const fs = require('fs') | ||
const fs = require("fs"); | ||
const pascalCase = require('pascal-case') | ||
const prettier = require('prettier') | ||
const TypeWriter = require('@gimenete/type-writer') | ||
const webhooks = require('@octokit/webhooks-definitions') | ||
const { pascalCase } = require("pascal-case"); | ||
const prettier = require("prettier"); | ||
const TypeWriter = require("@gimenete/type-writer"); | ||
const webhooks = require("@octokit/webhooks-definitions"); | ||
const signatures = [] | ||
const tw = new TypeWriter() | ||
const signatures = []; | ||
const tw = new TypeWriter(); | ||
webhooks.forEach(({ name, actions, examples }) => { | ||
if (!examples) { | ||
return | ||
return; | ||
} | ||
const typeName = `WebhookPayload${pascalCase(name)}` | ||
const typeName = `WebhookPayload${pascalCase(name)}`; | ||
tw.add(examples, { | ||
rootTypeName: typeName, | ||
namedKeyPaths: { | ||
[`${typeName}.repository`]: 'PayloadRepository', | ||
[`${typeName}.repository`]: "PayloadRepository", | ||
// This prevents a naming colision between the payload of a `installation_repositories` event | ||
// and the `repositories` attribute of a `installation` event | ||
'WebhookPayloadInstallation.repositories': 'WebhookPayloadInstallation_Repositories' | ||
"WebhookPayloadInstallation.repositories": | ||
"WebhookPayloadInstallation_Repositories" | ||
} | ||
}) | ||
}); | ||
@@ -30,7 +31,7 @@ const events = [ | ||
...actions.map(action => `'${name}.${action}'`) | ||
].join(' | ') | ||
].join(" | "); | ||
signatures.push(` | ||
public on (event: ${events}, callback: (event: Webhooks.WebhookEvent<Webhooks.${typeName}>) => (Promise<void> | void)): void | ||
`) | ||
}) | ||
`); | ||
}); | ||
@@ -50,3 +51,3 @@ const definition = ` | ||
declare namespace Webhooks { | ||
${tw.generate('typescript', { inlined: false })} | ||
${tw.generate("typescript", { inlined: false })} | ||
@@ -68,3 +69,3 @@ interface WebhookEvent<T> { | ||
public on (event: '*' | string[], callback: (event: Webhooks.WebhookEvent<any>) => Promise<void> | void): void | ||
${signatures.join('\n')} | ||
${signatures.join("\n")} | ||
@@ -81,6 +82,6 @@ public sign (data: any): string | ||
export = Webhooks | ||
` | ||
`; | ||
const filepath = 'index.d.ts' | ||
const output = prettier.format(definition, { filepath }) | ||
fs.writeFileSync(filepath, output) | ||
const filepath = "index.d.ts"; | ||
const output = prettier.format(definition, { filepath }); | ||
fs.writeFileSync(filepath, output); |
@@ -1,36 +0,51 @@ | ||
const { readFileSync, writeFileSync } = require('fs') | ||
const { readFileSync, writeFileSync } = require("fs"); | ||
const Table = require('table-builder') | ||
const Table = require("table-builder"); | ||
const WEBOOOKS = require('@octokit/webhooks-definitions') | ||
const WEBOOOKS = require("@octokit/webhooks-definitions"); | ||
// update lib/webhook-names.json | ||
const newWebhookNames = WEBOOOKS.reduce((list, event) => { | ||
list.push(event.name, ...event.actions.map(action => `${event.name}.${action}`)) | ||
return list | ||
}, ['*', 'error']).sort() | ||
writeFileSync('lib/webhook-names.json', JSON.stringify([...newWebhookNames], null, 2) + '\n') | ||
const newWebhookNames = WEBOOOKS.reduce( | ||
(list, event) => { | ||
list.push( | ||
event.name, | ||
...event.actions.map(action => `${event.name}.${action}`) | ||
); | ||
return list; | ||
}, | ||
["*", "error"] | ||
).sort(); | ||
writeFileSync( | ||
"lib/webhook-names.json", | ||
JSON.stringify([...newWebhookNames], null, 2) + "\n" | ||
); | ||
// update README.md | ||
const data = WEBOOOKS.map(w => { | ||
const link = `https://developer.github.com/v3/activity/events/types/#${w.name.replace(/[^a-z]/g, '')}event` | ||
const link = `https://developer.github.com/v3/activity/events/types/#${w.name.replace( | ||
/[^a-z]/g, | ||
"" | ||
)}event`; | ||
return { | ||
name: `<a href="${link}"><code>${w.name}</code></a>`, | ||
actions: w.actions.map(action => `<code>${action}</code>`).join('<br>') | ||
} | ||
}) | ||
const headers = { name: 'Event', actions: 'Actions' } | ||
actions: w.actions.map(action => `<code>${action}</code>`).join("<br>") | ||
}; | ||
}); | ||
const headers = { name: "Event", actions: "Actions" }; | ||
const tableHtml = new Table() | ||
.setHeaders(headers) | ||
.setData(data) | ||
.render() | ||
.render(); | ||
const readme = readFileSync('README.md').toString() | ||
const parts = readme.split(/<!-- \/?autogenerated via scripts\/update-known-events.js -->/) | ||
const readme = readFileSync("README.md").toString(); | ||
const parts = readme.split( | ||
/<!-- \/?autogenerated via scripts\/update-known-events.js -->/ | ||
); | ||
const newReadme = parts[0] + | ||
'<!-- autogenerated via scripts/update-known-events.js -->' + | ||
const newReadme = | ||
parts[0] + | ||
"<!-- autogenerated via scripts/update-known-events.js -->" + | ||
tableHtml + | ||
'<!-- /autogenerated via scripts/update-known-events.js -->' + | ||
parts[2] | ||
writeFileSync('README.md', newReadme) | ||
"<!-- /autogenerated via scripts/update-known-events.js -->" + | ||
parts[2]; | ||
writeFileSync("README.md", newReadme); |
@@ -1,18 +0,25 @@ | ||
module.exports = sign | ||
module.exports = sign; | ||
const crypto = require('crypto') | ||
const crypto = require("crypto"); | ||
function sign (secret, payload) { | ||
function sign(secret, payload) { | ||
if (!secret || !payload) { | ||
throw new TypeError('secret & payload required') | ||
throw new TypeError("secret & payload required"); | ||
} | ||
payload = typeof payload === 'string' ? payload : toNormalizedJsonString(payload) | ||
return 'sha1=' + crypto.createHmac('sha1', secret).update(payload).digest('hex') | ||
payload = | ||
typeof payload === "string" ? payload : toNormalizedJsonString(payload); | ||
return ( | ||
"sha1=" + | ||
crypto | ||
.createHmac("sha1", secret) | ||
.update(payload) | ||
.digest("hex") | ||
); | ||
} | ||
function toNormalizedJsonString (payload) { | ||
function toNormalizedJsonString(payload) { | ||
return JSON.stringify(payload).replace(/[^\\]\\u[\da-f]{4}/g, s => { | ||
return s.substr(0, 3) + s.substr(3).toUpperCase() | ||
}) | ||
return s.substr(0, 3) + s.substr(3).toUpperCase(); | ||
}); | ||
} |
@@ -28,11 +28,5 @@ { | ||
}, | ||
"added": [ | ||
], | ||
"removed": [ | ||
], | ||
"modified": [ | ||
"README.md" | ||
] | ||
"added": [], | ||
"removed": [], | ||
"modified": ["README.md"] | ||
} | ||
@@ -57,11 +51,5 @@ ], | ||
}, | ||
"added": [ | ||
], | ||
"removed": [ | ||
], | ||
"modified": [ | ||
"README.md" | ||
] | ||
"added": [], | ||
"removed": [], | ||
"modified": ["README.md"] | ||
}, | ||
@@ -68,0 +56,0 @@ "repository": { |
@@ -1,131 +0,137 @@ | ||
const test = require('tap').test | ||
const test = require("tap").test; | ||
const EventHandler = require('../../event-handler') | ||
const pushEventPayload = require('../fixtures/push-payload') | ||
const installationCreatedPayload = require('../fixtures/installation-created-payload') | ||
const EventHandler = require("../../event-handler"); | ||
const pushEventPayload = require("../fixtures/push-payload"); | ||
const installationCreatedPayload = require("../fixtures/installation-created-payload"); | ||
test('events', t => { | ||
t.plan(6) | ||
test("events", t => { | ||
t.plan(6); | ||
const eventHandler = new EventHandler() | ||
const eventHandler = new EventHandler(); | ||
const hooksCalled = [] | ||
function hook1 () { | ||
return Promise.resolve() | ||
.then(() => { | ||
hooksCalled.push('hook1') | ||
}) | ||
const hooksCalled = []; | ||
function hook1() { | ||
return Promise.resolve().then(() => { | ||
hooksCalled.push("hook1"); | ||
}); | ||
} | ||
function hook2 () { | ||
hooksCalled.push('hook2') | ||
function hook2() { | ||
hooksCalled.push("hook2"); | ||
} | ||
function hook3 () { | ||
hooksCalled.push('hook3') | ||
function hook3() { | ||
hooksCalled.push("hook3"); | ||
} | ||
function hook4 () { | ||
hooksCalled.push('hook4') | ||
function hook4() { | ||
hooksCalled.push("hook4"); | ||
} | ||
function hook5 () { | ||
hooksCalled.push('installation') | ||
function hook5() { | ||
hooksCalled.push("installation"); | ||
} | ||
function hook6 () { | ||
hooksCalled.push('installation.created') | ||
function hook6() { | ||
hooksCalled.push("installation.created"); | ||
} | ||
function hook7 (event) { | ||
hooksCalled.push(`* (${event.name})`) | ||
function hook7(event) { | ||
hooksCalled.push(`* (${event.name})`); | ||
} | ||
eventHandler.on('push', hook1) | ||
eventHandler.on('push', hook2) | ||
eventHandler.on('push', hook3) | ||
eventHandler.on(['push'], hook4) | ||
eventHandler.on('installation', hook5) | ||
eventHandler.on('installation.created', hook6) | ||
eventHandler.on('*', hook7) | ||
eventHandler.on("push", hook1); | ||
eventHandler.on("push", hook2); | ||
eventHandler.on("push", hook3); | ||
eventHandler.on(["push"], hook4); | ||
eventHandler.on("installation", hook5); | ||
eventHandler.on("installation.created", hook6); | ||
eventHandler.on("*", hook7); | ||
eventHandler.removeListener('push', hook3) | ||
eventHandler.removeListener(['push'], hook4) | ||
eventHandler.removeListener('unknown', () => {}) | ||
eventHandler.removeListener("push", hook3); | ||
eventHandler.removeListener(["push"], hook4); | ||
eventHandler.removeListener("unknown", () => {}); | ||
eventHandler.receive({ | ||
id: '123', | ||
name: 'push', | ||
payload: pushEventPayload | ||
}) | ||
eventHandler | ||
.receive({ | ||
id: "123", | ||
name: "push", | ||
payload: pushEventPayload | ||
}) | ||
.then(() => { | ||
return eventHandler.receive({ | ||
id: '456', | ||
name: 'installation', | ||
id: "456", | ||
name: "installation", | ||
payload: installationCreatedPayload | ||
}) | ||
}); | ||
}) | ||
.then(() => { | ||
t.deepEqual(hooksCalled, ['hook2', '* (push)', 'hook1', 'installation.created', 'installation', '* (installation)']) | ||
t.deepEqual(hooksCalled, [ | ||
"hook2", | ||
"* (push)", | ||
"hook1", | ||
"installation.created", | ||
"installation", | ||
"* (installation)" | ||
]); | ||
eventHandler.on('error', (error) => { | ||
t.ok(error.event.payload) | ||
t.pass('error event triggered') | ||
t.is(error.message, 'oops') | ||
}) | ||
eventHandler.on("error", error => { | ||
t.ok(error.event.payload); | ||
t.pass("error event triggered"); | ||
t.is(error.message, "oops"); | ||
}); | ||
eventHandler.on('push', () => { | ||
throw new Error('oops') | ||
}) | ||
eventHandler.on("push", () => { | ||
throw new Error("oops"); | ||
}); | ||
return eventHandler.receive({ | ||
id: '123', | ||
name: 'push', | ||
id: "123", | ||
name: "push", | ||
payload: pushEventPayload | ||
}) | ||
}); | ||
}) | ||
.catch(error => { | ||
t.is(error.errors.length, 1) | ||
t.is(error.errors[0].message, 'oops') | ||
t.is(error.errors.length, 1); | ||
t.is(error.errors[0].message, "oops"); | ||
}) | ||
.catch(t.error) | ||
}) | ||
.catch(t.error); | ||
}); | ||
test('options.transform', t => { | ||
t.plan(2) | ||
test("options.transform", t => { | ||
t.plan(2); | ||
const eventHandler = EventHandler({ | ||
transform: (event) => { | ||
t.is(event.id, '123') | ||
return 'funky' | ||
transform: event => { | ||
t.is(event.id, "123"); | ||
return "funky"; | ||
} | ||
}) | ||
}); | ||
eventHandler.on('push', (event) => { | ||
t.is(event, 'funky') | ||
}) | ||
eventHandler.on("push", event => { | ||
t.is(event, "funky"); | ||
}); | ||
eventHandler.receive({ | ||
id: '123', | ||
name: 'push', | ||
id: "123", | ||
name: "push", | ||
payload: pushEventPayload | ||
}) | ||
}) | ||
}); | ||
}); | ||
test('async options.transform', t => { | ||
test("async options.transform", t => { | ||
const eventHandler = EventHandler({ | ||
transform: (event) => { | ||
return Promise.resolve('funky') | ||
transform: event => { | ||
return Promise.resolve("funky"); | ||
} | ||
}) | ||
}); | ||
eventHandler.on('push', (event) => { | ||
t.is(event, 'funky') | ||
t.end() | ||
}) | ||
eventHandler.on("push", event => { | ||
t.is(event, "funky"); | ||
t.end(); | ||
}); | ||
eventHandler.receive({ | ||
id: '123', | ||
name: 'push', | ||
id: "123", | ||
name: "push", | ||
payload: pushEventPayload | ||
}) | ||
}) | ||
}); | ||
}); |
@@ -1,60 +0,56 @@ | ||
const EventEmitter = require('events') | ||
const Buffer = require('buffer').Buffer | ||
const EventEmitter = require("events"); | ||
const Buffer = require("buffer").Buffer; | ||
const test = require('tap').test | ||
const simple = require('simple-mock') | ||
const test = require("tap").test; | ||
const simple = require("simple-mock"); | ||
const createMiddleware = require('../../middleware') | ||
const createMiddleware = require("../../middleware"); | ||
const headers = { | ||
'x-github-delivery': '123e4567-e89b-12d3-a456-426655440000', | ||
'x-github-event': 'push', | ||
'x-hub-signature': 'sha1=f4d795e69b5d03c139cc6ea991ad3e5762d13e2f' | ||
} | ||
"x-github-delivery": "123e4567-e89b-12d3-a456-426655440000", | ||
"x-github-event": "push", | ||
"x-hub-signature": "sha1=f4d795e69b5d03c139cc6ea991ad3e5762d13e2f" | ||
}; | ||
test('Invalid payload', t => { | ||
const requestMock = new EventEmitter() | ||
requestMock.method = 'POST' | ||
requestMock.headers = headers | ||
requestMock.url = '/' | ||
test("Invalid payload", t => { | ||
const requestMock = new EventEmitter(); | ||
requestMock.method = "POST"; | ||
requestMock.headers = headers; | ||
requestMock.url = "/"; | ||
const responseMock = { | ||
end: simple.spy() | ||
} | ||
}; | ||
const middleware = createMiddleware({ secret: 'mysecret' }) | ||
middleware(requestMock, responseMock) | ||
const middleware = createMiddleware({ secret: "mysecret" }); | ||
middleware(requestMock, responseMock).then(() => { | ||
t.is(responseMock.statusCode, 400); | ||
t.is(responseMock.end.lastCall.arg, "SyntaxError: Invalid JSON"); | ||
t.end(); | ||
}); | ||
.then(() => { | ||
t.is(responseMock.statusCode, 400) | ||
t.is(responseMock.end.lastCall.arg, 'SyntaxError: Invalid JSON') | ||
t.end() | ||
}) | ||
requestMock.emit("data", Buffer.from("foo")); | ||
requestMock.emit("end"); | ||
}); | ||
requestMock.emit('data', Buffer.from('foo')) | ||
requestMock.emit('end') | ||
}) | ||
test("request error", t => { | ||
const requestMock = new EventEmitter(); | ||
requestMock.method = "POST"; | ||
requestMock.headers = headers; | ||
requestMock.url = "/"; | ||
test('request error', t => { | ||
const requestMock = new EventEmitter() | ||
requestMock.method = 'POST' | ||
requestMock.headers = headers | ||
requestMock.url = '/' | ||
const responseMock = { | ||
end: simple.spy() | ||
} | ||
}; | ||
const middleware = createMiddleware({ secret: 'mysecret' }) | ||
middleware(requestMock, responseMock) | ||
const middleware = createMiddleware({ secret: "mysecret" }); | ||
middleware(requestMock, responseMock).then(() => { | ||
t.is(responseMock.statusCode, 500); | ||
t.is(responseMock.end.lastCall.arg, "Error: oops"); | ||
.then(() => { | ||
t.is(responseMock.statusCode, 500) | ||
t.is(responseMock.end.lastCall.arg, 'Error: oops') | ||
t.end(); | ||
}); | ||
t.end() | ||
}) | ||
const error = new Error('oops') | ||
requestMock.emit('error', error) | ||
}) | ||
const error = new Error("oops"); | ||
requestMock.emit("error", error); | ||
}); |
@@ -1,36 +0,34 @@ | ||
const http = require('http') | ||
const http = require("http"); | ||
const axios = require('axios') | ||
const getPort = require('get-port') | ||
const promisify = require('pify') | ||
const simple = require('simple-mock') | ||
const Tap = require('tap') | ||
const axios = require("axios"); | ||
const getPort = require("get-port"); | ||
const promisify = require("pify"); | ||
const simple = require("simple-mock"); | ||
const Tap = require("tap"); | ||
const Webhooks = require('../../') | ||
const pushEventPayload = require('../fixtures/push-payload') | ||
const Webhooks = require("../../"); | ||
const pushEventPayload = require("../fixtures/push-payload"); | ||
const test = Tap.test | ||
const beforeEach = Tap.beforeEach | ||
const test = Tap.test; | ||
const beforeEach = Tap.beforeEach; | ||
beforeEach(() => { | ||
return getPort() | ||
return getPort().then(port => { | ||
this.port = port; | ||
}); | ||
}); | ||
.then((port) => { | ||
this.port = port | ||
}) | ||
}) | ||
test('initialised without options', (t) => { | ||
test("initialised without options", t => { | ||
try { | ||
Webhooks() | ||
t.fail('should throw error') | ||
Webhooks(); | ||
t.fail("should throw error"); | ||
} catch (error) { | ||
t.pass('throws errer if no "secret" option passed') | ||
t.pass('throws errer if no "secret" option passed'); | ||
} | ||
t.end() | ||
}) | ||
t.end(); | ||
}); | ||
test('GET /', (t) => { | ||
const api = new Webhooks({ secret: 'mysecret' }) | ||
const server = http.createServer(api.middleware) | ||
test("GET /", t => { | ||
const api = new Webhooks({ secret: "mysecret" }); | ||
const server = http.createServer(api.middleware); | ||
@@ -40,29 +38,29 @@ promisify(server.listen.bind(server))(this.port) | ||
.then(() => { | ||
return axios.get(`http://localhost:${this.port}`) | ||
return axios.get(`http://localhost:${this.port}`); | ||
}) | ||
.then(() => { | ||
t.fail('should return a 404') | ||
t.fail("should return a 404"); | ||
}) | ||
.catch(error => { | ||
t.is(error.response.status, 404) | ||
t.is(error.response.status, 404); | ||
}) | ||
.then(() => { | ||
server.close(t.end) | ||
server.close(t.end); | ||
}) | ||
.catch(t.error) | ||
}) | ||
.catch(t.error); | ||
}); | ||
test('POST / with push event payload', (t) => { | ||
t.plan(2) | ||
test("POST / with push event payload", t => { | ||
t.plan(2); | ||
const api = new Webhooks({ secret: 'mysecret' }) | ||
const server = http.createServer(api.middleware) | ||
const api = new Webhooks({ secret: "mysecret" }); | ||
const server = http.createServer(api.middleware); | ||
api.on('push', (event) => { | ||
t.is(event.id, '123e4567-e89b-12d3-a456-426655440000') | ||
}) | ||
api.on("push", event => { | ||
t.is(event.id, "123e4567-e89b-12d3-a456-426655440000"); | ||
}); | ||
@@ -74,7 +72,7 @@ promisify(server.listen.bind(server))(this.port) | ||
headers: { | ||
'X-GitHub-Delivery': '123e4567-e89b-12d3-a456-426655440000', | ||
'X-GitHub-Event': 'push', | ||
'X-Hub-Signature': 'sha1=f4d795e69b5d03c139cc6ea991ad3e5762d13e2f' | ||
"X-GitHub-Delivery": "123e4567-e89b-12d3-a456-426655440000", | ||
"X-GitHub-Event": "push", | ||
"X-Hub-Signature": "sha1=f4d795e69b5d03c139cc6ea991ad3e5762d13e2f" | ||
} | ||
}) | ||
}); | ||
}) | ||
@@ -85,33 +83,33 @@ | ||
.then(result => { | ||
t.is(result.status, 200) | ||
t.is(result.status, 200); | ||
}) | ||
.then(() => { | ||
server.close() | ||
server.close(); | ||
}) | ||
.catch(t.error) | ||
}) | ||
.catch(t.error); | ||
}); | ||
test('POST / with push event payload (request.body already parsed)', (t) => { | ||
t.plan(2) | ||
test("POST / with push event payload (request.body already parsed)", t => { | ||
t.plan(2); | ||
const api = new Webhooks({ secret: 'mysecret' }) | ||
const dataChunks = [] | ||
const api = new Webhooks({ secret: "mysecret" }); | ||
const dataChunks = []; | ||
const server = http.createServer((req, res) => { | ||
req.once('data', chunk => dataChunks.push(chunk)) | ||
req.once('end', () => { | ||
req.body = JSON.parse(Buffer.concat(dataChunks).toString()) | ||
api.middleware(req, res) | ||
req.once("data", chunk => dataChunks.push(chunk)); | ||
req.once("end", () => { | ||
req.body = JSON.parse(Buffer.concat(dataChunks).toString()); | ||
api.middleware(req, res); | ||
setTimeout(() => { | ||
res.statusCode = 500 | ||
res.end('Middleware timeout') | ||
}, 3000) | ||
}) | ||
}) | ||
res.statusCode = 500; | ||
res.end("Middleware timeout"); | ||
}, 3000); | ||
}); | ||
}); | ||
api.on('push', (event) => { | ||
t.is(event.id, '123e4567-e89b-12d3-a456-426655440000') | ||
}) | ||
api.on("push", event => { | ||
t.is(event.id, "123e4567-e89b-12d3-a456-426655440000"); | ||
}); | ||
@@ -123,7 +121,7 @@ promisify(server.listen.bind(server))(this.port) | ||
headers: { | ||
'X-GitHub-Delivery': '123e4567-e89b-12d3-a456-426655440000', | ||
'X-GitHub-Event': 'push', | ||
'X-Hub-Signature': 'sha1=f4d795e69b5d03c139cc6ea991ad3e5762d13e2f' | ||
"X-GitHub-Delivery": "123e4567-e89b-12d3-a456-426655440000", | ||
"X-GitHub-Event": "push", | ||
"X-Hub-Signature": "sha1=f4d795e69b5d03c139cc6ea991ad3e5762d13e2f" | ||
} | ||
}) | ||
}); | ||
}) | ||
@@ -134,17 +132,17 @@ | ||
.then(result => { | ||
t.is(result.status, 200) | ||
t.is(result.status, 200); | ||
}) | ||
.then(() => { | ||
server.close() | ||
server.close(); | ||
}) | ||
.catch(t.error) | ||
}) | ||
.catch(t.error); | ||
}); | ||
test('POST / with push event payload (no signature)', (t) => { | ||
const api = new Webhooks({ secret: 'mysecret' }) | ||
const server = http.createServer(api.middleware) | ||
const errorHandler = simple.spy() | ||
api.on('error', errorHandler) | ||
test("POST / with push event payload (no signature)", t => { | ||
const api = new Webhooks({ secret: "mysecret" }); | ||
const server = http.createServer(api.middleware); | ||
const errorHandler = simple.spy(); | ||
api.on("error", errorHandler); | ||
@@ -156,29 +154,29 @@ promisify(server.listen.bind(server))(this.port) | ||
headers: { | ||
'X-GitHub-Delivery': '123e4567-e89b-12d3-a456-426655440000', | ||
'X-GitHub-Event': 'push' | ||
"X-GitHub-Delivery": "123e4567-e89b-12d3-a456-426655440000", | ||
"X-GitHub-Event": "push" | ||
} | ||
}) | ||
}); | ||
}) | ||
.then(() => { | ||
t.fail('should return a 400') | ||
t.fail("should return a 400"); | ||
}) | ||
.catch(error => { | ||
t.is(error.response.status, 400) | ||
t.is(error.response.status, 400); | ||
}) | ||
.then(() => { | ||
t.is(errorHandler.callCount, 1, 'calls "error" event handler') | ||
server.close(t.end) | ||
t.is(errorHandler.callCount, 1, 'calls "error" event handler'); | ||
server.close(t.end); | ||
}) | ||
.catch(t.error) | ||
}) | ||
.catch(t.error); | ||
}); | ||
test('POST / with push event payload (invalid signature)', (t) => { | ||
const api = new Webhooks({ secret: 'mysecret' }) | ||
const server = http.createServer(api.middleware) | ||
const errorHandler = simple.spy() | ||
api.on('error', errorHandler) | ||
test("POST / with push event payload (invalid signature)", t => { | ||
const api = new Webhooks({ secret: "mysecret" }); | ||
const server = http.createServer(api.middleware); | ||
const errorHandler = simple.spy(); | ||
api.on("error", errorHandler); | ||
@@ -190,32 +188,32 @@ promisify(server.listen.bind(server))(this.port) | ||
headers: { | ||
'X-GitHub-Delivery': '123e4567-e89b-12d3-a456-426655440000', | ||
'X-GitHub-Event': 'push', | ||
'X-Hub-Signature': 'sha1=foo' | ||
"X-GitHub-Delivery": "123e4567-e89b-12d3-a456-426655440000", | ||
"X-GitHub-Event": "push", | ||
"X-Hub-Signature": "sha1=foo" | ||
} | ||
}) | ||
}); | ||
}) | ||
.then(() => { | ||
t.fail('should return a 400') | ||
t.fail("should return a 400"); | ||
}) | ||
.catch(error => { | ||
t.is(error.response.status, 400) | ||
t.is(error.response.status, 400); | ||
}) | ||
.then(() => { | ||
t.is(errorHandler.callCount, 1, 'calls "error" event handler') | ||
server.close(t.end) | ||
t.is(errorHandler.callCount, 1, 'calls "error" event handler'); | ||
server.close(t.end); | ||
}) | ||
.catch(t.error) | ||
}) | ||
.catch(t.error); | ||
}); | ||
test('POST / with hook error', (t) => { | ||
const api = new Webhooks({ secret: 'mysecret' }) | ||
const server = http.createServer(api.middleware) | ||
test("POST / with hook error", t => { | ||
const api = new Webhooks({ secret: "mysecret" }); | ||
const server = http.createServer(api.middleware); | ||
api.on('push', () => { | ||
throw new Error('Oops') | ||
}) | ||
api.on("push", () => { | ||
throw new Error("Oops"); | ||
}); | ||
@@ -227,22 +225,22 @@ promisify(server.listen.bind(server))(this.port) | ||
headers: { | ||
'X-GitHub-Delivery': '123e4567-e89b-12d3-a456-426655440000', | ||
'X-GitHub-Event': 'push', | ||
'X-Hub-Signature': 'sha1=f4d795e69b5d03c139cc6ea991ad3e5762d13e2f' | ||
"X-GitHub-Delivery": "123e4567-e89b-12d3-a456-426655440000", | ||
"X-GitHub-Event": "push", | ||
"X-Hub-Signature": "sha1=f4d795e69b5d03c139cc6ea991ad3e5762d13e2f" | ||
} | ||
}) | ||
}); | ||
}) | ||
.then(() => { | ||
t.fail('should return a 500') | ||
t.fail("should return a 500"); | ||
}) | ||
.catch(error => { | ||
t.is(error.response.status, 500) | ||
t.is(error.response.status, 500); | ||
}) | ||
.then(() => { | ||
server.close(t.end) | ||
server.close(t.end); | ||
}) | ||
.catch(t.error) | ||
}) | ||
.catch(t.error); | ||
}); |
@@ -1,37 +0,37 @@ | ||
const test = require('tap').test | ||
const test = require("tap").test; | ||
const sign = require('../../sign') | ||
const sign = require("../../sign"); | ||
const eventPayload = { | ||
foo: 'bar' | ||
} | ||
const secret = 'mysecret' | ||
foo: "bar" | ||
}; | ||
const secret = "mysecret"; | ||
test('sign() without options throws', (t) => { | ||
t.throws(sign) | ||
t.end() | ||
}) | ||
test("sign() without options throws", t => { | ||
t.throws(sign); | ||
t.end(); | ||
}); | ||
test('sign(undefined, eventPayload) without secret throws', (t) => { | ||
t.throws(sign.bind(null, undefined, eventPayload)) | ||
t.end() | ||
}) | ||
test("sign(undefined, eventPayload) without secret throws", t => { | ||
t.throws(sign.bind(null, undefined, eventPayload)); | ||
t.end(); | ||
}); | ||
test('sign(secret) without eventPayload throws', (t) => { | ||
t.throws(sign.bind(null, secret)) | ||
t.end() | ||
}) | ||
test("sign(secret) without eventPayload throws", t => { | ||
t.throws(sign.bind(null, secret)); | ||
t.end(); | ||
}); | ||
test('sign(secret, eventPayload) with eventPayload as object returns expected signature', (t) => { | ||
const signature = sign(secret, eventPayload) | ||
t.is(signature, 'sha1=d03207e4b030cf234e3447bac4d93add4c6643d8') | ||
test("sign(secret, eventPayload) with eventPayload as object returns expected signature", t => { | ||
const signature = sign(secret, eventPayload); | ||
t.is(signature, "sha1=d03207e4b030cf234e3447bac4d93add4c6643d8"); | ||
t.end() | ||
}) | ||
t.end(); | ||
}); | ||
test('sign(secret, eventPayload) with eventPayload as string returns expected signature', (t) => { | ||
const signature = sign(secret, JSON.stringify(eventPayload)) | ||
t.is(signature, 'sha1=d03207e4b030cf234e3447bac4d93add4c6643d8') | ||
test("sign(secret, eventPayload) with eventPayload as string returns expected signature", t => { | ||
const signature = sign(secret, JSON.stringify(eventPayload)); | ||
t.is(signature, "sha1=d03207e4b030cf234e3447bac4d93add4c6643d8"); | ||
t.end() | ||
}) | ||
t.end(); | ||
}); |
@@ -1,48 +0,48 @@ | ||
const test = require('tap').test | ||
const test = require("tap").test; | ||
test('@octokit/webhooks', (t) => { | ||
const Webhooks = require('../..') | ||
const api = new Webhooks({ secret: 'mysecret' }) | ||
test("@octokit/webhooks", t => { | ||
const Webhooks = require("../.."); | ||
const api = new Webhooks({ secret: "mysecret" }); | ||
t.type(api.sign, 'function') | ||
t.type(api.verify, 'function') | ||
t.type(api.on, 'function') | ||
t.type(api.removeListener, 'function') | ||
t.type(api.receive, 'function') | ||
t.type(api.middleware, 'function') | ||
t.type(api.verifyAndReceive, 'function') | ||
t.type(api.sign, "function"); | ||
t.type(api.verify, "function"); | ||
t.type(api.on, "function"); | ||
t.type(api.removeListener, "function"); | ||
t.type(api.receive, "function"); | ||
t.type(api.middleware, "function"); | ||
t.type(api.verifyAndReceive, "function"); | ||
t.end() | ||
}) | ||
t.end(); | ||
}); | ||
test('require("@octokit/webhooks/sign")', (t) => { | ||
test('require("@octokit/webhooks/sign")', t => { | ||
t.doesNotThrow(() => { | ||
require('../../sign') | ||
}) | ||
require("../../sign"); | ||
}); | ||
t.end() | ||
}) | ||
t.end(); | ||
}); | ||
test('require("@octokit/webhooks/verify")', (t) => { | ||
test('require("@octokit/webhooks/verify")', t => { | ||
t.doesNotThrow(() => { | ||
require('../../verify') | ||
}) | ||
require("../../verify"); | ||
}); | ||
t.end() | ||
}) | ||
t.end(); | ||
}); | ||
test('require("@octokit/webhooks/event-handler")', (t) => { | ||
test('require("@octokit/webhooks/event-handler")', t => { | ||
t.doesNotThrow(() => { | ||
require('../../event-handler') | ||
}) | ||
require("../../event-handler"); | ||
}); | ||
t.end() | ||
}) | ||
t.end(); | ||
}); | ||
test('require("@octokit/webhooks/middleware")', (t) => { | ||
test('require("@octokit/webhooks/middleware")', t => { | ||
t.doesNotThrow(() => { | ||
require('../../middleware') | ||
}) | ||
require("../../middleware"); | ||
}); | ||
t.end() | ||
}) | ||
t.end(); | ||
}); |
@@ -1,68 +0,80 @@ | ||
const test = require('tap').test | ||
const test = require("tap").test; | ||
const verify = require('../../verify') | ||
const verify = require("../../verify"); | ||
const eventPayload = { | ||
foo: 'bar' | ||
} | ||
const secret = 'mysecret' | ||
const signature = 'sha1=d03207e4b030cf234e3447bac4d93add4c6643d8' | ||
foo: "bar" | ||
}; | ||
const secret = "mysecret"; | ||
const signature = "sha1=d03207e4b030cf234e3447bac4d93add4c6643d8"; | ||
test('verify() without options throws', (t) => { | ||
t.throws(verify) | ||
t.end() | ||
}) | ||
test("verify() without options throws", t => { | ||
t.throws(verify); | ||
t.end(); | ||
}); | ||
test('verify(undefined, eventPayload) without secret throws', (t) => { | ||
t.throws(verify.bind(null, undefined, eventPayload)) | ||
t.end() | ||
}) | ||
test("verify(undefined, eventPayload) without secret throws", t => { | ||
t.throws(verify.bind(null, undefined, eventPayload)); | ||
t.end(); | ||
}); | ||
test('verify(secret) without eventPayload throws', (t) => { | ||
t.throws(verify.bind(null, secret)) | ||
t.end() | ||
}) | ||
test("verify(secret) without eventPayload throws", t => { | ||
t.throws(verify.bind(null, secret)); | ||
t.end(); | ||
}); | ||
test('verify(secret, eventPayload) without options.signature throws', (t) => { | ||
t.throws(verify.bind(null, secret, eventPayload)) | ||
t.end() | ||
}) | ||
test("verify(secret, eventPayload) without options.signature throws", t => { | ||
t.throws(verify.bind(null, secret, eventPayload)); | ||
t.end(); | ||
}); | ||
test('verify(secret, eventPayload, signature) returns true for correct signature', (t) => { | ||
const signatureMatches = verify(secret, eventPayload, signature) | ||
t.is(signatureMatches, true) | ||
test("verify(secret, eventPayload, signature) returns true for correct signature", t => { | ||
const signatureMatches = verify(secret, eventPayload, signature); | ||
t.is(signatureMatches, true); | ||
t.end() | ||
}) | ||
t.end(); | ||
}); | ||
test('verify(secret, eventPayload, signature) returns false for incorrect signature', (t) => { | ||
const signatureMatches = verify(secret, eventPayload, 'foo') | ||
t.is(signatureMatches, false) | ||
test("verify(secret, eventPayload, signature) returns false for incorrect signature", t => { | ||
const signatureMatches = verify(secret, eventPayload, "foo"); | ||
t.is(signatureMatches, false); | ||
t.end() | ||
}) | ||
t.end(); | ||
}); | ||
test('verify(secret, eventPayload, signature) returns false for correct secret', (t) => { | ||
const signatureMatches = verify('foo', eventPayload, signature) | ||
t.is(signatureMatches, false) | ||
test("verify(secret, eventPayload, signature) returns false for correct secret", t => { | ||
const signatureMatches = verify("foo", eventPayload, signature); | ||
t.is(signatureMatches, false); | ||
t.end() | ||
}) | ||
t.end(); | ||
}); | ||
test('verify(secret, eventPayload, signature) returns true if eventPayload contains (#71)', (t) => { | ||
test("verify(secret, eventPayload, signature) returns true if eventPayload contains (#71)", t => { | ||
// https://github.com/octokit/webhooks.js/issues/71 | ||
const signatureMatchesLowerCaseSequence = verify('development', { | ||
foo: 'Foo\n\u001b[34mbar: ♥♥♥♥♥♥♥♥\nthis-is-lost\u001b[0m\u001b[2K' | ||
}, 'sha1=7316ec5e7866e42e4aba4af550d21a5f036f949d') | ||
t.is(signatureMatchesLowerCaseSequence, true) | ||
const signatureMatchesUpperCaseSequence = verify('development', { | ||
foo: 'Foo\n\u001B[34mbar: ♥♥♥♥♥♥♥♥\nthis-is-lost\u001B[0m\u001B[2K' | ||
}, 'sha1=7316ec5e7866e42e4aba4af550d21a5f036f949d') | ||
t.is(signatureMatchesUpperCaseSequence, true) | ||
const signatureMatchesEscapedSequence = verify('development', { | ||
foo: '\\u001b' | ||
}, 'sha1=2c440a176f4cb84c8c921dfee882d594c2465097') | ||
t.is(signatureMatchesEscapedSequence, true) | ||
const signatureMatchesLowerCaseSequence = verify( | ||
"development", | ||
{ | ||
foo: "Foo\n\u001b[34mbar: ♥♥♥♥♥♥♥♥\nthis-is-lost\u001b[0m\u001b[2K" | ||
}, | ||
"sha1=7316ec5e7866e42e4aba4af550d21a5f036f949d" | ||
); | ||
t.is(signatureMatchesLowerCaseSequence, true); | ||
const signatureMatchesUpperCaseSequence = verify( | ||
"development", | ||
{ | ||
foo: "Foo\n\u001B[34mbar: ♥♥♥♥♥♥♥♥\nthis-is-lost\u001B[0m\u001B[2K" | ||
}, | ||
"sha1=7316ec5e7866e42e4aba4af550d21a5f036f949d" | ||
); | ||
t.is(signatureMatchesUpperCaseSequence, true); | ||
const signatureMatchesEscapedSequence = verify( | ||
"development", | ||
{ | ||
foo: "\\u001b" | ||
}, | ||
"sha1=2c440a176f4cb84c8c921dfee882d594c2465097" | ||
); | ||
t.is(signatureMatchesEscapedSequence, true); | ||
t.end() | ||
}) | ||
t.end(); | ||
}); |
@@ -1,3 +0,3 @@ | ||
import * as Webhooks from '..' | ||
import http from 'http' | ||
import * as Webhooks from ".."; | ||
import http from "http"; | ||
@@ -8,26 +8,25 @@ // ************************************************************ | ||
export default async function() { | ||
// Check empty constructor | ||
new Webhooks() | ||
// Check empty constructor | ||
new Webhooks(); | ||
// Check that all options are optional except for secret | ||
new Webhooks({ | ||
secret: 'bleh' | ||
}) | ||
// Check that all options are optional except for secret | ||
new Webhooks({ | ||
secret: "bleh" | ||
}); | ||
// Check all supported options | ||
const webhooks = new Webhooks({ | ||
secret: 'bleh', | ||
path: '/webhooks', | ||
transform: (event) => event | ||
}) | ||
// Check all supported options | ||
const webhooks = new Webhooks({ | ||
secret: "bleh", | ||
path: "/webhooks", | ||
transform: event => event | ||
}); | ||
webhooks.on('*', ({id, name, payload}) => { | ||
console.log(name, 'event received') | ||
const sig = webhooks.sign(payload) | ||
webhooks.verify(payload, sig) | ||
}) | ||
webhooks.on("*", ({ id, name, payload }) => { | ||
console.log(name, "event received"); | ||
const sig = webhooks.sign(payload); | ||
webhooks.verify(payload, sig); | ||
}); | ||
http.createServer(webhooks.middleware).listen(3000) | ||
http.createServer(webhooks.middleware).listen(3000); | ||
} |
@@ -1,20 +0,23 @@ | ||
const simple = require('simple-mock') | ||
const test = require('tap').test | ||
const simple = require("simple-mock"); | ||
const test = require("tap").test; | ||
const receiverOn = require('../../event-handler/on') | ||
const receiverOn = require("../../event-handler/on"); | ||
function noop () {} | ||
function noop() {} | ||
const state = { | ||
hooks: [] | ||
} | ||
}; | ||
test('receiver.on with invalid event name', t => { | ||
simple.mock(console, 'warn').callFn(function () {}) | ||
receiverOn(state, 'foo', noop) | ||
t.equals(console.warn.callCount, 1) | ||
t.equals(console.warn.lastCall.arg, '"foo" is not a known webhook name (https://developer.github.com/v3/activity/events/types/)') | ||
test("receiver.on with invalid event name", t => { | ||
simple.mock(console, "warn").callFn(function() {}); | ||
receiverOn(state, "foo", noop); | ||
t.equals(console.warn.callCount, 1); | ||
t.equals( | ||
console.warn.lastCall.arg, | ||
'"foo" is not a known webhook name (https://developer.github.com/v3/activity/events/types/)' | ||
); | ||
simple.restore() | ||
t.end() | ||
}) | ||
simple.restore(); | ||
t.end(); | ||
}); |
@@ -1,29 +0,29 @@ | ||
const test = require('tap').test | ||
const test = require("tap").test; | ||
const receive = require('../../event-handler/receive') | ||
const receive = require("../../event-handler/receive"); | ||
const state = { | ||
secret: 'mysecret', | ||
secret: "mysecret", | ||
hooks: [] | ||
} | ||
}; | ||
test('options: none', t => { | ||
test("options: none", t => { | ||
t.throws(() => { | ||
receive(state) | ||
}) | ||
t.end() | ||
}) | ||
receive(state); | ||
}); | ||
t.end(); | ||
}); | ||
test('options: name', t => { | ||
test("options: name", t => { | ||
t.throws(() => { | ||
receive(state, { name: 'foo' }) | ||
}) | ||
t.end() | ||
}) | ||
receive(state, { name: "foo" }); | ||
}); | ||
t.end(); | ||
}); | ||
test('options: name, payload', t => { | ||
test("options: name, payload", t => { | ||
t.doesNotThrow(() => { | ||
receive(state, { name: 'foo', payload: {} }) | ||
}) | ||
t.end() | ||
}) | ||
receive(state, { name: "foo", payload: {} }); | ||
}); | ||
t.end(); | ||
}); |
@@ -1,7 +0,7 @@ | ||
const test = require('tap').test | ||
const test = require("tap").test; | ||
const removeListener = require('../../event-handler/remove-listener') | ||
const removeListener = require("../../event-handler/remove-listener"); | ||
test('remove-listener: single listener', t => { | ||
const push = () => {} | ||
test("remove-listener: single listener", t => { | ||
const push = () => {}; | ||
@@ -12,17 +12,17 @@ const state = { | ||
} | ||
} | ||
}; | ||
t.doesNotThrow(() => { | ||
removeListener(state, 'push', push) | ||
}) | ||
t.deepEqual(state, { hooks: { push: [] } }) | ||
t.end() | ||
}) | ||
removeListener(state, "push", push); | ||
}); | ||
t.deepEqual(state, { hooks: { push: [] } }); | ||
t.end(); | ||
}); | ||
test('remove-listener: multiple listeners', t => { | ||
const push1 = () => {} | ||
const push2 = () => {} | ||
const push3 = () => {} | ||
test("remove-listener: multiple listeners", t => { | ||
const push1 = () => {}; | ||
const push2 = () => {}; | ||
const push3 = () => {}; | ||
const ping = () => {} | ||
const ping = () => {}; | ||
@@ -34,11 +34,11 @@ const state = { | ||
} | ||
} | ||
}; | ||
t.doesNotThrow(() => { | ||
removeListener(state, 'push', push1) | ||
removeListener(state, 'push', push2) | ||
removeListener(state, 'push', push3) | ||
}) | ||
t.deepEqual(state, { hooks: { push: [], ping: [ping] } }) | ||
t.end() | ||
}) | ||
removeListener(state, "push", push1); | ||
removeListener(state, "push", push2); | ||
removeListener(state, "push", push3); | ||
}); | ||
t.deepEqual(state, { hooks: { push: [], ping: [ping] } }); | ||
t.end(); | ||
}); |
@@ -1,35 +0,35 @@ | ||
const simple = require('simple-mock') | ||
const test = require('tap').test | ||
const simple = require("simple-mock"); | ||
const test = require("tap").test; | ||
const wrapErrorHandler = require('../../event-handler/wrap-error-handler') | ||
const wrapErrorHandler = require("../../event-handler/wrap-error-handler"); | ||
test('error thrown in error handler', t => { | ||
t.plan(2) | ||
test("error thrown in error handler", t => { | ||
t.plan(2); | ||
const messages = [] | ||
simple.mock(console, 'log', messages.push.bind(messages)) | ||
const messages = []; | ||
simple.mock(console, "log", messages.push.bind(messages)); | ||
t.doesNotThrow(() => { | ||
wrapErrorHandler(() => { | ||
throw new Error('oopsydoopsy') | ||
}, new Error('oops')) | ||
}) | ||
throw new Error("oopsydoopsy"); | ||
}, new Error("oops")); | ||
}); | ||
t.ok(messages.find(message => /FATAL/.test(message))) | ||
simple.restore() | ||
}) | ||
t.ok(messages.find(message => /FATAL/.test(message))); | ||
simple.restore(); | ||
}); | ||
test('error handler returns rejected Error', t => { | ||
t.plan(2) | ||
test("error handler returns rejected Error", t => { | ||
t.plan(2); | ||
const messages = [] | ||
simple.mock(console, 'log', messages.push.bind(messages)) | ||
const promise = Promise.reject(new Error('oopsydoopsy')) | ||
const messages = []; | ||
simple.mock(console, "log", messages.push.bind(messages)); | ||
const promise = Promise.reject(new Error("oopsydoopsy")); | ||
t.doesNotThrow(() => { | ||
wrapErrorHandler(() => promise, new Error('oops')) | ||
}) | ||
wrapErrorHandler(() => promise, new Error("oops")); | ||
}); | ||
promise.catch(() => { | ||
t.ok(messages.find(message => /FATAL/.test(message))) | ||
simple.restore() | ||
}) | ||
}) | ||
t.ok(messages.find(message => /FATAL/.test(message))); | ||
simple.restore(); | ||
}); | ||
}); |
@@ -1,8 +0,8 @@ | ||
const test = require('tap').test | ||
const test = require("tap").test; | ||
const Middleware = require('../../middleware') | ||
const Middleware = require("../../middleware"); | ||
test('options: none', t => { | ||
t.throws(Middleware) | ||
t.end() | ||
}) | ||
test("options: none", t => { | ||
t.throws(Middleware); | ||
t.end(); | ||
}); |
@@ -1,13 +0,13 @@ | ||
const simple = require('simple-mock') | ||
const test = require('tap').test | ||
const simple = require("simple-mock"); | ||
const test = require("tap").test; | ||
const middleware = require('../../middleware/middleware') | ||
const state = {} | ||
const middleware = require("../../middleware/middleware"); | ||
const state = {}; | ||
test('next() callback', t => { | ||
const next = simple.spy() | ||
test("next() callback", t => { | ||
const next = simple.spy(); | ||
middleware(state, { method: 'POST', url: '/foo' }, {}, next) | ||
t.is(next.callCount, 1) | ||
t.end() | ||
}) | ||
middleware(state, { method: "POST", url: "/foo" }, {}, next); | ||
t.is(next.callCount, 1); | ||
t.end(); | ||
}); |
@@ -1,26 +0,26 @@ | ||
module.exports = verify | ||
module.exports = verify; | ||
const crypto = require('crypto') | ||
const Buffer = require('buffer').Buffer | ||
const crypto = require("crypto"); | ||
const Buffer = require("buffer").Buffer; | ||
const sign = require('../sign') | ||
const sign = require("../sign"); | ||
function verify (secret, eventPayload, signature) { | ||
function verify(secret, eventPayload, signature) { | ||
if (!secret || !eventPayload || !signature) { | ||
throw new TypeError('secret, eventPayload & signature required') | ||
throw new TypeError("secret, eventPayload & signature required"); | ||
} | ||
const signatureBuffer = Buffer.from(signature) | ||
const verificationBuffer = Buffer.from(sign(secret, eventPayload)) | ||
const signatureBuffer = Buffer.from(signature); | ||
const verificationBuffer = Buffer.from(sign(secret, eventPayload)); | ||
if (signatureBuffer.length !== verificationBuffer.length) { | ||
return false | ||
return false; | ||
} | ||
return timingSafeEqual(signatureBuffer, verificationBuffer) | ||
return timingSafeEqual(signatureBuffer, verificationBuffer); | ||
} | ||
/* istanbul ignore next */ | ||
function timingSafeEqual (signatureBuffer, verificationBuffer) { | ||
return crypto.timingSafeEqual(signatureBuffer, verificationBuffer) | ||
function timingSafeEqual(signatureBuffer, verificationBuffer) { | ||
return crypto.timingSafeEqual(signatureBuffer, verificationBuffer); | ||
} |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
206660
14
6020
591