TypeScript strict mode (#2388)
This commit is contained in:
parent
8d680db112
commit
30a59f6df7
|
|
@ -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<HttpResponse>;
|
||||
public pollPromise: Promise<HttpResponse> | null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.pollPromise = null;
|
||||
}
|
||||
|
||||
public send(request: HttpRequest): Promise<HttpResponse> {
|
||||
if (request.method === "GET") {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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<void>;
|
||||
private transport?: ITransport;
|
||||
private startPromise?: Promise<void>;
|
||||
private stopError?: Error;
|
||||
private accessTokenFactory?: () => string | Promise<string>;
|
||||
|
||||
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<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.");
|
||||
}
|
||||
|
||||
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> {
|
||||
|
|
@ -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<INegotiateResponse> {
|
||||
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<void> {
|
||||
private async createTransport(url: string, requestedTransport: HttpTransportType | ITransport | undefined, negotiateResponse: INegotiateResponse, requestedTransferFormat: TransferFormat): Promise<void> {
|
||||
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<void> {
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<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) {
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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}.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -10,6 +10,6 @@ export interface IConnection {
|
|||
send(data: string | ArrayBuffer): Promise<void>;
|
||||
stop(error?: Error): Promise<void>;
|
||||
|
||||
onreceive: (data: string | ArrayBuffer) => void;
|
||||
onclose: (error?: Error) => void;
|
||||
onreceive: ((data: string | ArrayBuffer) => void) | null;
|
||||
onclose: ((error?: Error) => void) | null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,6 @@ export interface ITransport {
|
|||
connect(url: string, transferFormat: TransferFormat): Promise<void>;
|
||||
send(data: any): Promise<void>;
|
||||
stop(): Promise<void>;
|
||||
onreceive: (data: string | ArrayBuffer) => void;
|
||||
onclose: (error?: Error) => void;
|
||||
onreceive: ((data: string | ArrayBuffer) => void) | null;
|
||||
onclose: ((error?: Error) => void) | null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string>;
|
||||
private readonly accessTokenFactory: (() => string | Promise<string>) | 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<void>;
|
||||
private closeError: Error;
|
||||
private receiving?: Promise<void>;
|
||||
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<string>, logger: ILogger, logMessageContent: boolean) {
|
||||
constructor(httpClient: HttpClient, accessTokenFactory: (() => string | Promise<string>) | 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<void> {
|
||||
|
|
@ -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<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) {
|
||||
// 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<void> {
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,17 +8,23 @@ import { Arg, getDataDetail, sendMessage } from "./Utils";
|
|||
|
||||
export class ServerSentEventsTransport implements ITransport {
|
||||
private readonly httpClient: HttpClient;
|
||||
private readonly accessTokenFactory: () => string | Promise<string>;
|
||||
private readonly accessTokenFactory: (() => string | Promise<string>) | 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<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.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<void> {
|
||||
|
|
@ -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<void> {
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<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;
|
||||
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)}.`);
|
||||
|
|
|
|||
|
|
@ -7,14 +7,20 @@ import { Arg, getDataDetail } from "./Utils";
|
|||
|
||||
export class WebSocketTransport implements ITransport {
|
||||
private readonly logger: ILogger;
|
||||
private readonly accessTokenFactory: () => string | Promise<string>;
|
||||
private readonly accessTokenFactory: (() => string | Promise<string>) | undefined;
|
||||
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.accessTokenFactory = accessTokenFactory || (() => null);
|
||||
this.accessTokenFactory = accessTokenFactory;
|
||||
this.logMessageContent = logMessageContent;
|
||||
|
||||
this.onreceive = null;
|
||||
this.onclose = null;
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
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<void>((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<void> {
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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", {
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
||||
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.");
|
||||
|
|
|
|||
|
|
@ -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<void> {
|
|||
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<void> {
|
||||
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<any> {
|
||||
public readonly closed: boolean;
|
||||
public itemsReceived: [any];
|
||||
private itemsSource: PromiseSource<[any]>;
|
||||
public readonly closed: boolean = false;
|
||||
public itemsReceived: any[];
|
||||
private itemsSource: PromiseSource<any[]>;
|
||||
|
||||
get completed(): Promise<[any]> {
|
||||
get completed(): Promise<any[]> {
|
||||
return this.itemsSource.promise;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.itemsReceived = [] as [any];
|
||||
this.itemsSource = new PromiseSource<[any]>();
|
||||
this.itemsReceived = [];
|
||||
this.itemsSource = new PromiseSource<any[]>();
|
||||
}
|
||||
|
||||
public next(value: any) {
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ export function delay(durationInMilliseconds: number): Promise<void> {
|
|||
export class PromiseSource<T = void> implements Promise<T> {
|
||||
public promise: Promise<T>;
|
||||
|
||||
private resolver: (value?: T | PromiseLike<T>) => void;
|
||||
private rejecter: (reason?: any) => void;
|
||||
private resolver!: (value?: T | PromiseLike<T>) => void;
|
||||
private rejecter!: (reason?: any) => void;
|
||||
|
||||
constructor() {
|
||||
this.promise = new Promise<T>((resolve, reject) => {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
"suppressImplicitAnyIndexErrors": true,
|
||||
"noEmitOnError": true,
|
||||
"stripInternal": true,
|
||||
"strict": true,
|
||||
"lib": [ "es5", "es2015.promise", "es2015.iterable", "dom" ],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
|
|
|
|||
Loading…
Reference in New Issue