├── 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 | ![Pub](https://img.shields.io/pub/v/action_cable) 2 | 3 | 4 | 5 | [![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#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 | 88 | 89 | 90 | 91 | 92 | 93 |

Clinton

💻 ⚠️

Hamad AlGhanim

💻

Fareesh Vijayarangam

💻
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 | --------------------------------------------------------------------------------