UNPKG

@microsoft/signalr

Version:
187 lines 9.49 kB
"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 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 { // This is an internal type, not exported from 'index' so this is really just internal. get pollAborted() { return this._pollAbort.aborted; } constructor(httpClient, logger, options) { this._httpClient = httpClient; this._logger = logger; this._pollAbort = new AbortController_1.AbortController(); this._options = options; this._running = false; this.onreceive = null; this.onclose = null; } 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] = (0, 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"; } // 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 _poll(url, pollOptions) { try { while (this._running) { 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. ${(0, 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 (0, Utils_1.sendMessage)(this._logger, "LongPolling", this._httpClient, this._url, 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] = (0, Utils_1.getUserAgentHeader)(); headers[name] = value; const deleteOptions = { headers: { ...headers, ...this._options.headers }, timeout: this._options.timeout, withCredentials: this._options.withCredentials, }; let error; try { await this._httpClient.delete(this._url, deleteOptions); } catch (err) { error = err; } if (error) { if (error instanceof Errors_1.HttpError) { if (error.statusCode === 404) { this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) A 404 response was returned from sending a DELETE request."); } else { this._logger.log(ILogger_1.LogLevel.Trace, `(LongPolling transport) Error sending a DELETE request: ${error}`); } } } else { this._logger.log(ILogger_1.LogLevel.Trace, "(LongPolling transport) DELETE request accepted."); } } 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