This commit is contained in:
parent
31ef3c49df
commit
d2c1138429
|
|
@ -57,7 +57,7 @@ describe("Connection", () => {
|
|||
done();
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
expect(error.message).toBe("Cannot start a connection that is not in the 'Initial' state.");
|
||||
expect(error.message).toBe("Cannot start a connection that is not in the 'Disconnected' state.");
|
||||
done();
|
||||
});
|
||||
|
||||
|
|
@ -81,11 +81,13 @@ describe("Connection", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("cannot start a stopped connection", async (done) => {
|
||||
it("can start a stopped connection", async (done) => {
|
||||
let negotiateCalls = 0;
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
post(url: string): Promise<string> {
|
||||
return Promise.reject("error");
|
||||
negotiateCalls += 1;
|
||||
return Promise.reject("reached negotiate");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
return Promise.resolve("");
|
||||
|
|
@ -97,22 +99,18 @@ describe("Connection", () => {
|
|||
let connection = new HttpConnection("http://tempuri.org", options);
|
||||
|
||||
try {
|
||||
// start will fail and transition the connection to the Disconnected state
|
||||
await connection.start();
|
||||
}
|
||||
catch (e) {
|
||||
// The connection is not setup to be running so just ignore the error.
|
||||
} catch (e) {
|
||||
expect(e).toBe("reached negotiate");
|
||||
}
|
||||
|
||||
try {
|
||||
await connection.start();
|
||||
fail();
|
||||
done();
|
||||
}
|
||||
catch (e) {
|
||||
expect(e.message).toBe("Cannot start a connection that is not in the 'Initial' state.");
|
||||
done();
|
||||
} catch (e) {
|
||||
expect(e).toBe("reached negotiate");
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
it("can stop a starting connection", async (done) => {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import { ILogger, LogLevel } from "./ILogger"
|
|||
import { LoggerFactory } from "./Loggers"
|
||||
|
||||
const enum ConnectionState {
|
||||
Initial,
|
||||
Connecting,
|
||||
Connected,
|
||||
Disconnected
|
||||
|
|
@ -23,6 +22,7 @@ interface INegotiateResponse {
|
|||
|
||||
export class HttpConnection implements IConnection {
|
||||
private connectionState: ConnectionState;
|
||||
private baseUrl: string;
|
||||
private url: string;
|
||||
private readonly httpClient: IHttpClient;
|
||||
private readonly logger: ILogger;
|
||||
|
|
@ -35,16 +35,16 @@ export class HttpConnection implements IConnection {
|
|||
|
||||
constructor(url: string, options: IHttpConnectionOptions = {}) {
|
||||
this.logger = LoggerFactory.createLogger(options.logging);
|
||||
this.url = this.resolveUrl(url);
|
||||
this.baseUrl = this.resolveUrl(url);
|
||||
options = options || {};
|
||||
this.httpClient = options.httpClient || new HttpClient();
|
||||
this.connectionState = ConnectionState.Initial;
|
||||
this.connectionState = ConnectionState.Disconnected;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
if (this.connectionState != ConnectionState.Initial) {
|
||||
return Promise.reject(new Error("Cannot start a connection that is not in the 'Initial' state."));
|
||||
if (this.connectionState !== ConnectionState.Disconnected) {
|
||||
return Promise.reject(new Error("Cannot start a connection that is not in the 'Disconnected' state."));
|
||||
}
|
||||
|
||||
this.connectionState = ConnectionState.Connecting;
|
||||
|
|
@ -56,6 +56,8 @@ export class HttpConnection implements IConnection {
|
|||
private async startInternal(): Promise<void> {
|
||||
try {
|
||||
if (this.options.transport === TransportType.WebSockets) {
|
||||
// No need to add a connection ID in this case
|
||||
this.url = this.baseUrl;
|
||||
this.transport = this.createTransport(this.options.transport, [TransportType[TransportType.WebSockets]]);
|
||||
}
|
||||
else {
|
||||
|
|
@ -65,7 +67,7 @@ export class HttpConnection implements IConnection {
|
|||
headers.set("Authorization", `Bearer ${this.options.jwtBearer()}`);
|
||||
}
|
||||
|
||||
let negotiatePayload = await this.httpClient.post(this.resolveNegotiateUrl(this.url), "", headers);
|
||||
let negotiatePayload = await this.httpClient.post(this.resolveNegotiateUrl(this.baseUrl), "", headers);
|
||||
|
||||
let negotiateResponse: INegotiateResponse = JSON.parse(negotiatePayload);
|
||||
this.connectionId = negotiateResponse.connectionId;
|
||||
|
|
@ -76,7 +78,7 @@ export class HttpConnection implements IConnection {
|
|||
}
|
||||
|
||||
if (this.connectionId) {
|
||||
this.url += (this.url.indexOf("?") === -1 ? "?" : "&") + `id=${this.connectionId}`;
|
||||
this.url = this.baseUrl + (this.baseUrl.indexOf("?") === -1 ? "?" : "&") + `id=${this.connectionId}`;
|
||||
this.transport = this.createTransport(this.options.transport, negotiateResponse.availableTransports);
|
||||
}
|
||||
}
|
||||
|
|
@ -125,7 +127,7 @@ export class HttpConnection implements IConnection {
|
|||
}
|
||||
|
||||
private isITransport(transport: any): transport is ITransport {
|
||||
return typeof(transport) === "object" && "connect" in transport;
|
||||
return typeof (transport) === "object" && "connect" in transport;
|
||||
}
|
||||
|
||||
private changeState(from: ConnectionState, to: ConnectionState): Boolean {
|
||||
|
|
@ -144,7 +146,7 @@ export class HttpConnection implements IConnection {
|
|||
return this.transport.send(data);
|
||||
}
|
||||
|
||||
async stop(error? : Error): Promise<void> {
|
||||
async stop(error?: Error): Promise<void> {
|
||||
let previousState = this.connectionState;
|
||||
this.connectionState = ConnectionState.Disconnected;
|
||||
|
||||
|
|
@ -170,7 +172,7 @@ export class HttpConnection implements IConnection {
|
|||
}
|
||||
}
|
||||
|
||||
private resolveUrl(url: string) : string {
|
||||
private resolveUrl(url: string): string {
|
||||
// startsWith is not supported in IE
|
||||
if (url.lastIndexOf("https://", 0) === 0 || url.lastIndexOf("http://", 0) === 0) {
|
||||
return url;
|
||||
|
|
@ -198,7 +200,7 @@ export class HttpConnection implements IConnection {
|
|||
|
||||
private resolveNegotiateUrl(url: string): string {
|
||||
let index = url.indexOf("?");
|
||||
let negotiateUrl = this.url.substring(0, index === -1 ? url.length : index);
|
||||
let negotiateUrl = url.substring(0, index === -1 ? url.length : index);
|
||||
if (negotiateUrl[negotiateUrl.length - 1] !== "/") {
|
||||
negotiateUrl += "/";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,6 +154,9 @@ export class HubConnection {
|
|||
}
|
||||
|
||||
stop(): void {
|
||||
if (this.timeoutHandle) {
|
||||
clearTimeout(this.timeoutHandle);
|
||||
}
|
||||
return this.connection.stop();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
<!DOCTYPE html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
|
@ -22,15 +22,13 @@
|
|||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
}
|
||||
|
||||
var minified = getParameterByName('debug') !== 'true' ? '.min' : '';
|
||||
if (typeof Promise === 'undefined')
|
||||
{
|
||||
var minified = getParameterByName('release') === 'true' ? '.min' : '';
|
||||
if (typeof Promise === 'undefined') {
|
||||
document.write(
|
||||
'<script type="text/javascript" src="lib/signalr/signalr-clientES5' + minified + '.js"><\/script>' +
|
||||
'<script type="text/javascript" src="lib/signalr/signalr-msgpackprotocolES5' + minified + '.js"><\/script>');
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
document.write(
|
||||
'<script type="text/javascript" src="lib/signalr/signalr-client' + minified + '.js"><\/script>' +
|
||||
'<script type="text/javascript" src="lib/signalr/signalr-msgpackprotocol' + minified + '.js"><\/script>');
|
||||
|
|
@ -42,6 +40,8 @@
|
|||
<script src="js/connectionTests.js"></script>
|
||||
<script src="js/hubConnectionTests.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -255,16 +255,75 @@ describe('hubConnection', function () {
|
|||
done();
|
||||
});
|
||||
|
||||
hubConnection.start().then(function () {
|
||||
return hubConnection.invoke('InvokeWithString', message);
|
||||
hubConnection.start()
|
||||
.then(function () {
|
||||
return hubConnection.invoke('InvokeWithString', message);
|
||||
})
|
||||
.then(function () {
|
||||
return hubConnection.stop();
|
||||
})
|
||||
.catch(function (e) {
|
||||
fail(e);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can receive server calls without rebinding handler when restarted', function (done) {
|
||||
var options = {
|
||||
transport: transportType,
|
||||
protocol: protocol,
|
||||
logging: signalR.LogLevel.Trace
|
||||
};
|
||||
var hubConnection = new signalR.HubConnection(TESTHUBENDPOINT_URL, options);
|
||||
|
||||
var message = '你好 SignalR!';
|
||||
|
||||
// client side method names are case insensitive
|
||||
var methodName = 'message';
|
||||
var idx = Math.floor(Math.random() * (methodName.length - 1));
|
||||
methodName = methodName.substr(0, idx) + methodName[idx].toUpperCase() + methodName.substr(idx + 1);
|
||||
|
||||
let closeCount = 0;
|
||||
let invocationCount = 0;
|
||||
|
||||
hubConnection.onclose(function (e) {
|
||||
expect(e).toBeUndefined();
|
||||
closeCount += 1;
|
||||
if (closeCount === 1) {
|
||||
// Reconnect
|
||||
hubConnection.start()
|
||||
.then(function () {
|
||||
return hubConnection.invoke('InvokeWithString', message);
|
||||
})
|
||||
.then(function () {
|
||||
return hubConnection.stop();
|
||||
})
|
||||
.catch(function (e) {
|
||||
fail(e);
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
expect(invocationCount).toBe(2);
|
||||
done();
|
||||
}
|
||||
})
|
||||
.then(function () {
|
||||
return hubConnection.stop();
|
||||
})
|
||||
.catch(function (e) {
|
||||
fail(e);
|
||||
done();
|
||||
|
||||
hubConnection.on(methodName, function (msg) {
|
||||
expect(msg).toBe(message);
|
||||
invocationCount += 1;
|
||||
});
|
||||
|
||||
hubConnection.start()
|
||||
.then(function () {
|
||||
return hubConnection.invoke('InvokeWithString', message);
|
||||
})
|
||||
.then(function () {
|
||||
return hubConnection.stop();
|
||||
})
|
||||
.catch(function (e) {
|
||||
fail(e);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('closed with error if hub cannot be created', function (done) {
|
||||
|
|
@ -312,31 +371,80 @@ describe('hubConnection', function () {
|
|||
: new Uint8Array([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00])
|
||||
};
|
||||
|
||||
hubConnection.start().then(function () {
|
||||
return hubConnection.invoke('EchoComplexObject', complexObject);
|
||||
})
|
||||
.then(function (value) {
|
||||
if (protocol.name === "messagepack") {
|
||||
// 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
|
||||
value.ByteArray = new Uint8Array(value.ByteArray);
|
||||
hubConnection.start()
|
||||
.then(function () {
|
||||
return hubConnection.invoke('EchoComplexObject', complexObject);
|
||||
})
|
||||
.then(function (value) {
|
||||
if (protocol.name === "messagepack") {
|
||||
// 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
|
||||
value.ByteArray = new Uint8Array(value.ByteArray);
|
||||
|
||||
// GUIDs are serialized as raw type which is a string containing bytes which need to
|
||||
// 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
|
||||
// less than 0x80.
|
||||
let guidBytes = [];
|
||||
for (let i = 0; i < value.GUID.length; i++) {
|
||||
guidBytes.push(value.GUID.charCodeAt(i));
|
||||
// GUIDs are serialized as raw type which is a string containing bytes which need to
|
||||
// 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
|
||||
// less than 0x80.
|
||||
let guidBytes = [];
|
||||
for (let i = 0; i < value.GUID.length; i++) {
|
||||
guidBytes.push(value.GUID.charCodeAt(i));
|
||||
}
|
||||
value.GUID = new Uint8Array(guidBytes);
|
||||
}
|
||||
value.GUID = new Uint8Array(guidBytes);
|
||||
expect(value).toEqual(complexObject);
|
||||
})
|
||||
.then(function () {
|
||||
hubConnection.stop();
|
||||
})
|
||||
.catch(function (e) {
|
||||
fail(e);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can be restarted', function (done) {
|
||||
var message = '你好,世界!';
|
||||
|
||||
var options = {
|
||||
transport: transportType,
|
||||
protocol: protocol,
|
||||
logging: signalR.LogLevel.Trace
|
||||
};
|
||||
var hubConnection = new signalR.HubConnection(TESTHUBENDPOINT_URL, options);
|
||||
|
||||
let closeCount = 0;
|
||||
hubConnection.onclose(function (error) {
|
||||
expect(error).toBe(undefined);
|
||||
|
||||
// Start and invoke again
|
||||
if (closeCount === 0) {
|
||||
closeCount += 1;
|
||||
hubConnection.start().then(function () {
|
||||
hubConnection.invoke('Echo', message).then(function (result) {
|
||||
expect(result).toBe(message);
|
||||
}).catch(function (e) {
|
||||
fail(e);
|
||||
}).then(function () {
|
||||
hubConnection.stop()
|
||||
});
|
||||
}).catch(function (e) {
|
||||
fail(e);
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
expect(value).toEqual(complexObject);
|
||||
})
|
||||
.then(function () {
|
||||
hubConnection.stop();
|
||||
})
|
||||
.catch(function (e) {
|
||||
});
|
||||
|
||||
hubConnection.start().then(function () {
|
||||
hubConnection.invoke('Echo', message).then(function (result) {
|
||||
expect(result).toBe(message);
|
||||
}).catch(function (e) {
|
||||
fail(e);
|
||||
}).then(function () {
|
||||
hubConnection.stop()
|
||||
});
|
||||
}).catch(function (e) {
|
||||
fail(e);
|
||||
done();
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue