@whitesev/utils
Version:
一个常用的工具库
1,705 lines (1,681 loc) • 48.7 kB
text/typescript
import type {
HttpxAllowInterceptConfig,
HttpxHookErrorData,
HttpxMethod,
HttpxRequestOption,
HttpxRequestOptionConfig,
HttpxResponse,
HttpxResponseData,
HttpxPromise,
} from "./types/Httpx";
import { Utils } from "./Utils";
import { GenerateUUID } from "./UtilsCommon";
class Httpx {
private GM_Api = {
xmlHttpRequest: null as any,
};
private HttpxRequestHook = {
/**
* @private
*/
$config: {
configList: <
{
id: string;
fn: Function | Promise<Function>;
}[]
>[],
},
/**
* 发送请求前的回调
* 如果返回false则阻止本次返回
* @param details 当前的请求配置
* @private
*/
async beforeRequestCallBack(details: HttpxRequestOption) {
if (typeof details.allowInterceptConfig === "boolean") {
if (!details.allowInterceptConfig) {
// 不允许拦截
return details;
}
} else {
if (details.allowInterceptConfig != null) {
// 配置存在
// 细分处理是否拦截
if (
typeof details.allowInterceptConfig.beforeRequest === "boolean" &&
!details.allowInterceptConfig.beforeRequest
) {
// 设置了禁止拦截
return details;
}
} else {
// 配置不存在
// 默认允许拦截
}
}
for (let index = 0; index < this.$config.configList.length; index++) {
let item = this.$config.configList[index];
if (typeof item.fn === "function") {
let result = await item.fn(details);
if (result == null) {
return;
}
}
}
return details;
},
/**
* 添加请求前的回调处理配置
*/
add(fn: Function) {
if (typeof fn === "function") {
let uuid = GenerateUUID();
this.$config.configList.push({
id: uuid,
fn: fn,
});
return uuid;
} else {
console.warn(
"[Httpx-HttpxRequestHook.addBeforeRequestCallBack] fn is not a function"
);
}
},
/**
* 删除请求前的回调处理配置
* @param id
*/
delete(id: string) {
if (typeof id === "string") {
let findIndex = this.$config.configList.findIndex(
(item) => item.id === id
);
if (findIndex !== -1) {
this.$config.configList.splice(findIndex, 1);
return true;
}
}
return false;
},
/**
* 清空设置的请求前的回调处理配置
*/
clearAll() {
this.$config.configList = [];
},
};
private HttpxResponseHook = {
/**
* @private
*/
$config: {
configList: <
{
id: string;
successFn?: Function | Promise<Function>;
errorFn?: Function | Promise<Function>;
}[]
>[],
},
/**
* 成功的回调
* @param response 响应
* @param details 请求的配置
*/
async successResponseCallBack(
response: HttpxResponseData<HttpxRequestOption>,
details: HttpxRequestOption
) {
if (typeof details.allowInterceptConfig === "boolean") {
if (!details.allowInterceptConfig) {
// 不允许拦截
return details;
}
} else {
if (details.allowInterceptConfig != null) {
// 配置存在
// 细分处理是否拦截
if (
typeof details.allowInterceptConfig.afterResponseSuccess ===
"boolean" &&
!details.allowInterceptConfig.afterResponseSuccess
) {
// 设置了禁止拦截
return details;
}
} else {
// 配置不存在
// 默认允许拦截
}
}
for (let index = 0; index < this.$config.configList.length; index++) {
let item = this.$config.configList[index];
if (typeof item.successFn === "function") {
let result = await item.successFn(response, details);
if (result == null) {
return;
}
}
}
return response;
},
/**
* 失败的回调
* @param data 配置
* @returns
* 返回null|undefined就是拦截掉了
*/
async errorResponseCallBack<T extends HttpxHookErrorData>(data: T) {
if (typeof data.details.allowInterceptConfig === "boolean") {
if (!data.details.allowInterceptConfig) {
// 不允许拦截
return data;
}
} else {
if (data.details.allowInterceptConfig != null) {
// 配置存在
// 细分处理是否拦截
if (
typeof data.details.allowInterceptConfig.afterResponseError ===
"boolean" &&
!data.details.allowInterceptConfig.afterResponseError
) {
// 设置了禁止拦截
return data;
}
} else {
// 配置不存在
// 默认允许拦截
}
}
for (let index = 0; index < this.$config.configList.length; index++) {
let item = this.$config.configList[index];
if (typeof item.errorFn === "function") {
let result = await item.errorFn(data);
if (result == null) {
return;
}
}
}
return data;
},
/**
* 添加请求前的回调处理配置
*/
add(successFn?: Function, errorFn?: Function) {
let id = GenerateUUID();
this.$config.configList.push({
id: id,
successFn: successFn,
errorFn: errorFn,
});
return id;
},
/**
* 删除请求前的回调处理配置
* @param id
*/
delete(id: string) {
if (typeof id === "string") {
let findIndex = this.$config.configList.findIndex(
(item) => item.id === id
);
if (findIndex !== -1) {
this.$config.configList.splice(findIndex, 1);
return true;
}
}
return false;
},
/**
* 清空设置的请求前的回调处理配置
*/
clearAll() {
this.$config.configList = [];
},
};
private HttpxRequestOption = {
context: this,
/**
* 根据传入的参数处理获取details配置
*/
handleBeforeRequestOption(...args: (HttpxRequestOption | string)[]) {
let option: HttpxRequestOption = {};
if (typeof args[0] === "string") {
/* 传入的是url,details? */
let url = args[0];
option.url = url;
if (typeof args[1] === "object") {
/* 处理第二个参数details */
let details = args[1];
option = details;
option.url = url;
}
} else {
/* 传入的是details */
option = args[0];
}
return option;
},
/**
* 获取请求配置
* @param method 当前请求方法,默认get
* @param userRequestOption 用户的请求配置
* @param resolve promise回调
* @param reject 抛出错误回调
*/
getRequestOption(
method: HttpxMethod,
userRequestOption: HttpxRequestOption,
resolve: (...args: any[]) => void,
reject: (...args: any[]) => void
) {
let that = this;
let requestOption = <Required<HttpxRequestOption>>{
url: userRequestOption.url || this.context.#defaultDetails.url,
method: (method || "GET").toString().toUpperCase().trim(),
timeout:
userRequestOption.timeout || this.context.#defaultDetails.timeout,
responseType:
userRequestOption.responseType ||
this.context.#defaultDetails.responseType,
/* 对象使用深拷贝 */
headers: Utils.deepClone(this.context.#defaultDetails.headers),
data: userRequestOption.data || this.context.#defaultDetails.data,
redirect:
userRequestOption.redirect || this.context.#defaultDetails.redirect,
cookie: userRequestOption.cookie || this.context.#defaultDetails.cookie,
cookiePartition:
userRequestOption.cookiePartition ||
this.context.#defaultDetails.cookiePartition,
binary: userRequestOption.binary || this.context.#defaultDetails.binary,
nocache:
userRequestOption.nocache || this.context.#defaultDetails.nocache,
revalidate:
userRequestOption.revalidate ||
this.context.#defaultDetails.revalidate,
/* 对象使用深拷贝 */
context: Utils.deepClone(
userRequestOption.context || this.context.#defaultDetails.context
),
overrideMimeType:
userRequestOption.overrideMimeType ||
this.context.#defaultDetails.overrideMimeType,
anonymous:
userRequestOption.anonymous || this.context.#defaultDetails.anonymous,
fetch: userRequestOption.fetch || this.context.#defaultDetails.fetch,
/* 对象使用深拷贝 */
fetchInit: Utils.deepClone(this.context.#defaultDetails.fetchInit),
allowInterceptConfig: {
beforeRequest: (
this.context.#defaultDetails
.allowInterceptConfig as HttpxAllowInterceptConfig
).beforeRequest,
afterResponseSuccess: (
this.context.#defaultDetails
.allowInterceptConfig as HttpxAllowInterceptConfig
).afterResponseSuccess,
afterResponseError: (
this.context.#defaultDetails
.allowInterceptConfig as HttpxAllowInterceptConfig
).afterResponseError,
},
user: userRequestOption.user || this.context.#defaultDetails.user,
password:
userRequestOption.password || this.context.#defaultDetails.password,
onabort(...args) {
that.context.HttpxCallBack.onAbort(
userRequestOption as Required<HttpxRequestOption>,
resolve,
reject,
args
);
},
onerror(...args) {
that.context.HttpxCallBack.onError(
userRequestOption as Required<HttpxRequestOption>,
resolve,
reject,
args
);
},
onloadstart(...args) {
that.context.HttpxCallBack.onLoadStart(
userRequestOption as Required<HttpxRequestOption>,
args
);
},
onprogress(...args) {
that.context.HttpxCallBack.onProgress(
userRequestOption as Required<HttpxRequestOption>,
args
);
},
onreadystatechange(...args) {
that.context.HttpxCallBack.onReadyStateChange(
userRequestOption as Required<HttpxRequestOption>,
args
);
},
ontimeout(...args) {
that.context.HttpxCallBack.onTimeout(
userRequestOption as Required<HttpxRequestOption>,
resolve,
reject,
args
);
},
onload(...args) {
that.context.HttpxCallBack.onLoad(
userRequestOption as Required<HttpxRequestOption>,
resolve,
reject,
args
);
},
};
// 补全allowInterceptConfig参数
if (typeof userRequestOption.allowInterceptConfig === "boolean") {
Object.keys(
requestOption.allowInterceptConfig as HttpxAllowInterceptConfig
).forEach((keyName) => {
Reflect.set(
requestOption.allowInterceptConfig as HttpxAllowInterceptConfig,
keyName,
userRequestOption.allowInterceptConfig
);
});
} else {
if (
typeof userRequestOption.allowInterceptConfig === "object" &&
userRequestOption.allowInterceptConfig != null
) {
Object.keys(userRequestOption.allowInterceptConfig).forEach(
(keyName) => {
let value = Reflect.get(
userRequestOption.allowInterceptConfig as HttpxAllowInterceptConfig,
keyName
) as Boolean;
if (
typeof value === "boolean" &&
Reflect.has(
requestOption.allowInterceptConfig as HttpxAllowInterceptConfig,
keyName
)
) {
Reflect.set(
requestOption.allowInterceptConfig as HttpxAllowInterceptConfig,
keyName,
value
);
}
}
);
}
}
if (typeof this.context.GM_Api.xmlHttpRequest !== "function") {
// GM函数不存在,强制使用fetch
requestOption.fetch = true;
}
if (typeof requestOption.headers === "object") {
if (typeof userRequestOption.headers === "object") {
Object.keys(userRequestOption.headers).forEach((keyName, index) => {
if (
keyName in requestOption.headers &&
userRequestOption!.headers?.[keyName] == null
) {
/* 在默认的header中存在,且设置它新的值为空,那么就是默认的值 */
Reflect.deleteProperty(requestOption.headers, keyName);
} else {
requestOption.headers[keyName] =
userRequestOption?.headers?.[keyName];
}
});
} else {
/* details.headers为空 */
/* 不做处理 */
}
} else {
/* 默认的headers不是对象,那么就直接使用新的 */
Reflect.set(requestOption, "headers", userRequestOption.headers);
}
if (typeof requestOption.fetchInit === "object") {
/* 使用assign替换且添加 */
if (typeof userRequestOption.fetchInit === "object") {
Object.keys(userRequestOption.fetchInit).forEach((keyName, index) => {
if (
keyName in requestOption.fetchInit &&
(userRequestOption as any).fetchInit[keyName] == null
) {
/* 在默认的fetchInit中存在,且设置它新的值为空,那么就是默认的值 */
Reflect.deleteProperty(requestOption.fetchInit, keyName);
} else {
Reflect.set(
requestOption.fetchInit,
keyName,
Reflect.get(userRequestOption.fetchInit!, keyName)
);
}
});
}
} else {
Reflect.set(requestOption, "fetchInit", userRequestOption.fetchInit);
}
// 处理新的cookiePartition
if (
typeof requestOption.cookiePartition === "object" &&
requestOption.cookiePartition != null
) {
if (
Reflect.has(requestOption.cookiePartition, "topLevelSite") &&
typeof requestOption.cookiePartition.topLevelSite !== "string"
) {
// topLevelSite必须是字符串
Reflect.deleteProperty(requestOption.cookiePartition, "topLevelSite");
}
}
/* 完善请求的url */
try {
new URL(requestOption.url);
} catch (error) {
if (requestOption.url.startsWith("//")) {
// 补充https:
requestOption.url = globalThis.location.protocol + requestOption.url;
} else if (requestOption.url.startsWith("/")) {
// 补充origin
requestOption.url = globalThis.location.origin + requestOption.url;
} else {
// 补充origin+/
requestOption.url =
globalThis.location.origin + "/" + requestOption.url;
}
}
if (requestOption.fetchInit && !requestOption.fetch) {
// 清空fetchInit
Reflect.deleteProperty(requestOption, "fetchInit");
}
// 转换data类型
try {
/** 是否对数据进行处理 */
let processData = userRequestOption.processData ?? true;
if (requestOption.data != null && processData) {
let method = requestOption.method;
if (method === "GET" || method === "HEAD") {
// GET类型,data如果有,那么需要转为searchParams
let urlObj = new URL(requestOption.url);
let urlSearch = "";
let isHandler = false;
if (typeof requestOption.data === "string") {
isHandler = true;
urlSearch = requestOption.data;
} else if (typeof requestOption.data === "object") {
isHandler = true;
// URLSearchParams参数可以转普通的string:string,包括FormData
// @ts-ignore
let searchParams = new URLSearchParams(requestOption.data);
urlSearch = searchParams.toString();
}
if (isHandler) {
// GET/HEAD请求不支持data参数
// 对data进行处理了才可以删除
Reflect.deleteProperty(requestOption, "data");
}
if (urlSearch != "") {
if (urlObj.search === "") {
// url没有search参数,直接覆盖
urlObj.search = urlSearch;
} else {
// 有search参数
if (urlObj.search.endsWith("&")) {
// xxx=xxx&
urlObj.search = urlObj.search + urlSearch;
} else {
// xxx=xxx&xxx=
urlObj.search = urlObj.search + "&" + urlSearch;
}
}
}
requestOption.url = urlObj.toString();
} else if (method === "POST" && requestOption.headers != null) {
// POST类型,data如果是FormData,那么需要转为string
let headersKeyList = Object.keys(requestOption.headers);
let ContentTypeIndex = headersKeyList.findIndex((headerKey) => {
return (
headerKey.trim().toLowerCase() === "content-type" &&
typeof requestOption.headers[headerKey] === "string"
);
});
if (ContentTypeIndex !== -1) {
let ContentTypeKey = headersKeyList[ContentTypeIndex];
let ContentType = requestOption.headers[ContentTypeKey] as string;
// 设置了Content-Type
if (ContentType.includes("application/json")) {
// application/json
if (requestOption.data instanceof FormData) {
const entries: { [key: string]: any } = {};
requestOption.data.forEach((value, key) => {
entries[key] = value;
});
requestOption.data = JSON.stringify(entries);
} else if (typeof requestOption.data === "object") {
requestOption.data = JSON.stringify(requestOption.data);
}
} else if (
ContentType.includes("application/x-www-form-urlencoded")
) {
// application/x-www-form-urlencoded
if (typeof requestOption.data === "object") {
requestOption.data = new URLSearchParams(
// @ts-ignore
requestOption.data
).toString();
}
} else if (ContentType.includes("multipart/form-data")) {
// multipart/form-data
if (requestOption.data instanceof FormData) {
Reflect.deleteProperty(requestOption.headers, ContentTypeKey);
}
}
}
}
}
} catch (error) {
console.warn("Httpx ==> 转换data参数错误", error);
}
return requestOption;
},
/**
* 处理发送请求的配置,去除值为undefined、空function的值
* @param option
*/
removeRequestNullOption(
option: Required<HttpxRequestOption>
): HttpxRequestOption {
Object.keys(option).forEach((keyName) => {
if (
option[keyName as keyof HttpxRequestOption] == null ||
(option[keyName as keyof HttpxRequestOption] instanceof Function &&
Utils.isNull(option[keyName as keyof HttpxRequestOption]))
) {
Reflect.deleteProperty(option, keyName);
return;
}
});
if (Utils.isNull(option.url)) {
throw new TypeError(`Utils.Httpx 参数 url不符合要求: ${option.url}`);
}
return option;
},
/**
* 处理fetch的配置
* @param option
*/
handleFetchOption(option: Required<HttpxRequestOption>) {
/**
* fetch的请求配置
**/
let fetchRequestOption = <RequestInit>{};
if (
(option.method === "GET" || option.method === "HEAD") &&
option.data != null
) {
/* GET 或 HEAD 方法的请求不能包含 body 信息 */
Reflect.deleteProperty(option, "data");
}
/* 中止信号控制器 */
let abortController = new AbortController();
let signal = abortController.signal;
signal.onabort = () => {
option.onabort({
isFetch: true,
responseText: "",
response: null,
readyState: 4,
responseHeaders: "",
status: 0,
statusText: "",
error: "aborted",
});
};
// 设置请求
fetchRequestOption.method = option.method ?? "GET";
// 设置请求头
fetchRequestOption.headers = option.headers;
// 设置请求体
fetchRequestOption.body = option.data as string | FormData;
// 设置跨域
fetchRequestOption.mode = "cors";
// 设置包含
fetchRequestOption.credentials = "include";
// 设置不缓存
fetchRequestOption.cache = "no-cache";
// 设置始终重定向
fetchRequestOption.redirect = "follow";
// 设置referer跨域
fetchRequestOption.referrerPolicy = "origin-when-cross-origin";
// 设置信号中断
fetchRequestOption.signal = signal;
Object.assign(fetchRequestOption, option.fetchInit || {});
return {
fetchOption: option,
fetchRequestOption: fetchRequestOption,
abortController: abortController,
};
},
};
private HttpxCallBack = {
context: this,
/**
* onabort请求被取消-触发
* @param details 配置
* @param resolve 回调
* @param reject 抛出错误
* @param argsResult 返回的参数列表
*/
async onAbort(
details: Required<HttpxRequestOption>,
resolve: (resultOption: HttpxResponse<HttpxRequestOption>) => void,
reject: (...args: any[]) => void,
argsResult: any
) {
// console.log(argsResult);
if ("onabort" in details) {
details.onabort.apply(this, argsResult);
} else if ("onabort" in this.context.#defaultDetails) {
this.context.#defaultDetails!.onabort!.apply(this, argsResult);
}
let response = argsResult;
if (response.length) {
response = response[0];
}
if (
(await this.context.HttpxResponseHook.errorResponseCallBack({
type: "onabort",
error: new TypeError("request canceled"),
response: null,
details: details,
})) == null
) {
// reject(new TypeError("response is intercept with onabort"));
return;
}
resolve({
data: response,
details: details,
msg: "请求被取消",
status: false,
statusCode: -1,
type: "onabort",
});
},
/**
* onerror请求异常-触发
* @param details 配置
* @param resolve 回调
* @param reject 抛出错误
* @param argsResult 返回的参数列表
*/
async onError(
details: Required<HttpxRequestOption>,
resolve: (resultOption: HttpxResponse<HttpxRequestOption>) => void,
reject: (...args: any[]) => void,
argsResult: any
) {
// console.log(argsResult);
if ("onerror" in details) {
details.onerror.apply(this, argsResult);
} else if ("onerror" in this.context.#defaultDetails) {
this.context.#defaultDetails!.onerror!.apply(this, argsResult);
}
let response = argsResult;
if (response.length) {
response = response[0];
}
if (
(await this.context.HttpxResponseHook.errorResponseCallBack({
type: "onerror",
error: new TypeError("request error"),
response: response,
details: details,
})) == null
) {
// reject(new TypeError("response is intercept with onerror"));
return;
}
resolve({
data: response,
details: details,
msg: "请求异常",
status: false,
statusCode: response["status"],
type: "onerror",
});
},
/**
* ontimeout请求超时-触发
* @param details 配置
* @param resolve 回调
* @param reject 抛出错误
* @param argsResult 返回的参数列表
*/
async onTimeout(
details: Required<HttpxRequestOption>,
resolve: (resultOption: HttpxResponse<HttpxRequestOption>) => void,
reject: (...args: any[]) => void,
argsResult: any
) {
// console.log(argsResult);
if ("ontimeout" in details) {
details.ontimeout.apply(this, argsResult);
} else if ("ontimeout" in this.context.#defaultDetails) {
this.context.#defaultDetails!.ontimeout!.apply(this, argsResult);
}
let response = argsResult;
if (response.length) {
response = response[0];
}
if (
(await this.context.HttpxResponseHook.errorResponseCallBack({
type: "ontimeout",
error: new TypeError("request timeout"),
response: (argsResult || [null])[0],
details: details,
})) == null
) {
// reject(new TypeError("response is intercept with ontimeout"));
return;
}
resolve({
data: response,
details: details,
msg: "请求超时",
status: false,
statusCode: 0,
type: "ontimeout",
});
},
/**
* onloadstart请求开始-触发
* @param details 配置
* @param argsResult 返回的参数列表
*/
onLoadStart(details: Required<HttpxRequestOption>, argsResult: any[]) {
// console.log(argsResult);
if ("onloadstart" in details) {
details.onloadstart.apply(this, argsResult);
} else if ("onloadstart" in this.context.#defaultDetails) {
this.context.#defaultDetails!.onloadstart!.apply(this, argsResult);
}
},
/**
* onload加载完毕-触发
* @param details 请求的配置
* @param resolve 回调
* @param reject 抛出错误
* @param argsResult 返回的参数列表
*/
async onLoad(
details: Required<HttpxRequestOption>,
resolve: (resultOption: HttpxResponse<HttpxRequestOption>) => void,
reject: (...args: any[]) => void,
argsResult: any[]
) {
// console.log(argsResult);
/* X浏览器会因为设置了responseType导致不返回responseText */
let originResponse: HttpxResponseData<HttpxRequestOption> = argsResult[0];
/* responseText为空,response不为空的情况 */
if (
Utils.isNull(originResponse["responseText"]) &&
Utils.isNotNull(originResponse["response"])
) {
if (typeof originResponse["response"] === "object") {
Utils.tryCatch().run(() => {
originResponse["responseText"] = JSON.stringify(
originResponse["response"]
);
});
} else {
originResponse["responseText"] = originResponse["response"] as string;
}
}
/* response为空,responseText不为空的情况 */
if (
originResponse["response"] == null &&
typeof originResponse["responseText"] === "string" &&
originResponse["responseText"].trim() !== ""
) {
/** 原始的请求text */
let httpxResponseText = originResponse.responseText;
// 自定义个新的response
let httpxResponse: any = httpxResponseText;
if (details.responseType === "json") {
httpxResponse = Utils.toJSON(httpxResponseText);
} else if (details.responseType === "document") {
let parser = new DOMParser();
httpxResponse = parser.parseFromString(
httpxResponseText,
"text/html"
);
} else if (details.responseType === "arraybuffer") {
let encoder = new TextEncoder();
let arrayBuffer = encoder.encode(httpxResponseText);
httpxResponse = arrayBuffer;
} else if (details.responseType === "blob") {
let encoder = new TextEncoder();
let arrayBuffer = encoder.encode(httpxResponseText);
httpxResponse = new Blob([arrayBuffer]);
}
// 尝试覆盖原response
try {
let setStatus = Reflect.set(
originResponse,
"response",
httpxResponse
);
if (!setStatus) {
console.warn(
"[Httpx-HttpxCallBack.oonLoad] 覆盖原始 response 失败,尝试添加新的httpxResponse"
);
try {
Reflect.set(originResponse, "httpxResponse", httpxResponse);
} catch (error) {
console.warn(
"[Httpx-HttpxCallBack.oonLoad] httpxResponse 无法被覆盖"
);
}
}
} catch (error) {
console.warn(
"[Httpx-HttpxCallBack.oonLoad] 原始 response 无法被覆盖,尝试添加新的httpxResponse"
);
try {
Reflect.set(originResponse, "httpxResponse", httpxResponse);
} catch (error) {
console.warn(
"[Httpx-HttpxCallBack.oonLoad] httpxResponse 无法被覆盖"
);
}
}
}
/* Stay扩展中没有finalUrl,对应的是responseURL */
let originResponseURL = Reflect.get(originResponse, "responseURL");
if (originResponse["finalUrl"] == null && originResponseURL != null) {
Reflect.set(originResponse, "finalUrl", originResponseURL);
}
/* 状态码2xx都是成功的 */
if (Math.floor(originResponse.status / 100) === 2) {
if (
(await this.context.HttpxResponseHook.successResponseCallBack(
originResponse,
details
)) == null
) {
// reject(new TypeError("response is intercept with onloada"));
return;
}
resolve({
data: originResponse,
details: details,
msg: "请求成功",
status: true,
statusCode: originResponse.status,
type: "onload",
});
} else {
this.context.HttpxCallBack.onError(
details,
resolve,
reject,
argsResult
);
}
},
/**
* onprogress上传进度-触发
* @param details 配置
* @param argsResult 返回的参数列表
*/
onProgress(details: Required<HttpxRequestOption>, argsResult: any[]) {
// console.log(argsResult);
if ("onprogress" in details) {
details.onprogress.apply(this, argsResult);
} else if ("onprogress" in this.context.#defaultDetails) {
this.context.#defaultDetails!.onprogress!.apply(this, argsResult);
}
},
/**
* onreadystatechange准备状态改变-触发
* @param details 配置
* @param argsResult 返回的参数列表
*/
onReadyStateChange(
details: Required<HttpxRequestOption>,
argsResult: any[]
) {
// console.log(argsResult);
if ("onreadystatechange" in details) {
details.onreadystatechange.apply(this, argsResult);
} else if ("onreadystatechange" in this.context.#defaultDetails) {
this.context.#defaultDetails!.onreadystatechange!.apply(
this,
argsResult
);
}
},
};
private HttpxRequest = {
context: this,
/**
* 发送请求
* @param details
*/
async request(details: Required<HttpxRequestOption>) {
if (this.context.#LOG_DETAILS) {
console.log("[Httpx-HttpxRequest.request] 请求前的配置👇", details);
}
if (
typeof this.context.HttpxRequestHook.beforeRequestCallBack ===
"function"
) {
let hookResult =
await this.context.HttpxRequestHook.beforeRequestCallBack(details);
if (hookResult == null) {
return;
}
}
if (details.fetch) {
// 使用fetch请求
const {
fetchOption: fetchOption,
fetchRequestOption: fetchRequestOption,
abortController,
} = this.context.HttpxRequestOption.handleFetchOption(details);
return this.fetch(fetchOption, fetchRequestOption, abortController);
} else {
// 使用GM_xmlHttpRequest请求
return this.xmlHttpRequest(details);
}
},
/**
* 使用油猴函数GM_xmlhttpRequest发送请求
* @param details
*/
xmlHttpRequest(details: Required<HttpxRequestOption>) {
return this.context.GM_Api.xmlHttpRequest(details) as {
abort: () => void;
};
},
/**
* 使用fetch发送请求
* @param option
* @param fetchRequestOption
* @param abortController
*/
fetch(
option: Required<HttpxRequestOption>,
fetchRequestOption: RequestInit,
abortController: AbortController
) {
fetch(option.url, fetchRequestOption)
.then(async (fetchResponse) => {
/** 自定义的response */
let httpxResponse: HttpxResponseData<HttpxRequestOption> = {
isFetch: true,
finalUrl: fetchResponse.url,
readyState: 4,
// @ts-ignore
status: fetchResponse.status,
statusText: fetchResponse.statusText,
// @ts-ignore
response: void 0,
responseFetchHeaders: fetchResponse.headers,
responseHeaders: "",
// @ts-ignore
responseText: void 0,
responseType: option.responseType,
responseXML: void 0,
};
Object.assign(httpxResponse, option.context || {});
// 把headers转为字符串
for (const [key, value] of (fetchResponse.headers as any).entries()) {
httpxResponse.responseHeaders += `${key}: ${value}\n`;
}
/** 请求返回的类型 */
const fetchResponseType = fetchResponse.headers.get("Content-Type");
/* 如果需要stream,且获取到的是stream,那直接返回 */
if (
option.responseType === "stream" ||
(fetchResponse.headers.has("Content-Type") &&
fetchResponse.headers
.get("Content-Type")!
.includes("text/event-stream"))
) {
Reflect.set(httpxResponse, "isStream", true);
Reflect.set(httpxResponse, "response", fetchResponse.body);
Reflect.deleteProperty(httpxResponse, "responseText");
Reflect.deleteProperty(httpxResponse, "responseXML");
option.onload(httpxResponse);
return;
}
/** 响应 */
let response: any = "";
/** 响应字符串 */
let responseText = "";
/** 响应xml文档 */
let responseXML: XMLDocument | string = "";
/** 先获取二进制数据 */
let arrayBuffer = await fetchResponse.arrayBuffer();
/** 数据编码 */
let encoding = "utf-8";
if (fetchResponse.headers.has("Content-Type")) {
let charsetMatched = fetchResponse.headers
.get("Content-Type")
?.match(/charset=(.+)/);
if (charsetMatched) {
encoding = charsetMatched[1];
encoding = encoding.toLowerCase();
}
}
// Failed to construct 'TextDecoder': The encoding label provided ('"UTF-8"') is invalid.
// 去除引号
encoding = encoding.replace(/('|")/gi, "");
// 编码
let textDecoder = new TextDecoder(encoding);
responseText = textDecoder.decode(arrayBuffer);
response = responseText;
if (option.responseType === "arraybuffer") {
// response返回格式是二进制流
response = arrayBuffer;
} else if (option.responseType === "blob") {
// response返回格式是blob
response = new Blob([arrayBuffer]);
} else if (
option.responseType === "json" ||
(typeof fetchResponseType === "string" &&
fetchResponseType.includes("application/json"))
) {
// response返回格式是JSON格式
response = Utils.toJSON(responseText);
} else if (
option.responseType === "document" ||
option.responseType == null
) {
// response返回格式是文档格式
let parser = new DOMParser();
response = parser.parseFromString(responseText, "text/html");
}
// 转为XML结构
let parser = new DOMParser();
responseXML = parser.parseFromString(responseText, "text/xml");
Reflect.set(httpxResponse, "response", response);
Reflect.set(httpxResponse, "responseText", responseText);
Reflect.set(httpxResponse, "responseXML", responseXML);
// 执行回调
option.onload(httpxResponse);
})
.catch((error: any) => {
if (error.name === "AbortError") {
return;
}
option.onerror({
isFetch: true,
finalUrl: option.url,
readyState: 4,
status: 0,
statusText: "",
responseHeaders: "",
responseText: "",
error: error,
});
});
option.onloadstart({
isFetch: true,
finalUrl: option.url,
readyState: 1,
responseHeaders: "",
responseText: "",
status: 0,
statusText: "",
});
return {
abort() {
abortController.abort();
},
};
},
};
/**
* 默认配置
*/
#defaultDetails = <HttpxRequestOption>{
url: void 0,
timeout: 5000,
async: false,
responseType: void 0,
headers: void 0,
data: void 0,
redirect: void 0,
cookie: void 0,
cookiePartition: void 0,
binary: void 0,
nocache: void 0,
revalidate: void 0,
context: void 0,
overrideMimeType: void 0,
anonymous: void 0,
fetch: void 0,
fetchInit: void 0,
allowInterceptConfig: {
beforeRequest: true,
afterResponseSuccess: true,
afterResponseError: true,
},
user: void 0,
password: void 0,
onabort() {},
onerror() {},
ontimeout() {},
onloadstart() {},
onreadystatechange() {},
onprogress() {},
};
/**
* 当前使用请求时,输出请求的配置
*/
#LOG_DETAILS = false;
/**
* 实例化,可传入GM_xmlhttpRequest,未传入则使用window.fetch
* @param xmlHttpRequest
*/
constructor(xmlHttpRequest?: Function) {
if (typeof xmlHttpRequest !== "function") {
console.warn(
"[Httpx-constructor] 未传入GM_xmlhttpRequest函数或传入的GM_xmlhttpRequest不是Function,将默认使用window.fetch"
);
}
this.interceptors.request.context = this;
this.interceptors.response.context = this;
this.GM_Api.xmlHttpRequest = xmlHttpRequest;
}
/**
* 覆盖全局配置
* @param details 配置
*/
config(details?: Partial<HttpxRequestOptionConfig>): void;
/**
* 覆盖当前配置
* @param details
*/
config(details: HttpxRequestOptionConfig = {}) {
if ("logDetails" in details && typeof details["logDetails"] === "boolean") {
this.#LOG_DETAILS = details["logDetails"];
}
this.#defaultDetails = Utils.assign(this.#defaultDetails, details);
}
/**
* 拦截器
*/
interceptors = {
/**
* 请求拦截器
*/
request: {
context: null as any as Httpx,
/**
* 添加拦截器
* @param fn 设置的请求前回调函数,如果返回配置,则使用返回的配置,如果返回null|undefined,则阻止请求
*/
use(
fn: <T extends Required<HttpxRequestOption>>(
details: T
) => void | T | Promise<void | T>
) {
if (typeof fn !== "function") {
console.warn("[Httpx-interceptors-request] 请传入拦截器函数");
return;
}
return this.context.HttpxRequestHook.add(fn);
},
/**
* 移除拦截器
* @param id 通过use返回的id
*/
eject(id: string) {
return this.context.HttpxRequestHook.delete(id);
},
/**
* 移除所有拦截器
*/
ejectAll() {
this.context.HttpxRequestHook.clearAll();
},
},
/**
* 响应拦截器
*/
response: {
context: null as any as Httpx,
/**
* 添加拦截器
* @param successFn 设置的响应后回调函数,如果返回响应,则使用返回的响应,如果返回null|undefined,则阻止响应
* + 2xx 范围内的状态码都会触发该函数
* @param errorFn 设置的响应后回调函数,如果返回响应,则使用返回的响应,如果返回null|undefined,则阻止响应
* + 超出 2xx 范围的状态码都会触发该函数
*/
use(
successFn?: <T extends HttpxResponseData<HttpxRequestOption>>(
response: T,
details: HttpxRequestOption
) => void | T,
errorFn?: <T extends HttpxHookErrorData>(
data: T
) => void | T | Promise<void | T>
) {
if (typeof successFn !== "function" && typeof errorFn !== "function") {
console.warn("[Httpx-interceptors-response] 必须传入一个拦截器函数");
return;
}
return this.context.HttpxResponseHook.add(successFn!, errorFn!);
},
/**
* 移除拦截器
* @param id 通过use返回的id
*/
eject(id: string) {
return this.context.HttpxResponseHook.delete(id);
},
/**
* 移除所有拦截器
*/
ejectAll() {
this.context.HttpxResponseHook.clearAll();
},
},
};
/**
* 修改xmlHttpRequest
* @param httpRequest 网络请求函数
*/
setXMLHttpRequest(httpRequest: Function) {
this.GM_Api.xmlHttpRequest = httpRequest;
}
/**
* GET 请求
* @param url 网址
*/
get<T extends HttpxRequestOption>(
url: string // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* GET 请求
* @param details 配置
*/
get<T extends HttpxRequestOption>(
details: T // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* GET 请求
* @param url 网址
* @param details 配置
*/
get<T extends HttpxRequestOption>(
url: string,
details: T // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* GET 请求
* @param url 网址
* @param details 配置
*/
get(
...args: (string | HttpxRequestOption)[] // @ts-ignore
): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
let userRequestOption = this.HttpxRequestOption.handleBeforeRequestOption(
...args
);
let abortFn: Function | null = null;
let promise = new globalThis.Promise<HttpxResponse<HttpxRequestOption>>(
async (resolve, reject) => {
let requestOption = this.HttpxRequestOption.getRequestOption(
"GET",
userRequestOption,
resolve,
reject
);
Reflect.deleteProperty(requestOption, "onprogress");
this.HttpxRequestOption.removeRequestNullOption(requestOption);
const requestResult = await this.HttpxRequest.request(requestOption);
if (
requestResult != null &&
typeof requestResult.abort === "function"
) {
abortFn = requestResult.abort;
}
}
);
// @ts-ignore
promise.abort = () => {
if (typeof abortFn === "function") {
abortFn();
}
};
// @ts-ignore
return promise;
}
/**
* POST 请求
* @param details 配置
*/
post<T extends HttpxRequestOption>(
details: T // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* POST 请求
* @param url 网址
*/
post<T extends HttpxRequestOption>(
url: string
): // @ts-ignore
HttpxPromise<HttpxResponse<T>>;
/**
* POST 请求
* @param url 网址
* @param details 配置
*/
post<T extends HttpxRequestOption>(
url: string,
details: T // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* POST 请求
*/
post(
...args: (HttpxRequestOption | string)[] // @ts-ignore
): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
let userRequestOption = this.HttpxRequestOption.handleBeforeRequestOption(
...args
);
let abortFn: Function | null = null;
let promise = new Promise<HttpxResponse<HttpxRequestOption>>(
async (resolve, reject) => {
let requestOption = this.HttpxRequestOption.getRequestOption(
"POST",
userRequestOption,
resolve,
reject
);
// @ts-ignore
requestOption =
this.HttpxRequestOption.removeRequestNullOption(requestOption);
const requestResult = await this.HttpxRequest.request(requestOption);
if (
requestResult != null &&
typeof requestResult.abort === "function"
) {
abortFn = requestResult.abort;
}
}
);
// @ts-ignore
promise.abort = () => {
if (typeof abortFn === "function") {
abortFn();
}
};
// @ts-ignore
return promise;
}
/**
* HEAD 请求
* @param details 配置
*/
head<T extends HttpxRequestOption>(
details: T // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* HEAD 请求
* @param url 网址
*/
head<T extends HttpxRequestOption>(
url: string // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* HEAD 请求
* @param url 网址
* @param details 配置
*/
head<T extends HttpxRequestOption>(
url: string,
details: T // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* HEAD 请求
*/
head(
...args: (HttpxRequestOption | string)[] // @ts-ignore
): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
let userRequestOption = this.HttpxRequestOption.handleBeforeRequestOption(
...args
);
let abortFn: Function | null = null;
let promise = new Promise<HttpxResponse<HttpxRequestOption>>(
async (resolve, reject) => {
let requestOption = this.HttpxRequestOption.getRequestOption(
"HEAD",
userRequestOption,
resolve,
reject
);
Reflect.deleteProperty(requestOption, "onprogress");
// @ts-ignore
requestOption =
this.HttpxRequestOption.removeRequestNullOption(requestOption);
const requestResult = await this.HttpxRequest.request(requestOption);
if (
requestResult != null &&
typeof requestResult.abort === "function"
) {
abortFn = requestResult.abort;
}
}
);
// @ts-ignore
promise.abort = () => {
if (typeof abortFn === "function") {
abortFn();
}
};
// @ts-ignore
return promise;
}
/**
* OPTIONS 请求
* @param details 配置
*/
options<T extends HttpxRequestOption>(
details: T // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* OPTIONS 请求
* @param url 网址
*/
options<T extends HttpxRequestOption>(
url: string // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* OPTIONS 请求
* @param url 网址
* @param details 配置
*/
options<T extends HttpxRequestOption>(
url: string,
details: T // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* OPTIONS 请求
*/
options(
...args: (HttpxRequestOption | string)[] // @ts-ignore
): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
let userRequestOption = this.HttpxRequestOption.handleBeforeRequestOption(
...args
);
let abortFn: Function | null = null;
let promise = new Promise<HttpxResponse<HttpxRequestOption>>(
async (resolve, reject) => {
let requestOption = this.HttpxRequestOption.getRequestOption(
"OPTIONS",
userRequestOption,
resolve,
reject
);
Reflect.deleteProperty(requestOption, "onprogress");
// @ts-ignore
requestOption =
this.HttpxRequestOption.removeRequestNullOption(requestOption);
const requestResult = await this.HttpxRequest.request(requestOption);
if (
requestResult != null &&
typeof requestResult.abort === "function"
) {
abortFn = requestResult.abort;
}
}
);
// @ts-ignore
promise.abort = () => {
if (typeof abortFn === "function") {
abortFn();
}
};
// @ts-ignore
return promise;
}
/**
* DELETE 请求
* @param details 配置
*/
delete<T extends HttpxRequestOption>(
details: T // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* DELETE 请求
* @param url 网址
*/
delete<T extends HttpxRequestOption>(
url: string // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* DELETE 请求
* @param url 网址
* @param details 配置
*/
delete<T extends HttpxRequestOption>(
url: string,
details: T // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* DELETE 请求
*/
delete(
...args: (HttpxRequestOption | string)[] // @ts-ignore
): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
let userRequestOption = this.HttpxRequestOption.handleBeforeRequestOption(
...args
);
let abortFn: Function | null = null;
let promise = new Promise<HttpxResponse<HttpxRequestOption>>(
async (resolve, reject) => {
let requestOption = this.HttpxRequestOption.getRequestOption(
"DELETE",
userRequestOption,
resolve,
reject
);
Reflect.deleteProperty(requestOption, "onprogress");
// @ts-ignore
requestOption =
this.HttpxRequestOption.removeRequestNullOption(requestOption);
const requestResult = await this.HttpxRequest.request(requestOption);
if (
requestResult != null &&
typeof requestResult.abort === "function"
) {
abortFn = requestResult.abort;
}
}
);
// @ts-ignore
promise.abort = () => {
if (typeof abortFn === "function") {
abortFn();
}
};
// @ts-ignore
return promise;
}
/**
* PUT 请求
* @param details 配置
*/
put<T extends HttpxRequestOption>(
details: T // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* PUT 请求
* @param url 网址
*/
put<T extends HttpxRequestOption>(
url: string // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* PUT 请求
* @param url 网址
* @param details 配置
*/
put<T extends HttpxRequestOption>(
url: string,
details: T // @ts-ignore
): HttpxPromise<HttpxResponse<T>>;
/**
* PUT 请求
*/
put(
...args: (HttpxRequestOption | string)[] // @ts-ignore
): HttpxPromise<HttpxResponse<HttpxRequestOption>> {
let userRequestOption = this.HttpxRequestOption.handleBeforeRequestOption(
...args
);
let abortFn: Function | null = null;
let promise = new Promise<HttpxResponse<HttpxRequestOption>>(
async (resolve, reject) => {
let requestOption = this.HttpxRequestOption.getRequestOption(
"PUT",
userRequestOption,
resolve,
reject
);
// @ts-ignore
requestOption =
this.HttpxRequestOption.removeRequestNullOption(requestOption);
const requestResult = await this.HttpxRequest.request(requestOption);
if (
requestResult != null &&
typeof requestResult.abort === "function"
) {
abortFn = requestResult.abort;
}
}
);
// @ts-ignore
promise.abort = () => {
if (typeof abortFn === "function") {
abortFn();
}
};
// @ts-ignore
return promise;
}
}
export { Httpx };