twitter-lite
Advanced tools
Comparing version 0.9.2 to 0.9.3
@@ -0,1 +1,5 @@ | ||
# v0.9.3, 2019-Mar-25 | ||
- Return `_headers` in stream creation errors as well | ||
# v0.9.1, 2019-Jan-16 | ||
@@ -2,0 +6,0 @@ |
{ | ||
"name": "twitter-lite", | ||
"version": "0.9.2", | ||
"version": "0.9.3", | ||
"description": "A tiny, full-featured client / server library for the Twitter API", | ||
@@ -14,2 +14,5 @@ "source": [ | ||
"author": "Peter Piekarczyk <peter@peterp.me>", | ||
"contributors": [ | ||
"Dan Dascalescu (https://github.com/dandv)" | ||
], | ||
"license": "MIT", | ||
@@ -23,9 +26,9 @@ "files": [ | ||
"twitter", | ||
"rest", | ||
"api", | ||
"twitter api", | ||
"node-twitter", | ||
"twitter oauth", | ||
"twitter rest", | ||
"rest", | ||
"api", | ||
"streams", | ||
"twitter api" | ||
"twitter stream" | ||
], | ||
@@ -39,20 +42,19 @@ "dependencies": { | ||
"dotenv": "^6.2.0", | ||
"eslint": "^5.15.3", | ||
"eslint-plugin-jest": "^22.4.1", | ||
"flow-bin": "^0.68.0", | ||
"husky": "^0.14.3", | ||
"husky": "^1.3.1", | ||
"jest": "^23.6.0", | ||
"lint-staged": "^8.1.0", | ||
"microbundle": "^0.4.3", | ||
"prettier": "^1.15.3" | ||
"microbundle": "^0.4.3" | ||
}, | ||
"scripts": { | ||
"lint": "eslint ./", | ||
"prepare": "microbundle", | ||
"test": "jest", | ||
"release": "npm run -s prepare && npm test && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish", | ||
"precommit": "lint-staged" | ||
"test": "eslint . && jest --detectOpenHandles", | ||
"release": "npm run -s prepare && npm test && git tag $npm_package_version && git push && git push --tags && npm publish" | ||
}, | ||
"lint-staged": { | ||
"*.{js,md}": [ | ||
"prettier --write", | ||
"git add" | ||
] | ||
"husky": { | ||
"hooks": { | ||
"pre-commit": "eslint ." | ||
} | ||
}, | ||
@@ -59,0 +61,0 @@ "jest": { |
@@ -7,2 +7,3 @@ # Twitter Lite | ||
## Features | ||
@@ -18,2 +19,3 @@ | ||
## Why | ||
@@ -23,2 +25,3 @@ | ||
## Installation | ||
@@ -34,2 +37,3 @@ | ||
## Usage | ||
@@ -154,2 +158,3 @@ | ||
## Streams | ||
@@ -184,4 +189,4 @@ | ||
To stop a stream, call `stream.destroy()`. That might take a while though, if the stream receives a lot of traffic. Also, if you attempt to destroy a stream from an `on` handler, you may get an error about writing to a destroyed stream. | ||
To avoid both these issues, [defer](https://stackoverflow.com/questions/49804108/write-after-end-stream-error/53878933#53878933) the `destroy()` call: | ||
To stop a stream, call `stream.destroy()`. That might take a while though, if the stream receives a lot of traffic. Also, if you attempt to destroy a stream from an `on` handler, you *may* get an error about writing to a destroyed stream. | ||
In that case, try to [defer](https://stackoverflow.com/questions/49804108/write-after-end-stream-error/53878933#53878933) the `destroy()` call: | ||
@@ -192,4 +197,5 @@ ```es6 | ||
After you've destroyed a stream, you can create another one - see the ["should switch from one stream to another" test](https://github.com/draftbit/twitter-lite/blob/9e0845585c756aee10e8b0acaabfbc3e4f32b81c/test/stream.test.js#L131). | ||
After calling `stream.destroy()`, you can recreate the stream, if you wait long enough - see the "should reuse stream N times" test. Note that Twitter may return a "420 Enhance your calm" error if you switch streams too fast. There are no response headers specifying how long to wait, and [the error](https://stackoverflow.com/questions/13438965/avoid-420s-with-streaming-api), as well as [streaming limits](https://stackoverflow.com/questions/34962677/twitter-streaming-api-limits) in general, are poorly documented. Trial and error has shown that for tracked keywords, waiting 20 to 30 seconds between re-creating streams was enough. | ||
## Methods | ||
@@ -289,5 +295,6 @@ | ||
## Contributing | ||
With the library nearing v1.0, contributions are welcome! Areas especially in need of help involve multimedia (see [#33](https://github.com/draftbit/twitter-lite/issues/33) for example), adding tests (see [these](https://github.com/ttezel/twit/tree/master/tests) for reference), and [getting v1.0 out the door](https://github.com/draftbit/twitter-lite/issues/21). | ||
With the library nearing v1.0, contributions are welcome! Areas especially in need of help involve multimedia (see [#33](https://github.com/draftbit/twitter-lite/issues/33) for example), and adding tests (see [these](https://github.com/ttezel/twit/tree/master/tests) for reference). | ||
@@ -308,8 +315,7 @@ ### Development | ||
6. Add your contribution, along with test case(s). Note: feel free to skip the ["should DM user"](https://github.com/draftbit/twitter-lite/blob/34e8dbb3efb9a45564275f16473af59dbc4409e5/twitter.test.js#L167) test during development by changing that `it()` call to `it.skip()`, but remember to revert that change before committing. This will prevent your account from being flagged as [abusing the API to send too many DMs](https://github.com/draftbit/twitter-lite/commit/5ee2ce4232faa07453ea2f0b4d63ee7a6d119ce7). | ||
7. Make sure all tests pass. | ||
8. `git add` the changed files | ||
9. `npm run precommit` to lint with [prettier](https://www.npmjs.com/package/prettier) | ||
10. Commit using a [descriptive message](https://chris.beams.io/posts/git-commit/) (please squash commits into one per fix/improvement!) | ||
11. `git push` and submit your PR! | ||
7. Make sure all tests pass. **NOTE: tests will take over 10 minutes to finish.** | ||
8. Commit using a [descriptive message](https://chris.beams.io/posts/git-commit/) (please squash commits into one per fix/improvement!) | ||
9. `git push` and submit your PR! | ||
## Credits | ||
@@ -316,0 +322,0 @@ |
@@ -1,3 +0,3 @@ | ||
const EventEmitter = require("events"); | ||
const END = "\r\n"; | ||
const EventEmitter = require('events'); | ||
const END = '\r\n'; | ||
const END_LENGTH = 2; | ||
@@ -8,7 +8,7 @@ | ||
super(); | ||
this.buffer = ""; | ||
this.buffer = ''; | ||
} | ||
parse(buffer) { | ||
this.buffer += buffer.toString("utf8"); | ||
this.buffer += buffer.toString('utf8'); | ||
let index; | ||
@@ -23,9 +23,9 @@ let json; | ||
json = JSON.parse(json); | ||
this.emit(json.event || "data", json); | ||
this.emit(json.event || 'data', json); | ||
} catch (error) { | ||
error.source = json; | ||
this.emit("error", error); | ||
this.emit('error', error); | ||
} | ||
} else { | ||
this.emit("ping"); | ||
this.emit('ping'); | ||
} | ||
@@ -32,0 +32,0 @@ } |
149
twitter.js
@@ -1,8 +0,8 @@ | ||
const crypto = require("crypto"); | ||
const OAuth = require("oauth-1.0a"); | ||
const Fetch = require("cross-fetch"); | ||
const querystring = require("querystring"); | ||
const Stream = require("./stream"); | ||
const crypto = require('crypto'); | ||
const OAuth = require('oauth-1.0a'); | ||
const Fetch = require('cross-fetch'); | ||
const querystring = require('querystring'); | ||
const Stream = require('./stream'); | ||
const getUrl = (subdomain, endpoint = "1.1") => | ||
const getUrl = (subdomain, endpoint = '1.1') => | ||
`https://${subdomain}.twitter.com/${endpoint}`; | ||
@@ -13,9 +13,9 @@ | ||
consumer: { key, secret }, | ||
signature_method: "HMAC-SHA1", | ||
signature_method: 'HMAC-SHA1', | ||
hash_function(baseString, key) { | ||
return crypto | ||
.createHmac("sha1", key) | ||
.createHmac('sha1', key) | ||
.update(baseString) | ||
.digest("base64"); | ||
} | ||
.digest('base64'); | ||
}, | ||
}); | ||
@@ -27,3 +27,3 @@ | ||
const defaults = { | ||
subdomain: "api", | ||
subdomain: 'api', | ||
consumer_key: null, | ||
@@ -33,3 +33,3 @@ consumer_secret: null, | ||
access_token_secret: null, | ||
bearer_token: null | ||
bearer_token: null, | ||
}; | ||
@@ -42,10 +42,10 @@ | ||
const JSON_ENDPOINTS = [ | ||
"direct_messages/events/new", | ||
"direct_messages/welcome_messages/new", | ||
"direct_messages/welcome_messages/rules/new" | ||
'direct_messages/events/new', | ||
'direct_messages/welcome_messages/new', | ||
'direct_messages/welcome_messages/rules/new', | ||
]; | ||
const baseHeaders = { | ||
"Content-Type": "application/json", | ||
Accept: "application/json" | ||
'Content-Type': 'application/json', | ||
Accept: 'application/json', | ||
}; | ||
@@ -56,7 +56,7 @@ | ||
return string | ||
.replace(/!/g, "%21") | ||
.replace(/\*/g, "%2A") | ||
.replace(/'/g, "%27") | ||
.replace(/\(/g, "%28") | ||
.replace(/\)/g, "%29"); | ||
.replace(/!/g, '%21') | ||
.replace(/\*/g, '%2A') | ||
.replace(/'/g, '%27') | ||
.replace(/\(/g, '%28') | ||
.replace(/\)/g, '%29'); | ||
} | ||
@@ -67,6 +67,6 @@ | ||
const config = Object.assign({}, defaults, options); | ||
this.authType = config.bearer_token ? "App" : "User"; | ||
this.authType = config.bearer_token ? 'App' : 'User'; | ||
this.client = createOauthClient({ | ||
key: config.consumer_key, | ||
secret: config.consumer_secret | ||
secret: config.consumer_secret, | ||
}); | ||
@@ -76,7 +76,7 @@ | ||
key: config.access_token_key, | ||
secret: config.access_token_secret | ||
secret: config.access_token_secret, | ||
}; | ||
this.url = getUrl(config.subdomain); | ||
this.oauth = getUrl(config.subdomain, "oauth"); | ||
this.oauth = getUrl(config.subdomain, 'oauth'); | ||
this.config = config; | ||
@@ -92,7 +92,7 @@ } | ||
static _handleResponse(response) { | ||
const headers = response.headers.raw(); // https://github.com/bitinn/node-fetch/issues/495 | ||
const headers = response.headers.raw(); // TODO: see #44 | ||
// Return empty response on 204 "No content" | ||
if (response.status === 204) | ||
return { | ||
_headers: headers | ||
_headers: headers, | ||
}; | ||
@@ -109,13 +109,13 @@ // Otherwise, parse JSON response | ||
Authorization: | ||
"Basic " + | ||
'Basic ' + | ||
Buffer.from( | ||
this.config.consumer_key + ":" + this.config.consumer_secret | ||
).toString("base64"), | ||
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" | ||
this.config.consumer_key + ':' + this.config.consumer_secret | ||
).toString('base64'), | ||
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8', | ||
}; | ||
const results = await Fetch("https://api.twitter.com/oauth2/token", { | ||
method: "POST", | ||
body: "grant_type=client_credentials", | ||
headers | ||
const results = await Fetch('https://api.twitter.com/oauth2/token', { | ||
method: 'POST', | ||
body: 'grant_type=client_credentials', | ||
headers, | ||
}).then(Twitter._handleResponse); | ||
@@ -129,3 +129,3 @@ | ||
url: `${this.oauth}/request_token`, | ||
method: "POST" | ||
method: 'POST', | ||
}; | ||
@@ -135,3 +135,3 @@ | ||
if (twitterCallbackUrl) parameters = { oauth_callback: twitterCallbackUrl }; | ||
if (parameters) requestData.url += "?" + querystring.stringify(parameters); | ||
if (parameters) requestData.url += '?' + querystring.stringify(parameters); | ||
@@ -143,4 +143,4 @@ const headers = this.client.toHeader( | ||
const results = await Fetch(requestData.url, { | ||
method: "POST", | ||
headers: Object.assign({}, baseHeaders, headers) | ||
method: 'POST', | ||
headers: Object.assign({}, baseHeaders, headers), | ||
}) | ||
@@ -156,7 +156,7 @@ .then(res => res.text()) | ||
url: `${this.oauth}/access_token`, | ||
method: "POST" | ||
method: 'POST', | ||
}; | ||
let parameters = { oauth_verifier: options.verifier }; | ||
if (parameters) requestData.url += "?" + querystring.stringify(parameters); | ||
if (parameters) requestData.url += '?' + querystring.stringify(parameters); | ||
@@ -166,3 +166,3 @@ const headers = this.client.toHeader( | ||
key: options.key, | ||
secret: options.secret | ||
secret: options.secret, | ||
}) | ||
@@ -172,4 +172,4 @@ ); | ||
const results = await Fetch(requestData.url, { | ||
method: "POST", | ||
headers: Object.assign({}, baseHeaders, headers) | ||
method: 'POST', | ||
headers: Object.assign({}, baseHeaders, headers), | ||
}) | ||
@@ -184,3 +184,3 @@ .then(res => res.text()) | ||
* Construct the data and headers for an authenticated HTTP request to the Twitter API | ||
* @param {string} method - "GET" or "POST" | ||
* @param {string} method - 'GET' or 'POST' | ||
* @param {string} resource - the API endpoint | ||
@@ -194,10 +194,10 @@ * @param {object} parameters | ||
url: `${this.url}/${resource}.json`, | ||
method | ||
method, | ||
}; | ||
if (parameters) | ||
if (method === "POST") requestData.data = parameters; | ||
else requestData.url += "?" + querystring.stringify(parameters); | ||
if (method === 'POST') requestData.data = parameters; | ||
else requestData.url += '?' + querystring.stringify(parameters); | ||
let headers = {}; | ||
if (this.authType === "User") { | ||
if (this.authType === 'User') { | ||
headers = this.client.toHeader( | ||
@@ -208,3 +208,3 @@ this.client.authorize(requestData, this.token) | ||
headers = { | ||
Authorization: `Bearer ${this.config.bearer_token}` | ||
Authorization: `Bearer ${this.config.bearer_token}`, | ||
}; | ||
@@ -214,3 +214,3 @@ } | ||
requestData, | ||
headers | ||
headers, | ||
}; | ||
@@ -228,3 +228,3 @@ } | ||
const { requestData, headers } = this._makeRequest( | ||
"GET", | ||
'GET', | ||
resource, | ||
@@ -237,3 +237,3 @@ parameters | ||
.then(results => | ||
"errors" in results ? Promise.reject(results) : results | ||
'errors' in results ? Promise.reject(results) : results | ||
); | ||
@@ -252,3 +252,3 @@ } | ||
const { requestData, headers } = this._makeRequest( | ||
"POST", | ||
'POST', | ||
resource, | ||
@@ -263,13 +263,13 @@ JSON_ENDPOINTS.includes(resource) ? null : body // don't sign JSON bodies; only parameters | ||
body = percentEncode(querystring.stringify(body)); | ||
postHeaders["Content-Type"] = "application/x-www-form-urlencoded"; | ||
postHeaders['Content-Type'] = 'application/x-www-form-urlencoded'; | ||
} | ||
return Fetch(requestData.url, { | ||
method: "POST", | ||
method: 'POST', | ||
headers: postHeaders, | ||
body | ||
body, | ||
}) | ||
.then(Twitter._handleResponse) | ||
.then(results => | ||
"errors" in results ? Promise.reject(results) : results | ||
'errors' in results ? Promise.reject(results) : results | ||
); | ||
@@ -285,4 +285,4 @@ } | ||
stream(resource, parameters) { | ||
if (this.authType !== "User") | ||
throw new Error("Streams require user context authentication"); | ||
if (this.authType !== 'User') | ||
throw new Error('Streams require user context authentication'); | ||
@@ -294,4 +294,4 @@ const stream = new Stream(); | ||
const requestData = { | ||
url: `${getUrl("stream")}/${resource}.json`, | ||
method: "POST" | ||
url: `${getUrl('stream')}/${resource}.json`, | ||
method: 'POST', | ||
}; | ||
@@ -305,8 +305,8 @@ if (parameters) requestData.data = parameters; | ||
const request = Fetch(requestData.url, { | ||
method: "POST", | ||
method: 'POST', | ||
headers: { | ||
...headers, | ||
"Content-Type": "application/x-www-form-urlencoded" | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
body: percentEncode(querystring.stringify(parameters)) | ||
body: percentEncode(querystring.stringify(parameters)), | ||
}); | ||
@@ -318,12 +318,15 @@ | ||
response.status === 200 | ||
? stream.emit("start", response) | ||
: stream.emit("error", Error(`Status Code: ${response.status}`)); | ||
if (response.ok) { | ||
stream.emit('start', response); | ||
} else { | ||
response._headers = response.headers.raw(); // TODO: see #44 - could omit the line | ||
stream.emit('error', response); | ||
} | ||
response.body | ||
.on("data", chunk => stream.parse(chunk)) | ||
.on("error", error => stream.emit("error", error)) | ||
.on("end", () => stream.emit("end", response)); | ||
.on('data', chunk => stream.parse(chunk)) | ||
.on('error', error => stream.emit('error', error)) // no point in adding the original response headers | ||
.on('end', () => stream.emit('end', response)); | ||
}) | ||
.catch(error => stream.emit("error", error)); | ||
.catch(error => stream.emit('error', error)); | ||
@@ -330,0 +333,0 @@ return stream; |
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 3 instances in 1 package
1
326
25844
6
299