Explorar el Código

fix: 完善代码

余尚辉 hace 3 meses
padre
commit
19adbaf3ea

+ 5 - 4
dist/hs-cti.d.ts

@@ -4,11 +4,11 @@
 - Version 1.0.9 
 - JS Standard any
 - Author platformfe
-- Built on 2024/11/16 10:01:11
+- Built on 2024/11/27 10:37:38
 - GitHub 
 - Branch main
-- CommitID d83889f31bf3f437dd61266d77027454207ee34a
-- CommitMessage feat:init
+- CommitID 987d86cd91cde7d1c235ad106d79fc92c305b8ff
+- CommitMessage fix: 修改socket地址
 */
 /**
  * Minimal `EventEmitter` interface that is molded against the Node.js
@@ -191,7 +191,8 @@ declare enum Scene {
     Manual = "manual",
     Robot = "robot",
     Monitor = "monitor",
-    Predictive = "predictive"
+    Predictive = "predictive",
+    Wechat = "wechat"
 }
 type ExcludeScene = Scene.Manual | Scene.Robot | Scene.Predictive;
 /**

+ 35 - 21
dist/hs-cti.es5.esm.js

@@ -4,11 +4,11 @@
 - Version 1.0.9 
 - JS Standard es5
 - Author platformfe
-- Built on 2024/11/16 10:01:11
+- Built on 2024/11/27 10:37:38
 - GitHub 
 - Branch main
-- CommitID d83889f31bf3f437dd61266d77027454207ee34a
-- CommitMessage feat:init
+- CommitID 987d86cd91cde7d1c235ad106d79fc92c305b8ff
+- CommitMessage fix: 修改socket地址
 */
 import { Web, RequestPendingError, SessionState, UserAgent, UserAgentState, Registerer, RegistererState, Inviter, Invitation, Session, Messager } from 'sip.js';
 import io from 'socket.io-client';
@@ -2935,6 +2935,7 @@ var Scene;
   Scene["Robot"] = "robot";
   Scene["Monitor"] = "monitor";
   Scene["Predictive"] = "predictive";
+  Scene["Wechat"] = "wechat";
 })(Scene || (Scene = {}));
 /**
  * @enum {string} CTI 监听场景
@@ -3259,7 +3260,9 @@ var methodExceptMsgMap = {
   listen: ExceptMessage.CommonNetworkErrorMsg,
   setActiveService: ExceptMessage.CustomNetworkErrorMsg
 };
-var baseRequireParams = ['agent_id', 'saas_id', 'password', 'env', 'scene'];
+var baseRequireParams = ['agent_id', 'saas_id',
+// 'password',
+'env', 'scene'];
 var monitorRequireParams = ['monitorScene'];
 var allRequiredParams = __spreadArray(__spreadArray([], baseRequireParams, true), monitorRequireParams, true);
 
@@ -3608,7 +3611,7 @@ var hsTrackJPOST = function (_a) {
 };
 
 // 获取初始化配置
-var baseUrl = 'http://localhost:8090';
+var baseUrl = 'http://192.168.100.159:8090';
 var getInitConf = function (data) {
   return hsTrackJPOST({
     baseUrl: baseUrl,
@@ -3874,6 +3877,9 @@ function validateParams() {
     return descriptor;
   };
 }
+function generateUniqueId() {
+  return 'id-' + Date.now() + '-' + Math.floor(Math.random() * 10000);
+}
 
 /**
  * 本地提示音
@@ -3882,9 +3888,9 @@ function validateParams() {
  * _byeAudio 结束通话提示音
  */
 var audioList = {
-  _ringAudio: 'https://static.fuxicarbon.com/hs-cti/ring.wav',
-  _waitAudio: 'https://static.fuxicarbon.com/hs-cti/manual.wav',
-  _byeAudio: 'https://static.fuxicarbon.com/hs-cti/bye.wav'
+  _ringAudio: 'http://static.fuxicarbon.com/hs-cti/ring.wav',
+  _waitAudio: 'http://static.fuxicarbon.com/hs-cti/manual.wav',
+  _byeAudio: 'http://static.fuxicarbon.com/hs-cti/bye.wav'
 };
 /** @class HsCTI 红杉外呼类 */
 var HsCTI = /** @class */function (_super) {
@@ -3894,7 +3900,6 @@ var HsCTI = /** @class */function (_super) {
     var saas_id = hsCTIInitOptions.saas_id,
       agent_id = hsCTIInitOptions.agent_id,
       scene = hsCTIInitOptions.scene,
-      password = hsCTIInitOptions.password,
       env = hsCTIInitOptions.env,
       loggerLevel = hsCTIInitOptions.loggerLevel;
     _this = _super.call(this) || this;
@@ -4062,8 +4067,7 @@ var HsCTI = /** @class */function (_super) {
     _this._baseParams = {
       agent_id: agent_id,
       saas_id: saas_id,
-      scene: scene,
-      password: password
+      scene: scene
     };
     var baseTrackParams = {
       agent_id: agent_id,
@@ -4260,9 +4264,9 @@ var HsCTI = /** @class */function (_super) {
                 // FS 注册过期时间
                 fsRegisterExpireTime: 84000,
                 // 单次初始化唯一 ID
-                ctiSessionId: '23',
+                ctiSessionId: generateUniqueId(),
                 // IM websocket url
-                imWsServer: 'ws://localhost:8091/ws/cs-im',
+                imWsServer: 'ws://192.168.100.159:8091/ws/cs-im',
                 // IM websocket path
                 imWsPath: 'ws/cs-im'
               });
@@ -4414,7 +4418,10 @@ var HsCTI = /** @class */function (_super) {
               video: false
             },
             peerConnectionConfiguration: {
-              iceServers: []
+              // iceServers: []
+              iceServers: [{
+                urls: initOptions.ice_server
+              }]
             }
           }
         }
@@ -4490,23 +4497,30 @@ var HsCTI = /** @class */function (_super) {
           }
         },
         onInvite: function (invitation) {
-          var callId = invitation.request.getHeader('P-LIBRA-Callid') || '';
-          var ctiFlowId = invitation.request.getHeader('P-LIBRA-CtiFlowId') || '';
-          console.log("Request" + ctiFlowId, _this._baseParams.ctiFlowId);
-          if (_this.scene === Scene.Manual && ctiFlowId != _this._baseParams.ctiFlowId) {
-            _this.logger.error("cti_flow_id \u4E0D\u4E00\u81F4! fe: ".concat(_this._baseParams.ctiFlowId, " | P-LIBRA-CtiFlowId: ").concat(ctiFlowId));
-            return;
-          }
+          // this.scene = Scene.Robot
+          var callId = invitation.request.getHeader('P-LIBRA-CallId') || '';
+          console.log(callId, 2888888888);
+          // const ctiFlowId =
+          //   invitation.request.getHeader('P-LIBRA-CtiFlowId') || ''
+          // if (ctiFlowId != this._baseParams.ctiFlowId) {
+          //   this.logger.error(
+          //     `cti_flow_id 不一致! fe: ${this._baseParams.ctiFlowId} | P-LIBRA-CtiFlowId: ${ctiFlowId}`
+          //   )
+          //   return
+          // }
           _this._callId = callId;
+          console.log(callId, 1888888888);
           setBaseOption(BaseOption.TrackParams, {
             call_id: callId
           });
           _this.sessionStateChangeAndTrack(invitation.state);
           if ([Scene.Robot, Scene.Monitor].includes(_this.scene)) {
             _this.playAudio(AudioName.RingAudio);
+            console.log('playAudio', 1888888888);
           }
           if ([Scene.Manual].includes(_this.scene)) {
             _this.answer();
+            console.log('answer', 1888888888);
           }
           invitation.delegate = {
             onCancel: function (cancel) {

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 3 - 3
dist/hs-cti.es5.esm.prod.js


+ 35 - 21
dist/hs-cti.es5.umd.js

@@ -4,11 +4,11 @@
 - Version 1.0.9 
 - JS Standard es5
 - Author platformfe
-- Built on 2024/11/16 10:01:11
+- Built on 2024/11/27 10:37:38
 - GitHub 
 - Branch main
-- CommitID d83889f31bf3f437dd61266d77027454207ee34a
-- CommitMessage feat:init
+- CommitID 987d86cd91cde7d1c235ad106d79fc92c305b8ff
+- CommitMessage fix: 修改socket地址
 */
 (function (global, factory) {
     typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('sip.js'), require('socket.io-client')) :
@@ -2938,6 +2938,7 @@
       Scene["Robot"] = "robot";
       Scene["Monitor"] = "monitor";
       Scene["Predictive"] = "predictive";
+      Scene["Wechat"] = "wechat";
     })(exports.Scene || (exports.Scene = {}));
     /**
      * @enum {string} CTI 监听场景
@@ -3262,7 +3263,9 @@
       listen: ExceptMessage.CommonNetworkErrorMsg,
       setActiveService: ExceptMessage.CustomNetworkErrorMsg
     };
-    var baseRequireParams = ['agent_id', 'saas_id', 'password', 'env', 'scene'];
+    var baseRequireParams = ['agent_id', 'saas_id',
+    // 'password',
+    'env', 'scene'];
     var monitorRequireParams = ['monitorScene'];
     var allRequiredParams = __spreadArray(__spreadArray([], baseRequireParams, true), monitorRequireParams, true);
 
@@ -3611,7 +3614,7 @@
     };
 
     // 获取初始化配置
-    var baseUrl = 'http://localhost:8090';
+    var baseUrl = 'http://192.168.100.159:8090';
     var getInitConf = function (data) {
       return hsTrackJPOST({
         baseUrl: baseUrl,
@@ -3877,6 +3880,9 @@
         return descriptor;
       };
     }
+    function generateUniqueId() {
+      return 'id-' + Date.now() + '-' + Math.floor(Math.random() * 10000);
+    }
 
     /**
      * 本地提示音
@@ -3885,9 +3891,9 @@
      * _byeAudio 结束通话提示音
      */
     var audioList = {
-      _ringAudio: 'https://static.fuxicarbon.com/hs-cti/ring.wav',
-      _waitAudio: 'https://static.fuxicarbon.com/hs-cti/manual.wav',
-      _byeAudio: 'https://static.fuxicarbon.com/hs-cti/bye.wav'
+      _ringAudio: 'http://static.fuxicarbon.com/hs-cti/ring.wav',
+      _waitAudio: 'http://static.fuxicarbon.com/hs-cti/manual.wav',
+      _byeAudio: 'http://static.fuxicarbon.com/hs-cti/bye.wav'
     };
     /** @class HsCTI 红杉外呼类 */
     var HsCTI = /** @class */function (_super) {
@@ -3897,7 +3903,6 @@
         var saas_id = hsCTIInitOptions.saas_id,
           agent_id = hsCTIInitOptions.agent_id,
           scene = hsCTIInitOptions.scene,
-          password = hsCTIInitOptions.password,
           env = hsCTIInitOptions.env,
           loggerLevel = hsCTIInitOptions.loggerLevel;
         _this = _super.call(this) || this;
@@ -4065,8 +4070,7 @@
         _this._baseParams = {
           agent_id: agent_id,
           saas_id: saas_id,
-          scene: scene,
-          password: password
+          scene: scene
         };
         var baseTrackParams = {
           agent_id: agent_id,
@@ -4263,9 +4267,9 @@
                     // FS 注册过期时间
                     fsRegisterExpireTime: 84000,
                     // 单次初始化唯一 ID
-                    ctiSessionId: '23',
+                    ctiSessionId: generateUniqueId(),
                     // IM websocket url
-                    imWsServer: 'ws://localhost:8091/ws/cs-im',
+                    imWsServer: 'ws://192.168.100.159:8091/ws/cs-im',
                     // IM websocket path
                     imWsPath: 'ws/cs-im'
                   });
@@ -4417,7 +4421,10 @@
                   video: false
                 },
                 peerConnectionConfiguration: {
-                  iceServers: []
+                  // iceServers: []
+                  iceServers: [{
+                    urls: initOptions.ice_server
+                  }]
                 }
               }
             }
