diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml new file mode 100644 index 00000000..7cf9e265 --- /dev/null +++ b/.idea/libraries/Dart_Packages.xml @@ -0,0 +1,347 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/lib/client.dart b/lib/client.dart new file mode 100644 index 00000000..e4fe74c4 --- /dev/null +++ b/lib/client.dart @@ -0,0 +1,30 @@ +library angel_websocket.client; + +import 'dart:convert' show JSON; +import 'dart:html'; +import 'shared.dart'; + +class Angel { + String wsEndPoint; + WebSocket _socket; + + Angel(String this.wsEndPoint) { + _socket = new WebSocket(wsEndPoint); + } + + AngelService service(String path) { + return new AngelService._base(_socket, path.trim().replaceAll(new RegExp(r'(^\/+)|(\/+$)'), '')); + } +} + +class AngelService { + WebSocket _socket; + String path; + + AngelService._base(WebSocket this._socket, path) {} + + index([Map params]) { + AngelMessage request = new AngelMessage(path, 'index', body: params); + _socket.send(JSON.encode(request.toMap())); + } +} \ No newline at end of file diff --git a/lib/server.dart b/lib/server.dart new file mode 100644 index 00000000..dece693e --- /dev/null +++ b/lib/server.dart @@ -0,0 +1,86 @@ +///Exposes WebSocket functionality to Angel. +library angel_websocket.server; + +import 'dart:io'; +import 'package:angel_framework/angel_framework.dart'; +import 'shared.dart'; + +_respond(AngelMessage message, Service service, Angel app) async { + if (message.method == 'index') { + return await service.index(message.body['query']); + } + + else if (message.method == 'read') { + return await service.read(message.body['id'], message.body['query']); + } + + else if (message.method == 'modify') { + return await service.modify( + message.body['id'], message.body['data'] ?? {}, message.body['query']); + } + + else if (message.method == 'update') { + return await service.update( + message.body['id'], message.body['data'] ?? {}, message.body['query']); + } + + else if (message.method == 'remove') { + return await service.remove(message.body['id'], message.body['query']); + } + + else throw new AngelHttpException.NotImplemented( + message: "This service does not support a \"${message + .method}\" method."); +} + +_handleMsg(WebSocket socket, Angel app) { + return (msg) async { + String text = msg.toString(); + try { + AngelMessage incoming = new AngelMessage.fromMap( + app.god.serializeToMap(text)); + try { + Service service = app.service(incoming.service); + if (service == null) { + throw new AngelHttpException.NotFound( + message: 'The requested service does not exist.'); + } + + // Now, let's respond. :) + var result = await _respond(incoming, service, app); + AngelMessage response = new AngelMessage( + incoming.service, incoming.method, body: {'result': result}); + socket.add(app.god.serialize(response)); + } catch (e) { + AngelHttpException err = (e is AngelHttpException) + ? e + : new AngelHttpException(e); + AngelMessage response = new AngelMessage( + incoming.service, incoming.method, body: err.toMap()); + socket.add(app.god.serialize(response)); + } + } catch (e) { + // If we are sent invalid data, we're not even going to + // bother responding. :) + } + }; +} + +websocket({String endPoint: '/ws'}) { + return (Angel app) async { + app.get(endPoint, (RequestContext req, ResponseContext res) async { + if (WebSocketTransformer.isUpgradeRequest(req.underlyingRequest)) { + res + ..end() + ..willCloseItself = true; + WebSocket socket = await WebSocketTransformer.upgrade( + req.underlyingRequest); + + socket.listen(_handleMsg(socket, app)); + } else { + throw new AngelHttpException.BadRequest( + message: 'This endpoint is only available via WebSockets.'); + } + }); + }; +} \ No newline at end of file diff --git a/lib/shared.dart b/lib/shared.dart new file mode 100644 index 00000000..7ce9b229 --- /dev/null +++ b/lib/shared.dart @@ -0,0 +1,53 @@ +library angel_websocket; +import 'dart:math'; + +String _randomString(int length) { + var rand = new Random(); + var codeUnits = new List.generate( + length, + (index){ + return rand.nextInt(33)+89; + } + ); + + return new String.fromCharCodes(codeUnits); +} + +/// A WebSocket message sent from server to client, or vice-versa. +class AngelMessage { + String id; + String service; + String method; + Map body; + + AngelMessage(String this.service, String this.method, + {Map this.body: const {}}) { + id = _randomString(32); + } + + /// Parses a Map into an AngelMessage. + AngelMessage.fromMap(Map msg) { + bool invalid = !(msg['service'] is String) || + (msg['service'] is String && msg['service'].isEmpty); + invalid = invalid || !(msg['method'] is String) || + (msg['method'] is String && msg['method'].isEmpty); + + if (invalid) { + throw new Exception("Invalid message supplied."); + } else { + this.id = _randomString(32); + this.service = msg['service']; + this.method = msg['method']; + this.body = msg['body'] ?? {}; + } + } + + Map toMap() { + return { + 'id': id, + 'service': service, + 'method': method, + 'body': body + }; + } +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 00000000..35516d53 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,10 @@ +name: angel_websocket +description: WebSocket plugin for Angel +version: 0.0.0-dev.0 +author: thosakwe +homepage: https://github.com/angel-dart/angel_websocket +dependencies: + angel_framework: ">=0.0.0-dev < 0.1.0" +dev_dependencies: + http: ">= 0.11.3 < 0.12.0" + test: ">= 0.12.13 < 0.13.0" \ No newline at end of file