feat: add monitor
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..557edf3
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,15 @@
+language: node_js
+
+node_js:
+  - "8"
+
+env:
+  matrix:
+    - TEST_TYPE=build
+
+
+install:
+  - npm install
+
+script:
+  - npm run build
diff --git a/src/errors/ajaxErrors.ts b/src/errors/ajaxErrors.ts
new file mode 100644
index 0000000..6333430
--- /dev/null
+++ b/src/errors/ajaxErrors.ts
@@ -0,0 +1,63 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+import { ErrorsCategory } from '../services/config';
+export default class XHRError {
+
+  constructor() {
+    this.handleError();
+  }
+
+  handleError(){
+    if(!window.XMLHttpRequest){
+      return;
+    }
+    let xhrSend = XMLHttpRequest.prototype.send;
+    let _handleEvent = (event: Event) => {
+        try {
+          if (event && event.currentTarget && event.currentTarget.status !== 200) {
+            // this.category = ErrorsCategory.AJAX_ERROR;
+            // this.msg = event.target.response;
+            // this.url = event.target.responseURL;
+            // this.error = {
+            // status: event.target.status,
+            //     statusText: event.target.statusText
+            // };
+            // this.recordError();
+          }
+        } catch (error) {
+          console.log(error);
+        }
+      };
+      XMLHttpRequest.prototype.send = function(){
+          if (this.addEventListener){
+              this.addEventListener('error', _handleEvent);
+              this.addEventListener('load', _handleEvent);
+              this.addEventListener('abort', _handleEvent);
+          } else {
+              let tempStateChange = this.onreadystatechange;
+              this.onreadystatechange = function(event: any){
+                  tempStateChange.apply(this,arguments);
+                  if (this.readyState === 4) {
+                      _handleEvent(event);
+                  }
+              }
+          }
+          return xhrSend.apply(this,arguments);
+      }
+  }
+  
+}
\ No newline at end of file
diff --git a/src/errors/jsErrors.ts b/src/errors/jsErrors.ts
new file mode 100644
index 0000000..b16035f
--- /dev/null
+++ b/src/errors/jsErrors.ts
@@ -0,0 +1,36 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+export default class JSErrors{
+
+  constructor() {
+    this.handleErrors();
+  }
+
+  private handleErrors() {
+    window.onerror = (msg, url, line, col, error) => {
+      try {
+        console.log('msg', msg);
+        console.log(url);
+        console.log(line);
+        console.log(error);
+      } catch(error) {
+        console.log("js errors",error);
+      }
+    }
+  }
+}
diff --git a/src/monitor.ts b/src/monitor.ts
index c84030c..5b0b817 100644
--- a/src/monitor.ts
+++ b/src/monitor.ts
@@ -14,10 +14,35 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import JSErrors from './errors/jsErrors';
+import { TClientMonitor, TErrorsType } from './types';
 
 export default class ClientMonitor {
 
-  public init() {
-    // console.log('enter');
+  private errorTypes: TErrorsType = {
+    jsErrors: true,
+    promiseErrors: true,
+    consoleErrors: false,
+    vueErrors: false,
+    reactErrors: false,
+    ajaxErrors: true,
+    resourceErrors: true,
+  };
+
+  public init(options: TClientMonitor) {
+    this.errorTypes = options;
+    if (this.errorTypes.jsErrors) {
+      this.errorTypes.jsErrors = options.jsErrors;
+      new JSErrors();
+    }
+    if (this.errorTypes.promiseErrors) {
+      this.errorTypes.promiseErrors = options.promiseErrors || this.errorTypes.promiseErrors;
+    }
+    if (this.errorTypes.resourceErrors) {
+      this.errorTypes.resourceErrors = options.resourceErrors;
+    }
+    if (this.errorTypes.ajaxErrors) {
+      this.errorTypes.ajaxErrors = options.ajaxErrors || this.errorTypes.ajaxErrors;
+    }
   }
 }
diff --git a/src/services/TaskQueue.ts b/src/services/TaskQueue.ts
new file mode 100644
index 0000000..4c8e516
--- /dev/null
+++ b/src/services/TaskQueue.ts
@@ -0,0 +1,33 @@
+import API from "./api.js";
+
+/**
+ * 消息队列
+ */
+var TaskQueue = {
+
+    queues:[],  //待处理消息列表
+
+    /**
+     * 添加消息
+     * @param {*} reportUrl 上报url
+     * @param {*} data 上报数据
+     */
+    add:function(reportUrl,data){
+        this.queues.push({reportUrl,data});
+    },
+
+    /**
+     * 统一上报
+     */
+    fire:function(){
+        if(!this.queues || this.queues.length === 0){
+            return;
+        }
+        let item = this.queues[0];
+        item.reportUrl && new API(item.reportUrl).report(item.data);
+        this.queues.splice(0,1);
+        this.fire(); //递归
+    }
+};
+
+export default TaskQueue;
\ No newline at end of file
diff --git a/src/services/baseMonitor.ts b/src/services/baseMonitor.ts
new file mode 100644
index 0000000..cd04dc9
--- /dev/null
+++ b/src/services/baseMonitor.ts
@@ -0,0 +1,152 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+import { ErrorLevelEnum,ErrorCategoryEnum } from "./config";
+import utils from "./utils";
+import TaskQueue from "./TaskQueue"
+
+/**
+ * 监控基类
+ */
+class BaseMonitor {
+
+    /**
+     * 上报错误地址
+     * @param {*} params { reportUrl,extendsInfo }
+     */
+    constructor(params){
+        this.category = ErrorCategoryEnum.UNKNOW_ERROR; //错误类型
+        this.level = ErrorLevelEnum.INFO; //错误等级
+        this.msg = "";  //错误信息
+        this.url = "";  //错误信息地址
+        this.line = ""; //行数
+        this.col = "";  //列数
+        this.errorObj = "";  //错误堆栈
+
+        this.reportUrl = params.reportUrl; //上报错误地址
+        this.extendsInfo = params.extendsInfo; //扩展信息
+    }
+
+    /**
+     * 记录错误信息
+     */
+    recordError(){
+        this.handleRecordError();
+        //延迟记录日志
+        setTimeout(()=>{
+            TaskQueue.fire();
+        },100);
+    }
+
+    /**
+     * 处理记录日志
+     */
+    handleRecordError(){
+        try {
+            if(!this.msg){
+                return;
+            }
+            //过滤掉错误上报地址
+            if( this.reportUrl && this.url && this.url.toLowerCase().indexOf(this.reportUrl.toLowerCase())>=0 ){
+                console.log("统计错误接口异常",this.msg);
+                return;
+            }
+            let errorInfo = this.handleErrorInfo();
+            
+            console.log("\n````````````````````` "+this.category+" `````````````````````\n",errorInfo)
+            
+            //记录日志
+            TaskQueue.add(this.reportUrl,errorInfo);
+
+        } catch (error) {
+            console.log(error);
+        }
+    }
+
+    /**
+     * 处理错误信息
+     * @param {*} extendsInfo 
+     */
+    handleErrorInfo(){
+        let txt = "错误类别: " + this.category + "\r\n";
+        txt += "日志信息: " + this.msg + "\r\n";
+        txt += "url: " + this.url + "\r\n";
+        switch(this.category){
+            case ErrorCategoryEnum.JS_ERROR:
+                txt += "错误行号: " + this.line + "\r\n";
+                txt += "错误列号: " + this.col + "\r\n";
+                if (this.errorObj && this.errorObj.stack) {
+                    txt += "错误栈: " + this.errorObj.stack + "\r\n";
+                }
+                break;
+            default:
+                txt += "其他错误: " + JSON.stringify(this.errorObj) + "\r\n";
+                break;
+        }
+        let deviceInfo = this.getDeviceInfo();
+        txt += "设备信息: " + deviceInfo; //设备信息
+        let extendsInfo = this.getExtendsInfo();
+        let recordInfo = extendsInfo;
+        recordInfo.category = this.category; //错误分类
+        recordInfo.logType = this.level;  //错误级别
+        recordInfo.logInfo = txt;  //错误信息
+        recordInfo.deviceInfo = deviceInfo; //设备信息
+        return recordInfo;
+    }
+
+    /**
+     * 获取扩展信息
+     */
+    getExtendsInfo(){
+        try {
+            let ret = {};
+            let extendsInfo = this.extendsInfo || {};
+            let dynamicParams;
+            if(utils.isFunction(extendsInfo.getDynamic)){
+                dynamicParams = extendsInfo.getDynamic();   //获取动态参数
+            }
+            //判断动态方法返回的参数是否是对象
+            if(utils.isObject(dynamicParams)){
+                extendsInfo = {...extendsInfo,...dynamicParams};
+            }
+            //遍历扩展信息,排除动态方法
+            for(var key in extendsInfo){
+                if(!utils.isFunction(extendsInfo[key])){    //排除获取动态方法
+                    ret[key] = extendsInfo[key];
+                }
+            }
+            return ret;
+        } catch (error) {
+            console.log('call getExtendsInfo error',error);
+            return {};  
+        }
+    }
+    
+    /**
+     * 获取设备信息
+     */
+    getDeviceInfo(){
+        try {
+            let deviceInfo = DeviceInfo.getDeviceInfo();
+            return JSON.stringify(deviceInfo);
+        } catch (error) {
+            console.log(error);
+            return "";
+        }
+    }
+
+}
+export default BaseMonitor;
diff --git a/src/services/config.ts b/src/services/config.ts
new file mode 100644
index 0000000..a2c420a
--- /dev/null
+++ b/src/services/config.ts
@@ -0,0 +1,28 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+export enum ErrorsCategory {
+  AJAX_ERROR = 'ajaxError',
+  RESOURCE_ERROR = 'resourceError',
+  VUE_ERROR = 'vueError',
+  PROMISE_ERROR = 'promiseError',
+  JS_ERROR = 'jsError',
+  CONSOLE_INFO = 'consoleInfo',
+  CONSOLE_WARN = 'consoleWarn',
+  CONSOLE_ERROR = 'consoleError',
+  CROSS_SCRIPT_ERROR = 'crossSrciptError',
+  UNKNOW_ERROR = 'unknowError'
+}
diff --git a/src/services/reportLog.ts b/src/services/reportLog.ts
new file mode 100644
index 0000000..1425f97
--- /dev/null
+++ b/src/services/reportLog.ts
@@ -0,0 +1,89 @@
+/**
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License.  You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+class API {
+
+    constructor(url){
+        this.url = url;
+    }
+    
+    /**
+     * 上报信息 (默认方式)
+     */
+    report(data){
+        if(!this.checkUrl(this.url)){
+            console.log("上报信息url地址格式不正确,url=",this.url);
+            return;
+        }
+        console.log("上报地址:"+this.url);
+        this.sendInfo(data);
+    }
+
+    /**
+     * 发送消息
+     */
+    sendInfo(data){
+        try {
+            var xhr = new XMLHttpRequest();
+            xhr.open("POST",this.url,true);
+            //xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+            xhr.setRequestHeader("Content-Type", "application/json");
+            xhr.send(JSON.stringify(data));
+        } catch (error) {
+            console.log(error);
+        }
+    }
+
+    /**
+     * 通过img方式上报信息
+     */
+    reportByImg(data){
+        if(!this.checkUrl(this.url)){
+            console.log("上报信息url地址格式不正确,url=",this.url);
+            return;
+        }
+        try {
+            var img = new Image();
+            img.src = this.url+'?v='+new Date().getTime()+'&' + this.formatParams(data);
+        } catch (error) {
+            console.log(error);
+        }
+    }
+
+    /*
+     *格式化参数
+     */
+    formatParams(data) {
+        var arr = [];
+        for (var name in data) {
+            arr.push(encodeURIComponent(name) + "=" + encodeURIComponent(data[name]));
+        }
+        return arr.join("&");
+    }
+
+    /**
+     * 检测URL
+     */
+    checkUrl(url){
+        if(!url){
+            return false;
+        }
+        var urlRule =/^[hH][tT][tT][pP]([sS]?):\/\//;
+        return urlRule.test(url);
+    }
+
+}
+export default API;
\ No newline at end of file
diff --git a/src/services/type.d.ts b/src/services/type.d.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/services/type.d.ts
diff --git a/src/services/utils.ts b/src/services/utils.ts
new file mode 100644
index 0000000..cdab32d
--- /dev/null
+++ b/src/services/utils.ts
@@ -0,0 +1,38 @@
+export default {
+
+    type(obj) {
+        return Object.prototype.toString.call(obj).replace(/\[object\s|\]/g, '');
+    },
+
+    isFunction(func) {
+        return this.type(func) === "Function";
+    },
+
+    isArray(list) {
+        return this.type(list) === 'Array';
+    },
+
+    /**
+     * 是否为null
+     * @param {String} str 
+     */
+    isNull(str) {
+        return str == undefined || str == '' || str == null;
+    },
+
+    /**
+     * 对象是否为空
+     * @param {*} obj 
+     */
+    objectIsNull(obj) {
+        return JSON.stringify(obj) === "{}";
+    },
+
+    /**
+     * 是否是对象
+     * @param {*} obj 
+     */
+    isObject(obj){
+        return this.type(obj) === "Object";
+    }
+}
\ No newline at end of file
diff --git a/src/types.d.ts b/src/types.d.ts
new file mode 100644
index 0000000..12f8276
--- /dev/null
+++ b/src/types.d.ts
@@ -0,0 +1,29 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export interface TClientMonitor extends TErrorsType {
+  reportUrl: string;
+}
+
+export interface TErrorsType {
+  jsErrors: boolean;
+  promiseErrors: boolean;
+  consoleErrors: boolean;
+  vueErrors: boolean;
+  reactErrors: boolean;
+  ajaxErrors: boolean;
+  resourceErrors: boolean;
+}
diff --git a/test/index.html b/test/index.html
new file mode 100644
index 0000000..10eb0a4
--- /dev/null
+++ b/test/index.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <meta http-equiv="X-UA-Compatible" content="ie=edge">
+  <title>Document</title>
+</head>
+<body>
+  <div>console test</div>
+  <script src="../dist/monitor.js"></script>
+  <script>
+    new ClientMonitor().init({
+      reportUrl: "http://baidu.com",
+      consoleErrors:true,
+      jsErrors: true,
+      promiseErrors: false,
+      vueErrors: false,
+      reactErrors: false,
+      ajaxErrors: false,
+      resourceErrors: false
+    });
+    ss();
+  </script>
+</body>
+</html>
\ No newline at end of file