@@ -131,6 +131,12 @@ abstract class ZulipBinding {
131
131
132
132
/// Wraps the [AndroidNotificationHostApi] constructor.
133
133
AndroidNotificationHostApi get androidNotificationHost;
134
+
135
+ /// Generates a user agent header for HTTP requests.
136
+ ///
137
+ /// Uses [deviceInfo] to get operating system information
138
+ /// and [packageInfo] to get application version information.
139
+ Map <String , String > userAgentHeader ();
134
140
}
135
141
136
142
/// Like [device_info_plus.BaseDeviceInfo] , but without things we don't use.
@@ -140,13 +146,23 @@ abstract class BaseDeviceInfo {
140
146
141
147
/// Like [device_info_plus.AndroidDeviceInfo] , but without things we don't use.
142
148
class AndroidDeviceInfo extends BaseDeviceInfo {
149
+ /// The user-visible version string.
150
+ ///
151
+ /// E.g., "1.0" or "3.4b5" or "bananas". This field is an opaque string.
152
+ /// Do not assume that its value has any particular structure or that
153
+ /// values of RELEASE from different releases can be somehow ordered.
154
+ final String release;
155
+
143
156
/// The Android SDK version.
144
157
///
145
158
/// Possible values are defined in:
146
159
/// https://developer.android.com/reference/android/os/Build.VERSION_CODES.html
147
160
final int sdkInt;
148
161
149
- AndroidDeviceInfo ({required this .sdkInt});
162
+ AndroidDeviceInfo ({
163
+ required this .release,
164
+ required this .sdkInt,
165
+ });
150
166
}
151
167
152
168
/// Like [device_info_plus.IosDeviceInfo] , but without things we don't use.
@@ -159,6 +175,59 @@ class IosDeviceInfo extends BaseDeviceInfo {
159
175
IosDeviceInfo ({required this .systemVersion});
160
176
}
161
177
178
+ /// Like [device_info_plus.MacOsDeviceInfo] , but without things we don't use.
179
+ class MacOsDeviceInfo extends BaseDeviceInfo {
180
+ /// The major release number, such as 10 in version 10.9.3.
181
+ final int majorVersion;
182
+
183
+ /// The minor release number, such as 9 in version 10.9.3.
184
+ final int minorVersion;
185
+
186
+ /// The update release number, such as 3 in version 10.9.3.
187
+ final int patchVersion;
188
+
189
+ MacOsDeviceInfo ({
190
+ required this .majorVersion,
191
+ required this .minorVersion,
192
+ required this .patchVersion,
193
+ });
194
+ }
195
+
196
+ /// Like [device_info_plus.WindowsDeviceInfo] , currently only used to
197
+ /// determine if we're on Windows.
198
+ class WindowsDeviceInfo implements BaseDeviceInfo {}
199
+
200
+ /// Like [device_info_plus.LinuxDeviceInfo] , but without things we don't use.
201
+ ///
202
+ /// See:
203
+ /// https://www.freedesktop.org/software/systemd/man/os-release.html
204
+ class LinuxDeviceInfo implements BaseDeviceInfo {
205
+ /// A string identifying the operating system, without a version component,
206
+ /// and suitable for presentation to the user.
207
+ ///
208
+ /// Examples: 'Fedora', 'Debian GNU/Linux'.
209
+ ///
210
+ /// If not set, defaults to 'Linux'.
211
+ final String name;
212
+
213
+ /// A lower-case string identifying the operating system version, excluding
214
+ /// any OS name information or release code name, and suitable for processing
215
+ /// by scripts or usage in generated filenames.
216
+ ///
217
+ /// The version is mostly numeric, and contains no spaces or other characters
218
+ /// outside of 0–9, a–z, '.', '_' and '-'.
219
+ ///
220
+ /// Examples: '17', '11.04'.
221
+ ///
222
+ /// This field is optional and may be null on some systems.
223
+ final String ? versionId;
224
+
225
+ LinuxDeviceInfo ({
226
+ required this .name,
227
+ required this .versionId,
228
+ });
229
+ }
230
+
162
231
/// Like [package_info_plus.PackageInfo] , but without things we don't use.
163
232
class PackageInfo {
164
233
final String version;
@@ -191,6 +260,9 @@ class LiveZulipBinding extends ZulipBinding {
191
260
return ZulipBinding .instance as LiveZulipBinding ;
192
261
}
193
262
263
+ // Stored user agent header, since it remains constant.
264
+ Map <String , String >? _userAgentHeader;
265
+
194
266
@override
195
267
BaseDeviceInfo get deviceInfo => _deviceInfo! ;
196
268
BaseDeviceInfo ? _deviceInfo;
@@ -202,9 +274,16 @@ class LiveZulipBinding extends ZulipBinding {
202
274
Future <void > _prefetchDeviceInfo () async {
203
275
final info = await device_info_plus.DeviceInfoPlugin ().deviceInfo;
204
276
_deviceInfo = switch (info) {
205
- device_info_plus.AndroidDeviceInfo (: var version) => AndroidDeviceInfo (sdkInt: version.sdkInt),
206
- device_info_plus.IosDeviceInfo (: var systemVersion) => IosDeviceInfo (systemVersion: systemVersion),
207
- _ => throw UnimplementedError (),
277
+ device_info_plus.AndroidDeviceInfo () => AndroidDeviceInfo (release: info.version.release,
278
+ sdkInt: info.version.sdkInt),
279
+ device_info_plus.IosDeviceInfo () => IosDeviceInfo (systemVersion: info.systemVersion),
280
+ device_info_plus.MacOsDeviceInfo () => MacOsDeviceInfo (majorVersion: info.majorVersion,
281
+ minorVersion: info.minorVersion,
282
+ patchVersion: info.patchVersion),
283
+ device_info_plus.WindowsDeviceInfo () => WindowsDeviceInfo (),
284
+ device_info_plus.LinuxDeviceInfo () => LinuxDeviceInfo (name: info.name,
285
+ versionId: info.versionId),
286
+ _ => throw UnimplementedError (),
208
287
};
209
288
}
210
289
@@ -268,4 +347,39 @@ class LiveZulipBinding extends ZulipBinding {
268
347
269
348
@override
270
349
AndroidNotificationHostApi get androidNotificationHost => AndroidNotificationHostApi ();
350
+
351
+ @override
352
+ Map <String , String > userAgentHeader () {
353
+ return _userAgentHeader ?? = buildUserAgentHeader (deviceInfo, packageInfo);
354
+ }
355
+ }
356
+
357
+ @visibleForTesting
358
+ Map <String , String > buildUserAgentHeader (BaseDeviceInfo deviceInfo, PackageInfo packageInfo) {
359
+ final osInfo = switch (deviceInfo) {
360
+ AndroidDeviceInfo (
361
+ : var release) => 'Android $release ' , // "Android 14"
362
+ IosDeviceInfo (
363
+ : var systemVersion) => 'iOS $systemVersion ' , // "iOS 17.4"
364
+ MacOsDeviceInfo (
365
+ : var majorVersion,
366
+ : var minorVersion,
367
+ : var patchVersion) => 'macOS $majorVersion .$minorVersion .$patchVersion ' , // "macOS 14.5.0"
368
+ WindowsDeviceInfo () => 'Windows' , // "Windows"
369
+ LinuxDeviceInfo (
370
+ : var name,
371
+ : var versionId) => 'Linux; $name ${versionId != null ? ' $versionId ' : '' }' , // "Linux; Fedora Linux 40" or "Linux; Fedora Linux"
372
+ _ => throw UnimplementedError (),
373
+ };
374
+ final PackageInfo (: version, : buildNumber) = packageInfo;
375
+
376
+ // Possible examples:
377
+ // 'ZulipFlutter/0.0.15+15 (Android 14)'
378
+ // 'ZulipFlutter/0.0.15+15 (iOS 17.4)'
379
+ // 'ZulipFlutter/0.0.15+15 (macOS 14.5.0)'
380
+ // 'ZulipFlutter/0.0.15+15 (Windows)'
381
+ // 'ZulipFlutter/0.0.15+15 (Linux; Fedora Linux 40)'
382
+ return {
383
+ 'User-Agent' : 'ZulipFlutter/$version +$buildNumber ($osInfo )' ,
384
+ };
271
385
}
0 commit comments