twitter-api-v2
Version:
Strongly typed, full-featured, light, versatile yet powerful Twitter API v1.1 and v2 client for Node.js.
195 lines (194 loc) • 7.56 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RequestHandlerHelper = void 0;
const https_1 = require("https");
const settings_1 = require("../settings");
const TweetStream_1 = __importDefault(require("../stream/TweetStream"));
const types_1 = require("../types");
class RequestHandlerHelper {
constructor(requestData) {
this.requestData = requestData;
this.responseData = '';
}
get hrefPathname() {
const url = this.requestData.url;
return url.hostname + url.pathname;
}
isFormEncodedEndpoint() {
return this.requestData.url.href.startsWith('https://api.twitter.com/oauth/');
}
getRateLimitFromResponse(res) {
let rateLimit = undefined;
if (res.headers['x-rate-limit-limit']) {
rateLimit = {
limit: Number(res.headers['x-rate-limit-limit']),
remaining: Number(res.headers['x-rate-limit-remaining']),
reset: Number(res.headers['x-rate-limit-reset']),
};
if (this.requestData.rateLimitSaver) {
this.requestData.rateLimitSaver(rateLimit);
}
}
return rateLimit;
}
createRequestError(error) {
if (settings_1.TwitterApiV2Settings.debug) {
console.log('Request network error:', error);
}
return new types_1.ApiRequestError('Request failed.', {
request: this.req,
error,
});
}
formatV1Errors(errors) {
return errors
.map(({ code, message }) => `${message} (Twitter code ${code})`)
.join(', ');
}
formatV2Error(error) {
return `${error.title}: ${error.detail} (see ${error.type})`;
}
createResponseError({ res, data, rateLimit, code }) {
var _a;
if (settings_1.TwitterApiV2Settings.debug) {
console.log('Request failed with code', code, ', data:', data, 'response headers:', res.headers);
}
// Errors formatting.
let errorString = `Request failed with code ${code}`;
if ((_a = data === null || data === void 0 ? void 0 : data.errors) === null || _a === void 0 ? void 0 : _a.length) {
const errors = data.errors;
if ('code' in errors[0]) {
errorString += ' - ' + this.formatV1Errors(errors);
}
else {
errorString += ' - ' + this.formatV2Error(data);
}
}
return new types_1.ApiResponseError(errorString, {
code,
data,
headers: res.headers,
request: this.req,
response: res,
rateLimit,
});
}
getParsedResponse(res) {
var _a;
let data = this.responseData;
// Auto parse if server responds with JSON body
if (data.length && ((_a = res.headers['content-type']) === null || _a === void 0 ? void 0 : _a.includes('application/json'))) {
data = JSON.parse(data);
}
// f-e oauth token endpoints
else if (this.isFormEncodedEndpoint()) {
const response_form_entries = {};
for (const [item, value] of new URLSearchParams(data)) {
response_form_entries[item] = value;
}
data = response_form_entries;
}
return data;
}
requestErrorHandler(reject, requestError) {
reject(this.createRequestError(requestError));
}
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));
}
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)`);
}
// 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 = this.requestData.url;
console.log(`[${this.requestData.options.method} ${this.hrefPathname}]`, this.requestData.options);
if (url.search) {
console.log('Request parameters:', [...url.searchParams.entries()].map(([key, value]) => `${key}: ${value}`));
}
if (this.requestData.body) {
console.log('Request body:', this.requestData.body);
}
}
buildRequest() {
if (settings_1.TwitterApiV2Settings.debug) {
this.debugRequest();
}
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,
});
}
makeRequest() {
this.buildRequest();
return new Promise((resolve, reject) => {
const req = this.req;
// Handle request errors
req.on('error', this.requestErrorHandler.bind(this, reject));
req.on('response', this.classicResponseHandler.bind(this, resolve, reject));
if (this.requestData.body) {
req.write(this.requestData.body);
}
req.end();
});
}
async makeRequestAsStream() {
const { req, res, requestData } = await this.makeRequestAndResolveWhenReady();
return new TweetStream_1.default(requestData, req, res);
}
makeRequestAndResolveWhenReady() {
this.buildRequest();
return new Promise((resolve, reject) => {
const req = this.req;
// Handle request errors
req.on('error', this.requestErrorHandler.bind(this, reject));
req.on('response', this.streamResponseHandler.bind(this, resolve, reject));
if (this.requestData.body) {
req.write(this.requestData.body);
}
req.end();
});
}
}
exports.RequestHandlerHelper = RequestHandlerHelper;
exports.default = RequestHandlerHelper;