Skip to content

Commit 0851f40

Browse files
committed
Add new Pushy notifier bridge
1 parent fd53091 commit 0851f40

File tree

17 files changed

+630
-0
lines changed

17 files changed

+630
-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: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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(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 => 'critical',
37+
Notification::IMPORTANCE_HIGH => 'time-sensitive',
38+
Notification::IMPORTANCE_MEDIUM => 'active',
39+
Notification::IMPORTANCE_LOW => '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(int $interruptionLevel): static
186+
{
187+
if (!\in_array($interruptionLevel, self::INTERRUPTION_LEVEL, true)) {
188+
throw new InvalidArgumentException(sprintf('Pushy notification priority must be one of "%s".', implode(', ', self::INTERRUPTION_LEVEL)));
189+
}
190+
191+
$this->options['notification']['interruption_level'] = $interruptionLevel;
192+
193+
return $this;
194+
}
195+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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+
60+
if (!isset($options['to'])) {
61+
throw new InvalidArgumentException('Pushy notification requires declaration of receivers using "to" option.');
62+
}
63+
64+
$endpoint = sprintf('https://%s?api_key=%s', $this->getEndpoint(), $this->apiKey);
65+
$response = $this->client->request('POST', $endpoint, [
66+
'headers' => [
67+
'Accept' => 'application/json',
68+
'Content-Type' => 'application/json',
69+
],
70+
'json' => array_filter($options),
71+
]);
72+
73+
try {
74+
$statusCode = $response->getStatusCode();
75+
} catch (TransportExceptionInterface $e) {
76+
throw new TransportException('Could not reach the remote Pushy server.', $response, 0, $e);
77+
}
78+
79+
if (200 !== $statusCode) {
80+
throw new TransportException(sprintf('Unable to send the Pushy push notification: "%s".', $response->getContent(false)), $response);
81+
}
82+
83+
$result = $response->toArray(false);
84+
85+
if (!isset($result['id'])) {
86+
throw new TransportException(sprintf('Unable to find the message ID within the Pushy response: "%s".', $response->getContent(false)), $response);
87+
}
88+
89+
$sentMessage = new SentMessage($message, (string) $this);
90+
$sentMessage->setMessageId($result['id']);
91+
92+
return $sentMessage;
93+
}
94+
}
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