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

313 lines
9.2 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 {
addLikedQuestion,
addQuestion,
countDownLikedQuestion,
countUpLikedQuestion,
deleteLikedQuestion,
} from "features/Lessons/Questions/lessonQuestionSlice";
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;
case WebSocketReceivedMessagesCmds.LessonQuestionCreated:
if (Body.LessonId === store.getState().lessonPage.currentLessonId) {
dispatch(addQuestion(Body));
}
break;
case WebSocketReceivedMessagesCmds.LessonQuestionLiked:
dispatch(addLikedQuestion(Body));
break;
case WebSocketReceivedMessagesCmds.LessonQuestionCountUpLikes:
dispatch(countUpLikedQuestion(Body));
break;
case WebSocketReceivedMessagesCmds.LessonQuestionDisliked:
dispatch(deleteLikedQuestion(Body));
break;
case WebSocketReceivedMessagesCmds.LessonQuestionCountDownLikes:
dispatch(countDownLikedQuestion(Body));
break;
default:
console.error("Unknown message type:", Cmd);
}
}