Skip to content

Commit

Permalink
Merge pull request #1505 from ably/exclude-PresenceMessage-from-base-…
Browse files Browse the repository at this point in the history
…realtime

Exclude `PresenceMessage` from `BaseRealtime` bundle
  • Loading branch information
lawrence-forooghian authored Nov 27, 2023
2 parents a395593 + 10bac3c commit ae0f45a
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 ae0f45a

Please sign in to comment.