twitch-webhook
Advanced tools
Comparing version 1.1.1 to 1.1.2
@@ -7,2 +7,3 @@ { | ||
"plugins": ["mocha"], | ||
"extends": "standard", | ||
"rules": { | ||
@@ -9,0 +10,0 @@ "mocha/no-exclusive-tests": "error" |
{ | ||
"name": "twitch-webhook", | ||
"version": "1.1.1", | ||
"version": "1.1.2", | ||
"description": "A Node JS library for new Twitch Helix API Webhooks", | ||
@@ -42,3 +42,8 @@ "main": "src/index.js", | ||
"coveralls": "^3.0.0", | ||
"eslint-config-standard": "^11.0.0-beta.0", | ||
"eslint-plugin-import": "^2.8.0", | ||
"eslint-plugin-mocha": "^4.11.0", | ||
"eslint-plugin-node": "^5.2.1", | ||
"eslint-plugin-promise": "^3.6.0", | ||
"eslint-plugin-standard": "^3.0.1", | ||
"istanbul": "^0.4.5", | ||
@@ -59,2 +64,2 @@ "jsdoc": "^3.5.5", | ||
} | ||
} | ||
} |
'use strict' | ||
/** | ||
* Twitch error | ||
* Base error | ||
* | ||
* @extends Error | ||
*/ | ||
class FatalError extends Error { | ||
class BaseError extends Error {} | ||
/** | ||
* Library error | ||
* | ||
* @extends BaseError | ||
*/ | ||
class FatalError extends BaseError { | ||
/** | ||
* Constructs an instance of FatalError | ||
* | ||
* @param {string|Error} error | ||
* @param {string|Error} error - Error or error message | ||
*/ | ||
@@ -25,2 +34,3 @@ constructor (error) { | ||
* Access error | ||
* | ||
* @extends FatalError | ||
@@ -30,7 +40,8 @@ */ | ||
/** | ||
* Constructs an instance of RequestDenied | ||
* | ||
* @param {Object} response | ||
* @param {Object} response - Response | ||
*/ | ||
constructor (response) { | ||
super(`Invalid response status code ${response.statusCode}`) | ||
super(response) | ||
@@ -41,5 +52,24 @@ this.response = response | ||
/** | ||
* Webhook error | ||
* | ||
* @extends BaseError | ||
*/ | ||
class WebhookError extends BaseError { | ||
/** | ||
* Constructs an instance of FatalError | ||
* | ||
* @param {string} message - Error message | ||
*/ | ||
constructor (message) { | ||
super(message) | ||
Error.captureStackTrace(this) | ||
} | ||
} | ||
module.exports = { | ||
BaseError, | ||
FatalError, | ||
RequestDenied | ||
RequestDenied, | ||
WebhookError | ||
} |
@@ -55,3 +55,3 @@ const errors = require('./errors') | ||
} | ||
this._options.listen = options.listen || {} | ||
@@ -64,3 +64,8 @@ this._options.listen.host = options.listen.host || '0.0.0.0' | ||
this._apiUrl = options.baseApiUrl || 'https://api.twitch.tv/helix/' | ||
if (this._apiUrl.substr(-1) !== '/') { | ||
this._apiUrl += '/' | ||
} | ||
this._hubUrl = this._apiUrl + 'webhooks/hub' | ||
this._apiPathname = url.parse(this._apiUrl).pathname | ||
@@ -121,10 +126,4 @@ this._secrets = {} | ||
return new Promise((resolve, reject) => { | ||
this._server.close(err => { | ||
if (err) { | ||
return reject(err) | ||
} | ||
return resolve() | ||
}) | ||
return new Promise(resolve => { | ||
this._server.close(() => resolve()) | ||
}) | ||
@@ -174,3 +173,3 @@ } | ||
if (this._options.secret) { | ||
let secret = crypto | ||
const secret = crypto | ||
.createHmac('sha256', this._options.secret) | ||
@@ -244,3 +243,3 @@ .update(topic) | ||
delete this._secrets[queries['hub.topic']] // Yes, it's needed by design | ||
case "subscribe": // eslint-disable-line | ||
case 'subscribe': // eslint-disable-line no-fallthrough | ||
response.writeHead(200, { 'Content-Type': 'text/plain' }) | ||
@@ -290,4 +289,6 @@ response.end(queries['hub.challenge']) | ||
const endpoint = links && links.self && links.self.url | ||
const topic = endpoint && url.parse(endpoint, true).pathname.replace('/helix/', '') | ||
const topic = endpoint && url.parse(endpoint, true).pathname.replace(this._apiPathname, '') | ||
if (!endpoint || !topic) { | ||
this.emit('webhook-error', new errors.WebhookError('Topic is missing or incorrect')) | ||
response.writeHead(202, { 'Content-Type': 'text/plain' }) | ||
@@ -304,3 +305,4 @@ response.end() | ||
if (!signature) { | ||
if (!signature || !this._secrets[endpoint]) { | ||
this.emit('webhook-error', new errors.WebhookError('"x-hub-signature" is missing')) | ||
response.writeHead(202, { 'Content-Type': 'text/plain' }) | ||
@@ -319,2 +321,3 @@ response.end() | ||
body = '' | ||
this.emit('webhook-error', new errors.WebhookError('Request is very large')) | ||
response.writeHead(202, { 'Content-Type': 'text/plain' }) | ||
@@ -331,2 +334,3 @@ response.end() | ||
} catch (err) { | ||
this.emit('webhook-error', new errors.WebhookError('JSON is malformed')) | ||
response.writeHead(202, { 'Content-Type': 'text/plain' }) | ||
@@ -338,11 +342,9 @@ response.end() | ||
if (this._options.secret) { | ||
let storedSign | ||
if (this._secrets[endpoint]) { | ||
storedSign = crypto | ||
.createHmac('sha256', this._secrets[endpoint]) | ||
.update(body) | ||
.digest('hex') | ||
} | ||
let storedSign = crypto | ||
.createHmac('sha256', this._secrets[endpoint]) | ||
.update(body) | ||
.digest('hex') | ||
if (storedSign !== signature) { | ||
this.emit('webhook-error', new errors.WebhookError('"x-hub-signature" is incorrect')) | ||
response.writeHead(202, { 'Content-Type': 'text/plain' }) | ||
@@ -349,0 +351,0 @@ response.end() |
const assert = require('assert') | ||
const http = require('http') | ||
const request = require('request-promise') | ||
@@ -9,3 +10,3 @@ const errors = require('request-promise/errors') | ||
return request(requestOptions); | ||
return request(requestOptions) | ||
} | ||
@@ -39,3 +40,3 @@ | ||
.finally(status => { | ||
if (status == false) { | ||
if (status === false) { | ||
throw new Error('cannot start listening if "autoStart" is false') | ||
@@ -46,2 +47,22 @@ } | ||
let requests = [] | ||
function startMockedServer (port) { | ||
const server = http.createServer((request, response) => { | ||
requests.push(request.url) | ||
response.writeHead(202, { 'Content-Type': 'text/plain' }) | ||
response.end() | ||
}) | ||
server.unref() | ||
return new Promise((resolve, reject) => { | ||
server.on('error', reject).listen(port, resolve) | ||
}) | ||
} | ||
function checkRequestToMockedServer (callback) { | ||
if (requests.findIndex(callback) === -1) { | ||
throw new Error('request does not exist') | ||
} | ||
} | ||
module.exports = { | ||
@@ -51,3 +72,5 @@ sendRequest, | ||
hasStartedListening, | ||
hasStoppedListening | ||
hasStoppedListening, | ||
startMockedServer, | ||
checkRequestToMockedServer | ||
} |
@@ -6,18 +6,26 @@ const TwitchWebhook = require('../src/index') | ||
const Promise = require('bluebird') | ||
const request = require('request-promise') | ||
const crypto = require('crypto') | ||
const url = require('url') | ||
const client_id = process.env.CLIENT_ID | ||
const clientId = process.env.CLIENT_ID | ||
if (!client_id) { | ||
if (!clientId) { | ||
throw new Error('Twitch Client ID not provided') | ||
} | ||
const port = process.env.PORT || 9108 | ||
const freePort = port + 1 | ||
const callback = process.env.CALLBACK || 'https://216.58.210.174/' // Google IP :) | ||
let defaultPort = process.env.PORT || 9108 | ||
const webhookPort = defaultPort++ | ||
const testPort = defaultPort++ | ||
const securePort = defaultPort++ | ||
const apiPort = defaultPort++ | ||
const offlinePort = defaultPort++ | ||
const timeout = 10 * 1000 | ||
const secret = 'test secret :)' | ||
const callback = process.env.CALLBACK || 'https://216.58.210.174/' | ||
describe('TwitchWebhook', () => { | ||
let twitchWebhook | ||
let testWebhook | ||
let secureWebhook | ||
let offlineWebhook | ||
@@ -27,23 +35,53 @@ | ||
twitchWebhook = new TwitchWebhook({ | ||
client_id, | ||
client_id: clientId, | ||
callback, | ||
listen: { | ||
host: '127.0.0.1', | ||
port | ||
} | ||
port: webhookPort | ||
}, | ||
lease_seconds: 0 | ||
}) | ||
offlineWebhook = new TwitchWebhook({ | ||
client_id, | ||
client_id: clientId, | ||
callback, | ||
listen: { | ||
host: '127.0.0.1', | ||
port: freePort, | ||
port: offlinePort, | ||
autoStart: false | ||
} | ||
}, | ||
lease_seconds: 0 | ||
}) | ||
}) | ||
before(() => { | ||
return helpers.startMockedServer(apiPort) | ||
.then(() => { | ||
testWebhook = new TwitchWebhook({ | ||
client_id: clientId, | ||
callback, | ||
listen: { | ||
host: '127.0.0.1', | ||
port: testPort | ||
}, | ||
baseApiUrl: `http://127.0.0.1:${apiPort}/` | ||
}) | ||
secureWebhook = new TwitchWebhook({ | ||
client_id: clientId, | ||
callback, | ||
secret, | ||
listen: { | ||
host: '127.0.0.1', | ||
port: securePort | ||
}, | ||
baseApiUrl: `http://127.0.0.1:${apiPort}/` | ||
}) | ||
}) | ||
}) | ||
it('should contain errors', (done) => { | ||
assert(twitchWebhook.errors instanceof Object); | ||
assert(twitchWebhook.errors instanceof Object) | ||
assert(twitchWebhook.errors.FatalError === errors.FatalError) | ||
assert(twitchWebhook.errors.RequestDenied === errors.RequestDenied) | ||
done() | ||
@@ -54,6 +92,9 @@ }) | ||
try { | ||
let testWebhook = new TwitchWebhook(); | ||
new TwitchWebhook({ // eslint-disable-line no-new | ||
listen: false | ||
}) | ||
done(new Error('expected error')) | ||
} catch (err) { | ||
assert(err instanceof errors.FatalError); | ||
assert(err instanceof errors.FatalError) | ||
assert(err.message === 'Twitch Client ID not provided!') | ||
done() | ||
@@ -65,8 +106,10 @@ } | ||
try { | ||
let testWebhook = new TwitchWebhook({ | ||
client_id | ||
}); | ||
new TwitchWebhook({ // eslint-disable-line no-new | ||
client_id: clientId, | ||
listen: false | ||
}) | ||
done(new Error('expected error')) | ||
} catch (err) { | ||
assert(err instanceof errors.FatalError); | ||
assert(err instanceof errors.FatalError) | ||
assert(err.message === 'Callback URL not provided!') | ||
done() | ||
@@ -76,13 +119,69 @@ } | ||
it('should automaticaly start listening by default', () => { | ||
assert.equal(twitchWebhook.isListening(), true) | ||
return helpers.hasStartedListening(`http://127.0.0.1:${port}`) | ||
assert(twitchWebhook.isListening()) | ||
return helpers.hasStartedListening(`http://127.0.0.1:${webhookPort}`) | ||
}) | ||
it('should not automaticaly start listening if "autoStart" is false', () => { | ||
assert.equal(offlineWebhook.isListening(), false) | ||
return helpers.hasStoppedListening(`http://127.0.0.1:${freePort}`) | ||
assert(offlineWebhook.isListening() === false) | ||
return helpers.hasStoppedListening(`http://127.0.0.1:${offlinePort}`) | ||
}) | ||
it('should set "host" and "post" if one of them is undefined', (done) => { | ||
const tempWebhook = new TwitchWebhook({ | ||
client_id: clientId, | ||
callback, | ||
lease_seconds: 0 | ||
}) | ||
tempWebhook.on('listening', () => { | ||
helpers.hasStartedListening(`http://0.0.0.0:8443/`) | ||
.then(() => done()) | ||
.catch(done) | ||
.finally(() => tempWebhook.close()) | ||
}) | ||
}) | ||
it('should add trailing slash to base url of api if it does not exist', () => { | ||
const tempWebhook = new TwitchWebhook({ | ||
client_id: clientId, | ||
callback, | ||
lease_seconds: 0, | ||
baseApiUrl: `http://127.0.0.1:${apiPort}` | ||
}) | ||
tempWebhook.subscribe('test') | ||
.catch((err) => { | ||
throw new Error('unexpected error in #subscribe: ' + err.message) | ||
}) | ||
.then(() => { | ||
helpers.checkRequestToMockedServer((element) => { | ||
const params = url.parse(element, true).query | ||
return params['hub.topic'] === `http://127.0.0.1:${apiPort}/test` | ||
}) | ||
}) | ||
.finally(() => tempWebhook.close()) | ||
}) | ||
it('should add trailing slash to callback if it does not exist', () => { | ||
const tempWebhook = new TwitchWebhook({ | ||
client_id: clientId, | ||
callback: `http://127.0.0.1:${offlinePort}`, | ||
lease_seconds: 0, | ||
baseApiUrl: `http://127.0.0.1:${apiPort}` | ||
}) | ||
tempWebhook.subscribe('test') | ||
.catch((err) => { | ||
throw new Error('unexpected error in #subscribe: ' + err.message) | ||
}) | ||
.then(() => { | ||
helpers.checkRequestToMockedServer((element) => { | ||
const params = url.parse(element, true).query | ||
return params['hub.callback'] === `http://127.0.0.1:${offlinePort}/` | ||
}) | ||
}) | ||
.finally(() => tempWebhook.close()) | ||
}) | ||
describe('webhook', () => { | ||
@@ -93,3 +192,3 @@ describe('GET method', () => { | ||
{ | ||
url: `http://127.0.0.1:${port}` | ||
url: `http://127.0.0.1:${testPort}` | ||
}, | ||
@@ -100,10 +199,8 @@ 400 | ||
it('returns 200 response code if request with denied status was received', () => { | ||
it('returns 200 response code if "hub.mode" query is "denied"', () => { | ||
return helpers.checkResponseCode( | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
url: `http://127.0.0.1:${testPort}`, | ||
qs: { | ||
'hub.mode': 'denied', | ||
'hub.topic': 'https://api.twitch.tv/helix/users/follows?to_id=1337', | ||
'hub.reason': 'unauthorized' | ||
'hub.mode': 'denied' | ||
} | ||
@@ -115,24 +212,23 @@ }, | ||
it('returns 200 response code and "hub.challenge" if the subscribe or unsubscribe request was received', () => { | ||
it('returns 200 response code and "hub.challenge" if "hub.mode" query is "subscribe" or "unsubscribe"', () => { | ||
const modes = ['subscribe', 'unsubscribe'] | ||
const challenge = 'HzSGH_h04Cgl6VbDJm7IyXSNSlrhaLvBi9eft3bw' | ||
return Promise.each(modes, mode => { | ||
return helpers | ||
.checkResponseCode( | ||
return helpers.checkResponseCode( | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
url: `http://127.0.0.1:${testPort}`, | ||
qs: { | ||
'hub.mode': mode, | ||
'hub.topic': 'https://api.twitch.tv/helix/users/follows?to_id=1337', | ||
'hub.lease_seconds': 864000, | ||
'hub.challenge': 'HzSGH_h04Cgl6VbDJm7IyXSNSlrhaLvBi9eft3bw' | ||
'hub.challenge': challenge | ||
} | ||
}, | ||
200 | ||
) | ||
.then(response => { | ||
if (!response.body) { | ||
throw new Error('expected "hub.challenge"') | ||
} | ||
}) | ||
200 | ||
).then(response => { | ||
if (!response.body) { | ||
throw new Error('expected "hub.challenge"') | ||
} | ||
assert(response.body, challenge) | ||
}) | ||
}) | ||
@@ -143,10 +239,7 @@ }) | ||
describe('POST method', () => { | ||
it('returns 202 response code if data is very large', () => { | ||
const largeText = '0'.repeat(1e7) | ||
it('returns 202 response code if topic is missing', () => { | ||
return helpers.checkResponseCode( | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
method: 'POST', | ||
body: largeText | ||
url: `http://127.0.0.1:${testPort}`, | ||
method: 'POST' | ||
}, | ||
@@ -157,8 +250,10 @@ 202 | ||
it('returns 202 response code if json is malformed', () => { | ||
it('returns 202 response code if topic is incorrect', () => { | ||
return helpers.checkResponseCode( | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
url: `http://127.0.0.1:${testPort}`, | ||
method: 'POST', | ||
body: 'text,' | ||
headers: { | ||
link: `<http://127.0.0.1:${apiPort}/>; rel="self"` | ||
} | ||
}, | ||
@@ -169,7 +264,13 @@ 202 | ||
it('returns 202 error code if topic is missing', () => { | ||
it('returns 202 response code if request is very large', () => { | ||
const largeText = '0'.repeat(1e7) | ||
return helpers.checkResponseCode( | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
method: 'POST' | ||
url: `http://127.0.0.1:${testPort}`, | ||
method: 'POST', | ||
headers: { | ||
link: `<http://127.0.0.1:${apiPort}/test?param=value>; rel="self"` | ||
}, | ||
body: largeText | ||
}, | ||
@@ -180,10 +281,11 @@ 202 | ||
it('returns 202 error code if topic is incorrect', () => { | ||
it('returns 202 response code if json is malformed', () => { | ||
return helpers.checkResponseCode( | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
url: `http://127.0.0.1:${testPort}`, | ||
method: 'POST', | ||
headers: { | ||
link: '<https://api.twitch.tv/helix/>; rel="self"' | ||
} | ||
link: `<http://127.0.0.1:${apiPort}/test?param=value>; rel="self"` | ||
}, | ||
body: 'text,' | ||
}, | ||
@@ -197,6 +299,6 @@ 202 | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
url: `http://127.0.0.1:${testPort}`, | ||
method: 'POST', | ||
headers: { | ||
link: '<https://api.twitch.tv/helix/users/follows?to_id=1337>; rel="self"' | ||
link: `<http://127.0.0.1:${apiPort}/test?param=value>; rel="self"` | ||
}, | ||
@@ -208,2 +310,68 @@ json: {} | ||
}) | ||
describe('secret support', () => { | ||
it('returns 202 response code if "x-hub-signature" header is missing', () => { | ||
return helpers.checkResponseCode( | ||
{ | ||
url: `http://127.0.0.1:${securePort}`, | ||
method: 'POST', | ||
headers: { | ||
link: `<http://127.0.0.1:${apiPort}/test?param=value>; rel="self"` | ||
} | ||
}, | ||
202 | ||
) | ||
}) | ||
it('returns 202 response code if "x-hub-signature" header is incorrect', () => { | ||
return helpers.checkResponseCode( | ||
{ | ||
url: `http://127.0.0.1:${securePort}`, | ||
method: 'POST', | ||
headers: { | ||
link: `<http://127.0.0.1:${apiPort}/test?param=value>; rel="self"`, | ||
'x-hub-signature': 'sha256=text' | ||
}, | ||
json: {} | ||
}, | ||
202 | ||
) | ||
}) | ||
it('returns 200 response code if everything is ok', () => { | ||
// first step | ||
const storedSign = crypto | ||
.createHmac('sha256', secret) | ||
.update(`http://127.0.0.1:${apiPort}/test?param=value`) | ||
.digest('hex') | ||
// second step | ||
const body = JSON.stringify({ | ||
test: true | ||
}) | ||
const signature = crypto | ||
.createHmac('sha256', storedSign) | ||
.update(body) | ||
.digest('hex') | ||
return secureWebhook.subscribe('test', { | ||
param: 'value' | ||
}).catch((err) => { | ||
throw new Error('unexpected error in #subscribe: ' + err.message) | ||
}).then(() => { | ||
return helpers.checkResponseCode( | ||
{ | ||
url: `http://127.0.0.1:${securePort}`, | ||
method: 'POST', | ||
headers: { | ||
link: `<http://127.0.0.1:${apiPort}/test?param=value>; rel="self"`, | ||
'x-hub-signature': 'sha256=' + signature | ||
}, | ||
body | ||
}, | ||
200 | ||
) | ||
}) | ||
}) | ||
}) | ||
}) | ||
@@ -217,3 +385,3 @@ | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
url: `http://127.0.0.1:${testPort}`, | ||
method | ||
@@ -229,73 +397,195 @@ }, | ||
it('emits "denied" event if request with denied status was received', (done) => { | ||
twitchWebhook.once( | ||
'denied', | ||
() => done() | ||
) | ||
helpers.sendRequest( | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
qs: { | ||
'hub.mode': 'denied', | ||
'hub.topic': 'https://api.twitch.tv/helix/users/follows?to_id=1337', | ||
'hub.reason': 'unauthorized' | ||
} | ||
} | ||
); | ||
let query = { | ||
'hub.mode': 'denied' | ||
} | ||
testWebhook.once('denied', (obj) => { | ||
assert(typeof obj === 'object') | ||
assert.deepEqual(obj, query) | ||
done() | ||
}) | ||
helpers.sendRequest({ | ||
url: `http://127.0.0.1:${testPort}`, | ||
qs: query | ||
}) | ||
}) | ||
it('emits "subscribe" event if the subscribe request was received', (done) => { | ||
twitchWebhook.once( | ||
'subscribe', | ||
() => done() | ||
) | ||
helpers.sendRequest( | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
qs: { | ||
'hub.mode': 'subscribe', | ||
'hub.topic': 'https://api.twitch.tv/helix/users/follows?to_id=1337', | ||
'hub.lease_seconds': 864000, | ||
'hub.challenge': 'HzSGH_h04Cgl6VbDJm7IyXSNSlrhaLvBi9eft3bw' | ||
} | ||
} | ||
); | ||
let query = { | ||
'hub.mode': 'subscribe', | ||
'hub.challenge': 'HzSGH_h04Cgl6VbDJm7IyXSNSlrhaLvBi9eft3bw' | ||
} | ||
testWebhook.once('subscribe', (obj) => { | ||
assert(typeof obj === 'object') | ||
assert.deepEqual(obj, query) | ||
done() | ||
}) | ||
helpers.sendRequest({ | ||
url: `http://127.0.0.1:${testPort}`, | ||
qs: query | ||
}) | ||
}) | ||
it('emits "unsubscribe" event if the unsubscribe request was received', (done) => { | ||
twitchWebhook.once( | ||
'unsubscribe', | ||
() => done() | ||
) | ||
helpers.sendRequest( | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
qs: { | ||
'hub.mode': 'unsubscribe', | ||
'hub.topic': 'https://api.twitch.tv/helix/users/follows?to_id=1337', | ||
'hub.lease_seconds': 864000, | ||
'hub.challenge': 'HzSGH_h04Cgl6VbDJm7IyXSNSlrhaLvBi9eft3bw' | ||
} | ||
} | ||
); | ||
let query = { | ||
'hub.mode': 'unsubscribe', | ||
'hub.challenge': 'HzSGH_h04Cgl6VbDJm7IyXSNSlrhaLvBi9eft3bw' | ||
} | ||
testWebhook.once('unsubscribe', (obj) => { | ||
assert(typeof obj === 'object') | ||
assert.deepEqual(obj, query) | ||
done() | ||
}) | ||
helpers.sendRequest({ | ||
url: `http://127.0.0.1:${testPort}`, | ||
qs: query | ||
}) | ||
}) | ||
it('emits "*" event if request with topic was received', (done) => { | ||
twitchWebhook.once( | ||
'*', | ||
() => done() | ||
) | ||
const body = { | ||
test: true | ||
} | ||
testWebhook.once('*', (obj) => { | ||
assert(typeof obj === 'object') | ||
assert(obj.topic === 'test') | ||
assert(obj.endpoint === `http://127.0.0.1:${apiPort}/test?param=value`) | ||
assert.deepEqual(obj.event, body) | ||
done() | ||
}) | ||
helpers.sendRequest( | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
helpers.sendRequest({ | ||
url: `http://127.0.0.1:${testPort}`, | ||
method: 'POST', | ||
headers: { | ||
link: `<http://127.0.0.1:${apiPort}/test?param=value>; rel="self"` | ||
}, | ||
json: body | ||
}) | ||
}) | ||
it('emits event with the topic name if request with topic was received', (done) => { | ||
const body = { | ||
test: true | ||
} | ||
testWebhook.once('test', (obj) => { | ||
assert(typeof obj === 'object') | ||
assert(obj.topic === 'test') | ||
assert(obj.endpoint === `http://127.0.0.1:${apiPort}/test?param=value`) | ||
assert.deepEqual(obj.event, body) | ||
done() | ||
}) | ||
helpers.sendRequest({ | ||
url: `http://127.0.0.1:${testPort}`, | ||
method: 'POST', | ||
headers: { | ||
link: `<http://127.0.0.1:${apiPort}/test?param=value>; rel="self"` | ||
}, | ||
json: body | ||
}) | ||
}) | ||
describe('emits "webhook-error" event if incorrect request was received', () => { | ||
it('should emit if topic is missing', (done) => { | ||
testWebhook.once('webhook-error', (err) => { | ||
assert(err instanceof errors.WebhookError) | ||
assert(err.message === 'Topic is missing or incorrect') | ||
done() | ||
}) | ||
helpers.sendRequest({ | ||
url: `http://127.0.0.1:${testPort}`, | ||
method: 'POST' | ||
}) | ||
}) | ||
it('should emit if topic is incorrect', (done) => { | ||
testWebhook.once('webhook-error', (err) => { | ||
assert(err instanceof errors.WebhookError) | ||
assert(err.message === 'Topic is missing or incorrect') | ||
done() | ||
}) | ||
helpers.sendRequest({ | ||
url: `http://127.0.0.1:${testPort}`, | ||
method: 'POST', | ||
headers: { | ||
link: '<https://api.twitch.tv/helix/test>; rel="self"' | ||
link: `<http://127.0.0.1:${apiPort}/>; rel="self"` | ||
} | ||
}) | ||
}) | ||
it('should emit if request is very large', (done) => { | ||
const largeText = '0'.repeat(1e7) | ||
testWebhook.once('webhook-error', (err) => { | ||
assert(err instanceof errors.WebhookError) | ||
assert(err.message === 'Request is very large') | ||
done() | ||
}) | ||
helpers.sendRequest({ | ||
url: `http://127.0.0.1:${testPort}`, | ||
method: 'POST', | ||
headers: { | ||
link: `<http://127.0.0.1:${apiPort}/test?param=value>; rel="self"` | ||
}, | ||
json: {} | ||
} | ||
) | ||
body: largeText | ||
}) | ||
}) | ||
it('should emit if json is malformed', (done) => { | ||
testWebhook.once('webhook-error', (err) => { | ||
assert(err instanceof errors.WebhookError) | ||
assert(err.message === 'JSON is malformed') | ||
done() | ||
}) | ||
helpers.sendRequest({ | ||
url: `http://127.0.0.1:${testPort}`, | ||
method: 'POST', | ||
headers: { | ||
link: `<http://127.0.0.1:${apiPort}/test?param=value>; rel="self"` | ||
}, | ||
body: 'text,' | ||
}) | ||
}) | ||
describe('secret support', () => { | ||
it('should emit if "x-hub-signature" header is missing', (done) => { | ||
secureWebhook.once('webhook-error', (err) => { | ||
assert(err instanceof errors.WebhookError) | ||
assert(err.message === '"x-hub-signature" is missing') | ||
done() | ||
}) | ||
helpers.sendRequest({ | ||
url: `http://127.0.0.1:${securePort}`, | ||
method: 'POST', | ||
headers: { | ||
link: `<http://127.0.0.1:${apiPort}/test?param=value>; rel="self"` | ||
} | ||
}) | ||
}) | ||
it('should emit if "x-hub-signature" header is incorrect', (done) => { | ||
secureWebhook.once('webhook-error', (err) => { | ||
assert(err instanceof errors.WebhookError) | ||
assert(err.message === '"x-hub-signature" is incorrect') | ||
done() | ||
}) | ||
helpers.sendRequest({ | ||
url: `http://127.0.0.1:${securePort}`, | ||
method: 'POST', | ||
headers: { | ||
link: `<http://127.0.0.1:${apiPort}/test?param=value>; rel="self"`, | ||
'x-hub-signature': 'sha256=text' | ||
}, | ||
json: {} | ||
}) | ||
}) | ||
}) | ||
}) | ||
@@ -311,22 +601,12 @@ }) | ||
helpers.sendRequest( | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
method: 'POST', | ||
headers: { | ||
link: '<https://api.twitch.tv/helix/users/follows?to_id=1337>; rel="self"' | ||
}, | ||
json: { | ||
id: "436c70bb-a52f-4a6a-b4cc-6c57bc2ad227", | ||
topic: "https://api.twitch.tv/helix/users/follows?to_id=1337", | ||
type: "create", | ||
data: { | ||
from_id: 1336, | ||
to_id: 1337 | ||
}, | ||
timestamp: "2017-08-07T13:52:14.403795077Z" | ||
} | ||
helpers.sendRequest({ | ||
url: `http://127.0.0.1:${webhookPort}`, | ||
method: 'POST', | ||
headers: { | ||
link: '<https://api.twitch.tv/helix/users/follows?to_id=1337>; rel="self"' | ||
}, | ||
200 | ||
) | ||
json: { | ||
timestamp: '2017-08-07T13:52:14.403795077Z' | ||
} | ||
}) | ||
}) | ||
@@ -339,30 +619,21 @@ | ||
} | ||
done() | ||
}) | ||
helpers.sendRequest( | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
method: 'POST', | ||
headers: { | ||
link: '<https://api.twitch.tv/helix/streams?user_id=5678>; rel="self"' | ||
}, | ||
json: { | ||
data: [{ | ||
id: '0123456789', | ||
user_id: 5678, | ||
game_id: 21779, | ||
community_ids: [], | ||
type: 'live', | ||
title: 'Best Stream Ever', | ||
'viewer_count': 417, | ||
'started_at': '2017-12-01T10:09:45Z', | ||
language: 'en', | ||
'thumbnail_url': 'https://link/to/thumbnail.jpg', | ||
}] | ||
} | ||
helpers.sendRequest({ | ||
url: `http://127.0.0.1:${webhookPort}`, | ||
method: 'POST', | ||
headers: { | ||
link: '<https://api.twitch.tv/helix/streams?user_id=5678>; rel="self"' | ||
}, | ||
json: { | ||
data: [{ | ||
'started_at': '2017-12-01T10:09:45Z' | ||
}, { | ||
'started_at': '2017-12-02T11:49:47Z' | ||
}] | ||
} | ||
) | ||
}) | ||
}) | ||
}) | ||
}) | ||
@@ -374,4 +645,5 @@ | ||
it('should throw FatalError if the listener is already running', () => { | ||
return twitchWebhook.listen(freePort).catch(err => { | ||
return twitchWebhook.listen(offlinePort).catch(err => { | ||
assert(err instanceof errors.FatalError) | ||
assert(err.message === 'Listening is already started') | ||
}) | ||
@@ -385,3 +657,3 @@ }) | ||
it('starts listening with options', () => { | ||
return offlineWebhook.listen(freePort) | ||
return offlineWebhook.listen(offlinePort) | ||
}) | ||
@@ -394,9 +666,9 @@ }) | ||
it('returns true if listening is started', () => { | ||
assert.equal(twitchWebhook.isListening(), true) | ||
return helpers.hasStartedListening(`http://127.0.0.1:${port}`) | ||
assert(twitchWebhook.isListening()) | ||
return helpers.hasStartedListening(`http://127.0.0.1:${webhookPort}`) | ||
}) | ||
it('returns false if listening is not started', () => { | ||
assert.equal(offlineWebhook.isListening(), false) | ||
return helpers.hasStoppedListening(`http://127.0.0.1:${freePort}`) | ||
assert(offlineWebhook.isListening() === false) | ||
return helpers.hasStoppedListening(`http://127.0.0.1:${offlinePort}`) | ||
}) | ||
@@ -411,2 +683,3 @@ }) | ||
assert(err instanceof errors.RequestDenied) | ||
assert(typeof err.response === 'object') | ||
}) | ||
@@ -444,2 +717,3 @@ }) | ||
assert(err instanceof errors.RequestDenied) | ||
assert(typeof err.response === 'object') | ||
}) | ||
@@ -453,6 +727,2 @@ }) | ||
user_id: 123 | ||
}).then(() => { | ||
return twitchWebhook.unsubscribe('streams', { | ||
user_id: 123 | ||
}) | ||
}) | ||
@@ -466,6 +736,2 @@ }) | ||
user_id: 123 | ||
}).then(() => { | ||
return twitchWebhook.unsubscribe('https://api.twitch.tv/helix/streams', { | ||
user_id: 123 | ||
}) | ||
}) | ||
@@ -477,11 +743,29 @@ }) | ||
return twitchWebhook.subscribe('streams?user_id=123').then(() => { | ||
return twitchWebhook.unsubscribe('streams?user_id=123') | ||
}) | ||
return twitchWebhook.subscribe('streams?user_id=123') | ||
}) | ||
}) | ||
after(() => { | ||
return twitchWebhook.close() | ||
after(function () { | ||
if (twitchWebhook) { | ||
return twitchWebhook.close() | ||
} else { | ||
this.skip() | ||
} | ||
}) | ||
after(function () { | ||
if (secureWebhook) { | ||
return secureWebhook.close() | ||
} else { | ||
this.skip() | ||
} | ||
}) | ||
after(function () { | ||
if (testWebhook) { | ||
return testWebhook.close() | ||
} else { | ||
this.skip() | ||
} | ||
}) | ||
}) |
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
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
40451
1167
12
3