Skip to content

Commit

Permalink
Merge pull request #1428 from ably/1393-realtime-presence-tree-shaking
Browse files Browse the repository at this point in the history
[SDK-3732] Make realtime presence functionality tree-shakable
  • Loading branch information
lawrence-forooghian authored Nov 21, 2023
2 parents 7e604fb + 7cbd6fc commit b750df5
Show file tree
Hide file tree
Showing 9 changed files with 66 additions and 9 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', 'MsgPack'];
const moduleNames = ['Rest', 'Crypto', 'MsgPack', 'RealtimePresence'];

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

/**
`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;
_channels: any;
connection: Connection;

constructor(options: ClientOptions, modules: ModulesMap) {
super(options, modules);
Logger.logAction(Logger.LOG_MINOR, 'Realtime()', '');
this._RealtimePresence = modules.RealtimePresence ?? null;
this.connection = new Connection(this, this.options);
this._channels = new Channels(this);
if (options.autoConnect !== false) this.connect();
Expand Down
7 changes: 5 additions & 2 deletions src/common/lib/client/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ class Channel extends EventEmitter {
client: BaseClient;
name: string;
basePath: string;
presence: Presence;
private _presence: Presence;
get presence(): Presence {
return this._presence;
}
channelOptions: ChannelOptions;

constructor(client: BaseClient, name: string, channelOptions?: ChannelOptions) {
Expand All @@ -59,7 +62,7 @@ class Channel extends EventEmitter {
this.client = client;
this.name = name;
this.basePath = '/channels/' + encodeURIComponent(name);
this.presence = new Presence(this);
this._presence = new Presence(this);
this.channelOptions = normaliseChannelOptions(client._Crypto ?? null, channelOptions);
}

Expand Down
3 changes: 2 additions & 1 deletion src/common/lib/client/defaultrealtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import ProtocolMessage from '../types/protocolmessage';
import Platform from 'common/platform';
import { DefaultMessage } from '../types/defaultmessage';
import { MsgPack } from 'common/types/msgpack';
import RealtimePresence from './realtimepresence';

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

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

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

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

export const allCommonModules: ModulesMap = { Rest };
22 changes: 17 additions & 5 deletions src/common/lib/client/realtimechannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,13 @@ function validateChannelOptions(options?: API.Types.ChannelOptions) {

class RealtimeChannel extends Channel {
realtime: BaseRealtime;
presence: RealtimePresence;
private _realtimePresence: RealtimePresence | null;
get presence(): RealtimePresence {
if (!this._realtimePresence) {
Utils.throwMissingModuleError('RealtimePresence');
}
return this._realtimePresence;
}
connectionManager: ConnectionManager;
state: API.Types.ChannelState;
subscriptions: EventEmitter;
Expand Down Expand Up @@ -84,7 +90,7 @@ class RealtimeChannel extends Channel {
super(realtime, name, options);
Logger.logAction(Logger.LOG_MINOR, 'RealtimeChannel()', 'started; name = ' + name);
this.realtime = realtime;
this.presence = new RealtimePresence(this);
this._realtimePresence = realtime._RealtimePresence ? new realtime._RealtimePresence(this) : null;
this.connectionManager = realtime.connection.connectionManager;
this.state = 'initialized';
this.subscriptions = new EventEmitter();
Expand Down Expand Up @@ -616,7 +622,9 @@ class RealtimeChannel extends Channel {
if (this.state === 'attached') {
if (!resumed) {
/* On a loss of continuity, the presence set needs to be re-synced */
this.presence.onAttached(hasPresence);
if (this._realtimePresence) {
this._realtimePresence.onAttached(hasPresence);
}
}
const change = new ChannelStateChange(this.state, this.state, resumed, hasBacklog, message.error);
this._allChannelChanges.emit('update', change);
Expand Down Expand Up @@ -674,7 +682,9 @@ class RealtimeChannel extends Channel {
Logger.logAction(Logger.LOG_ERROR, 'RealtimeChannel.processMessage()', (e as Error).toString());
}
}
this.presence.setPresence(presence, isSync, syncChannelSerial as any);
if (this._realtimePresence) {
this._realtimePresence.setPresence(presence, isSync, syncChannelSerial as any);
}
break;
}
case actions.MESSAGE: {
Expand Down Expand Up @@ -810,7 +820,9 @@ class RealtimeChannel extends Channel {
if (state === this.state) {
return;
}
this.presence.actOnChannelState(state, hasPresence, reason);
if (this._realtimePresence) {
this._realtimePresence.actOnChannelState(state, hasPresence, reason);
}
if (state === 'suspended' && this.connectionManager.state.sendEvents) {
this.startRetryTimer();
} else {
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 @@ -42,5 +42,6 @@ if (Platform.Config.noUpgrade) {
export * from './modules/crypto';
export * from './modules/message';
export * from './modules/msgpack';
export * from './modules/realtimepresence';
export { Rest } from '../../common/lib/client/rest';
export { BaseRest, BaseRealtime, ErrorInfo };
1 change: 1 addition & 0 deletions src/platform/web/modules/realtimepresence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as RealtimePresence } from '../../../common/lib/client/realtimepresence';
34 changes: 34 additions & 0 deletions test/browser/modules.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
decodeEncryptedMessages,
Crypto,
MsgPack,
RealtimePresence,
} from '../../build/modules/index.js';

describe('browser/modules', function () {
Expand All @@ -20,11 +21,13 @@ describe('browser/modules', function () {
let testResourcesPath;
let loadTestData;
let testMessageEquality;
let randomString;

before((done) => {
ablyClientOptions = window.ablyHelpers.ablyClientOptions;
testResourcesPath = window.ablyHelpers.testResourcesPath;
testMessageEquality = window.ablyHelpers.testMessageEquality;
randomString = window.ablyHelpers.randomString;

loadTestData = async (dataPath) => {
return new Promise((resolve, reject) => {
Expand Down Expand Up @@ -317,4 +320,35 @@ describe('browser/modules', function () {
});
});
});

describe('RealtimePresence', () => {
describe('BaseRealtime without RealtimePresence', () => {
it('throws an error when attempting to access the `presence` property', () => {
const client = new BaseRealtime(ablyClientOptions(), {});
const channel = client.channels.get('channel');

expect(() => channel.presence).to.throw('RealtimePresence module not provided');
});
});

describe('BaseRealtime with RealtimePresence', () => {
it('offers realtime presence functionality', async () => {
const rxChannel = new BaseRealtime(ablyClientOptions(), { RealtimePresence }).channels.get('channel');
const txClientId = randomString();
const txChannel = new BaseRealtime(ablyClientOptions({ clientId: txClientId }), {
RealtimePresence,
}).channels.get('channel');

let resolveRxPresenceMessagePromise;
const rxPresenceMessagePromise = new Promise((resolve, reject) => {
resolveRxPresenceMessagePromise = resolve;
});
await rxChannel.presence.subscribe('enter', resolveRxPresenceMessagePromise);
await txChannel.presence.enter();

const rxPresenceMessage = await rxPresenceMessagePromise;
expect(rxPresenceMessage.clientId).to.equal(txClientId);
});
});
});
});

0 comments on commit b750df5

Please sign in to comment.