Skip to content

Commit

Permalink
Make transports tree-shakable
Browse files Browse the repository at this point in the history
We expose WebSocketTransport, XHRPolling, and XHRStreaming modules.

Resolves #1394.
  • Loading branch information
lawrence-forooghian committed Nov 8, 2023
1 parent 654cb78 commit 21d387c
Show file tree
Hide file tree
Showing 13 changed files with 205 additions and 63 deletions.
10 changes: 9 additions & 1 deletion scripts/moduleReport.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
const esbuild = require('esbuild');

// List of all modules accepted in ModulesMap
const moduleNames = ['Rest', 'Crypto', 'MsgPack', 'RealtimePresence'];
const moduleNames = [
'Rest',
'Crypto',
'MsgPack',
'RealtimePresence',
'XHRPolling',
'XHRStreaming',
'WebSocketTransport',
];

// List of all free-standing functions exported by the library along with the
// ModulesMap entries that we expect them to transitively import
Expand Down
21 changes: 21 additions & 0 deletions src/common/lib/client/baserealtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,45 @@ import ClientOptions from '../../types/ClientOptions';
import * as API from '../../../../ably';
import { ModulesMap } from './modulesmap';
import RealtimePresence from './realtimepresence';
import { TransportNames } from 'common/constants/TransportName';
import { TransportImplementations } from 'common/platform';

/**
`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 {
readonly _RealtimePresence: typeof RealtimePresence | null;
// Extra transport implementations available to this client, in addition to those in Platform.Transports.bundledImplementations
readonly _additionalTransportImplementations: TransportImplementations;
_channels: any;
connection: Connection;

constructor(options: ClientOptions, modules: ModulesMap) {
super(options, modules);
Logger.logAction(Logger.LOG_MINOR, 'Realtime()', '');
this._additionalTransportImplementations = BaseRealtime.transportImplementationsFromModules(modules);
this._RealtimePresence = modules.RealtimePresence ?? null;
this.connection = new Connection(this, this.options);
this._channels = new Channels(this);
if (options.autoConnect !== false) this.connect();
}

private static transportImplementationsFromModules(modules: ModulesMap) {
const transports: TransportImplementations = {};

if (modules.WebSocketTransport) {
transports[TransportNames.WebSocket] = modules.WebSocketTransport;
}
if (modules.XHRStreaming) {
transports[TransportNames.XhrStreaming] = modules.XHRStreaming;
}
if (modules.XHRPolling) {
transports[TransportNames.XhrPolling] = modules.XHRPolling;
}

return transports;
}

get channels() {
return this._channels;
}
Expand Down
9 changes: 8 additions & 1 deletion src/common/lib/client/defaultrealtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DefaultMessage } from '../types/defaultmessage';
import { MsgPack } from 'common/types/msgpack';
import RealtimePresence from './realtimepresence';
import { DefaultPresenceMessage } from '../types/defaultpresencemessage';
import initialiseWebSocketTransport from '../transport/websockettransport';

/**
`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.
Expand All @@ -20,7 +21,13 @@ export class DefaultRealtime extends BaseRealtime {
throw new Error('Expected DefaultRealtime._MsgPack to have been set');
}

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

static Utils = Utils;
Expand Down
4 changes: 4 additions & 0 deletions src/common/lib/client/modulesmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import { Rest } from './rest';
import { IUntypedCryptoStatic } from '../../types/ICryptoStatic';
import { MsgPack } from 'common/types/msgpack';
import RealtimePresence from './realtimepresence';
import { TransportInitialiser } from '../transport/connectionmanager';

export interface ModulesMap {
Rest?: typeof Rest;
Crypto?: IUntypedCryptoStatic;
MsgPack?: MsgPack;
RealtimePresence?: typeof RealtimePresence;
WebSocketTransport?: TransportInitialiser;
XHRPolling?: TransportInitialiser;
XHRStreaming?: TransportInitialiser;
}

export const allCommonModules: ModulesMap = { Rest };
28 changes: 17 additions & 11 deletions src/common/lib/transport/connectionmanager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import ProtocolMessage from 'common/lib/types/protocolmessage';
import * as Utils from 'common/lib/util/utils';
import Protocol, { PendingMessage } from './protocol';
import Defaults, { getAgentString } from 'common/lib/util/defaults';
import Platform from 'common/platform';
import Platform, { TransportImplementations } from 'common/platform';
import EventEmitter from '../util/eventemitter';
import MessageQueue from './messagequeue';
import Logger from '../util/logger';
Expand All @@ -12,14 +12,13 @@ import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from 'common/lib/types
import Auth from 'common/lib/client/auth';
import Message from 'common/lib/types/message';
import Multicaster, { MulticasterInstance } from 'common/lib/util/multicaster';
import WebSocketTransport from './websockettransport';
import Transport, { TransportCtor } from './transport';
import * as API from '../../../../ably';
import { ErrCallback } from 'common/types/utils';
import HttpStatusCodes from 'common/constants/HttpStatusCodes';
import BaseRealtime from '../client/baserealtime';
import { NormalisedClientOptions } from 'common/types/ClientOptions';
import TransportName from 'common/constants/TransportName';
import TransportName, { TransportNames } from 'common/constants/TransportName';

let globalObject = typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : self;

Expand Down Expand Up @@ -227,8 +226,8 @@ class ConnectionManager extends EventEmitter {

constructor(realtime: BaseRealtime, options: NormalisedClientOptions) {
super();
this.initTransports();
this.realtime = realtime;
this.initTransports();
this.options = options;
const timeouts = options.timeouts;
/* connectingTimeout: leave preferenceConnectTimeout (~6s) to try the
Expand Down Expand Up @@ -401,22 +400,29 @@ class ConnectionManager extends EventEmitter {
*********************/

