LongPollingTransport.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  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.LongPollingTransport = void 0;
  6. const AbortController_1 = require("./AbortController");
  7. const Errors_1 = require("./Errors");
  8. const ILogger_1 = require("./ILogger");
  9. const ITransport_1 = require("./ITransport");
  10. const Utils_1 = require("./Utils");
  11. // Not exported from 'index', this type is internal.
  12. /** @private */
  13. class LongPollingTransport {
  14. constructor(httpClient, logger, options) {
  15. this._httpClient = httpClient;
  16. this._logger = logger;
  17. this._pollAbort = new AbortController_1.AbortController();
  18. this._options = options;
  19. this._running = false;
  20. this.onreceive = null;
  21. this.onclose = null;
  22. }
  23. // This is an internal type, not exported from 'index' so this is really just internal.
  24. get pollAborted() {
  25. return this._pollAbort.aborted;
  26. }
  27. async connect(url, transferFormat) {
  28. Utils_1.Arg.isRequired(url, "url");
  29. Utils_1.Arg.isRequired(transferFormat, "transferFormat");
  30. Utils_1.Arg.isIn(transferFormat, ITransport_1.TransferFormat, "transferFormat");
  31. this._url = url;
  32. this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) Connecting.");
  33. // Allow binary format on Node and Browsers that support binary content (indicated by the presence of responseType property)
  34. if (transferFormat === ITransport_1.TransferFormat.Binary &&
  35. (typeof XMLHttpRequest !== "undefined" && typeof new XMLHttpRequest().responseType !== "string")) {
  36. throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported.");
  37. }
  38. const [name, value] = Utils_1.getUserAgentHeader();
  39. const headers = { [name]: value, ...this._options.headers };
  40. const pollOptions = {
  41. abortSignal: this._pollAbort.signal,
  42. headers,
  43. timeout: 100000,
  44. withCredentials: this._options.withCredentials,
  45. };
  46. if (transferFormat === ITransport_1.TransferFormat.Binary) {
  47. pollOptions.responseType = "arraybuffer";
  48. }
  49. // Make initial long polling request
  50. // Server uses first long polling request to finish initializing connection and it returns without data
  51. const pollUrl = `${url}&_=${Date.now()}`;
  52. this._logger.log(ILogger_1.LogLevel.Trace, `(LongPolling transport) polling: ${pollUrl}.`);
  53. const response = await this._httpClient.get(pollUrl, pollOptions);
  54. if (response.statusCode !== 200) {
  55. this._logger.log(ILogger_1.LogLevel.Error, `(LongPolling transport) Unexpected response code: ${response.statusCode}.`);
  56. // Mark running as false so that the poll immediately ends and runs the close logic
  57. this._closeError = new Errors_1.HttpError(response.statusText || "", response.statusCode);
  58. this._running = false;
  59. }
  60. else {
  61. this._running = true;
  62. }
  63. this._receiving = this._poll(this._url, pollOptions);
  64. }
  65. async _poll(url, pollOptions) {
  66. try {
  67. while (this._running) {
  68. try {
  69. const pollUrl = `${url}&_=${Date.now()}`;
  70. this._logger.log(ILogger_1.LogLevel.Trace, `(LongPolling transport) polling: ${pollUrl}.`);
  71. const response = await this._httpClient.get(pollUrl, pollOptions);
  72. if (response.statusCode === 204) {
  73. this._logger.log(ILogger_1.LogLevel.Information, "(LongPolling transport) Poll terminated by server.");
  74. this._running = false;
  75. }
  76. else if (response.statusCode !== 200) {
  77. this._logger.log(ILogger_1.LogLevel.Error, `(LongPolling transport) Unexpected response code: ${response.statusCode}.`);
  78. // Unexpected status code
  79. this._closeError = new Errors_1.HttpError(response.statusText || "", response.statusCode);
  80. this._running = false;
  81. }
  82. else {
  83. // Process the response
  84. if (response.content) {
  85. this._logger.log(ILogger_1.LogLevel.Trace, `(LongPolling transport) data received. ${Utils_1.getDataDetail(response.content, this._options.logMessageContent)}.`);
  86. if (this.onreceive) {
  87. this.onreceive(response.content);
  88. }
  89. }
  90. else {
  91. // This is another way timeout manifest.
  92. this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) Poll timed out, reissuing.");
  93. }
  94. }
  95. }
  96. catch (e) {
  97. if (!this._running) {
  98. // Log but disregard errors that occur after stopping
  99. this._logger.log(ILogger_1.LogLevel.Trace, `(LongPolling transport) Poll errored after shutdown: ${e.message}`);
  100. }
  101. else {
  102. if (e instanceof Errors_1.TimeoutError) {
  103. // Ignore timeouts and reissue the poll.
  104. this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) Poll timed out, reissuing.");
  105. }
  106. else {
  107. // Close the connection with the error as the result.
  108. this._closeError = e;
  109. this._running = false;
  110. }
  111. }
  112. }
  113. }
  114. }
  115. finally {
  116. this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) Polling complete.");
  117. // We will reach here with pollAborted==false when the server returned a response causing the transport to stop.
  118. // If pollAborted==true then client initiated the stop and the stop method will raise the close event after DELETE is sent.
  119. if (!this.pollAborted) {
  120. this._raiseOnClose();
  121. }
  122. }
  123. }
  124. async send(data) {
  125. if (!this._running) {
  126. return Promise.reject(new Error("Cannot send until the transport is connected"));
  127. }
  128. return Utils_1.sendMessage(this._logger, "LongPolling", this._httpClient, this._url, data, this._options);
  129. }
  130. async stop() {
  131. this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) Stopping polling.");
  132. // Tell receiving loop to stop, abort any current request, and then wait for it to finish
  133. this._running = false;
  134. this._pollAbort.abort();
  135. try {
  136. await this._receiving;
  137. // Send DELETE to clean up long polling on the server
  138. this._logger.log(ILogger_1.LogLevel.Trace, `(LongPolling transport) sending DELETE request to ${this._url}.`);
  139. const headers = {};
  140. const [name, value] = Utils_1.getUserAgentHeader();
  141. headers[name] = value;
  142. const deleteOptions = {
  143. headers: { ...headers, ...this._options.headers },
  144. timeout: this._options.timeout,
  145. withCredentials: this._options.withCredentials,
  146. };
  147. await this._httpClient.delete(this._url, deleteOptions);
  148. this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) DELETE request sent.");
  149. }
  150. finally {
  151. this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) Stop finished.");
  152. // Raise close event here instead of in polling
  153. // It needs to happen after the DELETE request is sent
  154. this._raiseOnClose();
  155. }
  156. }
  157. _raiseOnClose() {
  158. if (this.onclose) {
  159. let logMessage = "(LongPolling transport) Firing onclose event.";
  160. if (this._closeError) {
  161. logMessage += " Error: " + this._closeError;
  162. }
  163. this._logger.log(ILogger_1.LogLevel.Trace, logMessage);
  164. this.onclose(this._closeError);
  165. }
  166. }
  167. }
  168. exports.LongPollingTransport = LongPollingTransport;
  169. //# sourceMappingURL=LongPollingTransport.js.map