245 lines
5.7 KiB
TypeScript
245 lines
5.7 KiB
TypeScript
import { describe, expect, it } from "@jest/globals";
|
|
|
|
import { Server, WebSocket } from "mock-socket";
|
|
import type { Server as HttpServer } from "node:http";
|
|
import { Realm } from "../../../src/models/realm";
|
|
import { WebSocketServer } from "../../../src/services/webSocketServer";
|
|
import { Errors, MessageType } from "../../../src/enums";
|
|
import { wait } from "../../utils";
|
|
|
|
type Destroyable<T> = T & { destroy?: () => Promise<void> };
|
|
|
|
const checkOpen = async (c: WebSocket): Promise<boolean> => {
|
|
return new Promise((resolve) => {
|
|
c.onmessage = (event: object & { data?: string }): void => {
|
|
const message = JSON.parse(event.data as string);
|
|
resolve(message.type === MessageType.OPEN);
|
|
};
|
|
});
|
|
};
|
|
|
|
const checkSequence = async (
|
|
c: WebSocket,
|
|
msgs: { type: MessageType; error?: Errors }[],
|
|
): Promise<boolean> => {
|
|
return new Promise((resolve) => {
|
|
const restMessages = [...msgs];
|
|
|
|
const finish = (success = false): void => {
|
|
resolve(success);
|
|
};
|
|
|
|
c.onmessage = (event: object & { data?: string }): void => {
|
|
const [mes] = restMessages;
|
|
|
|
if (!mes) {
|
|
return finish();
|
|
}
|
|
|
|
restMessages.shift();
|
|
|
|
const message = JSON.parse(event.data as string);
|
|
if (message.type !== mes.type) {
|
|
return finish();
|
|
}
|
|
|
|
const isOk = !mes.error || message.payload?.msg === mes.error;
|
|
|
|
if (!isOk) {
|
|
return finish();
|
|
}
|
|
|
|
if (restMessages.length === 0) {
|
|
finish(true);
|
|
}
|
|
};
|
|
});
|
|
};
|
|
|
|
const createTestServer = ({
|
|
realm,
|
|
config,
|
|
url,
|
|
}: {
|
|
realm: Realm;
|
|
config: { path: string; key: string; concurrent_limit: number };
|
|
url: string;
|
|
}): Destroyable<WebSocketServer> => {
|
|
const server = new Server(url) as Server & HttpServer;
|
|
const webSocketServer: Destroyable<WebSocketServer> = new WebSocketServer({
|
|
server,
|
|
realm,
|
|
config,
|
|
});
|
|
|
|
server.on(
|
|
"connection",
|
|
(
|
|
socket: WebSocket & {
|
|
on?: (eventName: string, callback: () => void) => void;
|
|
},
|
|
) => {
|
|
const s = webSocketServer.socketServer;
|
|
s.emit("connection", socket, { url: socket.url });
|
|
|
|
socket.onclose = (): void => {
|
|
const userId = socket.url
|
|
.split("?")[1]
|
|
?.split("&")
|
|
.find((p) => p.startsWith("id"))
|
|
?.split("=")[1];
|
|
|
|
if (!userId) return;
|
|
|
|
const client = realm.getClientById(userId);
|
|
|
|
const clientSocket = client?.getSocket();
|
|
|
|
if (!clientSocket) return;
|
|
|
|
(clientSocket as unknown as WebSocket).listeners[
|
|
"server::close"
|
|
]?.forEach((s: () => void) => s());
|
|
};
|
|
|
|
socket.onmessage = (event: object & { data?: string }): void => {
|
|
const userId = socket.url
|
|
.split("?")[1]
|
|
?.split("&")
|
|
.find((p) => p.startsWith("id"))
|
|
?.split("=")[1];
|
|
|
|
if (!userId) return;
|
|
|
|
const client = realm.getClientById(userId);
|
|
|
|
const clientSocket = client?.getSocket();
|
|
|
|
if (!clientSocket) return;
|
|
|
|
(clientSocket as unknown as WebSocket).listeners[
|
|
"server::message"
|
|
]?.forEach((s: (data: object) => void) => s(event));
|
|
};
|
|
},
|
|
);
|
|
|
|
webSocketServer.destroy = async (): Promise<void> => {
|
|
server.close();
|
|
};
|
|
|
|
return webSocketServer;
|
|
};
|
|
|
|
describe("WebSocketServer", () => {
|
|
it("should return valid path", () => {
|
|
const realm = new Realm();
|
|
const config = { path: "/", key: "testKey", concurrent_limit: 1 };
|
|
const config2 = { ...config, path: "path" };
|
|
const server = new Server("path1") as Server & HttpServer;
|
|
const server2 = new Server("path2") as Server & HttpServer;
|
|
|
|
const webSocketServer = new WebSocketServer({ server, realm, config });
|
|
|
|
expect(webSocketServer.path).toBe("/peerjs");
|
|
|
|
const webSocketServer2 = new WebSocketServer({
|
|
server: server2,
|
|
realm,
|
|
config: config2,
|
|
});
|
|
|
|
expect(webSocketServer2.path).toBe("path/peerjs");
|
|
|
|
server.stop();
|
|
server2.stop();
|
|
});
|
|
|
|
it(`should check client's params`, async () => {
|
|
const realm = new Realm();
|
|
const config = { path: "/", key: "testKey", concurrent_limit: 1 };
|
|
const fakeURL = "ws://localhost:8080/peerjs";
|
|
|
|
const getError = async (
|
|
url: string,
|
|
validError: Errors = Errors.INVALID_WS_PARAMETERS,
|
|
): Promise<boolean> => {
|
|
const webSocketServer = createTestServer({ url, realm, config });
|
|
|
|
const ws = new WebSocket(url);
|
|
|
|
const errorSent = await checkSequence(ws, [
|
|
{ type: MessageType.ERROR, error: validError },
|
|
]);
|
|
|
|
ws.close();
|
|
|
|
await webSocketServer.destroy?.();
|
|
|
|
return errorSent;
|
|
};
|
|
|
|
expect(await getError(fakeURL)).toBe(true);
|
|
expect(await getError(`${fakeURL}?key=${config.key}`)).toBe(true);
|
|
expect(await getError(`${fakeURL}?key=${config.key}&id=1`)).toBe(true);
|
|
expect(
|
|
await getError(
|
|
`${fakeURL}?key=notValidKey&id=userId&token=userToken`,
|
|
Errors.INVALID_KEY,
|
|
),
|
|
).toBe(true);
|
|
});
|
|
|
|
it(`should check concurrent limit`, async () => {
|
|
const realm = new Realm();
|
|
const config = { path: "/", key: "testKey", concurrent_limit: 1 };
|
|
const fakeURL = "ws://localhost:8080/peerjs";
|
|
|
|
const createClient = (id: string): Destroyable<WebSocket> => {
|
|
// id in the path ensures that all mock servers listen on different urls
|
|
const url = `${fakeURL}${id}?key=${config.key}&id=${id}&token=${id}`;
|
|
const webSocketServer = createTestServer({ url, realm, config });
|
|
const ws: Destroyable<WebSocket> = new WebSocket(url);
|
|
|
|
ws.destroy = async (): Promise<void> => {
|
|
ws.close();
|
|
|
|
wait(10);
|
|
|
|
webSocketServer.destroy?.();
|
|
|
|
wait(10);
|
|
|
|
ws.destroy = undefined;
|
|
};
|
|
|
|
return ws;
|
|
};
|
|
|
|
const c1 = createClient("1");
|
|
|
|
expect(await checkOpen(c1)).toBe(true);
|
|
|
|
const c2 = createClient("2");
|
|
|
|
expect(
|
|
await checkSequence(c2, [
|
|
{ type: MessageType.ERROR, error: Errors.CONNECTION_LIMIT_EXCEED },
|
|
]),
|
|
).toBe(true);
|
|
|
|
await c1.destroy?.();
|
|
await c2.destroy?.();
|
|
|
|
await wait(10);
|
|
|
|
expect(realm.getClientsIds().length).toBe(0);
|
|
|
|
const c3 = createClient("3");
|
|
|
|
expect(await checkOpen(c3)).toBe(true);
|
|
|
|
await c3.destroy?.();
|
|
});
|
|
});
|