197 lines
5.8 KiB
TypeScript
197 lines
5.8 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?.();
|
|
});
|
|
});
|