@zyratalk1/zyra-twilio-wrapper 1.2.9 → 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 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
@@ -1383,7 +1383,77 @@ async function isWebhookConfigured(deps) {
1383
1383
  }
1384
1384
  }
1385
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
+
1386
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
+ ];
1387
1457
  function isUrl(value) {
1388
1458
  try {
1389
1459
  const url = new URL(value);
@@ -1395,7 +1465,28 @@ function isUrl(value) {
1395
1465
  function isAudioLikeInput(value) {
1396
1466
  return value.startsWith("http://") || value.startsWith("https://") || value.startsWith("data:audio/");
1397
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
+ }
1398
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
+ }
1399
1490
  const welcomeMessage = input.welcomeMessage?.trim();
1400
1491
  if (!welcomeMessage) {
1401
1492
  throw new Error("Payload is missing or empty. welcomeMessage is required.");
@@ -1419,10 +1510,50 @@ function validateWelcomeConfigInput(input) {
1419
1510
  welcomeMessage
1420
1511
  };
1421
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
+ }
1422
1546
  async function welcomeMessageConfig(deps, input) {
1423
1547
  deps.ensureAuthenticated();
1424
1548
  try {
1425
- const payload = validateWelcomeConfigInput(input);
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 };
1426
1557
  const res = await deps.axiosInstance.post(
1427
1558
  `${deps.serverUrl}/voipSdk.welcomeMessageConfig`,
1428
1559
  payload,
@@ -1483,8 +1614,11 @@ var ConfigService = class {
1483
1614
  async isWebhookConfigured() {
1484
1615
  return isWebhookConfigured(this.withDeps());
1485
1616
  }
1486
- async welcomeMessageConfig(welcomeType, welcomeMessage) {
1487
- 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 });
1488
1622
  }
1489
1623
  async getVoipProviderConfigStatus() {
1490
1624
  return getVoipProviderConfigStatus(this.withDeps());
@@ -2124,6 +2258,12 @@ var ReactNativeVoipSdk = class {
2124
2258
  async welcomeMessageConfig(welcomeType, welcomeMessage) {
2125
2259
  return this.configService.welcomeMessageConfig(welcomeType, welcomeMessage);
2126
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
+ }
2127
2267
  destroy() {
2128
2268
  logVoipDebug("destroy() invoked");
2129
2269
  this.authManager.destroy();