Skip to content

Commit 4067a44

Browse files
Merge pull request #1429 from nextcloud/redo/939/group-delete-pushes
Group delete pushes
2 parents 9990277 + b6fd592 commit 4067a44

File tree

3 files changed

+120
-27
lines changed

3 files changed

+120
-27
lines changed

lib/App.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public function markProcessed(INotification $notification): void {
8282
}
8383
foreach ($deleted as $user => $notifications) {
8484
foreach ($notifications as $data) {
85-
$this->push->pushDeleteToDevice((string) $user, $data['id'], $data['app']);
85+
$this->push->pushDeleteToDevice((string) $user, [$data['id']], $data['app']);
8686
}
8787
}
8888
if (!$isAlreadyDeferring) {

lib/Controller/EndpointController.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ public function deleteNotification(int $id): DataResponse {
185185
$deleted = $this->handler->deleteById($id, $this->getCurrentUser(), $notification);
186186

187187
if ($deleted) {
188-
$this->push->pushDeleteToDevice($this->getCurrentUser(), $id, $notification->getApp());
188+
$this->push->pushDeleteToDevice($this->getCurrentUser(), [$id], $notification->getApp());
189189
}
190190
} catch (NotificationNotFoundException $e) {
191191
}
@@ -207,7 +207,7 @@ public function deleteAllNotifications(): DataResponse {
207207

208208
$deletedSomething = $this->handler->deleteByUser($this->getCurrentUser());
209209
if ($deletedSomething) {
210-
$this->push->pushDeleteToDevice($this->getCurrentUser(), 0);
210+
$this->push->pushDeleteToDevice($this->getCurrentUser(), null);
211211
}
212212

213213
if ($shouldFlush) {

lib/Push.php

Lines changed: 117 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -70,21 +70,38 @@ class Push {
7070
protected $log;
7171
/** @var OutputInterface */
7272
protected $output;
73-
/** @var array */
73+
/**
74+
* @var array
75+
* @psalm-var array<string, list<string>>
76+
*/
7477
protected $payloadsToSend = [];
7578

7679
/** @var bool */
7780
protected $deferPreparing = false;
7881
/** @var bool */
7982
protected $deferPayloads = false;
80-
/** @var array[] */
83+
/**
84+
* @var array[] $userId => $appId => $notificationIds
85+
* @psalm-var array<string|int, array<string, list<int>>>
86+
*/
8187
protected $deletesToPush = [];
88+
/**
89+
* @var bool[] $userId => true
90+
* @psalm-var array<string|int, bool>
91+
*/
92+
protected $deleteAllsToPush = [];
8293
/** @var INotification[] */
8394
protected $notificationsToPush = [];
8495

85-
/** @var null[]|IUserStatus[] */
96+
/**
97+
* @var ?IUserStatus[]
98+
* @psalm-var array<string, ?IUserStatus>
99+
*/
86100
protected $userStatuses = [];
87-
/** @var array[] */
101+
/**
102+
* @var array[]
103+
* @psalm-var array<string, list<array{id: int, uid: string, token: int, deviceidentifier: string, devicepublickey: string, devicepublickeyhash: string, pushtokenhash: string, proxyserver: string, apptype: string}>>
104+
*/
88105
protected $userDevices = [];
89106
/** @var string[] */
90107
protected $loadDevicesForUsers = [];
@@ -170,9 +187,24 @@ public function flushPayloads(): void {
170187
$this->notificationsToPush = [];
171188
}
172189

190+
if (!empty($this->deleteAllsToPush)) {
191+
foreach ($this->deleteAllsToPush as $userId => $bool) {
192+
$this->pushDeleteToDevice((string) $userId, null);
193+
}
194+
$this->deleteAllsToPush = [];
195+
}
196+
173197
if (!empty($this->deletesToPush)) {
174-
foreach ($this->deletesToPush as $id => $data) {
175-
$this->pushDeleteToDevice($data['userId'], $id, $data['app']);
198+
foreach ($this->deletesToPush as $userId => $data) {
199+
foreach ($data as $client => $notificationIds) {
200+
if ($client === 'talk') {
201+
$this->pushDeleteToDevice((string) $userId, $notificationIds, $client);
202+
} else {
203+
foreach ($notificationIds as $notificationId) {
204+
$this->pushDeleteToDevice((string) $userId, [$notificationId], $client);
205+
}
206+
}
207+
}
176208
}
177209
$this->deletesToPush = [];
178210
}
@@ -181,6 +213,13 @@ public function flushPayloads(): void {
181213
$this->sendNotificationsToProxies();
182214
}
183215

216+
/**
217+
* @param array $devices
218+
* @psalm-param $devices list<array{id: int, uid: string, token: int, deviceidentifier: string, devicepublickey: string, devicepublickeyhash: string, pushtokenhash: string, proxyserver: string, apptype: string}>
219+
* @param string $app
220+
* @return array
221+
* @psalm-return list<array{id: int, uid: string, token: int, deviceidentifier: string, devicepublickey: string, devicepublickeyhash: string, pushtokenhash: string, proxyserver: string, apptype: string}>
222+
*/
184223
public function filterDeviceList(array $devices, string $app): array {
185224
$isTalkNotification = \in_array($app, ['spreed', 'talk', 'admin_notification_talk'], true);
186225

@@ -310,17 +349,47 @@ public function pushToDevice(int $id, INotification $notification, ?OutputInterf
310349
}
311350
}
312351

313-
public function pushDeleteToDevice(string $userId, int $notificationId, string $app = ''): void {
352+
/**
353+
* @param string $userId
354+
* @param ?int[] $notificationIds
355+
* @param string $app
356+
*/
357+
public function pushDeleteToDevice(string $userId, ?array $notificationIds, string $app = ''): void {
314358
if (!$this->config->getSystemValueBool('has_internet_connection', true)) {
315359
return;
316360
}
317361

318362
if ($this->deferPreparing) {
319-
$this->deletesToPush[$notificationId] = ['userId' => $userId, 'app' => $app];
363+
if ($notificationIds === null) {
364+
$this->deleteAllsToPush[$userId] = true;
365+
if (isset($this->deletesToPush[$userId])) {
366+
unset($this->deletesToPush[$userId]);
367+
}
368+
} else {
369+
if (isset($this->deleteAllsToPush[$userId])) {
370+
return;
371+
}
372+
373+
$isTalkNotification = \in_array($app, ['spreed', 'talk', 'admin_notification_talk'], true);
374+
$clientGroup = $isTalkNotification ? 'talk' : 'files';
375+
376+
if (!isset($this->deletesToPush[$userId])) {
377+
$this->deletesToPush[$userId] = [];
378+
}
379+
if (!isset($this->deletesToPush[$userId][$clientGroup])) {
380+
$this->deletesToPush[$userId][$clientGroup] = [];
381+
}
382+
383+
foreach ($notificationIds as $notificationId) {
384+
$this->deletesToPush[$userId][$clientGroup][] = $notificationId;
385+
}
386+
}
320387
$this->loadDevicesForUsers[] = $userId;
321388
return;
322389
}
323390

391+
$deleteAll = $notificationIds === null;
392+
324393
$user = $this->createFakeUserObject($userId);
325394

326395
if (!array_key_exists($userId, $this->userDevices)) {
@@ -330,8 +399,8 @@ public function pushDeleteToDevice(string $userId, int $notificationId, string $
330399
$devices = $this->userDevices[$userId];
331400
}
332401

333-
if ($notificationId !== 0 && $app !== '') {
334-
// Only filter when it's not a single delete
402+
if (!$deleteAll) {
403+
// Only filter when it's not delete-all
335404
$devices = $this->filterDeviceList($devices, $app);
336405
}
337406
if (empty($devices)) {
@@ -350,13 +419,23 @@ public function pushDeleteToDevice(string $userId, int $notificationId, string $
350419
}
351420

352421
try {
353-
$payload = json_encode($this->encryptAndSignDelete($userKey, $device, $notificationId));
354-
355422
$proxyServer = rtrim($device['proxyserver'], '/');
356423
if (!isset($this->payloadsToSend[$proxyServer])) {
357424
$this->payloadsToSend[$proxyServer] = [];
358425
}
359-
$this->payloadsToSend[$proxyServer][] = $payload;
426+
427+
if ($deleteAll) {
428+
$data = $this->encryptAndSignDelete($userKey, $device, null);
429+
$this->payloadsToSend[$proxyServer][] = json_encode($data['payload']);
430+
} else {
431+
$temp = $notificationIds;
432+
433+
while (!empty($temp)) {
434+
$data = $this->encryptAndSignDelete($userKey, $device, $temp);
435+
$temp = $data['remaining'];
436+
$this->payloadsToSend[$proxyServer][] = json_encode($data['payload']);
437+
}
438+
}
360439
} catch (\InvalidArgumentException $e) {
361440
// Failed to encrypt message for device: public key is invalid
362441
$this->deletePushToken($device['token']);
@@ -500,6 +579,7 @@ protected function validateToken(int $tokenId, int $maxAge): bool {
500579
* @param INotification $notification
501580
* @param bool $isTalkNotification
502581
* @return array
582+
* @psalm-return array{deviceIdentifier: string, pushTokenHash: string, subject: string, signature: string, priority: string, type: string}
503583
* @throws InvalidTokenException
504584
* @throws \InvalidArgumentException
505585
*/
@@ -562,21 +642,29 @@ protected function encryptAndSign(Key $userKey, array $device, int $id, INotific
562642
/**
563643
* @param Key $userKey
564644
* @param array $device
565-
* @param int $id
645+
* @param ?int[] $ids
566646
* @return array
647+
* @psalm-return array{remaining: list<int>, payload: array{deviceIdentifier: string, pushTokenHash: string, subject: string, signature: string, priority: string, type: string}}
567648
* @throws InvalidTokenException
568649
* @throws \InvalidArgumentException
569650
*/
570-
protected function encryptAndSignDelete(Key $userKey, array $device, int $id): array {
571-
if ($id === 0) {
651+
protected function encryptAndSignDelete(Key $userKey, array $device, ?array $ids): array {
652+
$remainingIds = [];
653+
if ($ids === null) {
572654
$data = [
573655
'delete-all' => true,
574656
];
575-
} else {
657+
} elseif (count($ids) === 1) {
576658
$data = [
577-
'nid' => $id,
659+
'nid' => array_pop($ids),
578660
'delete' => true,
579661
];
662+
} else {
663+
$remainingIds = array_splice($ids, 10);
664+
$data = [
665+
'nids' => $ids,
666+
'delete-multiple' => true,
667+
];
580668
}
581669

582670
if (!openssl_public_encrypt(json_encode($data), $encryptedSubject, $device['devicepublickey'], OPENSSL_PKCS1_PADDING)) {
@@ -589,18 +677,22 @@ protected function encryptAndSignDelete(Key $userKey, array $device, int $id): a
589677
$base64Signature = base64_encode($signature);
590678

591679
return [
592-
'deviceIdentifier' => $device['deviceidentifier'],
593-
'pushTokenHash' => $device['pushtokenhash'],
594-
'subject' => $base64EncryptedSubject,
595-
'signature' => $base64Signature,
596-
'priority' => 'normal',
597-
'type' => 'background',
680+
'remaining' => $remainingIds,
681+
'payload' => [
682+
'deviceIdentifier' => $device['deviceidentifier'],
683+
'pushTokenHash' => $device['pushtokenhash'],
684+
'subject' => $base64EncryptedSubject,
685+
'signature' => $base64Signature,
686+
'priority' => 'normal',
687+
'type' => 'background',
688+
]
598689
];
599690
}
600691

601692
/**
602693
* @param string $uid
603694
* @return array[]
695+
* @psalm-return list<array{id: int, uid: string, token: int, deviceidentifier: string, devicepublickey: string, devicepublickeyhash: string, pushtokenhash: string, proxyserver: string, apptype: string}>
604696
*/
605697
protected function getDevicesForUser(string $uid): array {
606698
$query = $this->db->getQueryBuilder();
@@ -618,6 +710,7 @@ protected function getDevicesForUser(string $uid): array {
618710
/**
619711
* @param string[] $userIds
620712
* @return array[]
713+
* @psalm-return array<string, list<array{id: int, uid: string, token: int, deviceidentifier: string, devicepublickey: string, devicepublickeyhash: string, pushtokenhash: string, proxyserver: string, apptype: string}>>
621714
*/
622715
protected function getDevicesForUsers(array $userIds): array {
623716
$query = $this->db->getQueryBuilder();

0 commit comments

Comments
 (0)