From 4f660642d112581f364b6617aa96fec09f2e97a4 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Fri, 5 Nov 2021 15:35:49 +0000 Subject: [PATCH 1/3] Add typings for ClientOptions --- common/lib/util/defaults.ts | 69 +++++++++++++------------ common/lib/util/logger.ts | 6 +++ common/types/AuthOptions.ts | 33 ++++++++++++ common/types/ClientOptions.ts | 97 +++++++++++++++++++++++++++++++++++ common/types/TokenRequest.ts | 9 ++++ common/types/utils.d.ts | 1 + 6 files changed, 182 insertions(+), 33 deletions(-) create mode 100644 common/types/AuthOptions.ts create mode 100644 common/types/ClientOptions.ts create mode 100644 common/types/TokenRequest.ts diff --git a/common/lib/util/defaults.ts b/common/lib/util/defaults.ts index 38b9525dba..2fdcc4b1b9 100644 --- a/common/lib/util/defaults.ts +++ b/common/lib/util/defaults.ts @@ -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) { @@ -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)); } @@ -107,6 +106,26 @@ 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 = {}; + for(const prop in Defaults.TIMEOUTS) { + timeouts[prop] = (options as Record)[prop] || (Defaults.TIMEOUTS as Record)[prop]; + } + return timeouts; +} + export function objectifyOptions(options: ClientOptions | string): ClientOptions { if(typeof options == 'string') { return (options.indexOf(':') == -1) ? {token: options} : {key: options}; @@ -114,7 +133,7 @@ export function objectifyOptions(options: ClientOptions | string): ClientOptions return options; } -export function normaliseOptions(options: ClientOptions): ClientOptions { +export function normaliseOptions(options: DeprecatedClientOptions): NormalisedClientOptions { /* Deprecated options */ if(options.host) { Logger.deprecated('host', 'restHost'); @@ -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)[prop]; - }; + const timeouts = getTimeouts(options); if('useBinaryProtocol' in options) { options.useBinaryProtocol = Platform.supportsBinary && options.useBinaryProtocol; @@ -236,13 +238,14 @@ export function normaliseOptions(options: ClientOptions): ClientOptions { options.promises = false; } - if(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; diff --git a/common/lib/util/logger.ts b/common/lib/util/logger.ts index 17a7d1b6d0..79f861dc31 100644 --- a/common/lib/util/logger.ts +++ b/common/lib/util/logger.ts @@ -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, diff --git a/common/types/AuthOptions.ts b/common/types/AuthOptions.ts new file mode 100644 index 0000000000..5d3969ea09 --- /dev/null +++ b/common/types/AuthOptions.ts @@ -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; +} diff --git a/common/types/ClientOptions.ts b/common/types/ClientOptions.ts new file mode 100644 index 0000000000..3abaf5c978 --- /dev/null +++ b/common/types/ClientOptions.ts @@ -0,0 +1,97 @@ +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; +} + +export type DeprecatedClientOptions = Modify; + maxMessageSize?: number; + timeouts?: Record; +}> + +export type NormalisedClientOptions = Modify; + maxMessageSize: number; +}> diff --git a/common/types/TokenRequest.ts b/common/types/TokenRequest.ts new file mode 100644 index 0000000000..66cf051107 --- /dev/null +++ b/common/types/TokenRequest.ts @@ -0,0 +1,9 @@ +export default interface TokenRequest { + capability: string; + clientId?: string; + keyName: string; + mac: string; + nonce: string; + timestamp: number; + ttl?: number; +} diff --git a/common/types/utils.d.ts b/common/types/utils.d.ts index b72340040c..a8307f96c6 100644 --- a/common/types/utils.d.ts +++ b/common/types/utils.d.ts @@ -1,3 +1,4 @@ export type StandardCallback = (err?: ErrorInfo | null, result?: T) => void; export type ErrCallback = (err?: ErrorInfo | null) => void; export type PaginatedResultCallback = StandardCallback>; +export type Modify = Omit & R; From bf9ad16552f124a5db8816a594a76e16f9288ca6 Mon Sep 17 00:00:00 2001 From: Owen Pearson Date: Mon, 8 Nov 2021 15:35:02 +0000 Subject: [PATCH 2/3] Add missing normalisation logic for custom agent client option --- common/lib/util/defaults.ts | 6 ++++++ common/types/ClientOptions.ts | 1 + 2 files changed, 7 insertions(+) diff --git a/common/lib/util/defaults.ts b/common/lib/util/defaults.ts index 2fdcc4b1b9..3a6634eed5 100644 --- a/common/lib/util/defaults.ts +++ b/common/lib/util/defaults.ts @@ -238,6 +238,12 @@ export function normaliseOptions(options: DeprecatedClientOptions): NormalisedCl options.promises = false; } + if(options.agents) { + for (var key in options.agents) { + Defaults.agent += ' ' + key + '/' + options.agents[key]; + } + } + return { ...options, useBinaryProtocol: ('useBinaryProtocol' in options) ? Platform.supportsBinary && options.useBinaryProtocol : Platform.preferBinary, diff --git a/common/types/ClientOptions.ts b/common/types/ClientOptions.ts index 3abaf5c978..7d70ad13db 100644 --- a/common/types/ClientOptions.ts +++ b/common/types/ClientOptions.ts @@ -75,6 +75,7 @@ export default interface ClientOptions extends AuthOptions { pushFullWait?: boolean; checkChannelsOnResume?: boolean; plugins?: Record; + agents?: string[]; } export type DeprecatedClientOptions = Modify Date: Wed, 24 Nov 2021 14:00:47 +0000 Subject: [PATCH 3/3] Add comment explaining usage of the Modify generic type --- common/types/utils.d.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/types/utils.d.ts b/common/types/utils.d.ts index a8307f96c6..5408d61094 100644 --- a/common/types/utils.d.ts +++ b/common/types/utils.d.ts @@ -1,4 +1,7 @@ export type StandardCallback = (err?: ErrorInfo | null, result?: T) => void; export type ErrCallback = (err?: ErrorInfo | null) => void; export type PaginatedResultCallback = StandardCallback>; +/** + * Use this to override specific property typings on an existing object type + */ export type Modify = Omit & R;