twitter-api-v2
Advanced tools
Comparing version 1.6.0 to 1.6.1
@@ -0,1 +1,8 @@ | ||
1.6.1 | ||
----- | ||
- Feat: New option for creating streams, `autoConnect` that is `true` by default. Setting the value to `false` will cause the `TweetStream` object to be returned immediately (not in a `Promise`), because connection isn't awaited. #92 | ||
- Fix: `autoReconnectRetries`: Setting this params to `Infinity` no longer causes the stream reconnection attempts to be delayed to next event loop turn. #92 | ||
- Fix: Use `https.request(options)` instead of `https.request(url, options)`, because some people has outdated dependencies that overwrite native Node's exported function and break its signature. #94 #96 | ||
- Feat: Next retry timeout computation can be customized by using `.nextRetryTimeout` property of `TweetStream` instance, that is function taking a `tryOccurence` and returning the number of milliseconds to wait before trying to reconnect. | ||
1.6.0 | ||
@@ -2,0 +9,0 @@ ----- |
@@ -24,7 +24,5 @@ /// <reference types="node" /> | ||
protected requestData: TRequestFullData | TRequestFullStreamData; | ||
protected static readonly FORM_ENCODED_ENDPOINTS = "https://api.twitter.com/oauth/"; | ||
protected req: ClientRequest; | ||
protected responseData: string; | ||
constructor(requestData: TRequestFullData | TRequestFullStreamData); | ||
get href(): string; | ||
get hrefPathname(): string; | ||
@@ -38,5 +36,6 @@ protected isFormEncodedEndpoint(): boolean; | ||
protected getParsedResponse(res: IncomingMessage): any; | ||
protected registerRequestErrorHandler(reject: TRequestRejecter): (requestError: Error) => void; | ||
protected registerResponseHandler(resolve: TResponseResolver<T>, reject: TResponseRejecter): (res: IncomingMessage) => void; | ||
protected registerStreamResponseHandler(resolve: TReadyRequestResolver, reject: TResponseRejecter): (res: IncomingMessage) => void; | ||
protected requestErrorHandler(reject: TRequestRejecter, requestError: Error): void; | ||
protected classicResponseHandler(resolve: TResponseResolver<T>, reject: TResponseRejecter, res: IncomingMessage): void; | ||
protected onResponseEndHandler(resolve: TResponseResolver<T>, reject: TResponseRejecter, res: IncomingMessage): void; | ||
protected streamResponseHandler(resolve: TReadyRequestResolver, reject: TResponseRejecter, res: IncomingMessage): void; | ||
protected debugRequest(): void; | ||
@@ -43,0 +42,0 @@ protected buildRequest(): void; |
@@ -16,11 +16,8 @@ "use strict"; | ||
} | ||
get href() { | ||
return this.requestData.url; | ||
} | ||
get hrefPathname() { | ||
const url = new URL(this.requestData.url); | ||
const url = this.requestData.url; | ||
return url.hostname + url.pathname; | ||
} | ||
isFormEncodedEndpoint() { | ||
return this.href.startsWith(RequestHandlerHelper.FORM_ENCODED_ENDPOINTS); | ||
return this.requestData.url.href.startsWith('https://api.twitter.com/oauth/'); | ||
} | ||
@@ -100,49 +97,44 @@ getRateLimitFromResponse(res) { | ||
} | ||
registerRequestErrorHandler(reject) { | ||
return (requestError) => { | ||
reject(this.createRequestError(requestError)); | ||
}; | ||
requestErrorHandler(reject, requestError) { | ||
reject(this.createRequestError(requestError)); | ||
} | ||
registerResponseHandler(resolve, reject) { | ||
return (res) => { | ||
const rateLimit = this.getRateLimitFromResponse(res); | ||
// Register the response data | ||
res.on('data', chunk => this.responseData += chunk); | ||
res.on('end', () => { | ||
const data = this.getParsedResponse(res); | ||
// Handle bad error codes | ||
const code = res.statusCode; | ||
if (code >= 400) { | ||
reject(this.createResponseError({ data, res, rateLimit, code })); | ||
} | ||
if (settings_1.TwitterApiV2Settings.debug) { | ||
console.log(`[${this.requestData.options.method} ${this.hrefPathname}]: Request succeeds with code ${res.statusCode}`); | ||
console.log('Response body:', data); | ||
} | ||
resolve({ | ||
data, | ||
headers: res.headers, | ||
rateLimit, | ||
}); | ||
}); | ||
}; | ||
classicResponseHandler(resolve, reject, res) { | ||
// Register the response data | ||
res.on('data', chunk => this.responseData += chunk); | ||
res.on('end', this.onResponseEndHandler.bind(this, resolve, reject, res)); | ||
} | ||
registerStreamResponseHandler(resolve, reject) { | ||
return (res) => { | ||
const code = res.statusCode; | ||
if (code < 400) { | ||
if (settings_1.TwitterApiV2Settings.debug) { | ||
console.log(`[${this.requestData.options.method} ${this.hrefPathname}]: Request succeeds with code ${res.statusCode} (starting stream)`); | ||
} | ||
// HTTP code ok, consume stream | ||
resolve({ req: this.req, res, requestData: this.requestData }); | ||
onResponseEndHandler(resolve, reject, res) { | ||
const rateLimit = this.getRateLimitFromResponse(res); | ||
const data = this.getParsedResponse(res); | ||
// Handle bad error codes | ||
const code = res.statusCode; | ||
if (code >= 400) { | ||
reject(this.createResponseError({ data, res, rateLimit, code })); | ||
} | ||
if (settings_1.TwitterApiV2Settings.debug) { | ||
console.log(`[${this.requestData.options.method} ${this.hrefPathname}]: Request succeeds with code ${res.statusCode}`); | ||
console.log('Response body:', data); | ||
} | ||
resolve({ | ||
data, | ||
headers: res.headers, | ||
rateLimit, | ||
}); | ||
} | ||
streamResponseHandler(resolve, reject, res) { | ||
const code = res.statusCode; | ||
if (code < 400) { | ||
if (settings_1.TwitterApiV2Settings.debug) { | ||
console.log(`[${this.requestData.options.method} ${this.hrefPathname}]: Request succeeds with code ${res.statusCode} (starting stream)`); | ||
} | ||
else { | ||
// Handle response normally, can only rejects | ||
this.registerResponseHandler(() => undefined, reject)(res); | ||
} | ||
}; | ||
// HTTP code ok, consume stream | ||
resolve({ req: this.req, res, requestData: this.requestData }); | ||
} | ||
else { | ||
// Handle response normally, can only rejects | ||
this.classicResponseHandler(() => undefined, reject, res); | ||
} | ||
} | ||
debugRequest() { | ||
const url = new URL(this.requestData.url); | ||
const url = this.requestData.url; | ||
console.log(`[${this.requestData.options.method} ${this.hrefPathname}]`, this.requestData.options); | ||
@@ -160,3 +152,13 @@ if (url.search) { | ||
} | ||
this.req = https_1.request(this.requestData.url, this.requestData.options); | ||
const url = this.requestData.url; | ||
const auth = url.username ? `${url.username}:${url.password}` : undefined; | ||
this.req = https_1.request({ | ||
...this.requestData.options, | ||
// Define URL params manually, addresses dependencies error https://github.com/PLhery/node-twitter-api-v2/issues/94 | ||
host: url.hostname, | ||
port: url.port || undefined, | ||
path: url.pathname + url.search, | ||
protocol: url.protocol, | ||
auth, | ||
}); | ||
} | ||
@@ -168,4 +170,4 @@ makeRequest() { | ||
// Handle request errors | ||
req.on('error', this.registerRequestErrorHandler(reject)); | ||
req.on('response', this.registerResponseHandler(resolve, reject)); | ||
req.on('error', this.requestErrorHandler.bind(this, reject)); | ||
req.on('response', this.classicResponseHandler.bind(this, resolve, reject)); | ||
if (this.requestData.body) { | ||
@@ -179,3 +181,3 @@ req.write(this.requestData.body); | ||
const { req, res, requestData } = await this.makeRequestAndResolveWhenReady(); | ||
return new TweetStream_1.default(req, res, requestData); | ||
return new TweetStream_1.default(requestData, req, res); | ||
} | ||
@@ -187,4 +189,4 @@ makeRequestAndResolveWhenReady() { | ||
// Handle request errors | ||
req.on('error', this.registerRequestErrorHandler(reject)); | ||
req.on('response', this.registerStreamResponseHandler(resolve, reject)); | ||
req.on('error', this.requestErrorHandler.bind(this, reject)); | ||
req.on('response', this.streamResponseHandler.bind(this, resolve, reject)); | ||
if (this.requestData.body) { | ||
@@ -198,3 +200,2 @@ req.write(this.requestData.body); | ||
exports.RequestHandlerHelper = RequestHandlerHelper; | ||
RequestHandlerHelper.FORM_ENCODED_ENDPOINTS = 'https://api.twitter.com/oauth/'; | ||
exports.default = RequestHandlerHelper; |
@@ -7,3 +7,3 @@ /// <reference types="node" /> | ||
export declare type TRequestFullData = { | ||
url: string; | ||
url: URL; | ||
options: RequestOptions; | ||
@@ -42,3 +42,12 @@ body?: any; | ||
payloadIsError?: (data: any) => boolean; | ||
autoConnect?: boolean; | ||
} | ||
interface IGetStreamRequestArgsAsync { | ||
payloadIsError?: (data: any) => boolean; | ||
autoConnect?: true; | ||
} | ||
interface IGetStreamRequestArgsSync { | ||
payloadIsError?: (data: any) => boolean; | ||
autoConnect: false; | ||
} | ||
export declare type TCustomizableRequestArgs = Pick<IGetHttpRequestArgs, 'headers' | 'params' | 'forceBodyMode' | 'enableAuth' | 'enableRateLimitSave'>; | ||
@@ -59,16 +68,13 @@ export declare abstract class ClientRequestMaker { | ||
protected saveRateLimit(originalUrl: string, rateLimit: TwitterRateLimit): void; | ||
/** | ||
* Send a new request and returns a wrapped `Promise<TwitterResponse<T>`. | ||
* | ||
* The request URL should not contains a query string, prefers using `parameters` for GET request. | ||
* If you need to pass a body AND query string parameter, duplicate parameters in the body. | ||
*/ | ||
/** Send a new request and returns a wrapped `Promise<TwitterResponse<T>`. */ | ||
send<T = any>(requestParams: IGetHttpRequestArgs): Promise<TwitterResponse<T>>; | ||
/** | ||
* Send a new request, then creates a stream from its as a `Promise<TwitterStream>`. | ||
* Create a new request, then creates a stream from it as a `TweetStream`. | ||
* | ||
* The request URL should not contains a query string, prefers using `parameters` for GET request. | ||
* If you need to pass a body AND query string parameter, duplicate parameters in the body. | ||
* Request will be sent only if `autoConnect` is not set or `true`: return type will be `Promise<TweetStream>`. | ||
* If `autoConnect` is `false`, a `TweetStream` is directly returned and you should call `stream.connect()` by yourself. | ||
*/ | ||
sendStream<T = any>(requestParams: IGetHttpRequestArgs & IGetStreamRequestArgs): Promise<TweetStream<T>>; | ||
sendStream<T = any>(requestParams: IGetHttpRequestArgs & IGetStreamRequestArgsSync): TweetStream<T>; | ||
sendStream<T = any>(requestParams: IGetHttpRequestArgs & IGetStreamRequestArgsAsync): Promise<TweetStream<T>>; | ||
sendStream<T = any>(requestParams: IGetHttpRequestArgs & IGetStreamRequestArgs): Promise<TweetStream<T>> | TweetStream<T>; | ||
protected buildOAuth(): OAuth1Helper; | ||
@@ -82,3 +88,3 @@ protected getOAuthAccessTokens(): { | ||
rawUrl: string; | ||
url: string; | ||
url: URL; | ||
method: string; | ||
@@ -85,0 +91,0 @@ headers: Record<string, string>; |
@@ -7,2 +7,3 @@ "use strict"; | ||
exports.ClientRequestMaker = void 0; | ||
const TweetStream_1 = __importDefault(require("../stream/TweetStream")); | ||
const helpers_1 = require("../helpers"); | ||
@@ -19,8 +20,3 @@ const oauth1_helper_1 = __importDefault(require("./oauth1.helper")); | ||
} | ||
/** | ||
* Send a new request and returns a wrapped `Promise<TwitterResponse<T>`. | ||
* | ||
* The request URL should not contains a query string, prefers using `parameters` for GET request. | ||
* If you need to pass a body AND query string parameter, duplicate parameters in the body. | ||
*/ | ||
/** Send a new request and returns a wrapped `Promise<TwitterResponse<T>`. */ | ||
send(requestParams) { | ||
@@ -41,8 +37,2 @@ const args = this.getHttpRequestArgs(requestParams); | ||
} | ||
/** | ||
* Send a new request, then creates a stream from its as a `Promise<TwitterStream>`. | ||
* | ||
* The request URL should not contains a query string, prefers using `parameters` for GET request. | ||
* If you need to pass a body AND query string parameter, duplicate parameters in the body. | ||
*/ | ||
sendStream(requestParams) { | ||
@@ -52,6 +42,7 @@ const args = this.getHttpRequestArgs(requestParams); | ||
const enableRateLimitSave = requestParams.enableRateLimitSave !== false; | ||
const enableAutoConnect = requestParams.autoConnect !== false; | ||
if (args.body) { | ||
request_param_helper_1.default.setBodyLengthHeader(options, args.body); | ||
} | ||
return new request_handler_helper_1.default({ | ||
const requestData = { | ||
url: args.url, | ||
@@ -62,4 +53,8 @@ options, | ||
payloadIsError: requestParams.payloadIsError, | ||
}) | ||
.makeRequestAsStream(); | ||
}; | ||
const stream = new TweetStream_1.default(requestData); | ||
if (!enableAutoConnect) { | ||
return stream; | ||
} | ||
return stream.connect(); | ||
} | ||
@@ -145,3 +140,3 @@ /* Token helpers */ | ||
rawUrl, | ||
url: urlObject.toString(), | ||
url: urlObject, | ||
method, | ||
@@ -148,0 +143,0 @@ headers, |
@@ -29,3 +29,16 @@ import { TClientTokens, TwitterApiBasicAuth, TwitterApiOAuth2Init, TwitterApiTokens, TwitterRateLimit, TwitterResponse, UserV1 } from './types'; | ||
payloadIsError?: (data: any) => boolean; | ||
/** | ||
* Choose to make or not initial connection. | ||
* Method `.connect` must be called on returned `TweetStream` object | ||
* to start stream if `autoConnect` is set to `false`. | ||
* Defaults to `true`. | ||
*/ | ||
autoConnect?: boolean; | ||
}; | ||
export declare type TStreamClientRequestArgsWithAutoConnect = TStreamClientRequestArgs & { | ||
autoConnect?: true; | ||
}; | ||
export declare type TStreamClientRequestArgsWithoutAutoConnect = TStreamClientRequestArgs & { | ||
autoConnect: false; | ||
}; | ||
/** | ||
@@ -92,8 +105,9 @@ * Base class for Twitter instances | ||
/** Stream request helpers */ | ||
getStream<T = any>(url: string, query?: TRequestQuery, { prefix }?: TStreamClientRequestArgs): Promise<TweetStream<T>>; | ||
deleteStream<T = any>(url: string, query?: TRequestQuery, { prefix }?: TStreamClientRequestArgs): Promise<TweetStream<T>>; | ||
postStream<T = any>(url: string, body?: TRequestBody, { prefix, ...rest }?: TStreamClientRequestArgs): Promise<TweetStream<T>>; | ||
putStream<T = any>(url: string, body?: TRequestBody, { prefix, ...rest }?: TStreamClientRequestArgs): Promise<TweetStream<T>>; | ||
patchStream<T = any>(url: string, body?: TRequestBody, { prefix, ...rest }?: TStreamClientRequestArgs): Promise<TweetStream<T>>; | ||
getStream<T = any>(url: string, query: TRequestQuery | undefined, options: TStreamClientRequestArgsWithoutAutoConnect): TweetStream<T>; | ||
getStream<T = any>(url: string, query?: TRequestQuery, options?: TStreamClientRequestArgsWithAutoConnect): Promise<TweetStream<T>>; | ||
getStream<T = any>(url: string, query?: TRequestQuery, options?: TStreamClientRequestArgs): Promise<TweetStream<T>> | TweetStream<T>; | ||
postStream<T = any>(url: string, body: TRequestBody | undefined, options: TStreamClientRequestArgsWithoutAutoConnect): TweetStream<T>; | ||
postStream<T = any>(url: string, body?: TRequestBody, options?: TStreamClientRequestArgsWithAutoConnect): Promise<TweetStream<T>>; | ||
postStream<T = any>(url: string, body?: TRequestBody, options?: TStreamClientRequestArgs): Promise<TweetStream<T>> | TweetStream<T>; | ||
} | ||
export {}; |
@@ -24,2 +24,3 @@ "use strict"; | ||
this._clientId = token._clientId; | ||
this._rateLimits = token._rateLimits; | ||
} | ||
@@ -173,26 +174,13 @@ else if (typeof token === 'object' && 'appKey' in token) { | ||
} | ||
/** Stream request helpers */ | ||
async getStream(url, query, { prefix = this._prefix } = {}) { | ||
if (prefix) | ||
url = prefix + url; | ||
getStream(url, query, { prefix = this._prefix, ...rest } = {}) { | ||
return this.sendStream({ | ||
url, | ||
url: prefix ? prefix + url : url, | ||
method: 'GET', | ||
query, | ||
...rest, | ||
}); | ||
} | ||
async deleteStream(url, query, { prefix = this._prefix } = {}) { | ||
if (prefix) | ||
url = prefix + url; | ||
postStream(url, body, { prefix = this._prefix, ...rest } = {}) { | ||
return this.sendStream({ | ||
url, | ||
method: 'DELETE', | ||
query, | ||
}); | ||
} | ||
async postStream(url, body, { prefix = this._prefix, ...rest } = {}) { | ||
if (prefix) | ||
url = prefix + url; | ||
return this.sendStream({ | ||
url, | ||
url: prefix ? prefix + url : url, | ||
method: 'POST', | ||
@@ -203,23 +191,3 @@ body, | ||
} | ||
async putStream(url, body, { prefix = this._prefix, ...rest } = {}) { | ||
if (prefix) | ||
url = prefix + url; | ||
return this.sendStream({ | ||
url, | ||
method: 'PUT', | ||
body, | ||
...rest, | ||
}); | ||
} | ||
async patchStream(url, body, { prefix = this._prefix, ...rest } = {}) { | ||
if (prefix) | ||
url = prefix + url; | ||
return this.sendStream({ | ||
url, | ||
method: 'PATCH', | ||
body, | ||
...rest, | ||
}); | ||
} | ||
} | ||
exports.default = TwitterApiBase; |
@@ -11,5 +11,12 @@ /// <reference types="node" /> | ||
} | ||
export interface IConnectTweetStreamParams { | ||
autoReconnect: boolean; | ||
autoReconnectRetries: number | 'unlimited'; | ||
/** Check for 'lost connection' status every `keepAliveTimeout` milliseconds. Defaults to 2 minutes (`120000`). */ | ||
keepAliveTimeout: number | 'disable'; | ||
nextRetryTimeout?: TStreamConnectRetryFn; | ||
} | ||
/** Returns a number of milliseconds to wait for {tryOccurence} (starting from 1) */ | ||
export declare type TStreamConnectRetryFn = (tryOccurence: number) => number; | ||
export declare class TweetStream<T = any> extends EventEmitter { | ||
protected req: ClientRequest; | ||
protected res: IncomingMessage; | ||
protected requestData: TRequestFullStreamData; | ||
@@ -19,9 +26,13 @@ autoReconnect: boolean; | ||
keepAliveTimeoutMs: number; | ||
nextRetryTimeout: TStreamConnectRetryFn; | ||
protected retryTimeout?: NodeJS.Timeout; | ||
protected keepAliveTimeout?: NodeJS.Timeout; | ||
protected parser: TweetStreamParser; | ||
constructor(req: ClientRequest, res: IncomingMessage, requestData: TRequestFullStreamData); | ||
protected req?: ClientRequest; | ||
protected res?: IncomingMessage; | ||
constructor(requestData: TRequestFullStreamData, req?: ClientRequest, res?: IncomingMessage); | ||
on(event: ETwitterStreamEvent.Data, handler: (data: T) => any): this; | ||
on(event: ETwitterStreamEvent.DataError, handler: (error: any) => any): this; | ||
on(event: ETwitterStreamEvent.Error, handler: (errorPayload: ITweetStreamError) => any): this; | ||
on(event: ETwitterStreamEvent.Connected, handler: () => any): this; | ||
on(event: ETwitterStreamEvent.ConnectionLost, handler: () => any): this; | ||
@@ -53,9 +64,11 @@ on(event: ETwitterStreamEvent.ConnectionError, handler: (error: Error) => any): this; | ||
*/ | ||
clone(): Promise<TweetStream<unknown>>; | ||
/** Make a new request to reconnect to Twitter. */ | ||
clone(): Promise<TweetStream<T>>; | ||
/** Start initial stream connection, setup options on current instance and returns itself. */ | ||
connect(options?: Partial<IConnectTweetStreamParams>): Promise<this>; | ||
/** Make a new request to (re)connect to Twitter. */ | ||
reconnect(): Promise<void>; | ||
protected onConnectionError(retries?: number): Promise<void>; | ||
protected makeAutoReconnectRetry(retries: number): void; | ||
protected onConnectionError(retryOccurence?: number): Promise<void>; | ||
protected makeAutoReconnectRetry(retryOccurence: number): void; | ||
[Symbol.asyncIterator](): AsyncGenerator<T, void, undefined>; | ||
} | ||
export default TweetStream; |
@@ -31,7 +31,6 @@ "use strict"; | ||
const TweetStreamParser_1 = __importStar(require("./TweetStreamParser")); | ||
const basicReconnectRetry = tryOccurence => Math.min((tryOccurence ** 2) * 1000, 25000); | ||
class TweetStream extends events_1.EventEmitter { | ||
constructor(req, res, requestData) { | ||
constructor(requestData, req, res) { | ||
super(); | ||
this.req = req; | ||
this.res = res; | ||
this.requestData = requestData; | ||
@@ -42,6 +41,11 @@ this.autoReconnect = false; | ||
this.keepAliveTimeoutMs = 1000 * 120; | ||
this.nextRetryTimeout = basicReconnectRetry; | ||
this.parser = new TweetStreamParser_1.default(); | ||
this.onKeepAliveTimeout = this.onKeepAliveTimeout.bind(this); | ||
this.initEventsFromParser(); | ||
this.initEventsFromRequest(); | ||
if (req && res) { | ||
this.req = req; | ||
this.res = res; | ||
this.initEventsFromRequest(); | ||
} | ||
} | ||
@@ -52,2 +56,5 @@ on(event, handler) { | ||
initEventsFromRequest() { | ||
if (!this.req || !this.res) { | ||
throw new Error('TweetStream error: You cannot init TweetStream without a request and response object.'); | ||
} | ||
const errorHandler = (err) => { | ||
@@ -122,6 +129,10 @@ this.emit(types_1.ETwitterStreamEvent.ConnectionError, err); | ||
this.unbindTimeouts(); | ||
this.req.removeAllListeners(); | ||
this.res.removeAllListeners(); | ||
// Close connection silentely | ||
this.req.destroy(); | ||
if (this.res) { | ||
this.res.removeAllListeners(); | ||
} | ||
if (this.req) { | ||
this.req.removeAllListeners(); | ||
// Close connection silentely | ||
this.req.destroy(); | ||
} | ||
} | ||
@@ -155,6 +166,31 @@ /** Terminate connection to Twitter. */ | ||
} | ||
/** Make a new request to reconnect to Twitter. */ | ||
/** Start initial stream connection, setup options on current instance and returns itself. */ | ||
async connect(options = {}) { | ||
if (typeof options.autoReconnect !== 'undefined') { | ||
this.autoReconnect = options.autoReconnect; | ||
} | ||
if (typeof options.autoReconnectRetries !== 'undefined') { | ||
this.autoReconnectRetries = options.autoReconnectRetries === 'unlimited' | ||
? Infinity | ||
: options.autoReconnectRetries; | ||
} | ||
if (typeof options.keepAliveTimeout !== 'undefined') { | ||
this.keepAliveTimeoutMs = options.keepAliveTimeout === 'disable' | ||
? Infinity | ||
: options.keepAliveTimeout; | ||
} | ||
if (typeof options.nextRetryTimeout !== 'undefined') { | ||
this.nextRetryTimeout = options.nextRetryTimeout; | ||
} | ||
await this.reconnect(); | ||
return this; | ||
} | ||
/** Make a new request to (re)connect to Twitter. */ | ||
async reconnect() { | ||
if (!this.req.destroyed) { | ||
this.closeWithoutEmit(); | ||
let initialConnection = true; | ||
if (this.req) { | ||
initialConnection = false; | ||
if (!this.req.destroyed) { | ||
this.closeWithoutEmit(); | ||
} | ||
} | ||
@@ -164,10 +200,10 @@ const { req, res } = await new request_handler_helper_1.default(this.requestData).makeRequestAndResolveWhenReady(); | ||
this.res = res; | ||
this.emit(types_1.ETwitterStreamEvent.Reconnected); | ||
this.emit(initialConnection ? types_1.ETwitterStreamEvent.Connected : types_1.ETwitterStreamEvent.Reconnected); | ||
this.parser.reset(); | ||
this.initEventsFromRequest(); | ||
} | ||
async onConnectionError(retries = this.autoReconnectRetries) { | ||
async onConnectionError(retryOccurence = 0) { | ||
this.unbindTimeouts(); | ||
// Close the request if necessary | ||
if (!this.req.destroyed) { | ||
if (this.req && !this.req.destroyed) { | ||
this.closeWithoutEmit(); | ||
@@ -180,3 +216,3 @@ } | ||
} | ||
if (retries <= 0) { | ||
if (retryOccurence >= this.autoReconnectRetries) { | ||
this.emit(types_1.ETwitterStreamEvent.ReconnectLimitExceeded); | ||
@@ -188,19 +224,18 @@ this.emit(types_1.ETwitterStreamEvent.ConnectionClosed); | ||
try { | ||
this.emit(types_1.ETwitterStreamEvent.ReconnectAttempt, this.autoReconnectRetries - retries); | ||
this.emit(types_1.ETwitterStreamEvent.ReconnectAttempt, retryOccurence); | ||
await this.reconnect(); | ||
} | ||
catch (e) { | ||
this.emit(types_1.ETwitterStreamEvent.ReconnectError, this.autoReconnectRetries - retries); | ||
this.emit(types_1.ETwitterStreamEvent.ReconnectError, retryOccurence); | ||
this.emit(types_1.ETwitterStreamEvent.Error, { | ||
type: types_1.ETwitterStreamEvent.ReconnectError, | ||
error: new Error(`Reconnect error - ${this.autoReconnectRetries - retries} attempts made yet.`), | ||
error: new Error(`Reconnect error - ${retryOccurence + 1} attempts made yet.`), | ||
}); | ||
this.makeAutoReconnectRetry(retries); | ||
this.makeAutoReconnectRetry(retryOccurence); | ||
} | ||
} | ||
makeAutoReconnectRetry(retries) { | ||
const tryOccurence = (this.autoReconnectRetries - retries) + 1; | ||
const nextRetry = Math.min((tryOccurence ** 2) * 1000, 25000); | ||
makeAutoReconnectRetry(retryOccurence) { | ||
const nextRetry = this.nextRetryTimeout(retryOccurence + 1); | ||
this.retryTimeout = setTimeout(() => { | ||
this.onConnectionError(retries - 1); | ||
this.onConnectionError(retryOccurence + 1); | ||
}, nextRetry); | ||
@@ -212,3 +247,3 @@ } | ||
while (true) { | ||
if (this.req.aborted) { | ||
if (!this.req || this.req.aborted) { | ||
throw new Error('Connection closed'); | ||
@@ -215,0 +250,0 @@ } |
export declare enum ETwitterStreamEvent { | ||
Connected = "connected", | ||
ConnectionError = "connection error", | ||
@@ -3,0 +4,0 @@ ConnectionClosed = "connection closed", |
@@ -6,2 +6,3 @@ "use strict"; | ||
(function (ETwitterStreamEvent) { | ||
ETwitterStreamEvent["Connected"] = "connected"; | ||
ETwitterStreamEvent["ConnectionError"] = "connection error"; | ||
@@ -8,0 +9,0 @@ ETwitterStreamEvent["ConnectionClosed"] = "connection closed"; |
export declare type NumberString = number | string; | ||
export declare type BooleanString = boolean | string; | ||
export declare type TypeOrArrayOf<T> = T | T[]; | ||
export declare type PromiseOrType<T> = T | Promise<T>; |
@@ -7,2 +7,4 @@ import TwitterApiSubClient from '../client.subclient'; | ||
import { ListMembershipsV1Paginator, ListMembersV1Paginator, ListOwnershipsV1Paginator, ListSubscribersV1Paginator, ListSubscriptionsV1Paginator } from '../paginators/list.paginator.v1'; | ||
import TweetStream from '../stream/TweetStream'; | ||
import { PromiseOrType } from '../types/shared.types'; | ||
/** | ||
@@ -188,3 +190,11 @@ * Base Twitter v1 client with only read right. | ||
*/ | ||
filterStream(params?: Partial<FilterStreamV1Params>): Promise<import("..").TweetStream<TweetV1>>; | ||
filterStream(params?: Partial<FilterStreamV1Params> & { | ||
autoConnect?: true; | ||
}): Promise<TweetStream<TweetV1>>; | ||
filterStream(params: Partial<FilterStreamV1Params> & { | ||
autoConnect: false; | ||
}): TweetStream<TweetV1>; | ||
filterStream(params?: Partial<FilterStreamV1Params> & { | ||
autoConnect?: boolean; | ||
}): PromiseOrType<TweetStream<TweetV1>>; | ||
/** | ||
@@ -195,3 +205,11 @@ * Returns a small random sample of all public statuses. | ||
*/ | ||
sampleStream(params?: Partial<SampleStreamV1Params>): Promise<import("..").TweetStream<TweetV1>>; | ||
sampleStream(params?: Partial<SampleStreamV1Params> & { | ||
autoConnect?: true; | ||
}): Promise<TweetStream<TweetV1>>; | ||
sampleStream(params: Partial<SampleStreamV1Params> & { | ||
autoConnect: false; | ||
}): TweetStream<TweetV1>; | ||
sampleStream(params?: Partial<SampleStreamV1Params> & { | ||
autoConnect?: boolean; | ||
}): PromiseOrType<TweetStream<TweetV1>>; | ||
/** | ||
@@ -198,0 +216,0 @@ * Create a client that is prefixed with `https//stream.twitter.com` instead of classic API URL. |
@@ -416,9 +416,3 @@ "use strict"; | ||
} | ||
/* Streaming API */ | ||
/** | ||
* Returns public statuses that match one or more filter predicates. | ||
* Multiple parameters may be specified which allows most clients to use a single connection to the Streaming API. | ||
* https://developer.twitter.com/en/docs/twitter-api/v1/tweets/filter-realtime/api-reference/post-statuses-filter | ||
*/ | ||
filterStream(params = {}) { | ||
filterStream({ autoConnect, ...params } = {}) { | ||
const parameters = {}; | ||
@@ -438,12 +432,7 @@ for (const [key, value] of Object.entries(params)) { | ||
const streamClient = this.stream; | ||
return streamClient.postStream('statuses/filter.json', parameters); | ||
return streamClient.postStream('statuses/filter.json', parameters, { autoConnect }); | ||
} | ||
/** | ||
* Returns a small random sample of all public statuses. | ||
* The Tweets returned by the default access level are the same, so if two different clients connect to this endpoint, they will see the same Tweets. | ||
* https://developer.twitter.com/en/docs/twitter-api/v1/tweets/sample-realtime/api-reference/get-statuses-sample | ||
*/ | ||
sampleStream(params = {}) { | ||
sampleStream({ autoConnect, ...params } = {}) { | ||
const streamClient = this.stream; | ||
return streamClient.getStream('statuses/sample.json', params); | ||
return streamClient.getStream('statuses/sample.json', params, { autoConnect }); | ||
} | ||
@@ -450,0 +439,0 @@ /** |
@@ -402,3 +402,3 @@ "use strict"; | ||
// Await for first promise to be finished | ||
await Promise.race([...currentUploads]); | ||
await Promise.race(currentUploads); | ||
} | ||
@@ -405,0 +405,0 @@ [readBuffer, nread] = await media_helpers_v1_1.readNextPartOf(fileHandle, chunkLength, offset, buffer); |
@@ -6,2 +6,4 @@ import TwitterApiSubClient from '../client.subclient'; | ||
import { UserBlockingUsersV2Paginator, UserFollowersV2Paginator, UserFollowingV2Paginator, UserMutingUsersV2Paginator } from '../paginators/user.paginator.v2'; | ||
import TweetStream from '../stream/TweetStream'; | ||
import { PromiseOrType } from '../types/shared.types'; | ||
/** | ||
@@ -164,3 +166,11 @@ * Base Twitter v2 client with only read right. | ||
*/ | ||
searchStream(options?: Partial<TweetSearchV2StreamParams>): Promise<import("..").TweetStream<TweetV2SingleStreamResult>>; | ||
searchStream(options?: Partial<TweetSearchV2StreamParams> & { | ||
autoConnect?: true; | ||
}): Promise<TweetStream<TweetV2SingleStreamResult>>; | ||
searchStream(options: Partial<TweetSearchV2StreamParams> & { | ||
autoConnect: false; | ||
}): TweetStream<TweetV2SingleStreamResult>; | ||
searchStream(options?: Partial<TweetSearchV2StreamParams> & { | ||
autoConnect?: boolean; | ||
}): PromiseOrType<TweetStream<TweetV2SingleStreamResult>>; | ||
/** | ||
@@ -183,3 +193,11 @@ * Return a list of rules currently active on the streaming endpoint, either as a list or individually. | ||
*/ | ||
sampleStream(options?: Partial<Tweetv2FieldsParams>): Promise<import("..").TweetStream<TweetV2SingleResult>>; | ||
sampleStream(options?: Partial<Tweetv2FieldsParams> & { | ||
autoConnect?: true; | ||
}): Promise<TweetStream<TweetV2SingleResult>>; | ||
sampleStream(options: Partial<Tweetv2FieldsParams> & { | ||
autoConnect: false; | ||
}): TweetStream<TweetV2SingleResult>; | ||
sampleStream(options?: Partial<Tweetv2FieldsParams> & { | ||
autoConnect?: boolean; | ||
}): PromiseOrType<TweetStream<TweetV2SingleResult>>; | ||
/** | ||
@@ -186,0 +204,0 @@ * Returns a list of recent compliance jobs. |
@@ -294,9 +294,4 @@ "use strict"; | ||
} | ||
/* Streaming API */ | ||
/** | ||
* Streams Tweets in real-time based on a specific set of filter rules. | ||
* https://developer.twitter.com/en/docs/twitter-api/tweets/filtered-stream/api-reference/get-tweets-search-stream | ||
*/ | ||
searchStream(options = {}) { | ||
return this.getStream('tweets/search/stream', options, { payloadIsError: helpers_1.isTweetStreamV2ErrorPayload }); | ||
searchStream({ autoConnect, ...options } = {}) { | ||
return this.getStream('tweets/search/stream', options, { payloadIsError: helpers_1.isTweetStreamV2ErrorPayload, autoConnect }); | ||
} | ||
@@ -313,8 +308,4 @@ /** | ||
} | ||
/** | ||
* Streams about 1% of all Tweets in real-time. | ||
* https://developer.twitter.com/en/docs/twitter-api/tweets/sampled-stream/api-reference/get-tweets-sample-stream | ||
*/ | ||
sampleStream(options = {}) { | ||
return this.getStream('tweets/sample/stream', options, { payloadIsError: helpers_1.isTweetStreamV2ErrorPayload }); | ||
sampleStream({ autoConnect, ...options } = {}) { | ||
return this.getStream('tweets/sample/stream', options, { payloadIsError: helpers_1.isTweetStreamV2ErrorPayload, autoConnect }); | ||
} | ||
@@ -321,0 +312,0 @@ /* Batch compliance */ |
{ | ||
"name": "twitter-api-v2", | ||
"version": "1.6.0", | ||
"version": "1.6.1", | ||
"description": "Strongly typed, full-featured, light, versatile yet powerful Twitter API v1.1 and v2 client for Node.js.", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -15,3 +15,3 @@ # Twitter API v2 | ||
✅ **Light: No dependencies, 16kb minified+gzipped** | ||
✅ **Light: No dependencies, 17kb minified+gzipped** | ||
@@ -39,3 +39,3 @@ ✅ **Bundled types for request parameters and responses** | ||
| -------------- | -------------- | ---------------- | ------------- | ---------- | --------------- | -------------:| -------------:| | ||
| twitter-api-v2 | v1.1, v2, labs | ✅ | ✅ | ✅ | 0 | ~16 kB | [![twitter-api-v2 install size badge](https://badgen.net/packagephobia/install/twitter-api-v2)](https://packagephobia.com/result?p=twitter-api-v2) | | ||
| twitter-api-v2 | v1.1, v2, labs | ✅ | ✅ | ✅ | 0 | ~17 kB | [![twitter-api-v2 install size badge](https://badgen.net/packagephobia/install/twitter-api-v2)](https://packagephobia.com/result?p=twitter-api-v2) | | ||
| twit | v1.1 | ❌ | ✅ | ❌ | 51 | ~214.5 kB | [![twit install size badge](https://badgen.net/packagephobia/install/twit)](https://packagephobia.com/result?p=twit) | | ||
@@ -42,0 +42,0 @@ | twitter | v1.1 | ❌ | ❌ | ❌ | 50 | ~182.1 kB | [![twitter install size badge](https://badgen.net/packagephobia/install/twitter)](https://packagephobia.com/result?p=twitter) | |
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
393264
8759