Skip to content

Commit 8a50060

Browse files
committed
feat: RealtimeChannel.attach returns ChannelStateChange
Makes it so that `RealtimeChannel.attach` exposes the `ChannelStateChange` via whatever async api (invoke callback or fulfill promise). This makes it easy for users to access flags on the `ChannelStateChange` to access information about the attachment.
1 parent 3915bcd commit 8a50060

File tree

3 files changed

+106
-26
lines changed

3 files changed

+106
-26
lines changed

ably.d.ts

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2556,9 +2556,9 @@ declare namespace Types {
25562556
/**
25572557
* Attach to this channel ensuring the channel is created in the Ably system and all messages published on the channel are received by any channel listeners registered using {@link RealtimeChannelCallbacks.subscribe | `subscribe()`}. Any resulting channel state change will be emitted to any listeners registered using the {@link EventEmitter.on | `on()`} or {@link EventEmitter.once | `once()`} methods. As a convenience, `attach()` is called implicitly if {@link RealtimeChannelCallbacks.subscribe | `subscribe()`} for the channel is called, or {@link RealtimePresenceCallbacks.enter | `enter()`} or {@link RealtimePresenceCallbacks.subscribe | `subscribe()`} are called on the {@link RealtimePresenceCallbacks} object for this channel.
25582558
*
2559-
* @param callback - A function which will be called upon completion of the operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error.
2559+
* @param callback - A function which will be called upon completion of the operation. If the operation succeeded and the channel became attached, then the function will be called with a {@link ChannelStateChange} object. If the channel was already attached the function will be called with `null`. If it failed, the function will be called with information about the error.
25602560
*/
2561-
attach(callback?: errorCallback): void;
2561+
attach(callback?: StandardCallback<ChannelStateChange | null>): void;
25622562
/**
25632563
* Detach from this channel. Any resulting channel state change is emitted to any listeners registered using the {@link EventEmitter.on | `on()`} or {@link EventEmitter.once | `once()`} methods. Once all clients globally have detached from the channel, the channel will be released in the Ably service within two minutes.
25642564
*
@@ -2590,32 +2590,44 @@ declare namespace Types {
25902590
*
25912591
* @param event - The event name.
25922592
* @param listener - An event listener function.
2593-
* @param callbackWhenAttached - A function which will be called upon completion of the channel {@link RealtimeChannelCallbacks.attach | `attach()`} operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error.
2593+
* @param callbackWhenAttached - A function which will be called upon completion of the channel {@link RealtimeChannelCallbacks.attach | `attach()`} operation. If the operation succeeded and the channel became attached, then the function will be called with a {@link ChannelStateChange} object. If the channel was already attached the function will be called with `null`. If it failed, the function will be called with information about the error.
25942594
*/
2595-
subscribe(event: string, listener?: messageCallback<Message>, callbackWhenAttached?: errorCallback): void;
2595+
subscribe(
2596+
event: string,
2597+
listener?: messageCallback<Message>,
2598+
callbackWhenAttached?: StandardCallback<ChannelStateChange | null>
2599+
): void;
25962600
/**
25972601
* Registers a listener for messages on this channel for multiple event name values.
25982602
*
25992603
* @param events - An array of event names.
26002604
* @param listener - An event listener function.
2601-
* @param callbackWhenAttached - A function which will be called upon completion of the channel {@link RealtimeChannelCallbacks.attach | `attach()`} operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error.
2605+
* @param callbackWhenAttached - A function which will be called upon completion of the channel {@link RealtimeChannelCallbacks.attach | `attach()`} operation. If the operation succeeded and the channel became attached, then the function will be called with a {@link ChannelStateChange} object. If the channel was already attached the function will be called with `null`. If it failed, the function will be called with information about the error.
26022606
*/
2603-
subscribe(events: Array<string>, listener?: messageCallback<Message>, callbackWhenAttached?: errorCallback): void;
2607+
subscribe(
2608+
events: Array<string>,
2609+
listener?: messageCallback<Message>,
2610+
callbackWhenAttached?: StandardCallback<ChannelStateChange | null>
2611+
): void;
26042612
/**
26052613
* Registers a listener for messages on this channel that match the supplied filter.
26062614
*
26072615
* @param filter - A {@link MessageFilter}.
26082616
* @param listener - An event listener function.
2609-
* @param callbackWhenAttached - A function which will be called upon completion of the channel {@link RealtimeChannelCallbacks.attach | `attach()`} operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error.
2617+
* @param callbackWhenAttached - A function which will be called upon completion of the channel {@link RealtimeChannelCallbacks.attach | `attach()`} operation. If the operation succeeded and the channel became attached, then the function will be called with a {@link ChannelStateChange} object. If the channel was already attached the function will be called with `null`. If it failed, the function will be called with information about the error.
26102618
*/
2611-
subscribe(filter: MessageFilter, listener?: messageCallback<Message>, callbackWhenAttached?: errorCallback): void;
2619+
subscribe(
2620+
filter: MessageFilter,
2621+
listener?: messageCallback<Message>,
2622+
callbackWhenAttached?: StandardCallback<ChannelStateChange | null>
2623+
): void;
26122624
/**
26132625
* Registers a listener for messages on this channel. The caller supplies a listener function, which is called each time one or more messages arrives on the channel.
26142626
*
26152627
* @param listener - An event listener function.
2616-
* @param callbackWhenAttached - A function which will be called upon completion of the channel {@link RealtimeChannelCallbacks.attach | `attach()`} operation. If the operation succeeded, then the function will be called with `null`. If it failed, the function will be called with information about the error.
2628+
* @param callbackWhenAttached - A function which will be called upon completion of the channel {@link RealtimeChannelCallbacks.attach | `attach()`} operation. If the operation succeeded and the channel became attached, then the function will be called with a {@link ChannelStateChange} object. If the channel was already attached the function will be called with `null`. If it failed, the function will be called with information about the error.
26172629
*/
2618-
subscribe(listener: messageCallback<Message>, callbackWhenAttached?: errorCallback): void;
2630+
subscribe(listener: messageCallback<Message>, callbackWhenAttached?: StandardCallback<ChannelStateChange>): void;
26192631
/**
26202632
* Publishes a single message to the channel with the given event name and payload. When publish is called with this client library, it won't attempt to implicitly attach to the channel, so long as [transient publishing](https://ably.com/docs/realtime/channels#transient-publish) is available in the library. Otherwise, the client will implicitly attach.
26212633
*
@@ -2666,9 +2678,9 @@ declare namespace Types {
26662678
/**
26672679
* Attach to this channel ensuring the channel is created in the Ably system and all messages published on the channel are received by any channel listeners registered using {@link RealtimeChannelPromise.subscribe | `subscribe()`}. Any resulting channel state change will be emitted to any listeners registered using the {@link EventEmitter.on | `on()`} or {@link EventEmitter.once | `once()`} methods. As a convenience, `attach()` is called implicitly if {@link RealtimeChannelPromise.subscribe | `subscribe()`} for the channel is called, or {@link RealtimePresencePromise.enter | `enter()`} or {@link RealtimePresencePromise.subscribe | `subscribe()`} are called on the {@link RealtimePresencePromise} object for this channel.
26682680
*
2669-
* @returns A promise which resolves upon success of the operation and rejects with an {@link ErrorInfo} object upon its failure.
2681+
* @returns A promise which, upon success, if the channel became attached will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be fulfilled with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object.
26702682
*/
2671-
attach(): Promise<void>;
2683+
attach(): Promise<ChannelStateChange | null>;
26722684
/**
26732685
* Detach from this channel. Any resulting channel state change is emitted to any listeners registered using the {@link EventEmitter.on | `on()`} or {@link EventEmitter.once | `once()`} methods. Once all clients globally have detached from the channel, the channel will be released in the Ably service within two minutes.
26742686
*
@@ -2694,32 +2706,32 @@ declare namespace Types {
26942706
*
26952707
* @param event - The event name.
26962708
* @param listener - An event listener function.
2697-
* @returns A promise which resolves upon success of the channel {@link RealtimeChannelPromise.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure.
2709+
* @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object.
26982710
*/
2699-
subscribe(event: string, listener?: messageCallback<Message>): Promise<void>;
2711+
subscribe(event: string, listener?: messageCallback<Message>): Promise<ChannelStateChange | null>;
27002712
/**
27012713
* Registers a listener for messages on this channel for multiple event name values.
27022714
*
27032715
* @param events - An array of event names.
27042716
* @param listener - An event listener function.
2705-
* @returns A promise which resolves upon success of the channel {@link RealtimeChannelPromise.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure.
2717+
* @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object.
27062718
*/
2707-
subscribe(events: Array<string>, listener?: messageCallback<Message>): Promise<void>;
2719+
subscribe(events: Array<string>, listener?: messageCallback<Message>): Promise<ChannelStateChange | null>;
27082720
/**
27092721
* Registers a listener for messages on this channel that match the supplied filter.
27102722
*
27112723
* @param filter - A {@link MessageFilter}.
27122724
* @param listener - An event listener function.
2713-
* @returns A promise which resolves upon success of the channel {@link RealtimeChannelPromise.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure.
2725+
* @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object.
27142726
*/
2715-
subscribe(filter: MessageFilter, listener?: messageCallback<Message>): Promise<void>;
2727+
subscribe(filter: MessageFilter, listener?: messageCallback<Message>): Promise<ChannelStateChange | null>;
27162728
/**
27172729
* Registers a listener for messages on this channel. The caller supplies a listener function, which is called each time one or more messages arrives on the channel.
27182730
*
27192731
* @param callback - An event listener function.
2720-
* @returns A promise which resolves upon success of the channel {@link RealtimeChannelPromise.attach | `attach()`} operation and rejects with an {@link ErrorInfo} object upon its failure.
2732+
* @returns A promise which, upon successful attachment to the channel, will be fulfilled with a {@link ChannelStateChange} object. If the channel was already attached the promise will be resolved with `null`. Upon failure, the promise will be rejected with an {@link ErrorInfo} object.
27212733
*/
2722-
subscribe(callback: messageCallback<Message>): Promise<void>;
2734+
subscribe(callback: messageCallback<Message>): Promise<ChannelStateChange | null>;
27232735
/**
27242736
* Publishes a single message to the channel with the given event name and payload. When publish is called with this client library, it won't attempt to implicitly attach to the channel, so long as [transient publishing](https://ably.com/docs/realtime/channels#transient-publish) is available in the library. Otherwise, the client will implicitly attach.
27252737
*

src/common/lib/client/realtimechannel.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import ConnectionErrors from '../transport/connectionerrors';
1212
import * as API from '../../../../ably';
1313
import ConnectionManager from '../transport/connectionmanager';
1414
import ConnectionStateChange from './connectionstatechange';
15-
import { ErrCallback, PaginatedResultCallback } from '../../types/utils';
15+
import { ErrCallback, PaginatedResultCallback, StandardCallback } from '../../types/utils';
1616
import Realtime from './realtime';
1717

1818
interface RealtimeHistoryParams {
@@ -272,7 +272,10 @@ class RealtimeChannel extends Channel {
272272
}
273273
}
274274

275-
attach(flags?: API.Types.ChannelMode[] | ErrCallback, callback?: ErrCallback): void | Promise<void> {
275+
attach(
276+
flags?: API.Types.ChannelMode[] | ErrCallback,
277+
callback?: StandardCallback<ChannelStateChange | null>
278+
): void | Promise<ChannelStateChange> {
276279
let _flags: API.Types.ChannelMode[] | null | undefined;
277280
if (typeof flags === 'function') {
278281
callback = flags;
@@ -296,14 +299,18 @@ class RealtimeChannel extends Channel {
296299
* current mode differs from requested mode */
297300
this._requestedFlags = _flags as API.Types.ChannelMode[];
298301
} else if (this.state === 'attached') {
299-
callback();
302+
callback(null, null);
300303
return;
301304
}
302305

