11import { api } from '@shared/lib/api-client' ;
22import type { ChatMessageDTO } from '@shared/types/api.types' ;
33import type { ChatMessage } from '@shared/types/chat' ;
4- import SockJS from 'sockjs-client' ;
5- import { Client , type StompSubscription } from '@stomp/stompjs' ;
4+ import { SharedStompClient } from '@shared/services/stompClient' ;
65
76/**
87 * Chat Service
@@ -31,32 +30,14 @@ export async function getChatHistory(conversationId: number): Promise<ChatMessag
3130 * WebSocket connection for real-time chat using STOMP over SockJS
3231 */
3332export class ChatWebSocket {
34- private client : Client | null = null ;
33+ private client : SharedStompClient | null = null ;
3534 private conversationId : number | null = null ;
36- private subscription : StompSubscription | null = null ;
35+ private unsubscribe : ( ( ) => void ) | null = null ;
3736 private onMessageCallback : ( ( message : ChatMessage ) => void ) | null = null ;
3837 private onErrorCallback : ( ( error : Error ) => void ) | null = null ;
3938 private onConnectCallback : ( ( ) => void ) | null = null ;
4039 private onDisconnectCallback : ( ( ) => void ) | null = null ;
4140
42- /**
43- * Get WebSocket URL based on environment
44- * SockJS requires http:// or https://, not ws:// or wss://
45- */
46- private getWebSocketUrl ( ) : string {
47- // Use same base URL as API client
48- const apiUrl = import . meta. env . VITE_API_URL || 'http://localhost:8080' ;
49- // Remove /api suffix if present
50- const baseUrl = apiUrl . replace ( / \/ a p i $ / , '' ) ;
51- // SockJS requires http:// or https://, it handles the WebSocket protocol internally
52- // Ensure we have http:// or https://
53- if ( ! baseUrl . startsWith ( 'http://' ) && ! baseUrl . startsWith ( 'https://' ) ) {
54- // If no protocol, default to http://
55- return `http://${ baseUrl } /ws-chat` ;
56- }
57- return `${ baseUrl } /ws-chat` ;
58- }
59-
6041 /**
6142 * Connect to WebSocket
6243 * @param conversationId - The conversation ID to connect to
@@ -74,8 +55,7 @@ export class ChatWebSocket {
7455 onConnect ?: ( ) => void ,
7556 onDisconnect ?: ( ) => void
7657 ) {
77- // Disconnect existing connection if any
78- if ( this . client ?. active ) {
58+ if ( this . client ?. isConnected ( ) ) {
7959 this . disconnect ( ) ;
8060 }
8161
@@ -85,25 +65,13 @@ export class ChatWebSocket {
8565 this . onConnectCallback = onConnect || null ;
8666 this . onDisconnectCallback = onDisconnect || null ;
8767
88- // Create STOMP client with SockJS
89- this . client = new Client ( {
90- webSocketFactory : ( ) => new SockJS ( this . getWebSocketUrl ( ) ) as any ,
91- reconnectDelay : 5000 ,
92- heartbeatIncoming : 4000 ,
93- heartbeatOutgoing : 4000 ,
94- debug : ( str ) => {
95- // Only log in development
96- if ( import . meta. env . DEV ) {
97- console . log ( '[STOMP]' , str ) ;
98- }
99- } ,
100- onConnect : ( frame ) => {
101- console . log ( '[ChatWebSocket] Connected:' , frame ) ;
102-
103- // Subscribe to conversation topic
68+ this . client = new SharedStompClient ( {
69+ token : accessToken ,
70+ debug : import . meta. env . DEV ,
71+ onConnect : ( ) => {
10472 if ( this . conversationId && this . client ) {
10573 const topic = `/topic/conversation/${ this . conversationId } ` ;
106- this . subscription = this . client . subscribe ( topic , ( message ) => {
74+ this . unsubscribe = this . client . subscribe ( topic , ( message ) => {
10775 try {
10876 const chatMessageDTO : ChatMessageDTO = JSON . parse ( message . body ) ;
10977 const chatMessage : ChatMessage = {
@@ -113,70 +81,45 @@ export class ChatWebSocket {
11381 senderAvatar : chatMessageDTO . senderAvatar ,
11482 content : chatMessageDTO . content ,
11583 timestamp : chatMessageDTO . timestamp ,
116- read : false , // New messages are unread
84+ read : false ,
11785 } ;
118-
119- if ( this . onMessageCallback ) {
120- this . onMessageCallback ( chatMessage ) ;
121- }
86+
87+ this . onMessageCallback ?.( chatMessage ) ;
12288 } catch ( err ) {
12389 console . error ( '[ChatWebSocket] Error parsing message:' , err ) ;
12490 }
12591 } ) ;
126-
127- console . log ( `[ChatWebSocket] Subscribed to ${ topic } ` ) ;
12892 }
12993
130- if ( this . onConnectCallback ) {
131- this . onConnectCallback ( ) ;
132- }
133- } ,
134- onStompError : ( frame ) => {
135- console . error ( '[ChatWebSocket] STOMP error:' , frame ) ;
136- const error = new Error ( frame . headers [ 'message' ] || 'WebSocket connection error' ) ;
137- if ( this . onErrorCallback ) {
138- this . onErrorCallback ( error ) ;
139- }
140- } ,
141- onWebSocketError : ( event ) => {
142- console . error ( '[ChatWebSocket] WebSocket error:' , event ) ;
143- const error = new Error ( 'WebSocket connection failed' ) ;
144- if ( this . onErrorCallback ) {
145- this . onErrorCallback ( error ) ;
146- }
94+ this . onConnectCallback ?.( ) ;
14795 } ,
14896 onDisconnect : ( ) => {
149- console . log ( '[ChatWebSocket] Disconnected' ) ;
150- if ( this . onDisconnectCallback ) {
151- this . onDisconnectCallback ( ) ;
152- }
97+ this . onDisconnectCallback ?.( ) ;
15398 } ,
154- connectHeaders : {
155- Authorization : `Bearer ${ accessToken } ` ,
99+ onError : ( error ) => {
100+ this . onErrorCallback ?. ( error ) ;
156101 } ,
157102 } ) ;
158103
159- // Activate the client
160- this . client . activate ( ) ;
104+ this . client
105+ . connect ( )
106+ . catch ( ( error ) => {
107+ this . onErrorCallback ?.( error ) ;
108+ } ) ;
161109 }
162110
163111 /**
164112 * Send a message via WebSocket
165113 * @param content - Message content
166114 */
167115 sendMessage ( content : string ) {
168- if ( ! this . client ?. active || ! this . conversationId ) {
116+ if ( ! this . client ?. isConnected ( ) || ! this . conversationId ) {
169117 console . error ( '[ChatWebSocket] Cannot send message: not connected' ) ;
170118 throw new Error ( 'WebSocket not connected' ) ;
171119 }
172120
173121 const destination = `/app/chat.sendMessage/${ this . conversationId } ` ;
174- const message = { content } ;
175-
176- this . client . publish ( {
177- destination,
178- body : JSON . stringify ( message ) ,
179- } ) ;
122+ this . client . publish ( destination , { content } ) ;
180123
181124 console . log ( '[ChatWebSocket] Message sent:' , { destination, content } ) ;
182125 }
@@ -185,15 +128,13 @@ export class ChatWebSocket {
185128 * Disconnect from WebSocket
186129 */
187130 disconnect ( ) {
188- if ( this . subscription ) {
189- this . subscription . unsubscribe ( ) ;
190- this . subscription = null ;
131+ if ( this . unsubscribe ) {
132+ this . unsubscribe ( ) ;
133+ this . unsubscribe = null ;
191134 }
192135
193136 if ( this . client ) {
194- if ( this . client . active ) {
195- this . client . deactivate ( ) ;
196- }
137+ this . client . disconnect ( ) ;
197138 this . client = null ;
198139 }
199140
@@ -210,7 +151,7 @@ export class ChatWebSocket {
210151 * Check if WebSocket is connected
211152 */
212153 isConnected ( ) : boolean {
213- return this . client ?. active || false ;
154+ return this . client ?. isConnected ( ) || false ;
214155 }
215156
216157 /**
0 commit comments