@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.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.9",
704
+ version: "2.3.0",
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(),
@@ -17059,8 +17283,8 @@ var Socket = class {
17059
17283
  return;
17060
17284
  this.send(SocketSendEvent.PING);
17061
17285
  const now = Date.now();
17062
- const { pingInterval, pingTimeout } = this.reconnectConfig;
17063
- if (now - this.lastPingTime > pingInterval + pingTimeout) {
17286
+ const { pingTimeout } = this.reconnectConfig;
17287
+ if (now - this.lastPingTime > this.pingInterval + pingTimeout) {
17064
17288
  this.callKit.logger.warn("Ping timeout not connected", {
17065
17289
  caller: "Socket.ping",
17066
17290
  type: "INCALL",
@@ -17075,23 +17299,27 @@ var Socket = class {
17075
17299
  }
17076
17300
  }
17077
17301
  checkPing() {
17078
- if (this.pingTimer) {
17079
- clearInterval(this.pingTimer);
17080
- }
17081
- const { pingInterval } = this.reconnectConfig;
17082
- this.pingTimer = setInterval(() => {
17302
+ this.heartbeatManager.start(this.pingInterval, () => {
17083
17303
  this.ping();
17084
- }, 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
+ );
17085
17316
  }
17086
17317
  /**
17087
17318
  * reset socket connection and all states
17088
17319
  */
17089
17320
  async reset(config) {
17090
17321
  const { force = false } = config || {};
17091
- if (this.pingTimer) {
17092
- clearInterval(this.pingTimer);
17093
- this.pingTimer = void 0;
17094
- }
17322
+ this.heartbeatManager.stop();
17095
17323
  if (force) {
17096
17324
  this.callKit.trigger(KitEvent.INCALL_CONNECT_EVENT, {
17097
17325
  event: "INCALL_RESET"
@@ -17102,6 +17330,12 @@ var Socket = class {
17102
17330
  this.setConnectAuthState("startConfirm", false);
17103
17331
  this.clearWebSocket();
17104
17332
  }
17333
+ /**
17334
+ * Destroy the heartbeat manager
17335
+ */
17336
+ destroyHeartbeat() {
17337
+ this.heartbeatManager.destroy();
17338
+ }
17105
17339
  attemptReconnect() {
17106
17340
  if (this.reconnectTimer) {
17107
17341
  clearTimeout(this.reconnectTimer);
@@ -17227,6 +17461,7 @@ var CallKit = class {
17227
17461
  content: {
17228
17462
  username,
17229
17463
  password,
17464
+ ua: navigator.userAgent,
17230
17465
  encryptionMethod,
17231
17466
  encryptionPassword
17232
17467
  }
@@ -17260,6 +17495,7 @@ var CallKit = class {
17260
17495
  iceInfo: user.iceInfo,
17261
17496
  iceGatheringTimeout: user.iceGatheringTimeout,
17262
17497
  logGather: user.logGather,
17498
+ keepaliveInterval: user.keepaliveInterval,
17263
17499
  phoneType: user.phoneType,
17264
17500
  // encryptionType is in extra
17265
17501
  ...extra
@@ -17537,4 +17773,3 @@ var CallKit = class {
17537
17773
  export {
17538
17774
  CallKit
17539
17775
  };
17540
- //# sourceMappingURL=index.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@koi-design/callkit",
3
- "version": "2.3.0-beta.9",
3
+ "version": "2.3.0",
4
4
  "description": "callkit",
5
5
  "author": "koi",
6
6
  "license": "ISC",