From 32e3f8f043de8c44908d5d94231bd31c884844aa Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Wed, 2 Aug 2023 14:48:58 -0300 Subject: [PATCH] Split REST functionality into a tree-shakable module MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This moves the following functionality from BaseClient into a new module named Rest: - methods that wrap REST endpoints (e.g. `stats`) - the `request` method - all functionality accessed via `BaseRest.channels` and `BaseRest.push` This allows us to now construct a BaseRealtime instance that doesn’t have REST functionality. Note that the BaseRest class always includes the Rest module. This comes after discussions in which we decided that it would be quite useless without it. Resolves #1374. Co-authored-by: Owen Pearson --- scripts/moduleReport.js | 4 +- src/common/lib/client/baseclient.ts | 277 +++--------------------- src/common/lib/client/baserealtime.ts | 8 +- src/common/lib/client/baserest.ts | 11 +- src/common/lib/client/modulesmap.ts | 8 +- src/common/lib/client/rest.ts | 295 ++++++++++++++++++++++++++ src/common/types/http.d.ts | 2 +- src/platform/web/modules.ts | 1 + test/browser/modules.test.js | 33 ++- 9 files changed, 381 insertions(+), 258 deletions(-) create mode 100644 src/common/lib/client/rest.ts diff --git a/scripts/moduleReport.js b/scripts/moduleReport.js index 19d1071672..61bdff99e3 100644 --- a/scripts/moduleReport.js +++ b/scripts/moduleReport.js @@ -1,7 +1,7 @@ const esbuild = require('esbuild'); // List of all modules accepted in ModulesMap -const moduleNames = []; +const moduleNames = ['Rest']; function formatBytes(bytes) { const kibibytes = bytes / 1024; @@ -40,7 +40,7 @@ const errors = []; const size = getImportSize([baseClient, moduleName]); console.log(`${baseClient} + ${moduleName}: ${formatBytes(size)}`); - if (!(baseClientSize < size)) { + if (!(baseClientSize < size) && !(baseClient === 'BaseRest' && moduleName === 'Rest')) { // Emit an error if adding the module does not increase the bundle size // (this means that the module is not being tree-shaken correctly). errors.push(new Error(`Adding ${moduleName} to ${baseClient} does not increase the bundle size.`)); diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index 1486df07b5..aeac034d62 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -1,24 +1,19 @@ -import * as Utils from '../util/utils'; import Logger, { LoggerOptions } from '../util/logger'; import Defaults from '../util/defaults'; import Auth from './auth'; -import Push from './push'; -import PaginatedResource, { HttpPaginatedResponse, PaginatedResult } from './paginatedresource'; -import Channel from './channel'; +import { HttpPaginatedResponse, PaginatedResult } from './paginatedresource'; import ErrorInfo from '../types/errorinfo'; import Stats from '../types/stats'; -import HttpMethods from '../../constants/HttpMethods'; -import { ChannelOptions } from '../../types/channel'; -import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; -import { ErrnoException, IHttp, RequestParams } from '../../types/http'; +import { StandardCallback } from '../../types/utils'; +import { IHttp, RequestParams } from '../../types/http'; import ClientOptions, { NormalisedClientOptions } from '../../types/ClientOptions'; import * as API from '../../../../ably'; import Platform from '../../platform'; import Message from '../types/message'; import PresenceMessage from '../types/presencemessage'; -import Resource from './resource'; import { ModulesMap } from './modulesmap'; +import { Rest } from './rest'; type BatchResult = API.Types.BatchResult; type BatchPublishSpec = API.Types.BatchPublishSpec; @@ -29,8 +24,6 @@ type BatchPresenceSuccessResult = API.Types.BatchPresenceSuccessResult; type BatchPresenceFailureResult = API.Types.BatchPresenceFailureResult; type BatchPresenceResult = BatchResult; -const noop = function () {}; - /** `BaseClient` acts as the base class for all of the client classes exported by the SDK. It is an implementation detail and this class is not advertised publicly. */ @@ -43,14 +36,10 @@ class BaseClient { serverTimeOffset: number | null; http: IHttp; auth: Auth; - channels: Channels; - push: Push; - constructor( - options: ClientOptions | string, - // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars - modules: ModulesMap - ) { + private readonly _rest: Rest | null; + + constructor(options: ClientOptions | string, modules: ModulesMap) { if (!options) { const msg = 'no options provided'; Logger.logAction(Logger.LOG_ERROR, 'BaseClient()', msg); @@ -97,8 +86,23 @@ class BaseClient { this.serverTimeOffset = null; this.http = new Platform.Http(normalOptions); this.auth = new Auth(this, normalOptions); - this.channels = new Channels(this); - this.push = new Push(this); + + this._rest = modules.Rest ? new modules.Rest(this) : null; + } + + private get rest(): Rest { + if (!this._rest) { + throw new ErrorInfo('Rest module not provided', 400, 40000); + } + return this._rest; + } + + get channels() { + return this.rest.channels; + } + + get push() { + return this.rest.push; } baseUri(host: string) { @@ -109,78 +113,11 @@ class BaseClient { params: RequestParams, callback: StandardCallback> ): Promise> | void { - /* params and callback are optional; see if params contains the callback */ - if (callback === undefined) { - if (typeof params == 'function') { - callback = params; - params = null; - } else { - return Utils.promisify(this, 'stats', [params]) as Promise>; - } - } - const headers = Defaults.defaultGetHeaders(this.options), - format = this.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - envelope = this.http.supportsLinkHeaders ? undefined : format; - - Utils.mixin(headers, this.options.headers); - - new PaginatedResource(this, '/stats', headers, envelope, function ( - body: unknown, - headers: Record, - unpacked?: boolean - ) { - const statsValues = unpacked ? body : JSON.parse(body as string); - for (let i = 0; i < statsValues.length; i++) statsValues[i] = Stats.fromValues(statsValues[i]); - return statsValues; - }).get(params as Record, callback); + return this.rest.stats(params, callback); } time(params?: RequestParams | StandardCallback, callback?: StandardCallback): Promise | void { - /* params and callback are optional; see if params contains the callback */ - if (callback === undefined) { - if (typeof params == 'function') { - callback = params; - params = null; - } else { - return Utils.promisify(this, 'time', [params]) as Promise; - } - } - - const _callback = callback || noop; - - const headers = Defaults.defaultGetHeaders(this.options); - if (this.options.headers) Utils.mixin(headers, this.options.headers); - const timeUri = (host: string) => { - return this.baseUri(host) + '/time'; - }; - this.http.do( - HttpMethods.Get, - this, - timeUri, - headers, - null, - params as RequestParams, - ( - err?: ErrorInfo | ErrnoException | null, - res?: unknown, - headers?: Record, - unpacked?: boolean - ) => { - if (err) { - _callback(err); - return; - } - if (!unpacked) res = JSON.parse(res as string); - const time = (res as number[])[0]; - if (!time) { - _callback(new ErrorInfo('Internal error (unexpected result type from GET /time)', 50000, 500)); - return; - } - /* calculate time offset only once for this device by adding to the prototype */ - this.serverTimeOffset = time - Utils.now(); - _callback(null, time); - } - ); + return this.rest.time(params, callback); } request( @@ -192,141 +129,17 @@ class BaseClient { customHeaders: Record, callback: StandardCallback> ): Promise> | void { - const useBinary = this.options.useBinaryProtocol, - encoder = useBinary ? Platform.Config.msgpack.encode : JSON.stringify, - decoder = useBinary ? Platform.Config.msgpack.decode : JSON.parse, - format = useBinary ? Utils.Format.msgpack : Utils.Format.json, - envelope = this.http.supportsLinkHeaders ? undefined : format; - params = params || {}; - const _method = method.toLowerCase() as HttpMethods; - const headers = - _method == 'get' - ? Defaults.defaultGetHeaders(this.options, { format, protocolVersion: version }) - : Defaults.defaultPostHeaders(this.options, { format, protocolVersion: version }); - - if (callback === undefined) { - return Utils.promisify(this, 'request', [method, path, version, params, body, customHeaders]) as Promise< - HttpPaginatedResponse - >; - } - - if (typeof body !== 'string') { - body = encoder(body); - } - Utils.mixin(headers, this.options.headers); - if (customHeaders) { - Utils.mixin(headers, customHeaders); - } - const paginatedResource = new PaginatedResource( - this, - path, - headers, - envelope, - async function (resbody: unknown, headers: Record, unpacked?: boolean) { - return Utils.ensureArray(unpacked ? resbody : decoder(resbody as string & Buffer)); - }, - /* useHttpPaginatedResponse: */ true - ); - - if (!Utils.arrIn(Platform.Http.methods, _method)) { - throw new ErrorInfo('Unsupported method ' + _method, 40500, 405); - } - - if (Utils.arrIn(Platform.Http.methodsWithBody, _method)) { - paginatedResource[_method as HttpMethods.Post](params, body, callback as PaginatedResultCallback); - } else { - paginatedResource[_method as HttpMethods.Get | HttpMethods.Delete]( - params, - callback as PaginatedResultCallback - ); - } + return this.rest.request(method, path, version, params, body, customHeaders, callback); } batchPublish( specOrSpecs: T - ): Promise; - batchPublish( - specOrSpecs: T, - callback?: StandardCallback - ): void | Promise { - if (callback === undefined) { - return Utils.promisify(this, 'batchPublish', [specOrSpecs]); - } - - let requestBodyDTO: BatchPublishSpec[]; - let singleSpecMode: boolean; - if (Utils.isArray(specOrSpecs)) { - requestBodyDTO = specOrSpecs; - singleSpecMode = false; - } else { - requestBodyDTO = [specOrSpecs]; - singleSpecMode = true; - } - - const format = this.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Defaults.defaultPostHeaders(this.options, { format }); - - if (this.options.headers) Utils.mixin(headers, this.options.headers); - - const requestBody = Utils.encodeBody(requestBodyDTO, format); - Resource.post( - this, - '/messages', - requestBody, - headers, - { newBatchResponse: 'true' }, - null, - (err, body, headers, unpacked) => { - if (err) { - callback(err); - return; - } - - const batchResults = (unpacked ? body : Utils.decodeBody(body, format)) as BatchPublishResult[]; - - // I don't love the below type assertions for `callback` but not sure how to avoid them - if (singleSpecMode) { - (callback as StandardCallback)(null, batchResults[0]); - } else { - (callback as StandardCallback)(null, batchResults); - } - } - ); + ): Promise { + return this.rest.batchPublish(specOrSpecs); } - batchPresence(channels: string[]): Promise; - batchPresence( - channels: string[], - callback?: StandardCallback - ): void | Promise { - if (callback === undefined) { - return Utils.promisify(this, 'batchPresence', [channels]); - } - - const format = this.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, - headers = Defaults.defaultPostHeaders(this.options, { format }); - - if (this.options.headers) Utils.mixin(headers, this.options.headers); - - const channelsParam = channels.join(','); - - Resource.get( - this, - '/presence', - headers, - { newBatchResponse: 'true', channels: channelsParam }, - null, - (err, body, headers, unpacked) => { - if (err) { - callback(err); - return; - } - - const batchResult = (unpacked ? body : Utils.decodeBody(body, format)) as BatchPresenceResult; - - callback(null, batchResult); - } - ); + batchPresence(channels: string[]): Promise { + return this.rest.batchPresence(channels); } setLog(logOptions: LoggerOptions): void { @@ -339,32 +152,4 @@ class BaseClient { static PresenceMessage = PresenceMessage; } -class Channels { - client: BaseClient; - all: Record; - - constructor(client: BaseClient) { - this.client = client; - this.all = Object.create(null); - } - - get(name: string, channelOptions?: ChannelOptions) { - name = String(name); - let channel = this.all[name]; - if (!channel) { - this.all[name] = channel = new Channel(this.client, name, channelOptions); - } else if (channelOptions) { - channel.setOptions(channelOptions); - } - - return channel; - } - - /* Included to support certain niche use-cases; most users should ignore this. - * Please do not use this unless you know what you're doing */ - release(name: string) { - delete this.all[String(name)]; - } -} - export default BaseClient; diff --git a/src/common/lib/client/baserealtime.ts b/src/common/lib/client/baserealtime.ts index 74057b9a58..fb7e888bb0 100644 --- a/src/common/lib/client/baserealtime.ts +++ b/src/common/lib/client/baserealtime.ts @@ -18,17 +18,21 @@ import { ModulesMap } from './modulesmap'; `BaseRealtime` is an export of the tree-shakable version of the SDK, and acts as the base class for the `DefaultRealtime` class exported by the non tree-shakable version. */ class BaseRealtime extends BaseClient { - channels: any; + _channels: any; connection: Connection; constructor(options: ClientOptions, modules: ModulesMap) { super(options, modules); Logger.logAction(Logger.LOG_MINOR, 'Realtime()', ''); this.connection = new Connection(this, this.options); - this.channels = new Channels(this); + this._channels = new Channels(this); if (options.autoConnect !== false) this.connect(); } + get channels() { + return this._channels; + } + connect(): void { Logger.logAction(Logger.LOG_MINOR, 'Realtime.connect()', ''); this.connection.connect(); diff --git a/src/common/lib/client/baserest.ts b/src/common/lib/client/baserest.ts index 6073aafd00..247145f989 100644 --- a/src/common/lib/client/baserest.ts +++ b/src/common/lib/client/baserest.ts @@ -1,6 +1,15 @@ import BaseClient from './baseclient'; +import ClientOptions from '../../types/ClientOptions'; +import { ModulesMap } from './modulesmap'; +import { Rest } from './rest'; /** `BaseRest` is an export of the tree-shakable version of the SDK, and acts as the base class for the `DefaultRest` class exported by the non tree-shakable version. + + It always includes the `Rest` module. */ -export class BaseRest extends BaseClient {} +export class BaseRest extends BaseClient { + constructor(options: ClientOptions | string, modules: ModulesMap) { + super(options, { Rest, ...modules }); + } +} diff --git a/src/common/lib/client/modulesmap.ts b/src/common/lib/client/modulesmap.ts index 13b207d8c5..c56c2d98e7 100644 --- a/src/common/lib/client/modulesmap.ts +++ b/src/common/lib/client/modulesmap.ts @@ -1,3 +1,7 @@ -export interface ModulesMap {} +import { Rest } from './rest'; -export const allCommonModules: ModulesMap = {}; +export interface ModulesMap { + Rest?: typeof Rest; +} + +export const allCommonModules: ModulesMap = { Rest }; diff --git a/src/common/lib/client/rest.ts b/src/common/lib/client/rest.ts new file mode 100644 index 0000000000..a9d11b3c3b --- /dev/null +++ b/src/common/lib/client/rest.ts @@ -0,0 +1,295 @@ +import * as Utils from '../util/utils'; +import Logger, { LoggerOptions } from '../util/logger'; +import Defaults from '../util/defaults'; +import Push from './push'; +import PaginatedResource, { HttpPaginatedResponse, PaginatedResult } from './paginatedresource'; +import Channel from './channel'; +import ErrorInfo from '../types/errorinfo'; +import Stats from '../types/stats'; +import HttpMethods from '../../constants/HttpMethods'; +import { ChannelOptions } from '../../types/channel'; +import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; +import { ErrnoException, RequestParams } from '../../types/http'; +import * as API from '../../../../ably'; +import Resource from './resource'; + +import Platform from '../../platform'; +import BaseClient from './baseclient'; + +type BatchResult = API.Types.BatchResult; +type BatchPublishSpec = API.Types.BatchPublishSpec; +type BatchPublishSuccessResult = API.Types.BatchPublishSuccessResult; +type BatchPublishFailureResult = API.Types.BatchPublishFailureResult; +type BatchPublishResult = BatchResult; +type BatchPresenceSuccessResult = API.Types.BatchPresenceSuccessResult; +type BatchPresenceFailureResult = API.Types.BatchPresenceFailureResult; +type BatchPresenceResult = BatchResult; + +const noop = function () {}; +export class Rest { + private readonly client: BaseClient; + readonly channels: Channels; + readonly push: Push; + + constructor(client: BaseClient) { + this.client = client; + this.channels = new Channels(this.client); + this.push = new Push(this.client); + } + + stats( + params: RequestParams, + callback: StandardCallback> + ): Promise> | void { + /* params and callback are optional; see if params contains the callback */ + if (callback === undefined) { + if (typeof params == 'function') { + callback = params; + params = null; + } else { + return Utils.promisify(this, 'stats', [params]) as Promise>; + } + } + const headers = Defaults.defaultGetHeaders(this.client.options), + format = this.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + envelope = this.client.http.supportsLinkHeaders ? undefined : format; + + Utils.mixin(headers, this.client.options.headers); + + new PaginatedResource(this.client, '/stats', headers, envelope, function ( + body: unknown, + headers: Record, + unpacked?: boolean + ) { + const statsValues = unpacked ? body : JSON.parse(body as string); + for (let i = 0; i < statsValues.length; i++) statsValues[i] = Stats.fromValues(statsValues[i]); + return statsValues; + }).get(params as Record, callback); + } + + time(params?: RequestParams | StandardCallback, callback?: StandardCallback): Promise | void { + /* params and callback are optional; see if params contains the callback */ + if (callback === undefined) { + if (typeof params == 'function') { + callback = params; + params = null; + } else { + return Utils.promisify(this, 'time', [params]) as Promise; + } + } + + const _callback = callback || noop; + + const headers = Defaults.defaultGetHeaders(this.client.options); + if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); + const timeUri = (host: string) => { + return this.client.baseUri(host) + '/time'; + }; + this.client.http.do( + HttpMethods.Get, + this.client, + timeUri, + headers, + null, + params as RequestParams, + ( + err?: ErrorInfo | ErrnoException | null, + res?: unknown, + headers?: Record, + unpacked?: boolean + ) => { + if (err) { + _callback(err); + return; + } + if (!unpacked) res = JSON.parse(res as string); + const time = (res as number[])[0]; + if (!time) { + _callback(new ErrorInfo('Internal error (unexpected result type from GET /time)', 50000, 500)); + return; + } + /* calculate time offset only once for this device by adding to the prototype */ + this.client.serverTimeOffset = time - Utils.now(); + _callback(null, time); + } + ); + } + + request( + method: string, + path: string, + version: number, + params: RequestParams, + body: unknown, + customHeaders: Record, + callback: StandardCallback> + ): Promise> | void { + const useBinary = this.client.options.useBinaryProtocol, + encoder = useBinary ? Platform.Config.msgpack.encode : JSON.stringify, + decoder = useBinary ? Platform.Config.msgpack.decode : JSON.parse, + format = useBinary ? Utils.Format.msgpack : Utils.Format.json, + envelope = this.client.http.supportsLinkHeaders ? undefined : format; + params = params || {}; + const _method = method.toLowerCase() as HttpMethods; + const headers = + _method == 'get' + ? Defaults.defaultGetHeaders(this.client.options, { format, protocolVersion: version }) + : Defaults.defaultPostHeaders(this.client.options, { format, protocolVersion: version }); + + if (callback === undefined) { + return Utils.promisify(this, 'request', [method, path, version, params, body, customHeaders]) as Promise< + HttpPaginatedResponse + >; + } + + if (typeof body !== 'string') { + body = encoder(body); + } + Utils.mixin(headers, this.client.options.headers); + if (customHeaders) { + Utils.mixin(headers, customHeaders); + } + const paginatedResource = new PaginatedResource( + this.client, + path, + headers, + envelope, + async function (resbody: unknown, headers: Record, unpacked?: boolean) { + return Utils.ensureArray(unpacked ? resbody : decoder(resbody as string & Buffer)); + }, + /* useHttpPaginatedResponse: */ true + ); + + if (!Utils.arrIn(Platform.Http.methods, _method)) { + throw new ErrorInfo('Unsupported method ' + _method, 40500, 405); + } + + if (Utils.arrIn(Platform.Http.methodsWithBody, _method)) { + paginatedResource[_method as HttpMethods.Post](params, body, callback as PaginatedResultCallback); + } else { + paginatedResource[_method as HttpMethods.Get | HttpMethods.Delete]( + params, + callback as PaginatedResultCallback + ); + } + } + + batchPublish( + specOrSpecs: T + ): Promise; + batchPublish( + specOrSpecs: T, + callback?: StandardCallback + ): void | Promise { + if (callback === undefined) { + return Utils.promisify(this, 'batchPublish', [specOrSpecs]); + } + + let requestBodyDTO: BatchPublishSpec[]; + let singleSpecMode: boolean; + if (Utils.isArray(specOrSpecs)) { + requestBodyDTO = specOrSpecs; + singleSpecMode = false; + } else { + requestBodyDTO = [specOrSpecs]; + singleSpecMode = true; + } + + const format = this.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + headers = Defaults.defaultPostHeaders(this.client.options, { format }); + + if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); + + const requestBody = Utils.encodeBody(requestBodyDTO, format); + Resource.post( + this.client, + '/messages', + requestBody, + headers, + { newBatchResponse: 'true' }, + null, + (err, body, headers, unpacked) => { + if (err) { + callback(err); + return; + } + + const batchResults = (unpacked ? body : Utils.decodeBody(body, format)) as BatchPublishResult[]; + + // I don't love the below type assertions for `callback` but not sure how to avoid them + if (singleSpecMode) { + (callback as StandardCallback)(null, batchResults[0]); + } else { + (callback as StandardCallback)(null, batchResults); + } + } + ); + } + + batchPresence(channels: string[]): Promise; + batchPresence( + channels: string[], + callback?: StandardCallback + ): void | Promise { + if (callback === undefined) { + return Utils.promisify(this, 'batchPresence', [channels]); + } + + const format = this.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json, + headers = Defaults.defaultPostHeaders(this.client.options, { format }); + + if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers); + + const channelsParam = channels.join(','); + + Resource.get( + this.client, + '/presence', + headers, + { newBatchResponse: 'true', channels: channelsParam }, + null, + (err, body, headers, unpacked) => { + if (err) { + callback(err); + return; + } + + const batchResult = (unpacked ? body : Utils.decodeBody(body, format)) as BatchPresenceResult; + + callback(null, batchResult); + } + ); + } + + setLog(logOptions: LoggerOptions): void { + Logger.setLog(logOptions.level, logOptions.handler); + } +} + +class Channels { + client: BaseClient; + all: Record; + + constructor(client: BaseClient) { + this.client = client; + this.all = Object.create(null); + } + + get(name: string, channelOptions?: ChannelOptions) { + name = String(name); + let channel = this.all[name]; + if (!channel) { + this.all[name] = channel = new Channel(this.client, name, channelOptions); + } else if (channelOptions) { + channel.setOptions(channelOptions); + } + + return channel; + } + + /* Included to support certain niche use-cases; most users should ignore this. + * Please do not use this unless you know what you're doing */ + release(name: string) { + delete this.all[String(name)]; + } +} diff --git a/src/common/types/http.d.ts b/src/common/types/http.d.ts index 16be6249cd..0727978b52 100644 --- a/src/common/types/http.d.ts +++ b/src/common/types/http.d.ts @@ -1,5 +1,5 @@ import HttpMethods from '../constants/HttpMethods'; -import BaseClient from '../lib/client/baseclient'; +import { BaseClient } from '../lib/client/baseclient'; import ErrorInfo from '../lib/types/errorinfo'; import { Agents } from 'got'; diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index e1031a9c17..8054981daf 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -47,4 +47,5 @@ if (Platform.Config.noUpgrade) { Platform.Defaults.upgradeTransports = []; } +export { Rest } from '../../common/lib/client/rest'; export { BaseRest, BaseRealtime, ErrorInfo }; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index 755d70063f..6ccbf64bbc 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -1,4 +1,4 @@ -import { BaseRest, BaseRealtime } from '../../build/modules/index.js'; +import { BaseRest, BaseRealtime, Rest } from '../../build/modules/index.js'; describe('browser/modules', function () { this.timeout(10 * 1000); @@ -12,11 +12,36 @@ describe('browser/modules', function () { describe('without any modules', () => { for (const clientClass of [BaseRest, BaseRealtime]) { - it(clientClass.name, async () => { - const client = new clientClass(ablyClientOptions()); + describe(clientClass.name, () => { + it('can be constructed', async () => { + expect(() => new clientClass(ablyClientOptions(), {})).not.to.throw(); + }); + }); + } + }); + + describe('Rest', () => { + describe('BaseRest without explicit Rest', () => { + it('offers REST functionality', async () => { + const client = new BaseRest(ablyClientOptions(), {}); const time = await client.time(); expect(time).to.be.a('number'); }); - } + }); + + describe('BaseRealtime with Rest', () => { + it('offers REST functionality', async () => { + const client = new BaseRealtime(ablyClientOptions(), { Rest }); + const time = await client.time(); + expect(time).to.be.a('number'); + }); + }); + + describe('BaseRealtime without Rest', () => { + it('throws an error when attempting to use REST functionality', async () => { + const client = new BaseRealtime(ablyClientOptions(), {}); + expect(() => client.time()).to.throw('Rest module not provided'); + }); + }); }); });