-
Notifications
You must be signed in to change notification settings - Fork 67
Expand file tree
/
Copy pathWebPushClient.php
More file actions
146 lines (132 loc) Β· 3.83 KB
/
Copy pathWebPushClient.php
File metadata and controls
146 lines (132 loc) Β· 3.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Notifications;
use OCA\Notifications\Vendor\Base64Url\Base64Url;
use OCA\Notifications\Vendor\Minishlink\WebPush\Subscription;
use OCA\Notifications\Vendor\Minishlink\WebPush\Utils;
use OCA\Notifications\Vendor\Minishlink\WebPush\VAPID;
use OCA\Notifications\Vendor\Minishlink\WebPush\WebPush;
use OCP\AppFramework\Services\IAppConfig;
class WebPushClient {
private WebPush $client;
/** @psalm-var array{publicKey: string, privateKey: string, subject: string} */
private array $vapid;
public function __construct(
protected IAppConfig $appConfig,
) {
$this->vapid = $this->getVapid();
}
public static function isValidP256dh(string $key): bool {
if (!preg_match('/^[A-Za-z0-9_-]{87}=*$/', $key)) {
return false;
}
try {
Utils::unserializePublicKey(Base64Url::decode($key));
} catch (\InvalidArgumentException) {
return false;
}
return true;
}
public static function isValidAuth(string $auth): bool {
if (!preg_match('/^[A-Za-z0-9_-]{22}=*$/', $auth)) {
return false;
}
try {
$a = Base64Url::decode($auth);
} catch (\InvalidArgumentException) {
return false;
}
return strlen($a) === 16;
}
private function getClient(): WebPush {
if (isset($this->client)) {
return $this->client;
}
$this->client = new WebPush(auth: ['VAPID' => $this->vapid]);
$this->client->setReuseVAPIDHeaders(true);
return $this->client;
}
/**
* @return array
* @psalm-return array{publicKey: string, privateKey: string, subject: string}
*/
private function getVapid(): array {
// Do not use lazy for now
try {
$publicKey = $this->appConfig->getAppValueString('webpush_vapid_pubkey');
$privateKey = $this->appConfig->getAppValueString('webpush_vapid_privkey');
} catch (\Throwable) {
// Decryption failed (e.g. mismatched instance secret), regenerate keys
$publicKey = '';
$privateKey = '';
}
if ($publicKey === '' || $privateKey === '') {
/** @var array{publicKey: string, privateKey: string} $vapid */
$vapid = VAPID::createVapidKeys();
$this->appConfig->setAppValueString(
'webpush_vapid_pubkey',
$vapid['publicKey']
);
$this->appConfig->setAppValueString(
'webpush_vapid_privkey',
$vapid['privateKey'],
sensitive: true
);
} else {
$vapid = [
'publicKey' => $publicKey,
'privateKey' => $privateKey,
];
}
$vapid['subject'] = 'https://nextcloud.com/contact/';
return $vapid;
}
/**
* @return string
*/
public function getVapidPublicKey(): string {
return $this->vapid['publicKey'];
}
/**
* Send one notification - blocking (should be avoided most of the time)
*/
public function notify(string $endpoint, string $uaPublicKey, string $auth, string $body): void {
$c = $this->getClient();
$c->queueNotification(
new Subscription($endpoint, $uaPublicKey, $auth, 'aes128gcm'),
$body
);
// the callback could be defined by the caller
// For the moment, it is used during registration only - no need to catch 404 &co
// as the registration isn't activated
$callback = function ($r): void {
};
$c->flushPooled($callback);
}
/**
* Queue one notification. [flush] needs to be called to actually send the notifications
* @throws \ErrorException
*/
public function enqueue(string $endpoint, string $uaPublicKey, string $auth, string $body, string $urgency = 'normal'): void {
$c = $this->getClient();
$c->queueNotification(
new Subscription($endpoint, $uaPublicKey, $auth, 'aes128gcm'),
$body,
options: [
'urgency' => $urgency
]
);
}
/**
* @param callable $callback
* @psalm-param $callback callable(MessageSentReport): void
*/
public function flush(callable $callback): void {
$c = $this->getClient();
$c->flushPooled($callback);
}
}