http-svc
Version:
A HTTP request service for browser and node.js
805 lines (804 loc) • 24.5 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
const middleware = require("@http-svc/middleware");
const getBuiltInMiddlewareName = (name) => `BUILT_IN_${name}`;
const createError = (ctx, message) => {
var _a;
const error = new Error(message || `Request Error: ${((_a = ctx.response) == null ? void 0 : _a.status) || "unknow status"}`);
error.config = ctx.request;
if (ctx.response) {
error.response = ctx.response;
if (!ctx.response.ok) {
error.code = ctx.response.status;
}
}
return error;
};
const isMiddleware = (middleware2) => {
if (middleware2.name && middleware2.handler)
return true;
return false;
};
const isNode = typeof window === "undefined";
const isArray = (val) => toString.call(val) === "[object Array]";
const isObject = (val) => val !== null && typeof val === "object";
const isRecordObj = (obj) => Object.prototype.toString.call(obj) === "[object Object]";
const isFormData = (val) => typeof FormData !== "undefined" && val instanceof FormData;
const isDate = (val) => toString.call(val) === "[object Date]";
const encodeParams = (val) => {
return encodeURIComponent(val).replace(/%3A/gi, ":").replace(/%24/g, "$").replace(/%2C/gi, ",").replace(/%20/g, "+").replace(/%5B/gi, "[").replace(/%5D/gi, "]");
};
const buildURL = (url, params) => {
const parts = [];
Object.keys(params).forEach((key) => {
let val = params[key];
if (val === null || typeof val === "undefined") {
return;
}
if (isArray(val)) {
key = key + "[]";
} else {
val = [val];
}
val.forEach((v) => {
if (isDate(v)) {
v = v.toString();
} else if (isObject(v)) {
v = JSON.stringify(v);
}
parts.push(encodeParams(key) + "=" + encodeParams(v));
});
});
const serializedParams = parts.join("&");
if (serializedParams) {
const hashMarkIndex = url.indexOf("#");
if (hashMarkIndex !== -1) {
url = url.slice(0, hashMarkIndex);
}
url += (url.indexOf("?") === -1 ? "?" : "&") + serializedParams;
}
return url;
};
const parseUrl = (url) => {
const [u, s] = url.split("?");
if (s) {
const searchParams = s.split("&");
if (searchParams.length) {
const params = {};
for (const searchParam of searchParams) {
const [key, value] = searchParam.split("=");
if (key && typeof value !== "undefined") {
params[key] = value;
}
}
return {
url: u,
params
};
}
}
return {
url
};
};
const CONTENT_TYPE_KEY = "Content-Type";
const getContentType = (headers) => (headers == null ? void 0 : headers[CONTENT_TYPE_KEY]) || (headers == null ? void 0 : headers["content-type"]);
const setContentType = (headers, contentType) => {
if (!contentType)
return;
headers[CONTENT_TYPE_KEY] = contentType;
};
const getCookie = (key, options) => {
if (!options)
options = {};
const isNoDecode = options.decode === false;
const template = options.template || document.cookie;
const decodedCookie = isNoDecode ? template : decodeURIComponent(template);
const cookies = decodedCookie.split(";");
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i];
while (cookie.charAt(0) === " ") {
cookie = cookie.substring(1);
}
const keyVal = cookie.split("=");
const name = keyVal[0];
const value = keyVal[1];
if (key === name)
return value;
}
return "";
};
const setObjectValue = (obj, keyOrKeys, value, spread) => {
if (Array.isArray(keyOrKeys)) {
const keys = [...keyOrKeys];
while (keys.length) {
if (keys.length === 1) {
setObjectValue(obj, keys[0], value);
break;
}
const key = keys.shift();
if (!isRecordObj(obj)) {
if (typeof obj[key] === "undefined" && spread) {
obj[key] = {};
} else {
console.warn(`The value of "${key}" is not a record object!`);
break;
}
}
obj = obj[key];
}
} else {
const key = keyOrKeys;
obj[key] = value;
}
};
const initCtx = async function(ctx, next) {
if (!ctx.request)
return next();
const { baseURL, headers, params, data, credentials = "include" } = ctx.config;
let url = ctx.config.url;
const { url: originUrl, params: originParams } = parseUrl(url);
if (originParams) {
url = ctx.request.url = originUrl;
ctx.request.params = {
...originParams,
...params || {}
};
} else {
ctx.request.url = url;
ctx.request.params = {
...params || {}
};
}
if (baseURL && /^(https?:)?\/\//.test(url) === false) {
ctx.request.url = `${baseURL}${url}`;
}
ctx.request.headers = {
...headers || {}
};
if (data) {
if (typeof data === "object") {
if (typeof FormData !== "undefined" && data instanceof FormData) {
const form = new FormData();
const entries = Array.from(data.entries());
entries.forEach(([key, value]) => {
form.append(key, value);
});
ctx.request.data = form;
} else if (Object.keys(data)) {
ctx.request.data = JSON.parse(JSON.stringify(data));
}
} else {
ctx.request.data = data;
}
}
ctx.request.credentials = credentials;
return await next();
};
class HttpSvcInitCtx extends middleware.HttpSvcMiddleware {
constructor() {
super(...arguments);
__publicField(this, "handler", initCtx);
__publicField(this, "name", getBuiltInMiddlewareName("INIT_CTX"));
}
}
__publicField(HttpSvcInitCtx, "handler", initCtx);
const timeout = async function(ctx, next) {
let ms = ctx.config.timeout;
if (typeof ctx.config.timeout !== "number") {
ms = isNode ? 350 : 1e4;
}
if (!ctx.abortController && ms) {
const abortController = new AbortController();
ctx.abortController = abortController;
ctx.timeoutId = setTimeout(() => {
abortController.abort();
}, ms);
await next();
clearTimeout(ctx.timeoutId);
} else {
await next();
}
};
class HttpSvcTimeout extends middleware.HttpSvcMiddleware {
constructor() {
super(timeout);
__publicField(this, "name", getBuiltInMiddlewareName("TIMEOUT"));
}
}
__publicField(HttpSvcTimeout, "handler", timeout);
const xsrf = async function(ctx, next, config) {
var _a, _b;
if (!ctx.request)
return next();
const payload = (config == null ? void 0 : config.payload) || { token: "xsrf_token" };
const { token, params, data, headers } = payload;
let xsrfValue;
if (params || data || headers) {
try {
if (!isNode) {
xsrfValue = getCookie(token, { decode: false });
} else {
xsrfValue = getCookie(token, { template: ((_a = ctx.request.headers) == null ? void 0 : _a["cookie"]) || ((_b = ctx.request.headers) == null ? void 0 : _b["Cookie"]) || "_", decode: false });
}
} catch (error) {
if (!isNode) {
console.warn(error);
}
}
}
if (xsrfValue) {
if (params) {
setObjectValue(ctx.request, ["params", params], xsrfValue, true);
}
if (data) {
if (typeof ctx.request.data === "undefined") {
setObjectValue(ctx.request, ["data", data], xsrfValue, true);
} else if (isObject(ctx.request.data)) {
if (isRecordObj(ctx.request.data)) {
setObjectValue(ctx.request, ["data", data], xsrfValue, true);
} else if (isFormData(ctx.request.data)) {
ctx.request.data.append(data, xsrfValue);
}
}
}
if (headers) {
setObjectValue(ctx.request, ["headers", headers], xsrfValue, true);
}
}
return await next();
};
class HttpSvcXsrf extends middleware.HttpSvcMiddleware {
constructor(payload) {
super({
handler: xsrf,
payload
});
__publicField(this, "name", getBuiltInMiddlewareName("XSRF"));
}
}
__publicField(HttpSvcXsrf, "handler", xsrf);
const isEqualContentType = (target, current) => {
if (!current)
return false;
return current.indexOf(target) > -1;
};
const jsonBody = (ctx, data, contentType) => {
if (!ctx.request)
return;
ctx.request.data = JSON.stringify(data);
if (!ctx.request.headers) {
const headers = {};
setContentType(headers, "application/json");
ctx.request.headers = headers;
} else if (!contentType) {
setContentType(ctx.request.headers, "application/json");
}
};
const body = async function(ctx, next, config) {
var _a;
if (!ctx.request)
return next();
const { params, headers } = ctx.request;
ctx.request.url = buildURL(ctx.request.url, params || {});
if (ctx.request.data) {
const isContinue = Object.prototype.toString.call(ctx.request.data) !== "[object Object]";
if (!isContinue) {
const contentType = getContentType(headers);
const data = ctx.request.data;
if ((_a = config == null ? void 0 : config.payload) == null ? void 0 : _a.stringify) {
jsonBody(ctx, data, contentType);
return await next();
}
if (isEqualContentType("application/x-www-form-urlencoded", contentType)) {
ctx.request.data = buildURL("", data).slice(1);
}
if (isEqualContentType("application/json", contentType || "application/json")) {
jsonBody(ctx, data, contentType);
}
if (isEqualContentType("multipart/form-data", contentType)) {
const keys = Object.keys(data);
if (keys.length) {
ctx.request.data = keys.reduce((form, key) => {
form.append(key, data[key]);
return form;
}, new FormData());
}
}
}
}
return await next();
};
class HttpSvcBody extends middleware.HttpSvcMiddleware {
constructor() {
super(body);
__publicField(this, "name", getBuiltInMiddlewareName("BODY"));
}
}
__publicField(HttpSvcBody, "handler", body);
const fetch = async (ctx, next) => {
var _a;
if (isNode) {
throw new Error("When you are in a Node environment, please use server-side fetch middleware.");
}
if (!window.fetch) {
throw new Error("When you are in a non-modern browser, please manually polyfill fetch.");
}
if (!ctx.request)
return await next();
if (ctx.response)
return await next();
const { url, method, headers, credentials } = ctx.request;
const body2 = ctx.request.data;
ctx.response = await window.fetch(url, {
method,
body: body2,
headers,
credentials,
signal: (_a = ctx.abortController) == null ? void 0 : _a.signal
});
return await next();
};
class HttpSvcFetch extends middleware.HttpSvcMiddleware {
constructor() {
super(...arguments);
__publicField(this, "handler", fetch);
__publicField(this, "name", getBuiltInMiddlewareName("FETCH"));
}
}
__publicField(HttpSvcFetch, "handler", fetch);
const resData = async function(ctx, next, config) {
var _a, _b;
await next();
if ((_a = ctx.response) == null ? void 0 : _a.ok) {
if (typeof ctx.response.data === "undefined") {
try {
const responseType = ((_b = config == null ? void 0 : config.payload) == null ? void 0 : _b.type) || "json";
ctx.response.data = await ctx.response[responseType]();
} catch (error) {
console.warn(error);
}
}
}
};
class HttpSvcResData extends middleware.HttpSvcMiddleware {
constructor() {
super(resData);
__publicField(this, "name", getBuiltInMiddlewareName("RES_DATA"));
}
}
__publicField(HttpSvcResData, "handler", resData);
const spray = async (ctx, next) => {
await next();
};
class HttpSvcSprayMiddleware extends middleware.HttpSvcMiddleware {
constructor(handler, payload) {
super();
__publicField(this, "name", getBuiltInMiddlewareName("SPRAY"));
this.handler = async (ctx, next) => {
return await handler(ctx, next, { payload });
};
}
}
__publicField(HttpSvcSprayMiddleware, "handler", spray);
const retry = async (ctx, next, config) => {
var _a;
const requestFn = (_a = ctx.request) == null ? void 0 : _a.function;
const { times, condition, onRetry } = (config == null ? void 0 : config.payload) || { times: 0 };
const isDone = () => {
return !times || ctx.retry === times;
};
const doRetry = async (extra) => {
var _a2;
if (!requestFn) {
throw createError(ctx, "Missing request function");
}
if (isDone() && !((_a2 = config == null ? void 0 : config.payload) == null ? void 0 : _a2.runtimeShortCircuit)) {
throw createError(ctx);
}
ctx.retry = ctx.retry ? ctx.retry + 1 : 1;
try {
if (onRetry) {
const newConfig = await onRetry(ctx.config, extra);
if (newConfig) {
ctx.config = newConfig;
}
}
if (ctx.response)
delete ctx.response;
await requestFn(ctx);
} catch (error) {
throw createError(ctx, error.message);
}
};
try {
await next();
} catch (error) {
if (isDone()) {
throw createError(ctx, error == null ? void 0 : error.message);
}
await doRetry();
return;
}
if (condition) {
const isMeetTheCondition = await condition(ctx.response);
if (isMeetTheCondition) {
await doRetry(isMeetTheCondition);
return;
}
}
if (!ctx.response || !ctx.response.ok) {
await doRetry();
}
};
class HttpSvcRetry extends middleware.HttpSvcMiddleware {
constructor() {
super(...arguments);
__publicField(this, "name", getBuiltInMiddlewareName("RETRY"));
__publicField(this, "handler", retry);
}
}
__publicField(HttpSvcRetry, "handler", retry);
const getKeyByKeyDict = (url, dict) => {
return Object.keys(dict).sort().reduce((k, c) => {
k += `_${c}:${dict[c]}`;
return k;
}, `api:${url}`);
};
const cache = async (ctx, next, config) => {
var _a;
if (!ctx.request || ctx.request.method !== "GET")
return await next();
if (!(config == null ? void 0 : config.payload))
return await next();
const { store, extra } = config.payload;
let key = config.payload.key;
if (!key || !store)
return await next();
if (typeof key !== "string")
key = getKeyByKeyDict(ctx.request.url, key);
const data = store.get(key, extra);
if (data) {
ctx.response = new Response(JSON.stringify(data), {
status: 200,
statusText: `Cache:${key}`
});
await next();
} else {
await next();
if ((_a = ctx.response) == null ? void 0 : _a.data) {
store.set(key, ctx.response.data, extra);
}
}
};
class HttpSvcCache extends middleware.HttpSvcMiddleware {
constructor(store) {
super({
handler: cache,
payload: {
store
}
});
__publicField(this, "name", getBuiltInMiddlewareName("CACHE"));
}
}
__publicField(HttpSvcCache, "handler", cache);
class HttpSvcControl {
constructor(httpSvc) {
this.httpSvc = httpSvc;
}
}
class ConfigControl extends HttpSvcControl {
provide(ctx = {}) {
const provider = {
inject: (key, value) => {
this.inject(ctx, key, value);
return provider;
},
disable: (key) => {
this.disable(ctx, key);
return provider;
},
get: () => {
return ctx;
}
};
return provider;
}
inject(ctx, name, config) {
ctx[name] = config;
}
disable(ctx, name) {
ctx[name] = {
...ctx[name] || {},
disabled: true
};
}
get(ctx, name) {
return ctx[name] || {};
}
}
const BUILT_IN_MIDDLEWARE = {
INIT_CTX: getBuiltInMiddlewareName("INIT_CTX"),
TIMEOUT: getBuiltInMiddlewareName("TIMEOUT"),
BODY: getBuiltInMiddlewareName("BODY"),
XSRF: getBuiltInMiddlewareName("XSRF"),
FETCH: getBuiltInMiddlewareName("FETCH"),
RETRY: getBuiltInMiddlewareName("RETRY"),
SPRAY: getBuiltInMiddlewareName("SPRAY"),
RES_DATA: getBuiltInMiddlewareName("RES_DATA"),
RES_EXTRACT: getBuiltInMiddlewareName("RES_EXTRACT"),
LOG: getBuiltInMiddlewareName("LOG"),
CACHE: getBuiltInMiddlewareName("CACHE")
};
const ORDER_PRIFIX = "$ORDER_";
class AssembleDispatcher {
constructor(control) {
__publicField(this, "middlewares", []);
__publicField(this, "middlewareConfigCtxProvider");
this.control = control;
this.middlewareConfigCtxProvider = this.control.httpSvc.configCtrl.provide();
}
with(middleware2, payload) {
if (!middleware2)
return this;
if (typeof middleware2 === "string") {
this.middlewares.push({
name: `${ORDER_PRIFIX}${middleware2}`,
handler: HttpSvcSprayMiddleware.handler
});
if (payload) {
this.inject(middleware2, payload);
}
} else if (typeof middleware2 === "function") {
this.middlewares.push(new HttpSvcSprayMiddleware(middleware2, payload));
} else if (isMiddleware(middleware2)) {
this.middlewares.push(middleware2);
if (payload) {
this.inject(middleware2.name, payload);
}
}
return this;
}
inject(name, payload) {
this.middlewareConfigCtxProvider.inject(name, { payload });
return this;
}
disable(name) {
this.middlewareConfigCtxProvider.disable(name);
return this;
}
request(config) {
const fn = this.control.compose(this.middlewares);
return this.control.httpSvc.requestCtrl.request(config, this.middlewareConfigCtxProvider.get(), fn);
}
}
class AssembleControl extends HttpSvcControl {
constructor() {
super(...arguments);
__publicField(this, "middlewares", []);
}
alreadyExistsIdx(middlewareName) {
return this.middlewares.findIndex((m) => m.name === middlewareName);
}
register(middlewares) {
if (Array.isArray(middlewares)) {
let reset = false;
middlewares.forEach((m) => {
if (isMiddleware(m)) {
reset = true;
const idx = this.alreadyExistsIdx(m.name);
if (idx > -1) {
this.middlewares[idx] = m;
} else {
this.middlewares.push(m);
}
}
});
if (reset) {
this.httpSvc.requestCtrl.reset();
}
}
}
setup() {
return new AssembleDispatcher(this);
}
disable(middlewareName) {
return this.setup().disable(middlewareName);
}
with(middleware2, payload) {
return this.setup().with(middleware2, payload);
}
compose(middlewares) {
const list1 = [...this.middlewares];
const list2 = [];
const builtInOverride = {
[BUILT_IN_MIDDLEWARE.RETRY]: null,
[BUILT_IN_MIDDLEWARE.BODY]: null,
[BUILT_IN_MIDDLEWARE.RES_DATA]: null,
[BUILT_IN_MIDDLEWARE.TIMEOUT]: null
};
const builtInNames = Object.keys(builtInOverride);
const unique = {};
(middlewares || []).forEach((m) => {
if (isMiddleware(m)) {
let middlewareName = m.name;
if (builtInNames.includes(middlewareName)) {
if (!builtInOverride[middlewareName]) {
builtInOverride[middlewareName] = m;
}
} else {
if (middlewareName.startsWith(ORDER_PRIFIX)) {
middlewareName = middlewareName.substring(ORDER_PRIFIX.length);
if (!unique[middlewareName] && !(middlewareName in builtInOverride)) {
const globalMiddlewareIndex = list1.findIndex((m2) => m2.name === middlewareName);
if (globalMiddlewareIndex > -1) {
const realM = list1[globalMiddlewareIndex];
list1.splice(globalMiddlewareIndex, 1);
unique[middlewareName] = realM;
list2.push(realM);
}
}
} else {
if (BUILT_IN_MIDDLEWARE.SPRAY === middlewareName) {
list2.push(m);
} else if (!unique[middlewareName]) {
unique[middlewareName] = m;
list2.push(m);
}
}
}
}
});
const list = [];
list1.forEach((m) => {
if (builtInNames.includes(m.name)) {
if (!builtInOverride[m.name]) {
builtInOverride[m.name] = m;
}
} else if (!unique[m.name]) {
list.push(m);
}
});
list.push(...list2);
const middlewareList = [
builtInOverride[BUILT_IN_MIDDLEWARE.RETRY] || new HttpSvcRetry(),
new HttpSvcInitCtx(),
...list,
builtInOverride[BUILT_IN_MIDDLEWARE.RES_DATA] || new HttpSvcResData(),
builtInOverride[BUILT_IN_MIDDLEWARE.BODY] || new HttpSvcBody(),
builtInOverride[BUILT_IN_MIDDLEWARE.TIMEOUT] || new HttpSvcTimeout(),
this.httpSvc.fetch
];
return async (context) => {
var _a, _b;
let index = -1;
const configCtrl = this.httpSvc.configCtrl;
const debug = configCtrl.get(context.middleware, "DEBUG").payload;
await dispatch(0);
if (debug) {
return context;
}
const disableResExtract = configCtrl.get(context.middleware, BUILT_IN_MIDDLEWARE.RES_EXTRACT).disabled;
if (!disableResExtract && ((_a = context.response) == null ? void 0 : _a.ok)) {
return ((_b = context.response) == null ? void 0 : _b.data) || null;
}
return context.response;
async function dispatch(i) {
if (i === middlewareList.length)
return;
if (i <= index)
throw createError(context, "The next() called multiple times");
index = i;
const middleware2 = middlewareList[index];
if (!middleware2) {
throw createError(context, "Middleware is not exist");
}
await middleware2.handler(context, dispatch.bind(null, i + 1), configCtrl.get(context.middleware, middleware2.name));
}
};
}
}
class RequestControl extends HttpSvcControl {
constructor() {
super(...arguments);
__publicField(this, "fn", null);
}
async request(config, middlewareCtx = {}, fn) {
if (!fn) {
if (!this.fn) {
this.generateRequestFunction();
}
fn = this.fn;
}
return await fn(this.createContext(config, middlewareCtx, fn));
}
generateRequestFunction() {
this.fn = this.httpSvc.assembleCtrl.compose();
}
reset() {
this.fn = null;
}
get useAsyncRequest() {
return (asyncRequest, config) => {
const inst = this.httpSvc.create(config);
asyncRequest(inst.request.bind(inst));
};
}
createContext(config, middleware2 = {}, requestFn) {
const { url, method } = config;
const ctx = {
config: {
baseURL: this.httpSvc.baseURL,
...config
},
useAsyncRequest: this.useAsyncRequest,
middleware: middleware2,
request: {
url,
method: (method || "get").toUpperCase(),
function: requestFn
}
};
return ctx;
}
}
class HttpService {
constructor(initConfig) {
__publicField(this, "fetch");
__publicField(this, "assembleCtrl", new AssembleControl(this));
__publicField(this, "configCtrl", new ConfigControl(this));
__publicField(this, "requestCtrl", new RequestControl(this));
__publicField(this, "baseURL", "//api.domain.com");
if (initConfig) {
if (Array.isArray(initConfig)) {
this.fetch = new HttpSvcFetch();
this.register(initConfig);
} else {
const { middlewares, fetch: fetch2, baseURL } = initConfig;
this.fetch = fetch2 || new HttpSvcFetch();
this.register(middlewares || []);
if (baseURL)
this.baseURL = baseURL;
}
} else {
this.fetch = new HttpSvcFetch();
this.register([]);
}
}
register(middlewares) {
this.assembleCtrl.register(middlewares);
}
request(config) {
return this.requestCtrl.request(config);
}
disable(middlewareName) {
return this.assembleCtrl.disable(middlewareName);
}
with(middleware2, payload) {
return this.assembleCtrl.with(middleware2, payload);
}
setFetch(fetch2) {
this.fetch = fetch2;
this.requestCtrl.reset();
}
create(config) {
return new HttpService({
fetch: this.fetch,
...config || {}
});
}
}
exports.BUILT_IN_MIDDLEWARE = BUILT_IN_MIDDLEWARE;
exports.HttpService = HttpService;
exports.HttpSvcCache = HttpSvcCache;
exports.HttpSvcRetry = HttpSvcRetry;
exports.HttpSvcSprayMiddleware = HttpSvcSprayMiddleware;
exports.HttpSvcXsrf = HttpSvcXsrf;
//# sourceMappingURL=index.cjs.map