Skip to content

Commit 5bc32a7

Browse files
alkihisLouis BérangerLouis Béranger
authored
Streaming + refactor (#18)
* Base for using streams in v1/v2 API * WIP: v1 stream helpers OK * Removed DS Store * Ignored DS Store * Documentation for v1 streaming * Stream API and client API v2 * Restructured type definitions * Splitted upload media init & requests * Simplified filter stream * Documentation for v2 endpoints * Unit test * Removed unused imported types * Better streaming documentation Co-authored-by: Louis Béranger <[email protected]> Co-authored-by: Louis Béranger <[email protected]>
1 parent 70b53dc commit 5bc32a7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2956
-743
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,7 @@ node_modules/
1919
.env
2020

2121
# IDE
22-
.idea
22+
.idea
23+
.vscode
24+
25+
*.DS_Store

.npmignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
./src/test/
2+
./test/

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ They caused me some frustration:
7171
- [x] link auth
7272
- [x] read/write/DM aware typing
7373
- [x] get/post methods
74+
- [x] custom http methods
75+
- [x] streaming
7476
- [ ] Twitter API V2 tweets methods
7577
- [x] Twitter API V2 users methods
7678
- [ ] Auto pagination
@@ -96,11 +98,15 @@ const manyTweets = await twitterClient.tweets.search('nodeJS').fetchLast(10000);
9698
// Manage errors
9799
try {
98100
const manyTweets = await twitterClient.tweets.search('nodeJS').fetchLast(100000000);
99-
} catch(e) {
101+
} catch (e) {
100102
if (e.errorCode === TwitterErrors.RATE_LIMIT_EXCEEDED) {
101103
console.log('please try again later!');
102104
} else {
103105
throw e;
104106
}
105107
}
106-
```
108+
```
109+
110+
## Streaming
111+
112+
See [Streaming part](./doc/streaming.md).

doc/streaming.md

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# Streaming
2+
3+
This lib supports streaming for v1 and v2 API.
4+
5+
## Using streaming
6+
7+
For both V1 and V2 APIs, streaming methods returns a `TweetStream` object.
8+
9+
Each event of `TweetStream` is stored into a TypeScript `enum`.
10+
11+
```ts
12+
import { ETwitterStreamEvent, TweetStream, TwitterApi, ETwitterApiError } from 'twitter-api-v2';
13+
14+
const client = new TwitterApi(); // (create a client)
15+
16+
let stream: TweetStream;
17+
try {
18+
// For example, can be any stream function
19+
stream = await client.v1.sampleStream();
20+
} catch (e) {
21+
// e is either a TwitterApiRequestError or a TwitterApiError
22+
if (e.type === ETwitterApiError.Request) {
23+
// Thrown if request fails (network error).
24+
console.log('Request failed.', e.requestError);
25+
}
26+
else if (e.type === ETwitterApiError.Response) {
27+
// Thrown if Twitter responds with a bad HTTP status
28+
console.log(
29+
'Twitter didnt accept your request. HTTP code:',
30+
e.code,
31+
', parsed response data:',
32+
e.data,
33+
);
34+
}
35+
}
36+
37+
// Awaits for a tweet
38+
stream.on(
39+
// Emitted when Node.js {response} emits a 'error' event (contains its payload).
40+
ETwitterStreamEvent.ConnectionError,
41+
err => console.log('Connection error!', err),
42+
);
43+
44+
stream.on(
45+
// Emitted when Node.js {response} is closed by remote or using .close().
46+
ETwitterStreamEvent.ConnectionClosed,
47+
() => console.log('Connection has been closed.'),
48+
);
49+
50+
stream.on(
51+
// Emitted when a Twitter payload (a tweet or not, given the endpoint).
52+
ETwitterStreamEvent.Data,
53+
eventData => console.log('Twitter has sent something:', eventData),
54+
);
55+
56+
stream.on(
57+
// Emitted when a Twitter sent a signal to maintain connection active
58+
ETwitterStreamEvent.DataKeepAlive,
59+
() => console.log('Twitter has a keep-alive packet.'),
60+
);
61+
62+
// Be sure to close the stream where you don't want to consume data anymore from it
63+
stream.close();
64+
```
65+
66+
## Specific API v1.1 implementations
67+
68+
API v1.1 streaming-related endpoints works only with classic OAuth1.0a authentification.
69+
70+
### Filter endpoint
71+
72+
Method: **`v1.filterStream`**.
73+
74+
Endpoint: `statuses/filter.json`.
75+
76+
Level: **Read-only**.
77+
78+
```ts
79+
const client = ...; // (create a OAuth 1.0a client)
80+
81+
const streamFilter = await client.v1.filterStream({
82+
// See FilterStreamParams interface.
83+
track: 'JavaScript',
84+
follow: [1842984n, '1850485928354'],
85+
});
86+
87+
// Event data will be tweets of v1 API.
88+
```
89+
90+
### Sample endpoint
91+
92+
Method: **`v1.sampleStream`**.
93+
94+
Endpoint: `statuses/sample.json`.
95+
96+
Level: **Read-only**.
97+
98+
```ts
99+
const client = ...; // (create a OAuth 1.0a client)
100+
101+
const stream = await client.v1.sampleStream();
102+
103+
// Event data will be tweets of v1 API.
104+
```
105+
106+
## Specific API v2 implementations
107+
108+
API v2 streaming-related endpoints works only with Bearer OAuth2 authentification.
109+
110+
### Search endpoint
111+
112+
Method: **`v2.searchStream`**.
113+
114+
Endpoint: `tweets/search/stream`.
115+
116+
Level: **Read-only**.
117+
118+
```ts
119+
const client = ...; // (create a Bearer OAuth2 client)
120+
121+
const stream = await client.v2.searchStream();
122+
123+
// Event data will be tweets of v2 API.
124+
```
125+
126+
### Search endpoint - Get applied rules
127+
128+
Method: **`v2.streamRules`**.
129+
130+
Endpoint: `tweets/search/stream/rules (GET)`.
131+
132+
Level: **Read-only**.
133+
134+
Returns: **`StreamingV2GetRulesResult`**.
135+
136+
```ts
137+
const client = ...; // (create a Bearer OAuth2 client)
138+
139+
const rules = await client.v2.streamRules();
140+
141+
// Log every rule ID
142+
console.log(rules.data.map(rule => rule.id));
143+
```
144+
145+
### Search endpoint - Add or delete rules
146+
147+
Method: **`v2.updateStreamRules`**.
148+
149+
Endpoint: `tweets/search/stream/rules (POST)`.
150+
151+
Level: **Read-write**.
152+
153+
Takes: **`StreamingV2UpdateRulesParams`**.
154+
Returns: **`StreamingV2UpdateRulesResult`**.
155+
156+
```ts
157+
const client = ...; // (create a Bearer OAuth2 client)
158+
159+
// Add rules
160+
const addedRules = await client.v2.updateStreamRules({
161+
add: [
162+
{ value: 'JavaScript', tag: 'js' },
163+
{ value: 'TypeScript', tag: 'ts' },
164+
],
165+
});
166+
167+
// Delete rules
168+
const deleteRules = await client.v2.updateStreamRules({
169+
delete: {
170+
ids: ['281646', '1534843'],
171+
},
172+
});
173+
```
174+
175+
### Sample endpoint
176+
177+
Method: **`v2.sampleStream`**.
178+
179+
Endpoint: `tweets/sample/stream`.
180+
181+
Level: **Read-only**.
182+
183+
```ts
184+
const client = ...; // (create a Bearer OAuth2 client)
185+
186+
const stream = await client.v2.sampleStream();
187+
188+
// Event data will be tweets of v2 API.
189+
```
190+
191+
## Make a custom request
192+
193+
If you know endpoint and parameters (or you don't want them to be parsed),
194+
you can make raw requests using shortcuts by HTTP methods:
195+
- `getStream()`
196+
- `postStream()`
197+
- `putStream()`
198+
- `deleteStream()`
199+
- `patchStream()`
200+
or using raw request handler:
201+
- `sendStream()`
202+
203+
NOTE: **Be careful to select the good API prefix for version 1.1. 1.1 does not use the same URL for classic endpoints and streaming endpoints**.
204+
You can access quicky to an instance with the streaming prefix using `v1.stream`.
205+
206+
```ts
207+
// For v1
208+
const streamFilter = await client.v1.stream.getStream('statuses/filter.json', { track: 'JavaScript,TypeScript' });
209+
// For v2
210+
const sampleFilterv2 = await client.v2.getStream('tweets/sample/stream');
211+
```
212+
213+
## `TweetStream` reference
214+
215+
### Methods / properties
216+
217+
- `.autoReconnect: boolean` / defaults `false` / Set this to `true` to enable experimental reconnect feature.
218+
- `.autoReconnectRetries: number` / default `5` / If `autoReconnect` is `true`, maximum tries made until give up. Each try is spaced by `min((attempt ** 2) * 100, 20000)` milliseconds.
219+
- `.close()`: Emits `ConnectionClosed` event and terminates connection.
220+
- `.destroy()`: Same as `close()`, but unbind all registred event listeners before.
221+
- `.clone(): Promise<TweetStream>`: Returns a new `TweetStream` with the same request parameters, with the same event listeners bound.
222+
- `.reconnect(): Promise<void>`: Tries to make a new request to Twitter with the same original parameters. If successful, continue streaming with new response.
223+
224+
### Events
225+
226+
All events are part of enum `ETwitterStreamEvent` exported by the package.
227+
228+
- `.ConnectionError`: Emitted with the `err` parameter given by `request.on('error')` or `response.on('error')` handlers.
229+
- `.ConnectionClosed`: Emitted when `.close()` is called or when the connection is manually closed by distant server.
230+
- `.ReconnectError`: Emitted when a auto-reconnect try attempt has failed. Event data is a `number` representing the number of times the request has been **re-made** (starts from `0`).
231+
- `.ReconnectLimitExceeded`: Emitted when `.autoReconnectRetries` limit exceeds.
232+
- `.DataKeepAlive`: Emitted when Twitter sends a `\r\n` to maintain connection open.
233+
- `.Data`: Emitted with stream data, when Twitter sends something.
234+
- `.TweetParseError`: When the thing sent by Twitter cannot be JSON-parsed. Contains the parse error.
235+
- `.Error`: Emitted either when a `.ConnectionError` or a `.TweetParseError` occurs. Contains `{ type: .ConnectionError | .TweetParseError, error: any }`.

0 commit comments

Comments
 (0)