// Used by tests
static get supportedTransports() {
static supportedTransports(additionalImplementations: TransportImplementations) {
const storage: TransportStorage = { supportedTransports: {} };
this.initTransports(storage);
this.initTransports(additionalImplementations, storage);
return storage.supportedTransports;
}

private static initTransports(storage: TransportStorage) {
WebSocketTransport(storage);
private static initTransports(additionalImplementations: TransportImplementations, storage: TransportStorage) {
const implementations = { ...Platform.Transports.bundledImplementations, ...additionalImplementations };

const initialiseWebSocketTransport = implementations[TransportNames.WebSocket];
if (initialiseWebSocketTransport) {
initialiseWebSocketTransport(storage);
}
Utils.arrForEach(Platform.Transports.order, function (transportName) {
const initFn = Platform.Transports.implementations[transportName]!;
initFn(ConnectionManager);
const initFn = implementations[transportName];
if (initFn) {
initFn(storage);
}
});
}

initTransports() {
ConnectionManager.initTransports(this);
ConnectionManager.initTransports(this.realtime._additionalTransportImplementations, this);
}

createTransportParams(host: string | null, mode: string): TransportParams {
Expand Down
3 changes: 2 additions & 1 deletion src/common/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export default class Platform {
static Http: typeof IHttp;
static Transports: {
order: TransportName[];
implementations: TransportImplementations;
// Transport implementations that always come with this platform
bundledImplementations: TransportImplementations;
};
static Defaults: IDefaults;
static WebStorage: IWebStorage | null;
Expand Down
6 changes: 5 additions & 1 deletion src/platform/nodejs/lib/transport/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { TransportNames } from 'common/constants/TransportName';
import initialiseNodeCometTransport from './nodecomettransport';
import { default as initialiseWebSocketTransport } from '../../../../common/lib/transport/websockettransport';

export default {
order: [TransportNames.Comet],
implementations: { [TransportNames.Comet]: initialiseNodeCometTransport },
bundledImplementations: {
[TransportNames.WebSocket]: initialiseWebSocketTransport,
[TransportNames.Comet]: initialiseNodeCometTransport,
},
};
26 changes: 20 additions & 6 deletions src/platform/web/lib/transport/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import { TransportNames } from 'common/constants/TransportName';
import TransportName from 'common/constants/TransportName';
import Platform from 'common/platform';
import initialiseXHRPollingTransport from './xhrpollingtransport';
import initialiseXHRStreamingTransport from './xhrstreamingtransport';
import { default as initialiseWebSocketTransport } from '../../../../common/lib/transport/websockettransport';

export default {
order: [TransportNames.XhrPolling, TransportNames.XhrStreaming],
implementations: {
[TransportNames.XhrPolling]: initialiseXHRPollingTransport,
[TransportNames.XhrStreaming]: initialiseXHRStreamingTransport,
// For reasons that I don’t understand, if we use [TransportNames.XhrStreaming] and [TransportNames.XhrPolling] for the keys in defaultTransports’s, then defaultTransports does not get tree-shaken. Hence using literals instead. They’re still correctly type-checked.

const order: TransportName[] = ['xhr_polling', 'xhr_streaming'];

const defaultTransports: (typeof Platform)['Transports'] = {
order,
bundledImplementations: {
web_socket: initialiseWebSocketTransport,
xhr_polling: initialiseXHRPollingTransport,
xhr_streaming: initialiseXHRStreamingTransport,
},
};

export default defaultTransports;

export const ModulesTransports: (typeof Platform)['Transports'] = {
order,
bundledImplementations: {},
};
5 changes: 3 additions & 2 deletions src/platform/web/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import BufferUtils from './lib/util/bufferutils';
import Http from './lib/util/http';
import Config from './config';
// @ts-ignore
import Transports from './lib/transport';
import { ModulesTransports } from './lib/transport';
import Logger from '../../common/lib/util/logger';
import { getDefaults } from '../../common/lib/util/defaults';
import WebStorage from './lib/util/webstorage';
Expand All @@ -19,7 +19,7 @@ import PlatformDefaults from './lib/util/defaults';
Platform.BufferUtils = BufferUtils;
Platform.Http = Http;
Platform.Config = Config;
Platform.Transports = Transports;
Platform.Transports = ModulesTransports;
Platform.WebStorage = WebStorage;

Logger.initLogHandlers();
Expand All @@ -44,5 +44,6 @@ export * from './modules/message';
export * from './modules/presencemessage';
export * from './modules/msgpack';
export * from './modules/realtimepresence';
export * from './modules/transports';
export { Rest } from '../../common/lib/client/rest';
export { BaseRest, BaseRealtime, ErrorInfo };
3 changes: 3 additions & 0 deletions src/platform/web/modules/transports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as XHRPolling } from '../lib/transport/xhrpollingtransport';
export { default as XHRStreaming } from '../lib/transport/xhrstreamingtransport';
export { default as WebSocketTransport } from '../../../common/lib/transport/websockettransport';
Loading

0 comments on commit 21d387c

Please sign in to comment.