Refactor HttpClient and use it in LongPollingTransport (#1243)
This commit is contained in:
parent
9a128b42ef
commit
44052ffcf6
|
|
@ -0,0 +1,31 @@
|
|||
// 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.
|
||||
|
||||
import { asyncit as it } from "./Utils";
|
||||
import { AbortController } from "../Microsoft.AspNetCore.SignalR.Client.TS/AbortController";
|
||||
|
||||
describe("AbortSignal", () => {
|
||||
describe("aborted", () => {
|
||||
it("is false on initialization", () => {
|
||||
expect(new AbortController().signal.aborted).toBe(false);
|
||||
});
|
||||
|
||||
it("is true when aborted", () => {
|
||||
let controller = new AbortController();
|
||||
let signal = controller.signal;
|
||||
controller.abort();
|
||||
expect(signal.aborted).toBe(true);
|
||||
})
|
||||
});
|
||||
|
||||
describe("onabort", () => {
|
||||
it("is called when abort is called", () => {
|
||||
let controller = new AbortController();
|
||||
let signal = controller.signal;
|
||||
let abortCalled = false;
|
||||
signal.onabort = () => abortCalled = true;
|
||||
controller.abort();
|
||||
expect(abortCalled).toBe(true);
|
||||
})
|
||||
})
|
||||
});
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
// 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.
|
||||
|
||||
import { asyncit as it } from "./Utils"
|
||||
import { TestHttpClient } from "./TestHttpClient";
|
||||
import { HttpRequest } from "../Microsoft.AspNetCore.SignalR.Client.TS/index";
|
||||
|
||||
describe("HttpClient", () => {
|
||||
describe("get", () => {
|
||||
it("sets the method and URL appropriately", async () => {
|
||||
let request: HttpRequest;
|
||||
let testClient = new TestHttpClient().on(r => {
|
||||
request = r; return "";
|
||||
});
|
||||
|
||||
await testClient.get("http://localhost");
|
||||
expect(request.method).toEqual("GET");
|
||||
expect(request.url).toEqual("http://localhost");
|
||||
});
|
||||
|
||||
it("overrides method and url in options", async () => {
|
||||
let request: HttpRequest;
|
||||
let testClient = new TestHttpClient().on(r => {
|
||||
request = r; return "";
|
||||
});
|
||||
|
||||
await testClient.get("http://localhost", {
|
||||
method: "OPTIONS",
|
||||
url: "http://wrong"
|
||||
});
|
||||
expect(request.method).toEqual("GET");
|
||||
expect(request.url).toEqual("http://localhost");
|
||||
})
|
||||
|
||||
it("copies other options", async () => {
|
||||
let request: HttpRequest;
|
||||
let testClient = new TestHttpClient().on(r => {
|
||||
request = r; return "";
|
||||
});
|
||||
|
||||
await testClient.get("http://localhost", {
|
||||
timeout: 42,
|
||||
});
|
||||
expect(request.timeout).toEqual(42);
|
||||
})
|
||||
});
|
||||
|
||||
describe("post", () => {
|
||||
it("sets the method and URL appropriately", async () => {
|
||||
let request: HttpRequest;
|
||||
let testClient = new TestHttpClient().on(r => {
|
||||
request = r; return "";
|
||||
});
|
||||
|
||||
await testClient.post("http://localhost");
|
||||
expect(request.method).toEqual("POST");
|
||||
expect(request.url).toEqual("http://localhost");
|
||||
});
|
||||
|
||||
it("overrides method and url in options", async () => {
|
||||
let request: HttpRequest;
|
||||
let testClient = new TestHttpClient().on(r => {
|
||||
request = r; return "";
|
||||
});
|
||||
|
||||
await testClient.post("http://localhost", {
|
||||
method: "OPTIONS",
|
||||
url: "http://wrong"
|
||||
});
|
||||
expect(request.method).toEqual("POST");
|
||||
expect(request.url).toEqual("http://localhost");
|
||||
})
|
||||
|
||||
it("copies other options", async () => {
|
||||
let request: HttpRequest;
|
||||
let testClient = new TestHttpClient().on(r => {
|
||||
request = r; return "";
|
||||
});
|
||||
|
||||
await testClient.post("http://localhost", {
|
||||
timeout: 42,
|
||||
});
|
||||
expect(request.timeout).toEqual(42);
|
||||
})
|
||||
});
|
||||
});
|
||||
|
|
@ -1,14 +1,15 @@
|
|||
// 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.
|
||||
|
||||
import { IHttpClient } from "../Microsoft.AspNetCore.SignalR.Client.TS/HttpClient"
|
||||
import { TestHttpClient } from "./TestHttpClient"
|
||||
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, TransferMode } from "../Microsoft.AspNetCore.SignalR.Client.TS/Transports"
|
||||
import { eachTransport, eachEndpointUrl } from "./Common";
|
||||
import { HttpResponse } from "../Microsoft.AspNetCore.SignalR.Client.TS/index";
|
||||
|
||||
describe("Connection", () => {
|
||||
describe("HttpConnection", () => {
|
||||
it("cannot be created with relative url if document object is not present", () => {
|
||||
expect(() => new HttpConnection("/test"))
|
||||
.toThrow(new Error("Cannot resolve '/test'."));
|
||||
|
|
@ -23,14 +24,9 @@ describe("Connection", () => {
|
|||
|
||||
it("starting connection fails if getting id fails", async (done) => {
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
post(url: string): Promise<string> {
|
||||
return Promise.reject("error");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
return Promise.resolve("");
|
||||
}
|
||||
},
|
||||
httpClient: new TestHttpClient()
|
||||
.on("POST", r => Promise.reject("error"))
|
||||
.on("GET", r => ""),
|
||||
logger: null
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
|
|
@ -49,8 +45,8 @@ describe("Connection", () => {
|
|||
|
||||
it("cannot start a running connection", async (done) => {
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
post(url: string): Promise<string> {
|
||||
httpClient: new TestHttpClient()
|
||||
.on("POST", r => {
|
||||
connection.start()
|
||||
.then(() => {
|
||||
fail();
|
||||
|
|
@ -60,13 +56,8 @@ describe("Connection", () => {
|
|||
expect(error.message).toBe("Cannot start a connection that is not in the 'Disconnected' state.");
|
||||
done();
|
||||
});
|
||||
|
||||
return Promise.reject("error");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
return Promise.resolve("");
|
||||
}
|
||||
},
|
||||
}),
|
||||
logger: null
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
|
|
@ -84,15 +75,12 @@ describe("Connection", () => {
|
|||
it("can start a stopped connection", async (done) => {
|
||||
let negotiateCalls = 0;
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
post(url: string): Promise<string> {
|
||||
httpClient: new TestHttpClient()
|
||||
.on("POST", r => {
|
||||
negotiateCalls += 1;
|
||||
return Promise.reject("reached negotiate");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
return Promise.resolve("");
|
||||
}
|
||||
},
|
||||
})
|
||||
.on("GET", r => ""),
|
||||
logger: null
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
|
|
@ -115,16 +103,15 @@ describe("Connection", () => {
|
|||
|
||||
it("can stop a starting connection", async (done) => {
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
post(url: string): Promise<string> {
|
||||
httpClient: new TestHttpClient()
|
||||
.on("POST", r => {
|
||||
connection.stop();
|
||||
return Promise.resolve("{}");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
return "{}";
|
||||
})
|
||||
.on("GET", r => {
|
||||
connection.stop();
|
||||
return Promise.resolve("");
|
||||
}
|
||||
},
|
||||
return "";
|
||||
}),
|
||||
logger: null
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
|
|
@ -164,16 +151,11 @@ describe("Connection", () => {
|
|||
}
|
||||
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
post(url: string): Promise<string> {
|
||||
return Promise.resolve("{ \"connectionId\": \"42\" }");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
return Promise.resolve("");
|
||||
}
|
||||
},
|
||||
httpClient: new TestHttpClient()
|
||||
.on("POST", r => "{ \"connectionId\": \"42\" }")
|
||||
.on("GET", r => ""),
|
||||
transport: fakeTransport,
|
||||
logger: null
|
||||
logger: null,
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
|
||||
|
|
@ -196,17 +178,16 @@ describe("Connection", () => {
|
|||
let negotiateUrl: string;
|
||||
let connection: HttpConnection;
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
post(url: string): Promise<string> {
|
||||
negotiateUrl = url;
|
||||
httpClient: new TestHttpClient()
|
||||
.on("POST", r => {
|
||||
negotiateUrl = r.url;
|
||||
connection.stop();
|
||||
return Promise.resolve("{}");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
return "{}";
|
||||
})
|
||||
.on("GET", r => {
|
||||
connection.stop();
|
||||
return Promise.resolve("");
|
||||
}
|
||||
},
|
||||
return "";
|
||||
}),
|
||||
logger: null
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
|
|
@ -231,14 +212,9 @@ describe("Connection", () => {
|
|||
}
|
||||
it(`cannot be started if requested ${TransportType[requestedTransport]} transport not available on server`, async done => {
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
post(url: string): Promise<string> {
|
||||
return Promise.resolve("{ \"connectionId\": \"42\", \"availableTransports\": [] }");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
return Promise.resolve("");
|
||||
}
|
||||
},
|
||||
httpClient: new TestHttpClient()
|
||||
.on("POST", r => "{ \"connectionId\": \"42\", \"availableTransports\": [] }")
|
||||
.on("GET", r => ""),
|
||||
transport: requestedTransport,
|
||||
logger: null
|
||||
} as IHttpConnectionOptions;
|
||||
|
|
@ -258,14 +234,9 @@ describe("Connection", () => {
|
|||
|
||||
it("cannot be started if no transport available on server and no transport requested", async done => {
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
post(url: string): Promise<string> {
|
||||
return Promise.resolve("{ \"connectionId\": \"42\", \"availableTransports\": [] }");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
return Promise.resolve("");
|
||||
}
|
||||
},
|
||||
httpClient: new TestHttpClient()
|
||||
.on("POST", r => "{ \"connectionId\": \"42\", \"availableTransports\": [] }")
|
||||
.on("GET", r => ""),
|
||||
logger: null
|
||||
} as IHttpConnectionOptions;
|
||||
|
||||
|
|
@ -283,14 +254,7 @@ describe("Connection", () => {
|
|||
|
||||
it('does not send negotiate request if WebSockets transport requested explicitly', async done => {
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
post(url: string): Promise<string> {
|
||||
return Promise.reject("Should not be called");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
return Promise.reject("Should not be called");
|
||||
}
|
||||
},
|
||||
httpClient: new TestHttpClient(),
|
||||
transport: TransportType.WebSockets,
|
||||
logger: null
|
||||
} as IHttpConnectionOptions;
|
||||
|
|
@ -327,14 +291,9 @@ describe("Connection", () => {
|
|||
} as ITransport;
|
||||
|
||||
let options: IHttpConnectionOptions = {
|
||||
httpClient: <IHttpClient>{
|
||||
post(url: string): Promise<string> {
|
||||
return Promise.resolve("{ \"connectionId\": \"42\", \"availableTransports\": [] }");
|
||||
},
|
||||
get(url: string): Promise<string> {
|
||||
return Promise.resolve("");
|
||||
}
|
||||
},
|
||||
httpClient: new TestHttpClient()
|
||||
.on("POST", r => "{ \"connectionId\": \"42\", \"availableTransports\": [] }")
|
||||
.on("GET", r => ""),
|
||||
transport: fakeTransport,
|
||||
logger: null
|
||||
} as IHttpConnectionOptions;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
// 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.
|
||||
|
||||
import { HttpClient, HttpRequest, HttpResponse } from "../Microsoft.AspNetCore.SignalR.Client.TS/HttpClient"
|
||||
|
||||
type TestHttpHandlerResult = HttpResponse | string;
|
||||
export type TestHttpHandler = (request: HttpRequest, next?: (request: HttpRequest) => Promise<HttpResponse>) => Promise<TestHttpHandlerResult> | TestHttpHandlerResult;
|
||||
|
||||
export class TestHttpClient extends HttpClient {
|
||||
private handler: (request: HttpRequest) => Promise<HttpResponse>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.handler = (request: HttpRequest) =>
|
||||
Promise.reject(`Request has no handler: ${request.method} ${request.url}`);
|
||||
|
||||
}
|
||||
|
||||
send(request: HttpRequest): Promise<HttpResponse> {
|
||||
return this.handler(request);
|
||||
}
|
||||
|
||||
on(handler: TestHttpHandler): TestHttpClient;
|
||||
on(method: string | RegExp, handler: TestHttpHandler): TestHttpClient;
|
||||
on(method: string | RegExp, url: string, handler: TestHttpHandler): TestHttpClient;
|
||||
on(method: string | RegExp, url: RegExp, handler: TestHttpHandler): TestHttpClient;
|
||||
on(methodOrHandler: string | RegExp | TestHttpHandler, urlOrHandler?: string | RegExp | TestHttpHandler, handler?: TestHttpHandler): TestHttpClient {
|
||||
let method: string | RegExp;
|
||||
let url: string | RegExp;
|
||||
if ((typeof methodOrHandler === "string") || (methodOrHandler instanceof RegExp)) {
|
||||
method = methodOrHandler;
|
||||
}
|
||||
else if (methodOrHandler) {
|
||||
handler = methodOrHandler;
|
||||
}
|
||||
|
||||
if ((typeof urlOrHandler === "string") || (urlOrHandler instanceof RegExp)) {
|
||||
url = urlOrHandler;
|
||||
}
|
||||
else if (urlOrHandler) {
|
||||
handler = urlOrHandler;
|
||||
}
|
||||
|
||||
// TypeScript callers won't be able to do this, because TypeScript checks this for us.
|
||||
if (!handler) {
|
||||
throw new Error("Missing required argument: 'handler'");
|
||||
}
|
||||
|
||||
let oldHandler = this.handler;
|
||||
let newHandler = async (request: HttpRequest) => {
|
||||
if (matches(method, request.method) && matches(url, request.url)) {
|
||||
let promise = handler(request, oldHandler);
|
||||
|
||||
let val: TestHttpHandlerResult;
|
||||
if (promise instanceof Promise) {
|
||||
val = await promise;
|
||||
} else {
|
||||
val = promise;
|
||||
}
|
||||
|
||||
if (typeof val === "string") {
|
||||
return new HttpResponse(200, "OK", val);
|
||||
}
|
||||
else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return await oldHandler(request);
|
||||
}
|
||||
};
|
||||
this.handler = newHandler;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
function matches(pattern: string | RegExp, actual: string): boolean {
|
||||
// Null or undefined pattern matches all.
|
||||
if (!pattern) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof pattern === "string") {
|
||||
return actual === pattern;
|
||||
}
|
||||
else {
|
||||
return pattern.test(actual);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,16 +3,20 @@
|
|||
|
||||
import { clearTimeout, setTimeout } from "timers";
|
||||
|
||||
export function asyncit(expectation: string, assertion?: () => Promise<any>, timeout?: number): void {
|
||||
export function asyncit(expectation: string, assertion?: () => Promise<any> | void, timeout?: number): void {
|
||||
let testFunction: (done: DoneFn) => void;
|
||||
if (assertion) {
|
||||
testFunction = done => {
|
||||
assertion()
|
||||
.then(() => done())
|
||||
.catch((err) => {
|
||||
fail(err);
|
||||
done();
|
||||
});
|
||||
let promise = assertion();
|
||||
if (promise) {
|
||||
promise.then(() => done())
|
||||
.catch((err) => {
|
||||
fail(err);
|
||||
done();
|
||||
});
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -54,4 +58,4 @@ export class PromiseSource<T> {
|
|||
reject(reason?: any) {
|
||||
this.rejecter(reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
// 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.
|
||||
|
||||
// Rough polyfill of https://developer.mozilla.org/en-US/docs/Web/API/AbortController
|
||||
// We don't actually ever use the API being polyfilled, we always use the polyfill because
|
||||
// it's a very new API right now.
|
||||
|
||||
export class AbortController implements AbortSignal {
|
||||
private isAborted: boolean = false;
|
||||
public onabort: () => void;
|
||||
|
||||
abort() {
|
||||
if (!this.isAborted) {
|
||||
this.isAborted = true;
|
||||
if (this.onabort) {
|
||||
this.onabort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get signal(): AbortSignal {
|
||||
return this;
|
||||
}
|
||||
|
||||
get aborted(): boolean {
|
||||
return this.isAborted;
|
||||
}
|
||||
}
|
||||
|
||||
export interface AbortSignal {
|
||||
aborted: boolean;
|
||||
onabort: () => void;
|
||||
}
|
||||
|
|
@ -7,4 +7,10 @@ export class HttpError extends Error {
|
|||
super(errorMessage);
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class TimeoutError extends Error {
|
||||
constructor(errorMessage: string = "A timeout occurred.") {
|
||||
super(errorMessage);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +1,86 @@
|
|||
// 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.
|
||||
|
||||
import { HttpError } from "./HttpError"
|
||||
import { TimeoutError, HttpError } from "./Errors";
|
||||
import { AbortSignal } from "./AbortController";
|
||||
|
||||
export interface IHttpClient {
|
||||
get(url: string, headers?: Map<string, string>): Promise<string>;
|
||||
post(url: string, content: string, headers?: Map<string, string>): Promise<string>;
|
||||
export interface HttpRequest {
|
||||
method?: string,
|
||||
url?: string,
|
||||
content?: string | ArrayBuffer,
|
||||
headers?: Map<string, string>,
|
||||
responseType?: XMLHttpRequestResponseType,
|
||||
abortSignal?: AbortSignal,
|
||||
timeout?: number,
|
||||
}
|
||||
|
||||
export class HttpClient implements IHttpClient {
|
||||
get(url: string, headers?: Map<string, string>): Promise<string> {
|
||||
return this.xhr("GET", url, headers);
|
||||
export class HttpResponse {
|
||||
constructor(statusCode: number, statusText: string, content: string);
|
||||
constructor(statusCode: number, statusText: string, content: ArrayBuffer);
|
||||
constructor(
|
||||
public readonly statusCode: number,
|
||||
public readonly statusText: string,
|
||||
public readonly content: string | ArrayBuffer) {
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class HttpClient {
|
||||
get(url: string): Promise<HttpResponse>;
|
||||
get(url: string, options: HttpRequest): Promise<HttpResponse>;
|
||||
get(url: string, options?: HttpRequest): Promise<HttpResponse> {
|
||||
return this.send({
|
||||
...options,
|
||||
method: "GET",
|
||||
url: url,
|
||||
});
|
||||
}
|
||||
|
||||
post(url: string, content: string, headers?: Map<string, string>): Promise<string> {
|
||||
return this.xhr("POST", url, headers, content);
|
||||
post(url: string): Promise<HttpResponse>;
|
||||
post(url: string, options: HttpRequest): Promise<HttpResponse>;
|
||||
post(url: string, options?: HttpRequest): Promise<HttpResponse> {
|
||||
return this.send({
|
||||
...options,
|
||||
method: "POST",
|
||||
url: url,
|
||||
});
|
||||
}
|
||||
|
||||
private xhr(method: string, url: string, headers?: Map<string, string>, content?: string): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
abstract send(request: HttpRequest): Promise<HttpResponse>;
|
||||
}
|
||||
|
||||
export class DefaultHttpClient extends HttpClient {
|
||||
send(request: HttpRequest): Promise<HttpResponse> {
|
||||
return new Promise<HttpResponse>((resolve, reject) => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.open(method, url, true);
|
||||
xhr.open(request.method, request.url, true);
|
||||
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
||||
if (headers) {
|
||||
headers.forEach((value, header) => xhr.setRequestHeader(header, value));
|
||||
|
||||
if (request.headers) {
|
||||
request.headers.forEach((value, header) => xhr.setRequestHeader(header, value));
|
||||
}
|
||||
|
||||
if (request.responseType) {
|
||||
xhr.responseType = request.responseType;
|
||||
}
|
||||
|
||||
if (request.abortSignal) {
|
||||
request.abortSignal.onabort = () => {
|
||||
xhr.abort();
|
||||
};
|
||||
}
|
||||
|
||||
if (request.timeout) {
|
||||
xhr.timeout = request.timeout;
|
||||
}
|
||||
|
||||
xhr.send(content);
|
||||
xhr.onload = () => {
|
||||
if (request.abortSignal) {
|
||||
request.abortSignal.onabort = null;
|
||||
}
|
||||
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
resolve(xhr.response || xhr.responseText);
|
||||
resolve(new HttpResponse(xhr.status, xhr.statusText, xhr.response || xhr.responseText))
|
||||
}
|
||||
else {
|
||||
reject(new HttpError(xhr.statusText, xhr.status));
|
||||
|
|
@ -40,6 +90,12 @@ export class HttpClient implements IHttpClient {
|
|||
xhr.onerror = () => {
|
||||
reject(new HttpError(xhr.statusText, xhr.status));
|
||||
}
|
||||
|
||||
xhr.ontimeout = () => {
|
||||
reject(new TimeoutError());
|
||||
}
|
||||
|
||||
xhr.send(request.content || "");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import { DataReceived, ConnectionClosed } from "./Common"
|
||||
import { IConnection } from "./IConnection"
|
||||
import { ITransport, TransferMode, TransportType, WebSocketTransport, ServerSentEventsTransport, LongPollingTransport } from "./Transports"
|
||||
import { IHttpClient, HttpClient } from "./HttpClient"
|
||||
import { HttpClient, DefaultHttpClient } from "./HttpClient"
|
||||
import { IHttpConnectionOptions } from "./IHttpConnectionOptions"
|
||||
import { ILogger, LogLevel } from "./ILogger"
|
||||
import { LoggerFactory } from "./Loggers"
|
||||
|
|
@ -24,7 +24,7 @@ export class HttpConnection implements IConnection {
|
|||
private connectionState: ConnectionState;
|
||||
private baseUrl: string;
|
||||
private url: string;
|
||||
private readonly httpClient: IHttpClient;
|
||||
private readonly httpClient: HttpClient;
|
||||
private readonly logger: ILogger;
|
||||
private readonly options: IHttpConnectionOptions;
|
||||
private transport: ITransport;
|
||||
|
|
@ -37,7 +37,7 @@ export class HttpConnection implements IConnection {
|
|||
this.logger = LoggerFactory.createLogger(options.logger);
|
||||
this.baseUrl = this.resolveUrl(url);
|
||||
options = options || {};
|
||||
this.httpClient = options.httpClient || new HttpClient();
|
||||
this.httpClient = options.httpClient || new DefaultHttpClient();
|
||||
this.connectionState = ConnectionState.Disconnected;
|
||||
this.options = options;
|
||||
}
|
||||
|
|
@ -67,9 +67,12 @@ export class HttpConnection implements IConnection {
|
|||
headers.set("Authorization", `Bearer ${this.options.accessToken()}`);
|
||||
}
|
||||
|
||||
let negotiatePayload = await this.httpClient.post(this.resolveNegotiateUrl(this.baseUrl), "", headers);
|
||||
let negotiatePayload = await this.httpClient.post(this.resolveNegotiateUrl(this.baseUrl), {
|
||||
content: "",
|
||||
headers
|
||||
});
|
||||
|
||||
let negotiateResponse: INegotiateResponse = JSON.parse(negotiatePayload);
|
||||
let negotiateResponse: INegotiateResponse = JSON.parse(<string>negotiatePayload.content);
|
||||
this.connectionId = negotiateResponse.connectionId;
|
||||
|
||||
// the user tries to stop the the connection when it is being started
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
// 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.
|
||||
|
||||
import { IHttpClient } from "./HttpClient"
|
||||
import { HttpClient } from "./HttpClient"
|
||||
import { TransportType, ITransport } from "./Transports"
|
||||
import { ILogger, LogLevel } from "./ILogger";
|
||||
|
||||
export interface IHttpConnectionOptions {
|
||||
httpClient?: IHttpClient;
|
||||
httpClient?: HttpClient;
|
||||
transport?: TransportType | ITransport;
|
||||
logger?: ILogger | LogLevel;
|
||||
accessToken?: () => string;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
// 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.
|
||||
|
||||
import { DataReceived, TransportClosed } from "./Common"
|
||||
import { IHttpClient } from "./HttpClient"
|
||||
import { HttpError } from "./HttpError"
|
||||
import { ILogger, LogLevel } from "./ILogger"
|
||||
import { IConnection } from "./IConnection"
|
||||
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";
|
||||
|
||||
export enum TransportType {
|
||||
WebSockets,
|
||||
|
|
@ -103,13 +104,13 @@ export class WebSocketTransport implements ITransport {
|
|||
}
|
||||
|
||||
export class ServerSentEventsTransport implements ITransport {
|
||||
private readonly httpClient: IHttpClient;
|
||||
private readonly httpClient: HttpClient;
|
||||
private readonly accessToken: () => string;
|
||||
private readonly logger: ILogger;
|
||||
private eventSource: EventSource;
|
||||
private url: string;
|
||||
|
||||
constructor(httpClient: IHttpClient, accessToken: () => string, logger: ILogger) {
|
||||
constructor(httpClient: HttpClient, accessToken: () => string, logger: ILogger) {
|
||||
this.httpClient = httpClient;
|
||||
this.accessToken = accessToken;
|
||||
this.logger = logger;
|
||||
|
|
@ -183,23 +184,23 @@ export class ServerSentEventsTransport implements ITransport {
|
|||
}
|
||||
|
||||
export class LongPollingTransport implements ITransport {
|
||||
private readonly httpClient: IHttpClient;
|
||||
private readonly httpClient: HttpClient;
|
||||
private readonly accessToken: () => string;
|
||||
private readonly logger: ILogger;
|
||||
|
||||
private url: string;
|
||||
private pollXhr: XMLHttpRequest;
|
||||
private shouldPoll: boolean;
|
||||
private pollAbort: AbortController;
|
||||
|
||||
constructor(httpClient: IHttpClient, accessToken: () => string, logger: ILogger) {
|
||||
constructor(httpClient: HttpClient, accessToken: () => string, logger: ILogger) {
|
||||
this.httpClient = httpClient;
|
||||
this.accessToken = accessToken;
|
||||
this.logger = logger;
|
||||
this.pollAbort = new AbortController();
|
||||
}
|
||||
|
||||
connect(url: string, requestedTransferMode: TransferMode, connection: IConnection): Promise<TransferMode> {
|
||||
this.url = url;
|
||||
this.shouldPoll = true;
|
||||
|
||||
// Set a flag indicating we have inherent keep-alive in this transport.
|
||||
connection.features.inherentKeepAlive = true;
|
||||
|
|
@ -213,73 +214,70 @@ export class LongPollingTransport implements ITransport {
|
|||
return Promise.resolve(requestedTransferMode);
|
||||
}
|
||||
|
||||
private poll(url: string, transferMode: TransferMode): void {
|
||||
if (!this.shouldPoll) {
|
||||
return;
|
||||
private async poll(url: string, transferMode: TransferMode): Promise<void> {
|
||||
let pollOptions: HttpRequest = {
|
||||
timeout: 120000,
|
||||
abortSignal: this.pollAbort.signal,
|
||||
headers: new Map<string, string>(),
|
||||
};
|
||||
|
||||
if (transferMode === TransferMode.Binary) {
|
||||
pollOptions.responseType = "arraybuffer";
|
||||
}
|
||||
|
||||
let pollXhr = new XMLHttpRequest();
|
||||
if (this.accessToken) {
|
||||
pollOptions.headers.set("Authorization", `Bearer ${this.accessToken()}`);
|
||||
}
|
||||
|
||||
pollXhr.onload = () => {
|
||||
if (pollXhr.status == 200) {
|
||||
if (this.onreceive) {
|
||||
try {
|
||||
let response = transferMode === TransferMode.Text
|
||||
? pollXhr.responseText
|
||||
: pollXhr.response;
|
||||
while (!this.pollAbort.signal.aborted) {
|
||||
try {
|
||||
let pollUrl = `${url}&_=${Date.now()}`;
|
||||
this.logger.log(LogLevel.Trace, `(LongPolling transport) polling: ${pollUrl}`);
|
||||
let response = await this.httpClient.get(pollUrl, pollOptions)
|
||||
if (response.statusCode === 204) {
|
||||
this.logger.log(LogLevel.Information, "(LongPolling transport) Poll terminated by server");
|
||||
|
||||
if (response) {
|
||||
this.logger.log(LogLevel.Trace, `(LongPolling transport) data received: ${response}`);
|
||||
this.onreceive(response);
|
||||
// Poll terminated by server
|
||||
if (this.onclose) {
|
||||
this.onclose();
|
||||
}
|
||||
this.pollAbort.abort();
|
||||
}
|
||||
else if (response.statusCode !== 200) {
|
||||
this.logger.log(LogLevel.Error, `(LongPolling transport) Unexpected response code: ${response.statusCode}`);
|
||||
|
||||
// Unexpected status code
|
||||
if (this.onclose) {
|
||||
this.onclose(new HttpError(response.statusText, response.statusCode));
|
||||
}
|
||||
this.pollAbort.abort();
|
||||
}
|
||||
else {
|
||||
// Process the response
|
||||
if (response.content) {
|
||||
this.logger.log(LogLevel.Trace, `(LongPolling transport) data received: ${response.content}`);
|
||||
if (this.onreceive) {
|
||||
this.onreceive(response.content);
|
||||
}
|
||||
else {
|
||||
this.logger.log(LogLevel.Information, "(LongPolling transport) timed out");
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.onclose) {
|
||||
this.onclose(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// This is another way timeout manifest.
|
||||
this.logger.log(LogLevel.Trace, "(LongPolling transport) Poll timed out, reissuing.");
|
||||
}
|
||||
}
|
||||
this.poll(url, transferMode);
|
||||
}
|
||||
else if (this.pollXhr.status == 204) {
|
||||
if (this.onclose) {
|
||||
this.onclose();
|
||||
} catch (e) {
|
||||
if (e instanceof TimeoutError) {
|
||||
// Ignore timeouts and reissue the poll.
|
||||
this.logger.log(LogLevel.Trace, "(LongPolling transport) Poll timed out, reissuing.");
|
||||
} else {
|
||||
// Close the connection with the error as the result.
|
||||
if (this.onclose) {
|
||||
this.onclose(e);
|
||||
}
|
||||
this.pollAbort.abort();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.onclose) {
|
||||
this.onclose(new HttpError(pollXhr.statusText, pollXhr.status));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pollXhr.onerror = () => {
|
||||
if (this.onclose) {
|
||||
// network related error or denied cross domain request
|
||||
this.onclose(new Error("Sending HTTP request failed."));
|
||||
}
|
||||
};
|
||||
|
||||
pollXhr.ontimeout = () => {
|
||||
this.poll(url, transferMode);
|
||||
}
|
||||
|
||||
this.pollXhr = pollXhr;
|
||||
|
||||
this.pollXhr.open("GET", `${url}&_=${Date.now()}`, true);
|
||||
if (this.accessToken) {
|
||||
this.pollXhr.setRequestHeader("Authorization", `Bearer ${this.accessToken()}`);
|
||||
}
|
||||
if (transferMode === TransferMode.Binary) {
|
||||
this.pollXhr.responseType = "arraybuffer";
|
||||
}
|
||||
|
||||
// TODO: consider making timeout configurable
|
||||
this.pollXhr.timeout = 120000;
|
||||
this.pollXhr.send();
|
||||
}
|
||||
|
||||
async send(data: any): Promise<void> {
|
||||
|
|
@ -287,11 +285,7 @@ export class LongPollingTransport implements ITransport {
|
|||
}
|
||||
|
||||
stop(): Promise<void> {
|
||||
this.shouldPoll = false;
|
||||
if (this.pollXhr) {
|
||||
this.pollXhr.abort();
|
||||
this.pollXhr = null;
|
||||
}
|
||||
this.pollAbort.abort();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
|
|
@ -299,12 +293,15 @@ export class LongPollingTransport implements ITransport {
|
|||
onclose: TransportClosed;
|
||||
}
|
||||
|
||||
async function send(httpClient: IHttpClient, url: string, accessToken: () => string, data: any): Promise<void> {
|
||||
async function send(httpClient: HttpClient, url: string, accessToken: () => string, content: string | ArrayBuffer): Promise<void> {
|
||||
let headers;
|
||||
if (accessToken) {
|
||||
headers = new Map<string, string>();
|
||||
headers.set("Authorization", `Bearer ${accessToken()}`)
|
||||
}
|
||||
|
||||
await httpClient.post(url, data, headers);
|
||||
await httpClient.post(url, {
|
||||
content,
|
||||
headers
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title></title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1 id="head1"></h1>
|
||||
<div>
|
||||
|
|
@ -48,16 +50,15 @@
|
|||
|
||||
<ul id="message-list"></ul>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<script type="text/javascript">
|
||||
if (typeof Promise === 'undefined')
|
||||
{
|
||||
if (typeof Promise === 'undefined') {
|
||||
document.write(
|
||||
'<script type="text/javascript" src="lib/signalr-client/signalr-clientES5.js"><\/script>' +
|
||||
'<script type="text/javascript" src="lib/signalr-client/signalr-msgpackprotocolES5.js"><\/script>');
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
document.write(
|
||||
'<script type="text/javascript" src="lib/signalr-client/signalr-client.js"><\/script>' +
|
||||
'<script type="text/javascript" src="lib/signalr-client/signalr-msgpackprotocol.js"><\/script>');
|
||||
|
|
@ -65,102 +66,102 @@
|
|||
</script>
|
||||
<script src="utils.js"></script>
|
||||
<script>
|
||||
var isConnected = false;
|
||||
function invoke(connection, method) {
|
||||
if (!isConnected) {
|
||||
return;
|
||||
}
|
||||
var argsArray = Array.prototype.slice.call(arguments);
|
||||
connection.invoke.apply(connection, argsArray.slice(1))
|
||||
.then(function(result) {
|
||||
var isConnected = false;
|
||||
function invoke(connection, method) {
|
||||
if (!isConnected) {
|
||||
return;
|
||||
}
|
||||
var argsArray = Array.prototype.slice.call(arguments);
|
||||
connection.invoke.apply(connection, argsArray.slice(1))
|
||||
.then(function (result) {
|
||||
console.log("invocation completed successfully: " + (result === null ? '(null)' : result));
|
||||
|
||||
if (result) {
|
||||
addLine('message-list', result);
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
.catch(function (err) {
|
||||
addLine('message-list', err, 'red');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getText(id) {
|
||||
return document.getElementById(id).value;
|
||||
}
|
||||
function getText(id) {
|
||||
return document.getElementById(id).value;
|
||||
}
|
||||
|
||||
let transportType = signalR.TransportType[getParameterByName('transport')] || signalR.TransportType.WebSockets;
|
||||
let logger = new signalR.ConsoleLogger(signalR.LogLevel.Information);
|
||||
let hubRoute = getParameterByName('hubType') || "default";
|
||||
console.log('Hub Route:' + hubRoute);
|
||||
let transportType = signalR.TransportType[getParameterByName('transport')] || signalR.TransportType.WebSockets;
|
||||
let logger = new signalR.ConsoleLogger(signalR.LogLevel.Trace);
|
||||
let hubRoute = getParameterByName('hubType') || "default";
|
||||
console.log('Hub Route:' + hubRoute);
|
||||
|
||||
document.getElementById('head1').innerHTML = signalR.TransportType[transportType];
|
||||
document.getElementById('head1').innerHTML = signalR.TransportType[transportType];
|
||||
|
||||
let connectButton = document.getElementById('connect');
|
||||
let disconnectButton = document.getElementById('disconnect');
|
||||
disconnectButton.disabled = true;
|
||||
var connection;
|
||||
|
||||
click('connect', function(event) {
|
||||
connectButton.disabled = true;
|
||||
disconnectButton.disabled = false;
|
||||
console.log('http://' + document.location.host + '/' + hubRoute);
|
||||
connection = new signalR.HubConnection(hubRoute, { transport: transportType, logging: logger });
|
||||
connection.on('Send', function(msg) {
|
||||
addLine('message-list', msg);
|
||||
});
|
||||
|
||||
connection.onclose(function(e) {
|
||||
if (e) {
|
||||
addLine('message-list', 'Connection closed with error: ' + e, 'red');
|
||||
}
|
||||
else {
|
||||
addLine('message-list', 'Disconnected', 'green');
|
||||
}
|
||||
});
|
||||
|
||||
connection.start()
|
||||
.then(function() {
|
||||
isConnected = true;
|
||||
addLine('message-list', 'Connected successfully', 'green');
|
||||
})
|
||||
.catch(function(err) {
|
||||
addLine('message-list', err, 'red');
|
||||
});
|
||||
});
|
||||
|
||||
click('disconnect', function(event) {
|
||||
connectButton.disabled = false;
|
||||
let connectButton = document.getElementById('connect');
|
||||
let disconnectButton = document.getElementById('disconnect');
|
||||
disconnectButton.disabled = true;
|
||||
connection.stop()
|
||||
.then(function() {
|
||||
isConnected = false;
|
||||
var connection;
|
||||
|
||||
click('connect', function (event) {
|
||||
connectButton.disabled = true;
|
||||
disconnectButton.disabled = false;
|
||||
console.log('http://' + document.location.host + '/' + hubRoute);
|
||||
connection = new signalR.HubConnection(hubRoute, { transport: transportType, logging: logger });
|
||||
connection.on('Send', function (msg) {
|
||||
addLine('message-list', msg);
|
||||
});
|
||||
});
|
||||
|
||||
click('broadcast', function(event) {
|
||||
let data = getText('message-text');
|
||||
invoke(connection, 'Send', data);
|
||||
});
|
||||
connection.onclose(function (e) {
|
||||
if (e) {
|
||||
addLine('message-list', 'Connection closed with error: ' + e, 'red');
|
||||
}
|
||||
else {
|
||||
addLine('message-list', 'Disconnected', 'green');
|
||||
}
|
||||
});
|
||||
|
||||
click('join-group', function(event) {
|
||||
let groupName = getText('message-text');
|
||||
invoke(connection, 'JoinGroup', groupName);
|
||||
});
|
||||
connection.start()
|
||||
.then(function () {
|
||||
isConnected = true;
|
||||
addLine('message-list', 'Connected successfully', 'green');
|
||||
})
|
||||
.catch(function (err) {
|
||||
addLine('message-list', err, 'red');
|
||||
});
|
||||
});
|
||||
|
||||
click('leave-group', function(event) {
|
||||
let groupName = getText('message-text');
|
||||
invoke(connection, 'LeaveGroup', groupName);
|
||||
});
|
||||
click('disconnect', function (event) {
|
||||
connectButton.disabled = false;
|
||||
disconnectButton.disabled = true;
|
||||
connection.stop()
|
||||
.then(function () {
|
||||
isConnected = false;
|
||||
});
|
||||
});
|
||||
|
||||
click('groupmsg', function(event) {
|
||||
let groupName = getText('target');
|
||||
let message = getText('private-message-text');
|
||||
invoke(connection, 'SendToGroup', groupName, message);
|
||||
});
|
||||
click('broadcast', function (event) {
|
||||
let data = getText('message-text');
|
||||
invoke(connection, 'Send', data);
|
||||
});
|
||||
|
||||
click('send', function(event) {
|
||||
let data = getText('me-message-text');
|
||||
invoke(connection, 'Echo', data);
|
||||
});
|
||||
click('join-group', function (event) {
|
||||
let groupName = getText('message-text');
|
||||
invoke(connection, 'JoinGroup', groupName);
|
||||
});
|
||||
|
||||
click('leave-group', function (event) {
|
||||
let groupName = getText('message-text');
|
||||
invoke(connection, 'LeaveGroup', groupName);
|
||||
});
|
||||
|
||||
click('groupmsg', function (event) {
|
||||
let groupName = getText('target');
|
||||
let message = getText('private-message-text');
|
||||
invoke(connection, 'SendToGroup', groupName, message);
|
||||
});
|
||||
|
||||
click('send', function (event) {
|
||||
let data = getText('me-message-text');
|
||||
invoke(connection, 'Echo', data);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title></title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1 id="transportName">Unknown Transport</h1>
|
||||
|
||||
|
|
@ -14,43 +16,42 @@
|
|||
|
||||
<ul id="messages"></ul>
|
||||
<script type="text/javascript">
|
||||
if (typeof Promise === 'undefined')
|
||||
{
|
||||
if (typeof Promise === 'undefined') {
|
||||
document.write('<script type="text/javascript" src="lib/signalr-client/signalr-clientES5.js"><\/script>');
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
document.write('<script type="text/javascript" src="lib/signalr-client/signalr-client.js"><\/script>');
|
||||
}
|
||||
</script>
|
||||
<script src="utils.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
let transportType = signalR.TransportType[getParameterByName('transport')] || signalR.TransportType.WebSockets;
|
||||
|
||||
document.getElementById('transportName').innerHTML = signalR.TransportType[transportType];
|
||||
|
||||
let url = 'http://' + document.location.host + '/chat';
|
||||
let connection = new signalR.HttpConnection(url, { transport: transportType, logging: new signalR.ConsoleLogger(signalR.LogLevel.Information) });
|
||||
let connection = new signalR.HttpConnection(url, { transport: transportType, logging: new signalR.ConsoleLogger(signalR.LogLevel.Trace) });
|
||||
|
||||
connection.onreceive = function(data) {
|
||||
connection.onreceive = function (data) {
|
||||
let child = document.createElement('li');
|
||||
child.innerText = data;
|
||||
document.getElementById('messages').appendChild(child);
|
||||
};
|
||||
|
||||
document.getElementById('sendmessage').addEventListener('submit', function(event) {
|
||||
document.getElementById('sendmessage').addEventListener('submit', function (event) {
|
||||
let data = document.getElementById('data').value;
|
||||
connection.send(data);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
connection.start().then(function() {
|
||||
connection.start().then(function () {
|
||||
console.log("Opened");
|
||||
}, function() {
|
||||
}, function () {
|
||||
console.log("Error opening connection");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title></title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1 id="transportName">Unknown Transport</h1>
|
||||
|
||||
|
|
@ -24,14 +26,12 @@
|
|||
|
||||
<ul id="messages"></ul>
|
||||
<script type="text/javascript">
|
||||
if (typeof Promise === 'undefined')
|
||||
{
|
||||
if (typeof Promise === 'undefined') {
|
||||
document.write(
|
||||
'<script type="text/javascript" src="lib/signalr-client/signalr-clientES5.js"><\/script>' +
|
||||
'<script type="text/javascript" src="lib/signalr-client/signalr-msgpackprotocolES5.js"><\/script>');
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
document.write(
|
||||
'<script type="text/javascript" src="lib/signalr-client/signalr-client.js"><\/script>' +
|
||||
'<script type="text/javascript" src="lib/signalr-client/signalr-msgpackprotocol.js"><\/script>');
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
</script>
|
||||
<script src="utils.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
let resultsList = document.getElementById('resultsList');
|
||||
let channelButton = document.getElementById('channelButton');
|
||||
let observableButton = document.getElementById('observableButton');
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
let connectButton = document.getElementById('connectButton');
|
||||
let disconnectButton = document.getElementById('disconnectButton');
|
||||
|
||||
let logger = new signalR.ConsoleLogger(signalR.LogLevel.Information);
|
||||
let logger = new signalR.ConsoleLogger(signalR.LogLevel.Trace);
|
||||
let transportType = signalR.TransportType[getParameterByName('transport')] || signalR.TransportType.WebSockets;
|
||||
|
||||
let invocationCounter = 0;
|
||||
|
|
@ -107,11 +107,12 @@
|
|||
addLine('resultsList', method + '(' + id + '):' + err, 'red');
|
||||
},
|
||||
complete: function () {
|
||||
addLine('resultsList', method + '(' + id + '): complete', 'green');
|
||||
addLine('resultsList', method + '(' + id + '): complete', 'green');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Reference in New Issue