Skip to content

feat(sideload): optional BYO FCM wake for offline proactive - #703

Open
ophirhan wants to merge 7 commits into
Codename-11:devfrom
ophirhan:feat/sideload-byo-fcm
Open

ophirhan wants to merge 7 commits into
Codename-11:devfrom
ophirhan:feat/sideload-byo-fcm

Conversation

@ophirhan

Copy link
Copy Markdown
Contributor

Summary

Optional bring-your-own Firebase Cloud Messaging wake path for sideload installs: when proactive delivery has no live Relay WebSocket subscriber, the host sends a high-priority data-only FCM wake so the phone can reconnect and drain the offline buffer. Full message text stays on Relay — FCM never carries the service account or the full body.

Google Play builds keep a no-op push controller. Host Firebase Admin credentials never ship in the APK.

Changes

  • Android (sideload): runtime Firebase client config (paste or pick google-services.json fields), token registration via POST /push/token, HermesFcmMessagingService for type=hermes_wake, Settings → Threads → Push wake (BYO FCM) with optional message-preview toggle.
  • Android (Google Play): no-op FcmPushControllerFactory so Play stays free of Firebase Messaging deps.
  • Plugin: fcm_sender.py (install/status/enable CLI, default ~/.hermes/secrets/fcm-sa.json, HTTP v1 send), token store, offline on_queued_wake hook from proactive channel, POST /push/token (+ clear) on the relay.
  • Dashboard: Push wake settings surface for host service-account install/enable/clear; transport allowlist for /push/token.
  • Secure Link: /relay/{tail:.*} HTTP reverse-proxy so phone HTTP (including push token register) works behind pinned TLS, not only bare relay.
  • Tests: plugin unit coverage for sender + push token HTTP; Android parser unit tests; CHANGELOG Unreleased notes.

Verification

  • python -m unittest plugin.tests.test_fcm_sender plugin.tests.test_push_token -v — 13 OK
  • ./gradlew :app:testSideloadDebugUnitTest --tests com.hermesandroid.relay.data.FcmClientConfigParserTest — BUILD SUCCESSFUL
  • On-device dogfood (physical sideload, 2026-10-10):
    • Host FCM status: configured=true, enabled=true, device_tokens=1
    • Force-stop app → relay clients=0
    • POST /phone/message → delivered=false, queued=true, message_id=b94439d755b3, buffered=1
    • Direct send_wake → FCM HTTP v1 200
    • Phone showed wake notification(s); reconnect drained full proactive text from Relay buffer
  • ./gradlew lint / full Android CI: deferred to GitHub Actions on this PR head

Screenshots

UI surface: Settings → Power tools → Threads → Push wake (BYO FCM) (sideload). No Play UI change.

Device proof: offline force-stop → wake banner → buffer drain confirmed on-device (operator dogfood). Screenshots can be attached in a follow-up comment if useful; no hosts/tokens/pins in images.

Compatibility / risk

  • Sideload-only Firebase Messaging dependency and manifest service; Play flavor unchanged (no-op).
  • Host-only Admin service account (RELAY_FCM_* / default secrets path). Disabled unless explicitly enabled after install.
  • Live WSS subscribers still get direct proactive delivery (delivered=true); FCM is offline-wake only.
  • Secure Link HTTP catch-all is required for token register behind pinned TLS; bare relay path remains session-auth 401 without a token.
  • Rollback: disable host FCM flag and/or omit sideload client config; Play builds unaffected.

Lineage / contributor credit

  • Source PR(s): N/A
  • Attribution preserved by: N/A

Checklist

  • Target branch is dev, unless this is a dev → main release PR or a focused production-tag hotfix PR to main
  • Scope is focused and related issues/PRs are linked
  • Android changes: lint and focused tests ran, or rationale is listed above
  • Translation changes: locale validation/review ran, or N/A is listed above
  • Server/plugin changes: focused tests ran, or N/A/rationale is listed above
  • Desktop changes: build/tests ran, or N/A/rationale is listed above
  • Docs/site changes: build or link/route checks ran, or N/A/rationale is listed above
  • UI changes were tested on a relevant device/emulator/desktop surface, or the missing proof is stated above
  • Commit messages follow Conventional Commits
  • CHANGELOG.md is updated for user-visible changes, or N/A is listed above
  • Public writing hygiene checked: no secrets, private infrastructure, personal names, or AI/process narration
  • Salvaged/replacement work links source PRs and preserves contributor authorship, or N/A is listed above

@hermes-relay-triage hermes-relay-triage Bot added area:android Kotlin app area:dashboard plugin/dashboard React UI area:plugin plugin/ Python relay + tools needs-maintainer-review Automated triage exhausted; needs a human labels Oct 10, 2026
@hermes-relay-triage

