twitter-api-v2
Advanced tools
Comparing version 1.10.0 to 1.10.1
@@ -0,1 +1,6 @@ | ||
1.10.1 | ||
------ | ||
- Fix: Crash when a v2 paginator is empty and response does not contains a `.meta` property #177 | ||
- Fix: "Memory leak" when response are abruptly closed by Twitter or OS, because no close/error listener on response object was attributed | ||
1.10.0 | ||
@@ -2,0 +7,0 @@ ------ |
/// <reference types="node" /> | ||
import type { IncomingMessage, ClientRequest } from 'http'; | ||
import TweetStream from '../stream/TweetStream'; | ||
import { ApiRequestError, ApiResponseError } from '../types'; | ||
import { ApiPartialResponseError, ApiRequestError, ApiResponseError } from '../types'; | ||
import type { ErrorV1, ErrorV2, TwitterRateLimit, TwitterResponse } from '../types'; | ||
import type { TRequestFullData, TRequestFullStreamData } from './request-maker.mixin'; | ||
import type { IncomingMessage, ClientRequest } from 'http'; | ||
import type { TRequestFullData, TRequestFullStreamData } from '../types/request-maker.mixin.types'; | ||
declare type TRequestReadyPayload = { | ||
@@ -15,3 +15,3 @@ req: ClientRequest; | ||
declare type TRequestRejecter = (error: ApiRequestError) => void; | ||
declare type TResponseRejecter = (error: ApiResponseError) => void; | ||
declare type TResponseRejecter = (error: ApiResponseError | ApiPartialResponseError) => void; | ||
interface IBuildErrorParams { | ||
@@ -26,2 +26,3 @@ res: IncomingMessage; | ||
protected req: ClientRequest; | ||
protected res: IncomingMessage; | ||
protected responseData: string; | ||
@@ -33,2 +34,3 @@ constructor(requestData: TRequestFullData | TRequestFullStreamData); | ||
protected createRequestError(error: Error): ApiRequestError; | ||
protected createPartialResponseError(error: Error, abortClose: boolean): ApiPartialResponseError; | ||
protected formatV1Errors(errors: ErrorV1[]): string; | ||
@@ -41,6 +43,8 @@ protected formatV2Error(error: ErrorV2): string; | ||
protected classicResponseHandler(resolve: TResponseResolver<T>, reject: TResponseRejecter, res: IncomingMessage): void; | ||
protected onResponseEndHandler(resolve: TResponseResolver<T>, reject: TResponseRejecter, res: IncomingMessage): void; | ||
protected onResponseEndHandler(resolve: TResponseResolver<T>, reject: TResponseRejecter): void; | ||
protected onResponseCloseHandler(resolve: TResponseResolver<T>, reject: TResponseRejecter): void; | ||
protected streamResponseHandler(resolve: TReadyRequestResolver, reject: TResponseRejecter, res: IncomingMessage): void; | ||
protected debugRequest(): void; | ||
protected buildRequest(): void; | ||
protected registerRequestEventDebugHandlers(req: ClientRequest): void; | ||
makeRequest(): Promise<TwitterResponse<T>>; | ||
@@ -47,0 +51,0 @@ makeRequestAsStream(): Promise<TweetStream<T>>; |
@@ -46,2 +46,18 @@ "use strict"; | ||
} | ||
createPartialResponseError(error, abortClose) { | ||
const res = this.res; | ||
let message = `Request failed with partial response with HTTP code ${res.statusCode}`; | ||
if (abortClose) { | ||
message += ' (connection abruptly closed)'; | ||
} | ||
else { | ||
message += ' (parse error)'; | ||
} | ||
return new types_1.ApiPartialResponseError(message, { | ||
request: this.req, | ||
response: this.res, | ||
responseError: error, | ||
rawContent: this.responseData, | ||
}); | ||
} | ||
formatV1Errors(errors) { | ||
@@ -90,7 +106,7 @@ return errors | ||
else if (this.isFormEncodedEndpoint()) { | ||
const response_form_entries = {}; | ||
const formEntries = {}; | ||
for (const [item, value] of new URLSearchParams(data)) { | ||
response_form_entries[item] = value; | ||
formEntries[item] = value; | ||
} | ||
data = response_form_entries; | ||
data = formEntries; | ||
} | ||
@@ -100,2 +116,4 @@ return data; | ||
requestErrorHandler(reject, requestError) { | ||
var _a, _b; | ||
(_b = (_a = this.requestData).requestEventDebugHandler) === null || _b === void 0 ? void 0 : _b.call(_a, 'request-error', { requestError }); | ||
reject(this.createRequestError(requestError)); | ||
@@ -108,17 +126,35 @@ this.req.removeAllListeners('timeout'); | ||
classicResponseHandler(resolve, reject, res) { | ||
this.res = res; | ||
// Register the response data | ||
res.on('data', chunk => this.responseData += chunk); | ||
res.on('end', this.onResponseEndHandler.bind(this, resolve, reject, res)); | ||
res.on('end', this.onResponseEndHandler.bind(this, resolve, reject)); | ||
res.on('close', this.onResponseCloseHandler.bind(this, resolve, reject)); | ||
// Debug handlers | ||
if (this.requestData.requestEventDebugHandler) { | ||
this.requestData.requestEventDebugHandler('response', { res }); | ||
res.on('aborted', error => this.requestData.requestEventDebugHandler('response-aborted', { error })); | ||
res.on('error', error => this.requestData.requestEventDebugHandler('response-error', { error })); | ||
res.on('close', () => this.requestData.requestEventDebugHandler('response-close', { data: this.responseData })); | ||
res.on('end', () => this.requestData.requestEventDebugHandler('response-end')); | ||
} | ||
} | ||
onResponseEndHandler(resolve, reject, res) { | ||
onResponseEndHandler(resolve, reject) { | ||
this.req.removeAllListeners('timeout'); | ||
const rateLimit = this.getRateLimitFromResponse(res); | ||
const data = this.getParsedResponse(res); | ||
const rateLimit = this.getRateLimitFromResponse(this.res); | ||
let data; | ||
try { | ||
data = this.getParsedResponse(this.res); | ||
} | ||
catch (e) { | ||
reject(this.createPartialResponseError(e, false)); | ||
return; | ||
} | ||
// Handle bad error codes | ||
const code = res.statusCode; | ||
const code = this.res.statusCode; | ||
if (code >= 400) { | ||
reject(this.createResponseError({ data, res, rateLimit, code })); | ||
reject(this.createResponseError({ data, res: this.res, rateLimit, code })); | ||
return; | ||
} | ||
if (settings_1.TwitterApiV2Settings.debug) { | ||
settings_1.TwitterApiV2Settings.logger.log(`[${this.requestData.options.method} ${this.hrefPathname}]: Request succeeds with code ${res.statusCode}`); | ||
settings_1.TwitterApiV2Settings.logger.log(`[${this.requestData.options.method} ${this.hrefPathname}]: Request succeeds with code ${this.res.statusCode}`); | ||
settings_1.TwitterApiV2Settings.logger.log('Response body:', data); | ||
@@ -128,6 +164,26 @@ } | ||
data, | ||
headers: res.headers, | ||
headers: this.res.headers, | ||
rateLimit, | ||
}); | ||
} | ||
onResponseCloseHandler(resolve, reject) { | ||
this.req.removeAllListeners('timeout'); | ||
const res = this.res; | ||
if (res.aborted) { | ||
// Try to parse the request (?) | ||
try { | ||
this.getParsedResponse(this.res); | ||
// Ok, try to resolve normally the request | ||
return this.onResponseEndHandler(resolve, reject); | ||
} | ||
catch (e) { | ||
// Parse error, just drop with content | ||
return reject(this.createPartialResponseError(e, true)); | ||
} | ||
} | ||
if (!res.complete) { | ||
return reject(this.createPartialResponseError(new Error('Response has been interrupted before response could be parsed.'), true)); | ||
} | ||
// else: end has been called | ||
} | ||
streamResponseHandler(resolve, reject, res) { | ||
@@ -173,2 +229,14 @@ const code = res.statusCode; | ||
} | ||
registerRequestEventDebugHandlers(req) { | ||
req.on('abort', () => this.requestData.requestEventDebugHandler('abort')); | ||
req.on('socket', socket => { | ||
this.requestData.requestEventDebugHandler('socket', { socket }); | ||
socket.on('error', error => this.requestData.requestEventDebugHandler('socket-error', { socket, error })); | ||
socket.on('connect', () => this.requestData.requestEventDebugHandler('socket-connect', { socket })); | ||
socket.on('close', withError => this.requestData.requestEventDebugHandler('socket-close', { socket, withError })); | ||
socket.on('end', () => this.requestData.requestEventDebugHandler('socket-end', { socket })); | ||
socket.on('lookup', (...data) => this.requestData.requestEventDebugHandler('socket-lookup', { socket, data })); | ||
socket.on('timeout', () => this.requestData.requestEventDebugHandler('socket-timeout', { socket })); | ||
}); | ||
} | ||
makeRequest() { | ||
@@ -184,2 +252,6 @@ this.buildRequest(); | ||
} | ||
// Debug handlers | ||
if (this.requestData.requestEventDebugHandler) { | ||
this.registerRequestEventDebugHandlers(req); | ||
} | ||
if (this.requestData.body) { | ||
@@ -186,0 +258,0 @@ req.write(this.requestData.body); |
/// <reference types="node" /> | ||
import { IClientSettings, TwitterRateLimit, TwitterResponse } from '../types'; | ||
import TweetStream from '../stream/TweetStream'; | ||
import type { RequestOptions } from 'https'; | ||
import OAuth1Helper from './oauth1.helper'; | ||
export declare type TRequestFullData = { | ||
url: URL; | ||
options: RequestOptions; | ||
body?: any; | ||
rateLimitSaver?: (rateLimit: TwitterRateLimit) => any; | ||
}; | ||
export declare type TRequestFullStreamData = TRequestFullData & { | ||
payloadIsError?: (data: any) => boolean; | ||
}; | ||
export declare type TRequestQuery = Record<string, string | number | boolean | string[] | undefined>; | ||
export declare type TRequestStringQuery = Record<string, string>; | ||
export declare type TRequestBody = Record<string, any> | Buffer; | ||
export declare type TBodyMode = 'json' | 'url' | 'form-data' | 'raw'; | ||
interface IWriteAuthHeadersArgs { | ||
headers: Record<string, string>; | ||
bodyInSignature: boolean; | ||
url: URL; | ||
method: string; | ||
query: TRequestQuery; | ||
body: TRequestBody; | ||
} | ||
export interface IGetHttpRequestArgs { | ||
url: string; | ||
method: string; | ||
query?: TRequestQuery; | ||
/** The URL parameters, if you specify an endpoint with `:id`, for example. */ | ||
params?: TRequestQuery; | ||
body?: TRequestBody; | ||
headers?: Record<string, string>; | ||
forceBodyMode?: TBodyMode; | ||
enableAuth?: boolean; | ||
enableRateLimitSave?: boolean; | ||
timeout?: number; | ||
} | ||
export interface IGetStreamRequestArgs { | ||
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'>; | ||
import type { IGetHttpRequestArgs, IGetStreamRequestArgs, IGetStreamRequestArgsAsync, IGetStreamRequestArgsSync, IWriteAuthHeadersArgs } from '../types/request-maker.mixin.types'; | ||
export declare abstract class ClientRequestMaker { | ||
@@ -94,2 +47,1 @@ protected _bearerToken?: string; | ||
} | ||
export {}; |
@@ -39,2 +39,3 @@ "use strict"; | ||
rateLimitSaver: enableRateLimitSave ? this.saveRateLimit.bind(this, args.rawUrl) : undefined, | ||
requestEventDebugHandler: requestParams.requestEventDebugHandler, | ||
}) | ||
@@ -41,0 +42,0 @@ .makeRequest(); |
/// <reference types="node" /> | ||
import type { RequestOptions } from 'https'; | ||
import type { TBodyMode, TRequestBody, TRequestQuery, TRequestStringQuery } from './request-maker.mixin'; | ||
import type { TBodyMode, TRequestBody, TRequestQuery, TRequestStringQuery } from '../types/request-maker.mixin.types'; | ||
export declare class RequestParamHelpers { | ||
@@ -5,0 +5,0 @@ static readonly JSON_1_1_ENDPOINTS: Set<string>; |
import type { IClientSettings, TClientTokens, TwitterApiBasicAuth, TwitterApiOAuth2Init, TwitterApiTokens, TwitterRateLimit, TwitterResponse, UserV1, UserV2Result } from './types'; | ||
import { ClientRequestMaker, TCustomizableRequestArgs, TRequestBody, TRequestQuery } from './client-mixins/request-maker.mixin'; | ||
import { ClientRequestMaker } from './client-mixins/request-maker.mixin'; | ||
import TweetStream from './stream/TweetStream'; | ||
import { SharedPromise } from './helpers'; | ||
import type { TCustomizableRequestArgs, TRequestBody, TRequestQuery } from './types/request-maker.mixin.types'; | ||
export declare type TGetClientRequestArgs = TCustomizableRequestArgs & { | ||
@@ -6,0 +7,0 @@ prefix?: string; |
@@ -11,2 +11,1 @@ export { default as default } from './client'; | ||
export * from './settings'; | ||
export { IGetHttpRequestArgs } from './client-mixins/request-maker.mixin'; |
@@ -109,5 +109,6 @@ "use strict"; | ||
canFetchNextPage(result) { | ||
return !!result.meta.next_token; | ||
var _a; | ||
return !!((_a = result.meta) === null || _a === void 0 ? void 0 : _a.next_token); | ||
} | ||
} | ||
exports.TimelineV2Paginator = TimelineV2Paginator; |
/// <reference types="node" /> | ||
import { EventEmitter } from 'events'; | ||
import type { IncomingMessage, ClientRequest } from 'http'; | ||
import { TRequestFullStreamData } from '../client-mixins/request-maker.mixin'; | ||
import { ETwitterStreamEvent } from '../types'; | ||
import { TRequestFullStreamData } from '../types/request-maker.mixin.types'; | ||
import TweetStreamParser from './TweetStreamParser'; | ||
@@ -7,0 +7,0 @@ interface ITweetStreamError { |
@@ -46,2 +46,3 @@ /// <reference types="node" /> | ||
Request = "request", | ||
PartialResponse = "partial-response", | ||
Response = "response" | ||
@@ -61,3 +62,3 @@ } | ||
declare abstract class ApiError extends Error { | ||
abstract type: ETwitterApiError.Request | ETwitterApiError.Response; | ||
abstract type: ETwitterApiError.Request | ETwitterApiError.Response | ETwitterApiError.PartialResponse; | ||
abstract request: ClientRequest; | ||
@@ -81,2 +82,21 @@ error: true; | ||
} | ||
interface IBuildApiPartialRequestError { | ||
readonly request: ClientRequest; | ||
readonly response: IncomingMessage; | ||
readonly rawContent: string; | ||
responseError: Error; | ||
} | ||
export declare class ApiPartialResponseError extends ApiError implements IBuildApiPartialRequestError { | ||
protected _options: any; | ||
type: ETwitterApiError.PartialResponse; | ||
constructor(message: string, options: IBuildApiPartialRequestError); | ||
get request(): ClientRequest; | ||
get response(): IncomingMessage; | ||
get responseError(): Error; | ||
get rawContent(): string; | ||
toJSON(): { | ||
type: ETwitterApiError.PartialResponse; | ||
error: Error; | ||
}; | ||
} | ||
interface IBuildApiResponseError { | ||
@@ -83,0 +103,0 @@ code: number; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.EApiV2ErrorCode = exports.EApiV1ErrorCode = exports.ApiResponseError = exports.ApiRequestError = exports.ETwitterApiError = void 0; | ||
exports.EApiV2ErrorCode = exports.EApiV1ErrorCode = exports.ApiResponseError = exports.ApiPartialResponseError = exports.ApiRequestError = exports.ETwitterApiError = void 0; | ||
var ETwitterApiError; | ||
(function (ETwitterApiError) { | ||
ETwitterApiError["Request"] = "request"; | ||
ETwitterApiError["PartialResponse"] = "partial-response"; | ||
ETwitterApiError["Response"] = "response"; | ||
@@ -38,2 +39,30 @@ })(ETwitterApiError = exports.ETwitterApiError || (exports.ETwitterApiError = {})); | ||
exports.ApiRequestError = ApiRequestError; | ||
class ApiPartialResponseError extends ApiError { | ||
constructor(message, options) { | ||
super(message); | ||
this.type = ETwitterApiError.PartialResponse; | ||
Error.captureStackTrace(this, this.constructor); | ||
// Do not show on Node stack trace | ||
Object.defineProperty(this, '_options', { value: options }); | ||
} | ||
get request() { | ||
return this._options.request; | ||
} | ||
get response() { | ||
return this._options.response; | ||
} | ||
get responseError() { | ||
return this._options.responseError; | ||
} | ||
get rawContent() { | ||
return this._options.rawContent; | ||
} | ||
toJSON() { | ||
return { | ||
type: this.type, | ||
error: this.responseError, | ||
}; | ||
} | ||
} | ||
exports.ApiPartialResponseError = ApiPartialResponseError; | ||
class ApiResponseError extends ApiError { | ||
@@ -40,0 +69,0 @@ constructor(message, options) { |
@@ -7,1 +7,2 @@ export * from './v1'; | ||
export * from './auth.types'; | ||
export { IGetHttpRequestArgs } from './request-maker.mixin.types'; |
{ | ||
"name": "twitter-api-v2", | ||
"version": "1.10.0", | ||
"version": "1.10.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", |
@@ -39,6 +39,6 @@ # Twitter API v2 | ||
Here's is a quick example of usage: | ||
Here's a quick example of usage: | ||
```ts | ||
import TwitterApi from 'twitter-api-v2'; | ||
import { TwitterApi } from 'twitter-api-v2'; | ||
@@ -45,0 +45,0 @@ // Instanciate with desired auth type (here's Bearer v2 auth) |
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
450885
140
9955