various fixes and additions, files, but fixed, code cleanup etc
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
import { json } from "@sveltejs/kit";
|
|
||||||
import { WebSocketServer } from "ws";
|
import { WebSocketServer } from "ws";
|
||||||
import type { WebSocket } from "ws";
|
import type { WebSocket } from "ws";
|
||||||
|
|
||||||
@@ -6,14 +5,20 @@ import type { WebSocket } from "ws";
|
|||||||
const rooms = new Map<string, WebSocket[]>();
|
const rooms = new Map<string, WebSocket[]>();
|
||||||
|
|
||||||
enum MessageType {
|
enum MessageType {
|
||||||
|
// requests
|
||||||
CREATE_ROOM = 'create',
|
CREATE_ROOM = 'create',
|
||||||
JOIN_ROOM = 'join',
|
JOIN_ROOM = 'join',
|
||||||
|
|
||||||
|
// responses
|
||||||
ROOM_CREATED = 'created',
|
ROOM_CREATED = 'created',
|
||||||
ROOM_JOINED = 'joined',
|
ROOM_JOINED = 'joined',
|
||||||
ROOM_READY = 'ready',
|
ROOM_READY = 'ready',
|
||||||
|
|
||||||
|
// webrtc
|
||||||
ICE_CANDIDATE = 'ice-candidate',
|
ICE_CANDIDATE = 'ice-candidate',
|
||||||
OFFER = 'offer',
|
OFFER = 'offer',
|
||||||
ANSWER = 'answer',
|
ANSWER = 'answer',
|
||||||
|
|
||||||
ERROR = 'error',
|
ERROR = 'error',
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,27 +29,50 @@ type Message = {
|
|||||||
|
|
||||||
function createRoom(socket: WebSocket): string {
|
function createRoom(socket: WebSocket): string {
|
||||||
let roomId = Math.random().toString(36).substring(2, 10);
|
let roomId = Math.random().toString(36).substring(2, 10);
|
||||||
rooms.set(roomId, [socket]);
|
rooms.set(roomId, []);
|
||||||
|
|
||||||
|
socket.send(JSON.stringify({ type: MessageType.ROOM_CREATED, data: roomId }));
|
||||||
|
|
||||||
|
joinRoom(roomId, socket);
|
||||||
|
|
||||||
return roomId;
|
return roomId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function joinRoom(roomId: string, socket: WebSocket) {
|
function joinRoom(roomId: string, socket: WebSocket) {
|
||||||
const room = rooms.get(roomId);
|
let room = rooms.get(roomId);
|
||||||
|
console.log(room?.length);
|
||||||
|
|
||||||
// should be unreachable
|
// should be unreachable
|
||||||
if (!room) {
|
if (!room) {
|
||||||
throw new Error(`Room ${roomId} does not exist`);
|
throw new Error(`Room ${roomId} does not exist`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (room.length == 2) {
|
||||||
|
socket.send(JSON.stringify({ type: MessageType.ERROR, data: 'Room is full' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// notify all clients in the room of the new client, except the client itself
|
// notify all clients in the room of the new client, except the client itself
|
||||||
room.forEach(client => {
|
room.forEach(client => {
|
||||||
client.send(JSON.stringify({ type: MessageType.JOIN_ROOM, data: roomId }));
|
client.send(JSON.stringify({ type: MessageType.JOIN_ROOM, data: roomId }));
|
||||||
});
|
});
|
||||||
room.push(socket);
|
room.push(socket);
|
||||||
|
|
||||||
// the client is now in the room and the peer knows about it
|
socket.addEventListener('close', (ev) => {
|
||||||
socket.send(JSON.stringify({ type: MessageType.ROOM_JOINED, data: null }));
|
room = rooms.get(roomId)
|
||||||
|
if (!room) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// for some reason, when you filter the array when the length is 1 it stays at 1, but we *know* that if its 1
|
||||||
|
// then when this client disconnects, the room should be deleted since the room is empty
|
||||||
|
if (room.length === 1) {
|
||||||
|
deleteRoom(roomId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rooms.set(roomId, room.filter(client => client !== ev.target));
|
||||||
|
});
|
||||||
|
|
||||||
// TODO: consider letting rooms get larger than 2 clients
|
// TODO: consider letting rooms get larger than 2 clients
|
||||||
if (room.length == 2) {
|
if (room.length == 2) {
|
||||||
@@ -63,8 +91,6 @@ export function confgiureWebsocketServer(ws: WebSocketServer) {
|
|||||||
ws.on('connection', socket => {
|
ws.on('connection', socket => {
|
||||||
// Handle messages from the client
|
// Handle messages from the client
|
||||||
socket.on('message', event => {
|
socket.on('message', event => {
|
||||||
console.log(event, typeof event);
|
|
||||||
|
|
||||||
let message;
|
let message;
|
||||||
|
|
||||||
if (event instanceof Buffer) { // Assuming JSON is sent as a string
|
if (event instanceof Buffer) { // Assuming JSON is sent as a string
|
||||||
@@ -92,8 +118,7 @@ export function confgiureWebsocketServer(ws: WebSocketServer) {
|
|||||||
switch (type) {
|
switch (type) {
|
||||||
case MessageType.CREATE_ROOM:
|
case MessageType.CREATE_ROOM:
|
||||||
// else, create a new room
|
// else, create a new room
|
||||||
const roomId = createRoom(socket);
|
createRoom(socket);
|
||||||
socket.send(JSON.stringify({ type: MessageType.ROOM_CREATED, data: roomId }));
|
|
||||||
break;
|
break;
|
||||||
case MessageType.JOIN_ROOM:
|
case MessageType.JOIN_ROOM:
|
||||||
// if join message has a roomId, join the room
|
// if join message has a roomId, join the room
|
||||||
@@ -109,6 +134,9 @@ export function confgiureWebsocketServer(ws: WebSocketServer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
joinRoom(message.data, socket);
|
joinRoom(message.data, socket);
|
||||||
|
|
||||||
|
// the client is now in the room and the peer knows about it
|
||||||
|
socket.send(JSON.stringify({ type: MessageType.ROOM_JOINED, data: null }));
|
||||||
break;
|
break;
|
||||||
case MessageType.OFFER:
|
case MessageType.OFFER:
|
||||||
case MessageType.ANSWER:
|
case MessageType.ANSWER:
|
||||||
@@ -130,10 +158,5 @@ export function confgiureWebsocketServer(ws: WebSocketServer) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle client disconnection
|
|
||||||
socket.on('close', () => {
|
|
||||||
// TODO: if this client was in a room, remove them from the room
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
} from "../utils/webrtcUtil";
|
} from "../utils/webrtcUtil";
|
||||||
|
|
||||||
let inputMessage: Writable<string> = writable("");
|
let inputMessage: Writable<string> = writable("");
|
||||||
|
let inputFile = writable(null);
|
||||||
|
let inputFileElement: HTMLInputElement;
|
||||||
|
|
||||||
function sendMessage() {
|
function sendMessage() {
|
||||||
if (!$peer) {
|
if (!$peer) {
|
||||||
@@ -17,13 +19,29 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$messages = [...$messages, `You: ${$inputMessage}`];
|
if (!$inputFile && !$inputMessage) {
|
||||||
$peer.send($inputMessage);
|
return;
|
||||||
$inputMessage = "";
|
}
|
||||||
|
|
||||||
|
if ($inputFile != null && $inputFile[0] !== undefined) {
|
||||||
|
$messages = [...$messages, `You: ${$inputFile[0].name}`];
|
||||||
|
$peer.send($inputFile[0]);
|
||||||
|
$inputFile = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($inputMessage) {
|
||||||
|
$messages = [...$messages, `You: ${$inputMessage}`];
|
||||||
|
$peer.send($inputMessage);
|
||||||
|
$inputMessage = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickFile() {
|
||||||
|
inputFileElement.click();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if $room && $connected}
|
{#if $room !== null && $connected === true}
|
||||||
{#if !$isRTCConnected}
|
{#if !$isRTCConnected}
|
||||||
<p>Waiting for peer to connect...</p>
|
<p>Waiting for peer to connect...</p>
|
||||||
{:else if !$dataChannelReady}
|
{:else if !$dataChannelReady}
|
||||||
@@ -34,7 +52,12 @@
|
|||||||
<p>{msg}</p>
|
<p>{msg}</p>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
bind:files={$inputFile}
|
||||||
|
bind:this={inputFileElement}
|
||||||
|
class="absolute opacity-0 -top-[9999px] -left-[9999px]"
|
||||||
|
/>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -43,6 +66,27 @@
|
|||||||
placeholder="Type your message..."
|
placeholder="Type your message..."
|
||||||
class="flex-grow p-2 rounded bg-gray-700 border border-gray-600 text-gray-100 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
class="flex-grow p-2 rounded bg-gray-700 border border-gray-600 text-gray-100 placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
on:click={pickFile}
|
||||||
|
disabled={!dataChannelReady}
|
||||||
|
aria-label="Pick file"
|
||||||
|
class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
><!-- Icon from Tabler Icons by Paweł Kuna - https://github.com/tabler/tabler-icons/blob/master/LICENSE --><path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="m15 7l-6.5 6.5a1.5 1.5 0 0 0 3 3L18 10a3 3 0 0 0-6-6l-6.5 6.5a4.5 4.5 0 0 0 9 9L21 13"
|
||||||
|
/></svg
|
||||||
|
>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
on:click={sendMessage}
|
on:click={sendMessage}
|
||||||
disabled={!dataChannelReady}
|
disabled={!dataChannelReady}
|
||||||
@@ -52,6 +96,4 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
|
||||||
<p>Waiting for peer to connect...</p>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -15,8 +15,23 @@ const callbacks = {
|
|||||||
console.log("Connected to peer");
|
console.log("Connected to peer");
|
||||||
isRTCConnected.set(true);
|
isRTCConnected.set(true);
|
||||||
},
|
},
|
||||||
|
//! TODO: come up with a more complex room system. This is largely for testing purposes
|
||||||
onMessage: (message: string | ArrayBuffer) => {
|
onMessage: (message: string | ArrayBuffer) => {
|
||||||
console.log("Received message:", message);
|
console.log("Received message:", message);
|
||||||
|
if (typeof message === 'object' && message instanceof Blob) {
|
||||||
|
// download the file
|
||||||
|
const url = URL.createObjectURL(message);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = message.name;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
messages.set([...get(messages), `Peer: ${message}`]);
|
messages.set([...get(messages), `Peer: ${message}`]);
|
||||||
},
|
},
|
||||||
onDataChannelOpen: () => {
|
onDataChannelOpen: () => {
|
||||||
@@ -67,8 +82,6 @@ export async function handleMessage(event: MessageEvent) {
|
|||||||
await get(peer)?.createOffer();
|
await get(peer)?.createOffer();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
default:
|
|
||||||
console.warn(`Unknown message type: ${message.type}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!get(peer)) {
|
if (!get(peer)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user