├── TODO
├── .gitignore
├── pubspec.yaml
├── CHANGELOG.md
├── test
└── channel_id_test.dart
├── lib
├── channel_id.dart
└── action_cable.dart
├── LICENSE
├── .all-contributorsrc
├── README.md
└── pubspec.lock
/TODO:
--------------------------------------------------------------------------------
1 | - websocket wrap for both IOWebsocket and HTMLWebSocket
2 | - testing
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # https://dart.dev/guides/libraries/private-files
2 | /.dart_tool/
3 | /.packages
4 | /build/
5 | /pubspec.lock # Except for application packages
6 |
7 | # IntelliJ
8 | *.iml
9 | *.ipr
10 | *.iws
11 | .idea/
12 |
13 | # Mac
14 | .DS_Store
15 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: action_cable
2 | version: 1.3.0
3 | description: >-
4 | ActionCable client port in dart, available in web, dartVM and flutter.
5 | homepage: https://github.com/namiwang/actioncable_dart
6 | documentation: https://github.com/namiwang/actioncable_dart
7 | environment:
8 | sdk: ">=3.2.0 <4.0.0"
9 | dependencies:
10 | web_socket_channel: ^3.0.1
11 | dev_dependencies:
12 | test:
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 2019-11-21 0.1.0
2 |
3 | First version
4 |
5 | # 2020-05-07 1.0.0
6 |
7 | Major update.
8 |
9 | - handles disconnecting
10 |
11 | # 2020-07-01 1.1.0
12 |
13 | - onConnectionLost and onCannotConnect
14 |
15 | # 2021-03-30 1.2.0
16 |
17 | - null safety
18 | - updated dependencies
19 |
20 | # 2024-10-25 1.3.0
21 |
22 | ### Breaking Changes
23 |
24 | - change connect function from `ActionCable.Connect` to `ActionCable.connect`
25 |
26 | ### Updates
27 |
28 | - support for dart 3
29 | - updated websocket dependency
30 | - improve readability of the core code by leveraging dart's null safety
31 |
--------------------------------------------------------------------------------
/test/channel_id_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:test/test.dart';
2 | import 'package:action_cable/channel_id.dart';
3 |
4 | void main() {
5 | group("ChannelId", () {
6 | test("it should correctly encode channel id", () {
7 | final channelId = encodeChannelId("Chat", {"id": 1}).toString();
8 | expect('{"channel":"ChatChannel","id":1}', channelId);
9 | });
10 |
11 | test("it should correctly encode channel id", () {
12 | final channelId = encodeChannelId("ChatChannel", {"id": 1}).toString();
13 | expect('{"channel":"ChatChannel","id":1}', channelId);
14 | });
15 |
16 | test("it should correctly parse channelId", () {
17 | final channelIdJSON = '{"id": 1, "channel": "NotificationChannel"}';
18 | final channelId = parseChannelId(channelIdJSON);
19 | expect('{"channel":"NotificationChannel","id":1}', channelId);
20 | });
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/lib/channel_id.dart:
--------------------------------------------------------------------------------
1 | //
2 | // ActionCable's channel identifier is a bit annoying: it's a string of encoded JSON.
3 | //
4 | // Thus, we have to make sure we always provide same encoded String (as key when storing callback functions) from different inputs
5 | // - "{\"channel\": \"ChatChannel\", id: \"1\"}"
6 | // - "{id: \"1\", \"channel\": \"ChatChannel\"}" // different order
7 | // - "{\"channel\": \"ChatChannel\", id: \"1\"}" // additional spaces
8 | //
9 | // Currently, we achieve that by encoding params Map into a SplayTreeMap before stringifying.
10 |
11 | import 'dart:collection';
12 | import 'dart:convert';
13 |
14 | String encodeChannelId(String channelName, Map? channelParams) {
15 | final fullChannelName =
16 | channelName.endsWith('Channel') ? channelName : "${channelName}Channel";
17 |
18 | Map channelId = channelParams == null ? {} : Map.from(channelParams);
19 | channelId['channel'] ??= fullChannelName;
20 |
21 | final orderedMap = SplayTreeMap.from(channelId);
22 | return jsonEncode(orderedMap);
23 | }
24 |
25 | String parseChannelId(String channelId) {
26 | return jsonEncode(SplayTreeMap.from(jsonDecode(channelId)));
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 https://github.com/namiwang/actioncable_dart
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.
22 |
--------------------------------------------------------------------------------
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "README.md"
4 | ],
5 | "imageSize": 100,
6 | "commit": false,
7 | "contributors": [
8 | {
9 | "login": "mclintprojects",
10 | "name": "Clinton",
11 | "avatar_url": "https://avatars1.githubusercontent.com/u/18232142?v=4",
12 | "profile": "https://clintonmbah.com",
13 | "contributions": [
14 | "code",
15 | "test"
16 | ]
17 | },
18 | {
19 | "login": "Dreamersoul",
20 | "name": "Hamad AlGhanim",
21 | "avatar_url": "https://avatars0.githubusercontent.com/u/6298868?v=4",
22 | "profile": "http://www.hamadalghanim.com",
23 | "contributions": [
24 | "code"
25 | ]
26 | },
27 | {
28 | "login": "fareesh",
29 | "name": "Fareesh Vijayarangam",
30 | "avatar_url": "https://avatars.githubusercontent.com/u/498161?v=4",
31 | "profile": "http://fareesh.itsalive.in",
32 | "contributions": [
33 | "code"
34 | ]
35 | }
36 | ],
37 | "contributorsPerLine": 7,
38 | "projectName": "actioncable_dart",
39 | "projectOwner": "namiwang",
40 | "repoType": "github",
41 | "repoHost": "https://github.com",
42 | "skipCi": true
43 | }
44 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 |
5 | [](#contributors-)
6 |
7 |
8 |
9 | # ActionCable in Dart
10 |
11 | ActionCable is the default realtime websocket framework and protocol in Rails.
12 |
13 | This is a dart port of the client and protocol implementation which is available in web, dartVM and flutter.
14 |
15 | ## Usage
16 |
17 | ### Connecting to a channel 🙌
18 |
19 | ```dart
20 | cable = ActionCable.connect(
21 | "ws://10.0.2.2:3000/cable",
22 | headers: {
23 | "Authorization": "Some Token",
24 | },
25 | onConnected: (){
26 | print("connected");
27 | },
28 | onConnectionLost: () {
29 | print("connection lost");
30 | },
31 | onCannotConnect: () {
32 | print("cannot connect");
33 | });
34 | ```
35 |
36 | ### Subscribing to channel 🎉
37 |
38 | ```dart
39 | cable.subscribe(
40 | "Chat", // either "Chat" and "ChatChannel" is fine
41 | channelParams: { "room": "private" },
42 | onSubscribed: (){}, // `confirm_subscription` received
43 | onDisconnected: (){}, // `disconnect` received
44 | onMessage: (Map message) {} // any other message received
45 | );
46 | ```
47 |
48 | ### Unsubscribing from a channel 🎃
49 |
50 | ```dart
51 | cable.unsubscribe(
52 | "Chat", // either "Chat" and "ChatChannel" is fine
53 | {"room": "private"}
54 | );
55 | ```
56 |
57 | ### Perform an action on your ActionCable server 🎇
58 |
59 | Requires that you have a method defined in your Rails Action Cable channel whose name matches the action property passed in.
60 |
61 | ```dart
62 | cable.performAction(
63 | "Chat",
64 | action: "send_message",
65 | channelParams: { "room": "private" },
66 | actionParams: { "message": "Hello private peeps! 😜" }
67 | );
68 | ```
69 |
70 | ### Disconnect from the ActionCable server
71 |
72 | ```dart
73 | cable.disconnect();
74 | ```
75 |
76 | ## ActionCable protocol
77 |
78 | Anycable has [a great doc](https://docs.anycable.io/#/misc/action_cable_protocol) on that topic.
79 |
80 | ## Contributors ✨
81 |
82 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
83 |
84 |
85 |
86 |
87 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
101 |
--------------------------------------------------------------------------------
/lib/action_cable.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'dart:convert';
3 | import 'package:web_socket_channel/io.dart';
4 | import 'channel_id.dart';
5 |
6 | typedef OnConnectedFunction = void Function();
7 | typedef OnConnectionLostFunction = void Function();
8 | typedef OnCannotConnectFunction = void Function();
9 | typedef OnChannelSubscribedFunction = void Function();
10 | typedef OnChannelDisconnectedFunction = void Function();
11 | typedef OnChannelMessageFunction = void Function(Map message);
12 |
13 | class ActionCable {
14 | DateTime? _lastPing;
15 | Timer? _timer;
16 | IOWebSocketChannel? _socketChannel;
17 | StreamSubscription? _listener;
18 |
19 | final OnConnectedFunction? onConnected;
20 | final OnConnectionLostFunction? onConnectionLost;
21 | final OnCannotConnectFunction? onCannotConnect;
22 |
23 | final Map
24 | _onChannelSubscribedCallbacks = {};
25 | final Map
26 | _onChannelDisconnectedCallbacks = {};
27 | final Map _onChannelMessageCallbacks = {};
28 |
29 | ActionCable.connect(
30 | String url, {
31 | Map headers = const {},
32 | this.onConnected,
33 | this.onConnectionLost,
34 | this.onCannotConnect,
35 | }) {
36 | _connect(url, headers);
37 | }
38 |
39 | void _connect(String url, Map headers) {
40 | _socketChannel = IOWebSocketChannel.connect(
41 | url,
42 | headers: headers,
43 | pingInterval: const Duration(seconds: 3),
44 | );
45 |
46 | _listener = _socketChannel?.stream.listen(
47 | _onData,
48 | onError: (error) {
49 | _handleError();
50 | },
51 | onDone: () {
52 | _handleDone();
53 | },
54 | );
55 |
56 | _timer = Timer.periodic(const Duration(seconds: 3), _healthCheck);
57 | }
58 |
59 | void disconnect() {
60 | _timer?.cancel();
61 | _listener?.cancel();
62 | _socketChannel?.sink.close();
63 | _socketChannel = null;
64 | _onChannelDisconnectedCallbacks.values
65 | .where((onDisconnected) => onDisconnected != null)
66 | .forEach((onDisconnected) {
67 | onDisconnected!();
68 | });
69 | }
70 |
71 | void _handleError() {
72 | disconnect();
73 | onCannotConnect?.call();
74 | }
75 |
76 | void _handleDone() {
77 | disconnect();
78 | onConnectionLost?.call();
79 | }
80 |
81 | void _healthCheck(Timer timer) {
82 | if (_lastPing == null) return;
83 | if (DateTime.now().difference(_lastPing!) > const Duration(seconds: 6)) {
84 | disconnect();
85 | onConnectionLost?.call();
86 | }
87 | }
88 |
89 | void subscribe(
90 | String channelName, {
91 | Map? channelParams,
92 | OnChannelSubscribedFunction? onSubscribed,
93 | OnChannelDisconnectedFunction? onDisconnected,
94 | OnChannelMessageFunction? onMessage,
95 | }) {
96 | final channelId = encodeChannelId(channelName, channelParams);
97 |
98 | _onChannelSubscribedCallbacks[channelId] = onSubscribed;
99 | _onChannelDisconnectedCallbacks[channelId] = onDisconnected;
100 | _onChannelMessageCallbacks[channelId] = onMessage;
101 |
102 | _send({
103 | 'identifier': channelId,
104 | 'command': 'subscribe',
105 | });
106 | }
107 |
108 | void unsubscribe(String channelName, {Map? channelParams}) {
109 | final channelId = encodeChannelId(channelName, channelParams);
110 |
111 | _onChannelSubscribedCallbacks.remove(channelId);
112 | _onChannelDisconnectedCallbacks.remove(channelId);
113 | _onChannelMessageCallbacks.remove(channelId);
114 |
115 | _send({
116 | 'identifier': channelId,
117 | 'command': 'unsubscribe',
118 | });
119 | }
120 |
121 | void performAction(
122 | String channelName, {
123 | String? action,
124 | Map? channelParams,
125 | Map? actionParams,
126 | }) {
127 | final channelId = encodeChannelId(channelName, channelParams);
128 |
129 | actionParams ??= {};
130 | actionParams['action'] = action;
131 |
132 | _send({
133 | 'identifier': channelId,
134 | 'command': 'message',
135 | 'data': jsonEncode(actionParams),
136 | });
137 | }
138 |
139 | void _onData(dynamic payload) {
140 | try {
141 | final data = jsonDecode(payload);
142 | if (data['type'] != null) {
143 | _handleProtocolMessage(data);
144 | } else {
145 | _handleDataMessage(data);
146 | }
147 | } catch (error) {
148 | throw 'InvalidPayload';
149 | }
150 | }
151 |
152 | void _handleProtocolMessage(Map payload) {
153 | switch (payload['type']) {
154 | case 'ping':
155 | _lastPing =
156 | DateTime.fromMillisecondsSinceEpoch(payload['message'] * 1000);
157 | break;
158 | case 'welcome':
159 | onConnected?.call();
160 | break;
161 | case 'disconnect':
162 | final identifier = payload['identifier'];
163 | if (identifier != null) {
164 | final channelId = parseChannelId(payload['identifier']);
165 | final onDisconnected = _onChannelDisconnectedCallbacks[channelId];
166 | onDisconnected?.call();
167 | } else {
168 | final reason = payload['reason'];
169 | if (reason != null && reason == 'unauthorized') {
170 | this.onCannotConnect?.call();
171 | }
172 | }
173 | break;
174 | case 'confirm_subscription':
175 | final channelId = parseChannelId(payload['identifier']);
176 | _onChannelSubscribedCallbacks[channelId]?.call();
177 | break;
178 | case 'reject_subscription':
179 | // throw 'Unimplemented';
180 | break;
181 | default:
182 | throw 'InvalidMessage';
183 | }
184 | }
185 |
186 | void _handleDataMessage(Map payload) {
187 | final channelId = parseChannelId(payload['identifier']);
188 | _onChannelMessageCallbacks[channelId]?.call(payload['message']);
189 | }
190 |
191 | void _send(Map payload) {
192 | if (_socketChannel != null) {
193 | _socketChannel!.sink.add(jsonEncode(payload));
194 | }
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | _fe_analyzer_shared:
5 | dependency: transitive
6 | description:
7 | name: _fe_analyzer_shared
8 | sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
9 | url: "https://pub.dev"
10 | source: hosted
11 | version: "67.0.0"
12 | analyzer:
13 | dependency: transitive
14 | description:
15 | name: analyzer
16 | sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
17 | url: "https://pub.dev"
18 | source: hosted
19 | version: "6.4.1"
20 | args:
21 | dependency: transitive
22 | description:
23 | name: args
24 | sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
25 | url: "https://pub.dev"
26 | source: hosted
27 | version: "2.6.0"
28 | async:
29 | dependency: transitive
30 | description:
31 | name: async
32 | sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
33 | url: "https://pub.dev"
34 | source: hosted
35 | version: "2.12.0"
36 | boolean_selector:
37 | dependency: transitive
38 | description:
39 | name: boolean_selector
40 | sha256: "5bbf32bc9e518d41ec49718e2931cd4527292c9b0c6d2dffcf7fe6b9a8a8cf72"
41 | url: "https://pub.dev"
42 | source: hosted
43 | version: "2.1.0"
44 | charcode:
45 | dependency: transitive
46 | description:
47 | name: charcode
48 | sha256: "8e36feea6de5ea69f2199f29cf42a450a855738c498b57c0b980e2d3cca9c362"
49 | url: "https://pub.dev"
50 | source: hosted
51 | version: "1.2.0"
52 | collection:
53 | dependency: transitive
54 | description:
55 | name: collection
56 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
57 | url: "https://pub.dev"
58 | source: hosted
59 | version: "1.19.1"
60 | convert:
61 | dependency: transitive
62 | description:
63 | name: convert
64 | sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
65 | url: "https://pub.dev"
66 | source: hosted
67 | version: "3.1.2"
68 | coverage:
69 | dependency: transitive
70 | description:
71 | name: coverage
72 | sha256: "88b0fddbe4c92910fefc09cc0248f5e7f0cd23e450ded4c28f16ab8ee8f83268"
73 | url: "https://pub.dev"
74 | source: hosted
75 | version: "1.10.0"
76 | crypto:
77 | dependency: transitive
78 | description:
79 | name: crypto
80 | sha256: "8be10341257b613566fdc9fd073c46f7c032ed329b1c732bda17aca29f2366c8"
81 | url: "https://pub.dev"
82 | source: hosted
83 | version: "3.0.0"
84 | file:
85 | dependency: transitive
86 | description:
87 | name: file
88 | sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
89 | url: "https://pub.dev"
90 | source: hosted
91 | version: "6.1.4"
92 | frontend_server_client:
93 | dependency: transitive
94 | description:
95 | name: frontend_server_client
96 | sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
97 | url: "https://pub.dev"
98 | source: hosted
99 | version: "4.0.0"
100 | glob:
101 | dependency: transitive
102 | description:
103 | name: glob
104 | sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
105 | url: "https://pub.dev"
106 | source: hosted
107 | version: "2.1.2"
108 | http_multi_server:
109 | dependency: transitive
110 | description:
111 | name: http_multi_server
112 | sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
113 | url: "https://pub.dev"
114 | source: hosted
115 | version: "3.2.1"
116 | http_parser:
117 | dependency: transitive
118 | description:
119 | name: http_parser
120 | sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360"
121 | url: "https://pub.dev"
122 | source: hosted
123 | version: "4.1.1"
124 | io:
125 | dependency: transitive
126 | description:
127 | name: io
128 | sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
129 | url: "https://pub.dev"
130 | source: hosted
131 | version: "1.0.4"
132 | js:
133 | dependency: transitive
134 | description:
135 | name: js
136 | sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
137 | url: "https://pub.dev"
138 | source: hosted
139 | version: "0.7.1"
140 | logging:
141 | dependency: transitive
142 | description:
143 | name: logging
144 | sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
145 | url: "https://pub.dev"
146 | source: hosted
147 | version: "1.3.0"
148 | matcher:
149 | dependency: transitive
150 | description:
151 | name: matcher
152 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
153 | url: "https://pub.dev"
154 | source: hosted
155 | version: "0.12.16+1"
156 | meta:
157 | dependency: transitive
158 | description:
159 | name: meta
160 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
161 | url: "https://pub.dev"
162 | source: hosted
163 | version: "1.16.0"
164 | mime:
165 | dependency: transitive
166 | description:
167 | name: mime
168 | sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
169 | url: "https://pub.dev"
170 | source: hosted
171 | version: "2.0.0"
172 | node_preamble:
173 | dependency: transitive
174 | description:
175 | name: node_preamble
176 | sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
177 | url: "https://pub.dev"
178 | source: hosted
179 | version: "2.0.2"
180 | package_config:
181 | dependency: transitive
182 | description:
183 | name: package_config
184 | sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
185 | url: "https://pub.dev"
186 | source: hosted
187 | version: "2.1.0"
188 | path:
189 | dependency: transitive
190 | description:
191 | name: path
192 | sha256: "2ad4cddff7f5cc0e2d13069f2a3f7a73ca18f66abd6f5ecf215219cdb3638edb"
193 | url: "https://pub.dev"
194 | source: hosted
195 | version: "1.8.0"
196 | pool:
197 | dependency: transitive
198 | description:
199 | name: pool
200 | sha256: "05955e3de2683e1746222efd14b775df7131139e07695dc8e24650f6b4204504"
201 | url: "https://pub.dev"
202 | source: hosted
203 | version: "1.5.0"
204 | pub_semver:
205 | dependency: transitive
206 | description:
207 | name: pub_semver
208 | sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
209 | url: "https://pub.dev"
210 | source: hosted
211 | version: "2.1.4"
212 | shelf:
213 | dependency: transitive
214 | description:
215 | name: shelf
216 | sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
217 | url: "https://pub.dev"
218 | source: hosted
219 | version: "1.4.2"
220 | shelf_packages_handler:
221 | dependency: transitive
222 | description:
223 | name: shelf_packages_handler
224 | sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
225 | url: "https://pub.dev"
226 | source: hosted
227 | version: "3.0.2"
228 | shelf_static:
229 | dependency: transitive
230 | description:
231 | name: shelf_static
232 | sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3
233 | url: "https://pub.dev"
234 | source: hosted
235 | version: "1.1.3"
236 | shelf_web_socket:
237 | dependency: transitive
238 | description:
239 | name: shelf_web_socket
240 | sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611"
241 | url: "https://pub.dev"
242 | source: hosted
243 | version: "2.0.0"
244 | source_map_stack_trace:
245 | dependency: transitive
246 | description:
247 | name: source_map_stack_trace
248 | sha256: "8c463326277f68a628abab20580047b419c2ff66756fd0affd451f73f9508c11"
249 | url: "https://pub.dev"
250 | source: hosted
251 | version: "2.1.0"
252 | source_maps:
253 | dependency: transitive
254 | description:
255 | name: source_maps
256 | sha256: "52de2200bb098de739794c82d09c41ac27b2e42fd7e23cce7b9c74bf653c7296"
257 | url: "https://pub.dev"
258 | source: hosted
259 | version: "0.10.10"
260 | source_span:
261 | dependency: transitive
262 | description:
263 | name: source_span
264 | sha256: d5f89a9e52b36240a80282b3dc0667dd36e53459717bb17b8fb102d30496606a
265 | url: "https://pub.dev"
266 | source: hosted
267 | version: "1.8.1"
268 | stack_trace:
269 | dependency: transitive
270 | description:
271 | name: stack_trace
272 | sha256: f8d9f247e2f9f90e32d1495ff32dac7e4ae34ffa7194c5ff8fcc0fd0e52df774
273 | url: "https://pub.dev"
274 | source: hosted
275 | version: "1.10.0"
276 | stream_channel:
277 | dependency: transitive
278 | description:
279 | name: stream_channel
280 | sha256: db47e4797198ee601990820437179bb90219f918962318d494ada2b4b11e6f6d
281 | url: "https://pub.dev"
282 | source: hosted
283 | version: "2.1.0"
284 | string_scanner:
285 | dependency: transitive
286 | description:
287 | name: string_scanner
288 | sha256: dd11571b8a03f7cadcf91ec26a77e02bfbd6bbba2a512924d3116646b4198fc4
289 | url: "https://pub.dev"
290 | source: hosted
291 | version: "1.1.0"
292 | term_glyph:
293 | dependency: transitive
294 | description:
295 | name: term_glyph
296 | sha256: a88162591b02c1f3a3db3af8ce1ea2b374bd75a7bb8d5e353bcfbdc79d719830
297 | url: "https://pub.dev"
298 | source: hosted
299 | version: "1.2.0"
300 | test:
301 | dependency: "direct dev"
302 | description:
303 | name: test
304 | sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f"
305 | url: "https://pub.dev"
306 | source: hosted
307 | version: "1.25.8"
308 | test_api:
309 | dependency: transitive
310 | description:
311 | name: test_api
312 | sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
313 | url: "https://pub.dev"
314 | source: hosted
315 | version: "0.7.3"
316 | test_core:
317 | dependency: transitive
318 | description:
319 | name: test_core
320 | sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d"
321 | url: "https://pub.dev"
322 | source: hosted
323 | version: "0.6.5"
324 | typed_data:
325 | dependency: transitive
326 | description:
327 | name: typed_data
328 | sha256: "53bdf7e979cfbf3e28987552fd72f637e63f3c8724c9e56d9246942dc2fa36ee"
329 | url: "https://pub.dev"
330 | source: hosted
331 | version: "1.3.0"
332 | vm_service:
333 | dependency: transitive
334 | description:
335 | name: vm_service
336 | sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
337 | url: "https://pub.dev"
338 | source: hosted
339 | version: "14.3.0"
340 | watcher:
341 | dependency: transitive
342 | description:
343 | name: watcher
344 | sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
345 | url: "https://pub.dev"
346 | source: hosted
347 | version: "1.1.0"
348 | web:
349 | dependency: transitive
350 | description:
351 | name: web
352 | sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
353 | url: "https://pub.dev"
354 | source: hosted
355 | version: "1.1.0"
356 | web_socket:
357 | dependency: transitive
358 | description:
359 | name: web_socket
360 | sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
361 | url: "https://pub.dev"
362 | source: hosted
363 | version: "0.1.6"
364 | web_socket_channel:
365 | dependency: "direct main"
366 | description:
367 | name: web_socket_channel
368 | sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f"
369 | url: "https://pub.dev"
370 | source: hosted
371 | version: "3.0.1"
372 | webkit_inspection_protocol:
373 | dependency: transitive
374 | description:
375 | name: webkit_inspection_protocol
376 | sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
377 | url: "https://pub.dev"
378 | source: hosted
379 | version: "1.2.1"
380 | yaml:
381 | dependency: transitive
382 | description:
383 | name: yaml
384 | sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
385 | url: "https://pub.dev"
386 | source: hosted
387 | version: "3.1.2"
388 | sdks:
389 | dart: ">=3.4.0 <4.0.0"
390 |
--------------------------------------------------------------------------------