Skip to content

Commit 4e445ef

Browse files
committed
Add new Pushy notifier bridge
1 parent fd53091 commit 4e445ef

File tree

17 files changed

+611
-0
lines changed

17 files changed

+611
-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: 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: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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\Message\MessageOptionsInterface;
16+
use Symfony\Component\Notifier\Notification\Notification;
17+
18+
/**
19+
* @author Joseph Bielawski <stloyd@gmail.com>
20+
*
21+
* @see https://pushy.me/docs/api/send-notifications
22+
*/
23+
final class PushyOptions implements MessageOptionsInterface
24+
{
25+
private const INTERRUPTION_LEVEL = ['passive', 'active', 'time-sensitive', 'critical'];
26+
27+
public function __construct(
28+
private array $options = [],
29+
) {
30+
}
31+
32+
public static function fromNotification(Notification $notification): self
33+
{
34+
$options = new self();
35+
$options->interruptionLevel(
36+
match ($notification->getImportance()) {
37+
Notification::IMPORTANCE_URGENT => 'critical',
38+
Notification::IMPORTANCE_HIGH => 'time-sensitive',
39+
Notification::IMPORTANCE_MEDIUM => 'active',
40+
Notification::IMPORTANCE_LOW => 'passive',
41+
}
42+
);
43+
44+
return $options;
45+
}
46+
47+
public function toArray(): array
48+
{
49+
return $this->options;
50+
}
51+
52+
public function getRecipientId(): ?string
53+
{
54+
return $this->options['to'] ?? null;
55+
}
56+
57+
/**
58+
* @see https://pushy.me/docs/api/send-notifications#request-schema
59+
*
60+
* @param string|string[] $to
61+
*
62+
* @return $this
63+
*/
64+
public function to(string|array $to): static
65+
{
66+
$this->options['to'] = $to;
67+
68+
return $this;
69+
}
70+
71+
/**
72+
* @see https://pushy.me/docs/api/send-notifications#request-schema
73+
*
74+
* @return $this
75+
*/
76+
public function contentAvailable(bool $bool): static
77+
{
78+
$this->options['content_available'] = $bool;
79+
80+
return $this;
81+
}
82+
83+
/**
84+
* @see https://pushy.me/docs/api/send-notifications#request-schema
85+
*
86+
* @return $this
87+
*/
88+
public function mutableContent(bool $bool): static
89+
{
90+
$this->options['mutable_content'] = $bool;
91+
92+
return $this;
93+
}
94+
95+
/**
96+
* @see https://pushy.me/docs/api/send-notifications#request-schema
97+
*
98+
* @return $this
99+
*/
100+
public function ttl(int $seconds): static
101+
{
102+
if ($seconds > (86400 * 365)) {
103+
throw new InvalidArgumentException('Pushy notification time to live cannot exceed 365 days.');
104+
}
105+
106+
$this->options['time_to_live'] = $seconds;
107+
108+
return $this;
109+
}
110+
111+
/**
112+
* @see https://pushy.me/docs/api/send-notifications#request-schema
113+
*
114+
* @return $this
115+
*/
116+
public function schedule(int $seconds): static
117+
{
118+
if (false === \DateTime::createFromFormat('U', $seconds)) {
119+
throw new InvalidArgumentException('Pushy notification schedule time must be correct Unix timestamp.');
120+
}
121+
122+
if (\DateTime::createFromFormat('U', $seconds) >= new \DateTime('+1 year')) {
123+
throw new InvalidArgumentException('Pushy notification schedule time cannot exceed 1 year.');
124+
}
125+
126+
$this->options['schedule'] = $seconds;
127+
128+
return $this;
129+
}
130+
131+
/**
132+
* @see https://pushy.me/docs/api/send-notifications#request-schema
133+
*
134+
* @return $this
135+
*/
136+
public function collapseKey(string $collapseKey): static
137+
{
138+
if (32 < \strlen($collapseKey)) {
139+
throw new InvalidArgumentException('Pushy notification collapse key cannot be longer than 32 characters.');
140+
}
141+
142+
$this->options['collapse_key'] = $collapseKey;
143+
144+
return $this;
145+
}
146+
147+
/**
148+
* @return $this
149+
*/
150+
public function body(string $body): static
151+
{
152+
$this->options['notification']['body'] = $body;
153+
154+
return $this;
155+
}
156+
157+
/**
158+
* @see https://pushy.me/docs/api/send-notifications#request-schema
159+
*
160+
* @return $this
161+
*/
162+
public function badge(int $badge): static
163+
{
164+
$this->options['notification']['badge'] = $badge;
165+
166+
return $this;
167+
}
168+
169+
/**
170+
* @see https://pushy.me/docs/api/send-notifications#request-schema
171+
*
172+
* @return $this
173+
*/
174+
public function threadId(int $threadId): static
175+
{
176+
$this->options['notification']['thread_id'] = $threadId;
177+
178+
return $this;
179+
}
180+
181+
/**
182+
* @see https://pushy.me/docs/api/send-notifications#request-schema
183+
*
184+
* @return $this
185+
*/
186+
public function interruptionLevel(int $interruptionLevel): static
187+
{
188+
if (!\in_array($interruptionLevel, self::INTERRUPTION_LEVEL, true)) {
189+
throw new InvalidArgumentException(sprintf('Pushy notification priority must be one of "%s".', implode(', ', self::INTERRUPTION_LEVEL)));
190+
}
191+
192+
$this->options['notification']['interruption_level'] = $interruptionLevel;
193+
194+
return $this;
195+
}
196+
}
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+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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\UnsupportedSchemeException;
15+
use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
16+
use Symfony\Component\Notifier\Transport\Dsn;
17+
use Symfony\Component\Notifier\Transport\TransportInterface;
18+
19+
/**
20+
* @author Joseph Bielawski <stloyd@gmail.com>
21+
*/
22+
final class PushyTransportFactory extends AbstractTransportFactory
23+
{
24+
public function create(Dsn $dsn): TransportInterface
25+
{
26+
if ('pushy' !== $dsn->getScheme()) {
27+
throw new UnsupportedSchemeException($dsn, 'pushy', $this->getSupportedSchemes());
28+
}
29+
30+
$apiKey = $dsn->getUser();
31+
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost();
32+
$port = $dsn->getPort();
33+
34+
return (new PushyTransport($apiKey, $this->client, $this->dispatcher))->setHost($host)->setPort($port);
35+
}
36+
37+
protected function getSupportedSchemes(): array
38+
{
39+
return ['pushy'];
40+
}
41+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy