diff --git a/clients/ts/FunctionalTests/ts/HubConnectionTests.ts b/clients/ts/FunctionalTests/ts/HubConnectionTests.ts index 01ebafd9ad..b3d1af3e9e 100644 --- a/clients/ts/FunctionalTests/ts/HubConnectionTests.ts +++ b/clients/ts/FunctionalTests/ts/HubConnectionTests.ts @@ -350,7 +350,7 @@ describe("hubConnection", () => { .build(); 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(); }); hubConnection.start(); @@ -632,7 +632,12 @@ describe("hubConnection", () => { const defaultClient = new DefaultHttpClient(TestLogger.instance); class TestClient extends HttpClient { - public pollPromise: Promise; + public pollPromise: Promise | null; + + constructor() { + super(); + this.pollPromise = null; + } public send(request: HttpRequest): Promise { if (request.method === "GET") { diff --git a/clients/ts/FunctionalTests/ts/WebDriverReporter.ts b/clients/ts/FunctionalTests/ts/WebDriverReporter.ts index a768150521..9f889d3ff9 100644 --- a/clients/ts/FunctionalTests/ts/WebDriverReporter.ts +++ b/clients/ts/FunctionalTests/ts/WebDriverReporter.ts @@ -56,7 +56,7 @@ class WebDriverReporter implements jasmine.CustomReporter { // Just report the first failure this.taplog(" ---"); - if (result.failedExpectations.length > 0) { + if (result.failedExpectations && result.failedExpectations.length > 0) { this.taplog(" - messages:"); for (const expectation of result.failedExpectations) { // Include YAML block with failed expectations diff --git a/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts b/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts index 198855f9cc..eca2b163f5 100644 --- a/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts +++ b/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts @@ -37,7 +37,19 @@ export class MessagePackHubProtocol implements IHubProtocol { if (logger === null) { 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. @@ -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) { throw new Error("Invalid payload."); } @@ -173,24 +185,27 @@ export class MessagePackHubProtocol implements IHubProtocol { throw new Error("Invalid payload for Completion message."); } - const completionMessage = { - error: null as string, - headers, - invocationId: properties[2], - result: null as any, - type: MessageType.Completion, - }; + let error: string | undefined; + let result: any; switch (resultKind) { case errorResult: - completionMessage.error = properties[4]; + error = properties[4]; break; case nonVoidResult: - completionMessage.result = properties[4]; + result = properties[4]; break; } - return completionMessage as CompletionMessage; + const completionMessage: CompletionMessage = { + error, + headers, + invocationId: properties[2], + result, + type: MessageType.Completion, + }; + + return completionMessage; } private writeInvocation(invocationMessage: InvocationMessage): ArrayBuffer { diff --git a/clients/ts/signalr-protocol-msgpack/tests/MessagePackHubProtocol.test.ts b/clients/ts/signalr-protocol-msgpack/tests/MessagePackHubProtocol.test.ts index 3142e69bad..8f70a7c796 100644 --- a/clients/ts/signalr-protocol-msgpack/tests/MessagePackHubProtocol.test.ts +++ b/clients/ts/signalr-protocol-msgpack/tests/MessagePackHubProtocol.test.ts @@ -66,12 +66,10 @@ describe("MessageHubProtocol", () => { error: "Err", headers: {}, invocationId: "abc", - result: null, type: MessageType.Completion, } as CompletionMessage], [[0x0b, 0x95, 0x03, 0x80, 0xa3, 0x61, 0x62, 0x63, 0x03, 0xa2, 0x4f, 0x4b], { - error: null, headers: {}, invocationId: "abc", result: "OK", @@ -79,15 +77,12 @@ describe("MessageHubProtocol", () => { } as CompletionMessage], [[0x08, 0x94, 0x03, 0x80, 0xa3, 0x61, 0x62, 0x63, 0x02], { - error: null, headers: {}, invocationId: "abc", - result: null, type: MessageType.Completion, } as CompletionMessage], [[0x0E, 0x95, 0x03, 0x80, 0xa3, 0x61, 0x62, 0x63, 0x03, 0xD6, 0xFF, 0x5A, 0x4A, 0x1A, 0x50], { - error: null, headers: {}, invocationId: "abc", 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) [[0x09, 0x95, 0x03, 0x80, 0xa3, 0x61, 0x62, 0x63, 0x02, 0x00], { - error: null, headers: {}, invocationId: "abc", - result: null, type: MessageType.Completion, } as CompletionMessage], ] as Array<[number[], CompletionMessage]>).forEach(([payload, expectedMessage]) => @@ -174,7 +167,6 @@ describe("MessageHubProtocol", () => { type: MessageType.StreamItem, } as StreamItemMessage, { - error: null, headers: {}, invocationId: "abc", result: "OK", diff --git a/clients/ts/signalr/src/AbortController.ts b/clients/ts/signalr/src/AbortController.ts index a5b9232f8a..aa5c366eea 100644 --- a/clients/ts/signalr/src/AbortController.ts +++ b/clients/ts/signalr/src/AbortController.ts @@ -8,7 +8,7 @@ // Not exported from index. export class AbortController implements AbortSignal { private isAborted: boolean = false; - public onabort: () => void; + public onabort: (() => void) | null = null; public abort() { if (!this.isAborted) { @@ -33,5 +33,5 @@ export interface AbortSignal { /** Indicates if the request has been aborted. */ aborted: boolean; /** Set this to a handler that will be invoked when the request is aborted. */ - onabort: () => void; + onabort: (() => void) | null; } diff --git a/clients/ts/signalr/src/HttpClient.ts b/clients/ts/signalr/src/HttpClient.ts index eaaaa5fdac..2b6c68bce7 100644 --- a/clients/ts/signalr/src/HttpClient.ts +++ b/clients/ts/signalr/src/HttpClient.ts @@ -166,15 +166,27 @@ export class DefaultHttpClient extends HttpClient { 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.withCredentials = true; xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest"); // Explicitly setting the Content-Type header for React Native on Android platform. xhr.setRequestHeader("Content-Type", "text/plain;charset=UTF-8"); - if (request.headers) { - Object.keys(request.headers) - .forEach((header) => xhr.setRequestHeader(header, request.headers[header])); + const headers = request.headers; + if (headers) { + Object.keys(headers) + .forEach((header) => { + xhr.setRequestHeader(header, headers[header]); + }); } if (request.responseType) { diff --git a/clients/ts/signalr/src/HttpConnection.ts b/clients/ts/signalr/src/HttpConnection.ts index 5b337a3aa8..6f56c5f841 100644 --- a/clients/ts/signalr/src/HttpConnection.ts +++ b/clients/ts/signalr/src/HttpConnection.ts @@ -37,14 +37,14 @@ export class HttpConnection implements IConnection { private readonly httpClient: HttpClient; private readonly logger: ILogger; private readonly options: IHttpConnectionOptions; - private transport: ITransport; - private startPromise: Promise; + private transport?: ITransport; + private startPromise?: Promise; private stopError?: Error; private accessTokenFactory?: () => string | Promise; public readonly features: any = {}; - public onreceive: (data: string | ArrayBuffer) => void; - public onclose: (e?: Error) => void; + public onreceive: ((data: string | ArrayBuffer) => void) | null; + public onclose: ((e?: Error) => void) | null; constructor(url: string, options: IHttpConnectionOptions = {}) { Arg.isRequired(url, "url"); @@ -53,12 +53,13 @@ export class HttpConnection implements IConnection { this.baseUrl = this.resolveUrl(url); options = options || {}; - options.accessTokenFactory = options.accessTokenFactory || (() => null); options.logMessageContent = options.logMessageContent || false; this.httpClient = options.httpClient || new DefaultHttpClient(this.logger); this.connectionState = ConnectionState.Disconnected; this.options = options; + this.onreceive = null; + this.onclose = null; } public start(): Promise; @@ -85,7 +86,8 @@ export class HttpConnection implements IConnection { 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 { @@ -101,7 +103,7 @@ export class HttpConnection implements IConnection { if (this.transport) { this.stopError = error; 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); // We should just call connect directly in this case. // No fallback or negotiate in this case. - await this.transport.connect(url, transferFormat); + await this.transport!.connect(url, transferFormat); } else { throw Error("Negotiation can only be skipped when using the WebSocket transport directly."); } } else { - let negotiateResponse: INegotiateResponse = null; + let negotiateResponse: INegotiateResponse | null = null; let redirects = 0; do { @@ -159,8 +161,8 @@ export class HttpConnection implements IConnection { this.features.inherentKeepAlive = true; } - this.transport.onreceive = this.onreceive; - this.transport.onclose = (e) => this.stopConnection(e); + this.transport!.onreceive = this.onreceive; + this.transport!.onclose = (e) => this.stopConnection(e); // only change the state if we were connecting to not overwrite // the state if the connection is already marked as Disconnected @@ -168,18 +170,20 @@ export class HttpConnection implements IConnection { } catch (e) { this.logger.log(LogLevel.Error, "Failed to start the connection: " + e); this.connectionState = ConnectionState.Disconnected; - this.transport = null; + this.transport = undefined; throw e; } } private async getNegotiationResponse(url: string): Promise { - const token = await this.accessTokenFactory(); let headers; - if (token) { - headers = { - ["Authorization"]: `Bearer ${token}`, - }; + if (this.accessTokenFactory) { + const token = await this.accessTokenFactory(); + if (token) { + headers = { + ["Authorization"]: `Bearer ${token}`, + }; + } } 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}`; } - private async createTransport(url: string, requestedTransport: HttpTransportType | ITransport, negotiateResponse: INegotiateResponse, requestedTransferFormat: TransferFormat): Promise { + private async createTransport(url: string, requestedTransport: HttpTransportType | ITransport | undefined, negotiateResponse: INegotiateResponse, requestedTransferFormat: TransferFormat): Promise { let connectUrl = this.createConnectUrl(url, negotiateResponse.connectionId); if (this.isITransport(requestedTransport)) { 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; } - const transports = negotiateResponse.availableTransports; + const transports = negotiateResponse.availableTransports || []; for (const endpoint of transports) { this.connectionState = ConnectionState.Connecting; const transport = this.resolveTransport(endpoint, requestedTransport, requestedTransferFormat); if (typeof transport === "number") { this.transport = this.constructTransport(transport); - if (negotiateResponse.connectionId === null) { + if (!negotiateResponse.connectionId) { negotiateResponse = await this.getNegotiationResponse(url); connectUrl = this.createConnectUrl(url, negotiateResponse.connectionId); } try { - await this.transport.connect(connectUrl, requestedTransferFormat); + await this.transport!.connect(connectUrl, requestedTransferFormat); this.changeState(ConnectionState.Connecting, ConnectionState.Connected); return; } catch (ex) { this.logger.log(LogLevel.Error, `Failed to start the transport '${HttpTransportType[transport]}': ${ex}`); this.connectionState = ConnectionState.Disconnected; - negotiateResponse.connectionId = null; + negotiateResponse.connectionId = undefined; } } } @@ -246,17 +253,17 @@ export class HttpConnection implements IConnection { private constructTransport(transport: HttpTransportType) { switch (transport) { 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: - 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: - 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: 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]; if (transport === null || transport === undefined) { 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 { - this.transport = null; + this.transport = undefined; // If we have a stopError, it takes precedence over the error from the transport 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); } diff --git a/clients/ts/signalr/src/HubConnection.ts b/clients/ts/signalr/src/HubConnection.ts index 0af04f33d2..d91556e375 100644 --- a/clients/ts/signalr/src/HubConnection.ts +++ b/clients/ts/signalr/src/HubConnection.ts @@ -27,12 +27,12 @@ export class HubConnection { private readonly logger: ILogger; private protocol: IHubProtocol; 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 id: number; private closedCallbacks: Array<(error?: Error) => void>; - private timeoutHandle: NodeJS.Timer; - private pingServerHandle: NodeJS.Timer; + private timeoutHandle?: NodeJS.Timer; + private pingServerHandle?: NodeJS.Timer; private receivedHandshakeResponse: boolean; private connectionState: HubConnectionState; @@ -79,6 +79,7 @@ export class HubConnection { this.methods = {}; this.closedCallbacks = []; this.id = 0; + this.receivedHandshakeResponse = false; this.connectionState = HubConnectionState.Disconnected; this.cachedPingMessage = this.protocol.writeMessage({ type: MessageType.Ping }); @@ -150,20 +151,21 @@ export class HubConnection { 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) { subject.error(error); return; - } - - if (invocationEvent.type === MessageType.Completion) { - if (invocationEvent.error) { - subject.error(new Error(invocationEvent.error)); + } else if (invocationEvent) { + // invocationEvent will not be null when an error is not passed to the callback + if (invocationEvent.type === MessageType.Completion) { + if (invocationEvent.error) { + subject.error(new Error(invocationEvent.error)); + } else { + subject.complete(); + } } 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 p = new Promise((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) { reject(error); return; - } - if (invocationEvent.type === MessageType.Completion) { - const completionMessage = invocationEvent as CompletionMessage; - if (completionMessage.error) { - reject(new Error(completionMessage.error)); + } else if (invocationEvent) { + // invocationEvent will not be null when an error is not passed to the callback + if (invocationEvent.type === MessageType.Completion) { + if (invocationEvent.error) { + reject(new Error(invocationEvent.error)); + } else { + resolve(invocationEvent.result); + } } 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) .catch((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; case MessageType.Close: 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; default: this.logger.log(LogLevel.Warning, "Invalid message type: " + message.type); @@ -428,7 +433,7 @@ export class HubConnection { Object.keys(callbacks) .forEach((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(); diff --git a/clients/ts/signalr/src/HubConnectionBuilder.ts b/clients/ts/signalr/src/HubConnectionBuilder.ts index 2990f38714..4ac5057b2e 100644 --- a/clients/ts/signalr/src/HubConnectionBuilder.ts +++ b/clients/ts/signalr/src/HubConnectionBuilder.ts @@ -14,13 +14,13 @@ import { Arg, ConsoleLogger } from "./Utils"; /** A builder for configuring {@link HubConnection} instances. */ export class HubConnectionBuilder { /** @internal */ - public protocol: IHubProtocol; + public protocol?: IHubProtocol; /** @internal */ - public httpConnectionOptions: IHttpConnectionOptions; + public httpConnectionOptions?: IHttpConnectionOptions; /** @internal */ - public url: string; + public url?: string; /** @internal */ - public logger: ILogger; + public logger?: ILogger; /** Configures console logging for the {@link HubConnection}. * diff --git a/clients/ts/signalr/src/IConnection.ts b/clients/ts/signalr/src/IConnection.ts index 0b418f1471..b97dc81ddb 100644 --- a/clients/ts/signalr/src/IConnection.ts +++ b/clients/ts/signalr/src/IConnection.ts @@ -10,6 +10,6 @@ export interface IConnection { send(data: string | ArrayBuffer): Promise; stop(error?: Error): Promise; - onreceive: (data: string | ArrayBuffer) => void; - onclose: (error?: Error) => void; + onreceive: ((data: string | ArrayBuffer) => void) | null; + onclose: ((error?: Error) => void) | null; } diff --git a/clients/ts/signalr/src/ITransport.ts b/clients/ts/signalr/src/ITransport.ts index 936bcb420a..f83b692df4 100644 --- a/clients/ts/signalr/src/ITransport.ts +++ b/clients/ts/signalr/src/ITransport.ts @@ -27,6 +27,6 @@ export interface ITransport { connect(url: string, transferFormat: TransferFormat): Promise; send(data: any): Promise; stop(): Promise; - onreceive: (data: string | ArrayBuffer) => void; - onclose: (error?: Error) => void; + onreceive: ((data: string | ArrayBuffer) => void) | null; + onclose: ((error?: Error) => void) | null; } diff --git a/clients/ts/signalr/src/LongPollingTransport.ts b/clients/ts/signalr/src/LongPollingTransport.ts index 83dd402905..6ec083214e 100644 --- a/clients/ts/signalr/src/LongPollingTransport.ts +++ b/clients/ts/signalr/src/LongPollingTransport.ts @@ -11,28 +11,36 @@ import { Arg, getDataDetail, sendMessage } from "./Utils"; // Not exported from 'index', this type is internal. export class LongPollingTransport implements ITransport { private readonly httpClient: HttpClient; - private readonly accessTokenFactory: () => string | Promise; + private readonly accessTokenFactory: (() => string | Promise) | undefined; private readonly logger: ILogger; private readonly logMessageContent: boolean; + private readonly pollAbort: AbortController; - private url: string; - private pollXhr: XMLHttpRequest; - private pollAbort: AbortController; + private url?: string; + private pollXhr?: XMLHttpRequest; private running: boolean; - private receiving: Promise; - private closeError: Error; + private receiving?: Promise; + 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. public get pollAborted() { return this.pollAbort.aborted; } - constructor(httpClient: HttpClient, accessTokenFactory: () => string | Promise, logger: ILogger, logMessageContent: boolean) { + constructor(httpClient: HttpClient, accessTokenFactory: (() => string | Promise) | undefined, logger: ILogger, logMessageContent: boolean) { this.httpClient = httpClient; - this.accessTokenFactory = accessTokenFactory || (() => null); + this.accessTokenFactory = accessTokenFactory; this.logger = logger; this.pollAbort = new AbortController(); this.logMessageContent = logMessageContent; + + this.running = false; + + this.onreceive = null; + this.onclose = null; } public async connect(url: string, transferFormat: TransferFormat): Promise { @@ -59,7 +67,7 @@ export class LongPollingTransport implements ITransport { pollOptions.responseType = "arraybuffer"; } - const token = await this.accessTokenFactory(); + const token = await this.getAccessToken(); this.updateHeaderToken(pollOptions, token); // 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}`); // 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; } else { this.running = true; @@ -80,7 +88,18 @@ export class LongPollingTransport implements ITransport { this.receiving = this.poll(this.url, pollOptions); } - private updateHeaderToken(request: HttpRequest, token: string) { + private async getAccessToken(): Promise { + if (this.accessTokenFactory) { + return await this.accessTokenFactory(); + } + + return null; + } + + private updateHeaderToken(request: HttpRequest, token: string | null) { + if (!request.headers) { + request.headers = {}; + } if (token) { // tslint:disable-next-line:no-string-literal request.headers["Authorization"] = `Bearer ${token}`; @@ -97,7 +116,7 @@ export class LongPollingTransport implements ITransport { try { while (this.running) { // 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); try { @@ -113,7 +132,7 @@ export class LongPollingTransport implements ITransport { this.logger.log(LogLevel.Error, `(LongPolling transport) Unexpected response code: ${response.statusCode}`); // Unexpected status code - this.closeError = new HttpError(response.statusText, response.statusCode); + this.closeError = new HttpError(response.statusText || "", response.statusCode); this.running = false; } else { // Process the response @@ -158,7 +177,7 @@ export class LongPollingTransport implements ITransport { if (!this.running) { 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 { @@ -177,9 +196,9 @@ export class LongPollingTransport implements ITransport { const deleteOptions: HttpRequest = { headers: {}, }; - const token = await this.accessTokenFactory(); + const token = await this.getAccessToken(); 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."); } finally { @@ -201,7 +220,4 @@ export class LongPollingTransport implements ITransport { this.onclose(this.closeError); } } - - public onreceive: (data: string | ArrayBuffer) => void; - public onclose: (error?: Error) => void; } diff --git a/clients/ts/signalr/src/ServerSentEventsTransport.ts b/clients/ts/signalr/src/ServerSentEventsTransport.ts index dff90c3469..3ae7ed7e51 100644 --- a/clients/ts/signalr/src/ServerSentEventsTransport.ts +++ b/clients/ts/signalr/src/ServerSentEventsTransport.ts @@ -8,17 +8,23 @@ import { Arg, getDataDetail, sendMessage } from "./Utils"; export class ServerSentEventsTransport implements ITransport { private readonly httpClient: HttpClient; - private readonly accessTokenFactory: () => string | Promise; + private readonly accessTokenFactory: (() => string | Promise) | undefined; private readonly logger: ILogger; private readonly logMessageContent: boolean; - private eventSource: EventSource; - private url: string; + private eventSource?: EventSource; + private url?: string; - constructor(httpClient: HttpClient, accessTokenFactory: () => string | Promise, logger: ILogger, logMessageContent: boolean) { + public onreceive: ((data: string | ArrayBuffer) => void) | null; + public onclose: ((error?: Error) => void) | null; + + constructor(httpClient: HttpClient, accessTokenFactory: (() => string | Promise) | undefined, logger: ILogger, logMessageContent: boolean) { this.httpClient = httpClient; - this.accessTokenFactory = accessTokenFactory || (() => null); + this.accessTokenFactory = accessTokenFactory; this.logger = logger; this.logMessageContent = logMessageContent; + + this.onreceive = null; + this.onclose = null; } public async connect(url: string, transferFormat: TransferFormat): Promise { @@ -32,9 +38,11 @@ export class ServerSentEventsTransport implements ITransport { this.logger.log(LogLevel.Trace, "(SSE transport) Connecting"); - const token = await this.accessTokenFactory(); - if (token) { - url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`; + if (this.accessTokenFactory) { + const token = await this.accessTokenFactory(); + if (token) { + url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`; + } } this.url = url; @@ -86,7 +94,7 @@ export class ServerSentEventsTransport implements ITransport { if (!this.eventSource) { 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 { @@ -97,14 +105,11 @@ export class ServerSentEventsTransport implements ITransport { private close(e?: Error) { if (this.eventSource) { this.eventSource.close(); - this.eventSource = null; + this.eventSource = undefined; if (this.onclose) { this.onclose(e); } } } - - public onreceive: (data: string | ArrayBuffer) => void; - public onclose: (error?: Error) => void; } diff --git a/clients/ts/signalr/src/Utils.ts b/clients/ts/signalr/src/Utils.ts index f259857d19..6e3c6db20b 100644 --- a/clients/ts/signalr/src/Utils.ts +++ b/clients/ts/signalr/src/Utils.ts @@ -22,19 +22,19 @@ export class Arg { } export function getDataDetail(data: any, includeContent: boolean): string { - let length: string = null; + let detail = ""; if (data instanceof ArrayBuffer) { - length = `Binary data of length ${data.byteLength}`; + detail = `Binary data of length ${data.byteLength}`; if (includeContent) { - length += `. Content: '${formatArrayBuffer(data)}'`; + detail += `. Content: '${formatArrayBuffer(data)}'`; } } else if (typeof data === "string") { - length = `String data of length ${data.length}`; + detail = `String data of length ${data.length}`; if (includeContent) { - length += `. Content: '${data}'.`; + detail += `. Content: '${data}'.`; } } - return length; + return detail; } export function formatArrayBuffer(data: ArrayBuffer): string { @@ -51,13 +51,15 @@ export function formatArrayBuffer(data: ArrayBuffer): string { return str.substr(0, str.length - 1); } -export async function sendMessage(logger: ILogger, transportName: string, httpClient: HttpClient, url: string, accessTokenFactory: () => string | Promise, content: string | ArrayBuffer, logMessageContent: boolean): Promise { +export async function sendMessage(logger: ILogger, transportName: string, httpClient: HttpClient, url: string, accessTokenFactory: (() => string | Promise) | undefined, content: string | ArrayBuffer, logMessageContent: boolean): Promise { let headers; - const token = await accessTokenFactory(); - if (token) { - headers = { - ["Authorization"]: `Bearer ${token}`, - }; + if (accessTokenFactory) { + const token = await accessTokenFactory(); + if (token) { + headers = { + ["Authorization"]: `Bearer ${token}`, + }; + } } logger.log(LogLevel.Trace, `(${transportName} transport) sending data. ${getDataDetail(content, logMessageContent)}.`); diff --git a/clients/ts/signalr/src/WebSocketTransport.ts b/clients/ts/signalr/src/WebSocketTransport.ts index 6476b1f173..58719d1d15 100644 --- a/clients/ts/signalr/src/WebSocketTransport.ts +++ b/clients/ts/signalr/src/WebSocketTransport.ts @@ -7,14 +7,20 @@ import { Arg, getDataDetail } from "./Utils"; export class WebSocketTransport implements ITransport { private readonly logger: ILogger; - private readonly accessTokenFactory: () => string | Promise; + private readonly accessTokenFactory: (() => string | Promise) | undefined; private readonly logMessageContent: boolean; - private webSocket: WebSocket; + private webSocket?: WebSocket; - constructor(accessTokenFactory: () => string | Promise, logger: ILogger, logMessageContent: boolean) { + public onreceive: ((data: string | ArrayBuffer) => void) | null; + public onclose: ((error?: Error) => void) | null; + + constructor(accessTokenFactory: (() => string | Promise) | undefined, logger: ILogger, logMessageContent: boolean) { this.logger = logger; - this.accessTokenFactory = accessTokenFactory || (() => null); + this.accessTokenFactory = accessTokenFactory; this.logMessageContent = logMessageContent; + + this.onreceive = null; + this.onclose = null; } public async connect(url: string, transferFormat: TransferFormat): Promise { @@ -28,9 +34,11 @@ export class WebSocketTransport implements ITransport { this.logger.log(LogLevel.Trace, "(WebSockets transport) Connecting"); - const token = await this.accessTokenFactory(); - if (token) { - url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`; + if (this.accessTokenFactory) { + const token = await this.accessTokenFactory(); + if (token) { + url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(token)}`; + } } return new Promise((resolve, reject) => { @@ -46,8 +54,9 @@ export class WebSocketTransport implements ITransport { resolve(); }; - webSocket.onerror = (event: ErrorEvent) => { - reject(event.error); + webSocket.onerror = (event: Event) => { + const error = (event instanceof ErrorEvent) ? event.error : null; + reject(error); }; webSocket.onmessage = (message: MessageEvent) => { @@ -84,11 +93,8 @@ export class WebSocketTransport implements ITransport { public stop(): Promise { if (this.webSocket) { this.webSocket.close(); - this.webSocket = null; + this.webSocket = undefined; } return Promise.resolve(); } - - public onreceive: (data: string | ArrayBuffer) => void; - public onclose: (error?: Error) => void; } diff --git a/clients/ts/signalr/tests/HttpClient.test.ts b/clients/ts/signalr/tests/HttpClient.test.ts index 780da82cca..79248c02d3 100644 --- a/clients/ts/signalr/tests/HttpClient.test.ts +++ b/clients/ts/signalr/tests/HttpClient.test.ts @@ -7,9 +7,10 @@ import { TestHttpClient } from "./TestHttpClient"; describe("HttpClient", () => { describe("get", () => { it("sets the method and URL appropriately", async () => { - let request: HttpRequest; + let request!: HttpRequest; const testClient = new TestHttpClient().on((r) => { - request = r; return ""; + request = r; + return ""; }); await testClient.get("http://localhost"); @@ -18,9 +19,10 @@ describe("HttpClient", () => { }); it("overrides method and url in options", async () => { - let request: HttpRequest; + let request!: HttpRequest; const testClient = new TestHttpClient().on((r) => { - request = r; return ""; + request = r; + return ""; }); await testClient.get("http://localhost", { @@ -32,9 +34,10 @@ describe("HttpClient", () => { }); it("copies other options", async () => { - let request: HttpRequest; + let request!: HttpRequest; const testClient = new TestHttpClient().on((r) => { - request = r; return ""; + request = r; + return ""; }); await testClient.get("http://localhost", { @@ -46,9 +49,10 @@ describe("HttpClient", () => { describe("post", () => { it("sets the method and URL appropriately", async () => { - let request: HttpRequest; + let request!: HttpRequest; const testClient = new TestHttpClient().on((r) => { - request = r; return ""; + request = r; + return ""; }); await testClient.post("http://localhost"); @@ -57,9 +61,10 @@ describe("HttpClient", () => { }); it("overrides method and url in options", async () => { - let request: HttpRequest; + let request!: HttpRequest; const testClient = new TestHttpClient().on((r) => { - request = r; return ""; + request = r; + return ""; }); await testClient.post("http://localhost", { @@ -71,9 +76,10 @@ describe("HttpClient", () => { }); it("copies other options", async () => { - let request: HttpRequest; + let request!: HttpRequest; const testClient = new TestHttpClient().on((r) => { - request = r; return ""; + request = r; + return ""; }); await testClient.post("http://localhost", { diff --git a/clients/ts/signalr/tests/HttpConnection.test.ts b/clients/ts/signalr/tests/HttpConnection.test.ts index fe190aab53..9d59b966cc 100644 --- a/clients/ts/signalr/tests/HttpConnection.test.ts +++ b/clients/ts/signalr/tests/HttpConnection.test.ts @@ -13,7 +13,6 @@ import { TestHttpClient } from "./TestHttpClient"; import { PromiseSource } from "./Utils"; const commonOptions: IHttpConnectionOptions = { - logger: null, }; const defaultConnectionId = "abc123"; @@ -165,8 +164,8 @@ describe("HttpConnection", () => { stop(): Promise { return Promise.resolve(); }, - onclose: undefined, - onreceive: undefined, + onclose: null, + onreceive: null, }; const options: IHttpConnectionOptions = { @@ -224,7 +223,7 @@ describe("HttpConnection", () => { const negotiateResponse = { ...defaultNegotiateResponse }; // Remove the requested transport from the response - negotiateResponse.availableTransports = negotiateResponse.availableTransports + negotiateResponse.availableTransports = negotiateResponse.availableTransports! .filter((f) => f.transport !== HttpTransportType[requestedTransport]); const options: IHttpConnectionOptions = { @@ -483,7 +482,7 @@ describe("HttpConnection", () => { .on("GET", (r) => { httpClientGetCount++; // tslint:disable-next-line:no-string-literal - const authorizationValue = r.headers["Authorization"]; + const authorizationValue = r.headers!["Authorization"]; if (httpClientGetCount === 1) { if (authorizationValue) { fail("First long poll request should have a authorization header."); diff --git a/clients/ts/signalr/tests/HubConnection.test.ts b/clients/ts/signalr/tests/HubConnection.test.ts index e09d9acad6..a33aee6586 100644 --- a/clients/ts/signalr/tests/HubConnection.test.ts +++ b/clients/ts/signalr/tests/HubConnection.test.ts @@ -13,7 +13,7 @@ import { TextMessageFormat } from "../src/TextMessageFormat"; 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()); } @@ -181,7 +181,7 @@ describe("HubConnection", () => { }); it("can process handshake and additional messages from binary", async () => { - let receivedProcotolData: ArrayBuffer; + let receivedProcotolData: ArrayBuffer | undefined; const mockProtocol = new TestProtocol(TransferFormat.Binary); mockProtocol.onreceive = (d) => receivedProcotolData = d as ArrayBuffer; @@ -202,14 +202,14 @@ describe("HubConnection", () => { connection.receiveBinary(new Uint8Array(data).buffer); // left over data is the message pack message - expect(receivedProcotolData.byteLength).toEqual(102); + expect(receivedProcotolData!.byteLength).toEqual(102); } finally { hubConnection.stop(); } }); it("can process handshake and additional messages from text", async () => { - let receivedProcotolData: string; + let receivedProcotolData: string | undefined; const mockProtocol = new TestProtocol(TransferFormat.Text); mockProtocol.onreceive = (d) => receivedProcotolData = d as string; @@ -281,7 +281,7 @@ describe("HubConnection", () => { const invokePromise = hubConnection.invoke("testMethod"); // 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"); } finally { @@ -474,12 +474,12 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection); try { - let closeError: Error = null; + let closeError: Error | undefined; hubConnection.onclose((e) => closeError = e); connection.receiveHandshakeResponse("Error!"); - expect(closeError.message).toEqual("Server returned handshake error: Error!"); + expect(closeError!.message).toEqual("Server returned handshake error: Error!"); } finally { hubConnection.stop(); } @@ -490,7 +490,7 @@ describe("HubConnection", () => { const hubConnection = createHubConnection(connection); try { let isClosed = false; - let closeError: Error = null; + let closeError: Error | undefined; hubConnection.onclose((e) => { isClosed = true; closeError = e; @@ -503,7 +503,7 @@ describe("HubConnection", () => { }); expect(isClosed).toEqual(true); - expect(closeError).toEqual(null); + expect(closeError).toBeUndefined(); } finally { hubConnection.stop(); } @@ -514,7 +514,7 @@ describe("HubConnection", () => { const hubConnection = createHubConnection(connection); try { let isClosed = false; - let closeError: Error = null; + let closeError: Error | undefined; hubConnection.onclose((e) => { isClosed = true; closeError = e; @@ -528,7 +528,7 @@ describe("HubConnection", () => { }); 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 { hubConnection.stop(); } @@ -622,12 +622,12 @@ describe("HubConnection", () => { try { connection.receiveHandshakeResponse(); - hubConnection.on(null, undefined); - hubConnection.on(undefined, null); - hubConnection.on("message", null); - hubConnection.on("message", undefined); - hubConnection.on(null, () => { }); - hubConnection.on(undefined, () => { }); + hubConnection.on(null!, undefined!); + hubConnection.on(undefined!, null!); + hubConnection.on("message", null!); + hubConnection.on("message", undefined!); + hubConnection.on(null!, () => { }); + hubConnection.on(undefined!, () => { }); // invoke a method to make sure we are not trying to use null/undefined connection.receive({ @@ -640,12 +640,12 @@ describe("HubConnection", () => { expect(warnings).toEqual(["No client method with the name 'message' found."]); - hubConnection.off(null, undefined); - hubConnection.off(undefined, null); - hubConnection.off("message", null); - hubConnection.off("message", undefined); - hubConnection.off(null, () => { }); - hubConnection.off(undefined, () => { }); + hubConnection.off(null!, undefined!); + hubConnection.off(undefined!, null!); + hubConnection.off("message", null!); + hubConnection.off("message", undefined!); + hubConnection.off(null!, () => { }); + hubConnection.off(undefined!, () => { }); } finally { hubConnection.stop(); } @@ -741,7 +741,7 @@ describe("HubConnection", () => { .subscribe(observer); // 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"); } finally { @@ -784,7 +784,7 @@ describe("HubConnection", () => { // Typically this would be called by the transport // triggers observer.error() - connection.onclose(new Error("Connection lost")); + connection.onclose!(new Error("Connection lost")); } finally { hubConnection.stop(); } @@ -845,7 +845,7 @@ describe("HubConnection", () => { hubConnection.onclose((e) => invocations++); hubConnection.onclose((e) => invocations++); // Typically this would be called by the transport - connection.onclose(); + connection.onclose!(); expect(invocations).toBe(2); } finally { hubConnection.stop(); @@ -856,12 +856,12 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection); try { - let error: Error; + let error: Error | undefined; hubConnection.onclose((e) => error = e); // Typically this would be called by the transport - connection.onclose(new Error("Test error.")); - expect(error.message).toBe("Test error."); + connection.onclose!(new Error("Test error.")); + expect(error!.message).toBe("Test error."); } finally { hubConnection.stop(); } @@ -871,10 +871,10 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection); try { - hubConnection.onclose(null); - hubConnection.onclose(undefined); + hubConnection.onclose(null!); + hubConnection.onclose(undefined!); // Typically this would be called by the transport - connection.onclose(); + connection.onclose!(); // expect no errors } finally { hubConnection.stop(); @@ -885,10 +885,10 @@ describe("HubConnection", () => { const connection = new TestConnection(); const hubConnection = createHubConnection(connection); try { - let state: HubConnectionState; + let state: HubConnectionState | undefined; hubConnection.onclose((e) => state = hubConnection.state); // Typically this would be called by the transport - connection.onclose(); + connection.onclose!(); expect(state).toBe(HubConnectionState.Disconnected); } finally { @@ -998,6 +998,18 @@ async function pingAndWait(connection: TestConnection): Promise { class TestConnection implements IConnection { 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 { return Promise.resolve(); } @@ -1029,21 +1041,22 @@ class TestConnection implements IConnection { public receive(data: any): void { const payload = JSON.stringify(data); - this.onreceive(TextMessageFormat.write(payload)); + this.invokeOnReceive(TextMessageFormat.write(payload)); } public receiveText(data: string) { - this.onreceive(data); + this.invokeOnReceive(data); } public receiveBinary(data: ArrayBuffer) { - this.onreceive(data); + this.invokeOnReceive(data); } - public onreceive: (data: string | ArrayBuffer) => void; - public onclose: (error?: Error) => void; - public sentData: any[]; - public lastInvocationId: string; + private invokeOnReceive(data: string | ArrayBuffer) { + if (this.onreceive) { + this.onreceive(data); + } + } } class TestProtocol implements IHubProtocol { @@ -1052,10 +1065,11 @@ class TestProtocol implements IHubProtocol { public readonly transferFormat: TransferFormat; - public onreceive: (data: string | ArrayBuffer) => void; + public onreceive: ((data: string | ArrayBuffer) => void) | null; constructor(transferFormat: TransferFormat) { this.transferFormat = transferFormat; + this.onreceive = null; } public parseMessages(input: any): HubMessage[] { @@ -1072,17 +1086,17 @@ class TestProtocol implements IHubProtocol { } class TestObserver implements IStreamSubscriber { - public readonly closed: boolean; - public itemsReceived: [any]; - private itemsSource: PromiseSource<[any]>; + public readonly closed: boolean = false; + public itemsReceived: any[]; + private itemsSource: PromiseSource; - get completed(): Promise<[any]> { + get completed(): Promise { return this.itemsSource.promise; } constructor() { - this.itemsReceived = [] as [any]; - this.itemsSource = new PromiseSource<[any]>(); + this.itemsReceived = []; + this.itemsSource = new PromiseSource(); } public next(value: any) { diff --git a/clients/ts/signalr/tests/HubConnectionBuilder.test.ts b/clients/ts/signalr/tests/HubConnectionBuilder.test.ts index 5ecb43618e..cb29ac4e3e 100644 --- a/clients/ts/signalr/tests/HubConnectionBuilder.test.ts +++ b/clients/ts/signalr/tests/HubConnectionBuilder.test.ts @@ -37,17 +37,17 @@ describe("HubConnectionBuilder", () => { eachMissingValue((val, name) => { it(`configureLogging throws if logger is ${name}`, () => { 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}`, () => { 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}`, () => { 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() .withUrl("http://example.com", HttpTransportType.WebSockets) .withHubProtocol(protocol); - expect(builder.httpConnectionOptions.transport).toBe(HttpTransportType.WebSockets); + expect(builder.httpConnectionOptions!.transport).toBe(HttpTransportType.WebSockets); }); it("can configure hub protocol", async () => { diff --git a/clients/ts/signalr/tests/JsonHubProtocol.test.ts b/clients/ts/signalr/tests/JsonHubProtocol.test.ts index e25779e386..61d9868905 100644 --- a/clients/ts/signalr/tests/JsonHubProtocol.test.ts +++ b/clients/ts/signalr/tests/JsonHubProtocol.test.ts @@ -71,33 +71,29 @@ describe("JsonHubProtocol", () => { result: null, type: MessageType.Completion, } 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: {}, invocationId: "abc", result: "OK", type: MessageType.Completion, } 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: {}, invocationId: "abc", result: null, type: MessageType.Completion, } 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: {}, invocationId: "abc", result: Date.UTC(2018, 0, 1, 11, 24, 0), type: MessageType.Completion, } 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", headers: {}, invocationId: "abc", @@ -165,7 +161,7 @@ describe("JsonHubProtocol", () => { })); 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); expect(messages).toEqual([ { @@ -175,7 +171,6 @@ describe("JsonHubProtocol", () => { type: MessageType.StreamItem, } as StreamItemMessage, { - error: null, headers: {}, invocationId: "abc", result: "OK", diff --git a/clients/ts/signalr/tests/LongPollingTransport.test.ts b/clients/ts/signalr/tests/LongPollingTransport.test.ts index e8660ff992..f0b050aa3f 100644 --- a/clients/ts/signalr/tests/LongPollingTransport.test.ts +++ b/clients/ts/signalr/tests/LongPollingTransport.test.ts @@ -20,7 +20,7 @@ describe("LongPollingTransport", () => { return new HttpResponse(200); } else { // 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; // Signal that the poll has completed. @@ -30,7 +30,7 @@ describe("LongPollingTransport", () => { } }) .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); const stopPromise = transport.stop(); @@ -53,7 +53,7 @@ describe("LongPollingTransport", () => { 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); @@ -84,7 +84,7 @@ describe("LongPollingTransport", () => { 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); diff --git a/clients/ts/signalr/tests/TestHttpClient.ts b/clients/ts/signalr/tests/TestHttpClient.ts index f41aca698f..37a0749f73 100644 --- a/clients/ts/signalr/tests/TestHttpClient.ts +++ b/clients/ts/signalr/tests/TestHttpClient.ts @@ -49,8 +49,8 @@ export class TestHttpClient extends HttpClient { const oldHandler = this.handler; const newHandler = async (request: HttpRequest) => { - if (matches(method, request.method) && matches(url, request.url)) { - const promise = handler(request, oldHandler); + if (matches(method, request.method!) && matches(url, request.url!)) { + const promise = handler!(request, oldHandler); let val: TestHttpHandlerResult; if (promise instanceof Promise) { diff --git a/clients/ts/signalr/tests/Utils.ts b/clients/ts/signalr/tests/Utils.ts index b82a9046ea..6be03892ee 100644 --- a/clients/ts/signalr/tests/Utils.ts +++ b/clients/ts/signalr/tests/Utils.ts @@ -12,8 +12,8 @@ export function delay(durationInMilliseconds: number): Promise { export class PromiseSource implements Promise { public promise: Promise; - private resolver: (value?: T | PromiseLike) => void; - private rejecter: (reason?: any) => void; + private resolver!: (value?: T | PromiseLike) => void; + private rejecter!: (reason?: any) => void; constructor() { this.promise = new Promise((resolve, reject) => { diff --git a/clients/ts/tsconfig.base.json b/clients/ts/tsconfig.base.json index 12e9de1591..f6a24b2505 100644 --- a/clients/ts/tsconfig.base.json +++ b/clients/ts/tsconfig.base.json @@ -14,6 +14,7 @@ "suppressImplicitAnyIndexErrors": true, "noEmitOnError": true, "stripInternal": true, + "strict": true, "lib": [ "es5", "es2015.promise", "es2015.iterable", "dom" ], "baseUrl": ".", "paths": {