Skip to content

Commit

Permalink
Make MessagePack functionality tree-shakable
Browse files Browse the repository at this point in the history
We move the MessagePack functionality into a tree-shakable MsgPack
module.

Resolves #1375.
  • Loading branch information
lawrence-forooghian committed Oct 26, 2023
1 parent 6d36786 commit badeeb7
Show file tree
Hide file tree
Showing 28 changed files with 183 additions and 37 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 = ['Rest', 'Crypto'];
const moduleNames = ['Rest', 'Crypto', 'MsgPack'];

// List of all free-standing functions exported by the library along with the
// ModulesMap entries that we expect them to transitively import
Expand Down
6 changes: 4 additions & 2 deletions src/common/lib/client/baseclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ModulesMap } from './modulesmap';
import { Rest } from './rest';
import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic';
import { throwMissingModuleError } from '../util/utils';
import { MsgPack } from 'common/types/msgpack';

/**
`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.
Expand All @@ -30,7 +31,7 @@ class BaseClient {

private readonly _rest: Rest | null;
readonly _Crypto: IUntypedCryptoStatic | null;
readonly _MsgPack = Platform.Config.msgpack;
readonly _MsgPack: MsgPack | null;

constructor(options: ClientOptions | string, modules: ModulesMap) {
if (!options) {
Expand All @@ -47,7 +48,8 @@ class BaseClient {
'initialized with clientOptions ' + Platform.Config.inspect(options)
);

const normalOptions = (this.options = Defaults.normaliseOptions(optionsObj));
this._MsgPack = modules.MsgPack ?? null;
const normalOptions = (this.options = Defaults.normaliseOptions(optionsObj, this._MsgPack));

/* process options */
if (normalOptions.key) {
Expand Down
10 changes: 9 additions & 1 deletion src/common/lib/client/defaultrealtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@ import ConnectionManager from '../transport/connectionmanager';
import ProtocolMessage from '../types/protocolmessage';
import Platform from 'common/platform';
import { DefaultMessage } from '../types/defaultmessage';
import { MsgPack } from 'common/types/msgpack';

/**
`DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version.
*/
export class DefaultRealtime extends BaseRealtime {
constructor(options: ClientOptions) {
super(options, { ...allCommonModules, Crypto: DefaultRealtime.Crypto ?? undefined });
const MsgPack = DefaultRealtime._MsgPack;
if (!MsgPack) {
throw new Error('Expected DefaultRealtime._MsgPack to have been set');
}

super(options, { ...allCommonModules, Crypto: DefaultRealtime.Crypto ?? undefined, MsgPack });
}

static Utils = Utils;
Expand All @@ -32,4 +38,6 @@ export class DefaultRealtime extends BaseRealtime {
}

static Message = DefaultMessage;

static _MsgPack: MsgPack | null = null;
}
14 changes: 13 additions & 1 deletion src/common/lib/client/defaultrest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@ import ClientOptions from '../../types/ClientOptions';
import { allCommonModules } from './modulesmap';
import Platform from 'common/platform';
import { DefaultMessage } from '../types/defaultmessage';
import { MsgPack } from 'common/types/msgpack';

/**
`DefaultRest` is the class that the non tree-shakable version of the SDK exports as `Rest`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version.
*/
export class DefaultRest extends BaseRest {
constructor(options: ClientOptions | string) {
super(options, { ...allCommonModules, Crypto: DefaultRest.Crypto ?? undefined });
const MsgPack = DefaultRest._MsgPack;
if (!MsgPack) {
throw new Error('Expected DefaultRest._MsgPack to have been set');
}

super(options, {
...allCommonModules,
Crypto: DefaultRest.Crypto ?? undefined,
MsgPack: DefaultRest._MsgPack ?? undefined,
});
}

private static _Crypto: typeof Platform.Crypto = null;
Expand All @@ -25,4 +35,6 @@ export class DefaultRest extends BaseRest {
}

static Message = DefaultMessage;

static _MsgPack: MsgPack | null = null;
}
2 changes: 2 additions & 0 deletions src/common/lib/client/modulesmap.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Rest } from './rest';
import { IUntypedCryptoStatic } from '../../types/ICryptoStatic';
import { MsgPack } from 'common/types/msgpack';

export interface ModulesMap {
Rest?: typeof Rest;
Crypto?: IUntypedCryptoStatic;
MsgPack?: MsgPack;
}

