mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-06-04 11:13:59 +08:00
增加 Loon UDP relay 的支持
This commit is contained in:
parent
e72745fbce
commit
c225751b6f
@ -2432,6 +2432,7 @@ var ProxyUtils = (function () {
|
||||
const targetPlatform = "Loon";
|
||||
const produce = (proxy) => {
|
||||
let obfs_opts, tls_opts;
|
||||
const udp_opts = proxy.udp ? ",udp=true" : "";
|
||||
switch (proxy.type) {
|
||||
case "ss":
|
||||
obfs_opts = ",,";
|
||||
@ -2445,8 +2446,8 @@ var ProxyUtils = (function () {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return `${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"${obfs_opts}`;
|
||||
|
||||
return `${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"${obfs_opts}${udp_opts}`;
|
||||
case "ssr":
|
||||
return `${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}",${proxy.protocol},{${proxy["protocol-param"] || ""}},${proxy.obfs},{${proxy["obfs-param"] || ""}}`;
|
||||
case "vmess":
|
||||
|
4
backend/sub-store.min.js
vendored
4
backend/sub-store.min.js
vendored
File diff suppressed because one or more lines are too long
@ -2,7 +2,7 @@
|
||||
Sub-Store 自动同步配置到gist
|
||||
由于机制特殊,注意需要按照说明进行配置!
|
||||
|
||||
「QX配置」
|
||||
「QX配置」
|
||||
[http_backend]
|
||||
https://raw.githubusercontent.com/Peng-YM/Sub-Store/master/backend/sub-store.min.js, tag=Sub-Store, path=/, enabled=true
|
||||
[task_local]
|
||||
|
@ -45,7 +45,9 @@ var ProxyUtils = (function () {
|
||||
function Base64Encoded() {
|
||||
const name = "Base64 Pre-processor";
|
||||
|
||||
const keys = ["dm1lc3M", "c3NyOi8v", "dHJvamFu", "c3M6Ly", "c3NkOi8v"];
|
||||
const keys = ["dm1lc3M", "c3NyOi8v", "dHJvamFu", "c3M6Ly", "c3NkOi8v",
|
||||
"c2hhZG93", "aHR0c"
|
||||
];
|
||||
|
||||
const test = function (raw) {
|
||||
return keys.some(k => raw.indexOf(k) !== -1);
|
||||
@ -244,21 +246,21 @@ var ProxyUtils = (function () {
|
||||
supported,
|
||||
};
|
||||
// get other params
|
||||
params = {};
|
||||
const other_params = {};
|
||||
line = line.split("/?")[1].split("&");
|
||||
if (line.length > 1) {
|
||||
for (const item of line) {
|
||||
const [key, val] = item.split("=");
|
||||
params[key] = val;
|
||||
other_params[key] = val.trim();
|
||||
}
|
||||
}
|
||||
proxy = {
|
||||
...proxy,
|
||||
name: Base64.safeDecode(params.remarks),
|
||||
name: other_params.remarks ? Base64.safeDecode(other_params.remarks) : proxy.server,
|
||||
"protocol-param":
|
||||
Base64.safeDecode(params.protoparam).replace(/\s/g, "") || "",
|
||||
Base64.safeDecode(other_params.protoparam || "").replace(/\s/g, ""),
|
||||
"obfs-param":
|
||||
Base64.safeDecode(params.obfsparam).replace(/\s/g, "") || "",
|
||||
Base64.safeDecode(other_params.obfsparam || "").replace(/\s/g, ""),
|
||||
};
|
||||
return proxy;
|
||||
};
|
||||
@ -280,8 +282,8 @@ var ProxyUtils = (function () {
|
||||
line = line.split("vmess://")[1];
|
||||
const content = Base64.safeDecode(line);
|
||||
if (/=\s*vmess/.test(content)) {
|
||||
const partitions = content.split(",").map((p) => p.trim());
|
||||
// Quantumult VMess URI format
|
||||
const partitions = content.split(",").map((p) => p.trim());
|
||||
// get keyword params
|
||||
const params = {};
|
||||
for (const part of partitions) {
|
||||
@ -299,16 +301,21 @@ var ProxyUtils = (function () {
|
||||
cipher: partitions[3],
|
||||
uuid: partitions[4].match(/^"(.*)"$/)[1],
|
||||
tls: params.obfs === "over-tls" || params.obfs === "wss",
|
||||
udp: JSON.parse(params["udp-relay"] || "false"),
|
||||
tfo: JSON.parse(params["fast-open"] || "false"),
|
||||
};
|
||||
|
||||
if (typeof params['udp-relay'] !== "undefined") proxy.udp = JSON.parse(params["udp-relay"]);
|
||||
if (typeof params['fast-open'] !== "undefined") proxy.udp = JSON.parse(params["fast-open"]);
|
||||
|
||||
// handle ws headers
|
||||
if (params.obfs === "ws" || params.obfs === "wss") {
|
||||
proxy.network = "ws";
|
||||
proxy["ws-path"] = params["obfs-uri"];
|
||||
proxy["ws-path"] = (params["obfs-path"] || '"/"').match(/^"(.*)"$/)[1];
|
||||
let obfs_host = params["obfs-header"];
|
||||
if (obfs_host && obfs_host.indexOf("Host") !== -1) {
|
||||
obfs_host = obfs_host.match(/Host:\s*([a-zA-Z0-9-.]*)/)[1];
|
||||
}
|
||||
proxy["ws-headers"] = {
|
||||
Host: params["obfs-host"] || proxy.server, // if no host provided, use the same as server
|
||||
Host: obfs_host || proxy.server, // if no host provided, use the same as server
|
||||
};
|
||||
}
|
||||
|
||||
@ -367,19 +374,15 @@ var ProxyUtils = (function () {
|
||||
|
||||
const parse = (line) => {
|
||||
const supported = {};
|
||||
// trojan forces to use 443 port
|
||||
if (line.indexOf(":443") === -1) {
|
||||
throw new Error("Trojan port should always be 443!");
|
||||
}
|
||||
line = line.split("trojan://")[1];
|
||||
const server = line.split("@")[1].split(":443")[0];
|
||||
const [server, port] = line.split("@")[1].split("?")[0].split(":");
|
||||
const name = decodeURIComponent(line.split("#")[1].trim());
|
||||
|
||||
return {
|
||||
name: name || `[Trojan] ${server}`, // trojan uri may have no server tag!
|
||||
type: "trojan",
|
||||
server,
|
||||
port: 443,
|
||||
port,
|
||||
password: line.split("@")[0],
|
||||
supported,
|
||||
};
|
||||
@ -836,8 +839,10 @@ var ProxyUtils = (function () {
|
||||
if (JSON.parse(params.ws || "false")) {
|
||||
proxy.network = "ws";
|
||||
proxy["ws-path"] = params["ws-path"];
|
||||
const res = params["ws-headers"].match(/(,|^|\s)*HOST:\s*(.*?)(,|$)/);
|
||||
const host = res ? res[2] : proxy.server;
|
||||
proxy["ws-headers"] = {
|
||||
Host: params.sni,
|
||||
Host: host || params.server,
|
||||
};
|
||||
}
|
||||
return proxy;
|
||||
@ -985,27 +990,30 @@ var ProxyUtils = (function () {
|
||||
};
|
||||
}
|
||||
|
||||
// sort by keywords
|
||||
function KeywordSortOperator(keywords) {
|
||||
// sort by regex
|
||||
function RegexSortOperator(expressions) {
|
||||
return {
|
||||
name: "Keyword Sort Operator",
|
||||
func: (proxies) =>
|
||||
proxies.sort((a, b) => {
|
||||
const oA = getKeywordOrder(keywords, a.name);
|
||||
const oB = getKeywordOrder(keywords, b.name);
|
||||
name: "Regex Sort Operator",
|
||||
func: (proxies) => {
|
||||
expressions = expressions.map(expr => buildRegex(expr));
|
||||
return proxies.sort((a, b) => {
|
||||
const oA = getRegexOrder(expressions, a.name);
|
||||
const oB = getRegexOrder(expressions, b.name);
|
||||
if (oA && !oB) return -1;
|
||||
if (oB && !oA) return 1;
|
||||
if (oA && oB) return oA < oB ? -1 : 1;
|
||||
if ((!oA && !oB) || (oA && oB && oA === oB))
|
||||
return a.name < b.name ? -1 : 1; // fallback to normal sort
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
function getKeywordOrder(keywords, str) {
|
||||
function getRegexOrder(expressions, str) {
|
||||
let order = null;
|
||||
for (let i = 0; i < keywords.length; i++) {
|
||||
if (str.indexOf(keywords[i]) !== -1) {
|
||||
for (let i = 0; i < expressions.length; i++) {
|
||||
if (expressions[i].test(str)) {
|
||||
order = i + 1; // plus 1 is important! 0 will be treated as false!!!
|
||||
break;
|
||||
}
|
||||
@ -1013,22 +1021,6 @@ var ProxyUtils = (function () {
|
||||
return order;
|
||||
}
|
||||
|
||||
// rename by keywords
|
||||
// keywords: [{old: "old", now: "now"}]
|
||||
function KeywordRenameOperator(keywords) {
|
||||
return {
|
||||
name: "Keyword Rename Operator",
|
||||
func: (proxies) => {
|
||||
return proxies.map((proxy) => {
|
||||
for (const {old, now} of keywords) {
|
||||
proxy.name = proxy.name.replaceAll(old, now).trim();
|
||||
}
|
||||
return proxy;
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// rename by regex
|
||||
// keywords: [{expr: "string format regex", now: "now"}]
|
||||
function RegexRenameOperator(regex) {
|
||||
@ -1037,7 +1029,7 @@ var ProxyUtils = (function () {
|
||||
func: (proxies) => {
|
||||
return proxies.map((proxy) => {
|
||||
for (const {expr, now} of regex) {
|
||||
proxy.name = proxy.name.replace(new RegExp(expr, "g"), now).trim();
|
||||
proxy.name = proxy.name.replace(buildRegex(expr, "g"), now).trim();
|
||||
}
|
||||
return proxy;
|
||||
});
|
||||
@ -1045,21 +1037,6 @@ var ProxyUtils = (function () {
|
||||
};
|
||||
}
|
||||
|
||||
// delete keywords operator
|
||||
// keywords: ['a', 'b', 'c']
|
||||
function KeywordDeleteOperator(keywords) {
|
||||
const keywords_ = keywords.map((k) => {
|
||||
return {
|
||||
old: k,
|
||||
now: "",
|
||||
};
|
||||
});
|
||||
return {
|
||||
name: "Keyword Delete Operator",
|
||||
func: KeywordRenameOperator(keywords_).func,
|
||||
};
|
||||
}
|
||||
|
||||
// delete regex operator
|
||||
// regex: ['a', 'b', 'c']
|
||||
function RegexDeleteOperator(regex) {
|
||||
@ -1094,16 +1071,10 @@ var ProxyUtils = (function () {
|
||||
(function () {
|
||||
// interface to get internal operators
|
||||
const $get = (name, args) => {
|
||||
const item = AVAILABLE_OPERATORS[name];
|
||||
const item = PROXY_PROCESSORS[name];
|
||||
return item(args);
|
||||
};
|
||||
const $process = (item, proxies) => {
|
||||
if (item.name.indexOf("Filter") !== -1) {
|
||||
return ApplyOperator(item, proxies);
|
||||
} else if (item.name.indexOf("Operator") !== -1) {
|
||||
return ApplyFilter(item, proxies);
|
||||
}
|
||||
};
|
||||
const $process = ApplyProcessor;
|
||||
eval(script);
|
||||
output = operator(proxies);
|
||||
})();
|
||||
@ -1113,19 +1084,6 @@ var ProxyUtils = (function () {
|
||||
}
|
||||
|
||||
/**************************** Filters ***************************************/
|
||||
// filter by keywords
|
||||
function KeywordFilter({keywords = [], keep = true}) {
|
||||
return {
|
||||
name: "Keyword Filter",
|
||||
func: (proxies) => {
|
||||
return proxies.map((proxy) => {
|
||||
const selected = keywords.some((k) => proxy.name.indexOf(k) !== -1);
|
||||
return keep ? selected : !selected;
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// filter useless proxies
|
||||
function UselessFilter() {
|
||||
const KEYWORDS = [
|
||||
@ -1139,8 +1097,8 @@ var ProxyUtils = (function () {
|
||||
];
|
||||
return {
|
||||
name: "Useless Filter",
|
||||
func: KeywordFilter({
|
||||
keywords: KEYWORDS,
|
||||
func: RegexFilter({
|
||||
regex: KEYWORDS,
|
||||
keep: false,
|
||||
}).func,
|
||||
};
|
||||
@ -1175,8 +1133,7 @@ var ProxyUtils = (function () {
|
||||
func: (proxies) => {
|
||||
return proxies.map((proxy) => {
|
||||
const selected = regex.some((r) => {
|
||||
r = new RegExp(r);
|
||||
return r.test(proxy.name);
|
||||
return buildRegex(r).test(proxy.name);
|
||||
});
|
||||
return keep ? selected : !selected;
|
||||
});
|
||||
@ -1432,7 +1389,6 @@ var ProxyUtils = (function () {
|
||||
}
|
||||
|
||||
return {
|
||||
"Keyword Filter": KeywordFilter,
|
||||
"Useless Filter": UselessFilter,
|
||||
"Region Filter": RegionFilter,
|
||||
"Regex Filter": RegexFilter,
|
||||
@ -1442,9 +1398,7 @@ var ProxyUtils = (function () {
|
||||
"Set Property Operator": SetPropertyOperator,
|
||||
"Flag Operator": FlagOperator,
|
||||
"Sort Operator": SortOperator,
|
||||
"Keyword Sort Operator": KeywordSortOperator,
|
||||
"Keyword Rename Operator": KeywordRenameOperator,
|
||||
"Keyword Delete Operator": KeywordDeleteOperator,
|
||||
"Regex Sort Operator": RegexSortOperator,
|
||||
"Regex Rename Operator": RegexRenameOperator,
|
||||
"Regex Delete Operator": RegexDeleteOperator,
|
||||
"Script Operator": ScriptOperator,
|
||||
@ -1560,6 +1514,7 @@ var ProxyUtils = (function () {
|
||||
const targetPlatform = "Loon";
|
||||
const produce = (proxy) => {
|
||||
let obfs_opts, tls_opts;
|
||||
const udp_opts = proxy.udp ? ",udp=true" : "";
|
||||
switch (proxy.type) {
|
||||
case "ss":
|
||||
obfs_opts = ",,";
|
||||
@ -1573,8 +1528,8 @@ var ProxyUtils = (function () {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return `${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"${obfs_opts}`;
|
||||
|
||||
return `${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}"${obfs_opts}${udp_opts}`;
|
||||
case "ssr":
|
||||
return `${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},"${proxy.password}",${proxy.protocol},{${proxy["protocol-param"] || ""}},${proxy.obfs},{${proxy["obfs-param"] || ""}}`;
|
||||
case "vmess":
|
||||
@ -1805,7 +1760,7 @@ var ProxyUtils = (function () {
|
||||
for (const processor of PROXY_PREPROCESSORS) {
|
||||
try {
|
||||
if (processor.test(raw)) {
|
||||
$.log(`Pre-processor [${processor.name}] activated`);
|
||||
$.info(`Pre-processor [${processor.name}] activated`);
|
||||
return processor.parse(raw);
|
||||
}
|
||||
} catch (e) {
|
||||
@ -1841,7 +1796,7 @@ var ProxyUtils = (function () {
|
||||
if (safeMatch(parser, line)) {
|
||||
lastParser = parser;
|
||||
matched = true;
|
||||
$.log(`Proxy parser: ${parser.name} is activated`);
|
||||
$.info(`Proxy parser: ${parser.name} is activated`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1874,14 +1829,18 @@ var ProxyUtils = (function () {
|
||||
const {mode, content} = item.args;
|
||||
if (mode === "link") {
|
||||
// if this is remote script, download it
|
||||
script = await $.http
|
||||
.get(content)
|
||||
.then((resp) => resp.body)
|
||||
.catch((err) => {
|
||||
throw new Error(
|
||||
`Error when downloading remote script: ${item.args.content}.\n Reason: ${err}`
|
||||
);
|
||||
});
|
||||
try {
|
||||
script = await $.http
|
||||
.get(content)
|
||||
.then((resp) => resp.body);
|
||||
} catch (err) {
|
||||
$.error(
|
||||
`Error when downloading remote script: ${item.args.content}.\n Reason: ${err}`
|
||||
);
|
||||
// skip the script if download failed.
|
||||
continue;
|
||||
}
|
||||
|
||||
} else {
|
||||
script = content;
|
||||
}
|
||||
@ -1892,7 +1851,7 @@ var ProxyUtils = (function () {
|
||||
continue;
|
||||
}
|
||||
|
||||
$.log(
|
||||
$.info(
|
||||
`Applying "${item.type}" with arguments:\n >>> ${
|
||||
JSON.stringify(item.args, null, 2) || "None"
|
||||
}`
|
||||
@ -1917,7 +1876,7 @@ var ProxyUtils = (function () {
|
||||
// filter unsupported proxies
|
||||
proxies = proxies.filter(proxy => !(proxy.supported && proxy.supported[targetPlatform] === false));
|
||||
|
||||
$.log(`Producing proxies for target: ${targetPlatform}`);
|
||||
$.info(`Producing proxies for target: ${targetPlatform}`);
|
||||
if (typeof producer.type === "undefined" || producer.type === 'SINGLE') {
|
||||
return proxies
|
||||
.map(proxy => {
|
||||
@ -2287,14 +2246,6 @@ function AND(...args) {
|
||||
return args.reduce((a, b) => a.map((c, i) => b[i] && c));
|
||||
}
|
||||
|
||||
function OR(...args) {
|
||||
return args.reduce((a, b) => a.map((c, i) => b[i] || c));
|
||||
}
|
||||
|
||||
function NOT(array) {
|
||||
return array.map((c) => !c);
|
||||
}
|
||||
|
||||
function FULL(length, bool) {
|
||||
return [...Array(length).keys()].map(() => bool);
|
||||
}
|
||||
@ -2635,370 +2586,6 @@ function API(name = "untitled", debug = false) {
|
||||
})(name, debug);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gist backup
|
||||
*/
|
||||
function Gist(backupKey, token) {
|
||||
const FILE_NAME = "Sub-Store";
|
||||
const http = HTTP({
|
||||
baseURL: "https://api.github.com",
|
||||
headers: {
|
||||
Authorization: `token ${token}`,
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.141 Safari/537.36",
|
||||
},
|
||||
events: {
|
||||
onResponse: (resp) => {
|
||||
if (/^[45]/.test(String(resp.statusCode))) {
|
||||
return Promise.reject(`ERROR: ${JSON.parse(resp.body).message}`);
|
||||
} else {
|
||||
return resp;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
async function locate() {
|
||||
return http.get("/gists").then((response) => {
|
||||
const gists = JSON.parse(response.body);
|
||||
for (let g of gists) {
|
||||
if (g.description === backupKey) {
|
||||
return g.id;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
});
|
||||
}
|
||||
|
||||
this.upload = async function (content) {
|
||||
const id = await locate();
|
||||
const files = {
|
||||
[FILE_NAME]: {content}
|
||||
};
|
||||
|
||||
if (id === -1) {
|
||||
// create a new gist for backup
|
||||
return http.post({
|
||||
url: "/gists",
|
||||
body: JSON.stringify({
|
||||
description: backupKey,
|
||||
public: false,
|
||||
files
|
||||
})
|
||||
});
|
||||
} else {
|
||||
// update an existing gist
|
||||
return http.patch({
|
||||
url: `/gists/${id}`,
|
||||
body: JSON.stringify({files})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.download = async function () {
|
||||
const id = await locate();
|
||||
if (id === -1) {
|
||||
return Promise.reject("未找到Gist备份!");
|
||||
} else {
|
||||
try {
|
||||
const {files} = await http
|
||||
.get(`/gists/${id}`)
|
||||
.then(resp => JSON.parse(resp.body));
|
||||
const url = files[FILE_NAME].raw_url;
|
||||
return await http.get(url).then(resp => resp.body);
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Mini Express Framework
|
||||
* https://github.com/Peng-YM/QuanX/blob/master/Tools/OpenAPI/Express.js
|
||||
*/
|
||||
function express({port} = {port: 3000}) {
|
||||
const {isNode} = ENV();
|
||||
const DEFAULT_HEADERS = {
|
||||
"Content-Type": "text/plain;charset=UTF-8",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "POST,GET,OPTIONS,PATCH,PUT,DELETE",
|
||||
"Access-Control-Allow-Headers":
|
||||
"Origin, X-Requested-With, Content-Type, Accept",
|
||||
};
|
||||
|
||||
// node support
|
||||
if (isNode) {
|
||||
const express_ = require("express");
|
||||
const bodyParser = require("body-parser");
|
||||
const app = express_();
|
||||
app.use(bodyParser.json({verify: rawBodySaver}));
|
||||
app.use(bodyParser.urlencoded({verify: rawBodySaver, extended: true}));
|
||||
app.use(bodyParser.raw({verify: rawBodySaver, type: "*/*"}));
|
||||
app.use((req, res, next) => {
|
||||
res.set(DEFAULT_HEADERS);
|
||||
next();
|
||||
});
|
||||
|
||||
// adapter
|
||||
app.start = () => {
|
||||
app.listen(port, () => {
|
||||
$.info(`Express started on port: ${port}`);
|
||||
});
|
||||
};
|
||||
return app;
|
||||
}
|
||||
|
||||
// route handlers
|
||||
const handlers = [];
|
||||
|
||||
// http methods
|
||||
const METHODS_NAMES = [
|
||||
"GET",
|
||||
"POST",
|
||||
"PUT",
|
||||
"DELETE",
|
||||
"PATCH",
|
||||
"OPTIONS",
|
||||
"HEAD'",
|
||||
"ALL",
|
||||
];
|
||||
|
||||
// dispatch url to route
|
||||
const dispatch = (request, start = 0) => {
|
||||
let {method, url, headers, body} = request;
|
||||
if (/json/i.test(headers["Content-Type"])) {
|
||||
body = JSON.parse(body);
|
||||
}
|
||||
|
||||
method = method.toUpperCase();
|
||||
const {path, query} = extractURL(url);
|
||||
|
||||
// pattern match
|
||||
let handler = null;
|
||||
let i;
|
||||
let longestMatchedPattern = 0;
|
||||
for (i = start; i < handlers.length; i++) {
|
||||
if (handlers[i].method === "ALL" || method === handlers[i].method) {
|
||||
const {pattern} = handlers[i];
|
||||
if (patternMatched(pattern, path)) {
|
||||
if (pattern.split("/").length > longestMatchedPattern) {
|
||||
handler = handlers[i];
|
||||
longestMatchedPattern = pattern.split("/").length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (handler) {
|
||||
// dispatch to next handler
|
||||
const next = () => {
|
||||
dispatch(method, url, i);
|
||||
};
|
||||
const req = {
|
||||
method,
|
||||
url,
|
||||
path,
|
||||
query,
|
||||
params: extractPathParams(handler.pattern, path),
|
||||
headers,
|
||||
body,
|
||||
};
|
||||
const res = Response();
|
||||
const cb = handler.callback;
|
||||
|
||||
const errFunc = err => {
|
||||
res.status(500).json({
|
||||
status: "failed",
|
||||
message: `Internal Server Error: ${err}`,
|
||||
});
|
||||
}
|
||||
|
||||
if (cb.constructor.name === 'AsyncFunction') {
|
||||
cb(req, res, next).catch(errFunc);
|
||||
} else {
|
||||
try {
|
||||
cb(req, res, next);
|
||||
} catch (err) {
|
||||
errFunc(err);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// no route, return 404
|
||||
const res = Response();
|
||||
res.status(404).json({
|
||||
status: "failed",
|
||||
message: "ERROR: 404 not found",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const app = {};
|
||||
|
||||
// attach http methods
|
||||
METHODS_NAMES.forEach((method) => {
|
||||
app[method.toLowerCase()] = (pattern, callback) => {
|
||||
// add handler
|
||||
handlers.push({method, pattern, callback});
|
||||
};
|
||||
});
|
||||
|
||||
// chainable route
|
||||
app.route = (pattern) => {
|
||||
const chainApp = {};
|
||||
METHODS_NAMES.forEach((method) => {
|
||||
chainApp[method.toLowerCase()] = (callback) => {
|
||||
// add handler
|
||||
handlers.push({method, pattern, callback});
|
||||
return chainApp;
|
||||
};
|
||||
});
|
||||
return chainApp;
|
||||
};
|
||||
|
||||
// start service
|
||||
app.start = () => {
|
||||
dispatch($request);
|
||||
};
|
||||
|
||||
return app;
|
||||
|
||||
/************************************************
|
||||
Utility Functions
|
||||
*************************************************/
|
||||
function rawBodySaver(req, res, buf, encoding) {
|
||||
if (buf && buf.length) {
|
||||
req.rawBody = buf.toString(encoding || "utf8");
|
||||
}
|
||||
}
|
||||
|
||||
function Response() {
|
||||
let statusCode = 200;
|
||||
const {isQX, isLoon, isSurge} = ENV();
|
||||
const headers = DEFAULT_HEADERS;
|
||||
const STATUS_CODE_MAP = {
|
||||
200: "HTTP/1.1 200 OK",
|
||||
201: "HTTP/1.1 201 Created",
|
||||
302: "HTTP/1.1 302 Found",
|
||||
307: "HTTP/1.1 307 Temporary Redirect",
|
||||
308: "HTTP/1.1 308 Permanent Redirect",
|
||||
404: "HTTP/1.1 404 Not Found",
|
||||
500: "HTTP/1.1 500 Internal Server Error",
|
||||
};
|
||||
return new (class {
|
||||
status(code) {
|
||||
statusCode = code;
|
||||
return this;
|
||||
}
|
||||
|
||||
send(body = "") {
|
||||
const response = {
|
||||
status: isQX ? STATUS_CODE_MAP[statusCode] : statusCode,
|
||||
body,
|
||||
headers,
|
||||
};
|
||||
if (isQX) {
|
||||
$done(response);
|
||||
} else if (isLoon || isSurge) {
|
||||
$done({
|
||||
response,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
end() {
|
||||
this.send();
|
||||
}
|
||||
|
||||
html(data) {
|
||||
this.set("Content-Type", "text/html;charset=UTF-8");
|
||||
this.send(data);
|
||||
}
|
||||
|
||||
json(data) {
|
||||
this.set("Content-Type", "application/json;charset=UTF-8");
|
||||
this.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
set(key, val) {
|
||||
headers[key] = val;
|
||||
return this;
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
function patternMatched(pattern, path) {
|
||||
if (pattern instanceof RegExp && pattern.test(path)) {
|
||||
return true;
|
||||
} else {
|
||||
// root pattern, match all
|
||||
if (pattern === "/") return true;
|
||||
// normal string pattern
|
||||
if (pattern.indexOf(":") === -1) {
|
||||
const spath = path.split("/");
|
||||
const spattern = pattern.split("/");
|
||||
for (let i = 0; i < spattern.length; i++) {
|
||||
if (spath[i] !== spattern[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// string pattern with path parameters
|
||||
else if (extractPathParams(pattern, path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function extractURL(url) {
|
||||
// extract path
|
||||
const match = url.match(/https?:\/\/[^\/]+(\/[^?]*)/) || [];
|
||||
const path = match[1] || "/";
|
||||
|
||||
// extract query string
|
||||
const split = url.indexOf("?");
|
||||
const query = {};
|
||||
if (split !== -1) {
|
||||
let hashes = url.slice(url.indexOf("?") + 1).split("&");
|
||||
for (let i = 0; i < hashes.length; i++) {
|
||||
hash = hashes[i].split("=");
|
||||
query[hash[0]] = hash[1];
|
||||
}
|
||||
}
|
||||
return {
|
||||
path,
|
||||
query,
|
||||
};
|
||||
}
|
||||
|
||||
function extractPathParams(pattern, path) {
|
||||
if (pattern.indexOf(":") === -1) {
|
||||
return null;
|
||||
} else {
|
||||
const params = {};
|
||||
for (let i = 0, j = 0; i < pattern.length; i++, j++) {
|
||||
if (pattern[i] === ":") {
|
||||
let key = [];
|
||||
let val = [];
|
||||
while (pattern[++i] !== "/" && i < pattern.length) {
|
||||
key.push(pattern[i]);
|
||||
}
|
||||
while (path[j] !== "/" && j < path.length) {
|
||||
val.push(path[j++]);
|
||||
}
|
||||
params[key.join("")] = val.join("");
|
||||
} else {
|
||||
if (pattern[i] !== path[j]) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/****************************************** Third Party Libraries **********************************************/
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user