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/BRTC/BRTCClient.php b/src/BRTC/BRTCClient.php new file mode 100644 index 0000000..f01af03 --- /dev/null +++ b/src/BRTC/BRTCClient.php @@ -0,0 +1,35 @@ +config = $config; + } + + private $client; + + /** + * Provides access to the BRTC API controller + * @return Controllers\APIController + */ + public function getClient() + { + if ($this->client == null) { + $this->client = new Controllers\APIController($this->config); + } + return $this->client; + } +} diff --git a/src/BRTC/Controllers/APIController.php b/src/BRTC/Controllers/APIController.php new file mode 100644 index 0000000..4b918d0 --- /dev/null +++ b/src/BRTC/Controllers/APIController.php @@ -0,0 +1,366 @@ + $accountId, + )); + + //validate and preprocess url + $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::BRTCDEFAULT) . $_queryBuilder); + + //prepare headers + $_headers = array( + 'user-agent' => BaseController::USER_AGENT, + 'Accept' => 'application/json', + 'content-type' => 'application/json; charset=utf-8' + ); + + //json encode body + $_bodyJson = Request\Body::Json($body); + + //set authentication via existing OAuth/configureAuth + $this->configureAuth($_headers, 'voice'); + + $_httpRequest = new HttpRequest(HttpMethod::POST, $_headers, $_queryUrl); + + //call on-before Http callback + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnBeforeRequest($_httpRequest); + } + + // Set request timeout + Request::timeout($this->config->getTimeout()); + + // and invoke the API call request to fetch the response + $response = Request::post($_queryUrl, $_headers, $_bodyJson); + + $_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body); + $_httpContext = new HttpContext($_httpRequest, $_httpResponse); + + //call on-after Http callback + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnAfterRequest($_httpContext); + } + + //handle errors defined at the API level + $this->validateResponse($_httpResponse, $_httpContext); + + $mapper = $this->getJsonMapper(); + $deserializedResponse = $mapper->mapClass( + $response->body, + 'BandwidthLib\\BRTC\\Models\\CreateEndpointResponse' + ); + return new ApiResponse($response->code, $response->headers, $deserializedResponse); + } + + /** + * Lists BRTC endpoints for an account. + * + * @param string $accountId + * @param string|null $type Filter by endpoint type + * @param string|null $status Filter by endpoint status + * @param string|null $direction Filter by endpoint direction + * @param string|null $pageToken Pagination token + * @param int|null $pageSize Number of results per page + * @return ApiResponse response from the API call + * @throws APIException Thrown if API call fails + */ + public function listEndpoints( + string $accountId, + ?string $type = null, + ?string $status = null, + ?string $direction = null, + ?string $pageToken = null, + ?int $pageSize = null + ) { + //prepare query string for API call + $_queryBuilder = '/accounts/{accountId}/endpoints'; + + //process optional query parameters + $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array( + 'accountId' => $accountId, + )); + + //process optional query parameters + APIHelper::appendUrlWithQueryParameters($_queryBuilder, array( + 'type' => $type, + 'status' => $status, + 'direction' => $direction, + 'pageToken' => $pageToken, + 'pageSize' => $pageSize, + )); + + //validate and preprocess url + $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::BRTCDEFAULT) . $_queryBuilder); + + //prepare headers + $_headers = array( + 'user-agent' => BaseController::USER_AGENT, + 'Accept' => 'application/json' + ); + + //set authentication via existing OAuth/configureAuth + $this->configureAuth($_headers, 'voice'); + + $_httpRequest = new HttpRequest(HttpMethod::GET, $_headers, $_queryUrl); + + //call on-before Http callback + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnBeforeRequest($_httpRequest); + } + + // Set request timeout + Request::timeout($this->config->getTimeout()); + + // and invoke the API call request to fetch the response + $response = Request::get($_queryUrl, $_headers); + + $_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body); + $_httpContext = new HttpContext($_httpRequest, $_httpResponse); + + //call on-after Http callback + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnAfterRequest($_httpContext); + } + + //handle errors defined at the API level + $this->validateResponse($_httpResponse, $_httpContext); + + $mapper = $this->getJsonMapper(); + $deserializedResponse = $mapper->mapClass( + $response->body, + 'BandwidthLib\\BRTC\\Models\\ListEndpointsResponse' + ); + 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 + ) { + //prepare query string for API call + $_queryBuilder = '/accounts/{accountId}/endpoints/{endpointId}'; + + //process optional query parameters + $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array( + 'accountId' => $accountId, + 'endpointId' => $endpointId, + )); + + //validate and preprocess url + $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::BRTCDEFAULT) . $_queryBuilder); + + //prepare headers + $_headers = array( + 'user-agent' => BaseController::USER_AGENT, + 'Accept' => 'application/json' + ); + + //set authentication via existing OAuth/configureAuth + $this->configureAuth($_headers, 'voice'); + + $_httpRequest = new HttpRequest(HttpMethod::GET, $_headers, $_queryUrl); + + //call on-before Http callback + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnBeforeRequest($_httpRequest); + } + + // Set request timeout + Request::timeout($this->config->getTimeout()); + + // and invoke the API call request to fetch the response + $response = Request::get($_queryUrl, $_headers); + + $_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body); + $_httpContext = new HttpContext($_httpRequest, $_httpResponse); + + //call on-after Http callback + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnAfterRequest($_httpContext); + } + + //handle errors defined at the API level + $this->validateResponse($_httpResponse, $_httpContext); + + $mapper = $this->getJsonMapper(); + $deserializedResponse = $mapper->mapClass( + $response->body, + 'BandwidthLib\\BRTC\\Models\\EndpointResponse' + ); + 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 + ) { + //prepare query string for API call + $_queryBuilder = '/accounts/{accountId}/endpoints/{endpointId}'; + + //process optional query parameters + $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array( + 'accountId' => $accountId, + 'endpointId' => $endpointId, + )); + + //validate and preprocess url + $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::BRTCDEFAULT) . $_queryBuilder); + + //prepare headers + $_headers = array( + 'user-agent' => BaseController::USER_AGENT, + 'Content-Type' => '', // prevent curl from injecting Content-Type: application/x-www-form-urlencoded on empty DELETE body + ); + + //set authentication via existing OAuth/configureAuth + $this->configureAuth($_headers, 'voice'); + + $_httpRequest = new HttpRequest(HttpMethod::DELETE, $_headers, $_queryUrl); + + //call on-before Http callback + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnBeforeRequest($_httpRequest); + } + + // Set request timeout + Request::timeout($this->config->getTimeout()); + + // and invoke the API call request to fetch the response + $response = Request::delete($_queryUrl, $_headers); + + $_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body); + $_httpContext = new HttpContext($_httpRequest, $_httpResponse); + + //call on-after Http callback + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnAfterRequest($_httpContext); + } + + //handle errors defined at the API level + $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 + ) { + //prepare query string for API call + $_queryBuilder = '/accounts/{accountId}/endpoints/{endpointId}/bxml'; + + //process optional query parameters + $_queryBuilder = APIHelper::appendUrlWithTemplateParameters($_queryBuilder, array( + 'accountId' => $accountId, + 'endpointId' => $endpointId, + )); + + //validate and preprocess url + $_queryUrl = APIHelper::cleanUrl($this->config->getBaseUri(Servers::BRTCDEFAULT) . $_queryBuilder); + + //prepare headers + $_headers = array( + 'user-agent' => BaseController::USER_AGENT, + 'content-type' => 'application/xml; charset=utf-8' + ); + + //set authentication via existing OAuth/configureAuth + $this->configureAuth($_headers, 'voice'); + + $_httpRequest = new HttpRequest(HttpMethod::PUT, $_headers, $_queryUrl); + + //call on-before Http callback + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnBeforeRequest($_httpRequest); + } + + // Set request timeout + Request::timeout($this->config->getTimeout()); + + // and invoke the API call request to fetch the response + $response = Request::put($_queryUrl, $_headers, $body); + + $_httpResponse = new HttpResponse($response->code, $response->headers, $response->raw_body); + $_httpContext = new HttpContext($_httpRequest, $_httpResponse); + + //call on-after Http callback + if ($this->getHttpCallBack() != null) { + $this->getHttpCallBack()->callOnAfterRequest($_httpContext); + } + + //handle errors defined at the API level + $this->validateResponse($_httpResponse, $_httpContext); + + return new ApiResponse($response->code, $response->headers, null); + } +} diff --git a/src/BRTC/Models/CreateEndpointRequest.php b/src/BRTC/Models/CreateEndpointRequest.php new file mode 100644 index 0000000..9690d59 --- /dev/null +++ b/src/BRTC/Models/CreateEndpointRequest.php @@ -0,0 +1,57 @@ +type = $type; + $this->direction = $direction; + $this->eventCallbackUrl = $eventCallbackUrl; + $this->eventFallbackUrl = $eventFallbackUrl; + $this->tag = $tag; + $this->connectionMetadata = $connectionMetadata; + } + + public function jsonSerialize(): array + { + $json = array(); + $json['type'] = $this->type; + $json['direction'] = $this->direction; + $json['eventCallbackUrl'] = $this->eventCallbackUrl; + $json['eventFallbackUrl'] = $this->eventFallbackUrl; + $json['tag'] = $this->tag; + $json['connectionMetadata'] = $this->connectionMetadata; + + return array_filter($json, function ($val) { + return $val !== null; + }); + } +} diff --git a/src/BRTC/Models/CreateEndpointResponse.php b/src/BRTC/Models/CreateEndpointResponse.php new file mode 100644 index 0000000..4464477 --- /dev/null +++ b/src/BRTC/Models/CreateEndpointResponse.php @@ -0,0 +1,40 @@ +links = func_get_arg(0); + $this->data = func_get_arg(1); + $this->errors = func_get_arg(2); + } + } + + public function jsonSerialize(): array + { + $json = array(); + $json['links'] = isset($this->links) ? array_values($this->links) : null; + $json['data'] = $this->data; + $json['errors'] = isset($this->errors) ? array_values($this->errors) : null; + + return array_filter($json); + } +} diff --git a/src/BRTC/Models/CreateEndpointResponseData.php b/src/BRTC/Models/CreateEndpointResponseData.php new file mode 100644 index 0000000..169f604 --- /dev/null +++ b/src/BRTC/Models/CreateEndpointResponseData.php @@ -0,0 +1,65 @@ +endpointId = $endpointId; + $this->type = $type; + $this->status = $status; + $this->creationTimestamp = $creationTimestamp; + $this->expirationTimestamp = $expirationTimestamp; + $this->tag = $tag; + $this->devices = $devices; + $this->token = $token; + } + + public function jsonSerialize(): array + { + $json = array(); + $json['endpointId'] = $this->endpointId; + $json['type'] = $this->type; + $json['status'] = $this->status; + $json['creationTimestamp'] = $this->creationTimestamp; + $json['expirationTimestamp'] = $this->expirationTimestamp; + $json['tag'] = $this->tag; + $json['devices'] = $this->devices; + $json['token'] = $this->token; + + return array_filter($json); + } +} diff --git a/src/BRTC/Models/Device.php b/src/BRTC/Models/Device.php new file mode 100644 index 0000000..724756b --- /dev/null +++ b/src/BRTC/Models/Device.php @@ -0,0 +1,41 @@ +deviceId = $deviceId; + $this->deviceName = $deviceName; + $this->status = $status; + $this->creationTimestamp = $creationTimestamp; + } + + public function jsonSerialize(): array + { + $json = array(); + $json['deviceId'] = $this->deviceId; + $json['deviceName'] = $this->deviceName; + $json['status'] = $this->status; + $json['creationTimestamp'] = $this->creationTimestamp; + + return array_filter($json); + } +} diff --git a/src/BRTC/Models/Endpoint.php b/src/BRTC/Models/Endpoint.php new file mode 100644 index 0000000..a12107a --- /dev/null +++ b/src/BRTC/Models/Endpoint.php @@ -0,0 +1,60 @@ +endpointId = $endpointId; + $this->type = $type; + $this->status = $status; + $this->creationTimestamp = $creationTimestamp; + $this->expirationTimestamp = $expirationTimestamp; + $this->tag = $tag; + $this->devices = $devices; + } + + public function jsonSerialize(): array + { + $json = array(); + $json['endpointId'] = $this->endpointId; + $json['type'] = $this->type; + $json['status'] = $this->status; + $json['creationTimestamp'] = $this->creationTimestamp; + $json['expirationTimestamp'] = $this->expirationTimestamp; + $json['tag'] = $this->tag; + $json['devices'] = $this->devices; + + return array_filter($json); + } +} diff --git a/src/BRTC/Models/EndpointEvent.php b/src/BRTC/Models/EndpointEvent.php new file mode 100644 index 0000000..27b27c7 --- /dev/null +++ b/src/BRTC/Models/EndpointEvent.php @@ -0,0 +1,71 @@ +endpointId = $endpointId; + $this->type = $type; + $this->status = $status; + $this->creationTimestamp = $creationTimestamp; + $this->expirationTimestamp = $expirationTimestamp; + $this->tag = $tag; + $this->eventTime = $eventTime; + $this->eventType = $eventType; + $this->device = $device; + } + + public function jsonSerialize(): array + { + $json = array(); + $json['endpointId'] = $this->endpointId; + $json['type'] = $this->type; + $json['status'] = $this->status; + $json['creationTimestamp'] = $this->creationTimestamp; + $json['expirationTimestamp'] = $this->expirationTimestamp; + $json['tag'] = $this->tag; + $json['eventTime'] = $this->eventTime; + $json['eventType'] = $this->eventType; + $json['device'] = $this->device; + + return array_filter($json); + } +} diff --git a/src/BRTC/Models/EndpointResponse.php b/src/BRTC/Models/EndpointResponse.php new file mode 100644 index 0000000..a30710f --- /dev/null +++ b/src/BRTC/Models/EndpointResponse.php @@ -0,0 +1,40 @@ +links = func_get_arg(0); + $this->data = func_get_arg(1); + $this->errors = func_get_arg(2); + } + } + + public function jsonSerialize(): array + { + $json = array(); + $json['links'] = isset($this->links) ? array_values($this->links) : null; + $json['data'] = $this->data; + $json['errors'] = isset($this->errors) ? array_values($this->errors) : null; + + return array_filter($json); + } +} diff --git a/src/BRTC/Models/Endpoints.php b/src/BRTC/Models/Endpoints.php new file mode 100644 index 0000000..3f1895b --- /dev/null +++ b/src/BRTC/Models/Endpoints.php @@ -0,0 +1,56 @@ +endpointId = $endpointId; + $this->type = $type; + $this->status = $status; + $this->creationTimestamp = $creationTimestamp; + $this->expirationTimestamp = $expirationTimestamp; + $this->tag = $tag; + } + + public function jsonSerialize(): array + { + $json = array(); + $json['endpointId'] = $this->endpointId; + $json['type'] = $this->type; + $json['status'] = $this->status; + $json['creationTimestamp'] = $this->creationTimestamp; + $json['expirationTimestamp'] = $this->expirationTimestamp; + $json['tag'] = $this->tag; + + return array_filter($json); + } +} diff --git a/src/BRTC/Models/ErrorObject.php b/src/BRTC/Models/ErrorObject.php new file mode 100644 index 0000000..cc090c0 --- /dev/null +++ b/src/BRTC/Models/ErrorObject.php @@ -0,0 +1,35 @@ +type = func_get_arg(0); + $this->description = func_get_arg(1); + } + } + + public function jsonSerialize(): array + { + $json = array(); + $json['type'] = $this->type; + $json['description'] = $this->description; + + return array_filter($json); + } +} diff --git a/src/BRTC/Models/Link.php b/src/BRTC/Models/Link.php new file mode 100644 index 0000000..8e9c545 --- /dev/null +++ b/src/BRTC/Models/Link.php @@ -0,0 +1,35 @@ +rel = func_get_arg(0); + $this->href = func_get_arg(1); + } + } + + public function jsonSerialize(): array + { + $json = array(); + $json['rel'] = $this->rel; + $json['href'] = $this->href; + + return array_filter($json); + } +} diff --git a/src/BRTC/Models/ListEndpointsResponse.php b/src/BRTC/Models/ListEndpointsResponse.php new file mode 100644 index 0000000..82f6fd0 --- /dev/null +++ b/src/BRTC/Models/ListEndpointsResponse.php @@ -0,0 +1,44 @@ +links = func_get_arg(0); + $this->page = func_get_arg(1); + $this->data = func_get_arg(2); + $this->errors = func_get_arg(3); + } + } + + public function jsonSerialize(): array + { + $json = array(); + $json['links'] = isset($this->links) ? array_values($this->links) : null; + $json['page'] = $this->page; + $json['data'] = isset($this->data) ? array_values($this->data) : null; + $json['errors'] = isset($this->errors) ? array_values($this->errors) : null; + + return array_filter($json); + } +} diff --git a/src/BRTC/Models/Page.php b/src/BRTC/Models/Page.php new file mode 100644 index 0000000..6e9cecd --- /dev/null +++ b/src/BRTC/Models/Page.php @@ -0,0 +1,43 @@ +pageSize = func_get_arg(0); + $this->totalElements = func_get_arg(1); + $this->totalPages = func_get_arg(2); + $this->pageNumber = func_get_arg(3); + } + } + + public function jsonSerialize(): array + { + $json = array(); + $json['pageSize'] = $this->pageSize; + $json['totalElements'] = $this->totalElements; + $json['totalPages'] = $this->totalPages; + $json['pageNumber'] = $this->pageNumber; + + return array_filter($json); + } +} diff --git a/src/BandwidthClient.php b/src/BandwidthClient.php index 8e80179..915ccbd 100644 --- a/src/BandwidthClient.php +++ b/src/BandwidthClient.php @@ -25,6 +25,7 @@ public function __construct($config) private $phoneNumberLookup; private $voice; private $webRtc; + private $brtc; /** * Provides access to Messaging client @@ -86,4 +87,16 @@ public function getWebRtc() return $this->webRtc; } + /** + * Provides access to BRTC client + * @return BRTC\BRTCClient + */ + public function getBRTC() + { + if ($this->brtc == null) { + $this->brtc = new BRTC\BRTCClient($this->config); + } + return $this->brtc; + } + } diff --git a/src/Configuration.php b/src/Configuration.php index 2f25f72..6467105 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -371,6 +371,7 @@ public function getBaseUri(string $server = Servers::DEFAULT_) Servers::PHONENUMBERLOOKUPDEFAULT => 'https://api.bandwidth.com/v2', Servers::VOICEDEFAULT => 'https://voice.bandwidth.com', Servers::WEBRTCDEFAULT => 'https://api.webrtc.bandwidth.com/v1', + Servers::BRTCDEFAULT => 'https://api.bandwidth.com/v2', ), Environments::CUSTOM => array( Servers::DEFAULT_ => '{base_url}', @@ -379,6 +380,7 @@ public function getBaseUri(string $server = Servers::DEFAULT_) Servers::PHONENUMBERLOOKUPDEFAULT => '{base_url}', Servers::VOICEDEFAULT => '{base_url}', Servers::WEBRTCDEFAULT => '{base_url}', + Servers::BRTCDEFAULT => '{base_url}', ), ); } diff --git a/src/Controllers/BaseController.php b/src/Controllers/BaseController.php index 7a41cc3..ee1dd98 100644 --- a/src/Controllers/BaseController.php +++ b/src/Controllers/BaseController.php @@ -82,14 +82,14 @@ protected function validateResponse(HttpResponse $response, HttpContext $_httpCo */ protected function configureAuth(&$headers, $authType) { - if (!empty($this->config->getAccessToken()) && - (empty($this->config->getAccessTokenExpiration()) || + 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 ( @@ -109,10 +109,10 @@ protected function configureAuth(&$headers, $authType) return; } - + $username = ''; $password = ''; - + switch ($authType) { case 'messaging': $username = $this->config->getMessagingBasicAuthUserName(); @@ -135,7 +135,7 @@ protected function configureAuth(&$headers, $authType) $password = $this->config->getMultiFactorAuthBasicAuthPassword(); break; } - + Request::auth($username, $password); } } diff --git a/src/Servers.php b/src/Servers.php index 4c22bea..ef5463e 100644 --- a/src/Servers.php +++ b/src/Servers.php @@ -41,4 +41,9 @@ class Servers * TODO: Write general description for this element */ const WEBRTCDEFAULT = "WebRtcDefault"; + + /** + * Base URL for BRTC endpoints + */ + const BRTCDEFAULT = "BRTCDefault"; } diff --git a/src/Voice/Bxml/Connect.php b/src/Voice/Bxml/Connect.php new file mode 100644 index 0000000..bceb7f4 --- /dev/null +++ b/src/Voice/Bxml/Connect.php @@ -0,0 +1,77 @@ +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; + } + + /** + * Sets the eventCallbackUrl attribute for Connect + * + * @param string $eventCallbackUrl The URL to send event callbacks to + * @return $this + */ + public function eventCallbackUrl(string $eventCallbackUrl): Connect { + $this->eventCallbackUrl = $eventCallbackUrl; + return $this; + } + + /** + * Converts the Connect verb into a DOMElement + * + * @param DOMDocument $doc + * @return DOMElement + */ + public function toBxml(DOMDocument $doc): DOMElement { + $element = $doc->createElement("Connect"); + + if(isset($this->eventCallbackUrl)) { + $element->setAttribute("eventCallbackUrl", $this->eventCallbackUrl); + } + + if(isset($this->endpoints)) { + 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..6a29845 --- /dev/null +++ b/src/Voice/Bxml/Endpoint.php @@ -0,0 +1,41 @@ +endpointId = $endpointId; + } + + /** + * Converts the Endpoint verb into a DOMElement + * + * @param DOMDocument $doc + * @return DOMElement + */ + public function toBxml(DOMDocument $doc): DOMElement { + $element = $doc->createElement("Endpoint"); + $element->appendChild($doc->createTextNode($this->endpointId)); + return $element; + } +} diff --git a/src/Voice/Controllers/APIController.php b/src/Voice/Controllers/APIController.php index 82a7461..659b831 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 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..8c04d6a 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,86 @@ public function testSyncTnLookup() { $this->assertIsString($response->getResult()->data->results[0]->countryCodeA3); $this->assertIsArray($response->getResult()->errors); } + + public function testCreateListGetDeleteEndpoint() { + $accountId = getenv("BW_ACCOUNT_ID"); + $brtcClient = self::$bandwidthClient->getBRTC()->getClient(); + + // Create endpoint + $createReq = new BandwidthLib\BRTC\Models\CreateEndpointRequest( + 'WEBRTC', + 'INBOUND', + getenv("BASE_CALLBACK_URL") . "/brtc/events", + null, + 'php-sdk-test' + ); + $createResp = $brtcClient->createEndpoint($accountId, $createReq)->getResult(); + $this->assertInstanceOf(BandwidthLib\BRTC\Models\CreateEndpointResponse::class, $createResp); + $this->assertIsArray($createResp->links); + $this->assertNotEmpty($createResp->links); + $this->assertInstanceOf(BandwidthLib\BRTC\Models\Link::class, $createResp->links[0]); + $this->assertIsString($createResp->links[0]->rel); + $this->assertIsString($createResp->links[0]->href); + $this->assertNotNull($createResp->data); + $this->assertInstanceOf(BandwidthLib\BRTC\Models\CreateEndpointResponseData::class, $createResp->data); + $this->assertNotNull($createResp->data->endpointId); + $this->assertIsString($createResp->data->endpointId); + $this->assertEquals('WEBRTC', $createResp->data->type); + $this->assertNotNull($createResp->data->status); + $this->assertNotNull($createResp->data->creationTimestamp); + $this->assertNotNull($createResp->data->expirationTimestamp); + $this->assertEquals('php-sdk-test', $createResp->data->tag); + $this->assertNotNull($createResp->data->token); + $this->assertIsString($createResp->data->token); + $this->assertIsArray($createResp->data->devices); + $this->assertIsArray($createResp->errors); + + $endpointId = $createResp->data->endpointId; + + // List endpoints + $listResp = $brtcClient->listEndpoints($accountId)->getResult(); + $this->assertInstanceOf(BandwidthLib\BRTC\Models\ListEndpointsResponse::class, $listResp); + $this->assertIsArray($listResp->links); + $this->assertNotEmpty($listResp->links); + $this->assertInstanceOf(BandwidthLib\BRTC\Models\Link::class, $listResp->links[0]); + $this->assertIsString($listResp->links[0]->rel); + $this->assertIsString($listResp->links[0]->href); + $this->assertNotNull($listResp->page); + $this->assertInstanceOf(BandwidthLib\BRTC\Models\Page::class, $listResp->page); + $this->assertIsArray($listResp->data); + $this->assertNotEmpty($listResp->data); + $this->assertIsArray($listResp->errors); + $this->assertInstanceOf(BandwidthLib\BRTC\Models\Endpoints::class, $listResp->data[0]); + $this->assertNotNull($listResp->data[0]->endpointId); + $this->assertNotNull($listResp->data[0]->type); + $this->assertNotNull($listResp->data[0]->status); + $this->assertNotNull($listResp->data[0]->creationTimestamp); + $this->assertNotNull($listResp->data[0]->expirationTimestamp); + $this->assertNotNull($listResp->data[0]->tag); + $ids = array_map(fn($ep) => $ep->endpointId, $listResp->data); + $this->assertContains($endpointId, $ids, 'Created endpoint should be in list'); + + // Get endpoint + $getResp = $brtcClient->getEndpoint($accountId, $endpointId)->getResult(); + $this->assertInstanceOf(BandwidthLib\BRTC\Models\EndpointResponse::class, $getResp); + $this->assertIsArray($getResp->links); + $this->assertNotEmpty($getResp->links); + $this->assertInstanceOf(BandwidthLib\BRTC\Models\Link::class, $getResp->links[0]); + $this->assertIsString($getResp->links[0]->rel); + $this->assertIsString($getResp->links[0]->href); + $this->assertNotNull($getResp->data); + $this->assertInstanceOf(BandwidthLib\BRTC\Models\Endpoint::class, $getResp->data); + $this->assertEquals($endpointId, $getResp->data->endpointId); + $this->assertEquals('WEBRTC', $getResp->data->type); + $this->assertNotNull($getResp->data->status); + $this->assertNotNull($getResp->data->creationTimestamp); + $this->assertNotNull($getResp->data->expirationTimestamp); + $this->assertEquals('php-sdk-test', $getResp->data->tag); + $this->assertIsArray($getResp->data->devices); + $this->assertIsArray($getResp->errors); + + // Delete endpoint + $deleteResp = $brtcClient->deleteEndpoint($accountId, $endpointId); + $this->assertEquals(204, $deleteResp->getStatusCode()); + } } diff --git a/tests/BrtcModelTest.php b/tests/BrtcModelTest.php new file mode 100644 index 0000000..a5bc438 --- /dev/null +++ b/tests/BrtcModelTest.php @@ -0,0 +1,120 @@ +assertEquals('ep-123', $event->endpointId); + $this->assertEquals('WEBRTC', $event->type); + $this->assertEquals('ACTIVE', $event->status); + $this->assertEquals('2025-01-01T00:00:00Z', $event->creationTimestamp); + $this->assertEquals('2025-01-02T00:00:00Z', $event->expirationTimestamp); + $this->assertEquals('test-tag', $event->tag); + $this->assertEquals('2025-01-01T01:00:00Z', $event->eventTime); + $this->assertEquals('DEVICE_CONNECTED', $event->eventType); + $this->assertInstanceOf(BandwidthLib\BRTC\Models\Device::class, $event->device); + $this->assertEquals('dev-123', $event->device->deviceId); + + $json = $event->jsonSerialize(); + $this->assertEquals('ep-123', $json['endpointId']); + $this->assertEquals('DEVICE_CONNECTED', $json['eventType']); + $this->assertEquals('2025-01-01T01:00:00Z', $json['eventTime']); + } + + public function testEndpointEventModelDefaults() { + $event = new BandwidthLib\BRTC\Models\EndpointEvent(); + $this->assertNull($event->endpointId); + $this->assertNull($event->type); + $this->assertNull($event->device); + $this->assertNull($event->eventType); + } + + public function testDeviceModel() { + $device = new BandwidthLib\BRTC\Models\Device('dev-456', 'My Phone', 'ACTIVE', '2025-01-01T00:00:00Z'); + + $this->assertEquals('dev-456', $device->deviceId); + $this->assertEquals('My Phone', $device->deviceName); + $this->assertEquals('ACTIVE', $device->status); + $this->assertEquals('2025-01-01T00:00:00Z', $device->creationTimestamp); + + $json = $device->jsonSerialize(); + $this->assertEquals('dev-456', $json['deviceId']); + $this->assertEquals('My Phone', $json['deviceName']); + $this->assertEquals('ACTIVE', $json['status']); + $this->assertEquals('2025-01-01T00:00:00Z', $json['creationTimestamp']); + } + + public function testDeviceModelDefaults() { + $device = new BandwidthLib\BRTC\Models\Device(); + $this->assertNull($device->deviceId); + $this->assertNull($device->deviceName); + $this->assertNull($device->status); + $this->assertNull($device->creationTimestamp); + } + + public function testPageModel() { + $page = new BandwidthLib\BRTC\Models\Page(20, 100, 5, 1); + + $this->assertEquals(20, $page->pageSize); + $this->assertEquals(100, $page->totalElements); + $this->assertEquals(5, $page->totalPages); + $this->assertEquals(1, $page->pageNumber); + + $json = $page->jsonSerialize(); + $this->assertEquals(20, $json['pageSize']); + $this->assertEquals(100, $json['totalElements']); + $this->assertEquals(5, $json['totalPages']); + $this->assertEquals(1, $json['pageNumber']); + } + + public function testPageModelDefaults() { + $page = new BandwidthLib\BRTC\Models\Page(); + $this->assertNull($page->pageSize); + $this->assertNull($page->totalElements); + } + + public function testLinkModel() { + $link = new BandwidthLib\BRTC\Models\Link('self', 'https://api.bandwidth.com/endpoints'); + + $this->assertEquals('self', $link->rel); + $this->assertEquals('https://api.bandwidth.com/endpoints', $link->href); + + $json = $link->jsonSerialize(); + $this->assertEquals('self', $json['rel']); + $this->assertEquals('https://api.bandwidth.com/endpoints', $json['href']); + } + + public function testErrorObjectModel() { + $error = new BandwidthLib\BRTC\Models\ErrorObject('VALIDATION_ERROR', 'Field is required'); + + $this->assertEquals('VALIDATION_ERROR', $error->type); + $this->assertEquals('Field is required', $error->description); + + $json = $error->jsonSerialize(); + $this->assertEquals('VALIDATION_ERROR', $json['type']); + $this->assertEquals('Field is required', $json['description']); + } +} diff --git a/tests/BxmlTest.php b/tests/BxmlTest.php index 44bdb2f..7fd5a74 100644 --- a/tests/BxmlTest.php +++ b/tests/BxmlTest.php @@ -540,4 +540,29 @@ 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 = 'endpoint-123endpoint-456'; + $responseXml = $response->toBxml(); + $this->assertEquals($expectedXml, $responseXml); + } + + public function testConnectWithEventCallbackUrl() { + $endpoint = new BandwidthLib\Voice\Bxml\Endpoint("endpoint-789"); + $connect = new BandwidthLib\Voice\Bxml\Connect([$endpoint]); + $connect->eventCallbackUrl("https://example.com/events"); + $response = new BandwidthLib\Voice\Bxml\Response(); + $response->addVerb($connect); + $expectedXml = 'endpoint-789'; + $responseXml = $response->toBxml(); + $this->assertEquals($expectedXml, $responseXml); + } }