Skip to content

Commit 393c05a

Browse files
feat: add Declarative Web Push message support (#216)
* refactor: implement message interface * feat: add Declarative Web Push message support * test: add tests for Declarative Web Push messages * docs: update readme * fix: styling * docs: update readme * fix: styling * fix: styling * chore: require minishlink/web-push v9.0.3 as minimum * doc: add note about declarative webpush --------- Co-authored-by: Joost de Bruijn <joostdebruijn1993@gmail.com>
1 parent 88292ee commit 393c05a

14 files changed

+664
-12
lines changed

README.md

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ __These keys must be safely stored and should not change.__
7272

7373
## Usage
7474

75-
Now you can use the channel in your `via()` method inside the notification and send a web push notification:
75+
Now you can use the channel in your `via()` method inside the notification and send a generic web push notification:
7676

7777
```php
7878
use Illuminate\Notifications\Notification;
@@ -107,6 +107,49 @@ class AccountApproved extends Notification
107107
}
108108
```
109109

110+
### Declarative Web Push messages
111+
112+
> **Note:** The specification for Declarative Web Push messages is still evolving and may change in the future. Browser support for this functionality is currently limited and may vary across platforms.
113+
114+
This package also supports [Declarative Web Push messages](https://www.w3.org/TR/push-api/#declarative-push-message), which aim to reduce the complexity of using push on the web in general and address some challenges of generic web push notifications like privacy concerns & battery life on mobile by making a client-side service worker optional while remaining fully backwards compatible:
115+
116+
```php
117+
use Illuminate\Notifications\Notification;
118+
use NotificationChannels\WebPush\DeclarativeWebPushMessage;
119+
use NotificationChannels\WebPush\WebPushChannel;
120+
121+
class AccountApproved extends Notification
122+
{
123+
public function via($notifiable)
124+
{
125+
return [WebPushChannel::class];
126+
}
127+
128+
public function toWebPush($notifiable, $notification)
129+
{
130+
return (new DeclarativeWebPushMessage)
131+
->title('Approved!')
132+
->icon('/approved-icon.png')
133+
->body('Your account was approved!')
134+
->action('View account', 'view_account', 'https://myapp.com/accounts')
135+
->navigate('https://myapp.com');
136+
// ->data(['id' => $notification->id])
137+
// ->badge()
138+
// ->dir()
139+
// ->image()
140+
// ->lang()
141+
// ->mutable()
142+
// ->renotify()
143+
// ->requireInteraction()
144+
// ->silent()
145+
// ->tag()
146+
// ->timestamp()
147+
// ->vibrate()
148+
// ->options(['TTL' => 1000, 'contentType' => 'application/json'])
149+
}
150+
}
151+
```
152+
110153
You can find the available options [here](https://github.com/web-push-libs/web-push-php#notifications-and-default-options).
111154

112155
### Save/Update Subscriptions

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"php": "^8.2",
2121
"illuminate/notifications": "^11.0|^12.0",
2222
"illuminate/support": "^11.0|^12.0",
23-
"minishlink/web-push": "^9.0"
23+
"minishlink/web-push": "^9.0.3"
2424
},
2525
"require-dev": {
2626
"larastan/larastan": "^3.1",

src/DeclarativeWebPushMessage.php

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
<?php
2+
3+
namespace NotificationChannels\WebPush;
4+
5+
use Illuminate\Support\Arr;
6+
use NotificationChannels\WebPush\Exceptions\MessageValidationFailed;
7+
8+
/**
9+
* @implements \Illuminate\Contracts\Support\Arrayable<string, mixed>
10+
*
11+
* @link https://www.w3.org/TR/push-api/#members
12+
*/
13+
class DeclarativeWebPushMessage implements WebPushMessageInterface
14+
{
15+
protected string $title;
16+
17+
/**
18+
* @var array<array-key, array{'title': string, 'action': string, 'navigate': string, 'icon'?: string}>
19+
*/
20+
protected array $actions = [];
21+
22+
protected string $badge;
23+
24+
protected string $body;
25+
26+
protected string $dir;
27+
28+
protected string $icon;
29+
30+
protected string $image;
31+
32+
protected string $lang;
33+
34+
protected bool $mutable;
35+
36+
protected string $navigate;
37+
38+
protected bool $renotify;
39+
40+
protected bool $requireInteraction;
41+
42+
protected bool $silent;
43+
44+
protected string $tag;
45+
46+
protected int $timestamp;
47+
48+
/**
49+
* @var array<int>
50+
*/
51+
protected array $vibrate;
52+
53+
protected mixed $data;
54+
55+
/**
56+
* @var array<string, mixed>
57+
*/
58+
protected array $options = [
59+
'contentType' => 'application/json',
60+
];
61+
62+
/**
63+
* Set the notification title.
64+
*
65+
* @return $this
66+
*/
67+
public function title(string $value): static
68+
{
69+
$this->title = $value;
70+
71+
return $this;
72+
}
73+
74+
/**
75+
* Add a notification action.
76+
*
77+
* @return $this
78+
*/
79+
public function action(string $title, string $action, string $navigate, ?string $icon = null): static
80+
{
81+
$this->actions[] = array_filter(['title' => $title, 'action' => $action, 'navigate' => $navigate, 'icon' => $icon]);
82+
83+
return $this;
84+
}
85+
86+
/**
87+
* Set the notification badge.
88+
*
89+
* @return $this
90+
*/
91+
public function badge(string $value): static
92+
{
93+
$this->badge = $value;
94+
95+
return $this;
96+
}
97+
98+
/**
99+
* Set the notification body.
100+
*
101+
* @return $this
102+
*/
103+
public function body(string $value): static
104+
{
105+
$this->body = $value;
106+
107+
return $this;
108+
}
109+
110+
/**
111+
* Set the notification direction.
112+
*
113+
* @return $this
114+
*/
115+
public function dir(string $value): static
116+
{
117+
$this->dir = $value;
118+
119+
return $this;
120+
}
121+
122+
/**
123+
* Set the notification icon url.
124+
*
125+
* @return $this
126+
*/
127+
public function icon(string $value): static
128+
{
129+
$this->icon = $value;
130+
131+
return $this;
132+
}
133+
134+
/**
135+
* Set the notification image url.
136+
*
137+
* @return $this
138+
*/
139+
public function image(string $value): static
140+
{
141+
$this->image = $value;
142+
143+
return $this;
144+
}
145+
146+
/**
147+
* Set the notification language.
148+
*
149+
* @return $this
150+
*/
151+
public function lang(string $value): static
152+
{
153+
$this->lang = $value;
154+
155+
return $this;
156+
}
157+
158+
/**
159+
* @return $this
160+
*/
161+
public function mutable(bool $value = true): static
162+
{
163+
$this->mutable = $value;
164+
165+
return $this;
166+
}
167+
168+
/**
169+
* Set the navigation target upon activation.
170+
*
171+
* @return $this
172+
*/
173+
public function navigate(string $value): static
174+
{
175+
$this->navigate = $value;
176+
177+
return $this;
178+
}
179+
180+
/**
181+
* @return $this
182+
*/
183+
public function renotify(bool $value = true): static
184+
{
185+
$this->renotify = $value;
186+
187+
return $this;
188+
}
189+
190+
/**
191+
* @return $this
192+
*/
193+
public function requireInteraction(bool $value = true): static
194+
{
195+
$this->requireInteraction = $value;
196+
197+
return $this;
198+
}
199+
200+
/**
201+
* @return $this
202+
*/
203+
public function silent(bool $value = true): static
204+
{
205+
$this->silent = $value;
206+
207+
return $this;
208+
}
209+
210+
/**
211+
* Set the notification tag.
212+
*
213+
* @return $this
214+
*/
215+
public function tag(string $value): static
216+
{
217+
$this->tag = $value;
218+
219+
return $this;
220+
}
221+
222+
/**
223+
* Set the timestamp associated with the notification.
224+
*
225+
* @return $this
226+
*/
227+
public function timestamp(int $value): static
228+
{
229+
$this->timestamp = $value;
230+
231+
return $this;
232+
}
233+
234+
/**
235+
* Set the notification vibration pattern.
236+
*
237+
* @param array<int> $value
238+
* @return $this
239+
*/
240+
public function vibrate(array $value): static
241+
{
242+
$this->vibrate = $value;
243+
244+
return $this;
245+
}
246+
247+
/**
248+
* Set the notification arbitrary data.
249+
*
250+
* @return $this
251+
*/
252+
public function data(mixed $value): static
253+
{
254+
$this->data = $value;
255+
256+
return $this;
257+
}
258+
259+
/**
260+
* Set the notification options.
261+
*
262+
* @link https://github.com/web-push-libs/web-push-php#notifications-and-default-options
263+
*
264+
* @param array<string, mixed> $value
265+
* @return $this
266+
*/
267+
public function options(array $value): static
268+
{
269+
$this->options = $value;
270+
271+
return $this;
272+
}
273+
274+
/**
275+
* Get the notification options.
276+
*
277+
* @return array<string, mixed>
278+
*/
279+
public function getOptions(): array
280+
{
281+
return $this->options;
282+
}
283+
284+
/**
285+
* Get an array representation of the message.
286+
*
287+
* @return array<string, mixed>
288+
*/
289+
public function toArray(): array
290+
{
291+
if (empty($this->title)) {
292+
throw MessageValidationFailed::titleRequired();
293+
}
294+
295+
if (empty($this->navigate)) {
296+
throw MessageValidationFailed::navigateRequired();
297+
}
298+
299+
return Arr::whereNotNull([
300+
'web_push' => 8030,
301+
'notification' => Arr::except(array_filter(get_object_vars($this)), ['mutable', 'options']),
302+
'mutable' => $this->mutable ?? null,
303+
]);
304+
}
305+
}

src/Events/NotificationFailed.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use Illuminate\Queue\SerializesModels;
66
use Minishlink\WebPush\MessageSentReport;
77
use NotificationChannels\WebPush\PushSubscription;
8-
use NotificationChannels\WebPush\WebPushMessage;
8+
use NotificationChannels\WebPush\WebPushMessageInterface;
99

1010
class NotificationFailed
1111
{
@@ -16,7 +16,7 @@ class NotificationFailed
1616
*
1717
* @return void
1818
*/
19-
public function __construct(public MessageSentReport $report, public PushSubscription $subscription, public WebPushMessage $message)
19+
public function __construct(public MessageSentReport $report, public PushSubscription $subscription, public WebPushMessageInterface $message)
2020
{
2121
//
2222
}

0 commit comments

Comments
 (0)