lms-frontend/src/core/services/websocketService.ts

256 lines
9.4 KiB
TypeScript

import { Dispatch } from '@reduxjs/toolkit';
import { setBannerUrl, setLogoUrl, setPrimaryColor, setUserProfilePictureUrl } from 'core/reducers/appSlice';
import { store } from 'core/store/store';
import { BrowserTabSession, Constants } from 'core/utils/utils';
import { WebSocketReceivedMessagesCmds } from 'core/utils/webSocket';
import { addLessonPageContent, deleteLessonPageContent, updateLessonPageContent, updateLessonPageContentPosition } from 'features/Lessons/LessonPage/lessonPageSlice';
import {
addLessonContent,
deleteLessonContent,
setLessonThumbnailTitle,
setLessonThumbnailUrl,
setPageEditorLessonState,
updateLessonContent,
updateLessonContentPosition,
} from 'features/Lessons/LessonPageEditor/lessonPageEditorSlice';
import { addLesson, updateLessonPreviewThumbnail, updateLessonPreviewTitle, updateLessonState } from 'features/Lessons/lessonsSlice';
import { addTeamMember, deleteTeamMember, updateTeamMemberRole } from 'features/Team/teamSlice';
import { setProfilePictureUrl } from 'features/UserProfile/userProfileSlice';
interface WebSocketMessage {
Cmd: number;
Body: any;
}
class WebSocketService {
private url: string;
private socket: WebSocket | null = null;
private reconnectInterval: number = 2000; // in ms
private offlineQueue: WebSocketMessage[] = [];
private firstConnect: boolean = true;
private messageHandler: ((message: WebSocketMessage, dispatch: Dispatch) => void) | null = null;
constructor(url: string) {
this.url = url;
}
private dispatch: Dispatch | null = null;
public connect(): void {
this.socket = new WebSocket(`${this.url}?auth=${localStorage.getItem('session')}&bts=${BrowserTabSession}`);
this.socket.onopen = () => {
console.log('WebSocket connected', this.firstConnect);
// Send all messages from the offline queue
this.offlineQueue.forEach((message) => this.send(message));
this.offlineQueue = [];
// Dispatch event to notify that the WebSocket connection is established
if (!this.firstConnect) {
document.dispatchEvent(webSocketConnectionEvent);
} else {
this.firstConnect = false;
}
};
this.socket.onmessage = (event: MessageEvent) => {
const data: WebSocketMessage = JSON.parse(event.data);
if (this.messageHandler) {
this.messageHandler(data, this.dispatch!);
} else {
console.error('No handler defined for WebSocket messages');
}
};
this.socket.onclose = () => {
console.log('WebSocket disconnected. Reconnecting...');
setTimeout(() => this.connect(), this.reconnectInterval);
};
this.socket.onerror = (error: Event) => {
console.error('WebSocket error:', error);
};
}
public setHandler(handler: (message: WebSocketMessage, dispatch: Dispatch) => void, dispatch: Dispatch): void {
this.messageHandler = handler;
this.dispatch = dispatch;
}
public send(message: WebSocketMessage): void {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(
JSON.stringify({
Cmd: message.Cmd,
Body: message.Body,
})
);
} else {
this.offlineQueue.push(message);
}
}
public disconnect(): void {
if (this.socket) {
this.socket.close();
}
}
}
const webSocketConnectionEventName = 'WebSocketConnectionEvent';
const webSocketConnectionEvent = new CustomEvent(webSocketConnectionEventName, {
detail: 'wsReconnect',
});
export function addWebSocketReconnectListener(callback: () => void): void {
document.addEventListener(webSocketConnectionEventName, callback);
}
export function removeWebSocketReconnectListener(callback: () => void): void {
document.removeEventListener(webSocketConnectionEventName, callback);
}
const webSocketService = new WebSocketService(Constants.WS_ADDRESS);
export default webSocketService;
export function WebSocketMessageHandler(message: WebSocketMessage, dispatch: Dispatch) {
const { Cmd, Body } = message;
console.log('WebSocketMessageHandler', Cmd, Body);
switch (Cmd) {
case WebSocketReceivedMessagesCmds.SettingsUpdated:
dispatch(setPrimaryColor(Body.PrimaryColor));
break;
case WebSocketReceivedMessagesCmds.SettingsUpdatedLogo:
dispatch(setLogoUrl(Body));
break;
case WebSocketReceivedMessagesCmds.SettingsUpdatedBanner:
dispatch(setBannerUrl(Body));
break;
case WebSocketReceivedMessagesCmds.SettingsUpdatedSubdomain:
localStorage.removeItem('session');
window.location.href = `${window.location.protocol}//${Body}.${window.location.hostname.split('.').slice(1).join('.')}`;
break;
case WebSocketReceivedMessagesCmds.TeamAddedMember:
dispatch(addTeamMember(Body));
break;
case WebSocketReceivedMessagesCmds.TeamUpdatedMemberRole:
dispatch(updateTeamMemberRole(Body));
break;
case WebSocketReceivedMessagesCmds.TeamDeletedMember:
dispatch(deleteTeamMember(Body));
break;
case WebSocketReceivedMessagesCmds.LessonCreated:
dispatch(addLesson(Body));
break;
case WebSocketReceivedMessagesCmds.LessonPreviewTitleUpdated:
dispatch(updateLessonPreviewTitle(Body));
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
dispatch(setLessonThumbnailTitle(Body.Title));
}
break;
case WebSocketReceivedMessagesCmds.LessonPreviewThumbnailUpdated:
dispatch(updateLessonPreviewThumbnail(Body));
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
dispatch(setLessonThumbnailUrl(Body.ThumbnailUrl));
}
break;
case WebSocketReceivedMessagesCmds.LessonStateUpdated:
dispatch(updateLessonState(Body));
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
dispatch(setPageEditorLessonState(Body.State));
}
break;
case WebSocketReceivedMessagesCmds.LessonAddedContent:
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
dispatch(addLessonPageContent(Body));
}
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
dispatch(addLessonContent(Body));
}
break;
case WebSocketReceivedMessagesCmds.LessonDeletedContent:
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
dispatch(deleteLessonPageContent(Body.ContentId));
}
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
dispatch(deleteLessonContent(Body.ContentId));
}
break;
case WebSocketReceivedMessagesCmds.LessonContentUpdated:
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
dispatch(
updateLessonPageContent({
id: Body.ContentId,
data: Body.Data,
})
);
}
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
dispatch(
updateLessonContent({
id: Body.ContentId,
data: Body.Data,
})
);
}
break;
case WebSocketReceivedMessagesCmds.LessonContentUpdatedPosition:
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
dispatch(
updateLessonPageContentPosition({
contentId: Body.ContentId,
position: Body.Position,
})
);
}
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
dispatch(
updateLessonContentPosition({
contentId: Body.ContentId,
position: Body.Position,
})
);
}
break;
case WebSocketReceivedMessagesCmds.LessonContentFileUpdated:
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
dispatch(
updateLessonPageContent({
id: Body.ContentId,
data: Body.Data,
})
);
}
if (Body.LessonId === store.getState().lessonPageEditor.currentLessonId) {
dispatch(
updateLessonContent({
id: Body.ContentId,
data: Body.Data,
})
);
}
break;
case WebSocketReceivedMessagesCmds.UserProfilePictureUpdated:
dispatch(setProfilePictureUrl(Body));
dispatch(setUserProfilePictureUrl(Body));
break;
default:
console.error('Unknown message type:', Cmd);
}
}