From 3f671b989910575d14766a10f63c2b2257b19c4e Mon Sep 17 00:00:00 2001 From: PTah Date: Thu, 11 Jun 2026 10:03:28 +1000 Subject: [PATCH] fix: FCM data-only push so Seaca controls sound (0.9.6) Send title and body in the data payload instead of notification block so onMessageReceived always runs and the app can honour silent mode in background. Co-authored-by: Cursor --- backend/app/services/mobile_notify.py | 7 ++++-- backend/app/version.py | 2 +- backend/tests/test_mobile_notify.py | 36 ++++++++++++++++++++++++++- frontend/src/version.ts | 2 +- 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/backend/app/services/mobile_notify.py b/backend/app/services/mobile_notify.py index 1d85c46..f5bb27d 100644 --- a/backend/app/services/mobile_notify.py +++ b/backend/app/services/mobile_notify.py @@ -141,13 +141,16 @@ def _send_fcm_data( } errors: list[str] = [] + # Data-only: Android always calls SeacaMessagingService.onMessageReceived so the app + # can honour per-device notification mode (sound / silent / off). + message_data = {**data, "title": title, "body": body} + with httpx.Client(timeout=12.0) as client: for token in tokens: payload = { "message": { "token": token, - "notification": {"title": title, "body": body}, - "data": data, + "data": message_data, "android": {"priority": "high"}, } } diff --git a/backend/app/version.py b/backend/app/version.py index 39ad46f..8fb610f 100644 --- a/backend/app/version.py +++ b/backend/app/version.py @@ -1,5 +1,5 @@ """Единый источник версии SAC (API, health, логи, OpenAPI).""" APP_NAME = "Security Alert Center" -APP_VERSION = "0.9.4" +APP_VERSION = "0.9.6" APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}" diff --git a/backend/tests/test_mobile_notify.py b/backend/tests/test_mobile_notify.py index 95ddf1c..9a0f7d1 100644 --- a/backend/tests/test_mobile_notify.py +++ b/backend/tests/test_mobile_notify.py @@ -1,6 +1,6 @@ """FCM push must not break event ingest.""" -from unittest.mock import patch +from unittest.mock import MagicMock, patch import pytest @@ -40,3 +40,37 @@ def test_send_fcm_data_raises_token_errors_when_required(): body="b", require_success=True, ) + + +def test_send_fcm_data_payload_is_data_only(): + posted: list[dict] = [] + + def capture_post(_url, *, headers, json): + posted.append(json) + response = MagicMock() + response.raise_for_status.return_value = None + return response + + with patch("app.services.mobile_notify._fcm_access_token", return_value="token"): + with patch("app.services.mobile_notify.get_effective_fcm_config") as mock_cfg: + mock_cfg.return_value.enabled = True + mock_cfg.return_value.project_id = "proj" + mock_cfg.return_value.service_account_path = "/etc/fcm.json" + mock_cfg.return_value.configured = True + mock_cfg.return_value.active = True + with patch("httpx.Client") as mock_client: + mock_client.return_value.__enter__.return_value.post = capture_post + _send_fcm_data( + ["device-token"], + {"kind": "event", "id": "42", "severity": "high"}, + title="Alert", + body="Summary text", + ) + + assert len(posted) == 1 + message = posted[0]["message"] + assert "notification" not in message + assert message["data"]["title"] == "Alert" + assert message["data"]["body"] == "Summary text" + assert message["data"]["kind"] == "event" + assert message["data"]["id"] == "42" diff --git a/frontend/src/version.ts b/frontend/src/version.ts index b1432df..a245fa9 100644 --- a/frontend/src/version.ts +++ b/frontend/src/version.ts @@ -1,4 +1,4 @@ /** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */ export const APP_NAME = "Security Alert Center"; -export const APP_VERSION = "0.9.4"; +export const APP_VERSION = "0.9.6"; export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`;