text: "use strict";
  // Licensed to the .NET Foundation under one or more agreements.
  // The .NET Foundation licenses this file to you under the MIT license.
  Object.defineProperty(exports, "__esModule", { value: true });
  exports.LongPollingTransport = void 0;
  const AbortController_1 = require("./AbortController");
  const Errors_1 = require("./Errors");
  const HeaderNames_1 = require("./HeaderNames");
  const ILogger_1 = require("./ILogger");
  const ITransport_1 = require("./ITransport");
  const Utils_1 = require("./Utils");
  // Not exported from 'index', this type is internal.
  /** @private */
  class LongPollingTransport {
      constructor(httpClient, accessTokenFactory, logger, options) {
          this._httpClient = httpClient;
          this._accessTokenFactory = accessTokenFactory;
          this._logger = logger;
          this._pollAbort = new AbortController_1.AbortController();
          this._options = options;
          this._running = false;
          this.onreceive = null;
          this.onclose = null;
      }
      // This is an internal type, not exported from 'index' so this is really just internal.
      get pollAborted() {
          return this._pollAbort.aborted;
      }
      async connect(url, transferFormat) {
          Utils_1.Arg.isRequired(url, "url");
          Utils_1.Arg.isRequired(transferFormat, "transferFormat");
          Utils_1.Arg.isIn(transferFormat, ITransport_1.TransferFormat, "transferFormat");
          this._url = url;
          this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) Connecting.");
          // Allow binary format on Node and Browsers that support binary content (indicated by the presence of responseType property)
          if (transferFormat === ITransport_1.TransferFormat.Binary &&
              (typeof XMLHttpRequest !== "undefined" && typeof new XMLHttpRequest().responseType !== "string")) {
              throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported.");
          }
          const [name, value] = Utils_1.getUserAgentHeader();
          const headers = { [name]: value, ...this._options.headers };
          const pollOptions = {
              abortSignal: this._pollAbort.signal,
              headers,
              timeout: 100000,
              withCredentials: this._options.withCredentials,
          };
          if (transferFormat === ITransport_1.TransferFormat.Binary) {
              pollOptions.responseType = "arraybuffer";
          }
          const token = await this._getAccessToken();
          this._updateHeaderToken(pollOptions, token);
          // Make initial long polling request
          // Server uses first long polling request to finish initializing connection and it returns without data
          const pollUrl = `${url}&_=${Date.now()}`;
          this._logger.log(ILogger_1.LogLevel.Trace, `(LongPolling transport) polling: ${pollUrl}.`);
          const response = await this._httpClient.get(pollUrl, pollOptions);
          if (response.statusCode !== 200) {
              this._logger.log(ILogger_1.LogLevel.Error, `(LongPolling transport) Unexpected response code: ${response.statusCode}.`);
              // Mark running as false so that the poll immediately ends and runs the close logic
              this._closeError = new Errors_1.HttpError(response.statusText || "", response.statusCode);
              this._running = false;
          }
          else {
              this._running = true;
          }
          this._receiving = this._poll(this._url, pollOptions);
      }
      async _getAccessToken() {
          if (this._accessTokenFactory) {
              return await this._accessTokenFactory();
          }
          return null;
      }
      _updateHeaderToken(request, token) {
          if (!request.headers) {
              request.headers = {};
          }
          if (token) {
              request.headers[HeaderNames_1.HeaderNames.Authorization] = `Bearer ${token}`;
              return;
          }
          if (request.headers[HeaderNames_1.HeaderNames.Authorization]) {
              delete request.headers[HeaderNames_1.HeaderNames.Authorization];
          }
      }
      async _poll(url, pollOptions) {
          try {
              while (this._running) {
                  // We have to get the access token on each poll, in case it changes
                  const token = await this._getAccessToken();
                  this._updateHeaderToken(pollOptions, token);
                  try {
                      const pollUrl = `${url}&_=${Date.now()}`;
                      this._logger.log(ILogger_1.LogLevel.Trace, `(LongPolling transport) polling: ${pollUrl}.`);
                      const response = await this._httpClient.get(pollUrl, pollOptions);
                      if (response.statusCode === 204) {
                          this._logger.log(ILogger_1.LogLevel.Information, "(LongPolling transport) Poll terminated by server.");
                          this._running = false;
                      }
                      else if (response.statusCode !== 200) {
                          this._logger.log(ILogger_1.LogLevel.Error, `(LongPolling transport) Unexpected response code: ${response.statusCode}.`);
                          // Unexpected status code
                          this._closeError = new Errors_1.HttpError(response.statusText || "", response.statusCode);
                          this._running = false;
                      }
                      else {
                          // Process the response
                          if (response.content) {
                              this._logger.log(ILogger_1.LogLevel.Trace, `(LongPolling transport) data received. ${Utils_1.getDataDetail(response.content, this._options.logMessageContent)}.`);
                              if (this.onreceive) {
                                  this.onreceive(response.content);
                              }
                          }
                          else {
                              // This is another way timeout manifest.
                              this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) Poll timed out, reissuing.");
                          }
                      }
                  }
                  catch (e) {
                      if (!this._running) {
                          // Log but disregard errors that occur after stopping
                          this._logger.log(ILogger_1.LogLevel.Trace, `(LongPolling transport) Poll errored after shutdown: ${e.message}`);
                      }
                      else {
                          if (e instanceof Errors_1.TimeoutError) {
                              // Ignore timeouts and reissue the poll.
                              this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) Poll timed out, reissuing.");
                          }
                          else {
                              // Close the connection with the error as the result.
                              this._closeError = e;
                              this._running = false;
                          }
                      }
                  }
              }
          }
          finally {
              this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) Polling complete.");
              // We will reach here with pollAborted==false when the server returned a response causing the transport to stop.
              // If pollAborted==true then client initiated the stop and the stop method will raise the close event after DELETE is sent.
              if (!this.pollAborted) {
                  this._raiseOnClose();
              }
          }
      }
      async send(data) {
          if (!this._running) {
              return Promise.reject(new Error("Cannot send until the transport is connected"));
          }
          return Utils_1.sendMessage(this._logger, "LongPolling", this._httpClient, this._url, this._accessTokenFactory, data, this._options);
      }
      async stop() {
          this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) Stopping polling.");
          // Tell receiving loop to stop, abort any current request, and then wait for it to finish
          this._running = false;
          this._pollAbort.abort();
          try {
              await this._receiving;
              // Send DELETE to clean up long polling on the server
              this._logger.log(ILogger_1.LogLevel.Trace, `(LongPolling transport) sending DELETE request to ${this._url}.`);
              const headers = {};
              const [name, value] = Utils_1.getUserAgentHeader();
              headers[name] = value;
              const deleteOptions = {
                  headers: { ...headers, ...this._options.headers },
                  timeout: this._options.timeout,
                  withCredentials: this._options.withCredentials,
              };
              const token = await this._getAccessToken();
              this._updateHeaderToken(deleteOptions, token);
              await this._httpClient.delete(this._url, deleteOptions);
              this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) DELETE request sent.");
          }
          finally {
              this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) Stop finished.");
              // Raise close event here instead of in polling
              // It needs to happen after the DELETE request is sent
              this._raiseOnClose();
          }
      }
      _raiseOnClose() {
          if (this.onclose) {
              let logMessage = "(LongPolling transport) Firing onclose event.";
              if (this._closeError) {
                  logMessage += " Error: " + this._closeError;
              }
              this._logger.log(ILogger_1.LogLevel.Trace, logMessage);
              this.onclose(this._closeError);
          }
      }
  }
  exports.LongPollingTransport = LongPollingTransport;
  //# sourceMappingURL=LongPollingTransport.js.map