@zyratalk1/zyra-twilio-wrapper 1.3.0 → 1.3.1

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.mts CHANGED
@@ -1,8 +1,23 @@
1
+ type MemberStatus = "Idle" | "Active" | "Ringing" | "ringing";
2
+ interface MemberStatusUpdate {
3
+ memberId: number;
4
+ status: MemberStatus;
5
+ timestamp: string;
6
+ }
1
7
  interface Config {
2
8
  serverUrl: string;
3
9
  identity: string;
4
10
  waitUrl?: string;
5
11
  sdkToken: string;
12
+ /** WebSocket URL for member status (default: wss://nextdev.zyratalk.com/websocket) */
13
+ websocketUrl?: string;
14
+ /** Connect member-status WebSocket after init (default: true) */
15
+ autoConnectMemberStatus?: boolean;
16
+ /**
17
+ * Routing member id for this agent. When set, Idle status updates for this member
18
+ * dismiss a ringing incoming call if the caller hung up before answer.
19
+ */
20
+ memberId?: number;
6
21
  accessToken?: string;
7
22
  enableAndroidVoiceRegister?: boolean;
8
23
  requestTimeoutMs?: number;
@@ -321,16 +336,28 @@ declare class BridgeClient {
321
336
 
322
337
  declare class ReactNativeVoipSdk {
323
338
  private readonly sdkConfig;
339
+ private readonly websocketUrl;
340
+ private readonly autoConnectMemberStatus;
341
+ private readonly memberId?;
324
342
  protected readonly bridge: BridgeClient;
325
343
  private readonly retry;
326
344
  private readonly authManager;
327
345
  private readonly callEngine;
328
346
  private readonly conferenceManager;
329
347
  private readonly configService;
348
+ private memberStatusWebSocket;
330
349
  private initialized;
331
350
  protected activeCallId: string | null;
332
351
  protected internalConferenceName: string;
352
+ private pendingIncomingCallId;
353
+ private incomingCallAccepted;
354
+ onMemberStatusUpdate?: (update: MemberStatusUpdate) => void;
355
+ onMemberStatusWebSocketConnected?: () => void;
356
+ onMemberStatusWebSocketDisconnected?: () => void;
333
357
  constructor(config: Config, nativeModule?: NativeModule);
358
+ private setupIncomingCallTracking;
359
+ private handleMemberStatusUpdate;
360
+ private dismissIncomingCallFromMemberIdle;
334
361
  on(eventName: SDKEventName | string, listener: (payload: Record<string, unknown>) => void): () => void;
335
362
  initializeSDK(config?: Partial<SDKConfig>): Promise<void>;
336
363
  authenticate(payload?: AuthPayload): Promise<ConsumerAuth>;
@@ -375,10 +402,60 @@ declare class ReactNativeVoipSdk {
375
402
  * Set hold music audio URL (MP3 or WAV HTTPS URL, e.g. from upload-audio).
376
403
  */
377
404
  holdMusicUrlConfig(audioUrl: string): Promise<CallResponse<HoldMusicUrlConfigResult>>;
405
+ /**
406
+ * Connect to member status WebSocket (default: wss://nextdev.zyratalk.com/websocket).
407
+ */
408
+ connectMemberStatusWebSocket(url?: string): void;
409
+ disconnectMemberStatusWebSocket(): void;
410
+ get isMemberStatusWebSocketConnected(): boolean;
411
+ getMemberStatus(memberId: number): MemberStatus | undefined;
412
+ getMemberStatusMap(): ReadonlyMap<number, MemberStatus>;
378
413
  destroy(): void;
379
414
  private ensureAuthenticated;
380
415
  }
381
416
 
417
+ interface MemberStatusWebSocketOptions {
418
+ url: string;
419
+ maxReconnectAttempts?: number;
420
+ reconnectDelayMs?: number;
421
+ onStatusUpdate?: (update: MemberStatusUpdate) => void;
422
+ onConnected?: () => void;
423
+ onDisconnected?: () => void;
424
+ onError?: (error: Error) => void;
425
+ }
426
+ /**
427
+ * WebSocket client for real-time routing member status updates.
428
+ * Protocol matches zyratalk-next websocket-service (same as browser VOIP wrapper).
429
+ */
430
+ declare class MemberStatusWebSocketClient {
431
+ private readonly url;
432
+ private readonly maxReconnectAttempts;
433
+ private readonly reconnectDelayMs;
434
+ private readonly onStatusUpdate?;
435
+ private readonly onConnected?;
436
+ private readonly onDisconnected?;
437
+ private readonly onError?;
438
+ private ws;
439
+ private reconnectTimeout;
440
+ private reconnectAttempts;
441
+ private isConnecting;
442
+ private intentionalClose;
443
+ private readonly statusMap;
444
+ constructor(options: MemberStatusWebSocketOptions);
445
+ get isConnected(): boolean;
446
+ getStatus(memberId: number): MemberStatus | undefined;
447
+ getStatusMap(): ReadonlyMap<number, MemberStatus>;
448
+ connect(): void;
449
+ disconnect(): void;
450
+ destroy(): void;
451
+ private clearReconnectTimeout;
452
+ }
453
+
454
+ /** Default API base URL when `serverUrl` is omitted (nextdev). */
455
+ declare const DEFAULT_SERVER_URL = "https://nextdev.zyratalk.com/api/trpc";
456
+ /** Default member-status WebSocket URL when `websocketUrl` is omitted (nextdev). */
457
+ declare const DEFAULT_WEBSOCKET_URL = "wss://nextdev.zyratalk.com/websocket/";
458
+
382
459
  /**
383
460
  * Backward-compatible wrapper class retaining the original export name.
384
461
  * Existing consumers can continue to construct `new ZyraTwilioWrapper(config)`.
@@ -399,4 +476,4 @@ declare class ZyraTwilioWrapper extends ReactNativeVoipSdk {
399
476
  destroy(): void;
400
477
  }
401
478
 
402
- export { type AuthPayload, type CallSession, type ConsumerAuth, type NativeModule, type Participant, ReactNativeVoipSdk, type SDKConfig, type SDKEventName, ZyraTwilioWrapper as default };
479
+ export { type AuthPayload, type CallSession, type ConsumerAuth, DEFAULT_SERVER_URL, DEFAULT_WEBSOCKET_URL, type MemberStatus, type MemberStatusUpdate, MemberStatusWebSocketClient, type NativeModule, type Participant, ReactNativeVoipSdk, type SDKConfig, type SDKEventName, ZyraTwilioWrapper as default };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,23 @@
1
+ type MemberStatus = "Idle" | "Active" | "Ringing" | "ringing";
2
+ interface MemberStatusUpdate {
3
+ memberId: number;
4
+ status: MemberStatus;
5
+ timestamp: string;
6
+ }
1
7
  interface Config {
2
8
  serverUrl: string;
3
9
  identity: string;
4
10
  waitUrl?: string;
5
11
  sdkToken: string;
12
+ /** WebSocket URL for member status (default: wss://nextdev.zyratalk.com/websocket) */
13
+ websocketUrl?: string;
14
+ /** Connect member-status WebSocket after init (default: true) */
15
+ autoConnectMemberStatus?: boolean;
16
+ /**
17
+ * Routing member id for this agent. When set, Idle status updates for this member
18
+ * dismiss a ringing incoming call if the caller hung up before answer.
19
+ */
20
+ memberId?: number;
6
21
  accessToken?: string;
7
22
  enableAndroidVoiceRegister?: boolean;
8
23
  requestTimeoutMs?: number;
@@ -321,16 +336,28 @@ declare class BridgeClient {
321
336
 
322
337
  declare class ReactNativeVoipSdk {
323
338
  private readonly sdkConfig;
339
+ private readonly websocketUrl;
340
+ private readonly autoConnectMemberStatus;
341
+ private readonly memberId?;
324
342
  protected readonly bridge: BridgeClient;
325
343
  private readonly retry;
326
344
  private readonly authManager;
327
345
  private readonly callEngine;
328
346
  private readonly conferenceManager;
329
347
  private readonly configService;
348
+ private memberStatusWebSocket;
330
349
  private initialized;
331
350
  protected activeCallId: string | null;
332
351
  protected internalConferenceName: string;
352
+ private pendingIncomingCallId;
353
+ private incomingCallAccepted;
354
+ onMemberStatusUpdate?: (update: MemberStatusUpdate) => void;
355
+ onMemberStatusWebSocketConnected?: () => void;
356
+ onMemberStatusWebSocketDisconnected?: () => void;
333
357
  constructor(config: Config, nativeModule?: NativeModule);
358
+ private setupIncomingCallTracking;
359
+ private handleMemberStatusUpdate;
360
+ private dismissIncomingCallFromMemberIdle;
334
361
  on(eventName: SDKEventName | string, listener: (payload: Record<string, unknown>) => void): () => void;
335
362
  initializeSDK(config?: Partial<SDKConfig>): Promise<void>;
336
363
  authenticate(payload?: AuthPayload): Promise<ConsumerAuth>;
@@ -375,10 +402,60 @@ declare class ReactNativeVoipSdk {
375
402
  * Set hold music audio URL (MP3 or WAV HTTPS URL, e.g. from upload-audio).
376
403
  */
377
404
  holdMusicUrlConfig(audioUrl: string): Promise<CallResponse<HoldMusicUrlConfigResult>>;
405
+ /**
406
+ * Connect to member status WebSocket (default: wss://nextdev.zyratalk.com/websocket).
407
+ */
408
+ connectMemberStatusWebSocket(url?: string): void;
409
+ disconnectMemberStatusWebSocket(): void;
410
+ get isMemberStatusWebSocketConnected(): boolean;
411
+ getMemberStatus(memberId: number): MemberStatus | undefined;
412
+ getMemberStatusMap(): ReadonlyMap<number, MemberStatus>;
378
413
  destroy(): void;
379
414
  private ensureAuthenticated;
380
415
  }
381
416
 
417
+ interface MemberStatusWebSocketOptions {
418
+ url: string;
419
+ maxReconnectAttempts?: number;
420
+ reconnectDelayMs?: number;
421
+ onStatusUpdate?: (update: MemberStatusUpdate) => void;
422
+ onConnected?: () => void;
423
+ onDisconnected?: () => void;
424
+ onError?: (error: Error) => void;
425
+ }
426
+ /**
427
+ * WebSocket client for real-time routing member status updates.
428
+ * Protocol matches zyratalk-next websocket-service (same as browser VOIP wrapper).
429
+ */
430
+ declare class MemberStatusWebSocketClient {
431
+ private readonly url;
432
+ private readonly maxReconnectAttempts;
433
+ private readonly reconnectDelayMs;
434
+ private readonly onStatusUpdate?;
435
+ private readonly onConnected?;
436
+ private readonly onDisconnected?;
437
+ private readonly onError?;
438
+ private ws;
439
+ private reconnectTimeout;
440
+ private reconnectAttempts;
441
+ private isConnecting;
442
+ private intentionalClose;
443
+ private readonly statusMap;
444
+ constructor(options: MemberStatusWebSocketOptions);
445
+ get isConnected(): boolean;
446
+ getStatus(memberId: number): MemberStatus | undefined;
447
+ getStatusMap(): ReadonlyMap<number, MemberStatus>;
448
+ connect(): void;
449
+ disconnect(): void;
450
+ destroy(): void;
451
+ private clearReconnectTimeout;
452
+ }
453
+
454
+ /** Default API base URL when `serverUrl` is omitted (nextdev). */
455
+ declare const DEFAULT_SERVER_URL = "https://nextdev.zyratalk.com/api/trpc";
456
+ /** Default member-status WebSocket URL when `websocketUrl` is omitted (nextdev). */
457
+ declare const DEFAULT_WEBSOCKET_URL = "wss://nextdev.zyratalk.com/websocket/";
458
+
382
459
  /**
383
460
  * Backward-compatible wrapper class retaining the original export name.
384
461
  * Existing consumers can continue to construct `new ZyraTwilioWrapper(config)`.
@@ -399,4 +476,4 @@ declare class ZyraTwilioWrapper extends ReactNativeVoipSdk {
399
476
  destroy(): void;
400
477
  }
401
478
 
402
- export { type AuthPayload, type CallSession, type ConsumerAuth, type NativeModule, type Participant, ReactNativeVoipSdk, type SDKConfig, type SDKEventName, ZyraTwilioWrapper as default };
479
+ export { type AuthPayload, type CallSession, type ConsumerAuth, DEFAULT_SERVER_URL, DEFAULT_WEBSOCKET_URL, type MemberStatus, type MemberStatusUpdate, MemberStatusWebSocketClient, type NativeModule, type Participant, ReactNativeVoipSdk, type SDKConfig, type SDKEventName, ZyraTwilioWrapper as default };
package/dist/index.js CHANGED
@@ -30,6 +30,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ DEFAULT_SERVER_URL: () => DEFAULT_SERVER_URL,
34
+ DEFAULT_WEBSOCKET_URL: () => DEFAULT_WEBSOCKET_URL,
35
+ MemberStatusWebSocketClient: () => MemberStatusWebSocketClient,
33
36
  ReactNativeVoipSdk: () => ReactNativeVoipSdk,
34
37
  default: () => ZyraTwilioWrapper
35
38
  });
@@ -46,6 +49,137 @@ function createErrorResult(error) {
46
49
  };
47
50
  }
48
51
 
52
+ // src/defaults.ts
53
+ var DEFAULT_SERVER_URL = "https://nextdev.zyratalk.com/api/trpc";
54
+ var DEFAULT_WEBSOCKET_URL = "wss://nextdev.zyratalk.com/websocket/";
55
+ function resolveWebSocketUrl(value) {
56
+ const trimmed = value?.trim();
57
+ return trimmed || DEFAULT_WEBSOCKET_URL;
58
+ }
59
+
60
+ // src/memberStatusWebSocket.ts
61
+ var DEFAULT_MAX_RECONNECT_ATTEMPTS = 5;
62
+ var DEFAULT_RECONNECT_DELAY_MS = 3e3;
63
+ var MemberStatusWebSocketClient = class {
64
+ constructor(options) {
65
+ this.ws = null;
66
+ this.reconnectTimeout = null;
67
+ this.reconnectAttempts = 0;
68
+ this.isConnecting = false;
69
+ this.intentionalClose = false;
70
+ this.statusMap = /* @__PURE__ */ new Map();
71
+ this.url = options.url;
72
+ this.maxReconnectAttempts = options.maxReconnectAttempts ?? DEFAULT_MAX_RECONNECT_ATTEMPTS;
73
+ this.reconnectDelayMs = options.reconnectDelayMs ?? DEFAULT_RECONNECT_DELAY_MS;
74
+ this.onStatusUpdate = options.onStatusUpdate;
75
+ this.onConnected = options.onConnected;
76
+ this.onDisconnected = options.onDisconnected;
77
+ this.onError = options.onError;
78
+ }
79
+ get isConnected() {
80
+ return this.ws?.readyState === WebSocket.OPEN;
81
+ }
82
+ getStatus(memberId) {
83
+ return this.statusMap.get(memberId);
84
+ }
85
+ getStatusMap() {
86
+ return this.statusMap;
87
+ }
88
+ connect() {
89
+ if (typeof WebSocket === "undefined") {
90
+ this.onError?.(
91
+ new Error("WebSocket is not available in this environment.")
92
+ );
93
+ return;
94
+ }
95
+ if (this.isConnecting || this.isConnected) {
96
+ return;
97
+ }
98
+ this.intentionalClose = false;
99
+ this.isConnecting = true;
100
+ try {
101
+ const ws = new WebSocket(this.url);
102
+ ws.onopen = () => {
103
+ this.isConnecting = false;
104
+ this.reconnectAttempts = 0;
105
+ this.clearReconnectTimeout();
106
+ console.log("[VOIP WEBSOCKET] connected", { url: this.url });
107
+ this.onConnected?.();
108
+ };
109
+ ws.onmessage = (event) => {
110
+ try {
111
+ const raw = typeof event.data === "string" ? event.data : String(event.data);
112
+ console.log("[VOIP WEBSOCKET] message", raw);
113
+ const message = JSON.parse(raw);
114
+ console.log("[VOIP WEBSOCKET] parsed", message);
115
+ if (message.type === "connected") {
116
+ console.log("[VOIP WEBSOCKET] handshake", message.message);
117
+ }
118
+ if (message.type === "status_update" && message.data) {
119
+ const { memberId, status, timestamp } = message.data;
120
+ console.log("[VOIP WEBSOCKET] status_update", {
121
+ memberId,
122
+ status,
123
+ timestamp
124
+ });
125
+ this.statusMap.set(memberId, status);
126
+ this.onStatusUpdate?.(message.data);
127
+ }
128
+ } catch (error) {
129
+ const parseError = error instanceof Error ? error : new Error("Failed to parse WebSocket message");
130
+ this.onError?.(parseError);
131
+ }
132
+ };
133
+ ws.onerror = () => {
134
+ this.isConnecting = false;
135
+ console.warn("[VOIP WEBSOCKET] error", { url: this.url });
136
+ this.onError?.(new Error("WebSocket connection error"));
137
+ };
138
+ ws.onclose = (event) => {
139
+ this.isConnecting = false;
140
+ this.ws = null;
141
+ console.log("[VOIP WEBSOCKET] closed", {
142
+ code: event.code,
143
+ reason: event.reason,
144
+ wasClean: event.wasClean
145
+ });
146
+ this.onDisconnected?.();
147
+ if (!this.intentionalClose && !event.wasClean && this.reconnectAttempts < this.maxReconnectAttempts) {
148
+ this.reconnectAttempts += 1;
149
+ const delay = this.reconnectDelayMs * Math.pow(2, this.reconnectAttempts - 1);
150
+ this.reconnectTimeout = setTimeout(() => {
151
+ this.connect();
152
+ }, delay);
153
+ }
154
+ };
155
+ this.ws = ws;
156
+ } catch (error) {
157
+ this.isConnecting = false;
158
+ const connectError = error instanceof Error ? error : new Error("Failed to create WebSocket connection");
159
+ this.onError?.(connectError);
160
+ }
161
+ }
162
+ disconnect() {
163
+ this.intentionalClose = true;
164
+ this.clearReconnectTimeout();
165
+ this.reconnectAttempts = 0;
166
+ if (this.ws) {
167
+ this.ws.close();
168
+ this.ws = null;
169
+ }
170
+ }
171
+ destroy() {
172
+ this.disconnect();
173
+ this.statusMap.clear();
174
+ }
175
+ clearReconnectTimeout() {
176
+ if (this.reconnectTimeout) {
177
+ clearTimeout(this.reconnectTimeout);
178
+ this.reconnectTimeout = null;
179
+ }
180
+ }
181
+ };
182
+
49
183
  // src/react-native-voip-sdk/bridge/event-emitter.ts
50
184
  var BridgeEventEmitter = class {
51
185
  constructor() {
@@ -1779,10 +1913,16 @@ function logVoipDebug(message, payload) {
1779
1913
  }
1780
1914
  console.log(`[VOIP SDK] ${message}`);
1781
1915
  }
1916
+ function isIdleMemberStatus(status) {
1917
+ return String(status).toLowerCase() === "idle";
1918
+ }
1782
1919
  var ReactNativeVoipSdk = class {
1783
1920
  constructor(config, nativeModule) {
1921
+ this.memberStatusWebSocket = null;
1784
1922
  this.initialized = false;
1785
1923
  this.activeCallId = null;
1924
+ this.pendingIncomingCallId = null;
1925
+ this.incomingCallAccepted = false;
1786
1926
  logVoipDebug("constructor() invoked", {
1787
1927
  hasServerUrl: Boolean(config.serverUrl),
1788
1928
  hasIdentity: Boolean(config.identity),
@@ -1790,6 +1930,9 @@ var ReactNativeVoipSdk = class {
1790
1930
  hasAccessToken: Boolean(config.accessToken)
1791
1931
  });
1792
1932
  this.internalConferenceName = `conf_${config.identity}_${Date.now()}`;
1933
+ this.websocketUrl = resolveWebSocketUrl(config.websocketUrl);
1934
+ this.autoConnectMemberStatus = config.autoConnectMemberStatus ?? true;
1935
+ this.memberId = config.memberId;
1793
1936
  this.sdkConfig = {
1794
1937
  serverUrl: config.serverUrl,
1795
1938
  consumerId: config.identity,
@@ -1827,6 +1970,79 @@ var ReactNativeVoipSdk = class {
1827
1970
  sdkToken: this.sdkConfig.sdkToken,
1828
1971
  ensureAuthenticated: this.ensureAuthenticated.bind(this)
1829
1972
  });
1973
+ this.setupIncomingCallTracking();
1974
+ }
1975
+ setupIncomingCallTracking() {
1976
+ this.bridge.on("onCallRinging", (payload) => {
1977
+ const direction = payload.direction;
1978
+ if (direction === "OUTBOUND") {
1979
+ return;
1980
+ }
1981
+ const callId = payload.callId;
1982
+ if (typeof callId === "string" && callId.length > 0) {
1983
+ this.pendingIncomingCallId = callId;
1984
+ this.incomingCallAccepted = false;
1985
+ logVoipDebug("incoming call ringing (pending)", { callId });
1986
+ }
1987
+ });
1988
+ this.bridge.on("onCallConnected", () => {
1989
+ this.incomingCallAccepted = true;
1990
+ this.pendingIncomingCallId = null;
1991
+ });
1992
+ this.bridge.on("onMissedCall", () => {
1993
+ this.pendingIncomingCallId = null;
1994
+ this.incomingCallAccepted = false;
1995
+ });
1996
+ this.bridge.on("onCallEnded", (payload) => {
1997
+ const callId = payload.callId;
1998
+ if (typeof callId === "string" && callId === this.pendingIncomingCallId) {
1999
+ this.pendingIncomingCallId = null;
2000
+ this.incomingCallAccepted = false;
2001
+ }
2002
+ });
2003
+ }
2004
+ handleMemberStatusUpdate(update) {
2005
+ logVoipDebug("WebSocket member status", {
2006
+ memberId: update.memberId,
2007
+ status: update.status,
2008
+ timestamp: update.timestamp,
2009
+ pendingIncomingCallId: this.pendingIncomingCallId,
2010
+ incomingCallAccepted: this.incomingCallAccepted
2011
+ });
2012
+ this.onMemberStatusUpdate?.(update);
2013
+ if (!this.pendingIncomingCallId || this.incomingCallAccepted) {
2014
+ return;
2015
+ }
2016
+ if (this.memberId != null && update.memberId !== this.memberId) {
2017
+ return;
2018
+ }
2019
+ if (isIdleMemberStatus(update.status)) {
2020
+ logVoipDebug("member Idle while incoming ringing \u2014 caller likely hung up", {
2021
+ memberId: update.memberId,
2022
+ callId: this.pendingIncomingCallId
2023
+ });
2024
+ void this.dismissIncomingCallFromMemberIdle();
2025
+ }
2026
+ }
2027
+ async dismissIncomingCallFromMemberIdle() {
2028
+ const callId = this.pendingIncomingCallId;
2029
+ if (!callId || this.incomingCallAccepted) {
2030
+ return;
2031
+ }
2032
+ this.pendingIncomingCallId = null;
2033
+ try {
2034
+ if (this.bridge.nativeModule.rejectCall) {
2035
+ await this.bridge.nativeModule.rejectCall({ callId });
2036
+ } else {
2037
+ await this.endCall(callId);
2038
+ }
2039
+ } catch (error) {
2040
+ logVoipDebug("dismissIncomingCallFromMemberIdle failed", {
2041
+ message: error instanceof Error ? error.message : String(error)
2042
+ });
2043
+ }
2044
+ this.bridge.emit("onMissedCall", { callId, reason: "member_idle" });
2045
+ this.bridge.emit("onCallEnded", { callId, reason: "member_idle" });
1830
2046
  }
1831
2047
  on(eventName, listener) {
1832
2048
  return this.bridge.on(eventName, listener);
@@ -1859,6 +2075,9 @@ var ReactNativeVoipSdk = class {
1859
2075
  }
1860
2076
  this.initialized = true;
1861
2077
  logVoipDebug("initializeSDK() success");
2078
+ if (this.websocketUrl && this.autoConnectMemberStatus) {
2079
+ this.connectMemberStatusWebSocket();
2080
+ }
1862
2081
  } catch (error) {
1863
2082
  logVoipDebug("initializeSDK() config check failure", {
1864
2083
  message: error instanceof Error ? error.message : String(error)
@@ -1999,6 +2218,8 @@ var ReactNativeVoipSdk = class {
1999
2218
  try {
2000
2219
  const id = callId ?? this.activeCallId;
2001
2220
  if (!id) return createErrorResult("No active call to answer");
2221
+ this.incomingCallAccepted = true;
2222
+ this.pendingIncomingCallId = null;
2002
2223
  await this.answerCall(id);
2003
2224
  return createSuccessResult("Call accepted", null);
2004
2225
  } catch (error) {
@@ -2010,6 +2231,8 @@ var ReactNativeVoipSdk = class {
2010
2231
  if (!this.bridge.nativeModule.rejectCall) {
2011
2232
  return createErrorResult("rejectCall not supported by native module");
2012
2233
  }
2234
+ this.pendingIncomingCallId = null;
2235
+ this.incomingCallAccepted = false;
2013
2236
  const result = await this.bridge.nativeModule.rejectCall({ callId });
2014
2237
  if (!result.success) {
2015
2238
  return createErrorResult(result.error ?? "Failed to reject call");
@@ -2264,8 +2487,61 @@ var ReactNativeVoipSdk = class {
2264
2487
  async holdMusicUrlConfig(audioUrl) {
2265
2488
  return this.configService.holdMusicUrlConfig(audioUrl);
2266
2489
  }
2490
+ /**
2491
+ * Connect to member status WebSocket (default: wss://nextdev.zyratalk.com/websocket).
2492
+ */
2493
+ connectMemberStatusWebSocket(url) {
2494
+ const wsUrl = (url ?? this.websocketUrl)?.trim();
2495
+ if (!wsUrl) {
2496
+ throw new Error(
2497
+ "WebSocket URL is required. Pass url or set websocketUrl in config."
2498
+ );
2499
+ }
2500
+ this.disconnectMemberStatusWebSocket();
2501
+ this.memberStatusWebSocket = new MemberStatusWebSocketClient({
2502
+ url: wsUrl,
2503
+ onStatusUpdate: (update) => this.handleMemberStatusUpdate(update),
2504
+ onConnected: () => {
2505
+ logVoipDebug("WebSocket connected", { url: wsUrl });
2506
+ this.onMemberStatusWebSocketConnected?.();
2507
+ },
2508
+ onDisconnected: () => {
2509
+ logVoipDebug("WebSocket disconnected", { url: wsUrl });
2510
+ this.onMemberStatusWebSocketDisconnected?.();
2511
+ },
2512
+ onError: (error) => {
2513
+ logVoipDebug("member status WebSocket error", {
2514
+ message: error.message
2515
+ });
2516
+ this.bridge.emit("onError", {
2517
+ scope: "member_status_websocket",
2518
+ message: error.message
2519
+ });
2520
+ }
2521
+ });
2522
+ this.memberStatusWebSocket.connect();
2523
+ logVoipDebug("member status WebSocket connecting", { url: wsUrl });
2524
+ }
2525
+ disconnectMemberStatusWebSocket() {
2526
+ if (this.memberStatusWebSocket) {
2527
+ this.memberStatusWebSocket.destroy();
2528
+ this.memberStatusWebSocket = null;
2529
+ }
2530
+ }
2531
+ get isMemberStatusWebSocketConnected() {
2532
+ return this.memberStatusWebSocket?.isConnected ?? false;
2533
+ }
2534
+ getMemberStatus(memberId) {
2535
+ return this.memberStatusWebSocket?.getStatus(memberId);
2536
+ }
2537
+ getMemberStatusMap() {
2538
+ return this.memberStatusWebSocket?.getStatusMap() ?? /* @__PURE__ */ new Map();
2539
+ }
2267
2540
  destroy() {
2268
2541
  logVoipDebug("destroy() invoked");
2542
+ this.disconnectMemberStatusWebSocket();
2543
+ this.pendingIncomingCallId = null;
2544
+ this.incomingCallAccepted = false;
2269
2545
  this.authManager.destroy();
2270
2546
  this.bridge.destroy();
2271
2547
  this.initialized = false;
@@ -2322,6 +2598,9 @@ var ZyraTwilioWrapper = class extends ReactNativeVoipSdk {
2322
2598
  };
2323
2599
  // Annotate the CommonJS export names for ESM import in node:
2324
2600
  0 && (module.exports = {
2601
+ DEFAULT_SERVER_URL,
2602
+ DEFAULT_WEBSOCKET_URL,
2603
+ MemberStatusWebSocketClient,
2325
2604
  ReactNativeVoipSdk
2326
2605
  });
2327
2606
  //# sourceMappingURL=index.js.map