From 5bf6a553238923c658180ea8fb7764d78db4da59 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=A0=9A=E7=84=B6?= <yanran.wwj@alipay.com>
Date: Tue, 28 Aug 2018 15:47:04 +0800
Subject: [PATCH] feat: refact certMgr and httpsServerMgr into TS

---
 bin/anyproxy                                 |   2 +-
 lib/{certMgr.js => certMgr.ts}               |  54 ++++----
 lib/{httpsServerMgr.js => httpsServerMgr.ts} | 125 ++++++++++++-------
 lib/proxy.js                                 |   2 +-
 lib/requestHandler/UserReqHandler.ts         |   4 +-
 lib/webInterface.js                          |   2 +-
 test/server/server.js                        |   2 +-
 typings/index.d.ts                           |   4 +
 8 files changed, 123 insertions(+), 72 deletions(-)
 rename lib/{certMgr.js => certMgr.ts} (62%)
 rename lib/{httpsServerMgr.js => httpsServerMgr.ts} (59%)

diff --git a/bin/anyproxy b/bin/anyproxy
index d373535..34f1cbf 100755
--- a/bin/anyproxy
+++ b/bin/anyproxy
@@ -9,7 +9,7 @@ const program = require('commander'),
   util = require('../dist/util').default,
   rootCACheck = require('./rootCaCheck'),
   startServer = require('./startServer'),
-  certMgr = require('../dist/certMgr'),
+  certMgr = require('../dist/certMgr').default,
   logUtil = require('../dist/log');
 
 program
diff --git a/lib/certMgr.js b/lib/certMgr.ts
similarity index 62%
rename from lib/certMgr.js
rename to lib/certMgr.ts
index 07e73e4..3b92420 100644
--- a/lib/certMgr.js
+++ b/lib/certMgr.ts
@@ -1,12 +1,20 @@
-'use strict'
+'use strict';
 
-const EasyCert = require('node-easy-cert');
-const co = require('co');
-const os = require('os');
-const inquirer = require('inquirer');
+import * as EasyCert from 'node-easy-cert';
+import * as co from 'co';
+import * as os from 'os';
+import * as inquirer from 'inquirer';
+import util from './util';
+import logUtil from './log';
 
-const util = require('./util').default;
-const logUtil = require('./log');
+declare interface ICertMgr {
+  ifRootCAFileExists?: boolean;
+  generateRootCA?: ( cb: (error: boolean, keyPath: string, crtPath: string) => void ) => void;
+  getCAStatus?: () => Generator;
+  trustRootCA?: () => Generator;
+  getRootCAFilePath?: () => string;
+  getCertificate?: (serverName: string, cb: (err: Error, key: string, crt: string) => void) => void;
+}
 
 const options = {
   rootDirPath: util.getAnyProxyPath('certificates'),
@@ -15,24 +23,24 @@ const options = {
     { name: 'countryName', value: 'CN' },
     { name: 'organizationName', value: 'AnyProxy' },
     { shortName: 'ST', value: 'SH' },
-    { shortName: 'OU', value: 'AnyProxy SSL Proxy' }
-  ]
+    { shortName: 'OU', value: 'AnyProxy SSL Proxy' },
+  ],
 };
 
 const easyCert = new EasyCert(options);
-const crtMgr = util.merge({}, easyCert);
+const crtMgr: ICertMgr = util.merge({}, easyCert);
 
 // rename function
 crtMgr.ifRootCAFileExists = easyCert.isRootCAFileExists;
 
