From 15becc512157716a13ff05d98e36c0f48abb1794 Mon Sep 17 00:00:00 2001 From: Gabe Kangas Date: Mon, 2 May 2022 22:13:36 -0700 Subject: [PATCH] Connect to websocket and start accepting messages --- core/chat/server.go | 2 +- web/components/stores/ClientConfigStore.tsx | 43 ++++++++++++++++--- .../eventhandlers/connectedclientinfo.ts | 5 +++ .../stores/eventhandlers/handleChatMessage.ts | 8 ++++ web/components/video/OwncastPlayer.tsx | 3 +- web/interfaces/chat-message.model.ts | 8 ++-- web/interfaces/socket-events.ts | 33 ++++++++++++++ web/interfaces/{user.ts => user.model.ts} | 0 web/services/chat-service.ts | 4 +- web/services/websocket-service.ts | 38 +++++++--------- web/utils/apis.ts | 2 - 11 files changed, 107 insertions(+), 39 deletions(-) create mode 100644 web/components/stores/eventhandlers/connectedclientinfo.ts create mode 100644 web/components/stores/eventhandlers/handleChatMessage.ts create mode 100644 web/interfaces/socket-events.ts rename web/interfaces/{user.ts => user.model.ts} (100%) diff --git a/core/chat/server.go b/core/chat/server.go index ac22864dd..30cd4b995 100644 --- a/core/chat/server.go +++ b/core/chat/server.go @@ -183,7 +183,7 @@ func (s *Server) HandleClientConnection(w http.ResponseWriter, r *http.Request) _, _ = w.Write([]byte(events.ErrorMaxConnectionsExceeded)) return } - + upgrader.CheckOrigin = func(r *http.Request) bool { return true } conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Debugln(err) diff --git a/web/components/stores/ClientConfigStore.tsx b/web/components/stores/ClientConfigStore.tsx index de5fb32e9..80a626595 100644 --- a/web/components/stores/ClientConfigStore.tsx +++ b/web/components/stores/ClientConfigStore.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-case-declarations */ import { useEffect, useLayoutEffect } from 'react'; import { atom, useRecoilState, useSetRecoilState } from 'recoil'; import { makeEmptyClientConfig, ClientConfig } from '../../interfaces/client-config.model'; @@ -13,6 +14,14 @@ import { getChatState, getChatVisibilityState, } from '../../interfaces/application-state'; +import { + SocketEvent, + ConnectedClientInfoEvent, + SocketMessageType, + ChatEvent, +} from '../../interfaces/socket-events'; +import handleConnectedClientInfoMessage from './eventhandlers/connectedclientinfo'; +import handleChatMessage from './eventhandlers/handleChatMessage'; // The config that comes from the API. export const clientConfigStateAtom = atom({ @@ -52,12 +61,14 @@ export const chatMessagesAtom = atom({ export function ClientConfigStore() { const setClientConfig = useSetRecoilState(clientConfigStateAtom); - const [appState, setAppState] = useRecoilState(appStateAtom); const setChatVisibility = useSetRecoilState(chatVisibilityAtom); - const [chatState, setChatState] = useRecoilState(chatStateAtom); + const setChatState = useSetRecoilState(chatStateAtom); const setChatMessages = useSetRecoilState(chatMessagesAtom); - const [accessToken, setAccessToken] = useRecoilState(accessTokenAtom); const setChatDisplayName = useSetRecoilState(chatDisplayNameAtom); + const [appState, setAppState] = useRecoilState(appStateAtom); + const [accessToken, setAccessToken] = useRecoilState(accessTokenAtom); + + let websocketService: WebsocketService; const updateClientConfig = async () => { try { @@ -89,8 +100,20 @@ export function ClientConfigStore() { } }; + const handleMessage = (message: SocketEvent) => { + switch (message.type) { + case SocketMessageType.CONNECTED_USER_INFO: + handleConnectedClientInfoMessage(message as ConnectedClientInfoEvent); + break; + case SocketMessageType.CHAT: + handleChatMessage(message as ChatEvent); + break; + default: + console.error('Unknown socket message type: ', message.type); + } + }; + const getChatHistory = async () => { - setChatState(ChatState.Loading); try { const messages = await ChatService.getChatHistory(accessToken); // console.log(`ChatService -> getChatHistory() messages: \n${JSON.stringify(messages)}`); @@ -98,6 +121,16 @@ export function ClientConfigStore() { } catch (error) { console.error(`ChatService -> getChatHistory() ERROR: \n${error}`); } + }; + + const startChat = async () => { + setChatState(ChatState.Loading); + try { + websocketService = new WebsocketService(accessToken, '/ws'); + websocketService.handleMessage = handleMessage; + } catch (error) { + console.error(`ChatService -> startChat() ERROR: \n${error}`); + } setChatState(ChatState.Available); }; @@ -111,8 +144,8 @@ export function ClientConfigStore() { return; } - console.log('access token changed', accessToken); getChatHistory(); + startChat(); }, [accessToken]); useEffect(() => { diff --git a/web/components/stores/eventhandlers/connectedclientinfo.ts b/web/components/stores/eventhandlers/connectedclientinfo.ts new file mode 100644 index 000000000..9c3c0fa26 --- /dev/null +++ b/web/components/stores/eventhandlers/connectedclientinfo.ts @@ -0,0 +1,5 @@ +import { ConnectedClientInfoEvent, SocketEvent } from '../../../interfaces/socket-events'; + +export default function handleConnectedClientInfoMessage(message: ConnectedClientInfoEvent) { + console.log('connected client', message); +} diff --git a/web/components/stores/eventhandlers/handleChatMessage.ts b/web/components/stores/eventhandlers/handleChatMessage.ts new file mode 100644 index 000000000..8a1e551d2 --- /dev/null +++ b/web/components/stores/eventhandlers/handleChatMessage.ts @@ -0,0 +1,8 @@ +import { + ChatEvent, + SocketEvent, +} from '../../../interfaces/socket-events'; + +export default function handleChatMessage(message: ChatEvent) { + console.log('chat message', message); +} diff --git a/web/components/video/OwncastPlayer.tsx b/web/components/video/OwncastPlayer.tsx index 3a2e64334..89b54d531 100644 --- a/web/components/video/OwncastPlayer.tsx +++ b/web/components/video/OwncastPlayer.tsx @@ -11,7 +11,8 @@ export default function OwncastPlayer(props) { autoplay: false, controls: true, responsive: true, - fluid: true, + fluid: false, + playsInline: true, liveui: true, preload: 'auto', controlBar: { diff --git a/web/interfaces/chat-message.model.ts b/web/interfaces/chat-message.model.ts index 0fe07e427..770427d6e 100644 --- a/web/interfaces/chat-message.model.ts +++ b/web/interfaces/chat-message.model.ts @@ -1,9 +1,7 @@ -import { User } from './user'; +import { SocketEvent } from './socket-events'; +import { User } from './user.model'; -export interface ChatMessage { - id: string; - type: string; - timestamp: Date; +export interface ChatMessage extends SocketEvent { user: User; body: string; } diff --git a/web/interfaces/socket-events.ts b/web/interfaces/socket-events.ts new file mode 100644 index 000000000..294a08082 --- /dev/null +++ b/web/interfaces/socket-events.ts @@ -0,0 +1,33 @@ +import { User } from './user.model'; + +export enum SocketMessageType { + CHAT = 'CHAT', + PING = 'PING', + NAME_CHANGE = 'NAME_CHANGE', + PONG = 'PONG', + SYSTEM = 'SYSTEM', + USER_JOINED = 'USER_JOINED', + CHAT_ACTION = 'CHAT_ACTION', + FEDIVERSE_ENGAGEMENT_FOLLOW = 'FEDIVERSE_ENGAGEMENT_FOLLOW', + FEDIVERSE_ENGAGEMENT_LIKE = 'FEDIVERSE_ENGAGEMENT_LIKE', + FEDIVERSE_ENGAGEMENT_REPOST = 'FEDIVERSE_ENGAGEMENT_REPOST', + CONNECTED_USER_INFO = 'CONNECTED_USER_INFO', + ERROR_USER_DISABLED = 'ERROR_USER_DISABLED', + ERROR_NEEDS_REGISTRATION = 'ERROR_NEEDS_REGISTRATION', + ERROR_MAX_CONNECTIONS_EXCEEDED = 'ERROR_MAX_CONNECTIONS_EXCEEDED', + VISIBILITY_UPDATE = 'VISIBILITY-UPDATE', +} + +export interface SocketEvent { + id: string; + timestamp: Date; + type: SocketMessageType; +} + +export interface ConnectedClientInfoEvent extends SocketEvent { + user: User; +} +export interface ChatEvent extends SocketEvent { + user: User; + body: string; +} \ No newline at end of file diff --git a/web/interfaces/user.ts b/web/interfaces/user.model.ts similarity index 100% rename from web/interfaces/user.ts rename to web/interfaces/user.model.ts diff --git a/web/services/chat-service.ts b/web/services/chat-service.ts index 07e18cf7e..a3dc9403e 100644 --- a/web/services/chat-service.ts +++ b/web/services/chat-service.ts @@ -24,8 +24,8 @@ class ChatService { body: JSON.stringify({ displayName: username }), }; - const response = await getUnauthedData(URL_CHAT_REGISTRATION, options); - return response; + const response = await getUnauthedData(URL_CHAT_REGISTRATION, options); + return response; } } diff --git a/web/services/websocket-service.ts b/web/services/websocket-service.ts index c0591c92c..fd58a815b 100644 --- a/web/services/websocket-service.ts +++ b/web/services/websocket-service.ts @@ -1,22 +1,7 @@ -import { message } from "antd"; +import { message } from 'antd'; +import { SocketMessageType } from '../interfaces/socket-events'; + -enum SocketMessageType { - CHAT = 'CHAT', - PING = 'PING', - NAME_CHANGE = 'NAME_CHANGE', - PONG = 'PONG', - SYSTEM = 'SYSTEM', - USER_JOINED = 'USER_JOINED', - CHAT_ACTION = 'CHAT_ACTION', - FEDIVERSE_ENGAGEMENT_FOLLOW = 'FEDIVERSE_ENGAGEMENT_FOLLOW', - FEDIVERSE_ENGAGEMENT_LIKE = 'FEDIVERSE_ENGAGEMENT_LIKE', - FEDIVERSE_ENGAGEMENT_REPOST = 'FEDIVERSE_ENGAGEMENT_REPOST', - CONNECTED_USER_INFO = 'CONNECTED_USER_INFO', - ERROR_USER_DISABLED = 'ERROR_USER_DISABLED', - ERROR_NEEDS_REGISTRATION = 'ERROR_NEEDS_REGISTRATION', - ERROR_MAX_CONNECTIONS_EXCEEDED = 'ERROR_MAX_CONNECTIONS_EXCEEDED', - VISIBILITY_UPDATE = 'VISIBILITY-UPDATE', -}; interface SocketMessage { type: SocketMessageType; @@ -32,9 +17,11 @@ export default class WebsocketService { websocketReconnectTimer: ReturnType; + handleMessage?: (message: SocketMessage) => void; + constructor(accessToken, path) { this.accessToken = accessToken; - this.path = 'http://localhost:8080/ws'; + this.path = path; // this.websocketReconnectTimer = null; // this.accessToken = accessToken; @@ -53,9 +40,10 @@ export default class WebsocketService { } createAndConnect() { - const url = new URL(this.path); + const url = new URL('ws://localhost:8080/ws'); url.searchParams.append('accessToken', this.accessToken); + console.log('connecting to ', url.toString()); const ws = new WebSocket(url.toString()); ws.onopen = this.onOpen.bind(this); // ws.onclose = this.onClose.bind(this); @@ -73,7 +61,8 @@ export default class WebsocketService { // On ws error just close the socket and let it re-connect again for now. onError(e) { - handleNetworkingError(`Socket error: ${JSON.parse(e)}`); + console.error(e) + handleNetworkingError(`Socket error: ${e}`); this.websocket.close(); // if (!this.isShutdown) { // this.scheduleReconnect(); @@ -95,6 +84,9 @@ export default class WebsocketService { for (let i = 0; i < messages.length; i++) { try { message = JSON.parse(messages[i]); + if (this.handleMessage) { + this.handleMessage(message); + } } catch (e) { console.error(e, e.data); return; @@ -133,6 +125,6 @@ export default class WebsocketService { function handleNetworkingError(error) { console.error( - `Chat has been disconnected and is likely not working for you. It's possible you were removed from chat. If this is a server configuration issue, visit troubleshooting steps to resolve. https://owncast.online/docs/troubleshooting/#chat-is-disabled: ${error}` + `Chat has been disconnected and is likely not working for you. It's possible you were removed from chat. If this is a server configuration issue, visit troubleshooting steps to resolve. https://owncast.online/docs/troubleshooting/#chat-is-disabled: ${error}`, ); -} \ No newline at end of file +} diff --git a/web/utils/apis.ts b/web/utils/apis.ts index a7c87bb50..01961f9cc 100644 --- a/web/utils/apis.ts +++ b/web/utils/apis.ts @@ -120,8 +120,6 @@ interface FetchOptions { auth?: boolean; } - - export async function fetchData(url: string, options?: FetchOptions) { const { data, method = 'GET', auth = true } = options || {};