fix(webpush): add WNS cache policy for Windows Edge push (#6034)

WNS rejects pywebpush default ttl=0 with 400 Bad Request unless X-WNS-Cache-Policy matches TTL; iOS/APNs endpoints are unaffected.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
G0m3e
2026-07-01 10:45:24 +08:00
committed by GitHub
parent ec07379a67
commit c57985d553
4 changed files with 72 additions and 2 deletions

View File

@@ -17,7 +17,7 @@ from app.db.message_oper import MessageOper
from app.db.systemconfig_oper import SystemConfigOper
from app.db.user_oper import get_current_active_superuser
from app.helper.service import ServiceConfigHelper
from app.helper.webpush import is_webpush_subscription_gone
from app.helper.webpush import is_webpush_subscription_gone, webpush_options_for_endpoint
from app.log import logger
from app.modules.wechat.WXBizMsgCrypt3 import WXBizMsgCrypt
from app.schemas.types import MessageChannel, SystemConfigKey
@@ -316,6 +316,7 @@ def send_notification(
data=json.dumps(payload.model_dump()),
vapid_private_key=settings.VAPID.get("privateKey"),
vapid_claims={"sub": settings.VAPID.get("subject")},
**webpush_options_for_endpoint(sub.get("endpoint")),
)
except WebPushException as err:
logger.error(f"WebPush发送失败: {str(err)}")

View File

@@ -2,6 +2,9 @@ from typing import Any
from pywebpush import WebPushException
# WNS 默认 TTLttl>0 时需配 X-WNS-Cache-Policy: cache
_WNS_DEFAULT_TTL = 86400
def is_webpush_subscription_gone(error: WebPushException) -> bool:
"""
@@ -10,3 +13,25 @@ def is_webpush_subscription_gone(error: WebPushException) -> bool:
response: Any = getattr(error, "response", None)
status_code = getattr(response, "status_code", None) or getattr(response, "status", None)
return status_code in {404, 410}
def is_wns_endpoint(endpoint: str | None) -> bool:
"""
判断是否为 Microsoft WNSEdge/Windows推送端点。
"""
return bool(endpoint and "notify.windows.com" in endpoint)
def webpush_options_for_endpoint(endpoint: str | None) -> dict[str, Any]:
"""
按推送服务返回 pywebpush 额外参数。
WNS 要求 TTL 与 X-WNS-Cache-Policy 一致,否则返回 400。
见 https://github.com/web-push-libs/pywebpush/issues/162
"""
if not is_wns_endpoint(endpoint):
return {}
return {
"ttl": _WNS_DEFAULT_TTL,
"headers": {"X-WNS-Cache-Policy": "cache"},
}

View File

@@ -4,7 +4,7 @@ from typing import Union, Tuple
from pywebpush import webpush, WebPushException
from app.core.config import global_vars, settings
from app.helper.webpush import is_webpush_subscription_gone
from app.helper.webpush import is_webpush_subscription_gone, webpush_options_for_endpoint
from app.log import logger
from app.modules import _ModuleBase, _MessageBase
from app.schemas import Notification
@@ -95,6 +95,7 @@ class WebPushModule(_ModuleBase, _MessageBase):
vapid_claims={
"sub": settings.VAPID.get("subject")
},
**webpush_options_for_endpoint(sub.get("endpoint")),
)
except WebPushException as err:
logger.error(f"WebPush发送失败: {str(err)}")