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.
165 lines
4.9 KiB
165 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(final 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(final 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(final 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, |
|
}; |
|
}
|
|
|