index.ts 5.7 KB

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