303306
this._attach(false, null, callback);
304307
}
305308

306-
_attach(forceReattach: boolean, attachReason: ErrorInfo | null, callback?: ErrCallback): void {
309+
_attach(
310+
forceReattach: boolean,
311+
attachReason: ErrorInfo | null,
312+
callback?: StandardCallback<ChannelStateChange>
313+
): void {
307314
if (!callback) {
308315
callback = function (err?: ErrorInfo | null) {
309316
if (err) {
@@ -325,7 +332,7 @@ class RealtimeChannel extends Channel {
325332
this.once(function (this: { event: string }, stateChange: ChannelStateChange) {
326333
switch (this.event) {
327334
case 'attached':
328-
callback?.();
335+
callback?.(null, stateChange);
329336
break;
330337
case 'detached':
331338
case 'suspended':
@@ -422,7 +429,7 @@ class RealtimeChannel extends Channel {
422429
this.sendMessage(msg, callback || noop);
423430
}
424431

425-
subscribe(...args: unknown[] /* [event], listener, [callback] */): void | Promise<void> {
432+
subscribe(...args: unknown[] /* [event], listener, [callback] */): void | Promise<ChannelStateChange> {
426433
const [event, listener, callback] = RealtimeChannel.processListenerArgs(args);
427434

428435
if (!callback && this.realtime.options.promises) {

test/realtime/channel.test.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,5 +1569,66 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async
15691569

15701570
channel.subscribe(subscriber);
15711571
});
1572+
1573+
it('attach_returns_state_change', function (done) {
1574+
var realtime = helper.AblyRealtime();
1575+
var channelName = 'attach_returns_state_chnage';
1576+
var channel = realtime.channels.get(channelName);
1577+
channel.attach(function (err, stateChange) {
1578+
if (err) {
1579+
closeAndFinish(done, realtime, err);
1580+
return;
1581+
}
1582+
1583+
try {
1584+
expect(stateChange.current).to.equal('attached');
1585+
expect(stateChange.previous).to.equal('attaching');
1586+
} catch (err) {
1587+
closeAndFinish(done, realtime, err);
1588+
return;
1589+
}
1590+
1591+
// for an already-attached channel, null is returned
1592+
channel.attach(function (err, stateChange) {
1593+
if (err) {
1594+
closeAndFinish(done, realtime, err);
1595+
return;
1596+
}
1597+
1598+
try {
1599+
expect(stateChange).to.equal(null);
1600+
} catch (err) {
1601+
closeAndFinish(done, realtime, err);
1602+
return;
1603+
}
1604+
closeAndFinish(done, realtime);
1605+
});
1606+
});
1607+
});
1608+
1609+
it('subscribe_returns_state_change', function (done) {
1610+
var realtime = helper.AblyRealtime();
1611+
var channelName = 'subscribe_returns_state_chnage';
1612+
var channel = realtime.channels.get(channelName);
1613+
channel.subscribe(
1614+
function () {}, // message listener
1615+
// attach callback
1616+
function (err, stateChange) {
1617+
if (err) {
1618+
closeAndFinish(done, realtime, err);
1619+
return;
1620+
}
1621+
1622+
try {
1623+
expect(stateChange.current).to.equal('attached');
1624+
expect(stateChange.previous).to.equal('attaching');
1625+
} catch (err) {
1626+
closeAndFinish(done, realtime, err);
1627+
return;
1628+
}
1629+
closeAndFinish(done, realtime);
1630+
}
1631+
);
1632+
});
15721633
});
15731634
});

0 commit comments

Comments
 (0)