websocket stuff

This commit is contained in:
vel 2022-01-26 22:30:05 -08:00
parent 013e6c5b01
commit cad2d85ce4
Signed by: velvox
GPG Key ID: 1C8200C1D689CEF5
16 changed files with 195 additions and 89 deletions

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode"
}

View File

@ -5,8 +5,19 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="8a64704d-5500-41a6-aa4c-e275933fc58c" name="Changes" comment=""> <list default="true" id="8a64704d-5500-41a6-aa4c-e275933fc58c" name="Changes" comment="">
<change beforePath="$PROJECT_DIR$/../frontend/src/components/Player.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/components/Player.tsx" afterDir="false" /> <change afterPath="$PROJECT_DIR$/Caddyfile" afterDir="false" />
<change afterPath="$PROJECT_DIR$/docker-compose.yml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/internal/ws/handlers.go" beforeDir="false" afterPath="$PROJECT_DIR$/internal/ws/handlers.go" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/.env.example" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/.env.example" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/interfaces/IMessage.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/interfaces/IMessage.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/interfaces/Identity.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/interfaces/Identity.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/pages/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/pages/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/pages/player.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/pages/player.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/../frontend/src/pages/player.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/pages/player.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/src/util/Message.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/src/util/Message.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/types/environment.d.ts" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/types/environment.d.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../frontend/yarn.lock" beforeDir="false" afterPath="$PROJECT_DIR$/../frontend/yarn.lock" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />

4
backend/Caddyfile Normal file
View File

@ -0,0 +1,4 @@
localhost:3001
file_server
reverse_proxy 127.0.0.1:8080

View File

@ -0,0 +1,3 @@
version: "3.9"
services:
caddy:

View File

