mirror of
https://github.com/avwo/whistle.git
synced 2026-10-18 22:51:32 +08:00
1191 lines
37 KiB
JavaScript
1191 lines
37 KiB
JavaScript
var path = require('path');
|
|
var http = require('http');
|
|
var fs = require('fs');
|
|
var net = require('net');
|
|
var dns = require('dns');
|
|
var https = require('https');
|
|
var crypto = require('crypto');
|
|
var qs = require('querystring');
|
|
var LRU = require('lru-cache');
|
|
var httpAgent = http.Agent;
|
|
var httpsAgent = require('https').Agent;
|
|
var parseUrl = require('./util/parse-url-safe');
|
|
var fse = require('fs-extra2');
|
|
var Buffer = require('safe-buffer').Buffer;
|
|
var extend = require('extend');
|
|
var tunnel = require('hagent').agent;
|
|
var socks = require('sockx');
|
|
var json5 = require('json5');
|
|
var pkgConf = require('../package.json');
|
|
var config = extend(exports, pkgConf);
|
|
var common = require('./util/common');
|
|
|
|
var customUIHost;
|
|
var customHostPluginMap = {};
|
|
var customPluginNameHost = {};
|
|
var WHISTLE_PLUGIN_RE = /^(?:whistle\.)?([a-z\d_\-]+)$/;
|
|
var CMD_RE = /^[\w]{1,12}(?:\s+-g)?$/;
|
|
var httpsAgents = new LRU({ max: 360 });
|
|
var socksAgents = new LRU({ max: 100 });
|
|
var version = process.version.substring(1).split('.');
|
|
var disableAgent = version[0] > 10;
|
|
var uid =
|
|
Date.now() + '-' + process.pid + '-' + Math.floor(Math.random() * 10000);
|
|
var IPV4_RE = /^([\d.]+)(:\d{1,5})?$/;
|
|
var IPV6_RE = /^\[([\w:]+)\](:\d{1,5})?$/;
|
|
var noop = function () {};
|
|
var DATA_KEY_RE = /^(clientip|clientid|tunneldata)([=.])([\w.-]+)$/i;
|
|
var LOCAL_UI_HOST_LIST = [
|
|
'local.whistlejs.com',
|
|
'local.wproxy.org',
|
|
'l.wproxy.org',
|
|
'rootca.pro'
|
|
];
|
|
var variableProperties = [
|
|
'encrypted',
|
|
'sockets',
|
|
'dataDirname',
|
|
'storage',
|
|
'baseDir',
|
|
'noGlobalPlugins',
|
|
'pluginsDataMap',
|
|
'globalData',
|
|
'username',
|
|
'password',
|
|
'debugMode',
|
|
'localUIHost',
|
|
'extra',
|
|
'rules',
|
|
'values',
|
|
'dnsCache',
|
|
'allowDisableShadowRules'
|
|
];
|
|
|
|
config.uid = uid;
|
|
config.rejectUnauthorized = false;
|
|
config.enableH2 = version[0] > 12; // 支持 HTTP2 要求的最低 Node 版本
|
|
config.ASSESTS_PATH = path.join(__dirname, '../assets');
|
|
config.REQ_FROM_HEADER = 'x-whistle-request-from';
|
|
config.DISABLE_RULES_HEADER = 'x-whistle-proxy-rules-' + uid;
|
|
config.WHISTLE_POLICY_HEADER = 'x-whistle-policy';
|
|
config.CUSTOM_CERT_HEADER = 'x-whistle-exists-custom-cert';
|
|
config.ENABLE_CAPTURE_HEADER = 'x-whistle-enable-capture';
|
|
config.CLIENT_IP_HEAD = 'x-forwarded-for';
|
|
config.HTTPS_FIELD = 'x-whistle-https-request';
|
|
config.HTTPS_PROTO_HEADER = 'x-forwarded-proto';
|
|
config.REAL_HOST_HEADER = 'x-whistle-real-host';
|
|
config.FWD_HOST_HEADER = 'x-forwarded-host';
|
|
config.INTERNAL_ID =
|
|
Date.now() + '/' + process.pid + '/' + Math.floor(Math.random() * 100000);
|
|
config.INTERNAL_ID_HEADER = '_x-whistle-internal-id';
|
|
config.SNI_PLUGIN_HEADER = 'x-whistle-sni-callback-plugin-' + uid;
|
|
config.PROXY_ID_HEADER = 'x-whistle-proxy-id-' + uid;
|
|
config.CLIENT_PORT_HEAD = 'x-whistle-client-port';
|
|
config.WEBUI_HEAD = 'x-forwarded-from-whistle-' + uid;
|
|
config.RES_RULES_HEAD = 'x-whistle-res-rules-' + uid;
|
|
config.CLIENT_INFO_HEAD = 'x-whistle-client-info-' + uid;
|
|
config.REMOTE_ADDR_HEAD = 'x-whistle-remote-address-' + uid;
|
|
config.REMOTE_PORT_HEAD = 'x-whistle-remote-port-' + uid;
|
|
config.ADDITIONAL_HEAD = 'x-whistle-additional-headers';
|
|
config.WEBUI_PATH = '/.whistle-path.5b6af7b9884e1165/';
|
|
config.PREVIEW_PATH_RE = /\?\?\?WHISTLE_PREVIEW_CHARSET=([A-Z\d_-]+)\?\?\?$/;
|
|
config.PLUGIN_HOOK_NAME_HEADER = 'x-whistle-plugin-hook-name_';
|
|
config.CLIENT_ID_HEADER = 'x-whistle-client-id';
|
|
config.CLIENT_ID_HEAD = 'x-whistle-client-id';
|
|
config.COMPOSER_CLIENT_ID_HEADER = 'x-whistle-client-id-' + uid;
|
|
config.TUNNEL_DATA_HEADER = 'x-whistle-tunnel-data';
|
|
config.TEMP_TUNNEL_DATA_HEADER = 'x-whistle-tunnel-data-' + uid;
|
|
config.UPGRADE_HEADER = 'x-whistle-upgrade-' + uid;
|
|
config.SNI_TYPE_HEADER = 'x-whistle-sni-type-' + uid;
|
|
config.ALPN_PROTOCOL_HEADER = 'x-whistle-alpn-protocol';
|
|
config.PLUGIN_HOOKS = {
|
|
SNI: 'sni-' + uid,
|
|
AUTH: 'auth-' + uid,
|
|
HTTP: 'http-' + uid,
|
|
UI: 'ui-' + uid,
|
|
TUNNEL: 'tunnel-' + uid,
|
|
TUNNEL_RULES: 'tunnel-rules-' + uid,
|
|
REQ_STATS: 'req-stats-' + uid,
|
|
RES_STATS: 'res-stats-' + uid,
|
|
REQ_RULES: 'req-rules-' + uid,
|
|
RES_RULES: 'res-rules-' + uid,
|
|
REQ_READ: 'req-read-' + uid,
|
|
REQ_WRITE: 'req-write-' + uid,
|
|
RES_READ: 'res-read-' + uid,
|
|
RES_WRITE: 'res-write-' + uid,
|
|
WS_REQ_READ: 'ws-req-read-' + uid,
|
|
WS_REQ_WRITE: 'ws-req-write-' + uid,
|
|
WS_RES_READ: 'ws-res-read-' + uid,
|
|
WS_RES_WRITE: 'ws-res-write-' + uid,
|
|
TUNNEL_REQ_READ: 'tunnel-req-read-' + uid,
|
|
TUNNEL_REQ_WRITE: 'tunnel-req-write-' + uid,
|
|
TUNNEL_RES_READ: 'tunnel-res-read-' + uid,
|
|
TUNNEL_RES_WRITE: 'tunnel-res-write-' + uid
|
|
};
|
|
|
|
var KEEP_ALIVE_MSECS = 10000;
|
|
var CONN_TIMEOUT = 30000;
|
|
|
|
config.CONN_TIMEOUT = CONN_TIMEOUT;
|
|
|
|
var getHomedir = common.getHomedir;
|
|
var getHomePath = common.getHomePath;
|
|
var getWhistlePath = common.getWhistlePath;
|
|
var getHostPort = common.getHostPort;
|
|
|
|
config.getHomedir = getHomedir;
|
|
config.getHomePath = getHomePath;
|
|
|
|
function getDataDir(dirname) {
|
|
var dir = path.join(getWhistlePath(), dirname || '.' + config.name);
|
|
fse.ensureDirSync(dir);
|
|
return dir;
|
|
}
|
|
|
|
config.baseDir = getDataDir();
|
|
config.TEMP_FILES_PATH = path.join(getWhistlePath(), 'temp_files');
|
|
config.CUSTOM_PLUGIN_PATH = path.join(getWhistlePath(), 'custom_plugins');
|
|
config.CUSTOM_CERTS_DIR = path.resolve(getWhistlePath(), 'custom_certs');
|
|
config.DEV_PLUGINS_PATH = path.resolve(getWhistlePath(), 'dev_plugins');
|
|
|
|
try {
|
|
fse.ensureDirSync(config.TEMP_FILES_PATH);
|
|
var blankFile = path.join(config.TEMP_FILES_PATH, 'blank');
|
|
if (!fs.existsSync(blankFile)) {
|
|
fs.writeFileSync(blankFile, '');
|
|
}
|
|
} catch (e) {}
|
|
try {
|
|
fse.ensureDirSync(config.CUSTOM_CERTS_DIR);
|
|
} catch (e) {}
|
|
|
|
exports.getWhistlePath = getWhistlePath;
|
|
exports.getDataDir = getDataDir;
|
|
var async_id_symbol;
|
|
if (version[0] < 16) {
|
|
try {
|
|
async_id_symbol = process.binding('async_wrap').async_id_symbol;
|
|
} catch (e) {}
|
|
}
|
|
var emptyHandle = {
|
|
asyncReset: noop,
|
|
getAsyncId: noop
|
|
};
|
|
|
|
function createAgent(agentConfig, https) {
|
|
var agent = new (https ? httpsAgent : httpAgent)(agentConfig);
|
|
if (async_id_symbol) {
|
|
var addRequest = agent.addRequest;
|
|
agent.addRequest = function (req, options) {
|
|
// fix: https://github.com/nodejs/node/issues/13539
|
|
var freeSockets = this.freeSockets[this.getName(options)];
|
|
if (freeSockets && freeSockets.length) {
|
|
var socket = freeSockets[0];
|
|
var handle = socket._handle;
|
|
if (!handle) {
|
|
socket._handle = emptyHandle;
|
|
} else if (typeof handle.asyncReset !== 'function') {
|
|
handle.asyncReset = noop;
|
|
}
|
|
var originalRef = socket.ref;
|
|
socket.ref = function () {
|
|
socket.ref = originalRef;
|
|
if (socket._handle === emptyHandle) {
|
|
delete socket._handle;
|
|
} else if (socket._handle.asyncReset === noop) {
|
|
delete socket._handle.asyncReset;
|
|
}
|
|
socket.ref();
|
|
};
|
|
}
|
|
var onSocket = req.onSocket;
|
|
req.onSocket = function (socket) {
|
|
try {
|
|
socket[async_id_symbol] = socket._handle.getAsyncId();
|
|
} catch (e) {}
|
|
onSocket.apply(this, arguments);
|
|
};
|
|
addRequest.apply(this, arguments);
|
|
};
|
|
}
|
|
return agent;
|
|
}
|
|
|
|
function capitalize(str) {
|
|
return (str && str[0].toUpperCase()) + str.substring(1);
|
|
}
|
|
|
|
function getHttpsAgent(options, reqOpts) {
|
|
var key = getCacheKey(options);
|
|
var agent = httpsAgents.get(key);
|
|
if (reqOpts) {
|
|
var headers = options.headers || {};
|
|
var proxyHeaders = {};
|
|
Object.keys(headers).forEach(function (key) {
|
|
var rawKey = key.split('-').map(capitalize).join('-');
|
|
proxyHeaders[rawKey] = headers[key];
|
|
});
|
|
reqOpts._tunnelProxyHeaders = proxyHeaders;
|
|
}
|
|
if (!agent) {
|
|
options.proxyAuth = options.auth;
|
|
options.rejectUnauthorized = config.rejectUnauthorized;
|
|
options.proxy = {
|
|
host: options.proxyHost,
|
|
port: options.proxyPort,
|
|
enableIntercept: options.enableIntercept,
|
|
keepStreamResume: options.keepStreamResume,
|
|
proxyTunnelPath: options.proxyTunnelPath
|
|
};
|
|
var agentName = options.isHttps ? 'httpsOverHttp' : 'httpOverHttp';
|
|
if (options.proxyServername) {
|
|
options.proxy.servername = options.proxyServername;
|
|
agentName += 's';
|
|
}
|
|
agent = new tunnel[agentName](options);
|
|
httpsAgents.set(key, agent);
|
|
agent.on('free', preventThrowOutError);
|
|
}
|
|
return agent;
|
|
}
|
|
|
|
exports.getHttpsAgent = getHttpsAgent;
|
|
|
|
function getCacheKey(options, isSocks) {
|
|
var auth = options.auth || options.proxyAuth;
|
|
if (!auth) {
|
|
var headers = options.headers || '';
|
|
auth = headers['proxy-authorization'] || '';
|
|
}
|
|
var ip = (!isSocks && options.clientIp) || '';
|
|
return [
|
|
options.proxyServername ? 'https-proxy' : '',
|
|
options.isHttps ? 'https' : 'http',
|
|
options.proxyHost,
|
|
options.proxyPort,
|
|
auth,
|
|
ip,
|
|
options.proxyTunnelPath || '',
|
|
(options.isHttps && options.cacheKey) || ''
|
|
].join(':');
|
|
}
|
|
|
|
function getAuths(_url) {
|
|
var options = typeof _url == 'string' ? parseUrl(_url) : _url;
|
|
if (!options || !options.auth) {
|
|
return [socks.auth.None()];
|
|
}
|
|
|
|
var auths = [];
|
|
options.auth.split('|').forEach(function (auth) {
|
|
auth = auth.trim();
|
|
if (auth) {
|
|
var index = auth.indexOf(':');
|
|
auths.push({
|
|
username: index == -1 ? auth : auth.substring(0, index),
|
|
password: index == -1 ? '' : auth.substring(index + 1)
|
|
});
|
|
}
|
|
});
|
|
|
|
return auths.length
|
|
? auths.map(function (auth) {
|
|
return socks.auth.UserPassword(auth.username, auth.password);
|
|
})
|
|
: [socks.auth.None()];
|
|
}
|
|
|
|
exports.getAuths = getAuths;
|
|
|
|
exports.setAuth = function (auth) {
|
|
if (auth) {
|
|
config.username = getString(auth.username);
|
|
var password = (config.password = getString(auth.password));
|
|
config.passwordHash = password && createHash(password);
|
|
setGuestAuth(auth);
|
|
}
|
|
return config;
|
|
};
|
|
|
|
exports.getPluginData = function (name) {
|
|
var pluginsDataMap = name && config.pluginsDataMap;
|
|
if (!pluginsDataMap) {
|
|
return;
|
|
}
|
|
var index = name.indexOf('whistle.');
|
|
if (index !== -1) {
|
|
name = name.substring(index + 8);
|
|
}
|
|
return pluginsDataMap[name];
|
|
};
|
|
|
|
exports.setGuestAuth = function (auth) {
|
|
auth && setGuestAuth(auth);
|
|
};
|
|
|
|
function parseHost(host) {
|
|
if (Array.isArray(host)) {
|
|
return host.filter(function (h) {
|
|
return h && typeof h === 'string';
|
|
});
|
|
}
|
|
host = typeof host === 'string' ? host.trim() : '';
|
|
return host && host.split('|').map(getHostname);
|
|
}
|
|
|
|
exports.setUIHost = function (host) {
|
|
customUIHost = parseHost(host);
|
|
};
|
|
|
|
exports.setPluginUIHost = function (pluginName, host) {
|
|
if (!pluginName || !WHISTLE_PLUGIN_RE.test(pluginName)) {
|
|
return;
|
|
}
|
|
pluginName = RegExp.$1;
|
|
host = parseHost(host);
|
|
if (host) {
|
|
customPluginNameHost[pluginName] = host;
|
|
host.forEach(function (h) {
|
|
delete customHostPluginMap[h];
|
|
});
|
|
} else {
|
|
delete customPluginNameHost[pluginName];
|
|
}
|
|
customHostPluginMap = {};
|
|
Object.keys(customPluginNameHost).forEach(function (name) {
|
|
var list = customPluginNameHost[name];
|
|
list &&
|
|
list.forEach(function (h) {
|
|
customHostPluginMap[h] = name;
|
|
});
|
|
});
|
|
};
|
|
|
|
function toBuffer(buf) {
|
|
if (buf == null || Buffer.isBuffer(buf)) {
|
|
return buf;
|
|
}
|
|
buf += '';
|
|
return Buffer.from(buf);
|
|
}
|
|
|
|
exports.toBuffer = toBuffer;
|
|
|
|
function setHeader(headers, name, value) {
|
|
var keys = Object.keys(headers);
|
|
for (var i = 0, len = keys.length; i < len; i++) {
|
|
var key = keys[i];
|
|
if (key.toLowerCase() === name) {
|
|
headers[key] = value;
|
|
return;
|
|
}
|
|
}
|
|
headers[name] = value;
|
|
}
|
|
|
|
exports.setHeader = setHeader;
|
|
|
|
function getTunnelPath(headers) {
|
|
var host = headers.host || headers.Host;
|
|
if (host) {
|
|
return host;
|
|
}
|
|
var keys = Object.keys(headers);
|
|
for (var i = 0, len = keys.length; i < len; i++) {
|
|
var key = keys[i];
|
|
if (key.toLowerCase() === 'host') {
|
|
return headers[key];
|
|
}
|
|
}
|
|
}
|
|
|
|
function connect(options, cb) {
|
|
var headers = options.headers || {};
|
|
var proxyOptions = {
|
|
method: 'CONNECT',
|
|
agent: false,
|
|
proxyTunnelPath: options.proxyTunnelPath,
|
|
enableIntercept: options.enableIntercept,
|
|
path: getTunnelPath(headers),
|
|
host: options.proxyHost,
|
|
port: options.proxyPort,
|
|
rejectUnauthorized: config.rejectUnauthorized,
|
|
headers: headers
|
|
};
|
|
var httpModule = http;
|
|
if (options.proxyServername) {
|
|
proxyOptions.servername = options.proxyServername;
|
|
httpModule = https;
|
|
}
|
|
var auth = options.proxyAuth || options.auth;
|
|
if (auth) {
|
|
auth = 'Basic ' + toBuffer(auth).toString('base64');
|
|
setHeader(headers, 'proxy-authorization', auth);
|
|
}
|
|
var timer = setTimeout(function () {
|
|
if (req) {
|
|
req.emit('error', new Error('Timeout'));
|
|
req.destroy();
|
|
req.socket && req.socket.destroy();
|
|
}
|
|
}, CONN_TIMEOUT);
|
|
var req = httpModule.request(proxyOptions);
|
|
req.on('error', noop);
|
|
req
|
|
.on('connect', function (res, socket) {
|
|
clearTimeout(timer);
|
|
socket.on('error', noop);
|
|
if (res.statusCode !== 200) {
|
|
var err = new Error(
|
|
'Tunneling socket could not be established, statusCode=' +
|
|
res.statusCode
|
|
);
|
|
err.statusCode = res.statusCode;
|
|
socket.destroy();
|
|
process.nextTick(function () {
|
|
req.emit('error', err);
|
|
});
|
|
return;
|
|
}
|
|
if (res.headers['x-whistle-allow-tunnel-ack']) {
|
|
socket.write('1');
|
|
}
|
|
cb(socket, res);
|
|
})
|
|
.end();
|
|
return req;
|
|
}
|
|
|
|
exports.connect = connect;
|
|
|
|
function preventThrowOutError(socket) {
|
|
if (socket.listeners('error').indexOf(freeSocketErrorListener) === -1) {
|
|
socket.once('error', freeSocketErrorListener);
|
|
}
|
|
}
|
|
|
|
function freeSocketErrorListener() {
|
|
var socket = this;
|
|
socket.destroy();
|
|
socket.emit('agentRemove');
|
|
socket.removeListener('error', freeSocketErrorListener);
|
|
}
|
|
|
|
function resolvePath(file) {
|
|
if (!file || !(file = file.trim())) {
|
|
return file;
|
|
}
|
|
|
|
return /^[\w-]+$/.test(file) ? file : path.resolve(file);
|
|
}
|
|
|
|
function getHostname(_url) {
|
|
if (typeof _url != 'string') {
|
|
return '';
|
|
}
|
|
if (_url.indexOf('/') != -1) {
|
|
return parseUrl(_url).hostname;
|
|
}
|
|
var index = _url.indexOf(':');
|
|
return index == -1 ? _url : _url.substring(0, index);
|
|
}
|
|
|
|
function getPaths(paths, isCustom) {
|
|
if (typeof paths === 'string') {
|
|
paths = paths.trim().split(/\s*[|,;]\s*/);
|
|
} else if (!Array.isArray(paths)) {
|
|
return;
|
|
}
|
|
var result = [];
|
|
paths.forEach(function (path) {
|
|
if (isCustom && ['self', 'buildin', 'buildIn', 'build-in'].indexOf(path) !== -1) {
|
|
result.push(config.CUSTOM_PLUGIN_PATH);
|
|
} else if (path && typeof path === 'string') {
|
|
result.push(path);
|
|
}
|
|
});
|
|
return result.length ? result : null;
|
|
}
|
|
|
|
function getSecureFilter(newConf) {
|
|
var secureFilter = newConf.secureFilter;
|
|
if (typeof secureFilter === 'function') {
|
|
return secureFilter;
|
|
}
|
|
if (!secureFilter || typeof secureFilter !== 'string') {
|
|
return;
|
|
}
|
|
try {
|
|
return require(secureFilter);
|
|
} catch (e) {}
|
|
}
|
|
|
|
function getString(str) {
|
|
return str && typeof str === 'string' ? str : '';
|
|
}
|
|
|
|
function createHash(str) {
|
|
var shasum = crypto.createHash('sha1');
|
|
shasum.update(str);
|
|
return shasum.digest('hex');
|
|
}
|
|
|
|
function readFileText(filepath, retry) {
|
|
try {
|
|
return fs.readFileSync(filepath, { encoding: 'utf8' }).trim();
|
|
} catch (e) {
|
|
if (!retry) {
|
|
return readFileText(filepath, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
function readFileBuffer(filepath, required, retry) {
|
|
try {
|
|
return fs.readFileSync(filepath);
|
|
} catch (e) {
|
|
if (!retry) {
|
|
return required !== false ? fs.readFileSync(filepath) : readFileBuffer(filepath, required, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
function isPort(port) {
|
|
return /^\d+$/.test(port) && port <= 65535 && port > 0;
|
|
}
|
|
|
|
function setGuestAuth(auth) {
|
|
if (auth.guestName === '-' && !auth.guestPassword) {
|
|
config.guest = {};
|
|
} else if (auth.guestName && auth.guestPassword) {
|
|
config.guest = {
|
|
username: auth.guestName,
|
|
password: auth.guestPassword
|
|
};
|
|
} else if ('guest' in auth) {
|
|
config.guest = auth.guest;
|
|
}
|
|
}
|
|
|
|
function parseString(str) {
|
|
str = str && typeof str === 'string' && str.trim();
|
|
if (!str) {
|
|
return '';
|
|
}
|
|
str = readFileText(str) || str;
|
|
if (!/\s/.test(str) && str.indexOf('%') !== -1) {
|
|
try {
|
|
str = decodeURIComponent(str);
|
|
} catch (e) {}
|
|
}
|
|
if (/^\{[\s\S]+\}$/.test(str) || /^\[[\s\S]+\]$/.test(str)) {
|
|
try {
|
|
return json5.parse(str);
|
|
} catch (e) {}
|
|
}
|
|
return str;
|
|
}
|
|
|
|
function getPluginList(list) {
|
|
if (typeof list == 'string') {
|
|
list = list.trim().split(/\s*[,|]\s*/);
|
|
}
|
|
var result;
|
|
if (Array.isArray(list)) {
|
|
list.forEach(function (name) {
|
|
if (WHISTLE_PLUGIN_RE.test(name)) {
|
|
result = result || [];
|
|
result.push(RegExp.$1);
|
|
}
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function readUIExt(uiExt) {
|
|
if (!uiExt) {
|
|
return;
|
|
}
|
|
var htmlPrepend = uiExt.htmlPrepend && readFileBuffer(uiExt.htmlPrepend, uiExt.required);
|
|
var htmlAppend = uiExt.htmlAppend && readFileBuffer(uiExt.htmlAppend, uiExt.required);
|
|
var jsPrepend = uiExt.jsPrepend && readFileBuffer(uiExt.jsPrepend, uiExt.required);
|
|
var jsAppend = uiExt.jsAppend && readFileBuffer(uiExt.jsAppend, uiExt.required);
|
|
if (htmlPrepend ||htmlAppend || jsPrepend || jsAppend) {
|
|
if (htmlPrepend) {
|
|
htmlPrepend = Buffer.concat([Buffer.from('<!DOCTYPE html>\n'), htmlPrepend]);
|
|
}
|
|
return {
|
|
htmlPrepend: htmlPrepend,
|
|
htmlAppend: htmlAppend,
|
|
jsPrepend: jsPrepend,
|
|
jsAppend: jsAppend
|
|
};
|
|
}
|
|
}
|
|
|
|
exports.extend = function (newConf) {
|
|
config.pluginHostMap = {};
|
|
config.uiport = config.port;
|
|
config.ipv6Only = false;
|
|
if (process.env.WHISTLE_MODE) {
|
|
newConf = newConf || {};
|
|
newConf.mode = process.env.WHISTLE_MODE + '|' + (newConf.mode || '');
|
|
}
|
|
var useAgent;
|
|
var newAgentConf;
|
|
if (newConf) {
|
|
if (newConf.specialPath && typeof newConf.specialPath === 'string') {
|
|
config.SPEC_PATH = newConf.specialPath;
|
|
}
|
|
config.uiExt = readUIExt(newConf.uiExt);
|
|
config.specialAuth = newConf.specialAuth;
|
|
config.uiMiddleware = newConf.uiMiddlewares || newConf.uiMiddleware;
|
|
newAgentConf = newConf.agentConfig;
|
|
if (newConf.cmdName && CMD_RE.test(newConf.cmdName)) {
|
|
config.cmdName = newConf.cmdName;
|
|
}
|
|
if (newConf.account && typeof newConf.account === 'string') {
|
|
config.account = newConf.account;
|
|
}
|
|
config.allowPluginList = getPluginList(newConf.allowPluginList);
|
|
config.blockPluginList = getPluginList(newConf.blockPluginList);
|
|
|
|
if (newConf.webUIPath && /^[\w.-]+$/.test(newConf.webUIPath)) {
|
|
config.WEBUI_PATH =
|
|
'/.' + newConf.webUIPath + config.WEBUI_PATH.substring(1);
|
|
}
|
|
if (newConf.cluster) {
|
|
config.headless = true;
|
|
config.workerIndex = process.env.workerIndex;
|
|
if (typeof newConf.mode !== 'string') {
|
|
newConf.mode = '';
|
|
}
|
|
}
|
|
var dnsServer = newConf.dnsServer;
|
|
var resolve6;
|
|
var dnsOptional;
|
|
if (dnsServer && typeof dnsServer === 'string') {
|
|
dnsServer = dnsServer.trim();
|
|
if (/^https?:\/\/.+/.test(dnsServer)) {
|
|
config.dnsOverHttps = dnsServer;
|
|
} else {
|
|
dnsServer = dnsServer.split(/[|,&]/);
|
|
}
|
|
}
|
|
|
|
if (dns.getServers && Array.isArray(dnsServer)) {
|
|
var newServers = [];
|
|
dnsServer.forEach(function (ip) {
|
|
ip = typeof ip === 'string' && ip.trim();
|
|
if (/^ipv6$/i.test(ip)) {
|
|
resolve6 = true;
|
|
} else if (ip === 'optional' || ip === 'default') {
|
|
dnsOptional = true;
|
|
} else if (net.isIP(ip)) {
|
|
newServers.push(ip);
|
|
} else if (IPV4_RE.test(ip) || IPV6_RE.test(ip)) {
|
|
var dnsIp = RegExp.$1;
|
|
var dnsPort = RegExp.$2;
|
|
if (net.isIP(dnsIp) && (!dnsPort || isPort(dnsPort.substring(1)))) {
|
|
newServers.push(ip);
|
|
}
|
|
}
|
|
});
|
|
if (newServers.length) {
|
|
dns.setServers(newServers);
|
|
config.resolve6 = resolve6;
|
|
config.dnsOptional = dnsOptional;
|
|
config.dnsServer = newServers.join();
|
|
}
|
|
}
|
|
|
|
if (newConf.inspect || newConf.inspectBrk) {
|
|
config.inspectMode = true;
|
|
process.env.PFORK_MODE = 'inline';
|
|
}
|
|
variableProperties.forEach(function (name) {
|
|
config[name] = newConf[name] || pkgConf[name];
|
|
});
|
|
var shadowRules = parseString(newConf.shadowRules);
|
|
var resolveShadowValues = function (obj) {
|
|
if (
|
|
(!obj.rules || typeof obj.rules === 'string') &&
|
|
obj.values &&
|
|
typeof obj.values !== 'string'
|
|
) {
|
|
config.shadowRules = obj.rules || obj.rules.trim();
|
|
if (Object.keys(obj.values).length) {
|
|
config.shadowValues = obj.values;
|
|
}
|
|
} else {
|
|
config.rules = extend({}, newConf.rules, obj);
|
|
}
|
|
};
|
|
if (typeof shadowRules === 'string') {
|
|
config.shadowRules = shadowRules.trim();
|
|
} else if (Array.isArray(shadowRules)) {
|
|
if (typeof shadowRules[0] === 'string') {
|
|
config.shadowRules = shadowRules[0].trim();
|
|
if (shadowRules[1]) {
|
|
config.rules = extend({}, newConf.rules, shadowRules[1]);
|
|
}
|
|
if (shadowRules[2]) {
|
|
config.values = extend({}, newConf.values, shadowRules[2]);
|
|
}
|
|
} else if (shadowRules[0]) {
|
|
resolveShadowValues(shadowRules[0]);
|
|
if (shadowRules[1]) {
|
|
config.values = extend({}, newConf.values, shadowRules[1]);
|
|
}
|
|
} else if (shadowRules[1]) {
|
|
config.values = extend({}, newConf.values, shadowRules[1]);
|
|
}
|
|
} else if (shadowRules) {
|
|
resolveShadowValues(shadowRules);
|
|
}
|
|
var extra = newConf.extra;
|
|
if (extra && typeof extra === 'string') {
|
|
extra = /^\s*\{[\w\W]*\}\s*$/.test(extra) ? extra : readFileText(extra);
|
|
try {
|
|
extra = extra && JSON.parse(extra);
|
|
if (extra && typeof extra === 'object') {
|
|
config.pluginsDataMap = extend({}, config.pluginsDataMap, extra);
|
|
}
|
|
} catch (e) {}
|
|
extra = null;
|
|
}
|
|
var customHandler = newConf.customHandler || newConf.customHandle;
|
|
if (typeof customHandler === 'function') {
|
|
config.customHandler = customHandler;
|
|
}
|
|
if (isPort(newConf.realPort) && config.realPort != config.port) {
|
|
config.realPort = newConf.realPort;
|
|
}
|
|
var realHost = newConf.realHost;
|
|
if (typeof realHost === 'string' && (!realHost || /^[\w.-]+$/.test(newConf.realHost))) {
|
|
config.realHost = newConf.realHost;
|
|
}
|
|
|
|
var socksPort = getHostPort(newConf.socksPort);
|
|
if (socksPort) {
|
|
config.socksPort = socksPort.port;
|
|
config.socksHost = socksPort.host;
|
|
}
|
|
var httpPort = getHostPort(newConf.httpPort);
|
|
if (httpPort) {
|
|
config.httpPort = httpPort.port;
|
|
config.httpHost = httpPort.host;
|
|
}
|
|
var httpsPort = getHostPort(newConf.httpsPort);
|
|
if (httpsPort) {
|
|
config.httpsPort = httpsPort.port;
|
|
config.httpsHost = httpsPort.host;
|
|
}
|
|
|
|
if (newConf.host && typeof newConf.host === 'string') {
|
|
config.defaultHost = newConf.host;
|
|
}
|
|
|
|
if (typeof newConf.authKey === 'string') {
|
|
config.authKey = newConf.authKey;
|
|
}
|
|
if (typeof newConf.guestAuthKey === 'string') {
|
|
config.guestAuthKey = newConf.guestAuthKey;
|
|
}
|
|
if (newConf.reqCacheSize > 0) {
|
|
config.reqCacheSize = newConf.reqCacheSize;
|
|
}
|
|
if (newConf.frameCacheSize > 0) {
|
|
config.frameCacheSize = newConf.frameCacheSize;
|
|
}
|
|
config.allowMultipleChoice = newConf.allowMultipleChoice;
|
|
if (typeof newConf.mode === 'string') {
|
|
var mode = newConf.mode.trim().split(/\s*[|,&]\s*/);
|
|
mode.forEach(function (m) {
|
|
m = m.trim();
|
|
if (
|
|
/^(pureProxy|debug|ipv6Only|master|client|diagnose|disableAuthUI|captureData|headless|strict|proxyServer|encrypted|noGzip|disableUpdateTips|proxifier2?)$/.test(m)
|
|
) {
|
|
config[m] = true;
|
|
} else if (m === 'keepProxyUI') {
|
|
config.keepProxyUI = true;
|
|
} else if (m === 'agent') {
|
|
useAgent = true;
|
|
} else if (m === 'disableUIAuth') {
|
|
config.disableAuthUI = true;
|
|
} else if (m === 'showPluginReq') {
|
|
config.showPluginReq = config.debugMode;
|
|
} else if (m === 'disableCustomCerts') {
|
|
config.disableCustomCerts = true;
|
|
} else if (m === 'nohost' || m === 'multienv' || m === 'multiEnv') {
|
|
config[m] = true;
|
|
config.multiEnv = true;
|
|
} else if (m === 'proxyOnly') {
|
|
config.pureProxy = true;
|
|
} else if (m === 'useMultipleRules' || m === 'enableMultipleRules') {
|
|
config.allowMultipleChoice = true;
|
|
} else if (m === 'disableMultipleRules') {
|
|
config.allowMultipleChoice = false;
|
|
} else if (
|
|
m === 'notAllowDisableRules' ||
|
|
m === 'notAllowedDisableRules'
|
|
) {
|
|
config.notAllowedDisableRules = true;
|
|
} else if (m === 'disableBackOption' || m === 'disabledBackOption') {
|
|
config.disabledBackOption = true;
|
|
} else if (
|
|
m === 'disableMultipleOption' ||
|
|
m === 'disabledMultipleOption'
|
|
) {
|
|
config.disabledMultipleOption = true;
|
|
} else if (
|
|
m === 'disableRulesOptions' ||
|
|
m === 'disabledRulesOptions'
|
|
) {
|
|
config.disabledRulesOptions = true;
|
|
config.disabledBackOption = true;
|
|
config.disabledMultipleOption = true;
|
|
config.notAllowedDisableRules = true;
|
|
} else if (
|
|
m === 'notAllowDisablePlugins' ||
|
|
m === 'notAllowedDisablePlugins'
|
|
) {
|
|
config.notAllowedDisablePlugins = true;
|
|
} else if (m === 'socks') {
|
|
config.socksMode = true;
|
|
} else if (m === 'network') {
|
|
config.networkMode = true;
|
|
config.captureData = true;
|
|
} else if (m === 'shadowRules') {
|
|
config.shadowRulesMode = true;
|
|
} else if (m === 'shadowRulesOnly') {
|
|
config.shadowRulesMode = true;
|
|
config.disableWebUI = true;
|
|
} else if (m === 'plugins') {
|
|
config.pluginsMode = true;
|
|
} else if (m === 'pluginsOnly') {
|
|
config.pluginsOnlyMode = true;
|
|
} else if (m === 'rules') {
|
|
config.rulesMode = true;
|
|
} else if (m === 'rulesOnly') {
|
|
config.rulesOnlyMode = true;
|
|
} else if (m === 'httpProxy') {
|
|
config.pureProxy = true;
|
|
} else if (
|
|
m === 'keepXFF' ||
|
|
m === 'x-forwarded-for' ||
|
|
m === 'forwardedFor'
|
|
) {
|
|
config.keepXFF = true;
|
|
} else if (m === 'x-forwarded-host') {
|
|
config.enableFwdHost = true;
|
|
} else if (m === 'x-forwarded-proto') {
|
|
config.enableFwdProto = true;
|
|
} else if (m === 'INADDR_ANY') {
|
|
config.INADDR_ANY = true;
|
|
} else if (m === 'buildIn' || m === 'build-in') {
|
|
process.env.PFORK_EXEC_PATH = process.execPath;
|
|
} else if (m === 'safe' || m === 'rejectUnauthorized') {
|
|
config.rejectUnauthorized = true;
|
|
} else if (m === 'persistentCapture') {
|
|
config.isEnableCapture = true;
|
|
config.persistentCapture = true;
|
|
} else if (['capture', 'intercept', 'enable-capture', 'enableCapture'].indexOf(m) !== -1) {
|
|
config.isEnableCapture = true;
|
|
} else if (['disable-capture', 'disableCapture'].indexOf(m) !== -1) {
|
|
config.isEnableCapture = false;
|
|
} else if (['http2', 'enable-http2', 'enableHttp2', 'enable-h2', 'enableH2'].indexOf(m) !== -1) {
|
|
config.isEnableHttp2 = true;
|
|
} else if (
|
|
['disable-http2', 'disableHttp2', 'disable-h2', 'disableH2'].indexOf(m) !== -1
|
|
) {
|
|
config.isEnableHttp2 = false;
|
|
} else if (m === 'hideLeftBar' || m === 'hideLeftMenu') {
|
|
config.hideLeftMenu = true;
|
|
} else if (DATA_KEY_RE.test(m)) {
|
|
var type = RegExp.$1;
|
|
var override = RegExp.$2 === '=';
|
|
var key = RegExp.$3.toLowerCase();
|
|
switch (type) {
|
|
case 'clientip':
|
|
config.overCipKey = override;
|
|
config.cipKey = key;
|
|
break;
|
|
case 'clientid':
|
|
config.overCidKey = override;
|
|
config.cidKey = key;
|
|
break;
|
|
default:
|
|
config.overTdKey = override;
|
|
config.tdKey = key;
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (config.headless) {
|
|
if (!config.pluginsMode) {
|
|
config.noGlobalPlugins = true;
|
|
}
|
|
config.pluginsOnlyMode = true;
|
|
config.disableWebUI = true;
|
|
delete config.rulesOnlyMode;
|
|
} else if (config.shadowRulesMode) {
|
|
config.networkMode = true;
|
|
delete config.rulesOnlyMode;
|
|
delete config.pluginsOnlyMode;
|
|
}
|
|
if (config.rulesOnlyMode) {
|
|
delete config.pluginsOnlyMode;
|
|
delete config.networkMode;
|
|
delete config.pluginsMode;
|
|
config.rulesMode = true;
|
|
} else if (config.pluginsOnlyMode) {
|
|
delete config.networkMode;
|
|
config.rulesMode = true;
|
|
config.pluginsMode = true;
|
|
} else if (config.networkMode) {
|
|
delete config.rulesMode;
|
|
delete config.pluginsMode;
|
|
} else if (config.rulesMode) {
|
|
delete config.pluginsMode;
|
|
delete config.networkMode;
|
|
}
|
|
}
|
|
if (/^\d+$/.test(newConf.timeout)) {
|
|
config.timeout = +newConf.timeout;
|
|
}
|
|
|
|
setGuestAuth(newConf);
|
|
config.disableAllRules = newConf.disableAllRules;
|
|
config.disableAllPlugins = newConf.disableAllPlugins;
|
|
if (newConf.replaceExistRule === false) {
|
|
config.replaceExistRule = false;
|
|
} else {
|
|
config.replaceExistRule = newConf.replaceRules;
|
|
}
|
|
if (newConf.replaceExistValue === false) {
|
|
config.replaceExistValue = false;
|
|
} else {
|
|
config.replaceExistValue = newConf.replaceValues;
|
|
}
|
|
if (newConf.certDir && typeof newConf.certDir === 'string') {
|
|
config.certDir = path.resolve(newConf.certDir);
|
|
}
|
|
var mw = newConf.middlewares || newConf.middleware;
|
|
if (typeof mw == 'string') {
|
|
config.middlewares = mw.trim().split(/\s*,\s*/);
|
|
}
|
|
var secureFilter = getSecureFilter(newConf);
|
|
if (typeof secureFilter === 'function') {
|
|
config.secureFilter = secureFilter;
|
|
}
|
|
if (typeof newConf.installPlugins === 'function' && typeof newConf.uninstallPlugins === 'function') {
|
|
config.installPlugins = newConf.installPlugins;
|
|
config.uninstallPlugins = newConf.uninstallPlugins;
|
|
config.epm = true;
|
|
}
|
|
if (typeof newConf.handleWebReq === 'function') {
|
|
config.handleWebReq = newConf.handleWebReq;
|
|
}
|
|
config.notUninstallPluginPaths = getPaths(newConf.notUninstallPluginPath || newConf.notUninstallPluginPaths);
|
|
config.pluginPaths = getPaths(newConf.pluginPaths || newConf.pluginsPath || newConf.pluginPath);
|
|
config.prePluginsPath = config.projectPluginPaths = getPaths(newConf.projectPluginPaths || newConf.projectPluginsPath || newConf.projectPluginPath);
|
|
config.accountPluginsPath = getPaths(newConf.accountPluginsPath);
|
|
config.customPluginPaths = getPaths(newConf.customPluginPaths || newConf.customPluginsPath || newConf.customPluginPath, true);
|
|
config.addon = getPaths(newConf.addonsPath || newConf.addonPath || newConf.addon);
|
|
if (config.accountPluginsPath) {
|
|
config.customPluginPaths = config.accountPluginsPath.concat(config.customPluginPaths || []);
|
|
}
|
|
if (config.customPluginPaths) {
|
|
config.prePluginsPath = (config.prePluginsPath || []).concat(config.customPluginPaths);
|
|
}
|
|
|
|
var pluginHost = newConf.pluginHost;
|
|
if (typeof pluginHost === 'string' && (pluginHost = pluginHost.trim())) {
|
|
pluginHost = qs.parse(pluginHost);
|
|
Object.keys(pluginHost).forEach(function (name) {
|
|
var host = pluginHost[name];
|
|
if (typeof host === 'string' && (host = host.trim())) {
|
|
host = host.toLowerCase().split(/\s*[|,&]\s*/);
|
|
host.forEach(function (h) {
|
|
config.pluginHostMap[h] = name;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
var port = getHostPort(newConf.port);
|
|
if (port) {
|
|
config.host = port.host;
|
|
config.uiport = config.port = port.port;
|
|
}
|
|
if (config.disableWebUI) {
|
|
config.notAllowedDisablePlugins = true;
|
|
} else {
|
|
var uiPort = getHostPort(newConf.uiport);
|
|
if (uiPort) {
|
|
var curHost = config.host || config.defaultHost;
|
|
if (curHost && uiPort.host && curHost !== uiPort.host) {
|
|
config.customUIPort = true;
|
|
} else {
|
|
config.customUIPort = uiPort.port != config.port;
|
|
}
|
|
config.uiport = uiPort.port;
|
|
config.uihost = uiPort.host;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!config.rulesMode) {
|
|
config.captureData = true;
|
|
}
|
|
|
|
if (!config.authKey) {
|
|
config.authKey = 'auto_' + (Date.now() + Math.random().toFixed(6));
|
|
}
|
|
config.middlewares = Array.isArray(config.middlewares)
|
|
? config.middlewares.map(resolvePath)
|
|
: [];
|
|
var uiHostList = (config.uiHostList = []);
|
|
if (typeof config.localUIHost === 'string') {
|
|
var localHostList = config.localUIHost.toLowerCase().split(/\s*[|,&]\s*/);
|
|
localHostList.forEach(function (hostname) {
|
|
hostname = getHostname(hostname);
|
|
if (hostname && LOCAL_UI_HOST_LIST.indexOf(hostname) == -1) {
|
|
uiHostList.push(hostname);
|
|
}
|
|
});
|
|
LOCAL_UI_HOST_LIST.push.apply(LOCAL_UI_HOST_LIST, uiHostList);
|
|
// 兼容老版本
|
|
config.customLocalUIHost = uiHostList[0];
|
|
}
|
|
|
|
config.setLocalUIHost = function (hostname) {
|
|
config.customLocalUIHost = getHostname(hostname);
|
|
};
|
|
|
|
config.localUIHost = 'local.whistlejs.com';
|
|
|
|
var isWebUIHost = function (host) {
|
|
if (
|
|
host === 'local.wproxy.org' ||
|
|
uiHostList.indexOf(host) !== -1 ||
|
|
(customUIHost && customUIHost.indexOf(host) !== -1)
|
|
) {
|
|
return true;
|
|
}
|
|
if (config.pureProxy) {
|
|
return false;
|
|
}
|
|
return LOCAL_UI_HOST_LIST.indexOf(host) !== -1;
|
|
};
|
|
var isLocalUIUrl = function (url) {
|
|
var host = getHostname(url);
|
|
return (
|
|
isWebUIHost(host) ||
|
|
!!config.pluginHostMap[host] ||
|
|
!!customHostPluginMap[host]
|
|
);
|
|
};
|
|
config.isLocalUIUrl = isLocalUIUrl;
|
|
exports.isWebUIHost = isWebUIHost;
|
|
exports.getPluginNameByHost = function (host) {
|
|
host = getHostname(host);
|
|
if (isWebUIHost(host)) {
|
|
return;
|
|
}
|
|
return config.pluginHostMap[host] || customHostPluginMap[host];
|
|
};
|
|
|
|
config.sockets = Math.max(parseInt(config.sockets, 10) || 0, 1);
|
|
if (config.strict) {
|
|
config.sockets = Math.max(config.sockets, 360);
|
|
}
|
|
var agentConfig = newAgentConf || {
|
|
maxSockets: config.sockets,
|
|
keepAlive: true,
|
|
keepAliveMsecs: KEEP_ALIVE_MSECS,
|
|
maxFreeSockets: 0
|
|
};
|
|
// node 11及以上版本缓存连接有问题,先禁掉
|
|
disableAgent = !newAgentConf && !useAgent && (disableAgent || config.debug);
|
|
config.httpAgent = disableAgent ? false : createAgent(agentConfig);
|
|
config.httpsAgent = disableAgent ? false : createAgent(agentConfig, true);
|
|
config.getSocksAgent = function (options) {
|
|
var key = getCacheKey(options, true);
|
|
var agent = socksAgents.get(key);
|
|
if (agent) {
|
|
return agent;
|
|
}
|
|
extend(options, agentConfig);
|
|
options.proxyPort = parseInt(options.proxyPort, 10) || 1080;
|
|
options.rejectUnauthorized = config.rejectUnauthorized;
|
|
options.localDNS = false;
|
|
options.auths = getAuths(options);
|
|
agent = options.isHttps
|
|
? new socks.HttpsAgent(options)
|
|
: new socks.HttpAgent(options);
|
|
socksAgents.set(key, agent);
|
|
agent.on('free', preventThrowOutError);
|
|
return agent;
|
|
};
|
|
var baseDir = config.baseDir
|
|
? path.resolve(getHomePath(config.baseDir), config.dataDirname)
|
|
: getDataDir(config.dataDirname);
|
|
var customDirs = path.join(baseDir, 'custom_dirs');
|
|
config.baseDir = baseDir;
|
|
config.storage = config.storage && encodeURIComponent(config.storage);
|
|
if (config.storage) {
|
|
baseDir = path.join(customDirs, config.storage);
|
|
}
|
|
fse.ensureDirSync(baseDir);
|
|
config.baseDirHash = createHash(baseDir);
|
|
if (config.password) {
|
|
config.passwordHash = createHash(config.password);
|
|
}
|
|
config.rulesDir = path.join(baseDir, 'rules');
|
|
config.valuesDir = path.join(baseDir, 'values');
|
|
config.propertiesDir = path.join(baseDir, 'properties');
|
|
if (config.storage && newConf.copy) {
|
|
var copyDir =
|
|
typeof newConf.copy == 'string' && encodeURIComponent(newConf.copy);
|
|
if (copyDir !== config.storage) {
|
|
var dataDir = copyDir ? path.join(customDirs, copyDir) : config.baseDir;
|
|
var rulesDir = path.join(dataDir, 'rules');
|
|
var valuesDir = path.join(dataDir, 'values');
|
|
var propsDir = path.join(dataDir, 'properties');
|
|
fse.ensureDirSync(rulesDir);
|
|
fse.ensureDirSync(valuesDir);
|
|
fse.ensureDirSync(propsDir);
|
|
fse.copySync(rulesDir, config.rulesDir);
|
|
fse.copySync(valuesDir, config.valuesDir);
|
|
fse.copySync(propsDir, config.propertiesDir);
|
|
}
|
|
}
|
|
var clientIdFile = path.join(config.baseDir, '.clientid');
|
|
var clientId = readFileText(clientIdFile);
|
|
if (!clientId) {
|
|
clientId = [
|
|
Date.now(),
|
|
Math.random(),
|
|
Math.random(),
|
|
Math.random(),
|
|
Math.random(),
|
|
Math.random()
|
|
].join();
|
|
fs.writeFileSync(clientIdFile, clientId);
|
|
}
|
|
config.clientId = clientId;
|
|
config.setModified = function (clientId, isRules) {
|
|
if (isRules) {
|
|
config.mrulesClientId = clientId || '';
|
|
config.mrulesTime = Date.now();
|
|
} else {
|
|
config.mvaluesClientId = clientId || '';
|
|
config.mvaluesTime = Date.now();
|
|
}
|
|
};
|
|
return config;
|
|
};
|