Findings from the 2026-08-07 "push notifications not received" investigation. The admin Diagnostics tab (user lookup + test push) has already shipped; the items below are the underlying fixes still to do.
- Issue:
client/mobile/push-notifications.js(setupRegistrationHandler) callsMeteor.call("deviceDetails.storeFCMToken", ...), but that method does not exist on the server — it fails withMethod not found [404]on every app start. Client-writable token methods were deliberately removed (seetests/deviceManagement.js"FCM token lookups are no longer exposed as Meteor methods"). - Impact: When FCM rotates a token (reinstall, restore, OS update), the server keeps sending to the stale token and the device silently stops receiving pushes.
- Fix: Add a secure, authenticated token-refresh method that requires the caller's
deviceUUID(and validates ownership/approved status), then call it from theregistrationhandler. Remove or guard the deadstoreFCMTokencall.
- Issue:
registerDeviceDetails(utils/api/deviceDetails.js) never records when a device was registered. Only the user document's top-levelcreatedAt(first device) exists;devices[].lastUpdatedis overwritten by every later mutation. - Impact: Admins cannot tell when a device was added — useful for diagnostics and security review (e.g. spotting unexpected device additions).
- Fix:
- Set
registeredAt: new Date()in both device-creation paths (first-device insert and the$pushfor additional devices). - Preserve
registeredAtin the update-existing-device branch (re-registration should not reset it — or decide explicitly that it should). - Expose
registeredAtin/api/admin/devices/listand/api/admin/diagnostics/user, and render it in the Devices and Diagnostics tabs. - Backfill note: existing devices have no value — display "unknown" or backfill from
lastUpdated.
- Set
- Issue:
configurePushNotificationspassesforegroundandpriorityin the iOS init block; the installed plugin logsSettings: Invalid option keyfor both. - Impact: Harmless, but produces misleading noise in device diagnostics logs.
- Fix: Remove the unsupported keys from the
iosinit options.
- Issue: The user's diagnostics log shared on 2026-08-07 contained a full, live FCM registration token.
- Fix: Treat that token as exposed — force the device to re-register (rotates the token) once item 1 lands. Consider redacting FCM tokens in the diagnostics log capture going forward.
- Issue: Resuming the app fired
notificationHistory.updateStatus(..., "timeout")~1,400 times in 6 seconds for an already-approved Aug 7 notification (QJSAZbvA8LSfP5ZKy). Chain:handleAppResume(client/mobile/src/ui/hooks/useNotificationHandler.js) restored a stalependingNotificationfrom localStorage — it was never cleared after the notification was approved.ActionsModalopened with the ancientcreatedAt;calculateInitialTime() <= 0triggershandleTimeout()inside the effect.onTimeOut(handleTimeoutinLandingPage.jsx) is not memoized, and the client method stub's Minimongo write re-renders the page, so the effect re-runs on every render → repeatedupdateStatuscalls until the first server ack closes the modal.
- Impact: DDP method flood, and the notification's
approvestatus was overwritten totimeout(audit trail corrupted). - Fixes:
- Clear
pendingNotificationfrom localStorage whenever the notification is resolved (approve/reject/timeout), and validatecreatedAton resume — discard expired entries instead of opening the modal. - Wrap
handleTimeoutinuseCallbackinLandingPage.jsx; add a one-shot guard (ref) inActionsModalsohandleTimeoutfires at most once per open. - Server-side:
notificationHistory.updateStatus(utils/api/notificationHistory.js) must requirethis.userId, verify ownership, and only allowpending → timeouttransitions (never overwrite a handled status). Add a DDP rate limit.
- Clear
- Issue:
notificationHistory.updateStatus,getLastIdByUser,getByUser, andgetByStatushave nothis.userId/ ownership checks — any connected client can read or rewrite any user's notification history by guessing/knowing ids. - Fix: Require authentication, scope reads to
this.userId, and validate status transitions server-side.