Skip to content

Commit a1e8f84

Browse files
authored
feat(dart_frog_web_socket): add package:dart_frog_web_socket (#438)
1 parent 572ae7d commit a1e8f84

File tree

12 files changed

+522
-0
lines changed

12 files changed

+522
-0
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: dart_frog_web_socket
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- ".github/workflows/dart_frog_web_socket.yaml"
7+
- "packages/dart_frog_web_socket/lib/**"
8+
- "packages/dart_frog_web_socket/test/**"
9+
- "packages/dart_frog_web_socket/pubspec.yaml"
10+
push:
11+
branches:
12+
- main
13+
paths:
14+
- ".github/workflows/dart_frog_web_socket.yaml"
15+
- "packages/dart_frog_web_socket/lib/**"
16+
- "packages/dart_frog_web_socket/test/**"
17+
- "packages/dart_frog_web_socket/pubspec.yaml"
18+
19+
jobs:
20+
build:
21+
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1
22+
with:
23+
working_directory: packages/dart_frog_web_socket
24+
25+
pana:
26+
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/pana.yml@v1
27+
with:
28+
working_directory: packages/dart_frog_web_socket
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# See https://www.dartlang.org/guides/libraries/private-files
2+
3+
# Files and directories created by pub
4+
.dart_tool/
5+
.packages
6+
build/
7+
pubspec.lock
8+
9+
# Test related files.
10+
coverage/
11+
test/.fixtures
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# 0.1.0-dev.1
2+
3+
- feat: initial dev release 🎉

packages/dart_frog_web_socket/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Very Good Ventures
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
[![Dart Frog Logo][logo_white]][dart_frog_link_dark]
2+
[![Dart Frog Logo][logo_black]][dart_frog_link_light]
3+
4+
[![ci][ci_badge]][ci_link]
5+
[![coverage][coverage_badge]][ci_link]
6+
[![pub package][pub_badge]][pub_link]
7+
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
8+
[![License: MIT][license_badge]][license_link]
9+
10+
WebSocket support for [Dart Frog][dart_frog_link].
11+
12+
Developed with 💙 by [Very Good Ventures][very_good_ventures_link] 🦄
13+
14+
## Quick Start 🚀
15+
16+
Use `webSocketHandler` to manage `WebSocket` connections in a Dart Frog route handler.
17+
18+
```dart
19+
// routes/ws.dart
20+
import 'package:dart_frog/dart_frog.dart';
21+
import 'package:dart_frog_web_socket/dart_frog_web_socket.dart';
22+
23+
Handler get onRequest {
24+
return webSocketHandler(
25+
(channel, protocol) {
26+
// A new connection was established.
27+
print('connected');
28+
// Subscribe to the stream of messages from the client.
29+
channel.stream.listen(
30+
(message) {
31+
// Handle incoming messages.
32+
print('received: $message');
33+
// Send outgoing messages to the connected client.
34+
channel.sink.add('pong');
35+
},
36+
// The connection was terminated.
37+
onDone: () => print('disconnected'),
38+
);
39+
},
40+
);
41+
}
42+
```
43+
44+
Connect a client to the remote WebSocket endpoint.
45+
46+
```dart
47+
// main.dart
48+
import 'package:web_socket_channel/web_socket_channel.dart';
49+
50+
void main() {
51+
// Connect to the remote WebSocket endpoint.
52+
final uri = Uri.parse('ws://localhost:8080/ws');
53+
final channel = WebSocketChannel.connect(uri);
54+
55+
// Listen to incoming messages from the server.
56+
channel.stream.listen(print);
57+
58+
// Send messages to the server.
59+
channel.sink.add('ping');
60+
}
61+
```
62+
63+
[ci_badge]: https://github.com/VeryGoodOpenSource/dart_frog/actions/workflows/dart_frog_web_socket.yaml/badge.svg
64+
[ci_link]: https://github.com/VeryGoodOpenSource/dart_frog/actions/workflows/dart_frog_web_socket.yaml
65+
[coverage_badge]: https://raw.githubusercontent.com/VeryGoodOpenSource/dart_frog/main/packages/dart_frog_web_socket/coverage_badge.svg
66+
[dart_frog_link]: https://github.com/verygoodopensource/dart_frog
67+
[dart_frog_link_dark]: https://github.com/verygoodopensource/dart_frog#gh-dark-mode-only
68+
[dart_frog_link_light]: https://github.com/verygoodopensource/dart_frog#gh-light-mode-only
69+
[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
70+
[license_link]: https://opensource.org/licenses/MIT
71+
[logo_black]: https://raw.githubusercontent.com/VeryGoodOpenSource/dart_frog/main/assets/dart_frog_logo_black.png#gh-light-mode-only
72+
[logo_white]: https://raw.githubusercontent.com/VeryGoodOpenSource/dart_frog/main/assets/dart_frog_logo_white.png#gh-dark-mode-only
73+
[pub_badge]: https://img.shields.io/pub/v/dart_frog_web_socket.svg
74+
[pub_link]: https://pub.dartlang.org/packages/dart_frog_web_socket
75+
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
76+
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
77+
[very_good_ventures_link]: https://verygood.ventures
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include: package:very_good_analysis/analysis_options.3.1.0.yaml
Lines changed: 20 additions & 0 deletions
Loading
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Example
2+
3+
Use `webSocketHandler` to manage `WebSocket` connections in a Dart Frog route handler.
4+
5+
```dart
6+
// routes/ws.dart
7+
import 'package:dart_frog/dart_frog.dart';
8+
import 'package:dart_frog_web_socket/dart_frog_web_socket.dart';
9+
10+
Handler get onRequest {
11+
return webSocketHandler(
12+
(channel, protocol) {
13+
// A new connection was established.
14+
print('connected');
15+
// Subscribe to the stream of messages from the client.
16+
channel.stream.listen(
17+
(message) {
18+
// Handle incoming messages.
19+
print('received: $message');
20+
// Send outgoing messages to the connected client.
21+
channel.sink.add('pong');
22+
},
23+
// The connection was terminated.
24+
onDone: () => print('disconnected'),
25+
);
26+
},
27+
);
28+
}
29+
```
30+
31+
Connect a client to the remote `WebSocket` endpoint.
32+
33+
```dart
34+
// main.dart
35+
import 'package:web_socket_channel/web_socket_channel.dart';
36+
37+
void main() {
38+
// Connect to the remote WebSocket endpoint.
39+
final uri = Uri.parse('ws://localhost:8080/ws');
40+
final channel = WebSocketChannel.connect(uri);
41+
42+
// Listen to incoming messages from the server.
43+
channel.stream.listen(print);
44+
45+
// Send messages to the server.
46+
channel.sink.add('ping');
47+
}
48+
```
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// WebSocket support for package:dart_frog
2+
library dart_frog_web_socket;
3+
4+
export 'package:web_socket_channel/web_socket_channel.dart';
5+
6+
export 'src/web_socket_handler.dart';
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import 'package:dart_frog/dart_frog.dart';
2+
import 'package:shelf_web_socket/shelf_web_socket.dart' as shelf_web_socket;
3+
import 'package:web_socket_channel/web_socket_channel.dart';
4+
5+
/// Creates a Dart Frog [Handler] that upgrades HTTP requests to WebSocket
6+
/// connections.
7+
///
8+
/// ```dart
9+
/// import 'package:dart_frog_web_socket/dart_frog_web_socket.dart';
10+
///
11+
/// final onRequest = webSocketHandler(
12+
/// (channel, protocol) {
13+
/// // A new connection was established.
14+
/// print('connected');
15+
/// // Subscribe to the stream of messages from the client.
16+
/// channel.stream.listen(
17+
/// (message) {
18+
/// // Handle incoming messages.
19+
/// print('received: $message');
20+
/// // Send outgoing messages to the connected client.
21+
/// channel.sink.add('pong');
22+
/// },
23+
/// // The connection was terminated.
24+
/// onDone: () => print('disconnected'),
25+
/// );
26+
/// },
27+
/// );
28+
/// ```
29+
///
30+
/// Only valid WebSocket upgrade requests are upgraded. If a request doesn't
31+
/// look like a WebSocket upgrade request, a 404 Not Found is returned; if a
32+
/// request looks like an upgrade request but is invalid, a 400 Bad Request is
33+
/// returned; and if a request is a valid upgrade request but has an origin that
34+
/// doesn't match [allowedOrigins] (see below), a 403 Forbidden is returned.
35+
///
36+
/// The [onConnection] must take a [WebSocketChannel] as the first argument
37+
/// and a string for the [WebSocket subprotocol][] as the second
38+
/// argument. The subprotocol is determined by looking at the client's
39+
/// `Sec-WebSocket-Protocol` header and selecting the first entry that also
40+
/// appears in [protocols]. If no subprotocols are shared between the client and
41+
/// the server, `null` will be passed instead and no subprotocol header will be
42+
/// sent to the client which may cause it to disconnect.
43+
///
44+
/// [WebSocket subprotocol]: https://tools.ietf.org/html/rfc6455#section-1.9
45+
///
46+
/// If [allowedOrigins] is passed, browser connections will only be accepted if
47+
/// they're made by a script from one of the given origins. This ensures that
48+
/// malicious scripts running in the browser are unable to fake a WebSocket
49+
/// handshake. Note that non-browser programs can still make connections freely.
50+
/// See also the WebSocket spec's discussion of [origin considerations][].
51+
///
52+
/// [origin considerations]: https://tools.ietf.org/html/rfc6455#section-10.2
53+
///
54+
/// If [pingInterval] is specified, it will get passed to the created
55+
/// channel instance, enabling round-trip disconnect detection.
56+
///
57+
/// This method uses [`package:shelf_web_socket`](https://pub.dev/packages/shelf_web_socket)
58+
/// internally.
59+
Handler webSocketHandler(
60+
void Function(WebSocketChannel channel, String? protocol) onConnection, {
61+
Iterable<String>? protocols,
62+
Iterable<String>? allowedOrigins,
63+
Duration? pingInterval,
64+
}) {
65+
return fromShelfHandler(
66+
shelf_web_socket.webSocketHandler(
67+
onConnection,
68+
protocols: protocols,
69+
allowedOrigins: allowedOrigins,
70+
pingInterval: pingInterval,
71+
),
72+
);
73+
}

0 commit comments

Comments
 (0)