Default push backend to ntfy; FCM opt-in with privacy warning (issue #10)
- IRIS_PUSH_BACKEND now defaults to ntfy (keeps push metadata on your own infrastructure); FCM is opt-in via IRIS_PUSH_BACKEND=fcm - build_push_backend(): ntfy for empty/unknown names, FCM only on explicit 'fcm' - gateway setup: warn when FCM is chosen (metadata routed via Google's servers) - README: privacy note + dedicated push section; new docs/playstore-listing.md with the FCM/ntfy privacy note for the Play Store listing - docs: 00/02/03/08/12/16 + setup.md updated to ntfy-default wording - tests: default-backend assertion updated (86/86 pass)
This commit is contained in:
1 parent
70282dfb65
commit
746d809d48
14 files changed
+136
-56
No files matched your search
@@ -8,6 +8,8 @@ Iris pairs with your running `hermes gateway` over a private WebSocket and gives
|
||||
|
||||
- **Native Hermes-Gateway integration** — your hermes → gateway → Iris app
|
||||
- **Absolute Privacy!** — everything stays on your own infrastructure
|
||||
(push: ntfy by default; FCM is opt-in and routes push metadata via Google —
|
||||
see [Push notifications](#push-notifications))
|
||||
- **No file limit**
|
||||
- **No character limit**
|
||||
- **Full markdown support** — tables, checkmarks, bold, inline code, code blocks + syntax highlighting…
|
||||
@@ -36,7 +38,22 @@ hermes-agent ──> hermes gateway ──(WebSocket :8790)──> Iris app (And
|
||||
- The app is a first-class hermes *messaging platform*, so everything the gateway
|
||||
already does just works: slash commands, cron delivery, `send_message` routing,
|
||||
coexistence with Telegram/Discord/etc.
|
||||
- Push notifications: FCM (primary) or ntfy (fallback).
|
||||
- Push notifications: ntfy (default) or FCM (opt-in).
|
||||
|
||||
## Push notifications
|
||||
|
||||
Push wakes a backgrounded/offline device; on reconnect the app syncs the
|
||||
outbox, so nothing is lost.
|
||||
|
||||
- **ntfy (default)** — push metadata stays on your own infrastructure
|
||||
(self-hosted ntfy recommended). This is the backend for truly private
|
||||
communication.
|
||||
- **FCM (opt-in, `IRIS_PUSH_BACKEND=fcm`)** — standard/reliable, but FCM push
|
||||
metadata (notification title, device token) is routed through **Google's
|
||||
servers**. If you want truly private communication, use ntfy instead.
|
||||
|
||||
Setup: [`docs/setup.md`](docs/setup.md) §4; details:
|
||||
[`docs/08-push.md`](docs/08-push.md).
|
||||
|
||||
## Build from source
|
||||
|
||||
@@ -99,7 +116,7 @@ Notes:
|
||||
- Remote access: Tailscale/WireGuard, or a reverse proxy with CA-signed WSS
|
||||
(`ANDROID_WS_CERT` / `ANDROID_WS_KEY`).
|
||||
|
||||
Full walkthrough, push setup (FCM/ntfy), and troubleshooting:
|
||||
Full walkthrough, push setup (ntfy/FCM), and troubleshooting:
|
||||
[`docs/setup.md`](docs/setup.md).
|
||||
|
||||
## Contributing
|
||||
|
||||
+1
-1
@@ -57,7 +57,7 @@ Everything in the feature checklist below.
|
||||
| Decision | Choice |
|
||||
|---|---|
|
||||
| Desktop app tech | **Compose Multiplatform** (shares Android code; "tweaked" for big screen) |
|
||||
| Push backend | **Both** — FCM primary, ntfy fallback (`IRIS_PUSH_BACKEND`) |
|
||||
| Push backend | **Both** — ntfy default, FCM optional (`IRIS_PUSH_BACKEND`) |
|
||||
| Media transport | **Over the WebSocket** (chunked binary frames; no extra Python deps) |
|
||||
| Phone default layout | **User-toggleable, single-pane default** (auto two-pane on large screens) |
|
||||
|
||||
|
||||
+2
-2
@@ -61,8 +61,8 @@ iris_x_hermes/
|
||||
- **`media.py`** — inbound chunked upload → `cache_*_from_bytes`; outbound
|
||||
`media.offer`/`media.pull` chunked streaming.
|
||||
- **`outbox.py`** — SQLite outbox per `chat_id` + monotonic sync cursor.
|
||||
- **`push.py`** — `PushBackend` interface; `FcmBackend` (httpx, FCM HTTP v1) and
|
||||
`NtfyBackend` (reuses hermes ntfy publish). Selected by `IRIS_PUSH_BACKEND`.
|
||||
- **`push.py`** — `PushBackend` interface; `NtfyBackend` (default) and
|
||||
`FcmBackend` (httpx, FCM HTTP v1). Selected by `IRIS_PUSH_BACKEND`.
|
||||
- **`pairing.py`** — token generation/verification (constant-time), device
|
||||
registry (SQLite), QR payload.
|
||||
- **`search.py`** — FTS5 query bridge over the hermes session store.
|
||||
|
||||
@@ -48,7 +48,7 @@ optional_env:
|
||||
prompt: "Allow all devices? (true/false)"
|
||||
password: false
|
||||
- name: IRIS_PUSH_BACKEND
|
||||
description: "Push backend: fcm (default) or ntfy"
|
||||
description: "Push backend: ntfy (default, keeps metadata off Google) or fcm"
|
||||
prompt: "Push backend"
|
||||
password: false
|
||||
- name: IRIS_FCM_SERVICE_ACCOUNT
|
||||
|
||||
+7
-4
@@ -1,7 +1,10 @@
|
||||
# 08 — Push Notifications, Outbox & Sync
|
||||
|
||||
The gateway can't reach a sleeping phone directly. Push goes through a cloud
|
||||
relay. **Decision: FCM primary, ntfy fallback** (`IRIS_PUSH_BACKEND`).
|
||||
relay. **Decision: ntfy default, FCM optional** (`IRIS_PUSH_BACKEND`).
|
||||
Privacy: FCM push metadata (notification title, device token) is routed
|
||||
through Google's servers — for truly private communication use ntfy
|
||||
(self-hosted), which keeps everything on your own infrastructure.
|
||||
|
||||
## 8.1 When push fires
|
||||
|
||||
@@ -54,9 +57,9 @@ class PushBackend(Protocol):
|
||||
def configured(self) -> bool: ...
|
||||
```
|
||||
|
||||
Selected at adapter init by `IRIS_PUSH_BACKEND` (`fcm` default, `ntfy`).
|
||||
Selected at adapter init by `IRIS_PUSH_BACKEND` (`ntfy` default, `fcm`).
|
||||
|
||||
### 8.2.1 `FcmBackend` (primary)
|
||||
### 8.2.1 `FcmBackend` (optional; metadata via Google)
|
||||
|
||||
- **FCM HTTP v1 API** via `httpx` (core dep). Auth = Firebase **service
|
||||
account** (`IRIS_FCM_SERVICE_ACCOUNT` JSON path) → mint a short-lived
|
||||
@@ -74,7 +77,7 @@ Selected at adapter init by `IRIS_PUSH_BACKEND` (`fcm` default, `ntfy`).
|
||||
- Batch: FCM allows up to 500 tokens/message; we send per-device (1 user, few
|
||||
devices).
|
||||
|
||||
### 8.2.2 `NtfyBackend` (fallback, self-host friendly)
|
||||
### 8.2.2 `NtfyBackend` (default, self-host friendly)
|
||||
|
||||
- Reuses hermes's existing ntfy publish path (hermes ships an ntfy adapter).
|
||||
- Publish to `NTFY_TOPIC` on `NTFY_SERVER_URL` (default `https://ntfy.sh`) via
|
||||
|
||||
@@ -77,15 +77,16 @@ hermes --version # sanity
|
||||
4. The app's `FirebaseMessagingService` obtains the FCM token at runtime and
|
||||
registers it via `hello` / `fcm.register`.
|
||||
|
||||
> Skip Firebase → set `IRIS_PUSH_BACKEND=ntfy` and configure `NTFY_TOPIC` /
|
||||
> `NTFY_SERVER_URL` (self-host ntfy or use ntfy.sh). See `08-push.md`.
|
||||
> Skip Firebase → the default is already ntfy: leave `IRIS_PUSH_BACKEND` unset
|
||||
> (or set it to `ntfy`) and configure `NTFY_TOPIC` / `NTFY_SERVER_URL`
|
||||
> (self-host ntfy or use ntfy.sh). See `08-push.md`.
|
||||
|
||||
## 12.6 Environment variables (summary)
|
||||
|
||||
**Secrets (`~/.hermes/.env`):**
|
||||
```
|
||||
IRIS_TOKEN=<64-hex>
|
||||
IRIS_PUSH_BACKEND=fcm # or ntfy
|
||||
IRIS_PUSH_BACKEND=ntfy # default; fcm = opt-in (metadata via Google)
|
||||
IRIS_FCM_SERVICE_ACCOUNT=/path/to/service-account.json
|
||||
# IRIS_FCM_SERVER_KEY=<legacy key> # fallback if no service account
|
||||
# NTFY_TOPIC=iris-push # when ntfy
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
| # | Decision | Choice | Rationale |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | Desktop app tech | **Compose Multiplatform** | Desktop = "the Android app, tweaked"; share protocol/state/UI. |
|
||||
| 2 | Push backend | **Both — FCM primary, ntfy fallback** | FCM is standard/reliable; ntfy for self-hosters with no Firebase. `IRIS_PUSH_BACKEND`. |
|
||||
| 2 | Push backend | **Both — ntfy default, FCM optional** (issue #10: FCM metadata — title, device token — is routed via Google's servers, contradicting the privacy claim; ntfy keeps it on your own infrastructure) | `IRIS_PUSH_BACKEND`. |
|
||||
| 3 | Media transport | **Over the WebSocket** | One transport, zero new Python deps; chunked binary frames. |
|
||||
| 4 | Phone default layout | **User-toggleable, single-pane default** | App-like on phones; auto two-pane on large screens; desktop defaults two-pane. |
|
||||
|
||||
|
||||
+2
-1
@@ -30,7 +30,7 @@ top-to-bottom once, then use the numbered docs as a lookup while implementing.
|
||||
| 5 | [`05-streaming.md`](05-streaming.md) | Streaming / reasoning / tools / intermediate. |
|
||||
| 6 | [`06-channels-cron-search.md`](06-channels-cron-search.md) | Channels, threads, cron delivery, search. |
|
||||
| 7 | [`07-media.md`](07-media.md) | Media upload/download + playback. |
|
||||
| 8 | [`08-push.md`](08-push.md) | Push (FCM + ntfy), outbox, sync. |
|
||||
| 8 | [`08-push.md`](08-push.md) | Push (ntfy default + FCM optional), outbox, sync. |
|
||||
| 9 | [`09-pairing-security.md`](09-pairing-security.md) | Pairing, auth, security model. |
|
||||
| 10 | [`10-android-app.md`](10-android-app.md) | When building the Android app. |
|
||||
| 11 | [`11-desktop-app.md`](11-desktop-app.md) | When building the Desktop app. |
|
||||
@@ -47,6 +47,7 @@ Machine-readable / diagrams:
|
||||
|
||||
- [`protocol/frames.schema.json`](protocol/frames.schema.json) — wire-frame schema.
|
||||
- [`diagrams/architecture.mmd`](diagrams/architecture.mmd) — mermaid architecture.
|
||||
- [`playstore-listing.md`](playstore-listing.md) — Play Store listing text (incl. the FCM/ntfy privacy note).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Play Store Listing
|
||||
|
||||
Copy-paste text for the Play Console (and the F-Droid / sideload pages).
|
||||
Keep this file in sync with the README whenever the privacy story changes.
|
||||
|
||||
## Short description (≤ 80 chars)
|
||||
|
||||
Private chat for your personal AI agent — Android & desktop.
|
||||
|
||||
## Full description
|
||||
|
||||
Iris is a native chat app for your personal AI agent (hermes-agent):
|
||||
streaming replies, visible reasoning, structured tool activity, channels,
|
||||
threads, media, search — Telegram-quality, on your own infrastructure.
|
||||
|
||||
**Absolute Privacy!** — everything stays on your own infrastructure:
|
||||
|
||||
- Your gateway, your machine, your data. No cloud middleman for chat.
|
||||
- **Push notifications:** ntfy by default — push metadata stays on your own
|
||||
(self-hosted) ntfy server.
|
||||
- **FCM is opt-in** (`IRIS_PUSH_BACKEND=fcm`): standard and reliable, but FCM
|
||||
push metadata (notification title, device token) is routed through
|
||||
**Google's servers**. If you want truly private communication, use ntfy
|
||||
(self-hosted) instead — it is the default.
|
||||
|
||||
No file limit. No character limit. Full markdown + HTML artifact preview.
|
||||
All settings live in the app.
|
||||
|
||||
Runs on Android and desktop (Linux, macOS, Windows) from one shared codebase.
|
||||
|
||||
## Privacy note (for the "Data safety" section / FAQ)
|
||||
|
||||
Iris talks directly to your own hermes gateway over a private, token-authenticated
|
||||
connection. By default, push notifications use ntfy, which you can self-host so
|
||||
that push metadata never leaves your infrastructure. If you explicitly enable
|
||||
FCM, push metadata (notification title, device token) is sent via Google's FCM
|
||||
servers; chat content itself is not sent to Google — FCM only carries a short
|
||||
preview, and full content is fetched from your gateway over the authenticated
|
||||
connection. For truly private communication, use the default ntfy backend
|
||||
(self-hosted).
|
||||
+23
-19
@@ -39,7 +39,8 @@ What it does:
|
||||
- Generates `IRIS_TOKEN` (64 hex chars) if none exists and stores it in
|
||||
`~/.hermes/.env` (it prints the token once, at generation).
|
||||
- Prompts for the WS bind host (default `127.0.0.1`), port (default `8790`),
|
||||
and push backend (`fcm` or `ntfy`, default `fcm`).
|
||||
and push backend (`ntfy` or `fcm`, default `ntfy`); warns when `fcm` is
|
||||
chosen (push metadata via Google's servers).
|
||||
- Prints the pairing payload (a QR-encodable `iris://pair?host=…&port=…&token=…`
|
||||
string), a scannable QR of that payload, and the server URL
|
||||
(`ws://<host>:8790/ws`).
|
||||
@@ -100,26 +101,10 @@ Push wakes a backgrounded/offline device; on reconnect the app syncs the
|
||||
outbox, so nothing is lost. Push fires when the device is offline, plus for
|
||||
high-priority events (approvals, clarifies, cron) even when a device is live.
|
||||
|
||||
### FCM (default; needs a Firebase project)
|
||||
|
||||
1. Create a Firebase project (console.firebase.google.com) and add an Android
|
||||
app with the app's applicationId; download `google-services.json` into
|
||||
`app/androidApp/`.
|
||||
2. Create a service account (Project settings → Service accounts → Generate new
|
||||
private key) and store the JSON path in `~/.hermes/.env`:
|
||||
`IRIS_FCM_SERVICE_ACCOUNT=/path/to/service-account.json`.
|
||||
3. Keep `IRIS_PUSH_BACKEND=fcm` (the default).
|
||||
|
||||
Without a Firebase project the FCM path is **inert** (the app's FCM service
|
||||
does nothing) — use ntfy below, or add Firebase later.
|
||||
|
||||
**What you see:** system notifications for new messages when the app is
|
||||
backgrounded; tapping one deep-links to the chat.
|
||||
|
||||
### ntfy (zero-config fallback)
|
||||
### ntfy (default; zero-config)
|
||||
|
||||
```
|
||||
IRIS_PUSH_BACKEND=ntfy
|
||||
IRIS_PUSH_BACKEND=ntfy # the default — can be left unset
|
||||
```
|
||||
|
||||
- The device **generates its own topic** automatically (no `NTFY_TOPIC` needed);
|
||||
@@ -128,10 +113,29 @@ IRIS_PUSH_BACKEND=ntfy
|
||||
recommended** — the public ntfy.sh SSE endpoint is flaky (it has served its
|
||||
web UI instead of the stream), while a self-hosted instance gives reliable
|
||||
SSE. For a real trust boundary use a private topic + `NTFY_AUTH_TOKEN`.
|
||||
- **Privacy:** ntfy keeps push metadata (title, topic) on your own
|
||||
infrastructure — this is the backend for truly private communication.
|
||||
|
||||
**What you see:** a low-priority foreground "ntfy listener" notification while
|
||||
the app is off; incoming pushes trigger a silent sync.
|
||||
|
||||
### FCM (opt-in; needs a Firebase project)
|
||||
|
||||
> **Privacy note:** FCM push metadata (notification title, device token) is
|
||||
> routed through **Google's servers**. If you want truly private
|
||||
> communication, use ntfy (self-hosted) instead — it is the default.
|
||||
|
||||
1. Create a Firebase project (console.firebase.google.com) and add an Android
|
||||
app with the app's applicationId; download `google-services.json` into
|
||||
`app/androidApp/`.
|
||||
2. Create a service account (Project settings → Service accounts → Generate new
|
||||
private key) and store the JSON path in `~/.hermes/.env`:
|
||||
`IRIS_FCM_SERVICE_ACCOUNT=/path/to/service-account.json`.
|
||||
3. Set `IRIS_PUSH_BACKEND=fcm`.
|
||||
|
||||
Without a Firebase project the FCM path is **inert** (the app's FCM service
|
||||
does nothing) — use ntfy (the default), or add Firebase later.
|
||||
|
||||
## 5. Remote access
|
||||
|
||||
- **Tailscale / WireGuard (recommended):** the gateway gets a stable tailnet IP;
|
||||
|
||||
@@ -33,8 +33,8 @@ register the (delivery-validated) file in the media registry and emit
|
||||
``validate_media_delivery_path`` at pull time.
|
||||
|
||||
Milestone M5: push + offline. Frames with no live subscriber are parked in
|
||||
the outbox (M3) AND wake the device via the push backend (``push.py``: FCM
|
||||
HTTP v1 primary, ntfy fallback, selected by ``IRIS_PUSH_BACKEND``).
|
||||
the outbox (M3) AND wake the device via the push backend (``push.py``: ntfy
|
||||
default, FCM HTTP v1 as an option, selected by ``IRIS_PUSH_BACKEND``).
|
||||
``notification`` frames render in-app banners and mirror to push (channel
|
||||
events, cron deliveries, approvals, clarifies); high-priority kinds push even
|
||||
when a device is live. ``fcm.register`` rotates push tokens (registry + live
|
||||
@@ -526,7 +526,7 @@ DEFAULT_PORT = 8790
|
||||
DEFAULT_HTTP_PORT = 8791 # docs/19: HTTP fallback leg
|
||||
DEFAULT_HOME_CHANNEL = "default"
|
||||
DEFAULT_HOME_CHANNEL_NAME = "Default"
|
||||
DEFAULT_PUSH_BACKEND = "fcm"
|
||||
DEFAULT_PUSH_BACKEND = "ntfy"
|
||||
DEFAULT_OUTBOX_RETENTION_HOURS = 72
|
||||
DEFAULT_MAX_UPLOAD_BYTES = 100 * 1024 * 1024 # 100 MB
|
||||
|
||||
@@ -1201,10 +1201,17 @@ def interactive_setup() -> None:
|
||||
)
|
||||
save_env_value("IRIS_HTTP_PORT", str(_parse_port(port)))
|
||||
backend = prompt(
|
||||
"Push backend (fcm/ntfy)",
|
||||
"Push backend (ntfy/fcm)",
|
||||
default=get_env_value("IRIS_PUSH_BACKEND") or DEFAULT_PUSH_BACKEND,
|
||||
)
|
||||
save_env_value("IRIS_PUSH_BACKEND", (backend or DEFAULT_PUSH_BACKEND).strip().lower())
|
||||
backend = (backend or DEFAULT_PUSH_BACKEND).strip().lower()
|
||||
save_env_value("IRIS_PUSH_BACKEND", backend)
|
||||
if backend == "fcm":
|
||||
print_warning(
|
||||
"FCM push metadata (notification title, device token) is routed "
|
||||
"through Google's servers. For truly private communication use "
|
||||
"ntfy (self-hosted) instead."
|
||||
)
|
||||
|
||||
# Pairing payload for the app's Connect screen (manual entry + QR scan).
|
||||
# Advertise a routable host: a bind wildcard (0.0.0.0/127.0.0.1) is
|
||||
@@ -1346,7 +1353,7 @@ class IrisAdapter(BasePlatformAdapter):
|
||||
# /fast): picker_id -> pending state. In-memory only — a gateway
|
||||
# restart expires them (a stale picker.select is a no-op).
|
||||
self._pending_pickers: dict[str, dict] = {}
|
||||
# M5: push backend (FCM primary, ntfy fallback) + the throttle for
|
||||
# M5: push backend (ntfy default, FCM optional) + the throttle for
|
||||
# the outbox-prune banner.
|
||||
self._push: PushBackend = build_push_backend(
|
||||
self.push_backend,
|
||||
|
||||
@@ -38,7 +38,7 @@ optional_env:
|
||||
prompt: "Allow all devices? (true/false)"
|
||||
password: false
|
||||
- name: IRIS_PUSH_BACKEND
|
||||
description: "Push backend: fcm (default) or ntfy"
|
||||
description: "Push backend: ntfy (default, keeps metadata off Google) or fcm"
|
||||
prompt: "Push backend"
|
||||
password: false
|
||||
- name: IRIS_FCM_SERVICE_ACCOUNT
|
||||
|
||||
+21
-14
@@ -1,4 +1,4 @@
|
||||
"""Push backends: FCM (primary) + ntfy (fallback).
|
||||
"""Push backends: ntfy (default) + FCM (optional).
|
||||
|
||||
``PushBackend`` interface with two implementations:
|
||||
- ``FcmBackend``: FCM HTTP v1 via ``httpx`` + a Firebase service account
|
||||
@@ -8,7 +8,7 @@
|
||||
(default ``https://ntfy.sh``) via ``httpx``; the app's listener
|
||||
subscribes to the topic.
|
||||
|
||||
Selected by ``IRIS_PUSH_BACKEND`` (``fcm`` default, ``ntfy`` fallback).
|
||||
Selected by ``IRIS_PUSH_BACKEND`` (``ntfy`` default, ``fcm`` optional).
|
||||
Fired when a frame has no live subscriber; the data payload drives a silent
|
||||
sync on the device (docs/08-push.md).
|
||||
|
||||
@@ -33,7 +33,9 @@ import httpx
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
FCM_SCOPE = "https://www.googleapis.com/auth/firebase.messaging"
|
||||
FCM_TOKEN_URL = "https://oauth2.googleapis.com/token"
|
||||
# Not a secret: the well-known Google OAuth2 token endpoint.
|
||||
# pi-lens-ignore: S105
|
||||
FCM_TOKEN_URL = "https://oauth2.googleapis.com/token" # noqa: S105
|
||||
FCM_V1_SEND_URL = "https://fcm.googleapis.com/v1/projects/{project_id}/messages:send"
|
||||
FCM_LEGACY_SEND_URL = "https://fcm.googleapis.com/fcm/send"
|
||||
# Refresh the cached access token this long before its expiry.
|
||||
@@ -54,7 +56,7 @@ class PushBackend:
|
||||
name: str = "push"
|
||||
# DeviceRegistry column that carries this backend's target token.
|
||||
# Not a secret: a DB column name (string literal), not a credential.
|
||||
# pi-lens-ignore: python-hardcoded-secrets
|
||||
# pi-lens-ignore: S105, python-hardcoded-secrets
|
||||
token_field: str = ""
|
||||
|
||||
def configured(self) -> bool:
|
||||
@@ -87,8 +89,8 @@ class FcmBackend(PushBackend):
|
||||
|
||||
name = "fcm"
|
||||
# Not a secret: a DB column name (string literal), not a credential.
|
||||
# pi-lens-ignore: python-hardcoded-secrets
|
||||
token_field = "fcm_token"
|
||||
# pi-lens-ignore: S105
|
||||
token_field = "fcm_token" # noqa: S105
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -257,8 +259,8 @@ class NtfyBackend(PushBackend):
|
||||
|
||||
name = "ntfy"
|
||||
# Not a secret: a DB column name (string literal), not a credential.
|
||||
# pi-lens-ignore: python-hardcoded-secrets
|
||||
token_field = "ntfy_topic"
|
||||
# pi-lens-ignore: S105
|
||||
token_field = "ntfy_topic" # noqa: S105
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -332,9 +334,14 @@ def build_push_backend(
|
||||
ntfy_server_url: str | None = None,
|
||||
ntfy_auth_token: str | None = None,
|
||||
) -> PushBackend:
|
||||
"""Select the backend by name (``IRIS_PUSH_BACKEND``; fcm default)."""
|
||||
if (name or "").strip().lower() == "ntfy":
|
||||
return NtfyBackend(
|
||||
topic=ntfy_topic, server_url=ntfy_server_url, auth_token=ntfy_auth_token
|
||||
)
|
||||
return FcmBackend(service_account=fcm_service_account, server_key=fcm_server_key)
|
||||
"""Select the backend by name (``IRIS_PUSH_BACKEND``; ntfy default).
|
||||
|
||||
ntfy is the default: it keeps push metadata on your own infrastructure.
|
||||
FCM is opt-in (``IRIS_PUSH_BACKEND=fcm``) — its metadata (title, device
|
||||
token) is routed through Google's servers.
|
||||
"""
|
||||
if (name or "").strip().lower() == "fcm":
|
||||
return FcmBackend(service_account=fcm_service_account, server_key=fcm_server_key)
|
||||
return NtfyBackend(
|
||||
topic=ntfy_topic, server_url=ntfy_server_url, auth_token=ntfy_auth_token
|
||||
)
|
||||
@@ -1404,7 +1404,7 @@ def test_push_backend_selection(plugin):
|
||||
push = plugin.push
|
||||
assert isinstance(push.build_push_backend("fcm"), push.FcmBackend)
|
||||
assert isinstance(push.build_push_backend("ntfy"), push.NtfyBackend)
|
||||
assert isinstance(push.build_push_backend(None), push.FcmBackend) # default
|
||||
assert isinstance(push.build_push_backend(None), push.NtfyBackend) # default
|
||||
assert isinstance(push.build_push_backend(" NTFY "), push.NtfyBackend)
|
||||
|
||||
assert push.build_push_backend("ntfy", ntfy_topic="my-topic").configured() is True
|
||||
|
||||
Reference in new issue
Block a user