From 865de160864d512628fecd2116ae49d6feeef7f1 Mon Sep 17 00:00:00 2001 From: BrennanConroy Date: Tue, 30 Oct 2018 22:13:24 -0700 Subject: [PATCH] Enable cookie jar for Node client using request package (#3202) --- clients/ts/FunctionalTests/Startup.cs | 11 + clients/ts/FunctionalTests/TestHub.cs | 10 + clients/ts/FunctionalTests/package-lock.json | 325 ++++++++++++++-- .../ts/FunctionalTests/scripts/run-tests.ts | 47 ++- .../ts/FunctionalTests/ts/ConnectionTests.ts | 1 + .../FunctionalTests/ts/HubConnectionTests.ts | 21 + .../FunctionalTests/ts/LogBannerReporter.ts | 7 +- .../ts/FunctionalTests/ts/WebSocketTests.ts | 1 + clients/ts/FunctionalTests/webpack.config.js | 1 + .../package-lock.json | 2 +- .../ts/signalr-protocol-msgpack/package.json | 2 +- clients/ts/signalr/package-lock.json | 363 +++++++++++++++++- clients/ts/signalr/package.json | 8 +- clients/ts/signalr/src/DefaultHttpClient.ts | 4 + clients/ts/signalr/src/HttpClient.ts | 10 + clients/ts/signalr/src/HttpConnection.ts | 2 +- clients/ts/signalr/src/NodeHttpClient.ts | 111 +++--- clients/ts/signalr/src/Polyfills.ts | 2 +- .../signalr/src/ServerSentEventsTransport.ts | 9 +- clients/ts/signalr/src/WebSocketTransport.ts | 23 +- .../signalr/tests/WebSocketTransport.test.ts | 7 +- clients/ts/signalr/webpack.config.js | 3 +- 22 files changed, 838 insertions(+), 132 deletions(-) diff --git a/clients/ts/FunctionalTests/Startup.cs b/clients/ts/FunctionalTests/Startup.cs index cb38654665..975de64581 100644 --- a/clients/ts/FunctionalTests/Startup.cs +++ b/clients/ts/FunctionalTests/Startup.cs @@ -103,6 +103,17 @@ namespace FunctionalTests routes.MapConnectionHandler("/echo"); }); + app.Use(async (context, next) => + { + if (context.Request.Path.Value.Contains("/negotiate")) + { + context.Response.Cookies.Append("testCookie", "testValue"); + context.Response.Cookies.Append("testCookie2", "testValue2"); + context.Response.Cookies.Append("expiredCookie", "doesntmatter", new CookieOptions() { Expires = DateTimeOffset.Now.AddHours(-1) }); + } + await next.Invoke(); + }); + app.UseSignalR(routes => { routes.MapHub("/testhub"); diff --git a/clients/ts/FunctionalTests/TestHub.cs b/clients/ts/FunctionalTests/TestHub.cs index 53230b5270..9c39d97d6e 100644 --- a/clients/ts/FunctionalTests/TestHub.cs +++ b/clients/ts/FunctionalTests/TestHub.cs @@ -88,5 +88,15 @@ namespace FunctionalTests { return Context.GetHttpContext().Request.Headers["Content-Type"]; } + + public string GetCookie(string cookieName) + { + var cookies = Context.GetHttpContext().Request.Cookies; + if (cookies.TryGetValue(cookieName, out var cookieValue)) + { + return cookieValue; + } + return null; + } } } diff --git a/clients/ts/FunctionalTests/package-lock.json b/clients/ts/FunctionalTests/package-lock.json index 289832d60b..98ea76c951 100644 --- a/clients/ts/FunctionalTests/package-lock.json +++ b/clients/ts/FunctionalTests/package-lock.json @@ -8,18 +8,123 @@ "version": "file:../signalr", "requires": { "eventsource": "^1.0.7", + "request": "^2.88.0", "ws": "^6.0.0" }, "dependencies": { + "@types/caseless": { + "version": "0.12.1", + "bundled": true + }, + "@types/eventsource": { + "version": "1.0.2", + "bundled": true + }, + "@types/form-data": { + "version": "2.2.1", + "bundled": true, + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "10.9.4", + "bundled": true + }, + "@types/request": { + "version": "2.47.1", + "bundled": true, + "requires": { + "@types/caseless": "*", + "@types/form-data": "*", + "@types/node": "*", + "@types/tough-cookie": "*" + } + }, + "@types/tough-cookie": { + "version": "2.3.3", + "bundled": true + }, + "ajv": { + "version": "5.5.2", + "bundled": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "asn1": { + "version": "0.2.4", + "bundled": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "bundled": true + }, "async-limiter": { "version": "1.0.0", "bundled": true }, - "debug": { - "version": "2.6.9", + "asynckit": { + "version": "0.4.0", + "bundled": true + }, + "aws-sign2": { + "version": "0.7.0", + "bundled": true + }, + "aws4": { + "version": "1.8.0", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", "bundled": true, "requires": { - "ms": "2.0.0" + "tweetnacl": "^0.14.3" + } + }, + "caseless": { + "version": "0.12.0", + "bundled": true + }, + "co": { + "version": "4.6.0", + "bundled": true + }, + "combined-stream": { + "version": "1.0.7", + "bundled": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "bundled": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "es6-promise": { @@ -33,16 +138,110 @@ "original": "^1.0.0" } }, + "extend": { + "version": "3.0.2", + "bundled": true + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "bundled": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true + }, + "form-data": { + "version": "2.3.3", + "bundled": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "bundled": true + }, + "har-validator": { + "version": "5.1.0", + "bundled": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, "is-typedarray": { "version": "1.0.0", "bundled": true }, - "ms": { - "version": "2.0.0", + "isstream": { + "version": "0.1.2", "bundled": true }, - "nan": { - "version": "2.11.0", + "jsbn": { + "version": "0.1.1", + "bundled": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "mime-db": { + "version": "1.37.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.21", + "bundled": true, + "requires": { + "mime-db": "~1.37.0" + } + }, + "oauth-sign": { + "version": "0.9.0", "bundled": true }, "original": { @@ -52,25 +251,98 @@ "url-parse": "^1.4.3" } }, + "performance-now": { + "version": "2.1.0", + "bundled": true + }, + "psl": { + "version": "1.1.29", + "bundled": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true + }, + "qs": { + "version": "6.5.2", + "bundled": true + }, "querystringify": { "version": "2.0.0", "bundled": true }, + "request": { + "version": "2.88.0", + "bundled": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, "requires-port": { "version": "1.0.0", "bundled": true }, - "tslib": { - "version": "1.9.3", + "safe-buffer": { + "version": "5.1.2", "bundled": true }, - "typedarray-to-buffer": { - "version": "3.1.5", + "safer-buffer": { + "version": "2.1.2", + "bundled": true + }, + "sshpk": { + "version": "1.15.1", "bundled": true, "requires": { - "is-typedarray": "^1.0.0" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" } }, + "tough-cookie": { + "version": "2.4.3", + "bundled": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true + }, "url-parse": { "version": "1.4.3", "bundled": true, @@ -79,14 +351,17 @@ "requires-port": "^1.0.0" } }, - "websocket": { - "version": "1.0.26", + "uuid": { + "version": "3.3.2", + "bundled": true + }, + "verror": { + "version": "1.10.0", "bundled": true, "requires": { - "debug": "^2.2.0", - "nan": "^2.3.3", - "typedarray-to-buffer": "^3.1.2", - "yaeti": "^0.0.6" + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" } }, "ws": { @@ -95,10 +370,6 @@ "requires": { "async-limiter": "~1.0.0" } - }, - "yaeti": { - "version": "0.0.6", - "bundled": true } } }, @@ -200,10 +471,6 @@ "safe-buffer": "~5.1.0" } }, - "tslib": { - "version": "1.9.3", - "bundled": true - }, "util-deprecate": { "version": "1.0.2", "bundled": true @@ -2819,9 +3086,9 @@ } }, "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", + "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", "dev": true, "optional": true }, diff --git a/clients/ts/FunctionalTests/scripts/run-tests.ts b/clients/ts/FunctionalTests/scripts/run-tests.ts index b2bc5db2c8..3fcda8945a 100644 --- a/clients/ts/FunctionalTests/scripts/run-tests.ts +++ b/clients/ts/FunctionalTests/scripts/run-tests.ts @@ -1,4 +1,4 @@ -import { ChildProcess, execSync, spawn } from "child_process"; +import { ChildProcess, exec, spawn } from "child_process"; import { EOL } from "os"; import { Readable } from "stream"; @@ -163,26 +163,23 @@ function runJest(url: string) { const configPath = path.resolve(__dirname, "..", "func.jest.config.js"); console.log("Starting Node tests using Jest."); - try { - execSync(`"${process.execPath}" "${jestPath}" --config "${configPath}"`, { env: { SERVER_URL: url }, timeout: 200000 }); - return 0; - } catch (error) { - console.log(error.message); - console.log(error.stderr); - console.log(error.stdout); - return error.status || 1; - } + return new Promise((resolve, reject) => { + const logStream = fs.createWriteStream(path.resolve(__dirname, "..", "..", "..", "..", "artifacts", "logs", "node.functionaltests.log")); + const p = exec(`"${process.execPath}" "${jestPath}" --config "${configPath}"`, { env: { SERVER_URL: url }, timeout: 200000, maxBuffer: 10 * 1024 * 1024 }, + (error: any, stdout, stderr) => { + if (error) { + console.log(error.message); + return resolve(error.code); + } + return resolve(0); + }); + p.stdout.pipe(logStream); + p.stderr.pipe(logStream); + }); } (async () => { try { - // Check if we got any browsers - if (config.browsers.length === 0) { - console.log("Unable to locate any suitable browsers. Skipping browser functional tests."); - process.exit(0); - return; // For good measure - } - const serverPath = path.resolve(__dirname, "..", "bin", configuration, "netcoreapp2.2", "FunctionalTests.dll"); debug(`Launching Functional Test Server: ${serverPath}`); @@ -243,14 +240,22 @@ function runJest(url: string) { // Pass server URL to tests conf.client.args = ["--server", url]; - const results = await runKarma(conf); + const jestExit = await runJest(url); - const jestExit = runJest(url); + // Check if we got any browsers + let karmaExit; + if (config.browsers.length === 0) { + console.log("Unable to locate any suitable browsers. Skipping browser functional tests."); + } else { + karmaExit = (await runKarma(conf)).exitCode; + } - console.log(`karma exit code: ${results.exitCode}`); + if (karmaExit) { + console.log(`karma exit code: ${karmaExit}`); + } console.log(`jest exit code: ${jestExit}`); - process.exit(results.exitCode !== 0 ? results.exitCode : jestExit); + process.exit(jestExit !== 0 ? jestExit : karmaExit); } catch (e) { console.error(e); process.exit(1); diff --git a/clients/ts/FunctionalTests/ts/ConnectionTests.ts b/clients/ts/FunctionalTests/ts/ConnectionTests.ts index 879844949e..4fca7c1223 100644 --- a/clients/ts/FunctionalTests/ts/ConnectionTests.ts +++ b/clients/ts/FunctionalTests/ts/ConnectionTests.ts @@ -10,6 +10,7 @@ import { TestLogger } from "./TestLogger"; // We want to continue testing HttpConnection, but we don't export it anymore. So just pull it in directly from the source file. import { HttpConnection } from "@aspnet/signalr/dist/esm/HttpConnection"; +import "./LogBannerReporter"; const commonOptions: IHttpConnectionOptions = { logMessageContent: true, diff --git a/clients/ts/FunctionalTests/ts/HubConnectionTests.ts b/clients/ts/FunctionalTests/ts/HubConnectionTests.ts index b5b8a841d2..8d54c23884 100644 --- a/clients/ts/FunctionalTests/ts/HubConnectionTests.ts +++ b/clients/ts/FunctionalTests/ts/HubConnectionTests.ts @@ -8,6 +8,7 @@ import { AbortError, DefaultHttpClient, HttpClient, HttpRequest, HttpResponse, H import { MessagePackHubProtocol } from "@aspnet/signalr-protocol-msgpack"; import { eachTransport, eachTransportAndProtocol, ENDPOINT_BASE_URL } from "./Common"; +import "./LogBannerReporter"; import { TestLogger } from "./TestLogger"; const TESTHUBENDPOINT_URL = ENDPOINT_BASE_URL + "/testhub"; @@ -562,6 +563,26 @@ describe("hubConnection", () => { }); }); } + + it("preserves cookies between requests", async (done) => { + const hubConnection = getConnectionBuilder(transportType).build(); + await hubConnection.start(); + const cookieValue = await hubConnection.invoke("GetCookie", "testCookie"); + const cookieValue2 = await hubConnection.invoke("GetCookie", "testCookie2"); + expect(cookieValue).toEqual("testValue"); + expect(cookieValue2).toEqual("testValue2"); + await hubConnection.stop(); + done(); + }); + + it("expired cookies are not preserved", async (done) => { + const hubConnection = getConnectionBuilder(transportType).build(); + await hubConnection.start(); + const cookieValue = await hubConnection.invoke("GetCookie", "expiredCookie"); + expect(cookieValue).toBeNull(); + await hubConnection.stop(); + done(); + }); }); }); diff --git a/clients/ts/FunctionalTests/ts/LogBannerReporter.ts b/clients/ts/FunctionalTests/ts/LogBannerReporter.ts index 82471ebb08..98d28da93a 100644 --- a/clients/ts/FunctionalTests/ts/LogBannerReporter.ts +++ b/clients/ts/FunctionalTests/ts/LogBannerReporter.ts @@ -16,4 +16,9 @@ export class LogBannerReporter implements jasmine.CustomReporter { } } -jasmine.getEnv().addReporter(new LogBannerReporter()); +if (typeof window !== "undefined" && (window as any).customReporterRegistered !== true) { + (window as any).customReporterRegistered = true; + jasmine.getEnv().addReporter(new LogBannerReporter()); +} else if (typeof window === "undefined") { + jasmine.getEnv().addReporter(new LogBannerReporter()); +} diff --git a/clients/ts/FunctionalTests/ts/WebSocketTests.ts b/clients/ts/FunctionalTests/ts/WebSocketTests.ts index 26b5e9c3f8..930ac6e24c 100644 --- a/clients/ts/FunctionalTests/ts/WebSocketTests.ts +++ b/clients/ts/FunctionalTests/ts/WebSocketTests.ts @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. import { ECHOENDPOINT_URL } from "./Common"; +import "./LogBannerReporter"; // On slower CI machines, these tests sometimes take longer than 5s jasmine.DEFAULT_TIMEOUT_INTERVAL = 10 * 1000; diff --git a/clients/ts/FunctionalTests/webpack.config.js b/clients/ts/FunctionalTests/webpack.config.js index 1c2e75025f..183096c1b8 100644 --- a/clients/ts/FunctionalTests/webpack.config.js +++ b/clients/ts/FunctionalTests/webpack.config.js @@ -37,5 +37,6 @@ module.exports = { externals: { "@aspnet/signalr": "signalR", "@aspnet/signalr-protocol-msgpack": "signalR.protocols.msgpack", + "request": "request", }, }; \ No newline at end of file diff --git a/clients/ts/signalr-protocol-msgpack/package-lock.json b/clients/ts/signalr-protocol-msgpack/package-lock.json index d18074493a..433ef5cd6e 100644 --- a/clients/ts/signalr-protocol-msgpack/package-lock.json +++ b/clients/ts/signalr-protocol-msgpack/package-lock.json @@ -1,6 +1,6 @@ { "name": "@aspnet/signalr-protocol-msgpack", - "version": "1.1.0-preview3-t000", + "version": "1.1.0-rtm-t000", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/clients/ts/signalr-protocol-msgpack/package.json b/clients/ts/signalr-protocol-msgpack/package.json index 07cc444f8f..ef2b30a969 100644 --- a/clients/ts/signalr-protocol-msgpack/package.json +++ b/clients/ts/signalr-protocol-msgpack/package.json @@ -1,6 +1,6 @@ { "name": "@aspnet/signalr-protocol-msgpack", - "version": "1.1.0-preview1-t000", + "version": "1.1.0-rtm-t000", "description": "MsgPack Protocol support for ASP.NET Core SignalR", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", diff --git a/clients/ts/signalr/package-lock.json b/clients/ts/signalr/package-lock.json index b374782b01..1f09c86a5b 100644 --- a/clients/ts/signalr/package-lock.json +++ b/clients/ts/signalr/package-lock.json @@ -1,26 +1,151 @@ { "name": "@aspnet/signalr", - "version": "1.1.0-preview3-t000", + "version": "1.1.0-rtm-t000", "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/caseless": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.1.tgz", + "integrity": "sha512-FhlMa34NHp9K5MY1Uz8yb+ZvuX0pnvn3jScRSNAb75KHGB8d3rEU6hqMs3Z2vjuytcMfRg6c5CHMc3wtYyD2/A==", + "dev": true + }, "@types/eventsource": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/eventsource/-/eventsource-1.0.2.tgz", "integrity": "sha512-CprOekOB/lzAiGDF1MPWHX053RVTCYyYU3M8HOQXpdD0QfXijM//Na/hZxHaQv4ydsiB1uOBQ3p8S5nXpP4nNQ==", "dev": true }, + "@types/form-data": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", + "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "10.9.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.9.4.tgz", "integrity": "sha512-fCHV45gS+m3hH17zgkgADUSi2RR1Vht6wOZ0jyHP8rjiQra9f+mIcgwPQHllmDocYOstIEbKlxbFDYlgrTPYqw==", "dev": true }, + "@types/request": { + "version": "2.47.1", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.47.1.tgz", + "integrity": "sha512-TV3XLvDjQbIeVxJ1Z3oCTDk/KuYwwcNKVwz2YaT0F5u86Prgc4syDAp6P96rkTQQ4bIdh+VswQIC9zS6NjY7/g==", + "dev": true, + "requires": { + "@types/caseless": "*", + "@types/form-data": "*", + "@types/node": "*", + "@types/tough-cookie": "*" + } + }, + "@types/tough-cookie": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.3.tgz", + "integrity": "sha512-MDQLxNFRLasqS4UlkWMSACMKeSm1x4Q3TxzUC7KQUsh6RK1ZrQ0VEyE3yzXcBu+K8ejVj4wuX32eUG02yNp+YQ==", + "dev": true + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, "async-limiter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "es6-promise": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.2.tgz", @@ -35,6 +160,132 @@ "original": "^1.0.0" } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", + "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "requires": { + "mime-db": "~1.37.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, "original": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", @@ -43,16 +294,111 @@ "url-parse": "^1.4.3" } }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, "querystringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz", "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==" }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sshpk": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.1.tgz", + "integrity": "sha512-mSdgNUaidk+dRU5MhYtN9zebdzF2iG0cNPWy8HG+W8y+fT1JnSkh0fzzpjOa0L7P8i1Rscz38t0h4gPcKz43xA==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, "url-parse": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.3.tgz", @@ -62,6 +408,21 @@ "requires-port": "^1.0.0" } }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "ws": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/ws/-/ws-6.0.0.tgz", diff --git a/clients/ts/signalr/package.json b/clients/ts/signalr/package.json index b0f1e562e0..e3bbe9d87e 100644 --- a/clients/ts/signalr/package.json +++ b/clients/ts/signalr/package.json @@ -1,6 +1,6 @@ { "name": "@aspnet/signalr", - "version": "1.1.0-preview1-t000", + "version": "1.1.0-rtm-t000", "description": "ASP.NET Core SignalR Client", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.js", @@ -38,10 +38,12 @@ "devDependencies": { "es6-promise": "^4.2.2", "@types/node": "^10.9.4", - "@types/eventsource": "^1.0.2" + "@types/eventsource": "^1.0.2", + "@types/request": "^2.47.1" }, "dependencies": { "ws": "^6.0.0", - "eventsource": "^1.0.7" + "eventsource": "^1.0.7", + "request": "^2.88.0" } } diff --git a/clients/ts/signalr/src/DefaultHttpClient.ts b/clients/ts/signalr/src/DefaultHttpClient.ts index 45e3473df6..0fac925a1e 100644 --- a/clients/ts/signalr/src/DefaultHttpClient.ts +++ b/clients/ts/signalr/src/DefaultHttpClient.ts @@ -45,4 +45,8 @@ export class DefaultHttpClient extends HttpClient { return this.httpClient.send(request); } + + public getCookieString(url: string): string { + return this.httpClient.getCookieString(url); + } } diff --git a/clients/ts/signalr/src/HttpClient.ts b/clients/ts/signalr/src/HttpClient.ts index efeff8ef24..32ca0c2794 100644 --- a/clients/ts/signalr/src/HttpClient.ts +++ b/clients/ts/signalr/src/HttpClient.ts @@ -141,4 +141,14 @@ export abstract class HttpClient { * @returns {Promise} A Promise that resolves with an HttpResponse describing the response, or rejects with an Error indicating a failure. */ public abstract send(request: HttpRequest): Promise; + + /** Gets all cookies that apply to the specified URL. + * + * @param url The URL that the cookies are valid for. + * @returns {string} A string containing all the key-value cookie pairs for the specified URL. + */ + // @ts-ignore + public getCookieString(url: string): string { + return ""; + } } diff --git a/clients/ts/signalr/src/HttpConnection.ts b/clients/ts/signalr/src/HttpConnection.ts index 2d4653aff1..2d7d0f65bf 100644 --- a/clients/ts/signalr/src/HttpConnection.ts +++ b/clients/ts/signalr/src/HttpConnection.ts @@ -299,7 +299,7 @@ export class HttpConnection implements IConnection { if (!this.options.WebSocket) { throw new Error("'WebSocket' is not supported in your environment."); } - return new WebSocketTransport(this.accessTokenFactory, this.logger, this.options.logMessageContent || false, this.options.WebSocket); + return new WebSocketTransport(this.httpClient, this.accessTokenFactory, this.logger, this.options.logMessageContent || false, this.options.WebSocket); case HttpTransportType.ServerSentEvents: if (!this.options.EventSource) { throw new Error("'EventSource' is not supported in your environment."); diff --git a/clients/ts/signalr/src/NodeHttpClient.ts b/clients/ts/signalr/src/NodeHttpClient.ts index b3a1071c35..bd9f1da86b 100644 --- a/clients/ts/signalr/src/NodeHttpClient.ts +++ b/clients/ts/signalr/src/NodeHttpClient.ts @@ -1,9 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -import * as http from "http"; -import * as https from "https"; -import { URL } from "url"; +import * as Request from "request"; import { AbortError, HttpError, TimeoutError } from "./Errors"; import { HttpClient, HttpRequest, HttpResponse } from "./HttpClient"; @@ -12,87 +10,70 @@ import { isArrayBuffer } from "./Utils"; export class NodeHttpClient extends HttpClient { private readonly logger: ILogger; + private readonly request: Request.RequestAPI; + private readonly cookieJar: Request.CookieJar; public constructor(logger: ILogger) { super(); this.logger = logger; + this.cookieJar = Request.jar(); + this.request = Request.defaults({ jar: this.cookieJar }); } - public send(request: HttpRequest): Promise { + public send(httpRequest: HttpRequest): Promise { return new Promise((resolve, reject) => { - const url = new URL(request.url!); - const options: http.RequestOptions = { + + let requestBody: Buffer | string; + if (isArrayBuffer(httpRequest.content)) { + requestBody = Buffer.from(httpRequest.content); + } else { + requestBody = httpRequest.content || ""; + } + + const currentRequest = this.request(httpRequest.url!, { + body: requestBody, + // If binary is expected 'null' should be used, otherwise for text 'utf8' + encoding: httpRequest.responseType === "arraybuffer" ? null : "utf8", headers: { // Tell auth middleware to 401 instead of redirecting "X-Requested-With": "XMLHttpRequest", - ...request.headers, + ...httpRequest.headers, }, - hostname: url.hostname, - method: request.method, - // /abc/xyz + ?id=12ssa_30 - path: url.pathname + url.search, - port: url.port, - }; + method: httpRequest.method, + timeout: httpRequest.timeout, + }, + (error, response, body) => { + if (httpRequest.abortSignal) { + httpRequest.abortSignal.onabort = null; + } - // "any" is used here because require() can't be correctly resolved by the compiler - // when httpOrHttps is typeof http | typeof https. Change to http when editing to get - // intellisense. - const httpOrHttps: any = url.protocol === "https" ? https : http; - - const req = httpOrHttps.request(options, (res: http.IncomingMessage) => { - const data: Buffer[] = []; - let dataLength = 0; - res.on("data", (chunk: any) => { - data.push(chunk); - // Buffer.concat will be slightly faster if we keep track of the length - dataLength += chunk.length; - }); - - res.on("end", () => { - if (request.abortSignal) { - request.abortSignal.onabort = null; + if (error) { + if (error.code === "ETIMEDOUT") { + this.logger.log(LogLevel.Warning, `Timeout from HTTP request.`); + reject(new TimeoutError()); } + this.logger.log(LogLevel.Warning, `Error from HTTP request. ${error}`); + reject(error); + return; + } - if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { - let resp: string | ArrayBuffer; - if (request.responseType === "arraybuffer") { - resp = Buffer.concat(data, dataLength); - resolve(new HttpResponse(res.statusCode, res.statusMessage || "", resp)); - } else { - resp = Buffer.concat(data, dataLength).toString(); - resolve(new HttpResponse(res.statusCode, res.statusMessage || "", resp)); - } - } else { - reject(new HttpError(res.statusMessage || "", res.statusCode || 0)); - } - }); + if (response.statusCode >= 200 && response.statusCode < 300) { + resolve(new HttpResponse(response.statusCode, response.statusMessage || "", body)); + } else { + reject(new HttpError(response.statusMessage || "", response.statusCode || 0)); + } }); - if (request.abortSignal) { - request.abortSignal.onabort = () => { - req.abort(); + if (httpRequest.abortSignal) { + httpRequest.abortSignal.onabort = () => { + currentRequest.abort(); reject(new AbortError()); }; } - - if (request.timeout) { - req.setTimeout(request.timeout, () => { - this.logger.log(LogLevel.Warning, `Timeout from HTTP request.`); - reject(new TimeoutError()); - }); - } - - req.on("error", (e: Error) => { - this.logger.log(LogLevel.Warning, `Error from HTTP request. ${e}`); - reject(e); - }); - - if (isArrayBuffer(request.content)) { - req.write(Buffer.from(request.content)); - } else { - req.write(request.content || ""); - } - req.end(); }); } + + public getCookieString(url: string): string { + return this.cookieJar.getCookieString(url); + } } diff --git a/clients/ts/signalr/src/Polyfills.ts b/clients/ts/signalr/src/Polyfills.ts index d242527d62..673ce73d5a 100644 --- a/clients/ts/signalr/src/Polyfills.ts +++ b/clients/ts/signalr/src/Polyfills.ts @@ -8,7 +8,7 @@ export interface EventSourceConstructor { } export interface WebSocketConstructor { - new(url: string, protocols?: string | string[]): WebSocket; + new(url: string, protocols?: string | string[], options?: any): WebSocket; readonly CLOSED: number; readonly CLOSING: number; readonly CONNECTING: number; diff --git a/clients/ts/signalr/src/ServerSentEventsTransport.ts b/clients/ts/signalr/src/ServerSentEventsTransport.ts index f5a31c62d6..87c5cdd91e 100644 --- a/clients/ts/signalr/src/ServerSentEventsTransport.ts +++ b/clients/ts/signalr/src/ServerSentEventsTransport.ts @@ -56,7 +56,14 @@ export class ServerSentEventsTransport implements ITransport { return; } - const eventSource = new this.eventSourceConstructor(url, { withCredentials: true }); + let eventSource: EventSource; + if (typeof window !== "undefined") { + eventSource = new this.eventSourceConstructor(url, { withCredentials: true }); + } else { + // Non-browser passes cookies via the dictionary + const cookies = this.httpClient.getCookieString(url); + eventSource = new this.eventSourceConstructor(url, { withCredentials: true, headers: { Cookie: cookies } } as EventSourceInit); + } try { eventSource.onmessage = (e: MessageEvent) => { diff --git a/clients/ts/signalr/src/WebSocketTransport.ts b/clients/ts/signalr/src/WebSocketTransport.ts index 79051d7d82..826f0d818c 100644 --- a/clients/ts/signalr/src/WebSocketTransport.ts +++ b/clients/ts/signalr/src/WebSocketTransport.ts @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +import { HttpClient } from "./HttpClient"; import { ILogger, LogLevel } from "./ILogger"; import { ITransport, TransferFormat } from "./ITransport"; import { WebSocketConstructor } from "./Polyfills"; @@ -12,17 +13,19 @@ export class WebSocketTransport implements ITransport { private readonly accessTokenFactory: (() => string | Promise) | undefined; private readonly logMessageContent: boolean; private readonly webSocketConstructor: WebSocketConstructor; + private readonly httpClient: HttpClient; private webSocket?: WebSocket; public onreceive: ((data: string | ArrayBuffer) => void) | null; public onclose: ((error?: Error) => void) | null; - constructor(accessTokenFactory: (() => string | Promise) | undefined, logger: ILogger, + constructor(httpClient: HttpClient, accessTokenFactory: (() => string | Promise) | undefined, logger: ILogger, logMessageContent: boolean, webSocketConstructor: WebSocketConstructor) { this.logger = logger; this.accessTokenFactory = accessTokenFactory; this.logMessageContent = logMessageContent; this.webSocketConstructor = webSocketConstructor; + this.httpClient = httpClient; this.onreceive = null; this.onclose = null; @@ -44,7 +47,23 @@ export class WebSocketTransport implements ITransport { return new Promise((resolve, reject) => { url = url.replace(/^http/, "ws"); - const webSocket = new this.webSocketConstructor(url); + let webSocket: WebSocket | undefined; + const cookies = this.httpClient.getCookieString(url); + + if (typeof window === "undefined" && cookies) { + // Only pass cookies when in non-browser environments + webSocket = new this.webSocketConstructor(url, undefined, { + headers: { + Cookie: `${cookies}`, + }, + }); + } + + if (!webSocket) { + // Chrome is not happy with passing 'undefined' as protocol + webSocket = new this.webSocketConstructor(url); + } + if (transferFormat === TransferFormat.Binary) { webSocket.binaryType = "arraybuffer"; } diff --git a/clients/ts/signalr/tests/WebSocketTransport.test.ts b/clients/ts/signalr/tests/WebSocketTransport.test.ts index ac2973fdec..e295a17cdf 100644 --- a/clients/ts/signalr/tests/WebSocketTransport.test.ts +++ b/clients/ts/signalr/tests/WebSocketTransport.test.ts @@ -6,6 +6,7 @@ import { TransferFormat } from "../src/ITransport"; import { WebSocketTransport } from "../src/WebSocketTransport"; import { VerifyLogger } from "./Common"; import { TestMessageEvent } from "./TestEventSource"; +import { TestHttpClient } from "./TestHttpClient"; import { TestCloseEvent, TestErrorEvent, TestEvent, TestWebSocket } from "./TestWebSocket"; import { registerUnhandledRejectionHandler } from "./Utils"; @@ -22,7 +23,7 @@ describe("WebSocketTransport", () => { it("connect waits for WebSocket to be connected", async () => { await VerifyLogger.run(async (logger) => { - const webSocket = new WebSocketTransport(undefined, logger, true, TestWebSocket); + const webSocket = new WebSocketTransport(new TestHttpClient(), undefined, logger, true, TestWebSocket); let connectComplete: boolean = false; const connectPromise = (async () => { @@ -44,7 +45,7 @@ describe("WebSocketTransport", () => { it("connect fails if there is error during connect", async () => { await VerifyLogger.run(async (logger) => { (global as any).ErrorEvent = TestErrorEvent; - const webSocket = new WebSocketTransport(undefined, logger, true, TestWebSocket); + const webSocket = new WebSocketTransport(new TestHttpClient(), undefined, logger, true, TestWebSocket); let connectComplete: boolean = false; const connectPromise = (async () => { @@ -204,7 +205,7 @@ describe("WebSocketTransport", () => { }); async function createAndStartWebSocket(logger: ILogger, url?: string, accessTokenFactory?: (() => string | Promise), format?: TransferFormat): Promise { - const webSocket = new WebSocketTransport(accessTokenFactory, logger, true, TestWebSocket); + const webSocket = new WebSocketTransport(new TestHttpClient(), accessTokenFactory, logger, true, TestWebSocket); const connectPromise = webSocket.connect(url || "http://example.com", format || TransferFormat.Text); diff --git a/clients/ts/signalr/webpack.config.js b/clients/ts/signalr/webpack.config.js index 34ba0143a6..54e3456aac 100644 --- a/clients/ts/signalr/webpack.config.js +++ b/clients/ts/signalr/webpack.config.js @@ -8,7 +8,6 @@ module.exports = baseConfig(__dirname, "signalr", { externals: [ "websocket", "eventsource", - "http", - "url", + "request" ] }); \ No newline at end of file