WebSocketTransport.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. "use strict";
  2. // Licensed to the .NET Foundation under one or more agreements.
  3. // The .NET Foundation licenses this file to you under the MIT license.
  4. Object.defineProperty(exports, "__esModule", { value: true });
  5. exports.WebSocketTransport = void 0;
  6. const HeaderNames_1 = require("./HeaderNames");
  7. const ILogger_1 = require("./ILogger");
  8. const ITransport_1 = require("./ITransport");
  9. const Utils_1 = require("./Utils");
  10. /** @private */
  11. class WebSocketTransport {
  12. constructor(httpClient, accessTokenFactory, logger, logMessageContent, webSocketConstructor, headers) {
  13. this._logger = logger;
  14. this._accessTokenFactory = accessTokenFactory;
  15. this._logMessageContent = logMessageContent;
  16. this._webSocketConstructor = webSocketConstructor;
  17. this._httpClient = httpClient;
  18. this.onreceive = null;
  19. this.onclose = null;
  20. this._headers = headers;
  21. }
  22. async connect(url, transferFormat) {
  23. Utils_1.Arg.isRequired(url, "url");
  24. Utils_1.Arg.isRequired(transferFormat, "transferFormat");
  25. Utils_1.Arg.isIn(transferFormat, ITransport_1.TransferFormat, "transferFormat");
  26. this._logger.log(ILogger_1.LogLevel.Trace, "(WebSockets transport) Connecting.");
  27. let token;
  28. if (this._accessTokenFactory) {
  29. token = await this._accessTokenFactory();
  30. }
  31. return new Promise((resolve, reject) => {
  32. url = url.replace(/^http/, "ws");
  33. let webSocket;
  34. const cookies = this._httpClient.getCookieString(url);
  35. let opened = false;
  36. if (Utils_1.Platform.isNode || Utils_1.Platform.isReactNative) {
  37. const headers = {};
  38. const [name, value] = Utils_1.getUserAgentHeader();
  39. headers[name] = value;
  40. if (token) {
  41. headers[HeaderNames_1.HeaderNames.Authorization] = `Bearer ${token}`;
  42. }
  43. if (cookies) {
  44. headers[HeaderNames_1.HeaderNames.Cookie] = cookies;
  45. }
  46. // Only pass headers when in non-browser environments
  47. webSocket = new this._webSocketConstructor(url, undefined, {
  48. headers: { ...headers, ...this._headers },
  49. });
  50. }
  51. else {
  52. if (token) {
  53. url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`;
  54. }
  55. }
  56. if (!webSocket) {
  57. // Chrome is not happy with passing 'undefined' as protocol
  58. webSocket = new this._webSocketConstructor(url);
  59. }
  60. if (transferFormat === ITransport_1.TransferFormat.Binary) {
  61. webSocket.binaryType = "arraybuffer";
  62. }
  63. webSocket.onopen = (_event) => {
  64. this._logger.log(ILogger_1.LogLevel.Information, `WebSocket connected to ${url}.`);
  65. this._webSocket = webSocket;
  66. opened = true;
  67. resolve();
  68. };
  69. webSocket.onerror = (event) => {
  70. let error = null;
  71. // ErrorEvent is a browser only type we need to check if the type exists before using it
  72. if (typeof ErrorEvent !== "undefined" && event instanceof ErrorEvent) {
  73. error = event.error;
  74. }
  75. else {
  76. error = "There was an error with the transport";
  77. }
  78. this._logger.log(ILogger_1.LogLevel.Information, `(WebSockets transport) ${error}.`);
  79. };
  80. webSocket.onmessage = (message) => {
  81. this._logger.log(ILogger_1.LogLevel.Trace, `(WebSockets transport) data received. ${Utils_1.getDataDetail(message.data, this._logMessageContent)}.`);
  82. if (this.onreceive) {
  83. try {
  84. this.onreceive(message.data);
  85. }
  86. catch (error) {
  87. this._close(error);
  88. return;
  89. }
  90. }
  91. };
  92. webSocket.onclose = (event) => {
  93. // Don't call close handler if connection was never established
  94. // We'll reject the connect call instead
  95. if (opened) {
  96. this._close(event);
  97. }
  98. else {
  99. let error = null;
  100. // ErrorEvent is a browser only type we need to check if the type exists before using it
  101. if (typeof ErrorEvent !== "undefined" && event instanceof ErrorEvent) {
  102. error = event.error;
  103. }
  104. else {
  105. error = "WebSocket failed to connect. The connection could not be found on the server,"
  106. + " either the endpoint may not be a SignalR endpoint,"
  107. + " the connection ID is not present on the server, or there is a proxy blocking WebSockets."
  108. + " If you have multiple servers check that sticky sessions are enabled.";
  109. }
  110. reject(new Error(error));
  111. }
  112. };
  113. });
  114. }
  115. send(data) {
  116. if (this._webSocket && this._webSocket.readyState === this._webSocketConstructor.OPEN) {
  117. this._logger.log(ILogger_1.LogLevel.Trace, `(WebSockets transport) sending data. ${Utils_1.getDataDetail(data, this._logMessageContent)}.`);
  118. this._webSocket.send(data);
  119. return Promise.resolve();
  120. }
  121. return Promise.reject("WebSocket is not in the OPEN state");
  122. }
  123. stop() {
  124. if (this._webSocket) {
  125. // Manually invoke onclose callback inline so we know the HttpConnection was closed properly before returning
  126. // This also solves an issue where websocket.onclose could take 18+ seconds to trigger during network disconnects
  127. this._close(undefined);
  128. }
  129. return Promise.resolve();
  130. }
  131. _close(event) {
  132. // webSocket will be null if the transport did not start successfully
  133. if (this._webSocket) {
  134. // Clear websocket handlers because we are considering the socket closed now
  135. this._webSocket.onclose = () => { };
  136. this._webSocket.onmessage = () => { };
  137. this._webSocket.onerror = () => { };
  138. this._webSocket.close();
  139. this._webSocket = undefined;
  140. }
  141. this._logger.log(ILogger_1.LogLevel.Trace, "(WebSockets transport) socket closed.");
  142. if (this.onclose) {
  143. if (this._isCloseEvent(event) && (event.wasClean === false || event.code !== 1000)) {
  144. this.onclose(new Error(`WebSocket closed with status code: ${event.code} (${event.reason || "no reason given"}).`));
  145. }
  146. else if (event instanceof Error) {
  147. this.onclose(event);
  148. }
  149. else {
  150. this.onclose();
  151. }
  152. }
  153. }
  154. _isCloseEvent(event) {
  155. return event && typeof event.wasClean === "boolean" && typeof event.code === "number";
  156. }
  157. }
  158. exports.WebSocketTransport = WebSocketTransport;
  159. //# sourceMappingURL=WebSocketTransport.js.map