@@ -310,6 +310,47 @@ protected function notify_using_webpush(): void
310310 */
311311 public function mark_notifications ($ notification_type_id , $ item_id , $ user_id , $ time = false , $ mark_read = true )
312312 {
313+ // Send dismiss push messages BEFORE deleting to close the browser notifications
314+ // This is called by the notification manager when phpBB marks notifications as read
315+ // (e.g., viewing a PM, viewing a topic, clicking "mark all read", etc.)
316+ if ($ notification_type_id !== false && $ item_id !== false && $ user_id !== false )
317+ {
318+ // When item_id and user_id are specific, send dismiss for each notification
319+ // Arrays are typically same-length parallel arrays or single notification type with specific item
320+ $ type_ids = is_array ($ notification_type_id ) ? $ notification_type_id : [$ notification_type_id ];
321+ $ item_ids = is_array ($ item_id ) ? $ item_id : [$ item_id ];
322+ $ user_ids = is_array ($ user_id ) ? $ user_id : [$ user_id ];
323+
324+ // Most common case: single notification (single type, item, user)
325+ if (count ($ type_ids ) === 1 && count ($ item_ids ) === 1 && count ($ user_ids ) === 1 )
326+ {
327+ $ this ->dismiss_using_webpush ($ type_ids [0 ], $ item_ids [0 ], $ user_ids [0 ]);
328+ }
329+ // Parallel arrays case: matching length arrays
330+ else if (count ($ type_ids ) === count ($ item_ids ) && count ($ item_ids ) === count ($ user_ids ))
331+ {
332+ for ($ i = 0 , $ iMax = count ($ type_ids ); $ i < $ iMax ; $ i ++)
333+ {
334+ $ this ->dismiss_using_webpush ($ type_ids [$ i ], $ item_ids [$ i ], $ user_ids [$ i ]);
335+ }
336+ }
337+ // Mixed case: iterate combinations (rare but handle it)
338+ else
339+ {
340+ foreach ($ type_ids as $ type )
341+ {
342+ foreach ($ item_ids as $ iid )
343+ {
344+ foreach ($ user_ids as $ uid )
345+ {
346+ $ this ->dismiss_using_webpush ($ type , $ iid , $ uid );
347+ }
348+ }
349+ }
350+ }
351+ }
352+
353+ // Delete the notifications from our table
313354 $ sql = 'DELETE FROM ' . $ this ->notification_webpush_table . '
314355 WHERE ' . ($ notification_type_id !== false ? $ this ->db ->sql_in_set ('notification_type_id ' , is_array ($ notification_type_id ) ? $ notification_type_id : [$ notification_type_id ]) : '1=1 ' ) .
315356 ($ user_id !== false ? ' AND ' . $ this ->db ->sql_in_set ('user_id ' , $ user_id ) : '' ) .
@@ -322,6 +363,24 @@ public function mark_notifications($notification_type_id, $item_id, $user_id, $t
322363 */
323364 public function mark_notifications_by_parent ($ notification_type_id , $ item_parent_id , $ user_id , $ time = false , $ mark_read = true )
324365 {
366+ // Send dismiss push messages BEFORE deleting
367+ // Query needed because service worker uses item_id (not item_parent_id) to match notification tags
368+ if ($ notification_type_id !== false && $ user_id !== false && $ item_parent_id !== false )
369+ {
370+ $ sql = 'SELECT notification_type_id, item_id, user_id
371+ FROM ' . $ this ->notification_webpush_table . '
372+ WHERE ' . $ this ->db ->sql_in_set ('notification_type_id ' , is_array ($ notification_type_id ) ? $ notification_type_id : [$ notification_type_id ]) .
373+ ' AND ' . $ this ->db ->sql_in_set ('user_id ' , is_array ($ user_id ) ? $ user_id : [$ user_id ]) .
374+ ' AND ' . $ this ->db ->sql_in_set ('item_parent_id ' , is_array ($ item_parent_id ) ? $ item_parent_id : [$ item_parent_id ], false , true );
375+ $ result = $ this ->db ->sql_query ($ sql );
376+ while ($ row = $ this ->db ->sql_fetchrow ($ result ))
377+ {
378+ $ this ->dismiss_using_webpush ($ row ['notification_type_id ' ], $ row ['item_id ' ], $ row ['user_id ' ]);
379+ }
380+ $ this ->db ->sql_freeresult ($ result );
381+ }
382+
383+ // Delete the notifications from our table
325384 $ sql = 'DELETE FROM ' . $ this ->notification_webpush_table . '
326385 WHERE ' . ($ notification_type_id !== false ? $ this ->db ->sql_in_set ('notification_type_id ' , is_array ($ notification_type_id ) ? $ notification_type_id : [$ notification_type_id ]) : '1=1 ' ) .
327386 ($ user_id !== false ? ' AND ' . $ this ->db ->sql_in_set ('user_id ' , $ user_id ) : '' ) .
@@ -493,4 +552,75 @@ protected function set_endpoint_padding(\Minishlink\WebPush\WebPush $web_push, s
493552 }
494553 }
495554 }
555+
556+ /**
557+ * Send dismiss message via Web Push to close a browser notification
558+ *
559+ * @param int $notification_type_id Notification type ID
560+ * @param int $item_id Item ID
561+ * @param int $user_id User ID
562+ * @return void
563+ */
564+ protected function dismiss_using_webpush (int $ notification_type_id , int $ item_id , int $ user_id ): void
565+ {
566+ // Get user subscriptions
567+ $ user_subscription_map = $ this ->get_user_subscription_map ([$ user_id ]);
568+ $ user_subscriptions = $ user_subscription_map [$ user_id ] ?? [];
569+
570+ if (empty ($ user_subscriptions ))
571+ {
572+ return ;
573+ }
574+
575+ $ auth = [
576+ 'VAPID ' => [
577+ 'subject ' => generate_board_url (false ),
578+ 'publicKey ' => $ this ->config ['wpn_webpush_vapid_public ' ],
579+ 'privateKey ' => $ this ->config ['wpn_webpush_vapid_private ' ],
580+ ],
581+ ];
582+
583+ $ web_push = new \Minishlink \WebPush \WebPush ($ auth );
584+
585+ // Create dismiss message
586+ $ data = [
587+ 'action ' => 'dismiss ' ,
588+ 'notifications ' => [[
589+ 'type_id ' => $ notification_type_id ,
590+ 'item_id ' => $ item_id ,
591+ ]],
592+ ];
593+ $ json_data = json_encode ($ data );
594+
595+ // Send dismiss message to all user's subscriptions
596+ foreach ($ user_subscriptions as $ subscription )
597+ {
598+ try
599+ {
600+ $ this ->set_endpoint_padding ($ web_push , $ subscription ['endpoint ' ]);
601+ $ push_subscription = Subscription::create ([
602+ 'endpoint ' => $ subscription ['endpoint ' ],
603+ 'keys ' => [
604+ 'p256dh ' => $ subscription ['p256dh ' ],
605+ 'auth ' => $ subscription ['auth ' ],
606+ ],
607+ ]);
608+ $ web_push ->queueNotification ($ push_subscription , $ json_data );
609+ }
610+ catch (\ErrorException $ exception )
611+ {
612+ // Ignore - dismiss is best-effort
613+ }
614+ }
615+
616+ // Flush and ignore any errors - dismiss messages are best-effort
617+ try
618+ {
619+ $ web_push ->flush ();
620+ }
621+ catch (\ErrorException $ exception )
622+ {
623+ // Ignore errors
624+ }
625+ }
496626}
0 commit comments