Skip to content

Commit 10068f9

Browse files
committed
channelgroup: use qualifier options
Previously we relied on a new BaseRealtime instance with it's own Channels object to separate usage of channels in the ChannelGroup from independent external usage of those channels from the regular client.channels.get() method. This led to various problems with shared Auth state such as nonces in token requests which caused connections to terminate and tests to fail. A simpler solution is to avoid creating a new client instance and instead share the Channel pool, but force the library to treat channels used from the ChannelGroup independently (with their own attachment) by setting dummy options in the qualifier, which is used as the key in the channel map. This implementation does not support channels in the channel group which already have a qualifier. This is acceptable for the experimental client-side simulation of the feature.
1 parent f09a541 commit 10068f9

File tree

1 file changed

+12
-10
lines changed

1 file changed

+12
-10
lines changed

src/common/lib/client/baserealtime.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,8 @@ class BaseRealtime extends BaseClient {
3636
this._decodeVcdiff = (modules.Vcdiff ?? (Platform.Vcdiff.supported && Platform.Vcdiff.bundledDecode)) || null;
3737
this.connection = new Connection(this, this.options);
3838
this._channels = new Channels(this);
39-
// avoid using the same channel pool as that exposed via this.channels()
40-
// to allow the channels to be used individually
4139
if (modules.ChannelGroups) {
42-
// disable channel groups on the new base realtime instance to avoid recursion
43-
const newModules = Object.assign({}, modules);
44-
delete newModules['ChannelGroups'];
45-
const newRealtime = new BaseRealtime(options, newModules);
46-
this._channelGroups = new modules.ChannelGroups(newRealtime.channels);
40+
this._channelGroups = new modules.ChannelGroups(this._channels);
4741
} else {
4842
this._channelGroups = null;
4943
}
@@ -249,14 +243,22 @@ class ChannelGroup {
249243
expression: RegExp;
250244
consumerGroup: ConsumerGroup;
251245

252-
constructor(readonly channels: Channels, filter: string, readonly options?: API.ChannelGroupOptions) {
246+
constructor(readonly channels: Channels, readonly filter: string, readonly options?: API.ChannelGroupOptions) {
253247
this.subscriptions = new EventEmitter();
254-
this.active = channels.get(options?.activeChannel || '$ably:active');
248+
this.active = channels.get(this.safeChannelName(options?.activeChannel || '$ably:active'));
255249
this.consumerGroup = new ConsumerGroup(channels, options?.consumerGroup?.name);
256250
this.consumerGroup.on('membership', () => this.updateAssignedChannels());
257251
this.expression = new RegExp(filter); // eslint-disable-line security/detect-non-literal-regexp
258252
}
259253

254+
// Add dummy options to the channel name so that it is treated as an independent channel
255+
// in the channel pool. This avoids any conflicts with external, independent use of individual
256+
// channels that happen to also be included in a channel group.
257+
private safeChannelName(name: string) {
258+
// base64 encode to ensure only allowed characters are used in the qualifier
259+
return `[?x-ably-channelgroup=${Utils.toBase64(this.filter)}]${name}`;
260+
}
261+
260262
async join() {
261263
await this.consumerGroup.join();
262264
await this.active.setOptions({ params: { rewind: '1' } });
@@ -332,7 +334,7 @@ class ChannelGroup {
332334
'ChannelGroups.subscribeChannel()',
333335
'setting up subscription to channel ' + channel
334336
);
335-
this.subscribedChannels[channel] = this.channels.get(channel);
337+
this.subscribedChannels[channel] = this.channels.get(this.safeChannelName(channel));
336338
this.subscribedChannels[channel].on(['detached', 'failed'], (event: ChannelStateChange) => {
337339
Logger.logAction(
338340
Logger.LOG_MAJOR,

0 commit comments

Comments
 (0)