@ -3,12 +3,15 @@ package ws
//todo better data deserialization //todo better data deserialization
type IdentityData struct { type IdentityData struct {
ClientID string `json:"clientId"` ClientID string `json:"clientID"`
User User `json:"user"` User User `json:"user"`
} }
func handleIdentifyEvent(message *Message) { func handleIdentifyEvent(message *Message) {
d := message.Data.(map[string]interface{}) d := message.Data.(map[string]interface{})
if id, ok := d["clientID"]; ok {
log.Infof("Client %s has sent identify event", id.(string))
}
m := Message{ m := Message{
MessageData: MessageData{ MessageData: MessageData{
Type: Identify, Type: Identify,

View File

@ -1,5 +1,6 @@
DISCORD_ID= DISCORD_ID=
DISCORD_SECRET= DISCORD_SECRET=
SECRET= SECRET=
CLIENT_ID= NEXT_PUBLIC_CLIENT_ID=
NEXTAUTH_URL= NEXTAUTH_URL=
NEXT_PUBLIC_WS_URI=

View File

@ -22,7 +22,8 @@
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-player": "^2.9.0", "react-player": "^2.9.0",
"uuid": "^8.3.2" "uuid": "^8.3.2",
"ws": "^8.4.2"
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "^12.0.8", "@next/bundle-analyzer": "^12.0.8",
@ -31,6 +32,7 @@
"@types/react-dom": "^17.0.3", "@types/react-dom": "^17.0.3",
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"@types/websocket": "^1.0.4", "@types/websocket": "^1.0.4",
"@types/ws": "^8.2.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"typescript": "4.3.2" "typescript": "4.3.2"
}, },

View File

@ -0,0 +1,23 @@
import { User } from "next-auth";
import { useEffect, useState } from "react";
import PlayerSocket from "../ws/websocket";
interface useWSProps {
user: User;
}
// todo write websocket reconnector
const useWS = ({ user }: useWSProps) => {
// todo checkout usecallback
const [socket, setSocket] = useState<PlayerSocket>();
useEffect(() => {
let socket = new PlayerSocket(user);
setSocket(socket);
return () => {
return socket.close();
};
}, []);
return socket;
};
export default useWS;

View File

@ -8,7 +8,7 @@ export enum MessageTypes {
interface IMessage { interface IMessage {
type: MessageTypes; type: MessageTypes;
data?: Record<string, unknown>; data?: unknown;
} }
export default IMessage; export default IMessage;

View File

@ -6,3 +6,5 @@ interface IdentityData {
playHead?: number; playHead?: number;
user: IUser; user: IUser;
} }
export default IdentityData;

View File

@ -1,6 +1,7 @@
import { Button, Link as ChakraLink, Text } from "@chakra-ui/react"; import { Button, Link as ChakraLink, Text } from "@chakra-ui/react";
import { GetServerSideProps, NextPage } from "next"; import { GetServerSideProps, NextPage } from "next";
import { getSession, signIn } from "next-auth/react"; import { getSession, signIn } from "next-auth/react";
import Head from "next/head";
import Link from "next/link"; import Link from "next/link";
import React from "react"; import React from "react";
import { Container } from "../components/Container"; import { Container } from "../components/Container";
@ -10,26 +11,30 @@ import { Main } from "../components/Main";
const Index: NextPage = () => { const Index: NextPage = () => {
return ( return (
<Container height="100vh"> <>
<Hero /> <Head>
<Main> <title>Watch Together</title>
<Button </Head>
maxWidth="200" <Container height="100vh">
alignSelf="center" <Hero />
onClick={() => signIn("discord")} <Main>
disabled <Button
> maxWidth="200"
Login With Discord alignSelf="center"
</Button> onClick={() => signIn("discord")}
</Main> >
<Footer> Login With Discord
<ChakraLink> </Button>
<Link href="https://velvox.dev"> </Main>
<Text>&copy;2022 Velvox</Text> <Footer>
</Link> <ChakraLink>
</ChakraLink> <Link href="https://velvox.dev">
</Footer> <Text>&copy;2022 Velvox</Text>
</Container> </Link>
</ChakraLink>
</Footer>
</Container>
</>
); );
}; };

View File

@ -1,14 +1,12 @@
import consola from "consola";
import { GetServerSideProps, NextPage } from "next"; import { GetServerSideProps, NextPage } from "next";
import { Session, User } from "next-auth"; import { User } from "next-auth";
import { getSession, useSession } from "next-auth/react"; import { getSession } from "next-auth/react";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import React, { useEffect, useRef } from "react"; import Head from "next/head";
import React, { useRef } from "react";
import ReactPlayer from "react-player"; import ReactPlayer from "react-player";
import { Container } from "../components/Container"; import { Container } from "../components/Container";
import { MessageTypes } from "../interfaces/IMessage"; import useWS from "../hooks/useWS";
import Message from "../util/Message";
import MessageUtil from "../util/MessageUtil";
const Player = dynamic(() => import("../components/Player"), { ssr: false }); const Player = dynamic(() => import("../components/Player"), { ssr: false });
@ -17,58 +15,52 @@ interface PlayerPageProps {
user: User; user: User;
} }
const pingEvent = (ws: WebSocket) => {
let interval = setInterval(() => {
if (ws.readyState === ws.CLOSED) {
clearInterval(interval);
return;
}
console.log("running ping event");
ws.send(MessageUtil.encode(new Message(MessageTypes.Ping)));
}, 20000);
};
const PlayerPage: NextPage<PlayerPageProps> = ({ URI, user }) => { const PlayerPage: NextPage<PlayerPageProps> = ({ URI, user }) => {
const playerRef = useRef<ReactPlayer>(); const playerRef = useRef<ReactPlayer>();
consola.wrapAll(); const socket = useWS({ user });
useEffect(() => { // useEffect(() => {
if (typeof window === "undefined") return; // if (typeof window === "undefined") return;
const ws = new WebSocket(URI); // const ws = new WebSocket(URI);
ws.onopen = () => { // ws.onopen = () => {
ws.send( // ws.send(
MessageUtil.encode( // MessageUtil.encode(
new Message(MessageTypes.Identify, { // new Message(MessageTypes.Identify, {
clientID: process.env.CLIENT_ID, // clientID: process.env.CLIENT_ID,
user: { // user: {
ID: user.id, // ID: user.id,
Name: user.name, // Name: user.name,
}, // },
}) // })
) // )
); // );
pingEvent(ws); // pingEvent(ws);
}; // };
ws.onmessage = (event) => { // ws.onmessage = (event) => {
console.log(event); // console.log(event);
console.log(JSON.parse(event.data)); // console.log(JSON.parse(event.data));
}; // };
ws.onclose = () => { // ws.onclose = () => {
ws.close(); // ws.close();
}; // };
ws.onerror = (err) => { // ws.onerror = (err) => {
console.log(err); // console.log(err);
return () => { // return () => {
ws.close(); // ws.close();
}; // };
}; // };
return () => { // return () => {
ws.close(); // ws.close();
}; // };
}, []); // }, []);
return ( return (
<Container height="100vh"> <>
<Player id="" ref={playerRef} /> <Head>
</Container> <title>Watch Together</title>
</Head>
<Container height="100vh">
<Player id="" ref={playerRef} />
</Container>
</>
); );
}; };
@ -84,7 +76,6 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
} }
return { return {
props: { props: {
URI: process.env.WS_URI,
user: session.user, user: session.user,
}, },
}; };

View File

@ -1,10 +1,7 @@
import IMessage, { MessageTypes } from "../interfaces/IMessage"; import IMessage, { MessageTypes } from "../interfaces/IMessage";
export default class Message implements IMessage { export default class Message implements IMessage {
constructor( constructor(public type: MessageTypes, public data?: unknown) {}
public type: MessageTypes,
public data?: Record<string, unknown>
) {}
toJSON(): Record<string, unknown> { toJSON(): Record<string, unknown> {
return { return {

View File

@ -0,0 +1,49 @@
// nice and easy way to get types for the
import { User } from "next-auth";
import IdentityData from "../interfaces/Identity";
import { MessageTypes } from "../interfaces/IMessage";
import Message from "../util/Message";
import MessageUtil from "../util/MessageUtil";
// browser socket
let Websocket: typeof WebSocket;
if (typeof window !== "undefined") {
Websocket = window.WebSocket;
} else {
Websocket = require("ws");
}
export default class PlayerSocket extends Websocket {
constructor(private user: User) {
super(process.env.NEXT_PUBLIC_WS_URI);
this.onopen = this.onOpen;
}
onOpen() {
this.send(
MessageUtil.encode(
new Message(MessageTypes.Identify, {
clientID: process.env.NEXT_PUBLIC_CLIENT_ID,
user: {
id: this.user.id,
name: this.user.name,
},
} as IdentityData)
)
);
this.pingEvent();
}
pingEvent() {
let interval = setInterval(() => {
if (!this.open) {
clearInterval(interval);
return;
}
console.log("[WS] running ping event");
this.send(MessageUtil.encode(new Message(MessageTypes.Ping)));
}, 20000);
}
get open() {
return this.readyState === this.OPEN;
}
}

View File

@ -3,7 +3,7 @@ declare namespace NodeJS {
DISCORD_ID: string; DISCORD_ID: string;
DISCORD_SECRET: string; DISCORD_SECRET: string;
SECRET: string; SECRET: string;
CLIENT_ID: string; NEXT_PUBLIC_CLIENT_ID: string;
WS_URI: string; NEXT_PUBLIC_WS_URI: string;
} }
} }

View File

@ -1029,6 +1029,13 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/ws@^8.2.2":
version "8.2.2"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.2.2.tgz#7c5be4decb19500ae6b3d563043cd407bf366c21"
integrity sha512-NOn5eIcgWLOo6qW8AcuLZ7G8PycXu0xTxxkS6Q18VWFxgPUSOwV0pBj2a/4viNZVu25i7RIB7GttdkAIUUXOOg==
dependencies:
"@types/node" "*"
acorn-walk@^8.0.0: acorn-walk@^8.0.0:
version "8.2.0" version "8.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
@ -3227,6 +3234,11 @@ ws@^7.3.1:
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==
ws@^8.4.2:
version "8.4.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b"
integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==
xtend@^4.0.2: xtend@^4.0.2:
version "4.0.2" version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"