@koi-design/callkit 2.3.0-beta.9 → 2.3.0

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.9",
731
+ version: "2.3.0",
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(),
@@ -17086,8 +17310,8 @@ var Socket = class {
17086
17310
  return;
17087
17311
  this.send(SocketSendEvent.PING);
17088
17312
  const now = Date.now();
17089
- const { pingInterval, pingTimeout } = this.reconnectConfig;
17090
- if (now - this.lastPingTime > pingInterval + pingTimeout) {
17313
+ const { pingTimeout } = this.reconnectConfig;
17314
+ if (now - this.lastPingTime > this.pingInterval + pingTimeout) {
17091
17315
  this.callKit.logger.warn("Ping timeout not connected", {
17092
17316
  caller: "Socket.ping",
17093
17317
  type: "INCALL",
@@ -17102,23 +17326,27 @@ var Socket = class {
17102
17326
  }
17103
17327
  }
17104
17328
  checkPing() {
17105
- if (this.pingTimer) {
17106
- clearInterval(this.pingTimer);
17107
- }
17108
- const { pingInterval } = this.reconnectConfig;
17109
- this.pingTimer = setInterval(() => {
17329
+ this.heartbeatManager.start(this.pingInterval, () => {
17110
17330
  this.ping();
17111
- }, 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
+ );
17112
17343
  }
17113
17344
  /**
17114
17345
  * reset socket connection and all states
17115
17346
  */
17116
17347
  async reset(config) {
17117
17348
  const { force = false } = config || {};
17118
- if (this.pingTimer) {
17119
- clearInterval(this.pingTimer);
17120
- this.pingTimer = void 0;
17121
- }
17349
+ this.heartbeatManager.stop();
17122
17350
  if (force) {
17123
17351
  this.callKit.trigger(KitEvent.INCALL_CONNECT_EVENT, {
17124
17352
  event: "INCALL_RESET"
@@ -17129,6 +17357,12 @@ var Socket = class {
17129
17357
  this.setConnectAuthState("startConfirm", false);
17130
17358
  this.clearWebSocket();
17131
17359
  }
17360
+ /**
17361
+ * Destroy the heartbeat manager
17362
+ */
17363
+ destroyHeartbeat() {
17364
+ this.heartbeatManager.destroy();
17365
+ }
17132
17366
  attemptReconnect() {
17133
17367
  if (this.reconnectTimer) {
17134
17368
  clearTimeout(this.reconnectTimer);
@@ -17254,6 +17488,7 @@ var CallKit = class {
17254
17488
  content: {
17255
17489
  username,
17256
17490
  password,
17491
+ ua: navigator.userAgent,
17257
17492
  encryptionMethod,
17258
17493
  encryptionPassword
17259
17494
  }
@@ -17287,6 +17522,7 @@ var CallKit = class {
17287
17522
  iceInfo: user.iceInfo,
17288
17523
  iceGatheringTimeout: user.iceGatheringTimeout,
17289
17524
  logGather: user.logGather,
17525
+ keepaliveInterval: user.keepaliveInterval,
17290
17526
  phoneType: user.phoneType,
17291
17527
  // encryptionType is in extra
17292
17528
  ...extra
@@ -17565,4 +17801,3 @@ var CallKit = class {
17565
17801
  0 && (module.exports = {
17566
17802
  CallKit
17567
17803
  });
17568
- //# sourceMappingURL=index.js.map