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.
Files changed (104) hide show
  1. package/LICENSE +124 -0
  2. package/README.md +191 -0
  3. package/bin/nohost.js +178 -0
  4. package/bin/plugin.js +70 -0
  5. package/bin/util.js +93 -0
  6. package/index.js +89 -0
  7. package/lib/config.js +42 -0
  8. package/lib/index.js +205 -0
  9. package/lib/main/cgi/getSettings.js +11 -0
  10. package/lib/main/cgi/getVersion.js +12 -0
  11. package/lib/main/cgi/login.js +14 -0
  12. package/lib/main/cgi/restart.js +5 -0
  13. package/lib/main/cgi/setAdmin.js +6 -0
  14. package/lib/main/cgi/setDomain.js +13 -0
  15. package/lib/main/cgi/status.js +122 -0
  16. package/lib/main/index.js +94 -0
  17. package/lib/main/router.js +79 -0
  18. package/lib/main/storage.js +132 -0
  19. package/lib/main/util.js +138 -0
  20. package/lib/main/whistleMgr.js +56 -0
  21. package/lib/main/worker.js +52 -0
  22. package/lib/main/workerNum.js +11 -0
  23. package/lib/plugins/account/whistle.share/menu.html +171 -0
  24. package/lib/plugins/account/whistle.share/package.json +20 -0
  25. package/lib/plugins/account/whistle.storage/index.js +43 -0
  26. package/lib/plugins/account/whistle.storage/package.json +8 -0
  27. package/lib/plugins/account/whistle.storage/rules.txt +1 -0
  28. package/lib/plugins/whistle.nohost/_rules.txt +38 -0
  29. package/lib/plugins/whistle.nohost/index.js +3 -0
  30. package/lib/plugins/whistle.nohost/initial.js +9 -0
  31. package/lib/plugins/whistle.nohost/lib/accountMgr.js +750 -0
  32. package/lib/plugins/whistle.nohost/lib/envMgr.js +114 -0
  33. package/lib/plugins/whistle.nohost/lib/rulesServer.js +85 -0
  34. package/lib/plugins/whistle.nohost/lib/tunnelRulesServer.js +71 -0
  35. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/account/changePassword.js +8 -0
  36. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/activeAccount.js +9 -0
  37. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/addAccount.js +8 -0
  38. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/allAccounts.js +9 -0
  39. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/changeNotice.js +8 -0
  40. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/changePassword.js +8 -0
  41. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/enableGuest.js +5 -0
  42. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/getAuthKey.js +3 -0
  43. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/getSettings.js +22 -0
  44. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/login.js +14 -0
  45. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/move.js +9 -0
  46. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/removeAccount.js +9 -0
  47. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setAccountRules.js +5 -0
  48. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setAuthKey.js +5 -0
  49. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setDefaultRules.js +5 -0
  50. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setEntryPatterns.js +6 -0
  51. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setJsonData.js +5 -0
  52. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setRulesTpl.js +5 -0
  53. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/setTestRules.js +5 -0
  54. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/admin/specPattern.js +4 -0
  55. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/allowlist.js +4 -0
  56. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/entryRules.js +4 -0
  57. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/follow.js +17 -0
  58. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/getEnv.js +12 -0
  59. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/list.js +43 -0
  60. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/patterns.js +14 -0
  61. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/pluginRules.js +61 -0
  62. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/proxy.js +42 -0
  63. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/proxyNetwork.js +12 -0
  64. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/redirect.js +10 -0
  65. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/selectEnv.js +30 -0
  66. package/lib/plugins/whistle.nohost/lib/uiServer/cgi/unfollow.js +4 -0
  67. package/lib/plugins/whistle.nohost/lib/uiServer/index.js +60 -0
  68. package/lib/plugins/whistle.nohost/lib/uiServer/network.js +38 -0
  69. package/lib/plugins/whistle.nohost/lib/uiServer/openAPI.js +94 -0
  70. package/lib/plugins/whistle.nohost/lib/uiServer/router.js +85 -0
  71. package/lib/plugins/whistle.nohost/lib/util.js +230 -0
  72. package/lib/plugins/whistle.nohost/lib/whistle.js +149 -0
  73. package/lib/plugins/whistle.nohost/lib/whistleMgr.js +94 -0
  74. package/lib/plugins/whistle.nohost/package.json +7 -0
  75. package/lib/plugins/whistle.nohost/rules.txt +3 -0
  76. package/lib/service/cgi/export.js +76 -0
  77. package/lib/service/cgi/import.js +33 -0
  78. package/lib/service/cgi/util.js +35 -0
  79. package/lib/service/index.js +37 -0
  80. package/lib/service/router.js +7 -0
  81. package/lib/service/server.js +25 -0
  82. package/lib/util/address.js +55 -0
  83. package/lib/util/getPort.js +19 -0
  84. package/lib/util/login.js +55 -0
  85. package/lib/util/parseDomain.js +17 -0
  86. package/lib/whistle.js +86 -0
  87. package/package.json +135 -0
  88. package/pnpm-workspace.yaml +6 -0
  89. package/public/admin.75d42731d4aa7f7a558d3abf2a375fcb.js +2 -0
  90. package/public/admin.75d42731d4aa7f7a558d3abf2a375fcb.js.LICENSE.txt +76 -0
  91. package/public/admin.html +11 -0
  92. package/public/button.js +2 -0
  93. package/public/button.js.LICENSE.txt +31 -0
  94. package/public/capture.7bab900f27c9bb1b0e33523e994554ae.js +2 -0
  95. package/public/capture.7bab900f27c9bb1b0e33523e994554ae.js.LICENSE.txt +83 -0
  96. package/public/capture.html +11 -0
  97. package/public/eed368d0656f03932671530c3132ed61.svg +1 -0
  98. package/public/favicon.ico +0 -0
  99. package/public/network.e4814ec8c966a8a789296679619ec573.js +2 -0
  100. package/public/network.e4814ec8c966a8a789296679619ec573.js.LICENSE.txt +76 -0
  101. package/public/network.html +11 -0
  102. package/public/select.cb638be6656d02a558f2690acd59901a.js +2 -0
  103. package/public/select.cb638be6656d02a558f2690acd59901a.js.LICENSE.txt +24 -0
  104. package/public/select.html +26 -0
