diff --git a/CHANGELOG.md b/CHANGELOG.md index ef03978..cd91ae2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +## 2.0.0+1 +* fixed on connect + +## 2.0.0 + +* support dart 3.0.0 + +**Breaking Changes — Dart 3 upgrade:** + +* Raised SDK constraint to `>=3.0.0 <4.0.0` +* Upgraded `stream` to `^4.0.0` (Dart 3 compatible) +* Upgraded `socket_io_common` to `^3.0.0` (Socket.IO v4.7+ protocol, Dart 3) + * All internal `src/` imports replaced with `package:socket_io_common/socket_io_common.dart` + * `ERROR` packet-type constant renamed to `CONNECT_ERROR` (protocol v5 alignment) + * `PacketParser.decodePayload` is now synchronous (returns `List`); callback pattern removed + * `PacketParser.encodePayload` no longer accepts `supportsBinary` parameter + * `PacketParser.decodePacket` second parameter is now positional `binaryType` (not `utf8decode`) +* Upgraded `uuid` to `^4.0.0` +* Replaced retired `pedantic` dev-dependency with `lints: ^3.0.0` +* Updated `analysis_options.yaml` to include `package:lints/recommended.yaml` + +**Bug Fixes (Dart 3 soundness):** + +* `namespace.dart`: Fixed JS-style boolean truthiness check `if (err)` → `if (err != null)` +* `namespace.dart`: Fixed JS-style `||` null-coalesce `err.data || err.message` → `err.data ?? err.message` +* `server.dart`: Fixed JS-style `if (err)` → `if (err != null)` in `set()` authorization callback +* `server.dart`: Fixed JS-style `&&` truthiness `oldSettings[key] && engine![...]` → null checks +* `engine/server.dart`: Fixed null-safety in `abortConnection` — `ServerErrorMessages[code]` is `String?` +* `polling_transport.dart`: Added null guard for `maxHttpBufferSize` comparison + ## 1.0.1 **Bug Fix:** diff --git a/README.md b/README.md index b578007..0313c38 100644 --- a/README.md +++ b/README.md @@ -1,96 +1,190 @@ -# socket.io-dart +# socket_io_plus -Port of awesome JavaScript Node.js library - [Socket.io v2.0.1](https://github.com/socketio/socket.io) - in Dart +A Dart 3 compatible port of the [Socket.IO](https://socket.io) server library. +Real-time, bidirectional, event-based communication — fully null-safe and ready for modern Dart. -## Usage +[![pub package](https://img.shields.io/pub/v/socket_io_plus.svg)](https://pub.dev/packages/socket_io_plus) +[![Dart SDK](https://img.shields.io/badge/Dart-%3E%3D3.0.0-blue)](https://dart.dev) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) + +--- + +## Features + +- Full Dart 3 null-safety support +- WebSocket and XHR/JSONP polling transports +- Namespace (multiplexing) support +- Room-based broadcasting +- In-memory socket adapter +- Compatible with the official [socket_io_client](https://pub.dev/packages/socket_io_client) and JS Socket.IO clients + +--- + +## Installation + +Add to your `pubspec.yaml`: + +```yaml +dependencies: + socket_io_plus: ^2.0.0 +``` + +Then run: + +```sh +dart pub get +``` + +--- + +## Quick Start + +### Server (Dart) ```dart -import 'package:socket_io/socket_io.dart'; +import 'package:socket_io_plus/socket_io.dart'; + +void main() async { + final io = Server(); + + // Default namespace + io.on('connection', (Socket client) { + print('Client connected: ${client.id}'); -main() { - var io = new Server(); - var nsp = io.of('/some'); - nsp.on('connection', (client) { - print('connection /some'); - client.on('msg', (data) { - print('data from /some => $data'); - client.emit('fromServer', "ok 2"); - }); + client.on('msg', (data) { + print('Message from client: $data'); + client.emit('fromServer', 'ok'); }); - io.on('connection', (client) { - print('connection default namespace'); - client.on('msg', (data) { - print('data from default => $data'); - client.emit('fromServer', "ok"); - }); - }); - io.listen(3000); + + client.on('disconnect', (_) { + print('Client disconnected: ${client.id}'); + }); + }); + + // Custom namespace + final nsp = io.of('/chat'); + nsp.on('connection', (Socket client) { + print('Client connected to /chat'); + + client.on('message', (data) { + // Broadcast to everyone in the namespace + nsp.emit('broadcastMessage', data); + }); + }); + + await io.listen(3000); + print('Server running on port 3000'); } ``` +### Client — JavaScript + ```js -// JS client -var socket = io('http://localhost:3000'); -socket.on('connect', function(){console.log('connect')}); -socket.on('event', function(data){console.log(data)}); -socket.on('disconnect', function(){console.log('disconnect')}); -socket.on('fromServer', function(e){console.log(e)}); +const socket = io('http://localhost:3000'); + +socket.on('connect', () => console.log('connected')); +socket.on('fromServer', (data) => console.log('from server:', data)); +socket.on('disconnect', () => console.log('disconnected')); + +socket.emit('msg', 'hello'); ``` +### Client — Dart + ```dart -// Dart client import 'package:socket_io_client/socket_io_client.dart' as IO; -IO.Socket socket = IO.io('http://localhost:3000'); -socket.on('connect', (_) { - print('connect'); - socket.emit('msg', 'test'); +void main() { + final socket = IO.io('http://localhost:3000'); + + socket.on('connect', (_) { + print('connected'); + socket.emit('msg', 'hello from Dart'); + }); + + socket.on('fromServer', (data) => print('from server: $data')); + socket.on('disconnect', (_) => print('disconnected')); +} +``` + +--- + +## Rooms + +Sockets can join and leave named rooms. Broadcast to all members of a room: + +```dart +io.on('connection', (Socket client) { + client.join('room1'); + + client.on('msg', (data) { + // Emit to all sockets in 'room1' + io.to('room1').emit('roomMessage', data); + }); +}); +``` + +--- + +## Namespaces + +Create separate communication channels that share the same underlying connection: + +```dart +final admin = io.of('/admin'); +admin.on('connection', (Socket client) { + client.emit('welcome', 'You are in the admin namespace'); }); -socket.on('event', (data) => print(data)); -socket.on('disconnect', (_) => print('disconnect')); -socket.on('fromServer', (_) => print(_)); ``` -## Multiplexing support +--- + +## Transports + +Powered by Engine.IO, two transports are supported: -Same as Socket.IO, this project allows you to create several Namespaces, which will act as separate communication channels but will share the same underlying connection. +| Transport | Description | +|-------------|------------------------------------------------| +| `polling` | XHR / JSONP long-polling (HTTP fallback) | +| `websocket` | Full-duplex WebSocket connection | -## Room support +Clients start with polling and upgrade to WebSocket automatically when available. -Within each Namespace, you can define arbitrary channels, called Rooms, that sockets can join and leave. You can then broadcast to any given room, reaching every socket that has joined it. +--- -## Transports support - Refers to [engine.io](https://github.com/socketio/engine.io) +## Migration from `socket_io` -- `polling`: XHR / JSONP polling transport. -- `websocket`: WebSocket transport. +This package is a Dart 3 compatible fork of [`socket_io`](https://pub.dev/packages/socket_io). To migrate: + +1. Replace `socket_io` with `socket_io_plus` in `pubspec.yaml` +2. Update all imports: + +```dart +// Before +import 'package:socket_io/socket_io.dart'; + +// After +import 'package:socket_io_plus/socket_io.dart'; +``` -## Adapters support +No other code changes are required. -* Default socket.io in-memory adapter class. Refers to [socket.io-adapter](https://github.com/socketio/socket.io-adapter) +--- -## Notes to Contributors +## Dart Client Package -### Fork socket.io-dart +Use [`socket_io_client`](https://pub.dev/packages/socket_io_client) to connect a Dart client to this server. -If you'd like to contribute back to the core, you can [fork this repository](https://help.github.com/articles/fork-a-repo) and send us a pull request, when it is ready. +--- -If you are new to Git or GitHub, please read [this guide](https://help.github.com/) first. +## Contributing -## Who Uses +Contributions are welcome! Please open an issue or submit a pull request. -* [Quire](https://quire.io) - a simple, collaborative, multi-level task management tool. -* [KEIKAI](https://keikai.io/) - a web spreadsheet for Big Data. +If you are new to Git or GitHub, read [this guide](https://help.github.com/) first. -## Socket.io Dart Client +--- -* [socket.io-client-dart](https://github.com/rikulo/socket.io-client-dart) +## License -## Contributors -* Thanks [@felangel](https://github.com/felangel) for https://github.com/rikulo/socket.io-dart/issues/7 -* Thanks [@ThinkDigitalSoftware](https://github.com/ThinkDigitalSoftware) for https://github.com/rikulo/socket.io-dart/pull/15 -* Thanks [@guilhermecaldas](https://github.com/guilhermecaldas) for https://github.com/rikulo/socket.io-dart/pull/16 -* Thanks [@jodinathan](https://github.com/jodinathan) for https://github.com/rikulo/socket.io-dart/pull/17 -* Thanks [@jodinathan](https://github.com/jodinathan) for https://github.com/rikulo/socket.io-dart/pull/18 -* Thanks [@nicobritos](https://github.com/nicobritos) for https://github.com/rikulo/socket.io-dart/pull/46 -* Thanks [@nicobritos](https://github.com/nicobritos) for https://github.com/rikulo/socket.io-dart/pull/47 \ No newline at end of file +This project is licensed under the [MIT License](LICENSE). \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index 80bb834..91bd51c 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,7 +2,7 @@ # # The commented part below is just for inspiration. Read the guide here: # https://www.dartlang.org/guides/language/analysis-options -include: package:pedantic/analysis_options.yaml +include: package:lints/recommended.yaml analyzer: # exclude: # - path/to/excluded/files/** diff --git a/example/README.md b/example/README.md index 756fa64..0f793ee 100644 --- a/example/README.md +++ b/example/README.md @@ -5,7 +5,7 @@ Port of awesome JavaScript Node.js library - [Socket.io v2.0.1](https://github.c ## Usage ```dart -import 'package:socket_io/socket_io.dart'; +import 'package:socket_io_plus/socket_io.dart'; main() { var io = new Server(); diff --git a/lib/socket_io.dart b/lib/socket_io.dart index d8148d5..37fe978 100644 --- a/lib/socket_io.dart +++ b/lib/socket_io.dart @@ -7,5 +7,5 @@ export 'src/engine/transport/jsonp_transport.dart' show JSONPTransport; export 'src/engine/transport/polling_transport.dart' show PollingTransport; export 'src/engine/transport/websocket_transport.dart' show WebSocketTransport; -export 'package:socket_io_common/src/engine/parser/parser.dart' +export 'package:socket_io_common/socket_io_common.dart' show PacketParser; diff --git a/lib/src/adapter/adapter.dart b/lib/src/adapter/adapter.dart index 4d4a08f..453b8d4 100644 --- a/lib/src/adapter/adapter.dart +++ b/lib/src/adapter/adapter.dart @@ -9,9 +9,9 @@ /// /// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'dart:async'; -import 'package:socket_io/src/namespace.dart'; -import 'package:socket_io_common/src/parser/parser.dart'; -import 'package:socket_io/src/util/event_emitter.dart'; +import 'package:socket_io_plus/src/namespace.dart'; +import 'package:socket_io_common/socket_io_common.dart'; +import 'package:socket_io_plus/src/util/event_emitter.dart'; abstract class Adapter { Map nsps = {}; @@ -128,8 +128,8 @@ class _MemoryStoreAdapter extends EventEmitter implements Adapter { var socket; packet['nsp'] = nsp.name; - encoder.encode(packet, (encodedPackets) { - if (rooms.isNotEmpty) { + var encodedPackets = encoder.encode(packet); + if (rooms.isNotEmpty) { for (var i = 0; i < rooms.length; i++) { var room = this.rooms[rooms[i]]; if (room == null) continue; @@ -146,17 +146,14 @@ class _MemoryStoreAdapter extends EventEmitter implements Adapter { } } } else { - for (var id in sids.keys) { - if (except.contains(id)) continue; - socket = nsp.connected[id]; - if (socket != null) socket.packet(encodedPackets, packetOpts); - } + for (var id in sids.keys) { + if (except.contains(id)) continue; + socket = nsp.connected[id]; + if (socket != null) socket.packet(encodedPackets, packetOpts); } - }); + } } - /// Gets a list of clients by sid. - /// /// @param {Array} explicit set of rooms to check. /// @param {Function} callback /// @api public diff --git a/lib/src/client.dart b/lib/src/client.dart index 745ebd5..eb35a59 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -10,9 +10,9 @@ /// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'package:logging/logging.dart'; -import 'package:socket_io/src/engine/socket.dart'; -import 'package:socket_io_common/src/parser/parser.dart'; -import 'package:socket_io/src/server.dart'; +import 'package:socket_io_plus/src/engine/socket.dart'; +import 'package:socket_io_common/socket_io_common.dart'; +import 'package:socket_io_plus/src/server.dart'; class Client { Server server; @@ -55,7 +55,7 @@ class Client { _logger.fine('connecting to namespace $name'); if (!server.nsps.containsKey(name)) { packet({ - 'type': ERROR, + 'type': CONNECT_ERROR, 'nsp': name, 'data': 'Invalid namespace' }); @@ -140,10 +140,7 @@ class Client { _logger.fine('writing packet $packet'); if (opts['preEncoded'] != true) { // not broadcasting, need to encode - encoder.encode(packet, (encodedPackets) { - // encode, then write results to engine - writeToEngine(encodedPackets); - }); + writeToEngine(encoder.encode(packet)); } else { // a broadcast pre-encodes a packet writeToEngine(packet); diff --git a/lib/src/engine/engine.dart b/lib/src/engine/engine.dart index a2672d7..d4ee045 100644 --- a/lib/src/engine/engine.dart +++ b/lib/src/engine/engine.dart @@ -8,8 +8,8 @@ /// 16/02/2017, Created by jumperchen /// /// Copyright (C) 2017 Potix Corporation. All Rights Reserved. -import 'package:socket_io/src/engine/server.dart'; -import 'package:socket_io/src/util/event_emitter.dart'; +import 'package:socket_io_plus/src/engine/server.dart'; +import 'package:socket_io_plus/src/util/event_emitter.dart'; class Engine extends EventEmitter { static Engine attach(server, [Map? options]) { diff --git a/lib/src/engine/server.dart b/lib/src/engine/server.dart index 4c552bb..019dab7 100644 --- a/lib/src/engine/server.dart +++ b/lib/src/engine/server.dart @@ -11,10 +11,10 @@ import 'dart:convert'; import 'dart:io' hide Socket; import 'package:logging/logging.dart'; -import 'package:socket_io/src/engine/connect.dart'; -import 'package:socket_io/src/engine/engine.dart'; -import 'package:socket_io/src/engine/socket.dart'; -import 'package:socket_io/src/engine/transport/transports.dart'; +import 'package:socket_io_plus/src/engine/connect.dart'; +import 'package:socket_io_plus/src/engine/engine.dart'; +import 'package:socket_io_plus/src/engine/socket.dart'; +import 'package:socket_io_plus/src/engine/transport/transports.dart'; import 'package:stream/stream.dart'; import 'package:uuid/uuid.dart'; @@ -428,9 +428,9 @@ class Server extends Engine { static void abortConnection(SocketConnect connect, code) { var socket = connect.websocket; if (socket?.readyState == HttpStatus.ok) { - var message = ServerErrorMessages.containsKey(code) - ? ServerErrorMessages[code] - : code; + final String message = ServerErrorMessages.containsKey(code) + ? (ServerErrorMessages[code] ?? '') + : '$code'; var length = utf8.encode(message).length; socket!.add('HTTP/1.1 400 Bad Request\r\n' 'Connection: close\r\n' diff --git a/lib/src/engine/socket.dart b/lib/src/engine/socket.dart index 34e56ad..6ec046f 100644 --- a/lib/src/engine/socket.dart +++ b/lib/src/engine/socket.dart @@ -12,10 +12,10 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:logging/logging.dart'; -import 'package:socket_io/src/engine/connect.dart'; -import 'package:socket_io/src/engine/server.dart'; -import 'package:socket_io/src/engine/transport/transports.dart'; -import 'package:socket_io/src/util/event_emitter.dart'; +import 'package:socket_io_plus/src/engine/connect.dart'; +import 'package:socket_io_plus/src/engine/server.dart'; +import 'package:socket_io_plus/src/engine/transport/transports.dart'; +import 'package:socket_io_plus/src/util/event_emitter.dart'; /// Client class (abstract). /// @@ -314,10 +314,8 @@ class Socket extends EventEmitter { var onDrain = (_) { if (sentCallbackFn.isNotEmpty) { var seqFn = sentCallbackFn[0]; - if (seqFn is Function) { - _logger.fine('executing send callback'); - seqFn(transport); - } + _logger.fine('executing send callback'); + seqFn(transport); /// else if (Array.isArray(seqFn)) { /// _logger.fine('executing batch send callback'); diff --git a/lib/src/engine/transport/jsonp_transport.dart b/lib/src/engine/transport/jsonp_transport.dart index 86df83e..e1e396b 100644 --- a/lib/src/engine/transport/jsonp_transport.dart +++ b/lib/src/engine/transport/jsonp_transport.dart @@ -9,8 +9,8 @@ /// /// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'dart:convert'; -import 'package:socket_io/src/engine/connect.dart'; -import 'package:socket_io/src/engine/transport/polling_transport.dart'; +import 'package:socket_io_plus/src/engine/connect.dart'; +import 'package:socket_io_plus/src/engine/transport/polling_transport.dart'; class JSONPTransport extends PollingTransport { late String head; diff --git a/lib/src/engine/transport/polling_transport.dart b/lib/src/engine/transport/polling_transport.dart index 397aad8..57c255e 100644 --- a/lib/src/engine/transport/polling_transport.dart +++ b/lib/src/engine/transport/polling_transport.dart @@ -12,9 +12,9 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:logging/logging.dart'; -import 'package:socket_io/src/engine/connect.dart'; -import 'package:socket_io_common/src/engine/parser/parser.dart'; -import 'package:socket_io/src/engine/transport/transports.dart'; +import 'package:socket_io_plus/src/engine/connect.dart'; +import 'package:socket_io_common/socket_io_common.dart'; +import 'package:socket_io_plus/src/engine/transport/transports.dart'; class PollingTransport extends Transport { @override @@ -136,7 +136,8 @@ class PollingTransport extends Transport { contentLength = chunks.length; } - if (contentLength > self.maxHttpBufferSize) { + if (self.maxHttpBufferSize != null && + contentLength > self.maxHttpBufferSize!) { chunks = ''; connect.close(); } @@ -180,18 +181,15 @@ class PollingTransport extends Transport { messageHandler!.handle(this, data); } else { var self = this; - var callback = (packet, [foo, bar]) { + var packets = PacketParser.decodePayload(data, null); + for (var packet in (packets is List ? packets : [packets])) { if ('close' == packet['type']) { _logger.fine('got xhr close packet'); self.onClose(); - return false; + break; } - - self.onPacket(packet); - return true; - }; - - PacketParser.decodePayload(data, callback: callback); + self.onPacket(packet as Map); + } } } @@ -225,7 +223,7 @@ class PollingTransport extends Transport { } var self = this; - PacketParser.encodePayload(packets, supportsBinary: supportsBinary == true, + PacketParser.encodePayload(packets, callback: (data) { var compress = packets.any((packet) { var opt = packet['options']; diff --git a/lib/src/engine/transport/transports.dart b/lib/src/engine/transport/transports.dart index 910e8d3..d25f520 100644 --- a/lib/src/engine/transport/transports.dart +++ b/lib/src/engine/transport/transports.dart @@ -9,12 +9,12 @@ /// /// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'package:logging/logging.dart'; -import 'package:socket_io/src/engine/connect.dart'; -import 'package:socket_io_common/src/engine/parser/parser.dart'; -import 'package:socket_io/src/engine/transport/jsonp_transport.dart'; -import 'package:socket_io/src/engine/transport/websocket_transport.dart'; -import 'package:socket_io/src/engine/transport/xhr_transport.dart'; -import 'package:socket_io/src/util/event_emitter.dart'; +import 'package:socket_io_plus/src/engine/connect.dart'; +import 'package:socket_io_common/socket_io_common.dart'; +import 'package:socket_io_plus/src/engine/transport/jsonp_transport.dart'; +import 'package:socket_io_plus/src/engine/transport/websocket_transport.dart'; +import 'package:socket_io_plus/src/engine/transport/xhr_transport.dart'; +import 'package:socket_io_plus/src/util/event_emitter.dart'; class Transports { static List upgradesTo(String from) { @@ -95,7 +95,7 @@ abstract class Transport extends EventEmitter { if (messageHandler != null) { messageHandler!.handle(this, data); } else { - onPacket(PacketParser.decodePacket(data, utf8decode: true)); + onPacket(PacketParser.decodePacket(data, null) as Map); } } diff --git a/lib/src/engine/transport/websocket_transport.dart b/lib/src/engine/transport/websocket_transport.dart index 4a23115..7865513 100644 --- a/lib/src/engine/transport/websocket_transport.dart +++ b/lib/src/engine/transport/websocket_transport.dart @@ -11,8 +11,8 @@ import 'dart:async'; /// /// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'package:logging/logging.dart'; -import 'package:socket_io_common/src/engine/parser/parser.dart'; -import 'package:socket_io/src/engine/transport/transports.dart'; +import 'package:socket_io_common/socket_io_common.dart'; +import 'package:socket_io_plus/src/engine/transport/transports.dart'; class WebSocketTransport extends Transport { static final Logger _logger = @@ -60,7 +60,8 @@ class WebSocketTransport extends Transport { for (var i = 0; i < packets.length; i++) { var packet = packets[i]; PacketParser.encodePacket(packet, - supportsBinary: supportsBinary, callback: (_) => send(_, packet)); + supportsBinary: supportsBinary ?? false, + callback: (_) => send(_, packet)); } } diff --git a/lib/src/engine/transport/xhr_transport.dart b/lib/src/engine/transport/xhr_transport.dart index 33dfeba..91ac7ea 100644 --- a/lib/src/engine/transport/xhr_transport.dart +++ b/lib/src/engine/transport/xhr_transport.dart @@ -8,8 +8,8 @@ /// 22/02/2017, Created by jumperchen /// /// Copyright (C) 2017 Potix Corporation. All Rights Reserved. -import 'package:socket_io/src/engine/connect.dart'; -import 'package:socket_io/src/engine/transport/polling_transport.dart'; +import 'package:socket_io_plus/src/engine/connect.dart'; +import 'package:socket_io_plus/src/engine/transport/polling_transport.dart'; class XHRTransport extends PollingTransport { XHRTransport(SocketConnect connect) : super(connect); diff --git a/lib/src/namespace.dart b/lib/src/namespace.dart index 192325a..7e93c7c 100644 --- a/lib/src/namespace.dart +++ b/lib/src/namespace.dart @@ -10,12 +10,12 @@ /// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'dart:async'; import 'package:logging/logging.dart'; -import 'package:socket_io/src/adapter/adapter.dart'; -import 'package:socket_io/src/client.dart'; -import 'package:socket_io_common/src/parser/parser.dart'; -import 'package:socket_io/src/server.dart'; -import 'package:socket_io/src/socket.dart'; -import 'package:socket_io/src/util/event_emitter.dart'; +import 'package:socket_io_plus/src/adapter/adapter.dart'; +import 'package:socket_io_plus/src/client.dart'; +import 'package:socket_io_common/socket_io_common.dart'; +import 'package:socket_io_plus/src/server.dart'; +import 'package:socket_io_plus/src/socket.dart'; +import 'package:socket_io_plus/src/util/event_emitter.dart'; /// Blacklisted events. @@ -83,7 +83,7 @@ class Namespace extends EventEmitter { int index, List fns, Socket socket, Function fn) { return fns[index](socket, (err) { // upon error, short-circuit - if (err) return fn(err); + if (err != null) return fn(err); // if no middleware left, summon callback if (fns.length <= index + 1) return fn(null); @@ -125,7 +125,7 @@ class Namespace extends EventEmitter { // don't use Timer.run() here scheduleMicrotask(() { if ('open' == client.conn.readyState) { - if (err != null) return socket.error(err.data || err.message); + if (err != null) return socket.error(err.data ?? err.message); // track socket self.sockets.add(socket); diff --git a/lib/src/server.dart b/lib/src/server.dart index ba71551..edc8841 100644 --- a/lib/src/server.dart +++ b/lib/src/server.dart @@ -11,14 +11,12 @@ import 'dart:async'; import 'dart:io'; import 'package:logging/logging.dart'; -import 'package:socket_io/src/client.dart'; -import 'package:socket_io/src/engine/engine.dart'; -import 'package:socket_io/src/namespace.dart'; -import 'package:socket_io_common/src/parser/parser.dart'; +import 'package:socket_io_plus/src/client.dart'; +import 'package:socket_io_plus/src/engine/engine.dart'; +import 'package:socket_io_plus/src/namespace.dart'; +import 'package:socket_io_common/socket_io_common.dart'; import 'package:stream/stream.dart'; -import 'namespace.dart'; - /// Socket.IO client source. /// Old settings for backwards compatibility Map oldSettings = { @@ -140,7 +138,7 @@ class Server { if ('authorization' == key && val != null) { use((socket, next) { val(socket.request, (err, authorized) { - if (err) { + if (err != null) { return next(Exception(err)); } ; @@ -155,7 +153,7 @@ class Server { origins(val); } else if ('resource' == key) { path(val); - } else if (oldSettings[key] && engine![oldSettings[key]]) { + } else if (oldSettings[key] != null && engine![oldSettings[key]] != null) { engine![oldSettings[key]] = val; } else { _logger.severe('Option $key is not valid. Please refer to the README.'); @@ -254,50 +252,37 @@ class Server { //// response.close(); //// }); - var completer = Completer(); var connectPacket = {'type': CONNECT, 'nsp': '/'}; - encoder.encode(connectPacket, (encodedPacket) { - // the CONNECT packet will be merged with Engine.IO handshake, - // to reduce the number of round trips - opts!['initialPacket'] = encodedPacket; - - _logger.fine('creating engine.io instance with opts $opts'); - // initialize engine - engine = Engine.attach(server, opts); + // the CONNECT packet will be merged with Engine.IO handshake, + // to reduce the number of round trips + opts['initialPacket'] = encoder.encode(connectPacket); - // attach static file serving -// if (self._serveClient) self.attachServe(srv); + _logger.fine('creating engine.io instance with opts $opts'); + // initialize engine + engine = Engine.attach(server, opts); - // Export http server - httpServer = server; + // Export http server + httpServer = server; - // bind to engine events - bind(engine!); + // bind to engine events + bind(engine!); - completer.complete(); - }); - await completer.future; -// }); } else { var connectPacket = {'type': CONNECT, 'nsp': '/'}; - encoder.encode(connectPacket, (encodedPacket) { - // the CONNECT packet will be merged with Engine.IO handshake, - // to reduce the number of round trips - opts!['initialPacket'] = encodedPacket; + // the CONNECT packet will be merged with Engine.IO handshake, + // to reduce the number of round trips + opts['initialPacket'] = encoder.encode(connectPacket); - _logger.fine('creating engine.io instance with opts $opts'); - // initialize engine - engine = Engine.attach(srv, opts); + _logger.fine('creating engine.io instance with opts $opts'); + // initialize engine + engine = Engine.attach(srv, opts); - // attach static file serving -// if (self._serveClient) self.attachServe(srv); + // Export http server + httpServer = srv; - // Export http server - httpServer = srv; + // bind to engine events + bind(engine!); - // bind to engine events - bind(engine!); - }); } return this; diff --git a/lib/src/socket.dart b/lib/src/socket.dart index 016c08a..304eac7 100644 --- a/lib/src/socket.dart +++ b/lib/src/socket.dart @@ -9,12 +9,12 @@ /// /// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'dart:io'; -import 'package:socket_io/src/adapter/adapter.dart'; -import 'package:socket_io/src/client.dart'; -import 'package:socket_io_common/src/parser/parser.dart'; -import 'package:socket_io/src/namespace.dart'; -import 'package:socket_io/src/server.dart'; -import 'package:socket_io/src/util/event_emitter.dart'; +import 'package:socket_io_plus/src/adapter/adapter.dart'; +import 'package:socket_io_plus/src/client.dart'; +import 'package:socket_io_common/socket_io_common.dart'; +import 'package:socket_io_plus/src/namespace.dart'; +import 'package:socket_io_plus/src/server.dart'; +import 'package:socket_io_plus/src/util/event_emitter.dart'; /// Module exports. // @@ -266,7 +266,10 @@ class Socket extends EventEmitter { // debug('socket connected - writing packet'); nsp.connected[id] = this; join(id); - packet({'type': CONNECT}); + // Socket.IO v3+ protocol requires CONNECT response to include {sid} in + // data so the client can confirm the namespace is joined. Without it, + // socket_io_client v3.x emits a connect_error and never fires onConnect. + packet({'type': CONNECT, 'data': {'sid': id}}); } /// Called with each packet. Called by `Client`. @@ -297,7 +300,7 @@ class Socket extends EventEmitter { ondisconnect(); break; - case ERROR: + case CONNECT_ERROR: emit('error', packet['data']); } } @@ -349,10 +352,10 @@ class Socket extends EventEmitter { /// /// @api private void onack(packet) { - Function ack = acks.remove(packet['id']); - if (ack is Function) { + dynamic ackFn = acks.remove(packet['id']); + if (ackFn is Function) { // debug('calling ack %s with %j', packet.id, packet.data); - Function.apply(ack, packet['data']); + Function.apply(ackFn, packet['data']); } else { // debug('bad ack %s', packet.id); } @@ -401,7 +404,7 @@ class Socket extends EventEmitter { /// @param {Object} error object /// @api private void error(err) { - packet({'type': ERROR, 'data': err}); + packet({'type': CONNECT_ERROR, 'data': err}); } /// Disconnects this client. diff --git a/lib/src/util/event_emitter.dart b/lib/src/util/event_emitter.dart index 7c8b66e..36617af 100644 --- a/lib/src/util/event_emitter.dart +++ b/lib/src/util/event_emitter.dart @@ -16,12 +16,12 @@ typedef EventHandler = dynamic Function(T data); /// Generic event emitting and handling. class EventEmitter { /// Mapping of events to a list of event handlers - Map> _events = - HashMap>(); + Map> _events = + HashMap>(); /// Mapping of events to a list of one-time event handlers - Map> _eventsOnce = - HashMap>(); + Map> _eventsOnce = + HashMap>(); /// This function triggers all the handlers currently listening /// to [event] and passes them [data]. @@ -29,32 +29,32 @@ class EventEmitter { final list0 = _events[event]; // todo: try to optimize this. Maybe remember the off() handlers and remove later? // handler might be off() inside handler; make a copy first - final list = list0 != null ? List.from(list0) : null; + final list = list0 != null ? List.from(list0) : null; list?.forEach((handler) { handler(data); }); - _eventsOnce.remove(event)?.forEach((EventHandler handler) { + _eventsOnce.remove(event)?.forEach((Function handler) { handler(data); }); } /// This function binds the [handler] as a listener to the [event] - void on(String event, EventHandler handler) { - _events.putIfAbsent(event, () => []); + void on(String event, EventHandler handler) { + _events.putIfAbsent(event, () => []); _events[event]!.add(handler); } /// This function binds the [handler] as a listener to the first /// occurrence of the [event]. When [handler] is called once, /// it is removed. - void once(String event, EventHandler handler) { - _eventsOnce.putIfAbsent(event, () => []); + void once(String event, EventHandler handler) { + _eventsOnce.putIfAbsent(event, () => []); _eventsOnce[event]!.add(handler); } /// This function attempts to unbind the [handler] from the [event] - void off(String event, [EventHandler? handler]) { + void off(String event, [Function? handler]) { if (handler != null) { _events[event]?.remove(handler); _eventsOnce[event]?.remove(handler); @@ -72,8 +72,8 @@ class EventEmitter { /// This function unbinds all the handlers for all the events. void clearListeners() { - _events = HashMap>(); - _eventsOnce = HashMap>(); + _events = HashMap>(); + _eventsOnce = HashMap>(); } /// Returns whether the event has registered. diff --git a/pubspec.yaml b/pubspec.yaml index b42d222..58e42fd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,20 +1,18 @@ -name: socket_io +name: socket_io_plus description: > Port of JS/Node library Socket.io. It enables real-time, bidirectional and - event-based communication cross-platform. -version: 1.0.1 -homepage: https://www.zkoss.org -repository: https://github.com/rikulo/socket.io-dart -issue_tracker: https://github.com/rikulo/socket.io-dart/issues + event-based communication cross-platform. Dart 3 compatible fork. +version: 2.0.0+1 +homepage: https://pub.dev/packages/socket_io_plus environment: - sdk: '>=2.12.0 <3.0.0' + sdk: '>=3.0.0 <4.0.0' dependencies: - stream: ^3.0.0 - socket_io_common: ^1.0.1 - uuid: ^3.0.4 + stream: ^4.0.0 + socket_io_common: ^3.0.0 + uuid: ^4.0.0 logging: ^1.0.0 dev_dependencies: - test: ^1.16.8 - pedantic: ^1.11.0 + test: ^1.24.0 + lints: ^3.0.0 diff --git a/test/socket.test.dart b/test/socket.test.dart index bc9b5f1..6bea01d 100644 --- a/test/socket.test.dart +++ b/test/socket.test.dart @@ -10,21 +10,21 @@ /// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'package:test/test.dart'; -import 'package:socket_io/socket_io.dart'; +import 'package:socket_io_plus/socket_io.dart'; void main() { group('Socket IO', () { test('Start standalone server', () async { var io = Server(); var nsp = io.of('/some'); - nsp.on('connection', (client) { + nsp.on('connection', (Socket client) { print('connection /some'); client.on('msg', (data) { print('data from /some => $data'); client.emit('fromServer', 'ok 2'); }); }); - io.on('connection', (client) { + io.on('connection', (Socket client) { print('connection default namespace'); client.on('msg', (data) { print('data from default => $data');