Skip to content

Commit

Permalink
Exclude PresenceMessage from BaseRealtime bundle
Browse files Browse the repository at this point in the history
Now PresenceMessage only gets included if you provide the
RealtimePresence module.
  • Loading branch information
lawrence-forooghian committed Nov 27, 2023
1 parent 875282c commit 10bac3c
Show file tree
Hide file tree
Showing 16 changed files with 127 additions and 32 deletions.
5 changes: 2 additions & 3 deletions src/common/lib/client/baserealtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@ import ProtocolMessage from '../types/protocolmessage';
import { ChannelOptions } from '../../types/channel';
import ClientOptions from '../../types/ClientOptions';
import * as API from '../../../../ably';
import { ModulesMap } from './modulesmap';
import RealtimePresence from './realtimepresence';
import { ModulesMap, RealtimePresenceModule } from './modulesmap';
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;
readonly _RealtimePresence: RealtimePresenceModule | null;
// Extra transport implementations available to this client, in addition to those in Platform.Transports.bundledImplementations
readonly _additionalTransportImplementations: TransportImplementations;
_channels: any;
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 @@ -11,6 +11,10 @@ import RealtimePresence from './realtimepresence';
import { DefaultPresenceMessage } from '../types/defaultpresencemessage';
import initialiseWebSocketTransport from '../transport/websockettransport';
import { FilteredSubscriptions } from './filteredsubscriptions';
import {
fromValues as presenceMessageFromValues,
fromValuesArray as presenceMessagesFromValuesArray,
} from '../types/presencemessage';

