@zyratalk1/zyra-twilio-wrapper 1.2.8 → 1.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.mts +9 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +152 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +152 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/types/interface.ts +25 -0
package/dist/index.d.mts
CHANGED
|
@@ -79,6 +79,11 @@ interface WelcomeConfigResult {
|
|
|
79
79
|
welcomeType: WelcomeType;
|
|
80
80
|
routingConfigId: number;
|
|
81
81
|
}
|
|
82
|
+
interface HoldMusicUrlConfigResult {
|
|
83
|
+
success: boolean;
|
|
84
|
+
message: string;
|
|
85
|
+
routingConfigId: number;
|
|
86
|
+
}
|
|
82
87
|
/**
|
|
83
88
|
* Routing Config
|
|
84
89
|
*/
|
|
@@ -366,6 +371,10 @@ declare class ReactNativeVoipSdk {
|
|
|
366
371
|
deregisterWebhook(): Promise<CallResponse<null>>;
|
|
367
372
|
isWebhookConfigured(): Promise<CallResponse<WebhookConfigResult>>;
|
|
368
373
|
welcomeMessageConfig(welcomeType: "audio" | "tts", welcomeMessage: string): Promise<CallResponse<WelcomeConfigResult>>;
|
|
374
|
+
/**
|
|
375
|
+
* Set hold music audio URL (MP3 or WAV HTTPS URL, e.g. from upload-audio).
|
|
376
|
+
*/
|
|
377
|
+
holdMusicUrlConfig(audioUrl: string): Promise<CallResponse<HoldMusicUrlConfigResult>>;
|
|
369
378
|
destroy(): void;
|
|
370
379
|
private ensureAuthenticated;
|
|
371
380
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -79,6 +79,11 @@ interface WelcomeConfigResult {
|
|
|
79
79
|
welcomeType: WelcomeType;
|
|
80
80
|
routingConfigId: number;
|
|
81
81
|
}
|
|
82
|
+
interface HoldMusicUrlConfigResult {
|
|
83
|
+
success: boolean;
|
|
84
|
+
message: string;
|
|
85
|
+
routingConfigId: number;
|
|
86
|
+
}
|
|
82
87
|
/**
|
|
83
88
|
* Routing Config
|
|
84
89
|
*/
|
|
@@ -366,6 +371,10 @@ declare class ReactNativeVoipSdk {
|
|
|
366
371
|
deregisterWebhook(): Promise<CallResponse<null>>;
|
|
367
372
|
isWebhookConfigured(): Promise<CallResponse<WebhookConfigResult>>;
|
|
368
373
|
welcomeMessageConfig(welcomeType: "audio" | "tts", welcomeMessage: string): Promise<CallResponse<WelcomeConfigResult>>;
|
|
374
|
+
/**
|
|
375
|
+
* Set hold music audio URL (MP3 or WAV HTTPS URL, e.g. from upload-audio).
|
|
376
|
+
*/
|
|
377
|
+
holdMusicUrlConfig(audioUrl: string): Promise<CallResponse<HoldMusicUrlConfigResult>>;
|
|
369
378
|
destroy(): void;
|
|
370
379
|
private ensureAuthenticated;
|
|
371
380
|
}
|
package/dist/index.js
CHANGED
|
@@ -319,11 +319,13 @@ var TwilioVoiceAdapter = class {
|
|
|
319
319
|
async endCall(input) {
|
|
320
320
|
console.log("[VOIP SDK] endCall() invoked", { callId: input.callId });
|
|
321
321
|
const tracked = this.calls.get(input.callId);
|
|
322
|
-
if (!tracked)
|
|
322
|
+
if (!tracked) {
|
|
323
|
+
console.log("[VOIP SDK] endCall() call already ended", { callId: input.callId });
|
|
324
|
+
return { success: true, data: {} };
|
|
325
|
+
}
|
|
323
326
|
try {
|
|
324
|
-
await tracked.call.disconnect();
|
|
325
327
|
this.calls.delete(input.callId);
|
|
326
|
-
|
|
328
|
+
await tracked.call.disconnect();
|
|
327
329
|
console.log("[VOIP SDK] call ended", { callId: input.callId });
|
|
328
330
|
return { success: true, data: {} };
|
|
329
331
|
} catch (error) {
|
|
@@ -694,6 +696,10 @@ var TwilioVoiceAdapter = class {
|
|
|
694
696
|
console.log("[VOIP DEBUG] call disconnected while held \u2014 keeping tracked entry", { callId });
|
|
695
697
|
return;
|
|
696
698
|
}
|
|
699
|
+
if (!heldEntry) {
|
|
700
|
+
console.log("[VOIP DEBUG] call disconnected but already removed", { callId });
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
697
703
|
this.calls.delete(callId);
|
|
698
704
|
this.emit("onCallEnded", {
|
|
699
705
|
callId,
|
|
@@ -1377,7 +1383,77 @@ async function isWebhookConfigured(deps) {
|
|
|
1377
1383
|
}
|
|
1378
1384
|
}
|
|
1379
1385
|
|
|
1386
|
+
// src/holdMusicUrlConfig.ts
|
|
1387
|
+
function isHttpUrl(value) {
|
|
1388
|
+
try {
|
|
1389
|
+
const url = new URL(value);
|
|
1390
|
+
return url.protocol === "http:" || url.protocol === "https:";
|
|
1391
|
+
} catch {
|
|
1392
|
+
return false;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
function validateHoldMusicUrlConfigInput(input) {
|
|
1396
|
+
const audioUrl = input.audioUrl?.trim() ?? "";
|
|
1397
|
+
if (!audioUrl) {
|
|
1398
|
+
throw new Error("audioUrl is required.");
|
|
1399
|
+
}
|
|
1400
|
+
if (!isHttpUrl(audioUrl)) {
|
|
1401
|
+
throw new Error(
|
|
1402
|
+
"audioUrl must be a valid URL (e.g. S3 URL from upload-audio with purpose hold)."
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
try {
|
|
1406
|
+
const pathname = new URL(audioUrl).pathname;
|
|
1407
|
+
const ext = pathname.replace(/^.*\./, "").toLowerCase();
|
|
1408
|
+
if (ext !== "mp3" && ext !== "wav") {
|
|
1409
|
+
throw new Error(
|
|
1410
|
+
"Audio URL must point to an .mp3 or .wav file. Only MP3 and WAV formats are accepted."
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
} catch (err) {
|
|
1414
|
+
if (err instanceof Error && err.message.startsWith("Audio URL")) throw err;
|
|
1415
|
+
throw new Error("audioUrl must be a valid URL with .mp3 or .wav file.");
|
|
1416
|
+
}
|
|
1417
|
+
return { audioUrl };
|
|
1418
|
+
}
|
|
1419
|
+
async function holdMusicUrlConfig(deps, input) {
|
|
1420
|
+
deps.ensureAuthenticated();
|
|
1421
|
+
try {
|
|
1422
|
+
const payload = validateHoldMusicUrlConfigInput(input);
|
|
1423
|
+
const res = await deps.axiosInstance.post(
|
|
1424
|
+
`${deps.serverUrl}/voipSdk.holdMusicUrlConfig`,
|
|
1425
|
+
payload,
|
|
1426
|
+
{
|
|
1427
|
+
headers: {
|
|
1428
|
+
Authorization: `Bearer ${deps.sdkToken}`
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
);
|
|
1432
|
+
const data = res?.data?.result ?? res?.data ?? null;
|
|
1433
|
+
if (!data) {
|
|
1434
|
+
throw new Error("Invalid response from server");
|
|
1435
|
+
}
|
|
1436
|
+
return createSuccessResult(data.message || "Hold music URL updated successfully", {
|
|
1437
|
+
success: data.success,
|
|
1438
|
+
message: data.message,
|
|
1439
|
+
routingConfigId: data.routingConfigId
|
|
1440
|
+
});
|
|
1441
|
+
} catch (error) {
|
|
1442
|
+
return createErrorResult(
|
|
1443
|
+
error instanceof Error ? error : "Failed to upsert hold music configuration"
|
|
1444
|
+
);
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1380
1448
|
// src/welcomeMessageConfig.ts
|
|
1449
|
+
var MAX_AUDIO_SIZE_BYTES = 2 * 1024 * 1024;
|
|
1450
|
+
var ALLOWED_AUDIO_EXTENSIONS = ["mp3", "wav"];
|
|
1451
|
+
var ALLOWED_AUDIO_MIME_TYPES = [
|
|
1452
|
+
"audio/mpeg",
|
|
1453
|
+
"audio/mp3",
|
|
1454
|
+
"audio/wav",
|
|
1455
|
+
"audio/x-wav"
|
|
1456
|
+
];
|
|
1381
1457
|
function isUrl(value) {
|
|
1382
1458
|
try {
|
|
1383
1459
|
const url = new URL(value);
|
|
@@ -1389,7 +1465,28 @@ function isUrl(value) {
|
|
|
1389
1465
|
function isAudioLikeInput(value) {
|
|
1390
1466
|
return value.startsWith("http://") || value.startsWith("https://") || value.startsWith("data:audio/");
|
|
1391
1467
|
}
|
|
1468
|
+
function validateAudioFile(file) {
|
|
1469
|
+
const ext = file.name.split(".").pop()?.toLowerCase() ?? "";
|
|
1470
|
+
const mime = (file.type ?? "").toLowerCase();
|
|
1471
|
+
const extOk = ALLOWED_AUDIO_EXTENSIONS.includes(ext);
|
|
1472
|
+
const mimeOk = ALLOWED_AUDIO_MIME_TYPES.includes(mime) || extOk;
|
|
1473
|
+
if (!mimeOk) {
|
|
1474
|
+
return "Only .mp3 and .wav audio files are accepted.";
|
|
1475
|
+
}
|
|
1476
|
+
if (file.size !== void 0 && file.size > MAX_AUDIO_SIZE_BYTES) {
|
|
1477
|
+
const mb = (file.size / (1024 * 1024)).toFixed(1);
|
|
1478
|
+
return `File size is ${mb} MB \u2014 maximum allowed is 2 MB.`;
|
|
1479
|
+
}
|
|
1480
|
+
return null;
|
|
1481
|
+
}
|
|
1392
1482
|
function validateWelcomeConfigInput(input) {
|
|
1483
|
+
if (input.audioFile) {
|
|
1484
|
+
const audioError = validateAudioFile(input.audioFile);
|
|
1485
|
+
if (audioError) {
|
|
1486
|
+
throw new Error(audioError);
|
|
1487
|
+
}
|
|
1488
|
+
return input;
|
|
1489
|
+
}
|
|
1393
1490
|
const welcomeMessage = input.welcomeMessage?.trim();
|
|
1394
1491
|
if (!welcomeMessage) {
|
|
1395
1492
|
throw new Error("Payload is missing or empty. welcomeMessage is required.");
|
|
@@ -1413,10 +1510,50 @@ function validateWelcomeConfigInput(input) {
|
|
|
1413
1510
|
welcomeMessage
|
|
1414
1511
|
};
|
|
1415
1512
|
}
|
|
1513
|
+
async function uploadAudioFile(deps, file, currentAudioUrl) {
|
|
1514
|
+
let uploadBase;
|
|
1515
|
+
try {
|
|
1516
|
+
uploadBase = new URL(deps.serverUrl).origin;
|
|
1517
|
+
} catch {
|
|
1518
|
+
uploadBase = deps.serverUrl.replace(/\/voipSdk.*$/, "").replace(/\/$/, "");
|
|
1519
|
+
}
|
|
1520
|
+
const uploadUrl = `${uploadBase}/api/voip/upload-audio`;
|
|
1521
|
+
const formData = new FormData();
|
|
1522
|
+
formData.append("file", {
|
|
1523
|
+
uri: file.uri,
|
|
1524
|
+
name: file.name,
|
|
1525
|
+
type: file.type || "audio/mpeg"
|
|
1526
|
+
});
|
|
1527
|
+
if (currentAudioUrl) {
|
|
1528
|
+
formData.append("currentUrl", currentAudioUrl);
|
|
1529
|
+
}
|
|
1530
|
+
const response = await fetch(uploadUrl, {
|
|
1531
|
+
method: "POST",
|
|
1532
|
+
headers: {
|
|
1533
|
+
Authorization: `Bearer ${deps.sdkToken}`
|
|
1534
|
+
},
|
|
1535
|
+
body: formData
|
|
1536
|
+
});
|
|
1537
|
+
const json = await response.json();
|
|
1538
|
+
if (!response.ok || !json.success) {
|
|
1539
|
+
throw new Error(json.message ?? `Audio upload failed with status ${response.status}`);
|
|
1540
|
+
}
|
|
1541
|
+
if (!json.url) {
|
|
1542
|
+
throw new Error("Server did not return an audio URL after upload.");
|
|
1543
|
+
}
|
|
1544
|
+
return json.url;
|
|
1545
|
+
}
|
|
1416
1546
|
async function welcomeMessageConfig(deps, input) {
|
|
1417
1547
|
deps.ensureAuthenticated();
|
|
1418
1548
|
try {
|
|
1419
|
-
|
|
1549
|
+
validateWelcomeConfigInput(input);
|
|
1550
|
+
let welcomeType = input.welcomeType;
|
|
1551
|
+
let welcomeMessage = input.welcomeMessage?.trim() ?? "";
|
|
1552
|
+
if (input.audioFile) {
|
|
1553
|
+
welcomeMessage = await uploadAudioFile(deps, input.audioFile);
|
|
1554
|
+
welcomeType = "audio";
|
|
1555
|
+
}
|
|
1556
|
+
const payload = { welcomeType, welcomeMessage };
|
|
1420
1557
|
const res = await deps.axiosInstance.post(
|
|
1421
1558
|
`${deps.serverUrl}/voipSdk.welcomeMessageConfig`,
|
|
1422
1559
|
payload,
|
|
@@ -1477,8 +1614,11 @@ var ConfigService = class {
|
|
|
1477
1614
|
async isWebhookConfigured() {
|
|
1478
1615
|
return isWebhookConfigured(this.withDeps());
|
|
1479
1616
|
}
|
|
1480
|
-
async welcomeMessageConfig(welcomeType, welcomeMessage) {
|
|
1481
|
-
return welcomeMessageConfig(this.withDeps(), { welcomeType, welcomeMessage });
|
|
1617
|
+
async welcomeMessageConfig(welcomeType, welcomeMessage, audioFile) {
|
|
1618
|
+
return welcomeMessageConfig(this.withDeps(), { welcomeType, welcomeMessage, audioFile });
|
|
1619
|
+
}
|
|
1620
|
+
async holdMusicUrlConfig(audioUrl) {
|
|
1621
|
+
return holdMusicUrlConfig(this.withDeps(), { audioUrl });
|
|
1482
1622
|
}
|
|
1483
1623
|
async getVoipProviderConfigStatus() {
|
|
1484
1624
|
return getVoipProviderConfigStatus(this.withDeps());
|
|
@@ -2118,6 +2258,12 @@ var ReactNativeVoipSdk = class {
|
|
|
2118
2258
|
async welcomeMessageConfig(welcomeType, welcomeMessage) {
|
|
2119
2259
|
return this.configService.welcomeMessageConfig(welcomeType, welcomeMessage);
|
|
2120
2260
|
}
|
|
2261
|
+
/**
|
|
2262
|
+
* Set hold music audio URL (MP3 or WAV HTTPS URL, e.g. from upload-audio).
|
|
2263
|
+
*/
|
|
2264
|
+
async holdMusicUrlConfig(audioUrl) {
|
|
2265
|
+
return this.configService.holdMusicUrlConfig(audioUrl);
|
|
2266
|
+
}
|
|
2121
2267
|
destroy() {
|
|
2122
2268
|
logVoipDebug("destroy() invoked");
|
|
2123
2269
|
this.authManager.destroy();
|