Skip to content

Commit

Permalink
Split REST functionality into a tree-shakable module
Browse files Browse the repository at this point in the history
This moves the following functionality from BaseRest 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 renders the BaseRest class fairly useless by itself (the above
functionality is pretty much everything it can do) but it allows us to
now construct a BaseRealtime instance that doesn’t have REST
functionality.

Resolves #1374.

Co-authored-by: Owen Pearson <owen.pearson@ably.com>
  • Loading branch information
lawrence-forooghian and owenpearson committed Aug 2, 2023
1 parent 8a723fa commit d74015b
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 171 deletions.
2 changes: 1 addition & 1 deletion scripts/moduleReport.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const esbuild = require('esbuild');

// List of all modules accepted in ModulesMap
const moduleNames = [];
const moduleNames = ['Rest'];

function formatBytes(bytes) {
const kb = bytes / 1024;
Expand Down
8 changes: 6 additions & 2 deletions src/common/lib/client/baserealtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,21 @@ import Message from '../types/message';
import { ModulesMap } from './modulesmap';

class BaseRealtime extends BaseRest {
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();
Expand Down
190 changes: 27 additions & 163 deletions src/common/lib/client/baserest.ts
Original file line number Diff line number Diff line change
@@ -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 Platform from '../../platform';
import Message from '../types/message';
import PresenceMessage from '../types/presencemessage';
import { ModulesMap } from './modulesmap';
import { Rest } from './rest';

const noop = function () {};
class BaseRest {
options: NormalisedClientOptions;
_currentFallback: null | {
Expand All @@ -28,14 +23,10 @@ class BaseRest {
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, 'BaseRest()', msg);
Expand Down Expand Up @@ -82,8 +73,23 @@ class BaseRest {
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) {
Expand All @@ -94,78 +100,11 @@ class BaseRest {
params: RequestParams,
callback: StandardCallback<PaginatedResult<Stats>>
): Promise<PaginatedResult<Stats>> | 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<PaginatedResult<Stats>>;
}
}
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<string, string>,
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<string, string>, callback);
return this.rest.stats(params, callback);
}

time(params?: RequestParams | StandardCallback<number>, callback?: StandardCallback<number>): Promise<number> | 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<number>;
}
}

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<string, string>,
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(
Expand All @@ -177,54 +116,7 @@ class BaseRest {
customHeaders: Record<string, string>,
callback: StandardCallback<HttpPaginatedResponse<unknown>>
): Promise<HttpPaginatedResponse<unknown>> | 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<unknown>
>;
}

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<string, string>, 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<unknown>);
} else {
paginatedResource[_method as HttpMethods.Get | HttpMethods.Delete](
params,
callback as PaginatedResultCallback<unknown>
);
}
return this.rest.request(method, path, version, params, body, customHeaders, callback);
}

setLog(logOptions: LoggerOptions): void {
Expand All @@ -237,32 +129,4 @@ class BaseRest {
static PresenceMessage = PresenceMessage;
}

class Channels {
client: BaseRest;
all: Record<string, Channel>;

constructor(client: BaseRest) {
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 BaseRest;
8 changes: 6 additions & 2 deletions src/common/lib/client/modulesmap.ts
Original file line number Diff line number Diff line change
@@ -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 };
Loading

0 comments on commit d74015b

Please sign in to comment.