Integrating MsgPack support in TS client
This commit is contained in:
parent
9eabce1b02
commit
4898c0d3df
|
|
@ -2,7 +2,7 @@ import { IHttpClient } from "../Microsoft.AspNetCore.SignalR.Client.TS/HttpClien
|
|||
import { HttpConnection } from "../Microsoft.AspNetCore.SignalR.Client.TS/HttpConnection"
|
||||
import { IHttpConnectionOptions } from "../Microsoft.AspNetCore.SignalR.Client.TS/IHttpConnectionOptions"
|
||||
import { DataReceived, TransportClosed } from "../Microsoft.AspNetCore.SignalR.Client.TS/Common"
|
||||
import { ITransport, TransportType } from "../Microsoft.AspNetCore.SignalR.Client.TS/Transports"
|
||||
import { ITransport, TransportType, TransferMode } from "../Microsoft.AspNetCore.SignalR.Client.TS/Transports"
|
||||
import { eachTransport } from "./Common";
|
||||
|
||||
describe("Connection", () => {
|
||||
|
|
@ -112,7 +112,7 @@ describe("Connection", () => {
|
|||
}
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
var connection = new HttpConnection("http://tempuri.org", options);
|
||||
let connection = new HttpConnection("http://tempuri.org", options);
|
||||
|
||||
try {
|
||||
await connection.start();
|
||||
|
|
@ -125,7 +125,7 @@ describe("Connection", () => {
|
|||
});
|
||||
|
||||
it("can stop a non-started connection", async (done) => {
|
||||
var connection = new HttpConnection("http://tempuri.org");
|
||||
let connection = new HttpConnection("http://tempuri.org");
|
||||
await connection.stop();
|
||||
done();
|
||||
});
|
||||
|
|
@ -133,16 +133,16 @@ describe("Connection", () => {
|
|||
it("preserves users connection string", async done => {
|
||||
let connectUrl: string;
|
||||
let fakeTransport: ITransport = {
|
||||
connect(url: string): Promise<void> {
|
||||
connect(url: string): Promise<TransferMode> {
|
||||
connectUrl = url;
|
||||
return Promise.reject("");
|
||||
return Promise.reject(TransferMode.Text);
|
||||
},
|
||||
send(data: any): Promise<void> {
|
||||
return Promise.reject("");
|
||||
},
|
||||
stop(): void { },
|
||||
onDataReceived: undefined,
|
||||
onClosed: undefined
|
||||
onClosed: undefined,
|
||||
}
|
||||
|
||||
let options: IHttpConnectionOptions = {
|
||||
|
|
@ -158,7 +158,7 @@ describe("Connection", () => {
|
|||
} as IHttpConnectionOptions;
|
||||
|
||||
|
||||
var connection = new HttpConnection("http://tempuri.org?q=myData", options);
|
||||
let connection = new HttpConnection("http://tempuri.org?q=myData", options);
|
||||
|
||||
try {
|
||||
await connection.start();
|
||||
|
|
@ -173,7 +173,7 @@ describe("Connection", () => {
|
|||
});
|
||||
|
||||
eachTransport((requestedTransport: TransportType) => {
|
||||
it(`Connection 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 = {
|
||||
httpClient: <IHttpClient>{
|
||||
options(url: string): Promise<string> {
|
||||
|
|
@ -186,7 +186,7 @@ describe("Connection", () => {
|
|||
transport: requestedTransport
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
var connection = new HttpConnection("http://tempuri.org", options);
|
||||
let connection = new HttpConnection("http://tempuri.org", options);
|
||||
try {
|
||||
await connection.start();
|
||||
fail();
|
||||
|
|
@ -199,7 +199,7 @@ describe("Connection", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it(`Connection 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 = {
|
||||
httpClient: <IHttpClient>{
|
||||
options(url: string): Promise<string> {
|
||||
|
|
@ -211,7 +211,7 @@ describe("Connection", () => {
|
|||
}
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
var connection = new HttpConnection("http://tempuri.org", options);
|
||||
let connection = new HttpConnection("http://tempuri.org", options);
|
||||
try {
|
||||
await connection.start();
|
||||
fail();
|
||||
|
|
@ -222,4 +222,42 @@ describe("Connection", () => {
|
|||
done();
|
||||
}
|
||||
});
|
||||
|
||||
[
|
||||
[TransferMode.Text, TransferMode.Text],
|
||||
[TransferMode.Text, TransferMode.Binary],
|
||||
[TransferMode.Binary, TransferMode.Text],
|
||||
[TransferMode.Binary, TransferMode.Binary],
|
||||
].forEach(([requestedTransferMode, transportTransferMode]) => {
|
||||
it(`connection returns ${transportTransferMode} transfer mode when ${requestedTransferMode} transfer mode is requested`, async () => {
|
||||
let fakeTransport = {
|
||||
// mode: TransferMode : TransferMode.Text
|
||||
connect(url: string, requestedTransferMode: TransferMode): Promise<TransferMode> { return Promise.resolve(transportTransferMode); },
|
||||
send(data: any): Promise<void> { return Promise.resolve(); },
|
||||
stop(): void {},
|
||||
onDataReceived: null,
|
||||
onClosed: null,
|
||||
mode: transportTransferMode
|
||||
} as ITransport;
|
||||
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
options(url: string): Promise<string> {
|
||||
return Promise.resolve("{ \"connectionId\": \"42\", \"availableTransports\": [] }");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
return Promise.resolve("");
|
||||
}
|
||||
},
|
||||
transport: fakeTransport
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
let connection = new HttpConnection("https://tempuri.org", options);
|
||||
connection.features.transferMode = requestedTransferMode;
|
||||
await connection.start();
|
||||
let actualTransferMode = connection.features.transferMode;
|
||||
|
||||
expect(actualTransferMode).toBe(transportTransferMode);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { IConnection } from "../Microsoft.AspNetCore.SignalR.Client.TS/IConnection"
|
||||
import { HubConnection } from "../Microsoft.AspNetCore.SignalR.Client.TS/HubConnection"
|
||||
import { DataReceived, ConnectionClosed } from "../Microsoft.AspNetCore.SignalR.Client.TS/Common"
|
||||
import { TransportType, ITransport } from "../Microsoft.AspNetCore.SignalR.Client.TS/Transports"
|
||||
import { TransportType, ITransport, TransferMode } from "../Microsoft.AspNetCore.SignalR.Client.TS/Transports"
|
||||
import { Observer } from "../Microsoft.AspNetCore.SignalR.Client.TS/Observable"
|
||||
import { TextMessageFormat } from "../Microsoft.AspNetCore.SignalR.Client.TS/Formatters"
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ describe("HubConnection", () => {
|
|||
let connection = new TestConnection();
|
||||
|
||||
let hubConnection = new HubConnection(connection);
|
||||
var invokePromise = hubConnection.send("testMethod", "arg", 42)
|
||||
let invokePromise = hubConnection.send("testMethod", "arg", 42)
|
||||
.catch((_) => { }); // Suppress exception and unhandled promise rejection warning.
|
||||
|
||||
// Verify the message is sent
|
||||
|
|
@ -52,7 +52,7 @@ describe("HubConnection", () => {
|
|||
let connection = new TestConnection();
|
||||
|
||||
let hubConnection = new HubConnection(connection);
|
||||
var invokePromise = hubConnection.invoke("testMethod", "arg", 42)
|
||||
let invokePromise = hubConnection.invoke("testMethod", "arg", 42)
|
||||
.catch((_) => { }); // Suppress exception and unhandled promise rejection warning.
|
||||
|
||||
// Verify the message is sent
|
||||
|
|
@ -76,7 +76,7 @@ describe("HubConnection", () => {
|
|||
let connection = new TestConnection();
|
||||
|
||||
let hubConnection = new HubConnection(connection);
|
||||
var invokePromise = hubConnection.invoke("testMethod", "arg", 42);
|
||||
let invokePromise = hubConnection.invoke("testMethod", "arg", 42);
|
||||
|
||||
connection.receive({ type: 3, invocationId: connection.lastInvocationId, error: "foo" });
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ describe("HubConnection", () => {
|
|||
let connection = new TestConnection();
|
||||
|
||||
let hubConnection = new HubConnection(connection);
|
||||
var invokePromise = hubConnection.invoke("testMethod", "arg", 42);
|
||||
let invokePromise = hubConnection.invoke("testMethod", "arg", 42);
|
||||
|
||||
connection.receive({ type: 3, invocationId: connection.lastInvocationId, result: "foo" });
|
||||
|
||||
|
|
@ -99,7 +99,7 @@ describe("HubConnection", () => {
|
|||
let connection = new TestConnection();
|
||||
|
||||
let hubConnection = new HubConnection(connection);
|
||||
var invokePromise = hubConnection.invoke("testMethod");
|
||||
let invokePromise = hubConnection.invoke("testMethod");
|
||||
hubConnection.stop();
|
||||
|
||||
let ex = await captureException(async () => await invokePromise);
|
||||
|
|
@ -110,7 +110,7 @@ describe("HubConnection", () => {
|
|||
let connection = new TestConnection();
|
||||
|
||||
let hubConnection = new HubConnection(connection);
|
||||
var invokePromise = hubConnection.invoke("testMethod");
|
||||
let invokePromise = hubConnection.invoke("testMethod");
|
||||
// Typically this would be called by the transport
|
||||
connection.onClosed(new Error("Connection lost"));
|
||||
|
||||
|
|
@ -137,7 +137,7 @@ describe("HubConnection", () => {
|
|||
let connection = new TestConnection();
|
||||
|
||||
let hubConnection = new HubConnection(connection);
|
||||
var invokePromise = hubConnection.stream("testStream", "arg", 42);
|
||||
let invokePromise = hubConnection.stream("testStream", "arg", 42);
|
||||
|
||||
// Verify the message is sent
|
||||
expect(connection.sentData.length).toBe(1);
|
||||
|
|
@ -168,7 +168,6 @@ describe("HubConnection", () => {
|
|||
|
||||
let ex = await captureException(async () => await observer.completed);
|
||||
expect(ex.message).toEqual("Error: foo");
|
||||
|
||||
});
|
||||
|
||||
it("completes the observer when a completion is received", async () => {
|
||||
|
|
@ -250,12 +249,14 @@ describe("HubConnection", () => {
|
|||
});
|
||||
|
||||
class TestConnection implements IConnection {
|
||||
readonly features: any = {};
|
||||
|
||||
start(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
send(data: any): Promise<void> {
|
||||
var invocation = TextMessageFormat.parse(data)[0];
|
||||
let invocation = TextMessageFormat.parse(data)[0];
|
||||
this.lastInvocationId = JSON.parse(invocation).invocationId;
|
||||
if (this.sentData) {
|
||||
this.sentData.push(invocation);
|
||||
|
|
@ -273,7 +274,7 @@ class TestConnection implements IConnection {
|
|||
};
|
||||
|
||||
receive(data: any): void {
|
||||
var payload = JSON.stringify(data);
|
||||
let payload = JSON.stringify(data);
|
||||
this.onDataReceived(TextMessageFormat.write(payload));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { DataReceived, ConnectionClosed } from "./Common"
|
||||
import { IConnection } from "./IConnection"
|
||||
import { ITransport, TransportType, WebSocketTransport, ServerSentEventsTransport, LongPollingTransport } from "./Transports"
|
||||
import { ITransport, TransferMode, TransportType, WebSocketTransport, ServerSentEventsTransport, LongPollingTransport } from "./Transports"
|
||||
import { IHttpClient, HttpClient } from "./HttpClient"
|
||||
import { IHttpConnectionOptions } from "./IHttpConnectionOptions"
|
||||
|
||||
enum ConnectionState {
|
||||
const enum ConnectionState {
|
||||
Initial,
|
||||
Connecting,
|
||||
Connected,
|
||||
|
|
@ -25,6 +25,8 @@ export class HttpConnection implements IConnection {
|
|||
private options: IHttpConnectionOptions;
|
||||
private startPromise: Promise<void>;
|
||||
|
||||
readonly features: any = {};
|
||||
|
||||
constructor(url: string, options: IHttpConnectionOptions = {}) {
|
||||
this.url = url;
|
||||
this.httpClient = options.httpClient || new HttpClient();
|
||||
|
|
@ -59,7 +61,14 @@ export class HttpConnection implements IConnection {
|
|||
this.transport = this.createTransport(this.options.transport, negotiateResponse.availableTransports);
|
||||
this.transport.onDataReceived = this.onDataReceived;
|
||||
this.transport.onClosed = e => this.stopConnection(true, e);
|
||||
await this.transport.connect(this.url);
|
||||
|
||||
let requestedTransferMode =
|
||||
this.features.transferMode === TransferMode.Binary
|
||||
? TransferMode.Binary
|
||||
: TransferMode.Text;
|
||||
|
||||
this.features.transferMode = await this.transport.connect(this.url, requestedTransferMode);
|
||||
|
||||
// only change the state if we were connecting to not overwrite
|
||||
// the state if the connection is already marked as Disconnected
|
||||
this.changeState(ConnectionState.Connecting, ConnectionState.Connected);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import { ConnectionClosed } from "./Common"
|
||||
import { IConnection } from "./IConnection"
|
||||
import { TransportType } from "./Transports"
|
||||
import { TransportType, TransferMode } from "./Transports"
|
||||
import { Subject, Observable } from "./Observable"
|
||||
export { TransportType } from "./Transports"
|
||||
export { HttpConnection } from "./HttpConnection"
|
||||
import { IHubProtocol, MessageType, HubMessage, CompletionMessage, ResultMessage, InvocationMessage, NegotiationMessage } from "./IHubProtocol";
|
||||
import { IHubProtocol, ProtocolType, MessageType, HubMessage, CompletionMessage, ResultMessage, InvocationMessage, NegotiationMessage } from "./IHubProtocol";
|
||||
import { JsonHubProtocol } from "./JsonHubProtocol";
|
||||
import { TextMessageFormat } from "./Formatters"
|
||||
|
||||
export { TransportType } from "./Transports"
|
||||
export { HttpConnection } from "./HttpConnection"
|
||||
export { JsonHubProtocol } from "./JsonHubProtocol"
|
||||
|
||||
export class HubConnection {
|
||||
private connection: IConnection;
|
||||
private callbacks: Map<string, (invocationUpdate: CompletionMessage | ResultMessage) => void>;
|
||||
|
|
@ -91,10 +93,22 @@ export class HubConnection {
|
|||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
let requestedTransferMode =
|
||||
(this.protocol.type === ProtocolType.Binary)
|
||||
? TransferMode.Binary
|
||||
: TransferMode.Text;
|
||||
|
||||
this.connection.features.transferMode = requestedTransferMode
|
||||
await this.connection.start();
|
||||
var actualTransferMode = this.connection.features.transferMode;
|
||||
|
||||
await this.connection.send(
|
||||
TextMessageFormat.write(
|
||||
JSON.stringify(<NegotiationMessage>{ protocol: this.protocol.name()})));
|
||||
JSON.stringify(<NegotiationMessage>{ protocol: this.protocol.name})));
|
||||
|
||||
if (requestedTransferMode === TransferMode.Binary && actualTransferMode === TransferMode.Text) {
|
||||
this.protocol = new Base64EncodedHubProtocol(this.protocol);
|
||||
}
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
|
|
@ -196,3 +210,38 @@ export class HubConnection {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Base64EncodedHubProtocol implements IHubProtocol {
|
||||
private wrappedProtocol: IHubProtocol;
|
||||
|
||||
constructor(protocol: IHubProtocol) {
|
||||
this.wrappedProtocol = protocol;
|
||||
this.name = this.wrappedProtocol.name;
|
||||
this.type = ProtocolType.Text;
|
||||
}
|
||||
|
||||
readonly name: string;
|
||||
readonly type: ProtocolType;
|
||||
|
||||
parseMessages(input: any): HubMessage[] {
|
||||
// atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use
|
||||
// base64-js module
|
||||
let s = atob(input);
|
||||
let payload = new Uint8Array(s.length);
|
||||
for (let i = 0; i < payload.length; i++) {
|
||||
payload[i] = s.charCodeAt(i);
|
||||
}
|
||||
return this.wrappedProtocol.parseMessages(payload.buffer);
|
||||
}
|
||||
|
||||
writeMessage(message: HubMessage) {
|
||||
let payload = new Uint8Array(this.wrappedProtocol.writeMessage(message));
|
||||
let s = "";
|
||||
for (var i = 0; i < payload.byteLength; i++) {
|
||||
s += String.fromCharCode(payload[i]);
|
||||
}
|
||||
// atob/btoa are browsers APIs but they can be polyfilled. If this becomes problematic we can use
|
||||
// base64-js module
|
||||
return btoa(s);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import { DataReceived, ConnectionClosed } from "./Common"
|
||||
import { TransportType, ITransport } from "./Transports"
|
||||
import { TransportType, TransferMode, ITransport } from "./Transports"
|
||||
|
||||
export interface IConnection {
|
||||
readonly features: any;
|
||||
|
||||
start(): Promise<void>;
|
||||
send(data: any): Promise<void>;
|
||||
stop(): void;
|
||||
|
|
|
|||
|
|
@ -28,8 +28,14 @@ export interface NegotiationMessage {
|
|||
readonly protocol: string;
|
||||
}
|
||||
|
||||
export const enum ProtocolType {
|
||||
Text = 1,
|
||||
Binary
|
||||
}
|
||||
|
||||
export interface IHubProtocol {
|
||||
name(): string;
|
||||
readonly name: string;
|
||||
readonly type: ProtocolType;
|
||||
parseMessages(input: any): HubMessage[];
|
||||
writeMessage(message: HubMessage): any;
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import { TextMessageFormat } from "./Formatters";
|
||||
import { IHubProtocol, HubMessage } from "./IHubProtocol";
|
||||
import { IHubProtocol, ProtocolType, HubMessage } from "./IHubProtocol";
|
||||
|
||||
export class JsonHubProtocol implements IHubProtocol {
|
||||
name(): string {
|
||||
return "json"
|
||||
}
|
||||
|
||||
readonly name: string = "json";
|
||||
|
||||
readonly type: ProtocolType = ProtocolType.Text;
|
||||
|
||||
parseMessages(input: string): HubMessage[] {
|
||||
if (!input) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { IHubProtocol, MessageType, HubMessage, InvocationMessage, ResultMessage, CompletionMessage } from "./IHubProtocol";
|
||||
import { IHubProtocol, ProtocolType, MessageType, HubMessage, InvocationMessage, ResultMessage, CompletionMessage } from "./IHubProtocol";
|
||||
import { BinaryMessageFormat } from "./Formatters"
|
||||
import * as msgpack5 from "msgpack5"
|
||||
|
||||
export class MessagePackHubProtocol implements IHubProtocol {
|
||||
name(): string {
|
||||
return "messagepack";
|
||||
}
|
||||
|
||||
readonly name: string = "messagepack";
|
||||
|
||||
readonly type: ProtocolType = ProtocolType.Binary;
|
||||
|
||||
parseMessages(input: ArrayBuffer): HubMessage[] {
|
||||
return BinaryMessageFormat.parse(input).map(m => this.parseMessage(m));
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { DataReceived, TransportClosed } from "./Common"
|
||||
import { IHttpClient } from "./HttpClient"
|
||||
import { HttpError } from "./HttpError"
|
||||
|
||||
export enum TransportType {
|
||||
WebSockets,
|
||||
|
|
@ -7,8 +8,13 @@ export enum TransportType {
|
|||
LongPolling
|
||||
}
|
||||
|
||||
export const enum TransferMode {
|
||||
Text = 1,
|
||||
Binary
|
||||
}
|
||||
|
||||
export interface ITransport {
|
||||
connect(url: string): Promise<void>;
|
||||
connect(url: string, requestedTransferMode: TransferMode): Promise<TransferMode>;
|
||||
send(data: any): Promise<void>;
|
||||
stop(): void;
|
||||
onDataReceived: DataReceived;
|
||||
|
|
@ -18,16 +24,20 @@ export interface ITransport {
|
|||
export class WebSocketTransport implements ITransport {
|
||||
private webSocket: WebSocket;
|
||||
|
||||
connect(url: string, queryString: string = ""): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
connect(url: string, requestedTransferMode: TransferMode): Promise<TransferMode> {
|
||||
|
||||
return new Promise<TransferMode>((resolve, reject) => {
|
||||
url = url.replace(/^http/, "ws");
|
||||
|
||||
let webSocket = new WebSocket(url);
|
||||
if (requestedTransferMode == TransferMode.Binary) {
|
||||
webSocket.binaryType = "arraybuffer";
|
||||
}
|
||||
|
||||
webSocket.onopen = (event: Event) => {
|
||||
console.log(`WebSocket connected to ${url}`);
|
||||
this.webSocket = webSocket;
|
||||
resolve();
|
||||
resolve(requestedTransferMode);
|
||||
};
|
||||
|
||||
webSocket.onerror = (event: Event) => {
|
||||
|
|
@ -78,20 +88,19 @@ export class WebSocketTransport implements ITransport {
|
|||
export class ServerSentEventsTransport implements ITransport {
|
||||
private eventSource: EventSource;
|
||||
private url: string;
|
||||
private queryString: string;
|
||||
private httpClient: IHttpClient;
|
||||
|
||||
constructor(httpClient: IHttpClient) {
|
||||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
connect(url: string): Promise<void> {
|
||||
connect(url: string, requestedTransferMode: TransferMode): Promise<TransferMode> {
|
||||
if (typeof (EventSource) === "undefined") {
|
||||
Promise.reject("EventSource not supported by the browser.")
|
||||
Promise.reject("EventSource not supported by the browser.");
|
||||
}
|
||||
this.url = url;
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
return new Promise<TransferMode>((resolve, reject) => {
|
||||
let eventSource = new EventSource(this.url);
|
||||
|
||||
try {
|
||||
|
|
@ -121,7 +130,8 @@ export class ServerSentEventsTransport implements ITransport {
|
|||
eventSource.onopen = () => {
|
||||
console.log(`SSE connected to ${this.url}`);
|
||||
this.eventSource = eventSource;
|
||||
resolve();
|
||||
// SSE is a text protocol
|
||||
resolve(TransferMode.Text);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
|
|
@ -155,19 +165,22 @@ export class LongPollingTransport implements ITransport {
|
|||
this.httpClient = httpClient;
|
||||
}
|
||||
|
||||
connect(url: string): Promise<void> {
|
||||
connect(url: string, requestedTransferMode: TransferMode): Promise<TransferMode> {
|
||||
this.url = url;
|
||||
this.shouldPoll = true;
|
||||
this.poll(this.url);
|
||||
return Promise.resolve();
|
||||
this.poll(this.url, requestedTransferMode);
|
||||
return Promise.resolve(requestedTransferMode);
|
||||
}
|
||||
|
||||
private poll(url: string): void {
|
||||
private poll(url: string, transferMode: TransferMode): void {
|
||||
if (!this.shouldPoll) {
|
||||
return;
|
||||
}
|
||||
|
||||
let pollXhr = new XMLHttpRequest();
|
||||
if (transferMode === TransferMode.Binary) {
|
||||
pollXhr.responseType = "arraybuffer";
|
||||
}
|
||||
|
||||
pollXhr.onload = () => {
|
||||
if (pollXhr.status == 200) {
|
||||
|
|
@ -187,7 +200,7 @@ export class LongPollingTransport implements ITransport {
|
|||
return;
|
||||
}
|
||||
}
|
||||
this.poll(url);
|
||||
this.poll(url, transferMode);
|
||||
}
|
||||
else if (this.pollXhr.status == 204) {
|
||||
if (this.onClosed) {
|
||||
|
|
@ -196,7 +209,7 @@ export class LongPollingTransport implements ITransport {
|
|||
}
|
||||
else {
|
||||
if (this.onClosed) {
|
||||
this.onClosed(new Error(`Status: ${pollXhr.status}, Message: ${pollXhr.responseText}`));
|
||||
this.onClosed(new HttpError(pollXhr.statusText, pollXhr.status));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -209,7 +222,7 @@ export class LongPollingTransport implements ITransport {
|
|||
};
|
||||
|
||||
pollXhr.ontimeout = () => {
|
||||
this.poll(url);
|
||||
this.poll(url, transferMode);
|
||||
}
|
||||
|
||||
this.pollXhr = pollXhr;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,15 @@ gulp.task('browserify-client', ['compile-ts-client'], () => {
|
|||
.pipe(gulp.dest(clientOutDir + '/../browser'));
|
||||
});
|
||||
|
||||
gulp.task('browserify-msgpackprotocol', ['compile-ts-client'], () => {
|
||||
return browserify(clientOutDir + '/MessagePackHubProtocol.js', {standalone: 'signalRMsgPack'})
|
||||
.bundle()
|
||||
.pipe(source('signalr-msgpackprotocol.js'))
|
||||
.pipe(gulp.dest(clientOutDir + '/../browser'));
|
||||
});
|
||||
|
||||
gulp.task('build-ts-client', ['clean', 'compile-ts-client', 'browserify-client']);
|
||||
gulp.task('browserify', [ 'browserify-client', 'browserify-msgpackprotocol']);
|
||||
|
||||
gulp.task('build-ts-client', ['clean', 'compile-ts-client', 'browserify']);
|
||||
|
||||
gulp.task('default', ['build-ts-client']);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@
|
|||
</ItemGroup>
|
||||
<Copy SourceFiles="@(JasmineFiles)" DestinationFolder="$(MSBuildProjectDirectory)/wwwroot/lib/jasmine" />
|
||||
|
||||
<Copy SourceFiles="$(MSBuildThisFileDirectory)..\dist\browser\signalr-client.js" DestinationFolder="$(MSBuildThisFileDirectory)wwwroot\lib\signalr-client" />
|
||||
<Copy SourceFiles="$(MSBuildThisFileDirectory)..\dist\browser\signalr-client.js;$(MSBuildThisFileDirectory)..\dist\browser\signalr-msgpackprotocol.js"
|
||||
DestinationFolder="$(MSBuildThisFileDirectory)wwwroot\lib\signalr" />
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
<script type="text/javascript" src="lib/jasmine/jasmine.js"></script>
|
||||
<script type="text/javascript" src="lib/jasmine/jasmine-html.js"></script>
|
||||
<script type="text/javascript" src="lib/jasmine/boot.js"></script>
|
||||
<script type="text/javascript" src="lib/signalr-client/signalr-client.js"></script>
|
||||
<script type="text/javascript" src="lib/signalr/signalr-client.js"></script>
|
||||
<script type="text/javascript" src="lib/signalr/signalr-msgpackprotocol.js"></script>
|
||||
<script src="js/common.js"></script>
|
||||
<script src="js/webSocketTests.js"></script>
|
||||
<script src="js/connectionTests.js"></script>
|
||||
|
|
|
|||
|
|
@ -5,5 +5,18 @@ function eachTransport(action) {
|
|||
signalR.TransportType.WebSockets,
|
||||
signalR.TransportType.ServerSentEvents,
|
||||
signalR.TransportType.LongPolling ];
|
||||
transportTypes.forEach(t => action(t));
|
||||
transportTypes.forEach(t => action(t));
|
||||
}
|
||||
|
||||
function eachTransportAndProtocol(action) {
|
||||
let transportTypes = [
|
||||
signalR.TransportType.WebSockets,
|
||||
signalR.TransportType.ServerSentEvents,
|
||||
signalR.TransportType.LongPolling ];
|
||||
let protocols = [
|
||||
new signalR.JsonHubProtocol(),
|
||||
new signalRMsgPack.MessagePackHubProtocol()
|
||||
];
|
||||
transportTypes.forEach(t =>
|
||||
protocols.forEach(p => action(t, p)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
const TESTHUBENDPOINT_URL = `http://${document.location.host}/testhub`;
|
||||
|
||||
describe('hubConnection', () => {
|
||||
eachTransport(transportType => {
|
||||
describe(`${signalR.TransportType[transportType]} transport`, () => {
|
||||
eachTransportAndProtocol((transportType, protocol) => {
|
||||
describe(`${protocol.name} over ${signalR.TransportType[transportType]} transport`, () => {
|
||||
it(`can invoke server method and receive result`, done => {
|
||||
const message = "Hi";
|
||||
let hubConnection = new signalR.HubConnection(new signalR.HttpConnection(TESTHUBENDPOINT_URL, { transport: transportType }));
|
||||
let hubConnection = new signalR.HubConnection(
|
||||
new signalR.HttpConnection(TESTHUBENDPOINT_URL, { transport: transportType }), protocol);
|
||||
hubConnection.onClosed = error => {
|
||||
expect(error).toBe(undefined);
|
||||
done();
|
||||
|
|
@ -31,7 +32,8 @@ describe('hubConnection', () => {
|
|||
});
|
||||
|
||||
it(`can stream server method and receive result`, done => {
|
||||
let hubConnection = new signalR.HubConnection(new signalR.HttpConnection(TESTHUBENDPOINT_URL, { transport: transportType }));
|
||||
let hubConnection = new signalR.HubConnection(
|
||||
new signalR.HttpConnection(TESTHUBENDPOINT_URL, { transport: transportType }), protocol);
|
||||
hubConnection.onClosed = error => {
|
||||
expect(error).toBe(undefined);
|
||||
done();
|
||||
|
|
@ -63,7 +65,8 @@ describe('hubConnection', () => {
|
|||
|
||||
it(`rethrows an exception from the server when invoking`, done => {
|
||||
const errorMessage = "An error occurred.";
|
||||
let hubConnection = new signalR.HubConnection(new signalR.HttpConnection(TESTHUBENDPOINT_URL, { transport: transportType }));
|
||||
let hubConnection = new signalR.HubConnection(
|
||||
new signalR.HttpConnection(TESTHUBENDPOINT_URL, { transport: transportType }), protocol);
|
||||
|
||||
hubConnection.start()
|
||||
.then(() => {
|
||||
|
|
@ -90,7 +93,8 @@ describe('hubConnection', () => {
|
|||
|
||||
it(`rethrows an exception from the server when streaming`, done => {
|
||||
const errorMessage = "An error occurred.";
|
||||
let hubConnection = new signalR.HubConnection(new signalR.HttpConnection(TESTHUBENDPOINT_URL, { transport: transportType }));
|
||||
let hubConnection = new signalR.HubConnection(
|
||||
new signalR.HttpConnection(TESTHUBENDPOINT_URL, { transport: transportType }), protocol);
|
||||
|
||||
hubConnection.start()
|
||||
.then(() => {
|
||||
|
|
@ -116,7 +120,8 @@ describe('hubConnection', () => {
|
|||
});
|
||||
|
||||
it(`can receive server calls`, done => {
|
||||
let client = new signalR.HubConnection(new signalR.HttpConnection(TESTHUBENDPOINT_URL, { transport: transportType }));
|
||||
let client = new signalR.HubConnection(
|
||||
new signalR.HttpConnection(TESTHUBENDPOINT_URL, { transport: transportType }), protocol);
|
||||
const message = "Hello SignalR";
|
||||
|
||||
let callbackPromise = new Promise((resolve, reject) => {
|
||||
|
|
@ -141,22 +146,23 @@ describe('hubConnection', () => {
|
|||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it(`over ${signalR.TransportType[transportType]} closed with error if hub cannot be created`, done =>{
|
||||
let errorRegex = {
|
||||
WebSockets: "1011", // Message is browser specific (e.g. 'Websocket closed with status code: 1011')
|
||||
LongPolling: "Status: 500",
|
||||
ServerSentEvents: "Error occurred"
|
||||
};
|
||||
it(`closed with error if hub cannot be created`, done => {
|
||||
let errorRegex = {
|
||||
WebSockets: "1011", // Message is browser specific (e.g. 'Websocket closed with status code: 1011')
|
||||
LongPolling: "Internal Server Error",
|
||||
ServerSentEvents: "Error occurred"
|
||||
};
|
||||
|
||||
let hubConnection = new signalR.HubConnection(new signalR.HttpConnection(`http://${document.location.host}/uncreatable`, { transport: transportType }));
|
||||
let hubConnection = new signalR.HubConnection(
|
||||
new signalR.HttpConnection(`http://${document.location.host}/uncreatable`, { transport: transportType }), protocol);
|
||||
|
||||
hubConnection.onClosed = error => {
|
||||
expect(error).toMatch(errorRegex[signalR.TransportType[transportType]]);
|
||||
done();
|
||||
}
|
||||
hubConnection.start();
|
||||
hubConnection.onClosed = error => {
|
||||
expect(error.message).toMatch(errorRegex[signalR.TransportType[transportType]]);
|
||||
done();
|
||||
}
|
||||
hubConnection.start();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ namespace Microsoft.AspNetCore.SignalR.Internal.Protocol
|
|||
completionMessage.HasResult ? NonVoidResult :
|
||||
VoidResult;
|
||||
|
||||
packer.PackArrayHeader(3 + resultKind != VoidResult ? 1 : 0);
|
||||
packer.PackArrayHeader(3 + (resultKind != VoidResult ? 1 : 0));
|
||||
packer.Pack(CompletionMessageType);
|
||||
packer.PackString(completionMessage.InvocationId);
|
||||
packer.Pack(resultKind);
|
||||
|
|
|
|||
Loading…
Reference in New Issue