Updated websocket

This commit is contained in:
thomashii 2021-07-10 12:32:42 +08:00
parent 0d294adb20
commit 62336e0d53
25 changed files with 158 additions and 223 deletions

View file

@ -52,7 +52,7 @@ abstract class BaseAngelClient extends Angel {
final List<Service> _services = []; final List<Service> _services = [];
final http.BaseClient client; final http.BaseClient client;
final _p = Context(style: Style.url); final Context _p = Context(style: Style.url);
@override @override
Stream<AngelAuthResult> get onAuthenticated => _onAuthenticated.stream; Stream<AngelAuthResult> get onAuthenticated => _onAuthenticated.stream;
@ -212,7 +212,7 @@ class BaseAngelService<Id, Data> extends Service<Id, Data?> {
final http.BaseClient client; final http.BaseClient client;
final AngelDeserializer<Data>? deserializer; final AngelDeserializer<Data>? deserializer;
final _p = Context(style: Style.url); final Context _p = Context(style: Style.url);
final StreamController<List<Data?>> _onIndexed = StreamController(); final StreamController<List<Data?>> _onIndexed = StreamController();
final StreamController<Data?> _onRead = StreamController(), final StreamController<Data?> _onRead = StreamController(),

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.pub" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Dart SDK" level="project" />
<orderEntry type="library" name="Dart Packages" level="project" />
</component>
</module>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="ECMAScript 6" />
<includedPredefinedLibrary name="Node.js Core" />
</component>
</project>

View file

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state>
<expanded-state>
<State>
<id />
</State>
<State>
<id>General</id>
</State>
<State>
<id>XPath</id>
</State>
</expanded-state>
<selected-state>
<State>
<id>AngularJS</id>
</State>
</selected-state>
</profile-state>
</entry>
</component>
</project>

View file

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/angel_proxy.iml" filepath="$PROJECT_DIR$/.idea/angel_proxy.iml" />
</modules>
</component>
</project>

View file

@ -1,8 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
<option name="filePath" value="$PROJECT_DIR$" />
<option name="scope" value="FOLDER" />
<option name="testRunnerOptions" value="-j 4" />
<method />
</configuration>
</component>

View file

@ -1,6 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Basic Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
<option name="filePath" value="$PROJECT_DIR$/test/basic_test.dart" />
<method />
</configuration>
</component>

View file

@ -1,6 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Pub Serve Tests" type="DartTestRunConfigurationType" factoryName="Dart Test" singleton="true">
<option name="filePath" value="$PROJECT_DIR$/test/pub_serve_test.dart" />
<method />
</configuration>
</component>

View file

@ -1,7 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="multiple.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" singleton="true" nameIsGenerated="true">
<option name="filePath" value="$PROJECT_DIR$/example/multiple.dart" />
<option name="workingDirectory" value="$PROJECT_DIR$" />
<method />
</configuration>
</component>

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View file

@ -1,11 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Multiple Proxies",
"type": "dart-cli",
"request": "launch",
"program": "${workspaceRoot}/example/multiple.dart"
}
]
}

View file

@ -1,32 +1,43 @@
# 4.0.0 # Change Log
## 4.0.1
* Updated README
## 4.0.0
* Migrated to support Dart SDK 2.12.x NNBD * Migrated to support Dart SDK 2.12.x NNBD
# 3.0.0 ## 3.0.0
* Migrated to work with Dart SDK 2.12.x Non NNBD * Migrated to work with Dart SDK 2.12.x Non NNBD
# 2.2.0 ## 2.2.0
* Use `http.Client` instead of `http.BaseClient`, and make it an * Use `http.Client` instead of `http.BaseClient`, and make it an
optional parameter. optional parameter.
* Allow `baseUrl` to accept `Uri` or `String`. * Allow `baseUrl` to accept `Uri` or `String`.
* Add `Proxy.pushState`. * Add `Proxy.pushState`.
# 2.1.2 ## 2.1.2
* Apply lints. * Apply lints.
# 2.1.1 ## 2.1.1
* Update for framework@2.0.0-alpha.15 * Update for framework@2.0.0-alpha.15
# 2.1.0 ## 2.1.0
- Use `Uri` instead of archaic `host`, `port`, and `mapTo`. Also cleaner + safer + easier. * Use `Uri` instead of archaic `host`, `port`, and `mapTo`. Also cleaner + safer + easier.
* Enable WebSocket proxying. * Enable WebSocket proxying.
# 2.0.0 ## 2.0.0
- Updates for Angel 2. Big thanks to @denkuy! * Updates for Angel 2. Big thanks to @denkuy!
- Use `package:path` for better path resolution. * Use `package:path` for better path resolution.
# 1.1.1 ## 1.1.1
- Removed reference to `io`; now works with HTTP/2. Thanks to @daniel-v! * Removed reference to `io`; now works with HTTP/2. Thanks to @daniel-v!

