163 lines
4.2 KiB
Dart
163 lines
4.2 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
|
import '../core/cache_manager.dart';
|
|
import '../core/constants/api_url.dart';
|
|
|
|
class WebSocketService extends ChangeNotifier {
|
|
static final WebSocketService _instance = WebSocketService._internal();
|
|
factory WebSocketService() => _instance;
|
|
WebSocketService._internal();
|
|
|
|
WebSocketChannel? _channel;
|
|
Timer? _reconnectTimer;
|
|
bool _isConnected = false;
|
|
bool _disposed = false;
|
|
|
|
final _eventController = StreamController<Map<String, dynamic>>.broadcast();
|
|
Stream<Map<String, dynamic>> get eventStream => _eventController.stream;
|
|
|
|
bool get isConnected => _isConnected;
|
|
|
|
Future<void> connect() async {
|
|
if (_isConnected || _disposed) return;
|
|
|
|
try {
|
|
final baseUrl = ApiUrl.baseUrl;
|
|
final token = CacheManager.authBox.get('auth_token') ?? '';
|
|
|
|
final wsUrl = _buildWsUrl(baseUrl);
|
|
if (wsUrl == null) return;
|
|
|
|
final uri = Uri.parse('$wsUrl/app/mpg-hris-key');
|
|
|
|
_channel = WebSocketChannel.connect(uri);
|
|
await _channel!.ready;
|
|
|
|
_isConnected = true;
|
|
_reconnectTimer?.cancel();
|
|
notifyListeners();
|
|
|
|
debugPrint('[WS] Terhubung ke $uri');
|
|
|
|
_channel!.stream.listen(
|
|
(message) => _handleMessage(message, token),
|
|
onDone: () {
|
|
debugPrint('[WS] Koneksi terputus');
|
|
_isConnected = false;
|
|
notifyListeners();
|
|
_scheduleReconnect();
|
|
},
|
|
onError: (error) {
|
|
debugPrint('[WS] Error: $error');
|
|
_isConnected = false;
|
|
notifyListeners();
|
|
_scheduleReconnect();
|
|
},
|
|
);
|
|
|
|
_subscribeToChannels(token);
|
|
} catch (e) {
|
|
debugPrint('[WS] Gagal connect: $e');
|
|
_isConnected = false;
|
|
_scheduleReconnect();
|
|
}
|
|
}
|
|
|
|
String? _buildWsUrl(String apiBaseUrl) {
|
|
try {
|
|
final uri = Uri.parse(apiBaseUrl);
|
|
final scheme = uri.scheme == 'https' ? 'wss' : 'ws';
|
|
|
|
if (uri.host.contains('trycloudflare.com')) {
|
|
return '$scheme://${uri.host}';
|
|
}
|
|
|
|
return '$scheme://${uri.host}:8080';
|
|
} catch (e) {
|
|
debugPrint('[WS] URL invalid: $e');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
void _subscribeToChannels(String token) {
|
|
final userId = CacheManager.authBox.get('user_id');
|
|
if (userId == null) return;
|
|
|
|
_sendSubscribe('private-user.$userId', token);
|
|
_sendSubscribe('pengumuman', null);
|
|
}
|
|
|
|
void _sendSubscribe(String channel, String? token) {
|
|
final data = {
|
|
'event': 'pusher:subscribe',
|
|
'data': {
|
|
'channel': channel,
|
|
if (token != null) 'auth': token,
|
|
},
|
|
};
|
|
_channel?.sink.add(jsonEncode(data));
|
|
debugPrint('[WS] Subscribe: $channel');
|
|
}
|
|
|
|
void _handleMessage(dynamic rawMessage, String token) {
|
|
try {
|
|
final message = jsonDecode(rawMessage as String) as Map<String, dynamic>;
|
|
final event = message['event'] as String?;
|
|
|
|
if (event == 'pusher:connection_established') {
|
|
debugPrint('[WS] Connection established');
|
|
_subscribeToChannels(token);
|
|
return;
|
|
}
|
|
|
|
if (event == 'pusher_internal:subscription_succeeded') {
|
|
debugPrint('[WS] Subscription berhasil: ${message['channel']}');
|
|
return;
|
|
}
|
|
|
|
if (event != null && !event.startsWith('pusher')) {
|
|
final dataRaw = message['data'];
|
|
final data = dataRaw is String ? jsonDecode(dataRaw) : dataRaw;
|
|
|
|
_eventController.add({
|
|
'event': event,
|
|
'channel': message['channel'],
|
|
'data': data,
|
|
});
|
|
|
|
debugPrint('[WS] Event diterima: $event');
|
|
}
|
|
} catch (e) {
|
|
debugPrint('[WS] Parse error: $e');
|
|
}
|
|
}
|
|
|
|
void _scheduleReconnect() {
|
|
_reconnectTimer?.cancel();
|
|
if (_disposed) return;
|
|
_reconnectTimer = Timer(const Duration(seconds: 5), () {
|
|
debugPrint('[WS] Reconnecting...');
|
|
connect();
|
|
});
|
|
}
|
|
|
|
void disconnect() {
|
|
_reconnectTimer?.cancel();
|
|
_channel?.sink.close();
|
|
_channel = null;
|
|
_isConnected = false;
|
|
notifyListeners();
|
|
debugPrint('[WS] Disconnected');
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_disposed = true;
|
|
disconnect();
|
|
_eventController.close();
|
|
super.dispose();
|
|
}
|
|
}
|