commit
fdc117e02f
|
@ -0,0 +1,130 @@
|
||||||
|
import * as https from "https";
|
||||||
|
import * as http from "http";
|
||||||
|
|
||||||
|
const MAX_DEFAULT_REDIRECTS = 20;
|
||||||
|
const redirectCodes = new Set([301, 302, 307, 308]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} FetchOptions
|
||||||
|
* @property {"GET" | "PUT" | "POST" | "DELETE"} [method] - Request method.
|
||||||
|
* @property {Record<string, string>} [headers] - Request headers.
|
||||||
|
* @property {"manual" | "follow"} [redirect] - Whether to follow redirects.
|
||||||
|
* @property {number} [maxRedirects] - Maximum amount of redirects to be followed.
|
||||||
|
* @property {AbortSignal} [signal] - Signal to abruptly cancel the request
|
||||||
|
* @property {Uint8Array | string} [body] - Defines a request body. Data must be serializable.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
|
* @param {FetchOptions} options
|
||||||
|
*/
|
||||||
|
export function nativeFetch(url, options) {
|
||||||
|
let state = "PENDING";
|
||||||
|
const data = {content: [], headers: null, statusCode: null, url: url, statusText: "", redirected: false};
|
||||||
|
const listeners = new Set();
|
||||||
|
const errors = new Set();
|
||||||
|
|
||||||
|
/** * @param {URL} url */
|
||||||
|
const execute = (url, options, redirectCount = 0) => {
|
||||||
|
const Module = url.protocol === "http" ? http : https;
|
||||||
|
|
||||||
|
const req = Module.request(url.href, {
|
||||||
|
headers: options.headers ?? {},
|
||||||
|
method: options.method ?? "GET"
|
||||||
|
}, res => {
|
||||||
|
if (redirectCodes.has(res.statusCode) && res.headers.location && options.redirect !== "manual") {
|
||||||
|
redirectCount++;
|
||||||
|
|
||||||
|
if (redirectCount >= (options.maxRedirects ?? MAX_DEFAULT_REDIRECTS)) {
|
||||||
|
state = "ABORTED";
|
||||||
|
const error = new Error(`Maximum amount of redirects reached (${options.maxRedirects ?? MAX_DEFAULT_REDIRECTS})`);
|
||||||
|
errors.forEach(e => e(error));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let final;
|
||||||
|
try {
|
||||||
|
final = new URL(res.headers.location);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
state = "ABORTED";
|
||||||
|
errors.forEach(e => e(error));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of new URL(url).searchParams.entries()) {
|
||||||
|
final.searchParams.set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return execute(final, options, redirectCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.on("data", chunk => data.content.push(chunk));
|
||||||
|
res.on("end", () => {
|
||||||
|
data.content = Buffer.concat(data.content);
|
||||||
|
data.headers = res.headers;
|
||||||
|
data.statusCode = res.statusCode;
|
||||||
|
data.url = url.toString();
|
||||||
|
data.statusText = res.statusMessage;
|
||||||
|
data.redirected = redirectCount > 0;
|
||||||
|
state = "DONE";
|
||||||
|
|
||||||
|
listeners.forEach(listener => listener());
|
||||||
|
});
|
||||||
|
res.on("error", error => {
|
||||||
|
state = "ABORTED";
|
||||||
|
errors.forEach(e => e(error));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.body) {
|
||||||
|
try {req.write(options.body)}
|
||||||
|
catch (error) {
|
||||||
|
state = "ABORTED";
|
||||||
|
errors.forEach(e => e(error));
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
req.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
req.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.signal) {
|
||||||
|
options.signal.addEventListener("abort", () => {
|
||||||
|
req.end();
|
||||||
|
state = "ABORTED";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = new URL(url);
|
||||||
|
execute(parsed, options);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
state = "ABORTED";
|
||||||
|
errors.forEach(e => e(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
onComplete(listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
},
|
||||||
|
onError(listener) {
|
||||||
|
errors.add(listener);
|
||||||
|
},
|
||||||
|
readData() {
|
||||||
|
switch (state) {
|
||||||
|
case "PENDING":
|
||||||
|
throw new Error("Cannot read data before request is done!");
|
||||||
|
case "ABORTED":
|
||||||
|
throw new Error("Request was aborted.");
|
||||||
|
case "DONE":
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -34,7 +34,15 @@ const makeRequest = (url, options, callback, setReq) => {
|
||||||
req.end();
|
req.end();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
req.end();
|
|
||||||
|
if (options.formData) {
|
||||||
|
// Make sure to close the socket.
|
||||||
|
try {req.write(options.formData);}
|
||||||
|
finally {req.end();}
|
||||||
|
} else {
|
||||||
|
req.end();
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const request = function (url, options, callback) {
|
const request = function (url, options, callback) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ export {default as https} from "./https";
|
||||||
export * as electron from "./electron";
|
export * as electron from "./electron";
|
||||||
export * as crypto from "./crypto";
|
export * as crypto from "./crypto";
|
||||||
export * as vm from "./vm";
|
export * as vm from "./vm";
|
||||||
|
export * from "./fetch";
|
||||||
|
|
||||||
// We can expose that without any issues.
|
// We can expose that without any issues.
|
||||||
export * as path from "path";
|
export * as path from "path";
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
import Remote from "../../polyfill/remote";
|
||||||
|
|
||||||
|
const methods = new Set(["GET", "PUT", "POST", "DELETE"]);
|
||||||
|
|
||||||
|
class FetchResponse extends Response {
|
||||||
|
constructor(options) {
|
||||||
|
super(options.content, {
|
||||||
|
headers: new Headers(options.headers),
|
||||||
|
method: options.method ?? "GET",
|
||||||
|
body: options.content,
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
|
||||||
|
this._options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
get url() {return this._options.url;}
|
||||||
|
get redirected() {return this._options.redirected;}
|
||||||
|
}
|
||||||
|
|
||||||
|
const convertSignal = signal => {
|
||||||
|
const listeners = new Set();
|
||||||
|
|
||||||
|
signal.addEventListener("abort", () => {
|
||||||
|
listeners.forEach(l => l());
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
addEventListener(_, listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} FetchOptions
|
||||||
|
* @property {"GET" | "PUT" | "POST" | "DELETE"} [method] - Request method.
|
||||||
|
* @property {Record<string, string>} [headers] - Request headers.
|
||||||
|
* @property {"manual" | "follow"} [redirect] - Whether to follow redirects.
|
||||||
|
* @property {number} [maxRedirects] - Maximum amount of redirects to be followed.
|
||||||
|
* @property {AbortSignal} [signal] - Signal to abruptly cancel the request
|
||||||
|
* @property {Uint8Array | string} [body] - Defines a request body. Data must be serializable.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
|
* @param {FetchOptions} options
|
||||||
|
* @returns {Promise<FetchResponse>}
|
||||||
|
*/
|
||||||
|
export default function fetch(url, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const data = {};
|
||||||
|
|
||||||
|
if (typeof options.headers === "object") {
|
||||||
|
data.headers = options.headers instanceof Headers ? Object.fromEntries(options.headers.entries()) : options.headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof options.body === "string" || options.body instanceof Uint8Array) data.body = options.body;
|
||||||
|
if (typeof options.method === "string" && methods.has(options.method)) data.method = options.method;
|
||||||
|
if (options.signal instanceof AbortSignal) data.signal = convertSignal(options.signal);
|
||||||
|
|
||||||
|
const ctx = Remote.nativeFetch(url, data);
|
||||||
|
|
||||||
|
ctx.onError(error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.onComplete(() => {
|
||||||
|
try {
|
||||||
|
const data = ctx.readData();
|
||||||
|
|
||||||
|
const req = new FetchResponse({
|
||||||
|
method: options.method ?? "GET",
|
||||||
|
status: data.statusCode,
|
||||||
|
...options,
|
||||||
|
...data
|
||||||
|
});
|
||||||
|
|
||||||
|
resolve(req);
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import Utils from "./utils";
|
||||||
import Webpack from "./webpack";
|
import Webpack from "./webpack";
|
||||||
import * as Legacy from "./legacy";
|
import * as Legacy from "./legacy";
|
||||||
import ContextMenu from "./contextmenu";
|
import ContextMenu from "./contextmenu";
|
||||||
|
import fetch from "./fetch";
|
||||||
import {DiscordModules} from "modules";
|
import {DiscordModules} from "modules";
|
||||||
|
|
||||||
const bounded = new Map();
|
const bounded = new Map();
|
||||||
|
@ -57,6 +58,7 @@ export default class BdApi {
|
||||||
Components = {
|
Components = {
|
||||||
get Tooltip() {return DiscordModules.Tooltip;}
|
get Tooltip() {return DiscordModules.Tooltip;}
|
||||||
}
|
}
|
||||||
|
Net = {fetch};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add legacy functions
|
// Add legacy functions
|
||||||
|
@ -126,6 +128,9 @@ BdApi.Components = {
|
||||||
get Tooltip() {return DiscordModules.Tooltip;}
|
get Tooltip() {return DiscordModules.Tooltip;}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
BdApi.Net = {fetch};
|
||||||
|
|
||||||
Object.freeze(BdApi);
|
Object.freeze(BdApi);
|
||||||
|
Object.freeze(BdApi.Net);
|
||||||
Object.freeze(BdApi.prototype);
|
Object.freeze(BdApi.prototype);
|
||||||
Object.freeze(BdApi.Components);
|
Object.freeze(BdApi.Components);
|
||||||
|
|
|
@ -7,6 +7,12 @@ import * as https from "./https";
|
||||||
import Buffer from "./buffer";
|
import Buffer from "./buffer";
|
||||||
import crypto from "./crypto";
|
import crypto from "./crypto";
|
||||||
import Remote from "./remote";
|
import Remote from "./remote";
|
||||||
|
import Logger from "common/logger";
|
||||||
|
|
||||||
|
const deprecated = new Map([
|
||||||
|
["request", "Use BdApi.Net.fetch instead."],
|
||||||
|
["https", "Use BdApi.Net.fetch instead."],
|
||||||
|
]);
|
||||||
|
|
||||||
const originalFs = Object.assign({}, fs);
|
const originalFs = Object.assign({}, fs);
|
||||||
originalFs.writeFileSync = (path, data, options) => fs.writeFileSync(path, data, Object.assign({}, options, {originalFs: true}));
|
originalFs.writeFileSync = (path, data, options) => fs.writeFileSync(path, data, Object.assign({}, options, {originalFs: true}));
|
||||||
|
@ -14,6 +20,10 @@ originalFs.writeFile = (path, data, options) => fs.writeFile(path, data, Object.
|
||||||
|
|
||||||
export const createRequire = function (path) {
|
export const createRequire = function (path) {
|
||||||
return mod => {
|
return mod => {
|
||||||
|
if (deprecated.has(mod)) {
|
||||||
|
Logger.warn("Remote~Require", `The "${mod}" module is marked as deprecated. ${deprecated.get(mod)}`);
|
||||||
|
}
|
||||||
|
|
||||||
switch (mod) {
|
switch (mod) {
|
||||||
case "request": return request;
|
case "request": return request;
|
||||||
case "https": return https;
|
case "https": return https;
|
||||||
|
@ -42,4 +52,4 @@ require.resolve = (path) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default require;
|
export default require;
|
||||||
|
|
Loading…
Reference in New Issue