Files
flutter-frame/lib/core/network/socket_manager.dart
2026-03-01 07:55:59 +09:00

110 lines
3.1 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:talker/talker.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import '../constants/api_constants.dart';
import '../constants/app_constants.dart';
import '../logging/app_logger.dart';
import '../storage/secure_storage.dart';
part 'socket_manager.g.dart';
@Riverpod(keepAlive: true)
SocketManager socketManager(Ref ref) {
final talker = ref.read(appLoggerProvider);
final secureStorage = ref.read(secureStorageProvider);
final manager = SocketManager(talker: talker, secureStorage: secureStorage);
ref.onDispose(manager.dispose);
return manager;
}
class SocketManager {
SocketManager({
required this.talker,
required this.secureStorage,
});
final Talker talker;
final SecureStorage secureStorage;
WebSocketChannel? _channel;
Timer? _reconnectTimer;
final _messageController = StreamController<Map<String, dynamic>>.broadcast();
bool _isConnected = false;
int _reconnectAttempts = 0;
static const int _maxReconnectAttempts = 5;
Stream<Map<String, dynamic>> get messageStream => _messageController.stream;
bool get isConnected => _isConnected;
Future<void> connect({String? path}) async {
try {
final token = await secureStorage.read(key: AppConstants.accessTokenKey);
final wsUrl = '${ApiConstants.wsUrl}${path ?? ''}';
final uri = Uri.parse(
token != null ? '$wsUrl?token=$token' : wsUrl,
);
_channel = WebSocketChannel.connect(uri);
await _channel!.ready;
_isConnected = true;
_reconnectAttempts = 0;
talker.info('WebSocket connected: $wsUrl');
_channel!.stream.listen(
(data) {
try {
final decoded = jsonDecode(data as String) as Map<String, dynamic>;
_messageController.add(decoded);
} catch (e) {
talker.error('WebSocket message parse error', e);
}
},
onError: (Object error) {
talker.error('WebSocket error', error);
_handleDisconnect();
},
onDone: () {
talker.warning('WebSocket disconnected');
_handleDisconnect();
},
);
} catch (e) {
talker.error('WebSocket connection failed', e);
_handleDisconnect();
}
}
void _handleDisconnect() {
_isConnected = false;
if (_reconnectAttempts < _maxReconnectAttempts) {
final delay = Duration(seconds: _reconnectAttempts * 2 + 1);
_reconnectTimer = Timer(delay, () {
_reconnectAttempts++;
talker.info('WebSocket reconnect attempt $_reconnectAttempts');
connect();
});
}
}
void send(Map<String, dynamic> data) {
if (_isConnected && _channel != null) {
_channel!.sink.add(jsonEncode(data));
} else {
talker.warning('WebSocket not connected, message not sent');
}
}
void dispose() {
_reconnectTimer?.cancel();
_channel?.sink.close();
_messageController.close();
_isConnected = false;
}
}