socket.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { util } from "./util";
  2. import { EventEmitter } from "eventemitter3";
  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 readonly WEB_SOCKET_PING_INTERVAL = 20000;//ms
  10. private _disconnected = false;
  11. private _id: string;
  12. private _messagesQueue: Array<any> = [];
  13. private _wsUrl: string;
  14. private _socket: WebSocket;
  15. private _wsPingTimer: any;
  16. constructor(
  17. secure: any,
  18. host: string,
  19. port: number,
  20. path: string,
  21. key: string,
  22. ) {
  23. super();
  24. const wsProtocol = secure ? "wss://" : "ws://";
  25. this._wsUrl = wsProtocol + host + ":" + port + path + "peerjs?key=" + key;
  26. }
  27. /** Check in with ID or get one from server. */
  28. start(id: string, token: string): void {
  29. this._id = id;
  30. this._wsUrl += "&id=" + id + "&token=" + token;
  31. this._startWebSocket();
  32. }
  33. /** Start up websocket communications. */
  34. private _startWebSocket(): void {
  35. if (this._socket) {
  36. return;
  37. }
  38. this._socket = new WebSocket(this._wsUrl);
  39. this._socket.onmessage = (event) => {
  40. let data;
  41. try {
  42. data = JSON.parse(event.data);
  43. } catch (e) {
  44. util.log("Invalid server message", event.data);
  45. return;
  46. }
  47. this.emit(SocketEventType.Message, data);
  48. };
  49. this._socket.onclose = (event) => {
  50. util.log("Socket closed.", event);;
  51. this._disconnected = true;
  52. clearTimeout(this._wsPingTimer);
  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) return;
  59. this._sendQueuedMessages();
  60. util.log("Socket open");
  61. this._scheduleHeartbeat();
  62. };
  63. }
  64. private _scheduleHeartbeat(): void {
  65. this._wsPingTimer = setTimeout(() => { this._sendHeartbeat() }, this.WEB_SOCKET_PING_INTERVAL);
  66. }
  67. private _sendHeartbeat(): void {
  68. if (!this._wsOpen()) {
  69. util.log(`Cannot send heartbeat, because socket closed`);
  70. return;
  71. }
  72. const message = JSON.stringify({ type: ServerMessageType.Heartbeat });
  73. this._socket.send(message);
  74. this._scheduleHeartbeat();
  75. }
  76. /** Is the websocket currently open? */
  77. private _wsOpen(): boolean {
  78. return !!this._socket && this._socket.readyState == 1;
  79. }
  80. /** Send queued messages. */
  81. private _sendQueuedMessages(): void {
  82. //Create copy of queue and clear it,
  83. //because send method push the message back to queue if smth will go wrong
  84. const copiedQueue = [...this._messagesQueue];
  85. this._messagesQueue = [];
  86. for (const message of copiedQueue) {
  87. this.send(message);
  88. }
  89. }
  90. /** Exposed send for DC & Peer. */
  91. send(data: any): void {
  92. if (this._disconnected) {
  93. return;
  94. }
  95. // If we didn't get an ID yet, we can't yet send anything so we should queue
  96. // up these messages.
  97. if (!this._id) {
  98. this._messagesQueue.push(data);
  99. return;
  100. }
  101. if (!data.type) {
  102. this.emit(SocketEventType.Error, "Invalid message");
  103. return;
  104. }
  105. if (!this._wsOpen()) {
  106. return;
  107. }
  108. const message = JSON.stringify(data);
  109. this._socket.send(message);
  110. }
  111. close(): void {
  112. if (!this._disconnected && !!this._socket) {
  113. this._socket.close();
  114. this._disconnected = true;
  115. clearTimeout(this._wsPingTimer);
  116. }
  117. }
  118. }