Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
2efd214
feat(webpush): Add, update, activate and delete webpush subscription
p1gp1g Nov 18, 2025
8b87232
feat(webpush): Registration with new endpoint, p256dh or auth is 'cre…
p1gp1g Nov 19, 2025
a5b09aa
feat(webpush): Return error if too many appTypes are supplied
p1gp1g Nov 19, 2025
d9ffa95
feat(webpush): Send activation token during registration
p1gp1g Nov 19, 2025
ee564f7
feat(webpush): Prepare webpush requests
p1gp1g Nov 20, 2025
80c7390
feat(webpush): Send web push requests
p1gp1g Nov 20, 2025
094eccb
feat(webpush): Delete expired web push subscriptions
p1gp1g Nov 21, 2025
edb1d58
feat(webpush): Send web push delete notifs
p1gp1g Nov 21, 2025
fb3c960
feat(webpush): Fix tests after 'Delete expired web push subscriptions'
p1gp1g Nov 21, 2025
942c3d4
feat(webpush): Add Urgency to web push notifs
p1gp1g Nov 21, 2025
9481d4a
feat(webpush): Add support for 429 status code with web push
p1gp1g Nov 21, 2025
1423ea8
feat(webpush): Fix composer for webpush
p1gp1g Nov 21, 2025
20a9090
feat(webpush): Fix urgency
p1gp1g Nov 21, 2025
0621e1b
feat(webpush): Add support for VAPID
p1gp1g Nov 21, 2025
1b5cf32
feat(webpush): Fix missing $deleteAll
p1gp1g Nov 24, 2025
429b50f
feat(webpush): Add API endpoint to get the VAPID pubkey
p1gp1g Nov 24, 2025
4a69214
feat(webpush): Add webpush capability
p1gp1g Nov 24, 2025
676fb5c
feat(webpush): Fix apptypes
p1gp1g Nov 24, 2025
0c0f2b2
feat(webpush): Add tests for webpush
p1gp1g Nov 25, 2025
0aa109c
feat(webpush): Get apptypes as a string
p1gp1g Nov 26, 2025
e0c6bb0
feat(webpush): Fix tests for apptypes as string
p1gp1g Nov 27, 2025
aec0a1b
feat(webpush): Include WebPushController in ApplicationTest
p1gp1g Nov 27, 2025
424adbc
feat(webpush): Allow multiple delete with webpush
p1gp1g Nov 28, 2025
6363888
feat(webpush): Lint
p1gp1g Dec 2, 2025
1182172
feat(webpush): Add composer lock
p1gp1g Dec 2, 2025
0027d1b
feat(webpush): Fix OpenAPI
p1gp1g Dec 3, 2025
dc2fb55
feat(webpush): Small fixes
p1gp1g Dec 3, 2025
4ef6030
feat(webpush): Fix VAPID auth
p1gp1g Dec 4, 2025
c3df6e4
ci: Try to fix psalm for now
nickvergessen Dec 5, 2025
5fa1b47
ci(cs): Ignore lib/Vendor/ dir from coding standards
nickvergessen Dec 5, 2025
294025e
feat(webpush): Reduce max size for endpoint
p1gp1g Dec 5, 2025
e2c0181
feat(webpush): Lint
p1gp1g Dec 6, 2025
180cf92
feat(webpush): Update query count
p1gp1g Dec 8, 2025
de4a2bf
feat(webpush): Keep vendor dir
p1gp1g Dec 8, 2025
1c94e08
feat(webpush): Fix psalm CI
p1gp1g Dec 8, 2025
c6b6bc8
feat(webpush): Lint
p1gp1g Dec 9, 2025
c57b446
feat(webpush): Init VAPID in constructor
p1gp1g Dec 9, 2025
1b14795
Rename appTypes
p1gp1g Jan 9, 2026
05fca1b
Store VAPID pubkey in plaintext
p1gp1g Jan 9, 2026
e73a982
Fix typo in comment
p1gp1g Jan 9, 2026
3c1e695
Add WebPushClient to Push constructor
p1gp1g Jan 9, 2026
36a6877
Apply suggestions from code review
p1gp1g Jan 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(webpush): Fix psalm CI
Signed-off-by: sim <[email protected]>
  • Loading branch information
p1gp1g committed Dec 9, 2025
commit 1c94e084b32b080af636d21c0047edbc423d30b7
6 changes: 3 additions & 3 deletions lib/Push.php
Original file line number Diff line number Diff line change
Expand Up @@ -641,8 +641,8 @@ protected function webPushCallback(MessageSentReport $report): void {
if ($report->isSubscriptionExpired()) {
$this->deleteWebPushTokenByEndpoint($report->getEndpoint());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This permanently removes all the device entries, even if the subscription is continued in the future?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It removes a single push subscription, endpoints are unique

} elseif ($report->getResponse()?->getStatusCode() === 429) {
$retryAfter = $report->getResponse()?->getHeader('Retry-After');
$this->cache->set('wp.' . $report->getEndpoint(), true, $retryAfter ?? 60);
$retryAfter = (int)($report->getResponse()?->getHeader('Retry-After')[0] ?? "60");
$this->cache->set('wp.' . $report->getEndpoint(), true, $retryAfter);
}
}

