Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 2s
206 lines
6.0 KiB
TypeScript
206 lines
6.0 KiB
TypeScript
import { serverFetch as fetch } from '@rocket.chat/server-fetch';
|
|
import { HTTP } from 'meteor/http';
|
|
import { URL } from 'meteor/url';
|
|
|
|
import { truncate } from '../../../lib/utils/stringUtils';
|
|
|
|
// Code extracted from https://github.com/meteor/meteor/blob/master/packages/deprecated/http
|
|
// Modified to:
|
|
// - Respect proxy envvars such as HTTP_PROXY and NO_PROXY
|
|
// - Respect HTTP_DEFAULT_TIMEOUT envvar or use 20s when it is not set
|
|
|
|
const envTimeout = parseInt(process.env.HTTP_DEFAULT_TIMEOUT || '', 10);
|
|
const defaultTimeout = !isNaN(envTimeout) ? envTimeout : 20000;
|
|
|
|
type HttpCallOptions = {
|
|
content?: string | URLSearchParams;
|
|
data?: Record<string, any>;
|
|
query?: string;
|
|
params?: Record<string, string>;
|
|
auth?: string;
|
|
headers?: Record<string, string>;
|
|
timeout?: number;
|
|
followRedirects?: boolean;
|
|
referrer?: string;
|
|
integrity?: string;
|
|
};
|
|
|
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
interface HTTPResponse {
|
|
statusCode?: number;
|
|
headers?: { [id: string]: string };
|
|
content?: string;
|
|
data?: any;
|
|
ok?: boolean;
|
|
redirected?: boolean;
|
|
}
|
|
|
|
type callbackFn = (error: Error | undefined, result?: HTTPResponse) => void;
|
|
|
|
// Fill in `response.data` if the content-type is JSON.
|
|
function populateData(response: Record<string, any>): void {
|
|
// Read Content-Type header, up to a ';' if there is one.
|
|
// A typical header might be "application/json; charset=utf-8"
|
|
// or just "application/json".
|
|
const contentType = (response.headers['content-type'] || ';').split(';')[0];
|
|
|
|
// Only try to parse data as JSON if server sets correct content type.
|
|
if (['application/json', 'text/javascript', 'application/javascript', 'application/x-javascript'].indexOf(contentType) >= 0) {
|
|
try {
|
|
response.data = JSON.parse(response.content);
|
|
} catch (err) {
|
|
response.data = null;
|
|
}
|
|
} else {
|
|
response.data = null;
|
|
}
|
|
}
|
|
|
|
function makeErrorByStatus(statusCode: number, content: string): Error {
|
|
let message = `failed [${statusCode}]`;
|
|
|
|
if (content) {
|
|
message += `${truncate(content.replace(/\n/g, ' '), 500)}`;
|
|
}
|
|
|
|
return new Error(message);
|
|
}
|
|
|
|
function _call(httpMethod: string, url: string, options: HttpCallOptions, callback: callbackFn): void {
|
|
const method = (httpMethod || '').toUpperCase();
|
|
|
|
if (!/^https?:\/\//.test(url)) {
|
|
throw new Error('url must be absolute and start with http:// or https://');
|
|
}
|
|
|
|
const headers: Record<string, string> = {};
|
|
let { content } = options;
|
|
|
|
if (!('timeout' in options)) {
|
|
options.timeout = defaultTimeout;
|
|
}
|
|
|
|
if (options.data) {
|
|
content = JSON.stringify(options.data);
|
|
headers['Content-Type'] = 'application/json';
|
|
}
|
|
|
|
let paramsForUrl;
|
|
let paramsForBody;
|
|
|
|
if (content || method === 'GET' || method === 'HEAD') {
|
|
paramsForUrl = options.params;
|
|
} else {
|
|
paramsForBody = options.params;
|
|
}
|
|
|
|
const newUrl = URL._constructUrl(url, options.query, paramsForUrl);
|
|
|
|
if (options.auth) {
|
|
if (options.auth.indexOf(':') < 0) {
|
|
throw new Error('auth option should be of the form "username:password"');
|
|
}
|
|
|
|
const base64 = Buffer.from(options.auth, 'ascii').toString('base64');
|
|
headers.Authorization = `Basic ${base64}`;
|
|
}
|
|
|
|
if (paramsForBody) {
|
|
const data = new URLSearchParams();
|
|
Object.entries(paramsForBody).forEach(([key, value]) => {
|
|
data.append(key, value);
|
|
});
|
|
content = data;
|
|
headers['Content-Type'] = 'application/x-www-form-urlencoded';
|
|
}
|
|
|
|
const { headers: receivedHeaders } = options;
|
|
|
|
if (receivedHeaders) {
|
|
Object.keys(receivedHeaders).forEach((key) => {
|
|
headers[key] = receivedHeaders[key];
|
|
});
|
|
}
|
|
|
|
// wrap callback to add a 'response' property on an error, in case
|
|
// we have both (http 4xx/5xx error, which has a response payload)
|
|
const wrappedCallback = ((cb: callbackFn): { (error: Error | undefined, response?: HTTPResponse): void } => {
|
|
let called = false;
|
|
return (error: Error | undefined, response?: HTTPResponse): void => {
|
|
if (!called) {
|
|
called = true;
|
|
if (error && response) {
|
|
(error as any).response = response;
|
|
}
|
|
cb(error, response);
|
|
}
|
|
};
|
|
})(callback);
|
|
|
|
// is false if false, otherwise always true
|
|
const followRedirects = options.followRedirects === false ? 'manual' : 'follow';
|
|
|
|
const requestOptions = {
|
|
method,
|
|
jar: false,
|
|
timeout: options.timeout,
|
|
body: content,
|
|
redirect: followRedirects,
|
|
referrer: options.referrer,
|
|
integrity: options.integrity,
|
|
headers,
|
|
ignoreSsrfValidation: true,
|
|
} as const;
|
|
|
|
fetch(newUrl, requestOptions)
|
|
.then(async (res) => {
|
|
const content = await res.text();
|
|
const response: HTTPResponse = {};
|
|
response.statusCode = res.status;
|
|
response.content = `${content}`;
|
|
|
|
// fetch headers don't allow simple read using bracket notation
|
|
// so we iterate their entries and assign them to a new Object
|
|
response.headers = {};
|
|
for (const entry of (res.headers as any).entries()) {
|
|
const [key, val] = entry;
|
|
response.headers[key] = val;
|
|
}
|
|
|
|
response.ok = res.ok;
|
|
response.redirected = res.redirected;
|
|
|
|
populateData(response);
|
|
|
|
if (response.statusCode >= 400) {
|
|
const error = makeErrorByStatus(response.statusCode, response.content);
|
|
wrappedCallback(error, response);
|
|
} else {
|
|
wrappedCallback(undefined, response);
|
|
}
|
|
})
|
|
.catch((err) => wrappedCallback(err));
|
|
}
|
|
|
|
function httpCallAsync(httpMethod: string, url: string, options: HttpCallOptions, callback: callbackFn): void;
|
|
function httpCallAsync(httpMethod: string, url: string, callback: callbackFn): void;
|
|
function httpCallAsync(httpMethod: string, url: string, optionsOrCallback: HttpCallOptions | callbackFn = {}, callback?: callbackFn): void {
|
|
// If the options argument was omitted, adjust the arguments:
|
|
if (!callback && typeof optionsOrCallback === 'function') {
|
|
return _call(httpMethod, url, {}, optionsOrCallback);
|
|
}
|
|
|
|
return _call(httpMethod, url, optionsOrCallback as HttpCallOptions, callback as callbackFn);
|
|
}
|
|
|
|
export const httpCall = async (httpMethod: string, url: string, options: HttpCallOptions) => {
|
|
return new Promise((resolve, reject) => {
|
|
httpCallAsync.bind(HTTP)(httpMethod, url, options, (error, result) => {
|
|
if (error) {
|
|
return reject(error);
|
|
}
|
|
resolve(result);
|
|
});
|
|
});
|
|
};
|