@@ -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