@@ -4493,23 +4500,30 @@
               }
             },
             onInvite: function (invitation) {
-              var callId = invitation.request.getHeader('P-LIBRA-Callid') || '';
-              var ctiFlowId = invitation.request.getHeader('P-LIBRA-CtiFlowId') || '';
-              console.log("Request" + ctiFlowId, _this._baseParams.ctiFlowId);
-              if (_this.scene === exports.Scene.Manual && ctiFlowId != _this._baseParams.ctiFlowId) {
-                _this.logger.error("cti_flow_id \u4E0D\u4E00\u81F4! fe: ".concat(_this._baseParams.ctiFlowId, " | P-LIBRA-CtiFlowId: ").concat(ctiFlowId));
-                return;
-              }
+              // this.scene = Scene.Robot
+              var callId = invitation.request.getHeader('P-LIBRA-CallId') || '';
+              console.log(callId, 2888888888);
+              // const ctiFlowId =
+              //   invitation.request.getHeader('P-LIBRA-CtiFlowId') || ''
+              // if (ctiFlowId != this._baseParams.ctiFlowId) {
+              //   this.logger.error(
+              //     `cti_flow_id 不一致! fe: ${this._baseParams.ctiFlowId} | P-LIBRA-CtiFlowId: ${ctiFlowId}`
+              //   )
+              //   return
+              // }
               _this._callId = callId;
+              console.log(callId, 1888888888);
               setBaseOption(BaseOption.TrackParams, {
                 call_id: callId
               });
               _this.sessionStateChangeAndTrack(invitation.state);
               if ([exports.Scene.Robot, exports.Scene.Monitor].includes(_this.scene)) {
                 _this.playAudio(AudioName.RingAudio);
+                console.log('playAudio', 1888888888);
               }
               if ([exports.Scene.Manual].includes(_this.scene)) {
                 _this.answer();
+                console.log('answer', 1888888888);
               }
               invitation.delegate = {
                 onCancel: function (cancel) {

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 3 - 3
dist/hs-cti.es5.umd.prod.js


+ 35 - 21
dist/hs-cti.es6.esm.js

@@ -4,11 +4,11 @@
 - Version 1.0.9 
 - JS Standard es6
 - Author platformfe
-- Built on 2024/11/16 10:01:11
+- Built on 2024/11/27 10:37:38
 - GitHub 
 - Branch main
-- CommitID d83889f31bf3f437dd61266d77027454207ee34a
-- CommitMessage feat:init
+- CommitID 987d86cd91cde7d1c235ad106d79fc92c305b8ff
+- CommitMessage fix: 修改socket地址
 */
 import { Web, UserAgent, UserAgentState, Registerer, RegistererState, Inviter, Invitation, Session, Messager, RequestPendingError, SessionState } from 'sip.js';
 import io from 'socket.io-client';
@@ -2367,6 +2367,7 @@ var Scene;
   Scene["Robot"] = "robot";
   Scene["Monitor"] = "monitor";
   Scene["Predictive"] = "predictive";
+  Scene["Wechat"] = "wechat";
 })(Scene || (Scene = {}));
 /**
  * @enum {string} CTI 监听场景
@@ -2686,7 +2687,9 @@ const methodExceptMsgMap = {
   listen: ExceptMessage.CommonNetworkErrorMsg,
   setActiveService: ExceptMessage.CustomNetworkErrorMsg
 };
-const baseRequireParams = ['agent_id', 'saas_id', 'password', 'env', 'scene'];
+const baseRequireParams = ['agent_id', 'saas_id',
+// 'password',
+'env', 'scene'];
 const monitorRequireParams = ['monitorScene'];
 const allRequiredParams = [...baseRequireParams, ...monitorRequireParams];
 
@@ -2978,7 +2981,7 @@ const hsTrackJPOST = ({
 };
 
 // 获取初始化配置
-const baseUrl = 'http://localhost:8090';
+const baseUrl = 'http://192.168.100.159:8090';
 const getInitConf = data => {
   return hsTrackJPOST({
     baseUrl,
@@ -3220,6 +3223,9 @@ function validateParams() {
     return descriptor;
   };
 }
+function generateUniqueId() {
+  return 'id-' + Date.now() + '-' + Math.floor(Math.random() * 10000);
+}
 
 /**
  * 本地提示音
@@ -3228,9 +3234,9 @@ function validateParams() {
  * _byeAudio 结束通话提示音
  */
 const audioList = {
-  _ringAudio: 'https://static.fuxicarbon.com/hs-cti/ring.wav',
-  _waitAudio: 'https://static.fuxicarbon.com/hs-cti/manual.wav',
-  _byeAudio: 'https://static.fuxicarbon.com/hs-cti/bye.wav'
+  _ringAudio: 'http://static.fuxicarbon.com/hs-cti/ring.wav',
+  _waitAudio: 'http://static.fuxicarbon.com/hs-cti/manual.wav',
+  _byeAudio: 'http://static.fuxicarbon.com/hs-cti/bye.wav'
 };
 /** @class HsCTI 红杉外呼类 */
 class HsCTI extends EventEmitter {
@@ -3239,7 +3245,6 @@ class HsCTI extends EventEmitter {
       saas_id,
       agent_id,
       scene,
-      password,
       env,
       loggerLevel
     } = hsCTIInitOptions;
@@ -3408,8 +3413,7 @@ class HsCTI extends EventEmitter {
     this._baseParams = {
       agent_id,
       saas_id,
-      scene,
-      password
+      scene
     };
     const baseTrackParams = {
       agent_id: agent_id,
@@ -3548,9 +3552,9 @@ class HsCTI extends EventEmitter {
         // FS 注册过期时间
         fsRegisterExpireTime: 84000,
         // 单次初始化唯一 ID
-        ctiSessionId: '23',
+        ctiSessionId: generateUniqueId(),
         // IM websocket url
-        imWsServer: 'ws://localhost:8091/ws/cs-im',
+        imWsServer: 'ws://192.168.100.159:8091/ws/cs-im',
         // IM websocket path
         imWsPath: 'ws/cs-im'
       });
@@ -3682,7 +3686,10 @@ class HsCTI extends EventEmitter {
             video: false
           },
           peerConnectionConfiguration: {
-            iceServers: []
+            // iceServers: []
+            iceServers: [{
+              urls: initOptions.ice_server
+            }]
           }
         }
       }
@@ -3752,23 +3759,30 @@ class HsCTI extends EventEmitter {
         }
       },
       onInvite: invitation => {
-        const callId = invitation.request.getHeader('P-LIBRA-Callid') || '';
-        const ctiFlowId = invitation.request.getHeader('P-LIBRA-CtiFlowId') || '';
-        console.log(`Request` + ctiFlowId, this._baseParams.ctiFlowId);
-        if (this.scene === Scene.Manual && ctiFlowId != this._baseParams.ctiFlowId) {
-          this.logger.error(`cti_flow_id 不一致! fe: ${this._baseParams.ctiFlowId} | P-LIBRA-CtiFlowId: ${ctiFlowId}`);
-          return;
-        }
+        // this.scene = Scene.Robot
+        const callId = invitation.request.getHeader('P-LIBRA-CallId') || '';
+        console.log(callId, 2888888888);
+        // const ctiFlowId =
+        //   invitation.request.getHeader('P-LIBRA-CtiFlowId') || ''
+        // if (ctiFlowId != this._baseParams.ctiFlowId) {
+        //   this.logger.error(
+        //     `cti_flow_id 不一致! fe: ${this._baseParams.ctiFlowId} | P-LIBRA-CtiFlowId: ${ctiFlowId}`
+        //   )
+        //   return
+        // }
         this._callId = callId;
+        console.log(callId, 1888888888);
         setBaseOption(BaseOption.TrackParams, {
           call_id: callId
         });
         this.sessionStateChangeAndTrack(invitation.state);
         if ([Scene.Robot, Scene.Monitor].includes(this.scene)) {
           this.playAudio(AudioName.RingAudio);
+          console.log('playAudio', 1888888888);
         }
         if ([Scene.Manual].includes(this.scene)) {
           this.answer();
+          console.log('answer', 1888888888);
         }
         invitation.delegate = {
           onCancel: cancel => {

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 3 - 3
dist/hs-cti.es6.esm.prod.js


+ 35 - 21
dist/hs-cti.es6.umd.js

@@ -4,11 +4,11 @@
 - Version 1.0.9 
 - JS Standard es6
 - Author platformfe
-- Built on 2024/11/16 10:01:11
+- Built on 2024/11/27 10:37:38
 - GitHub 
 - Branch main
-- CommitID d83889f31bf3f437dd61266d77027454207ee34a
-- CommitMessage feat:init
+- CommitID 987d86cd91cde7d1c235ad106d79fc92c305b8ff
+- CommitMessage fix: 修改socket地址
 */
 (function (global, factory) {
     typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('sip.js'), require('socket.io-client')) :
@@ -2370,6 +2370,7 @@
       Scene["Robot"] = "robot";
       Scene["Monitor"] = "monitor";
       Scene["Predictive"] = "predictive";
+      Scene["Wechat"] = "wechat";
     })(exports.Scene || (exports.Scene = {}));
     /**
      * @enum {string} CTI 监听场景
@@ -2689,7 +2690,9 @@
       listen: ExceptMessage.CommonNetworkErrorMsg,
       setActiveService: ExceptMessage.CustomNetworkErrorMsg
     };
-    const baseRequireParams = ['agent_id', 'saas_id', 'password', 'env', 'scene'];
+    const baseRequireParams = ['agent_id', 'saas_id',
+    // 'password',
+    'env', 'scene'];
     const monitorRequireParams = ['monitorScene'];
     const allRequiredParams = [...baseRequireParams, ...monitorRequireParams];
 
@@ -2981,7 +2984,7 @@
     };
 
     // 获取初始化配置
-    const baseUrl = 'http://localhost:8090';
+    const baseUrl = 'http://192.168.100.159:8090';
     const getInitConf = data => {
       return hsTrackJPOST({
         baseUrl,
@@ -3223,6 +3226,9 @@
         return descriptor;
       };
     }
+    function generateUniqueId() {
+      return 'id-' + Date.now() + '-' + Math.floor(Math.random() * 10000);
+    }
 
     /**
      * 本地提示音
@@ -3231,9 +3237,9 @@
      * _byeAudio 结束通话提示音
      */
     const audioList = {
-      _ringAudio: 'https://static.fuxicarbon.com/hs-cti/ring.wav',
-      _waitAudio: 'https://static.fuxicarbon.com/hs-cti/manual.wav',
-      _byeAudio: 'https://static.fuxicarbon.com/hs-cti/bye.wav'
+      _ringAudio: 'http://static.fuxicarbon.com/hs-cti/ring.wav',
+      _waitAudio: 'http://static.fuxicarbon.com/hs-cti/manual.wav',
+      _byeAudio: 'http://static.fuxicarbon.com/hs-cti/bye.wav'
     };
     /** @class HsCTI 红杉外呼类 */
     class HsCTI extends EventEmitter {
@@ -3242,7 +3248,6 @@
           saas_id,
           agent_id,
           scene,
-          password,
           env,
           loggerLevel
         } = hsCTIInitOptions;
@@ -3411,8 +3416,7 @@
         this._baseParams = {
           agent_id,
           saas_id,
-          scene,
-          password
+          scene
         };
         const baseTrackParams = {
           agent_id: agent_id,
@@ -3551,9 +3555,9 @@
             // FS 注册过期时间
             fsRegisterExpireTime: 84000,
             // 单次初始化唯一 ID
-            ctiSessionId: '23',
+            ctiSessionId: generateUniqueId(),
             // IM websocket url
-            imWsServer: 'ws://localhost:8091/ws/cs-im',
+            imWsServer: 'ws://192.168.100.159:8091/ws/cs-im',
             // IM websocket path
             imWsPath: 'ws/cs-im'
           });
@@ -3685,7 +3689,10 @@
                 video: false
               },
               peerConnectionConfiguration: {
-                iceServers: []
+                // iceServers: []
+                iceServers: [{
+                  urls: initOptions.ice_server
+                }]
               }
             }
           }
