diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 669c386..fe69b07 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -35,6 +35,8 @@ jobs:
BW_ACCOUNT_ID: ${{ secrets.BW_ACCOUNT_ID }}
BW_USERNAME: ${{ secrets.BW_USERNAME }}
BW_PASSWORD: ${{ secrets.BW_PASSWORD }}
+ BW_CLIENT_ID: ${{ secrets.BW_CLIENT_ID }}
+ BW_CLIENT_SECRET: ${{ secrets.BW_CLIENT_SECRET }}
BW_VOICE_APPLICATION_ID: ${{ secrets.BW_VOICE_APPLICATION_ID }}
BW_MESSAGING_APPLICATION_ID: ${{ secrets.BW_MESSAGING_APPLICATION_ID }}
BW_NUMBER: ${{ secrets.BW_NUMBER }}
diff --git a/.gitignore b/.gitignore
index 8cb8bd3..dd02192 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ composer.lock
.phpunit.result.cache
composer.phar
.idea
+.env*
\ No newline at end of file
diff --git a/src/Controllers/BaseController.php b/src/Controllers/BaseController.php
index 7a41cc3..beeb077 100644
--- a/src/Controllers/BaseController.php
+++ b/src/Controllers/BaseController.php
@@ -82,37 +82,9 @@ protected function validateResponse(HttpResponse $response, HttpContext $_httpCo
*/
protected function configureAuth(&$headers, $authType)
{
- if (!empty($this->config->getAccessToken()) &&
- (empty($this->config->getAccessTokenExpiration()) ||
- $this->config->getAccessTokenExpiration() > time() + 60)
- ) {
- $headers['Authorization'] = 'Bearer ' . $this->config->getAccessToken();
- return;
- }
-
- if (!empty($this->config->getClientId()) && !empty($this->config->getClientSecret())) {
- $_tokenUrl = 'https://api.bandwidth.com/api/v1/oauth2/token';
- $_tokenHeaders = array (
- 'User-Agent' => BaseController::USER_AGENT,
- 'Content-Type' => 'application/x-www-form-urlencoded',
- 'Authorization' => 'Basic ' . base64_encode(
- $this->config->getClientId() . ':' . $this->config->getClientSecret()
- )
- );
- $_tokenBody = Request\Body::Form([
- 'grant_type' => 'client_credentials'
- ]);
- $response = Request::post($_tokenUrl, $_tokenHeaders, $_tokenBody);
- $this->config->setAccessToken($response->body->access_token);
- $this->config->setAccessTokenExpiration(time() + $response->body->expires_in);
- $headers['Authorization'] = 'Bearer ' . $this->config->getAccessToken();
-
- return;
- }
-
$username = '';
$password = '';
-
+
switch ($authType) {
case 'messaging':
$username = $this->config->getMessagingBasicAuthUserName();
@@ -135,7 +107,44 @@ protected function configureAuth(&$headers, $authType)
$password = $this->config->getMultiFactorAuthBasicAuthPassword();
break;
}
-
+
Request::auth($username, $password);
}
+
+ /**
+ * Configure OAuth2 Bearer auth for BRTC endpoints using client credentials.
+ * Sets the Authorization header directly to avoid conflicts with Unirest's
+ * global Request::auth() state.
+ *
+ * @param array $headers The headers for the request (passed by reference)
+ */
+ protected function configureOAuth2Auth(&$headers)
+ {
+ // Clear any global state set by prior configureAuth() calls so
+ // Unirest doesn't interfere with the token request.
+ Request::auth('', '');
+ Request::clearDefaultHeaders();
+ $response = Request::post(
+ 'https://api.bandwidth.com/api/v1/oauth2/token',
+ [
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ 'Authorization' => 'Basic ' . base64_encode(
+ $this->config->getClientId() . ':' . $this->config->getClientSecret()
+ ),
+ ],
+ \Unirest\Request\Body::Form(['grant_type' => 'client_credentials'])
+ );
+
+ if ($response->code < 200 || $response->code > 299 || !isset($response->body->access_token)) {
+ throw new \RuntimeException(
+ "OAuth2 token request failed | status: {$response->code} | response: {$response->raw_body}"
+ );
+ }
+
+ $this->config->setAccessToken($response->body->access_token);
+ $this->config->setAccessTokenExpiration(time() + $response->body->expires_in);
+
+ $headers['Authorization'] = 'Bearer ' . $response->body->access_token;
+ }
+
}
diff --git a/src/Voice/Bxml/Connect.php b/src/Voice/Bxml/Connect.php
new file mode 100644
index 0000000..1cd34e9
--- /dev/null
+++ b/src/Voice/Bxml/Connect.php
@@ -0,0 +1,54 @@
+endpoints = $endpoints;
+ }
+
+ /**
+ * Add an Endpoint to the Connect verb
+ *
+ * @param Endpoint $endpoint
+ * @return $this
+ */
+ public function addEndpoint(Endpoint $endpoint): Connect {
+ $this->endpoints[] = $endpoint;
+ return $this;
+ }
+
+ /**
+ * Converts the Connect verb into a DOMElement
+ *
+ * @param DOMDocument $doc
+ * @return DOMElement
+ */
+ public function toBxml(DOMDocument $doc): DOMElement {
+ $element = $doc->createElement("Connect");
+ foreach ($this->endpoints as $endpoint) {
+ $element->appendChild($endpoint->toBxml($doc));
+ }
+ return $element;
+ }
+}
diff --git a/src/Voice/Bxml/Endpoint.php b/src/Voice/Bxml/Endpoint.php
new file mode 100644
index 0000000..445dff2
--- /dev/null
+++ b/src/Voice/Bxml/Endpoint.php
@@ -0,0 +1,41 @@
+id = $id;
+ }
+
+ /**
+ * Converts the Endpoint verb into a DOMElement
+ *
+ * @param DOMDocument $doc
+ * @return DOMElement
+ */
+ public function toBxml(DOMDocument $doc): DOMElement {
+ $element = $doc->createElement("Endpoint");
+ $element->setAttribute("id", $this->id);
+ return $element;
+ }
+}
diff --git a/src/Voice/Controllers/APIController.php b/src/Voice/Controllers/APIController.php
index 82a7461..a96a837 100644
--- a/src/Voice/Controllers/APIController.php
+++ b/src/Voice/Controllers/APIController.php
@@ -929,7 +929,7 @@ public function getDownloadCallRecording(
) {
//prepare query string for API call
- $_queryBuilder =
+ $_queryBuilder =
'/api/v2/accounts/{accountId}/calls/{callId}/recordings/{recordingId}/media';
//process optional query parameters
@@ -1040,7 +1040,7 @@ public function deleteRecordingMedia(
) {
//prepare query string for API call
- $_queryBuilder =
+ $_queryBuilder =
'/api/v2/accounts/{accountId}/calls/{callId}/recordings/{recordingId}/media';
//process optional query parameters
@@ -1149,7 +1149,7 @@ public function getCallTranscription(
) {
//prepare query string for API call
- $_queryBuilder =
+ $_queryBuilder =
'/api/v2/accounts/{accountId}/calls/{callId}/recordings/{recordingId}/transcription';
//process optional query parameters
@@ -1266,7 +1266,7 @@ public function createTranscribeCallRecording(
) {
//prepare query string for API call
- $_queryBuilder =
+ $_queryBuilder =
'/api/v2/accounts/{accountId}/calls/{callId}/recordings/{recordingId}/transcription';
//process optional query parameters
@@ -1386,7 +1386,7 @@ public function deleteCallTranscription(
) {
//prepare query string for API call
- $_queryBuilder =
+ $_queryBuilder =
'/api/v2/accounts/{accountId}/calls/{callId}/recordings/{recordingId}/transcription';
//process optional query parameters
@@ -1952,7 +1952,7 @@ public function getConferenceMember(
) {
//prepare query string for API call
- $_queryBuilder =
+ $_queryBuilder =
'/api/v2/accounts/{accountId}/conferences/{conferenceId}/members/{memberId}';
//process optional query parameters
@@ -2179,7 +2179,7 @@ public function getConferenceRecording(
) {
//prepare query string for API call
- $_queryBuilder =
+ $_queryBuilder =
'/api/v2/accounts/{accountId}/conferences/{conferenceId}/recordings/{recordingId}';
//process optional query parameters
@@ -2294,7 +2294,7 @@ public function getDownloadConferenceRecording(
) {
//prepare query string for API call
- $_queryBuilder =
+ $_queryBuilder =
'/api/v2/accounts/{accountId}/conferences/{conferenceId}/recordings/{recordingId}/media';
//process optional query parameters
@@ -2514,4 +2514,205 @@ public function getQueryCallRecordings(
);
return new ApiResponse($response->code, $response->headers, $deserializedResponse);
}
+
+ /**
+ * Creates a BRTC endpoint.
+ *
+ * @param string $accountId
+ * @param Models\CreateEndpointRequest $body
+ * @return ApiResponse response from the API call
+ * @throws APIException Thrown if API call fails
+ */
+ public function createEndpoint(
+ string $accountId,
+ Models\CreateEndpointRequest $body
+ ) {
+ $_queryBuilder = '/accounts/{accountId}/endpoints';
+ $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array(
+ 'accountId' => $accountId,
+ ));
+ $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::PHONENUMBERLOOKUPDEFAULT) . $_queryBuilder);
+ $_headers = array(
+ 'user-agent' => BaseController::USER_AGENT,
+ 'Accept' => 'application/json',
+ 'content-type' => 'application/json; charset=utf-8'
+ );
+ $_bodyJson = Request\Body::Json($body);
+ $this->configureOAuth2Auth($_headers);
+ $_httpRequest = new HttpRequest(HttpMethod::POST, $_headers, $_queryUrl);
+ if ($this->getHttpCallBack() != null) {
+ $this->getHttpCallBack()->callOnBeforeRequest($_httpRequest);
+ }
+ Request::timeout($this->config->getTimeout());
+ $response = Request::post($_queryUrl, $_headers, $_bodyJson);
+ $_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body);
+ $_httpContext = new HttpContext($_httpRequest, $_httpResponse);
+ if ($this->getHttpCallBack() != null) {
+ $this->getHttpCallBack()->callOnAfterRequest($_httpContext);
+ }
+ $this->validateResponse($_httpResponse, $_httpContext);
+ $mapper = $this->getJsonMapper();
+ $deserializedResponse = $mapper->mapClass($response->body->data, 'BandwidthLib\\Voice\\Models\\CreateEndpointResponse');
+ return new ApiResponse($response->code, $response->headers, $deserializedResponse);
+ }
+
+ /**
+ * Lists BRTC endpoints for an account.
+ *
+ * @param string $accountId
+ * @param array $queryParams Optional filter/pagination params
+ * @return ApiResponse response from the API call
+ * @throws APIException Thrown if API call fails
+ */
+ public function listEndpoints(
+ string $accountId,
+ array $queryParams = []
+ ) {
+ $_queryBuilder = '/accounts/{accountId}/endpoints';
+ $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array(
+ 'accountId' => $accountId,
+ ));
+ if (!empty($queryParams)) {
+ $_queryBuilder = APIHelper::appendUrlWithQueryParameters($_queryBuilder, $queryParams);
+ }
+ $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::PHONENUMBERLOOKUPDEFAULT) . $_queryBuilder);
+ $_headers = array(
+ 'user-agent' => BaseController::USER_AGENT,
+ 'Accept' => 'application/json'
+ );
+ $this->configureOAuth2Auth($_headers);
+ $_httpRequest = new HttpRequest(HttpMethod::GET, $_headers, $_queryUrl);
+ if ($this->getHttpCallBack() != null) {
+ $this->getHttpCallBack()->callOnBeforeRequest($_httpRequest);
+ }
+ Request::timeout($this->config->getTimeout());
+ $response = Request::get($_queryUrl, $_headers);
+ $_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body);
+ $_httpContext = new HttpContext($_httpRequest, $_httpResponse);
+ if ($this->getHttpCallBack() != null) {
+ $this->getHttpCallBack()->callOnAfterRequest($_httpContext);
+ }
+ $this->validateResponse($_httpResponse, $_httpContext);
+ $mapper = $this->getJsonMapper();
+ $deserializedResponse = $mapper->mapClassArray($response->body->data, 'BandwidthLib\\Voice\\Models\\Endpoint');
+ return new ApiResponse($response->code, $response->headers, $deserializedResponse);
+ }
+
+ /**
+ * Gets details for a specific BRTC endpoint.
+ *
+ * @param string $accountId
+ * @param string $endpointId
+ * @return ApiResponse response from the API call
+ * @throws APIException Thrown if API call fails
+ */
+ public function getEndpoint(
+ string $accountId,
+ string $endpointId
+ ) {
+ $_queryBuilder = '/accounts/{accountId}/endpoints/{endpointId}';
+ $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array(
+ 'accountId' => $accountId,
+ 'endpointId' => $endpointId,
+ ));
+ $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::PHONENUMBERLOOKUPDEFAULT) . $_queryBuilder);
+ $_headers = array(
+ 'user-agent' => BaseController::USER_AGENT,
+ 'Accept' => 'application/json'
+ );
+ $this->configureOAuth2Auth($_headers);
+ $_httpRequest = new HttpRequest(HttpMethod::GET, $_headers, $_queryUrl);
+ if ($this->getHttpCallBack() != null) {
+ $this->getHttpCallBack()->callOnBeforeRequest($_httpRequest);
+ }
+ Request::timeout($this->config->getTimeout());
+ $response = Request::get($_queryUrl, $_headers);
+ $_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body);
+ $_httpContext = new HttpContext($_httpRequest, $_httpResponse);
+ if ($this->getHttpCallBack() != null) {
+ $this->getHttpCallBack()->callOnAfterRequest($_httpContext);
+ }
+ $this->validateResponse($_httpResponse, $_httpContext);
+ $mapper = $this->getJsonMapper();
+ $deserializedResponse = $mapper->mapClass($response->body->data, 'BandwidthLib\\Voice\\Models\\Endpoint');
+ return new ApiResponse($response->code, $response->headers, $deserializedResponse);
+ }
+
+ /**
+ * Deletes a BRTC endpoint.
+ *
+ * @param string $accountId
+ * @param string $endpointId
+ * @return ApiResponse response from the API call
+ * @throws APIException Thrown if API call fails
+ */
+ public function deleteEndpoint(
+ string $accountId,
+ string $endpointId
+ ) {
+ $_queryBuilder = '/accounts/{accountId}/endpoints/{endpointId}';
+ $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array(
+ 'accountId' => $accountId,
+ 'endpointId' => $endpointId,
+ ));
+ $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::PHONENUMBERLOOKUPDEFAULT) . $_queryBuilder);
+ $_headers = array(
+ 'user-agent' => BaseController::USER_AGENT,
+ 'Content-Type' => '', // prevent curl from injecting Content-Type: application/x-www-form-urlencoded on empty DELETE body
+ );
+ $this->configureOAuth2Auth($_headers);
+ $_httpRequest = new HttpRequest(HttpMethod::DELETE, $_headers, $_queryUrl);
+ if ($this->getHttpCallBack() != null) {
+ $this->getHttpCallBack()->callOnBeforeRequest($_httpRequest);
+ }
+ Request::timeout($this->config->getTimeout());
+ $response = Request::delete($_queryUrl, $_headers);
+ $_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body);
+ $_httpContext = new HttpContext($_httpRequest, $_httpResponse);
+ if ($this->getHttpCallBack() != null) {
+ $this->getHttpCallBack()->callOnAfterRequest($_httpContext);
+ }
+ $this->validateResponse($_httpResponse, $_httpContext);
+ return new ApiResponse($response->code, $response->headers, null);
+ }
+
+ /**
+ * Updates the BXML for a BRTC endpoint.
+ *
+ * @param string $accountId
+ * @param string $endpointId
+ * @param string $body Valid BXML string
+ * @return ApiResponse response from the API call
+ * @throws APIException Thrown if API call fails
+ */
+ public function updateEndpointBxml(
+ string $accountId,
+ string $endpointId,
+ string $body
+ ) {
+ $_queryBuilder = '/accounts/{accountId}/endpoints/{endpointId}/bxml';
+ $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array(
+ 'accountId' => $accountId,
+ 'endpointId' => $endpointId,
+ ));
+ $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::PHONENUMBERLOOKUPDEFAULT) . $_queryBuilder);
+ $_headers = array(
+ 'user-agent' => BaseController::USER_AGENT,
+ 'content-type' => 'application/xml; charset=utf-8'
+ );
+ $this->configureOAuth2Auth($_headers);
+ $_httpRequest = new HttpRequest(HttpMethod::PUT, $_headers, $_queryUrl);
+ if ($this->getHttpCallBack() != null) {
+ $this->getHttpCallBack()->callOnBeforeRequest($_httpRequest);
+ }
+ Request::timeout($this->config->getTimeout());
+ $response = Request::put($_queryUrl, $_headers, $body);
+ $_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body);
+ $_httpContext = new HttpContext($_httpRequest, $_httpResponse);
+ if ($this->getHttpCallBack() != null) {
+ $this->getHttpCallBack()->callOnAfterRequest($_httpContext);
+ }
+ $this->validateResponse($_httpResponse, $_httpContext);
+ return new ApiResponse($response->code, $response->headers, null);
+ }
}
diff --git a/src/Voice/Models/CreateEndpointRequest.php b/src/Voice/Models/CreateEndpointRequest.php
new file mode 100644
index 0000000..b38e19e
--- /dev/null
+++ b/src/Voice/Models/CreateEndpointRequest.php
@@ -0,0 +1,31 @@
+type = $type;
+ $this->direction = $direction;
+ $this->eventCallbackUrl = $eventCallbackUrl;
+ $this->eventFallbackUrl = $eventFallbackUrl;
+ $this->tag = $tag;
+ }
+}
diff --git a/src/Voice/Models/CreateEndpointResponse.php b/src/Voice/Models/CreateEndpointResponse.php
new file mode 100644
index 0000000..2aaa6e0
--- /dev/null
+++ b/src/Voice/Models/CreateEndpointResponse.php
@@ -0,0 +1,40 @@
+endpointId = $endpointId;
+ $this->type = $type;
+ $this->status = $status;
+ $this->creationTimestamp = $creationTimestamp;
+ $this->expirationTimestamp = $expirationTimestamp;
+ $this->tag = $tag;
+ $this->devices = $devices;
+ $this->token = $token;
+ }
+}
diff --git a/src/Voice/Models/Device.php b/src/Voice/Models/Device.php
new file mode 100644
index 0000000..5ae0687
--- /dev/null
+++ b/src/Voice/Models/Device.php
@@ -0,0 +1,31 @@
+id = $id;
+ $this->status = $status;
+ $this->type = $type;
+ $this->createdTime = $createdTime;
+ $this->updatedTime = $updatedTime;
+ }
+}
diff --git a/src/Voice/Models/Endpoint.php b/src/Voice/Models/Endpoint.php
new file mode 100644
index 0000000..2503bfb
--- /dev/null
+++ b/src/Voice/Models/Endpoint.php
@@ -0,0 +1,46 @@
+endpointId = $endpointId;
+ $this->type = $type;
+ $this->status = $status;
+ $this->direction = $direction;
+ $this->eventCallbackUrl = $eventCallbackUrl;
+ $this->eventFallbackUrl = $eventFallbackUrl;
+ $this->tag = $tag;
+ $this->devices = $devices;
+ $this->creationTimestamp = $creationTimestamp;
+ $this->expirationTimestamp = $expirationTimestamp;
+ }
+}
diff --git a/src/Voice/Models/EndpointEvent.php b/src/Voice/Models/EndpointEvent.php
new file mode 100644
index 0000000..2360f74
--- /dev/null
+++ b/src/Voice/Models/EndpointEvent.php
@@ -0,0 +1,34 @@
+eventType = $eventType;
+ $this->endpointId = $endpointId;
+ $this->timestamp = $timestamp;
+ $this->status = $status;
+ $this->reason = $reason;
+ $this->details = $details;
+ }
+}
diff --git a/src/Voice/Models/Enums.php b/src/Voice/Models/Enums.php
new file mode 100644
index 0000000..0060c21
--- /dev/null
+++ b/src/Voice/Models/Enums.php
@@ -0,0 +1,38 @@
+message = $message;
+ $this->code = $code;
+ $this->details = $details;
+ }
+}
diff --git a/src/Voice/Models/Page.php b/src/Voice/Models/Page.php
new file mode 100644
index 0000000..b400d2e
--- /dev/null
+++ b/src/Voice/Models/Page.php
@@ -0,0 +1,28 @@
+page = $page;
+ $this->size = $size;
+ $this->total = $total;
+ $this->totalPages = $totalPages;
+ }
+}
diff --git a/src/Voice/VoiceClient.php b/src/Voice/VoiceClient.php
index 089b842..c79a0bd 100644
--- a/src/Voice/VoiceClient.php
+++ b/src/Voice/VoiceClient.php
@@ -34,4 +34,5 @@ public function getClient()
}
return $this->client;
}
+
}
diff --git a/tests/ApiTest.php b/tests/ApiTest.php
index 44c9bc9..e29e643 100644
--- a/tests/ApiTest.php
+++ b/tests/ApiTest.php
@@ -14,7 +14,6 @@ final class ApiTest extends TestCase
{
protected static $bandwidthClient;
protected static $messagingMFAClient;
-
public static function setUpBeforeClass(): void {
$config = new BandwidthLib\Configuration(
array(
@@ -72,7 +71,7 @@ public function testUploadDownloadMedia() {
$mediaId = "text-media-id-" . uniqid() . ".txt";
$content = "Hello world";
$contentType = 'text/plain';
-
+
//media upload
self::$messagingMFAClient->getMessaging()->getClient()->uploadMedia(getenv("BW_ACCOUNT_ID"), $mediaId, $content, $contentType);
@@ -130,7 +129,7 @@ public function testCreateCallWithAmdAndGetCallState() {
//get phone call information
// $response = self::$bandwidthClient->getVoice()->getClient()->getCall(getenv("BW_ACCOUNT_ID"), $callId);
- // if (($response->getStatus() == 404) ) {
+ // if (($response->getStatus() == 404) ) {
// $this->assertTrue(is_a($response->getResult()->enqueuedTime, 'DateTime'));
// }
}
@@ -230,7 +229,7 @@ public function testAsyncTnLookup() {
$this->assertIsString($statusResponse->getResult()->data->results[0]->countryCodeA3);
$this->assertIsArray($statusResponse->getResult()->errors);
}
-
+
public function testSyncTnLookup() {
$body = new BandwidthLib\PhoneNumberLookup\Models\CreateLookupRequest();
$body->phoneNumbers = [getenv("USER_NUMBER")];
@@ -251,4 +250,269 @@ public function testSyncTnLookup() {
$this->assertIsString($response->getResult()->data->results[0]->countryCodeA3);
$this->assertIsArray($response->getResult()->errors);
}
+
+ public function testCreateListGetDeleteEndpoint() {
+ $accountId = getenv("BW_ACCOUNT_ID");
+ $voiceClient = self::$bandwidthClient->getVoice()->getClient();
+
+ // Create endpoint
+ $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest(
+ 'WEBRTC',
+ 'INBOUND',
+ getenv("BASE_CALLBACK_URL") . "/brtc/events",
+ null,
+ 'php-sdk-test'
+ );
+ try {
+ $createResp = $voiceClient->createEndpoint($accountId, $createReq)->getResult();
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+ $this->assertNotNull($createResp->endpointId);
+ $this->assertEquals('WEBRTC', $createResp->type);
+
+ // List endpoints
+ try {
+ $endpoints = $voiceClient->listEndpoints($accountId)->getResult();
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+ $this->assertIsArray($endpoints);
+ $ids = array_map(fn($ep) => $ep->endpointId, $endpoints);
+ $this->assertContains($createResp->endpointId, $ids, 'Created endpoint should be in list');
+
+ // Get endpoint
+ try {
+ $endpoint = $voiceClient->getEndpoint($accountId, $createResp->endpointId)->getResult();
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+ $this->assertEquals($createResp->endpointId, $endpoint->endpointId);
+ $this->assertEquals('WEBRTC', $endpoint->type);
+
+ // Update endpoint BXML
+ // TODO: This endpoint currently is not implemented, commenting out until it is
+ // $bxml = 'Test BRTC';
+ // $voiceClient->updateEndpointBxml($accountId, $createResp->endpointId, $bxml);
+
+ // Delete endpoint
+ try {
+ $deleteResp = $voiceClient->deleteEndpoint($accountId, $createResp->endpointId);
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+ $this->assertEquals(204, $deleteResp->getStatusCode());
+ }
+
+ public function testCreateEndpointResponseFields() {
+ $accountId = getenv("BW_ACCOUNT_ID");
+ $voiceClient = self::$bandwidthClient->getVoice()->getClient();
+
+ $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest(
+ 'WEBRTC',
+ 'INBOUND',
+ getenv("BASE_CALLBACK_URL") . "/brtc/events",
+ getenv("BASE_CALLBACK_URL") . "/brtc/fallback",
+ 'php-sdk-fields-test'
+ );
+ try {
+ $createResp = $voiceClient->createEndpoint($accountId, $createReq)->getResult();
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+
+ $this->assertNotNull($createResp->endpointId);
+ $this->assertIsString($createResp->endpointId);
+ $this->assertEquals('WEBRTC', $createResp->type);
+ $this->assertNotNull($createResp->status);
+ $this->assertNotNull($createResp->creationTimestamp);
+ $this->assertNotNull($createResp->expirationTimestamp);
+ $this->assertEquals('php-sdk-fields-test', $createResp->tag);
+
+ // Cleanup
+ try {
+ $voiceClient->deleteEndpoint($accountId, $createResp->endpointId);
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+ }
+
+ public function testGetEndpointFields() {
+ $accountId = getenv("BW_ACCOUNT_ID");
+ $voiceClient = self::$bandwidthClient->getVoice()->getClient();
+
+ $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest(
+ 'WEBRTC',
+ 'INBOUND',
+ getenv("BASE_CALLBACK_URL") . "/brtc/events",
+ null,
+ 'php-sdk-get-test'
+ );
+ try {
+ $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId;
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+
+ try {
+ $endpoint = $voiceClient->getEndpoint($accountId, $endpointId)->getResult();
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+ $this->assertInstanceOf(BandwidthLib\Voice\Models\Endpoint::class, $endpoint);
+ $this->assertEquals($endpointId, $endpoint->endpointId);
+ $this->assertEquals('WEBRTC', $endpoint->type);
+ $this->assertNotNull($endpoint->status);
+ $this->assertNotNull($endpoint->direction);
+ $this->assertNotNull($endpoint->creationTimestamp);
+ $this->assertNotNull($endpoint->expirationTimestamp);
+ $this->assertEquals('php-sdk-get-test', $endpoint->tag);
+
+ // Cleanup
+ try {
+ $voiceClient->deleteEndpoint($accountId, $endpointId);
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+ }
+
+ public function testListEndpointsContainsCreated() {
+ $accountId = getenv("BW_ACCOUNT_ID");
+ $voiceClient = self::$bandwidthClient->getVoice()->getClient();
+
+ $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest(
+ 'WEBRTC',
+ 'INBOUND',
+ getenv("BASE_CALLBACK_URL") . "/brtc/events",
+ null,
+ 'php-sdk-list-test'
+ );
+ try {
+ $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId;
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+
+ try {
+ $endpoints = $voiceClient->listEndpoints($accountId)->getResult();
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+ $this->assertIsArray($endpoints);
+ $this->assertNotEmpty($endpoints);
+
+ $ids = array_map(fn($ep) => $ep->endpointId, $endpoints);
+ $this->assertContains($endpointId, $ids, 'Newly created endpoint should appear in list');
+
+ // Cleanup
+ try {
+ $voiceClient->deleteEndpoint($accountId, $endpointId);
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+ }
+
+ public function testListEndpointsEachItemIsEndpointInstance() {
+ $accountId = getenv("BW_ACCOUNT_ID");
+ $voiceClient = self::$bandwidthClient->getVoice()->getClient();
+
+ $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest(
+ 'WEBRTC',
+ 'INBOUND',
+ getenv("BASE_CALLBACK_URL") . "/brtc/events"
+ );
+ try {
+ $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId;
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+
+ try {
+ $endpoints = $voiceClient->listEndpoints($accountId)->getResult();
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+ foreach ($endpoints as $ep) {
+ $this->assertInstanceOf(BandwidthLib\Voice\Models\Endpoint::class, $ep);
+ $this->assertNotNull($ep->endpointId);
+ $this->assertNotNull($ep->type);
+ $this->assertNotNull($ep->status);
+ }
+
+ // Cleanup
+ try {
+ $voiceClient->deleteEndpoint($accountId, $endpointId);
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+ }
+
+ public function testCreateMultipleEndpointsAndDeleteAll() {
+ $accountId = getenv("BW_ACCOUNT_ID");
+ $voiceClient = self::$bandwidthClient->getVoice()->getClient();
+
+ $createdIds = [];
+ for ($i = 0; $i < 3; $i++) {
+ $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest(
+ 'WEBRTC',
+ 'INBOUND',
+ getenv("BASE_CALLBACK_URL") . "/brtc/events",
+ null,
+ "php-sdk-multi-{$i}"
+ );
+ try {
+ $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId;
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+ $this->assertNotNull($endpointId);
+ $createdIds[] = $endpointId;
+ }
+
+ $this->assertCount(3, $createdIds);
+
+ // Delete all created endpoints
+ foreach ($createdIds as $id) {
+ try {
+ $deleteResp = $voiceClient->deleteEndpoint($accountId, $id);
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+ $this->assertEquals(204, $deleteResp->getStatusCode());
+ }
+ }
+
+ public function testDeleteEndpointRemovedFromList() {
+ $accountId = getenv("BW_ACCOUNT_ID");
+ $voiceClient = self::$bandwidthClient->getVoice()->getClient();
+
+ $createReq = new BandwidthLib\Voice\Models\CreateEndpointRequest(
+ 'WEBRTC',
+ 'INBOUND',
+ getenv("BASE_CALLBACK_URL") . "/brtc/events",
+ null,
+ 'php-sdk-delete-check'
+ );
+ try {
+ $endpointId = $voiceClient->createEndpoint($accountId, $createReq)->getResult()->endpointId;
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+
+ // Delete it
+ try {
+ $voiceClient->deleteEndpoint($accountId, $endpointId);
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+
+ // Should no longer appear in list
+ try {
+ $endpoints = $voiceClient->listEndpoints($accountId)->getResult();
+ } catch (BandwidthLib\APIException $e) {
+ $this->fail("[{$e->getCode()}] {$e->getMessage()}");
+ }
+ $ids = array_map(fn($ep) => $ep->endpointId, $endpoints);
+ $this->assertNotContains($endpointId, $ids, 'Deleted endpoint should not appear in list');
+ }
}
diff --git a/tests/BxmlTest.php b/tests/BxmlTest.php
index 44bdb2f..e9f1b85 100644
--- a/tests/BxmlTest.php
+++ b/tests/BxmlTest.php
@@ -540,4 +540,18 @@ public function testStopTranscription() {
$responseXml = $response->toBxml();
$this->assertEquals($expectedXml, $responseXml);
}
+
+ public function testConnectAndEndpoint() {
+ $endpoint1 = new BandwidthLib\Voice\Bxml\Endpoint("endpoint-123");
+ $endpoint2 = new BandwidthLib\Voice\Bxml\Endpoint("endpoint-456");
+ $connect = new BandwidthLib\Voice\Bxml\Connect([
+ $endpoint1,
+ $endpoint2
+ ]);
+ $response = new BandwidthLib\Voice\Bxml\Response();
+ $response->addVerb($connect);
+ $expectedXml = '';
+ $responseXml = $response->toBxml();
+ $this->assertEquals($expectedXml, $responseXml);
+ }
}