socket.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import { EventEmitter } from "eventemitter3";
  2. import logger from "./logger";
  3. import { SocketEventType, ServerMessageType } from "./enums";
  4. /**
  5. * An abstraction on top of WebSockets to provide fastest
  6. * possible connection for peers.
  7. */
  8. export class Socket extends EventEmitter {
  9. private _disconnected: boolean = true;
  10. private _id?: string;
  11. private _messagesQueue: Array<object> = [];
  12. private _socket?: WebSocket;
  13. private _wsPingTimer?: NodeJS.Timeout;
  14. private readonly _baseUrl: string;
  15. constructor(
  16. secure: any,
  17. host: string,
  18. port: number,
  19. path: string,
  20. key: string,
  21. private readonly pingInterval: number = 5000,
  22. ) {
  23. super();
  24. const wsProtocol = secure ? "wss://" : "ws://";
  25. this._baseUrl = wsProtocol + host + ":" + port + path + "peerjs?key=" + key;
  26. }
  27. start(id: string, token: string): void {
  28. this._id = id;
  29. const wsUrl = `${this._baseUrl}&id=${id}&token=${token}`;
  30. if (!!this._socket || !this._disconnected) {
  31. return;
  32. }
  33. this._socket = new WebSocket(wsUrl);
  34. this._disconnected = false;
  35. this._socket.onmessage = (event) => {
  36. let data;
  37. try {
  38. data = JSON.parse(event.data);
  39. logger.log("Server message received:", data);
  40. } catch (e) {
  41. logger.log("Invalid server message", event.data);
  42. return;
  43. }
  44. this.emit(SocketEventType.Message, data);
  45. };
  46. this._socket.onclose = (event) => {
  47. if (this._disconnected) {
  48. return;
  49. }
  50. logger.log("Socket closed.", event);
  51. this._cleanup();
  52. this._disconnected = true;
  53. this.emit(SocketEventType.Disconnected);
  54. };
  55. // Take care of the queue of connections if necessary and make sure Peer knows
  56. // socket is open.
  57. this._socket.onopen = () => {
  58. if (this._disconnected) {
  59. return;
  60. }
  61. this._sendQueuedMessages();
  62. logger.log("Socket open");
  63. this._scheduleHeartbeat();
  64. };
  65. }
  66. private _scheduleHeartbeat(): void {
  67. this._wsPingTimer = setTimeout(() => {
  68. this._sendHeartbeat();
  69. }, this.pingInterval);
  70. }
  71. private _sendHeartbeat(): void {
  72. if (!this._wsOpen()) {
  73. logger.log(`Cannot send heartbeat, because socket closed`);
  74. return;
  75. }
  76. const message = JSON.stringify({ type: ServerMessageType.Heartbeat });
  77. this._socket!.send(message);
  78. this._scheduleHeartbeat();
  79. }
  80. /** Is the websocket currently open? */
  81. private _wsOpen(): boolean {
  82. return !!this._socket && this._socket.readyState === 1;
  83. }
  84. /** Send queued messages. */
  85. private _sendQueuedMessages(): void {
  86. //Create copy of queue and clear it,
  87. //because send method push the message back to queue if smth will go wrong
  88. const copiedQueue = [...this._messagesQueue];
  89. this._messagesQueue = [];
  90. for (const message of copiedQueue) {
  91. this.send(message);
  92. }
  93. }
  94. /** Exposed send for DC & Peer. */
  95. send(data: any): void {
  96. if (this._disconnected) {
  97. return;
  98. }
  99. // If we didn't get an ID yet, we can't yet send anything so we should queue
  100. // up these messages.
  101. if (!this._id) {
  102. this._messagesQueue.push(data);
  103. return;
  104. }
  105. if (!data.type) {
  106. this.emit(SocketEventType.Error, "Invalid message");
  107. return;
  108. }
  109. if (!this._wsOpen()) {
  110. return;
  111. }
  112. const message = JSON.stringify(data);
  113. this._socket!.send(message);
  114. }
  115. close(): void {
  116. if (this._disconnected) {
  117. return;
  118. }
  119. this._cleanup();
  120. this._disconnected = true;
  121. }
  122. private _cleanup(): void {
  123. if (!!this._socket) {
  124. this._socket.onopen = this._socket.onmessage = this._socket.onclose = null;
  125. this._socket.close();
  126. this._socket = undefined;
  127. }
  128. clearTimeout(this._wsPingTimer!);
  129. }
  130. }