Skip to content

Commit

Permalink
Make Realtime Channel and Presence not inherit from REST
Browse files Browse the repository at this point in the history
There is code that’s exclusively needed by the REST version of these
classes (e.g. presence `get()`, channel `publish()`), and which, in
order to reduce bundle size, we don’t want to pull in when importing the
BaseRealtime class. In order to do this, we need to sever the
inheritance relation between the Realtime and REST version of these
classes. (It’s also worth noting that, similarly to what I mentioned in
69c35f1, the IDL doesn’t mention any inheritance relation.)

The REST versions of these classes also contain functionality (channel
history and presence history, channel status) that should only be
available to a BaseRealtime client if the user has explicitly requested
REST functionality (by importing the Rest module). So, we gate this
functionality behind the Rest module (I should really have done this in
89c0761) and in doing so further reduce the bundle size of a REST-less
BaseRealtime.

I’ve moved the channel and presence REST code that’s also conditionally
needed by Realtime into classes called RestChannelMixin and
RestPresenceMixin. There’s no massively compelling reason for these
classes to exist, I just thought it might be good not to dump everything
directly inside the Rest module.

Resolves #1489.
  • Loading branch information
lawrence-forooghian committed Nov 14, 2023
1 parent 9636131 commit b75eb5b
Show file tree
Hide file tree
Showing 9 changed files with 257 additions and 160 deletions.
65 changes: 38 additions & 27 deletions src/common/lib/client/realtimechannel.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import ProtocolMessage from '../types/protocolmessage';
import EventEmitter from '../util/eventemitter';
import * as Utils from '../util/utils';
import RestChannel from './restchannel';
import Logger from '../util/logger';
import RealtimePresence from './realtimepresence';
import Message, { CipherOptions } from '../types/message';
Expand All @@ -14,6 +13,8 @@ import ConnectionManager from '../transport/connectionmanager';
import ConnectionStateChange from './connectionstatechange';
import { ErrCallback, PaginatedResultCallback, StandardCallback } from '../../types/utils';
import BaseRealtime from './baserealtime';
import { ChannelOptions } from '../../types/channel';
import { normaliseChannelOptions } from '../util/defaults';

interface RealtimeHistoryParams {
start?: number;
Expand Down Expand Up @@ -48,14 +49,16 @@ function validateChannelOptions(options?: API.Types.ChannelOptions) {
}
}

