@@ -222,11 +222,24 @@ class DefaultPushNotificationService implements IPushNotificationService {
222222
223223 if (userDeviceTokens.isNotEmpty) {
224224 // Add the send operation to the list of futures.
225+ // The result of this future will contain information about which
226+ // tokens succeeded and which failed.
225227 sendFutures.add (
226- client.sendBulkNotifications (
227- deviceTokens: userDeviceTokens,
228- payload: notification.payload,
229- ),
228+ client
229+ .sendBulkNotifications (
230+ deviceTokens: userDeviceTokens,
231+ payload: notification.payload,
232+ )
233+ .then ((result) {
234+ // After the send completes, trigger the cleanup process for
235+ // any failed tokens. This is a fire-and-forget operation.
236+ unawaited (
237+ _cleanupInvalidDevices (
238+ result.failedTokens,
239+ primaryProvider,
240+ ),
241+ );
242+ }),
230243 );
231244 }
232245 } catch (e, s) {
@@ -255,4 +268,50 @@ class DefaultPushNotificationService implements IPushNotificationService {
255268 );
256269 }
257270 }
271+
272+ /// Deletes device registrations associated with a list of invalid tokens.
273+ ///
274+ /// This method is called after a push notification send operation to prune
275+ /// the database of tokens that the provider has identified as unregistered
276+ /// or invalid (e.g., because the app was uninstalled).
277+ ///
278+ /// - [invalidTokens] : A list of device tokens that failed to be delivered.
279+ /// - [provider] : The push notification provider that reported the tokens as
280+ /// invalid.
281+ Future <void > _cleanupInvalidDevices (
282+ List <String > invalidTokens,
283+ PushNotificationProvider provider,
284+ ) async {
285+ if (invalidTokens.isEmpty) {
286+ return ; // Nothing to clean up.
287+ }
288+
289+ _log.info (
290+ 'Cleaning up ${invalidTokens .length } invalid device tokens for provider "$provider ".' ,
291+ );
292+
293+ // Retrieve the list of devices that match the filter criteria.
294+ final devicesToDelete = await _pushNotificationDeviceRepository.readAll (
295+ filter: {
296+ 'providerTokens.${provider .name }' : {r'$in' : invalidTokens},
297+ },
298+ );
299+
300+ try {
301+ // Delete the devices one by one.
302+ // While this is less efficient than a bulk delete, it is necessary
303+ // as `DataRepository` does not have a `deleteAll` method.
304+ await Future .forEach <PushNotificationDevice >(devicesToDelete.items, (
305+ device,
306+ ) async {
307+ await _pushNotificationDeviceRepository.delete (id: device.id);
308+ });
309+
310+ _log.info ('Successfully cleaned up invalid device tokens.' );
311+ } catch (e, s) {
312+ _log.severe ('Failed to clean up invalid device tokens.' , e, s);
313+ // We log the error but do not rethrow, as this is a background
314+ // cleanup task and should not crash the main application flow.
315+ }
316+ }
258317}
0 commit comments