Skip to content

Commit

Permalink
Merge pull request #854 from ably/feature/clientoptions-typescript
Browse files Browse the repository at this point in the history
Add typings for ClientOptions
  • Loading branch information
owenpearson authored Dec 1, 2021
2 parents f6d5d81 + d4c4505 commit 566efbd
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 28 deletions.
65 changes: 37 additions & 28 deletions common/lib/util/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import * as Utils from './utils';
import Logger from './logger';
import ErrorInfo from '../types/errorinfo';
import { version } from '../../../package.json';

type ClientOptions = any;
import ClientOptions, { DeprecatedClientOptions, NormalisedClientOptions } from '../../types/ClientOptions';

let agent = 'ably-js/' + version;
if (Platform.agent) {
Expand Down Expand Up @@ -87,14 +86,14 @@ export function environmentFallbackHosts (environment: string): string[] {
];
}

export function getFallbackHosts (options: ClientOptions): string[] {
export function getFallbackHosts (options: NormalisedClientOptions): string[] {
const fallbackHosts = options.fallbackHosts,
httpMaxRetryCount = typeof(options.httpMaxRetryCount) !== 'undefined' ? options.httpMaxRetryCount : Defaults.httpMaxRetryCount;

return fallbackHosts ? Utils.arrChooseN(fallbackHosts, httpMaxRetryCount) : [];
}

export function getHosts (options: ClientOptions): string[] {
export function getHosts (options: NormalisedClientOptions): string[] {
return [options.restHost].concat(getFallbackHosts(options));
}

Expand All @@ -107,14 +106,34 @@ function checkHost(host: string): void {
}
}

function getRealtimeHost(options: ClientOptions, production: boolean, environment: string): string {
if(options.realtimeHost) return options.realtimeHost;
/* prefer setting realtimeHost to restHost as a custom restHost typically indicates
* a development environment is being used that can't be inferred by the library */
if(options.restHost) {
Logger.logAction(Logger.LOG_MINOR, 'Defaults.normaliseOptions', 'restHost is set to "' + options.restHost + '" but realtimeHost is not set, so setting realtimeHost to "' + options.restHost + '" too. If this is not what you want, please set realtimeHost explicitly.');
return options.restHost
}
return production ? Defaults.REALTIME_HOST : environment + '-' + Defaults.REALTIME_HOST;
}

function getTimeouts(options: ClientOptions) {
/* Allow values passed in options to override default timeouts */
const timeouts: Record<string, number> = {};
for(const prop in Defaults.TIMEOUTS) {
timeouts[prop] = (options as Record<string, number>)[prop] || (Defaults.TIMEOUTS as Record<string, number>)[prop];
}
return timeouts;
}

export function objectifyOptions(options: ClientOptions | string): ClientOptions {
if(typeof options == 'string') {
return (options.indexOf(':') == -1) ? {token: options} : {key: options};
}
return options;
}

export function normaliseOptions(options: ClientOptions): ClientOptions {
export function normaliseOptions(options: DeprecatedClientOptions): NormalisedClientOptions {
/* Deprecated options */
if(options.host) {
Logger.deprecated('host', 'restHost');
Expand Down Expand Up @@ -188,33 +207,16 @@ export function normaliseOptions(options: ClientOptions): ClientOptions {
options.fallbackHosts = production ? Defaults.FALLBACK_HOSTS : environmentFallbackHosts(environment);
}

if(!options.realtimeHost) {
/* prefer setting realtimeHost to restHost as a custom restHost typically indicates
* a development environment is being used that can't be inferred by the library */
if(options.restHost) {
Logger.logAction(Logger.LOG_MINOR, 'Defaults.normaliseOptions', 'restHost is set to "' + options.restHost + '" but realtimeHost is not set, so setting realtimeHost to "' + options.restHost + '" too. If this is not what you want, please set realtimeHost explicitly.');
options.realtimeHost = options.restHost
} else {
options.realtimeHost = production ? Defaults.REALTIME_HOST : environment + '-' + Defaults.REALTIME_HOST;
}
}

if(!options.restHost) {
options.restHost = production ? Defaults.REST_HOST : environment + '-' + Defaults.REST_HOST;
}
const restHost = options.restHost || (production ? Defaults.REST_HOST : environment + '-' + Defaults.REST_HOST);
const realtimeHost = getRealtimeHost(options, production, environment);

Utils.arrForEach((options.fallbackHosts || []).concat(options.restHost, options.realtimeHost), checkHost);
Utils.arrForEach((options.fallbackHosts || []).concat(restHost, realtimeHost), checkHost);

options.port = options.port || Defaults.PORT;
options.tlsPort = options.tlsPort || Defaults.TLS_PORT;
options.maxMessageSize = options.maxMessageSize || Defaults.maxMessageSize;
if(!('tls' in options)) options.tls = true;

/* Allow values passed in options to override default timeouts */
options.timeouts = {};
for(var prop in Defaults.TIMEOUTS) {
options.timeouts[prop] = options[prop] || (Defaults.TIMEOUTS as Record<string, any>)[prop];
};
const timeouts = getTimeouts(options);

if('useBinaryProtocol' in options) {
options.useBinaryProtocol = Platform.supportsBinary && options.useBinaryProtocol;
Expand All @@ -237,12 +239,19 @@ export function normaliseOptions(options: ClientOptions): ClientOptions {
}

if(options.agents) {
for(var key in options.agents) {
for (var key in options.agents) {
Defaults.agent += ' ' + key + '/' + options.agents[key];
}
}

return options;
return {
...options,
useBinaryProtocol: ('useBinaryProtocol' in options) ? Platform.supportsBinary && options.useBinaryProtocol : Platform.preferBinary,
realtimeHost,
restHost,
maxMessageSize: options.maxMessageSize || Defaults.maxMessageSize,
timeouts
};
};

export default Defaults;
6 changes: 6 additions & 0 deletions common/lib/util/logger.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import Platform from 'platform';

export type LoggerOptions = {
handler: LoggerFunction,
level: LogLevels,
}
type LoggerFunction = (...args: string[]) => void;

enum LogLevels {
None = 0,
Error = 1,
Expand Down
33 changes: 33 additions & 0 deletions common/types/AuthOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import HttpMethods from "../constants/HttpMethods";
import ErrorInfo from "../lib/types/errorinfo";
import TokenDetails from "./TokenDetails";
import TokenParams from "./TokenParams";
import TokenRequest from "./TokenRequest";

export default interface AuthOptions {
/**
* A function which is called when a new token is required.
* The role of the callback is to either generate a signed TokenRequest which may then be submitted automatically
* by the library to the Ably REST API requestToken; or to provide a valid token in as a TokenDetails object.
**/
authCallback?: (data: TokenParams, callback: (error: ErrorInfo | string, tokenRequestOrDetails: TokenDetails | TokenRequest | string) => void) => void;
authHeaders?: { [index: string]: string };
authMethod?: HttpMethods;
authParams?: { [index: string]: string };

/**
* A URL that the library may use to obtain a token string (in plain text format), or a signed TokenRequest or TokenDetails (in JSON format).
**/
authUrl?: string;
key?: string;
queryTime?: boolean;
token?: TokenDetails | string;
tokenDetails?: TokenDetails;
useTokenAuth?: boolean;

/**
* Optional clientId that can be used to specify the identity for this client. In most cases
* it is preferable to instead specift a clientId in the token issued to this client.
*/
clientId?: string;
}
98 changes: 98 additions & 0 deletions common/types/ClientOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { LoggerOptions } from "../lib/util/logger";
import AuthOptions from "./AuthOptions";
import TokenParams from "./TokenParams";
import { Modify } from "./utils";

type Transport = 'web_socket' | 'xhr_streaming' | 'xhr_polling' | 'jsonp' | 'comet';

export default interface ClientOptions extends AuthOptions {
/**
* When true will automatically connect to Ably when library is instanced. This is true by default
*/
autoConnect?: boolean;

defaultTokenParams?: TokenParams;

/**
* When true, messages published on channels by this client will be echoed back to this client.
* This is true by default
*/
echoMessages?: boolean;

/**
* Use this only if you have been provided a dedicated environment by Ably
*/
environment?: string;

/**
* Logger configuration
*/
log?: LoggerOptions;
port?: number;

/**
* When true, messages will be queued whilst the connection is disconnected. True by default.
*/
queueMessages?: boolean;

restHost?: string;
realtimeHost?: string;
fallbackHosts?: string[];
fallbackHostsUseDefault?: boolean;

/**
* Can be used to explicitly recover a connection.
* See https://www.ably.com/documentation/realtime/connection#connection-state-recovery
*/
recover?: boolean | null | string | ((lastConnectionDetails: {
recoveryKey: string;
disconnectedAt: number;
location: string;
clientId: string | null;
}, callback: (shouldRecover: boolean) => void) => void);

/**
* Use a non-secure connection connection. By default, a TLS connection is used to connect to Ably
*/
tls?: boolean;
tlsPort?: number;

/**
* When true, the more efficient MsgPack binary encoding is used.
* When false, JSON text encoding is used.
*/
useBinaryProtocol?: boolean;

disconnectedRetryTimeout?: number;
suspendedRetryTimeout?: number;
closeOnUnload?: boolean;
idempotentRestPublishing?: boolean;
transportParams?: {[k: string]: string};
transports?: Transport[];

httpMaxRetryCount?: number;
restAgentOptions?: { keepAlive: boolean, maxSockets: number };
pushFullWait?: boolean;
checkChannelsOnResume?: boolean;
plugins?: Record<string, unknown>;
agents?: string[];
}

export type DeprecatedClientOptions = Modify<ClientOptions, {
host?: string;
wsHost?: string;
queueEvents?: boolean;
promises?: boolean;
headers?: Record<string, string>;
maxMessageSize?: number;
timeouts?: Record<string, number>;
}>

export type NormalisedClientOptions = Modify<DeprecatedClientOptions, {
realtimeHost: string;
restHost: string;
keyName?: string;
keySecret?: string;
timeouts: Record<string, number>;
maxMessageSize: number;
}>
9 changes: 9 additions & 0 deletions common/types/TokenRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default interface TokenRequest {
capability: string;
clientId?: string;
keyName: string;
mac: string;
nonce: string;
timestamp: number;
ttl?: number;
}
4 changes: 4 additions & 0 deletions common/types/utils.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export type StandardCallback<T> = (err?: ErrorInfo | null, result?: T) => void;
export type ErrCallback = (err?: ErrorInfo | null) => void;
export type PaginatedResultCallback<T> = StandardCallback<PaginatedResult<T>>;
/**
* Use this to override specific property typings on an existing object type
*/
export type Modify<T, R> = Omit<T, keyof R> & R;

0 comments on commit 566efbd

Please sign in to comment.