Add Android UnifiedPush support#1307
Conversation
|
| Filename | Overview |
|---|---|
| packages/app/modules/paseo-unified-push/android/src/main/java/sh/paseo/unifiedpush/PaseoUnifiedPushModule.kt | New Expo Kotlin module for UnifiedPush; getDistributors calls getPackageName/getDistributorIcon without catching NameNotFoundException, which can crash the entire registration flow. |
| packages/app/modules/paseo-unified-push/android/src/main/java/sh/paseo/unifiedpush/PaseoUnifiedPushService.kt | New background push service; shows system notifications in foreground (when eventSink is set) and uses a truncating toInt() for notification IDs that can collide or go negative. |
| packages/server/src/server/push/push-service.ts | Expanded to route Expo and Web Push subscriptions to separate transports; VAPID config, endpoint SSRF validation, and 404/410 cleanup are all handled correctly. |
| packages/server/src/server/push/token-store.ts | Clean migration from a flat token set to a typed subscription list; legacy file format parsed and migrated on first load. |
| packages/server/src/server/push/endpoint-security.ts | New SSRF guard for Web Push endpoints; IPv4-mapped IPv6 handling was flagged in a prior review thread. |
| packages/app/src/push/unified-push-shared.ts | Platform-independent helpers for UnifiedPush event handling, subscription normalization, and payload parsing; well-tested with pure functions. |
| packages/app/src/push/unified-push.android.ts | Android-specific registration and event subscription wiring; lacks a try-catch around getDistributors. |
| packages/app/src/hooks/use-push-token-registration.ts | Android UnifiedPush gated on unifiedPushVapidPublicKey; re-registration on every serverInfo update was flagged in a prior review thread. |
| packages/server/src/server/session.ts | Two new message handlers added cleanly; incoming messages are schema-validated before dispatch. |
| packages/server/src/server/websocket-server.ts | VAPID keypair loaded at startup and threaded into push sender; server_info now always emits capabilities.pushNotifications.webPushVapidPublicKey. |
| packages/protocol/src/messages.ts | New Zod schemas and types for Web Push subscription RPC and server capabilities; inlined into the existing discriminated union correctly. |
Sequence Diagram
sequenceDiagram
participant App as Android App (JS)
participant Module as PaseoUnifiedPushModule (Kotlin)
participant Service as PaseoUnifiedPushService (Kotlin)
participant UP as UnifiedPush Distributor
participant Server as Paseo Server
participant Web as Web Push Gateway
App->>Server: WebSocket connect
Server-->>App: "server_info { features.unifiedPush, webPushVapidPublicKey }"
App->>Module: getDistributors()
Module-->>App: distributors list
App->>Module: saveDistributor + registerDevice(vapidPublicKey)
Module->>UP: UnifiedPush.register(vapidPublicKey)
UP-->>Service: onNewEndpoint(endpoint, pubKey, auth)
Service-->>Module: eventSink registered event
Module-->>App: "message { action: registered }"
App->>Server: push.subscription.register.request
Server-->>App: push.subscription.register.response
Note over Server,Web: On agent completion
Server->>Web: Web Push notification (VAPID-signed)
Web->>UP: Deliver to device
UP->>Service: onMessage(decrypted payload)
Service-->>Module: eventSink message event
Module-->>App: "message { action: message }"
Reviews (2): Last reviewed commit: "fix: address unified push review feedbac..." | Re-trigger Greptile
| "name" to if (isInternal) "Internal FCM Distributor" else getPackageName(distributor), | ||
| "icon" to getDistributorIcon(distributor), |
There was a problem hiding this comment.
Both
getPackageName and getDistributorIcon call PackageManager APIs that throw PackageManager.NameNotFoundException when a package isn't found — but neither is wrapped in a try-catch. They're both called inside the getDistributors lambda, so any race between enumeration and lookup (package uninstalled between the two calls) would propagate the exception up through the Expo Function block and surface as a JS error in registerUnifiedPush, causing the entire UnifiedPush registration flow to fail silently.
| "name" to if (isInternal) "Internal FCM Distributor" else getPackageName(distributor), | |
| "icon" to getDistributorIcon(distributor), | |
| "name" to if (isInternal) "Internal FCM Distributor" else runCatching { getPackageName(distributor) }.getOrNull(), | |
| "icon" to runCatching { getDistributorIcon(distributor) }.getOrNull(), |
Linked issue
Closes #1292
Type of change
What does this PR do
Adds Android UnifiedPush support without depending on
expo-unified-push.server_info.features.unifiedPushand the advertised VAPID public key while preserving Expo Push for iOS and unsupported hosts.How did you verify it
Ran the focused UnifiedPush and push notification test set:
Observed: 9 test files passed, 202 tests passed.
Ran required repository checks:
Observed: all commands exited successfully.
npm run lintreported 0 warnings and 0 errors.npm run format:checkreported all matched files use the correct format.Checked that the external package was not introduced:
rg -n "expo-unified-push" package-lock.json packages docsObserved: no matches.
Checklist
npm run typecheckpassesnpm run lintpassesnpm run formatran (Biome)