socket.ts 3.5 KB

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