export const allCommonModules: ModulesMap = { Rest };
5 changes: 4 additions & 1 deletion src/common/lib/client/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function withAuthDetails(

function unenvelope<T>(
callback: ResourceCallback<T>,
MsgPack: MsgPack,
MsgPack: MsgPack | null,
format: Utils.Format | null
): ResourceCallback<T> {
return (err, body, outerHeaders, unpacked, outerStatusCode) => {
Expand Down Expand Up @@ -226,6 +226,9 @@ class Resource {
let decodedBody = body;
if (headers['content-type']?.indexOf('msgpack') > 0) {
try {
if (!client._MsgPack) {
Utils.throwMissingModuleError('MsgPack');
}
decodedBody = client._MsgPack.decode(body as Buffer);
} catch (decodeErr) {
Logger.logAction(
Expand Down
16 changes: 11 additions & 5 deletions src/common/lib/client/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,17 @@ export class Rest {
customHeaders: Record<string, string>,
callback: StandardCallback<HttpPaginatedResponse<unknown>>
): Promise<HttpPaginatedResponse<unknown>> | void {
const useBinary = this.client.options.useBinaryProtocol,
encoder = useBinary ? this.client._MsgPack.encode : JSON.stringify,
decoder = useBinary ? this.client._MsgPack.decode : JSON.parse,
format = useBinary ? Utils.Format.msgpack : Utils.Format.json,
envelope = this.client.http.supportsLinkHeaders ? undefined : format;
const [encoder, decoder, format] = (() => {
if (this.client.options.useBinaryProtocol) {
if (!this.client._MsgPack) {
Utils.throwMissingModuleError('MsgPack');
}
return [this.client._MsgPack.encode, this.client._MsgPack.decode, Utils.Format.msgpack];
} else {
return [JSON.stringify, JSON.parse, Utils.Format.json];
}
})();
const envelope = this.client.http.supportsLinkHeaders ? undefined : format;
params = params || {};
const _method = method.toLowerCase() as HttpMethods;
const headers =
Expand Down
2 changes: 1 addition & 1 deletion src/common/lib/types/devicedetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ class DeviceDetails {

static fromResponseBody(
body: Array<Record<string, unknown>> | Record<string, unknown>,
MsgPack: MsgPack,
MsgPack: MsgPack | null,
format?: Utils.Format
): DeviceDetails | DeviceDetails[] {
if (format) {
Expand Down
2 changes: 1 addition & 1 deletion src/common/lib/types/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ class Message {
static async fromResponseBody(
body: Array<Message>,
options: ChannelOptions | EncodingDecodingContext,
MsgPack: MsgPack,
MsgPack: MsgPack | null,
format?: Utils.Format
): Promise<Message[]> {
if (format) {
Expand Down
2 changes: 1 addition & 1 deletion src/common/lib/types/presencemessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class PresenceMessage {
static async fromResponseBody(
body: Record<string, unknown>[],
options: CipherOptions,
MsgPack: MsgPack,
MsgPack: MsgPack | null,
format?: Utils.Format
): Promise<PresenceMessage[]> {
const messages: PresenceMessage[] = [];
Expand Down
2 changes: 1 addition & 1 deletion src/common/lib/types/pushchannelsubscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class PushChannelSubscription {

static fromResponseBody(
body: Array<Record<string, unknown>> | Record<string, unknown>,
MsgPack: MsgPack,
MsgPack: MsgPack | null,
format?: Utils.Format
): PushChannelSubscription | PushChannelSubscription[] {
if (format) {
Expand Down
15 changes: 10 additions & 5 deletions src/common/lib/util/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ErrorInfo from 'common/lib/types/errorinfo';
import { version } from '../../../../package.json';
import ClientOptions, { InternalClientOptions, NormalisedClientOptions } from 'common/types/ClientOptions';
import IDefaults from '../../types/IDefaults';
import { MsgPack } from 'common/types/msgpack';

let agent = 'ably-js/' + version;

Expand Down Expand Up @@ -41,7 +42,7 @@ type CompleteDefaults = IDefaults & {
checkHost(host: string): void;
getRealtimeHost(options: ClientOptions, production: boolean, environment: string): string;
objectifyOptions(options: ClientOptions | string): ClientOptions;
normaliseOptions(options: InternalClientOptions): NormalisedClientOptions;
normaliseOptions(options: InternalClientOptions, MsgPack: MsgPack | null): NormalisedClientOptions;
defaultGetHeaders(options: NormalisedClientOptions, headersOptions?: HeadersOptions): Record<string, string>;
defaultPostHeaders(options: NormalisedClientOptions, headersOptions?: HeadersOptions): Record<string, string>;
};
Expand Down Expand Up @@ -185,7 +186,7 @@ export function objectifyOptions(options: ClientOptions | string): ClientOptions
return options;
}

export function normaliseOptions(options: InternalClientOptions): NormalisedClientOptions {
export function normaliseOptions(options: InternalClientOptions, MsgPack: MsgPack | null): NormalisedClientOptions {
if (typeof options.recover === 'function' && options.closeOnUnload === true) {
Logger.logAction(
Logger.LOG_ERROR,
Expand Down Expand Up @@ -222,10 +223,14 @@ export function normaliseOptions(options: InternalClientOptions): NormalisedClie

const timeouts = getTimeouts(options);

if ('useBinaryProtocol' in options) {
options.useBinaryProtocol = Platform.Config.supportsBinary && options.useBinaryProtocol;
if (MsgPack) {
if ('useBinaryProtocol' in options) {
options.useBinaryProtocol = Platform.Config.supportsBinary && options.useBinaryProtocol;
} else {
options.useBinaryProtocol = Platform.Config.preferBinary;
}
} else {
options.useBinaryProtocol = Platform.Config.preferBinary;
options.useBinaryProtocol = false;
}

const headers: Record<string, string> = {};
Expand Down
22 changes: 18 additions & 4 deletions src/common/lib/util/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,12 +452,26 @@ export function promisify<T>(ob: Record<string, any>, fnName: string, args: IArg
});
}

export function decodeBody<T>(body: unknown, MsgPack: MsgPack, format?: Format | null): T {
return format == 'msgpack' ? MsgPack.decode(body as Buffer) : JSON.parse(String(body));
export function decodeBody<T>(body: unknown, MsgPack: MsgPack | null, format?: Format | null): T {
if (format == 'msgpack') {
if (!MsgPack) {
throwMissingModuleError('MsgPack');
}
return MsgPack.decode(body as Buffer);
}

return JSON.parse(String(body));
}

export function encodeBody(body: unknown, MsgPack: MsgPack, format?: Format): string | Buffer {
return format == 'msgpack' ? (MsgPack.encode(body, true) as Buffer) : JSON.stringify(body);
export function encodeBody(body: unknown, MsgPack: MsgPack | null, format?: Format): string | Buffer {
if (format == 'msgpack') {
if (!MsgPack) {
throwMissingModuleError('MsgPack');
}
return MsgPack.encode(body, true) as Buffer;
}

return JSON.stringify(body);
}

export function allToLowerCase(arr: Array<string>): Array<string> {
Expand Down
3 changes: 0 additions & 3 deletions src/common/types/IPlatformConfig.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { MsgPack } from './msgpack';

export interface IPlatformConfig {
agent: string;
logTimestamps: boolean;
binaryType: BinaryType;
WebSocket: typeof WebSocket | typeof import('ws');
useProtocolHeartbeats: boolean;
msgpack: MsgPack;
supportsBinary: boolean;
preferBinary: boolean;
nextTick: process.nextTick;
Expand Down
2 changes: 0 additions & 2 deletions src/platform/nativescript/config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
/* eslint-disable no-undef */
import msgpack from '../web/lib/util/msgpack';
require('nativescript-websockets');

var randomBytes;
Expand Down Expand Up @@ -28,7 +27,6 @@ var Config = {
allowComet: true,
streamingSupported: false,
useProtocolHeartbeats: true,
msgpack: msgpack,
supportsBinary: typeof TextDecoder !== 'undefined' && TextDecoder,
preferBinary: false,
ArrayBuffer: ArrayBuffer,
Expand Down
1 change: 1 addition & 0 deletions src/platform/nativescript/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Platform.WebStorage = WebStorage;

for (const clientClass of [DefaultRest, DefaultRealtime]) {
clientClass.Crypto = Crypto;
clientClass._MsgPack = msgpack;
}

Logger.initLogHandlers();
Expand Down
1 change: 0 additions & 1 deletion src/platform/nodejs/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ const Config: IPlatformConfig = {
binaryType: 'nodebuffer' as BinaryType,
WebSocket,
useProtocolHeartbeats: false,
msgpack: require('@ably/msgpack-js'),
supportsBinary: true,
preferBinary: true,
nextTick: process.nextTick,
Expand Down
2 changes: 2 additions & 0 deletions src/platform/nodejs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Transports from './lib/transport';
import Logger from '../../common/lib/util/logger';
import { getDefaults } from '../../common/lib/util/defaults';
import PlatformDefaults from './lib/util/defaults';
import msgpack = require('@ably/msgpack-js');

const Crypto = createCryptoClass(BufferUtils);

Expand All @@ -26,6 +27,7 @@ Platform.WebStorage = null;

for (const clientClass of [DefaultRest, DefaultRealtime]) {
clientClass.Crypto = Crypto;
clientClass._MsgPack = msgpack;
}

Logger.initLogHandlers();
Expand Down
6 changes: 3 additions & 3 deletions src/platform/nodejs/lib/util/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import BaseClient from 'common/lib/client/baseclient';
import BaseRealtime from 'common/lib/client/baserealtime';
import { NormalisedClientOptions, RestAgentOptions } from 'common/types/ClientOptions';
import { isSuccessCode } from 'common/constants/HttpStatusCodes';
import { shallowEquals } from 'common/lib/util/utils';
import { shallowEquals, throwMissingModuleError } from 'common/lib/util/utils';

/***************************************************
*
Expand Down Expand Up @@ -42,8 +42,8 @@ const handler = function (uri: string, params: unknown, client: BaseClient | nul
body = JSON.parse(body as string);
break;
case 'application/x-msgpack':
if (!client) {
throw new ErrorInfo('Cannot use MessagePack without a client', 400, 40000);
if (!client?._MsgPack) {
throwMissingModuleError('MsgPack');
}
body = client._MsgPack.decode(body as Buffer);
}
Expand Down
2 changes: 0 additions & 2 deletions src/platform/react-native/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import msgpack from '../web/lib/util/msgpack';
import { IPlatformConfig } from '../../common/types/IPlatformConfig';
import BufferUtils from '../web/lib/util/bufferutils';

Expand All @@ -13,7 +12,6 @@ export default function (bufferUtils: typeof BufferUtils): IPlatformConfig {
allowComet: true,
streamingSupported: true,
useProtocolHeartbeats: true,
msgpack: msgpack,
supportsBinary: !!(typeof TextDecoder !== 'undefined' && TextDecoder),
preferBinary: false,
ArrayBuffer: typeof ArrayBuffer !== 'undefined' && ArrayBuffer,
Expand Down
1 change: 1 addition & 0 deletions src/platform/react-native/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Platform.WebStorage = WebStorage;

for (const clientClass of [DefaultRest, DefaultRealtime]) {
clientClass.Crypto = Crypto;
clientClass._MsgPack = msgpack;
}

Logger.initLogHandlers();
Expand Down
4 changes: 4 additions & 0 deletions src/platform/web-noencryption/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ Platform.Config = Config;
Platform.Transports = Transports;
Platform.WebStorage = WebStorage;

for (const clientClass of [DefaultRest, DefaultRealtime]) {
clientClass._MsgPack = msgpack;
}

Logger.initLogHandlers();

Platform.Defaults = getDefaults(PlatformDefaults);
Expand Down
2 changes: 0 additions & 2 deletions src/platform/web/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import msgpack from './lib/util/msgpack';
import { IPlatformConfig } from '../../common/types/IPlatformConfig';
import * as Utils from 'common/lib/util/utils';

Expand Down Expand Up @@ -37,7 +36,6 @@ const Config: IPlatformConfig = {
allowComet: allowComet(),
streamingSupported: true,
useProtocolHeartbeats: true,
msgpack: msgpack,
supportsBinary: !!globalObject.TextDecoder,
preferBinary: false,
ArrayBuffer: globalObject.ArrayBuffer,
Expand Down
1 change: 1 addition & 0 deletions src/platform/web/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Platform.WebStorage = WebStorage;

for (const clientClass of [DefaultRest, DefaultRealtime]) {
clientClass.Crypto = Crypto;
clientClass._MsgPack = msgpack;
}

Logger.initLogHandlers();
Expand Down
1 change: 1 addition & 0 deletions src/platform/web/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ if (Platform.Config.noUpgrade) {

export * from './modules/crypto';
export * from './modules/message';
export * from './modules/msgpack';
export { Rest } from '../../common/lib/client/rest';
export { BaseRest, BaseRealtime };
Loading

0 comments on commit badeeb7

Please sign in to comment.