Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rest lifecycle status RSL8 #985

Merged
merged 10 commits into from
May 27, 2022
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,16 @@ client.stats(function(err, statsPage) { // statsPage as PaginatedResult
client.time(function(err, time) { ... }); // time is in ms since epoch
```

### Getting the status of a channel

```javascript
channel.status(function(err, channelDetails) {
owenpearson marked this conversation as resolved.
Show resolved Hide resolved
channelDetails.channelId // The name of the channel
channelDetails.status.isActive // A boolean indicating whether the channel is active
channelDetails.status.occupancy // Contains metadata relating to the occupants of the channel
});
```

## Using the async API style

### Realtime Example
Expand Down Expand Up @@ -530,6 +540,10 @@ const ablyRestPromiseExample = async () => {
// Fetching the Ably service time
const time = await client.time();
console.log(`Ably service time: ${time}`);

// Getting the status of a channel
const channelDetails = await channel.status();
console.log(channelDetails);

client.close();
};
Expand Down
25 changes: 25 additions & 0 deletions ably.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,29 @@ declare namespace Types {

type Transport = 'web_socket' | 'xhr_streaming' | 'xhr_polling' | 'jsonp' | 'comet';

interface ChannelDetails {
channelId: string;
status: ChannelStatus;
}

interface ChannelStatus {
isActive: boolean;
occupancy: ChannelOccupancy;
}

interface ChannelOccupancy {
metrics: ChannelMetrics;
}

interface ChannelMetrics {
connections: number;
presenceConnections: number;
presenceMembers: number;
presenceSubscribers: number;
publishers: number;
subscribers: number;
}

// Interfaces
interface ClientOptions extends AuthOptions {
/**
Expand Down Expand Up @@ -650,13 +673,15 @@ declare namespace Types {
publish(messages: any, callback?: errorCallback): void;
publish(name: string, messages: any, callback?: errorCallback): void;
publish(name: string, messages: any, options?: PublishOptions, callback?: errorCallback): void;
status(callback: StandardCallback<ChannelDetails>): void;
}

class ChannelPromise extends ChannelBase {
presence: PresencePromise;
history: (params?: RestHistoryParams) => Promise<PaginatedResult<Message>>;
publish(messages: any, options?: PublishOptions): Promise<void>;
publish(name: string, messages: any, options?: PublishOptions): Promise<void>;
status(): Promise<ChannelDetails>;
}

class RealtimeChannelBase extends EventEmitter<channelEventCallback, ChannelStateChange, ChannelEvent> {
Expand Down
14 changes: 13 additions & 1 deletion common/lib/client/channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import ErrorInfo from '../types/errorinfo';
import PaginatedResource, { PaginatedResult } from './paginatedresource';
import Resource, { ResourceCallback } from './resource';
import { ChannelOptions } from '../../types/channel';
import { PaginatedResultCallback } from '../../types/utils';
import { PaginatedResultCallback, StandardCallback } from '../../types/utils';
import Rest from './rest';
import Realtime from './realtime';
import * as API from '../../../ably';

interface RestHistoryParams {
start?: number;
Expand Down Expand Up @@ -189,6 +190,17 @@ class Channel extends EventEmitter {
_publish(requestBody: unknown, headers: Record<string, string>, params: any, callback: ResourceCallback): void {
Resource.post(this.rest, this.basePath + '/messages', requestBody, headers, params, null, callback);
}

status(callback?: StandardCallback<API.Types.ChannelDetails>): void | Promise<API.Types.ChannelDetails> {
if (typeof callback !== 'function' && this.rest.options.promises) {
return Utils.promisify(this, 'status', []);
}

const format = this.rest.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json;
const headers = Utils.defaultPostHeaders(format);
owenpearson marked this conversation as resolved.
Show resolved Hide resolved

Resource.get<API.Types.ChannelDetails>(this.rest, this.basePath, headers, {}, format, callback || noop);
}
}

export default Channel;
20 changes: 10 additions & 10 deletions common/lib/client/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function withAuthDetails(
}
}

function unenvelope(callback: ResourceCallback, format: Utils.Format | null): ResourceCallback {
function unenvelope<T>(callback: ResourceCallback<T>, format: Utils.Format | null): ResourceCallback<T> {
return (err, body, outerHeaders, unpacked, outerStatusCode) => {
if (err && !body) {
callback(err);
Expand Down Expand Up @@ -88,8 +88,8 @@ function urlFromPathAndParams(path: string, params: Record<string, any>) {
return path + (params ? '?' : '') + paramString(params);
}

function logResponseHandler(
callback: ResourceCallback,
function logResponseHandler<T>(
callback: ResourceCallback<T>,
method: HttpMethods,
path: string,
params: Record<string, string>
Expand All @@ -116,27 +116,27 @@ function logResponseHandler(
);
}
if (callback) {
callback(err, body, headers, unpacked, statusCode);
callback(err, body as T, headers, unpacked, statusCode);
}
};
}

export type ResourceCallback = (
export type ResourceCallback<T = unknown> = (
err: ErrorInfo | null,
body?: unknown,
body?: T,
headers?: Record<string, string>,
unpacked?: boolean,
statusCode?: number
) => void;

class Resource {
static get(
static get<T = unknown>(
rest: Rest,
path: string,
headers: Record<string, string>,
params: Record<string, any>,
envelope: Utils.Format | null,
callback: ResourceCallback
callback: ResourceCallback<T>
): void {
Resource.do(HttpMethods.Get, rest, path, null, headers, params, envelope, callback);
}
Expand Down Expand Up @@ -188,15 +188,15 @@ class Resource {
Resource.do(HttpMethods.Put, rest, path, body, headers, params, envelope, callback);
}

static do(
static do<T>(
method: HttpMethods,
rest: Rest,
path: string,
body: unknown,
headers: Record<string, string>,
params: Record<string, any>,
envelope: Utils.Format | null,
callback: ResourceCallback
callback: ResourceCallback<T>
): void {
if (Logger.shouldLog(Logger.LOG_MICRO)) {
callback = logResponseHandler(callback, method, path, params);
Expand Down
71 changes: 71 additions & 0 deletions test/rest/status.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
'use strict';

define(['shared_helper', 'chai'], function (helper, chai) {
var rest;
var utils = helper.Utils;
var expect = chai.expect;

// RSL8
describe('rest/status', function () {
owenpearson marked this conversation as resolved.
Show resolved Hide resolved
this.timeout(30 * 1000);

before(function (done) {
helper.setupApp(function (err) {
if (err) {
done(err);
return;
}
rest = helper.AblyRest();
done();
});
});

it('status0', function (done) {
var channel = rest.channels.get('status0');
channel.status(function (err, channelDetails) {
try {
expect(channelDetails.channelId).to.equal('status0');
expect(channelDetails.status.isActive).to.be.a('boolean');
var metrics = channelDetails.status.occupancy.metrics;
expect(metrics.connections).to.be.a('number');
expect(metrics.presenceConnections).to.be.a('number');
expect(metrics.presenceMembers).to.be.a('number');
expect(metrics.presenceSubscribers).to.be.a('number');
expect(metrics.publishers).to.be.a('number');
expect(metrics.subscribers).to.be.a('number');
done();
} catch (err) {
done(err);
}
});
});

if (typeof Promise !== 'undefined') {
it('statusPromise', function (done) {
var rest = helper.AblyRest({ promises: true });
var channel = rest.channels.get('statusPromise');
channel
.status()
.then(function (channelDetails) {
try {
expect(channelDetails.channelId).to.equal('statusPromise');
expect(channelDetails.status.isActive).to.be.a('boolean');
var metrics = channelDetails.status.occupancy.metrics;
expect(metrics.connections).to.be.a('number');
expect(metrics.presenceConnections).to.be.a('number');
expect(metrics.presenceMembers).to.be.a('number');
expect(metrics.presenceSubscribers).to.be.a('number');
expect(metrics.publishers).to.be.a('number');
expect(metrics.subscribers).to.be.a('number');
done();
} catch (err) {
done(err);
}
})
['catch'](function (err) {
done(err);
});
});
}
});
});