adds custom events for the websocket

This commit is contained in:
StarAppeal
2024-12-09 02:41:24 +01:00
parent 808450ca43
commit 04d0785c69
15 changed files with 257 additions and 106 deletions
+7 -6
View File
@@ -1,10 +1,11 @@
import { WebSocket } from "ws";
import { DecodedToken } from "./decodedToken";
import {WebSocket} from "ws";
import {DecodedToken} from "./decodedToken";
import {IUser} from "../db/models/user";
export interface ExtendedWebSocket extends WebSocket {
payload: DecodedToken;
isAlive: boolean;
user:IUser;
asyncUpdates?: NodeJS.Timeout
payload: DecodedToken;
isAlive: boolean;
user: IUser;
asyncUpdates?: NodeJS.Timeout
}
@@ -0,0 +1,12 @@
import {ExtendedWebSocket} from "../../../interfaces/extendedWebsocket";
export abstract class CustomWebsocketEvent {
abstract event: string;
abstract handler: (data: any) => void;
protected ws: ExtendedWebSocket;
public constructor(ws: ExtendedWebSocket) {
this.ws = ws;
}
}
@@ -0,0 +1,17 @@
import {WebsocketEventType} from "./websocketEventType";
import {CustomWebsocketEvent} from "./customWebsocketEvent";
import {ExtendedWebSocket} from "../../../interfaces/extendedWebsocket";
export class ErrorEvent extends CustomWebsocketEvent {
event: string = WebsocketEventType.ERROR;
constructor(ws: ExtendedWebSocket) {
super(ws);
}
handler = async (data: any) => {
const {message, traceback} = data;
console.warn("Error message received", message);
console.warn("Traceback", traceback);
}
}
@@ -0,0 +1,17 @@
import {CustomWebsocketEvent} from "./customWebsocketEvent";
import {WebsocketEventType} from "./websocketEventType";
export class GetSettingsEvent extends CustomWebsocketEvent {
event = WebsocketEventType.GET_SETTINGS;
handler = async () => {
console.log("Getting settings");
this.ws.send(JSON.stringify({
type: "SETTINGS",
payload: {
timezone: this.ws.user.timezone,
},
}), {binary: false});
}
}
@@ -0,0 +1,57 @@
import {CustomWebsocketEvent} from "./customWebsocketEvent";
import {WebsocketEventType} from "./websocketEventType";
import {SpotifyTokenService} from "../../../db/services/spotifyTokenService";
import {UserService} from "../../../db/services/db/UserService";
import {getCurrentlyPlaying} from "../../../db/services/spotifyApiService";
export class GetSpotifyUpdatesEvent extends CustomWebsocketEvent {
event = WebsocketEventType.GET_SPOTIFY_UPDATES;
handler = async () => {
console.log("Starting Spotify updates");
// first execute the function once
this.spotifyUpdates()
.then(() => {
// then set the interval
this.ws.asyncUpdates = setInterval(() => {
this.spotifyUpdates();
}, 1000);
});
}
private async spotifyUpdates() {
console.log("Checking Spotify")
const user = this.ws.user;
const spotifyConfig = user.spotifyConfig;
if (!spotifyConfig) {
console.log("No Spotify config found");
// stop the interval
this.ws.emit(WebsocketEventType.STOP_SPOTIFY_UPDATES, {});
return;
}
if (Date.now() > spotifyConfig.expirationDate.getTime()) {
console.log("Token expired");
const token = await new SpotifyTokenService().refreshToken(spotifyConfig.refreshToken);
user.spotifyConfig = {
// use old refresh token because you don't get a new one
refreshToken: user.spotifyConfig!.refreshToken,
accessToken: token.access_token,
expirationDate: new Date(Date.now() + token.expires_in * 1000),
scope: token.scope,
};
const userService = await UserService.create();
await userService.updateUser(user);
console.log("Token refreshed and database updated");
}
const musicData = await getCurrentlyPlaying(user.spotifyConfig!.accessToken);
this.ws.send(JSON.stringify({
type: "SPOTIFY_UPDATE",
payload: musicData,
}), {binary: false});
}
}
@@ -0,0 +1,15 @@
import {CustomWebsocketEvent} from "./customWebsocketEvent";
import {WebsocketEventType} from "./websocketEventType";
export class GetStateEvent extends CustomWebsocketEvent {
event = WebsocketEventType.GET_STATE;
handler = async () => {
console.log("Getting state");
const messageToSend = {
type: "STATE",
payload: this.ws.user.lastState,
};
this.ws.send(JSON.stringify(messageToSend), {binary: false});
}
}
@@ -0,0 +1,31 @@
import {CustomWebsocketEvent} from "./customWebsocketEvent";
import {WebsocketEventType} from "./websocketEventType";
import {getCurrentWeather} from "../../../db/services/owmApiService";
export class GetWeatherUpdatesEvent extends CustomWebsocketEvent {
event = WebsocketEventType.GET_WEATHER_UPDATES;
handler = async () => {
console.log("Starting weather updates");
this.weatherUpdates().then(() => {
this.ws.asyncUpdates = setInterval(() => {
this.weatherUpdates();
}, 1000 * 60);
}
);
}
private async weatherUpdates() {
console.log("Checking weather")
const user = this.ws.user;
const weather = await getCurrentWeather(user.location);
console.log(weather);
this.ws.send(JSON.stringify({
type: "WEATHER_UPDATE",
payload: weather,
}), {binary: false});
}
}
@@ -0,0 +1,16 @@
import {CustomWebsocketEvent} from "./customWebsocketEvent";
import {WebsocketEventType} from "./websocketEventType";
export class StopSpotifyUpdatesEvent extends CustomWebsocketEvent {
event = WebsocketEventType.STOP_SPOTIFY_UPDATES;
handler = async () => {
if (this.ws.asyncUpdates) {
clearInterval(this.ws.asyncUpdates);
console.log("Spotify updates stopped");
}
}
}
@@ -0,0 +1,16 @@
import {CustomWebsocketEvent} from "./customWebsocketEvent";
import {WebsocketEventType} from "./websocketEventType";
export class StopWeatherUpdatesEvent extends CustomWebsocketEvent {
event = WebsocketEventType.STOP_WEATHER_UPDATES;
handler = async () => {
if (this.ws.asyncUpdates) {
clearInterval(this.ws.asyncUpdates);
console.log("Weather updates stopped");
}
}
}
@@ -0,0 +1,16 @@
import {WebsocketEventType} from "./websocketEventType";
import {CustomWebsocketEvent} from "./customWebsocketEvent";
import {IUser} from "../../../db/models/user";
export class UpdateUserEvent extends CustomWebsocketEvent {
event = WebsocketEventType.UPDATE_USER;
handler = async (data: IUser) => {
console.log("Updating user")
if (data) {
this.ws.user = data;
console.log("User updated")
}
}
}
@@ -0,0 +1,10 @@
export enum WebsocketEventType {
GET_SETTINGS = "GET_SETTINGS",
GET_STATE = "GET_STATE",
GET_SPOTIFY_UPDATES = "GET_SPOTIFY_UPDATES",
GET_WEATHER_UPDATES = "GET_WEATHER_UPDATES",
STOP_SPOTIFY_UPDATES = "STOP_SPOTIFY_UPDATES",
STOP_WEATHER_UPDATES = "STOP_WEATHER_UPDATES",
ERROR = "ERROR",
UPDATE_USER = "UPDATE_USER",
}
@@ -0,0 +1,23 @@
import {ExtendedWebSocket} from "../../../interfaces/extendedWebsocket";
import {GetSettingsEvent} from "./getSettingsEvent";
import {ErrorEvent} from "./errorEvent";
import {GetSpotifyUpdatesEvent} from "./getSpotifyUpdatesEvent";
import {GetStateEvent} from "./getStateEvent";
import {GetWeatherUpdatesEvent} from "./getWeatherUpdatesEvent";
import {StopSpotifyUpdatesEvent} from "./stopSpotifyUpdatesEvent";
import {StopWeatherUpdatesEvent} from "./stopWeatherUpdatesEvent";
import {UpdateUserEvent} from "./updateUserEvent";
import {CustomWebsocketEvent} from "./customWebsocketEvent";
export function getEventListeners(ws: ExtendedWebSocket): CustomWebsocketEvent[] {
return [
new GetSettingsEvent(ws),
new ErrorEvent(ws),
new GetSpotifyUpdatesEvent(ws),
new GetStateEvent(ws),
new GetWeatherUpdatesEvent(ws),
new StopSpotifyUpdatesEvent(ws),
new StopWeatherUpdatesEvent(ws),
new UpdateUserEvent(ws)
];
}
+6 -94
View File
@@ -1,8 +1,5 @@
import {ExtendedWebSocket} from "../../interfaces/extendedWebsocket";
import {getCurrentlyPlaying} from "../../db/services/spotifyApiService";
import {SpotifyTokenService} from "../../db/services/spotifyTokenService";
import {UserService} from "../../db/services/db/UserService";
import {getCurrentWeather} from "../../db/services/owmApiService";
import {CustomWebsocketEvent} from "./websocketCustomEvents/customWebsocketEvent";
export class WebsocketEventHandler {
constructor(private webSocket: ExtendedWebSocket) {
@@ -39,100 +36,15 @@ export class WebsocketEventHandler {
const {type} = messageJson;
console.log("Received message:", message);
if (type === "GET_SETTINGS") {
this.webSocket.send(JSON.stringify({
type: "SETTINGS",
payload: {
timezone: this.webSocket.user.timezone,
},
}), {binary: false});
}
if (type === "GET_STATE") {
const messageToSend = {
type: "STATE",
payload: this.webSocket.user.lastState,
};
this.webSocket.send(JSON.stringify(messageToSend), {binary: false});
}
if (type === "GET_SPOTIFY_UPDATES") {
console.log("Starting Spotify updates");
// first execute the function once
this.spotifyUpdates()
.then(() => {
// then set the interval
this.webSocket.asyncUpdates = setInterval(() => {
this.spotifyUpdates();
}, 1000);
});
}
if (type === "GET_WEATHER_UPDATES") {
console.log("Starting weather updates");
this.weatherUpdates().then(() => {
this.webSocket.asyncUpdates = setInterval(() => {
this.weatherUpdates();
}, 1000 * 60);
}
)
}
if (type === "STOP_SPOTIFY_UPDATES" || type === "STOP_WEATHER_UPDATES") {
if (this.webSocket.asyncUpdates) {
clearInterval(this.webSocket.asyncUpdates);
console.log("Async updates stopped");
}
}
if (type === "ERROR") {
const {message, traceback} = messageJson;
console.warn("Error message received", message);
console.warn("Traceback", traceback);
}
// emit event to the custom event handler
this.webSocket.emit(type, messageJson);
}
);
}
private async spotifyUpdates() {
console.log("Checking Spotify")
const user = this.webSocket.user;
const spotifyConfig = user.spotifyConfig;
if (!spotifyConfig) {
console.log("No Spotify config found");
return;
}
if (Date.now() > spotifyConfig.expirationDate.getTime()) {
console.log("Token expired");
const token = await new SpotifyTokenService().refreshToken(spotifyConfig.refreshToken);
user.spotifyConfig = {
// use old refresh token because you don't get a new one
refreshToken: user.spotifyConfig!.refreshToken,
accessToken: token.access_token,
expirationDate: new Date(Date.now() + token.expires_in * 1000),
scope: token.scope,
};
const userService = await UserService.create();
await userService.updateUser(user);
console.log("Token refreshed and database updated");
}
const musicData = await getCurrentlyPlaying(user.spotifyConfig!.accessToken);
this.webSocket.send(JSON.stringify({
type: "SPOTIFY_UPDATE",
payload: musicData,
}), {binary: false});
public registerCustomEvent(customWebsocketEvent:CustomWebsocketEvent ) {
// bind needed?
this.webSocket.on(customWebsocketEvent.event, customWebsocketEvent.handler.bind(customWebsocketEvent));
}
private async weatherUpdates() {
console.log("Checking weather")
const user = this.webSocket.user;
const weather = await getCurrentWeather(user.location);
console.log(weather);
this.webSocket.send(JSON.stringify({
type: "WEATHER_UPDATE",
payload: weather,
}), {binary: false});
}
}
@@ -17,7 +17,7 @@ export class WebsocketServerEventHandler {
this.webSocketServer.on(
"connection",
async (ws: ExtendedWebSocket, request: ExtendedIncomingMessage) => {
const user = await (await UserService.create()).getUserByUUID(request.payload.id);
const user = await (await UserService.create()).getUserByUUID(request.payload.uuid);
ws.user = user!;
+13 -5
View File
@@ -6,6 +6,7 @@ import {DecodedToken} from "./interfaces/decodedToken";
import {WebsocketServerEventHandler} from "./utils/websocket/websocketServerEventHandler";
import {WebsocketEventHandler} from "./utils/websocket/websocketEventHandler";
import {UserService} from "./db/services/db/UserService";
import {getEventListeners} from "./utils/websocket/websocketCustomEvents/websocketEventUtils";
export class ExtendedWebSocketServer {
private readonly _wss: WebSocketServer;
@@ -59,19 +60,26 @@ export class ExtendedWebSocketServer {
socketEventHandler.enablePongEvent();
socketEventHandler.enableMessageEvent();
// Register custom events
getEventListeners(ws).forEach((eventListener) => {
socketEventHandler.registerCustomEvent(eventListener);
});
const updateUserInterval = setInterval(async () => {
console.log("Updating user")
const userService = await UserService.create();
const user = await userService.getUserByUUID(ws.payload.id);
if (user) {
ws.user = user;
}
const user = await userService.getUserByUUID(ws.payload.uuid);
ws.emit("UPDATE_USER", user);
}, 15000);
socketEventHandler.enableDisconnectEvent(() => {
clearInterval(updateUserInterval);
console.log("stopped updating user");
});
// send initial state and settings
// think about emitting the data needed directly to the event Handler
ws.emit("GET_STATE", {});
ws.emit("GET_SETTINGS", {});
});
const interval = serverEventHandler.enableHeartbeat(30000);