diff --git a/.env.example b/.env.example index 50b0814..856e414 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,7 @@ RINGCENTRAL_CLIENT_ID= RINGCENTRAL_CLIENT_SECRET= RINGCENTRAL_SERVER_URL= -RINGCENTRAL_TOKEN= +RINGCENTRAL_JWT= #The below are only used for the test suite RINGCENTRAL_SENDER= RINGCENTRAL_RECEIVER= diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index a20396e..b6d9e00 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -59,7 +59,7 @@ jobs: RINGCENTRAL_CLIENT_ID: ${{ secrets.RINGCENTRAL_CLIENT_ID }} RINGCENTRAL_CLIENT_SECRET: ${{ secrets.RINGCENTRAL_CLIENT_SECRET }} RINGCENTRAL_SERVER_URL: ${{ secrets.RINGCENTRAL_SERVER_URL }} - RINGCENTRAL_TOKEN: ${{ secrets.RINGCENTRAL_TOKEN }} + RINGCENTRAL_JWT: ${{ secrets.RINGCENTRAL_JWT }} RINGCENTRAL_SENDER: ${{ secrets.RINGCENTRAL_SENDER }} RINGCENTRAL_RECEIVER: ${{ secrets.RINGCENTRAL_RECEIVER }} RINGCENTRAL_DELAY_REQUEST_SECONDS: ${{ secrets.RINGCENTRAL_DELAY_REQUEST_SECONDS }} diff --git a/README.md b/README.md index ba63c8c..6cafa38 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # A Laravel package for the RingCentral SDK for PHP -This is a simple Laravel Service Provider providing access to the [RingCentral SDK for PHP][client-library]. +This is a simple Laravel Service Provider providing access to the RingCentral API. Forked from [https://github.com/coxlr/laravel-ringcentral](https://github.com/coxlr/laravel-ringcentral), Created by [Lee Cox](https://github.com/coxlr) +With code from: [RingCentral Connect Platform PHP SDK](https://github.com/ringcentral/ringcentral-php) ## Installation @@ -12,7 +13,7 @@ This package requires PHP 8.0 and Laravel 8 or higher. To install the PHP client library using Composer: ```bash -composer require coxlr/laravel-ringcentral +composer require sheavescapital/laravel-ringcentral ``` The package will automatically register the `RingCentral` provider and facade. @@ -20,7 +21,7 @@ The package will automatically register the `RingCentral` provider and facade. You can publish the config file with: ```bash -php artisan vendor:publish --provider="Coxlr\RingCentral\RingCentralServiceProvider" --tag="config" +php artisan vendor:publish --provider="SheavesCapital\RingCentral\RingCentralServiceProvider" --tag="config" ``` @@ -30,7 +31,7 @@ Then update `config/ringcentral.php` with your credentials. Alternatively, you c RINGCENTRAL_CLIENT_ID=my_client_id RINGCENTRAL_CLIENT_SECRET=my_client_secret RINGCENTRAL_SERVER_URL=my_server_url -RINGCENTRAL_TOKEN=my_jwt +RINGCENTRAL_JWT=my_jwt ``` This package uses the JWT autentication method. You can learn more about setting up JWT for your RingCentral account [here](https://developers.ringcentral.com/guide/authentication/jwt/quick-start). @@ -136,28 +137,6 @@ $ringcentral->getMessageAttachmentById(12345678, 910111213, 45678910); -For more information on using the RingCentral client library, see the [official client library repository][client-library]. - -[client-library]: https://github.com/ringcentral/ringcentral-php - - -## Testing - -``` bash -composer test -``` -If using the RingCentral sandbox environment when testing set the following environment variable to true to handle sandbox message prefix. - -```dotenv -RINGCENTRAL_IS_SANDBOX=true -``` -An optional environment value can be set to prevent hitting RingCentral rate limits when testing. This will add a delay for the set seconds before each test. - -```dotenv -RINGCENTRAL_DELAY_REQUEST_SECONDS=20 -``` - - ## License The MIT License (MIT). Please see [License File](LICENSE.md) for more information. diff --git a/composer.json b/composer.json index 547e881..a397927 100644 --- a/composer.json +++ b/composer.json @@ -2,30 +2,20 @@ "name": "sheavescapital/laravel-ringcentral", "description": "A Laravel package for the RingCentral SDK for PHP", "keywords": [ - "coxlr", "laravel", "ringcentral" ], - "homepage": "https://github.com/coxlr/laravel-ringcentral", + "homepage": "https://github.com/sheavescapital/laravel-ringcentral", "license": "MIT", "authors": [ { - "name": "Lee Cox", - "email": "hello@leecox.dev", - "homepage": "https://leecox.dev", - "role": "Developer" + "name": "SheavesCapital" } ], - "repositories": [ - { - "type": "vcs", - "url": "git@github.com:sheavescapital/ringcentral-php" - } - ], "require": { "php": "^8.0", - "ringcentral/ringcentral-php": "dev-sheaves" + "laravel/framework": "^11.0" }, "require-dev": { "laravel/pint": "^1.18", @@ -36,12 +26,12 @@ }, "autoload": { "psr-4": { - "Coxlr\\RingCentral\\": "src" + "SheavesCapital\\RingCentral\\": "src" } }, "autoload-dev": { "psr-4": { - "Coxlr\\RingCentral\\Tests\\": "tests" + "SheavesCapital\\RingCentral\\Tests\\": "tests" } }, "scripts": { @@ -59,10 +49,10 @@ "extra": { "laravel": { "providers": [ - "Coxlr\\RingCentral\\RingCentralServiceProvider" + "SheavesCapital\\RingCentral\\RingCentralServiceProvider" ], "aliases": { - "RingCentral": "Coxlr\\RingCentral\\Facades\\RingCentral" + "RingCentral": "SheavesCapital\\RingCentral\\Facades\\RingCentral" } } }, diff --git a/config/ringcentral.php b/config/ringcentral.php index 9f5993e..925bba3 100644 --- a/config/ringcentral.php +++ b/config/ringcentral.php @@ -13,5 +13,5 @@ 'client_id' => function_exists('env') ? env('RINGCENTRAL_CLIENT_ID', '') : '', 'client_secret' => function_exists('env') ? env('RINGCENTRAL_CLIENT_SECRET', '') : '', 'server_url' => function_exists('env') ? env('RINGCENTRAL_SERVER_URL', '') : '', - 'token' => function_exists('env') ? env('RINGCENTRAL_TOKEN', '') : '', + 'jwt' => function_exists('env') ? env('RINGCENTRAL_JWT', '') : '', ]; diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4cd7234..cf2d444 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -11,7 +11,7 @@ verbose="true" > - + tests diff --git a/src/Exceptions/CouldNotAuthenticate.php b/src/Exceptions/CouldNotAuthenticate.php index 0b929af..e14fb62 100644 --- a/src/Exceptions/CouldNotAuthenticate.php +++ b/src/Exceptions/CouldNotAuthenticate.php @@ -1,6 +1,6 @@ clientId = $clientId; @@ -36,39 +38,102 @@ public function setServerUrl(string $serverUrl): static { return $this; } - public function setToken(string $token): static { - $this->token = $token; + public function setjWT(string $jwt): static { + $this->jwt = $jwt; return $this; } - public function clientId(): string { - return $this->clientId; + protected function apiKey(): string { + return base64_encode($this->clientId.':'.$this->clientSecret); } - public function clientSecret(): string { - return $this->clientSecret; + protected static function errorHandler(Response $response): void { + throw new \Exception($response->json('message'), $response->status()); } - public function serverUrl(): string { - return $this->serverUrl; + protected function login(): Response { + $response = Http::withHeaders([ + 'Authorization' => 'Basic '.$this->apiKey(), + 'Content-Type' => 'application/x-www-form-urlencoded', + ])->get($this->serverUrl.self::TOKEN_ENDPOINT, [ + 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer', + 'assertion' => $this->jwt, + 'access_token_ttl' => self::ACCESS_TOKEN_TTL, + 'refresh_token_ttl' => self::REFRESH_TOKEN_TTL, + ]); + $response->onError($this->errorHandler(...)); + $access_token = $response->json('access_token'); + Cache::put('ringcentral_access_token', $access_token, $response->json('expires_in')); + Cache::put('ringcentral_refresh_token', $response->json('refresh_token'), $response->json('refresh_token_expires_in')); + return $access_token; + } + + protected function refresh(): string { + $response = Http::withHeaders([ + 'Authorization' => 'Basic '.$this->apiKey(), + 'Content-Type' => 'application/x-www-form-urlencoded', + ])->get($this->serverUrl.self::TOKEN_ENDPOINT, [ + 'grant_type' => 'refresh_token', + 'refresh_token' => Cache::get('ringcentral_refresh_token'), + 'access_token_ttl' => self::ACCESS_TOKEN_TTL, + 'refresh_token_ttl' => self::REFRESH_TOKEN_TTL, + ]); + $response->onError($this->errorHandler(...)); + $access_token = $response->json('access_token'); + Cache::put('ringcentral_access_token', $access_token, $response->json('expires_in')); + return $access_token; + } + + protected function accessToken(): string { + if (Cache::has('ringcentral_access_token')) { + return Cache::get('ringcentral_access_token'); + } elseif (Cache::has('ringcentral_refresh_token')) { + return $this->refresh(); + } else { + return $this->login(); + } + } + + protected function prependPath(string $url): string { + return $this->serverUrl.self::URL_PREFIX.'/'.self::API_VERSION.$url; } - public function token(): string { - return $this->token; + public function get(string $url, array $query = [], array $headers = [], bool $prependPath = true): Response { + $response = Http::withToken($this->accessToken()) + ->withHeaders($headers) + ->get( + $prependPath ? $this->prependPath($url) : $url, + $query + ); + $response->onError($this->errorHandler(...)); + return $response; } - public function connect(): void { - $this->ringCentral = (new SDK($this->clientId(), $this->clientSecret(), $this->serverUrl()))->platform(); + public function post(string $url, array $body = [], array $headers = [], bool $prependPath = true): Response { + $response = Http::withToken($this->accessToken()) + ->withHeaders($headers) + ->post( + $prependPath ? $this->prependPath($url) : $url, + $body + ); + $response->onError($this->errorHandler(...)); + return $response; } - public function login(): void { - $this->ringCentral->login(['jwt' => $this->Token()]); - $this->setLoggedInExtension(); + public function delete(string $url, array $query = [], array $headers = [], bool $prependPath = true): Response { + $response = Http::withToken($this->accessToken()) + ->withHeaders($headers) + ->delete( + $prependPath ? $this->prependPath($url) : $url, + $query + ); + $response->onError($this->errorHandler(...)); + return $response; } public function setLoggedInExtension(): void { - $extension = $this->ringCentral->get('/account/~/extension/~/')->json(); + $extension = $this->get('/account/~/extension/~/')->json(); $this->loggedInExtensionId = $extension->id; $this->loggedInExtension = $extension->extensionNumber; } @@ -81,42 +146,7 @@ public function loggedInExtension(): string { return $this->loggedInExtension; } - /** - * @throws CouldNotAuthenticate - */ - public function authenticate(): bool { - if (! $this->ringCentral) { - $this->connect(); - } - - if (! $this->loggedIn()) { - $this->login(); - } - - if (! $this->ringCentral->loggedIn()) { - throw CouldNotAuthenticate::loginFailed(); - } - - return true; - } - - /** - * @throws ApiException - */ - public function loggedIn(): bool { - if ($this->ringCentral->loggedIn()) { - return $this->ringCentral->get('/account/~/extension/~/')->json()->permissions->admin->enabled ?? false; - } - - return false; - } - - /** - * @throws CouldNotSendMessage - * @throws CouldNotAuthenticate - * @throws ApiException - */ - public function sendMessage(array $message): ApiResponse { + public function sendMessage(array $message): Response { if (empty($message['from'])) { throw CouldNotSendMessage::toNumberNotProvided(); } @@ -129,9 +159,7 @@ public function sendMessage(array $message): ApiResponse { throw CouldNotSendMessage::textNotProvided(); } - $this->authenticate(); - - return $this->ringCentral->post('/account/~/extension/~/sms', [ + return $this->post('/account/~/extension/~/sms', [ 'from' => ['phoneNumber' => $message['from']], 'to' => [ ['phoneNumber' => $message['to']], @@ -140,21 +168,11 @@ public function sendMessage(array $message): ApiResponse { ]); } - /** - * @throws CouldNotAuthenticate - * @throws ApiException - */ public function getExtensions(): array { - $this->authenticate(); - - $r = $this->ringCentral->get('/account/~/extension'); - + $r = $this->get('/account/~/extension'); return $r->json()->records; } - /** - * @throws ApiException - */ protected function getMessages(string $extensionId, ?object $fromDate = null, ?object $toDate = null, ?int $perPage = 100): array { $dates = []; @@ -166,7 +184,7 @@ protected function getMessages(string $extensionId, ?object $fromDate = null, ?o $dates['dateTo'] = $toDate->format('c'); } - $r = $this->ringCentral->get('/account/~/extension/'.$extensionId.'/message-store', array_merge( + $r = $this->get('/account/~/extension/'.$extensionId.'/message-store', array_merge( [ 'messageType' => 'SMS', 'perPage' => $perPage, @@ -177,43 +195,19 @@ protected function getMessages(string $extensionId, ?object $fromDate = null, ?o return $r->json()->records; } - /** - * @throws CouldNotAuthenticate - * @throws ApiException - */ public function getMessagesForExtensionId(string $extensionId, ?object $fromDate = null, ?object $toDate = null, ?int $perPage = 100): array { - $this->authenticate(); - return $this->getMessages($extensionId, $fromDate, $toDate, $perPage); } - /** - * @throws CouldNotAuthenticate - * @throws ApiException - */ public function getPhoneNumbers(): array { - $this->authenticate(); - - return $this->ringCentral->get('/account/~/phone-number')->json()->records; + return $this->get('/account/~/phone-number')->json()->records; } - /** - * @throws CouldNotAuthenticate - * @throws ApiException - */ - public function getMessageAttachmentById(string $extensionId, string $messageId, string $attachementId): ApiResponse { - $this->authenticate(); - - return $this->ringCentral->get('/account/~/extension/'.$extensionId.'/message-store/'.$messageId.'/content/'.$attachementId); + public function getMessageAttachmentById(string $extensionId, string $messageId, string $attachementId): Response { + return $this->get('/account/~/extension/'.$extensionId.'/message-store/'.$messageId.'/content/'.$attachementId); } - /** - * @throws CouldNotAuthenticate - * @throws ApiException - */ - public function getCallLogs(?object $fromDate = null, ?object $toDate = null, bool $withRecording = true, ?int $perPage = 100) { - $this->authenticate(); - + public function getCallLogs(?object $fromDate = null, ?object $toDate = null, bool $withRecording = true, ?int $perPage = 100): array { $dates = []; if ($fromDate) { @@ -227,7 +221,7 @@ public function getCallLogs(?object $fromDate = null, ?object $toDate = null, bo $dates['recordingType'] = 'All'; } - $r = $this->ringCentral->get('/account/~/call-log', array_merge( + $r = $this->get('/account/~/call-log', array_merge( [ 'type' => 'Voice', 'perPage' => $perPage, @@ -238,13 +232,7 @@ public function getCallLogs(?object $fromDate = null, ?object $toDate = null, bo return $r->json()->records; } - /** - * @throws CouldNotAuthenticate - * @throws ApiException - */ - public function getCallLogsForExtensionId(string $extensionId, ?object $fromDate = null, ?object $toDate = null, bool $withRecording = true, ?int $perPage = 100) { - $this->authenticate(); - + public function getCallLogsForExtensionId(string $extensionId, ?object $fromDate = null, ?object $toDate = null, bool $withRecording = true, ?int $perPage = 100): array { $dates = []; if ($fromDate) { @@ -259,7 +247,7 @@ public function getCallLogsForExtensionId(string $extensionId, ?object $fromDate $dates['recordingType'] = 'All'; } - $r = $this->ringCentral->get('/account/~/extension/'.$extensionId.'/call-log', array_merge( + $r = $this->get('/account/~/extension/'.$extensionId.'/call-log', array_merge( [ 'type' => 'Voice', 'perPage' => $perPage, @@ -270,34 +258,16 @@ public function getCallLogsForExtensionId(string $extensionId, ?object $fromDate return $r->json()->records; } - /** - * @throws CouldNotAuthenticate - * @throws ApiException - */ - public function getRecordingById(string $recordingId): ApiResponse { - $this->authenticate(); - - return $this->ringCentral->get("https://media.ringcentral.com/restapi/v1.0/account/~/recording/{$recordingId}/content"); + public function getRecordingById(string $recordingId): Response { + return $this->get("https://media.ringcentral.com/restapi/v1.0/account/~/recording/{$recordingId}/content"); } - /** - * @throws CouldNotAuthenticate - * @throws ApiException - */ public function listWebhooks(): array { - $this->authenticate(); - - return $this->ringCentral->get('/subscription')->json()->records; + return $this->get('/subscription')->json()->records; } - /** - * @throws CouldNotAuthenticate - * @throws ApiException - */ - public function createWebhook(array $filters, int $expiresIn, ?string $address, ?string $verificationToken): ApiResponse { - $this->authenticate(); - - return $this->ringCentral->post('/subscription', [ + public function createWebhook(array $filters, int $expiresIn, ?string $address, ?string $verificationToken): Response { + return $this->post('/subscription', [ 'eventFilters' => $filters, 'expiresIn' => $expiresIn, 'deliveryMode' => [ @@ -307,13 +277,7 @@ public function createWebhook(array $filters, int $expiresIn, ?string $address, ]); } - /** - * @throws CouldNotAuthenticate - * @throws ApiException - */ - public function deleteWebhook(string $webhookId): ApiResponse { - $this->authenticate(); - - return $this->ringCentral->delete("/subscription/{$webhookId}"); + public function deleteWebhook(string $webhookId): Response { + return $this->delete("/subscription/{$webhookId}"); } } diff --git a/src/RingCentralServiceProvider.php b/src/RingCentralServiceProvider.php index b8f9fd5..25a0cd3 100644 --- a/src/RingCentralServiceProvider.php +++ b/src/RingCentralServiceProvider.php @@ -1,6 +1,6 @@ setClientSecret(config('ringcentral.client_secret')) ->setServerUrl(config('ringcentral.server_url')); - if ($this->ringCentralConfigHas('token')) { - $ringCentral->setToken(config('ringcentral.token')); + if ($this->ringCentralConfigHas('jwt')) { + $ringCentral->setJwt(config('ringcentral.jwt')); } return $ringCentral; diff --git a/tests/Feature/RingCentralTest.php b/tests/Feature/RingCentralTest.php index 914c081..36df87f 100644 --- a/tests/Feature/RingCentralTest.php +++ b/tests/Feature/RingCentralTest.php @@ -1,12 +1,12 @@ setClientId(env('RINGCENTRAL_CLIENT_ID')) ->setClientSecret(env('RINGCENTRAL_CLIENT_SECRET')) ->setServerUrl(env('RINGCENTRAL_SERVER_URL')) - ->setToken(env('RINGCENTRAL_TOKEN')); + ->setJwt(env('RINGCENTRAL_JWT')); $this->delay(); } @@ -68,7 +68,7 @@ public function it_can_send_an_sms_message(): void { /** @test */ public function it_can_retrieve_sent_sms_messages_for_a_given_extension_previous_24_hours(): void { - $this->ringCentral->authenticate(); + $this->ringCentral->setLoggedInExtension(); $extensionId = $this->ringCentral->loggedInExtensionId(); $result = $this->ringCentral->getMessagesForExtensionId($extensionId); @@ -87,6 +87,7 @@ public function it_can_retrieve_sent_sms_messages_for_a_given_extension_previous /** @test */ public function it_can_retrieve_sent_sms_messages_for_a_given_extension_from_a_set_date(): void { + $this->ringCentral->setLoggedInExtension(); $this->ringCentral->sendMessage([ 'from' => env('RINGCENTRAL_SENDER'), 'to' => env('RINGCENTRAL_RECEIVER'), @@ -116,6 +117,7 @@ public function it_can_retrieve_sent_sms_messages_for_a_given_extension_from_a_s /** @test */ public function it_can_retrieve_sent_sms_messages_for_a_given_extension_from_a_set_date_to_a_set_date(): void { + $this->ringCentral->setLoggedInExtension(); $this->ringCentral->sendMessage([ 'from' => env('RINGCENTRAL_SENDER'), 'to' => env('RINGCENTRAL_RECEIVER'), @@ -147,6 +149,7 @@ public function it_can_retrieve_sent_sms_messages_for_a_given_extension_from_a_s /** @test */ public function it_can_retrieve_sent_sms_messages_for_a_given_extension_with_per_page_limit_set(): void { + $this->ringCentral->setLoggedInExtension(); $this->ringCentral->sendMessage([ 'from' => env('RINGCENTRAL_SENDER'), 'to' => env('RINGCENTRAL_RECEIVER'), @@ -184,6 +187,7 @@ public function it_can_retrieve_sent_sms_messages_for_a_given_extension_with_per /** @test */ public function it_can_retrieve_an_sms_messages_attachement(): void { + $this->ringCentral->setLoggedInExtension(); $this->ringCentral->sendMessage([ 'from' => env('RINGCENTRAL_SENDER'), 'to' => env('RINGCENTRAL_RECEIVER'), @@ -207,7 +211,7 @@ public function it_can_retrieve_an_sms_messages_attachement(): void { $firstMessage['attachments'][0]->id ); - $this->assertNotNull($attachment->raw()); + $this->assertNotNull($attachment->json()); } /** @test */ @@ -242,7 +246,7 @@ public function it_requires_a_to_message_to_send_an_sms_message(): void { /** @test */ public function an_exception_is_thrown_if_message_not_sent(): void { - $this->expectException(ApiException::class); + $this->expectException(Exception::class); $this->ringCentral->sendMessage([ 'from' => env('RINGCENTRAL_SENDER'), diff --git a/tests/RingCentralServiceProviderTest.php b/tests/RingCentralServiceProviderTest.php index 4115e4a..3d803b7 100644 --- a/tests/RingCentralServiceProviderTest.php +++ b/tests/RingCentralServiceProviderTest.php @@ -1,8 +1,8 @@ assertEquals('my_client_id', $ringCentral->clientId()); $this->assertEquals('my_client_secret', $ringCentral->clientSecret()); $this->assertEquals('my_server_url', $ringCentral->serverUrl()); - $this->assertEquals('my_token', $ringCentral->token()); + $this->assertEquals('my_jwt', $ringCentral->jwt()); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index b1a998b..0844d43 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -1,10 +1,10 @@ set('ringcentral.client_id', 'my_client_id'); $app['config']->set('ringcentral.client_secret', 'my_client_secret'); $app['config']->set('ringcentral.server_url', 'my_server_url'); - $app['config']->set('ringcentral.token', 'my_token'); + $app['config']->set('ringcentral.jwt', 'my_jwt'); } /**