flutter-laravel-echo-client/lib/src/client/laravel_echo_socket_client.dart
2025-10-07 17:00:17 +05:00

139 lines
4.4 KiB
Dart

import 'dart:async';
import 'package:flutter_laravel_echo_client/src/api/socket_channel.dart';
import 'package:flutter_laravel_echo_client/src/channels/base_channel.dart';
import 'package:flutter_laravel_echo_client/src/channels/presence_channel.dart';
import 'package:flutter_laravel_echo_client/src/channels/private_channel.dart';
import 'package:flutter_laravel_echo_client/src/channels/public_channel.dart';
import 'package:flutter_laravel_echo_client/src/config/socket_config.dart';
import 'package:flutter_laravel_echo_client/src/core/auth.dart';
import 'package:flutter_laravel_echo_client/src/core/channel_registry.dart';
import 'package:socket_io_client/socket_io_client.dart' as io;
enum _ChannelType { public, private, presence }
class LaravelEchoSocketClient {
LaravelEchoSocketClient(SocketConfig config) : _config = config.copyWith() {
_auth = _config.authHeadersProvider ?? bearerProvider(_config.token);
}
SocketConfig _config;
late AuthHeadersProvider _auth;
io.Socket? _socket;
final _onConnected = StreamController<void>.broadcast();
final _onDisconnected = StreamController<void>.broadcast();
final _onError = StreamController<Object>.broadcast();
final _onReconnectAttempt = StreamController<int>.broadcast();
final _channels = ChannelRegistry<BaseChannel>();
bool get isConnected => _socket?.connected ?? false;
Stream<void> get onConnected => _onConnected.stream;
Stream<void> get onDisconnected => _onDisconnected.stream;
Stream<Object> get onError => _onError.stream;
Stream<int> get onReconnectAttempt => _onReconnectAttempt.stream;
Future<void> connect() async {
if (_socket?.connected ?? false) return;
final opts =
io.OptionBuilder()
.setTransports(_config.transports)
.setPath(_config.path)
.setQuery(_config.query ?? {})
.setReconnectionDelay(_config.reconnectionDelay.inMilliseconds)
.setReconnectionDelayMax(_config.reconnectionDelayMax.inMilliseconds)
.setTimeout(_config.ackTimeout.inMilliseconds)
.build();
_socket = io.io(_config.host.toString(), opts);
_socket!
..onConnect((_) {
_onConnected.add(null);
for (final ch in _channels.values) {
ch
..bind(_socket!)
..resubscribe();
}
})
..onDisconnect((_) => _onDisconnected.add(null))
..onConnectError((e) => _onError.add(e))
..onError((e) => _onError.add(e))
..onReconnectAttempt((n) => _onReconnectAttempt.add(n is int ? n : 0));
}
Future<void> disconnect({bool force = false}) async {
for (final channel in _channels.values) {
await channel.unsubscribe();
}
_channels.clear();
final socket = _socket;
_socket = null;
if (socket != null) {
socket
..off('connect')
..off('disconnect')
..off('connect_error')
..off('error')
..off('reconnect_attempt')
..disconnect();
if (force) socket.dispose();
}
}
void updateAuthToken(String token) {
_config = _config.copyWith(token: token);
_auth = _config.authHeadersProvider ?? bearerProvider(token);
final s = _socket;
if (s != null) {
s.io
..disconnect()
..connect();
}
}
SocketChannel channel(String name) => _getOrCreate(name, _ChannelType.public);
SocketPrivateChannel private(String name) => _getOrCreate(name, _ChannelType.private) as SocketPrivateChannel;
SocketPresenceChannel presence(String name) => _getOrCreate(name, _ChannelType.presence) as SocketPresenceChannel;
void leave(String name) {
for (final t in _ChannelType.values) {
leaveChannel(_fullName(name, t));
}
}
void leaveChannel(String fullName) {
final ch = _channels.remove(fullName);
ch?.unsubscribe();
}
BaseChannel _getOrCreate(String name, _ChannelType type) {
final full = _fullName(name, type);
return _channels.putIfAbsent(full, () {
final ch = switch (type) {
_ChannelType.public => PublicChannel(full, _socket, _auth),
_ChannelType.private => PrivateChannel(full, _socket, _auth),
_ChannelType.presence => PresenceChannel(full, _socket, _auth),
}..subscribe();
return ch;
});
}
String _fullName(String name, _ChannelType type) => switch (type) {
_ChannelType.public => name,
_ChannelType.private => 'private-$name',
_ChannelType.presence => 'presence-$name',
};
}