···1616import 'package:sparksocial/src/core/auth/data/repositories/onboarding_repository_impl.dart';
1717import 'package:sparksocial/src/core/network/atproto/data/repositories/actor_repository_impl.dart';
1818import 'package:sparksocial/src/core/network/atproto/data/repositories/graph_repository_impl.dart';
1919-import 'package:sparksocial/src/core/network/messages/data/services/chat_socket_service.dart';
2020-import 'package:sparksocial/src/core/network/messages/data/services/chat_api_service.dart';
2121-import 'package:sparksocial/src/core/network/messages/data/repositories/chat_repository.dart';
2222-import 'package:sparksocial/src/core/network/messages/data/repositories/chat_repository_impl.dart';
1919+import 'package:sparksocial/src/core/network/chat/data/repositories/chat_repository.dart';
2020+import 'package:sparksocial/src/core/network/chat/data/repositories/chat_repository_impl.dart';
23212422// This is the ONLY PLACE IN THE ENTIRE APP where implementations are imported
2523// All the other files should import interfaces only (polymorphism) to keep everything decoupled
···5654 sl.registerSingleton<AuthRepository>(AuthRepositoryImpl());
57555856 // Register Chat dependencies
5959- sl.registerLazySingleton<ChatSocketService>(() => ChatSocketService());
6060- sl.registerLazySingleton<ChatApiService>(() => ChatApiService());
6157 sl.registerSingleton<ChatRepository>(ChatRepositoryImpl());
62586359 // Register SprkRepository with its interface
···11+import 'package:sparksocial/src/core/network/chat/data/models/models.dart';
22+import 'package:web_socket_channel/web_socket_channel.dart';
33+44+/// Interface for Spark Chat API endpoints
55+abstract class ChatRepository {
66+ /// Send a message to another user
77+ ///
88+ /// [message] The message content to send
99+ /// [receiverDid] The DID of the user to send the message to
1010+ Future<SendMessageResponse> sendMessage(String message, String receiverDid);
1111+1212+ /// Get messages for a conversation with another user
1313+ ///
1414+ /// [otherDid] The DID of the other user in the conversation
1515+ /// [limit] Maximum number of messages to retrieve (default 50)
1616+ Future<GetMessagesResponse> getMessages(String otherDid, {int limit = 50});
1717+1818+ /// Get list of all chats (conversations) for the current user
1919+ Future<GetChatsResponse> getChats();
2020+2121+ /// Connect to WebSocket for real-time messaging
2222+ ///
2323+ /// Returns a WebSocketChannel for receiving real-time messages
2424+ WebSocketChannel connectWebSocket();
2525+2626+ /// Check the health status of the chat service
2727+ Future<HealthCheckResponse> healthCheck();
2828+2929+ /// Set the JWT token for authentication
3030+ ///
3131+ /// [token] The JWT token from ATProtocol authentication
3232+ void setAuthToken(String token);
3333+3434+ /// Close any open WebSocket connections
3535+ void closeWebSocket();
3636+}
···11-import 'dart:async';
22-import 'package:sparksocial/src/core/network/messages/data/models/message.dart';
33-44-abstract class ChatRepository {
55- /// Stream of conversations updates
66- Stream<List<Conversation>> get conversationsStream;
77-88- /// Stream of messages updates
99- Stream<List<ChatMessage>> get messagesStream;
1010-1111- /// Initialize the chat repository
1212- Future<void> initialize();
1313-1414- /// Get all conversations for the current user
1515- Future<List<Conversation>> getConversations();
1616-1717- /// Get messages for a specific conversation
1818- Future<List<ChatMessage>> getMessages(String conversationId);
1919-2020- /// Get a specific conversation by ID
2121- Future<Conversation?> getConversation(String conversationId);
2222-2323- /// Send a message to a conversation
2424- Future<void> sendMessage({
2525- required String conversationId,
2626- required String content,
2727- MessageType type = MessageType.text,
2828- });
2929-3030- /// Mark a conversation as read
3131- Future<void> markAsRead(String conversationId);
3232-3333- /// Create or get an existing conversation
3434- Future<Conversation> createOrGetConversation(Conversation newConversation);
3535-3636- /// Dispose resources
3737- void dispose();
3838-}
···11-import 'package:get_it/get_it.dart';
22-import 'package:socket_io_client/socket_io_client.dart' as io;
33-import 'package:sparksocial/src/core/auth/data/repositories/auth_repository.dart';
44-import 'package:sparksocial/src/core/config/app_config.dart';
55-import 'package:sparksocial/src/core/utils/logging/log_service.dart';
66-77-/// Service responsible for maintaining the Socket.io connection used by chat.
88-///
99-/// This service exposes a single [socket] getter that returns a connected
1010-/// [IO.Socket] instance. The same instance is reused for the lifetime of the
1111-/// application to ensure we do not create multiple connections for the same
1212-/// host, which could lead to duplicated events and increased resource usage.
1313-class ChatSocketService {
1414- ChatSocketService._();
1515-1616- // Singleton instance (registered in GetIt but also exposed via factory).
1717- static final ChatSocketService _instance = ChatSocketService._();
1818- factory ChatSocketService() => _instance;
1919-2020- final _sl = GetIt.instance;
2121- final _logger = GetIt.instance<LogService>().getLogger('ChatSocketService');
2222-2323- io.Socket? _socket;
2424-2525- /// Returns an active Socket.io connection or creates one if it does not exist.
2626- ///
2727- /// The JWT access token from the current [AuthRepository] session is included
2828- /// in the auth parameter so that the backend can authenticate the connection
2929- /// and automatically derive the user DID.
3030- Future<io.Socket> get socket async {
3131- if (_socket != null && _socket!.connected) {
3232- return _socket!;
3333- }
3434-3535- final authRepository = _sl<AuthRepository>();
3636- if (!authRepository.isAuthenticated || authRepository.session == null) {
3737- _logger.w('Attempted to create chat socket without an authenticated user');
3838- throw Exception('User is not authenticated');
3939- }
4040-4141- final jwt = authRepository.session!.accessJwt;
4242- final url = '${AppConfig.chatServiceUrl}/chat';
4343-4444- _logger.i('Connecting to chat socket at $url');
4545-4646- _socket = io.io(
4747- url,
4848- io.OptionBuilder()
4949- .setTransports(['websocket']) // Required for Flutter native
5050- .enableForceNew()
5151- .enableReconnection()
5252- .setAuth({'token': jwt}) // ATP JWT authentication
5353- .build(),
5454- );
5555-5656- _socket!.onConnect((_) {
5757- _logger.i('Chat socket connected');
5858- _logger.d('User DID: ${_socket!.id}');
5959-6060- // Emit user-online event as shown in the example
6161- _socket!.emit('user-online', {
6262- 'userId': _socket!.id // This is automatically set from JWT
6363- });
6464- });
6565-6666- _socket!.onDisconnect((_) => _logger.w('Chat socket disconnected'));
6767- _socket!.onConnectError((err) => _logger.e('Chat socket connection error', error: err));
6868-6969- return _socket!;
7070- }
7171-7272- /// Dispose the socket connection
7373- void dispose() {
7474- if (_socket != null) {
7575- _socket!.dispose();
7676- _socket = null;
7777- }
7878- }
7979-}