import React from 'react'; import type { MessageHandler } from '../../../../@types/messageHandler'; import useStore from '../hooks/useStore'; import type { MessageTypes, WSMessage, WSMessageWithID } from '../../../../@types/ws'; import type { Handler, RandomEvent, RandomEvents } from '../../../../@types/randomEvents'; import { Avatar, Box, Button, TextField, Typography } from '@mui/material'; import { v4 } from "uuid"; import { useSnackbar } from "notistack"; import { LockOutlined, PowerSettingsNew } from '@mui/icons-material' import { GUIConfig } from '../../../../modules/module.cfg-loader'; export type FrontEndMessanges = (MessageHandler & { randomEvents: RandomEventHandler, logout: () => Promise }); export class RandomEventHandler { private handler: { [eventName in keyof RandomEvents]: Handler[] } = { progress: [], finish: [] }; public on(name: T, listener: Handler) { if (Object.prototype.hasOwnProperty.call(this.handler, name)) { this.handler[name].push(listener as any); } else { this.handler[name] = [ listener as any ]; } } public emit(name: keyof RandomEvents, data: RandomEvent) { (this.handler[name] ?? []).forEach(handler => handler(data as any)); } public removeListener(name: T, listener: Handler) { this.handler[name] = (this.handler[name] as Handler[]).filter(a => a !== listener) as any; } } export const messageChannelContext = React.createContext(undefined); async function messageAndResponse(socket: WebSocket, msg: WSMessage): Promise> { const id = v4(); const ret = new Promise>((resolve) => { const handler = function({ data }: MessageEvent) { const parsed = JSON.parse(data.toString()) as WSMessageWithID; if (parsed.id === id) { socket.removeEventListener('message', handler); resolve(parsed); } } socket.addEventListener('message', handler); }); const toSend = msg as WSMessageWithID; toSend.id = id; socket.send(JSON.stringify(toSend)); return ret; } const MessageChannelProvider: FCWithChildren = ({ children }) => { const [store, dispatch] = useStore(); const [socket, setSocket] = React.useState(); const [publicWS, setPublicWS] = React.useState(); const [usePassword, setUsePassword] = React.useState<'waiting'|'yes'|'no'>('waiting'); const [isSetuped, setIsSetuped] = React.useState<'waiting'|'yes'|'no'>('waiting'); const { enqueueSnackbar } = useSnackbar(); React.useEffect(() => { const wss = new WebSocket(`ws://${process.env.NODE_ENV === 'development' ? 'localhost:3000' : window.location.host}/public`); wss.addEventListener('open', () => { setPublicWS(wss); }); wss.addEventListener('error', () => { enqueueSnackbar('Unable to connect to server. Please reload the page to try again.', { variant: 'error' }); }); }, []); React.useEffect(() => { (async () => { if (!publicWS) return; setUsePassword((await messageAndResponse(publicWS, { name: 'requirePassword', data: undefined })).data ? 'yes' : 'no'); setIsSetuped((await messageAndResponse(publicWS, { name: 'setuped', data: undefined })).data ? 'yes' : 'no'); })(); }, [publicWS]); const connect = (ev?: React.FormEvent) => { let search = new URLSearchParams(); if (ev) { ev.preventDefault(); const formData = new FormData(ev.currentTarget); const password = formData.get('password')?.toString(); if (!password) return enqueueSnackbar('Please provide both a username and password', { variant: 'error' }); search = new URLSearchParams({ password }); } const wws = new WebSocket(`ws://${process.env.NODE_ENV === 'development' ? 'localhost:3000' : window.location.host}/ws?${search}`, ); wws.addEventListener('open', () => { console.log('[INFO] [WS] Connected'); setSocket(wws); }); wws.addEventListener('error', (er) => { console.error(`[ERROR] [WS]`, er); enqueueSnackbar('Unable to connect to server. Please check the password and try again.', { variant: 'error' }); }) }; const setup = async (ev: React.FormEvent) => { ev.preventDefault(); if (!socket) return enqueueSnackbar('Invalid state: socket not found', { variant: 'error' }); const formData = new FormData(ev.currentTarget); const password = formData.get('password'); const data = { port: parseInt(formData.get('port')?.toString() ?? '') ?? 3000, password: password ? password.toString() : undefined } as GUIConfig; await messageAndResponse(socket!, { name: 'setupServer', data }); enqueueSnackbar(`The following settings have been set: Port=${data.port}, Password=${data.password ?? 'noPasswordRequired'}`, { variant: 'success', persist: true }); enqueueSnackbar('Please restart the server now.', { variant: 'info', persist: true }); } const randomEventHandler = React.useMemo(() => new RandomEventHandler(), []); React.useEffect(() => { (async () => { if (!socket) return; const currentService = await messageAndResponse(socket, { name: 'type', data: undefined }); if (currentService.data !== undefined) return dispatch({ type: 'service', payload: currentService.data }); if (store.service !== currentService.data) messageAndResponse(socket, { name: 'setup', data: store.service }); })(); }, [store.service, dispatch, socket]) React.useEffect(() => { if (!socket) return; /* finish is a placeholder */ const listener = (initalData: MessageEvent) => { const data = JSON.parse(initalData.data) as RandomEvent<'finish'>; randomEventHandler.emit(data.name, data); } socket.addEventListener('message', listener); return () => { socket.removeEventListener('message', listener); }; }, [ socket ]); if (usePassword === 'waiting') return <>; if (socket === undefined) { if (usePassword === 'no') { connect(undefined) return <>; } return Login You need to login in order to use this tool. ; } if (isSetuped === 'no') { return Confirm Please enter data that will be set to use this tool.
Leave blank to use no password (NOT RECOMMENDED)!
; } const messageHandler: FrontEndMessanges = { auth: async (data) => (await messageAndResponse(socket, { name: 'auth', data })).data, checkToken: async () => (await messageAndResponse(socket, { name: 'checkToken', data: undefined })).data, search: async (data) => (await messageAndResponse(socket, { name: 'search', data })).data, handleDefault: async (data) => (await messageAndResponse(socket, { name: 'default', data })).data, availableDubCodes: async () => (await messageAndResponse(socket, { name: 'availableDubCodes', data: undefined})).data, availableSubCodes: async () => (await messageAndResponse(socket, { name: 'availableSubCodes', data: undefined })).data, resolveItems: async (data) => (await messageAndResponse(socket, { name: 'resolveItems', data })).data, listEpisodes: async (data) => (await messageAndResponse(socket, { name: 'listEpisodes', data })).data, randomEvents: randomEventHandler, downloadItem: (data) => messageAndResponse(socket, { name: 'downloadItem', data }), isDownloading: async () => (await messageAndResponse(socket, { name: 'isDownloading', data: undefined })).data, writeToClipboard: async (data) => messageAndResponse(socket, { name: 'writeToClipboard', data }), openFolder: async (data) => messageAndResponse(socket, { name: 'openFolder', data }), logout: async () => (await messageAndResponse(socket, { name: 'changeProvider', data: undefined })).data, openFile: async (data) => await messageAndResponse(socket, { name: 'openFile', data }), openURL: async (data) => await messageAndResponse(socket, { name: 'openURL', data }) } return {children} ; }; export default MessageChannelProvider;