123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- // Licensed to the .NET Foundation under one or more agreements.
- // The .NET Foundation licenses this file to you under the MIT license.
- import { AbortController } from "./AbortController";
- import { HttpError, TimeoutError } from "./Errors";
- import { LogLevel } from "./ILogger";
- import { TransferFormat } from "./ITransport";
- import { Arg, getDataDetail, getUserAgentHeader, sendMessage } from "./Utils";
- // Not exported from 'index', this type is internal.
- /** @private */
- export class LongPollingTransport {
- constructor(httpClient, logger, options) {
- this._httpClient = httpClient;
- this._logger = logger;
- this._pollAbort = new 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) {
- Arg.isRequired(url, "url");
- Arg.isRequired(transferFormat, "transferFormat");
- Arg.isIn(transferFormat, TransferFormat, "transferFormat");
- this._url = url;
- this._logger.log(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 === 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] = getUserAgentHeader();
- const headers = { [name]: value, ...this._options.headers };
- const pollOptions = {
- abortSignal: this._pollAbort.signal,
- headers,
- timeout: 100000,
- withCredentials: this._options.withCredentials,
- };
- if (transferFormat === TransferFormat.Binary) {
- pollOptions.responseType = "arraybuffer";
- }
- // 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(LogLevel.Trace, `(LongPolling transport) polling: ${pollUrl}.`);
- const response = await this._httpClient.get(pollUrl, pollOptions);
- if (response.statusCode !== 200) {
- this._logger.log(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 HttpError(response.statusText || "", response.statusCode);
- this._running = false;
- }
- else {
- this._running = true;
- }
- this._receiving = this._poll(this._url, pollOptions);
- }
- async _poll(url, pollOptions) {
- try {
- while (this._running) {
- try {
- const pollUrl = `${url}&_=${Date.now()}`;
- this._logger.log(LogLevel.Trace, `(LongPolling transport) polling: ${pollUrl}.`);
- const response = await this._httpClient.get(pollUrl, pollOptions);
- if (response.statusCode === 204) {
- this._logger.log(LogLevel.Information, "(LongPolling transport) Poll terminated by server.");
- this._running = false;
- }
- else if (response.statusCode !== 200) {
- this._logger.log(LogLevel.Error, `(LongPolling transport) Unexpected response code: ${response.statusCode}.`);
- // Unexpected status code
- this._closeError = new HttpError(response.statusText || "", response.statusCode);
- this._running = false;
- }
- else {
- // Process the response
- if (response.content) {
- this._logger.log(LogLevel.Trace, `(LongPolling transport) data received. ${getDataDetail(response.content, this._options.logMessageContent)}.`);
- if (this.onreceive) {
- this.onreceive(response.content);
- }
- }
- else {
- // This is another way timeout manifest.
- this._logger.log(LogLevel.Trace, "(LongPolling transport) Poll timed out, reissuing.");
- }
- }
- }
- catch (e) {
- if (!this._running) {
- // Log but disregard errors that occur after stopping
- this._logger.log(LogLevel.Trace, `(LongPolling transport) Poll errored after shutdown: ${e.message}`);
- }
- else {
- if (e instanceof TimeoutError) {
- // Ignore timeouts and reissue the poll.
- this._logger.log(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(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 sendMessage(this._logger, "LongPolling", this._httpClient, this._url, data, this._options);
- }
- async stop() {
- this._logger.log(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(LogLevel.Trace, `(LongPolling transport) sending DELETE request to ${this._url}.`);
- const headers = {};
- const [name, value] = getUserAgentHeader();
- headers[name] = value;
- const deleteOptions = {
- headers: { ...headers, ...this._options.headers },
- timeout: this._options.timeout,
- withCredentials: this._options.withCredentials,
- };
- await this._httpClient.delete(this._url, deleteOptions);
- this._logger.log(LogLevel.Trace, "(LongPolling transport) DELETE request sent.");
- }
- finally {
- this._logger.log(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(LogLevel.Trace, logMessage);
- this.onclose(this._closeError);
- }
- }
- }
- //# sourceMappingURL=LongPollingTransport.js.map
|