Merge branch 'release/2.2'

This commit is contained in:
MikaelMengistu 2018-09-26 12:37:17 -07:00
commit a260d44fe2
17 changed files with 224 additions and 234 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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();

View File

@ -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": {

View File

@ -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.");
}

View File

@ -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"));
}

View File

@ -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="
}
}
}

View File

@ -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"
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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 || "");

View File

@ -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<string>) | undefined, content: string | ArrayBuffer, logMessageContent: boolean): Promise<void> {
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,

View File

@ -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<any>("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<any>("testMethod", "arg", 42)
@ -782,6 +812,8 @@ describe("HubConnection", () => {
const hubConnection = createHubConnection(connection, logger);
try {
await hubConnection.start();
const observer = new TestObserver();
hubConnection.stream<any>("testMethod")
.subscribe(observer);
@ -800,6 +832,8 @@ describe("HubConnection", () => {
const hubConnection = createHubConnection(connection, logger);
try {
await hubConnection.start();
const observer = new TestObserver();
hubConnection.stream<any>("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<any>("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<Error>();
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<void> {
@ -1105,7 +1114,11 @@ class TestConnection implements IConnection {
public send(data: any): Promise<void> {
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;
}

View File

@ -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`);

View File

@ -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,
};

View File

@ -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;