TypeScript strict mode (#2388)
This commit is contained in:
parent
8d680db112
commit
30a59f6df7
|
|
@ -350,7 +350,7 @@ describe("hubConnection", () => {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
hubConnection.onclose((error) => {
|
hubConnection.onclose((error) => {
|
||||||
expect(error.message).toEqual("Server returned an error on close: Connection closed with an error. InvalidOperationException: Unable to resolve service for type 'System.Object' while attempting to activate 'FunctionalTests.UncreatableHub'.");
|
expect(error!.message).toEqual("Server returned an error on close: Connection closed with an error. InvalidOperationException: Unable to resolve service for type 'System.Object' while attempting to activate 'FunctionalTests.UncreatableHub'.");
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
hubConnection.start();
|
hubConnection.start();
|
||||||
|
|
@ -632,7 +632,12 @@ describe("hubConnection", () => {
|
||||||
const defaultClient = new DefaultHttpClient(TestLogger.instance);
|
const defaultClient = new DefaultHttpClient(TestLogger.instance);
|
||||||
|
|
||||||
class TestClient extends HttpClient {
|
class TestClient extends HttpClient {
|
||||||
public pollPromise: Promise<HttpResponse>;
|
public pollPromise: Promise<HttpResponse> | null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.pollPromise = null;
|
||||||
|
}
|
||||||
|
|
||||||
public send(request: HttpRequest): Promise<HttpResponse> {
|
public send(request: HttpRequest): Promise<HttpResponse> {
|
||||||
if (request.method === "GET") {
|
if (request.method === "GET") {
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ class WebDriverReporter implements jasmine.CustomReporter {
|
||||||
|
|
||||||
// Just report the first failure
|
// Just report the first failure
|
||||||
this.taplog(" ---");
|
this.taplog(" ---");
|
||||||
if (result.failedExpectations.length > 0) {
|
if (result.failedExpectations && result.failedExpectations.length > 0) {
|
||||||
this.taplog(" - messages:");
|
this.taplog(" - messages:");
|
||||||
for (const expectation of result.failedExpectations) {
|
for (const expectation of result.failedExpectations) {
|
||||||
// Include YAML block with failed expectations
|
// Include YAML block with failed expectations
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,19 @@ export class MessagePackHubProtocol implements IHubProtocol {
|
||||||
if (logger === null) {
|
if (logger === null) {
|
||||||
logger = NullLogger.instance;
|
logger = NullLogger.instance;
|
||||||
}
|
}
|
||||||
return BinaryMessageFormat.parse(input).map((m) => this.parseMessage(m, logger));
|
|
||||||
|
const messages = BinaryMessageFormat.parse(input);
|
||||||
|
|
||||||
|
const hubMessages = [];
|
||||||
|
for (const message of messages) {
|
||||||
|
const parsedMessage = this.parseMessage(message, logger);
|
||||||
|
// Can be null for an unknown message. Unknown message is logged in parseMessage
|
||||||
|
if (parsedMessage) {
|
||||||
|
hubMessages.push(parsedMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hubMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Writes the specified HubMessage to an ArrayBuffer and returns it.
|
/** Writes the specified HubMessage to an ArrayBuffer and returns it.
|
||||||
|
|
@ -61,7 +73,7 @@ export class MessagePackHubProtocol implements IHubProtocol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseMessage(input: Uint8Array, logger: ILogger): HubMessage {
|
private parseMessage(input: Uint8Array, logger: ILogger): HubMessage | null {
|
||||||
if (input.length === 0) {
|
if (input.length === 0) {
|
||||||
throw new Error("Invalid payload.");
|
throw new Error("Invalid payload.");
|
||||||
}
|
}
|
||||||
|
|
@ -173,24 +185,27 @@ export class MessagePackHubProtocol implements IHubProtocol {
|
||||||
throw new Error("Invalid payload for Completion message.");
|
throw new Error("Invalid payload for Completion message.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const completionMessage = {
|
let error: string | undefined;
|
||||||
error: null as string,
|
let result: any;
|
||||||
headers,
|
|
||||||
invocationId: properties[2],
|
|
||||||
result: null as any,
|
|
||||||
type: MessageType.Completion,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (resultKind) {
|
switch (resultKind) {
|
||||||
case errorResult:
|
case errorResult:
|
||||||
completionMessage.error = properties[4];
|
error = properties[4];
|
||||||
break;
|
break;
|
||||||
case nonVoidResult:
|
case nonVoidResult:
|
||||||
completionMessage.result = properties[4];
|
result = properties[4];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return completionMessage as CompletionMessage;
|
const completionMessage: CompletionMessage = {
|
||||||
|
error,
|
||||||
|
headers,
|
||||||
|
invocationId: properties[2],
|
||||||
|
result,
|
||||||
|
type: MessageType.Completion,
|
||||||
|
};
|
||||||
|
|
||||||
|
return completionMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private writeInvocation(invocationMessage: InvocationMessage): ArrayBuffer {
|
private writeInvocation(invocationMessage: InvocationMessage): ArrayBuffer {
|
||||||
|
|
|
||||||
|
|
@ -66,12 +66,10 @@ describe("MessageHubProtocol", () => {
|
||||||
error: "Err",
|
error: "Err",
|
||||||
headers: {},
|
headers: {},
|
||||||
invocationId: "abc",
|
invocationId: "abc",
|
||||||
result: null,
|
|
||||||
type: MessageType.Completion,
|
type: MessageType.Completion,
|
||||||
} as CompletionMessage],
|
} as CompletionMessage],
|
||||||
[[0x0b, 0x95, 0x03, 0x80, 0xa3, 0x61, 0x62, 0x63, 0x03, 0xa2, 0x4f, 0x4b],
|
[[0x0b, 0x95, 0x03, 0x80, 0xa3, 0x61, 0x62, 0x63, 0x03, 0xa2, 0x4f, 0x4b],
|
||||||
{
|
{
|
||||||
error: null,
|
|
||||||
headers: {},
|
headers: {},
|
||||||
invocationId: "abc",
|
invocationId: "abc",
|
||||||
result: "OK",
|
result: "OK",
|
||||||
|
|
@ -79,15 +77,12 @@ describe("MessageHubProtocol", () => {
|
||||||
} as CompletionMessage],
|
} as CompletionMessage],
|
||||||
[[0x08, 0x94, 0x03, 0x80, 0xa3, 0x61, 0x62, 0x63, 0x02],
|
[[0x08, 0x94, 0x03, 0x80, 0xa3, 0x61, 0x62, 0x63, 0x02],
|
||||||
{
|
{
|
||||||
error: null,
|
|
||||||
headers: {},
|
headers: {},
|
||||||
invocationId: "abc",
|
invocationId: "abc",
|
||||||
result: null,
|
|
||||||
type: MessageType.Completion,
|
type: MessageType.Completion,
|
||||||
} as CompletionMessage],
|
} as CompletionMessage],
|
||||||
[[0x0E, 0x95, 0x03, 0x80, 0xa3, 0x61, 0x62, 0x63, 0x03, 0xD6, 0xFF, 0x5A, 0x4A, 0x1A, 0x50],
|
[[0x0E, 0x95, 0x03, 0x80, 0xa3, 0x61, 0x62, 0x63, 0x03, 0xD6, 0xFF, 0x5A, 0x4A, 0x1A, 0x50],
|
||||||
{
|
{
|
||||||
error: null,
|
|
||||||
headers: {},
|
headers: {},
|
||||||
invocationId: "abc",
|
invocationId: "abc",
|
||||||
result: new Date(Date.UTC(2018, 0, 1, 11, 24, 0)),
|
result: new Date(Date.UTC(2018, 0, 1, 11, 24, 0)),
|
||||||
|
|
@ -96,10 +91,8 @@ describe("MessageHubProtocol", () => {
|
||||||
// extra property at the end should be ignored (testing older protocol client working with newer protocol server)
|
// extra property at the end should be ignored (testing older protocol client working with newer protocol server)
|
||||||
[[0x09, 0x95, 0x03, 0x80, 0xa3, 0x61, 0x62, 0x63, 0x02, 0x00],
|
[[0x09, 0x95, 0x03, 0x80, 0xa3, 0x61, 0x62, 0x63, 0x02, 0x00],
|
||||||
{
|
{
|
||||||
error: null,
|
|
||||||
headers: {},
|
headers: {},
|
||||||
invocationId: "abc",
|
invocationId: "abc",
|
||||||
result: null,
|
|
||||||
type: MessageType.Completion,
|
type: MessageType.Completion,
|
||||||
} as CompletionMessage],
|
} as CompletionMessage],
|
||||||
] as Array<[number[], CompletionMessage]>).forEach(([payload, expectedMessage]) =>
|
] as Array<[number[], CompletionMessage]>).forEach(([payload, expectedMessage]) =>
|
||||||
|
|
@ -174,7 +167,6 @@ describe("MessageHubProtocol", () => {
|
||||||
type: MessageType.StreamItem,
|
type: MessageType.StreamItem,
|
||||||
} as StreamItemMessage,
|
} as StreamItemMessage,
|
||||||
{
|
{
|
||||||
error: null,
|
|
||||||
headers: {},
|
headers: {},
|
||||||
invocationId: "abc",
|
invocationId: "abc",
|
||||||
result: "OK",
|
result: "OK",
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
// Not exported from index.
|
// Not exported from index.
|
||||||
export class AbortController implements AbortSignal {
|
export class AbortController implements AbortSignal {
|
||||||
private isAborted: boolean = false;
|
private isAborted: boolean = false;
|
||||||
public onabort: () => void;
|
public onabort: (() => void) | null = null;
|
||||||
|
|
||||||
public abort() {
|
public abort() {
|
||||||
if (!this.isAborted) {
|
if (!this.isAborted) {
|
||||||
|
|
@ -33,5 +33,5 @@ export interface AbortSignal {
|
||||||
/** Indicates if the request has been aborted. */
|
/** Indicates if the request has been aborted. */
|
||||||
aborted: boolean;
|
aborted: boolean;
|
||||||
/** Set this to a handler that will be invoked when the request is aborted. */
|
/** Set this to a handler that will be invoked when the request is aborted. */
|
||||||
onabort: () => void;
|
onabort: (() => void) | null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -166,15 +166,27 @@ export class DefaultHttpClient extends HttpClient {
|
||||||
|
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
if (!request.method) {
|
||||||
|
reject(new Error("No method defined."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!request.url) {
|
||||||
|
reject(new Error("No url defined."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
xhr.open(request.method, request.url, true);
|
xhr.open(request.method, request.url, true);
|
||||||
xhr.withCredentials = true;
|
xhr.withCredentials = true;
|
||||||
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
||||||
// Explicitly setting the Content-Type header for React Native on Android platform.
|
// Explicitly setting the Content-Type header for React Native on Android platform.
|
||||||
xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
|
xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
|
||||||
|
|
||||||
if (request.headers) {
|
const headers = request.headers;
|
||||||
Object.keys(request.headers)
|
if (headers) {
|
||||||
.forEach((header) => xhr.setRequestHeader(header, request.headers[header]));
|
Object.keys(headers)
|
||||||
|
.forEach((header) => {
|
||||||
|
xhr.setRequestHeader(header, headers[header]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.responseType) {
|
if (request.responseType) {
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,14 @@ export class HttpConnection implements IConnection {
|
||||||
private readonly httpClient: HttpClient;
|
private readonly httpClient: HttpClient;
|
||||||
private readonly logger: ILogger;
|
private readonly logger: ILogger;
|
||||||
private readonly options: IHttpConnectionOptions;
|
private readonly options: IHttpConnectionOptions;
|
||||||
private transport: ITransport;
|
private transport?: ITransport;
|
||||||
private startPromise: Promise<void>;
|
private startPromise?: Promise<void>;
|
||||||
private stopError?: Error;
|
private stopError?: Error;
|
||||||
private accessTokenFactory?: () => string | Promise<string>;
|
private accessTokenFactory?: () => string | Promise<string>;
|
||||||
|
|
||||||
public readonly features: any = {};
|
public readonly features: any = {};
|
||||||
public onreceive: (data: string | ArrayBuffer) => void;
|
public onreceive: ((data: string | ArrayBuffer) => void) | null;
|
||||||
public onclose: (e?: Error) => void;
|
public onclose: ((e?: Error) => void) | null;
|
||||||
|
|
||||||
constructor(url: string, options: IHttpConnectionOptions = {}) {
|
constructor(url: string, options: IHttpConnectionOptions = {}) {
|
||||||
Arg.isRequired(url, "url");
|
Arg.isRequired(url, "url");
|
||||||
|
|
@ -53,12 +53,13 @@ export class HttpConnection implements IConnection {
|
||||||
this.baseUrl = this.resolveUrl(url);
|
this.baseUrl = this.resolveUrl(url);
|
||||||
|
|
||||||
options = options || {};
|
options = options || {};
|
||||||
options.accessTokenFactory = options.accessTokenFactory || (() => null);
|
|
||||||
options.logMessageContent = options.logMessageContent || false;
|
options.logMessageContent = options.logMessageContent || false;
|
||||||
|
|
||||||
this.httpClient = options.httpClient || new DefaultHttpClient(this.logger);
|
this.httpClient = options.httpClient || new DefaultHttpClient(this.logger);
|
||||||
this.connectionState = ConnectionState.Disconnected;
|
this.connectionState = ConnectionState.Disconnected;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
|
this.onreceive = null;
|
||||||
|
this.onclose = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public start(): Promise<void>;
|
public start(): Promise<void>;
|
||||||
|
|
@ -85,7 +86,8 @@ export class HttpConnection implements IConnection {
|
||||||
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);
|
// Transport will not be null if state is connected
|
||||||
|
return this.transport!.send(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async stop(error?: Error): Promise<void> {
|
public async stop(error?: Error): Promise<void> {
|
||||||
|
|
@ -101,7 +103,7 @@ export class HttpConnection implements IConnection {
|
||||||
if (this.transport) {
|
if (this.transport) {
|
||||||
this.stopError = error;
|
this.stopError = error;
|
||||||
await this.transport.stop();
|
await this.transport.stop();
|
||||||
this.transport = null;
|
this.transport = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,12 +120,12 @@ export class HttpConnection implements IConnection {
|
||||||
this.transport = this.constructTransport(HttpTransportType.WebSockets);
|
this.transport = this.constructTransport(HttpTransportType.WebSockets);
|
||||||
// We should just call connect directly in this case.
|
// We should just call connect directly in this case.
|
||||||
// No fallback or negotiate in this case.
|
// No fallback or negotiate in this case.
|
||||||
await this.transport.connect(url, transferFormat);
|
await this.transport!.connect(url, transferFormat);
|
||||||
} else {
|
} else {
|
||||||
throw Error("Negotiation can only be skipped when using the WebSocket transport directly.");
|
throw Error("Negotiation can only be skipped when using the WebSocket transport directly.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let negotiateResponse: INegotiateResponse = null;
|
let negotiateResponse: INegotiateResponse | null = null;
|
||||||
let redirects = 0;
|
let redirects = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
|
@ -159,8 +161,8 @@ export class HttpConnection implements IConnection {
|
||||||
this.features.inherentKeepAlive = true;
|
this.features.inherentKeepAlive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.transport.onreceive = this.onreceive;
|
this.transport!.onreceive = this.onreceive;
|
||||||
this.transport.onclose = (e) => this.stopConnection(e);
|
this.transport!.onclose = (e) => this.stopConnection(e);
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -168,18 +170,20 @@ export class HttpConnection implements IConnection {
|
||||||
} 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 = undefined;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getNegotiationResponse(url: string): Promise<INegotiateResponse> {
|
private async getNegotiationResponse(url: string): Promise<INegotiateResponse> {
|
||||||
const token = await this.accessTokenFactory();
|
|
||||||
let headers;
|
let headers;
|
||||||
if (token) {
|
if (this.accessTokenFactory) {
|
||||||
headers = {
|
const token = await this.accessTokenFactory();
|
||||||
["Authorization"]: `Bearer ${token}`,
|
if (token) {
|
||||||
};
|
headers = {
|
||||||
|
["Authorization"]: `Bearer ${token}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const negotiateUrl = this.resolveNegotiateUrl(url);
|
const negotiateUrl = this.resolveNegotiateUrl(url);
|
||||||
|
|
@ -201,11 +205,14 @@ export class HttpConnection implements IConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private createConnectUrl(url: string, connectionId: string) {
|
private createConnectUrl(url: string, connectionId: string | null | undefined) {
|
||||||
|
if (!connectionId) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
return url + (url.indexOf("?") === -1 ? "?" : "&") + `id=${connectionId}`;
|
return url + (url.indexOf("?") === -1 ? "?" : "&") + `id=${connectionId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createTransport(url: string, requestedTransport: HttpTransportType | ITransport, negotiateResponse: INegotiateResponse, requestedTransferFormat: TransferFormat): Promise<void> {
|
private async createTransport(url: string, requestedTransport: HttpTransportType | ITransport | undefined, negotiateResponse: INegotiateResponse, requestedTransferFormat: TransferFormat): Promise<void> {
|
||||||
let connectUrl = this.createConnectUrl(url, negotiateResponse.connectionId);
|
let connectUrl = this.createConnectUrl(url, negotiateResponse.connectionId);
|
||||||
if (this.isITransport(requestedTransport)) {
|
if (this.isITransport(requestedTransport)) {
|
||||||
this.logger.log(LogLevel.Debug, "Connection was provided an instance of ITransport, using that directly.");
|
this.logger.log(LogLevel.Debug, "Connection was provided an instance of ITransport, using that directly.");
|
||||||
|
|
@ -218,24 +225,24 @@ export class HttpConnection implements IConnection {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const transports = negotiateResponse.availableTransports;
|
const transports = negotiateResponse.availableTransports || [];
|
||||||
for (const endpoint of transports) {
|
for (const endpoint of transports) {
|
||||||
this.connectionState = ConnectionState.Connecting;
|
this.connectionState = ConnectionState.Connecting;
|
||||||
const transport = this.resolveTransport(endpoint, requestedTransport, requestedTransferFormat);
|
const transport = this.resolveTransport(endpoint, requestedTransport, requestedTransferFormat);
|
||||||
if (typeof transport === "number") {
|
if (typeof transport === "number") {
|
||||||
this.transport = this.constructTransport(transport);
|
this.transport = this.constructTransport(transport);
|
||||||
if (negotiateResponse.connectionId === null) {
|
if (!negotiateResponse.connectionId) {
|
||||||
negotiateResponse = await this.getNegotiationResponse(url);
|
negotiateResponse = await this.getNegotiationResponse(url);
|
||||||
connectUrl = this.createConnectUrl(url, negotiateResponse.connectionId);
|
connectUrl = this.createConnectUrl(url, negotiateResponse.connectionId);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await this.transport.connect(connectUrl, requestedTransferFormat);
|
await this.transport!.connect(connectUrl, requestedTransferFormat);
|
||||||
this.changeState(ConnectionState.Connecting, ConnectionState.Connected);
|
this.changeState(ConnectionState.Connecting, ConnectionState.Connected);
|
||||||
return;
|
return;
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
this.logger.log(LogLevel.Error, `Failed to start the transport '${HttpTransportType[transport]}': ${ex}`);
|
this.logger.log(LogLevel.Error, `Failed to start the transport '${HttpTransportType[transport]}': ${ex}`);
|
||||||
this.connectionState = ConnectionState.Disconnected;
|
this.connectionState = ConnectionState.Disconnected;
|
||||||
negotiateResponse.connectionId = null;
|
negotiateResponse.connectionId = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -246,17 +253,17 @@ export class HttpConnection implements IConnection {
|
||||||
private constructTransport(transport: HttpTransportType) {
|
private constructTransport(transport: HttpTransportType) {
|
||||||
switch (transport) {
|
switch (transport) {
|
||||||
case HttpTransportType.WebSockets:
|
case HttpTransportType.WebSockets:
|
||||||
return new WebSocketTransport(this.accessTokenFactory, this.logger, this.options.logMessageContent);
|
return new WebSocketTransport(this.accessTokenFactory, this.logger, this.options.logMessageContent || false);
|
||||||
case HttpTransportType.ServerSentEvents:
|
case HttpTransportType.ServerSentEvents:
|
||||||
return new ServerSentEventsTransport(this.httpClient, this.accessTokenFactory, this.logger, this.options.logMessageContent);
|
return new ServerSentEventsTransport(this.httpClient, this.accessTokenFactory, this.logger, this.options.logMessageContent || false);
|
||||||
case HttpTransportType.LongPolling:
|
case HttpTransportType.LongPolling:
|
||||||
return new LongPollingTransport(this.httpClient, this.accessTokenFactory, this.logger, this.options.logMessageContent);
|
return new LongPollingTransport(this.httpClient, this.accessTokenFactory, this.logger, this.options.logMessageContent || false);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown transport: ${transport}.`);
|
throw new Error(`Unknown transport: ${transport}.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private resolveTransport(endpoint: IAvailableTransport, requestedTransport: HttpTransportType, requestedTransferFormat: TransferFormat): HttpTransportType | null {
|
private resolveTransport(endpoint: IAvailableTransport, requestedTransport: HttpTransportType | undefined, requestedTransferFormat: TransferFormat): HttpTransportType | null {
|
||||||
const transport = HttpTransportType[endpoint.transport];
|
const transport = HttpTransportType[endpoint.transport];
|
||||||
if (transport === null || transport === undefined) {
|
if (transport === null || transport === undefined) {
|
||||||
this.logger.log(LogLevel.Debug, `Skipping transport '${endpoint.transport}' because it is not supported by this client.`);
|
this.logger.log(LogLevel.Debug, `Skipping transport '${endpoint.transport}' because it is not supported by this client.`);
|
||||||
|
|
@ -294,7 +301,7 @@ export class HttpConnection implements IConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async stopConnection(error?: Error): Promise<void> {
|
private async stopConnection(error?: Error): Promise<void> {
|
||||||
this.transport = null;
|
this.transport = undefined;
|
||||||
|
|
||||||
// If we have a stopError, it takes precedence over the error from the transport
|
// If we have a stopError, it takes precedence over the error from the transport
|
||||||
error = this.stopError || error;
|
error = this.stopError || error;
|
||||||
|
|
@ -346,6 +353,6 @@ export class HttpConnection implements IConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function transportMatches(requestedTransport: HttpTransportType, actualTransport: HttpTransportType) {
|
function transportMatches(requestedTransport: HttpTransportType | undefined, actualTransport: HttpTransportType) {
|
||||||
return !requestedTransport || ((actualTransport & requestedTransport) !== 0);
|
return !requestedTransport || ((actualTransport & requestedTransport) !== 0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,12 @@ export class HubConnection {
|
||||||
private readonly logger: ILogger;
|
private readonly logger: ILogger;
|
||||||
private protocol: IHubProtocol;
|
private protocol: IHubProtocol;
|
||||||
private handshakeProtocol: HandshakeProtocol;
|
private handshakeProtocol: HandshakeProtocol;
|
||||||
private callbacks: { [invocationId: string]: (invocationEvent: StreamItemMessage | CompletionMessage, error?: Error) => void };
|
private callbacks: { [invocationId: string]: (invocationEvent: StreamItemMessage | CompletionMessage | null, error?: Error) => void };
|
||||||
private methods: { [name: string]: Array<(...args: any[]) => void> };
|
private methods: { [name: string]: Array<(...args: any[]) => void> };
|
||||||
private id: number;
|
private id: number;
|
||||||
private closedCallbacks: Array<(error?: Error) => void>;
|
private closedCallbacks: Array<(error?: Error) => void>;
|
||||||
private timeoutHandle: NodeJS.Timer;
|
private timeoutHandle?: NodeJS.Timer;
|
||||||
private pingServerHandle: NodeJS.Timer;
|
private pingServerHandle?: NodeJS.Timer;
|
||||||
private receivedHandshakeResponse: boolean;
|
private receivedHandshakeResponse: boolean;
|
||||||
private connectionState: HubConnectionState;
|
private connectionState: HubConnectionState;
|
||||||
|
|
||||||
|
|
@ -79,6 +79,7 @@ export class HubConnection {
|
||||||
this.methods = {};
|
this.methods = {};
|
||||||
this.closedCallbacks = [];
|
this.closedCallbacks = [];
|
||||||
this.id = 0;
|
this.id = 0;
|
||||||
|
this.receivedHandshakeResponse = false;
|
||||||
this.connectionState = HubConnectionState.Disconnected;
|
this.connectionState = HubConnectionState.Disconnected;
|
||||||
|
|
||||||
this.cachedPingMessage = this.protocol.writeMessage({ type: MessageType.Ping });
|
this.cachedPingMessage = this.protocol.writeMessage({ type: MessageType.Ping });
|
||||||
|
|
@ -150,20 +151,21 @@ export class HubConnection {
|
||||||
return this.sendMessage(cancelMessage);
|
return this.sendMessage(cancelMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.callbacks[invocationDescriptor.invocationId] = (invocationEvent: CompletionMessage | StreamItemMessage, error?: Error) => {
|
this.callbacks[invocationDescriptor.invocationId] = (invocationEvent: CompletionMessage | StreamItemMessage | null, error?: Error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
subject.error(error);
|
subject.error(error);
|
||||||
return;
|
return;
|
||||||
}
|
} else if (invocationEvent) {
|
||||||
|
// invocationEvent will not be null when an error is not passed to the callback
|
||||||
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 {
|
||||||
|
subject.complete();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
subject.complete();
|
subject.next((invocationEvent.item) as T);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
subject.next((invocationEvent.item) as T);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -215,20 +217,22 @@ export class HubConnection {
|
||||||
const invocationDescriptor = this.createInvocation(methodName, args, false);
|
const invocationDescriptor = this.createInvocation(methodName, args, false);
|
||||||
|
|
||||||
const p = new Promise<any>((resolve, reject) => {
|
const p = new Promise<any>((resolve, reject) => {
|
||||||
this.callbacks[invocationDescriptor.invocationId] = (invocationEvent: StreamItemMessage | CompletionMessage, error?: Error) => {
|
// invocationId will always have a value for a non-blocking invocation
|
||||||
|
this.callbacks[invocationDescriptor.invocationId!] = (invocationEvent: StreamItemMessage | CompletionMessage | null, error?: Error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
return;
|
return;
|
||||||
}
|
} else if (invocationEvent) {
|
||||||
if (invocationEvent.type === MessageType.Completion) {
|
// invocationEvent will not be null when an error is not passed to the callback
|
||||||
const completionMessage = invocationEvent as CompletionMessage;
|
if (invocationEvent.type === MessageType.Completion) {
|
||||||
if (completionMessage.error) {
|
if (invocationEvent.error) {
|
||||||
reject(new Error(completionMessage.error));
|
reject(new Error(invocationEvent.error));
|
||||||
|
} else {
|
||||||
|
resolve(invocationEvent.result);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
resolve(completionMessage.result);
|
reject(new Error(`Unexpected message type: ${invocationEvent.type}`));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
reject(new Error(`Unexpected message type: ${invocationEvent.type}`));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -237,7 +241,8 @@ export class HubConnection {
|
||||||
this.sendMessage(message)
|
this.sendMessage(message)
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
reject(e);
|
reject(e);
|
||||||
delete this.callbacks[invocationDescriptor.invocationId];
|
// invocationId will always have a value for a non-blocking invocation
|
||||||
|
delete this.callbacks[invocationDescriptor.invocationId!];
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -349,7 +354,7 @@ export class HubConnection {
|
||||||
break;
|
break;
|
||||||
case MessageType.Close:
|
case MessageType.Close:
|
||||||
this.logger.log(LogLevel.Information, "Close message received from server.");
|
this.logger.log(LogLevel.Information, "Close message received from server.");
|
||||||
this.connection.stop(message.error ? new Error("Server returned an error on close: " + message.error) : null);
|
this.connection.stop(message.error ? new Error("Server returned an error on close: " + message.error) : undefined);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
this.logger.log(LogLevel.Warning, "Invalid message type: " + message.type);
|
this.logger.log(LogLevel.Warning, "Invalid message type: " + message.type);
|
||||||
|
|
@ -428,7 +433,7 @@ export class HubConnection {
|
||||||
Object.keys(callbacks)
|
Object.keys(callbacks)
|
||||||
.forEach((key) => {
|
.forEach((key) => {
|
||||||
const callback = callbacks[key];
|
const callback = callbacks[key];
|
||||||
callback(undefined, error ? error : new Error("Invocation canceled due to connection being closed."));
|
callback(null, error ? error : new Error("Invocation canceled due to connection being closed."));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.cleanupTimeout();
|
this.cleanupTimeout();
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,13 @@ import { Arg, ConsoleLogger } from "./Utils";
|
||||||
/** A builder for configuring {@link HubConnection} instances. */
|
/** A builder for configuring {@link HubConnection} instances. */
|
||||||
export class HubConnectionBuilder {
|
export class HubConnectionBuilder {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
public protocol: IHubProtocol;
|
public protocol?: IHubProtocol;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
public httpConnectionOptions: IHttpConnectionOptions;
|
public httpConnectionOptions?: IHttpConnectionOptions;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
public url: string;
|
public url?: string;
|
||||||
/** @internal */
|
/** @internal */
|
||||||
public logger: ILogger;
|
public logger?: ILogger;
|
||||||
|
|
||||||
/** Configures console logging for the {@link HubConnection}.
|
/** Configures console logging for the {@link HubConnection}.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,6 @@ export interface IConnection {
|
||||||
send(data: string | ArrayBuffer): Promise<void>;
|
send(data: string | ArrayBuffer): Promise<void>;
|
||||||
stop(error?: Error): Promise<void>;
|
stop(error?: Error): Promise<void>;
|
||||||
|
|
||||||
onreceive: (data: string | ArrayBuffer) => void;
|
onreceive: ((data: string | ArrayBuffer) => void) | null;
|
||||||
onclose: (error?: Error) => void;
|
onclose: ((error?: Error) => void) | null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,6 @@ export interface ITransport {
|
||||||
connect(url: string, transferFormat: TransferFormat): Promise<void>;
|
connect(url: string, transferFormat: TransferFormat): Promise<void>;
|
||||||
send(data: any): Promise<void>;
|
send(data: any): Promise<void>;
|
||||||
stop(): Promise<void>;
|
stop(): Promise<void>;
|
||||||
onreceive: (data: string | ArrayBuffer) => void;
|
onreceive: ((data: string | ArrayBuffer) => void) | null;
|
||||||
onclose: (error?: Error) => void;
|
onclose: ((error?: Error) => void) | null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,28 +11,36 @@ import { Arg, getDataDetail, sendMessage } from "./Utils";
|
||||||
// Not exported from 'index', this type is internal.
|
// Not exported from 'index', this type is internal.
|
||||||
export class LongPollingTransport implements ITransport {
|
export class LongPollingTransport implements ITransport {
|
||||||
private readonly httpClient: HttpClient;
|
private readonly httpClient: HttpClient;
|
||||||
private readonly accessTokenFactory: () => string | Promise<string>;
|
private readonly accessTokenFactory: (() => string | Promise<string>) | undefined;
|
||||||
private readonly logger: ILogger;
|
private readonly logger: ILogger;
|
||||||
private readonly logMessageContent: boolean;
|
private readonly logMessageContent: boolean;
|
||||||
|
private readonly pollAbort: AbortController;
|
||||||
|
|
||||||
private url: string;
|
private url?: string;
|
||||||
private pollXhr: XMLHttpRequest;
|
private pollXhr?: XMLHttpRequest;
|
||||||
private pollAbort: AbortController;
|
|
||||||
private running: boolean;
|
private running: boolean;
|
||||||
private receiving: Promise<void>;
|
private receiving?: Promise<void>;
|
||||||
private closeError: Error;
|
private closeError?: Error;
|
||||||
|
|
||||||
|
public onreceive: ((data: string | ArrayBuffer) => void) | null;
|
||||||
|
public onclose: ((error?: Error) => void) | null;
|
||||||
|
|
||||||
// This is an internal type, not exported from 'index' so this is really just internal.
|
// This is an internal type, not exported from 'index' so this is really just internal.
|
||||||
public get pollAborted() {
|
public get pollAborted() {
|
||||||
return this.pollAbort.aborted;
|
return this.pollAbort.aborted;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(httpClient: HttpClient, accessTokenFactory: () => string | Promise<string>, logger: ILogger, logMessageContent: boolean) {
|
constructor(httpClient: HttpClient, accessTokenFactory: (() => string | Promise<string>) | undefined, logger: ILogger, logMessageContent: boolean) {
|
||||||
this.httpClient = httpClient;
|
this.httpClient = httpClient;
|
||||||
this.accessTokenFactory = accessTokenFactory || (() => null);
|
this.accessTokenFactory = accessTokenFactory;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.pollAbort = new AbortController();
|
this.pollAbort = new AbortController();
|
||||||
this.logMessageContent = logMessageContent;
|
this.logMessageContent = logMessageContent;
|
||||||
|
|
||||||
|
this.running = false;
|
||||||
|
|
||||||
|
this.onreceive = null;
|
||||||
|
this.onclose = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async connect(url: string, transferFormat: TransferFormat): Promise<void> {
|
public async connect(url: string, transferFormat: TransferFormat): Promise<void> {
|
||||||
|
|
@ -59,7 +67,7 @@ export class LongPollingTransport implements ITransport {
|
||||||
pollOptions.responseType = "arraybuffer";
|
pollOptions.responseType = "arraybuffer";
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = await this.accessTokenFactory();
|
const token = await this.getAccessToken();
|
||||||
this.updateHeaderToken(pollOptions, token);
|
this.updateHeaderToken(pollOptions, token);
|
||||||
|
|
||||||
// Make initial long polling request
|
// Make initial long polling request
|
||||||
|
|
@ -71,7 +79,7 @@ export class LongPollingTransport implements ITransport {
|
||||||
this.logger.log(LogLevel.Error, `(LongPolling transport) Unexpected response code: ${response.statusCode}`);
|
this.logger.log(LogLevel.Error, `(LongPolling transport) Unexpected response code: ${response.statusCode}`);
|
||||||
|
|
||||||
// Mark running as false so that the poll immediately ends and runs the close logic
|
// Mark running as false so that the poll immediately ends and runs the close logic
|
||||||
this.closeError = new HttpError(response.statusText, response.statusCode);
|
this.closeError = new HttpError(response.statusText || "", response.statusCode);
|
||||||
this.running = false;
|
this.running = false;
|
||||||
} else {
|
} else {
|
||||||
this.running = true;
|
this.running = true;
|
||||||
|
|
@ -80,7 +88,18 @@ export class LongPollingTransport implements ITransport {
|
||||||
this.receiving = this.poll(this.url, pollOptions);
|
this.receiving = this.poll(this.url, pollOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateHeaderToken(request: HttpRequest, token: string) {
|
private async getAccessToken(): Promise<string | null> {
|
||||||
|
if (this.accessTokenFactory) {
|
||||||
|
return await this.accessTokenFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateHeaderToken(request: HttpRequest, token: string | null) {
|
||||||
|
if (!request.headers) {
|
||||||
|
request.headers = {};
|
||||||
|
}
|
||||||
if (token) {
|
if (token) {
|
||||||
// tslint:disable-next-line:no-string-literal
|
// tslint:disable-next-line:no-string-literal
|
||||||
request.headers["Authorization"] = `Bearer ${token}`;
|
request.headers["Authorization"] = `Bearer ${token}`;
|
||||||
|
|
@ -97,7 +116,7 @@ export class LongPollingTransport implements ITransport {
|
||||||
try {
|
try {
|
||||||
while (this.running) {
|
while (this.running) {
|
||||||
// We have to get the access token on each poll, in case it changes
|
// We have to get the access token on each poll, in case it changes
|
||||||
const token = await this.accessTokenFactory();
|
const token = await this.getAccessToken();
|
||||||
this.updateHeaderToken(pollOptions, token);
|
this.updateHeaderToken(pollOptions, token);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -113,7 +132,7 @@ export class LongPollingTransport implements ITransport {
|
||||||
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
|
||||||
this.closeError = new HttpError(response.statusText, response.statusCode);
|
this.closeError = new HttpError(response.statusText || "", response.statusCode);
|
||||||
this.running = false;
|
this.running = false;
|
||||||
} else {
|
} else {
|
||||||
// Process the response
|
// Process the response
|
||||||
|
|
@ -158,7 +177,7 @@ export class LongPollingTransport implements ITransport {
|
||||||
if (!this.running) {
|
if (!this.running) {
|
||||||
return Promise.reject(new Error("Cannot send until the transport is connected"));
|
return Promise.reject(new Error("Cannot send until the transport is connected"));
|
||||||
}
|
}
|
||||||
return sendMessage(this.logger, "LongPolling", this.httpClient, this.url, this.accessTokenFactory, data, this.logMessageContent);
|
return sendMessage(this.logger, "LongPolling", this.httpClient, this.url!, this.accessTokenFactory, data, this.logMessageContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async stop(): Promise<void> {
|
public async stop(): Promise<void> {
|
||||||
|
|
@ -177,9 +196,9 @@ export class LongPollingTransport implements ITransport {
|
||||||
const deleteOptions: HttpRequest = {
|
const deleteOptions: HttpRequest = {
|
||||||
headers: {},
|
headers: {},
|
||||||
};
|
};
|
||||||
const token = await this.accessTokenFactory();
|
const token = await this.getAccessToken();
|
||||||
this.updateHeaderToken(deleteOptions, token);
|
this.updateHeaderToken(deleteOptions, token);
|
||||||
await this.httpClient.delete(this.url, deleteOptions);
|
await this.httpClient.delete(this.url!, deleteOptions);
|
||||||
|
|
||||||
this.logger.log(LogLevel.Trace, "(LongPolling transport) DELETE request sent.");
|
this.logger.log(LogLevel.Trace, "(LongPolling transport) DELETE request sent.");
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -201,7 +220,4 @@ export class LongPollingTransport implements ITransport {
|
||||||
this.onclose(this.closeError);
|
this.onclose(this.closeError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onreceive: (data: string | ArrayBuffer) => void;
|
|
||||||
public onclose: (error?: Error) => void;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,23 @@ import { Arg, getDataDetail, sendMessage } from "./Utils";
|
||||||
|
|
||||||
export class ServerSentEventsTransport implements ITransport {
|
export class ServerSentEventsTransport implements ITransport {
|
||||||
private readonly httpClient: HttpClient;
|
private readonly httpClient: HttpClient;
|
||||||
private readonly accessTokenFactory: () => string | Promise<string>;
|
private readonly accessTokenFactory: (() => string | Promise<string>) | undefined;
|
||||||
private readonly logger: ILogger;
|
private readonly logger: ILogger;
|
||||||
private readonly logMessageContent: boolean;
|
private readonly logMessageContent: boolean;
|
||||||
private eventSource: EventSource;
|
private eventSource?: EventSource;
|
||||||
private url: string;
|
private url?: string;
|
||||||
|
|
||||||
constructor(httpClient: HttpClient, accessTokenFactory: () => string | Promise<string>, logger: ILogger, logMessageContent: boolean) {
|
public onreceive: ((data: string | ArrayBuffer) => void) | null;
|
||||||
|
public onclose: ((error?: Error) => void) | null;
|
||||||
|
|
||||||
|
constructor(httpClient: HttpClient, accessTokenFactory: (() => string | Promise<string>) | undefined, logger: ILogger, logMessageContent: boolean) {
|
||||||
this.httpClient = httpClient;
|
this.httpClient = httpClient;
|
||||||
this.accessTokenFactory = accessTokenFactory || (() => null);
|
this.accessTokenFactory = accessTokenFactory;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.logMessageContent = logMessageContent;
|
this.logMessageContent = logMessageContent;
|
||||||
|
|
||||||
|
this.onreceive = null;
|
||||||
|
this.onclose = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async connect(url: string, transferFormat: TransferFormat): Promise<void> {
|
public async connect(url: string, transferFormat: TransferFormat): Promise<void> {
|
||||||
|
|
@ -32,9 +38,11 @@ export class ServerSentEventsTransport implements ITransport {
|
||||||
|
|
||||||
this.logger.log(LogLevel.Trace, "(SSE transport) Connecting");
|
this.logger.log(LogLevel.Trace, "(SSE transport) Connecting");
|
||||||
|
|
||||||
const token = await this.accessTokenFactory();
|
if (this.accessTokenFactory) {
|
||||||
if (token) {
|
const token = await this.accessTokenFactory();
|
||||||
url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`;
|
if (token) {
|
||||||
|
url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
|
@ -86,7 +94,7 @@ export class ServerSentEventsTransport implements ITransport {
|
||||||
if (!this.eventSource) {
|
if (!this.eventSource) {
|
||||||
return Promise.reject(new Error("Cannot send until the transport is connected"));
|
return Promise.reject(new Error("Cannot send until the transport is connected"));
|
||||||
}
|
}
|
||||||
return sendMessage(this.logger, "SSE", this.httpClient, this.url, this.accessTokenFactory, data, this.logMessageContent);
|
return sendMessage(this.logger, "SSE", this.httpClient, this.url!, this.accessTokenFactory, data, this.logMessageContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public stop(): Promise<void> {
|
public stop(): Promise<void> {
|
||||||
|
|
@ -97,14 +105,11 @@ export class ServerSentEventsTransport implements ITransport {
|
||||||
private close(e?: Error) {
|
private close(e?: Error) {
|
||||||
if (this.eventSource) {
|
if (this.eventSource) {
|
||||||
this.eventSource.close();
|
this.eventSource.close();
|
||||||
this.eventSource = null;
|
this.eventSource = undefined;
|
||||||
|
|
||||||
if (this.onclose) {
|
if (this.onclose) {
|
||||||
this.onclose(e);
|
this.onclose(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public onreceive: (data: string | ArrayBuffer) => void;
|
|
||||||
public onclose: (error?: Error) => void;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,19 +22,19 @@ export class Arg {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDataDetail(data: any, includeContent: boolean): string {
|
export function getDataDetail(data: any, includeContent: boolean): string {
|
||||||
let length: string = null;
|
let detail = "";
|
||||||
if (data instanceof ArrayBuffer) {
|
if (data instanceof ArrayBuffer) {
|
||||||
length = `Binary data of length ${data.byteLength}`;
|
detail = `Binary data of length ${data.byteLength}`;
|
||||||
if (includeContent) {
|
if (includeContent) {
|
||||||
length += `. Content: '${formatArrayBuffer(data)}'`;
|
detail += `. Content: '${formatArrayBuffer(data)}'`;
|
||||||
}
|
}
|
||||||
} else if (typeof data === "string") {
|
} else if (typeof data === "string") {
|
||||||
length = `String data of length ${data.length}`;
|
detail = `String data of length ${data.length}`;
|
||||||
if (includeContent) {
|
if (includeContent) {
|
||||||
length += `. Content: '${data}'.`;
|
detail += `. Content: '${data}'.`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return length;
|
return detail;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatArrayBuffer(data: ArrayBuffer): string {
|
export function formatArrayBuffer(data: ArrayBuffer): string {
|
||||||
|
|
@ -51,13 +51,15 @@ export function formatArrayBuffer(data: ArrayBuffer): string {
|
||||||
return str.substr(0, str.length - 1);
|
return str.substr(0, str.length - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendMessage(logger: ILogger, transportName: string, httpClient: HttpClient, url: string, accessTokenFactory: () => string | Promise<string>, content: string | ArrayBuffer, logMessageContent: boolean): Promise<void> {
|
export async function sendMessage(logger: ILogger, transportName: string, httpClient: HttpClient, url: string, accessTokenFactory: (() => string | Promise<string>) | undefined, content: string | ArrayBuffer, logMessageContent: boolean): Promise<void> {
|
||||||
let headers;
|
let headers;
|
||||||
const token = await accessTokenFactory();
|
if (accessTokenFactory) {
|
||||||
if (token) {
|
const token = await accessTokenFactory();
|
||||||
headers = {
|
if (token) {
|
||||||
["Authorization"]: `Bearer ${token}`,
|
headers = {
|
||||||
};
|
["Authorization"]: `Bearer ${token}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log(LogLevel.Trace, `(${transportName} transport) sending data. ${getDataDetail(content, logMessageContent)}.`);
|
logger.log(LogLevel.Trace, `(${transportName} transport) sending data. ${getDataDetail(content, logMessageContent)}.`);
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,20 @@ import { Arg, getDataDetail } from "./Utils";
|
||||||
|
|
||||||
export class WebSocketTransport implements ITransport {
|
export class WebSocketTransport implements ITransport {
|
||||||
private readonly logger: ILogger;
|
private readonly logger: ILogger;
|
||||||
private readonly accessTokenFactory: () => string | Promise<string>;
|
private readonly accessTokenFactory: (() => string | Promise<string>) | undefined;
|
||||||
private readonly logMessageContent: boolean;
|
private readonly logMessageContent: boolean;
|
||||||
private webSocket: WebSocket;
|
private webSocket?: WebSocket;
|
||||||
|
|
||||||
constructor(accessTokenFactory: () => string | Promise<string>, logger: ILogger, logMessageContent: boolean) {
|
public onreceive: ((data: string | ArrayBuffer) => void) | null;
|
||||||
|
public onclose: ((error?: Error) => void) | null;
|
||||||
|
|
||||||
|
constructor(accessTokenFactory: (() => string | Promise<string>) | undefined, logger: ILogger, logMessageContent: boolean) {
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.accessTokenFactory = accessTokenFactory || (() => null);
|
this.accessTokenFactory = accessTokenFactory;
|
||||||
this.logMessageContent = logMessageContent;
|
this.logMessageContent = logMessageContent;
|
||||||
|
|
||||||
|
this.onreceive = null;
|
||||||
|
this.onclose = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async connect(url: string, transferFormat: TransferFormat): Promise<void> {
|
public async connect(url: string, transferFormat: TransferFormat): Promise<void> {
|
||||||
|
|
@ -28,9 +34,11 @@ export class WebSocketTransport implements ITransport {
|
||||||
|
|
||||||
this.logger.log(LogLevel.Trace, "(WebSockets transport) Connecting");
|
this.logger.log(LogLevel.Trace, "(WebSockets transport) Connecting");
|
||||||
|
|
||||||
const token = await this.accessTokenFactory();
|
if (this.accessTokenFactory) {
|
||||||
if (token) {
|
const token = await this.accessTokenFactory();
|
||||||
url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`;
|
if (token) {
|
||||||
|
url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
|
@ -46,8 +54,9 @@ export class WebSocketTransport implements ITransport {
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
webSocket.onerror = (event: ErrorEvent) => {
|
webSocket.onerror = (event: Event) => {
|
||||||
reject(event.error);
|
const error = (event instanceof ErrorEvent) ? event.error : null;
|
||||||
|
reject(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
webSocket.onmessage = (message: MessageEvent) => {
|
webSocket.onmessage = (message: MessageEvent) => {
|
||||||
|
|
@ -84,11 +93,8 @@ export class WebSocketTransport implements ITransport {
|
||||||
public stop(): Promise<void> {
|
public stop(): Promise<void> {
|
||||||
if (this.webSocket) {
|
if (this.webSocket) {
|
||||||
this.webSocket.close();
|
this.webSocket.close();
|
||||||
this.webSocket = null;
|
this.webSocket = undefined;
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onreceive: (data: string | ArrayBuffer) => void;
|
|
||||||
public onclose: (error?: Error) => void;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@ import { TestHttpClient } from "./TestHttpClient";
|
||||||
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;
|
||||||
const 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");
|
||||||
|
|
@ -18,9 +19,10 @@ describe("HttpClient", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("overrides method and url in options", async () => {
|
it("overrides method and url in options", async () => {
|
||||||
let request: HttpRequest;
|
let request!: HttpRequest;
|
||||||
const 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", {
|
||||||
|
|
@ -32,9 +34,10 @@ describe("HttpClient", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("copies other options", async () => {
|
it("copies other options", async () => {
|
||||||
let request: HttpRequest;
|
let request!: HttpRequest;
|
||||||
const 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", {
|
||||||
|
|
@ -46,9 +49,10 @@ describe("HttpClient", () => {
|
||||||
|
|
||||||
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;
|
||||||
const 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");
|
||||||
|
|
@ -57,9 +61,10 @@ describe("HttpClient", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("overrides method and url in options", async () => {
|
it("overrides method and url in options", async () => {
|
||||||
let request: HttpRequest;
|
let request!: HttpRequest;
|
||||||
const 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", {
|
||||||
|
|
@ -71,9 +76,10 @@ describe("HttpClient", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("copies other options", async () => {
|
it("copies other options", async () => {
|
||||||
let request: HttpRequest;
|
let request!: HttpRequest;
|
||||||
const 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", {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import { TestHttpClient } from "./TestHttpClient";
|
||||||
import { PromiseSource } from "./Utils";
|
import { PromiseSource } from "./Utils";
|
||||||
|
|
||||||
const commonOptions: IHttpConnectionOptions = {
|
const commonOptions: IHttpConnectionOptions = {
|
||||||
logger: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultConnectionId = "abc123";
|
const defaultConnectionId = "abc123";
|
||||||
|
|
@ -165,8 +164,8 @@ describe("HttpConnection", () => {
|
||||||
stop(): Promise<void> {
|
stop(): Promise<void> {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
},
|
||||||
onclose: undefined,
|
onclose: null,
|
||||||
onreceive: undefined,
|
onreceive: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const options: IHttpConnectionOptions = {
|
const options: IHttpConnectionOptions = {
|
||||||
|
|
@ -224,7 +223,7 @@ describe("HttpConnection", () => {
|
||||||
const negotiateResponse = { ...defaultNegotiateResponse };
|
const negotiateResponse = { ...defaultNegotiateResponse };
|
||||||
|
|
||||||
// Remove the requested transport from the response
|
// Remove the requested transport from the response
|
||||||
negotiateResponse.availableTransports = negotiateResponse.availableTransports
|
negotiateResponse.availableTransports = negotiateResponse.availableTransports!
|
||||||
.filter((f) => f.transport !== HttpTransportType[requestedTransport]);
|
.filter((f) => f.transport !== HttpTransportType[requestedTransport]);
|
||||||
|
|
||||||
const options: IHttpConnectionOptions = {
|
const options: IHttpConnectionOptions = {
|
||||||
|
|
@ -483,7 +482,7 @@ describe("HttpConnection", () => {
|
||||||
.on("GET", (r) => {
|
.on("GET", (r) => {
|
||||||
httpClientGetCount++;
|
httpClientGetCount++;
|
||||||
// tslint:disable-next-line:no-string-literal
|
// tslint:disable-next-line:no-string-literal
|
||||||
const authorizationValue = r.headers["Authorization"];
|
const authorizationValue = r.headers!["Authorization"];
|
||||||
if (httpClientGetCount === 1) {
|
if (httpClientGetCount === 1) {
|
||||||
if (authorizationValue) {
|
if (authorizationValue) {
|
||||||
fail("First long poll request should have a authorization header.");
|
fail("First long poll request should have a authorization header.");
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import { TextMessageFormat } from "../src/TextMessageFormat";
|
||||||
|
|
||||||
import { delay, PromiseSource } from "./Utils";
|
import { delay, PromiseSource } from "./Utils";
|
||||||
|
|
||||||
function createHubConnection(connection: IConnection, logger?: ILogger, protocol?: IHubProtocol) {
|
function createHubConnection(connection: IConnection, logger?: ILogger | null, protocol?: IHubProtocol | null) {
|
||||||
return HubConnection.create(connection, logger || NullLogger.instance, protocol || new JsonHubProtocol());
|
return HubConnection.create(connection, logger || NullLogger.instance, protocol || new JsonHubProtocol());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -181,7 +181,7 @@ describe("HubConnection", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can process handshake and additional messages from binary", async () => {
|
it("can process handshake and additional messages from binary", async () => {
|
||||||
let receivedProcotolData: ArrayBuffer;
|
let receivedProcotolData: ArrayBuffer | undefined;
|
||||||
|
|
||||||
const mockProtocol = new TestProtocol(TransferFormat.Binary);
|
const mockProtocol = new TestProtocol(TransferFormat.Binary);
|
||||||
mockProtocol.onreceive = (d) => receivedProcotolData = d as ArrayBuffer;
|
mockProtocol.onreceive = (d) => receivedProcotolData = d as ArrayBuffer;
|
||||||
|
|
@ -202,14 +202,14 @@ describe("HubConnection", () => {
|
||||||
connection.receiveBinary(new Uint8Array(data).buffer);
|
connection.receiveBinary(new Uint8Array(data).buffer);
|
||||||
|
|
||||||
// left over data is the message pack message
|
// left over data is the message pack message
|
||||||
expect(receivedProcotolData.byteLength).toEqual(102);
|
expect(receivedProcotolData!.byteLength).toEqual(102);
|
||||||
} finally {
|
} finally {
|
||||||
hubConnection.stop();
|
hubConnection.stop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can process handshake and additional messages from text", async () => {
|
it("can process handshake and additional messages from text", async () => {
|
||||||
let receivedProcotolData: string;
|
let receivedProcotolData: string | undefined;
|
||||||
|
|
||||||
const mockProtocol = new TestProtocol(TransferFormat.Text);
|
const mockProtocol = new TestProtocol(TransferFormat.Text);
|
||||||
mockProtocol.onreceive = (d) => receivedProcotolData = d as string;
|
mockProtocol.onreceive = (d) => receivedProcotolData = d as string;
|
||||||
|
|
@ -281,7 +281,7 @@ describe("HubConnection", () => {
|
||||||
|
|
||||||
const 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"));
|
||||||
|
|
||||||
expect(invokePromise).rejects.toThrow("Connection lost");
|
expect(invokePromise).rejects.toThrow("Connection lost");
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -474,12 +474,12 @@ describe("HubConnection", () => {
|
||||||
const connection = new TestConnection();
|
const connection = new TestConnection();
|
||||||
const hubConnection = createHubConnection(connection);
|
const hubConnection = createHubConnection(connection);
|
||||||
try {
|
try {
|
||||||
let closeError: Error = null;
|
let closeError: Error | undefined;
|
||||||
hubConnection.onclose((e) => closeError = e);
|
hubConnection.onclose((e) => closeError = e);
|
||||||
|
|
||||||
connection.receiveHandshakeResponse("Error!");
|
connection.receiveHandshakeResponse("Error!");
|
||||||
|
|
||||||
expect(closeError.message).toEqual("Server returned handshake error: Error!");
|
expect(closeError!.message).toEqual("Server returned handshake error: Error!");
|
||||||
} finally {
|
} finally {
|
||||||
hubConnection.stop();
|
hubConnection.stop();
|
||||||
}
|
}
|
||||||
|
|
@ -490,7 +490,7 @@ describe("HubConnection", () => {
|
||||||
const hubConnection = createHubConnection(connection);
|
const hubConnection = createHubConnection(connection);
|
||||||
try {
|
try {
|
||||||
let isClosed = false;
|
let isClosed = false;
|
||||||
let closeError: Error = null;
|
let closeError: Error | undefined;
|
||||||
hubConnection.onclose((e) => {
|
hubConnection.onclose((e) => {
|
||||||
isClosed = true;
|
isClosed = true;
|
||||||
closeError = e;
|
closeError = e;
|
||||||
|
|
@ -503,7 +503,7 @@ describe("HubConnection", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(isClosed).toEqual(true);
|
expect(isClosed).toEqual(true);
|
||||||
expect(closeError).toEqual(null);
|
expect(closeError).toBeUndefined();
|
||||||
} finally {
|
} finally {
|
||||||
hubConnection.stop();
|
hubConnection.stop();
|
||||||
}
|
}
|
||||||
|
|
@ -514,7 +514,7 @@ describe("HubConnection", () => {
|
||||||
const hubConnection = createHubConnection(connection);
|
const hubConnection = createHubConnection(connection);
|
||||||
try {
|
try {
|
||||||
let isClosed = false;
|
let isClosed = false;
|
||||||
let closeError: Error = null;
|
let closeError: Error | undefined;
|
||||||
hubConnection.onclose((e) => {
|
hubConnection.onclose((e) => {
|
||||||
isClosed = true;
|
isClosed = true;
|
||||||
closeError = e;
|
closeError = e;
|
||||||
|
|
@ -528,7 +528,7 @@ describe("HubConnection", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(isClosed).toEqual(true);
|
expect(isClosed).toEqual(true);
|
||||||
expect(closeError.message).toEqual("Server returned an error on close: Error!");
|
expect(closeError!.message).toEqual("Server returned an error on close: Error!");
|
||||||
} finally {
|
} finally {
|
||||||
hubConnection.stop();
|
hubConnection.stop();
|
||||||
}
|
}
|
||||||
|
|
@ -622,12 +622,12 @@ describe("HubConnection", () => {
|
||||||
try {
|
try {
|
||||||
connection.receiveHandshakeResponse();
|
connection.receiveHandshakeResponse();
|
||||||
|
|
||||||
hubConnection.on(null, undefined);
|
hubConnection.on(null!, undefined!);
|
||||||
hubConnection.on(undefined, null);
|
hubConnection.on(undefined!, null!);
|
||||||
hubConnection.on("message", null);
|
hubConnection.on("message", null!);
|
||||||
hubConnection.on("message", undefined);
|
hubConnection.on("message", undefined!);
|
||||||
hubConnection.on(null, () => { });
|
hubConnection.on(null!, () => { });
|
||||||
hubConnection.on(undefined, () => { });
|
hubConnection.on(undefined!, () => { });
|
||||||
|
|
||||||
// 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({
|
||||||
|
|
@ -640,12 +640,12 @@ describe("HubConnection", () => {
|
||||||
|
|
||||||
expect(warnings).toEqual(["No client method with the name 'message' found."]);
|
expect(warnings).toEqual(["No client method with the name 'message' found."]);
|
||||||
|
|
||||||
hubConnection.off(null, undefined);
|
hubConnection.off(null!, undefined!);
|
||||||
hubConnection.off(undefined, null);
|
hubConnection.off(undefined!, null!);
|
||||||
hubConnection.off("message", null);
|
hubConnection.off("message", null!);
|
||||||
hubConnection.off("message", undefined);
|
hubConnection.off("message", undefined!);
|
||||||
hubConnection.off(null, () => { });
|
hubConnection.off(null!, () => { });
|
||||||
hubConnection.off(undefined, () => { });
|
hubConnection.off(undefined!, () => { });
|
||||||
} finally {
|
} finally {
|
||||||
hubConnection.stop();
|
hubConnection.stop();
|
||||||
}
|
}
|
||||||
|
|
@ -741,7 +741,7 @@ describe("HubConnection", () => {
|
||||||
.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"));
|
||||||
|
|
||||||
expect(observer.completed).rejects.toThrow("Error: Connection lost");
|
expect(observer.completed).rejects.toThrow("Error: Connection lost");
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -784,7 +784,7 @@ describe("HubConnection", () => {
|
||||||
|
|
||||||
// Typically this would be called by the transport
|
// Typically this would be called by the transport
|
||||||
// triggers observer.error()
|
// triggers observer.error()
|
||||||
connection.onclose(new Error("Connection lost"));
|
connection.onclose!(new Error("Connection lost"));
|
||||||
} finally {
|
} finally {
|
||||||
hubConnection.stop();
|
hubConnection.stop();
|
||||||
}
|
}
|
||||||
|
|
@ -845,7 +845,7 @@ describe("HubConnection", () => {
|
||||||
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);
|
||||||
} finally {
|
} finally {
|
||||||
hubConnection.stop();
|
hubConnection.stop();
|
||||||
|
|
@ -856,12 +856,12 @@ describe("HubConnection", () => {
|
||||||
const connection = new TestConnection();
|
const connection = new TestConnection();
|
||||||
const hubConnection = createHubConnection(connection);
|
const hubConnection = createHubConnection(connection);
|
||||||
try {
|
try {
|
||||||
let error: Error;
|
let error: Error | undefined;
|
||||||
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."));
|
||||||
expect(error.message).toBe("Test error.");
|
expect(error!.message).toBe("Test error.");
|
||||||
} finally {
|
} finally {
|
||||||
hubConnection.stop();
|
hubConnection.stop();
|
||||||
}
|
}
|
||||||
|
|
@ -871,10 +871,10 @@ describe("HubConnection", () => {
|
||||||
const connection = new TestConnection();
|
const connection = new TestConnection();
|
||||||
const hubConnection = createHubConnection(connection);
|
const hubConnection = createHubConnection(connection);
|
||||||
try {
|
try {
|
||||||
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
|
||||||
connection.onclose();
|
connection.onclose!();
|
||||||
// expect no errors
|
// expect no errors
|
||||||
} finally {
|
} finally {
|
||||||
hubConnection.stop();
|
hubConnection.stop();
|
||||||
|
|
@ -885,10 +885,10 @@ describe("HubConnection", () => {
|
||||||
const connection = new TestConnection();
|
const connection = new TestConnection();
|
||||||
const hubConnection = createHubConnection(connection);
|
const hubConnection = createHubConnection(connection);
|
||||||
try {
|
try {
|
||||||
let state: HubConnectionState;
|
let state: HubConnectionState | undefined;
|
||||||
hubConnection.onclose((e) => state = hubConnection.state);
|
hubConnection.onclose((e) => state = hubConnection.state);
|
||||||
// Typically this would be called by the transport
|
// Typically this would be called by the transport
|
||||||
connection.onclose();
|
connection.onclose!();
|
||||||
|
|
||||||
expect(state).toBe(HubConnectionState.Disconnected);
|
expect(state).toBe(HubConnectionState.Disconnected);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -998,6 +998,18 @@ async function pingAndWait(connection: TestConnection): Promise<void> {
|
||||||
class TestConnection implements IConnection {
|
class TestConnection implements IConnection {
|
||||||
public readonly features: any = {};
|
public readonly features: any = {};
|
||||||
|
|
||||||
|
public onreceive: ((data: string | ArrayBuffer) => void) | null;
|
||||||
|
public onclose: ((error?: Error) => void) | null;
|
||||||
|
public sentData: any[];
|
||||||
|
public lastInvocationId: string | null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.onreceive = null;
|
||||||
|
this.onclose = null;
|
||||||
|
this.sentData = [];
|
||||||
|
this.lastInvocationId = null;
|
||||||
|
}
|
||||||
|
|
||||||
public start(): Promise<void> {
|
public start(): Promise<void> {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
@ -1029,21 +1041,22 @@ class TestConnection implements IConnection {
|
||||||
|
|
||||||
public receive(data: any): void {
|
public receive(data: any): void {
|
||||||
const payload = JSON.stringify(data);
|
const payload = JSON.stringify(data);
|
||||||
this.onreceive(TextMessageFormat.write(payload));
|
this.invokeOnReceive(TextMessageFormat.write(payload));
|
||||||
}
|
}
|
||||||
|
|
||||||
public receiveText(data: string) {
|
public receiveText(data: string) {
|
||||||
this.onreceive(data);
|
this.invokeOnReceive(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public receiveBinary(data: ArrayBuffer) {
|
public receiveBinary(data: ArrayBuffer) {
|
||||||
this.onreceive(data);
|
this.invokeOnReceive(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onreceive: (data: string | ArrayBuffer) => void;
|
private invokeOnReceive(data: string | ArrayBuffer) {
|
||||||
public onclose: (error?: Error) => void;
|
if (this.onreceive) {
|
||||||
public sentData: any[];
|
this.onreceive(data);
|
||||||
public lastInvocationId: string;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestProtocol implements IHubProtocol {
|
class TestProtocol implements IHubProtocol {
|
||||||
|
|
@ -1052,10 +1065,11 @@ class TestProtocol implements IHubProtocol {
|
||||||
|
|
||||||
public readonly transferFormat: TransferFormat;
|
public readonly transferFormat: TransferFormat;
|
||||||
|
|
||||||
public onreceive: (data: string | ArrayBuffer) => void;
|
public onreceive: ((data: string | ArrayBuffer) => void) | null;
|
||||||
|
|
||||||
constructor(transferFormat: TransferFormat) {
|
constructor(transferFormat: TransferFormat) {
|
||||||
this.transferFormat = transferFormat;
|
this.transferFormat = transferFormat;
|
||||||
|
this.onreceive = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public parseMessages(input: any): HubMessage[] {
|
public parseMessages(input: any): HubMessage[] {
|
||||||
|
|
@ -1072,17 +1086,17 @@ class TestProtocol implements IHubProtocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestObserver implements IStreamSubscriber<any> {
|
class TestObserver implements IStreamSubscriber<any> {
|
||||||
public readonly closed: boolean;
|
public readonly closed: boolean = false;
|
||||||
public itemsReceived: [any];
|
public itemsReceived: any[];
|
||||||
private itemsSource: PromiseSource<[any]>;
|
private itemsSource: PromiseSource<any[]>;
|
||||||
|
|
||||||
get completed(): Promise<[any]> {
|
get completed(): Promise<any[]> {
|
||||||
return this.itemsSource.promise;
|
return this.itemsSource.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.itemsReceived = [] as [any];
|
this.itemsReceived = [];
|
||||||
this.itemsSource = new PromiseSource<[any]>();
|
this.itemsSource = new PromiseSource<any[]>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public next(value: any) {
|
public next(value: any) {
|
||||||
|
|
|
||||||
|
|
@ -37,17 +37,17 @@ describe("HubConnectionBuilder", () => {
|
||||||
eachMissingValue((val, name) => {
|
eachMissingValue((val, name) => {
|
||||||
it(`configureLogging throws if logger is ${name}`, () => {
|
it(`configureLogging throws if logger is ${name}`, () => {
|
||||||
const builder = new HubConnectionBuilder();
|
const builder = new HubConnectionBuilder();
|
||||||
expect(() => builder.configureLogging(val)).toThrow("The 'logging' argument is required.");
|
expect(() => builder.configureLogging(val!)).toThrow("The 'logging' argument is required.");
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`withUrl throws if url is ${name}`, () => {
|
it(`withUrl throws if url is ${name}`, () => {
|
||||||
const builder = new HubConnectionBuilder();
|
const builder = new HubConnectionBuilder();
|
||||||
expect(() => builder.withUrl(val)).toThrow("The 'url' argument is required.");
|
expect(() => builder.withUrl(val!)).toThrow("The 'url' argument is required.");
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`withHubProtocol throws if protocol is ${name}`, () => {
|
it(`withHubProtocol throws if protocol is ${name}`, () => {
|
||||||
const builder = new HubConnectionBuilder();
|
const builder = new HubConnectionBuilder();
|
||||||
expect(() => builder.withHubProtocol(val)).toThrow("The 'protocol' argument is required.");
|
expect(() => builder.withHubProtocol(val!)).toThrow("The 'protocol' argument is required.");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -88,7 +88,7 @@ describe("HubConnectionBuilder", () => {
|
||||||
const builder = createConnectionBuilder()
|
const builder = createConnectionBuilder()
|
||||||
.withUrl("http://example.com", HttpTransportType.WebSockets)
|
.withUrl("http://example.com", HttpTransportType.WebSockets)
|
||||||
.withHubProtocol(protocol);
|
.withHubProtocol(protocol);
|
||||||
expect(builder.httpConnectionOptions.transport).toBe(HttpTransportType.WebSockets);
|
expect(builder.httpConnectionOptions!.transport).toBe(HttpTransportType.WebSockets);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can configure hub protocol", async () => {
|
it("can configure hub protocol", async () => {
|
||||||
|
|
|
||||||
|
|
@ -71,33 +71,29 @@ describe("JsonHubProtocol", () => {
|
||||||
result: null,
|
result: null,
|
||||||
type: MessageType.Completion,
|
type: MessageType.Completion,
|
||||||
} as CompletionMessage],
|
} as CompletionMessage],
|
||||||
[`{"type":3, "invocationId": "abc", "result": "OK", "error": null, "headers": {}}${TextMessageFormat.RecordSeparator}`,
|
[`{"type":3, "invocationId": "abc", "result": "OK", "headers": {}}${TextMessageFormat.RecordSeparator}`,
|
||||||
{
|
{
|
||||||
error: null,
|
|
||||||
headers: {},
|
headers: {},
|
||||||
invocationId: "abc",
|
invocationId: "abc",
|
||||||
result: "OK",
|
result: "OK",
|
||||||
type: MessageType.Completion,
|
type: MessageType.Completion,
|
||||||
} as CompletionMessage],
|
} as CompletionMessage],
|
||||||
[`{"type":3, "invocationId": "abc", "error": null, "result": null, "headers": {}}${TextMessageFormat.RecordSeparator}`,
|
[`{"type":3, "invocationId": "abc", "result": null, "headers": {}}${TextMessageFormat.RecordSeparator}`,
|
||||||
{
|
{
|
||||||
error: null,
|
|
||||||
headers: {},
|
headers: {},
|
||||||
invocationId: "abc",
|
invocationId: "abc",
|
||||||
result: null,
|
result: null,
|
||||||
type: MessageType.Completion,
|
type: MessageType.Completion,
|
||||||
} as CompletionMessage],
|
} as CompletionMessage],
|
||||||
[`{"type":3, "invocationId": "abc", "result": 1514805840000, "error": null, "headers": {}}${TextMessageFormat.RecordSeparator}`,
|
[`{"type":3, "invocationId": "abc", "result": 1514805840000, "headers": {}}${TextMessageFormat.RecordSeparator}`,
|
||||||
{
|
{
|
||||||
error: null,
|
|
||||||
headers: {},
|
headers: {},
|
||||||
invocationId: "abc",
|
invocationId: "abc",
|
||||||
result: Date.UTC(2018, 0, 1, 11, 24, 0),
|
result: Date.UTC(2018, 0, 1, 11, 24, 0),
|
||||||
type: MessageType.Completion,
|
type: MessageType.Completion,
|
||||||
} as CompletionMessage],
|
} as CompletionMessage],
|
||||||
[`{"type":3, "invocationId": "abc", "error": null, "result": null, "headers": {}, "extraParameter":"value"}${TextMessageFormat.RecordSeparator}`,
|
[`{"type":3, "invocationId": "abc", "result": null, "headers": {}, "extraParameter":"value"}${TextMessageFormat.RecordSeparator}`,
|
||||||
{
|
{
|
||||||
error: null,
|
|
||||||
extraParameter: "value",
|
extraParameter: "value",
|
||||||
headers: {},
|
headers: {},
|
||||||
invocationId: "abc",
|
invocationId: "abc",
|
||||||
|
|
@ -165,7 +161,7 @@ describe("JsonHubProtocol", () => {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it("can read multiple messages", () => {
|
it("can read multiple messages", () => {
|
||||||
const payload = `{"type":2, "invocationId": "abc", "headers": {}, "item": 8}${TextMessageFormat.RecordSeparator}{"type":3, "invocationId": "abc", "headers": {}, "result": "OK", "error": null}${TextMessageFormat.RecordSeparator}`;
|
const payload = `{"type":2, "invocationId": "abc", "headers": {}, "item": 8}${TextMessageFormat.RecordSeparator}{"type":3, "invocationId": "abc", "headers": {}, "result": "OK"}${TextMessageFormat.RecordSeparator}`;
|
||||||
const messages = new JsonHubProtocol().parseMessages(payload, NullLogger.instance);
|
const messages = new JsonHubProtocol().parseMessages(payload, NullLogger.instance);
|
||||||
expect(messages).toEqual([
|
expect(messages).toEqual([
|
||||||
{
|
{
|
||||||
|
|
@ -175,7 +171,6 @@ describe("JsonHubProtocol", () => {
|
||||||
type: MessageType.StreamItem,
|
type: MessageType.StreamItem,
|
||||||
} as StreamItemMessage,
|
} as StreamItemMessage,
|
||||||
{
|
{
|
||||||
error: null,
|
|
||||||
headers: {},
|
headers: {},
|
||||||
invocationId: "abc",
|
invocationId: "abc",
|
||||||
result: "OK",
|
result: "OK",
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ describe("LongPollingTransport", () => {
|
||||||
return new HttpResponse(200);
|
return new HttpResponse(200);
|
||||||
} else {
|
} else {
|
||||||
// Turn 'onabort' into a promise.
|
// Turn 'onabort' into a promise.
|
||||||
const abort = new Promise((resolve, reject) => r.abortSignal.onabort = resolve);
|
const abort = new Promise((resolve, reject) => r.abortSignal!.onabort = resolve);
|
||||||
await abort;
|
await abort;
|
||||||
|
|
||||||
// Signal that the poll has completed.
|
// Signal that the poll has completed.
|
||||||
|
|
@ -30,7 +30,7 @@ describe("LongPollingTransport", () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.on("DELETE", (r) => new HttpResponse(202));
|
.on("DELETE", (r) => new HttpResponse(202));
|
||||||
const transport = new LongPollingTransport(client, null, NullLogger.instance, false);
|
const transport = new LongPollingTransport(client, undefined, NullLogger.instance, false);
|
||||||
|
|
||||||
await transport.connect("http://example.com", TransferFormat.Text);
|
await transport.connect("http://example.com", TransferFormat.Text);
|
||||||
const stopPromise = transport.stop();
|
const stopPromise = transport.stop();
|
||||||
|
|
@ -53,7 +53,7 @@ describe("LongPollingTransport", () => {
|
||||||
return new HttpResponse(204);
|
return new HttpResponse(204);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const transport = new LongPollingTransport(client, null, NullLogger.instance, false);
|
const transport = new LongPollingTransport(client, undefined, NullLogger.instance, false);
|
||||||
|
|
||||||
const stopPromise = makeClosedPromise(transport);
|
const stopPromise = makeClosedPromise(transport);
|
||||||
|
|
||||||
|
|
@ -84,7 +84,7 @@ describe("LongPollingTransport", () => {
|
||||||
return new HttpResponse(202);
|
return new HttpResponse(202);
|
||||||
});
|
});
|
||||||
|
|
||||||
const transport = new LongPollingTransport(httpClient, null, NullLogger.instance, false);
|
const transport = new LongPollingTransport(httpClient, undefined, NullLogger.instance, false);
|
||||||
|
|
||||||
await transport.connect("http://tempuri.org", TransferFormat.Text);
|
await transport.connect("http://tempuri.org", TransferFormat.Text);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,8 @@ export class TestHttpClient extends HttpClient {
|
||||||
|
|
||||||
const oldHandler = this.handler;
|
const oldHandler = this.handler;
|
||||||
const 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!)) {
|
||||||
const promise = handler(request, oldHandler);
|
const promise = handler!(request, oldHandler);
|
||||||
|
|
||||||
let val: TestHttpHandlerResult;
|
let val: TestHttpHandlerResult;
|
||||||
if (promise instanceof Promise) {
|
if (promise instanceof Promise) {
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ export function delay(durationInMilliseconds: number): Promise<void> {
|
||||||
export class PromiseSource<T = void> implements Promise<T> {
|
export class PromiseSource<T = void> implements Promise<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;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.promise = new Promise<T>((resolve, reject) => {
|
this.promise = new Promise<T>((resolve, reject) => {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
"suppressImplicitAnyIndexErrors": true,
|
"suppressImplicitAnyIndexErrors": true,
|
||||||
"noEmitOnError": true,
|
"noEmitOnError": true,
|
||||||
"stripInternal": true,
|
"stripInternal": true,
|
||||||
|
"strict": true,
|
||||||
"lib": [ "es5", "es2015.promise", "es2015.iterable", "dom" ],
|
"lib": [ "es5", "es2015.promise", "es2015.iterable", "dom" ],
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue