@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 +78 -1
- package/dist/index.d.ts +78 -1
- package/dist/index.js +279 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +276 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/types/interface.ts +17 -0
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
|