class RealtimeChannel extends RestChannel {
realtime: BaseRealtime;
private _realtimePresence: RealtimePresence | null;
class RealtimeChannel extends EventEmitter {
name: string;
channelOptions: ChannelOptions;
client: BaseRealtime;
private _presence: RealtimePresence | null;
get presence(): RealtimePresence {
if (!this._realtimePresence) {
if (!this._presence) {
Utils.throwMissingModuleError('RealtimePresence');
}
return this._realtimePresence;
return this._presence;
}
connectionManager: ConnectionManager;
state: API.Types.ChannelState;
Expand Down Expand Up @@ -86,12 +89,14 @@ class RealtimeChannel extends RestChannel {
retryTimer?: number | NodeJS.Timeout | null;
retryCount: number = 0;

constructor(realtime: BaseRealtime, name: string, options?: API.Types.ChannelOptions) {
super(realtime, name, options);
constructor(client: BaseRealtime, name: string, options?: API.Types.ChannelOptions) {
super();
Logger.logAction(Logger.LOG_MINOR, 'RealtimeChannel()', 'started; name = ' + name);
this.realtime = realtime;
this._realtimePresence = realtime._RealtimePresence ? new realtime._RealtimePresence(this) : null;
this.connectionManager = realtime.connection.connectionManager;
this.name = name;
this.channelOptions = normaliseChannelOptions(client._Crypto ?? null, options);
this.client = client;
this._presence = client._RealtimePresence ? new client._RealtimePresence(this) : null;
this.connectionManager = client.connection.connectionManager;
this.state = 'initialized';
this.subscriptions = new EventEmitter();
this.syncChannelSerial = undefined;
Expand All @@ -106,7 +111,7 @@ class RealtimeChannel extends RestChannel {
this._attachResume = false;
this._decodingContext = {
channelOptions: this.channelOptions,
plugins: realtime.options.plugins || {},
plugins: client.options.plugins || {},
baseEncodedPreviousPayload: undefined,
};
this._lastPayload = {
Expand Down Expand Up @@ -156,7 +161,7 @@ class RealtimeChannel extends RestChannel {
_callback(err);
return;
}
RestChannel.prototype.setOptions.call(this, options);
this.channelOptions = normaliseChannelOptions(this.client._Crypto ?? null, options);
if (this._decodingContext) this._decodingContext.channelOptions = this.channelOptions;
if (this._shouldReattachToSetOptions(options)) {
/* This does not just do _attach(true, null, callback) because that would put us
Expand Down Expand Up @@ -236,7 +241,7 @@ class RealtimeChannel extends RestChannel {
} else {
messages = [Message.fromValues({ name: args[0], data: args[1] })];
}
const maxMessageSize = this.realtime.options.maxMessageSize;
const maxMessageSize = this.client.options.maxMessageSize;
Message.encodeArray(messages, this.channelOptions as CipherOptions, (err: Error | null) => {
if (err) {
callback(err);
Expand All @@ -258,12 +263,11 @@ class RealtimeChannel extends RestChannel {
);
return;
}
this.__publish(messages, callback);
this._publish(messages, callback);
});
}

// Double underscore used to prevent type conflict with underlying Channel._publish method
__publish(messages: Array<Message>, callback: ErrCallback) {
_publish(messages: Array<Message>, callback: ErrCallback) {
Logger.logAction(Logger.LOG_MICRO, 'RealtimeChannel.publish()', 'message count = ' + messages.length);
const state = this.state;
switch (state) {
Expand Down Expand Up @@ -483,7 +487,7 @@ class RealtimeChannel extends RestChannel {
}

sendMessage(msg: ProtocolMessage, callback?: ErrCallback): void {
this.connectionManager.send(msg, this.realtime.options.queueMessages, callback);
this.connectionManager.send(msg, this.client.options.queueMessages, callback);
}

sendPresence(presence: PresenceMessage | PresenceMessage[], callback?: ErrCallback): void {
Expand Down Expand Up @@ -523,8 +527,8 @@ class RealtimeChannel extends RestChannel {
if (this.state === 'attached') {
if (!resumed) {
/* On a loss of continuity, the presence set needs to be re-synced */
if (this._realtimePresence) {
this._realtimePresence.onAttached(hasPresence);
if (this._presence) {
this._presence.onAttached(hasPresence);
}
}
const change = new ChannelStateChange(this.state, this.state, resumed, hasBacklog, message.error);
Expand Down Expand Up @@ -583,8 +587,8 @@ class RealtimeChannel extends RestChannel {
Logger.logAction(Logger.LOG_ERROR, 'RealtimeChannel.processMessage()', (e as Error).toString());
}
}
if (this._realtimePresence) {
this._realtimePresence.setPresence(presence, isSync, syncChannelSerial as any);
if (this._presence) {
this._presence.setPresence(presence, isSync, syncChannelSerial as any);
}
break;
}
Expand Down Expand Up @@ -721,8 +725,8 @@ class RealtimeChannel extends RestChannel {
if (state === this.state) {
return;
}
if (this._realtimePresence) {
this._realtimePresence.actOnChannelState(state, hasPresence, reason);
if (this._presence) {
this._presence.actOnChannelState(state, hasPresence, reason);
}
if (state === 'suspended' && this.connectionManager.state.sendEvents) {
this.startRetryTimer();
Expand Down Expand Up @@ -829,7 +833,7 @@ class RealtimeChannel extends RestChannel {
Logger.logAction(Logger.LOG_MINOR, 'RealtimeChannel.startStateTimerIfNotRunning', 'timer expired');
this.stateTimer = null;
this.timeoutPendingState();
}, this.realtime.options.timeouts.realtimeRequestTimeout);
}, this.client.options.timeouts.realtimeRequestTimeout);
}
}

Expand All @@ -845,7 +849,7 @@ class RealtimeChannel extends RestChannel {
if (this.retryTimer) return;

this.retryCount++;
const retryDelay = Utils.getRetryTime(this.realtime.options.timeouts.channelRetryTimeout, this.retryCount);
const retryDelay = Utils.getRetryTime(this.client.options.timeouts.channelRetryTimeout, this.retryCount);

this.retryTimer = setTimeout(() => {
/* If connection is not connected, just leave in suspended, a reattach
Expand Down Expand Up @@ -881,6 +885,9 @@ class RealtimeChannel extends RestChannel {
}
}

// We fetch this first so that any module-not-provided error takes priority over other errors
const restMixin = this.client.rest.channelMixin;

if (params && params.untilAttach) {
if (this.state !== 'attached') {
callback(new ErrorInfo('option untilAttach requires the channel to be attached', 40000, 400));
Expand All @@ -900,7 +907,7 @@ class RealtimeChannel extends RestChannel {
params.from_serial = this.properties.attachSerial;
}

RestChannel.prototype._history.call(this, params, callback);
return restMixin.history(this, params, callback);
} as any;

whenState = ((state: string, listener: ErrCallback) => {
Expand Down Expand Up @@ -934,6 +941,10 @@ class RealtimeChannel extends RestChannel {
this.properties.channelSerial = channelSerial;
}
}

status(callback?: StandardCallback<API.Types.ChannelDetails>): void | Promise<API.Types.ChannelDetails> {
return this.client.rest.channelMixin.status(this, callback);
}
}

export default RealtimeChannel;
21 changes: 13 additions & 8 deletions src/common/lib/client/realtimepresence.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as Utils from '../util/utils';
import RestPresence from './restpresence';
import EventEmitter from '../util/eventemitter';
import Logger from '../util/logger';
import PresenceMessage, { fromValues as presenceMessageFromValues } from '../types/presencemessage';
Expand Down Expand Up @@ -27,11 +26,11 @@ interface RealtimeHistoryParams {
}

function getClientId(realtimePresence: RealtimePresence) {
return realtimePresence.channel.realtime.auth.clientId;
return realtimePresence.channel.client.auth.clientId;
}

function isAnonymousOrWildcard(realtimePresence: RealtimePresence) {
const realtime = realtimePresence.channel.realtime;
const realtime = realtimePresence.channel.client;
/* If not currently connected, we can't assume that we're an anonymous
* client, as realtime may inform us of our clientId in the CONNECTED
* message. So assume we're not anonymous and leave it to realtime to
Expand Down Expand Up @@ -78,7 +77,7 @@ function newerThan(item: PresenceMessage, existing: PresenceMessage) {
}
}

class RealtimePresence extends RestPresence {
class RealtimePresence extends EventEmitter {
channel: RealtimeChannel;
pendingPresence: { presence: PresenceMessage; callback: ErrCallback }[];
syncComplete: boolean;
Expand All @@ -88,7 +87,7 @@ class RealtimePresence extends RestPresence {
name?: string;

constructor(channel: RealtimeChannel) {
super(channel);
super();
this.channel = channel;
this.syncComplete = false;
this.members = new PresenceMap(this, (item) => item.clientId + ':' + item.connectionId);
Expand Down Expand Up @@ -244,8 +243,11 @@ class RealtimePresence extends RestPresence {
}
}

// Return type is any to avoid conflict with base Presence class
get(this: RealtimePresence, params: RealtimePresenceParams, callback: StandardCallback<PresenceMessage[]>): any {
get(
this: RealtimePresence,
params: RealtimePresenceParams,
callback: StandardCallback<PresenceMessage[]>
): void | Promise<PresenceMessage> {
const args = Array.prototype.slice.call(arguments);
if (args.length == 1 && typeof args[0] == 'function') args.unshift(null);

Expand Down Expand Up @@ -304,6 +306,9 @@ class RealtimePresence extends RestPresence {
}
}

// We fetch this first so that any module-not-provided error takes priority over other errors
const restMixin = this.channel.client.rest.presenceMixin;

if (params && params.untilAttach) {
if (this.channel.state === 'attached') {
delete params.untilAttach;
Expand All @@ -319,7 +324,7 @@ class RealtimePresence extends RestPresence {
}
}

RestPresence.prototype._history.call(this, params, callback);
return restMixin.history(this, params, callback);
}

setPresence(presenceSet: PresenceMessage[], isSync: boolean, syncChannelSerial?: string): void {
Expand Down
5 changes: 5 additions & 0 deletions src/common/lib/client/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import Resource from './resource';
import Platform from '../../platform';
import BaseClient from './baseclient';
import { useTokenAuth } from './auth';
import { RestChannelMixin } from './restchannelmixin';
import { RestPresenceMixin } from './restpresencemixin';

type BatchResult<T> = API.Types.BatchResult<T>;

Expand All @@ -39,6 +41,9 @@ export class Rest {
readonly channels: Channels;
readonly push: Push;

readonly channelMixin = RestChannelMixin;
readonly presenceMixin = RestPresenceMixin;

constructor(client: BaseClient) {
this.client = client;
this.channels = new Channels(this.client);
Expand Down
Loading

0 comments on commit b75eb5b

Please sign in to comment.