diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a436cad --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +## 0.9.0+3 +- Apply Pedantic recommendations diff --git a/.analysis_options b/analysis_options.yaml similarity index 81% rename from .analysis_options rename to analysis_options.yaml index 1030240..3c0d8b3 100644 --- a/.analysis_options +++ b/analysis_options.yaml @@ -2,9 +2,10 @@ # # 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 - analyzer: - strong-mode: true +# analyzer: +# strong-mode: true # exclude: # - path/to/excluded/files/** # linter: diff --git a/lib/socket_io.dart b/lib/socket_io.dart index 33bec75..d8148d5 100644 --- a/lib/socket_io.dart +++ b/lib/socket_io.dart @@ -7,4 +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' show PacketParser; +export 'package:socket_io_common/src/engine/parser/parser.dart' + show PacketParser; diff --git a/lib/src/adapter/adapter.dart b/lib/src/adapter/adapter.dart index eba3cb3..5c6c7ad 100644 --- a/lib/src/adapter/adapter.dart +++ b/lib/src/adapter/adapter.dart @@ -1,15 +1,13 @@ -/** - * adapter.dart - * - * Purpose: - * - * Description: - * - * History: - * 16/02/2017, Created by jumperchen - * - * Copyright (C) 2017 Potix Corporation. All Rights Reserved. - */ +/// adapter.dart +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 16/02/2017, Created by jumperchen +/// +/// 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'; @@ -20,104 +18,103 @@ abstract class Adapter { Map rooms; Map sids; - add(String id, String room, [fn([_])]); - del(String id, String room, [fn([_])]); - delAll(String id, [fn([_])]); - broadcast(Map packet, [Map opts]); - clients(List rooms, [fn([_])]); - clientRooms(String id, [fn(err, [_])]); + void add(String id, String room, [dynamic Function([dynamic]) fn]); + void del(String id, String room, [dynamic Function([dynamic]) fn]); + void delAll(String id, [dynamic Function([dynamic]) fn]); + void broadcast(Map packet, [Map opts]); + void clients(List rooms, [dynamic Function([dynamic]) fn]); + void clientRooms(String id, [dynamic Function(dynamic, [dynamic]) fn]); static Adapter newInstance(String key, Namespace nsp) { if ('default' == key) { - return new _MemoryStoreAdapter(nsp); + return _MemoryStoreAdapter(nsp); } - throw new UnimplementedError('not supported other adapter yet.'); + throw UnimplementedError('not supported other adapter yet.'); } } class _MemoryStoreAdapter extends EventEmitter implements Adapter { + @override Map nsps = {}; + @override Map rooms; + @override Map sids; Encoder encoder; Namespace nsp; _MemoryStoreAdapter(nsp) { this.nsp = nsp; - this.rooms = {}; - this.sids = {}; - this.encoder = nsp.server.encoder; + rooms = {}; + sids = {}; + encoder = nsp.server.encoder; } - /** - * Adds a socket to a room. - * - * @param {String} socket id - * @param {String} room name - * @param {Function} callback - * @api public - */ - - add(String id, String room, [fn([_])]) { - this.sids[id] = this.sids[id] ?? {}; - this.sids[id][room] = true; - this.rooms[room] = this.rooms[room] ?? new _Room(); - this.rooms[room].add(id); + /// Adds a socket to a room. + /// + /// @param {String} socket id + /// @param {String} room name + /// @param {Function} callback + /// @api public + + @override + void add(String id, String room, [dynamic Function([dynamic]) fn]) { + sids[id] = sids[id] ?? {}; + sids[id][room] = true; + rooms[room] = rooms[room] ?? _Room(); + rooms[room].add(id); if (fn != null) scheduleMicrotask(() => fn(null)); } - /** - * Removes a socket from a room. - * - * @param {String} socket id - * @param {String} room name - * @param {Function} callback - * @api public - */ - del(String id, String room, [fn([_])]) { - this.sids[id] = this.sids[id] ?? {}; - this.sids[id].remove(room); - if (this.rooms.containsKey(room)) { - this.rooms[room].del(id); - if (this.rooms[room].length == 0) this.rooms.remove(room); + /// Removes a socket from a room. + /// + /// @param {String} socket id + /// @param {String} room name + /// @param {Function} callback + /// @api public + @override + void del(String id, String room, [dynamic Function([dynamic]) fn]) { + sids[id] = sids[id] ?? {}; + sids[id].remove(room); + if (rooms.containsKey(room)) { + rooms[room].del(id); + if (rooms[room].length == 0) rooms.remove(room); } if (fn != null) scheduleMicrotask(() => fn(null)); } - /** - * Removes a socket from all rooms it's joined. - * - * @param {String} socket id - * @param {Function} callback - * @api public - */ - delAll(String id, [fn([_])]) { - var rooms = this.sids[id]; + /// Removes a socket from all rooms it's joined. + /// + /// @param {String} socket id + /// @param {Function} callback + /// @api public + @override + void delAll(String id, [dynamic Function([dynamic]) fn]) { + var rooms = sids[id]; if (rooms != null) { for (var room in rooms.keys) { - if (this.rooms.containsKey(room)) { - this.rooms[room].del(id); - if (this.rooms[room].length == 0) this.rooms.remove(room); + if (rooms.containsKey(room)) { + rooms[room].del(id); + if (rooms[room].length == 0) rooms.remove(room); } } } - this.sids.remove(id); + sids.remove(id); if (fn != null) scheduleMicrotask(() => fn(null)); } - /** - * Broadcasts a packet. - * - * Options: - * - `flags` {Object} flags for this packet - * - `except` {Array} sids that should be excluded - * - `rooms` {Array} list of rooms to broadcast to - * - * @param {Object} packet object - * @api public - */ - broadcast(Map packet, [Map opts]) { + /// Broadcasts a packet. + /// + /// Options: + /// - `flags` {Object} flags for this packet + /// - `except` {Array} sids that should be excluded + /// - `rooms` {Array} list of rooms to broadcast to + /// + /// @param {Object} packet object + /// @api public + @override + void broadcast(Map packet, [Map opts]) { opts = opts ?? {}; List rooms = opts['rooms'] ?? []; List except = opts['except'] ?? []; @@ -130,17 +127,17 @@ class _MemoryStoreAdapter extends EventEmitter implements Adapter { var ids = {}; var socket; - packet['nsp'] = this.nsp.name; - this.encoder.encode(packet, (encodedPackets) { + packet['nsp'] = nsp.name; + encoder.encode(packet, (encodedPackets) { if (rooms.isNotEmpty) { for (var i = 0; i < rooms.length; i++) { - var room = this.rooms[rooms[i]]; + var room = rooms[rooms[i]]; if (room == null) continue; var sockets = room.sockets; for (var id in sockets.keys) { if (sockets.containsKey(id)) { - if (ids[id] != null || except.indexOf(id) >= 0) continue; - socket = this.nsp.connected[id]; + if (ids[id] != null || except.contains(id)) continue; + socket = nsp.connected[id]; if (socket != null) { socket.packet(encodedPackets, packetOpts); ids[id] = true; @@ -149,23 +146,22 @@ class _MemoryStoreAdapter extends EventEmitter implements Adapter { } } } else { - for (var id in this.sids.keys) { - if (except.indexOf(id) >= 0) continue; - socket = this.nsp.connected[id]; + 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 - */ - clients(List rooms, [fn([_])]) { + /// Gets a list of clients by sid. + /// + /// @param {Array} explicit set of rooms to check. + /// @param {Function} callback + /// @api public + @override + void clients(List rooms, [dynamic Function([dynamic]) fn]) { rooms = rooms ?? []; var ids = {}; @@ -174,13 +170,13 @@ class _MemoryStoreAdapter extends EventEmitter implements Adapter { if (rooms.isNotEmpty) { for (var i = 0; i < rooms.length; i++) { - var room = this.rooms[rooms[i]]; + var room = rooms[rooms[i]]; if (room == null) continue; var sockets = room.sockets; for (var id in sockets.keys) { if (sockets.containsKey(id)) { if (ids[id] != null) continue; - socket = this.nsp.connected[id]; + socket = nsp.connected[id]; if (socket != null) { sids.add(id); ids[id] = true; @@ -190,7 +186,7 @@ class _MemoryStoreAdapter extends EventEmitter implements Adapter { } } else { for (var id in this.sids.keys) { - socket = this.nsp.connected[id]; + socket = nsp.connected[id]; if (socket != null) sids.add(id); } } @@ -198,55 +194,48 @@ class _MemoryStoreAdapter extends EventEmitter implements Adapter { if (fn != null) scheduleMicrotask(() => fn(sids)); } - /** - * Gets the list of rooms a given client has joined. - * - * @param {String} socket id - * @param {Function} callback - * @api public - */ - clientRooms(String id, [fn(err, [_])]) { - var rooms = this.sids[id]; + /// Gets the list of rooms a given client has joined. + /// + /// @param {String} socket id + /// @param {Function} callback + /// @api public + @override + void clientRooms(String id, [dynamic Function(dynamic, [dynamic]) fn]) { + var rooms = sids[id]; if (fn != null) scheduleMicrotask(() => fn(null, rooms?.keys)); } } -/** - * Room constructor. - * - * @api private - */ +/// Room constructor. +/// +/// @api private class _Room { Map sockets; int length; _Room() { - this.sockets = {}; - this.length = 0; + sockets = {}; + length = 0; } - /** - * Adds a socket to a room. - * - * @param {String} socket id - * @api private - */ - add(String id) { - if (!this.sockets.containsKey(id)) { - this.sockets[id] = true; - this.length++; + /// Adds a socket to a room. + /// + /// @param {String} socket id + /// @api private + void add(String id) { + if (!sockets.containsKey(id)) { + sockets[id] = true; + length++; } } - /** - * Removes a socket from a room. - * - * @param {String} socket id - * @api private - */ - del(String id) { - if (this.sockets.containsKey(id)) { - this.sockets.remove(id); - this.length--; + /// Removes a socket from a room. + /// + /// @param {String} socket id + /// @api private + void del(String id) { + if (sockets.containsKey(id)) { + sockets.remove(id); + length--; } } } diff --git a/lib/src/client.dart b/lib/src/client.dart index ac41161..3988afb 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -1,15 +1,13 @@ -/** - * client.dart - * - * Purpose: - * - * Description: - * - * History: - * 22/02/2017, Created by jumperchen - * - * Copyright (C) 2017 Potix Corporation. All Rights Reserved. - */ +/// client.dart +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 22/02/2017, Created by jumperchen +/// +/// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'package:logging/logging.dart'; import 'package:socket_io/src/engine/socket.dart'; @@ -26,50 +24,48 @@ class Client { List sockets = []; Map nsps = {}; List connectBuffer = []; - Logger _logger = new Logger('socket_io:Client'); - - /** - * Client constructor. - * - * @param {Server} server instance - * @param {Socket} connection - * @api private - */ - Client(Server this.server, Socket this.conn) { - this.encoder = new Encoder(); - this.decoder = new Decoder(); - this.id = conn.id; - this.request = conn.connect.request; - this.setup(); + final Logger _logger = Logger('socket_io:Client'); + + /// Client constructor. + /// + /// @param {Server} server instance + /// @param {Socket} connection + /// @api private + Client(this.server, this.conn) { + encoder = Encoder(); + decoder = Decoder(); + id = conn.id; + request = conn.connect.request; + setup(); } - /** - * Sets up event listeners. - * - * @api private - */ - setup() { - this.decoder.on('decoded', this.ondecoded); - this.conn.on('data', this.ondata); - this.conn.on('error', this.onerror); - this.conn.on('close', this.onclose); + /// Sets up event listeners. + /// + /// @api private + void setup() { + decoder.on('decoded', ondecoded); + conn.on('data', ondata); + conn.on('error', onerror); + conn.on('close', onclose); } - /** - * Connects a client to a namespace. - * - * @param {String} namespace name - * @api private - */ - connect(name, [query]) { + /// Connects a client to a namespace. + /// + /// @param {String} namespace name + /// @api private + void connect(name, [query]) { _logger.fine('connecting to namespace $name'); - if (!this.server.nsps.containsKey(name)) { - this.packet({'type': ERROR, 'nsp': name, 'data': 'Invalid namespace'}); + if (!server.nsps.containsKey(name)) { + packet({ + 'type': ERROR, + 'nsp': name, + 'data': 'Invalid namespace' + }); return; } - var nsp = this.server.of(name); - if ('/' != name && !this.nsps.containsKey('/')) { - this.connectBuffer.add(name); + var nsp = server.of(name); + if ('/' != name && !nsps.containsKey('/')) { + connectBuffer.add(name); return; } @@ -78,81 +74,73 @@ class Client { self.sockets.add(socket); self.nsps[nsp.name] = socket; - if ('/' == nsp.name && self.connectBuffer.length > 0) { + if ('/' == nsp.name && self.connectBuffer.isNotEmpty) { self.connectBuffer.forEach(self.connect); self.connectBuffer = []; } }); } - /** - * Disconnects from all namespaces and closes transport. - * - * @api private - */ - disconnect() { + /// Disconnects from all namespaces and closes transport. + /// + /// @api private + void disconnect() { // we don't use a for loop because the length of // `sockets` changes upon each iteration - this.sockets.toList().forEach((socket) { + sockets.toList().forEach((socket) { socket.disconnect(); }); - this.sockets.clear(); + sockets.clear(); - this.close(); + close(); } - /** - * Removes a socket. Called by each `Socket`. - * - * @api private - */ - remove(socket) { - var i = this.sockets.indexOf(socket); + /// Removes a socket. Called by each `Socket`. + /// + /// @api private + void remove(socket) { + var i = sockets.indexOf(socket); if (i >= 0) { - var nsp = this.sockets[i].nsp.name; - this.sockets.removeAt(i); - this.nsps.remove(nsp); + var nsp = sockets[i].nsp.name; + sockets.removeAt(i); + nsps.remove(nsp); } else { _logger.fine('ignoring remove for ${socket.id}'); } } - /** - * Closes the underlying connection. - * - * @api private - */ - close() { - if ('open' == this.conn.readyState) { + /// Closes the underlying connection. + /// + /// @api private + void close() { + if ('open' == conn.readyState) { _logger.fine('forcing transport close'); - this.conn.close(); - this.onclose('forced server close'); + conn.close(); + onclose('forced server close'); } } - /** - * Writes a packet to the transport. - * - * @param {Object} packet object - * @param {Object} options - * @api private - */ - packet(packet, [Map opts]) { + /// Writes a packet to the transport. + /// + /// @param {Object} packet object + /// @param {Object} options + /// @api private + void packet(packet, [Map opts]) { var self = this; opts = opts ?? {}; // this writes to the actual connection - writeToEngine(encodedPackets) { + void writeToEngine(encodedPackets) { if (opts['volatile'] != null && !self.conn.transport.writable) return; for (var i = 0; i < encodedPackets.length; i++) { self.conn.write(encodedPackets[i], {'compress': opts['compress']}); } } - if ('open' == this.conn.readyState) { + if ('open' == conn.readyState) { _logger.fine('writing packet $packet'); if (opts['preEncoded'] != true) { // not broadcasting, need to encode - this.encoder.encode(packet, (encodedPackets) { + encoder.encode(packet, (encodedPackets) { // encode, then write results to engine writeToEngine(encodedPackets); }); @@ -165,33 +153,29 @@ class Client { } } - /** - * Called with incoming transport data. - * - * @api private - */ - ondata(data) { + /// Called with incoming transport data. + /// + /// @api private + void ondata(data) { // try/catch is needed for protocol violations (GH-1880) try { - this.decoder.add(data); + decoder.add(data); } catch (e, st) { _logger.severe(e, st); - this.onerror(e); + onerror(e); } } - /** - * Called when parser fully decodes a packet. - * - * @api private - */ - ondecoded(packet) { + /// Called when parser fully decodes a packet. + /// + /// @api private + void ondecoded(packet) { if (CONNECT == packet['type']) { final nsp = packet['nsp']; final uri = Uri.parse(nsp); - this.connect(uri.path, uri.queryParameters); + connect(uri.path, uri.queryParameters); } else { - var socket = this.nsps[packet['nsp']]; + var socket = nsps[packet['nsp']]; if (socket != null) { socket.onpacket(packet); } else { @@ -200,50 +184,44 @@ class Client { } } - /** - * Handles an error. - * - * @param {Objcet} error object - * @api private - */ - onerror(err) { - this.sockets.forEach((socket) { + /// Handles an error. + /// + /// @param {Objcet} error object + /// @api private + void onerror(err) { + sockets.forEach((socket) { socket.onerror(err); }); - this.onclose('client error'); + onclose('client error'); } - /** - * Called upon transport close. - * - * @param {String} reason - * @api private - */ - onclose(reason) { + /// Called upon transport close. + /// + /// @param {String} reason + /// @api private + void onclose(reason) { _logger.fine('client close with reason $reason'); // ignore a potential subsequent `close` event - this.destroy(); + destroy(); // `nsps` and `sockets` are cleaned up seamlessly - if (this.sockets.isNotEmpty) { - new List.from(this.sockets).forEach((socket) { + if (sockets.isNotEmpty) { + List.from(sockets).forEach((socket) { socket.onclose(reason); }); - this.sockets.clear(); + sockets.clear(); } - this.decoder.destroy(); // clean up decoder + decoder.destroy(); // clean up decoder } - /** - * Cleans up event listeners. - * - * @api private - */ - destroy() { - this.conn.off('data', this.ondata); - this.conn.off('error', this.onerror); - this.conn.off('close', this.onclose); - this.decoder.off('decoded', this.ondecoded); + /// Cleans up event listeners. + /// + /// @api private + void destroy() { + conn.off('data', ondata); + conn.off('error', onerror); + conn.off('close', onclose); + decoder.off('decoded', ondecoded); } } diff --git a/lib/src/engine/connect.dart b/lib/src/engine/connect.dart index 707cce4..275fead 100644 --- a/lib/src/engine/connect.dart +++ b/lib/src/engine/connect.dart @@ -1,15 +1,13 @@ -/** - * connect.dart - * - * Purpose: - * - * Description: - * - * History: - * 06/03/2017, Created by jumperchen - * - * Copyright (C) 2017 Potix Corporation. All Rights Reserved. - */ +/// connect.dart +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 06/03/2017, Created by jumperchen +/// +/// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'package:stream/stream.dart'; import 'dart:async'; import 'dart:io'; @@ -31,19 +29,17 @@ class SocketConnect extends HttpConnectWrapper { Future get done { if (_completed == true) { - return new Future.value('done'); + return Future.value('done'); } - if (_socket != null) + if (_socket != null) { return _socket.done; - else { - _done = new Completer(); + } else { + _done = Completer(); return _done.future; } } - /** - * Closes the current connection. - */ + /// Closes the current connection. void close() { if (_done != null) { _done.complete('done'); diff --git a/lib/src/engine/engine.dart b/lib/src/engine/engine.dart index 24e6ddd..16e4897 100644 --- a/lib/src/engine/engine.dart +++ b/lib/src/engine/engine.dart @@ -1,39 +1,35 @@ -/** - * engine.dart - * - * Purpose: - * - * Description: - * - * History: - * 16/02/2017, Created by jumperchen - * - * Copyright (C) 2017 Potix Corporation. All Rights Reserved. - */ +/// engine.dart +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 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'; class Engine extends EventEmitter { static Engine attach(server, [Map options]) { - var engine = new Server(options); + var engine = Server(options); engine.attachTo(server, options); return engine; } dynamic operator [](Object key) {} - /** - * Associates the [key] with the given [value]. - * - * If the key was already in the map, its associated value is changed. - * Otherwise the key-value pair is added to the map. - */ + /// Associates the [key] with the given [value]. + /// + /// If the key was already in the map, its associated value is changed. + /// Otherwise the key-value pair is added to the map. void operator []=(String key, dynamic value) {} // init() {} // upgrades() {} // verify() {} // prepare() {} - close() {} + void close() {} // handleRequest() {} // handshake() {} } diff --git a/lib/src/engine/server.dart b/lib/src/engine/server.dart index 21a9869..da93704 100644 --- a/lib/src/engine/server.dart +++ b/lib/src/engine/server.dart @@ -1,15 +1,13 @@ -/** - * server - * - * Purpose: - * - * Description: - * - * History: - * 17/02/2017, Created by jumperchen - * - * Copyright (C) 2017 Potix Corporation. All Rights Reserved. - */ +/// server +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 17/02/2017, Created by jumperchen +/// +/// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'dart:convert'; import 'dart:io' hide Socket; import 'package:logging/logging.dart'; @@ -20,12 +18,10 @@ import 'package:socket_io/src/engine/transport/transports.dart'; import 'package:stream/stream.dart'; import 'package:uuid/uuid.dart'; -/** - * Server constructor. - * - * @param {Object} options - * @api public - */ +/// Server constructor. +/// +/// @param {Object} options +/// @api public class ServerErrors { static const int UNKNOWN_TRANSPORT = 0; static const int UNKNOWN_SID = 1; @@ -34,7 +30,7 @@ class ServerErrors { static const int FORBIDDEN = 4; } -const Map ServerErrorMessages = const { +const Map ServerErrorMessages = { 0: 'Transport unknown', 1: 'Session ID unknown', 2: 'Bad handshake method', @@ -43,7 +39,7 @@ const Map ServerErrorMessages = const { }; class Server extends Engine { - static final Logger _logger = new Logger("socket_io:engine.Server"); + static final Logger _logger = Logger('socket_io:engine.Server'); Map clients; int clientsCount; int pingTimeout; @@ -59,53 +55,53 @@ class Server extends Engine { Map perMessageDeflate; Map httpCompression; dynamic initialPacket; - Uuid _uuid = new Uuid(); + final Uuid _uuid = Uuid(); Server([Map opts]) { clients = {}; - this.clientsCount = 0; + clientsCount = 0; opts = opts ?? {}; - this.pingTimeout = opts['pingTimeout'] ?? 60000; - this.pingInterval = opts['pingInterval'] ?? 25000; - this.upgradeTimeout = opts['upgradeTimeout'] ?? 10000; - this.maxHttpBufferSize = opts['maxHttpBufferSize'] ?? 10E7; - this.transports = ['polling', 'websocket']; - this.allowUpgrades = false != opts['allowUpgrades']; - this.allowRequest = opts['allowRequest']; - this.cookie = opts['cookie'] == false + pingTimeout = opts['pingTimeout'] ?? 60000; + pingInterval = opts['pingInterval'] ?? 25000; + upgradeTimeout = opts['upgradeTimeout'] ?? 10000; + maxHttpBufferSize = opts['maxHttpBufferSize'] ?? 10E7; + transports = ['polling', 'websocket']; + allowUpgrades = false != opts['allowUpgrades']; + allowRequest = opts['allowRequest']; + cookie = opts['cookie'] == false ? false : opts['cookie'] ?? 'io'; //false != opts.cookie ? (opts.cookie || 'io') : false; - this.cookiePath = opts['cookiePath'] == false + cookiePath = opts['cookiePath'] == false ? false : opts['cookiePath'] ?? '/'; //false != opts.cookiePath ? (opts.cookiePath || '/') : false; - this.cookieHttpOnly = opts['cookieHttpOnly'] != false; + cookieHttpOnly = opts['cookieHttpOnly'] != false; if (!opts.containsKey('perMessageDeflate') || opts['perMessageDeflate'] == true) { - this.perMessageDeflate = + perMessageDeflate = opts['perMessageDeflate'] is Map ? opts['perMessageDeflate'] : {}; - if (!this.perMessageDeflate.containsKey('threshold')) - this.perMessageDeflate['threshold'] = 1024; + if (!perMessageDeflate.containsKey('threshold')) { + perMessageDeflate['threshold'] = 1024; + } + } + httpCompression = opts['httpCompression'] ?? {}; + if (!httpCompression.containsKey('threshold')) { + httpCompression['threshold'] = 1024; } - this.httpCompression = opts['httpCompression'] ?? {}; - if (!this.httpCompression.containsKey('threshold')) - this.httpCompression['threshold'] = 1024; - this.initialPacket = opts['initialPacket']; - this._init(); + initialPacket = opts['initialPacket']; + _init(); } - /** - * Initialize websocket server - * - * @api private - */ + /// Initialize websocket server + /// + /// @api private - _init() { + void _init() { // if (this.transports.indexOf('websocket') == -1) return; // if (this.ws) this.ws.close(); @@ -126,31 +122,27 @@ class Server extends Engine { // }); } - /** - * Returns a list of available transports for upgrade given a certain transport. - * - * @return {Array} - * @api public - */ + /// Returns a list of available transports for upgrade given a certain transport. + /// + /// @return {Array} + /// @api public List upgrades(transport) { - if (!this.allowUpgrades) return null; + if (!allowUpgrades) return null; return Transports.upgradesTo(transport); } - /** - * Verifies a request. - * - * @param {http.IncomingMessage} - * @return {Boolean} whether the request is valid - * @api private - */ + /// Verifies a request. + /// + /// @param {http.IncomingMessage} + /// @return {Boolean} whether the request is valid + /// @api private - verify(SocketConnect connect, bool upgrade, fn) { + void verify(SocketConnect connect, bool upgrade, fn) { // transport check var req = connect.request; var transport = req.uri.queryParameters['transport']; - if (this.transports.indexOf(transport) == -1) { + if (!transports.contains(transport)) { _logger.fine('unknown transport "$transport"'); return fn(ServerErrors.UNKNOWN_TRANSPORT, false); } @@ -158,35 +150,35 @@ class Server extends Engine { // sid check var sid = req.uri.queryParameters['sid']; if (sid != null) { - if (!this.clients.containsKey(sid)) { + if (!clients.containsKey(sid)) { return fn(ServerErrors.UNKNOWN_SID, false); } - if (!upgrade && this.clients[sid].transport.name != transport) { + if (!upgrade && clients[sid].transport.name != transport) { _logger.fine('bad request: unexpected transport without upgrade'); return fn(ServerErrors.BAD_REQUEST, false); } } else { // handshake is GET only - if ('GET' != req.method) + if ('GET' != req.method) { return fn(ServerErrors.BAD_HANDSHAKE_METHOD, false); - if (this.allowRequest == null) return fn(null, true); - return this.allowRequest(req, fn); + } + if (allowRequest == null) return fn(null, true); + return allowRequest(req, fn); } fn(null, true); } - /** - * Closes all clients. - * - * @api public - */ + /// Closes all clients. + /// + /// @api public - close() { + @override + void close() { _logger.fine('closing all open clients'); - for (var key in this.clients.keys) { - if (this.clients[key] != null) { - this.clients[key].close(true); + for (var key in clients.keys) { + if (clients[key] != null) { + clients[key].close(true); } } // if (this.ws) { @@ -196,22 +188,20 @@ class Server extends Engine { // } } - /** - * Handles an Engine.IO HTTP request. - * - * @param {http.IncomingMessage} request - * @param {http.ServerResponse|http.OutgoingMessage} response - * @api public - */ + /// Handles an Engine.IO HTTP request. + /// + /// @param {http.IncomingMessage} request + /// @param {http.ServerResponse|http.OutgoingMessage} response + /// @api public - handleRequest(SocketConnect connect) { + void handleRequest(SocketConnect connect) { var req = connect.request; _logger.fine('handling ${req.method} http request ${req.uri.path}'); // this.prepare(req); // req.res = res; var self = this; - this.verify(connect, false, (err, success) { + verify(connect, false, (err, success) { if (!success) { sendErrorMessage(req, err); return; @@ -227,15 +217,13 @@ class Server extends Engine { }); } - /** - * Sends an Engine.IO Error Message - * - * @param {http.ServerResponse} response - * @param {code} error code - * @api private - */ + /// Sends an Engine.IO Error Message + /// + /// @param {http.ServerResponse} response + /// @param {code} error code + /// @api private - static sendErrorMessage(HttpRequest req, code) { + static void sendErrorMessage(HttpRequest req, code) { var res = req.response; var isForbidden = !ServerErrorMessages.containsKey(code); if (isForbidden) { @@ -259,26 +247,22 @@ class Server extends Engine { json.encode({'code': code, 'message': ServerErrorMessages[code]})); } - /** - * generate a socket id. - * Overwrite this method to generate your custom socket id - * - * @param {Object} request object - * @api public - */ - generateId(SocketConnect connect) { + /// generate a socket id. + /// Overwrite this method to generate your custom socket id + /// + /// @param {Object} request object + /// @api public + String generateId(SocketConnect connect) { return _uuid.v1().replaceAll('-', ''); } - /** - * Handshakes a new client. - * - * @param {String} transport name - * @param {Object} request object - * @api private - */ - handshake(String transportName, SocketConnect connect) { - var id = this.generateId(connect); + /// Handshakes a new client. + /// + /// @param {String} transport name + /// @param {Object} request object + /// @api private + void handshake(String transportName, SocketConnect connect) { + var id = generateId(connect); _logger.fine('handshaking client $id'); var transport; @@ -286,10 +270,10 @@ class Server extends Engine { try { transport = Transports.newInstance(transportName, connect); if ('polling' == transportName) { - transport.maxHttpBufferSize = this.maxHttpBufferSize; - transport.httpCompression = this.httpCompression; + transport.maxHttpBufferSize = maxHttpBufferSize; + transport.httpCompression = httpCompression; } else if ('websocket' == transportName) { - transport.perMessageDeflate = this.perMessageDeflate; + transport.perMessageDeflate = perMessageDeflate; } if (req.uri.hasQuery && req.uri.queryParameters.containsKey('b64')) { @@ -301,15 +285,13 @@ class Server extends Engine { sendErrorMessage(req, ServerErrors.BAD_REQUEST); return; } - var socket = new Socket(id, this, transport, connect); + var socket = Socket(id, this, transport, connect); - if (false != this.cookie) { + if (false != cookie) { transport.on('headers', (headers) { - headers['Set-Cookie'] = '${this.cookie}=${Uri.encodeComponent(id)}' + - (this.cookiePath?.isNotEmpty == true - ? '; Path=${this.cookiePath}' - : '') + - (this.cookiePath?.isNotEmpty == true && this.cookieHttpOnly == true + headers['Set-Cookie'] = '${cookie}=${Uri.encodeComponent(id)}' + + (cookiePath?.isNotEmpty == true ? '; Path=${cookiePath}' : '') + + (cookiePath?.isNotEmpty == true && cookieHttpOnly == true ? '; HttpOnly' : ''); }); @@ -317,26 +299,24 @@ class Server extends Engine { transport.onRequest(connect); - this.clients[id] = socket; - this.clientsCount++; + clients[id] = socket; + clientsCount++; socket.once('close', (_) { - this.clients.remove(id); - this.clientsCount--; + clients.remove(id); + clientsCount--; }); - this.emit('connection', socket); + emit('connection', socket); } - /** - * Handles an Engine.IO HTTP Upgrade. - * - * @api public - */ - handleUpgrade(SocketConnect connect) { + /// Handles an Engine.IO HTTP Upgrade. + /// + /// @api public + void handleUpgrade(SocketConnect connect) { // this.prepare(req); - this.verify(connect, true, (err, success) { + verify(connect, true, (err, success) { if (!success) { abortConnection(connect, err); return; @@ -348,19 +328,17 @@ class Server extends Engine { // delegate to ws // self.ws.handleUpgrade(req, socket, head, function (conn) { - this.onWebSocket(connect); + onWebSocket(connect); // }); }); } - /** - * Called upon a ws.io connection. - * - * @param {ws.Socket} websocket - * @api private - */ + /// Called upon a ws.io connection. + /// + /// @param {ws.Socket} websocket + /// @api private - onWebSocket(SocketConnect connect) { + void onWebSocket(SocketConnect connect) { // socket.listen((_) {}, // onError: () => _logger.fine('websocket error before upgrade')); @@ -380,7 +358,7 @@ class Server extends Engine { // req.websocket = socket; if (id != null) { - var client = this.clients[id]; + var client = clients[id]; if (client == null) { _logger.fine('upgrade attempt for closed client'); connect.websocket.close(); @@ -400,25 +378,23 @@ class Server extends Engine { } else { transport.supportsBinary = true; } - transport.perMessageDeflate = this.perMessageDeflate; + transport.perMessageDeflate = perMessageDeflate; client.maybeUpgrade(transport); } } else { - this.handshake(connect.request.uri.queryParameters['transport'], connect); + handshake(connect.request.uri.queryParameters['transport'], connect); } } - /** - * Captures upgrade requests for a http.Server. - * - * @param {http.Server} server - * @param {Object} options - * @api public - */ - attachTo(StreamServer server, Map options) { + /// Captures upgrade requests for a http.Server. + /// + /// @param {http.Server} server + /// @param {Object} options + /// @api public + void attachTo(StreamServer server, Map options) { options = options ?? {}; var path = - (options['path'] ?? '/engine.io').replaceFirst(new RegExp(r"\/$"), ''); + (options['path'] ?? '/engine.io').replaceFirst(RegExp(r'\/$'), ''); // normalize path path += '/'; @@ -429,42 +405,40 @@ class Server extends Engine { _logger.fine('intercepting request for path "$path"'); if (WebSocketTransformer.isUpgradeRequest(req) && - this.transports.contains('websocket')) { + transports.contains('websocket')) { // print('init websocket... ${req.uri}'); var socket = await WebSocketTransformer.upgrade(req); - var socketConnect = new SocketConnect.fromWebSocket(connect, socket); + var socketConnect = SocketConnect.fromWebSocket(connect, socket); socketConnect.dataset['options'] = options; - this.handleUpgrade(socketConnect); + handleUpgrade(socketConnect); return socketConnect.done; } else { - var socketConnect = new SocketConnect(connect); + var socketConnect = SocketConnect(connect); socketConnect.dataset['options'] = options; - this.handleRequest(socketConnect); + handleRequest(socketConnect); return socketConnect.done; } }, preceding: true); } - /** - * Closes the connection - * - * @param {net.Socket} socket - * @param {code} error code - * @api private - */ + /// Closes the connection + /// + /// @param {net.Socket} socket + /// @param {code} error code + /// @api private - static abortConnection(SocketConnect connect, code) { + static void abortConnection(SocketConnect connect, code) { var socket = connect.websocket; if (socket.readyState == HttpStatus.ok) { var 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' + - 'Content-type: text/html\r\n' + - 'Content-Length: $length\r\n' + - '\r\n' + + socket.add('HTTP/1.1 400 Bad Request\r\n' + 'Connection: close\r\n' + 'Content-type: text/html\r\n' + 'Content-Length: $length\r\n' + '\r\n' + message); } socket.close(); diff --git a/lib/src/engine/socket.dart b/lib/src/engine/socket.dart index e3f76be..8ced907 100644 --- a/lib/src/engine/socket.dart +++ b/lib/src/engine/socket.dart @@ -1,15 +1,13 @@ -/** - * socket.dart - * - * Purpose: - * - * Description: - * - * History: - * 17/02/2017, Created by jumperchen - * - * Copyright (C) 2017 Potix Corporation. All Rights Reserved. - */ +/// socket.dart +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 17/02/2017, Created by jumperchen +/// +/// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -19,13 +17,11 @@ 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'; -/** - * Client class (abstract). - * - * @api private - */ +/// Client class (abstract). +/// +/// @api private class Socket extends EventEmitter { - static final Logger _logger = new Logger("socket_io:engine.Socket"); + static final Logger _logger = Logger('socket_io:engine.Socket'); String id; Server server; Transport transport; @@ -43,83 +39,79 @@ class Socket extends EventEmitter { Timer pingTimeoutTimer; Socket(this.id, this.server, this.transport, this.connect) { - this.upgrading = false; - this.upgraded = false; - this.readyState = 'opening'; - this.writeBuffer = []; - this.packetsFn = []; - this.sentCallbackFn = []; - this.cleanupFn = []; + upgrading = false; + upgraded = false; + readyState = 'opening'; + writeBuffer = []; + packetsFn = []; + sentCallbackFn = []; + cleanupFn = []; // Cache IP since it might not be in the req later - this.remoteAddress = connect.request.connectionInfo.remoteAddress; + remoteAddress = connect.request.connectionInfo.remoteAddress; - this.checkIntervalTimer = null; - this.upgradeTimeoutTimer = null; - this.pingTimeoutTimer = null; + checkIntervalTimer = null; + upgradeTimeoutTimer = null; + pingTimeoutTimer = null; - this.setTransport(transport); - this.onOpen(); + setTransport(transport); + onOpen(); } - /** - * Called upon transport considered open. - * - * @api private - */ + /// Called upon transport considered open. + /// + /// @api private - onOpen() { - this.readyState = 'open'; + void onOpen() { + readyState = 'open'; // sends an `open` packet - this.transport.sid = this.id; - this.sendPacket('open', + transport.sid = id; + sendPacket('open', data: json.encode({ - 'sid': this.id, - 'upgrades': this.getAvailableUpgrades(), - 'pingInterval': this.server.pingInterval, - 'pingTimeout': this.server.pingTimeout + 'sid': id, + 'upgrades': getAvailableUpgrades(), + 'pingInterval': server.pingInterval, + 'pingTimeout': server.pingTimeout })); // if (this.server.initialPacket != null) { // this.sendPacket('message', data: this.server.initialPacket); // } - this.emit('open'); - this.setPingTimeout(); + emit('open'); + setPingTimeout(); } - /** - * Called upon transport packet. - * - * @param {Object} packet - * @api private - */ + /// Called upon transport packet. + /// + /// @param {Object} packet + /// @api private - onPacket(packet) { - if ('open' == this.readyState) { + void onPacket(packet) { + if ('open' == readyState) { // export packet event _logger.fine('packet'); - this.emit('packet', packet); + emit('packet', packet); // Reset ping timeout on any packet, incoming data is a good sign of // other side's liveness - this.setPingTimeout(); + setPingTimeout(); switch (packet['type']) { case 'ping': _logger.fine('got ping'); - this.sendPacket('pong'); - this.emit('heartbeat'); + sendPacket('pong'); + emit('heartbeat'); break; case 'error': - this.onClose('parse error'); + onClose('parse error'); break; case 'message': var data = packet['data']; - this.emit('data', data); - this.emit('message', data); + emit('data', data); + emit('message', data); break; } } else { @@ -127,41 +119,33 @@ class Socket extends EventEmitter { } } - /** - * Called upon transport error. - * - * @param {Error} error object - * @api private - */ - onError(err) { + /// Called upon transport error. + /// + /// @param {Error} error object + /// @api private + void onError(err) { _logger.fine('transport error'); - this.onClose('transport error', err); + onClose('transport error', err); } - /** - * Sets and resets ping timeout timer based on client pings. - * - * @api private - */ - setPingTimeout() { - if (this.pingTimeoutTimer != null) { - this.pingTimeoutTimer.cancel(); + /// Sets and resets ping timeout timer based on client pings. + /// + /// @api private + void setPingTimeout() { + if (pingTimeoutTimer != null) { + pingTimeoutTimer.cancel(); } - this.pingTimeoutTimer = new Timer( - new Duration( - milliseconds: this.server.pingInterval + this.server.pingTimeout), - () { - this.onClose('ping timeout'); + pingTimeoutTimer = Timer( + Duration(milliseconds: server.pingInterval + server.pingTimeout), () { + onClose('ping timeout'); }); } - /** - * Attaches handlers for the given transport. - * - * @param {Transport} transport - * @api private - */ - setTransport(Transport transport) { + /// Attaches handlers for the given transport. + /// + /// @param {Transport} transport + /// @api private + void setTransport(Transport transport) { var onError = this.onError; var onPacket = this.onPacket; var flush = (_) => this.flush(); @@ -175,9 +159,9 @@ class Socket extends EventEmitter { this.transport.on('drain', flush); this.transport.once('close', onClose); // this function will manage packet events (also message callbacks) - this.setupSendCallback(); + setupSendCallback(); - this.cleanupFn.add(() { + cleanupFn.add(() { transport.off('error', onError); transport.off('packet', onPacket); transport.off('drain', flush); @@ -185,21 +169,19 @@ class Socket extends EventEmitter { }); } - /** - * Upgrades socket to the given transport - * - * @param {Transport} transport - * @api private - */ - maybeUpgrade(transport) { + /// Upgrades socket to the given transport + /// + /// @param {Transport} transport + /// @api private + void maybeUpgrade(transport) { _logger.fine( 'might upgrade socket transport from ${this.transport.name} to ${transport.name}'); - this.upgrading = true; - Map cleanupFn = {}; + upgrading = true; + var cleanupFn = {}; // set transport upgrade timer - this.upgradeTimeoutTimer = - new Timer(new Duration(milliseconds: this.server.upgradeTimeout), () { + upgradeTimeoutTimer = + Timer(Duration(milliseconds: server.upgradeTimeout), () { _logger.fine('client did not complete upgrade - closing transport'); cleanupFn['cleanup'](); if ('open' == transport.readyState) { @@ -222,23 +204,23 @@ class Socket extends EventEmitter { transport.send([ {'type': 'pong', 'data': 'probe'} ]); - this.emit('upgrading', transport); - if (this.checkIntervalTimer != null) { - this.checkIntervalTimer.cancel(); + emit('upgrading', transport); + if (checkIntervalTimer != null) { + checkIntervalTimer.cancel(); } - this.checkIntervalTimer = - new Timer.periodic(new Duration(milliseconds: 100), (_) => check()); - } else if ('upgrade' == packet['type'] && this.readyState != 'closed') { + checkIntervalTimer = + Timer.periodic(Duration(milliseconds: 100), (_) => check()); + } else if ('upgrade' == packet['type'] && readyState != 'closed') { _logger.fine('got upgrade packet - upgrading'); cleanupFn['cleanup'](); this.transport.discard(); - this.upgraded = true; - this.clearTransport(); - this.setTransport(transport); - this.emit('upgrade', transport); - this.setPingTimeout(); - this.flush(); - if (this.readyState == 'closing') { + upgraded = true; + clearTransport(); + setTransport(transport); + emit('upgrade', transport); + setPingTimeout(); + flush(); + if (readyState == 'closing') { transport.close(() { this.onClose('forced close'); }); @@ -265,226 +247,207 @@ class Socket extends EventEmitter { }; var cleanup = () { - this.upgrading = false; - this.checkIntervalTimer?.cancel(); - this.checkIntervalTimer = null; + upgrading = false; + checkIntervalTimer?.cancel(); + checkIntervalTimer = null; - this.upgradeTimeoutTimer?.cancel(); - this.upgradeTimeoutTimer = null; + upgradeTimeoutTimer?.cancel(); + upgradeTimeoutTimer = null; transport.off('packet', onPacket); transport.off('close', onTransportClose); transport.off('error', onError); - this.off('close', onClose); + off('close', onClose); }; cleanupFn['cleanup'] = cleanup; // define it later transport.on('packet', onPacket); transport.once('close', onTransportClose); transport.once('error', onError); - this.once('close', onClose); + once('close', onClose); } - /** - * Clears listeners and timers associated with current transport. - * - * @api private - */ - clearTransport() { + /// Clears listeners and timers associated with current transport. + /// + /// @api private + void clearTransport() { var cleanup; - var toCleanUp = this.cleanupFn.length; + var toCleanUp = cleanupFn.length; for (var i = 0; i < toCleanUp; i++) { - cleanup = this.cleanupFn.removeAt(0); + cleanup = cleanupFn.removeAt(0); cleanup(); } // silence further transport errors and prevent uncaught exceptions - this.transport.on('error', (_) { + transport.on('error', (_) { _logger.fine('error triggered by discarded transport'); }); // ensure transport won't stay open - this.transport.close(); + transport.close(); - this.pingTimeoutTimer?.cancel(); + pingTimeoutTimer?.cancel(); } - /** - * Called upon transport considered closed. - * Possible reasons: `ping timeout`, `client error`, `parse error`, - * `transport error`, `server close`, `transport close` - */ - onClose(reason, [description]) { - if ('closed' != this.readyState) { - this.readyState = 'closed'; - this.pingTimeoutTimer?.cancel(); - this.checkIntervalTimer?.cancel(); - this.checkIntervalTimer = null; - this.upgradeTimeoutTimer?.cancel(); + /// Called upon transport considered closed. + /// Possible reasons: `ping timeout`, `client error`, `parse error`, + /// `transport error`, `server close`, `transport close` + void onClose(reason, [description]) { + if ('closed' != readyState) { + readyState = 'closed'; + pingTimeoutTimer?.cancel(); + checkIntervalTimer?.cancel(); + checkIntervalTimer = null; + upgradeTimeoutTimer?.cancel(); // clean writeBuffer in next tick, so developers can still // grab the writeBuffer on 'close' event scheduleMicrotask(() { - this.writeBuffer = []; + writeBuffer = []; }); - this.packetsFn = []; - this.sentCallbackFn = []; - this.clearTransport(); - this.emit('close', [reason, description]); + packetsFn = []; + sentCallbackFn = []; + clearTransport(); + emit('close', [reason, description]); } } - /** - * Setup and manage send callback - * - * @api private - */ - setupSendCallback() { + /// Setup and manage send callback + /// + /// @api private + void setupSendCallback() { // the message was sent successfully, execute the callback var onDrain = (_) { - if (this.sentCallbackFn.isNotEmpty) { - var seqFn = this.sentCallbackFn[0]; + if (sentCallbackFn.isNotEmpty) { + var seqFn = sentCallbackFn[0]; if (seqFn is Function) { _logger.fine('executing send callback'); - seqFn(this.transport); + seqFn(transport); } - /** else if (Array.isArray(seqFn)) { - _logger.fine('executing batch send callback'); - for (var l = seqFn.length, i = 0; i < l; i++) { - if ('function' === typeof seqFn[i]) { - seqFn[i](self.transport); - } - } - }*/ + /// else if (Array.isArray(seqFn)) { + /// _logger.fine('executing batch send callback'); + /// for (var l = seqFn.length, i = 0; i < l; i++) { + /// if ('function' === typeof seqFn[i]) { + /// seqFn[i](self.transport); + /// } + /// } + /// } } }; - this.transport.on('drain', onDrain); + transport.on('drain', onDrain); - this.cleanupFn.add(() { - this.transport.off('drain', onDrain); + cleanupFn.add(() { + transport.off('drain', onDrain); }); } - /** - * Sends a message packet. - * - * @param {String} message - * @param {Object} options - * @param {Function} callback - * @return {Socket} for chaining - * @api public - */ - send(data, options, [callback]) => write(data, options, callback); - write(data, options, [callback]) { - this.sendPacket('message', - data: data, options: options, callback: callback); + /// Sends a message packet. + /// + /// @param {String} message + /// @param {Object} options + /// @param {Function} callback + /// @return {Socket} for chaining + /// @api public + void send(data, options, [callback]) => write(data, options, callback); + Socket write(data, options, [callback]) { + sendPacket('message', data: data, options: options, callback: callback); return this; } - /** - * Sends a packet. - * - * @param {String} packet type - * @param {String} optional, data - * @param {Object} options - * @api private - */ - sendPacket(type, {data, options, callback}) { + /// Sends a packet. + /// + /// @param {String} packet type + /// @param {String} optional, data + /// @param {Object} options + /// @api private + void sendPacket(type, {data, options, callback}) { options = options ?? {}; options['compress'] = false != options['compress']; - if ('closing' != this.readyState && 'closed' != this.readyState) { + if ('closing' != readyState && 'closed' != readyState) { // _logger.fine('sending packet "%s" (%s)', type, data); var packet = {'type': type, 'options': options}; if (data != null) packet['data'] = data; // exports packetCreate event - this.emit('packetCreate', packet); + emit('packetCreate', packet); - this.writeBuffer.add(packet); + writeBuffer.add(packet); // add send callback to object, if defined - if (callback != null) this.packetsFn.add(callback); + if (callback != null) packetsFn.add(callback); - this.flush(); + flush(); } } - /** - * Attempts to flush the packets buffer. - * - * @api private - */ - flush() { - if ('closed' != this.readyState && - this.transport.writable == true && - this.writeBuffer.length > 0) { + /// Attempts to flush the packets buffer. + /// + /// @api private + void flush() { + if ('closed' != readyState && + transport.writable == true && + writeBuffer.isNotEmpty) { _logger.fine('flushing buffer to transport'); - this.emit('flush', this.writeBuffer); - this.server.emit('flush', [this, this.writeBuffer]); - var wbuf = this.writeBuffer; - this.writeBuffer = []; - if (this.transport.supportsFraming == false) { - this.sentCallbackFn.add((_) => this.packetsFn.forEach((f) => f(_))); + emit('flush', writeBuffer); + server.emit('flush', [this, writeBuffer]); + var wbuf = writeBuffer; + writeBuffer = []; + if (transport.supportsFraming == false) { + sentCallbackFn.add((_) => packetsFn.forEach((f) => f(_))); } else { - this.sentCallbackFn.addAll(this.packetsFn); + sentCallbackFn.addAll(packetsFn); } - this.packetsFn = []; - this.transport.send(wbuf); - this.emit('drain'); - this.server.emit('drain', this); + packetsFn = []; + transport.send(wbuf); + emit('drain'); + server.emit('drain', this); } } - /** - * Get available upgrades for this socket. - * - * @api private - */ - getAvailableUpgrades() { + /// Get available upgrades for this socket. + /// + /// @api private + List getAvailableUpgrades() { var availableUpgrades = []; - var allUpgrades = this.server.upgrades(this.transport.name); + var allUpgrades = server.upgrades(transport.name); for (var i = 0, l = allUpgrades.length; i < l; ++i) { var upg = allUpgrades[i]; - if (this.server.transports.contains(upg)) { + if (server.transports.contains(upg)) { availableUpgrades.add(upg); } } return availableUpgrades; } - /** - * Closes the socket and underlying transport. - * - * @param {Boolean} optional, discard - * @return {Socket} for chaining - * @api public - */ + /// Closes the socket and underlying transport. + /// + /// @param {Boolean} optional, discard + /// @return {Socket} for chaining + /// @api public - close([discard = false]) { - if ('open' != this.readyState) return; - this.readyState = 'closing'; + void close([discard = false]) { + if ('open' != readyState) return; + readyState = 'closing'; - if (this.writeBuffer.isNotEmpty) { - this.once('drain', (_) => this.closeTransport(discard)); + if (writeBuffer.isNotEmpty) { + once('drain', (_) => closeTransport(discard)); return; } - this.closeTransport(discard); + closeTransport(discard); } - /** - * Closes the underlying transport. - * - * @param {Boolean} discard - * @api private - */ - closeTransport(discard) { - if (discard == true) this.transport.discard(); - this.transport.close(() => this.onClose('forced close')); + /// Closes the underlying transport. + /// + /// @param {Boolean} discard + /// @api private + void closeTransport(discard) { + if (discard == true) transport.discard(); + transport.close(() => onClose('forced close')); } } diff --git a/lib/src/engine/transport/jsonp_transport.dart b/lib/src/engine/transport/jsonp_transport.dart index 969a44c..23de1f9 100644 --- a/lib/src/engine/transport/jsonp_transport.dart +++ b/lib/src/engine/transport/jsonp_transport.dart @@ -1,15 +1,13 @@ -/** - * jsonp_transport.dart - * - * Purpose: - * - * Description: - * - * History: - * 22/02/2017, Created by jumperchen - * - * Copyright (C) 2017 Potix Corporation. All Rights Reserved. - */ +/// jsonp_transport.dart +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 22/02/2017, Created by jumperchen +/// +/// 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'; @@ -18,64 +16,62 @@ class JSONPTransport extends PollingTransport { String head; String foot; JSONPTransport(SocketConnect connect) : super(connect) { - this.head = '___eio[' + + head = '___eio[' + (connect.request.uri.queryParameters['j'] ?? '') - .replaceAll(new RegExp('[^0-9]'), '') + + .replaceAll(RegExp('[^0-9]'), '') + ']('; - this.foot = ');'; + foot = ');'; } - /** - * Handles incoming data. - * Due to a bug in \n handling by browsers, we expect a escaped string. - * - * @api private - */ - onData(data) { + /// Handles incoming data. + /// Due to a bug in \n handling by browsers, we expect a escaped string. + /// + /// @api private + @override + void onData(data) { // we leverage the qs module so that we get built-in DoS protection // and the fast alternative to decodeURIComponent data = parse(data)['d']; if (data is String) { // client will send already escaped newlines as \\\\n and newlines as \\n // \\n must be replaced with \n and \\\\n with \\n - data = data.replaceAllMapped(new RegExp(r'(\\)?\\n'), (match) { - throw new UnimplementedError('Not implemented yet'); + data = data.replaceAllMapped(RegExp(r'(\\)?\\n'), (match) { + throw UnimplementedError('Not implemented yet'); // print(match); // match // return slashes ? match : '\n'; }); - super.onData(data.replaceAll(new RegExp(r'\\\\n'), '\\n')); + super.onData(data.replaceAll(RegExp(r'\\\\n'), '\\n')); } } - /** - * Performs the write. - * - * @api private - */ - doWrite(data, options, [callback]) { + /// Performs the write. + /// + /// @api private + @override + void doWrite(data, options, [callback]) { // we must output valid javascript, not valid json // see: http://timelessrepo.com/json-isnt-a-javascript-subset var js = json .encode(data) - .replaceAll(new RegExp(r'\u2028'), '\\u2028') - .replaceAll(new RegExp(r'\u2029'), '\\u2029'); + .replaceAll(RegExp(r'\u2028'), '\\u2028') + .replaceAll(RegExp(r'\u2029'), '\\u2029'); // prepare response - data = this.head + js + this.foot; + data = head + js + foot; super.doWrite(data, options, callback); } static Map parse(String query) { - var search = new RegExp('([^&=]+)=?([^&]*)'); - var result = new Map(); + var search = RegExp('([^&=]+)=?([^&]*)'); + var result = {}; // Get rid off the beginning ? in query strings. if (query.startsWith('?')) query = query.substring(1); // A custom decoder. - decode(String s) => Uri.decodeComponent(s.replaceAll('+', ' ')); + String decode(String s) => Uri.decodeComponent(s.replaceAll('+', ' ')); // Go through all the matches and build the result map. for (Match match in search.allMatches(query)) { diff --git a/lib/src/engine/transport/polling_transport.dart b/lib/src/engine/transport/polling_transport.dart index 4a64b66..f22c78f 100644 --- a/lib/src/engine/transport/polling_transport.dart +++ b/lib/src/engine/transport/polling_transport.dart @@ -1,15 +1,13 @@ -/** - * polling_transport.dart - * - * Purpose: - * - * Description: - * - * History: - * 22/02/2017, Created by jumperchen - * - * Copyright (C) 2017 Potix Corporation. All Rights Reserved. - */ +/// polling_transport.dart +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 22/02/2017, Created by jumperchen +/// +/// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'dart:async'; import 'dart:convert'; import 'dart:io'; @@ -25,44 +23,42 @@ class PollingTransport extends Transport { @override bool get supportsFraming => false; - static final Logger _logger = - new Logger('socket_io:transport.PollingTransport'); + static final Logger _logger = Logger('socket_io:transport.PollingTransport'); int closeTimeout; Function shouldClose; SocketConnect dataReq; PollingTransport(connect) : super(connect) { - this.closeTimeout = 30 * 1000; - this.maxHttpBufferSize = null; - this.httpCompression = null; - this.name = 'polling'; + closeTimeout = 30 * 1000; + maxHttpBufferSize = null; + httpCompression = null; + name = 'polling'; } - onRequest(SocketConnect connect) { + @override + void onRequest(SocketConnect connect) { var res = connect.response; if ('GET' == connect.request.method) { - this.onPollRequest(connect); + onPollRequest(connect); } else if ('POST' == connect.request.method) { - this.onDataRequest(connect); + onDataRequest(connect); } else { res.statusCode = 500; res.close(); } } - Map _reqCleanups = {}; - Map _reqCloses = {}; + final Map _reqCleanups = {}; + final Map _reqCloses = {}; - /** - * The client sends a request awaiting for us to send data. - * - * @api private - */ - onPollRequest(SocketConnect connect) { + /// The client sends a request awaiting for us to send data. + /// + /// @api private + void onPollRequest(SocketConnect connect) { if (this.connect != null) { _logger.fine('request overlap'); // assert: this.res, '.req and .res should be (un)set together' - this.onError('overlap from client'); + onError('overlap from client'); this.connect.response.statusCode = 500; this.connect.close(); return; @@ -73,7 +69,7 @@ class PollingTransport extends Transport { this.connect = connect; var onClose = () { - this.onError('poll connection closed prematurely'); + onError('poll connection closed prematurely'); }; var cleanup = () { @@ -84,27 +80,25 @@ class PollingTransport extends Transport { _reqCleanups[connect] = cleanup; _reqCloses[connect] = onClose; - this.writable = true; - this.emit('drain'); + writable = true; + emit('drain'); // if we're still writable but had a pending close, trigger an empty send - if (this.writable && this.shouldClose != null) { + if (writable && shouldClose != null) { _logger.fine('triggering empty send to append close packet'); - this.send([ + send([ {'type': 'noop'} ]); } } - /** - * The client sends a request with data. - * - * @api private - */ - onDataRequest(SocketConnect connect) { - if (this.dataReq != null) { + /// The client sends a request with data. + /// + /// @api private + void onDataRequest(SocketConnect connect) { + if (dataReq != null) { // assert: this.dataRes, '.dataReq and .dataRes should be (un)set together' - this.onError('data request overlap from client'); + onError('data request overlap from client'); connect.response.statusCode = 500; connect.close(); return; @@ -113,7 +107,7 @@ class PollingTransport extends Transport { var isBinary = 'application/octet-stream' == connect.request.headers.value('content-type'); - this.dataReq = connect; + dataReq = connect; dynamic chunks = isBinary ? [0] : ''; var self = this; @@ -133,9 +127,9 @@ class PollingTransport extends Transport { contentLength = utf8.encode(chunks).length; } else { if (chunks is String) { - chunks += new String.fromCharCodes(data); + chunks += String.fromCharCodes(data); } else { - chunks.addAll(new String.fromCharCodes(data) + chunks.addAll(String.fromCharCodes(data) .split(',') .map((s) => int.parse(s)) .toList()); @@ -154,7 +148,7 @@ class PollingTransport extends Transport { var headers = {'Content-Type': 'text/html', 'Content-Length': 2}; - HttpResponse res = connect.response; + var res = connect.response; res.statusCode = 200; @@ -176,13 +170,12 @@ class PollingTransport extends Transport { } } - /** - * Processes the incoming data payload. - * - * @param {String} encoded payload - * @api private - */ - onData(data) { + /// Processes the incoming data payload. + /// + /// @param {String} encoded payload + /// @api private + @override + void onData(data) { _logger.fine('received "$data"'); if (messageHandler != null) { messageHandler.handle(this, data); @@ -202,39 +195,37 @@ class PollingTransport extends Transport { } } - /** - * Overrides onClose. - * - * @api private - */ - onClose() { - if (this.writable == true) { + /// Overrides onClose. + /// + /// @api private + @override + void onClose() { + if (writable == true) { // close pending poll request - this.send([ + send([ {'type': 'noop'} ]); } super.onClose(); } - /** - * Writes a packet payload. - * - * @param {Object} packet - * @api private - */ - send(List packets) { - this.writable = false; + /// Writes a packet payload. + /// + /// @param {Object} packet + /// @api private + @override + void send(List packets) { + writable = false; - if (this.shouldClose != null) { + if (shouldClose != null) { _logger.fine('appending close packet to payload'); packets.add({'type': 'close'}); - this.shouldClose(); - this.shouldClose = null; + shouldClose(); + shouldClose = null; } var self = this; - PacketParser.encodePayload(packets, supportsBinary: this.supportsBinary, + PacketParser.encodePayload(packets, supportsBinary: supportsBinary, callback: (data) { var compress = packets.any((packet) { var opt = packet['options']; @@ -244,27 +235,23 @@ class PollingTransport extends Transport { }); } - /** - * Writes data as response to poll request. - * - * @param {String} data - * @param {Object} options - * @api private - */ - write(data, [options]) { + /// Writes data as response to poll request. + /// + /// @param {String} data + /// @param {Object} options + /// @api private + void write(data, [options]) { _logger.fine('writing "$data"'); - this.doWrite(data, options, () { - Function fn = _reqCleanups.remove(this.connect); + doWrite(data, options, () { + var fn = _reqCleanups.remove(connect); if (fn != null) fn(); }); } - /** - * Performs the write. - * - * @api private - */ - doWrite(data, options, [callback]) { + /// Performs the write. + /// + /// @api private + void doWrite(data, options, [callback]) { var self = this; // explicit UTF-8 is required for pages not served under utf @@ -272,16 +259,16 @@ class PollingTransport extends Transport { var contentType = isString ? 'text/plain; charset=UTF-8' : 'application/octet-stream'; - final Map headers = {'Content-Type': contentType}; + final headers = {'Content-Type': contentType}; var respond = (data) { headers[HttpHeaders.contentLengthHeader] = data is String ? utf8.encode(data).length : data.length; - HttpResponse res = self.connect.response; + var res = self.connect.response; res.statusCode = 200; res.headers.clear(); // remove all default headers. - this.headers(this.connect, headers).forEach((k, v) { + this.headers(connect, headers).forEach((k, v) { res.headers.set(k, v); }); try { @@ -292,31 +279,31 @@ class PollingTransport extends Transport { if (headers.containsKey(HttpHeaders.contentEncodingHeader)) { res.add(data); } else { - res.write(new String.fromCharCodes(data)); + res.write(String.fromCharCodes(data)); } connect.close(); } } catch (e) { - Function fn = _reqCloses.remove(connect); + var fn = _reqCloses.remove(connect); if (fn != null) fn(); rethrow; } callback(); }; - if (this.httpCompression == null || options['compress'] != true) { + if (httpCompression == null || options['compress'] != true) { respond(data); return; } var len = isString ? utf8.encode(data).length : data.length; - if (len < this.httpCompression['threshold']) { + if (len < httpCompression['threshold']) { respond(data); return; } var encodings = - this.connect.request.headers.value(HttpHeaders.acceptEncodingHeader); + connect.request.headers.value(HttpHeaders.acceptEncodingHeader); var hasGzip = encodings.contains('gzip'); if (!hasGzip && !encodings.contains('deflate')) { respond(data); @@ -333,25 +320,24 @@ class PollingTransport extends Transport { headers[HttpHeaders.contentEncodingHeader] = encoding; respond(hasGzip ? gzip.encode(utf8.encode( - data is List ? new String.fromCharCodes(data as List) : data)) + data is List ? String.fromCharCodes(data as List) : data)) : data); // }); } - /** - * Closes the transport. - * - * @api private - */ - doClose([fn()]) { + /// Closes the transport. + /// + /// @api private + @override + void doClose([dynamic Function() fn]) { _logger.fine('closing'); var self = this; Timer closeTimeoutTimer; - if (this.dataReq != null) { + if (dataReq != null) { _logger.fine('aborting ongoing data request'); - this.dataReq = null; + dataReq = null; } var onClose = () { @@ -359,42 +345,38 @@ class PollingTransport extends Transport { if (fn != null) fn(); self.onClose(); }; - if (this.writable == true) { + if (writable == true) { _logger.fine('transport writable - closing right away'); - this.send([ + send([ {'type': 'close'} ]); onClose(); - } else if (this.discarded) { + } else if (discarded) { _logger.fine('transport discarded - closing right away'); onClose(); } else { _logger.fine('transport not writable - buffering orderly close'); - this.shouldClose = onClose; - closeTimeoutTimer = - new Timer(new Duration(milliseconds: this.closeTimeout), onClose); + shouldClose = onClose; + closeTimeoutTimer = Timer(Duration(milliseconds: closeTimeout), onClose); } } - /** - * Returns headers for a response. - * - * @param {http.IncomingMessage} request - * @param {Object} extra headers - * @api private - */ - headers(SocketConnect connect, [Map headers]) { + /// Returns headers for a response. + /// + /// @param {http.IncomingMessage} request + /// @param {Object} extra headers + /// @api private + Map headers(SocketConnect connect, [Map headers]) { headers = headers ?? {}; // prevent XSS warnings on IE // https://github.com/LearnBoost/socket.io/pull/1333 var ua = connect.request.headers.value('user-agent'); - if (ua != null && - (ua.indexOf(';MSIE') >= 0 || ua.indexOf('Trident/') >= 0)) { + if (ua != null && (ua.contains(';MSIE') || ua.contains('Trident/'))) { headers['X-XSS-Protection'] = '0'; } - this.emit('headers', headers); + emit('headers', headers); return headers; } } diff --git a/lib/src/engine/transport/transports.dart b/lib/src/engine/transport/transports.dart index e44b02a..94dd89f 100644 --- a/lib/src/engine/transport/transports.dart +++ b/lib/src/engine/transport/transports.dart @@ -1,15 +1,13 @@ -/** - * transports.dart - * - * Purpose: - * - * Description: - * - * History: - * 17/02/2017, Created by jumperchen - * - * Copyright (C) 2017 Potix Corporation. All Rights Reserved. - */ +/// transports.dart +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 17/02/2017, Created by jumperchen +/// +/// 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'; @@ -20,29 +18,29 @@ import 'package:socket_io/src/util/event_emitter.dart'; class Transports { static List upgradesTo(String from) { - if ("polling" == from) { - return ["websocket"]; + if ('polling' == from) { + return ['websocket']; } return []; } static Transport newInstance(String name, SocketConnect connect) { if ('websocket' == name) { - return new WebSocketTransport(connect); + return WebSocketTransport(connect); } else if ('polling' == name) { if (connect.request.uri.queryParameters.containsKey('j')) { - return new JSONPTransport(connect); + return JSONPTransport(connect); } else { - return new XHRTransport(connect); + return XHRTransport(connect); } } else { - throw new UnsupportedError('Unknown transport $name'); + throw UnsupportedError('Unknown transport $name'); } } } abstract class Transport extends EventEmitter { - static Logger _logger = new Logger('socket_io:transport.Transport'); + static final Logger _logger = Logger('socket_io:transport.Transport'); double maxHttpBufferSize; Map httpCompression; Map perMessageDeflate; @@ -56,8 +54,8 @@ abstract class Transport extends EventEmitter { MessageHandler messageHandler; Transport(connect) { - this.readyState = 'open'; - this.discarded = false; + readyState = 'open'; + discarded = false; var options = connect.dataset['options']; if (options != null) { messageHandler = options.containsKey('messageHandlerFactory') @@ -67,45 +65,45 @@ abstract class Transport extends EventEmitter { } void discard() { - this.discarded = true; + discarded = true; } void onRequest(SocketConnect connect) { this.connect = connect; } - void close([closeFn()]) { - if ('closed' == this.readyState || 'closing' == this.readyState) return; - this.readyState = 'closing'; - this.doClose(closeFn); + void close([dynamic Function() closeFn]) { + if ('closed' == readyState || 'closing' == readyState) return; + readyState = 'closing'; + doClose(closeFn); } - void doClose([callback()]); + void doClose([dynamic Function() callback]); void onError(msg, [desc]) { - this.writable = false; - if (this.hasListeners('error')) { - this.emit('error', {'msg': msg, 'desc': desc, 'type': 'TransportError'}); + writable = false; + if (hasListeners('error')) { + emit('error', {'msg': msg, 'desc': desc, 'type': 'TransportError'}); } else { _logger.fine('ignored transport error $msg ($desc)'); } } void onPacket(Map packet) { - this.emit('packet', packet); + emit('packet', packet); } - onData(data) { + void onData(data) { if (messageHandler != null) { messageHandler.handle(this, data); } else { - this.onPacket(PacketParser.decodePacket(data)); + onPacket(PacketParser.decodePacket(data)); } } void onClose() { - this.readyState = 'closed'; - this.emit('close'); + readyState = 'closed'; + emit('close'); } void send(List data); diff --git a/lib/src/engine/transport/websocket_transport.dart b/lib/src/engine/transport/websocket_transport.dart index 474353d..b2945cc 100644 --- a/lib/src/engine/transport/websocket_transport.dart +++ b/lib/src/engine/transport/websocket_transport.dart @@ -1,33 +1,35 @@ import 'dart:async'; -/** - * websocket_transport.dart - * - * Purpose: - * - * Description: - * - * History: - * 22/02/2017, Created by jumperchen - * - * Copyright (C) 2017 Potix Corporation. All Rights Reserved. - */ +/// websocket_transport.dart +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 22/02/2017, Created by jumperchen +/// +/// 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'; class WebSocketTransport extends Transport { - static Logger _logger = new Logger('socket_io:transport.WebSocketTransport'); + static final Logger _logger = + Logger('socket_io:transport.WebSocketTransport'); + @override bool get handlesUpgrades => true; + @override bool get supportsFraming => true; StreamSubscription subscription; WebSocketTransport(connect) : super(connect) { - this.name = 'websocket'; + name = 'websocket'; this.connect = connect; - subscription = connect.websocket - .listen(this.onData, onError: this.onError, onDone: this.onClose); + subscription = + connect.websocket.listen(onData, onError: onError, onDone: onClose); writable = true; } + @override void send(List packets) { var send = (data, Map packet) { _logger.fine('writing "$data"'); @@ -46,7 +48,7 @@ class WebSocketTransport extends Transport { // } // this.writable = false; - this.connect.websocket.add(data); + connect.websocket.add(data); }; // function onEnd (err) { @@ -57,11 +59,11 @@ class WebSocketTransport extends Transport { for (var i = 0; i < packets.length; i++) { var packet = packets[i]; PacketParser.encodePacket(packet, - supportsBinary: this.supportsBinary, - callback: (_) => send(_, packet)); + supportsBinary: supportsBinary, callback: (_) => send(_, packet)); } } + @override void onClose() { super.onClose(); @@ -72,8 +74,9 @@ class WebSocketTransport extends Transport { } } + @override void doClose([fn]) { - this.connect.websocket.close(); + connect.websocket.close(); if (fn != null) fn(); } } diff --git a/lib/src/engine/transport/xhr_transport.dart b/lib/src/engine/transport/xhr_transport.dart index 7888ba2..af6710e 100644 --- a/lib/src/engine/transport/xhr_transport.dart +++ b/lib/src/engine/transport/xhr_transport.dart @@ -1,33 +1,29 @@ -/** - * xhr_transport.dart - * - * Purpose: - * - * Description: - * - * History: - * 22/02/2017, Created by jumperchen - * - * Copyright (C) 2017 Potix Corporation. All Rights Reserved. - */ -import 'dart:io'; +/// xhr_transport.dart +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 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'; class XHRTransport extends PollingTransport { XHRTransport(SocketConnect connect) : super(connect); - /** - * Overrides `onRequest` to handle `OPTIONS`.. - * - * @param {http.IncomingMessage} - * @api private - */ - onRequest(SocketConnect connect) { - HttpRequest req = connect.request; + /// Overrides `onRequest` to handle `OPTIONS`.. + /// + /// @param {http.IncomingMessage} + /// @api private + @override + void onRequest(SocketConnect connect) { + var req = connect.request; if ('OPTIONS' == req.method) { var res = req.response; - Map headers = this.headers(connect); + var headers = this.headers(connect); headers['Access-Control-Allow-Headers'] = 'Content-Type'; headers.forEach((key, value) { res.headers.set(key, value); @@ -40,14 +36,13 @@ class XHRTransport extends PollingTransport { } } - /** - * Returns headers for a response. - * - * @param {http.IncomingMessage} request - * @param {Object} extra headers - * @api private - */ - headers(SocketConnect connect, [Map headers]) { + /// Returns headers for a response. + /// + /// @param {http.IncomingMessage} request + /// @param {Object} extra headers + /// @api private + @override + Map headers(SocketConnect connect, [Map headers]) { headers = headers ?? {}; var req = connect.request; if (req.headers.value('origin') != null) { diff --git a/lib/src/namespace.dart b/lib/src/namespace.dart index 19fd449..6fbf91f 100644 --- a/lib/src/namespace.dart +++ b/lib/src/namespace.dart @@ -1,15 +1,13 @@ -/** - * namespace.dart - * - * Purpose: - * - * Description: - * - * History: - * 17/02/2017, Created by jumperchen - * - * Copyright (C) 2017 Potix Corporation. All Rights Reserved. - */ +/// namespace.dart +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 17/02/2017, Created by jumperchen +/// +/// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'dart:async'; import 'package:logging/logging.dart'; import 'package:socket_io/src/adapter/adapter.dart'; @@ -19,18 +17,14 @@ import 'package:socket_io/src/server.dart'; import 'package:socket_io/src/socket.dart'; import 'package:socket_io/src/util/event_emitter.dart'; -/** - * Blacklisted events. - */ +/// Blacklisted events. List events = [ 'connect', // for symmetry with client 'connection', 'newListener' ]; -/** - * Flags. - */ +/// Flags. List flags = ['json', 'volatile']; class Namespace extends EventEmitter { @@ -43,56 +37,48 @@ class Namespace extends EventEmitter { List rooms = []; Map flags = {}; Adapter adapter; - Logger _logger = new Logger('socket_io:Namespace'); - - /** - * Namespace constructor. - * - * @param {Server} server instance - * @param {Socket} name - * @api private - */ - Namespace(Server this.server, String this.name) { - this.initAdapter(); + final Logger _logger = Logger('socket_io:Namespace'); + + /// Namespace constructor. + /// + /// @param {Server} server instance + /// @param {Socket} name + /// @api private + Namespace(this.server, this.name) { + initAdapter(); } - /** - * Initializes the `Adapter` for this nsp. - * Run upon changing adapter by `Server#adapter` - * in addition to the constructor. - * - * @api private - */ - initAdapter() { - this.adapter = Adapter.newInstance(this.server.adapter, this); + /// Initializes the `Adapter` for this nsp. + /// Run upon changing adapter by `Server#adapter` + /// in addition to the constructor. + /// + /// @api private + void initAdapter() { + adapter = Adapter.newInstance(server.adapter, this); } - /** - * Sets up namespace middleware. - * - * @return {Namespace} self - * @api public - */ - use(fn) { - this.fns.add(fn); + /// Sets up namespace middleware. + /// + /// @return {Namespace} self + /// @api public + Namespace use(fn) { + fns.add(fn); return this; } - /** - * Executes the middleware for an incoming client. - * - * @param {Socket} socket that will get added - * @param {Function} last fn call in the middleware - * @api private - */ - run(socket, fn) { + /// Executes the middleware for an incoming client. + /// + /// @param {Socket} socket that will get added + /// @param {Function} last fn call in the middleware + /// @api private + void run(socket, fn) { var fns = this.fns.sublist(0); if (fns.isEmpty) return fn(null); run0(0, fns, socket, fn); } - static run0(idx, fns, socket, fn) { + static void run0(idx, fns, socket, fn) { return fns[idx](socket, (err) { // upon error, short-circuit if (err) return fn(err); @@ -105,41 +91,35 @@ class Namespace extends EventEmitter { }); } - /** - * Targets a room when emitting. - * - * @param {String} name - * @return {Namespace} self - * @api public - */ + /// Targets a room when emitting. + /// + /// @param {String} name + /// @return {Namespace} self + /// @api public // in(String name) { // to(name); // } - /** - * Targets a room when emitting. - * - * @param {String} name - * @return {Namespace} self - * @api public - */ - to(String name) { - rooms = this.rooms?.isNotEmpty == true ? this.rooms : []; - if (!rooms.contains(name)) this.rooms.add(name); + /// Targets a room when emitting. + /// + /// @param {String} name + /// @return {Namespace} self + /// @api public + Namespace to(String name) { + rooms = rooms?.isNotEmpty == true ? rooms : []; + if (!rooms.contains(name)) rooms.add(name); return this; } - /** - * Adds a new client. - * - * @return {Socket} - * @api private - */ - add(Client client, query, fn) { - _logger.fine('adding socket to nsp ${this.name}'); - var socket = new Socket(this, client, query); + /// Adds a new client. + /// + /// @return {Socket} + /// @api private + Socket add(Client client, query, fn) { + _logger.fine('adding socket to nsp ${name}'); + var socket = Socket(this, client, query); var self = this; - this.run(socket, (err) { + run(socket, (err) { // don't use Timer.run() here scheduleMicrotask(() { if ('open' == client.conn.readyState) { @@ -166,89 +146,76 @@ class Namespace extends EventEmitter { return socket; } - /** - * Removes a client. Called by each `Socket`. - * - * @api private - */ - remove(socket) { - if (this.sockets.contains(socket)) { - this.sockets.remove(socket); + /// Removes a client. Called by each `Socket`. + /// + /// @api private + void remove(socket) { + if (sockets.contains(socket)) { + sockets.remove(socket); } else { _logger.fine('ignoring remove for ${socket.id}'); } } - /** - * Emits to all clients. - * - * @return {Namespace} self - * @api public - */ - emit(ev, [dynamic arg]) { + /// Emits to all clients. + /// + /// @return {Namespace} self + /// @api public + @override + void emit(ev, [dynamic arg]) { if (events.contains(ev)) { super.emit(ev, arg); } else { // @todo check how to handle it with Dart // if (hasBin(args)) { parserType = ParserType.binaryEvent; } // binary - List data = arg == null ? [ev] : [ev, arg]; + var data = arg == null ? [ev] : [ev, arg]; - Map packet = {'type': EVENT, 'data': data}; + var packet = {'type': EVENT, 'data': data}; - this - .adapter - .broadcast(packet, {'rooms': this.rooms, 'flags': this.flags}); + adapter.broadcast(packet, {'rooms': rooms, 'flags': flags}); - this.rooms = null; - this.flags = null; + rooms = null; + flags = null; } } - /** - * Sends a `message` event to all clients. - * - * @return {Namespace} self - * @api public - */ - send([args]) { + /// Sends a `message` event to all clients. + /// + /// @return {Namespace} self + /// @api public + void send([args]) { write(args); } - write([args]) { - this.emit('message', args); + Namespace write([args]) { + emit('message', args); return this; } - /** - * Gets a list of clients. - * - * @return {Namespace} self - * @api public - */ - clients(fn([_])) { - this.adapter.clients(this.rooms, fn); - this.rooms = []; + /// Gets a list of clients. + /// + /// @return {Namespace} self + /// @api public + Namespace clients(dynamic Function([dynamic]) fn) { + adapter.clients(rooms, fn); + rooms = []; return this; } - /** - * Sets the compress flag. - * - * @param {Boolean} if `true`, compresses the sending data - * @return {Socket} self - * @api public - */ - compress(compress) { - this.flags = this.flags.isEmpty ? this.flags : {}; - this.flags['compress'] = compress; + /// Sets the compress flag. + /// + /// @param {Boolean} if `true`, compresses the sending data + /// @return {Socket} self + /// @api public + Namespace compress(compress) { + flags = flags.isEmpty ? flags : {}; + flags['compress'] = compress; return this; } } -/** - * Apply flags from `Socket`. - */ +/// Apply flags from `Socket`. // @todo //exports.flags.forEach(function(flag){ // Namespace.prototype.__defineGetter__(flag, function(){ diff --git a/lib/src/server.dart b/lib/src/server.dart index de7c42c..123eacf 100644 --- a/lib/src/server.dart +++ b/lib/src/server.dart @@ -1,15 +1,13 @@ -/** - * server.dart - * - * Purpose: - * - * Description: - * - * History: - * 22/02/2017, Created by jumperchen - * - * Copyright (C) 2017 Potix Corporation. All Rights Reserved. - */ +/// server.dart +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 22/02/2017, Created by jumperchen +/// +/// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'dart:io'; import 'package:logging/logging.dart'; import 'package:socket_io/src/client.dart'; @@ -18,20 +16,18 @@ import 'package:socket_io/src/namespace.dart'; import 'package:socket_io_common/src/parser/parser.dart'; import 'package:stream/stream.dart'; -/** - * Socket.IO client source. - */ -/** - * Old settings for backwards compatibility - */ +import 'namespace.dart'; + +/// Socket.IO client source. +/// Old settings for backwards compatibility Map oldSettings = { - "transports": "transports", - "heartbeat timeout": "pingTimeout", - "heartbeat interval": "pingInterval", - "destroy buffer size": "maxHttpBufferSize" + 'transports': 'transports', + 'heartbeat timeout': 'pingTimeout', + 'heartbeat interval': 'pingInterval', + 'destroy buffer size': 'maxHttpBufferSize' }; -final Logger _logger = new Logger('socket_io:Server'); +final Logger _logger = Logger('socket_io:Server'); class Server { // Namespaces @@ -45,111 +41,101 @@ class Server { Engine engine; Encoder encoder; - /** - * Server constructor. - * - * @param {http.Server|Number|Object} http server, port or options - * @param {Object} options - * @api public - */ - Server({server: null, Map options}) { + /// Server constructor. + /// + /// @param {http.Server|Number|Object} http server, port or options + /// @param {Object} options + /// @api public + Server({server, Map options}) { options = options ?? {}; - this.nsps = {}; - this.path(options.containsKey('path') ? options['path'] : '/socket.io'); - this.serveClient(false != options['serveClient']); - this.adapter = - options.containsKey('adapter') ? options['adapter'] : 'default'; - this.origins(options.containsKey('origins') ? options['origins'] : '*:*'); - this.encoder = new Encoder(); - this.sockets = this.of('/'); + nsps = {}; + path(options.containsKey('path') ? options['path'] : '/socket.io'); + serveClient(false != options['serveClient']); + adapter = options.containsKey('adapter') ? options['adapter'] : 'default'; + origins(options.containsKey('origins') ? options['origins'] : '*:*'); + encoder = Encoder(); + sockets = of('/'); if (server != null) { - this.attach(server, options); + attach(server, options); } } - /** - * Server request verification function, that checks for allowed origins - * - * @param {http.IncomingMessage} request - * @param {Function} callback to be called with the result: `fn(err, success)` - */ - checkRequest(HttpRequest req, [Function fn]) { - String origin = req.headers.value('origin') != null - ? req.headers.value('origin') - : req.headers.value('referer'); + /// Server request verification function, that checks for allowed origins + /// + /// @param {http.IncomingMessage} request + /// @param {Function} callback to be called with the result: `fn(err, success)` + void checkRequest(HttpRequest req, [Function fn]) { + var origin = req.headers.value('origin') ?? req.headers.value('referer'); // file:// URLs produce a null Origin which can't be authorized via echo-back if (origin == null || origin.isEmpty) { origin = '*'; } - if (!origin.isEmpty && this._origins is Function) { - return this._origins(origin, fn); + if (origin.isNotEmpty && _origins is Function) { + return _origins(origin, fn); } - if (this._origins.contains('*:*')) { + if (_origins.contains('*:*')) { return fn(null, true); } - if (!origin.isEmpty) { + if (origin.isNotEmpty) { try { - Uri parts = Uri.parse(origin); - int defaultPort = 'https:' == parts.scheme ? 443 : 80; - int port = parts.port != null ? parts.port : defaultPort; - bool ok = - this._origins.indexOf(parts.host + ':' + port.toString()) >= 0 || - this._origins.indexOf(parts.host + ':*') >= 0 || - this._origins.indexOf('*:' + port.toString()) >= 0; + var parts = Uri.parse(origin); + var defaultPort = 'https:' == parts.scheme ? 443 : 80; + var port = parts.port ?? defaultPort; + var ok = _origins.indexOf(parts.host + ':' + port.toString()) >= 0 || + _origins.indexOf(parts.host + ':*') >= 0 || + _origins.indexOf('*:' + port.toString()) >= 0; return fn(null, ok); - } catch (ex) {} + } catch (ex) { + print(ex); + } } fn(null, false); } - /** - * Sets/gets whether client code is being served. - * - * @param {Boolean} whether to serve client code - * @return {Server|Boolean} self when setting or value when getting - * @api public - */ - serveClient([bool v]) { + /// Sets/gets whether client code is being served. + /// + /// @param {Boolean} whether to serve client code + /// @return {Server|Boolean} self when setting or value when getting + /// @api public + dynamic serveClient([bool v]) { if (v == null) { - return this._serveClient; + return _serveClient; } - this._serveClient = v; + _serveClient = v; return this; } - /** - * Backwards compatiblity. - * - * @api public - */ - set(String key, [val]) { + /// Backwards compatiblity. + /// + /// @api public + Server set(String key, [val]) { if ('authorization' == key && val != null) { - this.use((socket, next) { + use((socket, next) { val(socket.request, (err, authorized) { if (err) { - return next(new Exception(err)); + return next(Exception(err)); } ; if (!authorized) { - return next(new Exception('Not authorized')); + return next(Exception('Not authorized')); } next(); }); }); } else if ('origins' == key && val != null) { - this.origins(val); + origins(val); } else if ('resource' == key) { - this.path(val); - } else if (oldSettings[key] && this.engine[oldSettings[key]]) { - this.engine[oldSettings[key]] = val; + path(val); + } else if (oldSettings[key] && engine[oldSettings[key]]) { + engine[oldSettings[key]] = val; } else { _logger.severe('Option $key is not valid. Please refer to the README.'); } @@ -157,97 +143,85 @@ class Server { return this; } - /** - * Sets the client serving path. - * - * @param {String} pathname - * @return {Server|String} self when setting or value when getting - * @api public - */ - path([String v]) { - if (v == null || v.isEmpty) return this._path; - this._path = v.replaceFirst(new RegExp(r'/\/$/'), ''); + /// Sets the client serving path. + /// + /// @param {String} pathname + /// @return {Server|String} self when setting or value when getting + /// @api public + dynamic path([String v]) { + if (v == null || v.isEmpty) return _path; + _path = v.replaceFirst(RegExp(r'/\/$/'), ''); return this; } - /** - * Sets the adapter for rooms. - * - * @param {Adapter} pathname - * @return {Server|Adapter} self when setting or value when getting - * @api public - */ - String get adapter => this._adapter; - - void set adapter(String v) { - this._adapter = v; + /// Sets the adapter for rooms. + /// + /// @param {Adapter} pathname + /// @return {Server|Adapter} self when setting or value when getting + /// @api public + String get adapter => _adapter; + + set adapter(String v) { + _adapter = v; if (nsps.isNotEmpty) { - this.nsps.forEach((dynamic i, Namespace nsp) { - this.nsps[i].initAdapter(); + nsps.forEach((dynamic i, Namespace nsp) { + nsps[i].initAdapter(); }); } } - /** - * Sets the allowed origins for requests. - * - * @param {String} origins - * @return {Server|Adapter} self when setting or value when getting - * @api public - */ + /// Sets the allowed origins for requests. + /// + /// @param {String} origins + /// @return {Server|Adapter} self when setting or value when getting + /// @api public - origins([String v]) { - if (v == null || v.isEmpty) return this._origins; + dynamic origins([String v]) { + if (v == null || v.isEmpty) return _origins; - this._origins = v; + _origins = v; return this; } - /** - * Attaches socket.io to a server or port. - * - * @param {http.Server|Number} server or port - * @param {Object} options passed to engine.io - * @return {Server} self - * @api public - */ - listen(srv, [Map opts]) { + /// Attaches socket.io to a server or port. + /// + /// @param {http.Server|Number} server or port + /// @param {Object} options passed to engine.io + /// @return {Server} self + /// @api public + void listen(srv, [Map opts]) { attach(srv, opts); } - /** - * Attaches socket.io to a server or port. - * - * @param {http.Server|Number} server or port - * @param {Object} options passed to engine.io - * @return {Server} self - * @api public - */ - attach(srv, [Map opts]) { + /// Attaches socket.io to a server or port. + /// + /// @param {http.Server|Number} server or port + /// @param {Object} options passed to engine.io + /// @return {Server} self + /// @api public + Server attach(srv, [Map opts]) { if (srv is Function) { - String msg = 'You are trying to attach socket.io to an express ' + + var msg = 'You are trying to attach socket.io to an express ' 'request handler function. Please pass a http.Server instance.'; - throw new Exception(msg); + throw Exception(msg); } // handle a port as a string if (srv is String && int.parse(srv.toString()).toString() == srv) { srv = int.parse(srv.toString()); } - if (opts == null) { - opts = {}; - } + opts ??= {}; // set engine.io path to `/socket.io` if (!opts.containsKey('path')) { - opts['path'] = this.path(); + opts['path'] = path(); } // set origins verification - opts['allowRequest'] = this.checkRequest; + opts['allowRequest'] = checkRequest; if (srv is num) { _logger.fine('creating http server and binding to $srv'); int port = srv; - StreamServer server = new StreamServer(); + var server = StreamServer(); server.start(port: port); // HttpServer.bind(InternetAddress.ANY_IP_V4, port).then(( // HttpServer server) { @@ -259,57 +233,55 @@ class Server { //// }); var connectPacket = {'type': CONNECT, 'nsp': '/'}; - this.encoder.encode(connectPacket, (encodedPacket) { + 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 - this.engine = Engine.attach(server, opts); + engine = Engine.attach(server, opts); // attach static file serving // if (self._serveClient) self.attachServe(srv); // Export http server - this.httpServer = server; + httpServer = server; // bind to engine events - this.bind(this.engine); + bind(engine); }); // }); } else { var connectPacket = {'type': CONNECT, 'nsp': '/'}; - this.encoder.encode(connectPacket, (encodedPacket) { + 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 - this.engine = Engine.attach(srv, opts); + engine = Engine.attach(srv, opts); // attach static file serving // if (self._serveClient) self.attachServe(srv); // Export http server - this.httpServer = srv; + httpServer = srv; // bind to engine events - this.bind(this.engine); + bind(engine); }); } return this; } - /** - * Attaches the static file serving. - * - * @param {Function|http.Server} http server - * @api private - * @todo Include better way to serve files - */ + /// Attaches the static file serving. + /// + /// @param {Function|http.Server} http server + /// @api private + /// @todo Include better way to serve files // attachServe(srv){ // _logger.fine()('attaching client serving req handler'); // var url = this._path + '/socket.io.js'; @@ -327,14 +299,12 @@ class Server { // }) // } - /** - * Handles a request serving `/socket.io.js` - * - * @param {http.Request} req - * @param {http.Response} res - * @api private - * @todo Include better way to serve files - */ + /// Handles a request serving `/socket.io.js` + /// + /// @param {http.Request} req + /// @param {http.Response} res + /// @api private + /// @todo Include better way to serve files // serve(req, res){ // var etag = req.headers['if-none-match']; @@ -354,83 +324,75 @@ class Server { // res.end(clientSource); // } - /** - * Binds socket.io to an engine.io instance. - * - * @param {engine.Server} engine.io (or compatible) server - * @return {Server} self - * @api public - */ - bind(engine) { + /// Binds socket.io to an engine.io instance. + /// + /// @param {engine.Server} engine.io (or compatible) server + /// @return {Server} self + /// @api public + Server bind(engine) { this.engine = engine; - this.engine.on('connection', this.onconnection); + this.engine.on('connection', onconnection); return this; } - /** - * Called with each incoming transport connection. - * - * @param {engine.Socket} socket - * @return {Server} self - * @api public - */ - onconnection(conn) { + /// Called with each incoming transport connection. + /// + /// @param {engine.Socket} socket + /// @return {Server} self + /// @api public + Server onconnection(conn) { _logger.fine('incoming connection with id ${conn.id}'); - Client client = new Client(this, conn); + var client = Client(this, conn); client.connect('/'); return this; } - /** - * Looks up a namespace. - * - * @param {String} nsp name - * @param {Function} optional, nsp `connection` ev handler - * @api public - */ + /// Looks up a namespace. + /// + /// @param {String} nsp name + /// @param {Function} optional, nsp `connection` ev handler + /// @api public - of(name, [fn]) { + Namespace of(name, [fn]) { if (name.toString()[0] != '/') { name = '/' + name; } - if (!this.nsps.containsKey(name)) { + if (!nsps.containsKey(name)) { _logger.fine('initializing namespace $name'); - Namespace nsp = new Namespace(this, name); - this.nsps[name] = nsp; + var nsp = Namespace(this, name); + nsps[name] = nsp; } - if (fn != null) this.nsps[name].on('connect', fn); - return this.nsps[name]; + if (fn != null) nsps[name].on('connect', fn); + return nsps[name]; } - /** - * Closes server connection - * - * @api public - */ - close() { - this.nsps['/'].sockets.forEach((socket) { + /// Closes server connection + /// + /// @api public + void close() { + nsps['/'].sockets.forEach((socket) { socket.onclose(); }); - this.engine.close(); + engine.close(); - if (this.httpServer != null) { - this.httpServer.stop(); + if (httpServer != null) { + httpServer.stop(); } } // redirect to sockets method - to(_) => sockets.to(_); - use(_) => sockets.use(_); - send(_) => sockets.send(_); - write(_) => sockets.write(_); - clients(_) => sockets.clients(_); - compress(_) => sockets.compress(_); + Namespace to(_) => sockets.to(_); + Namespace use(_) => sockets.use(_); + void send(_) => sockets.send(_); + Namespace write(_) => sockets.write(_); + Namespace clients(_) => sockets.clients(_); + Namespace compress(_) => sockets.compress(_); // emitter - emit(event, data) => sockets.emit(event, data); - on(event, handler) => sockets.on(event, handler); - once(event, handler) => sockets.once(event, handler); - off(event, handler) => sockets.off(event, handler); + void emit(event, data) => sockets.emit(event, data); + void on(event, handler) => sockets.on(event, handler); + void once(event, handler) => sockets.once(event, handler); + void off(event, handler) => sockets.off(event, handler); } diff --git a/lib/src/socket.dart b/lib/src/socket.dart index e0a66ca..14938f7 100644 --- a/lib/src/socket.dart +++ b/lib/src/socket.dart @@ -1,15 +1,13 @@ -/** - * socket.dart - * - * Purpose: - * - * Description: - * - * History: - * 22/02/2017, Created by jumperchen - * - * Copyright (C) 2017 Potix Corporation. All Rights Reserved. - */ +/// socket.dart +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 22/02/2017, Created by jumperchen +/// +/// 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'; @@ -17,17 +15,13 @@ 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'; -/** - * Module exports. - */ +/// Module exports. // //module.exports = exports = Socket; -/** - * Blacklisted events. - * - * @api public - */ +/// Blacklisted events. +/// +/// @api public List events = [ 'error', @@ -37,14 +31,12 @@ List events = [ 'removeListener' ]; -/** - * Flags. - * - * @api private - */ +/// Flags. +/// +/// @api private List flags = ['json', 'volatile', 'broadcast']; -const List EVENTS = const [ +const List EVENTS = [ 'error', 'connect', 'disconnect', @@ -74,57 +66,56 @@ class Socket extends EventEmitter { Map data = {}; Socket(this.nsp, this.client, query) { - this.server = nsp.server; - this.adapter = this.nsp.adapter; - this.id = client.id; - this.request = client.request; - this.conn = client.conn; - this.handshake = this.buildHandshake(query); + server = nsp.server; + adapter = nsp.adapter; + id = client.id; + request = client.request; + conn = client.conn; + handshake = buildHandshake(query); } - /** - * Builds the `handshake` BC object - * - * @api private - */ - buildHandshake(query) { + /// Builds the `handshake` BC object + /// + /// @api private + Map buildHandshake(query) { final buildQuery = () { - var requestQuery = this.request.uri.queryParameters; + var requestQuery = request.uri.queryParameters; //if socket-specific query exist, replace query strings in requestQuery return query != null - ? (new Map.from(query)..addAll(requestQuery)) + ? (Map.from(query)..addAll(requestQuery)) : requestQuery; }; return { - 'headers': this.request.headers, - 'time': new DateTime.now().toString(), - 'address': this.conn.remoteAddress, - 'xdomain': this.request.headers.value('origin') != null, + 'headers': request.headers, + 'time': DateTime.now().toString(), + 'address': conn.remoteAddress, + 'xdomain': request.headers.value('origin') != null, // TODO 'secure': ! !this.request.connectionInfo.encrypted, - 'issued': new DateTime.now().millisecondsSinceEpoch, - 'url': this.request.uri.path, + 'issued': DateTime.now().millisecondsSinceEpoch, + 'url': request.uri.path, 'query': buildQuery() }; } Socket get json { - this.flags = this.flags ?? {}; - this.flags['json'] = true; + flags = flags ?? {}; + flags['json'] = true; return this; } Socket get volatile { - this.flags = this.flags ?? {}; - this.flags['volatile'] = true; + flags = flags ?? {}; + flags['volatile'] = true; return this; } Socket get broadcast { - this.flags = this.flags ?? {}; - this.flags['broadcast'] = true; + flags = flags ?? {}; + flags['broadcast'] = true; return this; } + @override void emit(String event, [data]) { emitWithAck(event, data); } @@ -133,39 +124,37 @@ class Socket extends EventEmitter { emitWithAck(event, data, binary: true); } - /** - * Emits to this client. - * - * @return {Socket} self - * @api public - */ + /// Emits to this client. + /// + /// @return {Socket} self + /// @api public void emitWithAck(String event, dynamic data, {Function ack, bool binary = false}) { if (EVENTS.contains(event)) { super.emit(event, data); } else { var packet = {}; - List sendData = data == null ? [event] : [event, data]; + var sendData = data == null ? [event] : [event, data]; var flags = this.flags ?? {}; if (ack != null) { - if (this.roomList.isNotEmpty || flags['broadcast'] == true) { - throw new UnsupportedError( + if (roomList.isNotEmpty || flags['broadcast'] == true) { + throw UnsupportedError( 'Callbacks are not supported when broadcasting'); } - this.acks['${this.nsp.ids}'] = ack; - packet['id'] = '${this.nsp.ids++}'; + acks['${nsp.ids}'] = ack; + packet['id'] = '${nsp.ids++}'; } packet['type'] = binary ? BINARY_EVENT : EVENT; packet['data'] = sendData; - if (this.roomList.isNotEmpty || flags['broadcast'] == true) { - this.adapter.broadcast(packet, { - 'except': [this.id], - 'rooms': this.roomList, + if (roomList.isNotEmpty || flags['broadcast'] == true) { + adapter.broadcast(packet, { + 'except': [id], + 'rooms': roomList, 'flags': flags }); } else { @@ -175,172 +164,154 @@ class Socket extends EventEmitter { } // // reset flags - this.roomList = []; + roomList = []; this.flags = null; // } // return this; } } - /** - * Targets a room when broadcasting. - * - * @param {String} name - * @return {Socket} self - * @api public - */ - to(String name) { - if (!this.roomList.contains(name)) this.roomList.add(name); + /// Targets a room when broadcasting. + /// + /// @param {String} name + /// @return {Socket} self + /// @api public + Socket to(String name) { + if (!roomList.contains(name)) roomList.add(name); return this; } - /** - * Sends a `message` event. - * - * @return {Socket} self - * @api public - */ - send(_) { - this.write(_); + /// Sends a `message` event. + /// + /// @return {Socket} self + /// @api public + void send(_) { + write(_); } - write(List data) { - this.emit('message', data); + Socket write(List data) { + emit('message', data); return this; } - /** - * Writes a packet. - * - * @param {Object} packet object - * @param {Object} options - * @api private - */ - packet(packet, [opts]) { + /// Writes a packet. + /// + /// @param {Object} packet object + /// @param {Object} options + /// @api private + void packet(packet, [opts]) { // ignore preEncoded = true. if (packet is Map) { - packet['nsp'] = this.nsp.name; + packet['nsp'] = nsp.name; } opts = opts ?? {}; opts['compress'] = false != opts['compress']; - this.client.packet(packet, opts); + client.packet(packet, opts); } - /** - * Joins a room. - * - * @param {String} room - * @param {Function} optional, callback - * @return {Socket} self - * @api private - */ - join(room, [fn]) { + /// Joins a room. + /// + /// @param {String} room + /// @param {Function} optional, callback + /// @return {Socket} self + /// @api private + Socket join(room, [fn]) { // debug('joining room %s', room); - if (this.roomMap.containsKey(room)) { + if (roomMap.containsKey(room)) { if (fn != null) fn(null); return this; } - this.adapter.add(this.id, room, ([err]) { + adapter.add(id, room, ([err]) { if (err != null) return fn?.call(err); // _logger.info('joined room %s', room); - this.roomMap[room] = room; + roomMap[room] = room; if (fn != null) fn(null); }); return this; } - /** - * Leaves a room. - * - * @param {String} room - * @param {Function} optional, callback - * @return {Socket} self - * @api private - */ - leave(room, fn) { + /// Leaves a room. + /// + /// @param {String} room + /// @param {Function} optional, callback + /// @return {Socket} self + /// @api private + Socket leave(room, fn) { // debug('leave room %s', room); - this.adapter.del(this.id, room, ([err]) { + adapter.del(id, room, ([err]) { if (err != null) return fn?.call(err); // _logger.info('left room %s', room); - this.roomMap.remove(room); + roomMap.remove(room); fn?.call(null); }); return this; } - /** - * Leave all rooms. - * - * @api private - */ + /// Leave all rooms. + /// + /// @api private - leaveAll() { - this.adapter.delAll(this.id); - this.roomMap = {}; + void leaveAll() { + adapter.delAll(id); + roomMap = {}; } - /** - * Called by `Namespace` upon succesful - * middleware execution (ie: authorization). - * - * @api private - */ + /// Called by `Namespace` upon succesful + /// middleware execution (ie: authorization). + /// + /// @api private - onconnect() { + void onconnect() { // debug('socket connected - writing packet'); - this.nsp.connected[this.id] = this; - this.join(this.id); - this.packet({'type': CONNECT}); + nsp.connected[id] = this; + join(id); + packet({'type': CONNECT}); } - /** - * Called with each packet. Called by `Client`. - * - * @param {Object} packet - * @api private - */ + /// Called with each packet. Called by `Client`. + /// + /// @param {Object} packet + /// @api private - onpacket(packet) { + void onpacket(packet) { // debug('got packet %j', packet); switch (packet['type']) { case EVENT: - this.onevent(packet); + onevent(packet); break; case BINARY_EVENT: - this.onevent(packet); + onevent(packet); break; case ACK: - this.onack(packet); + onack(packet); break; case BINARY_ACK: - this.onack(packet); + onack(packet); break; case DISCONNECT: - this.ondisconnect(); + ondisconnect(); break; case ERROR: - this.emit('error', packet['data']); + emit('error', packet['data']); } } - /** - * Called upon event packet. - * - * @param {Object} packet object - * @api private - */ - onevent(packet) { + /// Called upon event packet. + /// + /// @param {Object} packet object + /// @api private + void onevent(packet) { List args = packet['data'] ?? []; // debug('emitting event %j', args); if (null != packet['id']) { // debug('attaching ack callback to event'); - args.add(this.ack(packet['id'])); + args.add(ack(packet['id'])); } // dart doesn't support "String... rest" syntax. @@ -351,12 +322,10 @@ class Socket extends EventEmitter { } } - /** - * Produces an ack callback to emit with an event. - * - * @param {Number} packet id - * @api private - */ + /// Produces an ack callback to emit with an event. + /// + /// @param {Number} packet id + /// @api private Function ack(id) { var sent = false; return (_) { @@ -375,13 +344,11 @@ class Socket extends EventEmitter { }; } - /** - * Called upon ack packet. - * - * @api private - */ - onack(packet) { - Function ack = this.acks.remove(packet['id']); + /// Called upon ack packet. + /// + /// @api private + void onack(packet) { + Function ack = acks.remove(packet['id']); if (ack is Function) { // debug('calling ack %s with %j', packet.id, packet.data); Function.apply(ack, packet['data']); @@ -390,89 +357,77 @@ class Socket extends EventEmitter { } } - /** - * Called upon client disconnect packet. - * - * @api private - */ - ondisconnect() { + /// Called upon client disconnect packet. + /// + /// @api private + void ondisconnect() { // debug('got disconnect packet'); - this.onclose('client namespace disconnect'); + onclose('client namespace disconnect'); } - /** - * Handles a client error. - * - * @api private - */ - onerror(err) { - if (this.hasListeners('error')) { - this.emit('error', err); + /// Handles a client error. + /// + /// @api private + void onerror(err) { + if (hasListeners('error')) { + emit('error', err); } else { // console.error('Missing error handler on `socket`.'); // console.error(err.stack); } } - /** - * Called upon closing. Called by `Client`. - * - * @param {String} reason - * @param {Error} optional error object - * @api private - */ - onclose([reason]) { - if (!this.connected) return this; + /// Called upon closing. Called by `Client`. + /// + /// @param {String} reason + /// @param {Error} optional error object + /// @api private + dynamic onclose([reason]) { + if (!connected) return this; // debug('closing socket - reason %s', reason); - this.emit('disconnecting', reason); - this.leaveAll(); - this.nsp.remove(this); - this.client.remove(this); - this.connected = false; - this.disconnected = true; - this.nsp.connected.remove(this.id); - this.emit('disconnect', reason); + emit('disconnecting', reason); + leaveAll(); + nsp.remove(this); + client.remove(this); + connected = false; + disconnected = true; + nsp.connected.remove(id); + emit('disconnect', reason); } - /** - * Produces an `error` packet. - * - * @param {Object} error object - * @api private - */ - error(err) { - this.packet({'type': ERROR, 'data': err}); + /// Produces an `error` packet. + /// + /// @param {Object} error object + /// @api private + void error(err) { + packet({'type': ERROR, 'data': err}); } - /** - * Disconnects this client. - * - * @param {Boolean} if `true`, closes the underlying connection - * @return {Socket} self - * @api public - */ + /// Disconnects this client. + /// + /// @param {Boolean} if `true`, closes the underlying connection + /// @return {Socket} self + /// @api public - disconnect([close]) { - if (!this.connected) return this; + Socket disconnect([close]) { + if (!connected) return this; if (close == true) { - this.client.disconnect(); + client.disconnect(); } else { - this.packet({'type': DISCONNECT}); - this.onclose('server namespace disconnect'); + packet({'type': DISCONNECT}); + onclose('server namespace disconnect'); } return this; } - /** - * Sets the compress flag. - * - * @param {Boolean} if `true`, compresses the sending data - * @return {Socket} self - * @api public - */ - compress(compress) { - this.flags = this.flags ?? {}; - this.flags['compress'] = compress; + /// Sets the compress flag. + /// + /// @param {Boolean} if `true`, compresses the sending data + /// @return {Socket} self + /// @api public + Socket compress(compress) { + flags = flags ?? {}; + flags['compress'] = compress; return this; } } diff --git a/lib/src/util/event_emitter.dart b/lib/src/util/event_emitter.dart index bb952b0..90a1b26 100644 --- a/lib/src/util/event_emitter.dart +++ b/lib/src/util/event_emitter.dart @@ -1,112 +1,88 @@ -/** - * event_emitter.dart - * - * Purpose: - * - * Description: - * - * History: - * 11/23/2016, Created by Henri Chen - * - * Copyright (C) 2016 Potix Corporation. All Rights Reserved. - */ +/// event_emitter.dart +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 11/23/2016, Created by Henri Chen +/// +/// Copyright (C) 2016 Potix Corporation. All Rights Reserved. import 'dart:collection' show HashMap; -/** - * Handler type for handling the event emitted by an [EventEmitter]. - */ -typedef dynamic EventHandler(T data); +/// Handler type for handling the event emitted by an [EventEmitter]. +typedef EventHandler = dynamic Function(T data); -/** - * Generic event emitting and handling. - */ +/// Generic event emitting and handling. class EventEmitter { - /** - * Mapping of events to a list of event handlers - */ + /// Mapping of events to a list of event handlers Map> _events; - /** - * Mapping of events to a list of one-time event handlers - */ + /// Mapping of events to a list of one-time event handlers Map> _eventsOnce; - /** - * Constructor - */ + /// Constructor EventEmitter() { - this._events = new HashMap>(); - this._eventsOnce = new HashMap>(); + _events = HashMap>(); + _eventsOnce = HashMap>(); } - /** - * This function triggers all the handlers currently listening - * to [event] and passes them [data]. - */ + /// This function triggers all the handlers currently listening + /// to [event] and passes them [data]. void emit(String event, [dynamic data]) { - final list0 = this._events[event]; + 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 ? new List.from(list0) : null; + final list = list0 != null ? List.from(list0) : null; list?.forEach((handler) { handler(data); }); - this._eventsOnce.remove(event)?.forEach((EventHandler handler) { + _eventsOnce.remove(event)?.forEach((EventHandler handler) { handler(data); }); } - /** - * This function binds the [handler] as a listener to the [event] - */ + /// This function binds the [handler] as a listener to the [event] void on(String event, EventHandler handler) { - this._events.putIfAbsent(event, () => new List()); - this._events[event].add(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. - */ + /// 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) { - this._eventsOnce.putIfAbsent(event, () => new List()); - this._eventsOnce[event].add(handler); + _eventsOnce.putIfAbsent(event, () => []); + _eventsOnce[event].add(handler); } - /** - * This function attempts to unbind the [handler] from the [event] - */ + /// This function attempts to unbind the [handler] from the [event] void off(String event, [EventHandler handler]) { if (handler != null) { - this._events[event]?.remove(handler); - this._eventsOnce[event]?.remove(handler); - if (this._events[event]?.isEmpty == true) { - this._events.remove(event); + _events[event]?.remove(handler); + _eventsOnce[event]?.remove(handler); + if (_events[event]?.isEmpty == true) { + _events.remove(event); } - if (this._eventsOnce[event]?.isEmpty == true) { - this._eventsOnce.remove(event); + if (_eventsOnce[event]?.isEmpty == true) { + _eventsOnce.remove(event); } } else { - this._events.remove(event); - this._eventsOnce.remove(event); + _events.remove(event); + _eventsOnce.remove(event); } } - /** - * This function unbinds all the handlers for all the events. - */ + /// This function unbinds all the handlers for all the events. void clearListeners() { - this._events = new HashMap>(); - this._eventsOnce = new HashMap>(); + _events = HashMap>(); + _eventsOnce = HashMap>(); } - /** - * Returns whether the event has registered. - */ + /// Returns whether the event has registered. bool hasListeners(String event) { - return this._events[event]?.isNotEmpty == true || - this._eventsOnce[event]?.isNotEmpty == true; + return _events[event]?.isNotEmpty == true || + _eventsOnce[event]?.isNotEmpty == true; } } diff --git a/pubspec.yaml b/pubspec.yaml index ccc9e0c..b7ea0e9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,15 +1,19 @@ name: socket_io -description: Dartlang port of socket.io -version: 0.9.0+2 -author: jumperchen +description: > + Port of JS/Node library Socket.io. It enables real-time, bidirectional and + event-based communication cross-platform. +version: 0.9.0+3 +# author: jumperchen homepage: https://www.zkoss.org environment: sdk: '>=2.0.0 <3.0.0' dependencies: - stream: any - socket_io_common: any - uuid: any + stream: ^2.2.1+1 + socket_io_common: ^0.9.0+5 + uuid: ^2.0.4 + logging: ^0.11.3+2 dev_dependencies: test: ">=1.3.0 <2.0.0" + pedantic: ^1.0.0 diff --git a/test/socket.test.dart b/test/socket.test.dart index a13e920..9fb3c6f 100644 --- a/test/socket.test.dart +++ b/test/socket.test.dart @@ -1,36 +1,34 @@ -/** - * socket.test.dart - * - * Purpose: - * - * Description: - * - * History: - * 16/02/2017, Created by jumperchen - * - * Copyright (C) 2017 Potix Corporation. All Rights Reserved. - */ +/// socket.test.dart +/// +/// Purpose: +/// +/// Description: +/// +/// History: +/// 16/02/2017, Created by jumperchen +/// +/// Copyright (C) 2017 Potix Corporation. All Rights Reserved. import 'package:test/test.dart'; import 'package:socket_io/socket_io.dart'; -main() { +void main() { group('Socket IO', () { test('Start standalone server', () async { - var io = new Server(); + var io = 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.emit('fromServer', 'ok 2'); }); }); io.on('connection', (client) { print('connection default namespace'); client.on('msg', (data) { print('data from default => $data'); - client.emit('fromServer', "ok"); + client.emit('fromServer', 'ok'); }); }); io.listen(3000);