## 对 jssip 的包装

易米电话工具条基于 [jssip](http://jssip.net/)，通过 WebRTC 网关对接易米 sip 服务器。工具条主要封装了对易米 sip 服务器的消息处理，让用户通过浏览器直接使用易米 sip 服务器提供的服务。
推荐使用 Chrome 68+浏览器；IE 浏览器不支持 WebRTC，所以工具条不能在 IE 中使用。

### 如何安装使用

我们当前代码不能在 node 环境下执行： `localstorage` & `fetch` 都是浏览器方法，因此我们没有在 package.json[设置 main 字段而是设置 browser 字段](https://docs.npmjs.com/files/package.json#browser)。

因为不同客户需求，我们同时提供带UI界面的工具条以及不带界面通过程序直接调用的npm包，代码用umd方式打包。 如果使用带UI界面工具条建议在html 页面中通过 CDN应用；如果通过api调用则推荐用npm 安装。

注：根据这个[SO 讨论](https://stackoverflow.com/questions/14221579/how-do-i-add-comments-to-package-json-for-npm-install) 我们在 package.json 用 "//"添加几个要点，但重要的东西还是都这里详述。

### 如何打包

我们提供两种打包方式 `webpack` 和 `gulp(4.0)` 方式，webpack 通过 npm script 执行;
webpack 打包命令`npm run bundle` ; gulp 打包直接在命令行执行 `gulp bundle`

webpack 打包设置` libraryTarget: 'umd' `  确保包可以被别的项目 `require/import` ; gulp 没有设置umd，只适合导打包UI的代码。

webpack 打包需要对 css 准备处理，见下面描述。

使用 webpack 打包的一个好处是方便断点调试，gulp 即便有 sourcemap 也很难定位到源文件在打包后的实际位置，而且 webpack 打包速度远比 gulp+browserify 快。

gulp 4.0 和 3.0 主要不同是引入 `gulp.series & gulp.parallel` (vinyl-source-stream 要升级到 2.0)，网上很多文章还是用 3.0（看他们定义 task 的依赖关系可判断），所以我们使用 4.0 带有一定学习目的。

### .bablerc 设置

development 下只设置 target 是 chrome 和 firefox，production 才设置支持 IE

为了让代码能直接在 IE 里运行（然后弹出警告工具条不支持 IE :$）`.bablerc`设置增加了`"transform-runtime"`插件，它的几个设置含义还没仔细研究，但可以注意到如果 target 不设置 IE，打包出来的代码是一样的。

babel 的环境变量是这样设置：

[`Default: process.env.BABEL_ENV || process.env.NODE_ENV || "development"` ](https://babeljs.io/docs/en/next/options#envname)

设置 `BABEL_ENV` 而不是 `NODE_ENV` 让意图更明确，同时确实不设置 development 减少跨平台的代码

目前使用 babel 6, "babel-preset-env": "^1.7.0" 所以如果使用 jssip-emicnet 的项目用 babel 7 `@babel/preset-env` webpack打包会出错。

### 怎么起服务器

我们一共尝试过三种 http 服务器：

1. [http-server](https://www.npmjs.com/package/http-server)

2. [live-server](https://www.npmjs.com/package/live-server) (主要是针对 http-server 不能重刷页面)

3. webpack 带的[webpack-dev-server](https://github.com/webpack/webpack-dev-server)

没有别的原因，**不会**再引入别的服务器(比如 express，或者自己再写一个)。

网页刷新重连有几种做法：

1. webpack 的 devServer 做更新重连，devServer 使用"webpack-cli"，这其实是一个[bug](https://github.com/webpack/webpack-dev-server/issues/1422)所以造成目前没法用`webpack-command`
2. 用 webpack-command + live-server 做更新重连。但要再引入[live-server](https://www.npmjs.com/package/live-server) 这个包上一次更新是两年前，远不如[webpack-dev-server](https://www.npmjs.com/package/webpack-dev-server) 活跃。所以已经把 live-server 相关代码去掉了
3. 用 gulp 时使用 [browser-sync](https://www.npmjs.com/package/browser-sync)

### 断点调试

和微软工程师讨论了 6 天 [**vscode-chrome-debug**](https://github.com/Microsoft/vscode-chrome-debug/issues/723)，才终于发现原来是 webpack.config.js 没有设置`devtool: 'source-map'` 造成在 vscode 里断点停不下来。

断点调试大家一定熟练起来

### dependencies 设置

-   如果 jssip 里用的我们就不在列出，比如 debug
-   events, 这是模拟 node.js EventEmitter 在浏览器的实现，因为 jssip 已经设置所以也没在我们的 dependencies 列出
-   不使用 jQuery，网络请求使用 fetch

### 发布 es6 module 注意事项

-   我们所有用到 es6 功能 Chrome 66+ 都支持，所以不需要把 babel 转成 es5, `.bablerc` 设置好 target 确保不必要的转码。
-   我们代码里既有 commonjs 的 require 又有 es6 import 所以需要 babel 统一转成 commonjs。
-   mocha 单元测试还没有完全支持 es6 所以需要对单元测试代码做转码
-   发布的包使用umd，注意umd和es6 module区别

### 如何在回调函数里正确使用 this

首先，只有回调函数里才可能会用到 that , 不是回调函数不需`let that = this` (之前代码有这个问题)

其次 ，不要在每个回调函数前都习惯性的加 `let that = this` 并在函数体里用`that` , 这样代码很难维护。

建议一下几个方法：

1. 首选用箭头函数
2. 多层回调里，二级回调通过 .bind(this) 保证 this 值正确，参见 `Phone.init(params,callbackMap)`

注：

1. Ephone 类加了一个类变量 self 和 webpack-dev-server 发生冲突 `Uncaught TypeError: self.postMessage is not a function at sendMsg (webpack:///(:9000/webpack)-dev-server/client?:61:10)'`

2. 别的类也不能再设置 self，因为设置也最终也还是指向 Ephone(原因还是 100%确认)

参考 [How to access the correct `this` inside a callback?](https://stackoverflow.com/questions/20279484/how-to-access-the-correct-this-inside-a-callback)

### 如何让 mocha 5 运行 es6 代码

我们使用mocha 5, 对es6支持还不完善，所以

-   [es6 module](http://jamesknelson.com/testing-in-es6-with-mocha-and-babel-6/)
-   [Using Mocha with ES6 spec files](http://krasimirtsonev.com/blog/article/using-mocha-with-es6-spec-files)
-   [async/await](https://stackoverflow.com/questions/33527653/babel-6-regeneratorruntime-is-not-defined/51605910#51605910)

注：这个问题但还处在open状态 [Support ES6 style tests without transpiler usage](https://github.com/mochajs/mocha/issues/3006) 

### mocha 单元测试

1.  `localstorage` & `fetch` 都是浏览器方法，所以我们测试代码需要浏览器执行
2.  npm run unit
3.  用浏览器打开 test 文件夹下面的 index.html
4.  http://127.0.0.1:9999/?grep=`key` 比如 `http://127.0.0.1:9999/?grep=web`只跑相关用例

### 浏览器 mocha 测试步骤

1. [使 mocha 运行在浏览器端](https://mochajs.org/#running-mocha-in-the-browser)
2. 将 mocha 5测试代码转成浏览器可识别的代码(使用了 webpack + babel) 参考[如何让浏览器支持 ES6 中的 import 和 export 语法](https://blog.csdn.net/u012863664/article/details/72813941)
3. 因为浏览器无法处理 node_modules 目录下的代码，所以想使用浏览器的`script type="module"`必须写全路径，借助 webpack 把所有 import 的文件打包成一个文件是最简单的方法，[参见](https://github.com/vitalets/mocha-es6-modules/issues/4)
4. [For browsers, you need to use Webpack or Browserify to compile all test files.](https://x-team.com/blog/setting-up-javascript-testing-tools-for-es6/)

### css 文件处理

1. 按钮图标使用[阿里云矢量图标](http://www.iconfont.cn/),没有使用而外图片，所以 css 打包处理就是做最小化。
2. 通过 webpack 打包处理 css，但 webpack 需要在 js 文件 import css，这是 webpack 特定语法；而且这样做如果用 gulp 打包就还要引入`browserify-css` [参见](https://stackoverflow.com/questions/51807092/is-it-possible-to-let-webpack-bundle-css-without-import-css-in-my-entry-js)
3. sass 没有相应的 browserify 插件，但其实处理简单（css 也一样）就是把那行代码删了在 browserify。可以用两种方法,<1> 'gulp-replace' 生成一个临时文件再交给 browserify 处理 <2> 'browserify-replace'相对简单， 但帮助文档不详，查看源代码找到调用方法。

#### 更新

在 js 文件 import css 这个做法始终让我很不安，[在 SO 问了很久](https://stackoverflow.com/questions/51807092/is-it-possible-to-let-webpack-bundle-css-without-import-css-in-my-entry-js/) 都没答复，结果在 google `webpack entry css` 这三个关键字时候偶然发现答案。其实当时是在调查如何让 HtmlWebPackPlugin 不要对 index.html 加 css，保证 webpack 和 gulp 打包的东西都能正确显示。

做法就是一个 entry，但是设置文件名数组

```
    entry:{
      index: [
        './src/ui/index.js',
        './resources/css/phone.scss'
      ]
    },
    output:{
        path: path.resolve(__dirname, './dist'),
        filename: '[name].js'
   },
```

### 选择哪种 font-face

阿里字体图标库有多种格式下载，因为我们已经限定了浏览器的版本 ，选用 woff 对我们最合适。

TTF, OTF, WOFF, EOT, SVG [字体文件对浏览器的支持](https://creativemarket.com/blog/the-missing-guide-to-font-formats?tdsourcetag=s_pctim_aiomsg)

1. EOT 仅支持 IE8-11

2. OTF/TTF 支持 [ IE9-11, Edge12-14, Firefox 40-45, Chrome 43-49, Safari 8-9, Opera 32-35, iOS Sarfari 8.4-9.1, Android 4.4-44, Chrome for Android 46 ]

3. WOFF 支持[ IE9-11, Edge12-14, Firefox 40-45, Chrome 43-49, Safari 8-9, Opera 32-35, iOS Sarfari 8.4-9.1, Android 4.4-44, Chrome for Android 46 ]

4. WOFF2 支持[ Firefox 40-45, Chrome 43-49, Opera 32-35, Chrome for Android 46 ]

5. SVG 支持 [ Safari 8-9, iOS Sarfari 8.4-9.1 ]

关于 font-face 介绍，还可以参考 [1](https://www.jianshu.com/p/0fc36e7f7d2e),[2](https://stackoverflow.com/questions/11002820/why-should-we-include-ttf-eot-woff-svg-in-a-font-face)

### 统一文件 tab，引号，结尾分号设置

格式化工具[Beautify](https://marketplace.visualstudio.com/items?itemName=HookyQR.beautify) 和 [Prettier](https://prettier.io/)， 经过比较选择 [Prettier](editor.formatOnSave) Prettier 只能格式化 js 文件，其他文件用 vscode 自带 format document

通过 Prettier 统一文件 tab 设置，4 个空格；js 代码都用单引号，利用 Prettier 对 js 的**ASI**，注意前置括号会因此被加一个分号。

安装 Prettier 的 vscode 插件，设置 vscode `format on save` **true** 以及 `Ignore Trim Whitespace` **false**

以后合代码再发生此类问题一定查到原因。关于 Prettier 的其他叙述参见 `文件格式修改.md`

### 开发工具

开发人员需要标配以下几个工具：

1. vscode [2018 的版本](https://code.visualstudio.com/updates/)，并安装[**vscode-chrome-debug**](https://github.com/Microsoft/vscode-chrome-debug/issues/723) , 建议使用 [todo 插件](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.todo-tree)

2. Prettier

3. [Katalon](https://www.katalon.com/) 自动化 UI 测试 ，Katalon 有单独文件详细描述。最新版本[5.7.1](https://docs.katalon.com/display/KD/Release+Notes)

4. Markdown 编辑器

---

### 使用

```javascript
//首先创建一个实例
//具体配置信息和字段和 jssip 一样,除了 socekts 不需要配置
var ua = new JsSIPWrap(config)
```

创建完成后,需要登录

#### 登录

`ua.login();`

登录成功后就可以监听各类事件.目前定义有如下:

```
// 注册登录相关
connecting
connected
disconnected
registered
unregistered
registrationFailed

//会话相关
newRTCSession

// 收到新的消息
newMessage
由于消息都是 xml ,且 PBX 的消息种类比较多,后面需要对每个不同的消息分别定义不同事件,外层使用不用关心 PBX 消息类型
```

#### 拨打电话

```
ua.call(target, type)

// target 的是呼叫号码 ,type 是呼叫的类型 1:外线直接呼叫 2:回拨 3:内线互拨
```

#### 挂断

`ua.stop()`

#### 发送 xml 消息

```
ua.sendMessage(target, text)

//target 消息接受人,需要加企业和分机号拼接

// 如果发给 PBX 的事件,需要单独函数处理,上层不需要关系各种 PBX 事件
```

#### 注意

-   不管是发出 invite 或者收到 invite 消息,session 相关的事件都通过原生 RTCSession 来监听

#### 后续

-   需要把 ring 相关的封装写进来

-   发出的 PBX 消息事件接口完善

-   收到的 PBX 消息事件接口完善

    注，对 jssip 使用参考[tryit](https://github.com/versatica/tryit-jssip)

#### http 对外接口封装使用说明

可参见单元测试代码

```
-   获取初始化数据接口 getLoginData(un, pwd, switchNumber, callintype)
    该接口会获取登录所需的所有参数

-   调用示例
    getLoginData（'1006'，'1006'，'02566699734',2）
-   参数说明
    参数名 | 说明 |类型
    un | 分机号 | string
    pwd | 密码 |string
    switchNumber | 总机号 |string
    callintype | 呼叫模式 | number
```

```
-   web 接口统一调用接口 webApiHandler(functionName, webParam)

-   参数说明
    参数名 | 说明 |类型
    functionName | 请求方法名 |string
    webParam | 请求参数体 | object
```

#### webApiHandler 对外 function 的参数定义

注：目前webApiHandler 封装是对接老架构时候客户端直接调用es-web的接口，eid是必填输入参数，通过上一步 `getInfo` 获得。

对接新架构客户端不直接调用es-web，而是调用新架构后台对应接口，ccgeid是必填输入参数。对接新架构 webApiHandler实现需要重写，用来封装新架构接口

```
getEpProfile 获取企业属性

-   调用示例
    webApiHandler（"getEpProfile"，{un:'1006','pwd:1006',eid:65656}）

*   参数说明
    un | 分机号 | string
    pwd | 密码 |string
    eid | 企业 id |string
```

```
getMemberInfo 获取用户信息

-   调用示例
    webApiHandler（"getMemberInfo"，{un:'1006','pwd:1006',eid:65656}）

-   参数说明
    un | 分机号 | string
    pwd | 密码 |string
    eid | 企业 id |string
```

```
getGroups 获取所有技能组

-   调用示例
    webApiHandler（"getGroups"，{un:'1006','pwd:1006',eid:65656,needMembers:1}）

-   参数说明
    un | 分机号 | string
    pwd | 密码 |string
    eid | 企业 id |string
    needMembers | 不传或传空则不返回属于组的用户信息，传 1 返回 mids| number
```

```
updateInfo 更新用户呼叫模式

-   调用示例
    webApiHandler（"updateInfo"，{un:'1006','pwd:1006',eid:65656,jsonStr:{ "data": { "callintype": 2 } }}}）

-   参数说明
    un | 分机号 | string
    pwd | 密码 |string
    eid | 企业 id |string
    jsonStr| 用户新的信息 json 内容[或者 post 一个 json 文件]|object
```

```
searchEpMembers 获取技能组包含坐席

-   调用示例
    webApiHandler（"searchEpMembers"，{un:'1006','pwd:1006',eid:65656,searchGid:'2050',searchServiceControl:1}）

-   参数说明
    un | 分机号 | string
    pwd | 密码 |string
    eid | 企业 id |string
    start | 起始记录数 默认 0 |string
    length | 获取记录数，默认 10 |number
    searchGid | 组 id （筛选未分组时传-1） | string
    searchServiceControl |状态筛选 0-离线 1-空闲 2-暂离 3-消息请求 4-呼叫请求 5-通话中 6-话后处理| string
```

```
getMemberCallStates 获取坐席状态

-   调用示例
    webApiHandler（"getMemberCallStates"，{un:'1006','pwd:1006',eid:65656,uid:'234'}）

-   参数说明
    un | 分机号 | string
    pwd | 密码 |string
    eid | 企业 id |string
    uid | 用户 id |string
```
