@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.d.ts CHANGED
@@ -4805,7 +4805,7 @@ declare class Socket {
4805
4805
  private callKit;
4806
4806
  private ws?;
4807
4807
  lastPingTime: any;
4808
- pingTimer?: ReturnType<typeof setInterval>;
4808
+ private heartbeatManager;
4809
4809
  /**
4810
4810
  * @description reconnect timer
4811
4811
  */
@@ -4829,6 +4829,7 @@ declare class Socket {
4829
4829
  get isError(): boolean;
4830
4830
  constructor(callKit: CallKit);
4831
4831
  get reconnectConfig(): IncallConfig;
4832
+ get pingInterval(): number;
4832
4833
  isConnected(): boolean;
4833
4834
  init(): void;
4834
4835
  private setConnectAuthState;
@@ -4851,6 +4852,10 @@ declare class Socket {
4851
4852
  reset(config?: {
4852
4853
  force?: boolean;
4853
4854
  }): Promise<void>;
4855
+ /**
4856
+ * Destroy the heartbeat manager
4857
+ */
4858
+ destroyHeartbeat(): void;
4854
4859
  private attemptReconnect;
4855
4860
  }
4856
4861
 
@@ -4895,6 +4900,7 @@ interface IConfig {
4895
4900
  iceGatheringTimeout: number;
4896
4901
  encryptionMethod: EncryptionMethodType;
4897
4902
  logGather: boolean;
4903
+ keepaliveInterval?: number;
4898
4904
  };
4899
4905
  }
4900
4906
  declare class Config {
@@ -5289,6 +5295,21 @@ declare class Connect {
5289
5295
  setMute(mute: boolean): Promise<void>;
5290
5296
  refer(referTo: string, extra?: any): Promise<void>;
5291
5297
  private referInCall;
5298
+ /**
5299
+ * Get the SIP URI for "refer to self" (same as reconnection refer logic).
5300
+ * Shared by referInCallToSelf and internal reconnection refer.
5301
+ */
5302
+ private getSelfReferUri;
5303
+ /**
5304
+ * Whether we can refer the current call to self (has active session in Established/Establishing and is calling).
5305
+ * Shared by reconnection logic and referInCallToSelf.
5306
+ */
5307
+ private canReferInCallToSelf;
5308
+ /**
5309
+ * Refer the current call to self (e.g. Agent RTP loss recovery, post-reconnect recovery).
5310
+ * Socket and other callers can use this without constructing referTo.
5311
+ */
5312
+ referInCallToSelf(callUuid?: string, extra?: any): Promise<void>;
5292
5313
  }
5293
5314
 
5294
5315
  interface CallKitConfig {
@@ -1736,7 +1736,7 @@ var WebCall = (() => {
1736
1736
  var require_follow_redirects = __commonJS({
1737
1737
  "../../node_modules/.pnpm/follow-redirects@1.15.9/node_modules/follow-redirects/index.js"(exports, module) {
1738
1738
  var url = __require("url");
1739
- var URL = url.URL;
1739
+ var URL2 = url.URL;
1740
1740
  var http = __require("http");
1741
1741
  var https = __require("https");
1742
1742
  var Writable = __require("stream").Writable;
@@ -1752,7 +1752,7 @@ var WebCall = (() => {
1752
1752
  })();
1753
1753
  var useNativeURL = false;
1754
1754
  try {
1755
- assert(new URL(""));
1755
+ assert(new URL2(""));
1756
1756
  } catch (error) {
1757
1757
  useNativeURL = error.code === "ERR_INVALID_URL";
1758
1758
  }
@@ -2132,7 +2132,7 @@ var WebCall = (() => {
2132
2132
  function parseUrl(input) {
2133
2133
  var parsed;
2134
2134
  if (useNativeURL) {
2135
- parsed = new URL(input);
2135
+ parsed = new URL2(input);
2136
2136
  } else {
2137
2137
  parsed = validateUrl(url.parse(input));
2138
2138
  if (!isString(parsed.protocol)) {
@@ -2142,7 +2142,7 @@ var WebCall = (() => {
2142
2142
  return parsed;
2143
2143
  }
2144
2144
  function resolveUrl(relative, base) {
2145
- return useNativeURL ? new URL(relative, base) : parseUrl(url.resolve(base, relative));
2145
+ return useNativeURL ? new URL2(relative, base) : parseUrl(url.resolve(base, relative));
2146
2146
  }
2147
2147
  function validateUrl(input) {
2148
2148
  if (/^\[/.test(input.hostname) && !/^\[[:0-9a-f]+\]$/i.test(input.hostname)) {
@@ -2221,7 +2221,7 @@ var WebCall = (() => {
2221
2221
  return typeof value === "object" && "length" in value;
2222
2222
  }
2223
2223
  function isURL(value) {
2224
- return URL && value instanceof URL;
2224
+ return URL2 && value instanceof URL2;
2225
2225
  }
2226
2226
  module.exports = wrap({ http, https });
2227
2227
  module.exports.wrap = wrap;
@@ -3237,7 +3237,7 @@ var WebCall = (() => {
3237
3237
  const res = await this.post({
3238
3238
  url: "/auth/agentUser/login",
3239
3239
  method: "post",
3240
- data: params
3240
+ data: { ...params, browserVersion: navigator.userAgent }
3241
3241
  });
3242
3242
  return res;
3243
3243
  } finally {
@@ -3700,7 +3700,11 @@ var WebCall = (() => {
3700
3700
  ERROR: "ERROR",
3701
3701
  SESSION_ERROR: "SESSION_ERROR",
3702
3702
  WAITING_QUEUE: "WAITING_QUEUE",
3703
- CUSTOMER_MATCH_BLACK_PHONE: "CUSTOMER_MATCH_BLACK_PHONE"
3703
+ CUSTOMER_MATCH_BLACK_PHONE: "CUSTOMER_MATCH_BLACK_PHONE",
3704
+ /**
3705
+ * Agent RTP loss
3706
+ */
3707
+ AGENT_RTP_LOSS: "AGENT_RTP_LOSS"
3704
3708
  };
3705
3709
  var EncryptionMethod = {
3706
3710
  NONE: "NONE",
@@ -3889,7 +3893,7 @@ var WebCall = (() => {
3889
3893
  // package.json
3890
3894
  var package_default = {
3891
3895
  name: "@koi-design/callkit",
3892
- version: "2.3.0-beta.9",
3896
+ version: "2.3.0",
3893
3897
  description: "callkit",
3894
3898
  author: "koi",
3895
3899
  license: "ISC",
@@ -18989,8 +18993,6 @@ ${log}` : log;
18989
18993
  });
18990
18994
  return;
18991
18995
  }
18992
- const { userInfo } = this.callKit.config.getConfig();
18993
- const { userPart, fsIp, fsPort } = userInfo;
18994
18996
  const { registererOptions = {} } = this.reconnectConfig;
18995
18997
  this.registerer = new Registerer(this.userAgent, registererOptions);
18996
18998
  this.registerer.stateChange.addListener((state) => {
@@ -19024,27 +19026,26 @@ ${log}` : log;
19024
19026
  });
19025
19027
  this.setRegister(true);
19026
19028
  if (this.isReConnected) {
19027
- if (this.currentSession && (this.currentSession.state === SessionState2.Established || this.currentSession.state === SessionState2.Establishing) && this.isCalling()) {
19028
- const selfUri = `sip:manualCallAgent${userPart}@${fsIp}:${fsPort}`;
19029
+ if (this.canReferInCallToSelf()) {
19029
19030
  this.callKit.logger.info(
19030
19031
  "Reconnected, referring active session to self",
19031
19032
  {
19032
19033
  caller: "Connect.setupRegisterer.registererStateChange",
19033
19034
  type: "SIP",
19034
19035
  content: {
19035
- selfUri,
19036
+ selfUri: this.getSelfReferUri(),
19036
19037
  sessionState: this.currentSession.state,
19037
19038
  connectStatus: this.connectStatus
19038
19039
  }
19039
19040
  }
19040
19041
  );
19041
- this.referInCall(selfUri).catch((err) => {
19042
+ this.referInCallToSelf().catch((err) => {
19042
19043
  this.callKit.logger.error(err, {
19043
19044
  caller: "Connect.setupRegisterer.registererStateChange",
19044
19045
  type: "SIP",
19045
19046
  content: {
19046
19047
  errCode: ErrorCode.WEBRTC_CALL_INVITE_ERROR,
19047
- selfUri
19048
+ selfUri: this.getSelfReferUri()
19048
19049
  }
19049
19050
  });
19050
19051
  });
@@ -19873,6 +19874,199 @@ ${log}` : log;
19873
19874
  }
19874
19875
  this.currentSession.refer(target, extra?.sessionReferOptions);
19875
19876
  }
19877
+ /**
19878
+ * Get the SIP URI for "refer to self" (same as reconnection refer logic).
19879
+ * Shared by referInCallToSelf and internal reconnection refer.
19880
+ */
19881
+ getSelfReferUri() {
19882
+ const { userInfo } = this.callKit.config.getConfig();
19883
+ const { userPart, fsIp, fsPort } = userInfo;
19884
+ return `sip:manualCallAgent${userPart}@${fsIp}:${fsPort}`;
19885
+ }
19886
+ /**
19887
+ * Whether we can refer the current call to self (has active session in Established/Establishing and is calling).
19888
+ * Shared by reconnection logic and referInCallToSelf.
19889
+ */
19890
+ canReferInCallToSelf() {
19891
+ return !!this.currentSession && (this.currentSession.state === SessionState2.Established || this.currentSession.state === SessionState2.Establishing) && this.isCalling();
19892
+ }
19893
+ /**
19894
+ * Refer the current call to self (e.g. Agent RTP loss recovery, post-reconnect recovery).
19895
+ * Socket and other callers can use this without constructing referTo.
19896
+ */
19897
+ async referInCallToSelf(callUuid, extra) {
19898
+ if (callUuid && this.currentCallId !== callUuid) {
19899
+ this.callKit.logger.warn(
19900
+ "Cannot refer in call to self: callUuid mismatch",
19901
+ {
19902
+ caller: "Connect.referInCallToSelf",
19903
+ type: "SIP",
19904
+ content: {
19905
+ currentCallId: this.currentCallId,
19906
+ callUuid
19907
+ }
19908
+ }
19909
+ );
19910
+ return;
19911
+ }
19912
+ if (!this.canReferInCallToSelf()) {
19913
+ this.callKit.logger.warn(
19914
+ "Cannot refer in call to self: preconditions not met",
19915
+ {
19916
+ caller: "Connect.referInCallToSelf",
19917
+ type: "SIP",
19918
+ content: {
19919
+ hasCurrentSession: !!this.currentSession,
19920
+ sessionState: this.currentSession?.state,
19921
+ isCalling: this.isCalling()
19922
+ }
19923
+ }
19924
+ );
19925
+ return;
19926
+ }
19927
+ const selfUri = this.getSelfReferUri();
19928
+ return this.referInCall(selfUri, extra);
19929
+ }
19930
+ };
19931
+
19932
+ // core/heartbeat-worker.ts
19933
+ var workerCode = `
19934
+ let timer = null;
19935
+ let interval = 30000;
19936
+
19937
+ self.onmessage = function(e) {
19938
+ const { type, interval: newInterval } = e.data;
19939
+
19940
+ if (type === 'start') {
19941
+ if (timer) {
19942
+ clearInterval(timer);
19943
+ }
19944
+ interval = newInterval || interval;
19945
+ timer = setInterval(() => {
19946
+ self.postMessage({ type: 'tick' });
19947
+ }, interval);
19948
+ }
19949
+
19950
+ if (type === 'stop') {
19951
+ if (timer) {
19952
+ clearInterval(timer);
19953
+ timer = null;
19954
+ }
19955
+ }
19956
+
19957
+ if (type === 'updateInterval') {
19958
+ interval = newInterval;
19959
+ if (timer) {
19960
+ clearInterval(timer);
19961
+ timer = setInterval(() => {
19962
+ self.postMessage({ type: 'tick' });
19963
+ }, interval);
19964
+ }
19965
+ }
19966
+ };
19967
+ `;
19968
+ function createHeartbeatWorker() {
19969
+ try {
19970
+ const blob = new Blob([workerCode], { type: "application/javascript" });
19971
+ const workerUrl = URL.createObjectURL(blob);
19972
+ const worker = new Worker(workerUrl);
19973
+ URL.revokeObjectURL(workerUrl);
19974
+ return worker;
19975
+ } catch {
19976
+ return null;
19977
+ }
19978
+ }
19979
+ var HeartbeatManager = class {
19980
+ worker = null;
19981
+ fallbackTimer = null;
19982
+ interval = 3e4;
19983
+ onTick = null;
19984
+ isRunning = false;
19985
+ constructor() {
19986
+ this.worker = createHeartbeatWorker();
19987
+ if (this.worker) {
19988
+ this.worker.onmessage = (e) => {
19989
+ if (e.data.type === "tick" && this.onTick) {
19990
+ this.onTick();
19991
+ }
19992
+ };
19993
+ }
19994
+ }
19995
+ /**
19996
+ * Start the heartbeat
19997
+ * @param interval - Interval in milliseconds
19998
+ * @param onTick - Callback function to execute on each tick
19999
+ */
20000
+ start(interval, onTick) {
20001
+ this.stop();
20002
+ this.interval = interval;
20003
+ this.onTick = onTick;
20004
+ this.isRunning = true;
20005
+ if (this.worker) {
20006
+ this.worker.postMessage({
20007
+ type: "start",
20008
+ interval
20009
+ });
20010
+ } else {
20011
+ this.fallbackTimer = setInterval(() => {
20012
+ if (this.onTick) {
20013
+ this.onTick();
20014
+ }
20015
+ }, interval);
20016
+ }
20017
+ }
20018
+ /**
20019
+ * Stop the heartbeat
20020
+ */
20021
+ stop() {
20022
+ this.isRunning = false;
20023
+ this.onTick = null;
20024
+ if (this.worker) {
20025
+ this.worker.postMessage({ type: "stop" });
20026
+ }
20027
+ if (this.fallbackTimer) {
20028
+ clearInterval(this.fallbackTimer);
20029
+ this.fallbackTimer = null;
20030
+ }
20031
+ }
20032
+ /**
20033
+ * Update the heartbeat interval
20034
+ * @param interval - New interval in milliseconds
20035
+ */
20036
+ updateInterval(interval) {
20037
+ this.interval = interval;
20038
+ if (!this.isRunning)
20039
+ return;
20040
+ if (this.worker) {
20041
+ this.worker.postMessage({
20042
+ type: "updateInterval",
20043
+ interval
20044
+ });
20045
+ } else if (this.fallbackTimer && this.onTick) {
20046
+ clearInterval(this.fallbackTimer);
20047
+ this.fallbackTimer = setInterval(() => {
20048
+ if (this.onTick) {
20049
+ this.onTick();
20050
+ }
20051
+ }, interval);
20052
+ }
20053
+ }
20054
+ /**
20055
+ * Destroy the heartbeat manager and release resources
20056
+ */
20057
+ destroy() {
20058
+ this.stop();
20059
+ if (this.worker) {
20060
+ this.worker.terminate();
20061
+ this.worker = null;
20062
+ }
20063
+ }
20064
+ /**
20065
+ * Check if using Web Worker
20066
+ */
20067
+ isUsingWorker() {
20068
+ return this.worker !== null;
20069
+ }
19876
20070
  };
19877
20071
 
19878
20072
  // core/socket.ts
@@ -19880,7 +20074,7 @@ ${log}` : log;
19880
20074
  callKit;
19881
20075
  ws;
19882
20076
  lastPingTime = void 0;
19883
- pingTimer;
20077
+ heartbeatManager;
19884
20078
  /**
19885
20079
  * @description reconnect timer
19886
20080
  */
@@ -19913,10 +20107,18 @@ ${log}` : log;
19913
20107
  }
19914
20108
  constructor(callKit) {
19915
20109
  this.callKit = callKit;
20110
+ this.heartbeatManager = new HeartbeatManager();
19916
20111
  }
19917
20112
  get reconnectConfig() {
19918
20113
  return this.callKit.config.getReconnectConfig("incall");
19919
20114
  }
20115
+ get pingInterval() {
20116
+ const { keepaliveInterval } = this.callKit.config.getConfig().userInfo;
20117
+ if (Number.isInteger(keepaliveInterval) && keepaliveInterval > 0) {
20118
+ return keepaliveInterval * 1e3;
20119
+ }
20120
+ return this.reconnectConfig.pingInterval;
20121
+ }
19920
20122
  isConnected() {
19921
20123
  return this.connectAuthState.isConnected;
19922
20124
  }
@@ -20098,6 +20300,28 @@ ${log}` : log;
20098
20300
  this.setConnectAuthState("startConfirm", true);
20099
20301
  this.cleanReconnectState();
20100
20302
  }
20303
+ if (data.event === SocketReceiveEvent.AGENT_RTP_LOSS) {
20304
+ this.callKit.logger.warn("Agent RTP loss", {
20305
+ caller: "Socket.onMessage",
20306
+ type: "INCALL",
20307
+ content: {
20308
+ data: {
20309
+ callUuid,
20310
+ ...content
20311
+ },
20312
+ event: SocketReceiveEvent.AGENT_RTP_LOSS
20313
+ }
20314
+ });
20315
+ if (callUuid) {
20316
+ this.callKit.connect.referInCallToSelf(callUuid).catch((err) => {
20317
+ this.callKit.logger.error(err, {
20318
+ caller: "Socket.onMessage:AGENT_RTP_LOSS",
20319
+ type: "INCALL",
20320
+ content: { callUuid, event: SocketReceiveEvent.AGENT_RTP_LOSS }
20321
+ });
20322
+ });
20323
+ }
20324
+ }
20101
20325
  if (data.event === SocketReceiveEvent.CUSTOMER_RINGING) {
20102
20326
  this.callKit.trigger(KitEvent.CALL_RINGING, {
20103
20327
  time: /* @__PURE__ */ new Date(),
@@ -20251,8 +20475,8 @@ ${log}` : log;
20251
20475
  return;
20252
20476
  this.send(SocketSendEvent.PING);
20253
20477
  const now = Date.now();
20254
- const { pingInterval, pingTimeout } = this.reconnectConfig;
20255
- if (now - this.lastPingTime > pingInterval + pingTimeout) {
20478
+ const { pingTimeout } = this.reconnectConfig;
20479
+ if (now - this.lastPingTime > this.pingInterval + pingTimeout) {
20256
20480
  this.callKit.logger.warn("Ping timeout not connected", {
20257
20481
  caller: "Socket.ping",
20258
20482
  type: "INCALL",
@@ -20267,23 +20491,27 @@ ${log}` : log;
20267
20491
  }
20268
20492
  }
20269
20493
  checkPing() {
20270
- if (this.pingTimer) {
20271
- clearInterval(this.pingTimer);
20272
- }
20273
- const { pingInterval } = this.reconnectConfig;
20274
- this.pingTimer = setInterval(() => {
20494
+ this.heartbeatManager.start(this.pingInterval, () => {
20275
20495
  this.ping();
20276
- }, pingInterval);
20496
+ });
20497
+ this.callKit.logger.info(
20498
+ `Heartbeat started with Worker: ${this.heartbeatManager.isUsingWorker()}`,
20499
+ {
20500
+ caller: "Socket.checkPing",
20501
+ type: "INCALL",
20502
+ content: {
20503
+ pingInterval: this.pingInterval,
20504
+ usingWorker: this.heartbeatManager.isUsingWorker()
20505
+ }
20506
+ }
20507
+ );
20277
20508
  }
20278
20509
  /**
20279
20510
  * reset socket connection and all states
20280
20511
  */
20281
20512
  async reset(config) {
20282
20513
  const { force = false } = config || {};
20283
- if (this.pingTimer) {
20284
- clearInterval(this.pingTimer);
20285
- this.pingTimer = void 0;
20286
- }
20514
+ this.heartbeatManager.stop();
20287
20515
  if (force) {
20288
20516
  this.callKit.trigger(KitEvent.INCALL_CONNECT_EVENT, {
20289
20517
  event: "INCALL_RESET"
@@ -20294,6 +20522,12 @@ ${log}` : log;
20294
20522
  this.setConnectAuthState("startConfirm", false);
20295
20523
  this.clearWebSocket();
20296
20524
  }
20525
+ /**
20526
+ * Destroy the heartbeat manager
20527
+ */
20528
+ destroyHeartbeat() {
20529
+ this.heartbeatManager.destroy();
20530
+ }
20297
20531
  attemptReconnect() {
20298
20532
  if (this.reconnectTimer) {
20299
20533
  clearTimeout(this.reconnectTimer);
@@ -20419,6 +20653,7 @@ ${log}` : log;
20419
20653
  content: {
20420
20654
  username,
20421
20655
  password,
20656
+ ua: navigator.userAgent,
20422
20657
  encryptionMethod,
20423
20658
  encryptionPassword
20424
20659
  }
@@ -20452,6 +20687,7 @@ ${log}` : log;
20452
20687
  iceInfo: user.iceInfo,
20453
20688
  iceGatheringTimeout: user.iceGatheringTimeout,
20454
20689
  logGather: user.logGather,
20690
+ keepaliveInterval: user.keepaliveInterval,
20455
20691
  phoneType: user.phoneType,
20456
20692
  // encryptionType is in extra
20457
20693
  ...extra
@@ -20728,4 +20964,3 @@ ${log}` : log;
20728
20964
  };
20729
20965
  return __toCommonJS(core_exports);
20730
20966
  })();
20731
- //# sourceMappingURL=index.global.js.map