t880216t-server 1.5.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +124 -0
- package/README.md +191 -0
- package/bin/nohost.js +178 -0
- package/bin/plugin.js +70 -0
- package/bin/util.js +93 -0
- package/index.js +89 -0
- package/lib/config.js +42 -0
- package/lib/index.js +205 -0
- package/lib/main/cgi/getSettings.js +11 -0
- package/lib/main/cgi/getVersion.js +12 -0
- package/lib/main/cgi/login.js +14 -0
- package/lib/main/cgi/restart.js +5 -0
- package/lib/main/cgi/setAdmin.js +6 -0
- package/lib/main/cgi/setDomain.js +13 -0
- package/lib/main/cgi/status.js +122 -0
- package/lib/main/index.js +94 -0
- package/lib/main/router.js +79 -0
- package/lib/main/storage.js +132 -0
- package/lib/main/util.js +138 -0
- package/lib/main/whistleMgr.js +56 -0
- package/lib/main/worker.js +52 -0
- package/lib/main/workerNum.js +11 -0
- package/lib/plugins/account/whistle.share/menu.html +171 -0
- package/lib/plugins/account/whistle.share/package.json +20 -0
- package/lib/plugins/account/whistle.storage/index.js +43 -0
- package/lib/plugins/account/whistle.storage/package.json +8 -0
- package/lib/plugins/account/whistle.storage/rules.txt +1 -0
- package/lib/plugins/whistle.nohost/_rules.txt +38 -0
- package/lib/plugins/whistle.nohost/index.js +3 -0
- package/lib/plugins/whistle.nohost/initial.js +9 -0
- package/lib/plugins/whistle.nohost/lib/accountMgr.js +750 -0
- package/lib/plugins/whistle.nohost/lib/envMgr.js +114 -0
- package/lib/plugins/whistle.nohost/lib/rulesServer.js +85 -0
- package/lib/plugins/whistle.nohost/lib/tunnelRulesServer.js +71 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/account/changePassword.js +8 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/activeAccount.js +9 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/addAccount.js +8 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/allAccounts.js +9 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/changeNotice.js +8 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/changePassword.js +8 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/enableGuest.js +5 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/getAuthKey.js +3 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/getSettings.js +22 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/login.js +14 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/move.js +9 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/removeAccount.js +9 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setAccountRules.js +5 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setAuthKey.js +5 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setDefaultRules.js +5 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setEntryPatterns.js +6 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setJsonData.js +5 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setRulesTpl.js +5 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setTestRules.js +5 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/specPattern.js +4 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/allowlist.js +4 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/entryRules.js +4 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/follow.js +17 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/getEnv.js +12 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/list.js +43 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/patterns.js +14 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/pluginRules.js +61 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/proxy.js +42 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/proxyNetwork.js +12 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/redirect.js +10 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/selectEnv.js +30 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/cgi/unfollow.js +4 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/index.js +60 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/network.js +38 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/openAPI.js +94 -0
- package/lib/plugins/whistle.nohost/lib/uiServer/router.js +85 -0
- package/lib/plugins/whistle.nohost/lib/util.js +230 -0
- package/lib/plugins/whistle.nohost/lib/whistle.js +149 -0
- package/lib/plugins/whistle.nohost/lib/whistleMgr.js +94 -0
- package/lib/plugins/whistle.nohost/package.json +7 -0
- package/lib/plugins/whistle.nohost/rules.txt +3 -0
- package/lib/service/cgi/export.js +76 -0
- package/lib/service/cgi/import.js +33 -0
- package/lib/service/cgi/util.js +35 -0
- package/lib/service/index.js +37 -0
- package/lib/service/router.js +7 -0
- package/lib/service/server.js +25 -0
- package/lib/util/address.js +55 -0
- package/lib/util/getPort.js +19 -0
- package/lib/util/login.js +55 -0
- package/lib/util/parseDomain.js +17 -0
- package/lib/whistle.js +86 -0
- package/package.json +135 -0
- package/pnpm-workspace.yaml +6 -0
- package/public/admin.75d42731d4aa7f7a558d3abf2a375fcb.js +2 -0
- package/public/admin.75d42731d4aa7f7a558d3abf2a375fcb.js.LICENSE.txt +76 -0
- package/public/admin.html +11 -0
- package/public/button.js +2 -0
- package/public/button.js.LICENSE.txt +31 -0
- package/public/capture.7bab900f27c9bb1b0e33523e994554ae.js +2 -0
- package/public/capture.7bab900f27c9bb1b0e33523e994554ae.js.LICENSE.txt +83 -0
- package/public/capture.html +11 -0
- package/public/eed368d0656f03932671530c3132ed61.svg +1 -0
- package/public/favicon.ico +0 -0
- package/public/network.e4814ec8c966a8a789296679619ec573.js +2 -0
- package/public/network.e4814ec8c966a8a789296679619ec573.js.LICENSE.txt +76 -0
- package/public/network.html +11 -0
- package/public/select.cb638be6656d02a558f2690acd59901a.js +2 -0
- package/public/select.cb638be6656d02a558f2690acd59901a.js.LICENSE.txt +24 -0
- package/public/select.html +26 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
const LRU = require('lru-cache');
|
|
2
|
+
const {
|
|
3
|
+
COOKIE_NAME,
|
|
4
|
+
WHISTLE_ENV_HEADER,
|
|
5
|
+
decodeURIComponentSafe,
|
|
6
|
+
getClientId,
|
|
7
|
+
isFromComposer,
|
|
8
|
+
} = require('./util');
|
|
9
|
+
|
|
10
|
+
const followers = new LRU({ max: 10000, maxAge: 1000 * 60 * 30 });
|
|
11
|
+
const cache = new LRU({ max: 10000 });
|
|
12
|
+
let accountMgr;
|
|
13
|
+
|
|
14
|
+
class EnvMgr {
|
|
15
|
+
checkEnvName({ envList }, envName) {
|
|
16
|
+
if (!envName) {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
return envList.some(({ name }) => name === envName) ? envName : '';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
setFollower(followId, ctx) {
|
|
23
|
+
const clientId = getClientId(ctx);
|
|
24
|
+
if (clientId !== followId) {
|
|
25
|
+
followers.set(followId, clientId);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
unfollow(ctx) {
|
|
30
|
+
followers.del(getClientId(ctx));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getFollower(ctx) {
|
|
34
|
+
return followers.get(getClientId(ctx));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
setEnv(ip, name, envName) {
|
|
38
|
+
const account = accountMgr.getAccount(name);
|
|
39
|
+
if (!account) {
|
|
40
|
+
cache.set(ip, '');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
envName = this.checkEnvName(account, envName);
|
|
44
|
+
const env = { name, envName };
|
|
45
|
+
cache.set(ip, env);
|
|
46
|
+
return env;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getEnvFromCookie(ctx, withAccount) {
|
|
50
|
+
let env = decodeURIComponentSafe(ctx.cookies.get(COOKIE_NAME));
|
|
51
|
+
if (!env) {
|
|
52
|
+
return '';
|
|
53
|
+
}
|
|
54
|
+
const index = env.indexOf('/');
|
|
55
|
+
let name = env;
|
|
56
|
+
let envName;
|
|
57
|
+
if (index !== -1) {
|
|
58
|
+
name = env.substring(0, index);
|
|
59
|
+
envName = env.substring(index + 1);
|
|
60
|
+
}
|
|
61
|
+
const account = accountMgr.getAccount(name);
|
|
62
|
+
if (!account) {
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
envName = this.checkEnvName(account, envName);
|
|
66
|
+
env = { name, envName };
|
|
67
|
+
return withAccount ? { account, env } : { name, envName };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getEnv(ctx) {
|
|
71
|
+
let env = cache.get(getClientId(ctx));
|
|
72
|
+
if (env != null) {
|
|
73
|
+
if (env) {
|
|
74
|
+
const { name, envName } = env;
|
|
75
|
+
const account = accountMgr.getAccount(name);
|
|
76
|
+
if (!account || !this.checkEnvName(account, envName)) {
|
|
77
|
+
env.envName = '';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return env;
|
|
81
|
+
}
|
|
82
|
+
env = this.getEnvFromCookie(ctx);
|
|
83
|
+
cache.set(getClientId(ctx), env);
|
|
84
|
+
return env;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getEnvOnly(ctx) {
|
|
88
|
+
return cache.get(getClientId(ctx));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getEnvByHeader(ctx) {
|
|
92
|
+
let name = decodeURIComponentSafe(ctx.get(WHISTLE_ENV_HEADER));
|
|
93
|
+
if (!name) {
|
|
94
|
+
return (isFromComposer(ctx) && this.getEnvFromCookie(ctx, true)) || '';
|
|
95
|
+
}
|
|
96
|
+
const index = name.indexOf('/');
|
|
97
|
+
let envName = '';
|
|
98
|
+
if (index !== -1) {
|
|
99
|
+
envName = name.substring(index + 1);
|
|
100
|
+
name = name.substring(0, index);
|
|
101
|
+
}
|
|
102
|
+
const account = accountMgr.getAccount(name);
|
|
103
|
+
if (!account) {
|
|
104
|
+
return '';
|
|
105
|
+
}
|
|
106
|
+
envName = this.checkEnvName(account, envName);
|
|
107
|
+
return { account, env: { name, envName } };
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = (mgr) => {
|
|
112
|
+
accountMgr = mgr;
|
|
113
|
+
module.exports = new EnvMgr();
|
|
114
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const Koa = require('koa');
|
|
2
|
+
const accountMgr = require('./accountMgr');
|
|
3
|
+
const envMgr = require('./envMgr');
|
|
4
|
+
const whistleMgr = require('./whistleMgr');
|
|
5
|
+
const {
|
|
6
|
+
COOKIE_NAME,
|
|
7
|
+
ENV_MAX_AGE,
|
|
8
|
+
WHISTLE_ENV_HEADER,
|
|
9
|
+
WHISTLE_RULE_VALUE,
|
|
10
|
+
getRuleValue,
|
|
11
|
+
getDomain,
|
|
12
|
+
} = require('./util');
|
|
13
|
+
|
|
14
|
+
const getCookie = (value, maxAge, hostname) => {
|
|
15
|
+
return {
|
|
16
|
+
whistleEnvCookie: {
|
|
17
|
+
[COOKIE_NAME]: {
|
|
18
|
+
value,
|
|
19
|
+
maxAge,
|
|
20
|
+
domain: getDomain(hostname),
|
|
21
|
+
path: '/',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
module.exports = (server) => {
|
|
28
|
+
const app = new Koa();
|
|
29
|
+
app.proxy = true;
|
|
30
|
+
app.silent = true;
|
|
31
|
+
app.use(async (ctx) => {
|
|
32
|
+
let { account, env } = envMgr.getEnvByHeader(ctx);
|
|
33
|
+
if (!account && !ctx.get(WHISTLE_ENV_HEADER)) {
|
|
34
|
+
env = envMgr.getEnv(ctx);
|
|
35
|
+
account = env && accountMgr.getAccount(env.name);
|
|
36
|
+
}
|
|
37
|
+
const hostname = (ctx.get('host') || '').split(':')[0];
|
|
38
|
+
const noInject = getRuleValue(ctx) === 'none' || ctx.get('x-whistle-nohost-hide');
|
|
39
|
+
const injectRule = `htmlPrepend://{whistle.nohost/${noInject ? 'none' : 'inject'}.html} enable://safeHtml`;
|
|
40
|
+
if (!account) {
|
|
41
|
+
ctx.body = {
|
|
42
|
+
rules: `* resCookies://{whistleEnvCookie} ${injectRule}`,
|
|
43
|
+
values: getCookie('', -ENV_MAX_AGE, hostname),
|
|
44
|
+
};
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const name = account && account.name;
|
|
48
|
+
let envHeader = '';
|
|
49
|
+
let envKey = `${name}/`;
|
|
50
|
+
const { envName } = env;
|
|
51
|
+
const reqHeaders = {};
|
|
52
|
+
|
|
53
|
+
if (envName) {
|
|
54
|
+
envKey += envName;
|
|
55
|
+
let { rules, headers } = accountMgr.getRules(account.name, envName);
|
|
56
|
+
if (headers) {
|
|
57
|
+
headers = JSON.stringify(headers);
|
|
58
|
+
rules += `\n* reqHeaders://(${headers})`;
|
|
59
|
+
}
|
|
60
|
+
if (rules) {
|
|
61
|
+
reqHeaders[WHISTLE_RULE_VALUE] = encodeURIComponent(rules);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
reqHeaders[WHISTLE_ENV_HEADER] = encodeURIComponent(envKey);
|
|
65
|
+
const {
|
|
66
|
+
port,
|
|
67
|
+
remoteAddrHead,
|
|
68
|
+
remotePortHead,
|
|
69
|
+
} = await whistleMgr.fork(account);
|
|
70
|
+
const {
|
|
71
|
+
remoteAddress,
|
|
72
|
+
remotePort,
|
|
73
|
+
} = ctx.req.originalReq;
|
|
74
|
+
reqHeaders[remoteAddrHead] = remoteAddress;
|
|
75
|
+
reqHeaders[remotePortHead] = remotePort;
|
|
76
|
+
envHeader = `reqHeaders://(${JSON.stringify(reqHeaders)})`;
|
|
77
|
+
const proxyUrl = `internal-proxy://127.0.0.1:${port}`;
|
|
78
|
+
const cookie = getCookie(envKey, ENV_MAX_AGE, hostname);
|
|
79
|
+
ctx.body = {
|
|
80
|
+
rules: `* ${proxyUrl} ${envHeader} ${injectRule} ${cookie ? 'resCookies://{whistleEnvCookie}' : ''}`,
|
|
81
|
+
values: cookie,
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
server.on('request', app.callback());
|
|
85
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const Koa = require('koa');
|
|
2
|
+
const accountMgr = require('./accountMgr');
|
|
3
|
+
const envMgr = require('./envMgr');
|
|
4
|
+
const whistleMgr = require('./whistleMgr');
|
|
5
|
+
const {
|
|
6
|
+
WHISTLE_ENV_HEADER,
|
|
7
|
+
WHISTLE_RULE_VALUE,
|
|
8
|
+
} = require('./util');
|
|
9
|
+
|
|
10
|
+
const SEP_RE = /\s+/;
|
|
11
|
+
const CRLF_RE = /\s*[\r\n]+\s*/;
|
|
12
|
+
const CAPTURE_RE = /^(?:[^\r\n]*\s)?enable:\/\/(?:capture|https|intercept)(?:\s|$)/m;
|
|
13
|
+
|
|
14
|
+
module.exports = (server) => {
|
|
15
|
+
const app = new Koa();
|
|
16
|
+
app.proxy = true;
|
|
17
|
+
app.silent = true;
|
|
18
|
+
app.use(async (ctx) => {
|
|
19
|
+
let { account, env } = envMgr.getEnvByHeader(ctx);
|
|
20
|
+
if (!account) {
|
|
21
|
+
env = envMgr.getEnvOnly(ctx);
|
|
22
|
+
account = env && accountMgr.getAccount(env.name);
|
|
23
|
+
}
|
|
24
|
+
let proxyUrl = '';
|
|
25
|
+
if (account) {
|
|
26
|
+
const headers = {};
|
|
27
|
+
let envValue = `${account.name}/`;
|
|
28
|
+
if (env) {
|
|
29
|
+
const { envName } = env;
|
|
30
|
+
if (envName) {
|
|
31
|
+
envValue = `${envValue}${envName}`;
|
|
32
|
+
const { rules } = accountMgr.getRules(account.name, envName);
|
|
33
|
+
if (rules) {
|
|
34
|
+
// 设置了拦截https请求,则所有该环境的请求都开启
|
|
35
|
+
if (CAPTURE_RE.test(rules)) {
|
|
36
|
+
const capRules = [];
|
|
37
|
+
rules.trim().split(CRLF_RE).forEach((line) => {
|
|
38
|
+
if (CAPTURE_RE.test(line)) {
|
|
39
|
+
line.trim().split(SEP_RE).forEach(p => {
|
|
40
|
+
if (p) {
|
|
41
|
+
capRules.push(`enable://capture ${p}`);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
ctx.body = capRules.join('\n');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
headers[WHISTLE_RULE_VALUE] = encodeURIComponent(rules);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
headers[WHISTLE_ENV_HEADER] = encodeURIComponent(envValue);
|
|
54
|
+
const {
|
|
55
|
+
port,
|
|
56
|
+
remoteAddrHead,
|
|
57
|
+
remotePortHead,
|
|
58
|
+
} = await whistleMgr.fork(account);
|
|
59
|
+
const {
|
|
60
|
+
remoteAddress,
|
|
61
|
+
remotePort,
|
|
62
|
+
} = ctx.req.originalReq;
|
|
63
|
+
headers[remoteAddrHead] = remoteAddress;
|
|
64
|
+
headers[remotePortHead] = remotePort;
|
|
65
|
+
const envHeader = `reqHeaders://(${JSON.stringify(headers)})`;
|
|
66
|
+
proxyUrl = `internal-proxy://127.0.0.1:${port}`;
|
|
67
|
+
ctx.body = `* ${proxyUrl} ${envHeader}`;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
server.on('request', app.callback());
|
|
71
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module.exports = (ctx) => {
|
|
2
|
+
const {
|
|
3
|
+
jsonDataStr,
|
|
4
|
+
rulesTpl,
|
|
5
|
+
defaultRules,
|
|
6
|
+
accountRules,
|
|
7
|
+
testRules,
|
|
8
|
+
entryPatterns,
|
|
9
|
+
specPattern,
|
|
10
|
+
} = ctx.accountMgr;
|
|
11
|
+
ctx.body = {
|
|
12
|
+
ec: 0,
|
|
13
|
+
jsonData: jsonDataStr,
|
|
14
|
+
authKey: ctx.accountMgr.getAuthKey(),
|
|
15
|
+
rulesTpl,
|
|
16
|
+
defaultRules,
|
|
17
|
+
accountRules,
|
|
18
|
+
testRules,
|
|
19
|
+
entryPatterns,
|
|
20
|
+
specPattern,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const { checkLogin } = require('../../../../../../util/login');
|
|
2
|
+
|
|
3
|
+
module.exports = async (ctx, next) => {
|
|
4
|
+
const { username, password } = ctx.admin;
|
|
5
|
+
const accept = checkLogin(ctx, {
|
|
6
|
+
username,
|
|
7
|
+
password,
|
|
8
|
+
nameKey: 'nohost_admin_name',
|
|
9
|
+
authKey: 'nohost_admin_key',
|
|
10
|
+
});
|
|
11
|
+
if (accept) {
|
|
12
|
+
await next();
|
|
13
|
+
}
|
|
14
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
const { getClientId, isClientId } = require('../../util');
|
|
3
|
+
|
|
4
|
+
module.exports = (ctx) => {
|
|
5
|
+
const { followId } = ctx.request.query;
|
|
6
|
+
if (isClientId(followId)) {
|
|
7
|
+
ctx.envMgr.setFollower(followId, ctx);
|
|
8
|
+
ctx.type = 'html';
|
|
9
|
+
ctx.body = '<p style="text-align: center; padding: 20px 0;">设置成功,<a href="./">点击调整到选择环境页面</a></p>';
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
ctx.body = {
|
|
13
|
+
ec: 0,
|
|
14
|
+
clientId: getClientId(ctx),
|
|
15
|
+
followerIp: ctx.envMgr.getFollower(ctx),
|
|
16
|
+
};
|
|
17
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const Limiter = require('async-limiter');
|
|
2
|
+
const { gzip } = require('zlib');
|
|
3
|
+
const LRU = require('lru-cache');
|
|
4
|
+
|
|
5
|
+
const limiter = new Limiter({ concurrency: 10 });
|
|
6
|
+
const cache = new LRU({ max: 2 });
|
|
7
|
+
|
|
8
|
+
const compress = (body) => {
|
|
9
|
+
let promise = cache.get(body);
|
|
10
|
+
if (!promise) {
|
|
11
|
+
promise = new Promise((resolve, reject) => {
|
|
12
|
+
limiter.push((done) => {
|
|
13
|
+
gzip(body, (err, buf) => {
|
|
14
|
+
done();
|
|
15
|
+
if (err) {
|
|
16
|
+
delete cache[body];
|
|
17
|
+
reject(err);
|
|
18
|
+
} else {
|
|
19
|
+
resolve(buf);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
cache.set(body, promise);
|
|
25
|
+
}
|
|
26
|
+
return promise;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
module.exports = async (ctx) => {
|
|
30
|
+
const curEnv = ctx.envMgr.getEnv(ctx);
|
|
31
|
+
const list = ctx.accountMgr.getAccountList(ctx.request.query.parsed);
|
|
32
|
+
let body = JSON.stringify({
|
|
33
|
+
admin: ctx.admin,
|
|
34
|
+
ec: 0,
|
|
35
|
+
baseUrl: ctx.baseUrl,
|
|
36
|
+
curEnv,
|
|
37
|
+
list,
|
|
38
|
+
});
|
|
39
|
+
body = await compress(body);
|
|
40
|
+
ctx.set('content-encoding', 'gzip');
|
|
41
|
+
ctx.set('content-type', 'application/json');
|
|
42
|
+
ctx.body = body;
|
|
43
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const resolveValue = (text) => {
|
|
5
|
+
return text.substring(text.indexOf('```'));
|
|
6
|
+
};
|
|
7
|
+
const INJECT_HTML = resolveValue(fs.readFileSync(path.join(__dirname, '../../../_rules.txt'), { encoding: 'utf8' })); // eslint-disable-line
|
|
8
|
+
const INJECT_RULE = 'htmlPrepend://{whistle.nohost/inject.html}';
|
|
9
|
+
const WHISTLE_HOST = 'local.whistlejs.com';
|
|
10
|
+
let curBaseUrl;
|
|
11
|
+
let curPatterns;
|
|
12
|
+
let curRules;
|
|
13
|
+
|
|
14
|
+
module.exports = (ctx) => {
|
|
15
|
+
const {
|
|
16
|
+
patterns,
|
|
17
|
+
getBaseUrl,
|
|
18
|
+
getDomainList,
|
|
19
|
+
preRules,
|
|
20
|
+
postRules,
|
|
21
|
+
} = ctx.accountMgr;
|
|
22
|
+
const curRuntimeId = ctx.get('x-whistle-runtime-id');
|
|
23
|
+
const isSelf = curRuntimeId === ctx.runtimeId;
|
|
24
|
+
const baseUrl = !isSelf && getBaseUrl();
|
|
25
|
+
const domainList = getDomainList().concat([WHISTLE_HOST]);
|
|
26
|
+
if (baseUrl) {
|
|
27
|
+
if (curBaseUrl !== baseUrl || curPatterns !== patterns) {
|
|
28
|
+
curBaseUrl = baseUrl;
|
|
29
|
+
curPatterns = patterns;
|
|
30
|
+
let ignorePatterns = [];
|
|
31
|
+
const injectRule = [`internal-proxy://${baseUrl} ${INJECT_RULE} enable://clientId|multiClient|safeHtml`];
|
|
32
|
+
const normalRule = [`internal-proxy://${baseUrl} enable://clientId|multiClient`];
|
|
33
|
+
curRules = [];
|
|
34
|
+
patterns.forEach((item) => {
|
|
35
|
+
if (item.ignore) {
|
|
36
|
+
ignorePatterns.push(`excludeFilter://${item.pattern}`);
|
|
37
|
+
} else if (item.button) {
|
|
38
|
+
injectRule.push(item.pattern);
|
|
39
|
+
} else {
|
|
40
|
+
normalRule.push(item.pattern);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
ignorePatterns = ignorePatterns.join(' ');
|
|
44
|
+
if (normalRule.length > 1) {
|
|
45
|
+
normalRule.push(ignorePatterns);
|
|
46
|
+
curRules.push(normalRule.join(' '));
|
|
47
|
+
}
|
|
48
|
+
if (injectRule.length > 1) {
|
|
49
|
+
injectRule.push(ignorePatterns);
|
|
50
|
+
curRules.push(injectRule.join(' '));
|
|
51
|
+
}
|
|
52
|
+
curRules.unshift(`internal-proxy://${baseUrl} ignore://rule|html enable://hide|proxyHost|clientId|multiClient ${domainList.join(' ')}`);
|
|
53
|
+
curRules.push(`*/.whistle-path.5b6af7b9884e1165/ ignore://-proxy|-host reqHeaders://whistleInternalHost=${WHISTLE_HOST} enable://proxyTunnel`);
|
|
54
|
+
curRules.push('\n', INJECT_HTML);
|
|
55
|
+
curRules = preRules.concat(curRules).concat(postRules).join('\n');
|
|
56
|
+
}
|
|
57
|
+
ctx.body = curRules;
|
|
58
|
+
} else {
|
|
59
|
+
ctx.body = '';
|
|
60
|
+
}
|
|
61
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const LRU = require('lru-cache');
|
|
2
|
+
const { transformWhistle, changeFilter } = require('../../util');
|
|
3
|
+
const whistleMgr = require('../../whistleMgr');
|
|
4
|
+
|
|
5
|
+
const tempCache = new LRU({ maxAge: 5000 });
|
|
6
|
+
|
|
7
|
+
module.exports = async (ctx, next) => {
|
|
8
|
+
let { params: { name }, account } = ctx;
|
|
9
|
+
if (!account) {
|
|
10
|
+
account = ctx.accountMgr.getAccount(name);
|
|
11
|
+
if (!account) {
|
|
12
|
+
await next();
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
name = account.name;
|
|
17
|
+
}
|
|
18
|
+
const { req } = ctx;
|
|
19
|
+
req.url = req.url.substring(`/account/${name}`.length);
|
|
20
|
+
changeFilter(req, ctx.envMgr.getFollower(ctx));
|
|
21
|
+
req.headers.host = 'local.wproxy.org';
|
|
22
|
+
req.headers['x-forwarded-for'] = ctx.ip || '127.0.0.1';
|
|
23
|
+
const {
|
|
24
|
+
port,
|
|
25
|
+
remoteAddrHead,
|
|
26
|
+
remotePortHead,
|
|
27
|
+
} = await whistleMgr.fork(account);
|
|
28
|
+
const remoteAddr = ctx.get('x-whistle-remote-address');
|
|
29
|
+
if (remoteAddr) {
|
|
30
|
+
req.headers[remoteAddrHead] = remoteAddr;
|
|
31
|
+
req.headers[remotePortHead] = ctx.get('x-whistle-remote-port');
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
await transformWhistle(ctx, port);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err.code === 'ECONNREFUSED' && !tempCache.get(name)) {
|
|
37
|
+
tempCache.set(name, 1);
|
|
38
|
+
whistleMgr.kill(name);
|
|
39
|
+
}
|
|
40
|
+
throw err;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const { transformWhistle, changeFilter } = require('../../util');
|
|
2
|
+
const initNetwork = require('../network');
|
|
3
|
+
|
|
4
|
+
module.exports = async (ctx) => {
|
|
5
|
+
const { req } = ctx;
|
|
6
|
+
req.url = req.url.replace('/network', '');
|
|
7
|
+
req.headers.host = 'local.wproxy.org';
|
|
8
|
+
req.headers['x-forwarded-for'] = ctx.ip || '127.0.0.1';
|
|
9
|
+
const port = await initNetwork();
|
|
10
|
+
changeFilter(req);
|
|
11
|
+
await transformWhistle(ctx, port);
|
|
12
|
+
};
|