/**
`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 @@ -26,7 +30,11 @@ export class DefaultRealtime extends BaseRealtime {
...allCommonModules,
Crypto: DefaultRealtime.Crypto ?? undefined,
MsgPack,
RealtimePresence,
RealtimePresence: {
RealtimePresence,
presenceMessageFromValues,
presenceMessagesFromValuesArray,
},
WebSocketTransport: initialiseWebSocketTransport,
MessageInteractions: FilteredSubscriptions,
});
Expand Down
15 changes: 14 additions & 1 deletion src/common/lib/client/modulesmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,25 @@ import { TransportInitialiser } from '../transport/connectionmanager';
import XHRRequest from 'platform/web/lib/http/request/xhrrequest';
import fetchRequest from 'platform/web/lib/http/request/fetchrequest';
import { FilteredSubscriptions } from './filteredsubscriptions';
import {
fromValues as presenceMessageFromValues,
fromValuesArray as presenceMessagesFromValuesArray,
} from '../types/presencemessage';

export interface PresenceMessageModule {
presenceMessageFromValues: typeof presenceMessageFromValues;
presenceMessagesFromValuesArray: typeof presenceMessagesFromValuesArray;
}

export type RealtimePresenceModule = PresenceMessageModule & {
RealtimePresence: typeof RealtimePresence;
};

export interface ModulesMap {
Rest?: typeof Rest;
Crypto?: IUntypedCryptoStatic;
MsgPack?: MsgPack;
RealtimePresence?: typeof RealtimePresence;
RealtimePresence?: RealtimePresenceModule;
WebSocketTransport?: TransportInitialiser;
XHRPolling?: TransportInitialiser;
XHRStreaming?: TransportInitialiser;
Expand Down
19 changes: 10 additions & 9 deletions src/common/lib/client/realtimechannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,7 @@ import Message, {
} from '../types/message';
import ChannelStateChange from './channelstatechange';
import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo';
import PresenceMessage, {
fromValues as presenceMessageFromValues,
fromValuesArray as presenceMessagesFromValuesArray,
decode as decodePresenceMessage,
} from '../types/presencemessage';
import PresenceMessage, { decode as decodePresenceMessage } from '../types/presencemessage';
import ConnectionErrors from '../transport/connectionerrors';
import * as API from '../../../../ably';
import ConnectionManager from '../transport/connectionmanager';
Expand Down Expand Up @@ -109,7 +105,7 @@ class RealtimeChannel extends EventEmitter {
this.name = name;
this.channelOptions = normaliseChannelOptions(client._Crypto ?? null, options);
this.client = client;
this._presence = client._RealtimePresence ? new client._RealtimePresence(this) : null;
this._presence = client._RealtimePresence ? new client._RealtimePresence.RealtimePresence(this) : null;
this.connectionManager = client.connection.connectionManager;
this.state = 'initialized';
this.subscriptions = new EventEmitter();
Expand Down Expand Up @@ -509,8 +505,8 @@ class RealtimeChannel extends EventEmitter {
action: actions.PRESENCE,
channel: this.name,
presence: Utils.isArray(presence)
? presenceMessagesFromValuesArray(presence)
: [presenceMessageFromValues(presence)],
? this.client._RealtimePresence!.presenceMessagesFromValuesArray(presence)
: [this.client._RealtimePresence!.presenceMessageFromValues(presence)],
});
this.sendMessage(msg, callback);
}
Expand Down Expand Up @@ -585,7 +581,12 @@ class RealtimeChannel extends EventEmitter {
if (!message.presence) break;
// eslint-disable-next-line no-fallthrough
case actions.PRESENCE: {
const presence = message.presence as Array<PresenceMessage>;
const presence = message.presence;

if (!presence) {
break;
}

const { id, connectionId, timestamp } = message;

const options = this.channelOptions;
Expand Down
5 changes: 4 additions & 1 deletion src/common/lib/transport/comettransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,10 @@ abstract class CometTransport extends Transport {
try {
const items = this.decodeResponse(responseData);
if (items && items.length)
for (let i = 0; i < items.length; i++) this.onProtocolMessage(protocolMessageFromDeserialized(items[i]));
for (let i = 0; i < items.length; i++)
this.onProtocolMessage(
protocolMessageFromDeserialized(items[i], this.connectionManager.realtime._RealtimePresence)
);
} catch (e) {
Logger.logAction(
Logger.LOG_ERROR,
Expand Down
6 changes: 5 additions & 1 deletion src/common/lib/transport/connectionmanager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1914,7 +1914,11 @@ class ConnectionManager extends EventEmitter {
return;
}
if (Logger.shouldLog(Logger.LOG_MICRO)) {
Logger.logAction(Logger.LOG_MICRO, 'ConnectionManager.send()', 'queueing msg; ' + stringifyProtocolMessage(msg));
Logger.logAction(
Logger.LOG_MICRO,
'ConnectionManager.send()',
'queueing msg; ' + stringifyProtocolMessage(msg, this.realtime._RealtimePresence)
);
}
this.queue(msg, callback);
}
Expand Down
3 changes: 2 additions & 1 deletion src/common/lib/transport/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ class Protocol extends EventEmitter {
Logger.logAction(
Logger.LOG_MICRO,
'Protocol.send()',
'sending msg; ' + stringifyProtocolMessage(pendingMessage.message)
'sending msg; ' +
stringifyProtocolMessage(pendingMessage.message, this.transport.connectionManager.realtime._RealtimePresence)
);
}
pendingMessage.sendAttempted = true;
Expand Down
2 changes: 1 addition & 1 deletion src/common/lib/transport/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ abstract class Transport extends EventEmitter {
'received on ' +
this.shortName +
': ' +
stringifyProtocolMessage(message) +
stringifyProtocolMessage(message, this.connectionManager.realtime._RealtimePresence) +
'; connectionId = ' +
this.connectionManager.connectionId
);
Expand Down
9 changes: 8 additions & 1 deletion src/common/lib/transport/websockettransport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,14 @@ class WebSocketTransport extends Transport {
'data received; length = ' + data.length + '; type = ' + typeof data
);
try {
this.onProtocolMessage(deserializeProtocolMessage(data, this.connectionManager.realtime._MsgPack, this.format));
this.onProtocolMessage(
deserializeProtocolMessage(
data,
this.connectionManager.realtime._MsgPack,
this.connectionManager.realtime._RealtimePresence,
this.format
)
);
} catch (e) {
Logger.logAction(
Logger.LOG_ERROR,
Expand Down
39 changes: 31 additions & 8 deletions src/common/lib/types/protocolmessage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { MsgPack } from 'common/types/msgpack';
import { Types } from '../../../../ably';
import { PresenceMessageModule } from '../client/modulesmap';
import * as Utils from '../util/utils';
import ErrorInfo from './errorinfo';
import Message, { fromValues as messageFromValues, fromValuesArray as messagesFromValuesArray } from './message';
Expand Down Expand Up @@ -65,26 +66,46 @@ export const channelModes = ['PRESENCE', 'PUBLISH', 'SUBSCRIBE', 'PRESENCE_SUBSC

export const serialize = Utils.encodeBody;

export function deserialize(serialized: unknown, MsgPack: MsgPack | null, format?: Utils.Format): ProtocolMessage {
export function deserialize(
serialized: unknown,
MsgPack: MsgPack | null,
presenceMessageModule: PresenceMessageModule | null,
format?: Utils.Format
): ProtocolMessage {
const deserialized = Utils.decodeBody<Record<string, unknown>>(serialized, MsgPack, format);
return fromDeserialized(deserialized);
return fromDeserialized(deserialized, presenceMessageModule);
}

export function fromDeserialized(deserialized: Record<string, unknown>): ProtocolMessage {
export function fromDeserialized(
deserialized: Record<string, unknown>,
presenceMessageModule: PresenceMessageModule | null
): ProtocolMessage {
const error = deserialized.error;
if (error) deserialized.error = ErrorInfo.fromValues(error as ErrorInfo);
const messages = deserialized.messages as Message[];
if (messages) for (let i = 0; i < messages.length; i++) messages[i] = messageFromValues(messages[i]);
const presence = deserialized.presence as PresenceMessage[];
if (presence) for (let i = 0; i < presence.length; i++) presence[i] = presenceMessageFromValues(presence[i], true);
return Object.assign(new ProtocolMessage(), deserialized);

const presence = presenceMessageModule ? (deserialized.presence as PresenceMessage[]) : undefined;
if (presenceMessageModule) {
if (presence && presenceMessageModule)
for (let i = 0; i < presence.length; i++)
presence[i] = presenceMessageModule.presenceMessageFromValues(presence[i], true);
}
return Object.assign(new ProtocolMessage(), { ...deserialized, presence });
}

/**
* Used by the tests.
*/
export function fromDeserializedIncludingDependencies(deserialized: Record<string, unknown>): ProtocolMessage {
return fromDeserialized(deserialized, { presenceMessageFromValues, presenceMessagesFromValuesArray });
}

