Skip to content

Commit b15a479

Browse files
committed
feature #53927 [Notifier] Add new Pushy notifier bridge (stloyd)
This PR was merged into the 7.1 branch. Discussion ---------- [Notifier] Add new Pushy notifier bridge | Q | A | ------------- | --- | Branch? | 7.1 | Bug fix? | no | New feature? | yes <!-- please update src/**/CHANGELOG.md files --> | Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files --> | License | MIT This PR adds a new bridge for [Pushy](https://pushy.me). Based on documentation available: https://pushy.me/docs/api/send-notifications <!-- Replace this notice by a description of your feature/bugfix. This will help reviewers and should be a good start for the documentation. Additionally (see https://symfony.com/releases): - Always add tests and ensure they pass. - Bug fixes must be submitted against the lowest maintained branch where they apply (lowest branches are regularly merged to upper ones so they get the fixes too). - Features and deprecations must be submitted against the latest branch. - For new features, provide some code snippets to help understand usage. - Changelog entry should follow https://symfony.com/doc/current/contributing/code/conventions.html#writing-a-changelog-entry - Never break backward compatibility (see https://symfony.com/bc). --> Commits ------- b8f666d Add new Pushy notifier bridge
2 parents fd53091 + b8f666d commit b15a479

19 files changed

+706
-0
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2804,6 +2804,7 @@ private function registerNotifierConfiguration(array $config, ContainerBuilder $
28042804
NotifierBridge\PagerDuty\PagerDutyTransportFactory::class => 'notifier.transport_factory.pager-duty',
28052805
NotifierBridge\Plivo\PlivoTransportFactory::class => 'notifier.transport_factory.plivo',
28062806
NotifierBridge\Pushover\PushoverTransportFactory::class => 'notifier.transport_factory.pushover',
2807+
NotifierBridge\Pushy\PushyTransportFactory::class => 'notifier.transport_factory.pushy',
28072808
NotifierBridge\Redlink\RedlinkTransportFactory::class => 'notifier.transport_factory.redlink',
28082809
NotifierBridge\RingCentral\RingCentralTransportFactory::class => 'notifier.transport_factory.ring-central',
28092810
NotifierBridge\RocketChat\RocketChatTransportFactory::class => 'notifier.transport_factory.rocket-chat',

src/Symfony/Bundle/FrameworkBundle/Resources/config/notifier_transports.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
'ovh-cloud' => Bridge\OvhCloud\OvhCloudTransportFactory::class,
8888
'plivo' => Bridge\Plivo\PlivoTransportFactory::class,
8989
'pushover' => Bridge\Pushover\PushoverTransportFactory::class,
90+
'pushy' => Bridge\Pushy\PushyTransportFactory::class,
9091
'redlink' => Bridge\Redlink\RedlinkTransportFactory::class,
9192
'ring-central' => Bridge\RingCentral\RingCentralTransportFactory::class,
9293
'sendberry' => Bridge\Sendberry\SendberryTransportFactory::class,
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/Tests export-ignore
2+
/phpunit.xml.dist export-ignore
3+
/.gitattributes export-ignore
4+
/.gitignore export-ignore
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
7.1
5+
---
6+
7+
* Add the bridge
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Notifier\Bridge\Pushy\Enum;
13+
14+
/**
15+
* @author Joseph Bielawski <stloyd@gmail.com>
16+
*/
17+
enum InterruptionLevel: string
18+
{
19+
case ACTIVE = 'active';
20+
case CRITICAL = 'critical';
21+
case PASSIVE = 'passive';
22+
case TIME_SENSITIVE = 'time-sensitive';
23+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2024-present Fabien Potencier
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Notifier\Bridge\Pushy;
13+
14+
use Symfony\Component\Notifier\Bridge\Pushy\Enum\InterruptionLevel;
15+
use Symfony\Component\Notifier\Exception\InvalidArgumentException;
16+
use Symfony\Component\Notifier\Message\MessageOptionsInterface;
17+
use Symfony\Component\Notifier\Notification\Notification;
18+
19+
/**
20+
* @author Joseph Bielawski <stloyd@gmail.com>
21+
*
22+
* @see https://pushy.me/docs/api/send-notifications
23+
*/
24+
final class PushyOptions implements MessageOptionsInterface
25+
{
26+
public function __construct(
27+
private array $options = [],
28+
) {
29+
}
30+
31+
public static function fromNotification(Notification $notification): self
32+
{
33+
$options = new self();
34+
$options->interruptionLevel(
35+
match ($notification->getImportance()) {
36+
Notification::IMPORTANCE_URGENT => InterruptionLevel::CRITICAL,
37+
Notification::IMPORTANCE_HIGH => InterruptionLevel::TIME_SENSITIVE,
38+
Notification::IMPORTANCE_MEDIUM => InterruptionLevel::ACTIVE,
39+
Notification::IMPORTANCE_LOW => InterruptionLevel::PASSIVE,
40+
}
41+
);
42+
43+
return $options;
44+
}
45+
46+
public function toArray(): array
47+
{
48+
return $this->options;
49+
}
50+
51+
public function getRecipientId(): ?string
52+
{
53+
return $this->options['to'] ?? null;
54+
}
55+
56+
/**
57+
* @see https://pushy.me/docs/api/send-notifications#request-schema
58+
*
59+
* @param string|string[] $to
60+
*
61+
* @return $this
62+
*/
63+
public function to(string|array $to): static
64+
{
65+
$this->options['to'] = $to;
66+
67+
return $this;
68+
}
69+
70+
/**
71+
* @see https://pushy.me/docs/api/send-notifications#request-schema
72+
*
73+
* @return $this
74+
*/
75+
public function contentAvailable(bool $bool): static
76+
{
77+
$this->options['content_available'] = $bool;
78+
79+
return $this;
80+
}
81+
82+
/**
83+
* @see https://pushy.me/docs/api/send-notifications#request-schema
84+
*
85+
* @return $this
86+
*/
87+
public function mutableContent(bool $bool): static
88+
{
89+
$this->options['mutable_content'] = $bool;
90+
91+
return $this;
92+
}
93+
94+
/**
95+
* @see https://pushy.me/docs/api/send-notifications#request-schema
96+
*
97+
* @return $this
98+
*/
99+
public function ttl(int $seconds): static
100+
{
101+
if ($seconds > (86400 * 365)) {
102+
throw new InvalidArgumentException('Pushy notification time to live cannot exceed 365 days.');
103+
}
104+
105+
$this->options['time_to_live'] = $seconds;
106+
107+
return $this;
108+
}
109+
110+
/**
111+
* @see https://pushy.me/docs/api/send-notifications#request-schema
112+
*
113+
* @return $this
114+
*/
115+
public function schedule(int $seconds): static
116+
{
117+
if (false === \DateTime::createFromFormat('U', $seconds)) {
118+
throw new InvalidArgumentException('Pushy notification schedule time must be correct Unix timestamp.');
119+
}
120+
121+
if (\DateTime::createFromFormat('U', $seconds) >= new \DateTime('+1 year')) {
122+
throw new InvalidArgumentException('Pushy notification schedule time cannot exceed 1 year.');
123+
}
124+
125+
$this->options['schedule'] = $seconds;
126+
127+
return $this;
128+
}
129+
130+
/**
131+
* @see https://pushy.me/docs/api/send-notifications#request-schema
132+
*
133+
* @return $this
134+
*/
135+
public function collapseKey(string $collapseKey): static
136+
{
137+
if (32 < \strlen($collapseKey)) {
138+
throw new InvalidArgumentException('Pushy notification collapse key cannot be longer than 32 characters.');
139+
}
140+
141+
$this->options['collapse_key'] = $collapseKey;
142+
143+
return $this;
144+
}
145+
146+
/**
147+
* @return $this
148+
*/
149+
public function body(string $body): static
150+
{
151+
$this->options['notification']['body'] = $body;
152+
153+
return $this;
154+
}
155+
156+
/**
157+
* @see https://pushy.me/docs/api/send-notifications#request-schema
158+
*
159+
* @return $this
160+
*/
161+
public function badge(int $badge): static
162+
{
163+
$this->options['notification']['badge'] = $badge;
164+
165+
return $this;
166+
}
167+
168+
/**
169+
* @see https://pushy.me/docs/api/send-notifications#request-schema
170+
*
171+
* @return $this
172+
*/
173+
public function threadId(int $threadId): static
174+
{
175+
$this->options['notification']['thread_id'] = $threadId;
176+
177+
return $this;
178+
}
179+
180+
/**
181+
* @see https://pushy.me/docs/api/send-notifications#request-schema
182+
*
183+
* @return $this
184+
*/
185+
public function interruptionLevel(InterruptionLevel $interruptionLevel): static
186+
{
187+
$this->options['notification']['interruption_level'] = $interruptionLevel->value;
188+
189+
return $this;
190+
}
191+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Notifier\Bridge\Pushy;
13+
14+
use Symfony\Component\Notifier\Exception\InvalidArgumentException;
15+
use Symfony\Component\Notifier\Exception\TransportException;
16+
use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException;
17+
use Symfony\Component\Notifier\Message\MessageInterface;
18+
use Symfony\Component\Notifier\Message\PushMessage;
19+
use Symfony\Component\Notifier\Message\SentMessage;
20+
use Symfony\Component\Notifier\Transport\AbstractTransport;
21+
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
22+
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
23+
use Symfony\Contracts\HttpClient\HttpClientInterface;
24+
25+
/**
26+
* @author Joseph Bielawski <stloyd@gmail.com>
27+
*/
28+
final class PushyTransport extends AbstractTransport
29+
{
30+
protected const HOST = 'api.pushy.me';
31+
32+
public function __construct(
33+
#[\SensitiveParameter] private readonly string $apiKey,
34+
?HttpClientInterface $client = null,
35+
?EventDispatcherInterface $dispatcher = null,
36+
) {
37+
parent::__construct($client, $dispatcher);
38+
}
39+
40+
public function supports(MessageInterface $message): bool
41+
{
42+
return $message instanceof PushMessage && (null === $message->getOptions() || $message->getOptions() instanceof PushyOptions);
43+
}
44+
45+
public function __toString(): string
46+
{
47+
return sprintf('pushy://%s', $this->getEndpoint());
48+
}
49+
50+
protected function doSend(MessageInterface $message): SentMessage
51+
{
52+
if (!$message instanceof PushMessage) {
53+
throw new UnsupportedMessageTypeException(__CLASS__, PushMessage::class, $message);
54+
}
55+
56+
$options = $message->getOptions()?->toArray() ?? [];
57+
$options['data'] = $message->getContent();
58+
$options['notification']['title'] = $message->getSubject();
59+
$options['to'] ??= $message->getRecipientId();
60+
61+
if (!$options['to']) {
62+
throw new InvalidArgumentException(sprintf('The "%s" transport required the "to" option to be set.', __CLASS__));
63+
}
64+
65+
$endpoint = sprintf('https://%s?api_key=%s', $this->getEndpoint(), $this->apiKey);
66+
$response = $this->client->request('POST', $endpoint, [
67+
'headers' => [
68+
'Accept' => 'application/json',
69+
'Content-Type' => 'application/json',
70+
],
71+
'json' => array_filter($options),
72+
]);
73+
74+
try {
75+
$statusCode = $response->getStatusCode();
76+
} catch (TransportExceptionInterface $e) {
77+
throw new TransportException('Could not reach the remote Pushy server.', $response, 0, $e);
78+
}
79+
80+
if (200 !== $statusCode) {
81+
throw new TransportException(sprintf('Unable to send the Pushy push notification: "%s".', $response->getContent(false)), $response);
82+
}
83+
84+
$result = $response->toArray(false);
85+
86+
if (!isset($result['id'])) {
87+
throw new TransportException(sprintf('Unable to find the message ID within the Pushy response: "%s".', $response->getContent(false)), $response);
88+
}
89+
90+
$sentMessage = new SentMessage($message, (string) $this);
91+
$sentMessage->setMessageId($result['id']);
92+
93+
return $sentMessage;
94+
}
95+
}

0 commit comments

Comments
 (0)