Skip to content

Commit a7812b3

Browse files
rajveermalviyagnprice
authored andcommitted
notif: Remove deprecated realmUri; replace with realmUrl
Zulip server 9.0 in feature level 257, deprecated `realm_uri` in favor of `realm_url` in push notification payloads: https://zulip.com/api/changelog#changes-in-zulip-90 To be compatible with older servers, added a fallback to use both fields while parsing, otherwise use the newer field in code.
1 parent 443094d commit a7812b3

File tree

5 files changed

+73
-35
lines changed

5 files changed

+73
-35
lines changed

lib/api/notifications.dart

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ sealed class FcmMessageWithIdentity extends FcmMessage {
6464
///
6565
/// This is a real, absolute URL which is the base for all URLs a client uses
6666
/// with this realm. It corresponds to [GetServerSettingsResult.realmUri].
67-
final Uri realmUri;
67+
@JsonKey(readValue: _readRealmUrl) // TODO(server-9)
68+
final Uri realmUrl;
6869

6970
/// This user's ID within the server.
7071
///
@@ -75,9 +76,14 @@ sealed class FcmMessageWithIdentity extends FcmMessage {
7576
FcmMessageWithIdentity({
7677
required this.server,
7778
required this.realmId,
78-
required this.realmUri,
79+
required this.realmUrl,
7980
required this.userId,
8081
});
82+
83+
// TODO(server-9): FL 257 deprecated 'realm_uri' in favor of 'realm_url'.
84+
static String _readRealmUrl(Map<dynamic, dynamic> json, String key) {
85+
return (json['realm_url'] ?? json['realm_uri']) as String;
86+
}
8187
}
8288

8389
/// Parsed version of an FCM message of type `message`.
@@ -117,7 +123,7 @@ class MessageFcmMessage extends FcmMessageWithIdentity {
117123
MessageFcmMessage({
118124
required super.server,
119125
required super.realmId,
120-
required super.realmUri,
126+
required super.realmUrl,
121127
required super.userId,
122128
required this.senderId,
123129
required this.senderAvatarUrl,
@@ -147,6 +153,7 @@ class MessageFcmMessage extends FcmMessageWithIdentity {
147153
if (recipient.streamName != null) result['stream'] = recipient.streamName;
148154
result['topic'] = recipient.topic;
149155
}
156+
result['realm_uri'] = realmUrl.toString(); // TODO(server-9): deprecated in FL 257
150157
return result;
151158
}
152159
}
@@ -236,7 +243,7 @@ class RemoveFcmMessage extends FcmMessageWithIdentity {
236243
RemoveFcmMessage({
237244
required super.server,
238245
required super.realmId,
239-
required super.realmUri,
246+
required super.realmUrl,
240247
required super.userId,
241248
required this.zulipMessageIds,
242249
});
@@ -247,7 +254,11 @@ class RemoveFcmMessage extends FcmMessageWithIdentity {
247254
}
248255

249256
@override
250-
Map<String, dynamic> toJson() => _$RemoveFcmMessageToJson(this);
257+
Map<String, dynamic> toJson() {
258+
final result = _$RemoveFcmMessageToJson(this);
259+
result['realm_uri'] = realmUrl.toString(); // TODO(server-9): deprecated in FL 257
260+
return result;
261+
}
251262
}
252263

253264
class _IntListConverter extends JsonConverter<List<int>, String> {

lib/api/notifications.g.dart

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/notifications/display.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ class NotificationDisplayManager {
133133
} else {
134134
messagingStyle = MessagingStyle(
135135
user: Person(
136-
key: _personKey(data.realmUri, data.userId),
136+
key: _personKey(data.realmUrl, data.userId),
137137
name: zulipLocalizations.notifSelfUser),
138138
messages: [],
139139
isGroupConversation: switch (data.recipient) {
@@ -164,7 +164,7 @@ class NotificationDisplayManager {
164164
text: data.content,
165165
timestampMs: data.time * 1000,
166166
person: Person(
167-
key: _personKey(data.realmUri, data.senderId),
167+
key: _personKey(data.realmUrl, data.senderId),
168168
name: data.senderFullName,
169169
iconBitmap: await _fetchBitmap(data.senderAvatarUrl))));
170170

@@ -223,7 +223,7 @@ class NotificationDisplayManager {
223223
smallIconResourceName: 'zulip_notification', // This name must appear in keep.xml too: https://github.com/zulip/zulip-flutter/issues/528
224224
inboxStyle: InboxStyle(
225225
// TODO(#570) Show organization name, not URL
226-
summaryText: data.realmUri.toString()),
226+
summaryText: data.realmUrl.toString()),
227227

228228
// On Android 11 and lower, if autoCancel is not specified,
229229
// the summary notification may linger even after all child
@@ -336,10 +336,10 @@ class NotificationDisplayManager {
336336
static String _groupKey(FcmMessageWithIdentity data) {
337337
// The realm URL can't contain a `|`, because `|` is not a URL code point:
338338
// https://url.spec.whatwg.org/#url-code-points
339-
return "${data.realmUri}|${data.userId}";
339+
return "${data.realmUrl}|${data.userId}";
340340
}
341341

342-
static String _personKey(Uri realmUri, int userId) => "$realmUri|$userId";
342+
static String _personKey(Uri realmUrl, int userId) => "$realmUrl|$userId";
343343

344344
static void _onNotificationOpened(NotificationResponse response) async {
345345
final payload = jsonDecode(response.payload!) as Map<String, dynamic>;
@@ -366,7 +366,7 @@ class NotificationDisplayManager {
366366

367367
final globalStore = GlobalStoreWidget.of(context);
368368
final account = globalStore.accounts.firstWhereOrNull((account) =>
369-
account.realmUrl == data.realmUri && account.userId == data.userId);
369+
account.realmUrl == data.realmUrl && account.userId == data.userId);
370370
if (account == null) return; // TODO(log)
371371

372372
final narrow = switch (data.recipient) {

test/api/notifications_test.dart

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ void main() {
88
final baseBaseJson = {
99
"server": "zulip.example.cloud",
1010
"realm_id": "4",
11-
"realm_uri": "https://zulip.example.com/",
11+
"realm_uri": "https://zulip.example.com/", // TODO(server-9)
12+
"realm_url": "https://zulip.example.com/",
1213
"user_id": "234",
1314
};
1415

@@ -70,7 +71,8 @@ void main() {
7071
check(parse(streamJson))
7172
..server.equals(baseJson['server']!)
7273
..realmId.equals(4)
73-
..realmUri.equals(Uri.parse(baseJson['realm_uri']!))
74+
..realmUrl.equals(Uri.parse(baseJson['realm_url']!))
75+
..realmUrl.equals(Uri.parse(baseJson['realm_uri']!)) // TODO(server-9)
7476
..userId.equals(234)
7577
..senderId.equals(123)
7678
..senderAvatarUrl.equals(Uri.parse(streamJson['sender_avatar_url']!))
@@ -137,17 +139,28 @@ void main() {
137139
checkInert({ 'awesome_feature': 'enabled' });
138140
});
139141

142+
test('uses deprecated fields when newer fields are missing', () {
143+
final baseline = parse(dmJson);
144+
145+
// FL 257 deprecated 'realm_uri' in favor of 'realm_url'.
146+
final jsonSansRealm =
147+
{ ...dmJson }..remove('realm_url')..remove('realm_uri');
148+
check(parse({ ...jsonSansRealm, 'realm_url': 'https://zulip.example.com/' })).jsonEquals(baseline);
149+
});
150+
140151
group("parse failures on malformed 'message'", () {
141152
int n = 1;
142153
test("${n++}", () => checkParseFails({ ...dmJson }..remove('server')));
143154
test("${n++}", () => checkParseFails({ ...dmJson }..remove('realm_id')));
144155
test("${n++}", () => checkParseFails({ ...dmJson, 'realm_id': '12,34' }));
145156
test("${n++}", () => checkParseFails({ ...dmJson, 'realm_id': 'abc' }));
146-
test("${n++}", () => checkParseFails({ ...dmJson }..remove('realm_uri')));
157+
test("${n++}", () => checkParseFails({ ...dmJson }
158+
..remove('realm_url')
159+
..remove('realm_uri'))); // TODO(server-9)
147160
test(skip: true, // Dart's Uri.parse is lax in what it accepts.
148-
"${n++}", () => checkParseFails({ ...dmJson, 'realm_uri': 'zulip.example.com' }));
161+
"${n++}", () => checkParseFails({ ...dmJson, 'realm_url': 'zulip.example.com' }));
149162
test(skip: true, // Dart's Uri.parse is lax in what it accepts.
150-
"${n++}", () => checkParseFails({ ...dmJson, 'realm_uri': '/examplecorp' }));
163+
"${n++}", () => checkParseFails({ ...dmJson, 'realm_url': '/examplecorp' }));
151164

152165
test("${n++}", () => checkParseFails({ ...streamJson, 'stream_id': '12,34' }));
153166
test("${n++}", () => checkParseFails({ ...streamJson, 'stream_id': 'abc' }));
@@ -190,7 +203,8 @@ void main() {
190203
check(parse(baseJson))
191204
..server.equals(baseJson['server']!)
192205
..realmId.equals(4)
193-
..realmUri.equals(Uri.parse(baseJson['realm_uri']!))
206+
..realmUrl.equals(Uri.parse(baseJson['realm_url']!))
207+
..realmUrl.equals(Uri.parse(baseJson['realm_uri']!)) // TODO(server-9)
194208
..userId.equals(234)
195209
..zulipMessageIds.deepEquals([123, 234]);
196210
});
@@ -210,18 +224,29 @@ void main() {
210224
check(parse({ ...baseJson, 'awesome_feature': 'enabled' })).jsonEquals(baseline);
211225
});
212226

227+
test('uses deprecated fields when newer fields are missing', () {
228+
final baseline = parse(baseJson);
229+
230+
// FL 257 deprecated 'realm_uri' in favor of 'realm_url'.
231+
final jsonSansRealm =
232+
{ ...baseJson }..remove('realm_url')..remove('realm_uri');
233+
check(parse({ ...jsonSansRealm, 'realm_url': 'https://zulip.example.com/' })).jsonEquals(baseline);
234+
});
235+
213236
group('parse failures on malformed data', () {
214237
int n = 1;
215238

216239
test("${n++}", () => checkParseFails({ ...baseJson }..remove('server')));
217240
test("${n++}", () => checkParseFails({ ...baseJson }..remove('realm_id')));
218241
test("${n++}", () => checkParseFails({ ...baseJson, 'realm_id': 'abc' }));
219242
test("${n++}", () => checkParseFails({ ...baseJson, 'realm_id': '12,34' }));
220-
test("${n++}", () => checkParseFails({ ...baseJson }..remove('realm_uri')));
243+
test("${n++}", () => checkParseFails({ ...baseJson }
244+
..remove('realm_url')
245+
..remove('realm_uri'))); // TODO(server-9)
221246
test(skip: true, // Dart's Uri.parse is lax in what it accepts.
222-
"${n++}", () => checkParseFails({ ...baseJson, 'realm_uri': 'zulip.example.com' }));
247+
"${n++}", () => checkParseFails({ ...baseJson, 'realm_url': 'zulip.example.com' }));
223248
test(skip: true, // Dart's Uri.parse is lax in what it accepts.
224-
"${n++}", () => checkParseFails({ ...baseJson, 'realm_uri': '/examplecorp' }));
249+
"${n++}", () => checkParseFails({ ...baseJson, 'realm_url': '/examplecorp' }));
225250

226251
for (final badIntList in ["abc,34", "12,abc", "12,", ""]) {
227252
test("${n++}", () => checkParseFails({ ...baseJson, 'zulip_message_ids': badIntList }));
@@ -237,7 +262,7 @@ extension UnexpectedFcmMessageChecks on Subject<UnexpectedFcmMessage> {
237262
extension FcmMessageWithIdentityChecks on Subject<FcmMessageWithIdentity> {
238263
Subject<String> get server => has((x) => x.server, 'server');
239264
Subject<int> get realmId => has((x) => x.realmId, 'realmId');
240-
Subject<Uri> get realmUri => has((x) => x.realmUri, 'realmUri');
265+
Subject<Uri> get realmUrl => has((x) => x.realmUrl, 'realmUrl');
241266
Subject<int> get userId => has((x) => x.userId, 'userId');
242267
}
243268

test/notifications/display_test.dart

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -217,20 +217,20 @@ void main() {
217217
List<int>? expectedIconBitmap = kSolidBlueAvatar,
218218
}) {
219219
assert(messageStyleMessages.every((e) => e.userId == data.userId));
220-
assert(messageStyleMessages.every((e) => e.realmUri == data.realmUri));
220+
assert(messageStyleMessages.every((e) => e.realmUrl == data.realmUrl));
221221

222-
final expectedTag = '${data.realmUri}|${data.userId}|$expectedTagComponent';
223-
final expectedGroupKey = '${data.realmUri}|${data.userId}';
222+
final expectedTag = '${data.realmUrl}|${data.userId}|$expectedTagComponent';
223+
final expectedGroupKey = '${data.realmUrl}|${data.userId}';
224224
final expectedId =
225225
NotificationDisplayManager.notificationIdAsHashOf(expectedTag);
226226
const expectedIntentFlags =
227227
PendingIntentFlag.immutable | PendingIntentFlag.updateCurrent;
228-
final expectedSelfUserKey = '${data.realmUri}|${data.userId}';
228+
final expectedSelfUserKey = '${data.realmUrl}|${data.userId}';
229229

230230
final messageStyleMessagesChecks =
231231
messageStyleMessages.mapIndexed((i, messageData) {
232232
final expectedSenderKey =
233-
'${messageData.realmUri}|${messageData.senderId}';
233+
'${messageData.realmUrl}|${messageData.senderId}';
234234
final isLast = i == (messageStyleMessages.length - 1);
235235
return (Subject<Object?> it) => it.isA<MessagingStyleMessage>()
236236
..text.equals(messageData.content)
@@ -285,7 +285,7 @@ void main() {
285285
..groupKey.equals(expectedGroupKey)
286286
..isGroupSummary.equals(true)
287287
..inboxStyle.which((it) => it.isNotNull()
288-
..summaryText.equals(data.realmUri.toString()))
288+
..summaryText.equals(data.realmUrl.toString()))
289289
..autoCancel.equals(true)
290290
..contentIntent.isNull(),
291291
]);
@@ -327,7 +327,7 @@ void main() {
327327
}
328328

329329
Condition<Object?> conditionActiveNotif(MessageFcmMessage data, String tagComponent) {
330-
final expectedGroupKey = '${data.realmUri}|${data.userId}';
330+
final expectedGroupKey = '${data.realmUrl}|${data.userId}';
331331
final expectedTag = '$expectedGroupKey|$tagComponent';
332332
return (it) => it.isA<StatusBarNotification>()
333333
..id.equals(NotificationDisplayManager.notificationIdAsHashOf(expectedTag))
@@ -615,7 +615,7 @@ void main() {
615615
await init();
616616
final message = eg.streamMessage();
617617
final data = messageFcmMessage(message);
618-
final expectedGroupKey = '${data.realmUri}|${data.userId}';
618+
final expectedGroupKey = '${data.realmUrl}|${data.userId}';
619619

620620
check(testBinding.androidNotificationHost.activeNotifications).isEmpty();
621621

@@ -652,7 +652,7 @@ void main() {
652652
final data2 = messageFcmMessage(message2, streamName: stream.name);
653653
final message3 = eg.streamMessage(stream: stream, topic: topicA);
654654
final data3 = messageFcmMessage(message3, streamName: stream.name);
655-
final expectedGroupKey = '${data1.realmUri}|${data1.userId}';
655+
final expectedGroupKey = '${data1.realmUrl}|${data1.userId}';
656656

657657
check(testBinding.androidNotificationHost.activeNotifications).isEmpty();
658658

@@ -685,7 +685,7 @@ void main() {
685685
const topicB = 'Topic B';
686686
final message2 = eg.streamMessage(stream: stream, topic: topicB);
687687
final data2 = messageFcmMessage(message2, streamName: stream.name);
688-
final expectedGroupKey = '${data1.realmUri}|${data1.userId}';
688+
final expectedGroupKey = '${data1.realmUrl}|${data1.userId}';
689689

690690
check(testBinding.androidNotificationHost.activeNotifications).isEmpty();
691691

0 commit comments

Comments
 (0)