websock.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import * as message from "./message.js";
  2. import * as rendezvous from "./rendezvous.js";
  3. import * as globals from "./globals";
  4. type Keys = "message" | "open" | "close" | "error";
  5. export default class Websock {
  6. _websocket: WebSocket;
  7. _eventHandlers: { [key in Keys]: Function };
  8. _buf: (rendezvous.RendezvousMessage | message.Message)[];
  9. _status: any;
  10. _latency: number;
  11. _secretKey: [Uint8Array, number, number] | undefined;
  12. _uri: string;
  13. _isRendezvous: boolean;
  14. constructor(uri: string, isRendezvous: boolean = true) {
  15. this._eventHandlers = {
  16. message: (_: any) => {},
  17. open: () => {},
  18. close: () => {},
  19. error: () => {},
  20. };
  21. this._uri = uri;
  22. this._status = "";
  23. this._buf = [];
  24. this._websocket = new WebSocket(uri);
  25. this._websocket.onmessage = this._recv_message.bind(this);
  26. this._websocket.binaryType = "arraybuffer";
  27. this._latency = new Date().getTime();
  28. this._isRendezvous = isRendezvous;
  29. }
  30. latency(): number {
  31. return this._latency;
  32. }
  33. setSecretKey(key: Uint8Array) {
  34. this._secretKey = [key, 0, 0];
  35. }
  36. sendMessage(json: message.DeepPartial<message.Message>) {
  37. let data = message.Message.encode(
  38. message.Message.fromPartial(json)
  39. ).finish();
  40. let k = this._secretKey;
  41. if (k) {
  42. k[1] += 1;
  43. data = globals.encrypt(data, k[1], k[0]);
  44. }
  45. this._websocket.send(data);
  46. }
  47. sendRendezvous(data: rendezvous.DeepPartial<rendezvous.RendezvousMessage>) {
  48. this._websocket.send(
  49. rendezvous.RendezvousMessage.encode(
  50. rendezvous.RendezvousMessage.fromPartial(data)
  51. ).finish()
  52. );
  53. }
  54. parseMessage(data: Uint8Array) {
  55. return message.Message.decode(data);
  56. }
  57. parseRendezvous(data: Uint8Array) {
  58. return rendezvous.RendezvousMessage.decode(data);
  59. }
  60. // Event Handlers
  61. off(evt: Keys) {
  62. this._eventHandlers[evt] = () => {};
  63. }
  64. on(evt: Keys, handler: Function) {
  65. this._eventHandlers[evt] = handler;
  66. }
  67. async open(timeout: number = 12000): Promise<Websock> {
  68. return new Promise((resolve, reject) => {
  69. setTimeout(() => {
  70. if (this._status != "open") {
  71. reject(this._status || "Timeout");
  72. }
  73. }, timeout);
  74. this._websocket.onopen = () => {
  75. this._latency = new Date().getTime() - this._latency;
  76. this._status = "open";
  77. console.debug(">> WebSock.onopen");
  78. if (this._websocket?.protocol) {
  79. console.info(
  80. "Server choose sub-protocol: " + this._websocket.protocol
  81. );
  82. }
  83. this._eventHandlers.open();
  84. console.info("WebSock.onopen");
  85. resolve(this);
  86. };
  87. this._websocket.onclose = (e) => {
  88. if (this._status == "open") {
  89. // e.code 1000 means that the connection was closed normally.
  90. //
  91. }
  92. this._status = e;
  93. console.error("WebSock.onclose: ");
  94. console.error(e);
  95. this._eventHandlers.close(e);
  96. reject("Reset by the peer");
  97. };
  98. this._websocket.onerror = (e: any) => {
  99. if (!this._status) {
  100. reject("Failed to connect to " + (this._isRendezvous ? "rendezvous" : "relay") + " server");
  101. return;
  102. }
  103. this._status = e;
  104. console.error("WebSock.onerror: ")
  105. console.error(e);
  106. this._eventHandlers.error(e);
  107. };
  108. });
  109. }
  110. async next(
  111. timeout = 12000
  112. ): Promise<rendezvous.RendezvousMessage | message.Message> {
  113. const func = (
  114. resolve: (value: rendezvous.RendezvousMessage | message.Message) => void,
  115. reject: (reason: any) => void,
  116. tm0: number
  117. ) => {
  118. if (this._buf.length) {
  119. resolve(this._buf[0]);
  120. this._buf.splice(0, 1);
  121. } else {
  122. if (this._status != "open") {
  123. reject(this._status);
  124. return;
  125. }
  126. if (new Date().getTime() > tm0 + timeout) {
  127. reject("Timeout");
  128. } else {
  129. setTimeout(() => func(resolve, reject, tm0), 1);
  130. }
  131. }
  132. };
  133. return new Promise((resolve, reject) => {
  134. func(resolve, reject, new Date().getTime());
  135. });
  136. }
  137. close() {
  138. this._status = "";
  139. if (this._websocket) {
  140. if (
  141. this._websocket.readyState === WebSocket.OPEN ||
  142. this._websocket.readyState === WebSocket.CONNECTING
  143. ) {
  144. console.info("Closing WebSocket connection");
  145. this._websocket.close();
  146. }
  147. this._websocket.onmessage = () => {};
  148. }
  149. }
  150. _recv_message(e: any) {
  151. if (e.data instanceof window.ArrayBuffer) {
  152. let bytes = new Uint8Array(e.data);
  153. const k = this._secretKey;
  154. if (k) {
  155. k[2] += 1;
  156. bytes = globals.decrypt(bytes, k[2], k[0]);
  157. }
  158. this._buf.push(
  159. this._isRendezvous
  160. ? this.parseRendezvous(bytes)
  161. : this.parseMessage(bytes)
  162. );
  163. }
  164. this._eventHandlers.message(e.data);
  165. }
  166. }