@koi-design/callkit 2.3.0-beta.1 → 2.3.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -72,7 +72,7 @@ var Api = class {
72
72
  const res = await this.post({
73
73
  url: "/auth/agentUser/login",
74
74
  method: "post",
75
- data: params
75
+ data: { ...params, browserVersion: navigator.userAgent }
76
76
  });
77
77
  return res;
78
78
  } finally {
@@ -535,7 +535,11 @@ var SocketReceiveEvent = {
535
535
  ERROR: "ERROR",
536
536
  SESSION_ERROR: "SESSION_ERROR",
537
537
  WAITING_QUEUE: "WAITING_QUEUE",
538
- CUSTOMER_MATCH_BLACK_PHONE: "CUSTOMER_MATCH_BLACK_PHONE"
538
+ CUSTOMER_MATCH_BLACK_PHONE: "CUSTOMER_MATCH_BLACK_PHONE",
539
+ /**
540
+ * Agent RTP loss
541
+ */
542
+ AGENT_RTP_LOSS: "AGENT_RTP_LOSS"
539
543
  };
540
544
  var EncryptionMethod = {
541
545
  NONE: "NONE",
@@ -724,7 +728,7 @@ var Call = class {
724
728
  // package.json
725
729
  var package_default = {
726
730
  name: "@koi-design/callkit",
727
- version: "2.3.0-beta.1",
731
+ version: "2.3.0-beta.11",
728
732
  description: "callkit",
729
733
  author: "koi",
730
734
  license: "ISC",
@@ -15824,8 +15828,6 @@ var Connect = class {
15824
15828
  });
15825
15829
  return;
15826
15830
  }
15827
- const { userInfo } = this.callKit.config.getConfig();
15828
- const { userPart, fsIp, fsPort } = userInfo;
15829
15831
  const { registererOptions = {} } = this.reconnectConfig;
15830
15832
  this.registerer = new Registerer(this.userAgent, registererOptions);
15831
15833
  this.registerer.stateChange.addListener((state) => {
@@ -15859,27 +15861,26 @@ var Connect = class {
15859
15861
  });
15860
15862
  this.setRegister(true);
15861
15863
  if (this.isReConnected) {
15862
- if (this.currentSession && (this.currentSession.state === SessionState2.Established || this.currentSession.state === SessionState2.Establishing) && this.isCalling()) {
15863
- const selfUri = `sip:manualCallAgent${userPart}@${fsIp}:${fsPort}`;
15864
+ if (this.canReferInCallToSelf()) {
15864
15865
  this.callKit.logger.info(
15865
15866
  "Reconnected, referring active session to self",
15866
15867
  {
15867
15868
  caller: "Connect.setupRegisterer.registererStateChange",
15868
15869
  type: "SIP",
15869
15870
  content: {
15870
- selfUri,
15871
+ selfUri: this.getSelfReferUri(),
15871
15872
  sessionState: this.currentSession.state,
15872
15873
  connectStatus: this.connectStatus
15873
15874
  }
15874
15875
  }
15875
15876
  );
15876
- this.referInCall(selfUri).catch((err) => {
15877
+ this.referInCallToSelf().catch((err) => {
15877
15878
  this.callKit.logger.error(err, {
15878
15879
  caller: "Connect.setupRegisterer.registererStateChange",
15879
15880
  type: "SIP",
15880
15881
  content: {
15881
15882
  errCode: ErrorCode.WEBRTC_CALL_INVITE_ERROR,
15882
- selfUri
15883
+ selfUri: this.getSelfReferUri()
15883
15884
  }
15884
15885
  });
15885
15886
  });
@@ -16708,6 +16709,199 @@ var Connect = class {
16708
16709
  }
16709
16710
  this.currentSession.refer(target, extra?.sessionReferOptions);
16710
16711
  }
16712
+ /**
16713
+ * Get the SIP URI for "refer to self" (same as reconnection refer logic).
16714
+ * Shared by referInCallToSelf and internal reconnection refer.
16715
+ */
16716
+ getSelfReferUri() {
16717
+ const { userInfo } = this.callKit.config.getConfig();
16718
+ const { userPart, fsIp, fsPort } = userInfo;
16719
+ return `sip:manualCallAgent${userPart}@${fsIp}:${fsPort}`;
16720
+ }
16721
+ /**
16722
+ * Whether we can refer the current call to self (has active session in Established/Establishing and is calling).
16723
+ * Shared by reconnection logic and referInCallToSelf.
16724
+ */
16725
+ canReferInCallToSelf() {
16726
+ return !!this.currentSession && (this.currentSession.state === SessionState2.Established || this.currentSession.state === SessionState2.Establishing) && this.isCalling();
16727
+ }
16728
+ /**
16729
+ * Refer the current call to self (e.g. Agent RTP loss recovery, post-reconnect recovery).
16730
+ * Socket and other callers can use this without constructing referTo.
16731
+ */
16732
+ async referInCallToSelf(callUuid, extra) {
16733
+ if (callUuid && this.currentCallId !== callUuid) {
16734
+ this.callKit.logger.warn(
16735
+ "Cannot refer in call to self: callUuid mismatch",
16736
+ {
16737
+ caller: "Connect.referInCallToSelf",
16738
+ type: "SIP",
16739
+ content: {
16740
+ currentCallId: this.currentCallId,
16741
+ callUuid
16742
+ }
16743
+ }
16744
+ );
16745
+ return;
16746
+ }
16747
+ if (!this.canReferInCallToSelf()) {
16748
+ this.callKit.logger.warn(
16749
+ "Cannot refer in call to self: preconditions not met",
16750
+ {
16751
+ caller: "Connect.referInCallToSelf",
16752
+ type: "SIP",
16753
+ content: {
16754
+ hasCurrentSession: !!this.currentSession,
16755
+ sessionState: this.currentSession?.state,
16756
+ isCalling: this.isCalling()
16757
+ }
16758
+ }
16759
+ );
16760
+ return;
16761
+ }
16762
+ const selfUri = this.getSelfReferUri();
16763
+ return this.referInCall(selfUri, extra);
16764
+ }
16765
+ };
16766
+
16767
+ // core/heartbeat-worker.ts
16768
+ var workerCode = `
16769
+ let timer = null;
16770
+ let interval = 30000;
16771
+
16772
+ self.onmessage = function(e) {
16773
+ const { type, interval: newInterval } = e.data;
16774
+
16775
+ if (type === 'start') {
16776
+ if (timer) {
16777
+ clearInterval(timer);
16778
+ }
16779
+ interval = newInterval || interval;
16780
+ timer = setInterval(() => {
16781
+ self.postMessage({ type: 'tick' });
16782
+ }, interval);
16783
+ }
16784
+
16785
+ if (type === 'stop') {
16786
+ if (timer) {
16787
+ clearInterval(timer);
16788
+ timer = null;
16789
+ }
16790
+ }
16791
+
16792
+ if (type === 'updateInterval') {
16793
+ interval = newInterval;
16794
+ if (timer) {
16795
+ clearInterval(timer);
16796
+ timer = setInterval(() => {
16797
+ self.postMessage({ type: 'tick' });
16798
+ }, interval);
16799
+ }
16800
+ }
16801
+ };
16802
+ `;
16803
+ function createHeartbeatWorker() {
16804
+ try {
16805
+ const blob = new Blob([workerCode], { type: "application/javascript" });
16806
+ const workerUrl = URL.createObjectURL(blob);
16807
+ const worker = new Worker(workerUrl);
16808
+ URL.revokeObjectURL(workerUrl);
16809
+ return worker;
16810
+ } catch {
16811
+ return null;
16812
+ }
16813
+ }
16814
+ var HeartbeatManager = class {
16815
+ worker = null;
16816
+ fallbackTimer = null;
16817
+ interval = 3e4;
16818
+ onTick = null;
16819
+ isRunning = false;
16820
+ constructor() {
16821
+ this.worker = createHeartbeatWorker();
16822
+ if (this.worker) {
16823
+ this.worker.onmessage = (e) => {
16824
+ if (e.data.type === "tick" && this.onTick) {
16825
+ this.onTick();
16826
+ }
16827
+ };
16828
+ }
16829
+ }
16830
+ /**
16831
+ * Start the heartbeat
16832
+ * @param interval - Interval in milliseconds
16833
+ * @param onTick - Callback function to execute on each tick
16834
+ */
16835
+ start(interval, onTick) {
16836
+ this.stop();
16837
+ this.interval = interval;
16838
+ this.onTick = onTick;
16839
+ this.isRunning = true;
16840
+ if (this.worker) {
16841
+ this.worker.postMessage({
16842
+ type: "start",
16843
+ interval
16844
+ });
16845
+ } else {
16846
+ this.fallbackTimer = setInterval(() => {
16847
+ if (this.onTick) {
16848
+ this.onTick();
16849
+ }
16850
+ }, interval);
16851
+ }
16852
+ }
16853
+ /**
16854
+ * Stop the heartbeat
16855
+ */
16856
+ stop() {
16857
+ this.isRunning = false;
16858
+ this.onTick = null;
16859
+ if (this.worker) {
16860
+ this.worker.postMessage({ type: "stop" });
16861
+ }
16862
+ if (this.fallbackTimer) {
16863
+ clearInterval(this.fallbackTimer);
16864
+ this.fallbackTimer = null;
16865
+ }
16866
+ }
16867
+ /**
16868
+ * Update the heartbeat interval
16869
+ * @param interval - New interval in milliseconds
16870
+ */
16871
+ updateInterval(interval) {
16872
+ this.interval = interval;
16873
+ if (!this.isRunning)
16874
+ return;
16875
+ if (this.worker) {
16876
+ this.worker.postMessage({
16877
+ type: "updateInterval",
16878
+ interval
16879
+ });
16880
+ } else if (this.fallbackTimer && this.onTick) {
16881
+ clearInterval(this.fallbackTimer);
16882
+ this.fallbackTimer = setInterval(() => {
16883
+ if (this.onTick) {
16884
+ this.onTick();
16885
+ }
16886
+ }, interval);
16887
+ }
16888
+ }
16889
+ /**
16890
+ * Destroy the heartbeat manager and release resources
16891
+ */
16892
+ destroy() {
16893
+ this.stop();
16894
+ if (this.worker) {
16895
+ this.worker.terminate();
16896
+ this.worker = null;
16897
+ }
16898
+ }
16899
+ /**
16900
+ * Check if using Web Worker
16901
+ */
16902
+ isUsingWorker() {
16903
+ return this.worker !== null;
16904
+ }
16711
16905
  };
16712
16906
 
16713
16907
  // core/socket.ts
@@ -16715,7 +16909,7 @@ var Socket = class {
16715
16909
  callKit;
16716
16910
  ws;
16717
16911
  lastPingTime = void 0;
16718
- pingTimer;
16912
+ heartbeatManager;
16719
16913
  /**
16720
16914
  * @description reconnect timer
16721
16915
  */
@@ -16748,10 +16942,18 @@ var Socket = class {
16748
16942
  }
16749
16943
  constructor(callKit) {
16750
16944
  this.callKit = callKit;
16945
+ this.heartbeatManager = new HeartbeatManager();
16751
16946
  }
16752
16947
  get reconnectConfig() {
16753
16948
  return this.callKit.config.getReconnectConfig("incall");
16754
16949
  }
16950
+ get pingInterval() {
16951
+ const { keepaliveInterval } = this.callKit.config.getConfig().userInfo;
16952
+ if (Number.isInteger(keepaliveInterval) && keepaliveInterval > 0) {
16953
+ return keepaliveInterval * 1e3;
16954
+ }
16955
+ return this.reconnectConfig.pingInterval;
16956
+ }
16755
16957
  isConnected() {
16756
16958
  return this.connectAuthState.isConnected;
16757
16959
  }
@@ -16933,6 +17135,28 @@ var Socket = class {
16933
17135
  this.setConnectAuthState("startConfirm", true);
16934
17136
  this.cleanReconnectState();
16935
17137
  }
17138
+ if (data.event === SocketReceiveEvent.AGENT_RTP_LOSS) {
17139
+ this.callKit.logger.warn("Agent RTP loss", {
17140
+ caller: "Socket.onMessage",
17141
+ type: "INCALL",
17142
+ content: {
17143
+ data: {
17144
+ callUuid,
17145
+ ...content
17146
+ },
17147
+ event: SocketReceiveEvent.AGENT_RTP_LOSS
17148
+ }
17149
+ });
17150
+ if (callUuid) {
17151
+ this.callKit.connect.referInCallToSelf(callUuid).catch((err) => {
17152
+ this.callKit.logger.error(err, {
17153
+ caller: "Socket.onMessage:AGENT_RTP_LOSS",
17154
+ type: "INCALL",
17155
+ content: { callUuid, event: SocketReceiveEvent.AGENT_RTP_LOSS }
17156
+ });
17157
+ });
17158
+ }
17159
+ }
16936
17160
  if (data.event === SocketReceiveEvent.CUSTOMER_RINGING) {
16937
17161
  this.callKit.trigger(KitEvent.CALL_RINGING, {
16938
17162
  time: /* @__PURE__ */ new Date(),
@@ -17045,7 +17269,7 @@ var Socket = class {
17045
17269
  return;
17046
17270
  }
17047
17271
  const { userInfo, version } = this.callKit.config.getConfig();
17048
- const { sessionId, extno, agentId, phoneType } = userInfo;
17272
+ const { sessionId, extno, agentId } = userInfo;
17049
17273
  if (!sessionId) {
17050
17274
  this.callKit.logger.error("sessionId is empty", {
17051
17275
  caller: "Socket.send",
@@ -17065,7 +17289,6 @@ var Socket = class {
17065
17289
  if (SocketSendEvent.CALL === event) {
17066
17290
  msg.phoneNum = extno;
17067
17291
  msg.agentId = agentId;
17068
- msg.phoneType = phoneType;
17069
17292
  if (message?.sourceType === CallSourceType.phoneNum) {
17070
17293
  delete msg.workOrderId;
17071
17294
  } else if (message?.sourceType === CallSourceType.workOrderId) {
@@ -17087,8 +17310,8 @@ var Socket = class {
17087
17310
  return;
17088
17311
  this.send(SocketSendEvent.PING);
17089
17312
  const now = Date.now();
17090
- const { pingInterval, pingTimeout } = this.reconnectConfig;
17091
- if (now - this.lastPingTime > pingInterval + pingTimeout) {
17313
+ const { pingTimeout } = this.reconnectConfig;
17314
+ if (now - this.lastPingTime > this.pingInterval + pingTimeout) {
17092
17315
  this.callKit.logger.warn("Ping timeout not connected", {
17093
17316
  caller: "Socket.ping",
17094
17317
  type: "INCALL",
@@ -17103,23 +17326,27 @@ var Socket = class {
17103
17326
  }
17104
17327
  }
17105
17328
  checkPing() {
17106
- if (this.pingTimer) {
17107
- clearInterval(this.pingTimer);
17108
- }
17109
- const { pingInterval } = this.reconnectConfig;
17110
- this.pingTimer = setInterval(() => {
17329
+ this.heartbeatManager.start(this.pingInterval, () => {
17111
17330
  this.ping();
17112
- }, pingInterval);
17331
+ });
17332
+ this.callKit.logger.info(
17333
+ `Heartbeat started with Worker: ${this.heartbeatManager.isUsingWorker()}`,
17334
+ {
17335
+ caller: "Socket.checkPing",
17336
+ type: "INCALL",
17337
+ content: {
17338
+ pingInterval: this.pingInterval,
17339
+ usingWorker: this.heartbeatManager.isUsingWorker()
17340
+ }
17341
+ }
17342
+ );
17113
17343
  }
17114
17344
  /**
17115
17345
  * reset socket connection and all states
17116
17346
  */
17117
17347
  async reset(config) {
17118
17348
  const { force = false } = config || {};
17119
- if (this.pingTimer) {
17120
- clearInterval(this.pingTimer);
17121
- this.pingTimer = void 0;
17122
- }
17349
+ this.heartbeatManager.stop();
17123
17350
  if (force) {
17124
17351
  this.callKit.trigger(KitEvent.INCALL_CONNECT_EVENT, {
17125
17352
  event: "INCALL_RESET"
@@ -17130,6 +17357,12 @@ var Socket = class {
17130
17357
  this.setConnectAuthState("startConfirm", false);
17131
17358
  this.clearWebSocket();
17132
17359
  }
17360
+ /**
17361
+ * Destroy the heartbeat manager
17362
+ */
17363
+ destroyHeartbeat() {
17364
+ this.heartbeatManager.destroy();
17365
+ }
17133
17366
  attemptReconnect() {
17134
17367
  if (this.reconnectTimer) {
17135
17368
  clearTimeout(this.reconnectTimer);
@@ -17255,6 +17488,7 @@ var CallKit = class {
17255
17488
  content: {
17256
17489
  username,
17257
17490
  password,
17491
+ ua: navigator.userAgent,
17258
17492
  encryptionMethod,
17259
17493
  encryptionPassword
17260
17494
  }
@@ -17288,6 +17522,7 @@ var CallKit = class {
17288
17522
  iceInfo: user.iceInfo,
17289
17523
  iceGatheringTimeout: user.iceGatheringTimeout,
17290
17524
  logGather: user.logGather,
17525
+ keepaliveInterval: user.keepaliveInterval,
17291
17526
  phoneType: user.phoneType,
17292
17527
  // encryptionType is in extra
17293
17528
  ...extra
@@ -17385,7 +17620,10 @@ var CallKit = class {
17385
17620
  caller: "CallKit.register",
17386
17621
  content: {}
17387
17622
  });
17388
- this.connect.register();
17623
+ const { userInfo } = this.config.getConfig();
17624
+ if (userInfo.phoneType === PhoneTypeEnum.SIP) {
17625
+ this.connect.register();
17626
+ }
17389
17627
  }
17390
17628
  async unregister() {
17391
17629
  if (!this.config.check())
@@ -17495,10 +17733,13 @@ var CallKit = class {
17495
17733
  force
17496
17734
  }
17497
17735
  });
17498
- if (this.connect.isCalling()) {
17499
- await this.hangup();
17736
+ const { userInfo } = this.config.getConfig();
17737
+ if (userInfo.phoneType === PhoneTypeEnum.SIP) {
17738
+ if (this.connect.isCalling()) {
17739
+ await this.hangup();
17740
+ }
17741
+ await this.connect.reset({ force });
17500
17742
  }
17501
- await this.connect.reset({ force });
17502
17743
  if (this.config.isLogin()) {
17503
17744
  await this.logout({ isReset: false });
17504
17745
  } else {