@@ -0,0 +1,79 @@
1
+ const bodyParser = require('koa-bodyparser');
2
+ const { passToWhistle, passToService } = require('./util');
3
+ const login = require('./cgi/login');
4
+ const restart = require('./cgi/restart');
5
+ const getSettings = require('./cgi/getSettings');
6
+ const setAdmin = require('./cgi/setAdmin');
7
+ const setDomain = require('./cgi/setDomain');
8
+ const getVersion = require('./cgi/getVersion');
9
+ const status = require('./cgi/status');
10
+ const config = require('../config');
11
+
12
+ const PUB_KEY_RE = /^[\w.-]+\.pub\.[a-z\d]+$/i;
13
+
14
+ const passDirect = async (ctx) => {
15
+ await passToWhistle(ctx);
16
+ };
17
+ const getPassHandler = (cgiPath) => {
18
+ return async (ctx) => {
19
+ const { req } = ctx;
20
+ req.url = req.url.replace(cgiPath, `/plugin.nohost${cgiPath}`);
21
+ await passToWhistle(ctx);
22
+ };
23
+ };
24
+
25
+ const handleValues = (ctx) => {
26
+ if (PUB_KEY_RE.test(ctx.query.name)) {
27
+ ctx.headers['x-whistle-auth-key'] = config.mainAuthKey;
28
+ }
29
+ return passDirect(ctx);
30
+ };
31
+
32
+ module.exports = (router) => {
33
+ router.get('/cgi-bin/get-custom-certs-info', passDirect);
34
+ router.post('/cgi-bin/certs/remove', passDirect);
35
+ router.post('/cgi-bin/certs/upload', passDirect);
36
+ router.get('/cgi-bin/rootca', passDirect);
37
+ router.get('/rules', passDirect);
38
+ router.get('/values', handleValues);
39
+ router.all('/cgi-bin/sessions/export', passToService);
40
+ router.all('/cgi-bin/sessions/import', passToService);
41
+ router.get('/status', status);
42
+ router.all('/cgi-bin/(.*)', getPassHandler('/cgi-bin/'));
43
+ router.all('/network/(.*)', getPassHandler('/network/'));
44
+ router.all(/^\/account\/\$(\d+)\//, async (ctx) => {
45
+ const index = ctx.params[0];
46
+ ctx.url = ctx.url.replace(`/account/$${index}/`, '/');
47
+ await passToWhistle(ctx, null, index);
48
+ });
49
+ router.all('/account/(.*)', getPassHandler('/account/'));
50
+ router.all('/user/:name', (ctx) => {
51
+ const name = ctx.params.name.replace(/\..*$/, '');
52
+ ctx.redirect(`${ctx.path.slice(-1) === '/' ? '../' : ''}../account/${name}/`);
53
+ });
54
+ router.all('/open-api/(.*)', getPassHandler('/open-api/'));
55
+ router.all('/follow', getPassHandler('/follow'));
56
+ router.all('/unfollow', getPassHandler('/unfollow'));
57
+ router.all('/redirect', getPassHandler('/redirect'));
58
+
59
+ router.all('/p/:name/(.*)', async (ctx) => {
60
+ let { name } = ctx.params;
61
+ const segPath = `/p/${name}`;
62
+ name = name.indexOf('.') === -1 ? `/plugin.${name}` : name;
63
+ ctx.req.url = ctx.req.url.replace(segPath, name);
64
+ await passToWhistle(ctx);
65
+ });
66
+ router.all('/p/:name', (ctx) => {
67
+ ctx.redirect(ctx.req.url.replace(/(\?|$)/, '/$1'));
68
+ });
69
+ router.all('/whistle/(.*)', passDirect);
70
+ router.all('/whistle.(.*)', passDirect);
71
+ router.all('/plugin.(.*)', passDirect);
72
+ router.get('/get-version', getVersion);
73
+ router.all('/admin.html', login);
74
+ router.all('/main/cgi-bin/(.*)', login, bodyParser({ formLimit: '1mb' }));
75
+ router.post('/main/cgi-bin/restart', restart);
76
+ router.get('/main/cgi-bin/get-settings', getSettings);
77
+ router.post('/main/cgi-bin/set-admin', setAdmin);
78
+ router.post('/main/cgi-bin/set-domain', setDomain);
79
+ };
@@ -0,0 +1,132 @@
1
+ const Storage = require('whistle/lib/rules/storage');
2
+ const path = require('path');
3
+ const net = require('net');
4
+ const { parse } = require('url');
5
+ const https = require('https');
6
+ const { isLocalAddress } = require('../util/address');
7
+ const { shasum } = require('../util/login');
8
+ const config = require('../config');
9
+ const parseDomain = require('../util/parseDomain');
10
+ const { registry } = require('../../package.json');
11
+
12
+ const registryOpts = parse(registry);
13
+ const storage = new Storage(path.join(process.env.WHISTLE_PATH, '.nohost'));
14
+ const HOST_RE = /^https?:\/\/([^/]+)/;
15
+ const TUNNEL_PATH_RE = /^([^/]+)$/;
16
+ const INNER_PATH_RE = /^(?:\w+:\/\/[^/]+)?\/\.nohost-inner-path\.\//;
17
+ const MAX_DOMAIN_LEN = 128;
18
+ const DEFAULT_PASSWORD = shasum('123456');
19
+ const ILLEGAL_DOMAIN_RE = /[^\w.,\s-]/;
20
+ const noop = () => {};
21
+
22
+ const checkDomain = (domain) => {
23
+ return typeof domain === 'string' && !ILLEGAL_DOMAIN_RE.test(domain) && domain.length < MAX_DOMAIN_LEN;
24
+ };
25
+
26
+ exports.checkDomain = checkDomain;
27
+
28
+ registryOpts.rejectUnauthorized = false;
29
+ const updateVersion = () => {
30
+ const client = https.get(registryOpts, (res) => {
31
+ res.on('error', noop);
32
+ if (res.statusCode !== 200) {
33
+ return;
34
+ }
35
+ let body;
36
+ res.on('data', (chunk) => {
37
+ body = body ? Buffer.concat([body, chunk]) : chunk;
38
+ });
39
+ res.on('end', () => {
40
+ body = body && `${body}`;
41
+ try {
42
+ const ver = body && JSON.parse(body)['dist-tags'].latest;
43
+ exports.latestVersion = ver;
44
+ storage.setProperty('latestVersion', ver);
45
+ } catch (e) {}
46
+ });
47
+ });
48
+ client.on('error', noop);
49
+ client.end();
50
+ };
51
+
52
+ exports.latestVersion = storage.getProperty('latestVersion');
53
+ updateVersion();
54
+ setInterval(updateVersion, 1000 * 60 * 30);
55
+
56
+ const initData = () => {
57
+ const domain = storage.getProperty('domain');
58
+ if (!checkDomain(domain)) {
59
+ storage.setProperty('domain', '');
60
+ }
61
+ };
62
+
63
+ initData();
64
+
65
+ const getString = (str) => {
66
+ return typeof str === 'string' ? str : '';
67
+ };
68
+
69
+ const getReqDomain = (req, isConnect) => {
70
+ let host = req.headers['x-whistle-real-host'] || req.headers.host;
71
+ if (isConnect ? TUNNEL_PATH_RE.test(req.url) : (!host && HOST_RE.test(req.url))) {
72
+ host = RegExp.$1;
73
+ }
74
+ return host && typeof host === 'string' ? host.split(':', 2) : '';
75
+ };
76
+
77
+ const getDomain = () => {
78
+ return storage.getProperty('domain');
79
+ };
80
+
81
+ const isUIDomain = (domain) => {
82
+ return domain === 'admin.nohost.pro' || parseDomain(`${getDomain()},${config.domain}`).includes(domain);
83
+ };
84
+
85
+ exports.isUIRequest = (req, isConnect) => {
86
+ const domain = getReqDomain(req, isConnect);
87
+ if (!domain) {
88
+ return;
89
+ }
90
+ if (req.headers['x-whistle-nohost-ui'] || isUIDomain(domain[0])) {
91
+ return true;
92
+ }
93
+ if (INNER_PATH_RE.test(req.url)) {
94
+ req.url = req.url.replace('/.nohost-inner-path./', '/');
95
+ return true;
96
+ }
97
+ if (!net.isIP(domain[0]) || config.portStr !== (domain[1] || '80')) {
98
+ return;
99
+ }
100
+ if (isLocalAddress(domain[0])) {
101
+ return true;
102
+ }
103
+ req.headers['x-whistle-nohost-ui'] = 1;
104
+ return false;
105
+ };
106
+
107
+ const getAdmin = () => {
108
+ const admin = storage.getProperty('admin') || '';
109
+ const username = getString(admin.username) || 'admin';
110
+ const password = getString(admin.password) || DEFAULT_PASSWORD;
111
+ return { username, password };
112
+ };
113
+
114
+ exports.setAdmin = (admin) => {
115
+ if (admin) {
116
+ const { username, password } = admin;
117
+ if (getString(username) && /^[\w.-]{1,32}$/.test(username) && getString(password)) {
118
+ const oldAdmin = getAdmin();
119
+ admin = { username, password: shasum(password) };
120
+ storage.setProperty('admin', admin);
121
+ return admin.username !== oldAdmin.username || admin.password !== oldAdmin.password;
122
+ }
123
+ }
124
+ };
125
+
126
+ exports.getAdmin = getAdmin;
127
+
128
+ exports.setDomain = (str) => {
129
+ storage.setProperty('domain', str);
130
+ };
131
+
132
+ exports.getDomain = getDomain;
@@ -0,0 +1,138 @@
1
+ const { proxy, getRawHeaders } = require('@nohost/router');
2
+ const { isIP } = require('net');
3
+ const cpuNum = require('os').cpus().length;
4
+ const {
5
+ getRemoteAddr,
6
+ getRemotePort,
7
+ } = require('whistle/lib/util');
8
+ const { fork } = require('./whistleMgr');
9
+ const startService = require('../service');
10
+ const { fork: forkWorker } = require('./worker');
11
+ const config = require('../config');
12
+
13
+ const LOCALHOST = '127.0.0.1';
14
+ const FROM_RE = /[?&]from=[\w.-]+(?:&|$)/;
15
+ const ERROR_HEADERS = { 'x-server': 'nohost/router' };
16
+
17
+ const sendError = (res, err) => {
18
+ if (res.writeHead) {
19
+ res.writeHead(500, ERROR_HEADERS);
20
+ res.end(err.stack);
21
+ } else {
22
+ res.destroy();
23
+ }
24
+ };
25
+ const proxyCtx = async (ctx, options) => {
26
+ const svrRes = await proxy(ctx.req, ctx.res, options);
27
+ ctx.status = svrRes.statusCode;
28
+ ctx.set(svrRes.headers);
29
+ ctx.body = svrRes;
30
+ };
31
+
32
+ exports.passToWhistle = async (req, res, index) => {
33
+ const ctx = !res && req;
34
+ if (ctx) {
35
+ res = ctx.res;
36
+ req = ctx.req;
37
+ }
38
+ const options = { host: LOCALHOST };
39
+ let remoteAddrHead;
40
+ let remotePortHead;
41
+ const isSingleton = index == null;
42
+ try {
43
+ const result = isSingleton ? await fork() : await forkWorker(index);
44
+ options.port = result.port;
45
+ remoteAddrHead = result.remoteAddrHead;
46
+ remotePortHead = result.remotePortHead;
47
+ } catch (err) {
48
+ if (ctx) {
49
+ throw err;
50
+ }
51
+ return sendError(res, err);
52
+ }
53
+ if (req._hasError) {
54
+ return;
55
+ }
56
+ const { headers } = req;
57
+ const remoteAddress = !isSingleton && headers['x-whistle-remote-address'];
58
+ const remotePort = !isSingleton && headers['x-whistle-remote-port'];
59
+ if (remotePort > 0 && remotePort < 65536 && isIP(remoteAddress)) {
60
+ headers[remoteAddrHead] = remoteAddress;
61
+ headers[remotePortHead] = remotePort;
62
+ delete headers['x-whistle-remote-address'];
63
+ delete headers['x-whistle-remote-port'];
64
+ } else {
65
+ headers[remoteAddrHead] = getRemoteAddr(req);
66
+ headers[remotePortHead] = getRemotePort(req);
67
+ }
68
+ if (ctx) {
69
+ return proxyCtx(ctx, options);
70
+ }
71
+ if (req.upgrade) {
72
+ return proxy(req, res, options);
73
+ }
74
+ try {
75
+ const svrRes = await proxy(req, res, options);
76
+ res.writeHead(svrRes.statusCode, getRawHeaders(svrRes));
77
+ svrRes.pipe(res);
78
+ } catch (err) {
79
+ sendError(res, err);
80
+ }
81
+ };
82
+
83
+ const MAX_REQUESTS = 120;
84
+ const conns = [0];
85
+
86
+ const getServerIndex = () => {
87
+ const len = conns.length;
88
+ let min = conns[0];
89
+ if (min < MAX_REQUESTS) {
90
+ ++conns[0];
91
+ return 0;
92
+ }
93
+ let index = 0;
94
+ for (let i = 1; i < len; i++) {
95
+ if (min > conns[i]) {
96
+ min = conns[i];
97
+ index = i;
98
+ }
99
+ }
100
+ if (min < MAX_REQUESTS || len >= cpuNum) {
101
+ ++conns[index];
102
+ return index;
103
+ }
104
+ conns[len] = 1;
105
+ return len;
106
+ };
107
+
108
+ exports.passToService = async (ctx) => {
109
+ const index = getServerIndex();
110
+ const port = await startService(index);
111
+ await proxyCtx(ctx, { host: LOCALHOST, port });
112
+ --conns[index];
113
+ };
114
+
115
+ const REDIRECT_PAGES = {
116
+ '/': '/select.html',
117
+ '/share.html': '/share.html',
118
+ '/select.html': '/select.html',
119
+ '/index.html': '/select.html',
120
+ '/capture.html': '/',
121
+ '/data.html': '/',
122
+ };
123
+
124
+ exports.getRedirectUrl = (ctx) => {
125
+ let { redirect } = config;
126
+ const { pageName, originalUrl: url, accountName } = ctx;
127
+ const page = REDIRECT_PAGES[pageName];
128
+ if (!redirect || !page || FROM_RE.test(url)) {
129
+ return;
130
+ }
131
+ redirect = `${redirect}${page}`;
132
+ const index = url.indexOf('?');
133
+ let query = index === -1 ? '' : url.substring(index + 1);
134
+ if (accountName) {
135
+ query = `${query ? `${query}&` : ''}account=${encodeURIComponent(accountName)}`;
136
+ }
137
+ return query ? `${redirect}?${query}` : redirect;
138
+ };
@@ -0,0 +1,56 @@
1
+ const path = require('path');
2
+ const { fork } = require('pfork');
3
+ const { getAdmin, getDomain } = require('./storage');
4
+ const config = require('../config');
5
+
6
+ const script = path.join(__dirname, '../whistle.js');
7
+ const DELAY_TIME = 1000 * 6;
8
+ let server;
9
+
10
+ exports.fork = () => {
11
+ if (!server) {
12
+ const admin = getAdmin();
13
+ server = new Promise((resolve, reject) => fork({
14
+ script,
15
+ baseDir: config.baseDir,
16
+ username: admin.username,
17
+ password: admin.password,
18
+ debugMode: config.debugMode,
19
+ domain: `${getDomain()},${config.domain}`,
20
+ realPort: config.port,
21
+ realHost: config.host || '',
22
+ authKey: config.authKey,
23
+ mainAuthKey: config.mainAuthKey,
24
+ storageServer: config.storage,
25
+ dnsServer: config.dnsServer,
26
+ globalPluginPath: config.globalPluginPath,
27
+ accountPluginPath: config.accountPluginPath,
28
+ }, (err, options, child) => {
29
+ if (err) {
30
+ server = null;
31
+ reject(err);
32
+ } else {
33
+ server.whistleProcess = child;
34
+ child.once('close', () => {
35
+ server = null;
36
+ });
37
+ resolve(options);
38
+ }
39
+ }));
40
+ }
41
+ return server;
42
+ };
43
+
44
+ exports.restart = () => {
45
+ if (server) {
46
+ if (server.whistleProcess) {
47
+ server.whistleProcess.kill(DELAY_TIME);
48
+ } else {
49
+ const svr = server;
50
+ server.then(() => {
51
+ svr.whistleProcess.kill(DELAY_TIME);
52
+ });
53
+ }
54
+ server = null;
55
+ }
56
+ };
@@ -0,0 +1,52 @@
1
+ const p = require('pfork');
2
+ const path = require('path');
3
+ const config = require('../config');
4
+ const workerNum = require('./workerNum');
5
+
6
+ const WHISTLE_WORKER = path.join(__dirname, '../plugins/whistle.nohost/lib/whistle.js');
7
+ const DELAY = 6000;
8
+ const cache = {};
9
+
10
+ const getName = (index) => {
11
+ if (index > 0) {
12
+ index %= workerNum;
13
+ }
14
+ return `$${index}`;
15
+ };
16
+
17
+ exports.fork = (index) => {
18
+ if (index > 0) {
19
+ index %= workerNum;
20
+ }
21
+ const name = getName(index);
22
+ cache[name] = cache[name] || new Promise((resolve, reject) => {
23
+ p.fork({
24
+ worker: true,
25
+ pluginsPath: config.pluginsPath,
26
+ value: name,
27
+ password: `${Math.random()}`,
28
+ guestName: '-',
29
+ storage: `whistle.nohost/${name}`,
30
+ script: WHISTLE_WORKER,
31
+ storageServer: config.storage,
32
+ dnsServer: config.dnsServer,
33
+ accountPluginPath: config.accountPluginPath,
34
+ }, (err, result, child) => {
35
+ if (err) {
36
+ delete cache[name];
37
+ return reject(err);
38
+ }
39
+ child.on('exit', () => {
40
+ delete cache[name];
41
+ });
42
+ resolve(result);
43
+ });
44
+ });
45
+ return cache[name];
46
+ };
47
+ exports.kill = (index) => {
48
+ p.kill({
49
+ script: WHISTLE_WORKER,
50
+ value: getName(index),
51
+ }, DELAY);
52
+ };
@@ -0,0 +1,11 @@
1
+ const os = require('os');
2
+
3
+ const WORKER_NUM_FROM_ENV = parseInt(process.env.NOHOST_WORKER_NUM, 10);
4
+ const RESERVE_MEM = 1024 * 1024 * 1204 * 2;
5
+ if (WORKER_NUM_FROM_ENV > 0) {
6
+ module.exports = WORKER_NUM_FROM_ENV;
7
+ } else {
8
+ const WORKER_NUM_FROM_MEM = Math.floor(Math.max(os.totalmem() - RESERVE_MEM, 0) / (1024 * 1024 * 500)) || 1;
9
+ const WORKER_NUM_FROM_CPU = os.cpus().length * 4;
10
+ module.exports = Math.min(WORKER_NUM_FROM_MEM, WORKER_NUM_FROM_CPU);
11
+ }
@@ -0,0 +1,171 @@
1
+ <script>
2
+ var w2 = window.whistleBridge;
3
+ var showModal = w2.showModal;
4
+ var baseUrl = window.parent.location.href
5
+ .replace(/[?#].*$/, "")
6
+ .replace(/\/nohost_share\/.*$/, "")
7
+ .replace(/\/$/, "");
8
+ var codeList = [];
9
+
10
+ for (var c1 = 0; c1 < 10; c1++) {
11
+ codeList.push(c1 + "");
12
+ }
13
+
14
+ for (var c2 = 97; c2 <= 122; ++c2) {
15
+ codeList.push(String.fromCharCode(c2));
16
+ }
17
+
18
+ var codeLen = codeList.length;
19
+
20
+ function getExportList() {
21
+ var list = localStorage.exportInfoList;
22
+ try {
23
+ list = JSON.parse(list);
24
+ if (Array.isArray(list)) {
25
+ return list.filter(function(item) {
26
+ return item && item.name && item.date;
27
+ });
28
+ }
29
+ } catch (e) {}
30
+ return [];
31
+ }
32
+
33
+ function saveExportList(name, date, username, code, env, route) {
34
+ var list = getExportList();
35
+ list.push({ name, date, username, code, env, route });
36
+ if (list.length > 100) {
37
+ list = list.slice(-30);
38
+ }
39
+ localStorage.exportInfoList = JSON.stringify(list);
40
+ return list;
41
+ }
42
+ // 如果有构建,可以用模板代替
43
+ function createTable(list) {
44
+ var highlight = list;
45
+ list = list || getExportList();
46
+ var len = (Date.now() + "").length;
47
+ list = list
48
+ .reverse()
49
+ .map(function(item, i) {
50
+ var username = item.username;
51
+ var code = item.code;
52
+ var route = item.route || "";
53
+ if (route) {
54
+ route = "&route=" + encodeURIComponent(route);
55
+ }
56
+ username = username ? "&username=" + encodeURIComponent(username) : "";
57
+ var url =
58
+ baseUrl +
59
+ "/nohost_share/" +
60
+ "?name=" +
61
+ encodeURIComponent(item.name) +
62
+ "&date=" +
63
+ encodeURIComponent(item.date) +
64
+ username +
65
+ (code ? "&encrypted=1" : "") +
66
+ route;
67
+ var date = new Date(
68
+ parseInt(item.name.substring(0, len), 10)
69
+ ).toLocaleString();
70
+ return (
71
+ "<tr" +
72
+ (!i && highlight ? ' style="font-weight: bold;"' : "") +
73
+ "><th>" +
74
+ (i + 1) +
75
+ "</th><td>" +
76
+ date +
77
+ '</td><td style="overflow: hidden"><a style="font-size: 13px;" href="' +
78
+ url +
79
+ '" target="_blank">' +
80
+ url +
81
+ "</a>" +
82
+ (code ? "<div>提取码:" + code + "</div>" : "") +
83
+ '</td><td><a href="javascript:;" class="w-copy-text-with-tips" data-clipboard-text="' +
84
+ url +
85
+ '">复制链接</a>' +
86
+ (code
87
+ ? '<br/><a href="javascript:;" class="w-copy-text-with-tips" data-clipboard-text="' +
88
+ code +
89
+ '">复制提取码</a>'
90
+ : "") +
91
+ "</td>"
92
+ );
93
+ })
94
+ .join("");
95
+ if (!list) {
96
+ list = '<tr><td colspan="4" style="text-align: center;">Empty</td></tr>';
97
+ }
98
+ var head =
99
+ '<thead><th style="width: 60px">#</th><th style="width: 180px">Date</th><th>URL</th><th style="width: 120px">Operation</th></thead>';
100
+ return (
101
+ '<table class="table">' + head + "<tbody><tr>" + list + "</tbody></table>"
102
+ );
103
+ }
104
+
105
+ function showFeeds(list) {
106
+ w2.showModal({
107
+ width: 1200,
108
+ title: "抓包分享记录",
109
+ body: createTable(list)
110
+ });
111
+ }
112
+
113
+ function getEnvName(item) {
114
+ var headers = item && item.req && item.req.headers;
115
+ return headers && headers["x-whistle-nohost-env"];
116
+ }
117
+
118
+ function exportSessions(options, code) {
119
+ var selectedList = options.selectedList;
120
+ if (!selectedList.length) {
121
+ selectedList = [options.activeItem];
122
+ }
123
+ var name = Date.now() + "" + Math.floor(Math.random() * 10000);
124
+ w2.request(
125
+ {
126
+ url: baseUrl + "/export_sessions?route=!required!",
127
+ crossDomain: true,
128
+ type: "post",
129
+ data: {
130
+ code: code || "",
131
+ name: name,
132
+ sessions: JSON.stringify(selectedList)
133
+ }
134
+ },
135
+ function(data) {
136
+ if (!data) {
137
+ return w2.toast.error("分享失败,请稍后再试!");
138
+ }
139
+ var env = getEnvName(selectedList[0]);
140
+ showFeeds(
141
+ saveExportList(name, data.date, data.username, code, env, data.route)
142
+ );
143
+ }
144
+ );
145
+ }
146
+ w2.addNetworkListener(function(options) {
147
+ if (options.name === "【分享】生成链接") {
148
+ w2.showModal({
149
+ width: 320,
150
+ body:
151
+ '<p style="font-size: 18px; text-align: center;">是否设置提取码?</p><span style="color: #666;">设置提取码后,用户只有输入提取码才能查看。</span><button type="button" class="close" data-dismiss="modal" style="position: absolute; top: 3px; right: 5px;"><span aria-hidden="true">&times;</span></button>',
152
+ footer:
153
+ '<button type="button" onclick="exportWithCode()" class="btn btn-primary" data-dismiss="modal"><span class="glyphicon glyphicon-lock" style="margin-right: 3px;"></span>设置提取码</button><button type="button" onclick="exportWithoutCode()" class="btn btn-default" data-dismiss="modal">不设置</button>',
154
+ methods: {
155
+ exportWithoutCode: function() {
156
+ exportSessions(options);
157
+ },
158
+ exportWithCode: function() {
159
+ var code = [];
160
+ for (var i = 0; i < 4; i++) {
161
+ code[i] = codeList[Math.floor(Math.random() * 100000) % codeLen];
162
+ }
163
+ exportSessions(options, code.join(""));
164
+ }
165
+ }
166
+ });
167
+ } else {
168
+ showFeeds();
169
+ }
170
+ });
171
+ </script>
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "whistle.share",
3
+ "version": "1.0.0",
4
+ "whistleConfig": {
5
+ "priority": 100,
6
+ "hideLongProtocol": true,
7
+ "hideShortProtocol": true,
8
+ "networkMenus": [
9
+ {
10
+ "name": "【分享】生成链接",
11
+ "page": "/menu.html",
12
+ "required": true
13
+ },
14
+ {
15
+ "name": "【分享】查看记录",
16
+ "page": "/menu.html"
17
+ }
18
+ ]
19
+ }
20
+ }