Took JefeHome from a Phase 1 ntfy-only notification app to a full smart home platform with FCM dual delivery, OAuth2 auth via Ory Hydra, Google Smart Home fulfillment, and a device registry. 26 files changed across Python server and Kotlin Android, Firebase project "CasaJefe" provisioned, and FCM push confirmed end-to-end on a Pixel 9 Pro.
Server: Four New Systems
The backend grew four major subsystems in one pass. Auth middleware replaces the old API key check with dual auth: Bearer token introspection against Ory Hydra's admin API, with X-API-Key fallback for CLI usage and a dev-mode pass-through when both are disabled. FCM provider slots into the existing NotificationProvider ABC alongside ntfy, using firebase-admin's multicast send with automatic cleanup of unregistered tokens. Device registry adds full CRUD on /devices with SQLite-backed models for devices, device states, and FCM tokens. Smart Home fulfillment handles all four Google intents (SYNC, QUERY, EXECUTE, DISCONNECT) on a single /smarthome endpoint, mapping commands like OnOff and BrightnessAbsolute to device state updates. A Home Graph API client handles Report State and Request Sync using the same Firebase service account.
Android: Auth, FCM, and Device Management
The Android app got OAuth2 via PKCE with Chrome Custom Tabs, an OkHttp AuthInterceptor that auto-attaches Bearer tokens and refreshes on 401, and a FirebaseMessagingService for push delivery. A new login screen gates the app with OAuth or API key entry. The bottom nav gained a Devices tab with a card-based device list, on/off toggles, and an add dialog with device type selection. Firebase BOM 33.7.0 and Chrome Custom Tabs (browser 1.8.0) joined the dependency graph.
Hydra Integration
Registered JefeHome as a public OAuth2 client in the existing Ory Hydra instance with authorization_code and refresh_token grants, PKCE support, and a com.jefehome://callback redirect URI. The Android deep link intent filter and OAuthManager handle the full PKCE flow. OAuth2 is feature-flagged off by default so the API key path works immediately while Hydra testing happens separately.
Build Adventures
The Android build surfaced the usual gauntlet: no Gradle wrapper (copied from FreeChat), missing gradle.properties with android.useAndroidX, build-tools 34 not installed in a read-only SDK (pinned to 36.0.0 instead), a Hilt DI cycle between AuthInterceptor and OAuthManager (solved with Lazy injection), and extended Material Icons not resolving (stripped down to base filled set). The server had a subtler bug: /smarthome was in PUBLIC_PATHS so auth middleware set user_id to None, while /devices used "dev-user" — meaning SYNC and EXECUTE couldn't find each other's devices. Caught it during integration testing and unified the auth path.
The Push Notification Saga
FCM token registration was the last mile. The FirebaseMessagingService.onNewToken() callback only fires on first token generation, not every launch. The initial build never registered because the app wasn't proactively fetching its token. Added a FcmTokenRegistrar singleton that grabs the token via FirebaseMessaging.getInstance().token on startup and POSTs it to /devices/register. Even then, the first install registered before login (so the server rejected it), requiring a force-stop and reopen after entering the API key. Notification permissions were off by default on the Pixel. Once enabled, FCM delivered successfully with message ID confirmation from the CasaJefe project.
What's Next
- Test OAuth2 login flow end-to-end through Kratos/Hydra
- Integrate with existing smart home devices (real hardware, not just the registry)
- Cloudflare tunnel for JefeHome + Google Actions Console setup for "Hey Google" control
- Commit the full Phase 2-3 changeset