Expand Down Expand Up @@ -851,7 +851,7 @@ protected function encodeNotif(int $id, INotification $notification, int $maxLen
/**
* @param ?int[] $ids
* @return array
* @psalm-return array{remaining: list<int>, data: array{delete-all: bool, nid: int, delete: bool, nids: int[], delete-multiple: int}}
* @psalm-return array{data: array{'delete-all'?: true, 'delete-multiple'?: true, delete?: true, nid?: int, nids?: int[]}, remaining: int[]}
*/
protected function encodeDeleteNotifs(?array $ids): array {
$remainingIds = [];
Expand Down
15 changes: 8 additions & 7 deletions lib/WebPushClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

class WebPushClient {
private WebPush $client;
/** @psalm-var array{publicKey: string, privateKey: string} */
/** @psalm-var array{publicKey: string, privateKey: string, subject: string} */
private array $vapid;

public function __construct(
Expand Down Expand Up @@ -62,10 +62,10 @@ private function getClient(): WebPush {

/**
* @return array
* @psalm-return array{publicKey: string, privateKey: string}
* @psalm-return array{publicKey: string, privateKey: string, subject: string}
*/
private function getVapid(): array {
if (!empty($this->vapid) && array_key_exists('publicKey', $this->vapid) && array_key_exists('privateKey', $this->vapid)) {
if (array_key_exists('publicKey', $this->vapid) && array_key_exists('privateKey', $this->vapid) && array_key_exists('subject', $this->vapid) ) {
return $this->vapid;
}
$publicKey = $this->appConfig->getValueString(
Expand All @@ -79,7 +79,8 @@ private function getVapid(): array {
lazy: true
);
if ($publicKey === '' || $privateKey === '') {
$this->vapid = VAPID::createVapidKeys();
/** @var array{publicKey: string, privateKey: string} $vapid */
$vapid = VAPID::createVapidKeys();
$this->appConfig->setValueString(
Application::APP_ID,
'webpush_vapid_pubkey',
Expand All @@ -95,13 +96,13 @@ private function getVapid(): array {
sensitive: true
);
} else {
$this->vapid = [
$vapid = [
'publicKey' => $publicKey,
'privateKey' => $privateKey,
];
}
$this->vapid['subject'] = 'https://github.com/nextcloud/notifications';
return $this->vapid;
$vapid['subject'] = 'https://github.com/nextcloud/notifications';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we have freedom of choice and it doesn't need to be a valid link, I'd prefer something not referring to a Microsoft product:

Suggested change
$vapid['subject'] = 'https://github.com/nextcloud/notifications';
$vapid['subject'] = 'https://nextcloud.com/webpush-notifications';

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be a valid address so a push server may contact if there is an issue. https://nextcloud.com/contact/ ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's better then, but at the same time there is nothing we as Nextcloud can do, if a Nextcloud instance goes rogue and each Nextcloud server itself doesn't have a public contact page.

return $this->vapid = $vapid;
}

/**
Expand Down
29 changes: 24 additions & 5 deletions tests/psalm-baseline.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="6.12.0@cf420941d061a57050b6c468ef2c778faf40aee2">
<files psalm-version="6.13.1@1e3b7f0a8ab32b23197b91107adc0a7ed8a05b51">
<file src="lib/Controller/PushController.php">
<UndefinedClass>
<code><![CDATA[$this->identityProof]]></code>
Expand All @@ -9,29 +9,48 @@
<code><![CDATA[protected]]></code>
</UndefinedClass>
</file>
<file src="lib/Controller/WebPushController.php">
<UndefinedClass>
<code><![CDATA[$this->tokenProvider]]></code>
<code><![CDATA[$this->tokenProvider]]></code>
<code><![CDATA[$this->tokenProvider]]></code>
<code><![CDATA[Uuid]]></code>
<code><![CDATA[protected]]></code>
<code><![CDATA[protected]]></code>
</UndefinedClass>
</file>
<file src="lib/Push.php">
<LessSpecificReturnStatement>
<code><![CDATA[$devices]]></code>
<code><![CDATA[$devices]]></code>
<code><![CDATA[$otherDevices]]></code>
<code><![CDATA[$otherDevices]]></code>
<code><![CDATA[$talkDevices]]></code>
<code><![CDATA[array_filter($devices, function ($device) use ($app) {
$apptypes = explode(',', $device['apptypes']);
return $device['activated'] && (\in_array($app, $apptypes)
|| (\in_array('all', $apptypes) && !\in_array('-' . $app, $apptypes)));
})]]></code>
</LessSpecificReturnStatement>
<MoreSpecificReturnType>
<code><![CDATA[list<array{id: int, uid: string, token: int, deviceidentifier: string, devicepublickey: string, devicepublickeyhash: string, pushtokenhash: string, proxyserver: string, apptype: string}>]]></code>
<code><![CDATA[list<array{id: int, uid: string, token: int, deviceidentifier: string, devicepublickey: string, devicepublickeyhash: string, pushtokenhash: string, proxyserver: string, apptype: string}>]]></code>
<code><![CDATA[list<array{id: int, uid: string, token: int, endpoint: string, p256dh: string, auth: string, apptypes: string, activated: bool, activation_token: string}>]]></code>
<code><![CDATA[list<array{id: int, uid: string, token: int, endpoint: string, p256dh: string, auth: string, apptypes: string, activated: bool, activation_token: string}>]]></code>
</MoreSpecificReturnType>
<UndefinedClass>
<code><![CDATA[$e]]></code>
<code><![CDATA[$e]]></code>
<code><![CDATA[$this->keyManager]]></code>
<code><![CDATA[$this->keyManager]]></code>
<code><![CDATA[$this->tokenProvider]]></code>
<code><![CDATA[ClientException]]></code>
<code><![CDATA[Key]]></code>
<code><![CDATA[Key]]></code>
<code><![CDATA[ServerException]]></code>
<code><![CDATA[protected]]></code>
<code><![CDATA[protected]]></code>
</UndefinedClass>
</file>
<file src="lib/WebPushClient.php">
<RedundantPropertyInitializationCheck>
<code><![CDATA[isset($this->client)]]></code>
</RedundantPropertyInitializationCheck>
</file>
</files>