register-queue: Add push_devices in response.

This commit adds a `push_devices` dictionary to
`POST /register` response, keyed with push account ID,
where each entry describes the user's push device's
registration status and error code (if registration failed).
This commit is contained in:
Prakhar Pratyush
2025-06-17 15:45:41 +05:30
committed by Tim Abbott
parent 6a4b06b6f4
commit afe6986991
7 changed files with 68 additions and 6 deletions

View File

@@ -49,6 +49,7 @@ from zerver.lib.narrow_predicate import check_narrow_for_events
from zerver.lib.navigation_views import get_navigation_views_for_user
from zerver.lib.onboarding_steps import get_next_onboarding_steps
from zerver.lib.presence import get_presence_for_user, get_presences_for_realm
from zerver.lib.push_notifications import get_push_devices
from zerver.lib.realm_icon import realm_icon_url
from zerver.lib.realm_logo import get_realm_logo_source, get_realm_logo_url
from zerver.lib.scheduled_messages import (
@@ -918,6 +919,9 @@ def fetch_initial_state_data(
# abuse.
state["giphy_api_key"] = settings.GIPHY_API_KEY if settings.GIPHY_API_KEY else ""
if want("push_device"):
state["push_devices"] = {} if user_profile is None else get_push_devices(user_profile)
if user_profile is None:
# To ensure we have the correct user state set.
assert state["is_admin"] is False

View File

@@ -9,7 +9,7 @@ from collections.abc import Iterable, Mapping, Sequence
from dataclasses import dataclass
from email.headerregistry import Address
from functools import cache
from typing import TYPE_CHECKING, Any, Optional, TypeAlias, Union
from typing import TYPE_CHECKING, Any, Literal, Optional, TypeAlias, Union
import lxml.html
import orjson
@@ -25,7 +25,7 @@ from firebase_admin import exceptions as firebase_exceptions
from firebase_admin import initialize_app as firebase_initialize_app
from firebase_admin import messaging as firebase_messaging
from firebase_admin.messaging import UnregisteredError as FCMUnregisteredError
from typing_extensions import override
from typing_extensions import TypedDict, override
from analytics.lib.counts import COUNT_STATS, do_increment_logging_stat
from zerver.actions.realm_settings import (
@@ -54,6 +54,7 @@ from zerver.models import (
AbstractPushDeviceToken,
ArchivedMessage,
Message,
PushDevice,
PushDeviceToken,
Realm,
Recipient,
@@ -1586,3 +1587,21 @@ class HostnameAlreadyInUseBouncerError(JsonableError):
# This message is not read by any of the client apps, just potentially displayed
# via server administration tools, so it doesn't need translations.
return "A server with hostname {hostname} already exists"
class PushDeviceInfoDict(TypedDict):
status: Literal["active", "pending", "failed"]
error_code: str | None
def get_push_devices(user_profile: UserProfile) -> dict[str, PushDeviceInfoDict]:
# We intentionally don't try to save a database query
# if `push_notifications_configured()` is False, in order to avoid
# risk of clients deleting their Account records if the server
# has its mobile notifications configuration temporarily disabled.
rows = PushDevice.objects.filter(user=user_profile)
return {
str(row.push_account_id): {"status": row.status, "error_code": row.error_code}
for row in rows
}

View File

@@ -44,6 +44,7 @@ from zerver.models.presence import UserPresence as UserPresence
from zerver.models.presence import UserStatus as UserStatus
from zerver.models.push_notifications import AbstractPushDevice as AbstractPushDevice
from zerver.models.push_notifications import AbstractPushDeviceToken as AbstractPushDeviceToken
from zerver.models.push_notifications import PushDevice as PushDevice
from zerver.models.push_notifications import PushDeviceToken as PushDeviceToken
from zerver.models.realm_audit_logs import AbstractRealmAuditLog as AbstractRealmAuditLog
from zerver.models.realm_audit_logs import RealmAuditLog as RealmAuditLog

View File

@@ -17339,6 +17339,40 @@ paths:
before using this API key.
**Changes**: Added in Zulip 4.0 (feature level 47).
push_devices:
type: object
description: |
Present if `push_device` is present in `fetch_event_types`.
Dictionary where each entry describes the user's push device's
registration status and error code (if registration failed).
**Changes**: New in Zulip 11.0 (feature level ZF-1653f2).
additionalProperties:
description: |
`{push_account_id}`: Dictionary containing the details of
a push device with the push account ID as the key.
type: object
additionalProperties: false
properties:
status:
type: string
description: |
The push account's registration status.
Either `"active"`, `"pending"`, or `"failed"`.
error_code:
type: string
nullable: true
description: |
The error code returned when registration to bouncer fails, `null` otherwise.
Either `"INVALID_BOUNCER_PUBLIC_KEY"`, `"BAD_REQUEST"`, or `"REQUEST_EXPIRED"`.
The client should use these error codes to fix the issue which led to registration
failure and retry.
- `"INVALID_BOUNCER_PUBLIC_KEY"` - Inform the user to update app.
- `"REQUEST_EXPIRED` - Retry with a fresh payload.
enable_desktop_notifications:
deprecated: true
type: boolean

View File

@@ -1241,7 +1241,7 @@ class FetchQueriesTest(ZulipTestCase):
self.login_user(user)
with (
self.assert_database_query_count(47),
self.assert_database_query_count(48),
mock.patch("zerver.lib.events.always_want") as want_mock,
):
fetch_initial_state_data(user, realm=user.realm)
@@ -1260,6 +1260,7 @@ class FetchQueriesTest(ZulipTestCase):
navigation_views=1,
onboarding_steps=1,
presence=1,
push_device=1,
# 2 of the 3 queries here are a single query that is used
# for all the 'realm', 'stream', 'subscription'
# and 'realm_user_groups' event types.

View File

@@ -117,6 +117,7 @@ class HomeTest(ZulipTestCase):
"password_max_length",
"presences",
"presence_last_update_id",
"push_devices",
"queue_id",
"realm_allow_edit_history",
"realm_allow_message_editing",
@@ -285,7 +286,7 @@ class HomeTest(ZulipTestCase):
# Verify succeeds once logged-in
with (
self.assert_database_query_count(57),
self.assert_database_query_count(58),
patch("zerver.lib.cache.cache_set") as cache_mock,
):
result = self._get_home_page(stream="Denmark")
@@ -593,7 +594,7 @@ class HomeTest(ZulipTestCase):
# Verify number of queries for Realm admin isn't much higher than for normal users.
self.login("iago")
with (
self.assert_database_query_count(56),
self.assert_database_query_count(57),
patch("zerver.lib.cache.cache_set") as cache_mock,
):
result = self._get_home_page()
@@ -625,7 +626,7 @@ class HomeTest(ZulipTestCase):
self._get_home_page()
# Then for the second page load, measure the number of queries.
with self.assert_database_query_count(52):
with self.assert_database_query_count(53):
result = self._get_home_page()
# Do a sanity check that our new streams were in the payload.