export function fromValues(values: unknown): ProtocolMessage {
return Object.assign(new ProtocolMessage(), values);
}

export function stringify(msg: any): string {
export function stringify(msg: any, presenceMessageModule: PresenceMessageModule | null): string {
let result = '[ProtocolMessage';
if (msg.action !== undefined) result += '; action=' + ActionName[msg.action] || msg.action;

Expand All @@ -96,7 +117,8 @@ export function stringify(msg: any): string {
}

if (msg.messages) result += '; messages=' + toStringArray(messagesFromValuesArray(msg.messages));
if (msg.presence) result += '; presence=' + toStringArray(presenceMessagesFromValuesArray(msg.presence));
if (msg.presence && presenceMessageModule)
result += '; presence=' + toStringArray(presenceMessageModule.presenceMessagesFromValuesArray(msg.presence));
if (msg.error) result += '; error=' + ErrorInfo.fromValues(msg.error).toString();
if (msg.auth && msg.auth.accessToken) result += '; token=' + msg.auth.accessToken;
if (msg.flags) result += '; flags=' + flagNames.filter(msg.hasFlag).join(',');
Expand Down Expand Up @@ -128,6 +150,7 @@ class ProtocolMessage {
channelSerial?: string | null;
msgSerial?: number;
messages?: Message[];
// This will be undefined if we skipped decoding this property due to user not requesting presence functionality — see `fromDeserialized`
presence?: PresenceMessage[];
auth?: unknown;
connectionDetails?: Record<string, unknown>;
Expand Down
2 changes: 1 addition & 1 deletion src/platform/nativescript/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DefaultRest } from '../../common/lib/client/defaultrest';
import { DefaultRealtime } from '../../common/lib/client/defaultrealtime';
import Platform from '../../common/platform';
import ErrorInfo from '../../common/lib/types/errorinfo';
import { fromDeserialized as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage';
import { fromDeserializedIncludingDependencies as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage';

// Platform Specific
import BufferUtils from '../web/lib/util/bufferutils';
Expand Down
2 changes: 1 addition & 1 deletion src/platform/nodejs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DefaultRest } from '../../common/lib/client/defaultrest';
import { DefaultRealtime } from '../../common/lib/client/defaultrealtime';
import Platform from '../../common/platform';
import ErrorInfo from '../../common/lib/types/errorinfo';
import { fromDeserialized as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage';
import { fromDeserializedIncludingDependencies as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage';

// Platform Specific
import BufferUtils from './lib/util/bufferutils';
Expand Down
2 changes: 1 addition & 1 deletion src/platform/react-native/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DefaultRest } from '../../common/lib/client/defaultrest';
import { DefaultRealtime } from '../../common/lib/client/defaultrealtime';
import Platform from '../../common/platform';
import ErrorInfo from '../../common/lib/types/errorinfo';
import { fromDeserialized as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage';
import { fromDeserializedIncludingDependencies as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage';

// Platform Specific
import BufferUtils from '../web/lib/util/bufferutils';
Expand Down
2 changes: 1 addition & 1 deletion src/platform/web/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DefaultRest } from '../../common/lib/client/defaultrest';
import { DefaultRealtime } from '../../common/lib/client/defaultrealtime';
import Platform from '../../common/platform';
import ErrorInfo from '../../common/lib/types/errorinfo';
import { fromDeserialized as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage';
import { fromDeserializedIncludingDependencies as protocolMessageFromDeserialized } from '../../common/lib/types/protocolmessage';

// Platform Specific
import BufferUtils from './lib/util/bufferutils';
Expand Down
15 changes: 14 additions & 1 deletion src/platform/web/modules/realtimepresence.ts
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
export { default as RealtimePresence } from '../../../common/lib/client/realtimepresence';
import { RealtimePresenceModule } from 'common/lib/client/modulesmap';
import { default as realtimePresenceClass } from '../../../common/lib/client/realtimepresence';
import {
fromValues as presenceMessageFromValues,
fromValuesArray as presenceMessagesFromValuesArray,
} from '../../../common/lib/types/presencemessage';

const RealtimePresence: RealtimePresenceModule = {
RealtimePresence: realtimePresenceClass,
presenceMessageFromValues,
presenceMessagesFromValuesArray,
};

export { RealtimePresence };
23 changes: 23 additions & 0 deletions test/browser/modules.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,29 @@ describe('browser/modules', function () {

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

it('doesn’t break when it receives a PRESENCE ProtocolMessage', async () => {
const rxClient = new BaseRealtime(ablyClientOptions(), { WebSocketTransport, FetchRequest });
const rxChannel = rxClient.channels.get('channel');

await rxChannel.attach();

const receivedMessagePromise = new Promise((resolve) => rxChannel.subscribe(resolve));

const txClient = new BaseRealtime(ablyClientOptions({ clientId: randomString() }), {
WebSocketTransport,
FetchRequest,
RealtimePresence,
});
const txChannel = txClient.channels.get('channel');

await txChannel.publish('message', 'body');
await txChannel.presence.enter();

// The idea being here that in order for receivedMessagePromise to resolve, rxClient must have first processed the PRESENCE ProtocolMessage that resulted from txChannel.presence.enter()

await receivedMessagePromise;
});
});

describe('BaseRealtime with RealtimePresence', () => {
Expand Down

0 comments on commit 10bac3c

Please sign in to comment.