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 <cursoragent@cursor.com>
This commit is contained in:
2026-06-11 10:03:28 +10:00
parent 3f7095ed03
commit 3f671b9899
4 changed files with 42 additions and 5 deletions
+5 -2
View File
@@ -141,13 +141,16 @@ def _send_fcm_data(
} }
errors: list[str] = [] 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: with httpx.Client(timeout=12.0) as client:
for token in tokens: for token in tokens:
payload = { payload = {
"message": { "message": {
"token": token, "token": token,
"notification": {"title": title, "body": body}, "data": message_data,
"data": data,
"android": {"priority": "high"}, "android": {"priority": "high"},
} }
} }
+1 -1
View File
@@ -1,5 +1,5 @@
"""Единый источник версии SAC (API, health, логи, OpenAPI).""" """Единый источник версии SAC (API, health, логи, OpenAPI)."""
APP_NAME = "Security Alert Center" APP_NAME = "Security Alert Center"
APP_VERSION = "0.9.4" APP_VERSION = "0.9.6"
APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}" APP_VERSION_LABEL = f"{APP_NAME} v.{APP_VERSION}"
+35 -1
View File
@@ -1,6 +1,6 @@
"""FCM push must not break event ingest.""" """FCM push must not break event ingest."""
from unittest.mock import patch from unittest.mock import MagicMock, patch
import pytest import pytest
@@ -40,3 +40,37 @@ def test_send_fcm_data_raises_token_errors_when_required():
body="b", body="b",
require_success=True, 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"
+1 -1
View File
@@ -1,4 +1,4 @@
/** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */ /** Fallback до загрузки /health; при релизе держите в sync с backend/app/version.py */
export const APP_NAME = "Security Alert Center"; 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}`; export const APP_VERSION_LABEL = `${APP_NAME} v.${APP_VERSION}`;