180 lines
4.6 KiB
TypeScript
180 lines
4.6 KiB
TypeScript
import CBOR from "cbor";
|
|
import { useEffect, useState } from "react";
|
|
import useWebSocket, { ReadyState } from "react-use-websocket";
|
|
import * as Automerge from "@automerge/automerge";
|
|
import * as uuid from "uuid";
|
|
import {} from "libsodium";
|
|
import Upload from "./Upload";
|
|
import { wsUrl } from "./constants";
|
|
|
|
const connectionStatusMap = {
|
|
[ReadyState.CONNECTING]: "Connecting",
|
|
[ReadyState.OPEN]: "Open",
|
|
[ReadyState.CLOSING]: "Closing",
|
|
[ReadyState.CLOSED]: "Closed",
|
|
[ReadyState.UNINSTANTIATED]: "Uninstantiated",
|
|
};
|
|
|
|
export default function Room({ roomId }) {
|
|
const [readyState, setReadyState] = useState(ReadyState.CLOSED);
|
|
const [connectedClients, setConnectedClients] = useState([]);
|
|
const [clientId, setClientId] = useState<string | null>(null);
|
|
const [chats, setChats] = useState([]);
|
|
|
|
const [doc, setDoc] = useState(Automerge.init());
|
|
const [syncState, setSyncState] = useState(Automerge.initSyncState());
|
|
|
|
const [message, setMessage] = useState("");
|
|
const [addItemName, setAddItemName] = useState("");
|
|
|
|
function updateDoc(newDoc) {
|
|
setDoc(newDoc);
|
|
}
|
|
|
|
const {
|
|
sendMessage,
|
|
lastJsonMessage,
|
|
readyState: newReadyState,
|
|
} = useWebSocket(wsUrl, {
|
|
share: true,
|
|
onOpen: ({}) => {
|
|
console.log("Shiet, connected.");
|
|
},
|
|
onMessage: async (event) => {
|
|
const data = CBOR.decode(await event.data.arrayBuffer());
|
|
console.log("received", data);
|
|
|
|
if (data.type === "ServerHello") {
|
|
setClientId(uuid.stringify(data.client_id));
|
|
}
|
|
|
|
if (data.type === "RoomClientList") {
|
|
setConnectedClients(data.clients.map((x) => uuid.stringify(x)));
|
|
}
|
|
|
|
if (data.type === "ChatMessage") {
|
|
setChats([...chats, data]);
|
|
}
|
|
|
|
if (data.type === "Automerge") {
|
|
const [nextDoc, nextSyncState, patch] = Automerge.receiveSyncMessage(
|
|
doc,
|
|
syncState,
|
|
data.message[1]
|
|
);
|
|
setDoc(nextDoc);
|
|
setSyncState(nextSyncState);
|
|
console.log("patch", patch);
|
|
}
|
|
},
|
|
});
|
|
|
|
function sendWtfMessage(data) {
|
|
let cbor = CBOR.encode(data);
|
|
console.log(
|
|
"cbor-encoded",
|
|
[...new Uint8Array(cbor)]
|
|
.map((x) => x.toString(16).padStart(2, "0"))
|
|
.join(" ")
|
|
);
|
|
sendMessage(cbor);
|
|
}
|
|
|
|
useEffect(() => {
|
|
console.log("hellosu", readyState, newReadyState);
|
|
if (
|
|
readyState === ReadyState.CONNECTING &&
|
|
newReadyState === ReadyState.OPEN
|
|
) {
|
|
// On Open
|
|
sendWtfMessage({ type: "JoinRoom", room_id: roomId });
|
|
console.log("Sent connection message");
|
|
}
|
|
setReadyState(newReadyState);
|
|
}, [newReadyState]);
|
|
|
|
function onSubmit(e) {
|
|
e.preventDefault();
|
|
sendWtfMessage({
|
|
type: "ChatMessage",
|
|
timestamp: new Date().toISOString(),
|
|
message_id: uuid.v4(),
|
|
room_id: roomId,
|
|
author: clientId,
|
|
content: message,
|
|
});
|
|
setMessage("");
|
|
}
|
|
|
|
function addItem(e) {
|
|
e.preventDefault();
|
|
const newDoc = Automerge.change(doc, (doc) => {
|
|
if (!doc.items) doc.items = [];
|
|
doc.items.push({
|
|
id: uuid.v4(),
|
|
content: addItemName,
|
|
});
|
|
});
|
|
updateDoc(newDoc);
|
|
const [syncMessage, binary] = Automerge.generateSyncMessage(doc, syncState);
|
|
if (syncMessage)
|
|
sendWtfMessage({
|
|
type: "Automerge",
|
|
client_id: clientId,
|
|
room_id: roomId,
|
|
message: binary,
|
|
});
|
|
setAddItemName("");
|
|
}
|
|
|
|
const items = doc.items || [];
|
|
const connectionStatus = connectionStatusMap[readyState];
|
|
|
|
if (newReadyState !== ReadyState.OPEN) return <>Connecting...</>;
|
|
|
|
return (
|
|
<>
|
|
<Upload roomId={roomId} />
|
|
<hr />
|
|
<p>Room Id: {roomId}</p>
|
|
<p>Connection status: {connectionStatus}</p>
|
|
Connected:
|
|
<ul>
|
|
{connectedClients.map((x) => (
|
|
<li key={x}>{x}</li>
|
|
))}
|
|
</ul>
|
|
Messages:
|
|
<ul>
|
|
{chats.map((x) => (
|
|
<li key={x.message_id}>
|
|
[{x.timestamp}] {uuid.stringify(x.author)}: {x.content}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<form onSubmit={onSubmit}>
|
|
<input
|
|
type="text"
|
|
value={message}
|
|
onChange={(e) => setMessage(e.target.value)}
|
|
placeholder="Type a message..."
|
|
/>
|
|
</form>
|
|
Grubs:
|
|
<ul>
|
|
{items.map((x) => (
|
|
<li key={x.id}>{x.content}</li>
|
|
))}
|
|
</ul>
|
|
<form onSubmit={addItem}>
|
|
<input
|
|
type="text"
|
|
value={addItemName}
|
|
onChange={(e) => setAddItemName(e.target.value)}
|
|
placeholder="Type a message..."
|
|
/>
|
|
<button type="submit">add item</button>
|
|
</form>
|
|
</>
|
|
);
|
|
}
|