A framework for building convergent cross-platform Nextcloud clients using Flutter.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

166 lines
4.9 KiB

// ignore_for_file: public_member_api_docs
library nextcloud_push_proxy;
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart';
import 'package:shelf_router/shelf_router.dart';
/// Implements the listening part of a Nextcloud push proxy
class NextcloudPushProxy {
HttpServer? _server;
late StreamController<PushProxyDevice> _onNewDeviceController;
late StreamController<PushProxyNotification> _onNewNotificationController;
Stream<PushProxyDevice>? _onNewDeviceStream;
Stream<PushProxyNotification>? _onNewNotificationStream;
/// Listens for new devices
Stream<PushProxyDevice> get onNewDevice {
if (_onNewDeviceStream == null) {
throw Exception('Server not created');
}
return _onNewDeviceStream!;
}
/// Listens for new notifications
Stream<PushProxyNotification> get onNewNotification {
if (_onNewNotificationStream == null) {
throw Exception('Server not created');
}
return _onNewNotificationStream!;
}
late final _router = Router()
..post('/devices', _devicesHandler)
..post('/notifications', _notificationsHandler)
..get('/health', (final _) async => Response.ok(''));
Future<Response> _devicesHandler(Request request) async {
final data = Uri(query: await request.readAsString()).queryParameters;
_onNewDeviceController.add(
PushProxyDevice(
pushToken: data['pushToken']!,
deviceIdentifier: data['deviceIdentifier']!,
deviceIdentifierSignature: data['deviceIdentifierSignature']!,
userPublicKey: data['userPublicKey']!,
),
);
return Response.ok('');
}
Future<Response> _notificationsHandler(Request request) async {
final data = Uri(query: await request.readAsString()).queryParameters;
for (final notification in data.values) {
final notificationData = json.decode(notification) as Map<String, dynamic>;
_onNewNotificationController.add(
PushProxyNotification(
deviceIdentifier: notificationData['deviceIdentifier']! as String,
pushTokenHash: notificationData['pushTokenHash']! as String,
subject: notificationData['subject']! as String,
signature: notificationData['signature']! as String,
priority: notificationData['priority']! as String,
type: notificationData['type']! as String,
),
);
}
return Response.ok('');
}
/// Creates a server listening on the [port]
Future create({
final bool logging = true,
final int port = 8080,
}) async {
if (_server != null) {
throw Exception('Server already created');
}
_onNewDeviceController = StreamController<PushProxyDevice>();
_onNewNotificationController = StreamController<PushProxyNotification>();
_onNewDeviceStream = _onNewDeviceController.stream.asBroadcastStream();
_onNewNotificationStream = _onNewNotificationController.stream.asBroadcastStream();
var handler = Cascade().add(_router).handler;
if (logging) {
handler = logRequests().addHandler(handler);
}
final server = await serve(
handler,
InternetAddress.anyIPv4,
port,
);
server.autoCompress = true;
_server = server;
}
/// Closes the server
Future close() async {
if (_server != null) {
await _server!.close();
_server = null;
await _onNewDeviceController.close();
await _onNewNotificationController.close();
}
}
}
class PushProxyDevice {
PushProxyDevice({
required this.pushToken,
required this.deviceIdentifier,
required this.deviceIdentifierSignature,
required this.userPublicKey,
});
factory PushProxyDevice.fromJson(Map<String, dynamic> data) => PushProxyDevice(
pushToken: data['pushToken'] as String,
deviceIdentifier: data['deviceIdentifier'] as String,
deviceIdentifierSignature: data['deviceIdentifierSignature'] as String,
userPublicKey: data['userPublicKey'] as String,
);
Map<String, dynamic> toJson() => {
'pushToken': pushToken,
'deviceIdentifier': deviceIdentifier,
'deviceIdentifierSignature': deviceIdentifierSignature,
'userPublicKey': userPublicKey,
};
final String pushToken;
final String deviceIdentifier;
final String deviceIdentifierSignature;
final String userPublicKey;
}
class PushProxyNotification {
PushProxyNotification({
required this.deviceIdentifier,
required this.pushTokenHash,
required this.subject,
required this.signature,
required this.priority,
required this.type,
});
final String deviceIdentifier;
final String pushTokenHash;
final String subject;
final String signature;
final String priority;
final String type;
Map<String, String> toPushNotificationData() => {
'subject': subject,
'signature': signature,
'priority': priority,
'type': type,
};
}