View file

@ -1,12 +1,12 @@
# angel3_proxy # Angel3 Proxy
[![version](https://img.shields.io/badge/pub-v4.0.0-brightgreen)](https://pub.dartlang.org/packages/angel3_proxy)
[![version](https://img.shields.io/badge/pub-v4.0.1-brightgreen)](https://pub.dartlang.org/packages/angel3_proxy)
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion) [![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/proxy/LICENSE) [![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/proxy/LICENSE)
Angel middleware to forward requests to another server (i.e. `webdev serve`). Angel middleware to forward requests to another server (i.e. `webdev serve`). Also supports WebSockets.
Also supports WebSockets.
```dart ```dart
import 'package:angel3_proxy/angel3_proxy.dart'; import 'package:angel3_proxy/angel3_proxy.dart';
@ -23,15 +23,17 @@ void main() async {
``` ```
You can also restrict the proxy to serving only from a specific root: You can also restrict the proxy to serving only from a specific root:
```dart ```dart
Proxy(baseUrl, publicPath: '/remote'); Proxy(baseUrl, publicPath: '/remote');
``` ```
Also, you can map requests to a root path on the remote server: Also, you can map requests to a root path on the remote server:
```dart ```dart
Proxy(baseUrl.replace(path: '/path')); Proxy(baseUrl.replace(path: '/path'));
``` ```
Request bodies will be forwarded as well, if they are not empty. This allows things like POST requests to function. Request bodies will be forwarded as well, if they are not empty. This allows things like POST requests to function.
For a request body to be forwarded, the body must not have already been parsed. For a request body to be forwarded, the body must not have already been parsed.

View file

@ -5,7 +5,7 @@ import 'package:angel3_framework/angel3_framework.dart';
import 'package:angel3_framework/http.dart'; import 'package:angel3_framework/http.dart';
import 'package:http_parser/http_parser.dart'; import 'package:http_parser/http_parser.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:path/path.dart' as p; import 'package:path/path.dart';
final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)'); final RegExp _straySlashes = RegExp(r'(^/+)|(/+$)');
final MediaType _fallbackMediaType = MediaType('application', 'octet-stream'); final MediaType _fallbackMediaType = MediaType('application', 'octet-stream');
@ -16,6 +16,8 @@ final MediaType _fallbackMediaType = MediaType('application', 'octet-stream');
class Proxy { class Proxy {
String? _prefix; String? _prefix;
final Context _p = Context(style: Style.url);
/// The underlying [Client] to use. /// The underlying [Client] to use.
final http.Client httpClient; final http.Client httpClient;
@ -82,12 +84,12 @@ class Proxy {
Future<bool> handleRequest(RequestContext req, ResponseContext res) { Future<bool> handleRequest(RequestContext req, ResponseContext res) {
var path = req.path.replaceAll(_straySlashes, ''); var path = req.path.replaceAll(_straySlashes, '');
if (_prefix!.isNotEmpty) { if (_prefix?.isNotEmpty == true) {
if (!p.isWithin(_prefix!, path) && !p.equals(_prefix!, path)) { if (!_p.isWithin(_prefix!, path) && !_p.equals(_prefix!, path)) {
return Future<bool>.value(true); return Future<bool>.value(true);
} }
path = p.relative(path, from: _prefix); path = _p.relative(path, from: _prefix);
} }
return servePath(path, req, res); return servePath(path, req, res);
@ -98,7 +100,7 @@ class Proxy {
String path, RequestContext req, ResponseContext res) async { String path, RequestContext req, ResponseContext res) async {
http.StreamedResponse rs; http.StreamedResponse rs;
var uri = baseUrl.replace(path: p.join(baseUrl.path, path)); var uri = baseUrl.replace(path: _p.join(baseUrl.path, path));
try { try {
if (req is HttpRequestContext && if (req is HttpRequestContext &&
@ -123,13 +125,13 @@ class Proxy {
var headers = <String, String>{ var headers = <String, String>{
'host': uri.authority, 'host': uri.authority,
'x-forwarded-for': req.remoteAddress.address, 'x-forwarded-for': req.remoteAddress.address,
'x-forwarded-port': req.uri!.port.toString(), 'x-forwarded-port': req.uri?.port.toString() ?? '',
'x-forwarded-host': 'x-forwarded-host':
req.headers!.host ?? req.headers!.value('host') ?? 'none', req.headers?.host ?? req.headers?.value('host') ?? 'none',
'x-forwarded-proto': uri.scheme, 'x-forwarded-proto': uri.scheme,
}; };
req.headers!.forEach((name, values) { req.headers?.forEach((name, values) {
headers[name] = values.join(','); headers[name] = values.join(',');
}); });
@ -139,8 +141,8 @@ class Proxy {
List<int>? body; List<int>? body;
if (!req.hasParsedBody) { if (!req.hasParsedBody) {
body = await req.body! body = await req.body
.fold<BytesBuilder>(BytesBuilder(), (bb, buf) => bb..add(buf)) ?.fold<BytesBuilder>(BytesBuilder(), (bb, buf) => bb..add(buf))
.then((bb) => bb.takeBytes()); .then((bb) => bb.takeBytes());
} }
@ -149,13 +151,17 @@ class Proxy {
rq.headers['host'] = rq.url.host; rq.headers['host'] = rq.url.host;
rq.encoding = Utf8Codec(allowMalformed: true); rq.encoding = Utf8Codec(allowMalformed: true);
if (body != null) rq.bodyBytes = body; if (body != null) {
rq.bodyBytes = body;
}
return httpClient.send(rq); return httpClient.send(rq);
} }
var future = accessRemote(); var future = accessRemote();
if (timeout != null) future = future.timeout(timeout!); if (timeout != null) {
future = future.timeout(timeout!);
}
rs = await future; rs = await future;
} on TimeoutException catch (e, st) { } on TimeoutException catch (e, st) {
if (recoverFromDead) return true; if (recoverFromDead) return true;

View file

@ -1,7 +1,8 @@
name: angel3_proxy name: angel3_proxy
version: 4.0.0 version: 4.0.1
description: Angel middleware to forward requests to another server (i.e. pub serve). description: Angel middleware to forward requests to another server (i.e. pub serve).
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/proxy homepage: https://angel3-framework.web.app/
repository: https://github.com/dukefirehawk/angel/tree/angel3/packages/proxy
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:

View file

@ -9,7 +9,7 @@ import 'package:test/test.dart';
import 'common.dart'; import 'common.dart';
void main() { void main() {
Angel? app; late Angel app;
var client = http.IOClient(); var client = http.IOClient();
//late HttpServer server; //late HttpServer server;
late HttpServer testServer; late HttpServer testServer;
@ -17,7 +17,7 @@ void main() {
setUp(() async { setUp(() async {
app = Angel(); app = Angel();
var appHttp = AngelHttp(app!); var appHttp = AngelHttp(app);
testServer = await startTestServer(); testServer = await startTestServer();
@ -33,15 +33,15 @@ void main() {
print('Proxy 1 on: ${proxy1.baseUrl}'); print('Proxy 1 on: ${proxy1.baseUrl}');
print('Proxy 2 on: ${proxy2.baseUrl}'); print('Proxy 2 on: ${proxy2.baseUrl}');
app!.all('/proxy/*', proxy1.handleRequest); app.all('/proxy/*', proxy1.handleRequest);
app!.all('*', proxy2.handleRequest); app.all('*', proxy2.handleRequest);
app!.fallback((req, res) { app.fallback((req, res) {
print('Intercepting empty from ${req.uri}'); print('Intercepting empty from ${req.uri}');
res.write('intercept empty'); res.write('intercept empty');
}); });
app!.logger = Logger('angel'); app.logger = Logger('angel');
Logger.root.onRecord.listen((rec) { Logger.root.onRecord.listen((rec) {
print(rec); print(rec);
@ -56,7 +56,6 @@ void main() {
tearDown(() async { tearDown(() async {
await testServer.close(force: true); await testServer.close(force: true);
//await server.close(force: true); //await server.close(force: true);
app = null;
url = null; url = null;
}); });

View file

@ -1,7 +1,7 @@
name: angel3_static name: angel3_static
description: This library provides a virtual directory to serve static files for Angel3 framework. description: This library provides a virtual directory to serve static files for Angel3 framework.
version: 4.0.2 version: 4.0.2
homepage: https://github.com/dukefirehawk/angel homepage: https://angel3-framework.web.app/
repository: https://github.com/dukefirehawk/angel/tree/angel3/packages/static repository: https://github.com/dukefirehawk/angel/tree/angel3/packages/static
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'

View file

@ -1,63 +1,88 @@
# 4.0.0 # Change Log
## 4.0.1
* Updated README
* Fixed NNBD issues
## 4.0.0
* Migrated to support Dart SDK 2.12.x NNBD * Migrated to support Dart SDK 2.12.x NNBD
# 3.0.0 ## 3.0.0
* Migrated to work with Dart SDK 2.12.x Non NNBD * Migrated to work with Dart SDK 2.12.x Non NNBD
# 2.0.3 ## 2.0.3
* Remove `WebSocketController.plugin`. * Remove `WebSocketController.plugin`.
* Remove any unawaited futures. * Remove any unawaited futures.
# 2.0.2 ## 2.0.2
* Update `stream_channel` to `2.0.0`. * Update `stream_channel` to `2.0.0`.
* Use `angel_framework^@2.0.0-rc.0`. * Use `angel_framework^@2.0.0-rc.0`.
# 2.0.1 ## 2.0.1
* Add `reconnectOnClose` and `reconnectinterval` parameters in top-level `WebSockets` constructors. * Add `reconnectOnClose` and `reconnectinterval` parameters in top-level `WebSockets` constructors.
* Close `WebSocketExtraneousEventHandler`. * Close `WebSocketExtraneousEventHandler`.
* Add onAuthenticated to server-side. * Add onAuthenticated to server-side.
# 2.0.0 ## 2.0.0
* Update to work with `client@2.0.0`. * Update to work with `client@2.0.0`.
# 2.0.0-alpha.8 ## 2.0.0-alpha.8
* Support for WebSockets over HTTP/2 (though in practice this doesn't often happen, if ever). * Support for WebSockets over HTTP/2 (though in practice this doesn't often happen, if ever).
# 2.0.0-alpha.7 ## 2.0.0-alpha.7
* Replace `WebSocketSynchronizer` with `StreamChannel<WebSocketEvent>`. * Replace `WebSocketSynchronizer` with `StreamChannel<WebSocketEvent>`.
# 2.0.0-alpha.6 ## 2.0.0-alpha.6
* Explicit import of `import 'package:http/io_client.dart' as http;` * Explicit import of `import 'package:http/io_client.dart' as http;`
# 2.0.0-alpha.5 ## 2.0.0-alpha.5
* Update `http` dependency. * Update `http` dependency.
# 2.0.0-alpha.4 ## 2.0.0-alpha.4
* Remove `package:json_god`. * Remove `package:json_god`.
* Make `WebSocketContext` take any `StreamChannel`. * Make `WebSocketContext` take any `StreamChannel`.
* Strong typing updates. * Strong typing updates.
# 2.0.0-alpha.3 ## 2.0.0-alpha.3
* Directly import Angel HTTP. * Directly import Angel HTTP.
# 2.0.0-alpha.2 ## 2.0.0-alpha.2
* Updated for the next version of `angel_client`. * Updated for the next version of `angel_client`.
# 2.0.0-alpha.1 ## 2.0.0-alpha.1
* Refactorings for updated Angel 2 versions. * Refactorings for updated Angel 2 versions.
* Remove `package:dart2_constant`. * Remove `package:dart2_constant`.
# 2.0.0-alpha ## 2.0.0-alpha
* Depend on Dart 2 and Angel 2. * Depend on Dart 2 and Angel 2.
# 1.1.2 ## 1.1.2
* Dart 2 updates. * Dart 2 updates.
* Added `handleClient`, which is nice for external implementations * Added `handleClient`, which is nice for external implementations
that plug into `AngelWebSocket`. that plug into `AngelWebSocket`.
# 1.1.1 ## 1.1.1
* Deprecated `unwrap`. * Deprecated `unwrap`.
* Service streams now pump out `e.data`, rather than the actual event. * Service streams now pump out `e.data`, rather than the actual event.
# 1.1.0+1 ## 1.1.0+1
* Added `unwrap`.
* Added `unwrap`.

View file

@ -1,29 +1,24 @@
# angel3_websocket # Angel3 Websocket Library
[![version](https://img.shields.io/badge/pub-v4.0.0-brightgreen)](https://pub.dartlang.org/packages/angel3_websocket)
[![version](https://img.shields.io/badge/pub-v4.0.1-brightgreen)](https://pub.dartlang.org/packages/angel3_websocket)
[![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety) [![Null Safety](https://img.shields.io/badge/null-safety-brightgreen)](https://dart.dev/null-safety)
[![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion) [![Gitter](https://img.shields.io/gitter/room/angel_dart/discussion)](https://gitter.im/angel_dart/discussion)
[![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/websocket/LICENSE) [![License](https://img.shields.io/github/license/dukefirehawk/angel)](https://github.com/dukefirehawk/angel/tree/angel3/packages/websocket/LICENSE)
WebSocket plugin for Angel. WebSocket plugin for Angel3. This plugin broadcasts events from hooked services via WebSockets. In addition, it adds itself to the app's IoC container as `AngelWebSocket`, so that it can be used in controllers as well.
This plugin broadcasts events from hooked services via WebSockets.
In addition, it adds itself to the app's IoC container as `AngelWebSocket`, so that it can be used
in controllers as well.
WebSocket contexts are add to `req.properties` as `'socket'`. WebSocket contexts are add to `req.properties` as `'socket'`.
## Usage
# Usage ### Server-side
**Server-side**
```dart ```dart
import "package:angel3_framework/angel3_framework.dart"; import "package:angel3_framework/angel3_framework.dart";
import "package:angel3_websocket/server.dart"; import "package:angel3_websocket/server.dart";
main() async { void main() async {
var app = Angel(); var app = Angel();
var ws = AngelWebSocket(); var ws = AngelWebSocket();
@ -38,8 +33,7 @@ main() async {
``` ```
Filtering events is easy with hooked services. Just return a `bool`, whether Filtering events is easy with hooked services. Just return a `bool`, whether synchronously or asynchronously.
synchronously or asynchronously.
```dart ```dart
myService.properties['ws:filter'] = (HookedServiceEvent e, WebSocketContext socket) async { myService.properties['ws:filter'] = (HookedServiceEvent e, WebSocketContext socket) async {
@ -51,14 +45,14 @@ myService.index({
}); });
``` ```
**Adding Handlers within a Controller** #### Adding Handlers within a Controller
`WebSocketController` extends a normal `Controller`, but also listens to WebSockets. `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:angel3_framework/angel3_framework.dart";
import "package:angel_websocket/server.dart"; import "package:angel3_websocket/server.dart";
@Expose("/") @Expose("/")
class MyController extends WebSocketController { class MyController extends WebSocketController {
@ -86,25 +80,22 @@ class MyController extends WebSocketController {
} }
``` ```
**Client Use** ### Client Use
This repo also provides two client libraries `browser` and `io` that extend the base This repo also provides two client libraries `browser` and `io` that extend the base `angel3_client` interface, and allow you to use a very similar API on the client to that of the server.
`angel3_client` interface, and allow you to use a very similar API on the client to that of
the server.
The provided clients also automatically try to reconnect their WebSockets when disconnected, The provided clients also automatically try to reconnect their WebSockets when disconnected, which means you can restart your development server without having to reload browser windows.
which means you can restart your development server without having to reload browser windows.
They also provide streams of data that pump out filtered data as it comes in from the server. They also provide streams of data that pump out filtered data as it comes in from the server.
Clients can even perform authentication over WebSockets. Clients can even perform authentication over WebSockets.
**In the Browser** #### In the Browser
```dart ```dart
import "package:angel3_websocket/browser.dart"; import "package:angel3_websocket/browser.dart";
main() async { void main() async {
Angel app = WebSockets("/ws"); Angel app = WebSockets("/ws");
await app.connect(); await app.connect();
@ -127,7 +118,7 @@ main() async {
} }
``` ```
**CLI Client** #### CLI Client
```dart ```dart
import "package:angel3_framework/common.dart"; import "package:angel3_framework/common.dart";
@ -163,4 +154,4 @@ main() async {
// Authenticate a WebSocket, if you were not already authenticated... // Authenticate a WebSocket, if you were not already authenticated...
app.authenticateViaJwt('<some-jwt>'); app.authenticateViaJwt('<some-jwt>');
} }

View file

@ -241,7 +241,7 @@ abstract class BaseWebSocketClient extends BaseAngelClient {
if (_socket == null) { if (_socket == null) {
_queue.addLast(action); _queue.addLast(action);
} else { } else {
socket!.sink.add(serialize(action)); socket?.sink.add(serialize(action));
} }
} }

View file

@ -42,7 +42,7 @@ class WebSockets extends BaseWebSocketClient {
StreamSubscription<Event>? sub; StreamSubscription<Event>? sub;
t = Timer.periodic(Duration(milliseconds: 500), (timer) { t = Timer.periodic(Duration(milliseconds: 500), (timer) {
if (!ctrl.isClosed) { if (!ctrl.isClosed) {
if (wnd.closed!) { if (wnd.closed == true) {
ctrl.addError(AngelHttpException.notAuthenticated( ctrl.addError(AngelHttpException.notAuthenticated(
message: message:
errorMessage ?? 'Authentication via popup window failed.')); errorMessage ?? 'Authentication via popup window failed.'));
@ -60,7 +60,7 @@ class WebSockets extends BaseWebSocketClient {
ctrl.add((e as CustomEvent).detail.toString()); ctrl.add((e as CustomEvent).detail.toString());
t.cancel(); t.cancel();
ctrl.close(); ctrl.close();
sub!.cancel(); sub?.cancel();
} }
}); });

View file

@ -7,6 +7,7 @@ import 'package:angel3_framework/angel3_framework.dart';
HookedServiceEventListener doNotBroadcast([provider]) { HookedServiceEventListener doNotBroadcast([provider]) {
return (HookedServiceEvent e) { return (HookedServiceEvent e) {
if (e.params != null && e.params!.containsKey('provider')) { if (e.params != null && e.params!.containsKey('provider')) {
var eParam = e.params!;
var deny = false; var deny = false;
var providers = provider is Iterable ? provider : [provider]; var providers = provider is Iterable ? provider : [provider];
@ -14,18 +15,15 @@ HookedServiceEventListener doNotBroadcast([provider]) {
if (deny) break; if (deny) break;
if (p is Providers) { if (p is Providers) {
deny = deny || deny = deny || p == eParam['provider'] || eParam['provider'] == p.via;
p == e.params!['provider'] ||
e.params!['provider'] == p.via;
} else if (p == null) { } else if (p == null) {
deny = true; deny = true;
} else { } else {
deny = deny = deny || (eParam['provider'] as Providers).via == p.toString();
deny || (e.params!['provider'] as Providers).via == p.toString();
} }
} }
e.params!['broadcast'] = false; eParam['broadcast'] = false;
} }
}; };
} }

View file

@ -14,6 +14,7 @@ import 'package:stream_channel/stream_channel.dart';
import 'package:web_socket_channel/io.dart'; import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:collection/collection.dart' show IterableExtension; import 'package:collection/collection.dart' show IterableExtension;
import 'package:logging/logging.dart';
import 'angel3_websocket.dart'; import 'angel3_websocket.dart';
import 'constants.dart'; import 'constants.dart';
export 'angel3_websocket.dart'; export 'angel3_websocket.dart';
@ -26,6 +27,8 @@ typedef WebSocketResponseSerializer = String Function(dynamic data);
/// Broadcasts events from [HookedService]s, and handles incoming [WebSocketAction]s. /// Broadcasts events from [HookedService]s, and handles incoming [WebSocketAction]s.
class AngelWebSocket { class AngelWebSocket {
final _log = Logger('AngelWebSocket');
final List<WebSocketContext> _clients = <WebSocketContext>[]; final List<WebSocketContext> _clients = <WebSocketContext>[];
final List<String> _servicesAlreadyWired = []; final List<String> _servicesAlreadyWired = [];
@ -258,6 +261,7 @@ class AngelWebSocket {
return null; return null;
} }
} catch (e, st) { } catch (e, st) {
_log.severe('Unable to handle unknown action');
catchError(e, st, socket); catchError(e, st, socket);
} }
} }
@ -266,15 +270,15 @@ class AngelWebSocket {
Future handleAuth(WebSocketAction action, WebSocketContext socket) async { Future handleAuth(WebSocketAction action, WebSocketContext socket) async {
if (allowAuth != false && if (allowAuth != false &&
action.eventName == authenticateAction && action.eventName == authenticateAction &&
action.params!['query'] is Map && action.params?['query'] is Map &&
action.params!['query']['jwt'] is String) { action.params?['query']['jwt'] is String) {
try { try {
var auth = socket.request.container!.make<AngelAuth>()!; var auth = socket.request.container!.make<AngelAuth>()!;
var jwt = action.params!['query']['jwt'] as String; var jwt = action.params!['query']['jwt'] as String;
AuthToken token; AuthToken token;
token = AuthToken.validate(jwt, auth.hmac); token = AuthToken.validate(jwt, auth.hmac);
var user = await auth.deserializer!(token.userId as Object); var user = await auth.deserializer(token.userId as Object);
socket.request socket.request
..container!.registerSingleton<AuthToken>(token) ..container!.registerSingleton<AuthToken>(token)
..container!.registerSingleton(user, as: user.runtimeType); ..container!.registerSingleton(user, as: user.runtimeType);
@ -282,6 +286,7 @@ class AngelWebSocket {
socket.send(authenticatedEvent, socket.send(authenticatedEvent,
{'token': token.serialize(auth.hmac), 'data': user}); {'token': token.serialize(auth.hmac), 'data': user});
} catch (e, st) { } catch (e, st) {
_log.severe('Authentication failed');
catchError(e, st, socket); catchError(e, st, socket);
} }
} else { } else {
@ -345,6 +350,7 @@ class AngelWebSocket {
} }
} }
} catch (e, st) { } catch (e, st) {
_log.severe('Invalid data');
catchError(e, st, socket); catchError(e, st, socket);
} }
} }
@ -353,16 +359,16 @@ class AngelWebSocket {
// Send an error // Send an error
if (e is AngelHttpException) { if (e is AngelHttpException) {
socket.sendError(e); socket.sendError(e);
app!.logger?.severe(e.message, e.error ?? e, e.stackTrace); app?.logger?.severe(e.message, e.error ?? e, e.stackTrace);
} else if (sendErrors) { } else if (sendErrors) {
var err = AngelHttpException(e, var err = AngelHttpException(e,
message: e.toString(), stackTrace: st, errors: [st.toString()]); message: e.toString(), stackTrace: st, errors: [st.toString()]);
socket.sendError(err); socket.sendError(err);
app!.logger?.severe(err.message, e, st); app?.logger?.severe(err.message, e, st);
} else { } else {
var err = AngelHttpException(e); var err = AngelHttpException(e);
socket.sendError(err); socket.sendError(err);
app!.logger?.severe(e.toString(), e, st); app?.logger?.severe(e.toString(), e, st);
} }
} }
@ -383,10 +389,10 @@ class AngelWebSocket {
/// Configures an [Angel] instance to listen for WebSocket connections. /// Configures an [Angel] instance to listen for WebSocket connections.
Future configureServer(Angel app) async { Future configureServer(Angel app) async {
app.container!.registerSingleton(this); app.container?.registerSingleton(this);
if (runtimeType != AngelWebSocket) { if (runtimeType != AngelWebSocket) {
app.container!.registerSingleton<AngelWebSocket>(this); app.container?.registerSingleton<AngelWebSocket>(this);
} }
// Set up services // Set up services
@ -397,7 +403,7 @@ class AngelWebSocket {
}); });
if (synchronizationChannel != null) { if (synchronizationChannel != null) {
synchronizationChannel!.stream synchronizationChannel?.stream
.listen((e) => batchEvent(e, notify: false)); .listen((e) => batchEvent(e, notify: false));
} }
@ -406,7 +412,7 @@ class AngelWebSocket {
/// Handles an incoming [WebSocketContext]. /// Handles an incoming [WebSocketContext].
Future<void> handleClient(WebSocketContext socket) async { Future<void> handleClient(WebSocketContext socket) async {
var origin = socket.request.headers!.value('origin'); var origin = socket.request.headers?.value('origin');
if (allowedOrigins != null && !allowedOrigins!.contains(origin)) { if (allowedOrigins != null && !allowedOrigins!.contains(origin)) {
throw AngelHttpException.forbidden( throw AngelHttpException.forbidden(
message: message:
@ -418,7 +424,7 @@ class AngelWebSocket {
_onConnection.add(socket); _onConnection.add(socket);
socket.request.container!.registerSingleton<WebSocketContext>(socket); socket.request.container?.registerSingleton<WebSocketContext>(socket);
socket.channel.stream.listen( socket.channel.stream.listen(
(data) { (data) {
@ -451,11 +457,11 @@ class AngelWebSocket {
return false; return false;
} else if (req is Http2RequestContext && res is Http2ResponseContext) { } else if (req is Http2RequestContext && res is Http2ResponseContext) {
var connection = var connection =
req.headers!['connection']?.map((s) => s.toLowerCase().trim()); req.headers?['connection']?.map((s) => s.toLowerCase().trim());
var upgrade = req.headers!.value('upgrade')?.toLowerCase(); var upgrade = req.headers?.value('upgrade')?.toLowerCase();
var version = req.headers!.value('sec-websocket-version'); var version = req.headers?.value('sec-websocket-version');
var key = req.headers!.value('sec-websocket-key'); var key = req.headers?.value('sec-websocket-key');
var protocol = req.headers!.value('sec-websocket-protocol'); var protocol = req.headers?.value('sec-websocket-protocol');
if (connection == null) { if (connection == null) {
throw AngelHttpException.badRequest( throw AngelHttpException.badRequest(

View file

@ -1,7 +1,8 @@
name: angel3_websocket name: angel3_websocket
description: Support for using pkg:angel_client with WebSockets. Designed for Angel. description: This library provides WebSockets support for Angel3 framework.
version: 4.0.0 version: 4.0.1
homepage: https://github.com/dukefirehawk/angel/tree/angel3/packages/websocket homepage: https://angel3-framework.web.app/
repository: https://github.com/dukefirehawk/angel/tree/angel3/packages/websocket
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
@ -15,10 +16,10 @@ dependencies:
stream_channel: ^2.1.0 stream_channel: ^2.1.0
web_socket_channel: ^2.1.0 web_socket_channel: ^2.1.0
collection: ^1.15.0 collection: ^1.15.0
logging: ^1.0.1
dev_dependencies: dev_dependencies:
angel3_container: ^3.0.0 angel3_container: ^3.0.0
angel3_model: ^3.0.0 angel3_model: ^3.0.0
logging: ^1.0.1
pedantic: ^1.11.0 pedantic: ^1.11.0
test: ^1.17.4 test: ^1.17.4

View file

@ -18,10 +18,8 @@ void main() {
setUp(() async { setUp(() async {
app = Angel(); app = Angel();
http = AngelHttp(app, useZone: false); http = AngelHttp(app, useZone: false);
var auth = AngelAuth(); var auth = AngelAuth(
serializer: (_) async => 'baz', deserializer: (_) async => USER);
auth.serializer = (_) async => 'baz';
auth.deserializer = (_) async => USER;
auth.strategies['local'] = LocalAuthStrategy( auth.strategies['local'] = LocalAuthStrategy(
(username, password) async { (username, password) async {