UNPKG

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
"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;