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 22, 2023
1 parent e54b732 commit 954d6e7
Show file tree
Hide file tree
Showing 13 changed files with 123 additions and 33 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,8 +9,7 @@ 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';
import { RealtimePublishing } from './realtimepublishing';
Expand All @@ -19,7 +18,7 @@ import { RealtimePublishing } from './realtimepublishing';
`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;
readonly __RealtimePublishing: typeof RealtimePublishing | null;
// Extra transport implementations available to this client, in addition to those in Platform.Transports.bundledImplementations
readonly _additionalTransportImplementations: TransportImplementations;
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 @@ -12,6 +12,10 @@ import { DefaultPresenceMessage } from '../types/defaultpresencemessage';
import initialiseWebSocketTransport from '../transport/websockettransport';
import { FilteredSubscriptions } from './filteredsubscriptions';
import { RealtimePublishing } from './realtimepublishing';
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 @@ -27,7 +31,11 @@ export class DefaultRealtime extends BaseRealtime {
...allCommonModules,
Crypto: DefaultRealtime.Crypto ?? undefined,
MsgPack,
RealtimePresence,
RealtimePresence: {
RealtimePresence,
presenceMessageFromValues,
presenceMessagesFromValuesArray,
},
WebSocketTransport: initialiseWebSocketTransport,
MessageInteractions: FilteredSubscriptions,
RealtimePublishing: RealtimePublishing,
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 @@ -7,12 +7,25 @@ import XHRRequest from 'platform/web/lib/http/request/xhrrequest';
import fetchRequest from 'platform/web/lib/http/request/fetchrequest';
import { FilteredSubscriptions } from './filteredsubscriptions';
import { RealtimePublishing } from './realtimepublishing';
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 @@ -10,11 +10,7 @@ import RealtimePresence from './realtimepresence';
import Message, { decode as decodeMessage } 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 @@ -102,7 +98,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 @@ -440,8 +436,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 @@ -516,7 +512,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
6 changes: 5 additions & 1 deletion src/common/lib/types/defaultprotocolmessage.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import ProtocolMessage, { fromDeserialized } from './protocolmessage';
import {
fromValues as presenceMessageFromValues,
fromValuesArray as presenceMessagesFromValuesArray,
} from './presencemessage';

/**
`DefaultProtocolMessage` is the class returned by `DefaultRealtime`’s `ProtocolMessage` static property. It exposes the static methods that are used by the tests.
*/
export class DefaultProtocolMessage extends ProtocolMessage {
static fromDeserialized(deserialized: Record<string, unknown>): ProtocolMessage {
return fromDeserialized(deserialized);
return fromDeserialized(deserialized, { presenceMessageFromValues, presenceMessagesFromValuesArray });
}
}
37 changes: 25 additions & 12 deletions src/common/lib/types/protocolmessage.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
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';
import PresenceMessage, {
fromValues as presenceMessageFromValues,
fromValuesArray as presenceMessagesFromValuesArray,
} from './presencemessage';
import PresenceMessage from './presencemessage';

export const actions = {
HEARTBEAT: 0,
Expand Down Expand Up @@ -65,26 +63,39 @@ 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 });
}

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 +107,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 +140,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
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 };
24 changes: 24 additions & 0 deletions test/browser/modules.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,30 @@ 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,
RealtimePublishing,
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 954d6e7

Please sign in to comment.