From f3aed00b344b75c225415a365eb5891444623ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=9A=E7=84=B6?= Date: Tue, 10 Jul 2018 22:01:06 +0800 Subject: [PATCH] basic support for typescript --- bin/anyproxy | 10 +- gulpfile.js | 21 +++ lib/configUtil.js | 16 -- lib/configUtil.ts | 20 +++ lib/log.js | 105 ------------- lib/log.ts | 125 ++++++++++++++++ lib/util.js | 323 ---------------------------------------- lib/util.ts | 366 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 10 +- proxy.js | 14 +- tsconfig.json | 22 +++ 11 files changed, 575 insertions(+), 457 deletions(-) create mode 100644 gulpfile.js delete mode 100644 lib/configUtil.js create mode 100644 lib/configUtil.ts delete mode 100644 lib/log.js create mode 100644 lib/log.ts delete mode 100644 lib/util.js create mode 100644 lib/util.ts create mode 100644 tsconfig.json diff --git a/bin/anyproxy b/bin/anyproxy index 108bc5d..09b4fe4 100755 --- a/bin/anyproxy +++ b/bin/anyproxy @@ -5,9 +5,9 @@ const program = require('commander'), color = require('colorful'), packageInfo = require('../package.json'), - ruleLoader = require('../lib/ruleLoader'), - util = require('../lib/util'), - logUtil = require('../lib/log'); + ruleLoader = require('../dist/ruleLoader'), + util = require('../dist/util'), + logUtil = require('../dist/log'); program .version(packageInfo.version) @@ -23,13 +23,13 @@ program .parse(process.argv); if (program.clear) { - require('../lib/certMgr').clearCerts(() => { + require('../dist/certMgr').clearCerts(() => { util.deleteFolderContentsRecursive(util.getAnyProxyPath('cache')); console.log(color.green('done !')); process.exit(0); }); } else if (program.root) { - require('../lib/certMgr').generateRootCA(() => { + require('../dist/certMgr').generateRootCA(() => { process.exit(0); }); } else { diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..a5668c0 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,21 @@ +const gulp = require('gulp'); +const ts = require('gulp-typescript'); + +const tsObject = ts.createProject('./tsconfig.json'); +const tsFileList = ['./*.ts', 'lib/*.ts', 'lib/**/*.js']; +/* +* transfer electron ts to js +*/ + +function compileTS() { + gulp.src(tsFileList, { base: './lib' }) + .pipe(tsObject()) + .pipe(gulp.dest('./dist/')); +} + +compileTS(); + +// gulp.watch(tsFileList, (event) => { +// console.info('file changed'); +// compileTS(); +// }); diff --git a/lib/configUtil.js b/lib/configUtil.js deleted file mode 100644 index 2b12773..0000000 --- a/lib/configUtil.js +++ /dev/null @@ -1,16 +0,0 @@ -/** -* a util to set and get all configuable constant -* -*/ -const path = require('path'); - -const USER_HOME = process.env.HOME || process.env.USERPROFILE; -const DEFAULT_ANYPROXY_HOME = path.join(USER_HOME, '/.anyproxy/'); - -/** -* return AnyProxy's home path -*/ -module.exports.getAnyProxyHome = function () { - const ENV_ANYPROXY_HOME = process.env.ANYPROXY_HOME || ''; - return ENV_ANYPROXY_HOME || DEFAULT_ANYPROXY_HOME; -} diff --git a/lib/configUtil.ts b/lib/configUtil.ts new file mode 100644 index 0000000..31000a1 --- /dev/null +++ b/lib/configUtil.ts @@ -0,0 +1,20 @@ +/** +* a util to set and get all configuable constant +* +*/ + +(function (): void { + const path = require('path'); + + const USER_HOME = process.env.HOME || process.env.USERPROFILE; + const DEFAULT_ANYPROXY_HOME = path.join(USER_HOME, '/.anyproxy/'); + + /** + * return AnyProxy's home path + */ + module.exports.getAnyProxyHome = function (): string { + const ENV_ANYPROXY_HOME = process.env.ANYPROXY_HOME || ''; + return ENV_ANYPROXY_HOME || DEFAULT_ANYPROXY_HOME; + } +})(); + diff --git a/lib/log.js b/lib/log.js deleted file mode 100644 index 18adb3e..0000000 --- a/lib/log.js +++ /dev/null @@ -1,105 +0,0 @@ -'use strict' - -const color = require('colorful'); -const util = require('./util'); - -let ifPrint = true; -let logLevel = 0; -const LogLevelMap = { - tip: 0, - system_error: 1, - rule_error: 2, - warn: 3, - debug: 4, -}; - -function setPrintStatus(status) { - ifPrint = !!status; -} - -function setLogLevel(level) { - logLevel = parseInt(level, 10); -} - -function printLog(content, type) { - if (!ifPrint) { - return; - } - - const timeString = util.formatDate(new Date(), 'YYYY-MM-DD hh:mm:ss'); - switch (type) { - case LogLevelMap.tip: { - if (logLevel > 0) { - return; - } - console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content)); - break; - } - - case LogLevelMap.system_error: { - if (logLevel > 1) { - return; - } - console.error(color.red(`[AnyProxy ERROR][${timeString}]: ` + content)); - break; - } - - case LogLevelMap.rule_error: { - if (logLevel > 2) { - return; - } - - console.error(color.red(`[AnyProxy RULE_ERROR][${timeString}]: ` + content)); - break; - } - - case LogLevelMap.warn: { - if (logLevel > 3) { - return; - } - - console.error(color.magenta(`[AnyProxy WARN][${timeString}]: ` + content)); - break; - } - - case LogLevelMap.debug: { - console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content)); - return; - } - - default : { - console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content)); - break; - } - } -} - -module.exports.printLog = printLog; - -module.exports.debug = (content) => { - printLog(content, LogLevelMap.debug); -}; - -module.exports.info = (content) => { - printLog(content, LogLevelMap.tip); -}; - -module.exports.warn = (content) => { - printLog(content, LogLevelMap.warn); -}; - -module.exports.error = (content) => { - printLog(content, LogLevelMap.error); -}; - -module.exports.ruleError = (content) => { - printLog(content, LogLevelMap.rule_error); -}; - -module.exports.setPrintStatus = setPrintStatus; -module.exports.setLogLevel = setLogLevel; -module.exports.T_TIP = LogLevelMap.tip; -module.exports.T_ERR = LogLevelMap.system_error; -module.exports.T_RULE_ERROR = LogLevelMap.rule_error; -module.exports.T_WARN = LogLevelMap.warn; -module.exports.T_DEBUG = LogLevelMap.debug; diff --git a/lib/log.ts b/lib/log.ts new file mode 100644 index 0000000..1cbf52c --- /dev/null +++ b/lib/log.ts @@ -0,0 +1,125 @@ +'use strict'; + + import * as color from 'colorful'; + import util from './util'; + + let ifPrint = true; + let logLevel = 0; + enum LogLevelMap { + tip = 0, + system_error = 1, + error = 1, + rule_error = 2, + warn = 3, + debug = 4, + }; + + function setPrintStatus(status: boolean): void { + ifPrint = !!status; + } + + function setLogLevel(level: string): void { + logLevel = parseInt(level, 10); + } + + function printLog(content: string, type?: LogLevelMap) { + if (!ifPrint) { + return; + } + + const timeString = util.formatDate(new Date(), 'YYYY-MM-DD hh:mm:ss'); + switch (type) { + case LogLevelMap.tip: { + if (logLevel > 0) { + return; + } + console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content)); + break; + } + + case LogLevelMap.system_error: { + if (logLevel > 1) { + return; + } + console.error(color.red(`[AnyProxy ERROR][${timeString}]: ` + content)); + break; + } + + case LogLevelMap.rule_error: { + if (logLevel > 2) { + return; + } + + console.error(color.red(`[AnyProxy RULE_ERROR][${timeString}]: ` + content)); + break; + } + + case LogLevelMap.warn: { + if (logLevel > 3) { + return; + } + + console.error(color.magenta(`[AnyProxy WARN][${timeString}]: ` + content)); + break; + } + + case LogLevelMap.debug: { + console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content)); + return; + } + + default: { + console.log(color.cyan(`[AnyProxy Log][${timeString}]: ` + content)); + break; + } + } + } + + module.exports.printLog = printLog; + + function debug (content): void { + printLog(content, LogLevelMap.debug); + }; + + function info (content): void { + printLog(content, LogLevelMap.tip); + }; + + function warn (content) { + printLog(content, LogLevelMap.warn); + }; + + function error (content) { + printLog(content, LogLevelMap.system_error); + }; + + function ruleError (content) { + printLog(content, LogLevelMap.rule_error); + }; + + module.exports.setPrintStatus = setPrintStatus; + module.exports.setLogLevel = setLogLevel; + module.exports.T_TIP = LogLevelMap.tip; + module.exports.T_ERR = LogLevelMap.system_error; + module.exports.T_RULE_ERROR = LogLevelMap.rule_error; + module.exports.T_WARN = LogLevelMap.warn; + module.exports.T_DEBUG = LogLevelMap.debug; + +const LogUtil = { + setPrintStatus, + setLogLevel, + printLog, + debug, + info, + warn, + error, + ruleError, + T_TIP: LogLevelMap.tip, + T_ERR: LogLevelMap.error, + T_RULE_ERROR: LogLevelMap.rule_error, + T_WARN: LogLevelMap.warn, + T_DEBUG: LogLevelMap.debug +} + +export default LogUtil; +module.exports = LogUtil; diff --git a/lib/util.js b/lib/util.js deleted file mode 100644 index a09e924..0000000 --- a/lib/util.js +++ /dev/null @@ -1,323 +0,0 @@ -'use strict'; - -const fs = require('fs'), - path = require('path'), - mime = require('mime-types'), - color = require('colorful'), - crypto = require('crypto'), - Buffer = require('buffer').Buffer, - logUtil = require('./log'); -const networkInterfaces = require('os').networkInterfaces(); - -// {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"} -module.exports.lower_keys = (obj) => { - for (const key in obj) { - const val = obj[key]; - delete obj[key]; - - obj[key.toLowerCase()] = val; - } - - return obj; -}; - -module.exports.merge = function (baseObj, extendObj) { - for (const key in extendObj) { - baseObj[key] = extendObj[key]; - } - - return baseObj; -}; - -function getUserHome() { - return process.env.HOME || process.env.USERPROFILE; -} -module.exports.getUserHome = getUserHome; - -function getAnyProxyHome() { - const home = path.join(getUserHome(), '/.anyproxy/'); - if (!fs.existsSync(home)) { - fs.mkdirSync(home); - } - return home; -} -module.exports.getAnyProxyHome = getAnyProxyHome; - -module.exports.getAnyProxyPath = function (pathName) { - const home = getAnyProxyHome(); - const targetPath = path.join(home, pathName); - if (!fs.existsSync(targetPath)) { - fs.mkdirSync(targetPath); - } - return targetPath; -} - -module.exports.simpleRender = function (str, object, regexp) { - return String(str).replace(regexp || (/\{\{([^{}]+)\}\}/g), (match, name) => { - if (match.charAt(0) === '\\') { - return match.slice(1); - } - return (object[name] != null) ? object[name] : ''; - }); -}; - -module.exports.filewalker = function (root, cb) { - root = root || process.cwd(); - - const ret = { - directory: [], - file: [] - }; - - fs.readdir(root, (err, list) => { - if (list && list.length) { - list.map((item) => { - const fullPath = path.join(root, item), - stat = fs.lstatSync(fullPath); - - if (stat.isFile()) { - ret.file.push({ - name: item, - fullPath - }); - } else if (stat.isDirectory()) { - ret.directory.push({ - name: item, - fullPath - }); - } - }); - } - - cb && cb.apply(null, [null, ret]); - }); -}; - -/* -* 获取文件所对应的content-type以及content-length等信息 -* 比如在useLocalResponse的时候会使用到 -*/ -module.exports.contentType = function (filepath) { - return mime.contentType(path.extname(filepath)); -}; - -/* -* 读取file的大小,以byte为单位 -*/ -module.exports.contentLength = function (filepath) { - try { - const stat = fs.statSync(filepath); - return stat.size; - } catch (e) { - logUtil.printLog(color.red('\nfailed to ready local file : ' + filepath)); - logUtil.printLog(color.red(e)); - return 0; - } -}; - -/* -* remove the cache before requiring, the path SHOULD BE RELATIVE TO UTIL.JS -*/ -module.exports.freshRequire = function (modulePath) { - delete require.cache[require.resolve(modulePath)]; - return require(modulePath); -}; - -/* -* format the date string -* @param date Date or timestamp -* @param formatter YYYYMMDDHHmmss -*/ -module.exports.formatDate = function (date, formatter) { - if (typeof date !== 'object') { - date = new Date(date); - } - const transform = function (value) { - return value < 10 ? '0' + value : value; - }; - return formatter.replace(/^YYYY|MM|DD|hh|mm|ss/g, (match) => { - switch (match) { - case 'YYYY': - return transform(date.getFullYear()); - case 'MM': - return transform(date.getMonth() + 1); - case 'mm': - return transform(date.getMinutes()); - case 'DD': - return transform(date.getDate()); - case 'hh': - return transform(date.getHours()); - case 'ss': - return transform(date.getSeconds()); - default: - return '' - } - }); -}; - - -/** -* get headers(Object) from rawHeaders(Array) -* @param rawHeaders [key, value, key2, value2, ...] - -*/ - -module.exports.getHeaderFromRawHeaders = function (rawHeaders) { - const headerObj = {}; - const _handleSetCookieHeader = function (key, value) { - if (headerObj[key].constructor === Array) { - headerObj[key].push(value); - } else { - headerObj[key] = [headerObj[key], value]; - } - }; - - if (!!rawHeaders) { - for (let i = 0; i < rawHeaders.length; i += 2) { - const key = rawHeaders[i]; - let value = rawHeaders[i + 1]; - - if (typeof value === 'string') { - value = value.replace(/\0+$/g, ''); // 去除 \u0000的null字符串 - } - - if (!headerObj[key]) { - headerObj[key] = value; - } else { - // headers with same fields could be combined with comma. Ref: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 - // set-cookie should NOT be combined. Ref: https://tools.ietf.org/html/rfc6265 - if (key.toLowerCase() === 'set-cookie') { - _handleSetCookieHeader(key, value); - } else { - headerObj[key] = headerObj[key] + ',' + value; - } - } - } - } - return headerObj; -}; - -module.exports.getAllIpAddress = function getAllIpAddress() { - const allIp = []; - - Object.keys(networkInterfaces).map((nic) => { - networkInterfaces[nic].filter((detail) => { - if (detail.family.toLowerCase() === 'ipv4') { - allIp.push(detail.address); - } - }); - }); - - return allIp.length ? allIp : ['127.0.0.1']; -}; - -function deleteFolderContentsRecursive(dirPath, ifClearFolderItself) { - if (!dirPath.trim() || dirPath === '/') { - throw new Error('can_not_delete_this_dir'); - } - - if (fs.existsSync(dirPath)) { - fs.readdirSync(dirPath).forEach((file) => { - const curPath = path.join(dirPath, file); - if (fs.lstatSync(curPath).isDirectory()) { - deleteFolderContentsRecursive(curPath, true); - } else { // delete all files - fs.unlinkSync(curPath); - } - }); - - if (ifClearFolderItself) { - try { - // ref: https://github.com/shelljs/shelljs/issues/49 - const start = Date.now(); - while (true) { - try { - fs.rmdirSync(dirPath); - break; - } catch (er) { - if (process.platform === 'win32' && (er.code === 'ENOTEMPTY' || er.code === 'EBUSY' || er.code === 'EPERM')) { - // Retry on windows, sometimes it takes a little time before all the files in the directory are gone - if (Date.now() - start > 1000) throw er; - } else if (er.code === 'ENOENT') { - break; - } else { - throw er; - } - } - } - } catch (e) { - throw new Error('could not remove directory (code ' + e.code + '): ' + dirPath); - } - } - } -} - -module.exports.deleteFolderContentsRecursive = deleteFolderContentsRecursive; - -module.exports.getFreePort = function () { - return new Promise((resolve, reject) => { - const server = require('net').createServer(); - server.unref(); - server.on('error', reject); - server.listen(0, () => { - const port = server.address().port; - server.close(() => { - resolve(port); - }); - }); - }); -} - -module.exports.collectErrorLog = function (error) { - if (error && error.code && error.toString()) { - return error.toString(); - } else { - let result = [error, error.stack].join('\n'); - try { - const errorString = error.toString(); - if (errorString.indexOf('You may only yield a function') >= 0) { - result = 'Function is not yieldable. Did you forget to provide a generator or promise in rule file ? \nFAQ http://anyproxy.io/4.x/#faq'; - } - } catch (e) {} - return result - } -} - -module.exports.isFunc = function (source) { - return source && Object.tostring.call(source) === '[object Function]'; -}; - -/** -* @param {object} content -* @returns the size of the content -*/ -module.exports.getByteSize = function (content) { - return Buffer.byteLength(content); -}; - -/* -* identify whether the -*/ -module.exports.isIpDomain = function (domain) { - if (!domain) { - return false; - } - const ipReg = /^\d+?\.\d+?\.\d+?\.\d+?$/; - - return ipReg.test(domain); -}; - -/** -* To generic a Sec-WebSocket-Accept value -* 1. append the `Sec-WebSocket-Key` request header with `matic string` -* 2. get sha1 hash of the string -* 3. get base64 of the sha1 hash -*/ -module.exports.genericWsSecAccept = function (wsSecKey) { - // the string to generate the Sec-WebSocket-Accept - const magicString = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; - const targetString = `${wsSecKey}${magicString}`; - const shasum = crypto.createHash('sha1'); - shasum.update(targetString); - return shasum.digest('base64'); -} diff --git a/lib/util.ts b/lib/util.ts new file mode 100644 index 0000000..cfc31ed --- /dev/null +++ b/lib/util.ts @@ -0,0 +1,366 @@ +'use strict'; + +// (function (): void { +// const fs = require('fs'), +// path = require('path'), +// mime = require('mime-types'), +// color = require('colorful'), +// crypto = require('crypto'), +// Buffer = require('buffer').Buffer, +// logUtil = require('./log'); +import * as fs from 'fs'; +import * as path from 'path'; +import * as mime from 'mime-types'; +import * as color from 'colorful'; +import {Buffer} from 'buffer'; +import * as crypto from 'crypto'; +// import buffer from 'buffer'; +import logUtil from './log'; +// const Buffer = buffer.Buffer; + + const networkInterfaces = require('os').networkInterfaces(); + + // {"Content-Encoding":"gzip"} --> {"content-encoding":"gzip"} + function lower_keys (obj: object): object { + for (const key in obj) { + const val = obj[key]; + delete obj[key]; + + obj[key.toLowerCase()] = val; + } + + return obj; + }; + + function merge (baseObj: object, extendObj: object): object { + for (const key in extendObj) { + baseObj[key] = extendObj[key]; + } + + return baseObj; + }; + + function getUserHome(): string { + return process.env.HOME || process.env.USERPROFILE; + } + + function getAnyProxyHome(): string { + const home = path.join(getUserHome(), '/.anyproxy/'); + if (!fs.existsSync(home)) { + fs.mkdirSync(home); + } + return home; + } + + function getAnyProxyPath (pathName): string { + const home = getAnyProxyHome(); + const targetPath = path.join(home, pathName); + if (!fs.existsSync(targetPath)) { + fs.mkdirSync(targetPath); + } + return targetPath; + } + + /** + * 简易字符串render替换 + */ + function simpleRender (str: string, object: object, regexp: RegExp) { + return String(str).replace(regexp || (/\{\{([^{}]+)\}\}/g), (match, name) => { + if (match.charAt(0) === '\\') { + return match.slice(1); + } + return (object[name] != null) ? object[name] : ''; + }); + }; + + /** + * 读取指定目录下的子目录 + */ + function filewalker (root: string, cb: Function) { + root = root || process.cwd(); + + const ret = { + directory: [], + file: [] + }; + + fs.readdir(root, (err, list) => { + if (list && list.length) { + list.map((item) => { + const fullPath = path.join(root, item), + stat = fs.lstatSync(fullPath); + + if (stat.isFile()) { + ret.file.push({ + name: item, + fullPath + }); + } else if (stat.isDirectory()) { + ret.directory.push({ + name: item, + fullPath + }); + } + }); + } + + cb && cb.apply(null, [null, ret]); + }); + }; + + /* + * 获取文件所对应的content-type以及content-length等信息 + * 比如在useLocalResponse的时候会使用到 + */ + function contentType (filepath: string): string { + return mime.contentType(path.extname(filepath)); + }; + + /* + * 读取file的大小,以byte为单位 + */ + function contentLength (filepath: string): number { + try { + const stat = fs.statSync(filepath); + return stat.size; + } catch (e) { + logUtil.printLog(color.red('\nfailed to ready local file : ' + filepath)); + logUtil.printLog(color.red(e)); + return 0; + } + }; + + /* + * remove the cache before requiring, the path SHOULD BE RELATIVE TO UTIL.JS + */ + function freshRequire (modulePath: string): NodeModule { + delete require.cache[require.resolve(modulePath)]; + return require(modulePath); + }; + + /* + * format the date string + * @param date Date or timestamp + * @param formatter YYYYMMDDHHmmss + */ + function formatDate (date: Date | number, formatter: string): string { + let finalDate : Date; + if (typeof date !== 'object') { + finalDate = new Date(date); + } else { + finalDate = date; + } + const transform = function (value) { + return value < 10 ? '0' + value : value; + }; + return formatter.replace(/^YYYY|MM|DD|hh|mm|ss/g, (match) => { + switch (match) { + case 'YYYY': + return transform(finalDate.getFullYear()); + case 'MM': + return transform(finalDate.getMonth() + 1); + case 'mm': + return transform(finalDate.getMinutes()); + case 'DD': + return transform(finalDate.getDate()); + case 'hh': + return transform(finalDate.getHours()); + case 'ss': + return transform(finalDate.getSeconds()); + default: + return '' + } + }); + }; + + + /** + * get headers(Object) from rawHeaders(Array) + * @param rawHeaders [key, value, key2, value2, ...] + + */ + + function getHeaderFromRawHeaders (rawHeaders: Array) { + const headerObj = {}; + const _handleSetCookieHeader = function (key, value) { + if (headerObj[key].constructor === Array) { + headerObj[key].push(value); + } else { + headerObj[key] = [headerObj[key], value]; + } + }; + + if (!!rawHeaders) { + for (let i = 0; i < rawHeaders.length; i += 2) { + const key = rawHeaders[i]; + let value = rawHeaders[i + 1]; + + if (typeof value === 'string') { + value = value.replace(/\0+$/g, ''); // 去除 \u0000的null字符串 + } + + if (!headerObj[key]) { + headerObj[key] = value; + } else { + // headers with same fields could be combined with comma. Ref: https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + // set-cookie should NOT be combined. Ref: https://tools.ietf.org/html/rfc6265 + if (key.toLowerCase() === 'set-cookie') { + _handleSetCookieHeader(key, value); + } else { + headerObj[key] = headerObj[key] + ',' + value; + } + } + } + } + return headerObj; + }; + + function getAllIpAddress (): Array { + const allIp = []; + + Object.keys(networkInterfaces).map((nic) => { + networkInterfaces[nic].filter((detail) => { + if (detail.family.toLowerCase() === 'ipv4') { + allIp.push(detail.address); + } + }); + }); + + return allIp.length ? allIp : ['127.0.0.1']; + }; + + function deleteFolderContentsRecursive(dirPath: string, ifClearFolderItself: boolean): void { + if (!dirPath.trim() || dirPath === '/') { + throw new Error('can_not_delete_this_dir'); + } + + if (fs.existsSync(dirPath)) { + fs.readdirSync(dirPath).forEach((file) => { + const curPath = path.join(dirPath, file); + if (fs.lstatSync(curPath).isDirectory()) { + deleteFolderContentsRecursive(curPath, true); + } else { // delete all files + fs.unlinkSync(curPath); + } + }); + + if (ifClearFolderItself) { + try { + // ref: https://github.com/shelljs/shelljs/issues/49 + const start = Date.now(); + while (true) { + try { + fs.rmdirSync(dirPath); + break; + } catch (er) { + if (process.platform === 'win32' && (er.code === 'ENOTEMPTY' || er.code === 'EBUSY' || er.code === 'EPERM')) { + // Retry on windows, sometimes it takes a little time before all the files in the directory are gone + if (Date.now() - start > 1000) throw er; + } else if (er.code === 'ENOENT') { + break; + } else { + throw er; + } + } + } + } catch (e) { + throw new Error('could not remove directory (code ' + e.code + '): ' + dirPath); + } + } + } + } + + function getFreePort (): Promise { + return new Promise((resolve, reject) => { + const server = require('net').createServer(); + server.unref(); + server.on('error', reject); + server.listen(0, () => { + const port = server.address().port; + server.close(() => { + resolve(port); + }); + }); + }); + } + + function collectErrorLog (error: any): string { + if (error && error.code && error.toString()) { + return error.toString(); + } else { + let result = [error, error.stack].join('\n'); + try { + const errorString = error.toString(); + if (errorString.indexOf('You may only yield a function') >= 0) { + result = 'Function is not yieldable. Did you forget to provide a generator or promise in rule file ? \nFAQ http://anyproxy.io/4.x/#faq'; + } + } catch (e) {} + return result + } + } + + function isFunc (source: object): boolean { + return source && Object.prototype.toString.call(source) === '[object Function]'; + }; + + /** + * @param {object} content + * @returns the size of the content + */ + function getByteSize (content: Buffer): number { + return Buffer.byteLength(content); + }; + + /* + * identify whether the + */ + function isIpDomain (domain: string): boolean { + if (!domain) { + return false; + } + const ipReg = /^\d+?\.\d+?\.\d+?\.\d+?$/; + + return ipReg.test(domain); + }; + + /** + * To generic a Sec-WebSocket-Accept value + * 1. append the `Sec-WebSocket-Key` request header with `matic string` + * 2. get sha1 hash of the string + * 3. get base64 of the sha1 hash + */ + function genericWsSecAccept (wsSecKey) { + // the string to generate the Sec-WebSocket-Accept + const magicString = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; + const targetString = `${wsSecKey}${magicString}`; + const shasum = crypto.createHash('sha1'); + shasum.update(targetString); + return shasum.digest('base64'); + } + +const Util = { + lower_keys, + merge, + getUserHome, + contentType, + getAnyProxyPath, + getAnyProxyHome, + simpleRender, + filewalker, + contentLength, + freshRequire, + getHeaderFromRawHeaders, + getAllIpAddress, + getFreePort, + collectErrorLog, + isFunc, + isIpDomain, + getByteSize, + deleteFolderContentsRecursive, + genericWsSecAccept, + formatDate +} + +export default Util; +module.exports = Util; +// })(); \ No newline at end of file diff --git a/package.json b/package.json index e28527a..c9e3f0f 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "ws": "^5.1.0" }, "devDependencies": { + "@types/node": "^8.10.21", "antd": "^2.5.0", "autoprefixer": "^6.4.1", "babel-core": "^6.14.0", @@ -60,6 +61,8 @@ "eslint-plugin-react": "^7.4.0", "extract-text-webpack-plugin": "^3.0.2", "file-loader": "^0.9.0", + "gulp": "^3.9.1", + "gulp-typescript": "^5.0.0-alpha.3", "https-proxy-agent": "^1.0.0", "jasmine": "^2.5.3", "koa": "^1.2.1", @@ -85,12 +88,16 @@ "stream-equal": "0.1.8", "style-loader": "^0.13.1", "svg-inline-loader": "^0.7.1", + "typescript": "^2.9.2", "url-loader": "^0.5.7", + "vuepress": "^0.10.2", "webpack": "^3.10.0", "worker-loader": "^0.7.1" }, "scripts": { "prepublish": "npm run buildweb", + "build": "NODE_ENV=production && node gulpfile.js", + "dev": "NODE_ENV=test && node gulpfile.js", "test": "node test.js", "lint": "eslint .", "testserver": "node test/server/startServer.js", @@ -98,7 +105,8 @@ "buildweb": "NODE_ENV=production webpack --config web/webpack.config.js --colors", "webserver": "NODE_ENV=test webpack --config web/webpack.config.js --colors --watch", "doc:serve": "node build_scripts/prebuild-doc.js && gitbook serve ./docs-src ./docs --log debug", - "doc:build": "./build_scripts/build-doc-site.sh" + "doc:build": "./build_scripts/build-doc-site.sh", + "doc:dev": "node build_scripts/prebuild-doc.js && vuepress dev docs-src" }, "pre-commit": [ "lint" diff --git a/proxy.js b/proxy.js index a80ca2b..5318982 100644 --- a/proxy.js +++ b/proxy.js @@ -4,14 +4,14 @@ const http = require('http'), https = require('https'), async = require('async'), color = require('colorful'), - certMgr = require('./lib/certMgr'), - Recorder = require('./lib/recorder'), - logUtil = require('./lib/log'), - util = require('./lib/util'), + certMgr = require('./dist/certMgr'), + Recorder = require('./dist/recorder'), + logUtil = require('./dist/log'), + util = require('./dist/util'), events = require('events'), co = require('co'), - WebInterface = require('./lib/webInterface'), - wsServerMgr = require('./lib/wsServerMgr'), + WebInterface = require('./dist/webInterface'), + wsServerMgr = require('./dist/wsServerMgr'), ThrottleGroup = require('stream-throttle').ThrottleGroup; // const memwatch = require('memwatch-next'); @@ -382,6 +382,6 @@ module.exports.ProxyServer = ProxyServer; module.exports.ProxyRecorder = Recorder; module.exports.ProxyWebServer = WebInterface; module.exports.utils = { - systemProxyMgr: require('./lib/systemProxyMgr'), + systemProxyMgr: require('./dist/systemProxyMgr'), certMgr, }; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7e454fc --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOnSave": true, + "compilerOptions": { + "moduleResolution": "node", + "target": "es5", + "module": "commonjs", + "baseUrl": "./lib", + "allowJs": true, + "lib": ["es6"], + "allowSyntheticDefaultImports": true, + "noUnusedLocals": true, + "outDir": "./dist" + }, + "exclude": [ + "node_modules", + "./dist" + ], + "include": [ + "./lib/*", + "./bin/*" + ] +}