Skip to content

Commit 02b3ccb

Browse files
committed
refactor: replace SockJS with SharedStompClient for WebSocket handling in ChatWebSocket, and implement notification management with a new useNotifications hook
1 parent 67d0d42 commit 02b3ccb

File tree

3 files changed

+389
-87
lines changed

3 files changed

+389
-87
lines changed

apps/jobboard-frontend/src/modules/mentorship/services/chat.service.ts

Lines changed: 28 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { api } from '@shared/lib/api-client';
22
import type { ChatMessageDTO } from '@shared/types/api.types';
33
import 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
*/
3332
export 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(/\/api$/, '');
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

Comments
 (0)