Client-side 'on' not working :(
This commit is contained in:
parent
876e998445
commit
00b681f04b
5 changed files with 196 additions and 54 deletions
19
README.md
19
README.md
|
@ -27,21 +27,24 @@ main() async {
|
||||||
|
|
||||||
**Adding Handlers within a Controller**
|
**Adding Handlers within a Controller**
|
||||||
|
|
||||||
|
`WebSocketController` extends a normal `Controller`, but also listens to WebSockets.
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import "package:angel_framework/angel_framework.dart";
|
import "package:angel_framework/angel_framework.dart";
|
||||||
import "package:angel_websocket/server.dart";
|
import "package:angel_websocket/server.dart";
|
||||||
|
|
||||||
@Expose("/")
|
@Expose("/")
|
||||||
class MyController extends Controller {
|
class MyController extends WebSocketController {
|
||||||
@override
|
@override
|
||||||
Future call(AngelBase app) async {
|
void onConnect(WebSocketContext socket) {
|
||||||
var ws = app.container.make(AngelWebSocket);
|
// On connect...
|
||||||
ws.onConnection.listen((WebSocketContext socket) {
|
}
|
||||||
socket.on["message"].listen((WebSocketEvent e) {
|
|
||||||
socket.send("new_message", { "text": e.data["text"] });
|
// Dependency injection works, too..
|
||||||
});
|
@ExposeWs("read_message")
|
||||||
});
|
void sendMessage(WebSocketContext socket, Db db) async {
|
||||||
|
socket.send("found_message", db.collection("messages").findOne(where.id("...")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
89
lib/cli.dart
89
lib/cli.dart
|
@ -12,13 +12,16 @@ class WebSocketClient extends Angel {
|
||||||
WebSocket _socket;
|
WebSocket _socket;
|
||||||
Map<Pattern, List<WebSocketService>> _services = {};
|
Map<Pattern, List<WebSocketService>> _services = {};
|
||||||
WebSocket get underlyingSocket => _socket;
|
WebSocket get underlyingSocket => _socket;
|
||||||
|
_WebSocketEventTable on = new _WebSocketEventTable();
|
||||||
|
|
||||||
WebSocketClient(String wsEndpoint) : super(wsEndpoint);
|
WebSocketClient(String wsEndpoint) : super(wsEndpoint);
|
||||||
|
|
||||||
onData(data) {
|
onData(data) async {
|
||||||
var fromJson = JSON.decode(data);
|
var fromJson = JSON.decode(data);
|
||||||
|
print("a: $fromJson");
|
||||||
var e = new WebSocketEvent(
|
var e = new WebSocketEvent(
|
||||||
eventName: fromJson['eventName'], data: fromJson['data']);
|
eventName: fromJson['eventName'], data: fromJson['data']);
|
||||||
|
print("b: $e");
|
||||||
var split = e.eventName.split("::");
|
var split = e.eventName.split("::");
|
||||||
var serviceName = split[0];
|
var serviceName = split[0];
|
||||||
var services = _services[serviceName];
|
var services = _services[serviceName];
|
||||||
|
@ -30,37 +33,41 @@ class WebSocketClient extends Angel {
|
||||||
exc.errors = exc.errors ?? [];
|
exc.errors = exc.errors ?? [];
|
||||||
exc.errors.addAll(e.data['errors'] ?? []);
|
exc.errors.addAll(e.data['errors'] ?? []);
|
||||||
throw exc;
|
throw exc;
|
||||||
} else if (services != null) {
|
} else {
|
||||||
e.eventName = split[1];
|
on._getStreamForEvent(serviceName).add(e.data);
|
||||||
|
|
||||||
for (WebSocketService service in services) {
|
if (services != null) {
|
||||||
service._onAllEvents.add(e);
|
e.eventName = split[1];
|
||||||
switch (e.eventName) {
|
|
||||||
case srv.HookedServiceEvent.INDEXED:
|
for (WebSocketService service in services) {
|
||||||
service._onIndexed.add(e);
|
service._onAllEvents.add(e);
|
||||||
break;
|
switch (e.eventName) {
|
||||||
case srv.HookedServiceEvent.READ:
|
case srv.HookedServiceEvent.INDEXED:
|
||||||
service._onRead.add(e);
|
service._onIndexed.add(e);
|
||||||
break;
|
break;
|
||||||
case srv.HookedServiceEvent.CREATED:
|
case srv.HookedServiceEvent.READ:
|
||||||
service._onCreated.add(e);
|
service._onRead.add(e);
|
||||||
break;
|
break;
|
||||||
case srv.HookedServiceEvent.MODIFIED:
|
case srv.HookedServiceEvent.CREATED:
|
||||||
service._onModified.add(e);
|
service._onCreated.add(e);
|
||||||
break;
|
break;
|
||||||
case srv.HookedServiceEvent.UPDATED:
|
case srv.HookedServiceEvent.MODIFIED:
|
||||||
service._onUpdated.add(e);
|
service._onModified.add(e);
|
||||||
break;
|
break;
|
||||||
case srv.HookedServiceEvent.REMOVED:
|
case srv.HookedServiceEvent.UPDATED:
|
||||||
service._onRemoved.add(e);
|
service._onUpdated.add(e);
|
||||||
break;
|
break;
|
||||||
case "error":
|
case srv.HookedServiceEvent.REMOVED:
|
||||||
service._onError.add(e);
|
service._onRemoved.add(e);
|
||||||
break;
|
break;
|
||||||
default:
|
case "error":
|
||||||
if (service._on._events.containsKey(e.eventName))
|
service._onError.add(e);
|
||||||
service._on._events[e.eventName].add(e);
|
break;
|
||||||
break;
|
default:
|
||||||
|
if (service._on._events.containsKey(e.eventName))
|
||||||
|
service._on._events[e.eventName].add(e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,10 +79,7 @@ class WebSocketClient extends Angel {
|
||||||
}
|
}
|
||||||
|
|
||||||
void send(String eventName, data) {
|
void send(String eventName, data) {
|
||||||
_socket.add(JSON.encode({
|
_socket.add(JSON.encode({"eventName": eventName, "data": data}));
|
||||||
"eventName": eventName,
|
|
||||||
"data": data
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -112,7 +116,8 @@ class _WebSocketServiceTransformer
|
||||||
|
|
||||||
stream.listen((WebSocketEvent e) {
|
stream.listen((WebSocketEvent e) {
|
||||||
if (_outputType != null && e.eventName != "error")
|
if (_outputType != null && e.eventName != "error")
|
||||||
e.data = god.deserialize(god.serialize(e.data), outputType: _outputType);
|
e.data =
|
||||||
|
god.deserialize(god.serialize(e.data), outputType: _outputType);
|
||||||
_stream.add(e);
|
_stream.add(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -205,3 +210,15 @@ class WebSocketService extends Service {
|
||||||
eventName: "$_path::remove", id: id, params: params)));
|
eventName: "$_path::remove", id: id, params: params)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _WebSocketEventTable {
|
||||||
|
Map<String, StreamController<Map>> _handlers = {};
|
||||||
|
|
||||||
|
StreamController<Map> _getStreamForEvent(eventName) {
|
||||||
|
if (!_handlers.containsKey(eventName))
|
||||||
|
_handlers[eventName] = new StreamController<Map>.broadcast();
|
||||||
|
return _handlers[eventName];
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<Map> operator [](String key) => _getStreamForEvent(key).stream;
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ library angel_websocket.server;
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:mirrors';
|
||||||
import 'package:angel_framework/angel_framework.dart';
|
import 'package:angel_framework/angel_framework.dart';
|
||||||
import 'package:json_god/json_god.dart' as god;
|
import 'package:json_god/json_god.dart' as god;
|
||||||
import 'package:merge_map/merge_map.dart';
|
import 'package:merge_map/merge_map.dart';
|
||||||
|
@ -10,10 +11,7 @@ import 'angel_websocket.dart';
|
||||||
export 'angel_websocket.dart';
|
export 'angel_websocket.dart';
|
||||||
|
|
||||||
part 'websocket_context.dart';
|
part 'websocket_context.dart';
|
||||||
|
part 'websocket_controller.dart';
|
||||||
class Realtime {
|
|
||||||
const Realtime();
|
|
||||||
}
|
|
||||||
|
|
||||||
class AngelWebSocket extends AngelPlugin {
|
class AngelWebSocket extends AngelPlugin {
|
||||||
Angel _app;
|
Angel _app;
|
||||||
|
@ -130,7 +128,7 @@ class AngelWebSocket extends AngelPlugin {
|
||||||
|
|
||||||
if (fromJson is Map && fromJson.containsKey("eventName")) {
|
if (fromJson is Map && fromJson.containsKey("eventName")) {
|
||||||
socket._onAll.add(fromJson);
|
socket._onAll.add(fromJson);
|
||||||
socket.on._getStreamForEvent(fromJson["eventName"].toString()).add(fromJson);
|
socket.on._getStreamForEvent(fromJson["eventName"].toString()).add(fromJson["data"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action.eventName.contains("::")) {
|
if (action.eventName.contains("::")) {
|
||||||
|
|
103
lib/websocket_controller.dart
Normal file
103
lib/websocket_controller.dart
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
part of angel_websocket.server;
|
||||||
|
|
||||||
|
class ExposeWs {
|
||||||
|
final String eventName;
|
||||||
|
|
||||||
|
const ExposeWs(this.eventName);
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketController extends Controller {
|
||||||
|
Map<String, MethodMirror> _handlers = {};
|
||||||
|
Map<String, Symbol> _handlerSymbols = {};
|
||||||
|
InstanceMirror _instanceMirror;
|
||||||
|
AngelWebSocket ws;
|
||||||
|
|
||||||
|
WebSocketController():super() {
|
||||||
|
_instanceMirror = reflect(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future call(Angel app) async {
|
||||||
|
await super.call(app);
|
||||||
|
|
||||||
|
ClassMirror classMirror = reflectClass(this.runtimeType);
|
||||||
|
classMirror.instanceMembers.forEach((sym, mirror) {
|
||||||
|
if (mirror.isRegularMethod) {
|
||||||
|
InstanceMirror exposeMirror = mirror.metadata.firstWhere(
|
||||||
|
(mirror) => mirror.reflectee is ExposeWs,
|
||||||
|
orElse: () => null);
|
||||||
|
|
||||||
|
if (exposeMirror != null) {
|
||||||
|
ExposeWs exposeWs = exposeMirror.reflectee;
|
||||||
|
_handlers[exposeWs.eventName] = mirror;
|
||||||
|
_handlerSymbols[exposeWs.eventName] = sym;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AngelWebSocket ws = app.container.make(AngelWebSocket);
|
||||||
|
|
||||||
|
ws.onConnection.listen((socket) async {
|
||||||
|
await onConnect(socket);
|
||||||
|
|
||||||
|
socket.onData.listen(onData);
|
||||||
|
|
||||||
|
socket.onAll.listen((Map data) async {
|
||||||
|
await onAllEvents(data);
|
||||||
|
|
||||||
|
if (_handlers.containsKey(data["eventName"])) {
|
||||||
|
var methodMirror = _handlers[data["eventName"]];
|
||||||
|
try {
|
||||||
|
// Load parameters, and execute
|
||||||
|
List args = [];
|
||||||
|
|
||||||
|
for (int i = 0; i < methodMirror.parameters.length; i++) {
|
||||||
|
ParameterMirror parameter = methodMirror.parameters[i];
|
||||||
|
String name = MirrorSystem.getName(parameter.simpleName);
|
||||||
|
|
||||||
|
if (parameter.type.reflectedType == RequestContext ||
|
||||||
|
name == "req")
|
||||||
|
args.add(socket.requestContext);
|
||||||
|
else if (parameter.type.reflectedType == ResponseContext ||
|
||||||
|
name == "res")
|
||||||
|
args.add(socket.responseContext);
|
||||||
|
else if (parameter.type == AngelWebSocket)
|
||||||
|
args.add(socket);
|
||||||
|
else {
|
||||||
|
if (socket.requestContext.params.containsKey(name)) {
|
||||||
|
args.add(socket.requestContext.params[name]);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
args.add(app.container.make(parameter.type.reflectedType));
|
||||||
|
continue;
|
||||||
|
} catch (e) {
|
||||||
|
throw new AngelHttpException.BadRequest(
|
||||||
|
message: "Missing parameter '$name'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _instanceMirror.invoke(_handlerSymbols[data["eventName"]], args);
|
||||||
|
} catch (e) {
|
||||||
|
// Send an error
|
||||||
|
if (e is AngelHttpException)
|
||||||
|
socket.sendError(e);
|
||||||
|
else
|
||||||
|
socket.sendError(new AngelHttpException(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void broadcast(String eventName, data) {
|
||||||
|
ws.batchEvent(new WebSocketEvent(eventName: eventName, data: data));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future onConnect(WebSocketContext socket) async {}
|
||||||
|
|
||||||
|
Future onAllEvents(Map data) async {}
|
||||||
|
|
||||||
|
void onData(data) {}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ main() {
|
||||||
client.WebSocketClient clientApp;
|
client.WebSocketClient clientApp;
|
||||||
client.WebSocketService clientTodos;
|
client.WebSocketService clientTodos;
|
||||||
Stream<Map> customEventStream;
|
Stream<Map> customEventStream;
|
||||||
|
Stream<Map> customEventStream2;
|
||||||
WebSocket socket;
|
WebSocket socket;
|
||||||
AngelWebSocket webSocket = new AngelWebSocket("/ws");
|
AngelWebSocket webSocket = new AngelWebSocket("/ws");
|
||||||
|
|
||||||
|
@ -36,11 +37,13 @@ main() {
|
||||||
customEventStream = socket.on["custom"];
|
customEventStream = socket.on["custom"];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
await app.configure(new Custom2Controller());
|
||||||
await app.configure(startTestServer);
|
await app.configure(startTestServer);
|
||||||
|
|
||||||
socket = await WebSocket.connect(app.properties["ws_url"]);
|
socket = await WebSocket.connect(app.properties["ws_url"]);
|
||||||
clientApp = new client.WebSocketClient(app.properties["ws_url"]);
|
clientApp = new client.WebSocketClient(app.properties["ws_url"]);
|
||||||
await clientApp.connect();
|
await clientApp.connect();
|
||||||
|
customEventStream2 = clientApp.on["custom2"];
|
||||||
|
|
||||||
clientTodos = clientApp.service("api/todos", type: Todo);
|
clientTodos = clientApp.service("api/todos", type: Todo);
|
||||||
});
|
});
|
||||||
|
@ -61,8 +64,7 @@ main() {
|
||||||
String json = await socket.first;
|
String json = await socket.first;
|
||||||
print(json);
|
print(json);
|
||||||
|
|
||||||
WebSocketEvent e =
|
WebSocketEvent e = god.deserialize(json, outputType: WebSocketEvent);
|
||||||
god.deserialize(json, outputType: WebSocketEvent);
|
|
||||||
expect(e.eventName, equals("api/todos::indexed"));
|
expect(e.eventName, equals("api/todos::indexed"));
|
||||||
expect(e.data[0]["when"], equals("now"));
|
expect(e.data[0]["when"], equals("now"));
|
||||||
});
|
});
|
||||||
|
@ -87,10 +89,29 @@ main() {
|
||||||
|
|
||||||
var data = await customEventStream.first;
|
var data = await customEventStream.first;
|
||||||
|
|
||||||
expect(data["eventName"], equals("custom"));
|
expect(data["hello"], equals("world"));
|
||||||
expect(data["data"]["hello"], equals("world"));
|
});
|
||||||
|
|
||||||
|
test("custom event via ws controller", () async {
|
||||||
|
clientApp.send("custom2", {"hello": "world"});
|
||||||
|
|
||||||
|
var data = customEventStream2.first;
|
||||||
|
print("Received data from server: $data");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Realtime()
|
|
||||||
class FakeService extends server.Service {}
|
class FakeService extends server.Service {}
|
||||||
|
|
||||||
|
@server.Expose("/custom2")
|
||||||
|
class Custom2Controller extends WebSocketController {
|
||||||
|
@override
|
||||||
|
Future onConnect(WebSocketContext socket) async {
|
||||||
|
print(
|
||||||
|
"Got a WS connection from session #${socket.requestContext.session.id}!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExposeWs("custom2")
|
||||||
|
void sayFoo(WebSocketContext socket, server.RequestContext req, AngelWebSocket ws) {
|
||||||
|
socket.send("custom2", {"franken": "stein"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue