Add TSLint rule file and fix all violations (#1381)

This commit is contained in:
BrennanConroy 2018-02-07 15:26:13 -08:00 committed by GitHub
parent f08da32eb2
commit 331bf3515d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 1132 additions and 937 deletions

View File

@ -10,7 +10,8 @@
}, },
"scripts": { "scripts": {
"clean": "node ../node_modules/rimraf/bin.js ./wwwroot/dist", "clean": "node ../node_modules/rimraf/bin.js ./wwwroot/dist",
"build": "npm run build:tsc && npm run build:rollup", "build": "npm run build:lint && npm run build:tsc && npm run build:rollup",
"build:lint": "node ../node_modules/tslint/bin/tslint -c ../tslint.json -p ./tsconfig.json",
"build:tsc": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json", "build:tsc": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json",
"build:rollup": "node ../node_modules/rollup/bin/rollup -c" "build:rollup": "node ../node_modules/rollup/bin/rollup -c"
}, },

View File

@ -1,13 +1,13 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { TransportType, IHubProtocol, JsonHubProtocol } from "@aspnet/signalr" import { IHubProtocol, JsonHubProtocol, TransportType } from "@aspnet/signalr";
import { MessagePackHubProtocol } from "@aspnet/signalr-protocol-msgpack" import { MessagePackHubProtocol } from "@aspnet/signalr-protocol-msgpack";
export const ECHOENDPOINT_URL = "http://" + document.location.host + "/echo"; export const ECHOENDPOINT_URL = "http://" + document.location.host + "/echo";
export function getTransportTypes(): TransportType[] { export function getTransportTypes(): TransportType[] {
var transportTypes = []; const transportTypes = [];
if (typeof WebSocket !== "undefined") { if (typeof WebSocket !== "undefined") {
transportTypes.push(TransportType.WebSockets); transportTypes.push(TransportType.WebSockets);
} }
@ -20,13 +20,13 @@ export function getTransportTypes(): TransportType[] {
} }
export function eachTransport(action: (transport: TransportType) => void) { export function eachTransport(action: (transport: TransportType) => void) {
getTransportTypes().forEach(function (t) { getTransportTypes().forEach((t) => {
return action(t); return action(t);
}); });
} }
export function eachTransportAndProtocol(action: (transport: TransportType, protocol: IHubProtocol) => void) { export function eachTransportAndProtocol(action: (transport: TransportType, protocol: IHubProtocol) => void) {
var protocols : IHubProtocol[] = [new JsonHubProtocol()]; const protocols: IHubProtocol[] = [new JsonHubProtocol()];
// IE9 does not support XmlHttpRequest advanced features so disable for now // IE9 does not support XmlHttpRequest advanced features so disable for now
// This can be enabled if we fix: https://github.com/aspnet/SignalR/issues/742 // This can be enabled if we fix: https://github.com/aspnet/SignalR/issues/742
if (typeof new XMLHttpRequest().responseType === "string") { if (typeof new XMLHttpRequest().responseType === "string") {
@ -35,9 +35,9 @@ export function eachTransportAndProtocol(action: (transport: TransportType, prot
// Everything works fine in the module // Everything works fine in the module
protocols.push(new MessagePackHubProtocol()); protocols.push(new MessagePackHubProtocol());
} }
getTransportTypes().forEach(function (t) { getTransportTypes().forEach((t) => {
return protocols.forEach(function (p) { return protocols.forEach((p) => {
return action(t, p); return action(t, p);
}); });
}); });
} }

View File

@ -1,63 +1,63 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { HttpConnection, LogLevel, TransportType } from "@aspnet/signalr" import { HttpConnection, LogLevel, TransportType } from "@aspnet/signalr";
import { eachTransport, ECHOENDPOINT_URL } from "./Common" import { eachTransport, ECHOENDPOINT_URL } from "./Common";
describe('connection', function () { describe("connection", () => {
if (typeof WebSocket !== 'undefined') { if (typeof WebSocket !== "undefined") {
it("can connect to the server without specifying transport explicitly", function (done) { it("can connect to the server without specifying transport explicitly", (done) => {
var message = "Hello World!"; const message = "Hello World!";
var connection = new HttpConnection(ECHOENDPOINT_URL); const connection = new HttpConnection(ECHOENDPOINT_URL);
var received = ""; let received = "";
connection.onreceive = function (data) { connection.onreceive = (data) => {
received += data; received += data;
if (data == message) { if (data === message) {
connection.stop(); connection.stop();
} }
}; };
connection.onclose = function (error) { connection.onclose = (error) => {
expect(error).toBeUndefined(); expect(error).toBeUndefined();
done(); done();
}; };
connection.start().then(function () { connection.start().then(() => {
connection.send(message); connection.send(message);
}).catch(function (e) { }).catch((e) => {
fail(); fail();
done(); done();
}); });
}); });
} }
eachTransport(function (transportType) { eachTransport((transportType) => {
it("over " + TransportType[transportType] + " can send and receive messages", function (done) { it("over " + TransportType[transportType] + " can send and receive messages", (done) => {
var message = "Hello World!"; const message = "Hello World!";
// the url should be resolved relative to the document.location.host // the url should be resolved relative to the document.location.host
// and the leading '/' should be automatically added to the url // and the leading '/' should be automatically added to the url
var connection = new HttpConnection("echo", { const connection = new HttpConnection("echo", {
logger: LogLevel.Trace,
transport: transportType, transport: transportType,
logger: LogLevel.Trace
}); });
var received = ""; let received = "";
connection.onreceive = function (data) { connection.onreceive = (data) => {
received += data; received += data;
if (data == message) { if (data === message) {
connection.stop(); connection.stop();
} }
}; };
connection.onclose = function (error) { connection.onclose = (error) => {
expect(error).toBeUndefined(); expect(error).toBeUndefined();
done(); done();
}; };
connection.start().then(function () { connection.start().then(() => {
connection.send(message); connection.send(message);
}).catch(function (e) { }).catch((e) => {
fail(); fail();
done(); done();
}); });

View File

@ -1,394 +1,394 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { TransportType, HubConnection, LogLevel } from "@aspnet/signalr"; import { HubConnection, LogLevel, TransportType } from "@aspnet/signalr";
import { eachTransportAndProtocol, eachTransport } from "./Common"; import { eachTransport, eachTransportAndProtocol } from "./Common";
var TESTHUBENDPOINT_URL = '/testhub'; const TESTHUBENDPOINT_URL = "/testhub";
describe('hubConnection', function () { describe("hubConnection", () => {
eachTransportAndProtocol(function (transportType, protocol) { eachTransportAndProtocol((transportType, protocol) => {
describe(protocol.name + ' over ' + TransportType[transportType] + ' transport', function () { describe(protocol.name + " over " + TransportType[transportType] + " transport", () => {
it('can invoke server method and receive result', function (done) { it("can invoke server method and receive result", (done) => {
var message = '你好,世界!'; const message = "你好,世界!";
var hubConnection = new HubConnection(TESTHUBENDPOINT_URL, { const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType, transport: transportType,
protocol: protocol,
logger: LogLevel.Trace
}); });
hubConnection.onclose(function (error) { hubConnection.onclose((error) => {
expect(error).toBe(undefined); expect(error).toBe(undefined);
done(); done();
}); });
hubConnection.start().then(function () { hubConnection.start().then(() => {
hubConnection.invoke('Echo', message).then(function (result) { hubConnection.invoke("Echo", message).then((result) => {
expect(result).toBe(message); expect(result).toBe(message);
}).catch(function (e) { }).catch((e) => {
fail(e); fail(e);
}).then(function () { }).then(() => {
hubConnection.stop(); hubConnection.stop();
}); });
}).catch(function (e) { }).catch((e) => {
fail(e); fail(e);
done(); done();
}); });
}); });
it('can invoke server method non-blocking and not receive result', function (done) { it("can invoke server method non-blocking and not receive result", (done) => {
var message = '你好,世界!'; const message = "你好,世界!";
var hubConnection = new HubConnection(TESTHUBENDPOINT_URL, { const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType, transport: transportType,
protocol: protocol,
logger: LogLevel.Trace
}); });
hubConnection.onclose(function (error) { hubConnection.onclose((error) => {
expect(error).toBe(undefined); expect(error).toBe(undefined);
done(); done();
}); });
hubConnection.start().then(function () { hubConnection.start().then(() => {
hubConnection.send('Echo', message).catch(function (e) { hubConnection.send("Echo", message).catch((e) => {
fail(e); fail(e);
}).then(function () { }).then(() => {
hubConnection.stop(); hubConnection.stop();
}); });
}).catch(function (e) { }).catch((e) => {
fail(e); fail(e);
done(); done();
}); });
}); });
it('can invoke server method structural object and receive structural result', function (done) { it("can invoke server method structural object and receive structural result", (done) => {
var hubConnection = new HubConnection(TESTHUBENDPOINT_URL, { const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType, transport: transportType,
protocol: protocol,
logger: LogLevel.Trace
}); });
hubConnection.on('CustomObject', function (customObject) { hubConnection.on("CustomObject", (customObject) => {
expect(customObject.Name).toBe('test'); expect(customObject.Name).toBe("test");
expect(customObject.Value).toBe(42); expect(customObject.Value).toBe(42);
hubConnection.stop(); hubConnection.stop();
}); });
hubConnection.onclose(function (error) { hubConnection.onclose((error) => {
expect(error).toBe(undefined); expect(error).toBe(undefined);
done(); done();
}); });
hubConnection.start().then(function () { hubConnection.start().then(() => {
hubConnection.send('SendCustomObject', { Name: 'test', Value: 42 }); hubConnection.send("SendCustomObject", { Name: "test", Value: 42 });
}).catch(function (e) { }).catch((e) => {
fail(e); fail(e);
done(); done();
}); });
}); });
it('can stream server method and receive result', function (done) { it("can stream server method and receive result", (done) => {
var hubConnection = new HubConnection(TESTHUBENDPOINT_URL, { const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType, transport: transportType,
protocol: protocol,
logger: LogLevel.Trace
}); });
hubConnection.onclose(function (error) { hubConnection.onclose((error) => {
expect(error).toBe(undefined); expect(error).toBe(undefined);
done(); done();
}); });
var received = []; const received = [];
hubConnection.start().then(function () { hubConnection.start().then(() => {
hubConnection.stream('Stream').subscribe({ hubConnection.stream("Stream").subscribe({
next: function next(item) { complete: function complete() {
received.push(item); expect(received).toEqual(["a", "b", "c"]);
hubConnection.stop();
}, },
error: function error(err) { error: function error(err) {
fail(err); fail(err);
hubConnection.stop(); hubConnection.stop();
}, },
complete: function complete() { next: function next(item) {
expect(received).toEqual(['a', 'b', 'c']); received.push(item);
hubConnection.stop(); },
}
}); });
}).catch(function (e) { }).catch((e) => {
fail(e); fail(e);
done(); done();
}); });
}); });
it('rethrows an exception from the server when invoking', function (done) { it("rethrows an exception from the server when invoking", (done) => {
var errorMessage = 'An error occurred.'; const errorMessage = "An error occurred.";
var hubConnection = new HubConnection(TESTHUBENDPOINT_URL, { const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType, transport: transportType,
protocol: protocol,
logger: LogLevel.Trace
}); });
hubConnection.start().then(function () { hubConnection.start().then(() => {
hubConnection.invoke('ThrowException', errorMessage).then(function () { hubConnection.invoke("ThrowException", errorMessage).then(() => {
// exception expected but none thrown // exception expected but none thrown
fail(); fail();
}).catch(function (e) { }).catch((e) => {
expect(e.message).toBe(errorMessage); expect(e.message).toBe(errorMessage);
}).then(function () { }).then(() => {
return hubConnection.stop(); return hubConnection.stop();
}).then(function () { }).then(() => {
done(); done();
}); });
}).catch(function (e) { }).catch((e) => {
fail(e); fail(e);
done(); done();
}); });
}); });
it('throws an exception when invoking streaming method with invoke', function (done) { it("throws an exception when invoking streaming method with invoke", (done) => {
var hubConnection = new HubConnection(TESTHUBENDPOINT_URL, { const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType, transport: transportType,
protocol: protocol,
logger: LogLevel.Trace
}); });
hubConnection.start().then(function () { hubConnection.start().then(() => {
hubConnection.invoke('EmptyStream').then(function () { hubConnection.invoke("EmptyStream").then(() => {
// exception expected but none thrown // exception expected but none thrown
fail(); fail();
}).catch(function (e) { }).catch((e) => {
expect(e.message).toBe('The client attempted to invoke the streaming \'EmptyStream\' method in a non-streaming fashion.'); expect(e.message).toBe("The client attempted to invoke the streaming 'EmptyStream' method in a non-streaming fashion.");
}).then(function () { }).then(() => {
return hubConnection.stop(); return hubConnection.stop();
}).then(function () { }).then(() => {
done(); done();
}); });
}).catch(function (e) { }).catch((e) => {
fail(e); fail(e);
done(); done();
}); });
}); });
it('throws an exception when receiving a streaming result for method called with invoke', function (done) { it("throws an exception when receiving a streaming result for method called with invoke", (done) => {
var hubConnection = new HubConnection(TESTHUBENDPOINT_URL, { const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType, transport: transportType,
protocol: protocol,
logger: LogLevel.Trace
}); });
hubConnection.start().then(function () { hubConnection.start().then(() => {
hubConnection.invoke('Stream').then(function () { hubConnection.invoke("Stream").then(() => {
// exception expected but none thrown // exception expected but none thrown
fail(); fail();
}).catch(function (e) { }).catch((e) => {
expect(e.message).toBe('The client attempted to invoke the streaming \'Stream\' method in a non-streaming fashion.'); expect(e.message).toBe("The client attempted to invoke the streaming 'Stream' method in a non-streaming fashion.");
}).then(function () { }).then(() => {
return hubConnection.stop(); return hubConnection.stop();
}).then(function () { }).then(() => {
done(); done();
}); });
}).catch(function (e) { }).catch((e) => {
fail(e); fail(e);
done(); done();
}); });
}); });
it('rethrows an exception from the server when streaming', function (done) { it("rethrows an exception from the server when streaming", (done) => {
var errorMessage = 'An error occurred.'; const errorMessage = "An error occurred.";
var hubConnection = new HubConnection(TESTHUBENDPOINT_URL, { const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType, transport: transportType,
protocol: protocol,
logger: LogLevel.Trace
}); });
hubConnection.start().then(function () { hubConnection.start().then(() => {
hubConnection.stream('StreamThrowException', errorMessage).subscribe({ hubConnection.stream("StreamThrowException", errorMessage).subscribe({
next: function next(item) { complete: function complete() {
hubConnection.stop(); hubConnection.stop();
fail(); fail();
}, },
error: function error(err) { error: function error(err) {
expect(err.message).toEqual('An error occurred.'); expect(err.message).toEqual("An error occurred.");
hubConnection.stop(); hubConnection.stop();
done(); done();
}, },
complete: function complete() {
hubConnection.stop();
fail();
}
});
}).catch(function (e) {
fail(e);
done();
});
});
it('throws an exception when invoking hub method with stream', function (done) {
var hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
transport: transportType,
protocol: protocol,
logger: LogLevel.Trace
});
hubConnection.start().then(function () {
hubConnection.stream('Echo', '42').subscribe({
next: function next(item) { next: function next(item) {
hubConnection.stop(); hubConnection.stop();
fail(); fail();
}, },
error: function error(err) {
expect(err.message).toEqual('The client attempted to invoke the non-streaming \'Echo\' method in a streaming fashion.');
hubConnection.stop();
done();
},
complete: function complete() {
hubConnection.stop();
fail();
}
}); });
}).catch(function (e) { }).catch((e) => {
fail(e); fail(e);
done(); done();
}); });
}); });
it('can receive server calls', function (done) { it("throws an exception when invoking hub method with stream", (done) => {
var hubConnection = new HubConnection(TESTHUBENDPOINT_URL, { const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType, transport: transportType,
protocol: protocol,
logger: LogLevel.Trace
}); });
var message = '你好 SignalR'; hubConnection.start().then(() => {
hubConnection.stream("Echo", "42").subscribe({
complete: function complete() {
hubConnection.stop();
fail();
},
error: function error(err) {
expect(err.message).toEqual("The client attempted to invoke the non-streaming 'Echo' method in a streaming fashion.");
hubConnection.stop();
done();
},
next: function next(item) {
hubConnection.stop();
fail();
},
});
}).catch((e) => {
fail(e);
done();
});
});
it("can receive server calls", (done) => {
const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType,
});
const message = "你好 SignalR";
// client side method names are case insensitive // client side method names are case insensitive
var methodName = 'message'; let methodName = "message";
var idx = Math.floor(Math.random() * (methodName.length - 1)); const idx = Math.floor(Math.random() * (methodName.length - 1));
methodName = methodName.substr(0, idx) + methodName[idx].toUpperCase() + methodName.substr(idx + 1); methodName = methodName.substr(0, idx) + methodName[idx].toUpperCase() + methodName.substr(idx + 1);
hubConnection.on(methodName, function (msg) { hubConnection.on(methodName, (msg) => {
expect(msg).toBe(message); expect(msg).toBe(message);
done(); done();
}); });
hubConnection.start() hubConnection.start()
.then(function () { .then(() => {
return hubConnection.invoke('InvokeWithString', message); return hubConnection.invoke("InvokeWithString", message);
}) })
.then(function () { .then(() => {
return hubConnection.stop(); return hubConnection.stop();
}) })
.catch(function (e) { .catch((e) => {
fail(e); fail(e);
done(); done();
}); });
}); });
it('can receive server calls without rebinding handler when restarted', function (done) { it("can receive server calls without rebinding handler when restarted", (done) => {
var hubConnection = new HubConnection(TESTHUBENDPOINT_URL, { const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType, transport: transportType,
protocol: protocol,
logger: LogLevel.Trace
}); });
var message = '你好 SignalR'; const message = "你好 SignalR";
// client side method names are case insensitive // client side method names are case insensitive
var methodName = 'message'; let methodName = "message";
var idx = Math.floor(Math.random() * (methodName.length - 1)); const idx = Math.floor(Math.random() * (methodName.length - 1));
methodName = methodName.substr(0, idx) + methodName[idx].toUpperCase() + methodName.substr(idx + 1); methodName = methodName.substr(0, idx) + methodName[idx].toUpperCase() + methodName.substr(idx + 1);
let closeCount = 0; let closeCount = 0;
let invocationCount = 0; let invocationCount = 0;
hubConnection.onclose(function (e) { hubConnection.onclose((e) => {
expect(e).toBeUndefined(); expect(e).toBeUndefined();
closeCount += 1; closeCount += 1;
if (closeCount === 1) { if (closeCount === 1) {
// Reconnect // Reconnect
hubConnection.start() hubConnection.start()
.then(function () { .then(() => {
return hubConnection.invoke('InvokeWithString', message); return hubConnection.invoke("InvokeWithString", message);
}) })
.then(function () { .then(() => {
return hubConnection.stop(); return hubConnection.stop();
}) })
.catch(function (e) { .catch((error) => {
fail(e); fail(error);
done(); done();
}); });
} else { } else {
expect(invocationCount).toBe(2); expect(invocationCount).toBe(2);
done(); done();
} }
}) });
hubConnection.on(methodName, function (msg) { hubConnection.on(methodName, (msg) => {
expect(msg).toBe(message); expect(msg).toBe(message);
invocationCount += 1; invocationCount += 1;
}); });
hubConnection.start() hubConnection.start()
.then(function () { .then(() => {
return hubConnection.invoke('InvokeWithString', message); return hubConnection.invoke("InvokeWithString", message);
}) })
.then(function () { .then(() => {
return hubConnection.stop(); return hubConnection.stop();
}) })
.catch(function (e) { .catch((e) => {
fail(e); fail(e);
done(); done();
}); });
}); });
it('closed with error if hub cannot be created', function (done) { it("closed with error if hub cannot be created", (done) => {
var errorRegex = { const errorRegex = {
WebSockets: '1011|1005', // Message is browser specific (e.g. 'Websocket closed with status code: 1011'), Edge and IE report 1005 even though the server sent 1011 LongPolling: "Internal Server Error",
LongPolling: 'Internal Server Error', ServerSentEvents: "Error occurred",
ServerSentEvents: 'Error occurred' WebSockets: "1011|1005", // Message is browser specific (e.g. 'Websocket closed with status code: 1011'), Edge and IE report 1005 even though the server sent 1011
}; };
var hubConnection = new HubConnection('http://' + document.location.host + '/uncreatable', { const hubConnection = new HubConnection("http://" + document.location.host + "/uncreatable", {
logger: LogLevel.Trace,
protocol,
transport: transportType, transport: transportType,
protocol: protocol,
logger: LogLevel.Trace
}); });
hubConnection.onclose(function (error) { hubConnection.onclose((error) => {
expect(error.message).toMatch(errorRegex[TransportType[transportType]]); expect(error.message).toMatch(errorRegex[TransportType[transportType]]);
done(); done();
}); });
hubConnection.start(); hubConnection.start();
}); });
it('can handle different types', function (done) { it("can handle different types", (done) => {
var hubConnection = new HubConnection(TESTHUBENDPOINT_URL, { const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType, transport: transportType,
protocol: protocol,
logger: LogLevel.Trace
}); });
hubConnection.onclose(function (error) { hubConnection.onclose((error) => {
expect(error).toBe(undefined); expect(error).toBe(undefined);
done(); done();
}); });
var complexObject = { const complexObject = {
String: 'Hello, World!',
IntArray: [0x01, 0x02, 0x03, 0xff],
ByteArray: protocol.name === "json" ByteArray: protocol.name === "json"
? "aGVsbG8=" ? "aGVsbG8="
: new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f]), : new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f]),
GUID: protocol.name === "json" GUID: protocol.name === "json"
? "00010203-0405-0607-0706-050403020100" ? "00010203-0405-0607-0706-050403020100"
: new Uint8Array([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00]) : new Uint8Array([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00]),
IntArray: [0x01, 0x02, 0x03, 0xff],
String: "Hello, World!",
}; };
hubConnection.start() hubConnection.start()
.then(function () { .then(() => {
return hubConnection.invoke('EchoComplexObject', complexObject); return hubConnection.invoke("EchoComplexObject", complexObject);
}) })
.then(function (value) { .then((value) => {
if (protocol.name === "messagepack") { if (protocol.name === "messagepack") {
// msgpack creates a Buffer for byte arrays and jasmine fails to compare a Buffer // msgpack creates a Buffer for byte arrays and jasmine fails to compare a Buffer
// and a Uint8Array even though Buffer instances are also Uint8Array instances // and a Uint8Array even though Buffer instances are also Uint8Array instances
@ -398,7 +398,7 @@ describe('hubConnection', function () {
// be extracted. Note that with msgpack5 the original bytes will be encoded with utf8 // be extracted. Note that with msgpack5 the original bytes will be encoded with utf8
// and needs to be decoded. To not go into utf8 encoding intricacies the test uses values // and needs to be decoded. To not go into utf8 encoding intricacies the test uses values
// less than 0x80. // less than 0x80.
let guidBytes = []; const guidBytes = [];
for (let i = 0; i < value.GUID.length; i++) { for (let i = 0; i < value.GUID.length; i++) {
guidBytes.push(value.GUID.charCodeAt(i)); guidBytes.push(value.GUID.charCodeAt(i));
} }
@ -406,40 +406,40 @@ describe('hubConnection', function () {
} }
expect(value).toEqual(complexObject); expect(value).toEqual(complexObject);
}) })
.then(function () { .then(() => {
hubConnection.stop(); hubConnection.stop();
}) })
.catch(function (e) { .catch((e) => {
fail(e); fail(e);
done(); done();
}); });
}); });
it('can be restarted', function (done) { it("can be restarted", (done) => {
var message = '你好,世界!'; const message = "你好,世界!";
var hubConnection = new HubConnection(TESTHUBENDPOINT_URL, { const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
logger: LogLevel.Trace,
protocol,
transport: transportType, transport: transportType,
protocol: protocol,
logger: LogLevel.Trace
}); });
let closeCount = 0; let closeCount = 0;
hubConnection.onclose(function (error) { hubConnection.onclose((error) => {
expect(error).toBe(undefined); expect(error).toBe(undefined);
// Start and invoke again // Start and invoke again
if (closeCount === 0) { if (closeCount === 0) {
closeCount += 1; closeCount += 1;
hubConnection.start().then(function () { hubConnection.start().then(() => {
hubConnection.invoke('Echo', message).then(function (result) { hubConnection.invoke("Echo", message).then((result) => {
expect(result).toBe(message); expect(result).toBe(message);
}).catch(function (e) { }).catch((e) => {
fail(e); fail(e);
}).then(function () { }).then(() => {
hubConnection.stop() hubConnection.stop();
}); });
}).catch(function (e) { }).catch((e) => {
fail(e); fail(e);
done(); done();
}); });
@ -448,15 +448,15 @@ describe('hubConnection', function () {
} }
}); });
hubConnection.start().then(function () { hubConnection.start().then(() => {
hubConnection.invoke('Echo', message).then(function (result) { hubConnection.invoke("Echo", message).then((result) => {
expect(result).toBe(message); expect(result).toBe(message);
}).catch(function (e) { }).catch((e) => {
fail(e); fail(e);
}).then(function () { }).then(() => {
hubConnection.stop() hubConnection.stop();
}); });
}).catch(function (e) { }).catch((e) => {
fail(e); fail(e);
done(); done();
}); });
@ -464,53 +464,53 @@ describe('hubConnection', function () {
}); });
}); });
eachTransport(function (transportType) { eachTransport((transportType) => {
describe(' over ' + TransportType[transportType] + ' transport', function () { describe(" over " + TransportType[transportType] + " transport", () => {
it('can connect to hub with authorization', async function (done) { it("can connect to hub with authorization", async (done) => {
var message = '你好,世界!'; const message = "你好,世界!";
var hubConnection; let hubConnection;
getJwtToken('http://' + document.location.host + '/generateJwtToken') getJwtToken("http://" + document.location.host + "/generateJwtToken")
.then(jwtToken => { .then((jwtToken) => {
hubConnection = new HubConnection('/authorizedhub', { hubConnection = new HubConnection("/authorizedhub", {
transport: transportType, accessTokenFactory: () => jwtToken,
logger: LogLevel.Trace, logger: LogLevel.Trace,
accessTokenFactory: () => jwtToken transport: transportType,
}); });
hubConnection.onclose(function (error) { hubConnection.onclose((error) => {
expect(error).toBe(undefined); expect(error).toBe(undefined);
done(); done();
}); });
return hubConnection.start(); return hubConnection.start();
}) })
.then(() => { .then(() => {
return hubConnection.invoke('Echo', message) return hubConnection.invoke("Echo", message);
}) })
.then(response => { .then((response) => {
expect(response).toEqual(message); expect(response).toEqual(message);
done(); done();
}) })
.catch(err => { .catch((err) => {
fail(err); fail(err);
done(); done();
}); });
}); });
if (transportType != TransportType.LongPolling) { if (transportType !== TransportType.LongPolling) {
it("terminates if no messages received within timeout interval", function (done) { it("terminates if no messages received within timeout interval", (done) => {
var hubConnection = new HubConnection(TESTHUBENDPOINT_URL, { const hubConnection = new HubConnection(TESTHUBENDPOINT_URL, {
transport: transportType,
logger: LogLevel.Trace, logger: LogLevel.Trace,
timeoutInMilliseconds: 100 timeoutInMilliseconds: 100,
transport: transportType,
}); });
var timeout = setTimeout(200, function () { const timeout = setTimeout(200, () => {
fail("Server timeout did not fire within expected interval"); fail("Server timeout did not fire within expected interval");
}); });
hubConnection.start().then(function () { hubConnection.start().then(() => {
hubConnection.onclose(function (error) { hubConnection.onclose((error) => {
clearTimeout(timeout); clearTimeout(timeout);
expect(error).toEqual(new Error("Server timeout elapsed without receiving a message from the server.")); expect(error).toEqual(new Error("Server timeout elapsed without receiving a message from the server."));
done(); done();
@ -521,25 +521,24 @@ describe('hubConnection', function () {
}); });
}); });
function getJwtToken(url) : Promise<string> { function getJwtToken(url): Promise<string> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open('GET', url, true); xhr.open("GET", url, true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
xhr.send(); xhr.send();
xhr.onload = () => { xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) { if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.response || xhr.responseText); resolve(xhr.response || xhr.responseText);
} } else {
else {
reject(new Error(xhr.statusText)); reject(new Error(xhr.statusText));
} }
}; };
xhr.onerror = () => { xhr.onerror = () => {
reject(new Error(xhr.statusText)); reject(new Error(xhr.statusText));
} };
}); });
} }
}); });

View File

@ -3,26 +3,26 @@
import { ECHOENDPOINT_URL } from "./Common"; import { ECHOENDPOINT_URL } from "./Common";
if (typeof WebSocket !== 'undefined') { if (typeof WebSocket !== "undefined") {
describe('WebSockets', function () { describe("WebSockets", () => {
it('can be used to connect to SignalR', function (done) { it("can be used to connect to SignalR", (done) => {
var message = "message"; const message = "message";
var webSocket = new WebSocket(ECHOENDPOINT_URL.replace(/^http/, "ws")); const webSocket = new WebSocket(ECHOENDPOINT_URL.replace(/^http/, "ws"));
webSocket.onopen = function () { webSocket.onopen = () => {
webSocket.send(message); webSocket.send(message);
}; };
var received = ""; let received = "";
webSocket.onmessage = function (event) { webSocket.onmessage = (event) => {
received += event.data; received += event.data;
if (received === message) { if (received === message) {
webSocket.close(); webSocket.close();
} }
}; };
webSocket.onclose = function (event) { webSocket.onclose = (event) => {
if (!event.wasClean) { if (!event.wasClean) {
fail("connection closed with unexpected status code: " + event.code + " " + event.reason); fail("connection closed with unexpected status code: " + event.code + " " + event.reason);
} }

View File

@ -1,8 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import 'es6-promise/dist/es6-promise.auto.js'; import "es6-promise/dist/es6-promise.auto.js";
import './ConnectionTests' import "./ConnectionTests";
import './HubConnectionTests' import "./HubConnectionTests";
import './WebSocketTests' import "./WebSocketTests";

View File

@ -16,6 +16,27 @@
"integrity": "sha512-KA4GKOpgXnrqEH2eCVhiv2CsxgXGQJgV1X0vsGlh+WCnxbeAE1GT44ZsTU1IN5dEeV/gDupKa7gWo08V5IxWVQ==", "integrity": "sha512-KA4GKOpgXnrqEH2eCVhiv2CsxgXGQJgV1X0vsGlh+WCnxbeAE1GT44ZsTU1IN5dEeV/gDupKa7gWo08V5IxWVQ==",
"dev": true "dev": true
}, },
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
},
"ansi-styles": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
"integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
"dev": true
},
"argparse": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
"integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
"dev": true,
"requires": {
"sprintf-js": "1.0.3"
}
},
"arr-diff": { "arr-diff": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
@ -43,6 +64,32 @@
"integrity": "sha1-GcenYEc3dEaPILLS0DNyrX1Mv10=", "integrity": "sha1-GcenYEc3dEaPILLS0DNyrX1Mv10=",
"dev": true "dev": true
}, },
"babel-code-frame": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
"integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
"dev": true,
"requires": {
"chalk": "1.1.3",
"esutils": "2.0.2",
"js-tokens": "3.0.2"
},
"dependencies": {
"chalk": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
"dev": true,
"requires": {
"ansi-styles": "2.2.1",
"escape-string-regexp": "1.0.5",
"has-ansi": "2.0.0",
"strip-ansi": "3.0.1",
"supports-color": "2.0.0"
}
}
}
},
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -76,6 +123,58 @@
"integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
"dev": true "dev": true
}, },
"chalk": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz",
"integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==",
"dev": true,
"requires": {
"ansi-styles": "3.2.0",
"escape-string-regexp": "1.0.5",
"supports-color": "4.5.0"
},
"dependencies": {
"ansi-styles": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz",
"integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==",
"dev": true,
"requires": {
"color-convert": "1.9.1"
}
},
"supports-color": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
"integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
"dev": true,
"requires": {
"has-flag": "2.0.0"
}
}
}
},
"color-convert": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz",
"integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
},
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
},
"commander": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.14.0.tgz",
"integrity": "sha512-okPpdvdJr6mUGi2XzupC+irQxzwGLVaBzacFC14hjLv8NColXEsxsU+QaeuSSXpQUak5g2K0vQ7WjA1e8svczg==",
"dev": true
},
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -88,12 +187,36 @@
"integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
"dev": true "dev": true
}, },
"diff": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz",
"integrity": "sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==",
"dev": true
},
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"esprima": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
"integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==",
"dev": true
},
"estree-walker": { "estree-walker": {
"version": "0.3.1", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.3.1.tgz", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.3.1.tgz",
"integrity": "sha1-5rGlHPcpJSTnI3wxLl/mZgwc4ao=", "integrity": "sha1-5rGlHPcpJSTnI3wxLl/mZgwc4ao=",
"dev": true "dev": true
}, },
"esutils": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
"dev": true
},
"exit": { "exit": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
@ -200,6 +323,21 @@
"is-glob": "2.0.1" "is-glob": "2.0.1"
} }
}, },
"has-ansi": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
"integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
"dev": true,
"requires": {
"ansi-regex": "2.1.1"
}
},
"has-flag": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
"integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
"dev": true
},
"inflight": { "inflight": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
@ -317,6 +455,22 @@
"integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=", "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=",
"dev": true "dev": true
}, },
"js-tokens": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
"integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
"dev": true
},
"js-yaml": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
"integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==",
"dev": true,
"requires": {
"argparse": "1.0.9",
"esprima": "4.0.0"
}
},
"kind-of": { "kind-of": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
@ -579,6 +733,12 @@
"micromatch": "2.3.11" "micromatch": "2.3.11"
} }
}, },
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
"dev": true
},
"source-map-resolve": { "source-map-resolve": {
"version": "0.5.1", "version": "0.5.1",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz",
@ -598,6 +758,62 @@
"integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
"dev": true "dev": true
}, },
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "2.1.1"
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
"dev": true
},
"tslib": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.0.tgz",
"integrity": "sha512-f/qGG2tUkrISBlQZEjEqoZ3B2+npJjIf04H1wuAv9iA8i04Icp+61KRXxFdha22670NJopsZCIjhC3SnjPRKrQ==",
"dev": true
},
"tslint": {
"version": "5.9.1",
"resolved": "https://registry.npmjs.org/tslint/-/tslint-5.9.1.tgz",
"integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=",
"dev": true,
"requires": {
"babel-code-frame": "6.26.0",
"builtin-modules": "1.1.1",
"chalk": "2.3.0",
"commander": "2.14.0",
"diff": "3.4.0",
"glob": "7.1.2",
"js-yaml": "3.10.0",
"minimatch": "3.0.4",
"resolve": "1.5.0",
"semver": "5.5.0",
"tslib": "1.9.0",
"tsutils": "2.21.0"
}
},
"tsutils": {
"version": "2.21.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.21.0.tgz",
"integrity": "sha512-zlOHTYtTwvTiKxUyAU8wiKzPpAgwZrGjb7AY18VUlxuCgBiTMVorIl5HjrCT8V64Hm34RI1BZITJMVQpBLMxVg==",
"dev": true,
"requires": {
"tslib": "1.9.0"
}
},
"typescript": { "typescript": {
"version": "2.7.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.1.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.1.tgz",

View File

@ -18,6 +18,7 @@
"rollup-plugin-commonjs": "^8.2.6", "rollup-plugin-commonjs": "^8.2.6",
"rollup-plugin-node-resolve": "^3.0.2", "rollup-plugin-node-resolve": "^3.0.2",
"rollup-plugin-sourcemaps": "^0.4.2", "rollup-plugin-sourcemaps": "^0.4.2",
"tslint": "^5.9.1",
"typescript": "^2.7.1", "typescript": "^2.7.1",
"uglify-js": "^3.3.5" "uglify-js": "^3.3.5"
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@aspnet/signalr-protocol-msgpack", "name": "@aspnet/signalr-protocol-msgpack",
"version": "1.0.0-preview1-t000", "version": "1.0.0-preview2-t000",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -12,7 +12,8 @@
}, },
"scripts": { "scripts": {
"clean": "node ../node_modules/rimraf/bin.js ./dist", "clean": "node ../node_modules/rimraf/bin.js ./dist",
"build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:browser && npm run build:uglify", "build": "npm run clean && npm run build:lint && npm run build:esm && npm run build:cjs && npm run build:browser && npm run build:uglify",
"build:lint": "node ../node_modules/tslint/bin/tslint -c ../tslint.json -p ./tsconfig.json",
"build:esm": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module es2015 --outDir ./dist/esm --target ES2015 -d", "build:esm": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module es2015 --outDir ./dist/esm --target ES2015 -d",
"build:cjs": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module commonjs --outDir ./dist/cjs --target ES5", "build:cjs": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module commonjs --outDir ./dist/cjs --target ES5",
"build:browser": "node ../node_modules/rollup/bin/rollup -c", "build:browser": "node ../node_modules/rollup/bin/rollup -c",

View File

@ -6,13 +6,12 @@ export class BinaryMessageFormat {
// The length prefix of binary messages is encoded as VarInt. Read the comment in // The length prefix of binary messages is encoded as VarInt. Read the comment in
// the BinaryMessageParser.TryParseMessage for details. // the BinaryMessageParser.TryParseMessage for details.
static write(output: Uint8Array): ArrayBuffer { public static write(output: Uint8Array): ArrayBuffer {
// msgpack5 uses returns Buffer instead of Uint8Array on IE10 and some other browser // msgpack5 uses returns Buffer instead of Uint8Array on IE10 and some other browser
// in which case .byteLength does will be undefined // in which case .byteLength does will be undefined
let size = output.byteLength || output.length; let size = output.byteLength || output.length;
let lenBuffer = []; const lenBuffer = [];
do do {
{
let sizePart = size & 0x7f; let sizePart = size & 0x7f;
size = size >> 7; size = size >> 7;
if (size > 0) { if (size > 0) {
@ -26,15 +25,15 @@ export class BinaryMessageFormat {
// in which case .byteLength does will be undefined // in which case .byteLength does will be undefined
size = output.byteLength || output.length; size = output.byteLength || output.length;
let buffer = new Uint8Array(lenBuffer.length + size); const buffer = new Uint8Array(lenBuffer.length + size);
buffer.set(lenBuffer, 0); buffer.set(lenBuffer, 0);
buffer.set(output, lenBuffer.length); buffer.set(output, lenBuffer.length);
return buffer.buffer; return buffer.buffer;
} }
static parse(input: ArrayBuffer): Uint8Array[] { public static parse(input: ArrayBuffer): Uint8Array[] {
let result: Uint8Array[] = []; const result: Uint8Array[] = [];
let uint8Array = new Uint8Array(input); const uint8Array = new Uint8Array(input);
const maxLengthPrefixSize = 5; const maxLengthPrefixSize = 5;
const numBitsToShift = [0, 7, 14, 21, 28 ]; const numBitsToShift = [0, 7, 14, 21, 28 ];
@ -42,13 +41,12 @@ export class BinaryMessageFormat {
let numBytes = 0; let numBytes = 0;
let size = 0; let size = 0;
let byteRead; let byteRead;
do do {
{
byteRead = uint8Array[offset + numBytes]; byteRead = uint8Array[offset + numBytes];
size = size | ((byteRead & 0x7f) << (numBitsToShift[numBytes])); size = size | ((byteRead & 0x7f) << (numBitsToShift[numBytes]));
numBytes++; numBytes++;
} }
while (numBytes < Math.min(maxLengthPrefixSize, input.byteLength - offset) && (byteRead & 0x80) != 0); while (numBytes < Math.min(maxLengthPrefixSize, input.byteLength - offset) && (byteRead & 0x80) !== 0);
if ((byteRead & 0x80) !== 0 && numBytes < maxLengthPrefixSize) { if ((byteRead & 0x80) !== 0 && numBytes < maxLengthPrefixSize) {
throw new Error("Cannot read message size."); throw new Error("Cannot read message size.");
@ -63,8 +61,7 @@ export class BinaryMessageFormat {
result.push(uint8Array.slice result.push(uint8Array.slice
? uint8Array.slice(offset + numBytes, offset + numBytes + size) ? uint8Array.slice(offset + numBytes, offset + numBytes + size)
: uint8Array.subarray(offset + numBytes, offset + numBytes + size)); : uint8Array.subarray(offset + numBytes, offset + numBytes + size));
} } else {
else {
throw new Error("Incomplete message."); throw new Error("Incomplete message.");
} }
@ -73,4 +70,4 @@ export class BinaryMessageFormat {
return result; return result;
} }
} }

View File

@ -1,33 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { IHubProtocol, ProtocolType, MessageType, HubMessage, InvocationMessage, StreamItemMessage, CompletionMessage, StreamInvocationMessage, MessageHeaders } from "@aspnet/signalr"; import { CompletionMessage, HubMessage, IHubProtocol, InvocationMessage, MessageHeaders, MessageType, ProtocolType, StreamInvocationMessage, StreamItemMessage } from "@aspnet/signalr";
import { BinaryMessageFormat } from "./BinaryMessageFormat" import { Buffer } from "buffer";
import { Buffer } from 'buffer';
import * as msgpack5 from "msgpack5"; import * as msgpack5 from "msgpack5";
import { BinaryMessageFormat } from "./BinaryMessageFormat";
export class MessagePackHubProtocol implements IHubProtocol { export class MessagePackHubProtocol implements IHubProtocol {
readonly name: string = "messagepack"; public readonly name: string = "messagepack";
readonly type: ProtocolType = ProtocolType.Binary; public readonly type: ProtocolType = ProtocolType.Binary;
parseMessages(input: ArrayBuffer): HubMessage[] { public parseMessages(input: ArrayBuffer): HubMessage[] {
return BinaryMessageFormat.parse(input).map(m => this.parseMessage(m)); return BinaryMessageFormat.parse(input).map((m) => this.parseMessage(m));
} }
private parseMessage(input: Uint8Array): HubMessage { private parseMessage(input: Uint8Array): HubMessage {
if (input.length == 0) { if (input.length === 0) {
throw new Error("Invalid payload."); throw new Error("Invalid payload.");
} }
let msgpack = msgpack5(); const msgpack = msgpack5();
let properties = msgpack.decode(new Buffer(input)); const properties = msgpack.decode(new Buffer(input));
if (properties.length == 0 || !(properties instanceof Array)) { if (properties.length === 0 || !(properties instanceof Array)) {
throw new Error("Invalid payload."); throw new Error("Invalid payload.");
} }
let messageType = properties[0] as MessageType; const messageType = properties[0] as MessageType;
switch (messageType) { switch (messageType) {
case MessageType.Invocation: case MessageType.Invocation:
@ -44,52 +44,51 @@ export class MessagePackHubProtocol implements IHubProtocol {
} }
private createPingMessage(properties: any[]): HubMessage { private createPingMessage(properties: any[]): HubMessage {
if (properties.length != 1) { if (properties.length !== 1) {
throw new Error("Invalid payload for Ping message."); throw new Error("Invalid payload for Ping message.");
} }
return { return {
// Ping messages have no headers. // Ping messages have no headers.
type: MessageType.Ping type: MessageType.Ping,
} as HubMessage; } as HubMessage;
} }
private createInvocationMessage(headers: MessageHeaders, properties: any[]): InvocationMessage { private createInvocationMessage(headers: MessageHeaders, properties: any[]): InvocationMessage {
if (properties.length != 5) { if (properties.length !== 5) {
throw new Error("Invalid payload for Invocation message."); throw new Error("Invalid payload for Invocation message.");
} }
let invocationId = properties[2] as string; const invocationId = properties[2] as string;
if (invocationId) { if (invocationId) {
return { return {
headers,
type: MessageType.Invocation,
invocationId: invocationId,
target: properties[3] as string,
arguments: properties[4], arguments: properties[4],
};
}
else {
return {
headers, headers,
invocationId,
target: properties[3] as string,
type: MessageType.Invocation, type: MessageType.Invocation,
};
} else {
return {
arguments: properties[4],
headers,
target: properties[3], target: properties[3],
arguments: properties[4] type: MessageType.Invocation,
}; };
} }
} }
private createStreamItemMessage(headers: MessageHeaders, properties: any[]): StreamItemMessage { private createStreamItemMessage(headers: MessageHeaders, properties: any[]): StreamItemMessage {
if (properties.length != 4) { if (properties.length !== 4) {
throw new Error("Invalid payload for stream Result message."); throw new Error("Invalid payload for stream Result message.");
} }
return { return {
headers, headers,
type: MessageType.StreamItem,
invocationId: properties[2], invocationId: properties[2],
item: properties[3] item: properties[3],
type: MessageType.StreamItem,
} as StreamItemMessage; } as StreamItemMessage;
} }
@ -102,19 +101,19 @@ export class MessagePackHubProtocol implements IHubProtocol {
const voidResult = 2; const voidResult = 2;
const nonVoidResult = 3; const nonVoidResult = 3;
let resultKind = properties[3]; const resultKind = properties[3];
if ((resultKind === voidResult && properties.length != 4) || if ((resultKind === voidResult && properties.length !== 4) ||
(resultKind !== voidResult && properties.length != 5)) { (resultKind !== voidResult && properties.length !== 5)) {
throw new Error("Invalid payload for Completion message."); throw new Error("Invalid payload for Completion message.");
} }
let completionMessage = { const completionMessage = {
headers,
type: MessageType.Completion,
invocationId: properties[2],
error: null as string, error: null as string,
result: null as any headers,
invocationId: properties[2],
result: null as any,
type: MessageType.Completion,
}; };
switch (resultKind) { switch (resultKind) {
@ -129,7 +128,7 @@ export class MessagePackHubProtocol implements IHubProtocol {
return completionMessage as CompletionMessage; return completionMessage as CompletionMessage;
} }
writeMessage(message: HubMessage): ArrayBuffer { public writeMessage(message: HubMessage): ArrayBuffer {
switch (message.type) { switch (message.type) {
case MessageType.Invocation: case MessageType.Invocation:
return this.writeInvocation(message as InvocationMessage); return this.writeInvocation(message as InvocationMessage);
@ -144,26 +143,26 @@ export class MessagePackHubProtocol implements IHubProtocol {
} }
private writeInvocation(invocationMessage: InvocationMessage): ArrayBuffer { private writeInvocation(invocationMessage: InvocationMessage): ArrayBuffer {
let msgpack = msgpack5(); const msgpack = msgpack5();
let payload = msgpack.encode([MessageType.Invocation, invocationMessage.headers || {}, invocationMessage.invocationId || null, const payload = msgpack.encode([MessageType.Invocation, invocationMessage.headers || {}, invocationMessage.invocationId || null,
invocationMessage.target, invocationMessage.arguments]); invocationMessage.target, invocationMessage.arguments]);
return BinaryMessageFormat.write(payload.slice()); return BinaryMessageFormat.write(payload.slice());
} }
private writeStreamInvocation(streamInvocationMessage: StreamInvocationMessage): ArrayBuffer { private writeStreamInvocation(streamInvocationMessage: StreamInvocationMessage): ArrayBuffer {
let msgpack = msgpack5(); const msgpack = msgpack5();
let payload = msgpack.encode([MessageType.StreamInvocation, streamInvocationMessage.headers || {}, streamInvocationMessage.invocationId, const payload = msgpack.encode([MessageType.StreamInvocation, streamInvocationMessage.headers || {}, streamInvocationMessage.invocationId,
streamInvocationMessage.target, streamInvocationMessage.arguments]); streamInvocationMessage.target, streamInvocationMessage.arguments]);
return BinaryMessageFormat.write(payload.slice()); return BinaryMessageFormat.write(payload.slice());
} }
private readHeaders(properties: any): MessageHeaders { private readHeaders(properties: any): MessageHeaders {
let headers: MessageHeaders = properties[1] as MessageHeaders; const headers: MessageHeaders = properties[1] as MessageHeaders;
if (typeof headers !== "object") { if (typeof headers !== "object") {
throw new Error("Invalid headers."); throw new Error("Invalid headers.");
} }
return headers; return headers;
} }
} }

View File

@ -3,4 +3,4 @@
// This is where we add any polyfills we'll need for the browser. It is the entry module for browser-specific builds. // This is where we add any polyfills we'll need for the browser. It is the entry module for browser-specific builds.
export * from './index' export * from "./index";

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
export { MessagePackHubProtocol } from "./MessagePackHubProtocol"; export { MessagePackHubProtocol } from "./MessagePackHubProtocol";

View File

@ -1,6 +1,6 @@
{ {
"name": "@aspnet/signalr", "name": "@aspnet/signalr",
"version": "1.0.0-preview1-t000", "version": "1.0.0-preview2-t000",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -12,7 +12,8 @@
}, },
"scripts": { "scripts": {
"clean": "node ../node_modules/rimraf/bin.js ./dist ./.rpt2_cache", "clean": "node ../node_modules/rimraf/bin.js ./dist ./.rpt2_cache",
"build": "npm run clean && npm run build:esm && npm run build:cjs && npm run build:browser && npm run build:uglify", "build": "npm run clean && npm run build:lint && npm run build:esm && npm run build:cjs && npm run build:browser && npm run build:uglify",
"build:lint": "node ../node_modules/tslint/bin/tslint -c ../tslint.json -p ./tsconfig.json",
"build:esm": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module es2015 --outDir ./dist/esm --target ES2015 -d && node ./build/process-dts.js", "build:esm": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module es2015 --outDir ./dist/esm --target ES2015 -d && node ./build/process-dts.js",
"build:cjs": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module commonjs --outDir ./dist/cjs --target ES5", "build:cjs": "node ../node_modules/typescript/bin/tsc --project ./tsconfig.json --module commonjs --outDir ./dist/cjs --target ES5",
"build:browser": "node ../node_modules/rollup/bin/rollup -c", "build:browser": "node ../node_modules/rollup/bin/rollup -c",

View File

@ -1,8 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { asyncit as it } from "./Utils";
import { AbortController } from "../src/AbortController"; import { AbortController } from "../src/AbortController";
import { asyncit as it } from "./Utils";
describe("AbortSignal", () => { describe("AbortSignal", () => {
describe("aborted", () => { describe("aborted", () => {
@ -11,21 +11,21 @@ describe("AbortSignal", () => {
}); });
it("is true when aborted", () => { it("is true when aborted", () => {
let controller = new AbortController(); const controller = new AbortController();
let signal = controller.signal; const signal = controller.signal;
controller.abort(); controller.abort();
expect(signal.aborted).toBe(true); expect(signal.aborted).toBe(true);
}) });
}); });
describe("onabort", () => { describe("onabort", () => {
it("is called when abort is called", () => { it("is called when abort is called", () => {
let controller = new AbortController(); const controller = new AbortController();
let signal = controller.signal; const signal = controller.signal;
let abortCalled = false; let abortCalled = false;
signal.onabort = () => abortCalled = true; signal.onabort = () => abortCalled = true;
controller.abort(); controller.abort();
expect(abortCalled).toBe(true); expect(abortCalled).toBe(true);
}) });
}) });
}); });

View File

@ -1,14 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { Base64EncodedHubProtocol } from "../src/Base64EncodedHubProtocol" import { Base64EncodedHubProtocol } from "../src/Base64EncodedHubProtocol";
import { IHubProtocol, HubMessage, ProtocolType } from "../src/IHubProtocol" import { HubMessage, IHubProtocol, ProtocolType } from "../src/IHubProtocol";
class FakeHubProtocol implements IHubProtocol { class FakeHubProtocol implements IHubProtocol {
name: "fakehubprotocol"; public name: "fakehubprotocol";
type: ProtocolType; public type: ProtocolType;
parseMessages(input: any): HubMessage[] { public parseMessages(input: any): HubMessage[] {
let s = ""; let s = "";
new Uint8Array(input).forEach((item: any) => { new Uint8Array(input).forEach((item: any) => {
@ -18,9 +18,9 @@ class FakeHubProtocol implements IHubProtocol {
return JSON.parse(s); return JSON.parse(s);
} }
writeMessage(message: HubMessage): any { public writeMessage(message: HubMessage): any {
let s = JSON.stringify(message); const s = JSON.stringify(message);
let payload = new Uint8Array(s.length); const payload = new Uint8Array(s.length);
for (let i = 0; i < payload.length; i++) { for (let i = 0; i < payload.length; i++) {
payload[i] = s.charCodeAt(i); payload[i] = s.charCodeAt(i);
} }
@ -36,9 +36,9 @@ describe("Base64EncodedHubProtocol", () => {
["1.0:A;", new Error("Invalid length: '1.0'")], ["1.0:A;", new Error("Invalid length: '1.0'")],
["2:A;", new Error("Invalid message size.")], ["2:A;", new Error("Invalid message size.")],
["2:ABC;", new Error("Invalid message size.")], ["2:ABC;", new Error("Invalid message size.")],
] as [string, Error][]).forEach(([payload, expected_error]) => { ] as Array<[string, Error]>).forEach(([payload, expectedError]) => {
it(`should fail to parse '${payload}'`, () => { it(`should fail to parse '${payload}'`, () => {
expect(() => new Base64EncodedHubProtocol(new FakeHubProtocol()).parseMessages(payload)).toThrow(expected_error); expect(() => new Base64EncodedHubProtocol(new FakeHubProtocol()).parseMessages(payload)).toThrow(expectedError);
}); });
}); });
@ -47,10 +47,10 @@ describe("Base64EncodedHubProtocol", () => {
] as [[string, any]]).forEach(([payload, message]) => { ] as [[string, any]]).forEach(([payload, message]) => {
it(`should be able to parse '${payload}'`, () => { it(`should be able to parse '${payload}'`, () => {
let globalAny: any = global; const globalAny: any = global;
globalAny.atob = function(input: any) { return input }; globalAny.atob = (input: any) => input;
let result = new Base64EncodedHubProtocol(new FakeHubProtocol()).parseMessages(payload); const result = new Base64EncodedHubProtocol(new FakeHubProtocol()).parseMessages(payload);
expect(result).toEqual(message); expect(result).toEqual(message);
delete globalAny.atob; delete globalAny.atob;
@ -59,13 +59,13 @@ describe("Base64EncodedHubProtocol", () => {
([ ([
[{}, "2:{};"], [{}, "2:{};"],
] as [any, string][]).forEach(([message, payload]) => { ] as Array<[any, string]>).forEach(([message, payload]) => {
it(`should be able to write '${JSON.stringify(message)}'`, () => { it(`should be able to write '${JSON.stringify(message)}'`, () => {
let globalAny: any = global; const globalAny: any = global;
globalAny.btoa = function(input: any) { return input }; globalAny.btoa = (input: any) => input;
let result = new Base64EncodedHubProtocol(new FakeHubProtocol()).writeMessage(message); const result = new Base64EncodedHubProtocol(new FakeHubProtocol()).writeMessage(message);
expect(result).toEqual(payload); expect(result).toEqual(payload);
delete globalAny.btoa; delete globalAny.btoa;

View File

@ -1,23 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { ITransport, TransportType } from "../src/Transports" import { ITransport, TransportType } from "../src/Transports";
export function eachTransport(action: (transport: TransportType) => void) { export function eachTransport(action: (transport: TransportType) => void) {
let transportTypes = [ const transportTypes = [
TransportType.WebSockets, TransportType.WebSockets,
TransportType.ServerSentEvents, TransportType.ServerSentEvents,
TransportType.LongPolling ]; TransportType.LongPolling ];
transportTypes.forEach(t => action(t)); transportTypes.forEach((t) => action(t));
}; }
export function eachEndpointUrl(action: (givenUrl: string, expectedUrl: string) => void) { export function eachEndpointUrl(action: (givenUrl: string, expectedUrl: string) => void) {
let urls = [ const urls = [
[ "http://tempuri.org/endpoint/?q=my/Data", "http://tempuri.org/endpoint/negotiate?q=my/Data" ], [ "http://tempuri.org/endpoint/?q=my/Data", "http://tempuri.org/endpoint/negotiate?q=my/Data" ],
[ "http://tempuri.org/endpoint?q=my/Data", "http://tempuri.org/endpoint/negotiate?q=my/Data" ], [ "http://tempuri.org/endpoint?q=my/Data", "http://tempuri.org/endpoint/negotiate?q=my/Data" ],
[ "http://tempuri.org/endpoint", "http://tempuri.org/endpoint/negotiate" ], [ "http://tempuri.org/endpoint", "http://tempuri.org/endpoint/negotiate" ],
[ "http://tempuri.org/endpoint/", "http://tempuri.org/endpoint/negotiate" ] [ "http://tempuri.org/endpoint/", "http://tempuri.org/endpoint/negotiate" ],
]; ];
urls.forEach(t => action(t[0], t[1])); urls.forEach((t) => action(t[0], t[1]));
} }

View File

@ -1,15 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { asyncit as it } from "./Utils"
import { TestHttpClient } from "./TestHttpClient";
import { HttpRequest } from "../src/index"; import { HttpRequest } from "../src/index";
import { TestHttpClient } from "./TestHttpClient";
import { asyncit as it } from "./Utils";
describe("HttpClient", () => { describe("HttpClient", () => {
describe("get", () => { describe("get", () => {
it("sets the method and URL appropriately", async () => { it("sets the method and URL appropriately", async () => {
let request: HttpRequest; let request: HttpRequest;
let testClient = new TestHttpClient().on(r => { const testClient = new TestHttpClient().on((r) => {
request = r; return ""; request = r; return "";
}); });
@ -20,21 +20,21 @@ describe("HttpClient", () => {
it("overrides method and url in options", async () => { it("overrides method and url in options", async () => {
let request: HttpRequest; let request: HttpRequest;
let testClient = new TestHttpClient().on(r => { const testClient = new TestHttpClient().on((r) => {
request = r; return ""; request = r; return "";
}); });
await testClient.get("http://localhost", { await testClient.get("http://localhost", {
method: "OPTIONS", method: "OPTIONS",
url: "http://wrong" url: "http://wrong",
}); });
expect(request.method).toEqual("GET"); expect(request.method).toEqual("GET");
expect(request.url).toEqual("http://localhost"); expect(request.url).toEqual("http://localhost");
}) });
it("copies other options", async () => { it("copies other options", async () => {
let request: HttpRequest; let request: HttpRequest;
let testClient = new TestHttpClient().on(r => { const testClient = new TestHttpClient().on((r) => {
request = r; return ""; request = r; return "";
}); });
@ -42,13 +42,13 @@ describe("HttpClient", () => {
timeout: 42, timeout: 42,
}); });
expect(request.timeout).toEqual(42); expect(request.timeout).toEqual(42);
}) });
}); });
describe("post", () => { describe("post", () => {
it("sets the method and URL appropriately", async () => { it("sets the method and URL appropriately", async () => {
let request: HttpRequest; let request: HttpRequest;
let testClient = new TestHttpClient().on(r => { const testClient = new TestHttpClient().on((r) => {
request = r; return ""; request = r; return "";
}); });
@ -59,21 +59,21 @@ describe("HttpClient", () => {
it("overrides method and url in options", async () => { it("overrides method and url in options", async () => {
let request: HttpRequest; let request: HttpRequest;
let testClient = new TestHttpClient().on(r => { const testClient = new TestHttpClient().on((r) => {
request = r; return ""; request = r; return "";
}); });
await testClient.post("http://localhost", { await testClient.post("http://localhost", {
method: "OPTIONS", method: "OPTIONS",
url: "http://wrong" url: "http://wrong",
}); });
expect(request.method).toEqual("POST"); expect(request.method).toEqual("POST");
expect(request.url).toEqual("http://localhost"); expect(request.url).toEqual("http://localhost");
}) });
it("copies other options", async () => { it("copies other options", async () => {
let request: HttpRequest; let request: HttpRequest;
let testClient = new TestHttpClient().on(r => { const testClient = new TestHttpClient().on((r) => {
request = r; return ""; request = r; return "";
}); });
@ -81,6 +81,6 @@ describe("HttpClient", () => {
timeout: 42, timeout: 42,
}); });
expect(request.timeout).toEqual(42); expect(request.timeout).toEqual(42);
}) });
}); });
}); });

View File

@ -1,16 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { TestHttpClient } from "./TestHttpClient" import { DataReceived, TransportClosed } from "../src/Common";
import { HttpConnection } from "../src/HttpConnection" import { HttpConnection } from "../src/HttpConnection";
import { IHttpConnectionOptions } from "../src/HttpConnection" import { IHttpConnectionOptions } from "../src/HttpConnection";
import { DataReceived, TransportClosed } from "../src/Common"
import { ITransport, TransportType, TransferMode } from "../src/Transports"
import { eachTransport, eachEndpointUrl } from "./Common";
import { HttpResponse } from "../src/index"; import { HttpResponse } from "../src/index";
import { ITransport, TransferMode, TransportType } from "../src/Transports";
import { eachEndpointUrl, eachTransport } from "./Common";
import { TestHttpClient } from "./TestHttpClient";
const commonOptions: IHttpConnectionOptions = { const commonOptions: IHttpConnectionOptions = {
logger: null logger: null,
}; };
describe("HttpConnection", () => { describe("HttpConnection", () => {
@ -20,38 +20,37 @@ describe("HttpConnection", () => {
}); });
it("cannot be created with relative url if window object is not present", () => { it("cannot be created with relative url if window object is not present", () => {
(<any>global).window = {}; (global as any).window = {};
expect(() => new HttpConnection("/test", commonOptions)) expect(() => new HttpConnection("/test", commonOptions))
.toThrow(new Error("Cannot resolve '/test'.")); .toThrow(new Error("Cannot resolve '/test'."));
delete (<any>global).window; delete (global as any).window;
}); });
it("starting connection fails if getting id fails", async (done) => { it("starting connection fails if getting id fails", async (done) => {
let options: IHttpConnectionOptions = { const options: IHttpConnectionOptions = {
...commonOptions, ...commonOptions,
httpClient: new TestHttpClient() httpClient: new TestHttpClient()
.on("POST", r => Promise.reject("error")) .on("POST", (r) => Promise.reject("error"))
.on("GET", r => ""), .on("GET", (r) => ""),
} as IHttpConnectionOptions; } as IHttpConnectionOptions;
let connection = new HttpConnection("http://tempuri.org", options); const connection = new HttpConnection("http://tempuri.org", options);
try { try {
await connection.start(); await connection.start();
fail(); fail();
done(); done();
} } catch (e) {
catch (e) {
expect(e).toBe("error"); expect(e).toBe("error");
done(); done();
} }
}); });
it("cannot start a running connection", async (done) => { it("cannot start a running connection", async (done) => {
let options: IHttpConnectionOptions = { const options: IHttpConnectionOptions = {
...commonOptions, ...commonOptions,
httpClient: new TestHttpClient() httpClient: new TestHttpClient()
.on("POST", r => { .on("POST", (r) => {
connection.start() connection.start()
.then(() => { .then(() => {
fail(); fail();
@ -65,12 +64,11 @@ describe("HttpConnection", () => {
}), }),
} as IHttpConnectionOptions; } as IHttpConnectionOptions;
let connection = new HttpConnection("http://tempuri.org", options); const connection = new HttpConnection("http://tempuri.org", options);
try { try {
await connection.start(); await connection.start();
} } catch (e) {
catch (e) {
// This exception is thrown after the actual verification is completed. // This exception is thrown after the actual verification is completed.
// The connection is not setup to be running so just ignore the error. // The connection is not setup to be running so just ignore the error.
} }
@ -78,17 +76,17 @@ describe("HttpConnection", () => {
it("can start a stopped connection", async (done) => { it("can start a stopped connection", async (done) => {
let negotiateCalls = 0; let negotiateCalls = 0;
let options: IHttpConnectionOptions = { const options: IHttpConnectionOptions = {
...commonOptions, ...commonOptions,
httpClient: new TestHttpClient() httpClient: new TestHttpClient()
.on("POST", r => { .on("POST", (r) => {
negotiateCalls += 1; negotiateCalls += 1;
return Promise.reject("reached negotiate"); return Promise.reject("reached negotiate");
}) })
.on("GET", r => ""), .on("GET", (r) => ""),
} as IHttpConnectionOptions; } as IHttpConnectionOptions;
let connection = new HttpConnection("http://tempuri.org", options); const connection = new HttpConnection("http://tempuri.org", options);
try { try {
await connection.start(); await connection.start();
@ -106,40 +104,39 @@ describe("HttpConnection", () => {
}); });
it("can stop a starting connection", async (done) => { it("can stop a starting connection", async (done) => {
let options: IHttpConnectionOptions = { const options: IHttpConnectionOptions = {
...commonOptions, ...commonOptions,
httpClient: new TestHttpClient() httpClient: new TestHttpClient()
.on("POST", r => { .on("POST", (r) => {
connection.stop(); connection.stop();
return "{}"; return "{}";
}) })
.on("GET", r => { .on("GET", (r) => {
connection.stop(); connection.stop();
return ""; return "";
}), }),
} as IHttpConnectionOptions; } as IHttpConnectionOptions;
let connection = new HttpConnection("http://tempuri.org", options); const connection = new HttpConnection("http://tempuri.org", options);
try { try {
await connection.start(); await connection.start();
done(); done();
} } catch (e) {
catch (e) {
fail(); fail();
done(); done();
} }
}); });
it("can stop a non-started connection", async (done) => { it("can stop a non-started connection", async (done) => {
let connection = new HttpConnection("http://tempuri.org", commonOptions); const connection = new HttpConnection("http://tempuri.org", commonOptions);
await connection.stop(); await connection.stop();
done(); done();
}); });
it("preserves users connection string", async done => { it("preserves users connection string", async (done) => {
let connectUrl: string; let connectUrl: string;
let fakeTransport: ITransport = { const fakeTransport: ITransport = {
connect(url: string): Promise<TransferMode> { connect(url: string): Promise<TransferMode> {
connectUrl = url; connectUrl = url;
return Promise.reject(TransferMode.Text); return Promise.reject(TransferMode.Text);
@ -150,27 +147,25 @@ describe("HttpConnection", () => {
stop(): Promise<void> { stop(): Promise<void> {
return Promise.resolve(); return Promise.resolve();
}, },
onreceive: undefined,
onclose: undefined, onclose: undefined,
} onreceive: undefined,
};
let options: IHttpConnectionOptions = { const options: IHttpConnectionOptions = {
...commonOptions, ...commonOptions,
httpClient: new TestHttpClient() httpClient: new TestHttpClient()
.on("POST", r => "{ \"connectionId\": \"42\" }") .on("POST", (r) => "{ \"connectionId\": \"42\" }")
.on("GET", r => ""), .on("GET", (r) => ""),
transport: fakeTransport, transport: fakeTransport,
} as IHttpConnectionOptions; } as IHttpConnectionOptions;
const connection = new HttpConnection("http://tempuri.org?q=myData", options);
let connection = new HttpConnection("http://tempuri.org?q=myData", options);
try { try {
await connection.start(); await connection.start();
fail(); fail();
done(); done();
} } catch (e) {
catch (e) {
} }
expect(connectUrl).toBe("http://tempuri.org?q=myData&id=42"); expect(connectUrl).toBe("http://tempuri.org?q=myData&id=42");
@ -178,18 +173,18 @@ describe("HttpConnection", () => {
}); });
eachEndpointUrl((givenUrl: string, expectedUrl: string) => { eachEndpointUrl((givenUrl: string, expectedUrl: string) => {
it("negotiate request puts 'negotiate' at the end of the path", async done => { it("negotiate request puts 'negotiate' at the end of the path", async (done) => {
let negotiateUrl: string; let negotiateUrl: string;
let connection: HttpConnection; let connection: HttpConnection;
let options: IHttpConnectionOptions = { const options: IHttpConnectionOptions = {
...commonOptions, ...commonOptions,
httpClient: new TestHttpClient() httpClient: new TestHttpClient()
.on("POST", r => { .on("POST", (r) => {
negotiateUrl = r.url; negotiateUrl = r.url;
connection.stop(); connection.stop();
return "{}"; return "{}";
}) })
.on("GET", r => { .on("GET", (r) => {
connection.stop(); connection.stop();
return ""; return "";
}), }),
@ -214,62 +209,59 @@ describe("HttpConnection", () => {
if (requestedTransport === TransportType.WebSockets) { if (requestedTransport === TransportType.WebSockets) {
return; return;
} }
it(`cannot be started if requested ${TransportType[requestedTransport]} transport not available on server`, async done => { it(`cannot be started if requested ${TransportType[requestedTransport]} transport not available on server`, async (done) => {
let options: IHttpConnectionOptions = { const options: IHttpConnectionOptions = {
...commonOptions, ...commonOptions,
httpClient: new TestHttpClient() httpClient: new TestHttpClient()
.on("POST", r => "{ \"connectionId\": \"42\", \"availableTransports\": [] }") .on("POST", (r) => "{ \"connectionId\": \"42\", \"availableTransports\": [] }")
.on("GET", r => ""), .on("GET", (r) => ""),
transport: requestedTransport, transport: requestedTransport,
} as IHttpConnectionOptions; } as IHttpConnectionOptions;
let connection = new HttpConnection("http://tempuri.org", options); const connection = new HttpConnection("http://tempuri.org", options);
try { try {
await connection.start(); await connection.start();
fail(); fail();
done(); done();
} } catch (e) {
catch (e) {
expect(e.message).toBe("No available transports found."); expect(e.message).toBe("No available transports found.");
done(); done();
} }
}); });
}); });
it("cannot be started if no transport available on server and no transport requested", async done => { it("cannot be started if no transport available on server and no transport requested", async (done) => {
let options: IHttpConnectionOptions = { const options: IHttpConnectionOptions = {
...commonOptions, ...commonOptions,
httpClient: new TestHttpClient() httpClient: new TestHttpClient()
.on("POST", r => "{ \"connectionId\": \"42\", \"availableTransports\": [] }") .on("POST", (r) => "{ \"connectionId\": \"42\", \"availableTransports\": [] }")
.on("GET", r => ""), .on("GET", (r) => ""),
} as IHttpConnectionOptions; } as IHttpConnectionOptions;
let connection = new HttpConnection("http://tempuri.org", options); const connection = new HttpConnection("http://tempuri.org", options);
try { try {
await connection.start(); await connection.start();
fail(); fail();
done(); done();
} } catch (e) {
catch (e) {
expect(e.message).toBe("No available transports found."); expect(e.message).toBe("No available transports found.");
done(); done();
} }
}); });
it('does not send negotiate request if WebSockets transport requested explicitly', async done => { it("does not send negotiate request if WebSockets transport requested explicitly", async (done) => {
let options: IHttpConnectionOptions = { const options: IHttpConnectionOptions = {
...commonOptions, ...commonOptions,
httpClient: new TestHttpClient(), httpClient: new TestHttpClient(),
transport: TransportType.WebSockets, transport: TransportType.WebSockets,
} as IHttpConnectionOptions; } as IHttpConnectionOptions;
let connection = new HttpConnection("http://tempuri.org", options); const connection = new HttpConnection("http://tempuri.org", options);
try { try {
await connection.start(); await connection.start();
fail(); fail();
done(); done();
} } catch (e) {
catch (e) {
// WebSocket is created when the transport is connecting which happens after // WebSocket is created when the transport is connecting which happens after
// negotiate request would be sent. No better/easier way to test this. // negotiate request would be sent. No better/easier way to test this.
expect(e.message).toBe("WebSocket is not defined"); expect(e.message).toBe("WebSocket is not defined");
@ -284,28 +276,28 @@ describe("HttpConnection", () => {
[TransferMode.Binary, TransferMode.Binary], [TransferMode.Binary, TransferMode.Binary],
].forEach(([requestedTransferMode, transportTransferMode]) => { ].forEach(([requestedTransferMode, transportTransferMode]) => {
it(`connection returns ${transportTransferMode} transfer mode when ${requestedTransferMode} transfer mode is requested`, async () => { it(`connection returns ${transportTransferMode} transfer mode when ${requestedTransferMode} transfer mode is requested`, async () => {
let fakeTransport = { const fakeTransport = {
// mode: TransferMode : TransferMode.Text // mode: TransferMode : TransferMode.Text
connect(url: string, requestedTransferMode: TransferMode): Promise<TransferMode> { return Promise.resolve(transportTransferMode); }, connect(url: string, requestedTransferMode: TransferMode): Promise<TransferMode> { return Promise.resolve(transportTransferMode); },
mode: transportTransferMode,
onclose: null,
onreceive: null,
send(data: any): Promise<void> { return Promise.resolve(); }, send(data: any): Promise<void> { return Promise.resolve(); },
stop(): Promise<void> { return Promise.resolve(); }, stop(): Promise<void> { return Promise.resolve(); },
onreceive: null,
onclose: null,
mode: transportTransferMode
} as ITransport; } as ITransport;
let options: IHttpConnectionOptions = { const options: IHttpConnectionOptions = {
...commonOptions, ...commonOptions,
httpClient: new TestHttpClient() httpClient: new TestHttpClient()
.on("POST", r => "{ \"connectionId\": \"42\", \"availableTransports\": [] }") .on("POST", (r) => "{ \"connectionId\": \"42\", \"availableTransports\": [] }")
.on("GET", r => ""), .on("GET", (r) => ""),
transport: fakeTransport, transport: fakeTransport,
} as IHttpConnectionOptions; } as IHttpConnectionOptions;
let connection = new HttpConnection("https://tempuri.org", options); const connection = new HttpConnection("https://tempuri.org", options);
connection.features.transferMode = requestedTransferMode; connection.features.transferMode = requestedTransferMode;
await connection.start(); await connection.start();
let actualTransferMode = connection.features.transferMode; const actualTransferMode = connection.features.transferMode;
expect(actualTransferMode).toBe(transportTransferMode); expect(actualTransferMode).toBe(transportTransferMode);
}); });

View File

@ -1,32 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { IConnection } from "../src/IConnection" import { ConnectionClosed, DataReceived } from "../src/Common";
import { HubConnection } from "../src/HubConnection" import { HubConnection } from "../src/HubConnection";
import { DataReceived, ConnectionClosed } from "../src/Common" import { IConnection } from "../src/IConnection";
import { TransportType, ITransport, TransferMode } from "../src/Transports" import { MessageType } from "../src/IHubProtocol";
import { Observer } from "../src/Observable" import { ILogger, LogLevel } from "../src/ILogger";
import { TextMessageFormat } from "../src/TextMessageFormat" import { Observer } from "../src/Observable";
import { ILogger, LogLevel } from "../src/ILogger" import { TextMessageFormat } from "../src/TextMessageFormat";
import { MessageType } from "../src/IHubProtocol" import { ITransport, TransferMode, TransportType } from "../src/Transports";
import { asyncit as it, captureException, delay, PromiseSource } from './Utils';
import { IHubConnectionOptions } from "../src/HubConnection"; import { IHubConnectionOptions } from "../src/HubConnection";
import { asyncit as it, captureException, delay, PromiseSource } from "./Utils";
const commonOptions: IHubConnectionOptions = { const commonOptions: IHubConnectionOptions = {
logger: null, logger: null,
} };
describe("HubConnection", () => { describe("HubConnection", () => {
describe("start", () => { describe("start", () => {
it("sends negotiation message", async () => { it("sends negotiation message", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
await hubConnection.start(); await hubConnection.start();
expect(connection.sentData.length).toBe(1) expect(connection.sentData.length).toBe(1);
expect(JSON.parse(connection.sentData[0])).toEqual({ expect(JSON.parse(connection.sentData[0])).toEqual({
protocol: "json" protocol: "json",
}); });
await hubConnection.stop(); await hubConnection.stop();
}); });
@ -34,21 +34,21 @@ describe("HubConnection", () => {
describe("send", () => { describe("send", () => {
it("sends a non blocking invocation", async () => { it("sends a non blocking invocation", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.send("testMethod", "arg", 42) const invokePromise = hubConnection.send("testMethod", "arg", 42)
.catch((_) => { }); // Suppress exception and unhandled promise rejection warning. .catch((_) => { }); // Suppress exception and unhandled promise rejection warning.
// Verify the message is sent // Verify the message is sent
expect(connection.sentData.length).toBe(1); expect(connection.sentData.length).toBe(1);
expect(JSON.parse(connection.sentData[0])).toEqual({ expect(JSON.parse(connection.sentData[0])).toEqual({
type: MessageType.Invocation,
target: "testMethod",
arguments: [ arguments: [
"arg", "arg",
42 42,
] ],
target: "testMethod",
type: MessageType.Invocation,
}); });
// Close the connection // Close the connection
@ -58,22 +58,22 @@ describe("HubConnection", () => {
describe("invoke", () => { describe("invoke", () => {
it("sends an invocation", async () => { it("sends an invocation", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.invoke("testMethod", "arg", 42) const invokePromise = hubConnection.invoke("testMethod", "arg", 42)
.catch((_) => { }); // Suppress exception and unhandled promise rejection warning. .catch((_) => { }); // Suppress exception and unhandled promise rejection warning.
// Verify the message is sent // Verify the message is sent
expect(connection.sentData.length).toBe(1); expect(connection.sentData.length).toBe(1);
expect(JSON.parse(connection.sentData[0])).toEqual({ expect(JSON.parse(connection.sentData[0])).toEqual({
type: MessageType.Invocation,
invocationId: connection.lastInvocationId,
target: "testMethod",
arguments: [ arguments: [
"arg", "arg",
42 42,
] ],
invocationId: connection.lastInvocationId,
target: "testMethod",
type: MessageType.Invocation,
}); });
// Close the connection // Close the connection
@ -81,22 +81,22 @@ describe("HubConnection", () => {
}); });
it("rejects the promise when an error is received", async () => { it("rejects the promise when an error is received", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.invoke("testMethod", "arg", 42); const invokePromise = hubConnection.invoke("testMethod", "arg", 42);
connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId, error: "foo" }); connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId, error: "foo" });
let ex = await captureException(async () => invokePromise); const ex = await captureException(async () => invokePromise);
expect(ex.message).toBe("foo"); expect(ex.message).toBe("foo");
}); });
it("resolves the promise when a result is received", async () => { it("resolves the promise when a result is received", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.invoke("testMethod", "arg", 42); const invokePromise = hubConnection.invoke("testMethod", "arg", 42);
connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId, result: "foo" }); connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId, result: "foo" });
@ -104,84 +104,84 @@ describe("HubConnection", () => {
}); });
it("completes pending invocations when stopped", async () => { it("completes pending invocations when stopped", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.invoke("testMethod"); const invokePromise = hubConnection.invoke("testMethod");
hubConnection.stop(); hubConnection.stop();
let ex = await captureException(async () => await invokePromise); const ex = await captureException(async () => await invokePromise);
expect(ex.message).toBe("Invocation canceled due to connection being closed."); expect(ex.message).toBe("Invocation canceled due to connection being closed.");
}); });
it("completes pending invocations when connection is lost", async () => { it("completes pending invocations when connection is lost", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.invoke("testMethod"); const invokePromise = hubConnection.invoke("testMethod");
// Typically this would be called by the transport // Typically this would be called by the transport
connection.onclose(new Error("Connection lost")); connection.onclose(new Error("Connection lost"));
let ex = await captureException(async () => await invokePromise); const ex = await captureException(async () => await invokePromise);
expect(ex.message).toBe("Connection lost"); expect(ex.message).toBe("Connection lost");
}); });
}); });
describe("on", () => { describe("on", () => {
it("invocations ignored in callbacks not registered", async () => { it("invocations ignored in callbacks not registered", async () => {
let warnings: string[] = []; const warnings: string[] = [];
let logger = <ILogger>{ const logger = {
log: function (logLevel: LogLevel, message: string) { log: (logLevel: LogLevel, message: string) => {
if (logLevel === LogLevel.Warning) { if (logLevel === LogLevel.Warning) {
warnings.push(message); warnings.push(message);
} }
} },
}; } as ILogger;
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: logger }); const hubConnection = new HubConnection(connection, { logger });
connection.receive({ connection.receive({
type: MessageType.Invocation,
invocationId: 0,
target: "message",
arguments: ["test"], arguments: ["test"],
nonblocking: true invocationId: 0,
nonblocking: true,
target: "message",
type: MessageType.Invocation,
}); });
expect(warnings).toEqual(["No client method with the name 'message' found."]); expect(warnings).toEqual(["No client method with the name 'message' found."]);
}); });
it("callback invoked when servers invokes a method on the client", async () => { it("callback invoked when servers invokes a method on the client", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let value = ""; let value = "";
hubConnection.on("message", v => value = v); hubConnection.on("message", (v) => value = v);
connection.receive({ connection.receive({
type: MessageType.Invocation,
invocationId: 0,
target: "message",
arguments: ["test"], arguments: ["test"],
nonblocking: true invocationId: 0,
nonblocking: true,
target: "message",
type: MessageType.Invocation,
}); });
expect(value).toBe("test"); expect(value).toBe("test");
}); });
it("can have multiple callbacks", async () => { it("can have multiple callbacks", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let numInvocations1 = 0; let numInvocations1 = 0;
let numInvocations2 = 0; let numInvocations2 = 0;
hubConnection.on("message", () => numInvocations1++); hubConnection.on("message", () => numInvocations1++);
hubConnection.on("message", () => numInvocations2++); hubConnection.on("message", () => numInvocations2++);
connection.receive({ connection.receive({
type: MessageType.Invocation,
invocationId: 0,
target: "message",
arguments: [], arguments: [],
nonblocking: true invocationId: 0,
nonblocking: true,
target: "message",
type: MessageType.Invocation,
}); });
expect(numInvocations1).toBe(1); expect(numInvocations1).toBe(1);
@ -189,56 +189,56 @@ describe("HubConnection", () => {
}); });
it("can unsubscribe from on", async () => { it("can unsubscribe from on", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
var numInvocations = 0; let numInvocations = 0;
var callback = () => numInvocations++; const callback = () => numInvocations++;
hubConnection.on("message", callback); hubConnection.on("message", callback);
connection.receive({ connection.receive({
type: MessageType.Invocation,
invocationId: 0,
target: "message",
arguments: [], arguments: [],
nonblocking: true invocationId: 0,
nonblocking: true,
target: "message",
type: MessageType.Invocation,
}); });
hubConnection.off("message", callback); hubConnection.off("message", callback);
connection.receive({ connection.receive({
type: MessageType.Invocation,
invocationId: 0,
target: "message",
arguments: [], arguments: [],
nonblocking: true invocationId: 0,
nonblocking: true,
target: "message",
type: MessageType.Invocation,
}); });
expect(numInvocations).toBe(1); expect(numInvocations).toBe(1);
}); });
it("unsubscribing from non-existing callbacks no-ops", async () => { it("unsubscribing from non-existing callbacks no-ops", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
hubConnection.off("_", () => { }); hubConnection.off("_", () => { });
hubConnection.on("message", t => { }); hubConnection.on("message", (t) => { });
hubConnection.on("message", () => { }); hubConnection.on("message", () => { });
}); });
it("using null/undefined for methodName or method no-ops", async () => { it("using null/undefined for methodName or method no-ops", async () => {
let warnings: string[] = []; const warnings: string[] = [];
let logger = <ILogger>{ const logger = {
log: function (logLevel: LogLevel, message: string) { log(logLevel: LogLevel, message: string) {
if (logLevel === LogLevel.Warning) { if (logLevel === LogLevel.Warning) {
warnings.push(message); warnings.push(message);
} }
} },
}; } as ILogger;
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, { logger: logger }); const hubConnection = new HubConnection(connection, { logger });
hubConnection.on(null, undefined); hubConnection.on(null, undefined);
hubConnection.on(undefined, null); hubConnection.on(undefined, null);
@ -249,11 +249,11 @@ describe("HubConnection", () => {
// invoke a method to make sure we are not trying to use null/undefined // invoke a method to make sure we are not trying to use null/undefined
connection.receive({ connection.receive({
type: MessageType.Invocation,
invocationId: 0,
target: "message",
arguments: [], arguments: [],
nonblocking: true invocationId: 0,
nonblocking: true,
target: "message",
type: MessageType.Invocation,
}); });
expect(warnings).toEqual(["No client method with the name 'message' found."]); expect(warnings).toEqual(["No client method with the name 'message' found."]);
@ -269,21 +269,21 @@ describe("HubConnection", () => {
describe("stream", () => { describe("stream", () => {
it("sends an invocation", async () => { it("sends an invocation", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.stream("testStream", "arg", 42); const invokePromise = hubConnection.stream("testStream", "arg", 42);
// Verify the message is sent // Verify the message is sent
expect(connection.sentData.length).toBe(1); expect(connection.sentData.length).toBe(1);
expect(JSON.parse(connection.sentData[0])).toEqual({ expect(JSON.parse(connection.sentData[0])).toEqual({
type: MessageType.StreamInvocation,
invocationId: connection.lastInvocationId,
target: "testStream",
arguments: [ arguments: [
"arg", "arg",
42 42,
] ],
invocationId: connection.lastInvocationId,
target: "testStream",
type: MessageType.StreamInvocation,
}); });
// Close the connection // Close the connection
@ -291,24 +291,24 @@ describe("HubConnection", () => {
}); });
it("completes with an error when an error is yielded", async () => { it("completes with an error when an error is yielded", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let observer = new TestObserver(); const observer = new TestObserver();
hubConnection.stream<any>("testMethod", "arg", 42) hubConnection.stream<any>("testMethod", "arg", 42)
.subscribe(observer); .subscribe(observer);
connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId, error: "foo" }); connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId, error: "foo" });
let ex = await captureException(async () => await observer.completed); const ex = await captureException(async () => await observer.completed);
expect(ex.message).toEqual("Error: foo"); expect(ex.message).toEqual("Error: foo");
}); });
it("completes the observer when a completion is received", async () => { it("completes the observer when a completion is received", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let observer = new TestObserver(); const observer = new TestObserver();
hubConnection.stream<any>("testMethod", "arg", 42) hubConnection.stream<any>("testMethod", "arg", 42)
.subscribe(observer); .subscribe(observer);
@ -318,38 +318,38 @@ describe("HubConnection", () => {
}); });
it("completes pending streams when stopped", async () => { it("completes pending streams when stopped", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let observer = new TestObserver(); const observer = new TestObserver();
hubConnection.stream<any>("testMethod") hubConnection.stream<any>("testMethod")
.subscribe(observer); .subscribe(observer);
hubConnection.stop(); hubConnection.stop();
let ex = await captureException(async () => await observer.completed); const ex = await captureException(async () => await observer.completed);
expect(ex.message).toEqual("Error: Invocation canceled due to connection being closed."); expect(ex.message).toEqual("Error: Invocation canceled due to connection being closed.");
}); });
it("completes pending streams when connection is lost", async () => { it("completes pending streams when connection is lost", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let observer = new TestObserver(); const observer = new TestObserver();
hubConnection.stream<any>("testMethod") hubConnection.stream<any>("testMethod")
.subscribe(observer); .subscribe(observer);
// Typically this would be called by the transport // Typically this would be called by the transport
connection.onclose(new Error("Connection lost")); connection.onclose(new Error("Connection lost"));
let ex = await captureException(async () => await observer.completed); const ex = await captureException(async () => await observer.completed);
expect(ex.message).toEqual("Error: Connection lost"); expect(ex.message).toEqual("Error: Connection lost");
}); });
it("yields items as they arrive", async () => { it("yields items as they arrive", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let observer = new TestObserver(); const observer = new TestObserver();
hubConnection.stream<any>("testMethod") hubConnection.stream<any>("testMethod")
.subscribe(observer); .subscribe(observer);
@ -367,11 +367,11 @@ describe("HubConnection", () => {
}); });
it("does not require error function registered", async () => { it("does not require error function registered", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let observer = hubConnection.stream("testMethod").subscribe({ const observer = hubConnection.stream("testMethod").subscribe({
next: val => { } next: (val) => { },
}); });
// Typically this would be called by the transport // Typically this would be called by the transport
@ -380,11 +380,11 @@ describe("HubConnection", () => {
}); });
it("does not require complete function registered", async () => { it("does not require complete function registered", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let observer = hubConnection.stream("testMethod").subscribe({ const observer = hubConnection.stream("testMethod").subscribe({
next: val => { } next: (val) => { },
}); });
// Send completion to trigger observer.complete() // Send completion to trigger observer.complete()
@ -393,11 +393,11 @@ describe("HubConnection", () => {
}); });
it("can be canceled", () => { it("can be canceled", () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let observer = new TestObserver(); const observer = new TestObserver();
let subscription = hubConnection.stream("testMethod") const subscription = hubConnection.stream("testMethod")
.subscribe(observer); .subscribe(observer);
connection.receive({ type: MessageType.StreamItem, invocationId: connection.lastInvocationId, item: 1 }); connection.receive({ type: MessageType.StreamItem, invocationId: connection.lastInvocationId, item: 1 });
@ -412,29 +412,29 @@ describe("HubConnection", () => {
// Verify the cancel is sent // Verify the cancel is sent
expect(connection.sentData.length).toBe(2); expect(connection.sentData.length).toBe(2);
expect(JSON.parse(connection.sentData[1])).toEqual({ expect(JSON.parse(connection.sentData[1])).toEqual({
invocationId: connection.lastInvocationId,
type: MessageType.CancelInvocation, type: MessageType.CancelInvocation,
invocationId: connection.lastInvocationId
}); });
}); });
}); });
describe("onClose", () => { describe("onClose", () => {
it("can have multiple callbacks", async () => { it("can have multiple callbacks", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let invocations = 0; let invocations = 0;
hubConnection.onclose(e => invocations++); hubConnection.onclose((e) => invocations++);
hubConnection.onclose(e => invocations++); hubConnection.onclose((e) => invocations++);
// Typically this would be called by the transport // Typically this would be called by the transport
connection.onclose(); connection.onclose();
expect(invocations).toBe(2); expect(invocations).toBe(2);
}); });
it("callbacks receive error", async () => { it("callbacks receive error", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let error: Error; let error: Error;
hubConnection.onclose(e => error = e); hubConnection.onclose((e) => error = e);
// Typically this would be called by the transport // Typically this would be called by the transport
connection.onclose(new Error("Test error.")); connection.onclose(new Error("Test error."));
@ -442,8 +442,8 @@ describe("HubConnection", () => {
}); });
it("ignores null callbacks", async () => { it("ignores null callbacks", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
hubConnection.onclose(null); hubConnection.onclose(null);
hubConnection.onclose(undefined); hubConnection.onclose(undefined);
// Typically this would be called by the transport // Typically this would be called by the transport
@ -456,9 +456,9 @@ describe("HubConnection", () => {
it("can receive ping messages", async () => { it("can receive ping messages", async () => {
// Receive the ping mid-invocation so we can see that the rest of the flow works fine // Receive the ping mid-invocation so we can see that the rest of the flow works fine
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, commonOptions); const hubConnection = new HubConnection(connection, commonOptions);
let invokePromise = hubConnection.invoke("testMethod", "arg", 42); const invokePromise = hubConnection.invoke("testMethod", "arg", 42);
connection.receive({ type: MessageType.Ping }); connection.receive({ type: MessageType.Ping });
connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId, result: "foo" }); connection.receive({ type: MessageType.Completion, invocationId: connection.lastInvocationId, result: "foo" });
@ -467,11 +467,11 @@ describe("HubConnection", () => {
}); });
it("does not terminate if messages are received", async () => { it("does not terminate if messages are received", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, { ...commonOptions, timeoutInMilliseconds: 100 }); const hubConnection = new HubConnection(connection, { ...commonOptions, timeoutInMilliseconds: 100 });
let p = new PromiseSource<Error>(); const p = new PromiseSource<Error>();
hubConnection.onclose(error => p.resolve(error)); hubConnection.onclose((e) => p.resolve(e));
await hubConnection.start(); await hubConnection.start();
@ -486,69 +486,67 @@ describe("HubConnection", () => {
connection.stop(); connection.stop();
let error = await p.promise; const error = await p.promise;
expect(error).toBeUndefined(); expect(error).toBeUndefined();
}); });
it("terminates if no messages received within timeout interval", async () => { it("terminates if no messages received within timeout interval", async () => {
let connection = new TestConnection(); const connection = new TestConnection();
let hubConnection = new HubConnection(connection, { ...commonOptions, timeoutInMilliseconds: 100 }); const hubConnection = new HubConnection(connection, { ...commonOptions, timeoutInMilliseconds: 100 });
let p = new PromiseSource<Error>(); const p = new PromiseSource<Error>();
hubConnection.onclose(error => p.resolve(error)); hubConnection.onclose((e) => p.resolve(e));
await hubConnection.start(); await hubConnection.start();
let error = await p.promise; const error = await p.promise;
expect(error).toEqual(new Error("Server timeout elapsed without receiving a message from the server.")); expect(error).toEqual(new Error("Server timeout elapsed without receiving a message from the server."));
}); });
}) });
}); });
class TestConnection implements IConnection { class TestConnection implements IConnection {
readonly features: any = {}; public readonly features: any = {};
start(): Promise<void> { public start(): Promise<void> {
return Promise.resolve(); return Promise.resolve();
}; }
send(data: any): Promise<void> { public send(data: any): Promise<void> {
let invocation = TextMessageFormat.parse(data)[0]; const invocation = TextMessageFormat.parse(data)[0];
let invocationId = JSON.parse(invocation).invocationId; const invocationId = JSON.parse(invocation).invocationId;
if (invocationId) { if (invocationId) {
this.lastInvocationId = invocationId; this.lastInvocationId = invocationId;
} }
if (this.sentData) { if (this.sentData) {
this.sentData.push(invocation); this.sentData.push(invocation);
} } else {
else {
this.sentData = [invocation]; this.sentData = [invocation];
} }
return Promise.resolve(); return Promise.resolve();
}; }
stop(error?: Error): Promise<void> { public stop(error?: Error): Promise<void> {
if (this.onclose) { if (this.onclose) {
this.onclose(error); this.onclose(error);
} }
return Promise.resolve(); return Promise.resolve();
}; }
receive(data: any): void { public receive(data: any): void {
let payload = JSON.stringify(data); const payload = JSON.stringify(data);
this.onreceive(TextMessageFormat.write(payload)); this.onreceive(TextMessageFormat.write(payload));
} }
onreceive: DataReceived; public onreceive: DataReceived;
onclose: ConnectionClosed; public onclose: ConnectionClosed;
sentData: any[]; public sentData: any[];
lastInvocationId: string; public lastInvocationId: string;
}; }
class TestObserver implements Observer<any> class TestObserver implements Observer<any> {
{
public itemsReceived: [any]; public itemsReceived: [any];
private itemsSource: PromiseSource<[any]>; private itemsSource: PromiseSource<[any]>;
@ -557,19 +555,19 @@ class TestObserver implements Observer<any>
} }
constructor() { constructor() {
this.itemsReceived = <[any]>[]; this.itemsReceived = [] as [any];
this.itemsSource = new PromiseSource<[any]>(); this.itemsSource = new PromiseSource<[any]>();
} }
next(value: any) { public next(value: any) {
this.itemsReceived.push(value); this.itemsReceived.push(value);
} }
error(err: any) { public error(err: any) {
this.itemsSource.reject(new Error(err)); this.itemsSource.reject(new Error(err));
} }
complete() { public complete() {
this.itemsSource.resolve(this.itemsReceived); this.itemsSource.resolve(this.itemsReceived);
} }
}; }

View File

@ -1,8 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { LoggerFactory } from "../src/Loggers" import { ILogger, LogLevel } from "../src/ILogger";
import { ILogger, LogLevel } from "../src/ILogger" import { LoggerFactory } from "../src/Loggers";
describe("LoggerFactory", () => { describe("LoggerFactory", () => {
it("creates ConsoleLogger when no logging specified", () => { it("creates ConsoleLogger when no logging specified", () => {
@ -18,7 +18,7 @@ describe("LoggerFactory", () => {
}); });
it("does not create its own logger if the user provides one", () => { it("does not create its own logger if the user provides one", () => {
let customLogger : ILogger = { log: LogLevel => {} }; const customLogger: ILogger = { log: (logLevel) => {} };
expect(LoggerFactory.createLogger(customLogger)).toBe(customLogger); expect(LoggerFactory.createLogger(customLogger)).toBe(customLogger);
}); });
}); });

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { HttpClient, HttpRequest, HttpResponse } from "../src/HttpClient" import { HttpClient, HttpRequest, HttpResponse } from "../src/HttpClient";
type TestHttpHandlerResult = HttpResponse | string; type TestHttpHandlerResult = HttpResponse | string;
export type TestHttpHandler = (request: HttpRequest, next?: (request: HttpRequest) => Promise<HttpResponse>) => Promise<TestHttpHandlerResult> | TestHttpHandlerResult; export type TestHttpHandler = (request: HttpRequest, next?: (request: HttpRequest) => Promise<HttpResponse>) => Promise<TestHttpHandlerResult> | TestHttpHandlerResult;
@ -16,28 +16,26 @@ export class TestHttpClient extends HttpClient {
} }
send(request: HttpRequest): Promise<HttpResponse> { public send(request: HttpRequest): Promise<HttpResponse> {
return this.handler(request); return this.handler(request);
} }
on(handler: TestHttpHandler): TestHttpClient; public on(handler: TestHttpHandler): TestHttpClient;
on(method: string | RegExp, handler: TestHttpHandler): TestHttpClient; public on(method: string | RegExp, handler: TestHttpHandler): TestHttpClient;
on(method: string | RegExp, url: string, handler: TestHttpHandler): TestHttpClient; public on(method: string | RegExp, url: string, handler: TestHttpHandler): TestHttpClient;
on(method: string | RegExp, url: RegExp, handler: TestHttpHandler): TestHttpClient; public on(method: string | RegExp, url: RegExp, handler: TestHttpHandler): TestHttpClient;
on(methodOrHandler: string | RegExp | TestHttpHandler, urlOrHandler?: string | RegExp | TestHttpHandler, handler?: TestHttpHandler): TestHttpClient { public on(methodOrHandler: string | RegExp | TestHttpHandler, urlOrHandler?: string | RegExp | TestHttpHandler, handler?: TestHttpHandler): TestHttpClient {
let method: string | RegExp; let method: string | RegExp;
let url: string | RegExp; let url: string | RegExp;
if ((typeof methodOrHandler === "string") || (methodOrHandler instanceof RegExp)) { if ((typeof methodOrHandler === "string") || (methodOrHandler instanceof RegExp)) {
method = methodOrHandler; method = methodOrHandler;
} } else if (methodOrHandler) {
else if (methodOrHandler) {
handler = methodOrHandler; handler = methodOrHandler;
} }
if ((typeof urlOrHandler === "string") || (urlOrHandler instanceof RegExp)) { if ((typeof urlOrHandler === "string") || (urlOrHandler instanceof RegExp)) {
url = urlOrHandler; url = urlOrHandler;
} } else if (urlOrHandler) {
else if (urlOrHandler) {
handler = urlOrHandler; handler = urlOrHandler;
} }
@ -46,10 +44,10 @@ export class TestHttpClient extends HttpClient {
throw new Error("Missing required argument: 'handler'"); throw new Error("Missing required argument: 'handler'");
} }
let oldHandler = this.handler; const oldHandler = this.handler;
let newHandler = async (request: HttpRequest) => { const newHandler = async (request: HttpRequest) => {
if (matches(method, request.method) && matches(url, request.url)) { if (matches(method, request.method) && matches(url, request.url)) {
let promise = handler(request, oldHandler); const promise = handler(request, oldHandler);
let val: TestHttpHandlerResult; let val: TestHttpHandlerResult;
if (promise instanceof Promise) { if (promise instanceof Promise) {
@ -60,12 +58,10 @@ export class TestHttpClient extends HttpClient {
if (typeof val === "string") { if (typeof val === "string") {
return new HttpResponse(200, "OK", val); return new HttpResponse(200, "OK", val);
} } else {
else {
return val; return val;
} }
} } else {
else {
return await oldHandler(request); return await oldHandler(request);
} }
}; };
@ -83,8 +79,7 @@ function matches(pattern: string | RegExp, actual: string): boolean {
if (typeof pattern === "string") { if (typeof pattern === "string") {
return actual === pattern; return actual === pattern;
} } else {
else {
return pattern.test(actual); return pattern.test(actual);
} }
} }

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { TextMessageFormat } from "../src/TextMessageFormat" import { TextMessageFormat } from "../src/TextMessageFormat";
describe("Text Message Formatter", () => { describe("Text Message Formatter", () => {
([ ([
@ -9,20 +9,20 @@ describe("Text Message Formatter", () => {
["\u001e\u001e", ["", ""]], ["\u001e\u001e", ["", ""]],
["Hello\u001e", ["Hello"]], ["Hello\u001e", ["Hello"]],
["Hello,\u001eWorld!\u001e", ["Hello,", "World!"]], ["Hello,\u001eWorld!\u001e", ["Hello,", "World!"]],
] as [string, string[]][]).forEach(([payload, expected_messages]) => { ] as Array<[string, string[]]>).forEach(([payload, expectedMessages]) => {
it(`should parse '${payload}' correctly`, () => { it(`should parse '${payload}' correctly`, () => {
let messages = TextMessageFormat.parse(payload); const messages = TextMessageFormat.parse(payload);
expect(messages).toEqual(expected_messages); expect(messages).toEqual(expectedMessages);
}) });
}); });
([ ([
["", new Error("Message is incomplete.")], ["", new Error("Message is incomplete.")],
["ABC", new Error("Message is incomplete.")], ["ABC", new Error("Message is incomplete.")],
["ABC\u001eXYZ", new Error("Message is incomplete.")], ["ABC\u001eXYZ", new Error("Message is incomplete.")],
] as [string, Error][]).forEach(([payload, expected_error]) => { ] as Array<[string, Error]>).forEach(([payload, expectedError]) => {
it(`should fail to parse '${payload}'`, () => { it(`should fail to parse '${payload}'`, () => {
expect(() => TextMessageFormat.parse(payload)).toThrow(expected_error); expect(() => TextMessageFormat.parse(payload)).toThrow(expectedError);
}); });
}); });
}); });

View File

@ -4,8 +4,8 @@
export function asyncit(expectation: string, assertion?: () => Promise<any> | void, timeout?: number): void { export function asyncit(expectation: string, assertion?: () => Promise<any> | void, timeout?: number): void {
let testFunction: (done: DoneFn) => void; let testFunction: (done: DoneFn) => void;
if (assertion) { if (assertion) {
testFunction = done => { testFunction = (done) => {
let promise = assertion(); const promise = assertion();
if (promise) { if (promise) {
promise.then(() => done()) promise.then(() => done())
.catch((err) => { .catch((err) => {
@ -31,13 +31,13 @@ export async function captureException(fn: () => Promise<any>): Promise<Error> {
} }
export function delay(durationInMilliseconds: number): Promise<void> { export function delay(durationInMilliseconds: number): Promise<void> {
let source = new PromiseSource<void>(); const source = new PromiseSource<void>();
setTimeout(() => source.resolve(), durationInMilliseconds); setTimeout(() => source.resolve(), durationInMilliseconds);
return source.promise; return source.promise;
} }
export class PromiseSource<T> { export class PromiseSource<T> {
public promise: Promise<T> public promise: Promise<T>;
private resolver: (value?: T | PromiseLike<T>) => void; private resolver: (value?: T | PromiseLike<T>) => void;
private rejecter: (reason?: any) => void; private rejecter: (reason?: any) => void;
@ -49,11 +49,11 @@ export class PromiseSource<T> {
}); });
} }
resolve(value?: T | PromiseLike<T>) { public resolve(value?: T | PromiseLike<T>) {
this.resolver(value); this.resolver(value);
} }
reject(reason?: any) { public reject(reason?: any) {
this.rejecter(reason); this.rejecter(reason);
} }
} }

View File

@ -9,7 +9,7 @@ export class AbortController implements AbortSignal {
private isAborted: boolean = false; private isAborted: boolean = false;
public onabort: () => void; public onabort: () => void;
abort() { public abort() {
if (!this.isAborted) { if (!this.isAborted) {
this.isAborted = true; this.isAborted = true;
if (this.onabort) { if (this.onabort) {

View File

@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { IHubProtocol, HubMessage, ProtocolType } from "./IHubProtocol" import { HubMessage, IHubProtocol, ProtocolType } from "./IHubProtocol";
export class Base64EncodedHubProtocol implements IHubProtocol { export class Base64EncodedHubProtocol implements IHubProtocol {
private wrappedProtocol: IHubProtocol; private wrappedProtocol: IHubProtocol;
@ -12,49 +12,49 @@ export class Base64EncodedHubProtocol implements IHubProtocol {
this.type = ProtocolType.Text; this.type = ProtocolType.Text;
} }
readonly name: string; public readonly name: string;
readonly type: ProtocolType; public readonly type: ProtocolType;
parseMessages(input: any): HubMessage[] { public parseMessages(input: any): HubMessage[] {
// The format of the message is `size:message;` // The format of the message is `size:message;`
let pos = input.indexOf(":"); const pos = input.indexOf(":");
if (pos == -1 || input[input.length - 1] != ';') { if (pos === -1 || input[input.length - 1] !== ";") {
throw new Error("Invalid payload."); throw new Error("Invalid payload.");
} }
let lenStr = input.substring(0, pos); const lenStr = input.substring(0, pos);
if (!/^[0-9]+$/.test(lenStr)) { if (!/^[0-9]+$/.test(lenStr)) {
throw new Error(`Invalid length: '${lenStr}'`); throw new Error(`Invalid length: '${lenStr}'`);
} }
let messageSize = parseInt(lenStr, 10); const messageSize = parseInt(lenStr, 10);
// 2 accounts for ':' after message size and trailing ';' // 2 accounts for ':' after message size and trailing ';'
if (messageSize != input.length - pos - 2) { if (messageSize !== input.length - pos - 2) {
throw new Error("Invalid message size."); throw new Error("Invalid message size.");
} }
let encodedMessage = input.substring(pos + 1, input.length - 1); const encodedMessage = input.substring(pos + 1, input.length - 1);
// atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use // atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use
// base64-js module // base64-js module
let s = atob(encodedMessage); const s = atob(encodedMessage);
let payload = new Uint8Array(s.length); const payload = new Uint8Array(s.length);
for (let i = 0; i < payload.length; i++) { for (let i = 0; i < payload.length; i++) {
payload[i] = s.charCodeAt(i); payload[i] = s.charCodeAt(i);
} }
return this.wrappedProtocol.parseMessages(payload.buffer); return this.wrappedProtocol.parseMessages(payload.buffer);
} }
writeMessage(message: HubMessage): any { public writeMessage(message: HubMessage): any {
let payload = new Uint8Array(this.wrappedProtocol.writeMessage(message)); const payload = new Uint8Array(this.wrappedProtocol.writeMessage(message));
let s = ""; let s = "";
for (let i = 0; i < payload.byteLength; i++) { for (let i = 0; i < payload.byteLength; i++) {
s += String.fromCharCode(payload[i]); s += String.fromCharCode(payload[i]);
} }
// atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use // atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use
// base64-js module // base64-js module
let encodedMessage = btoa(s); const encodedMessage = btoa(s);
return `${encodedMessage.length.toString()}:${encodedMessage};`; return `${encodedMessage.length.toString()}:${encodedMessage};`;
} }
} }

View File

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
export class HttpError extends Error { export class HttpError extends Error {
statusCode: number; public statusCode: number;
constructor(errorMessage: string, statusCode: number) { constructor(errorMessage: string, statusCode: number) {
super(errorMessage); super(errorMessage);
this.statusCode = statusCode; this.statusCode = statusCode;

View File

@ -1,17 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { TimeoutError, HttpError } from "./Errors";
import { AbortSignal } from "./AbortController"; import { AbortSignal } from "./AbortController";
import { HttpError, TimeoutError } from "./Errors";
export interface HttpRequest { export interface HttpRequest {
method?: string, method?: string;
url?: string, url?: string;
content?: string | ArrayBuffer, content?: string | ArrayBuffer;
headers?: Map<string, string>, headers?: Map<string, string>;
responseType?: XMLHttpRequestResponseType, responseType?: XMLHttpRequestResponseType;
abortSignal?: AbortSignal, abortSignal?: AbortSignal;
timeout?: number, timeout?: number;
} }
export class HttpResponse { export class HttpResponse {
@ -25,33 +25,33 @@ export class HttpResponse {
} }
export abstract class HttpClient { export abstract class HttpClient {
get(url: string): Promise<HttpResponse>; public get(url: string): Promise<HttpResponse>;
get(url: string, options: HttpRequest): Promise<HttpResponse>; public get(url: string, options: HttpRequest): Promise<HttpResponse>;
get(url: string, options?: HttpRequest): Promise<HttpResponse> { public get(url: string, options?: HttpRequest): Promise<HttpResponse> {
return this.send({ return this.send({
...options, ...options,
method: "GET", method: "GET",
url: url, url,
}); });
} }
post(url: string): Promise<HttpResponse>; public post(url: string): Promise<HttpResponse>;
post(url: string, options: HttpRequest): Promise<HttpResponse>; public post(url: string, options: HttpRequest): Promise<HttpResponse>;
post(url: string, options?: HttpRequest): Promise<HttpResponse> { public post(url: string, options?: HttpRequest): Promise<HttpResponse> {
return this.send({ return this.send({
...options, ...options,
method: "POST", method: "POST",
url: url, url,
}); });
} }
abstract send(request: HttpRequest): Promise<HttpResponse>; public abstract send(request: HttpRequest): Promise<HttpResponse>;
} }
export class DefaultHttpClient extends HttpClient { export class DefaultHttpClient extends HttpClient {
send(request: HttpRequest): Promise<HttpResponse> { public send(request: HttpRequest): Promise<HttpResponse> {
return new Promise<HttpResponse>((resolve, reject) => { return new Promise<HttpResponse>((resolve, reject) => {
let xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
xhr.open(request.method, request.url, true); xhr.open(request.method, request.url, true);
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
@ -80,20 +80,19 @@ export class DefaultHttpClient extends HttpClient {
} }
if (xhr.status >= 200 && xhr.status < 300) { if (xhr.status >= 200 && xhr.status < 300) {
resolve(new HttpResponse(xhr.status, xhr.statusText, xhr.response || xhr.responseText)) resolve(new HttpResponse(xhr.status, xhr.statusText, xhr.response || xhr.responseText));
} } else {
else {
reject(new HttpError(xhr.statusText, xhr.status)); reject(new HttpError(xhr.statusText, xhr.status));
} }
}; };
xhr.onerror = () => { xhr.onerror = () => {
reject(new HttpError(xhr.statusText, xhr.status)); reject(new HttpError(xhr.statusText, xhr.status));
} };
xhr.ontimeout = () => { xhr.ontimeout = () => {
reject(new TimeoutError()); reject(new TimeoutError());
} };
xhr.send(request.content || ""); xhr.send(request.content || "");
}); });

View File

@ -1,12 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { DataReceived, ConnectionClosed } from "./Common" import { ConnectionClosed, DataReceived } from "./Common";
import { IConnection } from "./IConnection" import { DefaultHttpClient, HttpClient } from "./HttpClient";
import { ITransport, TransferMode, TransportType, WebSocketTransport, ServerSentEventsTransport, LongPollingTransport } from "./Transports" import { IConnection } from "./IConnection";
import { HttpClient, DefaultHttpClient } from "./HttpClient" import { ILogger, LogLevel } from "./ILogger";
import { ILogger, LogLevel } from "./ILogger" import { LoggerFactory } from "./Loggers";
import { LoggerFactory } from "./Loggers" import { ITransport, LongPollingTransport, ServerSentEventsTransport, TransferMode, TransportType, WebSocketTransport } from "./Transports";
export interface IHttpConnectionOptions { export interface IHttpConnectionOptions {
httpClient?: HttpClient; httpClient?: HttpClient;
@ -18,12 +18,12 @@ export interface IHttpConnectionOptions {
const enum ConnectionState { const enum ConnectionState {
Connecting, Connecting,
Connected, Connected,
Disconnected Disconnected,
} }
interface INegotiateResponse { interface INegotiateResponse {
connectionId: string connectionId: string;
availableTransports: string[] availableTransports: string[];
} }
export class HttpConnection implements IConnection { export class HttpConnection implements IConnection {
@ -37,7 +37,7 @@ export class HttpConnection implements IConnection {
private connectionId: string; private connectionId: string;
private startPromise: Promise<void>; private startPromise: Promise<void>;
readonly features: any = {}; public readonly features: any = {};
constructor(url: string, options: IHttpConnectionOptions = {}) { constructor(url: string, options: IHttpConnectionOptions = {}) {
this.logger = LoggerFactory.createLogger(options.logger); this.logger = LoggerFactory.createLogger(options.logger);
@ -51,7 +51,7 @@ export class HttpConnection implements IConnection {
this.options = options; this.options = options;
} }
async start(): Promise<void> { public async start(): Promise<void> {
if (this.connectionState !== ConnectionState.Disconnected) { if (this.connectionState !== ConnectionState.Disconnected) {
return Promise.reject(new Error("Cannot start a connection that is not in the 'Disconnected' state.")); return Promise.reject(new Error("Cannot start a connection that is not in the 'Disconnected' state."));
} }
@ -68,25 +68,24 @@ export class HttpConnection implements IConnection {
// No need to add a connection ID in this case // No need to add a connection ID in this case
this.url = this.baseUrl; this.url = this.baseUrl;
this.transport = this.createTransport(this.options.transport, [TransportType[TransportType.WebSockets]]); this.transport = this.createTransport(this.options.transport, [TransportType[TransportType.WebSockets]]);
} } else {
else {
let headers; let headers;
let token = this.options.accessTokenFactory(); const token = this.options.accessTokenFactory();
if (token) { if (token) {
headers = new Map<string, string>(); headers = new Map<string, string>();
headers.set("Authorization", `Bearer ${token}`); headers.set("Authorization", `Bearer ${token}`);
} }
let negotiatePayload = await this.httpClient.post(this.resolveNegotiateUrl(this.baseUrl), { const negotiatePayload = await this.httpClient.post(this.resolveNegotiateUrl(this.baseUrl), {
content: "", content: "",
headers headers,
}); });
let negotiateResponse: INegotiateResponse = JSON.parse(<string>negotiatePayload.content); const negotiateResponse: INegotiateResponse = JSON.parse(negotiatePayload.content as string);
this.connectionId = negotiateResponse.connectionId; this.connectionId = negotiateResponse.connectionId;
// the user tries to stop the the connection when it is being started // the user tries to stop the the connection when it is being started
if (this.connectionState == ConnectionState.Disconnected) { if (this.connectionState === ConnectionState.Disconnected) {
return; return;
} }
@ -97,9 +96,9 @@ export class HttpConnection implements IConnection {
} }
this.transport.onreceive = this.onreceive; this.transport.onreceive = this.onreceive;
this.transport.onclose = e => this.stopConnection(true, e); this.transport.onclose = (e) => this.stopConnection(true, e);
let requestedTransferMode = const requestedTransferMode =
this.features.transferMode === TransferMode.Binary this.features.transferMode === TransferMode.Binary
? TransferMode.Binary ? TransferMode.Binary
: TransferMode.Text; : TransferMode.Text;
@ -109,13 +108,12 @@ export class HttpConnection implements IConnection {
// only change the state if we were connecting to not overwrite // only change the state if we were connecting to not overwrite
// the state if the connection is already marked as Disconnected // the state if the connection is already marked as Disconnected
this.changeState(ConnectionState.Connecting, ConnectionState.Connected); this.changeState(ConnectionState.Connecting, ConnectionState.Connected);
} } catch (e) {
catch (e) {
this.logger.log(LogLevel.Error, "Failed to start the connection. " + e); this.logger.log(LogLevel.Error, "Failed to start the connection. " + e);
this.connectionState = ConnectionState.Disconnected; this.connectionState = ConnectionState.Disconnected;
this.transport = null; this.transport = null;
throw e; throw e;
}; }
} }
private createTransport(transport: TransportType | ITransport, availableTransports: string[]): ITransport { private createTransport(transport: TransportType | ITransport, availableTransports: string[]): ITransport {
@ -143,36 +141,35 @@ export class HttpConnection implements IConnection {
return typeof (transport) === "object" && "connect" in transport; return typeof (transport) === "object" && "connect" in transport;
} }
private changeState(from: ConnectionState, to: ConnectionState): Boolean { private changeState(from: ConnectionState, to: ConnectionState): boolean {
if (this.connectionState == from) { if (this.connectionState === from) {
this.connectionState = to; this.connectionState = to;
return true; return true;
} }
return false; return false;
} }
send(data: any): Promise<void> { public send(data: any): Promise<void> {
if (this.connectionState != ConnectionState.Connected) { if (this.connectionState !== ConnectionState.Connected) {
throw new Error("Cannot send data if the connection is not in the 'Connected' State"); throw new Error("Cannot send data if the connection is not in the 'Connected' State");
} }
return this.transport.send(data); return this.transport.send(data);
} }
async stop(error?: Error): Promise<void> { public async stop(error?: Error): Promise<void> {
let previousState = this.connectionState; const previousState = this.connectionState;
this.connectionState = ConnectionState.Disconnected; this.connectionState = ConnectionState.Disconnected;
try { try {
await this.startPromise; await this.startPromise;
} } catch (e) {
catch (e) {
// this exception is returned to the user as a rejected Promise from the start method // this exception is returned to the user as a rejected Promise from the start method
} }
this.stopConnection(/*raiseClosed*/ previousState == ConnectionState.Connected, error); this.stopConnection(/*raiseClosed*/ previousState === ConnectionState.Connected, error);
} }
private stopConnection(raiseClosed: Boolean, error?: Error) { private stopConnection(raiseClosed: boolean, error?: Error) {
if (this.transport) { if (this.transport) {
this.transport.stop(); this.transport.stop();
this.transport = null; this.transport = null;
@ -197,28 +194,28 @@ export class HttpConnection implements IConnection {
return url; return url;
} }
if (typeof window === 'undefined' || !window || !window.document) { if (typeof window === "undefined" || !window || !window.document) {
throw new Error(`Cannot resolve '${url}'.`); throw new Error(`Cannot resolve '${url}'.`);
} }
let parser = window.document.createElement("a"); const parser = window.document.createElement("a");
parser.href = url; parser.href = url;
let baseUrl = (!parser.protocol || parser.protocol === ":") const baseUrl = (!parser.protocol || parser.protocol === ":")
? `${window.document.location.protocol}//${(parser.host || window.document.location.host)}` ? `${window.document.location.protocol}//${(parser.host || window.document.location.host)}`
: `${parser.protocol}//${parser.host}`; : `${parser.protocol}//${parser.host}`;
if (!url || url[0] != '/') { if (!url || url[0] !== "/") {
url = '/' + url; url = "/" + url;
} }
let normalizedUrl = baseUrl + url; const normalizedUrl = baseUrl + url;
this.logger.log(LogLevel.Information, `Normalizing '${url}' to '${normalizedUrl}'`); this.logger.log(LogLevel.Information, `Normalizing '${url}' to '${normalizedUrl}'`);
return normalizedUrl; return normalizedUrl;
} }
private resolveNegotiateUrl(url: string): string { private resolveNegotiateUrl(url: string): string {
let index = url.indexOf("?"); const index = url.indexOf("?");
let negotiateUrl = url.substring(0, index === -1 ? url.length : index); let negotiateUrl = url.substring(0, index === -1 ? url.length : index);
if (negotiateUrl[negotiateUrl.length - 1] !== "/") { if (negotiateUrl[negotiateUrl.length - 1] !== "/") {
negotiateUrl += "/"; negotiateUrl += "/";
@ -228,6 +225,6 @@ export class HttpConnection implements IConnection {
return negotiateUrl; return negotiateUrl;
} }
onreceive: DataReceived; public onreceive: DataReceived;
onclose: ConnectionClosed; public onclose: ConnectionClosed;
} }

View File

@ -1,19 +1,19 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { ConnectionClosed } from "./Common" import { Base64EncodedHubProtocol } from "./Base64EncodedHubProtocol";
import { IConnection } from "./IConnection" import { ConnectionClosed } from "./Common";
import { HttpConnection, IHttpConnectionOptions } from "./HttpConnection" import { HttpConnection, IHttpConnectionOptions } from "./HttpConnection";
import { TransportType, TransferMode } from "./Transports" import { IConnection } from "./IConnection";
import { Subject, Observable } from "./Observable" import { CancelInvocationMessage, CompletionMessage, HubMessage, IHubProtocol, InvocationMessage, MessageType, NegotiationMessage, ProtocolType, StreamInvocationMessage, StreamItemMessage } from "./IHubProtocol";
import { IHubProtocol, ProtocolType, MessageType, HubMessage, CompletionMessage, StreamItemMessage, InvocationMessage, StreamInvocationMessage, NegotiationMessage, CancelInvocationMessage } from "./IHubProtocol"; import { ILogger, LogLevel } from "./ILogger";
import { JsonHubProtocol } from "./JsonHubProtocol"; import { JsonHubProtocol } from "./JsonHubProtocol";
import { TextMessageFormat } from "./TextMessageFormat" import { ConsoleLogger, LoggerFactory, NullLogger } from "./Loggers";
import { Base64EncodedHubProtocol } from "./Base64EncodedHubProtocol" import { Observable, Subject } from "./Observable";
import { ILogger, LogLevel } from "./ILogger" import { TextMessageFormat } from "./TextMessageFormat";
import { ConsoleLogger, NullLogger, LoggerFactory } from "./Loggers" import { TransferMode, TransportType } from "./Transports";
export { JsonHubProtocol } export { JsonHubProtocol };
export interface IHubConnectionOptions extends IHttpConnectionOptions { export interface IHubConnectionOptions extends IHttpConnectionOptions {
protocol?: IHubProtocol; protocol?: IHubProtocol;
@ -27,7 +27,7 @@ export class HubConnection {
private readonly logger: ILogger; private readonly logger: ILogger;
private protocol: IHubProtocol; private protocol: IHubProtocol;
private callbacks: Map<string, (invocationEvent: StreamItemMessage | CompletionMessage, error?: Error) => void>; private callbacks: Map<string, (invocationEvent: StreamItemMessage | CompletionMessage, error?: Error) => void>;
private methods: Map<string, ((...args: any[]) => void)[]>; private methods: Map<string, Array<(...args: any[]) => void>>;
private id: number; private id: number;
private closedCallbacks: ConnectionClosed[]; private closedCallbacks: ConnectionClosed[];
private timeoutHandle: NodeJS.Timer; private timeoutHandle: NodeJS.Timer;
@ -42,8 +42,7 @@ export class HubConnection {
if (typeof urlOrConnection === "string") { if (typeof urlOrConnection === "string") {
this.connection = new HttpConnection(urlOrConnection, options); this.connection = new HttpConnection(urlOrConnection, options);
} } else {
else {
this.connection = urlOrConnection; this.connection = urlOrConnection;
} }
@ -54,7 +53,7 @@ export class HubConnection {
this.connection.onclose = (error?: Error) => this.connectionClosed(error); this.connection.onclose = (error?: Error) => this.connectionClosed(error);
this.callbacks = new Map<string, (invocationEvent: HubMessage, error?: Error) => void>(); this.callbacks = new Map<string, (invocationEvent: HubMessage, error?: Error) => void>();
this.methods = new Map<string, ((...args: any[]) => void)[]>(); this.methods = new Map<string, Array<(...args: any[]) => void>>();
this.closedCallbacks = []; this.closedCallbacks = [];
this.id = 0; this.id = 0;
} }
@ -65,18 +64,16 @@ export class HubConnection {
} }
// Parse the messages // Parse the messages
let messages = this.protocol.parseMessages(data); const messages = this.protocol.parseMessages(data);
for (var i = 0; i < messages.length; ++i) {
var message = messages[i];
for (const message of messages) {
switch (message.type) { switch (message.type) {
case MessageType.Invocation: case MessageType.Invocation:
this.invokeClientMethod(message); this.invokeClientMethod(message);
break; break;
case MessageType.StreamItem: case MessageType.StreamItem:
case MessageType.Completion: case MessageType.Completion:
let callback = this.callbacks.get(message.invocationId); const callback = this.callbacks.get(message.invocationId);
if (callback != null) { if (callback != null) {
if (message.type === MessageType.Completion) { if (message.type === MessageType.Completion) {
this.callbacks.delete(message.invocationId); this.callbacks.delete(message.invocationId);
@ -110,45 +107,44 @@ export class HubConnection {
} }
private invokeClientMethod(invocationMessage: InvocationMessage) { private invokeClientMethod(invocationMessage: InvocationMessage) {
let methods = this.methods.get(invocationMessage.target.toLowerCase()); const methods = this.methods.get(invocationMessage.target.toLowerCase());
if (methods) { if (methods) {
methods.forEach(m => m.apply(this, invocationMessage.arguments)); methods.forEach((m) => m.apply(this, invocationMessage.arguments));
if (invocationMessage.invocationId) { if (invocationMessage.invocationId) {
// This is not supported in v1. So we return an error to avoid blocking the server waiting for the response. // This is not supported in v1. So we return an error to avoid blocking the server waiting for the response.
let message = "Server requested a response, which is not supported in this version of the client." const message = "Server requested a response, which is not supported in this version of the client.";
this.logger.log(LogLevel.Error, message); this.logger.log(LogLevel.Error, message);
this.connection.stop(new Error(message)) this.connection.stop(new Error(message));
} }
} } else {
else {
this.logger.log(LogLevel.Warning, `No client method with the name '${invocationMessage.target}' found.`); this.logger.log(LogLevel.Warning, `No client method with the name '${invocationMessage.target}' found.`);
} }
} }
private connectionClosed(error?: Error) { private connectionClosed(error?: Error) {
this.callbacks.forEach(callback => { this.callbacks.forEach((callback) => {
callback(undefined, error ? error : new Error("Invocation canceled due to connection being closed.")); callback(undefined, error ? error : new Error("Invocation canceled due to connection being closed."));
}); });
this.callbacks.clear(); this.callbacks.clear();
this.closedCallbacks.forEach(c => c.apply(this, [error])); this.closedCallbacks.forEach((c) => c.apply(this, [error]));
this.cleanupTimeout(); this.cleanupTimeout();
} }
async start(): Promise<void> { public async start(): Promise<void> {
let requestedTransferMode = const requestedTransferMode =
(this.protocol.type === ProtocolType.Binary) (this.protocol.type === ProtocolType.Binary)
? TransferMode.Binary ? TransferMode.Binary
: TransferMode.Text; : TransferMode.Text;
this.connection.features.transferMode = requestedTransferMode this.connection.features.transferMode = requestedTransferMode;
await this.connection.start(); await this.connection.start();
var actualTransferMode = this.connection.features.transferMode; const actualTransferMode = this.connection.features.transferMode;
await this.connection.send( await this.connection.send(
TextMessageFormat.write( TextMessageFormat.write(
JSON.stringify(<NegotiationMessage>{ protocol: this.protocol.name }))); JSON.stringify({ protocol: this.protocol.name } as NegotiationMessage)));
this.logger.log(LogLevel.Information, `Using HubProtocol '${this.protocol.name}'.`); this.logger.log(LogLevel.Information, `Using HubProtocol '${this.protocol.name}'.`);
@ -159,21 +155,21 @@ export class HubConnection {
this.configureTimeout(); this.configureTimeout();
} }
stop(): Promise<void> { public stop(): Promise<void> {
this.cleanupTimeout(); this.cleanupTimeout();
return this.connection.stop(); return this.connection.stop();
} }
stream<T>(methodName: string, ...args: any[]): Observable<T> { public stream<T>(methodName: string, ...args: any[]): Observable<T> {
let invocationDescriptor = this.createStreamInvocation(methodName, args); const invocationDescriptor = this.createStreamInvocation(methodName, args);
let subject = new Subject<T>(() => { const subject = new Subject<T>(() => {
let cancelInvocation: CancelInvocationMessage = this.createCancelInvocation(invocationDescriptor.invocationId); const cancelInvocation: CancelInvocationMessage = this.createCancelInvocation(invocationDescriptor.invocationId);
let message: any = this.protocol.writeMessage(cancelInvocation); const cancelMessage: any = this.protocol.writeMessage(cancelInvocation);
this.callbacks.delete(invocationDescriptor.invocationId); this.callbacks.delete(invocationDescriptor.invocationId);
return this.connection.send(message); return this.connection.send(cancelMessage);
}); });
this.callbacks.set(invocationDescriptor.invocationId, (invocationEvent: CompletionMessage | StreamItemMessage, error?: Error) => { this.callbacks.set(invocationDescriptor.invocationId, (invocationEvent: CompletionMessage | StreamItemMessage, error?: Error) => {
@ -185,20 +181,18 @@ export class HubConnection {
if (invocationEvent.type === MessageType.Completion) { if (invocationEvent.type === MessageType.Completion) {
if (invocationEvent.error) { if (invocationEvent.error) {
subject.error(new Error(invocationEvent.error)); subject.error(new Error(invocationEvent.error));
} } else {
else {
subject.complete(); subject.complete();
} }
} } else {
else { subject.next((invocationEvent.item) as T);
subject.next(<T>(invocationEvent.item));
} }
}); });
let message = this.protocol.writeMessage(invocationDescriptor); const message = this.protocol.writeMessage(invocationDescriptor);
this.connection.send(message) this.connection.send(message)
.catch(e => { .catch((e) => {
subject.error(e); subject.error(e);
this.callbacks.delete(invocationDescriptor.invocationId); this.callbacks.delete(invocationDescriptor.invocationId);
}); });
@ -206,41 +200,39 @@ export class HubConnection {
return subject; return subject;
} }
send(methodName: string, ...args: any[]): Promise<void> { public send(methodName: string, ...args: any[]): Promise<void> {
let invocationDescriptor = this.createInvocation(methodName, args, true); const invocationDescriptor = this.createInvocation(methodName, args, true);
let message = this.protocol.writeMessage(invocationDescriptor); const message = this.protocol.writeMessage(invocationDescriptor);
return this.connection.send(message); return this.connection.send(message);
} }
invoke(methodName: string, ...args: any[]): Promise<any> { public invoke(methodName: string, ...args: any[]): Promise<any> {
let invocationDescriptor = this.createInvocation(methodName, args, false); const invocationDescriptor = this.createInvocation(methodName, args, false);
let p = new Promise<any>((resolve, reject) => { const p = new Promise<any>((resolve, reject) => {
this.callbacks.set(invocationDescriptor.invocationId, (invocationEvent: StreamItemMessage | CompletionMessage, error?: Error) => { this.callbacks.set(invocationDescriptor.invocationId, (invocationEvent: StreamItemMessage | CompletionMessage, error?: Error) => {
if (error) { if (error) {
reject(error); reject(error);
return; return;
} }
if (invocationEvent.type === MessageType.Completion) { if (invocationEvent.type === MessageType.Completion) {
let completionMessage = <CompletionMessage>invocationEvent; const completionMessage = invocationEvent as CompletionMessage;
if (completionMessage.error) { if (completionMessage.error) {
reject(new Error(completionMessage.error)); reject(new Error(completionMessage.error));
} } else {
else {
resolve(completionMessage.result); resolve(completionMessage.result);
} }
} } else {
else {
reject(new Error(`Unexpected message type: ${invocationEvent.type}`)); reject(new Error(`Unexpected message type: ${invocationEvent.type}`));
} }
}); });
let message = this.protocol.writeMessage(invocationDescriptor); const message = this.protocol.writeMessage(invocationDescriptor);
this.connection.send(message) this.connection.send(message)
.catch(e => { .catch((e) => {
reject(e); reject(e);
this.callbacks.delete(invocationDescriptor.invocationId); this.callbacks.delete(invocationDescriptor.invocationId);
}); });
@ -249,7 +241,7 @@ export class HubConnection {
return p; return p;
} }
on(methodName: string, method: (...args: any[]) => void) { public on(methodName: string, method: (...args: any[]) => void) {
if (!methodName || !method) { if (!methodName || !method) {
return; return;
} }
@ -262,23 +254,23 @@ export class HubConnection {
this.methods.get(methodName).push(method); this.methods.get(methodName).push(method);
} }
off(methodName: string, method: (...args: any[]) => void) { public off(methodName: string, method: (...args: any[]) => void) {
if (!methodName || !method) { if (!methodName || !method) {
return; return;
} }
methodName = methodName.toLowerCase(); methodName = methodName.toLowerCase();
let handlers = this.methods.get(methodName); const handlers = this.methods.get(methodName);
if (!handlers) { if (!handlers) {
return; return;
} }
var removeIdx = handlers.indexOf(method); const removeIdx = handlers.indexOf(method);
if (removeIdx != -1) { if (removeIdx !== -1) {
handlers.splice(removeIdx, 1); handlers.splice(removeIdx, 1);
} }
} }
onclose(callback: ConnectionClosed) { public onclose(callback: ConnectionClosed) {
if (callback) { if (callback) {
this.closedCallbacks.push(callback); this.closedCallbacks.push(callback);
} }
@ -293,40 +285,39 @@ export class HubConnection {
private createInvocation(methodName: string, args: any[], nonblocking: boolean): InvocationMessage { private createInvocation(methodName: string, args: any[], nonblocking: boolean): InvocationMessage {
if (nonblocking) { if (nonblocking) {
return { return {
type: MessageType.Invocation,
target: methodName,
arguments: args, arguments: args,
target: methodName,
type: MessageType.Invocation,
}; };
} } else {
else { const id = this.id;
let id = this.id;
this.id++; this.id++;
return { return {
type: MessageType.Invocation, arguments: args,
invocationId: id.toString(), invocationId: id.toString(),
target: methodName, target: methodName,
arguments: args, type: MessageType.Invocation,
}; };
} }
} }
private createStreamInvocation(methodName: string, args: any[]): StreamInvocationMessage { private createStreamInvocation(methodName: string, args: any[]): StreamInvocationMessage {
let id = this.id; const id = this.id;
this.id++; this.id++;
return { return {
type: MessageType.StreamInvocation, arguments: args,
invocationId: id.toString(), invocationId: id.toString(),
target: methodName, target: methodName,
arguments: args, type: MessageType.StreamInvocation,
}; };
} }
private createCancelInvocation(id: string): CancelInvocationMessage { private createCancelInvocation(id: string): CancelInvocationMessage {
return { return {
type: MessageType.CancelInvocation,
invocationId: id, invocationId: id,
type: MessageType.CancelInvocation,
}; };
} }
} }

View File

@ -1,8 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { DataReceived, ConnectionClosed } from "./Common" import { ConnectionClosed, DataReceived } from "./Common";
import { TransportType, TransferMode, ITransport } from "./Transports" import { ITransport, TransferMode, TransportType } from "./Transports";
export interface IConnection { export interface IConnection {
readonly features: any; readonly features: any;

View File

@ -10,7 +10,7 @@ export const enum MessageType {
Ping = 6, Ping = 6,
} }
export type MessageHeaders = { [key: string]: string }; export interface MessageHeaders { [key: string]: string; }
export type HubMessage = InvocationMessage | StreamInvocationMessage | StreamItemMessage | CompletionMessage | CancelInvocationMessage | PingMessage; export type HubMessage = InvocationMessage | StreamInvocationMessage | StreamItemMessage | CompletionMessage | CancelInvocationMessage | PingMessage;
@ -26,13 +26,13 @@ export interface HubInvocationMessage extends HubMessageBase {
export interface InvocationMessage extends HubInvocationMessage { export interface InvocationMessage extends HubInvocationMessage {
readonly type: MessageType.Invocation; readonly type: MessageType.Invocation;
readonly target: string; readonly target: string;
readonly arguments: Array<any>; readonly arguments: any[];
} }
export interface StreamInvocationMessage extends HubInvocationMessage { export interface StreamInvocationMessage extends HubInvocationMessage {
readonly type: MessageType.StreamInvocation; readonly type: MessageType.StreamInvocation;
readonly target: string; readonly target: string;
readonly arguments: Array<any> readonly arguments: any[];
} }
export interface StreamItemMessage extends HubInvocationMessage { export interface StreamItemMessage extends HubInvocationMessage {
@ -60,7 +60,7 @@ export interface CancelInvocationMessage extends HubInvocationMessage {
export const enum ProtocolType { export const enum ProtocolType {
Text = 1, Text = 1,
Binary Binary,
} }
export interface IHubProtocol { export interface IHubProtocol {

View File

@ -6,7 +6,7 @@ export enum LogLevel {
Information, Information,
Warning, Warning,
Error, Error,
None None,
} }
export interface ILogger { export interface ILogger {

View File

@ -1,33 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { HubMessage, IHubProtocol, ProtocolType } from "./IHubProtocol";
import { TextMessageFormat } from "./TextMessageFormat"; import { TextMessageFormat } from "./TextMessageFormat";
import { IHubProtocol, ProtocolType, HubMessage } from "./IHubProtocol";
export const JSON_HUB_PROTOCOL_NAME: string = "json"; export const JSON_HUB_PROTOCOL_NAME: string = "json";
export class JsonHubProtocol implements IHubProtocol { export class JsonHubProtocol implements IHubProtocol {
readonly name: string = JSON_HUB_PROTOCOL_NAME; public readonly name: string = JSON_HUB_PROTOCOL_NAME;
readonly type: ProtocolType = ProtocolType.Text; public readonly type: ProtocolType = ProtocolType.Text;
parseMessages(input: string): HubMessage[] { public parseMessages(input: string): HubMessage[] {
if (!input) { if (!input) {
return []; return [];
} }
// Parse the messages // Parse the messages
let messages = TextMessageFormat.parse(input); const messages = TextMessageFormat.parse(input);
let hubMessages = []; const hubMessages = [];
for (var i = 0; i < messages.length; ++i) { for (const message of messages) {
hubMessages.push(JSON.parse(messages[i])); hubMessages.push(JSON.parse(message));
} }
return hubMessages; return hubMessages;
} }
writeMessage(message: HubMessage): string { public writeMessage(message: HubMessage): string {
return TextMessageFormat.write(JSON.stringify(message)); return TextMessageFormat.write(JSON.stringify(message));
} }
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { ILogger, LogLevel } from "./ILogger" import { ILogger, LogLevel } from "./ILogger";
export class NullLogger implements ILogger { export class NullLogger implements ILogger {
log(logLevel: LogLevel, message: string): void { public log(logLevel: LogLevel, message: string): void {
} }
} }
@ -15,7 +15,7 @@ export class ConsoleLogger implements ILogger {
this.minimumLogLevel = minimumLogLevel; this.minimumLogLevel = minimumLogLevel;
} }
log(logLevel: LogLevel, message: string): void { public log(logLevel: LogLevel, message: string): void {
if (logLevel >= this.minimumLogLevel) { if (logLevel >= this.minimumLogLevel) {
switch (logLevel) { switch (logLevel) {
case LogLevel.Error: case LogLevel.Error:
@ -35,8 +35,8 @@ export class ConsoleLogger implements ILogger {
} }
} }
export namespace LoggerFactory { export class LoggerFactory {
export function createLogger(logging?: ILogger | LogLevel) { public static createLogger(logging?: ILogger | LogLevel) {
if (logging === undefined) { if (logging === undefined) {
return new ConsoleLogger(LogLevel.Information); return new ConsoleLogger(LogLevel.Information);
} }
@ -45,10 +45,10 @@ export namespace LoggerFactory {
return new NullLogger(); return new NullLogger();
} }
if ((<ILogger>logging).log) { if ((logging as ILogger).log) {
return <ILogger>logging; return logging as ILogger;
} }
return new ConsoleLogger(<LogLevel>logging); return new ConsoleLogger(logging as LogLevel);
} }
} }

View File

@ -11,8 +11,8 @@ export interface Observer<T> {
} }
export class Subscription<T> { export class Subscription<T> {
subject: Subject<T>; private subject: Subject<T>;
observer: Observer<T>; private observer: Observer<T>;
constructor(subject: Subject<T>, observer: Observer<T>) { constructor(subject: Subject<T>, observer: Observer<T>) {
this.subject = subject; this.subject = subject;
@ -20,7 +20,7 @@ export class Subscription<T> {
} }
public dispose(): void { public dispose(): void {
let index: number = this.subject.observers.indexOf(this.observer); const index: number = this.subject.observers.indexOf(this.observer);
if (index > -1) { if (index > -1) {
this.subject.observers.splice(index, 1); this.subject.observers.splice(index, 1);
} }
@ -36,8 +36,8 @@ export interface Observable<T> {
} }
export class Subject<T> implements Observable<T> { export class Subject<T> implements Observable<T> {
observers: Observer<T>[]; public observers: Array<Observer<T>>;
cancelCallback: () => Promise<void>; public cancelCallback: () => Promise<void>;
constructor(cancelCallback: () => Promise<void>) { constructor(cancelCallback: () => Promise<void>) {
this.observers = []; this.observers = [];
@ -45,13 +45,13 @@ export class Subject<T> implements Observable<T> {
} }
public next(item: T): void { public next(item: T): void {
for (let observer of this.observers) { for (const observer of this.observers) {
observer.next(item); observer.next(item);
} }
} }
public error(err: any): void { public error(err: any): void {
for (let observer of this.observers) { for (const observer of this.observers) {
if (observer.error) { if (observer.error) {
observer.error(err); observer.error(err);
} }
@ -59,7 +59,7 @@ export class Subject<T> implements Observable<T> {
} }
public complete(): void { public complete(): void {
for (let observer of this.observers) { for (const observer of this.observers) {
if (observer.complete) { if (observer.complete) {
observer.complete(); observer.complete();
} }

View File

@ -2,18 +2,18 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
export class TextMessageFormat { export class TextMessageFormat {
static RecordSeparator = String.fromCharCode(0x1e); private static RecordSeparator = String.fromCharCode(0x1e);
static write(output: string): string { public static write(output: string): string {
return `${output}${TextMessageFormat.RecordSeparator}`; return `${output}${TextMessageFormat.RecordSeparator}`;
} }
static parse(input: string): string[] { public static parse(input: string): string[] {
if (input[input.length - 1] != TextMessageFormat.RecordSeparator) { if (input[input.length - 1] !== TextMessageFormat.RecordSeparator) {
throw new Error("Message is incomplete."); throw new Error("Message is incomplete.");
} }
let messages = input.split(TextMessageFormat.RecordSeparator); const messages = input.split(TextMessageFormat.RecordSeparator);
messages.pop(); messages.pop();
return messages; return messages;
} }

View File

@ -1,22 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import { DataReceived, TransportClosed } from "./Common";
import { HttpClient, HttpRequest } from "./HttpClient";
import { HttpError, TimeoutError } from "./Errors";
import { ILogger, LogLevel } from "./ILogger";
import { IConnection } from "./IConnection";
import { AbortController } from "./AbortController"; import { AbortController } from "./AbortController";
import { DataReceived, TransportClosed } from "./Common";
import { HttpError, TimeoutError } from "./Errors";
import { HttpClient, HttpRequest } from "./HttpClient";
import { IConnection } from "./IConnection";
import { ILogger, LogLevel } from "./ILogger";
export enum TransportType { export enum TransportType {
WebSockets, WebSockets,
ServerSentEvents, ServerSentEvents,
LongPolling LongPolling,
} }
export const enum TransferMode { export const enum TransferMode {
Text = 1, Text = 1,
Binary Binary,
} }
export interface ITransport { export interface ITransport {
@ -37,17 +37,17 @@ export class WebSocketTransport implements ITransport {
this.accessTokenFactory = accessTokenFactory || (() => null); this.accessTokenFactory = accessTokenFactory || (() => null);
} }
connect(url: string, requestedTransferMode: TransferMode, connection: IConnection): Promise<TransferMode> { public connect(url: string, requestedTransferMode: TransferMode, connection: IConnection): Promise<TransferMode> {
return new Promise<TransferMode>((resolve, reject) => { return new Promise<TransferMode>((resolve, reject) => {
url = url.replace(/^http/, "ws"); url = url.replace(/^http/, "ws");
let token = this.accessTokenFactory(); const token = this.accessTokenFactory();
if (token) { if (token) {
url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`; url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`;
} }
let webSocket = new WebSocket(url); const webSocket = new WebSocket(url);
if (requestedTransferMode == TransferMode.Binary) { if (requestedTransferMode === TransferMode.Binary) {
webSocket.binaryType = "arraybuffer"; webSocket.binaryType = "arraybuffer";
} }
@ -66,23 +66,22 @@ export class WebSocketTransport implements ITransport {
if (this.onreceive) { if (this.onreceive) {
this.onreceive(message.data); this.onreceive(message.data);
} }
} };
webSocket.onclose = (event: CloseEvent) => { webSocket.onclose = (event: CloseEvent) => {
// webSocket will be null if the transport did not start successfully // webSocket will be null if the transport did not start successfully
if (this.onclose && this.webSocket) { if (this.onclose && this.webSocket) {
if (event.wasClean === false || event.code !== 1000) { if (event.wasClean === false || event.code !== 1000) {
this.onclose(new Error(`Websocket closed with status code: ${event.code} (${event.reason})`)); this.onclose(new Error(`Websocket closed with status code: ${event.code} (${event.reason})`));
} } else {
else {
this.onclose(); this.onclose();
} }
} }
} };
}); });
} }
send(data: any): Promise<void> { public send(data: any): Promise<void> {
if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) { if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) {
this.webSocket.send(data); this.webSocket.send(data);
return Promise.resolve(); return Promise.resolve();
@ -91,7 +90,7 @@ export class WebSocketTransport implements ITransport {
return Promise.reject("WebSocket is not in the OPEN state"); return Promise.reject("WebSocket is not in the OPEN state");
} }
stop(): Promise<void> { public stop(): Promise<void> {
if (this.webSocket) { if (this.webSocket) {
this.webSocket.close(); this.webSocket.close();
this.webSocket = null; this.webSocket = null;
@ -99,8 +98,8 @@ export class WebSocketTransport implements ITransport {
return Promise.resolve(); return Promise.resolve();
} }
onreceive: DataReceived; public onreceive: DataReceived;
onclose: TransportClosed; public onclose: TransportClosed;
} }
export class ServerSentEventsTransport implements ITransport { export class ServerSentEventsTransport implements ITransport {
@ -116,19 +115,19 @@ export class ServerSentEventsTransport implements ITransport {
this.logger = logger; this.logger = logger;
} }
connect(url: string, requestedTransferMode: TransferMode, connection: IConnection): Promise<TransferMode> { public connect(url: string, requestedTransferMode: TransferMode, connection: IConnection): Promise<TransferMode> {
if (typeof (EventSource) === "undefined") { if (typeof (EventSource) === "undefined") {
Promise.reject("EventSource not supported by the browser."); Promise.reject("EventSource not supported by the browser.");
} }
this.url = url; this.url = url;
return new Promise<TransferMode>((resolve, reject) => { return new Promise<TransferMode>((resolve, reject) => {
let token = this.accessTokenFactory(); const token = this.accessTokenFactory();
if (token) { if (token) {
url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`; url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`;
} }
let eventSource = new EventSource(url); const eventSource = new EventSource(url);
try { try {
eventSource.onmessage = (e: MessageEvent) => { eventSource.onmessage = (e: MessageEvent) => {
@ -152,26 +151,25 @@ export class ServerSentEventsTransport implements ITransport {
if (this.eventSource && this.onclose) { if (this.eventSource && this.onclose) {
this.onclose(new Error(e.message || "Error occurred")); this.onclose(new Error(e.message || "Error occurred"));
} }
} };
eventSource.onopen = () => { eventSource.onopen = () => {
this.logger.log(LogLevel.Information, `SSE connected to ${this.url}`); this.logger.log(LogLevel.Information, `SSE connected to ${this.url}`);
this.eventSource = eventSource; this.eventSource = eventSource;
// SSE is a text protocol // SSE is a text protocol
resolve(TransferMode.Text); resolve(TransferMode.Text);
} };
} } catch (e) {
catch (e) {
return Promise.reject(e); return Promise.reject(e);
} }
}); });
} }
async send(data: any): Promise<void> { public async send(data: any): Promise<void> {
return send(this.httpClient, this.url, this.accessTokenFactory, data); return send(this.httpClient, this.url, this.accessTokenFactory, data);
} }
stop(): Promise<void> { public stop(): Promise<void> {
if (this.eventSource) { if (this.eventSource) {
this.eventSource.close(); this.eventSource.close();
this.eventSource = null; this.eventSource = null;
@ -179,8 +177,8 @@ export class ServerSentEventsTransport implements ITransport {
return Promise.resolve(); return Promise.resolve();
} }
onreceive: DataReceived; public onreceive: DataReceived;
onclose: TransportClosed; public onclose: TransportClosed;
} }
export class LongPollingTransport implements ITransport { export class LongPollingTransport implements ITransport {
@ -199,7 +197,7 @@ export class LongPollingTransport implements ITransport {
this.pollAbort = new AbortController(); this.pollAbort = new AbortController();
} }
connect(url: string, requestedTransferMode: TransferMode, connection: IConnection): Promise<TransferMode> { public connect(url: string, requestedTransferMode: TransferMode, connection: IConnection): Promise<TransferMode> {
this.url = url; this.url = url;
// Set a flag indicating we have inherent keep-alive in this transport. // Set a flag indicating we have inherent keep-alive in this transport.
@ -215,26 +213,26 @@ export class LongPollingTransport implements ITransport {
} }
private async poll(url: string, transferMode: TransferMode): Promise<void> { private async poll(url: string, transferMode: TransferMode): Promise<void> {
let pollOptions: HttpRequest = { const pollOptions: HttpRequest = {
timeout: 120000,
abortSignal: this.pollAbort.signal, abortSignal: this.pollAbort.signal,
headers: new Map<string, string>(), headers: new Map<string, string>(),
timeout: 120000,
}; };
if (transferMode === TransferMode.Binary) { if (transferMode === TransferMode.Binary) {
pollOptions.responseType = "arraybuffer"; pollOptions.responseType = "arraybuffer";
} }
let token = this.accessTokenFactory(); const token = this.accessTokenFactory();
if (token) { if (token) {
pollOptions.headers.set("Authorization", `Bearer ${token}`); pollOptions.headers.set("Authorization", `Bearer ${token}`);
} }
while (!this.pollAbort.signal.aborted) { while (!this.pollAbort.signal.aborted) {
try { try {
let pollUrl = `${url}&_=${Date.now()}`; const pollUrl = `${url}&_=${Date.now()}`;
this.logger.log(LogLevel.Trace, `(LongPolling transport) polling: ${pollUrl}`); this.logger.log(LogLevel.Trace, `(LongPolling transport) polling: ${pollUrl}`);
let response = await this.httpClient.get(pollUrl, pollOptions) const response = await this.httpClient.get(pollUrl, pollOptions);
if (response.statusCode === 204) { if (response.statusCode === 204) {
this.logger.log(LogLevel.Information, "(LongPolling transport) Poll terminated by server"); this.logger.log(LogLevel.Information, "(LongPolling transport) Poll terminated by server");
@ -243,8 +241,7 @@ export class LongPollingTransport implements ITransport {
this.onclose(); this.onclose();
} }
this.pollAbort.abort(); this.pollAbort.abort();
} } else if (response.statusCode !== 200) {
else if (response.statusCode !== 200) {
this.logger.log(LogLevel.Error, `(LongPolling transport) Unexpected response code: ${response.statusCode}`); this.logger.log(LogLevel.Error, `(LongPolling transport) Unexpected response code: ${response.statusCode}`);
// Unexpected status code // Unexpected status code
@ -252,16 +249,14 @@ export class LongPollingTransport implements ITransport {
this.onclose(new HttpError(response.statusText, response.statusCode)); this.onclose(new HttpError(response.statusText, response.statusCode));
} }
this.pollAbort.abort(); this.pollAbort.abort();
} } else {
else {
// Process the response // Process the response
if (response.content) { if (response.content) {
this.logger.log(LogLevel.Trace, `(LongPolling transport) data received: ${response.content}`); this.logger.log(LogLevel.Trace, `(LongPolling transport) data received: ${response.content}`);
if (this.onreceive) { if (this.onreceive) {
this.onreceive(response.content); this.onreceive(response.content);
} }
} } else {
else {
// This is another way timeout manifest. // This is another way timeout manifest.
this.logger.log(LogLevel.Trace, "(LongPolling transport) Poll timed out, reissuing."); this.logger.log(LogLevel.Trace, "(LongPolling transport) Poll timed out, reissuing.");
} }
@ -281,29 +276,29 @@ export class LongPollingTransport implements ITransport {
} }
} }
async send(data: any): Promise<void> { public async send(data: any): Promise<void> {
return send(this.httpClient, this.url, this.accessTokenFactory, data); return send(this.httpClient, this.url, this.accessTokenFactory, data);
} }
stop(): Promise<void> { public stop(): Promise<void> {
this.pollAbort.abort(); this.pollAbort.abort();
return Promise.resolve(); return Promise.resolve();
} }
onreceive: DataReceived; public onreceive: DataReceived;
onclose: TransportClosed; public onclose: TransportClosed;
} }
async function send(httpClient: HttpClient, url: string, accessTokenFactory: () => string, content: string | ArrayBuffer): Promise<void> { async function send(httpClient: HttpClient, url: string, accessTokenFactory: () => string, content: string | ArrayBuffer): Promise<void> {
let headers; let headers;
let token = accessTokenFactory(); const token = accessTokenFactory();
if (token) { if (token) {
headers = new Map<string, string>(); headers = new Map<string, string>();
headers.set("Authorization", `Bearer ${accessTokenFactory()}`) headers.set("Authorization", `Bearer ${accessTokenFactory()}`);
} }
await httpClient.post(url, { await httpClient.post(url, {
content, content,
headers headers,
}); });
} }

View File

@ -3,6 +3,6 @@
// This is where we add any polyfills we'll need for the browser. It is the entry module for browser-specific builds. // This is where we add any polyfills we'll need for the browser. It is the entry module for browser-specific builds.
import 'es6-promise/dist/es6-promise.auto.js'; import "es6-promise/dist/es6-promise.auto.js";
export * from './index' export * from "./index";

View File

@ -2,14 +2,14 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Everything that users need to access must be exported here. Including interfaces. // Everything that users need to access must be exported here. Including interfaces.
export * from "./Common" export * from "./Common";
export * from "./Errors" export * from "./Errors";
export * from "./HttpClient" export * from "./HttpClient";
export * from "./HttpConnection" export * from "./HttpConnection";
export * from "./HubConnection" export * from "./HubConnection";
export * from "./IConnection" export * from "./IConnection";
export * from "./IHubProtocol" export * from "./IHubProtocol";
export * from "./ILogger" export * from "./ILogger";
export * from "./Loggers" export * from "./Loggers";
export * from "./Transports" export * from "./Transports";
export * from "./Observable" export * from "./Observable";

13
client-ts/tslint.json Normal file
View File

@ -0,0 +1,13 @@
{
"extends": "tslint:recommended",
"rules": {
"max-line-length": { "options": [300] },
"member-ordering": false,
"interface-name": false,
"unified-signatures": false,
"max-classes-per-file": false,
"no-empty": false,
"no-bitwise": false,
"no-console": false
}
}