diff --git a/clients/ts/FunctionalTests/package-lock.json b/clients/ts/FunctionalTests/package-lock.json index 06c313f807..289832d60b 100644 --- a/clients/ts/FunctionalTests/package-lock.json +++ b/clients/ts/FunctionalTests/package-lock.json @@ -8,9 +8,13 @@ "version": "file:../signalr", "requires": { "eventsource": "^1.0.7", - "websocket": "^1.0.26" + "ws": "^6.0.0" }, "dependencies": { + "async-limiter": { + "version": "1.0.0", + "bundled": true + }, "debug": { "version": "2.6.9", "bundled": true, @@ -85,6 +89,13 @@ "yaeti": "^0.0.6" } }, + "ws": { + "version": "6.0.0", + "bundled": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, "yaeti": { "version": "0.0.6", "bundled": true @@ -1086,6 +1097,19 @@ "debug": "~3.1.0", "engine.io-parser": "~2.1.0", "ws": "~3.3.1" + }, + "dependencies": { + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + } } }, "engine.io-client": { @@ -1105,6 +1129,19 @@ "ws": "~3.3.1", "xmlhttprequest-ssl": "~1.5.4", "yeast": "0.1.2" + }, + "dependencies": { + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } + } } }, "engine.io-parser": { @@ -1523,14 +1560,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1545,20 +1580,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -1675,8 +1707,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -1688,7 +1719,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -1703,7 +1733,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -1711,14 +1740,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -1737,7 +1764,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -1818,8 +1844,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -1831,7 +1856,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -1953,7 +1977,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2799,7 +2822,8 @@ "version": "2.10.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", - "dev": true + "dev": true, + "optional": true }, "nanomatch": { "version": "1.2.13", @@ -3710,15 +3734,6 @@ "mime-types": "~2.1.18" } }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, "typescript": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.0.1.tgz", @@ -4018,29 +4033,6 @@ } } }, - "websocket": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.26.tgz", - "integrity": "sha512-fjcrYDPIQxpTnqFQ9JjxUQcdvR89MFAOjPBlF+vjOt49w/XW4fJknUoMz/mDIn2eK1AdslVojcaOxOqyZZV8rw==", - "dev": true, - "requires": { - "debug": "^2.2.0", - "nan": "^2.3.3", - "typedarray-to-buffer": "^3.1.2", - "yaeti": "^0.0.6" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -4063,14 +4055,12 @@ "dev": true }, "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.0.0.tgz", + "integrity": "sha512-c2UlYcAZp1VS8AORtpq6y4RJIkJ9dQz18W32SpR/qXGfLDZ2jU4y4wKvvZwqbi7U6gxFQTeE+urMbXU/tsDy4w==", "dev": true, "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "async-limiter": "~1.0.0" } }, "xmlbuilder": { @@ -4091,12 +4081,6 @@ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", "dev": true }, - "yaeti": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=", - "dev": true - }, "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", diff --git a/clients/ts/FunctionalTests/package.json b/clients/ts/FunctionalTests/package.json index a483ed4cc3..4e4254da95 100644 --- a/clients/ts/FunctionalTests/package.json +++ b/clients/ts/FunctionalTests/package.json @@ -32,7 +32,7 @@ "karma-summary-reporter": "^1.5.0", "ts-node": "^4.1.0", "typescript": "^3.0.1", - "websocket": " ^1.0.26" + "ws": " ^6.0.0" }, "scripts": { "clean": "node ../common/node_modules/rimraf/bin.js ./wwwroot/dist ./obj/js", diff --git a/clients/ts/FunctionalTests/ts/WebSocketTests.ts b/clients/ts/FunctionalTests/ts/WebSocketTests.ts index 152e64b8cb..26b5e9c3f8 100644 --- a/clients/ts/FunctionalTests/ts/WebSocketTests.ts +++ b/clients/ts/FunctionalTests/ts/WebSocketTests.ts @@ -20,10 +20,9 @@ describe("WebSockets", () => { return; } } else { - const websocketModule = require("websocket"); - const hasWebsocket = websocketModule && websocketModule.w3cwebsocket; - if (hasWebsocket) { - webSocket = new websocketModule.w3cwebsocket(ECHOENDPOINT_URL.replace(/^http/, "ws")); + const websocketModule = require("ws"); + if (websocketModule) { + webSocket = new websocketModule(ECHOENDPOINT_URL.replace(/^http/, "ws")); } else { // No WebSockets implementations in current environment, skip test done(); diff --git a/clients/ts/signalr-protocol-msgpack/package-lock.json b/clients/ts/signalr-protocol-msgpack/package-lock.json index 68493e002a..38c6007853 100644 --- a/clients/ts/signalr-protocol-msgpack/package-lock.json +++ b/clients/ts/signalr-protocol-msgpack/package-lock.json @@ -10,7 +10,7 @@ "integrity": "sha512-EiFdEaD7EQNFl4PDBvlbPlX4hV8rJhEKfFj58jkjqvj1gN6E1lShu24cXeWH/RQ5nf+/ei4WGp70xp2ubBaE5Q==", "dev": true, "requires": { - "@types/node": "*" + "@types/node": "8.5.5" } }, "@types/msgpack5": { @@ -19,7 +19,7 @@ "integrity": "sha512-E3wILUjTXONukpiI6tmqpLwf7eV3MVTdxpjz56FqNn7koMF/6sSPUh5TxMlwgoOhyeejxwVoNZUiDcdqChKkAw==", "dev": true, "requires": { - "@types/bl": "*" + "@types/bl": "0.8.31" } }, "@types/node": { @@ -39,8 +39,8 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" + "readable-stream": "2.3.6", + "safe-buffer": "5.1.1" } }, "buffer": { @@ -49,8 +49,8 @@ "integrity": "sha512-xXvjQhVNz50v2nPeoOsNqWCLGfiv4ji/gXZM28jnVwdLJxH4mFyqgqCKfaK9zf1KUbG6zTkjLOy7ou+jSMarGA==", "dev": true, "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" + "base64-js": "1.2.1", + "ieee754": "1.1.8" } }, "core-util-is": { @@ -79,10 +79,10 @@ "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.0.2.tgz", "integrity": "sha512-rEIx0/KFtWGtqlF5D/NIMzOHDhm7AhIFzHR3/PLqMrXXbMKoSitDE/IDuTactlTjxEc0ScmHx/5qoH015uL7xA==", "requires": { - "bl": "^1.2.1", - "inherits": "^2.0.3", - "readable-stream": "^2.3.3", - "safe-buffer": "^5.1.1" + "bl": "1.2.2", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "safe-buffer": "5.1.1" } }, "process-nextick-args": { @@ -95,13 +95,13 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" } }, "safe-buffer": { @@ -114,7 +114,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "util-deprecate": { diff --git a/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts b/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts index de51e54daf..38564d14fb 100644 --- a/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts +++ b/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts @@ -7,6 +7,7 @@ import * as msgpack5 from "msgpack5"; import { CompletionMessage, HubMessage, IHubProtocol, ILogger, InvocationMessage, LogLevel, MessageHeaders, MessageType, NullLogger, StreamInvocationMessage, StreamItemMessage, TransferFormat } from "@aspnet/signalr"; import { BinaryMessageFormat } from "./BinaryMessageFormat"; +import { isArrayBuffer } from "./Utils"; // TypeDoc's @inheritDoc and @link don't work across modules :( @@ -31,7 +32,7 @@ export class MessagePackHubProtocol implements IHubProtocol { */ public parseMessages(input: ArrayBuffer | Buffer, logger: ILogger): HubMessage[] { // The interface does allow "string" to be passed in, but this implementation does not. So let's throw a useful error. - if (!(input instanceof ArrayBuffer) && !(input instanceof Buffer)) { + if (!(input instanceof Buffer) && !(isArrayBuffer(input))) { throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer or Buffer."); } diff --git a/clients/ts/signalr-protocol-msgpack/src/Utils.ts b/clients/ts/signalr-protocol-msgpack/src/Utils.ts new file mode 100644 index 0000000000..f0994cf0b3 --- /dev/null +++ b/clients/ts/signalr-protocol-msgpack/src/Utils.ts @@ -0,0 +1,11 @@ +// 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. + +// Copied from signalr/Utils.ts +/** @private */ +export function isArrayBuffer(val: any): val is ArrayBuffer { + return val && typeof ArrayBuffer !== "undefined" && + (val instanceof ArrayBuffer || + // Sometimes we get an ArrayBuffer that doesn't satisfy instanceof + (val.constructor && val.constructor.name === "ArrayBuffer")); +} diff --git a/clients/ts/signalr/package-lock.json b/clients/ts/signalr/package-lock.json index 1d10943ec8..66d96f3dc6 100644 --- a/clients/ts/signalr/package-lock.json +++ b/clients/ts/signalr/package-lock.json @@ -4,12 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@types/events": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", - "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", - "dev": true - }, "@types/eventsource": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/eventsource/-/eventsource-1.0.2.tgz", @@ -22,23 +16,10 @@ "integrity": "sha512-fCHV45gS+m3hH17zgkgADUSi2RR1Vht6wOZ0jyHP8rjiQra9f+mIcgwPQHllmDocYOstIEbKlxbFDYlgrTPYqw==", "dev": true }, - "@types/websocket": { - "version": "0.0.40", - "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-0.0.40.tgz", - "integrity": "sha512-ldteZwWIgl9cOy7FyvYn+39Ah4+PfpVE72eYKw75iy2L0zTbhbcwvzeJ5IOu6DQP93bjfXq0NGHY6FYtmYoqFQ==", - "dev": true, - "requires": { - "@types/events": "*", - "@types/node": "*" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } + "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==" }, "es6-promise": { "version": "4.2.2", @@ -51,30 +32,15 @@ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", "requires": { - "original": "^1.0.0" + "original": "1.0.2" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "nan": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", - "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==" - }, "original": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", "requires": { - "url-parse": "^1.4.3" + "url-parse": "1.4.3" } }, "querystringify": { @@ -87,38 +53,22 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "requires": { - "is-typedarray": "^1.0.0" - } - }, "url-parse": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.3.tgz", "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==", "requires": { - "querystringify": "^2.0.0", - "requires-port": "^1.0.0" + "querystringify": "2.0.0", + "requires-port": "1.0.0" } }, - "websocket": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.26.tgz", - "integrity": "sha512-fjcrYDPIQxpTnqFQ9JjxUQcdvR89MFAOjPBlF+vjOt49w/XW4fJknUoMz/mDIn2eK1AdslVojcaOxOqyZZV8rw==", + "ws": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.0.0.tgz", + "integrity": "sha512-c2UlYcAZp1VS8AORtpq6y4RJIkJ9dQz18W32SpR/qXGfLDZ2jU4y4wKvvZwqbi7U6gxFQTeE+urMbXU/tsDy4w==", "requires": { - "debug": "^2.2.0", - "nan": "^2.3.3", - "typedarray-to-buffer": "^3.1.2", - "yaeti": "^0.0.6" + "async-limiter": "1.0.0" } - }, - "yaeti": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", - "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" } } } diff --git a/clients/ts/signalr/package.json b/clients/ts/signalr/package.json index 3f3e272028..b0f1e562e0 100644 --- a/clients/ts/signalr/package.json +++ b/clients/ts/signalr/package.json @@ -37,11 +37,11 @@ ], "devDependencies": { "es6-promise": "^4.2.2", - "@types/websocket": "^0.0.40", + "@types/node": "^10.9.4", "@types/eventsource": "^1.0.2" }, "dependencies": { - "websocket": "^1.0.26", + "ws": "^6.0.0", "eventsource": "^1.0.7" } } diff --git a/clients/ts/signalr/src/HandshakeProtocol.ts b/clients/ts/signalr/src/HandshakeProtocol.ts index be1b7383d1..4c306e80b8 100644 --- a/clients/ts/signalr/src/HandshakeProtocol.ts +++ b/clients/ts/signalr/src/HandshakeProtocol.ts @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. import { TextMessageFormat } from "./TextMessageFormat"; +import { isArrayBuffer } from "./Utils"; /** @private */ export interface HandshakeRequestMessage { @@ -27,7 +28,7 @@ export class HandshakeProtocol { let messageData: string; let remainingData: any; - if (data instanceof ArrayBuffer || (typeof Buffer !== "undefined" && data instanceof Buffer)) { + if (isArrayBuffer(data) || (typeof Buffer !== "undefined" && data instanceof Buffer)) { // Format is binary but still need to read JSON text from handshake response const binaryData = new Uint8Array(data); const separatorIndex = binaryData.indexOf(TextMessageFormat.RecordSeparatorCode); @@ -56,7 +57,11 @@ export class HandshakeProtocol { // At this point we should have just the single handshake message const messages = TextMessageFormat.parse(messageData); - responseMessage = JSON.parse(messages[0]); + const response = JSON.parse(messages[0]); + if (response.type) { + throw new Error("Expected a handshake response from the server."); + } + responseMessage = response; // multiple messages could have arrived with handshake // return additional data to be parsed as usual, or null if all parsed diff --git a/clients/ts/signalr/src/HttpConnection.ts b/clients/ts/signalr/src/HttpConnection.ts index 99a013c2e4..1dbe24b846 100644 --- a/clients/ts/signalr/src/HttpConnection.ts +++ b/clients/ts/signalr/src/HttpConnection.ts @@ -39,7 +39,7 @@ let WebSocketModule: any = null; let EventSourceModule: any = null; if (typeof window === "undefined" && typeof require !== "undefined") { // tslint:disable-next-line:no-var-requires - WebSocketModule = require("websocket"); + WebSocketModule = require("ws"); // tslint:disable-next-line:no-var-requires EventSourceModule = require("eventsource"); } @@ -73,9 +73,8 @@ export class HttpConnection implements IConnection { if (!isNode && typeof WebSocket !== "undefined" && !options.WebSocket) { options.WebSocket = WebSocket; } else if (isNode && !options.WebSocket) { - const websocket = WebSocketModule && WebSocketModule.w3cwebsocket; - if (websocket) { - options.WebSocket = WebSocketModule.w3cwebsocket; + if (WebSocketModule) { + options.WebSocket = WebSocketModule; } } diff --git a/clients/ts/signalr/src/HubConnection.ts b/clients/ts/signalr/src/HubConnection.ts index 1810bc6da4..2160f21144 100644 --- a/clients/ts/signalr/src/HubConnection.ts +++ b/clients/ts/signalr/src/HubConnection.ts @@ -31,6 +31,8 @@ export class HubConnection { private id: number; private closedCallbacks: Array<(error?: Error) => void>; private receivedHandshakeResponse: boolean; + private handshakeResolver!: (value?: PromiseLike<{}>) => void; + private handshakeRejecter!: (reason?: any) => void; private connectionState: HubConnectionState; // The type of these a) doesn't matter and b) varies when building in browser and node contexts @@ -106,6 +108,11 @@ export class HubConnection { this.logger.log(LogLevel.Debug, "Starting HubConnection."); this.receivedHandshakeResponse = false; + // Set up the promise before any connection is started otherwise it could race with received messages + const handshakePromise = new Promise((resolve, reject) => { + this.handshakeResolver = resolve; + this.handshakeRejecter = reject; + }); await this.connection.start(this.protocol.transferFormat); @@ -120,6 +127,8 @@ export class HubConnection { this.resetTimeoutPeriod(); this.resetKeepAliveInterval(); + // Wait for the handshake to complete before marking connection as connected + await handshakePromise; this.connectionState = HubConnectionState.Connected; } @@ -388,19 +397,23 @@ export class HubConnection { // We don't want to wait on the stop itself. // tslint:disable-next-line:no-floating-promises this.connection.stop(error); + this.handshakeRejecter(error); throw error; } if (responseMessage.error) { const message = "Server returned handshake error: " + responseMessage.error; this.logger.log(LogLevel.Error, message); + this.handshakeRejecter(message); // We don't want to wait on the stop itself. // tslint:disable-next-line:no-floating-promises this.connection.stop(new Error(message)); + throw new Error(message); } else { this.logger.log(LogLevel.Debug, "Server handshake complete."); } + this.handshakeResolver(); return remainingData; } diff --git a/clients/ts/signalr/src/NodeHttpClient.ts b/clients/ts/signalr/src/NodeHttpClient.ts index 557f037192..3412085dc8 100644 --- a/clients/ts/signalr/src/NodeHttpClient.ts +++ b/clients/ts/signalr/src/NodeHttpClient.ts @@ -7,6 +7,7 @@ import { URL } from "url"; import { AbortError, HttpError, TimeoutError } from "./Errors"; import { HttpClient, HttpRequest, HttpResponse } from "./HttpClient"; import { ILogger, LogLevel } from "./ILogger"; +import { isArrayBuffer } from "./Utils"; export class NodeHttpClient extends HttpClient { private readonly logger: ILogger; @@ -80,7 +81,7 @@ export class NodeHttpClient extends HttpClient { reject(e); }); - if (request.content instanceof ArrayBuffer) { + if (isArrayBuffer(request.content)) { req.write(Buffer.from(request.content)); } else { req.write(request.content || ""); diff --git a/clients/ts/signalr/src/Utils.ts b/clients/ts/signalr/src/Utils.ts index 2099811678..159219d613 100644 --- a/clients/ts/signalr/src/Utils.ts +++ b/clients/ts/signalr/src/Utils.ts @@ -25,7 +25,7 @@ export class Arg { /** @private */ export function getDataDetail(data: any, includeContent: boolean): string { let detail = ""; - if (data instanceof ArrayBuffer) { + if (isArrayBuffer(data)) { detail = `Binary data of length ${data.byteLength}`; if (includeContent) { detail += `. Content: '${formatArrayBuffer(data)}'`; @@ -54,6 +54,15 @@ export function formatArrayBuffer(data: ArrayBuffer): string { return str.substr(0, str.length - 1); } +// Also in signalr-protocol-msgpack/Utils.ts +/** @private */ +export function isArrayBuffer(val: any): val is ArrayBuffer { + return val && typeof ArrayBuffer !== "undefined" && + (val instanceof ArrayBuffer || + // Sometimes we get an ArrayBuffer that doesn't satisfy instanceof + (val.constructor && val.constructor.name === "ArrayBuffer")); +} + /** @private */ export async function sendMessage(logger: ILogger, transportName: string, httpClient: HttpClient, url: string, accessTokenFactory: (() => string | Promise) | undefined, content: string | ArrayBuffer, logMessageContent: boolean): Promise { let headers; @@ -68,7 +77,7 @@ export async function sendMessage(logger: ILogger, transportName: string, httpCl logger.log(LogLevel.Trace, `(${transportName} transport) sending data. ${getDataDetail(content, logMessageContent)}.`); - const responseType = content instanceof ArrayBuffer ? "arraybuffer" : "text"; + const responseType = isArrayBuffer(content) ? "arraybuffer" : "text"; const response = await httpClient.post(url, { content, headers, diff --git a/clients/ts/signalr/tests/HubConnection.test.ts b/clients/ts/signalr/tests/HubConnection.test.ts index cbd0a5fddd..6945b88dde 100644 --- a/clients/ts/signalr/tests/HubConnection.test.ts +++ b/clients/ts/signalr/tests/HubConnection.test.ts @@ -23,7 +23,7 @@ registerUnhandledRejectionHandler(); describe("HubConnection", () => { describe("start", () => { - it("sends negotiation message", async () => { + it("sends handshake message", async () => { await VerifyLogger.run(async (logger) => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, logger); @@ -163,12 +163,16 @@ describe("HubConnection", () => { protocolCalled = true; }; - const connection = new TestConnection(); + const connection = new TestConnection(false); const hubConnection = createHubConnection(connection, logger, mockProtocol); try { + let startCompleted = false; + const startPromise = hubConnection.start().then(() => startCompleted = true); const data = "{}" + TextMessageFormat.RecordSeparator; + expect(startCompleted).toBe(false); connection.receiveText(data); + await startPromise; // message only contained handshake response expect(protocolCalled).toEqual(false); @@ -187,13 +191,18 @@ describe("HubConnection", () => { protocolCalled = true; }; - const connection = new TestConnection(); + const connection = new TestConnection(false); const hubConnection = createHubConnection(connection, logger, mockProtocol); try { + let startCompleted = false; + const startPromise = hubConnection.start().then(() => startCompleted = true); + expect(startCompleted).toBe(false); + // handshake response + message separator const data = [0x7b, 0x7d, 0x1e]; connection.receiveBinary(new Uint8Array(data).buffer); + await startPromise; // message only contained handshake response expect(protocolCalled).toEqual(false); @@ -210,9 +219,13 @@ describe("HubConnection", () => { const mockProtocol = new TestProtocol(TransferFormat.Binary); mockProtocol.onreceive = (d) => receivedProcotolData = d as ArrayBuffer; - const connection = new TestConnection(); + const connection = new TestConnection(false); const hubConnection = createHubConnection(connection, logger, mockProtocol); try { + let startCompleted = false; + const startPromise = hubConnection.start().then(() => startCompleted = true); + expect(startCompleted).toBe(false); + // handshake response + message separator + message pack message const data = [ 0x7b, 0x7d, 0x1e, 0x65, 0x95, 0x03, 0x80, 0xa1, 0x30, 0x01, 0xd9, 0x5d, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6c, @@ -224,6 +237,7 @@ describe("HubConnection", () => { ]; connection.receiveBinary(new Uint8Array(data).buffer); + await startPromise; // left over data is the message pack message expect(receivedProcotolData!.byteLength).toEqual(102); @@ -240,12 +254,17 @@ describe("HubConnection", () => { const mockProtocol = new TestProtocol(TransferFormat.Text); mockProtocol.onreceive = (d) => receivedProcotolData = d as string; - const connection = new TestConnection(); + const connection = new TestConnection(false); const hubConnection = createHubConnection(connection, logger, mockProtocol); try { + let startCompleted = false; + const startPromise = hubConnection.start().then(() => startCompleted = true); + expect(startCompleted).toBe(false); + const data = "{}" + TextMessageFormat.RecordSeparator + "{\"type\":6}" + TextMessageFormat.RecordSeparator; connection.receiveText(data); + await startPromise; expect(receivedProcotolData).toEqual("{\"type\":6}" + TextMessageFormat.RecordSeparator); } finally { @@ -259,7 +278,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, logger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); const invokePromise = hubConnection.invoke("testMethod", "arg", 42); @@ -277,7 +296,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, logger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); const invokePromise = hubConnection.invoke("testMethod", "arg", 42); @@ -296,7 +315,7 @@ describe("HubConnection", () => { const hubConnection = createHubConnection(connection, logger); - connection.receiveHandshakeResponse(); + await hubConnection.start(); const invokePromise = hubConnection.invoke("testMethod"); await hubConnection.stop(); @@ -311,7 +330,7 @@ describe("HubConnection", () => { const hubConnection = createHubConnection(connection, logger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); const invokePromise = hubConnection.invoke("testMethod"); // Typically this would be called by the transport @@ -340,7 +359,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, wrappingLogger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); connection.receive({ arguments: ["test"], @@ -370,7 +389,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, wrappingLogger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); const handler = () => { }; hubConnection.on("message", handler); @@ -396,7 +415,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, logger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); let count = 0; const handler = () => { count++; }; @@ -432,7 +451,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, logger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); let count = 0; const handler = () => { count++; }; @@ -468,7 +487,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, logger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); let count = 0; const handler = () => { count++; }; @@ -494,7 +513,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, logger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); let value = ""; hubConnection.on("message", (v) => value = v); @@ -515,13 +534,22 @@ describe("HubConnection", () => { it("stop on handshake error", async () => { await VerifyLogger.run(async (logger) => { - const connection = new TestConnection(); + const connection = new TestConnection(false); const hubConnection = createHubConnection(connection, logger); try { let closeError: Error | undefined; hubConnection.onclose((e) => closeError = e); - connection.receiveHandshakeResponse("Error!"); + let startCompleted = false; + const startPromise = hubConnection.start().then(() => startCompleted = true); + expect(startCompleted).toBe(false); + try { + connection.receiveHandshakeResponse("Error!"); + } catch { + } + await expect(startPromise) + .rejects + .toThrow("Server returned handshake error: Error!"); expect(closeError!.message).toEqual("Server returned handshake error: Error!"); } finally { @@ -543,7 +571,7 @@ describe("HubConnection", () => { closeError = e; }); - connection.receiveHandshakeResponse(); + await hubConnection.start(); connection.receive({ type: MessageType.Close, @@ -569,7 +597,7 @@ describe("HubConnection", () => { closeError = e; }); - connection.receiveHandshakeResponse(); + await hubConnection.start(); connection.receive({ error: "Error!", @@ -589,7 +617,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, logger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); let numInvocations1 = 0; let numInvocations2 = 0; @@ -616,7 +644,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, logger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); let numInvocations = 0; const callback = () => numInvocations++; @@ -674,7 +702,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, wrappingLogger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); hubConnection.on(null!, undefined!); hubConnection.on(undefined!, null!); @@ -714,11 +742,13 @@ describe("HubConnection", () => { const hubConnection = createHubConnection(connection, logger); try { + await hubConnection.start(); + hubConnection.stream("testStream", "arg", 42); - // Verify the message is sent - expect(connection.sentData.length).toBe(1); - expect(JSON.parse(connection.sentData[0])).toEqual({ + // Verify the message is sent (+ handshake) + expect(connection.sentData.length).toBe(2); + expect(JSON.parse(connection.sentData[1])).toEqual({ arguments: [ "arg", 42, @@ -741,7 +771,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, logger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); const observer = new TestObserver(); hubConnection.stream("testMethod", "arg", 42) @@ -761,7 +791,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, logger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); const observer = new TestObserver(); hubConnection.stream("testMethod", "arg", 42) @@ -782,6 +812,8 @@ describe("HubConnection", () => { const hubConnection = createHubConnection(connection, logger); try { + await hubConnection.start(); + const observer = new TestObserver(); hubConnection.stream("testMethod") .subscribe(observer); @@ -800,6 +832,8 @@ describe("HubConnection", () => { const hubConnection = createHubConnection(connection, logger); try { + await hubConnection.start(); + const observer = new TestObserver(); hubConnection.stream("testMethod") .subscribe(observer); @@ -819,7 +853,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, logger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); const observer = new TestObserver(); hubConnection.stream("testMethod") @@ -848,6 +882,7 @@ describe("HubConnection", () => { const hubConnection = createHubConnection(connection, logger); try { + await hubConnection.start(); hubConnection.stream("testMethod").subscribe(NullSubscriber.instance); // Typically this would be called by the transport @@ -865,6 +900,7 @@ describe("HubConnection", () => { const hubConnection = createHubConnection(connection, logger); try { + await hubConnection.start(); hubConnection.stream("testMethod").subscribe(NullSubscriber.instance); // Send completion to trigger observer.complete() @@ -881,7 +917,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, logger); try { - connection.receiveHandshakeResponse(); + await hubConnection.start(); const observer = new TestObserver(); const subscription = hubConnection.stream("testMethod") @@ -896,9 +932,9 @@ describe("HubConnection", () => { // Observer should no longer receive messages expect(observer.itemsReceived).toEqual([1]); - // Verify the cancel is sent - expect(connection.sentData.length).toBe(2); - expect(JSON.parse(connection.sentData[1])).toEqual({ + // Verify the cancel is sent (+ handshake) + expect(connection.sentData.length).toBe(3); + expect(JSON.parse(connection.sentData[2])).toEqual({ invocationId: connection.lastInvocationId, type: MessageType.CancelInvocation, }); @@ -986,6 +1022,7 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection, logger); try { + await hubConnection.start(); const invokePromise = hubConnection.invoke("testMethod", "arg", 42); connection.receive({ type: MessageType.Ping }); @@ -1025,37 +1062,6 @@ describe("HubConnection", () => { }); }); - it("does not timeout if message was received before HubConnection.start", async () => { - await VerifyLogger.run(async (logger) => { - const connection = new TestConnection(); - const hubConnection = createHubConnection(connection, logger); - try { - hubConnection.serverTimeoutInMilliseconds = 200; - - const p = new PromiseSource(); - hubConnection.onclose((e) => p.resolve(e)); - - // send message before start to trigger timeout handler - // testing for regression where we didn't cleanup timer if request received before start created a timer - await connection.receive({ type: MessageType.Ping }); - - await hubConnection.start(); - - for (let i = 0; i < 6; i++) { - await pingAndWait(connection); - } - - await connection.stop(); - - const error = await p.promise; - - expect(error).toBeUndefined(); - } finally { - await hubConnection.stop(); - } - }); - }); - it("terminates if no messages received within timeout interval", async () => { await VerifyLogger.run(async (logger) => { const connection = new TestConnection(); @@ -1092,11 +1098,14 @@ class TestConnection implements IConnection { public sentData: any[]; public lastInvocationId: string | null; - constructor() { + private autoHandshake: boolean | null; + + constructor(autoHandshake: boolean = true) { this.onreceive = null; this.onclose = null; this.sentData = []; this.lastInvocationId = null; + this.autoHandshake = autoHandshake; } public start(): Promise { @@ -1105,7 +1114,11 @@ class TestConnection implements IConnection { public send(data: any): Promise { const invocation = TextMessageFormat.parse(data)[0]; - const invocationId = JSON.parse(invocation).invocationId; + const parsedInvocation = JSON.parse(invocation); + const invocationId = parsedInvocation.invocationId; + if (parsedInvocation.protocol && parsedInvocation.version && this.autoHandshake) { + this.receiveHandshakeResponse(); + } if (invocationId) { this.lastInvocationId = invocationId; } diff --git a/clients/ts/signalr/tests/HubConnectionBuilder.test.ts b/clients/ts/signalr/tests/HubConnectionBuilder.test.ts index 293c896a54..d2361fa41a 100644 --- a/clients/ts/signalr/tests/HubConnectionBuilder.test.ts +++ b/clients/ts/signalr/tests/HubConnectionBuilder.test.ts @@ -65,7 +65,10 @@ describe("HubConnectionBuilder", () => { // Start the connection const closed = makeClosedPromise(connection); - await connection.start(); + + // start waits for handshake before returning, we don't care in this test + // tslint:disable-next-line:no-floating-promises + connection.start(); const pollRequest = await pollSent.promise; expect(pollRequest.url).toMatch(/http:\/\/example.com\?id=abc123.*/); @@ -109,7 +112,10 @@ describe("HubConnectionBuilder", () => { // Start the connection const closed = makeClosedPromise(connection); - await connection.start(); + + // start waits for handshake before returning, we don't care in this test + // tslint:disable-next-line:no-floating-promises + connection.start(); const negotiateRequest = await negotiateReceived.promise; expect(negotiateRequest.content).toBe(`{"protocol":"${protocol.name}","version":1}\x1E`); diff --git a/clients/ts/webpack.config.base.js b/clients/ts/webpack.config.base.js index 43f3d5f633..6779afc2ff 100644 --- a/clients/ts/webpack.config.base.js +++ b/clients/ts/webpack.config.base.js @@ -74,7 +74,6 @@ module.exports = function (modulePath, browserBaseName, options) { new webpack.IgnorePlugin(/vertx/), new webpack.IgnorePlugin(/NodeHttpClient/), new webpack.IgnorePlugin(/eventsource/), - new webpack.IgnorePlugin(/websocket/), ], externals: options.externals, }; diff --git a/src/Microsoft.AspNetCore.SignalR.Common/Protocol/HandshakeProtocol.cs b/src/Microsoft.AspNetCore.SignalR.Common/Protocol/HandshakeProtocol.cs index 4a2c38c4c5..ed1965daed 100644 --- a/src/Microsoft.AspNetCore.SignalR.Common/Protocol/HandshakeProtocol.cs +++ b/src/Microsoft.AspNetCore.SignalR.Common/Protocol/HandshakeProtocol.cs @@ -148,7 +148,7 @@ namespace Microsoft.AspNetCore.SignalR.Protocol case TypePropertyName: // a handshake response does not have a type // check the incoming message was not any other type of message - throw new InvalidDataException("Handshake response should not have a 'type' value."); + throw new InvalidDataException("Expected a handshake response from the server."); case ErrorPropertyName: error = JsonUtils.ReadAsString(reader, ErrorPropertyName); break;