owncast/web/components/stores/ClientConfigStore.tsx
2022-05-13 14:44:16 -07:00

210 lines
6.3 KiB
TypeScript

/* eslint-disable no-case-declarations */
import { useEffect } from 'react';
import { atom, useRecoilState, useSetRecoilState } from 'recoil';
import { makeEmptyClientConfig, ClientConfig } from '../../interfaces/client-config.model';
import ClientConfigService from '../../services/client-config-service';
import ChatService from '../../services/chat-service';
import WebsocketService from '../../services/websocket-service';
import { ChatMessage } from '../../interfaces/chat-message.model';
import { ServerStatus, makeEmptyServerStatus } from '../../interfaces/server-status.model';
import {
AppState,
ChatState,
VideoState,
ChatVisibilityState,
getChatState,
getChatVisibilityState,
} from '../../interfaces/application-state';
import {
ConnectedClientInfoEvent,
MessageType,
ChatEvent,
SocketEvent,
} from '../../interfaces/socket-events';
import handleConnectedClientInfoMessage from './eventhandlers/connectedclientinfo';
import handleChatMessage from './eventhandlers/handleChatMessage';
import ServerStatusService from '../../services/status-service';
// Server status is what gets updated such as viewer count, durations,
// stream title, online/offline state, etc.
export const serverStatusState = atom<ServerStatus>({
key: 'serverStatusState',
default: makeEmptyServerStatus(),
});
// The config that comes from the API.
export const clientConfigStateAtom = atom({
key: 'clientConfigState',
default: makeEmptyClientConfig(),
});
export const appStateAtom = atom<AppState>({
key: 'appStateAtom',
default: AppState.Loading,
});
export const chatStateAtom = atom<ChatState>({
key: 'chatStateAtom',
default: ChatState.Offline,
});
export const videoStateAtom = atom<VideoState>({
key: 'videoStateAtom',
default: VideoState.Unavailable,
});
export const chatVisibilityAtom = atom<ChatVisibilityState>({
key: 'chatVisibility',
default: ChatVisibilityState.Visible,
});
export const chatDisplayNameAtom = atom<string>({
key: 'chatDisplayName',
default: null,
});
export const accessTokenAtom = atom<string>({
key: 'accessTokenAtom',
default: null,
});
export const chatMessagesAtom = atom<ChatMessage[]>({
key: 'chatMessages',
default: [] as ChatMessage[],
});
export const websocketServiceAtom = atom<WebsocketService>({
key: 'websocketServiceAtom',
default: null,
});
export function ClientConfigStore() {
const setClientConfig = useSetRecoilState<ClientConfig>(clientConfigStateAtom);
const setServerStatus = useSetRecoilState<ServerStatus>(serverStatusState);
const setChatVisibility = useSetRecoilState<ChatVisibilityState>(chatVisibilityAtom);
const setChatState = useSetRecoilState<ChatState>(chatStateAtom);
const [chatMessages, setChatMessages] = useRecoilState<ChatMessage[]>(chatMessagesAtom);
const setChatDisplayName = useSetRecoilState<string>(chatDisplayNameAtom);
const [appState, setAppState] = useRecoilState<AppState>(appStateAtom);
const [accessToken, setAccessToken] = useRecoilState<string>(accessTokenAtom);
const setWebsocketService = useSetRecoilState<WebsocketService>(websocketServiceAtom);
let ws: WebsocketService;
const updateClientConfig = async () => {
try {
const config = await ClientConfigService.getConfig();
setClientConfig(config);
} catch (error) {
console.error(`ClientConfigService -> getConfig() ERROR: \n${error}`);
}
};
const updateServerStatus = async () => {
try {
const status = await ServerStatusService.getStatus();
setServerStatus(status);
if (status.online) {
setAppState(AppState.Online);
} else {
setAppState(AppState.Offline);
}
return status;
} catch (error) {
console.error(`serverStatusState -> getStatus() ERROR: \n${error}`);
return null;
}
};
const handleUserRegistration = async (optionalDisplayName?: string) => {
try {
setAppState(AppState.Registering);
const response = await ChatService.registerUser(optionalDisplayName);
console.log(`ChatService -> registerUser() response: \n${response}`);
const { accessToken: newAccessToken, displayName: newDisplayName } = response;
if (!newAccessToken) {
return;
}
console.log('setting access token', newAccessToken);
setAccessToken(newAccessToken);
// setLocalStorage('accessToken', newAccessToken);
setChatDisplayName(newDisplayName);
} catch (e) {
console.error(`ChatService -> registerUser() ERROR: \n${e}`);
}
};
const handleMessage = (message: SocketEvent) => {
switch (message.type) {
case MessageType.CONNECTED_USER_INFO:
handleConnectedClientInfoMessage(message as ConnectedClientInfoEvent);
break;
case MessageType.CHAT:
handleChatMessage(message as ChatEvent, chatMessages, setChatMessages);
break;
default:
console.error('Unknown socket message type: ', message.type);
}
};
const getChatHistory = async () => {
try {
const messages = await ChatService.getChatHistory(accessToken);
const updatedChatMessages = [...messages, ...chatMessages];
setChatMessages(updatedChatMessages);
} catch (error) {
console.error(`ChatService -> getChatHistory() ERROR: \n${error}`);
}
};
const startChat = async () => {
setChatState(ChatState.Loading);
try {
ws = new WebsocketService(accessToken, '/ws');
ws.handleMessage = handleMessage;
setWebsocketService(ws);
} catch (error) {
console.error(`ChatService -> startChat() ERROR: \n${error}`);
}
};
useEffect(() => {
updateClientConfig();
handleUserRegistration();
}, []);
useEffect(() => {
setInterval(() => {
updateServerStatus();
}, 5000);
updateServerStatus();
}, []);
useEffect(() => {
if (!accessToken) {
return;
}
getChatHistory();
startChat();
}, [accessToken]);
useEffect(() => {
const updatedChatState = getChatState(appState);
console.log('updatedChatState', updatedChatState);
setChatState(updatedChatState);
const updatedChatVisibility = getChatVisibilityState(appState);
console.log(
'app state: ',
AppState[appState],
'chat state:',
ChatState[updatedChatState],
'chat visibility:',
ChatVisibilityState[updatedChatVisibility],
);
setChatVisibility(updatedChatVisibility);
}, [appState]);
return null;
}