@@ -3755,23 +3762,30 @@
             }
           },
           onInvite: invitation => {
-            const callId = invitation.request.getHeader('P-LIBRA-Callid') || '';
-            const ctiFlowId = invitation.request.getHeader('P-LIBRA-CtiFlowId') || '';
-            console.log(`Request` + ctiFlowId, this._baseParams.ctiFlowId);
-            if (this.scene === exports.Scene.Manual && ctiFlowId != this._baseParams.ctiFlowId) {
-              this.logger.error(`cti_flow_id 不一致! fe: ${this._baseParams.ctiFlowId} | P-LIBRA-CtiFlowId: ${ctiFlowId}`);
-              return;
-            }
+            // this.scene = Scene.Robot
+            const callId = invitation.request.getHeader('P-LIBRA-CallId') || '';
+            console.log(callId, 2888888888);
+            // const ctiFlowId =
+            //   invitation.request.getHeader('P-LIBRA-CtiFlowId') || ''
+            // if (ctiFlowId != this._baseParams.ctiFlowId) {
+            //   this.logger.error(
+            //     `cti_flow_id 不一致! fe: ${this._baseParams.ctiFlowId} | P-LIBRA-CtiFlowId: ${ctiFlowId}`
+            //   )
+            //   return
+            // }
             this._callId = callId;
+            console.log(callId, 1888888888);
             setBaseOption(BaseOption.TrackParams, {
               call_id: callId
             });
             this.sessionStateChangeAndTrack(invitation.state);
             if ([exports.Scene.Robot, exports.Scene.Monitor].includes(this.scene)) {
               this.playAudio(AudioName.RingAudio);
+              console.log('playAudio', 1888888888);
             }
             if ([exports.Scene.Manual].includes(this.scene)) {
               this.answer();
+              console.log('answer', 1888888888);
             }
             invitation.delegate = {
               onCancel: cancel => {

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 3 - 3
dist/hs-cti.es6.umd.prod.js


+ 1 - 1
src/api/hs-cti/ctiSdkModel.ts

@@ -1,7 +1,7 @@
 export interface CTIBaseOptions {
   agent_id: string
   saas_id?: string
-  password: string
+  // password: string
   scene: string
   ctiFlowId?: string
   monitorScene?: string

+ 1404 - 0
src/hs-cti/HsCTI-trash.ts

@@ -0,0 +1,1404 @@
+import { Logger, LoggerLevels } from '../Logger'
+import {
+  type Invitation,
+  type RegistererOptions,
+  type UserAgentOptions,
+  type LogLevel,
+  Registerer,
+  RegistererState,
+  SessionState,
+  UserAgentState,
+  // TransportState,
+  UserAgent,
+  Web,
+  Core,
+  URI,
+  RequestPendingError,
+  RegistererRegisterOptions
+} from 'sip.js'
+import EventEmitter from '../eventemitter'
+import SdSocket from './HsSocket'
+import {
+  getUserMedia,
+  assignStream,
+  checkCTIStatus,
+  handleApiRes,
+  validateParams,
+  generateUniqueId
+} from './tools'
+import { upperCamelToLowerSnake } from '../utils'
+
+import {
+  getInitConf,
+  getCtiFlowId,
+  agentCheckIn,
+  agentCheckOut,
+  agentSetIdle,
+  agentSetBusy,
+  getAgentStatus,
+  manualCall,
+  manualHang,
+  listen,
+  loadAgentGroupData,
+  setActiveServiceTask
+} from '../api/hs-cti/ctiSdk'
+
+import {
+  type SocketStatusChangeParams,
+  type SIPStatusChangeParams,
+  type CTIEventParams,
+  AudioName,
+  // TrackSource,
+  SocketEvent,
+  ExceptMessage
+} from './type'
+import {
+  type HsCTIInitOptions,
+  type RequiredHsCTIInitOptions,
+  Scene,
+  CTIStatus,
+  SocketStatus,
+  SIPStatus,
+  SessionStatus,
+  CTIErrorType,
+  SdkErrorCode,
+  HskTerminatedCode,
+  CTIEvent,
+  type CTIRes
+} from './outputType'
+import type {
+  CTIBaseOptions,
+  CTIManualCallOptions,
+  InitOptions
+} from '../api/hs-cti/ctiSdkModel'
+// import { serverTrack } from '../api/fetchApi'
+import {
+  setBaseOption,
+  resetBaseOption,
+  BaseOption
+  // getBaseOption
+} from './storage'
+/**
+ * 本地提示音
+ * _ringAudio 机器人外呼/监听,等待接起提示音
+ * _waitAudio 主动外呼,点击拨打时的等待音
+ * _byeAudio 结束通话提示音
+ */
+const audioList = {
+  _ringAudio: 'http://static.fuxicarbon.com/hs-cti/ring.wav',
+  _waitAudio: 'http://static.fuxicarbon.com/hs-cti/manual.wav',
+  _byeAudio: 'http://static.fuxicarbon.com/hs-cti/bye.wav'
+}
+
+/** @class SdCTI 水滴外呼类 */
+export class SdCTI extends EventEmitter {
+  private logger: Logger
+  public loggerLevel: LoggerLevels
+  public scene: Scene
+  public agent_id: string
+  public saas_id: string
+  /** SdCTI 单例 */
+  public static instance: SdCTI | undefined
+  /** 通过接口获取的初始化配置 */
+  private _initOptions: InitOptions | undefined
+
+  /** SdCTI 状态 */
+  private _ctiStatus!: CTIStatus
+  private _ctiStatusList!: CTIStatus[]
+  /** socket 状态 */
+  private _socketStatus!: SocketStatus
+  private _socketStatusList!: SocketStatus[]
+  /** sip 状态 */
+  private _sipStatus!: SIPStatus
+  private _sipStatusList!: SIPStatus[]
+
+  /** IM socket connection */
+  private _socket!: SdSocket
+  /** SIP.js 的 UserAgent 用户代理 */
+  private _sipUserAgent: UserAgent | undefined
+  /** 用户注册实例 */
+  private _sipRegisterer?: Registerer
+  /** RTC 会话 */
+  private _incomingSession: Invitation | undefined
+  /** 一次通话唯一标识 ID */
+  private _callId!: string
+  private _ctiFlowIdList!: string[]
+  /** 基本请求参数 */
+  private _baseParams: CTIBaseOptions
+
+  /** 等待音本地媒体流 */
+  private _waitAudio: HTMLAudioElement
+  /** 振铃提示音本地媒体流 */
+  private _ringAudio: HTMLAudioElement
+  /** 结束通话提示音本地媒体流 */
+  private _byeAudio: HTMLAudioElement
+  /** 远端语音流 */
+  private _remoteAudio: HTMLAudioElement
+  /** SIP心跳请求实例 */
+  private optionsPingRequest?: Core.OutgoingRequest
+  /** 是否处于心跳逻辑执行中 */
+  private optionsPingRunning: boolean
+  /** 心跳定时器 */
+  private optionsPingTimeout?: ReturnType<typeof setTimeout>
+  /** 重试规则定时器 */
+  private registrationAttemptTimeout?: ReturnType<typeof setTimeout>
+  /** 心跳失败标识,false代表心跳失败 */
+  private optionsPingFailure: boolean
+  /** UA重连是否开启 */
+  private shouldBeConnected: boolean
+  /** 是否允许注册UA */
+  private shouldBeRegistered: boolean
+  /** 重试注册次数 */
+  private registerAttempts: number
+  /**
+   * @param HsCTIInitOptions 初始化 SdCTI 的配置
+   */
+  private constructor(
+    HsCTIInitOptions: HsCTIInitOptions | RequiredHsCTIInitOptions
+  ) {
+    const { saas_id, agent_id, scene, env, loggerLevel } = HsCTIInitOptions
+    /** 调用父类构造函数,初始化 EventEmitter */
+    super()
+    this.loggerLevel = window.ctiLoggerLevel || loggerLevel || LoggerLevels.log
+    this.logger = new Logger(this.loggerLevel, 'SdCTI')
+    this._waitAudio = new Audio()
+    this._ringAudio = new Audio()
+    this._byeAudio = new Audio()
+    this._remoteAudio = new Audio()
+    this.shouldBeConnected = true
+    this.shouldBeRegistered = true
+    this.optionsPingRequest = undefined
+    this.optionsPingRunning = false
+    this.optionsPingFailure = false
+    this.registerAttempts = 10
+
+    this.saas_id = saas_id
+    this.agent_id = agent_id
+    this.scene = scene
+    setBaseOption(BaseOption.ENV, env)
+    setBaseOption(BaseOption.LoggerLevel, this.loggerLevel)
+
+    this._baseParams = { agent_id, saas_id, scene }
+    const baseTrackParams = { agent_id: agent_id, vcc_id: saas_id, scene }
+
+    if (scene === Scene.Monitor) {
+      const { monitorScene } = HsCTIInitOptions
+      this._baseParams = { ...this._baseParams, monitorScene }
+      setBaseOption(
+        BaseOption.TrackParams,
+        { ...baseTrackParams, monitor_scene: monitorScene },
+        true
+      )
+    } else {
+      setBaseOption(BaseOption.TrackParams, baseTrackParams, true)
+    }
+
+    this.initInstanceOptions()
+    this.setCTIStatus(CTIStatus.Initial)
+  }
+
+  /**
+   * @public getInstance 获取 SdCTI 的实例
+   * @param HsCTIInitOptions 初始化 SdCTI 的配置
+   */
+  @validateParams()
+  public static getInstance(
+    HsCTIInitOptions: HsCTIInitOptions | RequiredHsCTIInitOptions
+  ): SdCTI {
+    if (!SdCTI.instance) {
+      SdCTI.instance = new SdCTI(HsCTIInitOptions)
+    }
+    return SdCTI.instance
+  }
+
+  get getCTIStatus() {
+    return this._ctiStatus
+  }
+  get getSocketStatus() {
+    return this._socketStatus
+  }
+  get getSIPStatus() {
+    return this._sipStatus
+  }
+  get getSessionStatus() {
+    return this._incomingSession
+      ? SessionStatus[this._incomingSession.state]
+      : undefined
+  }
+
+  /** @private stopLocalAudio 停止本地语音提示 */
+  private stopLocalAudio() {
+    switch (this.scene) {
+      case Scene.Robot:
+      case Scene.Monitor:
+        this.stopAudio(AudioName.RingAudio, true)
+        break
+      case Scene.Manual:
+        // this.stopAudio(AudioName.WaitAudio, true)
+        break
+    }
+  }
+
+  /** @private init 初始化SDK */
+  @getUserMedia()
+  public init() {
+    // 不允许重复初始化,需要在如果重复初始化需要提前销毁sdk
+    const [lastStatus] = this._ctiStatusList.slice(-1)
+    if (lastStatus !== CTIStatus.Initial) return
+    this.setAudioSrc(AudioName.ByeAudio, false)
+    switch (this.scene) {
+      case Scene.Robot:
+      case Scene.Monitor:
+        this.setAudioSrc(AudioName.RingAudio, true)
+        break
+      case Scene.Manual:
+        // this.setAudioSrc(AudioName.WaitAudio, true)
+        break
+    }
+    this.getInitConfig()
+  }
+
+  /** @private getInitConfig 获取连接 socket 和 SIP.js 的配置并初始化 */
+  private async getInitConfig() {
+    const res = await getInitConf(this._baseParams)
+    const { code, data, msg } = res
+    let initOptions = data
+    initOptions = {
+      ...initOptions,
+      imHeartTime: 3,
+      // IM 重试次数
+      imRetryCount: 3,
+      // FS 心跳间隔
+      fsHeartTime: 60,
+      // FS 重试次数,
+      fsRetryCount: 3,
+      // FS 重试间隔时间
+      fsRetryTime: 60,
+      // FS 注册过期时间
+      fsRegisterExpireTime: 84000,
+      // 单次初始化唯一 ID
+      ctiSessionId: generateUniqueId(),
+      // IM websocket url
+      imWsServer: 'ws://192.168.100.159:8091/ws/cs-im',
+      // IM websocket path
+      imWsPath: 'ws/cs-im'
+    }
+    if (code === 0) {
+      setBaseOption(BaseOption.TrackParams, {
+        sip_server: initOptions.sipServer,
+        cti_session_id: initOptions.ctiSessionId
+      })
+      this._initOptions = initOptions
+      this.initSocket(initOptions)
+      this.initSIPJS(initOptions)
+    } else {
+      this.eventEmitAndTrack(CTIEvent.OnCtiError, {
+        type: CTIErrorType.ServerTerminated,
+        code: HskTerminatedCode.GetInitConfig,
+        msg,
+        method: 'getInitConfig'
+      })
+      this.initInstanceOptions()
+    }
+  }
+
+  /**
+   * @private initSocket 初始化 socket 连接
+   * @param {InitOptions} 初始化接口获取的基本配置信息
+   */
+  private initSocket(initOptions: InitOptions) {
+    this.setSocketStatus({ status: SocketStatus.Initial })
+    this._socket = new SdSocket({
+      agent_id: this.agent_id,
+      saas_id: this.saas_id,
+      // appCode: initOptions.appCode,
+      imWsServer: initOptions.imWsServer,
+      imWsPath: initOptions.imWsPath,
+      imHeartTime: initOptions.imHeartTime,
+      imRetryCount: initOptions.imRetryCount,
+      ctiSessionId: initOptions.ctiSessionId,
+      loggerLevel: this.loggerLevel
+    })
+
+    this._socket.on(SocketEvent.SocketDownEvent, ({ eventData }) => {
+      const { eventName, ext } = eventData
+      // serverTrack({
+      //   ...getBaseOption(BaseOption.TrackParams),
+      //   source: TrackSource.FeIMDown,
+      //   event_name: eventName,
+      //   ext
+      // })
+      this.logger.log(
+        `socket server down | ${eventName} | ${JSON.stringify(ext)}`
+      )
+      this.handleSocketDownEvent({ eventName, ext })
+    })
+
+    this._socket.on(SocketEvent.SetSocketStatus, res =>
+      this.setSocketStatus(res)
+    )
+
+    this._socket.initSocket()
+  }
+
+  /** @private handleSocketDownEven 处理服务端下行事件 */
+  private handleSocketDownEvent({ eventName, ext }: CTIEventParams) {
+    const ctiFlowId = this._baseParams.ctiFlowId
+    const extCtiFlowId = ext.ctiFlowId
+    /** 手动外呼 & 监听场景特殊校验:cti_flow_id 一致性 */
+    if (
+      [Scene.Manual, Scene.Monitor].includes(this.scene) &&
+      ctiFlowId &&
+      extCtiFlowId &&
+      extCtiFlowId !== ctiFlowId
+    ) {
+      this.logger.error(
+        `cti_flow_id | 不一致! fe: ${ctiFlowId}, server: ${extCtiFlowId}, eventName: ${eventName}`
+      )
+      // serverTrack({
+      //   ...getBaseOption(BaseOption.TrackParams),
+      //   source: TrackSource.FeIMDown,
+      //   event_name: 'socket_down_cti_flow_id_diff',
+      //   ext: {
+      //     server_event_name: upperCamelToLowerSnake(eventName),
+      //     server_cti_flow_id: extCtiFlowId
+      //   }
+      // })
+      return
+    }
+
+    this.serverEventEmit({ eventName, ext })
+  }
+
+  /**
+   * @private eventEmitAndTrack SDK 对外抛出事件并统一埋点
+   * @param {CTIEvent} eventName 事件名称
+   * @param {object} ext 事件参数
+   * @param { error } error 错误详情或错误辅助信息
+   */
+  private eventEmitAndTrack(
+    eventName: CTIEvent,
+    ext: object,
+    error?: string | object
+  ) {
+    this.logger.debug(`sdk emit | ${eventName} | ${JSON.stringify(ext)}`)
+    // serverTrack({
+    //   ...getBaseOption(BaseOption.TrackParams),
+    //   source: TrackSource.FeEmit,
+    //   event_name: eventName,
+    //   ext,
+    //   error
+    // })
+    try {
+      this.emit(eventName, ext)
+      console.log(error)
+    } catch (error) {
+      this.logger.error(`业务监听 ${eventName} 事件,处理回调时报错: ${error}`)
+    }
+  }
+
+  /** @private serverEventEmit 统一处理服务端推送的事件 */
+  private serverEventEmit({ eventName, ext }: CTIEventParams) {
+    switch (eventName) {
+      case CTIEvent.OnRingStart:
+        this.stopLocalAudio()
+        break
+      case CTIEvent.OnAgentWorkReport:
+        this.eventEmitAndTrack(eventName, ext)
+        /** 主动外呼:用户接起后停止响铃等待音  */
+        if (['11'].includes(ext.workStatus)) {
+          this.stopLocalAudio()
+        }
+        break
+      case CTIEvent.OnRingEnd:
+      case CTIEvent.OnMethodResponseEvent:
+      case CTIEvent.OnAgentGroupQuery:
+      case CTIEvent.OnEventPrompt:
+      case CTIEvent.OnPrompt:
+        /**  TODO: 后 4 个事件未来服务端不再推送时删掉  */
+        break
+      default:
+        this.eventEmitAndTrack(eventName, ext)
+    }
+  }
+
+  /** @private initSIPJS 初始化 SIP.js */
+  private initSIPJS(initOptions: InitOptions) {
+    this.setSipStatus({ status: SIPStatus.Initial })
+
+    const userAgentOptions: UserAgentOptions = {
+      /** sip 底层依赖的 websocket 连接 */
+      transportOptions: {
+        server: initOptions.wss_server
+      },
+      /** sip 连接 */
+      uri: UserAgent.makeURI(initOptions.sip_server),
+      /** sip 日志等级 */
+      logLevel: LoggerLevels[this.loggerLevel] as LogLevel,
+      /** User-Agent 字符串的值,因为 FS 解析不了其他字段,所以把 ctiSessionId 放这了 */
+      userAgentString: initOptions.ctiSessionId,
+      /** 坐席 FS 注册密码 */
+      authorizationPassword: initOptions.phone_pwd,
+      /** SDK 默认 60,服务端目前是 30,服务端早于 SDK 即可 */
+      // noAnswerTimeout: 60,
+      /** 接受 dialog 之外的 NOTIFY  */
+      allowLegacyNotifications: true,
+      /** 会话描述配置 */
+      sessionDescriptionHandlerFactoryOptions: {
+        constraints: {
+          audio: true,
+          video: false
+        },
+        /** 通路地址,用于 NAT 穿墙 */
+        peerConnectionConfiguration: {
+          iceServers: [{ urls: initOptions.ice_server }]
+        }
+      }
+    }
+    this._sipUserAgent = new UserAgent(userAgentOptions)
+    // 监听UA的状态变化
+    this.sipstateChangeListener()
+    // 设置UA的回调监听
+    this._sipUserAgent.delegate = this.sipDelegate()
+    /** 启动 UserAgent 并 Registerer 注册 */
+    this.connect()!.catch((error: Error) => {
+      const err = `SIP UserAgent 启动失败: ${error}`
+      this.logger.error(err)
+      this.setSipStatus({
+        status: SIPStatus.Terminated,
+        code: HskTerminatedCode.SIPInitUserAgent,
+        error: err,
+        method: 'initSIPJS'
+      })
+    })
+
+    window.addEventListener('online', () => {
+      this.logger.log(`Online`)
+      if (this.shouldBeConnected) {
+        this.connect()
+      }
+    })
+  }
+  private connect() {
+    this.shouldBeConnected = true
+    if (this._sipUserAgent?.state !== UserAgentState.Started) {
+      return this._sipUserAgent?.start()
+    }
+    return this._sipUserAgent?.reconnect()
+  }
+  /** @private sipstateChangeListener SIP.js 内置的事件监听 */
+  private sipstateChangeListener() {
+    /** SIP UserAgent 的状态监听 */
+    this._sipUserAgent!.stateChange.addListener((newState: UserAgentState) => {
+      switch (newState) {
+        case UserAgentState.Started:
+          this.setSipStatus({ status: SIPStatus.Started })
+          break
+        case UserAgentState.Stopped:
+          this.setSipStatus({
+            status: SIPStatus.Terminated,
+            code: this._sipStatusList.includes(SIPStatus.Started)
+              ? HskTerminatedCode.SIPUserAgentStateStopped
+              : HskTerminatedCode.SIPInitUserAgent,
+            error: 'SIP UserAgentState 状态停止',
+            method: 'sipstateChangeListener'
+          })
+          break
+      }
+    })
+
+    /** SIP 底层依赖的 Websocket 连接状态监听 */
+    // this._sipUserAgent!.transport.stateChange.addListener(
+    //   (newState: TransportState) => {
+    //     switch (newState) {
+    //       case TransportState.Connecting:
+    //         this.setSipStatus({ status: SIPStatus.Connecting })
+    //         break
+    //       case TransportState.Connected:
+    //         this.setSipStatus({ status: SIPStatus.Connected })
+    //         break
+    //       case TransportState.Disconnected:
+    //         this.setSipStatus({
+    //           status: SIPStatus.Terminated,
+    //           code: this._sipStatusList.includes(SIPStatus.Connected)
+    //             ? HskTerminatedCode.SIPTransportStateDisconnected
+    //             : HskTerminatedCode.SIPInitTransport,
+    //           error: 'SIP 底层的 Webscoket 连接断开',
+    //           method: 'sipstateChangeListener'
+    //         })
+    //         break
+    //     }
+    //   }
+    // )
+  }
+  /** 开始心跳检测 */
+  private optionsPingStart(initOptions: InitOptions): void {
+    this.logger.log('开始心跳检测')
+
+    const requestURI = this._sipUserAgent!.configuration.uri
+    const toURI = this._sipUserAgent!.configuration.uri
+    const fromURI = this._sipUserAgent!.userAgentCore.configuration.aor
+
+    this.optionsPingRun(requestURI, fromURI, toURI, initOptions)
+  }
+
+  /** @private optionsPingRun SIP 心跳监测主逻辑 */
+  private optionsPingRun(
+    requestURI: URI,
+    fromURI: URI,
+    toURI: URI,
+    initOptions: InitOptions
+  ) {
+    if (initOptions.fsHeartTime < 1) {
+      throw new Error('缺少心跳间隔频次')
+    }
+    // 防止重复执行心跳逻辑
+    if (this.optionsPingRunning) {
+      return
+    }
+    this.optionsPingRunning = true
+
+    this.optionsPingTimeout = setTimeout(() => {
+      this.optionsPingTimeout = undefined
+      // 心跳正常后续逻辑
+      const onPingSuccess = () => {
+        this.optionsPingFailure = false
+        if (this.optionsPingRunning) {
+          this.optionsPingRunning = false
+          this.optionsPingRun(requestURI, fromURI, toURI, initOptions)
+        }
+      }
+
+      // 心跳异常后续逻辑
+      const onPingFailure = (res: Core.IncomingResponse | string) => {
+        this.logger.error('sip心跳失败')
+        this.optionsPingFailure = true
+        this.optionsPingRunning = false
+        this._sipUserAgent!.transport.disconnect()
+        console.log(res)
+        // 报告链接错误,上报错误
+        // serverTrack({
+        //   ...getBaseOption(BaseOption.TrackParams),
+        //   source: TrackSource.FeSIP,
+        //   event_name: CTIEvent.OnCtiError,
+        //   msg: 'sip_heart_beat_err',
+        //   method: 'optionsPingRun',
+        //   code: HskTerminatedCode.SipHeartBeatErr,
+        //   error: `${JSON.stringify(res)}`
+        // })
+        // this.logger.error('heartbeat' + res?.message.statusCode)
+      }
+
+      const core = this._sipUserAgent!.userAgentCore
+      const message = core.makeOutgoingRequestMessage(
+        'OPTIONS',
+        requestURI,
+        fromURI,
+        toURI,
+        { userAgentString: initOptions.ctiSessionId },
+        []
+      )
+      this.optionsPingRequest = core.request(message, {
+        onAccept: res => {
+          this.logger.debug('heartbeat' + res.message.statusCode)
+          this.optionsPingRequest = undefined
+          onPingSuccess()
+        },
+        onReject: res => {
+          this.optionsPingRequest = undefined
+          // - 408 发送上行事件超时
+          // - 503 服务异常
+          console.warn(res)
+          if (
+            res.message.statusCode === 408 ||
+            res.message.statusCode === 503
+          ) {
+            onPingFailure(res)
+          } else {
+            onPingSuccess()
+          }
+        }
+      })
+    }, initOptions.fsHeartTime * 1000)
+  }
+
+  /** 停止心跳检测 */
+  private optionsPingStop(): void {
+    this.optionsPingRunning = false
+    this.optionsPingFailure = false
+    if (this.optionsPingRequest) {
+      this.optionsPingRequest.dispose()
+      this.optionsPingRequest = undefined
+    }
+    if (this.optionsPingTimeout) {
+      clearTimeout(this.optionsPingTimeout)
+      this.optionsPingTimeout = undefined
+    }
+  }
+
+  /**
+   * @private newSipRegisterer 创建 SIP Registerer
+   * @param {number} fsRegisterExpireTime FS 注册过期时间
+   */
+  private newSipRegisterer(fsRegisterExpireTime: number) {
+    const registererOptions: RegistererOptions = {
+      expires: fsRegisterExpireTime
+    }
+    this._sipRegisterer = new Registerer(this._sipUserAgent!, registererOptions)
+
+    /** 注册状态变化事件处理程序 */
+    this._sipRegisterer.stateChange.addListener((newState: RegistererState) => {
+      const state = `sip_registerer_state_${upperCamelToLowerSnake(newState)}`
+      this.logger.debug(state)
+      // serverTrack({
+      //   ...getBaseOption(BaseOption.TrackParams),
+      //   source: TrackSource.FeSIP,
+      //   event_name: state
+      // })
+      switch (newState) {
+        case RegistererState.Registered:
+          this.setSipStatus({ status: SIPStatus.Ready })
+          break
+        case RegistererState.Unregistered:
+          // this.setSipStatus({
+          //   status: SIPStatus.Terminated,
+          //   code: this._sipStatusList.includes(SIPStatus.Ready)
+          //     ? HskTerminatedCode.SIPRegistererStateTerminated
+          //     : HskTerminatedCode.SIPUnRegistered,
+          //   error: 'SIP Registerer 注册异常',
+          //   method: 'newSipRegisterer'
+          // })
+          if (this.shouldBeRegistered) {
+            this.attemptRegistration()
+          }
+          break
+        case RegistererState.Terminated:
+          if (!this.shouldBeConnected) {
+            this.setSipStatus({
+              status: SIPStatus.Terminated,
+              code: this._sipStatusList.includes(SIPStatus.Ready)
+                ? HskTerminatedCode.SIPRegistererStateTerminated
+                : HskTerminatedCode.SIPInitRegister,
+              error: 'SIP Registerer 状态注销',
+              method: 'newSipRegisterer'
+            })
+          }
+          break
+      }
+    })
+    return this.attemptRegistration(true)
+  }
+
+  private attemptRegistration(withoutDelay = false): Promise<void> {
+    this.logger.log(`注册尝试开始${withoutDelay ? '需要等待' : '立即执行'}`)
+
+    if (this.registrationAttemptTimeout !== undefined) {
+      this.logger.log('正在注册')
+      return Promise.resolve()
+    }
+
+    const _register = (): Promise<void> => {
+      if (!this._sipRegisterer) {
+        this.logger.log('暂无registerer,无法执行后续逻辑')
+        return Promise.resolve()
+      }
+
+      if (!this._sipUserAgent!.isConnected()) {
+        this.logger.log('SIP UserAgent未连接,无法执行后续逻行')
+        return Promise.resolve()
+      }
+
+      if (this._sipUserAgent!.state === UserAgentState.Stopped) {
+        this.logger.log('SIP UA已停止,无需注册')
+        return Promise.resolve()
+      }
+
+      return this._sipRegisterer.register(this.registerOptions()).then(() => {
+        return
+      })
+    }
+
+    const computeRegistrationTimeout = (lowerBound: number): number => {
+      const upperBound = lowerBound * 2
+      return 1000 * (Math.random() * (upperBound - lowerBound) + lowerBound)
+    }
+
+    return new Promise<void>((resolve, reject) => {
+      this.registrationAttemptTimeout = setTimeout(() => {
+        _register()
+          .then(() => {
+            this.registrationAttemptTimeout = undefined
+            resolve()
+          })
+          .catch(error => {
+            this.registrationAttemptTimeout = undefined
+            if (error instanceof RequestPendingError) {
+              resolve()
+            } else {
+              reject(error)
+            }
+          }),
+          withoutDelay ? 0 : computeRegistrationTimeout(1)
+      })
+    })
+  }
+  /**
+   * @private sessionStateChangeAndTrack SIP SessionState change event
+   * @param SessionState
+   */
+  private sessionStateChangeAndTrack(status: SessionState) {
+    this.eventEmitAndTrack(CTIEvent.OnSessionStatusChange, {
+      status
+    })
+    const trackName = `sip_session_state_${upperCamelToLowerSnake(status)}`
+    this.logger.log(trackName)
+    // serverTrack({
+    //   ...getBaseOption(BaseOption.TrackParams),
+    //   source: TrackSource.FeSIP,
+    //   event_name: trackName
+    // })
+  }
+  /**
+   * @private register的事件接收器,例如register动作是否成功
+   * @returns 注册配置项
+   */
+  private registerOptions(): RegistererRegisterOptions {
+    // eslint-disable-next-line @typescript-eslint/no-this-alias
+    const self: SdCTI = this
+    return {
+      requestDelegate: {
+        onReject(): void {
+          if (self.registerAttempts < 1) return
+          self.attemptRegistration()
+          self.registerAttempts--
+        }
+      }
+    }
+  }
+  /** @private sipDelegate SIP 的事件接收器,例如 FS 的 INVITE 来电,SIP 链接断开等 */
+  private sipDelegate() {
+    // eslint-disable-next-line @typescript-eslint/no-this-alias
+    const self: SdCTI = this
+    return {
+      /** 与 FS 的连接断开事件,如果无 error 则为正常的调用 SIP.js 的 stop() 方法断开 */
+      onDisconnect(error?: Error): void {
+        let optionsPingFailure = false
+        optionsPingFailure = self.optionsPingFailure
+        self.optionsPingStop()
+        if (error || optionsPingFailure) {
+          /** 断开连接后停止发送心跳 */
+          // 如果已经注册过则取消注册
+          if (self._sipRegisterer) {
+            self._sipRegisterer.dispose()
+            self._sipRegisterer = undefined
+            self.shouldBeRegistered = false
+          }
+          /** 如果开启重连开关,则进行重连 */
+          if (self.shouldBeConnected) {
+            self.setSipStatus({
+              status: SIPStatus.ReTry
+            })
+            self.attemptReconnection()
+          }
+        } else {
+          self.setSipStatus({
+            status: SIPStatus.Terminated
+          })
+        }
+      },
+
+      onConnect(): void {
+        self.shouldBeRegistered = true
+        self.newSipRegisterer(
+          (self._initOptions as InitOptions).fsRegisterExpireTime
+        )
+        // 与fs建立连接成功后开始发送sip心跳
+        self.optionsPingStart(self._initOptions as InitOptions)
+      },
+
+      /** 接受来自 FS 的 INVITE 请求,即接电话事件 */
+      onInvite(invitation: Invitation): void {
+        /** 解析水滴自定义的单次会话 callId */
+        const callId = invitation.request.getHeader('P-LIBRA-Callid') || ''
+        /** 手动外呼场景下,如果 ctiFlowId 不同直接不接受 dialog 会话的任何操作 */
+        const ctiFlowId =
+          invitation.request.getHeader('P-LIBRA-CtiFlowId') || ''
+        if (
+          self.scene === Scene.Manual &&
+          ctiFlowId !== self._baseParams.ctiFlowId
+        ) {
+          // serverTrack({
+          //   ...getBaseOption(BaseOption.TrackParams),
+          //   source: TrackSource.FeSIP,
+          //   event_name: 'sip_cti_flow_id_diff',
+          //   ext: {
+          //     server_cti_flow_id: ctiFlowId,
+          //     call_id: callId
+          //   }
+          // })
+          self.logger.error(
+            `cti_flow_id 不一致! fe: ${self._baseParams.ctiFlowId} | P-LIBRA-CtiFlowId: ${ctiFlowId}`
+          )
+          return
+        }
+        self._callId = callId
+        setBaseOption(BaseOption.TrackParams, {
+          call_id: callId
+        })
+        /** INVITE 初始状态 */
+        self.sessionStateChangeAndTrack(invitation.state)
+        self._incomingSession = invitation
+
+        /** 机器人外呼和监听收到 INVITE 请求只振铃,不接起 */
+        if ([Scene.Robot, Scene.Monitor].includes(self.scene)) {
+          self.playAudio(AudioName.RingAudio)
+        }
+
+        /** 手动外呼和微信语音自动接起 */
+        if ([Scene.Manual, Scene.Wechat].includes(self.scene)) {
+          self.answer()
+        }
+
+        /** 单次会话的事件监听 */
+        invitation.delegate = {
+          /** 通话请求被取消 */
+          onCancel(cancel) {
+            const error = `sip_invitation_on_cancel | ${cancel.request.data}`
+            self.logger.error(error)
+            self.eventEmitAndTrack(
+              CTIEvent.OnCtiError,
+              {
+                type: CTIErrorType.SdkError,
+                code: SdkErrorCode.InvitationCancel,
+                msg: '当前通话已结束',
+                method: 'sipDelegate'
+              },
+              error
+            )
+          },
+          /** 接受来着 FS 的 BYE 请求,并回复 200 OK */
+          onBye(bye) {
+            self.logger.log('sip_invitation_on_bye | bye.accept()')
+            bye.accept()
+          }
+        }
+        /** 会话状态变化监听 */
+        invitation.stateChange.addListener((newState: SessionState) => {
+          self.sessionStateChangeAndTrack(newState)
+
+          switch (newState) {
+            case SessionState.Initial:
+            case SessionState.Establishing:
+              break
+            case SessionState.Established:
+              /** 仅 Scene.Robot 和 Scene.Monitor 在坐席接起后关闭振铃提示 */
+              if ([Scene.Robot, Scene.Monitor].includes(self.scene)) {
+                self.stopLocalAudio()
+              }
+              /** 远端语音流关联到本地 */
+              invitation.sessionDescriptionHandler instanceof
+                Web.SessionDescriptionHandler &&
+                assignStream(
+                  invitation.sessionDescriptionHandler.remoteMediaStream,
+                  self._remoteAudio,
+                  self.logger
+                )
+              break
+            case SessionState.Terminating:
+              break
+            case SessionState.Terminated:
+              /** 播放停止通话等待音,1s 后停止 */
+              self.playAudio(AudioName.ByeAudio)
+              setTimeout(() => {
+                self.stopAudio(AudioName.ByeAudio, false)
+              }, 1000)
+              self.stopLocalAudio()
+              self._incomingSession = undefined
+              break
+            default:
+              break
+          }
+        })
+      }
+    }
+  }
+
+  /**
+   * UA重连逻辑
+   * @param reconnectionAttempt
+   * @returns void
+   * 参考来源:https://github.com/onsip/SIP.js/blob/main/src/platform/web/session-manager/session-manager.ts
+   */
+  private attemptReconnection(reconnectionAttempt = 1): void {
+    const reconnectionAttempts = 10
+    const reconnectionDelay = 3
+
+    if (!this.shouldBeConnected) {
+      this.logger.log('不需要重连')
+      return
+    }
+
+    if (reconnectionAttempt > reconnectionAttempts) {
+      this.setSipStatus({
+        status: SIPStatus.Terminated,
+        code: HskTerminatedCode.SIPOnDisconnect,
+        error: `超过重连次数,终止重连`,
+        method: 'attemptReconnection'
+      })
+      this.logger.log('超过重连次数,终止重连')
+      return
+    }
+
+    if (reconnectionAttempt === 1) {
+      this.logger.log(
+        `重连中:第${reconnectionAttempt}/${reconnectionAttempts}次`
+      )
+    } else {
+      this.logger.log(
+        `重连中:第${reconnectionAttempt}/${reconnectionAttempts}次,将在${reconnectionDelay}后执行`
+      )
+    }
+
+    setTimeout(
+      () => {
+        this._sipUserAgent
+          ?.reconnect()
+          .then(() => {
+            this.logger.log(
+              `重连第${reconnectionAttempt}/${reconnectionAttempts}次成功`
+            )
+          })
+          .catch(error => {
+            this.logger.error(
+              `重连第${reconnectionAttempt}/${reconnectionAttempts}次失败`
+            )
+            this.logger.error(error.message)
+            this.attemptReconnection(++reconnectionAttempt)
+          })
+      },
+      reconnectionAttempt === 1 ? 0 : reconnectionDelay * 1000
+    )
+  }
+  /**
+   * @private setSocketStatus Socket 状态流转
+   * @param SocketStatusChangeParams Socket 状态流转参数及错误详情等
+   */
+  private setSocketStatus({ status, code, error }: SocketStatusChangeParams) {
+    if (status === this.getSocketStatus) return
+    /** Socket 状态流转 */
+    this._socketStatus = status
+    this._socketStatusList.push(status)
+
+    const logInfo = `socket status | ${status} | ${this._socketStatusList}`
+    status === SocketStatus.Terminated
+      ? this.logger.warn(logInfo)
+      : this.logger.debug(logInfo)
+    /** 尝试修改 CTI 状态 */
+    this.socketOrSipStatusChange(status, this.getSIPStatus)
+
+    if (error) {
+      /** 抛出 Socket 类型的错误详情 */
+      this.eventEmitAndTrack(
+        CTIEvent.OnCtiError,
+        {
+          type: CTIErrorType.SdkTerminated,
+          code,
+          msg: ExceptMessage.CommonNetworkErrorMsg,
+          method: 'setSocketStatus'
+        },
+        {
+          error_msg: error,
+          socket_status_list: this._socketStatusList,
+          sip_status_list: this._sipStatusList,
+          cti_status_list: this._ctiStatusList
+        }
+      )
+      /** 如果是被踢出的情况,只关闭 IM Socket 和 SIP Socket 的连接,不发送 Registerer 失效事件 */
+      if (code === HskTerminatedCode.SocketRepeatLogin) {
+        this.setCTIStatus(CTIStatus.Terminated)
+        this.initInstanceOptions()
+        this._socket?.closeSocket()
+        this._sipUserAgent?.transport.disconnect()
+        this._sipUserAgent = undefined
+      } else {
+        this.clearSocketAndSip()
+      }
+    } else {
+      // serverTrack({
+      //   ...getBaseOption(BaseOption.TrackParams),
+      //   source: TrackSource.FeStatus,
+      //   event_name: `socket_status_${upperCamelToLowerSnake(status)}`
+      // })
+    }
+  }
+  /**
+   * @private setSipStatus SIP 状态流转
+   * @param SIPStatusChangeParams SIP 状态流转参数及错误详情等
+   */
+  private setSipStatus({ status, code, error, method }: SIPStatusChangeParams) {
+    if (status === this.getSIPStatus) return
+    /** SIP 状态流转 */
+    this._sipStatus = status
+    this._sipStatusList.push(status)
+    const logInfo = `sip status | ${status} | ${this._sipStatusList}`
+    status === SIPStatus.Terminated
+      ? this.logger.warn(logInfo)
+      : this.logger.debug(logInfo)
+    /** 尝试修改 CTI 状态 */
+    this.socketOrSipStatusChange(this.getSocketStatus, status)
+
+    if (error) {
+      /** 抛出 SIP 类型的错误详情 */
+      this.eventEmitAndTrack(
+        CTIEvent.OnCtiError,
+        {
+          type: CTIErrorType.SdkTerminated,
+          code,
+          msg: ExceptMessage.CommonNetworkErrorMsg,
+          method: method || 'setSipStatus'
+        },
+        {
+          error_msg: error,
+          socket_status_list: this._socketStatusList,
+          sip_status_list: this._sipStatusList,
+          cti_status_list: this._ctiStatusList
+        }
+      )
+      this.clearSocketAndSip()
+    } else {
+      // serverTrack({
+      //   ...getBaseOption(BaseOption.TrackParams),
+      //   source: TrackSource.FeStatus,
+      //   event_name: `sip_status_${upperCamelToLowerSnake(status)}`
+      // })
+    }
+  }
+  /**
+   * @private socketOrSipStatusChange Socket 或 SIP 状态变化可能引起 CTI 状态变化
+   * @param {SocketStatus} socketStatus
+   * @param {SIPStatus} sipStatus
+   */
+  private socketOrSipStatusChange(
+    socketStatus: SocketStatus,
+    sipStatus: SIPStatus
+  ) {
+    if (socketStatus === SocketStatus.Ready && sipStatus === SIPStatus.Ready) {
+      this.setCTIStatus(CTIStatus.Ready)
+    }
+    if (socketStatus === SocketStatus.ReTry || sipStatus === SIPStatus.ReTry) {
+      this.setCTIStatus(CTIStatus.ReTry)
+    }
+    if (
+      socketStatus === SocketStatus.Terminated ||
+      sipStatus === SIPStatus.Terminated
+    ) {
+      this.setCTIStatus(CTIStatus.Terminated)
+    }
+  }
+  /**
+   * @private setCTIStatus CTI 状态流转
+   * @param {CTIStatus} ctiStatus
+   */
+  private setCTIStatus(ctiStatus: CTIStatus) {
+    if (ctiStatus === this.getCTIStatus) return
+    /** CTI 状态流转 */
+    this._ctiStatus = ctiStatus
+    this._ctiStatusList.push(ctiStatus)
+    const logInfo = `cti status | ${ctiStatus} | ${this._ctiStatusList}`
+    if (ctiStatus === CTIStatus.Terminated) {
+      this.setSocketStatus({ status: SocketStatus.Terminated })
+      this.setSipStatus({ status: SIPStatus.Terminated })
+      this.stopLocalAudio()
+      this.logger.warn(logInfo)
+    } else {
+      this.logger.debug(logInfo)
+    }
+    /** TODO: 后续后端调整完逻辑把这个干掉,目前先增加如果 CTI 状态 OK 则调用后端签入 */
+    if (ctiStatus === CTIStatus.Ready) {
+      this.setSocketStatus({ status: SocketStatus.Ready })
+      this.setSipStatus({ status: SIPStatus.Ready })
+      this.checkIn()
+    }
+  }
+
+  /** @private initInstanceOptions 初始化实例 */
+  private initInstanceOptions() {
+    this._callId = ''
+    this._ctiFlowIdList = []
+    this._socketStatusList = []
+    this._sipStatusList = []
+    this._ctiStatusList = []
+    this._incomingSession = undefined
+    SdCTI.instance = undefined
+    this._initOptions = undefined
+    resetBaseOption()
+  }
+  /** @private clearSocketAndSip 优雅关闭 SIP 和 socket 并重置实例 */
+  private clearSocketAndSip() {
+    this.shouldBeConnected = false
+    this.shouldBeRegistered = false
+    this.initInstanceOptions()
+    this._socket?.closeSocket()
+    this._sipUserAgent?.stop()
+    this._sipUserAgent = undefined
+    this._sipRegisterer = undefined
+  }
+
+  /**
+   * @private clearSocketAndSip 设置等待音 src
+   * @param {AudioName} audioName
+   * @param {boolean} loop
+   */
+  private setAudioSrc(audioName: AudioName, loop: boolean) {
+    this.logger.debug(`media | 设置等待音 src: ${audioList[audioName]}`)
+    this[audioName].src = audioList[audioName]
+    this[audioName].currentTime = 0
+    this[audioName].autoplay = false
+    this[audioName].loop = loop
+  }
+
+  /**
+   * @private playAudio 播放等待音
+   * @param {AudioName} audioName
+   */
+  private playAudio(audioName: AudioName) {
+    this.logger.debug(`media | 播放等待音: ${audioName}`)
+    this[audioName].play()
+  }
+
+  /**
+   * @private stopAudio 停止等待音并重新设置等待音 src
+   * @param {AudioName} audioName
+   * @param {boolean} loop
+   */
+  private stopAudio(audioName: AudioName, loop: boolean) {
+    this.logger.debug(`media | 停止等待音: ${audioName}`)
+    this[audioName].src = ''
+    setTimeout(() => {
+      this.setAudioSrc(audioName, loop)
+    }, 1000)
+  }
+
+  /** @private _getCtiFlowId 获取手动外呼场景需要的 ctiFlowId */
+  @handleApiRes()
+  private async _getCtiFlowId() {
+    const res = await getCtiFlowId(this._baseParams)
+    const { code, data } = res
+    if (code === 0) {
+      this._baseParams.ctiFlowId = data
+      this._ctiFlowIdList.push(data)
+      setBaseOption(BaseOption.TrackParams, {
+        cti_flow_id: data,
+        cti_flow_id_list: JSON.stringify(this._ctiFlowIdList)
+      })
+    }
+    return res
+  }
+
+  /** @private checkIn 服务端签入,CTIStatus Ready 时自动调用,坐席状态变更 */
+  @checkCTIStatus(ExceptMessage.CommonNetworkErrorMsg)
+  @handleApiRes()
+  private async checkIn() {
+    const res = await agentCheckIn(this._baseParams)
+    if (res.code === 0) {
+      this.eventEmitAndTrack(CTIEvent.OnInitalSuccess, {
+        saas_id: this.saas_id,
+        agent_id: this.agent_id,
+        scene: this.scene,
+        phoneNum: this._initOptions!.phone_num,
+        sipServer: this._initOptions!.sip_server
+      })
+    }
+    return res
+  }
+
+  /** @private checkOut 服务端签出, unInit 时自动调用,坐席状态变更 */
+  @handleApiRes()
+  private async checkOut() {
+    return await agentCheckOut(this._baseParams)
+  }
+
+  /** @public setIdle 服务端置闲,坐席状态变更 */
+  @checkCTIStatus(ExceptMessage.CustomNetworkErrorMsg)
+  @handleApiRes()
+  public async setIdle() {
+    return await agentSetIdle(this._baseParams)
+  }
+
+  /** @public setBusy 服务端置忙,坐席状态变更 */
+  @checkCTIStatus(ExceptMessage.CommonNetworkErrorMsg)
+  @handleApiRes()
+  public async setBusy() {
+    return await agentSetBusy(this._baseParams)
+  }
+
+  /**
+   * @public makeCall 服务端主动外呼
+   * @param CTIManualCallOptions
+   */
+  @checkCTIStatus(ExceptMessage.CommonNetworkErrorMsg)
+  @handleApiRes()
+  public async makeCall({ called, caller, ext }: CTIManualCallOptions) {
+    /** 主动外呼之前先获取 ctiFlowId,如果状态不正确直接 return */
+    const flowIdRes = await this._getCtiFlowId()
+    if (flowIdRes && flowIdRes.code !== 0) {
+      return flowIdRes
+    }
+    // this.playAudio(AudioName.WaitAudio)
+    /** 主动外呼允许增加额外入参,透传给服务端 */
+    let params = { ...this._baseParams, called, caller }
+    if (ext) {
+      params = { ...params, ...ext }
+    }
+    const res = await manualCall(params)
+    const { code, data } = res
+    if (code === 0) {
+      /** 如果 FS 的 INVITE 没取到 callId 这个逻辑是兜底 */
+      if (this._callId === '' && data) {
+        this._callId = data
+        setBaseOption(BaseOption.TrackParams, {
+          call_id: data
+        })
+      }
+    } else {
+      this.stopLocalAudio()
+    }
+    return res
+  }
+
+  /** @public answer SDK SIP 接起 */
+  @checkCTIStatus(ExceptMessage.CommonNetworkErrorMsg)
+  public answer() {
+    return new Promise<CTIRes>((resolve, reject) => {
+      this._incomingSession
+        ?.accept({
+          sessionDescriptionHandlerOptions: {
+            constraints: {
+              audio: true,
+              video: false
+            }
+          }
+        })
+        .then(() => {
+          resolve({
+            code: 0,
+            data: 'answer',
+            msg: 'SIP 接起电话成功'
+          })
+          // serverTrack({
+          //   ...getBaseOption(BaseOption.TrackParams),
+          //   source: TrackSource.FeSIP,
+          //   event_name: 'sip_accept_success'
+          // })
+          this.logger.debug('sip_accept_success')
+        })
+        .catch(err => {
+          const errorData = {
+            type: CTIErrorType.SdkError,
+            code: SdkErrorCode.Answer,
+            msg:
+              this.scene === Scene.Manual
+                ? ExceptMessage.ManualCallAnswerErrorMsg
+                : ExceptMessage.RobotOrWeChatAnswerErrorMsg,
+            method: 'answer'
+          }
+          reject(errorData)
+          this.eventEmitAndTrack(CTIEvent.OnCtiError, errorData, `${err}`)
+          this.logger.error(`${CTIEvent.OnCtiError} | ${err}`)
+        })
+    })
+  }
+
+  /** @public bye SDK SIP 挂断 */
+  @checkCTIStatus(ExceptMessage.CommonNetworkErrorMsg)
+  public bye() {
+    return new Promise<CTIRes>((resolve, reject) => {
+      this._incomingSession
+        ?.bye()
+        .then(() => {
+          resolve({
+            code: 0,
+            data: 'bye',
+            msg: 'SIP 挂断电话成功'
+          })
+          // serverTrack({
+          //   ...getBaseOption(BaseOption.TrackParams),
+          //   source: TrackSource.FeSIP,
+          //   event_name: 'sip_bye_success'
+          // })
+          this.logger.debug('sip_bye_success')
+        })
+        .catch(err => {
+          const errorData = {
+            type: CTIErrorType.SdkError,
+            code: SdkErrorCode.Bye,
+            msg: ExceptMessage.SipByeErrorMsg,
+            method: 'bye'
+          }
+          reject(errorData)
+          this.eventEmitAndTrack(CTIEvent.OnCtiError, errorData, `${err}`)
+          this.logger.error(`${CTIEvent.OnCtiError} | ${err}`)
+        })
+        .finally(() => {
+          if (this.scene !== Scene.Monitor) this.turnHang()
+        })
+    })
+  }
+  public async serverBye() {
+    return await this.bye()
+  }
+  /** @public serverBye 服务端挂断 */
+  @checkCTIStatus(ExceptMessage.CommonNetworkErrorMsg)
+  @handleApiRes()
+  private async turnHang() {
+    return await manualHang({ ...this._baseParams, callId: this._callId })
+  }
+  /** @public getAgentStatus 获取坐席状态 */
+  @handleApiRes()
+  public async getAgentStatus() {
+    return await getAgentStatus(this._baseParams)
+  }
+  /**
+   * @public loadAgentGroupData 监听-根据监听组 ID 获取监听组成员
+   * @param {string[]} monitorIds
+   */
+  @checkCTIStatus(ExceptMessage.CommonNetworkErrorMsg)
+  @handleApiRes()
+  public async loadAgentGroupData(monitorIds: string[]) {
+    return await loadAgentGroupData({ ...this._baseParams, monitorIds })
+  }
+  /**
+   * @public listen 监听-服务端发起监听
+   * @param {string} monitoredAgNo
+   */
+  @checkCTIStatus(ExceptMessage.CommonNetworkErrorMsg)
+  @handleApiRes()
+  public async listen(monitoredAgNo: string) {
+    /** 监听之前先获取 ctiFlowId,如果状态不正确直接 return */
+    const flowIdRes = await this._getCtiFlowId()
+    if (flowIdRes && flowIdRes.code !== 0) {
+      return flowIdRes
+    }
+    return await listen({
+      ...this._baseParams,
+      agent_id: monitoredAgNo,
+      leaderAgentId: this.agent_id
+    })
+  }
+  /**
+   * @public setActiveService 机器人外呼-签入人工组
+   * @param {string} serviceId
+   */
+  @checkCTIStatus(ExceptMessage.CustomNetworkErrorMsg)
+  @handleApiRes()
+  public async setActiveService(serviceId: string) {
+    return await setActiveServiceTask({ ...this._baseParams, serviceId })
+  }
+  /** @public unInit 卸载 SDK,checkOut 成功后断开 socket 和 sip 连接,并销毁 SdCTI 实例 */
+  public async unInit() {
+    await this.checkOut()
+    this.optionsPingStop()
+    await this.setCTIStatus(CTIStatus.Terminated)
+    await this.clearSocketAndSip()
+  }
+}

+ 24 - 23
src/hs-cti/HsCTI.ts

@@ -57,7 +57,8 @@ import {
   checkCTIStatus,
   getUserMedia,
   handleApiRes,
-  validateParams
+  validateParams,
+  generateUniqueId
 } from './tools'
 import { Invitation, SessionState, UserAgentState } from 'sip.js'
 
@@ -68,9 +69,9 @@ import { Invitation, SessionState, UserAgentState } from 'sip.js'
  * _byeAudio 结束通话提示音
  */
 const audioList = {
-  _ringAudio: 'https://static.fuxicarbon.com/hs-cti/ring.wav',
-  _waitAudio: 'https://static.fuxicarbon.com/hs-cti/manual.wav',
-  _byeAudio: 'https://static.fuxicarbon.com/hs-cti/bye.wav'
+  _ringAudio: 'http://static.fuxicarbon.com/hs-cti/ring.wav',
+  _waitAudio: 'http://static.fuxicarbon.com/hs-cti/manual.wav',
+  _byeAudio: 'http://static.fuxicarbon.com/hs-cti/bye.wav'
 }
 
 /** @class HsCTI 红杉外呼类 */
@@ -121,8 +122,7 @@ export class HsCTI extends EventEmitter {
   private constructor(
     hsCTIInitOptions: HsCTIInitOptions | RequiredHsCTIInitOptions
   ) {
-    const { saas_id, agent_id, scene, password, env, loggerLevel } =
-      hsCTIInitOptions
+    const { saas_id, agent_id, scene, env, loggerLevel } = hsCTIInitOptions
     super()
     this.loggerLevel = window.ctiLoggerLevel || loggerLevel || LoggerLevels.log
     this.logger = new Logger(this.loggerLevel, 'HsCTI')
@@ -138,7 +138,7 @@ export class HsCTI extends EventEmitter {
     setBaseOption(BaseOption.ENV, env)
     setBaseOption(BaseOption.LoggerLevel, this.loggerLevel)
 
-    this._baseParams = { agent_id, saas_id, scene, password }
+    this._baseParams = { agent_id, saas_id, scene }
     const baseTrackParams = { agent_id: agent_id, vcc_id: saas_id, scene }
     if (scene === Scene.Monitor) {
       const { monitorScene } = hsCTIInitOptions
@@ -280,7 +280,7 @@ export class HsCTI extends EventEmitter {
       // FS 注册过期时间
       fsRegisterExpireTime: 84000,
       // 单次初始化唯一 ID
-      ctiSessionId: '23',
+      ctiSessionId: generateUniqueId(),
       // IM websocket url
       imWsServer: 'ws://192.168.100.159:8091/ws/cs-im',
       // IM websocket path
@@ -416,7 +416,8 @@ export class HsCTI extends EventEmitter {
             video: false
           },
           peerConnectionConfiguration: {
-            iceServers: []
+            // iceServers: []
+            iceServers: [{ urls: initOptions.ice_server }]
           }
         }
       }
@@ -481,30 +482,30 @@ export class HsCTI extends EventEmitter {
         }
       },
       onInvite: (invitation: Invitation) => {
-        const callId = invitation.request.getHeader('P-LIBRA-Callid') || ''
-        const ctiFlowId =
-          invitation.request.getHeader('P-LIBRA-CtiFlowId') || ''
-        console.log(`Request` + ctiFlowId, this._baseParams.ctiFlowId)
-        if (
-          this.scene === Scene.Manual &&
-          ctiFlowId != this._baseParams.ctiFlowId
-        ) {
-          this.logger.error(
-            `cti_flow_id 不一致! fe: ${this._baseParams.ctiFlowId} | P-LIBRA-CtiFlowId: ${ctiFlowId}`
-          )
-          return
-        }
+        // this.scene = Scene.Robot
+        const callId = invitation.request.getHeader('P-LIBRA-CallId') || ''
+        console.log(callId, 2888888888)
+        // const ctiFlowId =
+        //   invitation.request.getHeader('P-LIBRA-CtiFlowId') || ''
+        // if (ctiFlowId != this._baseParams.ctiFlowId) {
+        //   this.logger.error(
+        //     `cti_flow_id 不一致! fe: ${this._baseParams.ctiFlowId} | P-LIBRA-CtiFlowId: ${ctiFlowId}`
+        //   )
+        //   return
+        // }
         this._callId = callId
+        console.log(callId, 1888888888)
         setBaseOption(BaseOption.TrackParams, {
           call_id: callId
         })
         this.sessionStateChangeAndTrack(invitation.state)
         if ([Scene.Robot, Scene.Monitor].includes(this.scene)) {
           this.playAudio(AudioName.RingAudio)
+          console.log('playAudio', 1888888888)
         }
-
         if ([Scene.Manual].includes(this.scene)) {
           this.answer()
+          console.log('answer', 1888888888)
         }
 
         invitation.delegate = {

+ 2 - 1
src/hs-cti/outputType.ts

@@ -17,7 +17,8 @@ export enum Scene {
   Manual = 'manual',
   Robot = 'robot',
   Monitor = 'monitor',
-  Predictive = 'predictive'
+  Predictive = 'predictive',
+  Wechat = 'wechat'
 }
 
 // type ExcludeSceneKey = Exclude<keyof typeof Scene, 'Monitor'>

+ 4 - 0
src/hs-cti/tools.ts

@@ -242,3 +242,7 @@ export function validateParams() {
     return descriptor
   }
 }
+
+export function generateUniqueId() {
+  return 'id-' + Date.now() + '-' + Math.floor(Math.random() * 10000);
+}

+ 1 - 1
src/hs-cti/type.ts

@@ -168,7 +168,7 @@ export const methodExceptMsgMap: { [key: string]: ExceptMessage } = {
 export const baseRequireParams: (keyof HsCTIInitOptions)[] = [
   'agent_id',
   'saas_id',
-  'password',
+  // 'password',
   'env',
   'scene'
 ]

+ 7 - 5
src/sdk.html

@@ -9,7 +9,7 @@
 
   <!-- <script src="https://static.fuxicarbon.com/hs-cti/socket.io.min.js"></script> -->
   <script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>
-  <script src="https://static.fuxicarbon.com/hs-cti/SIP.min.js"></script>
+  <script src="http://static.fuxicarbon.com/hs-cti/SIP.min.js"></script>
   <script src="hs-cti.es6.umd.js"></script>
 
   <style type="text/css">
@@ -37,12 +37,11 @@ const HS_CTI = getInstance({
     saas_id: 'mdj',
     // 目前每个业务线写死,用于后端鉴权,前端仅透传
     // 业务场景详见 Scene 枚举,
-    scene: Scene.Manual,
+    scene: Scene.Robot,
     // SDK 日志等级
     loggerLevel: LoggerLevels.debug,
     // 环境变量
-    env: 'development',
-    password:'23'
+    env: 'development'
 })
 HS_CTI.init()
 console.log(HS_CTI)
@@ -98,6 +97,9 @@ window.addEventListener('load', ()=>{
   HS_CTI.on('CTIErrorType ',(res)=>{
     console.log(res,9000000000000)
   })
+  HS_CTI.on('OnRingStart ',(res)=>{
+    console.log(res,7000000000000)
+  })
   HS_CTI.on('OnSessionStatusChange ',(res)=>{
     console.log(res,6000000000000)
   })
@@ -114,7 +116,7 @@ window.addEventListener('load', ()=>{
     console.log(res,"坐席监控")
   })
   makeCall_btn.addEventListener('click',()=>{
-    HS_CTI.makeCall({"saas_id":"mdj","caller":"1000","called":"13269511810"}).then((res) => {
+    HS_CTI.makeCall({"saas_id":"mdj","caller":"1000","called":"13241676588"}).then((res) => {
       console.log(res)
     })
   })

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio