twitch-webhook
Advanced tools
Comparing version 1.0.2 to 1.1.0
@@ -22,4 +22,4 @@ const TwitchWebhook = require('twitch-webhook') | ||
twitchWebhook.on('*', ({ topic, event }) => { | ||
console.log(event) | ||
twitchWebhook.on('*', ({ topic, endpoint, event }) => { | ||
console.log(topic, event) | ||
}) | ||
@@ -26,0 +26,0 @@ |
{ | ||
"name": "twitch-webhook", | ||
"version": "1.0.2", | ||
"version": "1.1.0", | ||
"description": "A Node JS library for new Twitch Helix API Webhooks", | ||
@@ -35,2 +35,3 @@ "main": "src/index.js", | ||
"is-absolute-url": "^2.1.0", | ||
"parse-link-header": "^1.0.1", | ||
"querystring": "^0.2.0", | ||
@@ -37,0 +38,0 @@ "request": "^2.83.0", |
@@ -34,12 +34,12 @@ # Node.js Twitch Helix Webhooks | ||
twitchWebhook.on('streams', ({ topic, event }) => { | ||
twitchWebhook.on('streams', ({ topic, endpoint, event }) => { | ||
console.log(event) | ||
}) | ||
twitchWebhook.on('users/follows', ({ topic, event }) => { | ||
twitchWebhook.on('users/follows', ({ topic, endpoint, event }) => { | ||
console.log(event) | ||
}) | ||
twitchWebhook.on('*', ({ topic, event }) => { | ||
console.log(event) | ||
twitchWebhook.on('*', ({ topic, endpoint, event }) => { | ||
console.log(topic, event) | ||
}) | ||
@@ -46,0 +46,0 @@ |
@@ -11,2 +11,3 @@ const errors = require('./errors') | ||
const isAbsoluteUrl = require('is-absolute-url') | ||
const parseLinkHeader = require('parse-link-header') | ||
@@ -84,2 +85,3 @@ /** | ||
* @param {...any} [args] - Arguments | ||
* @throws {Promise<FatalError>} If listening is already started | ||
* @return {Promise} | ||
@@ -89,3 +91,3 @@ */ | ||
if (this.isListening()) { | ||
return Promise.reject(new errors.FatalError('Listen already started')) | ||
return Promise.reject(new errors.FatalError('Listening is already started')) | ||
} | ||
@@ -144,2 +146,3 @@ | ||
* @param {Object} options - Topic options | ||
* @throws {Promise<RequestDenied>} If the hub finds any errors in the request | ||
* @return {Promise} | ||
@@ -179,9 +182,5 @@ */ | ||
.catch(err => { | ||
throw new errors.FatalError(err) | ||
throw new errors.RequestDenied(err) | ||
}) | ||
.then(response => { | ||
if (response.statusCode !== 202) { | ||
throw new errors.RequestDenied(response) | ||
} | ||
if (this._options.secret) { | ||
@@ -198,2 +197,4 @@ this._secrets[topic] = requestOptions.qs['hub.secret'] | ||
* @param {Object} options - Topic options | ||
* @throws {RequestDenied} If hub finds any errors in the request | ||
* @return {Promise} | ||
*/ | ||
@@ -208,2 +209,3 @@ subscribe (topic, options = {}) { | ||
* @param {string} topic - Topic name | ||
* @throws {RequestDenied} If hub finds any errors in the request | ||
* @return {Promise} | ||
@@ -230,3 +232,3 @@ */ | ||
_processConnection (request, response) { | ||
const queries = url.parse(request.url, true).query || {} | ||
const queries = url.parse(request.url, true).query | ||
@@ -255,3 +257,3 @@ switch (queries['hub.mode']) { | ||
/** | ||
* Fix fields with date in response | ||
* Fix fields with date | ||
* | ||
@@ -263,3 +265,3 @@ * @private | ||
*/ | ||
_fixDateInResponse (topic, data) { | ||
_fixDate (topic, data) { | ||
switch (topic) { | ||
@@ -270,3 +272,5 @@ case 'users/follows': | ||
case 'streams': | ||
data.started_at = new Date(data.started_at) | ||
for (let stream of data.data) { | ||
stream.started_at = new Date(stream.started_at) | ||
} | ||
break | ||
@@ -286,2 +290,11 @@ } | ||
_processUpdates (request, response) { | ||
const links = parseLinkHeader(request.headers.link) | ||
const endpoint = links && links.self && links.self.url | ||
const topic = endpoint && url.parse(endpoint, true).pathname.replace('/helix/', '') | ||
if (!endpoint || !topic) { | ||
response.writeHead(202, { 'Content-Type': 'text/plain' }) | ||
response.end() | ||
return | ||
} | ||
let signature | ||
@@ -323,16 +336,10 @@ if (this._options.secret) { | ||
const topic = data && data.topic | ||
const topicName = | ||
topic && url.parse(topic).pathname.replace('/helix/', '') | ||
if (!topic || !topicName) { | ||
response.writeHead(202, { 'Content-Type': 'text/plain' }) | ||
response.end() | ||
return | ||
} | ||
if (this._options.secret) { | ||
const storedSign = crypto | ||
.createHmac('sha256', this._secrets[topic]) | ||
.update(body) | ||
.digest('hex') | ||
let storedSign | ||
if (this._secrets[endpoint]) { | ||
storedSign = crypto | ||
.createHmac('sha256', this._secrets[endpoint]) | ||
.update(body) | ||
.digest('hex') | ||
} | ||
@@ -350,6 +357,7 @@ if (storedSign !== signature) { | ||
let payload = {} | ||
payload.topic = topicName | ||
payload.event = this._fixDateInResponse(topicName, data) | ||
payload.topic = topic | ||
payload.endpoint = endpoint | ||
payload.event = this._fixDate(topic, data) | ||
this.emit(topicName, payload) | ||
this.emit(topic, payload) | ||
this.emit('*', payload) | ||
@@ -356,0 +364,0 @@ }) |
@@ -5,7 +5,11 @@ const assert = require('assert') | ||
function checkResponseCode (requestOptions, requiredCode) { | ||
function sendRequest (requestOptions) { | ||
requestOptions.resolveWithFullResponse = true | ||
requestOptions.simple = false | ||
return request(requestOptions).then(response => { | ||
return request(requestOptions); | ||
} | ||
function checkResponseCode (requestOptions, requiredCode) { | ||
return sendRequest(requestOptions).then(response => { | ||
assert.equal( | ||
@@ -42,2 +46,3 @@ response.statusCode, | ||
module.exports = { | ||
sendRequest, | ||
checkResponseCode, | ||
@@ -44,0 +49,0 @@ hasStartedListening, |
@@ -45,2 +45,30 @@ const TwitchWebhook = require('../src/index') | ||
it('should contain errors', (done) => { | ||
assert(twitchWebhook.errors instanceof Object); | ||
done() | ||
}) | ||
it('should throw FatalError if the Twitch Client ID is not provided', (done) => { | ||
try { | ||
let testWebhook = new TwitchWebhook(); | ||
done(new Error('expected error')) | ||
} catch (err) { | ||
assert(err instanceof errors.FatalError); | ||
done() | ||
} | ||
}) | ||
it('should throw FatalError if the Callback URL is not provided', (done) => { | ||
try { | ||
let testWebhook = new TwitchWebhook({ | ||
client_id | ||
}); | ||
done(new Error('expected error')) | ||
} catch (err) { | ||
assert(err instanceof errors.FatalError); | ||
done() | ||
} | ||
}) | ||
it('should automaticaly start listening by default', () => { | ||
@@ -100,3 +128,3 @@ assert.equal(twitchWebhook.isListening(), true) | ||
if (!response.body) { | ||
throw new Error('expeced "hub.challenge"') | ||
throw new Error('expected "hub.challenge"') | ||
} | ||
@@ -143,3 +171,3 @@ }) | ||
it('returns 200 response code if everything is ok', () => { | ||
it('returns 202 error code if topic is incorrect', () => { | ||
return helpers.checkResponseCode( | ||
@@ -149,6 +177,20 @@ { | ||
method: 'POST', | ||
json: { | ||
topic: 'https://api.twitch.tv/helix/users/follows?to_id=1337' | ||
headers: { | ||
link: '<https://api.twitch.tv/helix/>; rel="self"' | ||
} | ||
}, | ||
202 | ||
) | ||
}) | ||
it('returns 200 response code if everything is ok', () => { | ||
return helpers.checkResponseCode( | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
method: 'POST', | ||
headers: { | ||
link: '<https://api.twitch.tv/helix/users/follows?to_id=1337>; rel="self"' | ||
}, | ||
json: {} | ||
}, | ||
200 | ||
@@ -174,2 +216,142 @@ ) | ||
describe('events', () => { | ||
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' | ||
} | ||
} | ||
); | ||
}) | ||
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' | ||
} | ||
} | ||
); | ||
}) | ||
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' | ||
} | ||
} | ||
); | ||
}) | ||
it('emits "*" event if request with topic was received', (done) => { | ||
twitchWebhook.once( | ||
'*', | ||
() => done() | ||
) | ||
helpers.sendRequest( | ||
{ | ||
url: `http://127.0.0.1:${port}`, | ||
method: 'POST', | ||
headers: { | ||
link: '<https://api.twitch.tv/helix/test>; rel="self"' | ||
}, | ||
json: {} | ||
} | ||
) | ||
}) | ||
}) | ||
describe('date fix', () => { | ||
it('should fix "timestamp" field in "users/follows" topic', (done) => { | ||
twitchWebhook.once('users/follows', ({event}) => { | ||
assert(event.timestamp instanceof Date) | ||
done() | ||
}) | ||
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" | ||
} | ||
}, | ||
200 | ||
) | ||
}) | ||
it('should fix "started_at" fields in "streams" topic', (done) => { | ||
twitchWebhook.once('streams', ({event}) => { | ||
for (let stream of event.data) { | ||
assert(stream['started_at'] instanceof Date) | ||
} | ||
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', | ||
}] | ||
} | ||
} | ||
) | ||
}) | ||
}) | ||
describe('#listen', () => { | ||
@@ -207,32 +389,74 @@ afterEach(() => offlineWebhook.close()) | ||
describe.skip('#subscribe', () => {}) | ||
describe('#unsubscribe', () => { | ||
it('should throw FatalError if the request status is bad', function () { | ||
it('should throw RequestDenied if the request status is bad', function () { | ||
this.timeout(timeout) | ||
return twitchWebhook.unsubscribe('streams').catch(err => { | ||
assert(err instanceof errors.FatalError) | ||
assert(err instanceof errors.RequestDenied) | ||
}) | ||
}) | ||
it('should throw RequestDenied if request status is denied', function () { | ||
it('should return nothing if everything is ok', function () { | ||
this.timeout(timeout) | ||
return twitchWebhook | ||
.unsubscribe('streams', { | ||
return twitchWebhook.unsubscribe('streams', { | ||
user_id: 123 | ||
}) | ||
}) | ||
it('should not supplement link if topic url is absolute', function () { | ||
this.timeout(timeout) | ||
return twitchWebhook.unsubscribe('https://api.twitch.tv/helix/streams', { | ||
user_id: 123 | ||
}) | ||
}) | ||
it('should not supplement link if topic options is not exists', function () { | ||
this.timeout(timeout) | ||
return twitchWebhook.unsubscribe('https://api.twitch.tv/helix/streams?user_id=123') | ||
}) | ||
}) | ||
describe('#subscribe', () => { | ||
it('should throw RequestDenied if the request status is bad', function () { | ||
this.timeout(timeout) | ||
return twitchWebhook.subscribe('streams').catch(err => { | ||
assert(err instanceof errors.RequestDenied) | ||
}) | ||
}) | ||
it('should return nothing if everything is ok', function () { | ||
this.timeout(timeout) | ||
return twitchWebhook.subscribe('streams', { | ||
user_id: 123 | ||
}).then(() => { | ||
return twitchWebhook.unsubscribe('streams', { | ||
user_id: 123 | ||
}) | ||
.catch(err => { | ||
assert(err instanceof errors.RequestDenied) | ||
}) | ||
}) | ||
}) | ||
it('should return nothing if everything is ok', function () { | ||
it('should not supplement link if topic url is absolute', function () { | ||
this.timeout(timeout) | ||
return twitchWebhook.unsubscribe('streams', { | ||
return twitchWebhook.subscribe('https://api.twitch.tv/helix/streams', { | ||
user_id: 123 | ||
}).then(() => { | ||
return twitchWebhook.unsubscribe('https://api.twitch.tv/helix/streams', { | ||
user_id: 123 | ||
}) | ||
}) | ||
}) | ||
it('should not supplement link if topic options is not exists', function () { | ||
this.timeout(timeout) | ||
return twitchWebhook.subscribe('streams?user_id=123').then(() => { | ||
return twitchWebhook.unsubscribe('streams?user_id=123') | ||
}) | ||
}) | ||
}) | ||
@@ -239,0 +463,0 @@ |
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
29969
858
2
6
+ Addedparse-link-header@^1.0.1
+ Addedparse-link-header@1.0.1(transitive)
+ Addedpsl@1.13.0(transitive)
+ Addedxtend@4.0.2(transitive)
- Removedpsl@1.10.0(transitive)