Skip to content
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
bf81c1a
Implement BRTC Endpoints API with models, controllers, and tests
smoghe-bw Mar 11, 2026
e4e4382
Refactor Connect and Endpoint classes to make toBxml method public; a…
smoghe-bw Mar 11, 2026
69f3305
Refactor APIController to integrate BRTC endpoint methods; remove End…
smoghe-bw Mar 11, 2026
3c425ff
Update APIController to use PHONENUMBERLOOKUPDEFAULT server for endpo…
smoghe-bw Mar 11, 2026
e77d05d
Add error handling for createEndpoint in ApiTest to improve test reli…
smoghe-bw Mar 11, 2026
8bcf942
Refactor ApiTest to use endpointClient for endpoint-related tests
smoghe-bw Mar 11, 2026
c5a405a
Trigger build
smoghe-bw Mar 12, 2026
8ceb7c7
Add BRTC OAuth2 token management to Configuration and BaseController;…
smoghe-bw Mar 16, 2026
b404ff9
Remove BRTC OAuth2 access token management from Configuration and Bas…
smoghe-bw Mar 16, 2026
2424fa7
Refactor ApiTest to simplify error handling by removing apiFailMsg me…
smoghe-bw Mar 16, 2026
f625ed8
Trigger build
smoghe-bw Mar 17, 2026
d477bd1
Fix authorization header configuration in BaseController for token ma…
smoghe-bw Mar 17, 2026
13f7408
Refactor authentication methods in BaseController and APIController t…
smoghe-bw Mar 17, 2026
c516d6d
Clear global basic auth before BRTC token exchange and handle token r…
smoghe-bw Mar 17, 2026
ef6edf9
Simplify token request body format in BaseController for BRTC authent…
smoghe-bw Mar 17, 2026
3d8f45a
Refactor BRTC token exchange to use cURL for improved error handling …
smoghe-bw Mar 17, 2026
741accb
Refactor configureBrtcAuth to streamline access token handling and im…
smoghe-bw Mar 17, 2026
8eb21da
Refactor configureBrtcAuth to remove access token expiration check an…
smoghe-bw Mar 17, 2026
d4b7b47
Clear global Basic auth before setting Bearer token in configureBrtcA…
smoghe-bw Mar 17, 2026
d7d9913
Rename configureBrtcAuth to configureOAuth2Auth for clarity and updat…
smoghe-bw Mar 18, 2026
dcd3fee
Refactor grant_type parameter in configureOAuth2Auth to use associati…
smoghe-bw Mar 18, 2026
486744b
Remove User-Agent header from OAuth2 token request for cleaner API calls
smoghe-bw Mar 18, 2026
f21f31a
Refactor grant_type parameter in configureOAuth2Auth to use Request\B…
smoghe-bw Mar 18, 2026
c7f870b
Update grant_type parameter in configureOAuth2Auth to use query strin…
smoghe-bw Mar 18, 2026
c7433fb
Clear global Basic auth in configureOAuth2Auth to prevent header over…
smoghe-bw Mar 18, 2026
ad2c358
Refactor endpoint properties and update API response handling for con…
smoghe-bw Mar 18, 2026
107f54f
Update authentication handling to use Request\Body::form for grant_ty…
smoghe-bw Mar 18, 2026
65ddbd7
Address PR review feedback: restructure BRTC into own module
Mar 27, 2026
8e3edc2
Remove try/catch blocks from BRTC tests and consolidate into single test
Mar 27, 2026
9682727
Add BRTC model unit tests and strengthen API/BXML test assertions
Mar 30, 2026
eb39a00
Fix models to match BRTC API spec: query params, Link, and ErrorObject
Apr 1, 2026
5de91da
Fix integration test: don't assert links array is non-empty
Apr 1, 2026
fd6acde
Remove BRTC model unit tests
Apr 3, 2026
09ebf21
Fix array_filter bug in BRTC models and improve test reliability
Apr 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ composer.lock
.phpunit.result.cache
composer.phar
.idea
.env*
69 changes: 39 additions & 30 deletions src/Controllers/BaseController.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,37 +82,9 @@ protected function validateResponse(HttpResponse $response, HttpContext $_httpCo
*/
protected function configureAuth(&$headers, $authType)
{
if (!empty($this->config->getAccessToken()) &&
Comment thread
smoghe-bw marked this conversation as resolved.
(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();
Expand All @@ -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)
Comment thread
smoghe-bw marked this conversation as resolved.
Outdated
{
// 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;
}

}
54 changes: 54 additions & 0 deletions src/Voice/Bxml/Connect.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
/**
* Connect.php
*
* Implementation of the BXML Connect verb for BRTC endpoints
*
* @copyright Bandwidth INC
*/

namespace BandwidthLib\Voice\Bxml;

use DOMDocument;
use DOMElement;

require_once "Verb.php";

class Connect extends Verb {
Comment thread
smoghe-bw marked this conversation as resolved.
/**
* @var array
Comment thread
smoghe-bw marked this conversation as resolved.
Outdated
*/
private $endpoints;

/**
* @param array $endpoints Array of Endpoint objects
*/
public function __construct(array $endpoints = []) {
$this->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) {
Comment thread
smoghe-bw marked this conversation as resolved.
Outdated
$element->appendChild($endpoint->toBxml($doc));
}
return $element;
}
}
41 changes: 41 additions & 0 deletions src/Voice/Bxml/Endpoint.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
/**
* Endpoint.php
*
* Implementation of the BXML Endpoint verb for BRTC endpoints
*
* @copyright Bandwidth INC
*/

namespace BandwidthLib\Voice\Bxml;

use DOMDocument;
use DOMElement;

require_once "Verb.php";

class Endpoint extends Verb {
/**
* @var string
*/
private $id;
Comment thread
smoghe-bw marked this conversation as resolved.
Outdated

/**
* @param string $id The endpointId to connect to
*/
public function __construct(string $id) {
$this->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);
Comment thread
smoghe-bw marked this conversation as resolved.
Outdated
return $element;
}
}
Loading
Loading