From e0bd368ded8e56a5b7995856d20be86a421d01c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=BBy=C5=82a?= Date: Thu, 27 Apr 2023 10:41:06 +0200 Subject: [PATCH] Added redlink notifier --- .../FrameworkExtension.php | 1 + .../Resources/config/notifier_transports.php | 4 + .../Notifier/Bridge/Redlink/.gitattributes | 4 + .../Notifier/Bridge/Redlink/.gitignore | 3 + .../Notifier/Bridge/Redlink/CHANGELOG.md | 7 ++ .../Component/Notifier/Bridge/Redlink/LICENSE | 19 +++ .../Notifier/Bridge/Redlink/README.md | 26 +++++ .../Bridge/Redlink/RedlinkOptions.php | 94 +++++++++++++++ .../Bridge/Redlink/RedlinkTransport.php | 109 ++++++++++++++++++ .../Redlink/RedlinkTransportFactory.php | 45 ++++++++ .../Tests/RedlinkTransportFactoryTest.php | 50 ++++++++ .../Redlink/Tests/RedlinkTransportTest.php | 56 +++++++++ .../Notifier/Bridge/Redlink/composer.json | 30 +++++ .../Notifier/Bridge/Redlink/phpunit.xml.dist | 31 +++++ .../Exception/UnsupportedSchemeException.php | 4 + .../UnsupportedSchemeExceptionTest.php | 2 + src/Symfony/Component/Notifier/Transport.php | 1 + 17 files changed, 486 insertions(+) create mode 100644 src/Symfony/Component/Notifier/Bridge/Redlink/.gitattributes create mode 100644 src/Symfony/Component/Notifier/Bridge/Redlink/.gitignore create mode 100644 src/Symfony/Component/Notifier/Bridge/Redlink/CHANGELOG.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Redlink/LICENSE create mode 100644 src/Symfony/Component/Notifier/Bridge/Redlink/README.md create mode 100644 src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkOptions.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkTransport.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkTransportFactory.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Redlink/Tests/RedlinkTransportFactoryTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Redlink/Tests/RedlinkTransportTest.php create mode 100644 src/Symfony/Component/Notifier/Bridge/Redlink/composer.json create mode 100644 src/Symfony/Component/Notifier/Bridge/Redlink/phpunit.xml.dist diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index df3ff3feb406..e40878c332b5 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -2761,6 +2761,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $ NotifierBridge\PagerDuty\PagerDutyTransportFactory::class => 'notifier.transport_factory.pager-duty', NotifierBridge\Plivo\PlivoTransportFactory::class => 'notifier.transport_factory.plivo', NotifierBridge\Pushover\PushoverTransportFactory::class => 'notifier.transport_factory.pushover', + NotifierBridge\Redlink\RedlinkTransportFactory::class => 'notifier.transport_factory.redlink', NotifierBridge\RingCentral\RingCentralTransportFactory::class => 'notifier.transport_factory.ring-central', NotifierBridge\RocketChat\RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat', NotifierBridge\Sendberry\SendberryTransportFactory::class => 'notifier.transport_factory.sendberry', diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php index 1bbb06bc3c0f..b90392a846d0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php @@ -295,5 +295,9 @@ ->set('notifier.transport_factory.ntfy', Bridge\Ntfy\NtfyTransportFactory::class) ->parent('notifier.transport_factory.abstract') ->tag('texter.transport_factory') + + ->set('notifier.transport_factory.redlink', Bridge\Redlink\RedlinkTransportFactory::class) + ->parent('notifier.transport_factory.abstract') + ->tag('texter.transport_factory') ; }; diff --git a/src/Symfony/Component/Notifier/Bridge/Redlink/.gitattributes b/src/Symfony/Component/Notifier/Bridge/Redlink/.gitattributes new file mode 100644 index 000000000000..84c7add058fb --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Redlink/.gitattributes @@ -0,0 +1,4 @@ +/Tests export-ignore +/phpunit.xml.dist export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/src/Symfony/Component/Notifier/Bridge/Redlink/.gitignore b/src/Symfony/Component/Notifier/Bridge/Redlink/.gitignore new file mode 100644 index 000000000000..c49a5d8df5c6 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Redlink/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Notifier/Bridge/Redlink/CHANGELOG.md b/src/Symfony/Component/Notifier/Bridge/Redlink/CHANGELOG.md new file mode 100644 index 000000000000..1f2c8f86cde7 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Redlink/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +6.3 +--- + + * Add the bridge diff --git a/src/Symfony/Component/Notifier/Bridge/Redlink/LICENSE b/src/Symfony/Component/Notifier/Bridge/Redlink/LICENSE new file mode 100644 index 000000000000..3ed9f412ce53 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Redlink/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023-present Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Notifier/Bridge/Redlink/README.md b/src/Symfony/Component/Notifier/Bridge/Redlink/README.md new file mode 100644 index 000000000000..9950d8b7d505 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Redlink/README.md @@ -0,0 +1,26 @@ +Redlink Notifier +================= + +Provides [Redlink](https://redlink.pl) integration for Symfony Notifier. + +DSN example +----------- + +``` +REDLINK_DSN=redlink://API_TOKEN:APP_TOKEN@default?from=SENDER_NAME&version=VERSION +``` + +where: + +- `API_TOKEN` is your user API token, you can get it from the user dashboard +- `APP_TOKEN` is your application's API token +- `SENDER_NAME` is sender ID that was previously added through the user dashboard +- `VERSION` is API version that you want to use, ex. v2.1, optional + +Resources +--------- + +* [Contributing](https://symfony.com/doc/current/contributing/index.html) +* [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkOptions.php b/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkOptions.php new file mode 100644 index 000000000000..c15d5c9639a9 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkOptions.php @@ -0,0 +1,94 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Redlink; + +use Symfony\Component\Notifier\Message\MessageOptionsInterface; + +/** + * @author Mateusz Żyła + */ +final class RedlinkOptions implements MessageOptionsInterface +{ + public function __construct(protected array $options = []) + { + } + + public function toArray(): array + { + return array_filter($this->options); + } + + public function getRecipientId(): ?string + { + return $this->options['externalId'] ?? null; + } + + /** + * @return $this + */ + public function validity(int $validity): static + { + $this->options['validity'] = $validity; + + return $this; + } + + /** + * @return $this + */ + public function scheduleTime(int $scheduleTime): static + { + $this->options['scheduleTime'] = $scheduleTime; + + return $this; + } + + /** + * @return $this + */ + public function type(int $type): static + { + $this->options['type'] = $type; + + return $this; + } + + /** + * @return $this + */ + public function shortLink(bool $shortLink): static + { + $this->options['shortLink'] = $shortLink; + + return $this; + } + + /** + * @return $this + */ + public function webhookUrl(string $webhookUrl): static + { + $this->options['webhookUrl'] = $webhookUrl; + + return $this; + } + + /** + * @return $this + */ + public function externalId(string $externalId): static + { + $this->options['externalId'] = $externalId; + + return $this; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkTransport.php b/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkTransport.php new file mode 100644 index 000000000000..691275796c60 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkTransport.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Redlink; + +use Symfony\Component\Notifier\Exception\TransportException; +use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; +use Symfony\Component\Notifier\Message\MessageInterface; +use Symfony\Component\Notifier\Message\SentMessage; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Transport\AbstractTransport; +use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; +use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +/** + * @author Mateusz Żyła + */ +final class RedlinkTransport extends AbstractTransport +{ + protected const HOST = 'api.redlink.pl'; + + public function __construct( + #[\SensitiveParameter] + private readonly string $apiToken, + #[\SensitiveParameter] + private readonly string $appToken, + private readonly ?string $from, + private readonly ?string $version, + HttpClientInterface $client = null, + EventDispatcherInterface $dispatcher = null, + ) { + parent::__construct($client, $dispatcher); + } + + public function supports(MessageInterface $message): bool + { + return $message instanceof SmsMessage; + } + + public function __toString(): string + { + return sprintf( + 'redlink://%s?from=%s&version=%s', + $this->getEndpoint(), + $this->from, + $this->version + ); + } + + protected function doSend(MessageInterface $message): SentMessage + { + if (!$message instanceof SmsMessage) { + throw new UnsupportedMessageTypeException(__CLASS__, SmsMessage::class, $message); + } + + $options = ($opts = $message->getOptions()) ? $opts->toArray() : []; + + $from = $message->getFrom() ?: $this->from; + + $endpoint = sprintf('https://%s/%s/sms', $this->getEndpoint(), $this->version); + + $response = $this->client->request('POST', $endpoint, [ + 'headers' => [ + 'Authorization' => $this->apiToken, + 'Application-Key' => $this->appToken, + ], + 'json' => array_merge([ + 'sender' => $from, + 'message' => $message->getSubject(), + 'phoneNumbers' => [ + $message->getPhone(), + ], + ], array_filter($options)), + ]); + + try { + $statusCode = $response->getStatusCode(); + } catch (TransportExceptionInterface $e) { + throw new TransportException('Could not reach the remote Redlink server.', $response, 0, $e); + } + + $content = $response->toArray(false); + + if (200 !== $statusCode) { + $requestUniqueIdentifier = $content['meta']['uniqId'] ?? ''; + + $errorMessage = $content['errors'][0]['message'] ?? ''; + + throw new TransportException(sprintf('Unable to send the SMS: '.$errorMessage.'. UniqId: (%s).', $requestUniqueIdentifier), $response); + } + + $messageId = $content['data'][0]['externalId'] ?? ''; + + $sentMessage = new SentMessage($message, (string) $this); + + $sentMessage->setMessageId($messageId); + + return $sentMessage; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkTransportFactory.php b/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkTransportFactory.php new file mode 100644 index 000000000000..cb8033d3bf8e --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Redlink/RedlinkTransportFactory.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Redlink; + +use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; +use Symfony\Component\Notifier\Transport\AbstractTransportFactory; +use Symfony\Component\Notifier\Transport\Dsn; +use Symfony\Component\Notifier\Transport\TransportInterface; + +/** + * @author Mateusz Żyła + */ +final class RedlinkTransportFactory extends AbstractTransportFactory +{ + public function create(Dsn $dsn): TransportInterface + { + if ('redlink' !== $dsn->getScheme()) { + throw new UnsupportedSchemeException($dsn, 'redlink', $this->getSupportedSchemes()); + } + + $apiKey = $dsn->getUser(); + $appToken = $dsn->getPassword(); + $host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); + $port = $dsn->getPort(); + + $from = $dsn->getRequiredOption('from'); + $version = $dsn->getRequiredOption('version'); + + return (new RedlinkTransport($apiKey, $appToken, $from, $version, $this->client, $this->dispatcher))->setHost($host)->setPort($port); + } + + protected function getSupportedSchemes(): array + { + return ['redlink']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Redlink/Tests/RedlinkTransportFactoryTest.php b/src/Symfony/Component/Notifier/Bridge/Redlink/Tests/RedlinkTransportFactoryTest.php new file mode 100644 index 000000000000..6a9c2b76b201 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Redlink/Tests/RedlinkTransportFactoryTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Redlink\Tests; + +use Symfony\Component\Notifier\Bridge\Redlink\RedlinkTransportFactory; +use Symfony\Component\Notifier\Test\TransportFactoryTestCase; + +final class RedlinkTransportFactoryTest extends TransportFactoryTestCase +{ + public function createFactory(): RedlinkTransportFactory + { + return new RedlinkTransportFactory(); + } + + public static function createProvider(): iterable + { + yield [ + 'redlink://api.redlink.pl?from=TEST&version=v2.1', + 'redlink://aaaaa:bbbbbb@api.redlink.pl?from=TEST&version=v2.1', + ]; + } + + public static function supportsProvider(): iterable + { + yield [true, 'redlink://aaaaa:bbbbbb@default?from=TEST']; + yield [false, 'somethingElse://aaaaa:bbbbbb@default?from=TEST']; + } + + public static function missingRequiredOptionProvider(): iterable + { + yield 'missing option: from' => ['redlink://apiToken:appToken@default']; + yield 'missing option: version' => ['redlink://apiToken:appToken@default?from=TEST']; + } + + public static function unsupportedSchemeProvider(): iterable + { + yield ['somethingElse://apiToken:appToken@default?from=FROM&version=FROM']; + yield ['somethingElse://apiToken:appToken@default?from=FROM']; + yield ['somethingElse://apiToken:appToken@default']; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Redlink/Tests/RedlinkTransportTest.php b/src/Symfony/Component/Notifier/Bridge/Redlink/Tests/RedlinkTransportTest.php new file mode 100644 index 000000000000..353e39c8bf53 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Redlink/Tests/RedlinkTransportTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Notifier\Bridge\Redlink\Tests; + +use Symfony\Component\HttpClient\MockHttpClient; +use Symfony\Component\Notifier\Bridge\Redlink\RedlinkOptions; +use Symfony\Component\Notifier\Bridge\Redlink\RedlinkTransport; +use Symfony\Component\Notifier\Message\ChatMessage; +use Symfony\Component\Notifier\Message\PushMessage; +use Symfony\Component\Notifier\Message\SmsMessage; +use Symfony\Component\Notifier\Test\TransportTestCase; +use Symfony\Component\Notifier\Tests\Transport\DummyMessage; +use Symfony\Contracts\HttpClient\HttpClientInterface; + +final class RedlinkTransportTest extends TransportTestCase +{ + public static function createTransport(HttpClientInterface $client = null): RedlinkTransport + { + return (new RedlinkTransport( + 'testApiToken', + 'testAppToken', + 'TEST', + 'v2.1', + $client ?? new MockHttpClient() + ))->setHost('api.redlink.pl'); + } + + public static function toStringProvider(): iterable + { + yield ['redlink://api.redlink.pl?from=TEST&version=v2.1', self::createTransport()]; + } + + public static function supportedMessagesProvider(): iterable + { + yield [new SmsMessage('+48123123123', 'Summary')]; + yield [new SmsMessage('+48123123123', 'Summary', '')]; + yield [new SmsMessage('+48123123123', 'Summary', 'customSender')]; + yield [new SmsMessage('+48123123123', 'Summary', '', (new RedlinkOptions())->externalId('aaa-aaa-aaa'))]; + } + + public static function unsupportedMessagesProvider(): iterable + { + yield [new PushMessage('Hi!', 'Hello!')]; + yield [new ChatMessage('Hello!')]; + yield [new DummyMessage()]; + } +} diff --git a/src/Symfony/Component/Notifier/Bridge/Redlink/composer.json b/src/Symfony/Component/Notifier/Bridge/Redlink/composer.json new file mode 100644 index 000000000000..43983e98a92c --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Redlink/composer.json @@ -0,0 +1,30 @@ +{ + "name": "symfony/redlink-notifier", + "type": "symfony-notifier-bridge", + "description": "Symfony Redlink Notifier Bridge", + "keywords": ["sms", "redlink", "notifier"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Mateusz Żyła", + "email": "mateusz.zyla@protonmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1", + "symfony/http-client": "^5.4|^6.0", + "symfony/notifier": "^6.3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Notifier\\Bridge\\Redlink\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/src/Symfony/Component/Notifier/Bridge/Redlink/phpunit.xml.dist b/src/Symfony/Component/Notifier/Bridge/Redlink/phpunit.xml.dist new file mode 100644 index 000000000000..ba9ade8a6b84 --- /dev/null +++ b/src/Symfony/Component/Notifier/Bridge/Redlink/phpunit.xml.dist @@ -0,0 +1,31 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + + ./Resources + ./Tests + ./vendor + + + diff --git a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php index a9082a546026..e3f952585334 100644 --- a/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php +++ b/src/Symfony/Component/Notifier/Exception/UnsupportedSchemeException.php @@ -188,6 +188,10 @@ class UnsupportedSchemeException extends LogicException 'class' => Bridge\Pushover\PushoverTransportFactory::class, 'package' => 'symfony/pushover-notifier', ], + 'redlink' => [ + 'class' => Bridge\Redlink\RedlinkTransportFactory::class, + 'package' => 'symfony/redlink-notifier', + ], 'ringcentral' => [ 'class' => Bridge\RingCentral\RingCentralTransportFactory::class, 'package' => 'symfony/ring-central-notifier', diff --git a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php index dc4ab7928221..07a52db5bece 100644 --- a/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php +++ b/src/Symfony/Component/Notifier/Tests/Exception/UnsupportedSchemeExceptionTest.php @@ -70,6 +70,7 @@ public static function setUpBeforeClass(): void Bridge\Plivo\PlivoTransportFactory::class => false, Bridge\Pushover\PushoverTransportFactory::class => false, Bridge\RingCentral\RingCentralTransportFactory::class => false, + Bridge\Redlink\RedlinkTransportFactory::class => false, Bridge\RocketChat\RocketChatTransportFactory::class => false, Bridge\Sendberry\SendberryTransportFactory::class => false, Bridge\Sendinblue\SendinblueTransportFactory::class => false, @@ -146,6 +147,7 @@ public static function messageWhereSchemeIsPartOfSchemeToPackageMapProvider(): \ yield ['onesignal', 'symfony/one-signal-notifier']; yield ['ovhcloud', 'symfony/ovh-cloud-notifier']; yield ['plivo', 'symfony/plivo-notifier']; + yield ['redlink', 'symfony/redlink-notifier']; yield ['ringcentral', 'symfony/ring-central-notifier']; yield ['rocketchat', 'symfony/rocket-chat-notifier']; yield ['sendberry', 'symfony/sendberry-notifier']; diff --git a/src/Symfony/Component/Notifier/Transport.php b/src/Symfony/Component/Notifier/Transport.php index 7959aaf8c066..e1051d2e9606 100644 --- a/src/Symfony/Component/Notifier/Transport.php +++ b/src/Symfony/Component/Notifier/Transport.php @@ -71,6 +71,7 @@ final class Transport Bridge\PagerDuty\PagerDutyTransportFactory::class, Bridge\Plivo\PlivoTransportFactory::class, Bridge\Pushover\PushoverTransportFactory::class, + Bridge\Redlink\RedlinkTransportFactory::class, Bridge\RingCentral\RingCentralTransportFactory::class, Bridge\RocketChat\RocketChatTransportFactory::class, Bridge\Sendberry\SendberryTransportFactory::class,