Copy link
Copy Markdown

🤖 Hermes-Relay automated PR intake

Thanks for contributing the optional sideload push-wake path! The PR targets dev, includes all template sections, reports exact focused Python/Android test commands and results, and describes physical-device wake/buffer-drain testing, compatibility, and rollback. Android lint/full CI is explicitly deferred; checks are still in progress.

To round out the evidence:

  • Attach sanitized screenshots or video of the Android and dashboard settings, with device/Android version and browser/viewport details. The wake test is described, but visual proof and dashboard UI verification aren't provided yet.
  • Record locale validation/review for the changed translations, plus focused dashboard API and Secure Link proxy test commands/results, or explicitly state what hasn't been run.

A maintainer will review the Android, dashboard, and plugin scope, including the credential and Secure Link boundaries; this automated intake does not assess implementation correctness.

@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Review candidate unavailable

The build for PR #703 head f44a2478f89d completed with cancelled and did not publish a candidate bundle.

View workflow run

Sideload-only Firebase Messaging with runtime FirebaseOptions from Settings
(no google-services.json bake-in). Phone registers token via POST /push/token;
relay stores tokens and sends high-priority data wakes when proactive queues
offline. Service account stays on the Mac (RELAY_FCM_SERVICE_ACCOUNT_JSON).
googlePlay flavor remains a no-op controller.
Host: auto-discover ~/.hermes/secrets/fcm-sa.json, Dashboard Settings →
Push wake upload/enable/clear, CLI install|status on fcm_sender, and
.env merge for status. Phone: message preview default + hide toggle,
google-services file picker, clearer Mac-vs-phone copy. Secure Link
/relay/* reverse-proxy + transport allowlist for push/token so register
works behind pinned TLS.
Avoid PEM-shaped / PRIVATE KEY placeholders in unit fixtures so
public-repo hygiene-scan stays green without implying live material.
Replace service-account private_key unit fixtures with inert placeholders
so branch hygiene-scan does not treat test material as publishable secrets.
@ophirhan
ophirhan force-pushed the feat/sideload-byo-fcm branch from f44a247 to 14b1064 Compare October 18, 2026 04:39
@ophirhan

Copy link
Copy Markdown
Contributor Author

Rebased onto current dev (force-with-lease). Hygiene-scan clean.

Previous head was ~26 commits behind; merge conflicts cleared by rebase. Android + plugin checks re-running on the new tip.

Ready for another look when CI settles. Happy to address anything remaining on the BYO FCM / offline wake path.

…rings

Catch-all /relay/* must not proxy operator paths or mutate /relay/health
(security tests expect 404/405). Add missing FCM hide/load strings to
locale catalogs and refresh localization-status hashes.
@ophirhan

Copy link
Copy Markdown
Contributor Author

Follow-up on current dev tip:

  • Secure Link /relay/* HTTP catch-all is now allowlisted (phone surfaces only). Operator paths like /relay/security stay 404; non-GET /relay/health returns 405 (was 502 via proxy).
  • Android locale catalogs: filled missing FCM hide/load strings + refreshed localization-status hashes (translation catalog check).

Local smoke: GET health 200; mutating health 405; forbidden tails 404; /relay/push/token still proxies.

…M strings

Focused plugin test hung reusing one aiohttp Response across mocked
_proxy_http calls. Android lint failed on LocalContext.getString in
ProactiveSettingsScreen; switch to LocalResources.
@ophirhan

Copy link
Copy Markdown
Contributor Author

CI follow-up on 629c0e18:

  1. plugin focused tests — test_health_describes_all_fixed_namespaces timed out because the _proxy_http mock returned the same aiohttp.web.Response instance on every call; TestClient hung after the first use. Mock now builds a fresh Response per call.
  2. Android Lint — LocalContextGetResourceValueCall in ProactiveSettingsScreen FCM import paths; switched string lookups to LocalResources.current (same pattern as other screens).

Local: SecureProxyRouteTests + security suite green; hygiene-scan clean vs upstream/dev.

@ophirhan

Copy link
Copy Markdown
Contributor Author

CI is green on 629c0e18 (Required + Android build/lint/test + plugin focused/syntax + dashboard).

Ready for review/merge when you have bandwidth.

Includes:

  • optional BYO FCM wake (sideload)
  • Secure Link /relay/* reverse-proxy allowlist + health 405 on mutates
  • FCM locale catalog fill
  • plugin test mock: fresh aiohttp.web.Response per call (no shared Response hang)
  • Compose: LocalResources for FCM strings

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:android Kotlin app area:dashboard plugin/dashboard React UI area:plugin plugin/ Python relay + tools needs-maintainer-review Automated triage exhausted; needs a human

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant