Clean up lint/LSP across gateway, Android, and desktop (alpha -> stable)
Gateway (gateway-plugin/): - Fix interactive_setup broken imports: print helpers were imported from the wrong hermes module (hermes_cli.config instead of hermes_cli.cli_output) plus a non-existent print_code; the try/except swallowed the ImportError so `hermes gateway setup` for android always bailed out early. - Fix release_scoped_lock type error (str | None passed where str required). - Rewrite empty `except: pass` blocks as contextlib.suppress with rationale. - Restructure two ambiguous ws_server try blocks (hello-auth, frame loop). - Ruff cleanup: type annotations, import sorting, line wrapping, magic values -> named constants, `raise ... from e`, complexity. Add gateway-plugin/ruff.toml. - Add pyrightconfig.json so the Python LSP resolves hermes-runtime imports. - Suppress verified false positives inline (parameterized SQL, column-name "secrets", hermes-generated media path). Android (app/androidApp + app/shared): - Consolidate launcher icons into a single mipmap-anydpi (minSdk 29 >= 26) with the monochrome layer; clears ObsoleteSdkInt + MonochromeLauncherIcon. - Bump core-splashscreen 1.0.1 -> 1.2.0; pin targetSdk 34 (deliberate). - Suppress verified findings inline (LAN ws:// default, correct GCM IV usage). Desktop (app/desktopApp): - Move the desktop to a Java 21 runtime (org.gradle.java.home) and set the desktop jvmTarget to 21 (Android stays JVM 17 / minSdk 29). Fixes the startup UnsupportedClassVersionError and restores Markdown renderer 0.44.0. Tooling/config: - .pi-lens.json: disable verified-noisy heuristics (documented in docs). - .gitleaks.toml: allowlist git-ignored false-positive paths. - docs/18-code-review.md: full findings + verification. Verified: ruff clean, pyright 0 errors, 64/64 gateway tests, all Kotlin tests, Android lint 0 issues, Android installed+launched on device, desktop launches on JDK 21.
This commit is contained in:
1 parent
9f3f9842c8
commit
678c0344c8
27 files changed
+928
-454
No files matched your search
+35
-26
@@ -25,7 +25,7 @@ import logging
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
from urllib.parse import quote
|
||||
|
||||
import httpx
|
||||
@@ -41,6 +41,9 @@ _TOKEN_REFRESH_MARGIN_S = 600.0
|
||||
_DEFAULT_NTFY_SERVER = "https://ntfy.sh"
|
||||
_NTFY_BODY_LIMIT = 4096
|
||||
_HTTP_TIMEOUT_S = 15.0
|
||||
# HTTP status boundaries: 200 == success; >= 300 == redirect/error range.
|
||||
_HTTP_OK = 200
|
||||
_HTTP_ERROR_MIN = 300
|
||||
|
||||
_NTFY_PRIORITY = {"high": "5", "normal": "3", "low": "1"}
|
||||
|
||||
@@ -50,6 +53,8 @@ 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
|
||||
token_field: str = ""
|
||||
|
||||
def configured(self) -> bool:
|
||||
@@ -63,7 +68,7 @@ class PushBackend:
|
||||
chat_id: str,
|
||||
title: str,
|
||||
body: str,
|
||||
data: Dict[str, Any],
|
||||
data: dict[str, Any],
|
||||
token: str,
|
||||
priority: str = "normal",
|
||||
data_only: bool = False,
|
||||
@@ -81,18 +86,20 @@ class FcmBackend(PushBackend):
|
||||
"""FCM HTTP v1 (service account) or legacy ``/fcm/send`` (server key)."""
|
||||
|
||||
name = "fcm"
|
||||
# Not a secret: a DB column name (string literal), not a credential.
|
||||
# pi-lens-ignore: python-hardcoded-secrets
|
||||
token_field = "fcm_token"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
service_account: Optional[str] = None,
|
||||
server_key: Optional[str] = None,
|
||||
service_account: str | None = None,
|
||||
server_key: str | None = None,
|
||||
):
|
||||
self._sa_path = (service_account or "").strip() or None
|
||||
self._server_key = (server_key or "").strip() or None
|
||||
self._sa: Optional[Dict[str, Any]] = None
|
||||
self._sa: dict[str, Any] | None = None
|
||||
self._sa_failed = False
|
||||
self._access_token: Optional[str] = None
|
||||
self._access_token: str | None = None
|
||||
self._token_expiry = 0.0
|
||||
self._lock = threading.Lock()
|
||||
|
||||
@@ -101,13 +108,13 @@ class FcmBackend(PushBackend):
|
||||
return True
|
||||
return bool(self._sa_path and Path(self._sa_path).is_file())
|
||||
|
||||
def _load_sa(self) -> Optional[Dict[str, Any]]:
|
||||
def _load_sa(self) -> dict[str, Any] | None:
|
||||
if self._sa is not None:
|
||||
return self._sa
|
||||
if not self._sa_path or self._sa_failed:
|
||||
return None
|
||||
try:
|
||||
with open(self._sa_path, "r", encoding="utf-8") as f:
|
||||
with open(self._sa_path, encoding="utf-8") as f:
|
||||
sa = json.load(f)
|
||||
if isinstance(sa, dict) and sa.get("client_email") and sa.get("private_key"):
|
||||
self._sa = sa
|
||||
@@ -117,7 +124,7 @@ class FcmBackend(PushBackend):
|
||||
self._sa_failed = True
|
||||
return None
|
||||
|
||||
async def _authorization(self, client: httpx.AsyncClient) -> Optional[str]:
|
||||
async def _authorization(self, client: httpx.AsyncClient) -> str | None:
|
||||
"""Bearer token: the legacy server key, or a cached service-account
|
||||
OAuth2 access token (JWT-bearer grant, minted with PyJWT)."""
|
||||
if self._server_key:
|
||||
@@ -158,7 +165,7 @@ class FcmBackend(PushBackend):
|
||||
except Exception:
|
||||
logger.warning("android: FCM token exchange failed", exc_info=True)
|
||||
return None
|
||||
if resp.status_code != 200:
|
||||
if resp.status_code != _HTTP_OK:
|
||||
logger.warning(
|
||||
"android: FCM token exchange HTTP %s: %s",
|
||||
resp.status_code, resp.text[:200],
|
||||
@@ -186,7 +193,7 @@ class FcmBackend(PushBackend):
|
||||
chat_id: str,
|
||||
title: str,
|
||||
body: str,
|
||||
data: Dict[str, Any],
|
||||
data: dict[str, Any],
|
||||
token: str,
|
||||
priority: str = "normal",
|
||||
data_only: bool = False,
|
||||
@@ -197,7 +204,7 @@ class FcmBackend(PushBackend):
|
||||
notification = None if data_only else {"title": title or "Iris", "body": body or ""}
|
||||
async with httpx.AsyncClient(timeout=_HTTP_TIMEOUT_S) as client:
|
||||
if self._server_key:
|
||||
payload: Dict[str, Any] = {"to": token}
|
||||
payload: dict[str, Any] = {"to": token}
|
||||
if notification:
|
||||
payload["notification"] = notification
|
||||
if data:
|
||||
@@ -209,7 +216,7 @@ class FcmBackend(PushBackend):
|
||||
project_id = (sa or {}).get("project_id")
|
||||
if not project_id:
|
||||
return False
|
||||
message: Dict[str, Any] = {"token": token}
|
||||
message: dict[str, Any] = {"token": token}
|
||||
if notification:
|
||||
message["notification"] = notification
|
||||
if data:
|
||||
@@ -231,7 +238,7 @@ class FcmBackend(PushBackend):
|
||||
except Exception:
|
||||
logger.warning("android: FCM send failed (network)", exc_info=True)
|
||||
return False
|
||||
if resp.status_code >= 300:
|
||||
if resp.status_code >= _HTTP_ERROR_MIN:
|
||||
# 404 NOT_FOUND = stale/invalid registration token.
|
||||
logger.warning(
|
||||
"android: FCM send HTTP %s: %s", resp.status_code, resp.text[:200]
|
||||
@@ -249,13 +256,15 @@ 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"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
topic: Optional[str] = None,
|
||||
server_url: Optional[str] = None,
|
||||
auth_token: Optional[str] = None,
|
||||
topic: str | None = None,
|
||||
server_url: str | None = None,
|
||||
auth_token: str | None = None,
|
||||
):
|
||||
self._topic = (topic or "").strip() or None
|
||||
self._server = (
|
||||
@@ -279,7 +288,7 @@ class NtfyBackend(PushBackend):
|
||||
chat_id: str,
|
||||
title: str,
|
||||
body: str,
|
||||
data: Dict[str, Any],
|
||||
data: dict[str, Any],
|
||||
token: str,
|
||||
priority: str = "normal",
|
||||
data_only: bool = False,
|
||||
@@ -306,7 +315,7 @@ class NtfyBackend(PushBackend):
|
||||
except Exception:
|
||||
logger.warning("android: ntfy publish failed (network)", exc_info=True)
|
||||
return False
|
||||
if resp.status_code >= 300:
|
||||
if resp.status_code >= _HTTP_ERROR_MIN:
|
||||
logger.warning(
|
||||
"android: ntfy publish HTTP %s: %s", resp.status_code, resp.text[:200]
|
||||
)
|
||||
@@ -315,17 +324,17 @@ class NtfyBackend(PushBackend):
|
||||
|
||||
|
||||
def build_push_backend(
|
||||
name: Optional[str],
|
||||
name: str | None,
|
||||
*,
|
||||
fcm_service_account: Optional[str] = None,
|
||||
fcm_server_key: Optional[str] = None,
|
||||
ntfy_topic: Optional[str] = None,
|
||||
ntfy_server_url: Optional[str] = None,
|
||||
ntfy_auth_token: Optional[str] = None,
|
||||
fcm_service_account: str | None = None,
|
||||
fcm_server_key: str | None = None,
|
||||
ntfy_topic: str | None = None,
|
||||
ntfy_server_url: str | None = None,
|
||||
ntfy_auth_token: str | None = None,
|
||||
) -> PushBackend:
|
||||
"""Select the backend by name (``ANDROID_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)
|
||||
return FcmBackend(service_account=fcm_service_account, server_key=fcm_server_key)
|
||||
Reference in new issue
Block a user