implemented an E2E-encrypted Nextcloud push-v2 notification system with support for RSA decryption and signature verification; introduced an iOS Notification Service Extension and native AppDelegate handlers for Talk actions (inline reply and mark-as-read); replaced the legacy notification registration with a new lifecycle managing app passwords and secure keypair storage; added background message handling with tray synchronization and a test notification utility in the settings.
This commit is contained in:
@@ -11,7 +11,15 @@ class CapabilitiesResponse {
|
||||
@JsonKey(defaultValue: false)
|
||||
final bool viewForeignTimetables;
|
||||
|
||||
CapabilitiesResponse({required this.viewForeignTimetables});
|
||||
/// Whether the backend push-proxy feature is configured and enabled for this
|
||||
/// user. The app only registers for push when this is true.
|
||||
@JsonKey(defaultValue: false)
|
||||
final bool pushNotifications;
|
||||
|
||||
CapabilitiesResponse({
|
||||
required this.viewForeignTimetables,
|
||||
required this.pushNotifications,
|
||||
});
|
||||
|
||||
factory CapabilitiesResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$CapabilitiesResponseFromJson(json);
|
||||
|
||||
@@ -10,8 +10,12 @@ CapabilitiesResponse _$CapabilitiesResponseFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => CapabilitiesResponse(
|
||||
viewForeignTimetables: json['viewForeignTimetables'] as bool? ?? false,
|
||||
pushNotifications: json['pushNotifications'] as bool? ?? false,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$CapabilitiesResponseToJson(
|
||||
CapabilitiesResponse instance,
|
||||
) => <String, dynamic>{'viewForeignTimetables': instance.viewForeignTimetables};
|
||||
) => <String, dynamic>{
|
||||
'viewForeignTimetables': instance.viewForeignTimetables,
|
||||
'pushNotifications': instance.pushNotifications,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../../errors/marianumconnect_error.dart';
|
||||
import '../../marianumconnect_api.dart';
|
||||
import '../../marianumconnect_endpoint.dart';
|
||||
|
||||
/// Registers (upserts) this device's push subscription with MarianumConnect via
|
||||
/// `PUT /api/mobile/v1/me/push-device`. The backend verifies the Nextcloud
|
||||
/// device-identifier signature, stores the routing metadata and starts
|
||||
/// forwarding Nextcloud pushes to this device's FCM token. Responds 204.
|
||||
class PushDeviceRegister {
|
||||
final Dio _dio;
|
||||
|
||||
PushDeviceRegister({Dio? dio}) : _dio = dio ?? MarianumConnectApi.dio();
|
||||
|
||||
Future<void> run({
|
||||
required String deviceIdentifier,
|
||||
required String deviceIdentifierSignature,
|
||||
required String userPublicKey,
|
||||
required String pushToken,
|
||||
required String platform,
|
||||
String? appVersion,
|
||||
}) async {
|
||||
try {
|
||||
await _dio.put<void>(
|
||||
MarianumConnectEndpoint.resolve('me/push-device'),
|
||||
data: {
|
||||
'deviceIdentifier': deviceIdentifier,
|
||||
'deviceIdentifierSignature': deviceIdentifierSignature,
|
||||
'userPublicKey': userPublicKey,
|
||||
'pushToken': pushToken,
|
||||
'platform': platform,
|
||||
'appVersion': ?appVersion,
|
||||
},
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
throw mapMarianumConnectError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../../errors/marianumconnect_error.dart';
|
||||
import '../../marianumconnect_api.dart';
|
||||
import '../../marianumconnect_endpoint.dart';
|
||||
|
||||
/// Triggers a test push to all of the current user's registered devices via
|
||||
/// `POST /api/mobile/v1/me/push-device/test`. Returns the number of devices the
|
||||
/// backend dispatched to (0 when none are registered).
|
||||
class PushDeviceTest {
|
||||
final Dio _dio;
|
||||
|
||||
PushDeviceTest({Dio? dio}) : _dio = dio ?? MarianumConnectApi.dio();
|
||||
|
||||
Future<int> run() async {
|
||||
try {
|
||||
final response = await _dio.post<Map<String, dynamic>>(
|
||||
MarianumConnectEndpoint.resolve('me/push-device/test'),
|
||||
);
|
||||
return (response.data?['devices'] as num?)?.toInt() ?? 0;
|
||||
} on DioException catch (e) {
|
||||
throw mapMarianumConnectError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../../errors/marianumconnect_error.dart';
|
||||
import '../../marianumconnect_api.dart';
|
||||
import '../../marianumconnect_endpoint.dart';
|
||||
|
||||
/// Removes this device's push subscription from MarianumConnect via
|
||||
/// `DELETE /api/mobile/v1/me/push-device?deviceIdentifier=...`. Idempotent
|
||||
/// (204 even when the row is already gone).
|
||||
class PushDeviceUnregister {
|
||||
final Dio _dio;
|
||||
|
||||
PushDeviceUnregister({Dio? dio}) : _dio = dio ?? MarianumConnectApi.dio();
|
||||
|
||||
Future<void> run({required String deviceIdentifier}) async {
|
||||
try {
|
||||
await _dio.delete<void>(
|
||||
MarianumConnectEndpoint.resolve('me/push-device'),
|
||||
queryParameters: {'deviceIdentifier': deviceIdentifier},
|
||||
);
|
||||
} on DioException catch (e) {
|
||||
throw mapMarianumConnectError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user