diff --git a/clients/ts/signalr-protocol-msgpack/src/BinaryMessageFormat.ts b/clients/ts/signalr-protocol-msgpack/src/BinaryMessageFormat.ts index 3f40b9e7c9..3de3bb0c37 100644 --- a/clients/ts/signalr-protocol-msgpack/src/BinaryMessageFormat.ts +++ b/clients/ts/signalr-protocol-msgpack/src/BinaryMessageFormat.ts @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Not exported from index. export class BinaryMessageFormat { // The length prefix of binary messages is encoded as VarInt. Read the comment in diff --git a/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts b/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts index 9722783551..a4b2aeb42d 100644 --- a/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts +++ b/clients/ts/signalr-protocol-msgpack/src/MessagePackHubProtocol.ts @@ -8,20 +8,53 @@ import { CompletionMessage, HubMessage, IHubProtocol, ILogger, InvocationMessage import { BinaryMessageFormat } from "./BinaryMessageFormat"; +// TypeDoc's @inheritDoc and @link don't work across modules :( + +/** Implements the MessagePack Hub Protocol */ export class MessagePackHubProtocol implements IHubProtocol { - + /** The name of the protocol. This is used by SignalR to resolve the protocol between the client and server. */ public readonly name: string = "messagepack"; + /** The version of the protocol. */ public readonly version: number = 1; - + /** The TransferFormat of the protocol. */ public readonly transferFormat: TransferFormat = TransferFormat.Binary; + /** Creates an array of HubMessage objects from the specified serialized representation. + * + * @param {ArrayBuffer} input An ArrayBuffer containing the serialized representation. + * @param {ILogger} logger A logger that will be used to log messages that occur during parsing. + */ public parseMessages(input: ArrayBuffer, logger: ILogger): HubMessage[] { + // The interface does allow "string" to be passed in, but this implementation does not. So let's throw a useful error. + if (!(input instanceof ArrayBuffer)) { + throw new Error("Invalid input for MessagePack hub protocol. Expected an ArrayBuffer."); + } + if (logger === null) { logger = NullLogger.instance; } return BinaryMessageFormat.parse(input).map((m) => this.parseMessage(m, logger)); } + /** Writes the specified HubMessage to an ArrayBuffer and returns it. + * + * @param {HubMessage} message The message to write. + * @returns {ArrayBuffer} An ArrayBuffer containing the serialized representation of the message. + */ + public writeMessage(message: HubMessage): ArrayBuffer { + switch (message.type) { + case MessageType.Invocation: + return this.writeInvocation(message as InvocationMessage); + case MessageType.StreamInvocation: + return this.writeStreamInvocation(message as StreamInvocationMessage); + case MessageType.StreamItem: + case MessageType.Completion: + throw new Error(`Writing messages of type '${message.type}' is not supported.`); + default: + throw new Error("Invalid message type."); + } + } + private parseMessage(input: Uint8Array, logger: ILogger): HubMessage { if (input.length === 0) { throw new Error("Invalid payload."); @@ -154,20 +187,6 @@ export class MessagePackHubProtocol implements IHubProtocol { return completionMessage as CompletionMessage; } - public writeMessage(message: HubMessage): ArrayBuffer { - switch (message.type) { - case MessageType.Invocation: - return this.writeInvocation(message as InvocationMessage); - case MessageType.StreamInvocation: - return this.writeStreamInvocation(message as StreamInvocationMessage); - case MessageType.StreamItem: - case MessageType.Completion: - throw new Error(`Writing messages of type '${message.type}' is not supported.`); - default: - throw new Error("Invalid message type."); - } - } - private writeInvocation(invocationMessage: InvocationMessage): ArrayBuffer { const msgpack = msgpack5(); const payload = msgpack.encode([MessageType.Invocation, invocationMessage.headers || {}, invocationMessage.invocationId || null, diff --git a/clients/ts/signalr/spec/HubConnectionBuilder.spec.ts b/clients/ts/signalr/spec/HubConnectionBuilder.spec.ts index e9f4c5a7aa..fdbf097c66 100644 --- a/clients/ts/signalr/spec/HubConnectionBuilder.spec.ts +++ b/clients/ts/signalr/spec/HubConnectionBuilder.spec.ts @@ -197,15 +197,15 @@ class TestProtocol implements IHubProtocol { public name: string = "test"; public version: number = 1; public transferFormat: TransferFormat = TransferFormat.Text; - public parseMessages(input: any, logger: ILogger): HubMessage[] { + public parseMessages(input: string | ArrayBuffer, logger: ILogger): HubMessage[] { throw new Error("Method not implemented."); } - public writeMessage(message: HubMessage) { + public writeMessage(message: HubMessage): string | ArrayBuffer { throw new Error("Method not implemented."); } } -function createConnectionBuilder(logger?: ILogger | LogLevel): HubConnectionBuilder { +function createConnectionBuilder(logger?: ILogger): HubConnectionBuilder { // We don't want to spam test output with logs. This can be changed as needed return new HubConnectionBuilder() .configureLogging(logger || NullLogger.instance); diff --git a/clients/ts/signalr/src/AbortController.ts b/clients/ts/signalr/src/AbortController.ts index 3992ddeb1e..a5b9232f8a 100644 --- a/clients/ts/signalr/src/AbortController.ts +++ b/clients/ts/signalr/src/AbortController.ts @@ -5,6 +5,7 @@ // We don't actually ever use the API being polyfilled, we always use the polyfill because // it's a very new API right now. +// Not exported from index. export class AbortController implements AbortSignal { private isAborted: boolean = false; public onabort: () => void; @@ -27,7 +28,10 @@ export class AbortController implements AbortSignal { } } +/** Represents a signal that can be monitored to determine if a request has been aborted. */ 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; } diff --git a/clients/ts/signalr/src/Errors.ts b/clients/ts/signalr/src/Errors.ts index 4e52f76aaf..4bed501a61 100644 --- a/clients/ts/signalr/src/Errors.ts +++ b/clients/ts/signalr/src/Errors.ts @@ -1,10 +1,19 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +/** Error thrown when an HTTP request fails. */ export class HttpError extends Error { // tslint:disable-next-line:variable-name private __proto__: Error; + + /** The HTTP status code represented by this error. */ public statusCode: number; + + /** Constructs a new instance of {@link HttpError}. + * + * @param {string} errorMessage A descriptive error message. + * @param {number} statusCode The HTTP status code represented by this error. + */ constructor(errorMessage: string, statusCode: number) { const trueProto = new.target.prototype; super(errorMessage); @@ -16,9 +25,15 @@ export class HttpError extends Error { } } +/** Error thrown when a timeout elapses. */ export class TimeoutError extends Error { // tslint:disable-next-line:variable-name private __proto__: Error; + + /** Constructs a new instance of {@link TimeoutError}. + * + * @param {string} errorMessage A descriptive error message. + */ constructor(errorMessage: string = "A timeout occurred.") { const trueProto = new.target.prototype; super(errorMessage); diff --git a/clients/ts/signalr/src/HttpClient.ts b/clients/ts/signalr/src/HttpClient.ts index 42e353eee2..66837a01a0 100644 --- a/clients/ts/signalr/src/HttpClient.ts +++ b/clients/ts/signalr/src/HttpClient.ts @@ -5,20 +5,59 @@ import { AbortSignal } from "./AbortController"; import { HttpError, TimeoutError } from "./Errors"; import { ILogger, LogLevel } from "./ILogger"; +/** Represents an HTTP request. */ export interface HttpRequest { + /** The HTTP method to use for the request. */ method?: string; + + /** The URL for the request. */ url?: string; + + /** The body content for the request. May be a string or an ArrayBuffer (for binary data). */ content?: string | ArrayBuffer; + + /** An object describing headers to apply to the request. */ headers?: { [key: string]: string }; + + /** The XMLHttpRequestResponseType to apply to the request. */ responseType?: XMLHttpRequestResponseType; + + /** An AbortSignal that can be monitored for cancellation. */ abortSignal?: AbortSignal; + + /** The time to wait for the request to complete before throwing a TimeoutError. Measured in milliseconds. */ timeout?: number; } +/** Represents an HTTP response. */ export class HttpResponse { + /** Constructs a new instance of {@link HttpResponse} with the specified status code. + * + * @param {number} statusCode The status code of the response. + */ constructor(statusCode: number); + + /** Constructs a new instance of {@link HttpResponse} with the specified status code and message. + * + * @param {number} statusCode The status code of the response. + * @param {string} statusText The status message of the response. + */ constructor(statusCode: number, statusText: string); + + /** Constructs a new instance of {@link HttpResponse} with the specified status code, message and string content. + * + * @param {number} statusCode The status code of the response. + * @param {string} statusText The status message of the response. + * @param {string} content The content of the response. + */ constructor(statusCode: number, statusText: string, content: string); + + /** Constructs a new instance of {@link HttpResponse} with the specified status code, message and binary content. + * + * @param {number} statusCode The status code of the response. + * @param {string} statusText The status message of the response. + * @param {ArrayBuffer} content The content of the response. + */ constructor(statusCode: number, statusText: string, content: ArrayBuffer); constructor( public readonly statusCode: number, @@ -27,8 +66,24 @@ export class HttpResponse { } } +/** Abstraction over an HTTP client. + * + * This class provides an abstraction over an HTTP client so that a different implementation can be provided on different platforms. + */ export abstract class HttpClient { + /** Issues an HTTP GET request to the specified URL, returning a Promise that resolves with an {@link HttpResponse} representing the result. + * + * @param {string} url The URL for the request. + * @returns {Promise} A Promise that resolves with an {@link HttpResponse} describing the response, or rejects with an Error indicating a failure. + */ public get(url: string): Promise; + + /** Issues an HTTP GET request to the specified URL, returning a Promise that resolves with an {@link HttpResponse} representing the result. + * + * @param {string} url The URL for the request. + * @param {HttpRequest} options Additional options to configure the request. The 'url' field in this object will be overridden by the url parameter. + * @returns {Promise} A Promise that resolves with an {@link HttpResponse} describing the response, or rejects with an Error indicating a failure. + */ public get(url: string, options: HttpRequest): Promise; public get(url: string, options?: HttpRequest): Promise { return this.send({ @@ -38,7 +93,19 @@ export abstract class HttpClient { }); } + /** Issues an HTTP POST request to the specified URL, returning a Promise that resolves with an {@link HttpResponse} representing the result. + * + * @param {string} url The URL for the request. + * @returns {Promise} A Promise that resolves with an {@link HttpResponse} describing the response, or rejects with an Error indicating a failure. + */ public post(url: string): Promise; + + /** Issues an HTTP POST request to the specified URL, returning a Promise that resolves with an {@link HttpResponse} representing the result. + * + * @param {string} url The URL for the request. + * @param {HttpRequest} options Additional options to configure the request. The 'url' field in this object will be overridden by the url parameter. + * @returns {Promise} A Promise that resolves with an {@link HttpResponse} describing the response, or rejects with an Error indicating a failure. + */ public post(url: string, options: HttpRequest): Promise; public post(url: string, options?: HttpRequest): Promise { return this.send({ @@ -48,7 +115,19 @@ export abstract class HttpClient { }); } + /** Issues an HTTP DELETE request to the specified URL, returning a Promise that resolves with an {@link HttpResponse} representing the result. + * + * @param {string} url The URL for the request. + * @returns {Promise} A Promise that resolves with an {@link HttpResponse} describing the response, or rejects with an Error indicating a failure. + */ public delete(url: string): Promise; + + /** Issues an HTTP DELETE request to the specified URL, returning a Promise that resolves with an {@link HttpResponse} representing the result. + * + * @param {string} url The URL for the request. + * @param {HttpRequest} options Additional options to configure the request. The 'url' field in this object will be overridden by the url parameter. + * @returns {Promise} A Promise that resolves with an {@link HttpResponse} describing the response, or rejects with an Error indicating a failure. + */ public delete(url: string, options: HttpRequest): Promise; public delete(url: string, options?: HttpRequest): Promise { return this.send({ @@ -58,17 +137,25 @@ export abstract class HttpClient { }); } + /** Issues an HTTP request to the specified URL, returning a {@link Promise} that resolves with an {@link HttpResponse} representing the result. + * + * @param {HttpRequest} request An {@link HttpRequest} describing the request to send. + * @returns {Promise} A Promise that resolves with an HttpResponse describing the response, or rejects with an Error indicating a failure. + */ public abstract send(request: HttpRequest): Promise; } +/** Default implementation of {@link HttpClient}. */ export class DefaultHttpClient extends HttpClient { private readonly logger: ILogger; - constructor(logger: ILogger) { + /** Creates a new instance of the {@link DefaultHttpClient}, using the provided {@link ILogger} to log messages. */ + public constructor(logger: ILogger) { super(); this.logger = logger; } + /** @inheritDoc */ public send(request: HttpRequest): Promise { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); diff --git a/clients/ts/signalr/src/HubConnection.ts b/clients/ts/signalr/src/HubConnection.ts index 6c4e804687..c69a5d24fd 100644 --- a/clients/ts/signalr/src/HubConnection.ts +++ b/clients/ts/signalr/src/HubConnection.ts @@ -10,6 +10,7 @@ import { Arg, Subject } from "./Utils"; const DEFAULT_TIMEOUT_IN_MS: number = 30 * 1000; +/** Represents a connection to a SignalR Hub. */ export class HubConnection { private readonly connection: IConnection; private readonly logger: ILogger; @@ -22,6 +23,11 @@ export class HubConnection { private timeoutHandle: NodeJS.Timer; private receivedHandshakeResponse: boolean; + /** The server timeout in milliseconds. + * + * If this timeout elapses without receiving any messages from the server, the connection will be terminated with an error. + * The default timeout value is 30,000 milliseconds (30 seconds). + */ public serverTimeoutInMilliseconds: number; /** @internal */ @@ -54,6 +60,10 @@ export class HubConnection { this.id = 0; } + /** Starts the connection. + * + * @returns {Promise} A Promise that resolves when the connection has been successfully established, or rejects with an error. + */ public async start(): Promise { const handshakeRequest: HandshakeRequestMessage = { protocol: this.protocol.name, @@ -77,6 +87,10 @@ export class HubConnection { this.configureTimeout(); } + /** Stops the connection. + * + * @returns {Promise} A Promise that resolves when the connection has been successfully terminated, or rejects with an error. + */ public stop(): Promise { this.logger.log(LogLevel.Debug, "Stopping HubConnection."); @@ -84,6 +98,13 @@ export class HubConnection { return this.connection.stop(); } + /** Invokes a streaming hub method on the server using the specified name and arguments. + * + * @typeparam T The type of the items returned by the server. + * @param {string} methodName The name of the server method to invoke. + * @param {any[]} args The arguments used to invoke the server method. + * @returns {IStreamResult} An object that yields results from the server as they are received. + */ public stream(methodName: string, ...args: any[]): IStreamResult { const invocationDescriptor = this.createStreamInvocation(methodName, args); @@ -124,6 +145,15 @@ export class HubConnection { return subject; } + /** Invokes a hub method on the server using the specified name and arguments. Does not wait for a response from the receiver. + * + * The Promise returned by this method resolves when the client has sent the invocation to the server. The server may still + * be processing the invocation. + * + * @param {string} methodName The name of the server method to invoke. + * @param {any[]} args The arguments used to invoke the server method. + * @returns {Promise} A Promise that resolves when the invocation has been successfully sent, or rejects with an error. + */ public send(methodName: string, ...args: any[]): Promise { const invocationDescriptor = this.createInvocation(methodName, args, true); @@ -132,6 +162,17 @@ export class HubConnection { return this.connection.send(message); } + /** Invokes a hub method on the server using the specified name and arguments. + * + * The Promise returned by this method resolves when the server indicates it has finished invoking the method. When the promise + * resolves, the server has finished invoking the method. If the server method returns a result, it is produced as the result of + * resolving the Promise. + * + * @typeparam T The expected return type. + * @param {string} methodName The name of the server method to invoke. + * @param {any[]} args The arguments used to invoke the server method. + * @returns {Promise} A Promise that resolves with the result of the server method (if any), or rejects with an error. + */ public invoke(methodName: string, ...args: any[]): Promise { const invocationDescriptor = this.createInvocation(methodName, args, false); @@ -165,6 +206,11 @@ export class HubConnection { return p; } + /** Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * + * @param {string} methodName The name of the hub method to define. + * @param {Function} newMethod The handler that will be raised when the hub method is invoked. + */ public on(methodName: string, newMethod: (...args: any[]) => void) { if (!methodName || !newMethod) { return; @@ -183,7 +229,22 @@ export class HubConnection { this.methods[methodName].push(newMethod); } - public off(methodName: string, method?: (...args: any[]) => void) { + /** Removes all handlers for the specified hub method. + * + * @param {string} methodName The name of the method to remove handlers for. + */ + public off(methodName: string): void; + + /** Removes the specified handler for the specified hub method. + * + * You must pass the exact same Function instance as was previously passed to {@link on}. Passing a different instance (even if the function + * body is the same) will not remove the handler. + * + * @param {string} methodName The name of the method to remove handlers for. + * @param {Function} method The handler to remove. This must be the same Function instance as the one passed to {@link on}. + */ + public off(methodName: string, method: (...args: any[]) => void): void; + public off(methodName: string, method?: (...args: any[]) => void): void { if (!methodName) { return; } @@ -207,6 +268,10 @@ export class HubConnection { } + /** Registers a handler that will be invoked when the connection is closed. + * + * @param {Function} callback The handler that will be invoked when the connection is closed. Optionally receives a single argument containing the error that caused the connection to close (if any). + */ public onclose(callback: (error?: Error) => void) { if (callback) { this.closedCallbacks.push(callback); diff --git a/clients/ts/signalr/src/HubConnectionBuilder.ts b/clients/ts/signalr/src/HubConnectionBuilder.ts index b5a4f42b93..2990f38714 100644 --- a/clients/ts/signalr/src/HubConnectionBuilder.ts +++ b/clients/ts/signalr/src/HubConnectionBuilder.ts @@ -11,6 +11,7 @@ import { JsonHubProtocol } from "./JsonHubProtocol"; import { NullLogger } from "./Loggers"; import { Arg, ConsoleLogger } from "./Utils"; +/** A builder for configuring {@link HubConnection} instances. */ export class HubConnectionBuilder { /** @internal */ public protocol: IHubProtocol; @@ -21,6 +22,19 @@ export class HubConnectionBuilder { /** @internal */ public logger: ILogger; + /** Configures console logging for the {@link HubConnection}. + * + * @param {LogLevel} logLevel The minimum level of messages to log. Anything at this level, or a more severe level, will be logged. + * @returns The {@link HubConnectionBuilder} instance, for chaining. + */ + public configureLogging(logLevel: LogLevel): HubConnectionBuilder; + + /** Configures custom logging for the {@link HubConnection}. + * + * @param {ILogger} logger An object implementing the {@link ILogger} interface, which will be used to write all log messages. + * @returns The {@link HubConnectionBuilder} instance, for chaining. + */ + public configureLogging(logger: ILogger): HubConnectionBuilder; public configureLogging(logging: LogLevel | ILogger): HubConnectionBuilder { Arg.isRequired(logging, "logging"); @@ -33,9 +47,30 @@ export class HubConnectionBuilder { return this; } + /** Configures the {@link HubConnection} to use HTTP-based transports to connect to the specified URL. + * + * The transport will be selected automatically based on what the server and client support. + * + * @param {string} url The URL the connection will use. + * @returns The {@link HubConnectionBuilder} instance, for chaining. + */ public withUrl(url: string): HubConnectionBuilder; - public withUrl(url: string, options: IHttpConnectionOptions): HubConnectionBuilder; + + /** Configures the {@link HubConnection} to use the specified HTTP-based transport to connect to the specified URL. + * + * @param {string} url The URL the connection will use. + * @param {HttpTransportType} transportType The specific transport to use. + * @returns The {@link HubConnectionBuilder} instance, for chaining. + */ public withUrl(url: string, transportType: HttpTransportType): HubConnectionBuilder; + + /** Configures the {@link HubConnection} to use HTTP-based transports to connect to the specified URL. + * + * @param {string} url The URL the connection will use. + * @param {IHttpConnectionOptions} options An options object used to configure the connection. + * @returns The {@link HubConnectionBuilder} instance, for chaining. + */ + public withUrl(url: string, options: IHttpConnectionOptions): HubConnectionBuilder; public withUrl(url: string, transportTypeOrOptions?: IHttpConnectionOptions | HttpTransportType): HubConnectionBuilder { Arg.isRequired(url, "url"); @@ -54,6 +89,10 @@ export class HubConnectionBuilder { return this; } + /** Configures the {@link HubConnection} to use the specified Hub Protocol. + * + * @param {IHubProtocol} protocol The {@link IHubProtocol} implementation to use. + */ public withHubProtocol(protocol: IHubProtocol): HubConnectionBuilder { Arg.isRequired(protocol, "protocol"); @@ -61,6 +100,10 @@ export class HubConnectionBuilder { return this; } + /** Creates a {@link HubConnection} from the configuration options specified in this builder. + * + * @returns {HubConnection} The configured {@link HubConnection}. + */ public build(): HubConnection { // If httpConnectionOptions has a logger, use it. Otherwise, override it with the one // provided to configureLogger diff --git a/clients/ts/signalr/src/IHttpConnectionOptions.ts b/clients/ts/signalr/src/IHttpConnectionOptions.ts index cdb6ad73f3..d6433d2def 100644 --- a/clients/ts/signalr/src/IHttpConnectionOptions.ts +++ b/clients/ts/signalr/src/IHttpConnectionOptions.ts @@ -5,11 +5,37 @@ import { HttpClient } from "./HttpClient"; import { ILogger, LogLevel } from "./ILogger"; import { HttpTransportType, ITransport } from "./ITransport"; +/** Options provided to the 'withUrl' method on {@link HubConnectionBuilder} to configure options for the HTTP-based transports. */ export interface IHttpConnectionOptions { + /** An {@link HttpClient} that will be used to make HTTP requests. */ httpClient?: HttpClient; + + /** An {@link HttpTransportType} value specifying the transport to use for the connection. */ transport?: HttpTransportType | ITransport; + + /** Configures the logger used for logging. + * + * Provide an {@link ILogger} instance, and log messages will be logged via that instance. Alternatively, provide a value from + * the {@link LogLevel} enumeration and a default logger which logs to the Console will be configured to log messages of the specified + * level (or higher). + */ logger?: ILogger | LogLevel; - accessTokenFactory?: () => string | Promise; + + /** A function that provides an access token required for HTTP Bearer authentication. + * + * @returns {string | Promise} A string containing the access token, or a Promise that resolves to a string containing the access token. + */ + accessTokenFactory?(): string | Promise; + + /** A boolean indicating if message content should be logged. + * + * Message content can contain sensitive user data, so this is disabled by default. + */ logMessageContent?: boolean; + + /** A boolean indicating if negotiation should be skipped. + * + * Negotiation can only be skipped when the {@link transport} property is set to 'HttpTransportType.WebSockets'. + */ skipNegotiation?: boolean; } diff --git a/clients/ts/signalr/src/IHubProtocol.ts b/clients/ts/signalr/src/IHubProtocol.ts index ee30cc182c..8293f87ca9 100644 --- a/clients/ts/signalr/src/IHubProtocol.ts +++ b/clients/ts/signalr/src/IHubProtocol.ts @@ -4,69 +4,162 @@ import { ILogger } from "./ILogger"; import { TransferFormat } from "./ITransport"; -export const enum MessageType { +/** Defines the type of a Hub Message. */ +export enum MessageType { + /** Indicates the message is an Invocation message and implements the {@link InvocationMessage} interface. */ Invocation = 1, + /** Indicates the message is a StreamItem message and implements the {@link StreamItemMessage} interface. */ StreamItem = 2, + /** Indicates the message is a Completion message and implements the {@link CompletionMessage} interface. */ Completion = 3, + /** Indicates the message is a Stream Invocation message and implements the {@link StreamInvocationMessage} interface. */ StreamInvocation = 4, + /** Indicates the message is a Cancel Invocation message and implements the {@link CancelInvocationMessage} interface. */ CancelInvocation = 5, + /** Indicates the message is a Ping message and implements the {@link PingMessage} interface. */ Ping = 6, + /** Indicates the message is a Close message and implements the {@link CloseMessage} interface. */ Close = 7, } -export interface MessageHeaders { [key: string]: string; } +/** Defines a dictionary of string keys and string values representing headers attached to a Hub message. */ +export interface MessageHeaders { + /** Gets or sets the header with the specified key. */ + [key: string]: string; +} -export type HubMessage = InvocationMessage | StreamInvocationMessage | StreamItemMessage | CompletionMessage | CancelInvocationMessage | PingMessage | CloseMessage; +/** Union type of all known Hub messages. */ +export type HubMessage = + InvocationMessage | + StreamInvocationMessage | + StreamItemMessage | + CompletionMessage | + CancelInvocationMessage | + PingMessage | + CloseMessage; +/** Defines properties common to all Hub messages. */ export interface HubMessageBase { + /** A {@link MessageType} value indicating the type of this message. */ readonly type: MessageType; } +/** Defines properties common to all Hub messages relating to a specific invocation. */ export interface HubInvocationMessage extends HubMessageBase { + /** A {@link MessageHeaders} dictionary containing headers attached to the message. */ readonly headers?: MessageHeaders; + /** The ID of the invocation relating to this message. + * + * This is expected to be present for {@link StreamInvocationMessage} and {@link CompletionMessage}. It may + * be 'undefined' for an {@link InvocationMessage} if the sender does not expect a response. + */ readonly invocationId?: string; } +/** A hub message representing a non-streaming invocation. */ export interface InvocationMessage extends HubInvocationMessage { readonly type: MessageType.Invocation; + /** The target method name. */ readonly target: string; + /** The target method arguments. */ readonly arguments: any[]; } +/** A hub message representing a streaming invocation. */ export interface StreamInvocationMessage extends HubInvocationMessage { + /** @inheritDoc */ readonly type: MessageType.StreamInvocation; + + /** The invocation ID. */ + readonly invocationId: string; + /** The target method name. */ readonly target: string; + /** The target method arguments. */ readonly arguments: any[]; } +/** A hub message representing a single item produced as part of a result stream. */ export interface StreamItemMessage extends HubInvocationMessage { + /** @inheritDoc */ readonly type: MessageType.StreamItem; + + /** The invocation ID. */ + readonly invocationId: string; + + /** The item produced by the server. */ readonly item?: any; } +/** A hub message representing the result of an invocation. */ export interface CompletionMessage extends HubInvocationMessage { + /** @inheritDoc */ readonly type: MessageType.Completion; + /** The invocation ID. */ + readonly invocationId: string; + /** The error produced by the invocation, if any. + * + * Either {@link error} or {@link result} must be defined, but not both. + */ readonly error?: string; + /** The result produced by the invocation, if any. + * + * Either {@link error} or {@link result} must be defined, but not both. + */ readonly result?: any; } +/** A hub message indicating that the sender is still active. */ export interface PingMessage extends HubMessageBase { + /** @inheritDoc */ readonly type: MessageType.Ping; } +/** A hub message indicating that the sender is closing the connection. + * + * If {@link error} is defined, the sender is closing the connection due to an error. + */ export interface CloseMessage extends HubMessageBase { + /** @inheritDoc */ readonly type: MessageType.Close; + /** The error that triggered the close, if any. + * + * If this property is undefined, the connection was closed normally and without error. + */ readonly error?: string; } +/** A hub message sent to request that a streaming invocation be canceled. */ export interface CancelInvocationMessage extends HubInvocationMessage { + /** @inheritDoc */ readonly type: MessageType.CancelInvocation; + /** The invocation ID. */ + readonly invocationId: string; } +/** A protocol abstraction for communicating with SignalR Hubs. */ export interface IHubProtocol { + /** The name of the protocol. This is used by SignalR to resolve the protocol between the client and server. */ readonly name: string; + /** The version of the protocol. */ readonly version: number; + /** The {@link TransferFormat} of the protocol. */ readonly transferFormat: TransferFormat; - parseMessages(input: any, logger: ILogger): HubMessage[]; - writeMessage(message: HubMessage): any; + + /** Creates an array of {@link HubMessage} objects from the specified serialized representation. + * + * If {@link transferFormat} is 'Text', the {@link input} parameter must be a string, otherwise it must be an ArrayBuffer. + * + * @param {string | ArrayBuffer} input A string, or ArrayBuffer containing the serialized representation. + * @param {ILogger} logger A logger that will be used to log messages that occur during parsing. + */ + parseMessages(input: string | ArrayBuffer, logger: ILogger): HubMessage[]; + + /** Writes the specified {@link HubMessage} to a string or ArrayBuffer and returns it. + * + * If {@link transferFormat} is 'Text', the result of this method will be a string, otherwise it will be an ArrayBuffer. + * + * @param {HubMessage} message The message to write. + * @returns {string | ArrayBuffer} A string or ArrayBuffer containing the serialized representation of the message. + */ + writeMessage(message: HubMessage): string | ArrayBuffer; } diff --git a/clients/ts/signalr/src/ILogger.ts b/clients/ts/signalr/src/ILogger.ts index 6c14afa671..0469583a66 100644 --- a/clients/ts/signalr/src/ILogger.ts +++ b/clients/ts/signalr/src/ILogger.ts @@ -2,16 +2,33 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // These values are designed to match the ASP.NET Log Levels since that's the pattern we're emulating here. +/** Indicates the severity of a log message. + * + * Log Levels are ordered in increasing severity. So `Debug` is more severe than `Trace`, etc. + */ export enum LogLevel { + /** Log level for very low severity diagnostic messages. */ Trace = 0, + /** Log level for low severity diagnostic messages. */ Debug = 1, + /** Log level for informational diagnostic messages. */ Information = 2, + /** Log level for diagnostic messages that indicate a non-fatal problem. */ Warning = 3, + /** Log level for diagnostic messages that indicate a failure in the current operation. */ Error = 4, + /** Log level for diagnostic messages that indicate a failure that will terminate the entire application. */ Critical = 5, + /** The highest possible log level. Used when configuring logging to indicate that no log messages should be emitted. */ None = 6, } +/** An abstraction that provides a sink for diagnostic messages. */ export interface ILogger { + /** Called by the framework to emit a diagnostic message. + * + * @param {LogLevel} logLevel The severity level of the message. + * @param {string} message The message. + */ log(logLevel: LogLevel, message: string): void; } diff --git a/clients/ts/signalr/src/ITransport.ts b/clients/ts/signalr/src/ITransport.ts index 84e52bc205..b5c4fb1654 100644 --- a/clients/ts/signalr/src/ITransport.ts +++ b/clients/ts/signalr/src/ITransport.ts @@ -1,17 +1,25 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +/** Specifies a specific HTTP transport type. */ export enum HttpTransportType { + /** Specifies the WebSockets transport. */ WebSockets, + /** Specifies the Server-Sent Events transport. */ ServerSentEvents, + /** Specifies the Long Polling transport. */ LongPolling, } +/** Specifies the transfer format for a connection. */ export enum TransferFormat { + /** Specifies that only text data will be transmitted over the connection. */ Text = 1, + /** Specifies that binary data will be transmitted over the connection. */ Binary, } +/** An abstraction over the behavior of transports. This is designed to support the framework and not intended for use by applications. */ export interface ITransport { connect(url: string, transferFormat: TransferFormat): Promise; send(data: any): Promise; diff --git a/clients/ts/signalr/src/JsonHubProtocol.ts b/clients/ts/signalr/src/JsonHubProtocol.ts index 261d7c290c..c954c1f8c4 100644 --- a/clients/ts/signalr/src/JsonHubProtocol.ts +++ b/clients/ts/signalr/src/JsonHubProtocol.ts @@ -7,16 +7,30 @@ import { TransferFormat } from "./ITransport"; import { NullLogger } from "./Loggers"; import { TextMessageFormat } from "./TextMessageFormat"; -export const JSON_HUB_PROTOCOL_NAME: string = "json"; +const JSON_HUB_PROTOCOL_NAME: string = "json"; +/** Implements the JSON Hub Protocol. */ export class JsonHubProtocol implements IHubProtocol { + /** @inheritDoc */ public readonly name: string = JSON_HUB_PROTOCOL_NAME; + /** @inheritDoc */ public readonly version: number = 1; + /** @inheritDoc */ public readonly transferFormat: TransferFormat = TransferFormat.Text; + /** Creates an array of {@link HubMessage} objects from the specified serialized representation. + * + * @param {string} input A string containing the serialized representation. + * @param {ILogger} logger A logger that will be used to log messages that occur during parsing. + */ public parseMessages(input: string, logger: ILogger): HubMessage[] { + // The interface does allow "ArrayBuffer" to be passed in, but this implementation does not. So let's throw a useful error. + if (typeof input !== "string") { + throw new Error("Invalid input for JSON hub protocol. Expected a string."); + } + if (!input) { return []; } @@ -61,6 +75,11 @@ export class JsonHubProtocol implements IHubProtocol { return hubMessages; } + /** Writes the specified {@link HubMessage} to a string and returns it. + * + * @param {HubMessage} message The message to write. + * @returns {string} A string containing the serialized representation of the message. + */ public writeMessage(message: HubMessage): string { return TextMessageFormat.write(JSON.stringify(message)); } diff --git a/clients/ts/signalr/src/Loggers.ts b/clients/ts/signalr/src/Loggers.ts index 1b250d876c..884cec297d 100644 --- a/clients/ts/signalr/src/Loggers.ts +++ b/clients/ts/signalr/src/Loggers.ts @@ -3,11 +3,14 @@ import { ILogger, LogLevel } from "./ILogger"; +/** A logger that does nothing when log messages are sent to it. */ export class NullLogger implements ILogger { + /** The singleton instance of the {@link NullLogger}. */ public static instance: ILogger = new NullLogger(); private constructor() {} + /** @inheritDoc */ public log(logLevel: LogLevel, message: string): void { } } diff --git a/clients/ts/signalr/src/Stream.ts b/clients/ts/signalr/src/Stream.ts index 21e027210b..8dca2fb526 100644 --- a/clients/ts/signalr/src/Stream.ts +++ b/clients/ts/signalr/src/Stream.ts @@ -7,17 +7,45 @@ // depend on RxJS in the core library, so instead we duplicate the minimum logic needed and then users can easily adapt these into // proper RxJS observables if they want. +/** Defines the expected type for a receiver of results streamed by the server. + * + * @typeparam T The type of the items being sent by the server. + */ export interface IStreamSubscriber { + /** A boolean that will be set by the {@link IStreamResult} when the stream is closed. */ closed?: boolean; + /** Called by the framework when a new item is available. */ next(value: T): void; + /** Called by the framework when an error has occurred. + * + * After this method is called, no additional methods on the {@link IStreamSubscriber} will be called. + */ error(err: any): void; + /** Called by the framework when the end of the stream is reached. + * + * After this method is called, no additional methods on the {@link IStreamSubscriber} will be called. + */ complete(): void; } +/** Defines the result of a streaming hub method. + * + * @typeparam T The type of the items being sent by the server. + */ export interface IStreamResult { - subscribe(observer: IStreamSubscriber): ISubscription; + /** Attaches a {@link IStreamSubscriber}, which will be invoked when new items are available from the stream. + * + * @param {IStreamSubscriber} observer The subscriber to attach. + * @returns {ISubscription} A subscription that can be disposed to terminate the stream and stop calling methods on the {@link IStreamSubscriber}. + */ + subscribe(subscriber: IStreamSubscriber): ISubscription; } +/** An interface that allows an {@link IStreamSubscriber} to be disconnected from a stream. + * + * @typeparam T The type of the items being sent by the server. + */ export interface ISubscription { + /** Disconnects the {@link IStreamSubscriber} associated with this subscription from the stream. */ dispose(): void; } diff --git a/clients/ts/signalr/src/TextMessageFormat.ts b/clients/ts/signalr/src/TextMessageFormat.ts index 0b4a03e4a6..15868db650 100644 --- a/clients/ts/signalr/src/TextMessageFormat.ts +++ b/clients/ts/signalr/src/TextMessageFormat.ts @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Not exported from index export class TextMessageFormat { public static RecordSeparatorCode = 0x1e; public static RecordSeparator = String.fromCharCode(TextMessageFormat.RecordSeparatorCode); diff --git a/clients/ts/signalr/src/index.ts b/clients/ts/signalr/src/index.ts index f1aee7490f..9489f5d829 100644 --- a/clients/ts/signalr/src/index.ts +++ b/clients/ts/signalr/src/index.ts @@ -2,14 +2,15 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Everything that users need to access must be exported here. Including interfaces. -export * from "./Errors"; -export * from "./HttpClient"; -export * from "./IHttpConnectionOptions"; -export * from "./HubConnection"; -export * from "./HubConnectionBuilder"; -export * from "./IHubProtocol"; -export * from "./ILogger"; -export * from "./ITransport"; -export * from "./Stream"; -export * from "./Loggers"; -export * from "./JsonHubProtocol"; +export { AbortSignal } from "./AbortController"; +export { HttpError, TimeoutError } from "./Errors"; +export { DefaultHttpClient, HttpClient, HttpRequest, HttpResponse } from "./HttpClient"; +export { IHttpConnectionOptions } from "./IHttpConnectionOptions"; +export { HubConnection } from "./HubConnection"; +export { HubConnectionBuilder } from "./HubConnectionBuilder"; +export { MessageType, MessageHeaders, HubMessage, HubMessageBase, HubInvocationMessage, InvocationMessage, StreamInvocationMessage, StreamItemMessage, CompletionMessage, PingMessage, CloseMessage, CancelInvocationMessage, IHubProtocol } from "./IHubProtocol"; +export { ILogger, LogLevel } from "./ILogger"; +export { HttpTransportType, TransferFormat, ITransport } from "./ITransport"; +export { IStreamSubscriber, IStreamResult, ISubscription } from "./Stream"; +export { NullLogger } from "./Loggers"; +export { JsonHubProtocol } from "./JsonHubProtocol";