-crtMgr.generateRootCA = function (cb) {
+crtMgr.generateRootCA = function(cb: (error: boolean, keyPath: string, crtPath: string) => void): void {
   doGenerate(false);
 
   // set default common name of the cert
-  function doGenerate(overwrite) {
+  function doGenerate(overwrite: boolean): void {
     const rootOptions = {
       commonName: 'AnyProxy',
-      overwrite: !!overwrite
+      overwrite: !!overwrite,
     };
 
     easyCert.generateRootCA(rootOptions, (error, keyPath, crtPath) => {
@@ -41,10 +49,11 @@ crtMgr.generateRootCA = function (cb) {
   }
 };
 
-crtMgr.getCAStatus = function *() {
-  return co(function *() {
+crtMgr.getCAStatus = function *(): Generator {
+  return co(function *(): Generator {
     const result = {
       exist: false,
+      trusted: undefined,
     };
     const ifExist = easyCert.isRootCAFileExists();
     if (!ifExist) {
@@ -57,12 +66,12 @@ crtMgr.getCAStatus = function *() {
       return result;
     }
   });
-}
+};
 
 /**
  * trust the root ca by command
  */
-crtMgr.trustRootCA = function *() {
+crtMgr.trustRootCA = function *(): Generator {
   const platform = os.platform();
   const rootCAPath = crtMgr.getRootCAFilePath();
   const trustInquiry = [
@@ -70,8 +79,8 @@ crtMgr.trustRootCA = function *() {
       type: 'list',
       name: 'trustCA',
       message: 'The rootCA is not trusted yet, install it to the trust store now?',
-      choices: ['Yes', "No, I'll do it myself"]
-    }
+      choices: ['Yes', 'No, I\'ll do it myself'],
+    },
   ];
 
   if (platform === 'darwin') {
@@ -79,7 +88,8 @@ crtMgr.trustRootCA = function *() {
     if (answer.trustCA === 'Yes') {
       logUtil.info('About to trust the root CA, this may requires your password');
       // https://ss64.com/osx/security-cert.html
-      const result = util.execScriptSync(`sudo security add-trusted-cert -d -k /Library/Keychains/System.keychain ${rootCAPath}`);
+      const result =
+        (util.execScriptSync(`sudo security add-trusted-cert -d -k /Library/Keychains/System.keychain ${rootCAPath}`) as IExecScriptResult);
       if (result.status === 0) {
         logUtil.info('Root CA install, you are ready to intercept the https now');
       } else {
@@ -98,6 +108,6 @@ crtMgr.trustRootCA = function *() {
     logUtil.info('You can install the root CA manually.');
   }
   logUtil.info('The root CA file path is: ' + crtMgr.getRootCAFilePath());
-}
+};
 
-module.exports = crtMgr;
+export default crtMgr;
diff --git a/lib/httpsServerMgr.js b/lib/httpsServerMgr.ts
similarity index 59%
rename from lib/httpsServerMgr.js
rename to lib/httpsServerMgr.ts
index 6ce6380..6d29c22 100644
--- a/lib/httpsServerMgr.js
+++ b/lib/httpsServerMgr.ts
@@ -1,25 +1,46 @@
-'use strict'
+'use strict';
 
-//manage https servers
-const async = require('async'),
-  https = require('https'),
-  tls = require('tls'),
-  crypto = require('crypto'),
-  color = require('colorful'),
-  certMgr = require('./certMgr'),
-  logUtil = require('./log'),
-  util = require('./util').default,
-  wsServerMgr = require('./wsServerMgr'),
-  co = require('co'),
-  constants = require('constants'),
-  asyncTask = require('async-task-mgr');
+import * as async from 'async';
+import * as https from 'https';
+import * as http from 'http';
+import * as tls from 'tls';
+import * as crypto from 'crypto';
+import * as color from 'colorful';
+import * as WebSocket from 'ws';
+import * as constants from 'constants';
+import * as AsyncTask from 'async-task-mgr';
+import Recorder from './recorder';
+import certMgr from './certMgr';
+import logUtil from './log';
+import util from './util';
+import * as wsServerMgr from './wsServerMgr';
+import * as co from 'co';
 
-const createSecureContext = tls.createSecureContext || crypto.createSecureContext;
-//using sni to avoid multiple ports
-function SNIPrepareCert(serverName, SNICallback) {
-  let keyContent,
-    crtContent,
-    ctx;
+// // manage https servers
+// const async = require('async'),
+//   https = require('https'),
+//   tls = require('tls'),
+//   crypto = require('crypto'),
+//   color = require('colorful'),
+//   certMgr = require('./certMgr'),
+//   logUtil = require('./log'),
+//   util = require('./util').default,
+//   wsServerMgr = require('./wsServerMgr'),
+//   co = require('co'),
+//   constants = require('constants'),
+//   asyncTask = require('async-task-mgr');
+
+declare type THttpsRequestHanlder = (req: http.IncomingMessage, userRes: http.ServerResponse) => void;
+declare type TWsRequestHandler =
+  (userRule: AnyProxyRule, recorder: Recorder, wsClient: WebSocket, wsReq: http.IncomingMessage) => void;
+
+const createSecureContext = tls.createSecureContext || (crypto as any).createSecureContext;
+// using sni to avoid multiple ports
+function SNIPrepareCert(
+  serverName: string, SNICallback: (error: Error, ctx: tls.SecureContext) => void): void {
+  let keyContent;
+  let crtContent;
+  let ctx;
 
   async.series([
     (callback) => {
@@ -37,13 +58,13 @@ function SNIPrepareCert(serverName, SNICallback) {
       try {
         ctx = createSecureContext({
           key: keyContent,
-          cert: crtContent
+          cert: crtContent,
         });
         callback();
       } catch (e) {
         callback(e);
       }
-    }
+    },
   ], (err) => {
     if (!err) {
       const tipText = 'proxy server for __NAME established'.replace('__NAME', serverName);
@@ -56,18 +77,17 @@ function SNIPrepareCert(serverName, SNICallback) {
   });
 }
 
-//config.port - port to start https server
-//config.handler - request handler
-
-
 /**
  * Create an https server
  *
  * @param {object} config
- * @param {number} config.port
- * @param {function} config.handler
+ * @param {number} config.port port to start https server
+ * @param {function} config.handler  request handler
  */
-function createHttpsServer(config) {
+function createHttpsServer(config: {
+  port: number;
+  handler: THttpsRequestHanlder;
+}): Promise<https.Server> {
   if (!config || !config.port || !config.handler) {
     throw (new Error('please assign a port'));
   }
@@ -78,7 +98,7 @@ function createHttpsServer(config) {
         secureOptions: constants.SSL_OP_NO_SSLv3 || constants.SSL_OP_NO_TLSv1,
         SNICallback: SNIPrepareCert,
         key: keyContent,
-        cert: crtContent
+        cert: crtContent,
       }, config.handler).listen(config.port);
       resolve(server);
     });
@@ -92,7 +112,11 @@ function createHttpsServer(config) {
 * @param @required {number} config.port the port to listen on
 * @param @required {function} handler the handler of each connect
 */
-function createIPHttpsServer(config) {
+function createIPHttpsServer(config: {
+  port: number;
+  ip: string;
+  handler: THttpsRequestHanlder;
+}): Promise<https.Server> {
   if (!config || !config.port || !config.handler) {
     throw (new Error('please assign a port'));
   }
@@ -106,7 +130,7 @@ function createIPHttpsServer(config) {
       const server = https.createServer({
         secureOptions: constants.SSL_OP_NO_SSLv3 || constants.SSL_OP_NO_TLSv1,
         key: keyContent,
-        cert: crtContent
+        cert: crtContent,
       }, config.handler).listen(config.port);
 
       resolve(server);
@@ -122,26 +146,39 @@ function createIPHttpsServer(config) {
  * @param {function} config.handler handler to deal https request
  *
  */
-class httpsServerMgr {
-  constructor(config) {
+class HttpsServerMgr {
+  private instanceDefaultHost: string;
+  private httpsAsyncTask: AsyncTask;
+  private handler: THttpsRequestHanlder;
+  private wsHandler: TWsRequestHandler;
+  constructor(config: {
+    handler: THttpsRequestHanlder;
+    wsHandler: TWsRequestHandler;
+  }) {
     if (!config || !config.handler) {
       throw new Error('handler is required');
     }
     this.instanceDefaultHost = '127.0.0.1';
-    this.httpsAsyncTask = new asyncTask();
+    this.httpsAsyncTask = new AsyncTask();
     this.handler = config.handler;
-    this.wsHandler = config.wsHandler
+    this.wsHandler = config.wsHandler;
   }
 
-  getSharedHttpsServer(hostname) {
+  public getSharedHttpsServer(hostname: string): Promise<{
+      host: string;
+      port: number;
+    }> {
     // ip address will have a unique name
     const finalHost = util.isIpDomain(hostname) ? hostname : this.instanceDefaultHost;
 
     const self = this;
-    function prepareServer(callback) {
+    function prepareServer(callback: (e: Error, result?: {
+      host: string;
+      port: number;
+    }) => void): void {
       let instancePort;
       co(util.getFreePort)
-        .then(co.wrap(function *(port) {
+        .then(co.wrap(function *(port: number): Generator {
           instancePort = port;
           let httpsServer = null;
 
@@ -150,18 +187,18 @@ class httpsServerMgr {
             httpsServer = yield createIPHttpsServer({
               ip: hostname,
               port,
-              handler: self.handler
+              handler: self.handler,
             });
           } else {
             httpsServer = yield createHttpsServer({
               port,
-              handler: self.handler
+              handler: self.handler,
             });
           }
 
           wsServerMgr.getWsServer({
             server: httpsServer,
-            connHandler: self.wsHandler
+            connHandler: self.wsHandler,
           });
 
           httpsServer.on('upgrade', (req, cltSocket, head) => {
@@ -175,7 +212,7 @@ class httpsServerMgr {
           callback(null, result);
           return result;
         }))
-        .catch(e => {
+        .catch((e) => {
           callback(e);
         });
     }
@@ -194,4 +231,4 @@ class httpsServerMgr {
   }
 }
 
-export default httpsServerMgr;
+export default HttpsServerMgr;
diff --git a/lib/proxy.js b/lib/proxy.js
index 85c25bc..44062a0 100644
--- a/lib/proxy.js
+++ b/lib/proxy.js
@@ -4,7 +4,7 @@ const http = require('http'),
   https = require('https'),
   async = require('async'),
   color = require('colorful'),
-  certMgr = require('./certMgr'),
+  certMgr = require('./certMgr').default,
   Recorder = require('./recorder'),
   logUtil = require('./log'),
   util = require('./util').default,
diff --git a/lib/requestHandler/UserReqHandler.ts b/lib/requestHandler/UserReqHandler.ts
index d1be4e0..5f589d4 100644
--- a/lib/requestHandler/UserReqHandler.ts
+++ b/lib/requestHandler/UserReqHandler.ts
@@ -1,6 +1,6 @@
 /// <reference path="../../typings/index.d.ts" />
 
-declare interface ErrorResponse {
+declare interface IErrorResponse {
   statusCode: number;
   header: OneLevelObjectType;
   body: string;
@@ -201,7 +201,7 @@ function fetchRemoteResponse(
 /*
 * get error response for exception scenarios
 */
-function getErrorResponse(error: NodeJS.ErrnoException, fullUrl: string): ErrorResponse {
+function getErrorResponse(error: NodeJS.ErrnoException, fullUrl: string): IErrorResponse {
   // default error response
   const errorResponse = {
     statusCode: 500,
diff --git a/lib/webInterface.js b/lib/webInterface.js
index 003da6d..80fd15c 100644
--- a/lib/webInterface.js
+++ b/lib/webInterface.js
@@ -10,7 +10,7 @@ const express = require('express'),
   events = require('events'),
   qrCode = require('qrcode-npm'),
   util = require('./util').default,
-  certMgr = require('./certMgr'),
+  certMgr = require('./certMgr').default,
   wsServer = require('./wsServer'),
   juicer = require('juicer'),
   ip = require('ip'),
diff --git a/test/server/server.js b/test/server/server.js
index fcab17f..b7bab33 100644
--- a/test/server/server.js
+++ b/test/server/server.js
@@ -4,7 +4,7 @@ const koaBody = require('koa-body');
 const send = require('koa-send');
 const path = require('path');
 const https = require('https');
-const certMgr = require('../../lib/certMgr');
+const certMgr = require('../../lib/certMgr').default;
 const fs = require('fs');
 const nurl = require('url');
 const color = require('colorful');
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 12a6a93..abbcd08 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -78,4 +78,8 @@ declare interface AnyProxyReponseDetail {
 
 declare interface OneLevelObjectType {
   [key: string]: string | boolean | number
+}
+
+declare interface IExecScriptResult {
+  status: number;
 }
\ No newline at end of file