@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.mjs CHANGED
@@ -45,7 +45,7 @@ var Api = class {
45
45
  const res = await this.post({
46
46
  url: "/auth/agentUser/login",
47
47
  method: "post",
48
- data: params
48
+ data: { ...params, browserVersion: navigator.userAgent }
49
49
  });
50
50
  return res;
51
51
  } finally {
@@ -508,7 +508,11 @@ var SocketReceiveEvent = {
508
508
  ERROR: "ERROR",
509
509
  SESSION_ERROR: "SESSION_ERROR",
510
510
  WAITING_QUEUE: "WAITING_QUEUE",
511
- CUSTOMER_MATCH_BLACK_PHONE: "CUSTOMER_MATCH_BLACK_PHONE"
511
+ CUSTOMER_MATCH_BLACK_PHONE: "CUSTOMER_MATCH_BLACK_PHONE",
512
+ /**
513
+ * Agent RTP loss
514
+ */
515
+ AGENT_RTP_LOSS: "AGENT_RTP_LOSS"
512
516
  };
513
517
  var EncryptionMethod = {
514
518
  NONE: "NONE",
@@ -697,7 +701,7 @@ var Call = class {
697
701
  // package.json
698
702
  var package_default = {
699
703
  name: "@koi-design/callkit",
700
- version: "2.3.0-beta.1",
704
+ version: "2.3.0-beta.11",
701
705
  description: "callkit",
702
706
  author: "koi",
703
707
  license: "ISC",
@@ -15797,8 +15801,6 @@ var Connect = class {
15797
15801
  });
15798
15802
  return;
15799
15803
  }
15800
- const { userInfo } = this.callKit.config.getConfig();
15801
- const { userPart, fsIp, fsPort } = userInfo;
15802
15804
  const { registererOptions = {} } = this.reconnectConfig;
15803
15805
  this.registerer = new Registerer(this.userAgent, registererOptions);
15804
15806
  this.registerer.stateChange.addListener((state) => {
@@ -15832,27 +15834,26 @@ var Connect = class {
15832
15834
  });
15833
15835
  this.setRegister(true);
15834
15836
  if (this.isReConnected) {
15835
- if (this.currentSession && (this.currentSession.state === SessionState2.Established || this.currentSession.state === SessionState2.Establishing) && this.isCalling()) {
15836
- const selfUri = `sip:manualCallAgent${userPart}@${fsIp}:${fsPort}`;
15837
+ if (this.canReferInCallToSelf()) {
15837
15838
  this.callKit.logger.info(
15838
15839
  "Reconnected, referring active session to self",
15839
15840
  {
15840
15841
  caller: "Connect.setupRegisterer.registererStateChange",
15841
15842
  type: "SIP",
15842
15843
  content: {
15843
- selfUri,
15844
+ selfUri: this.getSelfReferUri(),
15844
15845
  sessionState: this.currentSession.state,
15845
15846
  connectStatus: this.connectStatus
15846
15847
  }
15847
15848
  }
15848
15849
  );
15849
- this.referInCall(selfUri).catch((err) => {
15850
+ this.referInCallToSelf().catch((err) => {
15850
15851
  this.callKit.logger.error(err, {
15851
15852
  caller: "Connect.setupRegisterer.registererStateChange",
15852
15853
  type: "SIP",
15853
15854
  content: {
15854
15855
  errCode: ErrorCode.WEBRTC_CALL_INVITE_ERROR,
15855
- selfUri
15856
+ selfUri: this.getSelfReferUri()
15856
15857
  }
15857
15858
  });
15858
15859
  });
@@ -16681,6 +16682,199 @@ var Connect = class {
16681
16682
  }
16682
16683
  this.currentSession.refer(target, extra?.sessionReferOptions);
16683
16684
  }
16685
+ /**
16686
+ * Get the SIP URI for "refer to self" (same as reconnection refer logic).
16687
+ * Shared by referInCallToSelf and internal reconnection refer.
16688
+ */
16689
+ getSelfReferUri() {
16690
+ const { userInfo } = this.callKit.config.getConfig();
16691
+ const { userPart, fsIp, fsPort } = userInfo;
16692
+ return `sip:manualCallAgent${userPart}@${fsIp}:${fsPort}`;
16693
+ }
16694
+ /**
16695
+ * Whether we can refer the current call to self (has active session in Established/Establishing and is calling).
16696
+ * Shared by reconnection logic and referInCallToSelf.
16697
+ */
16698
+ canReferInCallToSelf() {
16699
+ return !!this.currentSession && (this.currentSession.state === SessionState2.Established || this.currentSession.state === SessionState2.Establishing) && this.isCalling();
16700
+ }
16701
+ /**
16702
+ * Refer the current call to self (e.g. Agent RTP loss recovery, post-reconnect recovery).
16703
+ * Socket and other callers can use this without constructing referTo.
16704
+ */
16705
+ async referInCallToSelf(callUuid, extra) {
16706
+ if (callUuid && this.currentCallId !== callUuid) {
16707
+ this.callKit.logger.warn(
16708
+ "Cannot refer in call to self: callUuid mismatch",
16709
+ {
16710
+ caller: "Connect.referInCallToSelf",
16711
+ type: "SIP",
16712
+ content: {
16713
+ currentCallId: this.currentCallId,
16714
+ callUuid
16715
+ }
16716
+ }
16717
+ );
16718
+ return;
16719
+ }
16720
+ if (!this.canReferInCallToSelf()) {
16721
+ this.callKit.logger.warn(
16722
+ "Cannot refer in call to self: preconditions not met",
16723
+ {
16724
+ caller: "Connect.referInCallToSelf",
16725
+ type: "SIP",
16726
+ content: {
16727
+ hasCurrentSession: !!this.currentSession,
16728
+ sessionState: this.currentSession?.state,
16729
+ isCalling: this.isCalling()
16730
+ }
16731
+ }
16732
+ );
16733
+ return;
16734
+ }
16735
+ const selfUri = this.getSelfReferUri();
16736
+ return this.referInCall(selfUri, extra);
16737
+ }
16738
+ };
16739
+
16740
+ // core/heartbeat-worker.ts
16741
+ var workerCode = `
16742
+ let timer = null;
16743
+ let interval = 30000;
16744
+
16745
+ self.onmessage = function(e) {
16746
+ const { type, interval: newInterval } = e.data;
16747
+
16748
+ if (type === 'start') {
16749
+ if (timer) {
16750
+ clearInterval(timer);
16751
+ }
16752
+ interval = newInterval || interval;
16753
+ timer = setInterval(() => {
16754
+ self.postMessage({ type: 'tick' });
16755
+ }, interval);
16756
+ }
16757
+
16758
+ if (type === 'stop') {
16759
+ if (timer) {
16760
+ clearInterval(timer);
16761
+ timer = null;
16762
+ }
16763
+ }
16764
+
16765
+ if (type === 'updateInterval') {
16766
+ interval = newInterval;
16767
+ if (timer) {
16768
+ clearInterval(timer);
16769
+ timer = setInterval(() => {
16770
+ self.postMessage({ type: 'tick' });
16771
+ }, interval);
16772
+ }
16773
+ }
16774
+ };
16775
+ `;
16776
+ function createHeartbeatWorker() {
16777
+ try {
16778
+ const blob = new Blob([workerCode], { type: "application/javascript" });
16779
+ const workerUrl = URL.createObjectURL(blob);
16780
+ const worker = new Worker(workerUrl);
16781
+ URL.revokeObjectURL(workerUrl);
16782
+ return worker;
16783
+ } catch {
16784
+ return null;
16785
+ }
16786
+ }
16787
+ var HeartbeatManager = class {
16788
+ worker = null;
16789
+ fallbackTimer = null;
16790
+ interval = 3e4;
16791
+ onTick = null;
16792
+ isRunning = false;
16793
+ constructor() {
16794
+ this.worker = createHeartbeatWorker();
16795
+ if (this.worker) {
16796
+ this.worker.onmessage = (e) => {
16797
+ if (e.data.type === "tick" && this.onTick) {
16798
+ this.onTick();
16799
+ }
16800
+ };
16801
+ }
16802
+ }
16803
+ /**
16804
+ * Start the heartbeat
16805
+ * @param interval - Interval in milliseconds
16806
+ * @param onTick - Callback function to execute on each tick
16807
+ */
16808
+ start(interval, onTick) {
16809
+ this.stop();
16810
+ this.interval = interval;
16811
+ this.onTick = onTick;
16812
+ this.isRunning = true;
16813
+ if (this.worker) {
16814
+ this.worker.postMessage({
16815
+ type: "start",
16816
+ interval
16817
+ });
16818
+ } else {
16819
+ this.fallbackTimer = setInterval(() => {
16820
+ if (this.onTick) {
16821
+ this.onTick();
16822
+ }
16823
+ }, interval);
16824
+ }
16825
+ }
16826
+ /**
16827
+ * Stop the heartbeat
16828
+ */
16829
+ stop() {
16830
+ this.isRunning = false;
16831
+ this.onTick = null;
16832
+ if (this.worker) {
16833
+ this.worker.postMessage({ type: "stop" });
16834
+ }
16835
+ if (this.fallbackTimer) {
16836
+ clearInterval(this.fallbackTimer);
16837
+ this.fallbackTimer = null;
16838
+ }
16839
+ }
16840
+ /**
16841
+ * Update the heartbeat interval
16842
+ * @param interval - New interval in milliseconds
16843
+ */
16844
+ updateInterval(interval) {
16845
+ this.interval = interval;
16846
+ if (!this.isRunning)
16847
+ return;
16848
+ if (this.worker) {
16849
+ this.worker.postMessage({
16850
+ type: "updateInterval",
16851
+ interval
16852
+ });
16853
+ } else if (this.fallbackTimer && this.onTick) {
16854
+ clearInterval(this.fallbackTimer);
16855
+ this.fallbackTimer = setInterval(() => {
16856
+ if (this.onTick) {
16857
+ this.onTick();
16858
+ }
16859
+ }, interval);
16860
+ }
16861
+ }
16862
+ /**
16863
+ * Destroy the heartbeat manager and release resources
16864
+ */
16865
+ destroy() {
16866
+ this.stop();
16867
+ if (this.worker) {
16868
+ this.worker.terminate();
16869
+ this.worker = null;
16870
+ }
16871
+ }
16872
+ /**
16873
+ * Check if using Web Worker
16874
+ */
16875
+ isUsingWorker() {
16876
+ return this.worker !== null;
16877
+ }
16684
16878
  };
16685
16879
 
16686
16880
  // core/socket.ts
@@ -16688,7 +16882,7 @@ var Socket = class {
16688
16882
  callKit;
16689
16883
  ws;
16690
16884
  lastPingTime = void 0;
16691
- pingTimer;
16885
+ heartbeatManager;
16692
16886
  /**
16693
16887
  * @description reconnect timer
16694
16888
  */
@@ -16721,10 +16915,18 @@ var Socket = class {
16721
16915
  }
16722
16916
  constructor(callKit) {
16723
16917
  this.callKit = callKit;
16918
+ this.heartbeatManager = new HeartbeatManager();
16724
16919
  }
16725
16920
  get reconnectConfig() {
16726
16921
  return this.callKit.config.getReconnectConfig("incall");
16727
16922
  }
16923
+ get pingInterval() {
16924
+ const { keepaliveInterval } = this.callKit.config.getConfig().userInfo;
16925
+ if (Number.isInteger(keepaliveInterval) && keepaliveInterval > 0) {
16926
+ return keepaliveInterval * 1e3;
16927
+ }
16928
+ return this.reconnectConfig.pingInterval;
16929
+ }
16728
16930
  isConnected() {
16729
16931
  return this.connectAuthState.isConnected;
16730
16932
  }
@@ -16906,6 +17108,28 @@ var Socket = class {
16906
17108
  this.setConnectAuthState("startConfirm", true);
16907
17109
  this.cleanReconnectState();
16908
17110
  }
17111
+ if (data.event === SocketReceiveEvent.AGENT_RTP_LOSS) {
17112
+ this.callKit.logger.warn("Agent RTP loss", {
17113
+ caller: "Socket.onMessage",
17114
+ type: "INCALL",
17115
+ content: {
17116
+ data: {
17117
+ callUuid,
17118
+ ...content
17119
+ },
17120
+ event: SocketReceiveEvent.AGENT_RTP_LOSS
17121
+ }
17122
+ });
17123
+ if (callUuid) {
17124
+ this.callKit.connect.referInCallToSelf(callUuid).catch((err) => {
17125
+ this.callKit.logger.error(err, {
17126
+ caller: "Socket.onMessage:AGENT_RTP_LOSS",
17127
+ type: "INCALL",
17128
+ content: { callUuid, event: SocketReceiveEvent.AGENT_RTP_LOSS }
17129
+ });
17130
+ });
17131
+ }
17132
+ }
16909
17133
  if (data.event === SocketReceiveEvent.CUSTOMER_RINGING) {
16910
17134
  this.callKit.trigger(KitEvent.CALL_RINGING, {
16911
17135
  time: /* @__PURE__ */ new Date(),
@@ -17018,7 +17242,7 @@ var Socket = class {
17018
17242
  return;
17019
17243
  }
17020
17244
  const { userInfo, version } = this.callKit.config.getConfig();
17021
- const { sessionId, extno, agentId, phoneType } = userInfo;
17245
+ const { sessionId, extno, agentId } = userInfo;
17022
17246
  if (!sessionId) {
17023
17247
  this.callKit.logger.error("sessionId is empty", {
17024
17248
  caller: "Socket.send",
@@ -17038,7 +17262,6 @@ var Socket = class {
17038
17262
  if (SocketSendEvent.CALL === event) {
17039
17263
  msg.phoneNum = extno;
17040
17264
  msg.agentId = agentId;
17041
- msg.phoneType = phoneType;
17042
17265
  if (message?.sourceType === CallSourceType.phoneNum) {
17043
17266
  delete msg.workOrderId;
17044
17267
  } else if (message?.sourceType === CallSourceType.workOrderId) {
@@ -17060,8 +17283,8 @@ var Socket = class {
17060
17283
  return;
17061
17284
  this.send(SocketSendEvent.PING);
17062
17285
  const now = Date.now();
17063
- const { pingInterval, pingTimeout } = this.reconnectConfig;
17064
- if (now - this.lastPingTime > pingInterval + pingTimeout) {
17286
+ const { pingTimeout } = this.reconnectConfig;
17287
+ if (now - this.lastPingTime > this.pingInterval + pingTimeout) {
17065
17288
  this.callKit.logger.warn("Ping timeout not connected", {
17066
17289
  caller: "Socket.ping",
17067
17290
  type: "INCALL",
@@ -17076,23 +17299,27 @@ var Socket = class {
17076
17299
  }
17077
17300
  }
17078
17301
  checkPing() {
17079
- if (this.pingTimer) {
17080
- clearInterval(this.pingTimer);
17081
- }
17082
- const { pingInterval } = this.reconnectConfig;
17083
- this.pingTimer = setInterval(() => {
17302
+ this.heartbeatManager.start(this.pingInterval, () => {
17084
17303
  this.ping();
17085
- }, pingInterval);
17304
+ });
17305
+ this.callKit.logger.info(
17306
+ `Heartbeat started with Worker: ${this.heartbeatManager.isUsingWorker()}`,
17307
+ {
17308
+ caller: "Socket.checkPing",
17309
+ type: "INCALL",
17310
+ content: {
17311
+ pingInterval: this.pingInterval,
17312
+ usingWorker: this.heartbeatManager.isUsingWorker()
17313
+ }
17314
+ }
17315
+ );
17086
17316
  }
17087
17317
  /**
17088
17318
  * reset socket connection and all states
17089
17319
  */
17090
17320
  async reset(config) {
17091
17321
  const { force = false } = config || {};
17092
- if (this.pingTimer) {
17093
- clearInterval(this.pingTimer);
17094
- this.pingTimer = void 0;
17095
- }
17322
+ this.heartbeatManager.stop();
17096
17323
  if (force) {
17097
17324
  this.callKit.trigger(KitEvent.INCALL_CONNECT_EVENT, {
17098
17325
  event: "INCALL_RESET"
@@ -17103,6 +17330,12 @@ var Socket = class {
17103
17330
  this.setConnectAuthState("startConfirm", false);
17104
17331
  this.clearWebSocket();
17105
17332
  }
17333
+ /**
17334
+ * Destroy the heartbeat manager
17335
+ */
17336
+ destroyHeartbeat() {
17337
+ this.heartbeatManager.destroy();
17338
+ }
17106
17339
  attemptReconnect() {
17107
17340
  if (this.reconnectTimer) {
17108
17341
  clearTimeout(this.reconnectTimer);
@@ -17228,6 +17461,7 @@ var CallKit = class {
17228
17461
  content: {
17229
17462
  username,
17230
17463
  password,
17464
+ ua: navigator.userAgent,
17231
17465
  encryptionMethod,
17232
17466
  encryptionPassword
17233
17467
  }
@@ -17261,6 +17495,7 @@ var CallKit = class {
17261
17495
  iceInfo: user.iceInfo,
17262
17496
  iceGatheringTimeout: user.iceGatheringTimeout,
17263
17497
  logGather: user.logGather,
17498
+ keepaliveInterval: user.keepaliveInterval,
17264
17499
  phoneType: user.phoneType,
17265
17500
  // encryptionType is in extra
17266
17501
  ...extra
@@ -17358,7 +17593,10 @@ var CallKit = class {
17358
17593
  caller: "CallKit.register",
17359
17594
  content: {}
17360
17595
  });
17361
- this.connect.register();
17596
+ const { userInfo } = this.config.getConfig();
17597
+ if (userInfo.phoneType === PhoneTypeEnum.SIP) {
17598
+ this.connect.register();
17599
+ }
17362
17600
  }
17363
17601
  async unregister() {
17364
17602
  if (!this.config.check())
@@ -17468,10 +17706,13 @@ var CallKit = class {
17468
17706
  force
17469
17707
  }
17470
17708
  });
17471
- if (this.connect.isCalling()) {
17472
- await this.hangup();
17709
+ const { userInfo } = this.config.getConfig();
17710
+ if (userInfo.phoneType === PhoneTypeEnum.SIP) {
17711
+ if (this.connect.isCalling()) {
17712
+ await this.hangup();
17713
+ }
17714
+ await this.connect.reset({ force });
17473
17715
  }
17474
- await this.connect.reset({ force });
17475
17716
  if (this.config.isLogin()) {
17476
17717
  await this.logout({ isReset: false });
17477
17718
  } else {