jld3103
2 years ago
17 changed files with 48 additions and 637 deletions
@ -1,12 +0,0 @@
|
||||
# Files and directories created by pub. |
||||
.dart_tool/ |
||||
.packages |
||||
|
||||
# Conventional directory for build outputs. |
||||
build/ |
||||
|
||||
# Omit committing pubspec.lock for library packages; see |
||||
# https://dart.dev/guides/libraries/private-files#pubspeclock. |
||||
pubspec.lock |
||||
|
||||
tmp/ |
@ -1,17 +0,0 @@
|
||||
FROM dart:stable as builder |
||||
|
||||
WORKDIR /app |
||||
|
||||
ADD pubspec.yaml . |
||||
RUN dart pub get |
||||
|
||||
|
||||
ADD lib ./lib |
||||
ADD bin ./bin |
||||
RUN dart compile exe bin/unified_push.dart -o bin/nextcloud-push-proxy |
||||
|
||||
FROM debian:bullseye-slim |
||||
|
||||
COPY --from=builder /app/bin/nextcloud-push-proxy /usr/local/bin/ |
||||
|
||||
CMD ["nextcloud-push-proxy", "/data/devices.json"] |
@ -1 +0,0 @@
|
||||
include: package:nit_picking/dart.yaml |
@ -1,68 +0,0 @@
|
||||
import 'dart:convert'; |
||||
import 'dart:io'; |
||||
|
||||
import 'package:crypto/crypto.dart'; |
||||
import 'package:nextcloud_push_proxy/nextcloud_push_proxy.dart'; |
||||
|
||||
Future main(final List<String> args) async { |
||||
if (args.length != 1) { |
||||
throw Exception('Provide the file where to store devices'); |
||||
} |
||||
final devices = <PushProxyDevice>[]; |
||||
final devicesFile = File(args[0]); |
||||
if (devicesFile.existsSync()) { |
||||
devices.addAll( |
||||
(json.decode(devicesFile.readAsStringSync()) as List) |
||||
.map((final d) => PushProxyDevice.fromJson(d as Map<String, dynamic>)), |
||||
); |
||||
} |
||||
|
||||
final server = NextcloudPushProxy(); |
||||
|
||||
watchSignals((final signal) async { |
||||
print('Got exit signal, shutting down'); |
||||
await server.close(); |
||||
exit(1); |
||||
}); |
||||
|
||||
await server.create(); |
||||
|
||||
server.onNewDevice.listen((final device) { |
||||
if (!devices.map((final d) => d.pushToken).contains(device.pushToken)) { |
||||
devices.add(device); |
||||
devicesFile |
||||
..createSync(recursive: true) |
||||
..writeAsString(json.encode(devices.map((final d) => d.toJson()).toList())); |
||||
} |
||||
}); |
||||
|
||||
server.onNewNotification.listen((final notification) async { |
||||
for (final device in devices) { |
||||
if (notification.pushTokenHash == sha512.convert(utf8.encode(device.pushToken)).toString()) { |
||||
final request = await HttpClient().postUrl(Uri.parse(device.pushToken)) |
||||
..followRedirects = false |
||||
..persistentConnection = true |
||||
..add(utf8.encode(json.encode(notification.toPushNotificationData()))); |
||||
|
||||
final response = await request.close(); |
||||
if (response.statusCode > 299) { |
||||
print('Failed to send notification'); |
||||
} |
||||
} |
||||
} |
||||
}); |
||||
|
||||
print('Listening on *:8080'); |
||||
} |
||||
|
||||
void watchSignals(final Function(ProcessSignal signal) callback) { |
||||
for (final signal in [ |
||||
ProcessSignal.sighup, |
||||
ProcessSignal.sigint, |
||||
ProcessSignal.sigterm, |
||||
ProcessSignal.sigusr1, |
||||
ProcessSignal.sigusr2, |
||||
]) { |
||||
signal.watch().listen(callback); |
||||
} |
||||
} |
@ -1,163 +0,0 @@
|
||||
// ignore_for_file: public_member_api_docs |
||||
|
||||
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.call).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, |
||||
}; |
||||
} |
@ -1,7 +0,0 @@
|
||||
sdk: |
||||
- stable |
||||
|
||||
stages: |
||||
- all: |
||||
- analyze: --fatal-infos . |
||||
- format: --output=none --set-exit-if-changed --line-length 120 . |
@ -1,16 +0,0 @@
|
||||
name: nextcloud_push_proxy |
||||
version: 1.0.0 |
||||
|
||||
environment: |
||||
sdk: '>=2.19.0 <3.0.0' |
||||
|
||||
dependencies: |
||||
crypto: ^3.0.2 |
||||
shelf: ^1.3.1 |
||||
shelf_router: ^1.1.3 |
||||
|
||||
dev_dependencies: |
||||
nit_picking: |
||||
git: |
||||
url: https://github.com/stack11/dart_nit_picking |
||||
ref: 0b2ee0d |
Loading…
Reference in new issue