push_notifications: Replace pm_users field with recipient_user_ids.

The encrypted push notification payload previously included a `pm_users`
field for group direct messages.

This commit replaces the `pm_users` field with `recipient_user_ids`.
(new name to migrate from the old "private" keyword)

It's a sorted array of all user IDs participating in a 1:1 or group DM
conversation, including both `user_id` and `sender_id`.

* Previously, `pm_users` was included only for group DMs;
`recipient_user_ids` is present for both 1:1 and group DM conversations.

* The old `pm_users` field was a string containing a comma-separated
list of sorted user IDs. `recipient_user_ids` has a more structured
array format.

Signed-off-by: Prakhar Pratyush <prakhar@zulip.com>
This commit is contained in:
Prakhar Pratyush
2025-10-22 13:21:06 +05:30
committed by Tim Abbott
parent 9592d91b68
commit 7ebaa9f4e4
5 changed files with 78 additions and 18 deletions

View File

@@ -20,6 +20,14 @@ format used by the Zulip server that they are interacting with.
## Changes in Zulip 12.0
**Feature level 429**
* Replaced the `pm_users` field with `recipient_user_ids` in
[E2EE mobile push notifications payload](/api/mobile-notifications)
for group direct message. Previously, `pm_users` was included only
for group DMs; `recipient_user_ids` is present for both 1:1 and
group DM conversations.
**Feature level 428**
* [`GET /events`](/api/get-events): When a user is deactivated,

View File

@@ -59,10 +59,10 @@ Sample JSON data that gets encrypted:
{
"content": "test content",
"message_id": 46,
"pm_users": "6,10,12,15",
"realm_name": "Zulip Dev",
"realm_url": "http://zulip.testserver",
"recipient_type": "direct",
"recipient_user_ids": [6,10,12,15],
"sender_avatar_url": "https://secure.gravatar.com/avatar/818c212b9f8830dfef491b3f7da99a14?d=identicon&version=1",
"sender_full_name": "aaron",
"sender_id": 6,
@@ -72,14 +72,16 @@ Sample JSON data that gets encrypted:
}
```
- **Group direct messages**: The `pm_users` string field is only
present for group direct messages, containing a sorted comma-separated
list of all user IDs in the group direct message conversation,
including both `user_id` and `sender_id`.
- The `recipient_user_ids` field is a sorted array of all user IDs in
the direct message conversation, including both `user_id` and
`sender_id`.
**Changes**: New in Zulip 11.0 (feature level 413).
**Changes**: In Zulip 12.0 (feature level 429), replaced the
`pm_users` field with `recipient_user_ids`. The old `pm_users` field
was only present for group DMs, and was a string containing a
comma-separated list of sorted user IDs.
### New group direct message
New in Zulip 11.0 (feature level 413).
### Remove notifications

View File

@@ -35,7 +35,7 @@ DESKTOP_WARNING_VERSION = "5.9.3"
# new level means in api_docs/changelog.md, as well as "**Changes**"
# entries in the endpoint's documentation in `zulip.yaml`.
API_FEATURE_LEVEL = 428
API_FEATURE_LEVEL = 429
# Bump the minor PROVISION_VERSION to indicate that folks should provision
# only when going from an old version of the code to a newer version. Bump

View File

@@ -1032,16 +1032,29 @@ def get_message_payload(
data["channel_id"] = channel_id
data["topic"] = get_topic_display_name(message.topic_name(), user_profile.default_language)
elif message.recipient.type == Recipient.DIRECT_MESSAGE_GROUP:
data["recipient_type"] = "private" if for_legacy_clients else "direct"
else:
assert message.recipient.type in [Recipient.PERSONAL, Recipient.DIRECT_MESSAGE_GROUP]
if for_legacy_clients:
data["recipient_type"] = "private"
if message.recipient.type == Recipient.DIRECT_MESSAGE_GROUP:
# For group DMs, we need to fetch the users for the pm_users field.
# Note that this doesn't do a separate database query, because both
# functions use the get_display_recipient_by_id cache.
recipients = get_display_recipient(message.recipient)
if len(recipients) > 2:
data["pm_users"] = direct_message_group_users(message.recipient.id)
else: # Recipient.PERSONAL
data["recipient_type"] = "private" if for_legacy_clients else "direct"
else:
data["recipient_type"] = "direct"
# For 1:1 and group DMs, we need to fetch the users for the `recipient_user_ids`
# field. Note that this doesn't do a separate database query, because it uses
# the `get_display_recipient_by_id` cache.
display_recipients = get_display_recipient(message.recipient)
recipient_user_ids = set(
display_recipient["id"] for display_recipient in display_recipients
)
if len(recipient_user_ids) == 1: # Recipient.PERSONAL
recipient_user_ids.add(message.sender_id)
data["recipient_user_ids"] = sorted(recipient_user_ids)
return data

View File

@@ -733,6 +733,43 @@ class SendPushNotificationTest(E2EEPushNotificationTestCase):
"user_id": hamlet.id,
"sender_id": aaron.id,
"recipient_type": "direct",
"recipient_user_ids": sorted([aaron.id, hamlet.id]),
"type": "message",
"message_id": message_id,
"time": datetime_to_timestamp(time_now),
"content": "test content",
"sender_full_name": aaron.full_name,
"sender_avatar_url": absolute_avatar_url(aaron),
}
with mock.patch("zerver.lib.push_notifications.send_push_notifications") as m:
handle_push_notification(hamlet.id, missed_message)
self.assertEqual(m.call_args.args[1], expected_payload_data_to_encrypt)
def test_payload_data_to_encrypt_group_direct_message(self) -> None:
aaron = self.example_user("aaron")
cordelia = self.example_user("cordelia")
hamlet = self.example_user("hamlet")
realm = get_realm("zulip")
time_now = now()
self.register_push_devices_for_notification()
with time_machine.travel(time_now, tick=False):
message_id = self.send_group_direct_message(
from_user=aaron, to_users=[hamlet, cordelia], skip_capture_on_commit_callbacks=True
)
missed_message = {
"message_id": message_id,
"trigger": NotificationTriggers.DIRECT_MESSAGE,
}
expected_payload_data_to_encrypt = {
"realm_url": realm.url,
"realm_name": realm.name,
"user_id": hamlet.id,
"sender_id": aaron.id,
"recipient_type": "direct",
"recipient_user_ids": sorted([aaron.id, cordelia.id, hamlet.id]),
"type": "message",
"message_id": message_id,
"time": datetime_to_timestamp(time_now),