index.spec.ts 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import { describe, expect, it } from "@jest/globals";
  2. import { Server, WebSocket } from 'mock-socket';
  3. import type {Server as HttpServer} from 'node:http';
  4. import { Realm } from '../../../src/models/realm';
  5. import { WebSocketServer } from '../../../src/services/webSocketServer';
  6. import { Errors, MessageType } from '../../../src/enums';
  7. import { wait } from '../../utils';
  8. type Destroyable<T> = T & { destroy?: () => Promise<void>; };
  9. const checkOpen = async (c: WebSocket): Promise<boolean> => {
  10. return new Promise(resolve => {
  11. c.onmessage = (event: object & { data?: string; }): void => {
  12. const message = JSON.parse(event.data as string);
  13. resolve(message.type === MessageType.OPEN);
  14. };
  15. });
  16. };
  17. const checkSequence = async (c: WebSocket, msgs: { type: MessageType; error?: Errors; }[]): Promise<boolean> => {
  18. return new Promise(resolve => {
  19. const restMessages = [...msgs];
  20. const finish = (success = false): void => {
  21. resolve(success);
  22. };
  23. c.onmessage = (event: object & { data?: string; }): void => {
  24. const [mes] = restMessages;
  25. if (!mes) {
  26. return finish();
  27. }
  28. restMessages.shift();
  29. const message = JSON.parse(event.data as string);
  30. if (message.type !== mes.type) {
  31. return finish();
  32. }
  33. const isOk = !mes.error || message.payload?.msg === mes.error;
  34. if (!isOk) {
  35. return finish();
  36. }
  37. if (restMessages.length === 0) {
  38. finish(true);
  39. }
  40. };
  41. });
  42. };
  43. const createTestServer = ({ realm, config, url }: { realm: Realm; config: { path: string; key: string; concurrent_limit: number; }; url: string; }): Destroyable<WebSocketServer> => {
  44. const server = new Server(url) as Server & HttpServer;
  45. const webSocketServer: Destroyable<WebSocketServer> = new WebSocketServer({ server, realm, config });
  46. server.on('connection', (socket: WebSocket & { on?: (eventName: string, callback: () => void) => void; }) => {
  47. const s = webSocketServer.socketServer;
  48. s.emit('connection', socket, { url: socket.url });
  49. socket.onclose = (): void => {
  50. const userId = socket.url.split('?')[1]?.split('&').find(p => p.startsWith('id'))?.split('=')[1];
  51. if (!userId) return;
  52. const client = realm.getClientById(userId);
  53. const clientSocket = client?.getSocket();
  54. if (!clientSocket) return;
  55. (clientSocket as unknown as WebSocket).listeners['server::close']?.forEach((s: () => void) => s());
  56. };
  57. socket.onmessage = (event: object & { data?: string; }): void => {
  58. const userId = socket.url.split('?')[1]?.split('&').find(p => p.startsWith('id'))?.split('=')[1];
  59. if (!userId) return;
  60. const client = realm.getClientById(userId);
  61. const clientSocket = client?.getSocket();
  62. if (!clientSocket) return;
  63. (clientSocket as unknown as WebSocket).listeners['server::message']?.forEach((s: (data: object) => void) => s(event));
  64. };
  65. });
  66. webSocketServer.destroy = async (): Promise<void> => {
  67. server.close();
  68. };
  69. return webSocketServer;
  70. };
  71. describe('WebSocketServer', () => {
  72. it('should return valid path', () => {
  73. const realm = new Realm();
  74. const config = { path: '/', key: 'testKey', concurrent_limit: 1 };
  75. const config2 = { ...config, path: 'path' };
  76. const server = new Server('path1') as Server & HttpServer;
  77. const server2 = new Server('path2') as Server & HttpServer;
  78. const webSocketServer = new WebSocketServer({ server, realm, config });
  79. expect(webSocketServer.path).toBe('/peerjs');
  80. const webSocketServer2 = new WebSocketServer({ server: server2, realm, config: config2 });
  81. expect(webSocketServer2.path).toBe('path/peerjs');
  82. server.stop();
  83. server2.stop();
  84. });
  85. it(`should check client's params`, async () => {
  86. const realm = new Realm();
  87. const config = { path: '/', key: 'testKey', concurrent_limit: 1 };
  88. const fakeURL = 'ws://localhost:8080/peerjs';
  89. const getError = async (url: string, validError: Errors = Errors.INVALID_WS_PARAMETERS): Promise<boolean> => {
  90. const webSocketServer = createTestServer({ url, realm, config });
  91. const ws = new WebSocket(url);
  92. const errorSent = await checkSequence(ws, [{ type: MessageType.ERROR, error: validError }]);
  93. ws.close();
  94. await webSocketServer.destroy?.();
  95. return errorSent;
  96. };
  97. expect(await getError(fakeURL)).toBe(true);
  98. expect(await getError(`${fakeURL}?key=${config.key}`)).toBe(true);
  99. expect(await getError(`${fakeURL}?key=${config.key}&id=1`)).toBe(true);
  100. expect(await getError(`${fakeURL}?key=notValidKey&id=userId&token=userToken`, Errors.INVALID_KEY)).toBe(true);
  101. });
  102. it(`should check concurrent limit`, async () => {
  103. const realm = new Realm();
  104. const config = { path: '/', key: 'testKey', concurrent_limit: 1 };
  105. const fakeURL = 'ws://localhost:8080/peerjs';
  106. const createClient = (id: string): Destroyable<WebSocket> => {
  107. // id in the path ensures that all mock servers listen on different urls
  108. const url = `${fakeURL}${id}?key=${config.key}&id=${id}&token=${id}`;
  109. const webSocketServer = createTestServer({ url, realm, config });
  110. const ws: Destroyable<WebSocket> = new WebSocket(url);
  111. ws.destroy = async (): Promise<void> => {
  112. ws.close();
  113. wait(10);
  114. webSocketServer.destroy?.();
  115. wait(10);
  116. ws.destroy = undefined;
  117. };
  118. return ws;
  119. };
  120. const c1 = createClient('1');
  121. expect(await checkOpen(c1)).toBe(true);
  122. const c2 = createClient('2');
  123. expect(await checkSequence(c2, [
  124. { type: MessageType.ERROR, error: Errors.CONNECTION_LIMIT_EXCEED }
  125. ])).toBe(true);
  126. await c1.destroy?.();
  127. await c2.destroy?.();
  128. await wait(10);
  129. expect(realm.getClientsIds().length).toBe(0);
  130. const c3 = createClient('3');
  131. expect(await checkOpen(c3)).toBe(true);
  132. await c3.destroy?.();
  133. });
  134. });