Files
noctis/src/types/websocket.ts

217 lines
5.5 KiB
TypeScript

export enum RoomConnectionState {
CONNECTING,
RECONNECTING,
CONNECTED,
DISCONNECTED,
}
export interface Room {
id: string | null;
participants: number;
connectionState: RoomConnectionState;
}
export enum WebSocketMessageType {
// room messages
CREATE_ROOM = "create",
JOIN_ROOM = "join",
LEAVE_ROOM = "leave",
CHECK_ROOM_EXISTS = "check",
REQUEST_CHALLENGE = "request-challenge",
// response messages
ROOM_CREATED = "created",
ROOM_JOINED = "joined",
ROOM_LEFT = "left",
ROOM_READY = "ready",
ROOM_STATUS = "status",
CHALLENGE = "challenge",
// webrtc messages
WEBRTC_OFFER = "offer",
WERTC_ANSWER = "answer",
WEBRTC_ICE_CANDIDATE = "ice-candidate",
ERROR = "error",
}
// TODO: name the interfaces better
export type WebSocketMessage =
| CreateRoomMessage
| JoinRoomMessage
| LeaveRoomMessage
| CheckRoomExistsMessage
| RequestChallengeMessage
| RoomCreatedMessage
| RoomJoinedMessage
| RoomLeftMessage
| RoomStatusMessage
| RoomReadyMessage
| ChallengeMessage
| OfferMessage
| AnswerMessage
| IceCandidateMessage
| ErrorMessage;
interface ErrorMessage {
type: WebSocketMessageType.ERROR;
data: string;
}
// ====== Query Messages ======
interface CreateRoomMessage {
type: WebSocketMessageType.CREATE_ROOM;
roomName?: string;
nonce: string;
challenge: string;
}
// TODO: this is used as a query message, but it's also used as a response message
interface JoinRoomMessage {
type: WebSocketMessageType.JOIN_ROOM;
roomId: string;
nonce?: string;
challenge?: string;
}
interface LeaveRoomMessage {
type: WebSocketMessageType.LEAVE_ROOM;
roomId: string;
}
interface CheckRoomExistsMessage {
type: WebSocketMessageType.CHECK_ROOM_EXISTS;
// if sha256(roomId + challenge + nonce) has a certain number of leading zeros, then we can give the status to the user
roomId: string;
nonce: string;
challenge: string;
}
interface RequestChallengeMessage {
type: WebSocketMessageType.REQUEST_CHALLENGE;
}
// ====== Response Messages ======
interface RoomCreatedMessage {
type: WebSocketMessageType.ROOM_CREATED;
data: string;
}
interface RoomJoinedMessage {
type: WebSocketMessageType.ROOM_JOINED;
roomId: string;
participants: number;
}
interface RoomLeftMessage {
type: WebSocketMessageType.ROOM_LEFT;
roomId: string;
}
interface RoomStatusMessage {
type: WebSocketMessageType.ROOM_STATUS;
roomId: string;
status: 'found' | 'not-found';
}
interface RoomReadyMessage {
type: WebSocketMessageType.ROOM_READY;
data: {
isInitiator: boolean;
};
}
interface ChallengeMessage {
type: WebSocketMessageType.CHALLENGE;
challenge: string;
difficulty: number;
}
// ====== WebRTC signaling messages ======
// as the server, we dont do anything with these messages other than relay them to the other peers in the room
interface OfferMessage {
type: WebSocketMessageType.WEBRTC_OFFER;
data: {
roomId: string;
sdp: RTCSessionDescriptionInit;
};
}
interface AnswerMessage {
type: WebSocketMessageType.WERTC_ANSWER;
data: {
roomId: string;
sdp: RTCSessionDescriptionInit;
};
}
interface IceCandidateMessage {
type: WebSocketMessageType.WEBRTC_ICE_CANDIDATE;
data: {
roomId: string;
candidate: RTCIceCandidateInit;
};
}
export interface SocketCallbacks {
onOpen: () => void;
onClose: () => void;
}
export class Socket {
public ws: WebSocket;
public addEventListener: typeof WebSocket.prototype.addEventListener;
public removeEventListener: typeof WebSocket.prototype.removeEventListener;
public close: typeof WebSocket.prototype.close;
// maps WebSocketMessageType to an array of functions that handle that message
// this allows for consumbers to subscribe to a specific message type and handle it themselves
private functionStack: Map<WebSocketMessageType, Function[]>;
constructor(webSocket: WebSocket) {
this.ws = webSocket;
this.ws.addEventListener("open", () => {
console.log("WebSocket opened");
});
this.functionStack = new Map();
this.ws.addEventListener("message", async (event) => {
console.log("WebSocket received message:", event.data);
const message: WebSocketMessage = JSON.parse(event.data);
if (this.functionStack.has(message.type)) {
for (let func of this.functionStack.get(message.type)!) {
func(message);
}
}
});
this.addEventListener = this.ws.addEventListener.bind(this.ws);
this.removeEventListener = this.ws.removeEventListener.bind(this.ws);
this.close = this.ws.close.bind(this.ws);
}
get readyState(): number {
return this.ws.readyState;
}
public send(message: WebSocketMessage) {
console.log("Sending message:", message);
this.ws.send(JSON.stringify(message));
}
public handleEvent<T extends WebSocketMessageType>(messageType: T, func: (message: WebSocketMessage & { type: T }) => void): () => void {
if (!this.functionStack.has(messageType)) {
this.functionStack.set(messageType, []);
}
this.functionStack.get(messageType)!.push(func);
return () => {
this.functionStack.get(messageType)!.splice(this.functionStack.get(messageType)!.indexOf(func), 1);
}
}
}