Skip to content

Commit

Permalink
Merge pull request #1410 from ably/989-implement-batch-functionality
Browse files Browse the repository at this point in the history
[SDK-1904] Implement batch operations
  • Loading branch information
lawrence-forooghian authored Aug 2, 2023
2 parents 9d8865a + 88bac53 commit 53ad2ec
Show file tree
Hide file tree
Showing 7 changed files with 1,348 additions and 5 deletions.
281 changes: 281 additions & 0 deletions ably.d.ts

Large diffs are not rendered by default.

78 changes: 78 additions & 0 deletions src/common/lib/client/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ import ClientOptions from '../../types/ClientOptions';
import HttpMethods from '../../constants/HttpMethods';
import HttpStatusCodes from 'common/constants/HttpStatusCodes';
import Platform from '../../platform';
import Resource from './resource';

type BatchResult<T> = API.Types.BatchResult<T>;
type TokenRevocationTargetSpecifier = API.Types.TokenRevocationTargetSpecifier;
type TokenRevocationOptions = API.Types.TokenRevocationOptions;
type TokenRevocationSuccessResult = API.Types.TokenRevocationSuccessResult;
type TokenRevocationFailureResult = API.Types.TokenRevocationFailureResult;
type TokenRevocationResult = BatchResult<TokenRevocationSuccessResult | TokenRevocationFailureResult>;

const MAX_TOKEN_LENGTH = Math.pow(2, 17);
function noop() {}
Expand Down Expand Up @@ -1054,6 +1062,76 @@ class Auth {
static isTokenErr(error: IPartialErrorInfo) {
return error.code && error.code >= 40140 && error.code < 40150;
}

revokeTokens(
specifiers: TokenRevocationTargetSpecifier[],
options?: TokenRevocationOptions,
callback?: API.Types.StandardCallback<TokenRevocationResult>
): void;
revokeTokens(
specifiers: TokenRevocationTargetSpecifier[],
options?: TokenRevocationOptions
): Promise<TokenRevocationResult>;
revokeTokens(
specifiers: TokenRevocationTargetSpecifier[],
optionsOrCallbackArg?: TokenRevocationOptions | API.Types.StandardCallback<TokenRevocationResult>,
callbackArg?: API.Types.StandardCallback<TokenRevocationResult>
): void | Promise<TokenRevocationResult> {
if (useTokenAuth(this.client.options)) {
throw new ErrorInfo('Cannot revoke tokens when using token auth', 40162, 401);
}

const keyName = this.client.options.keyName!;

let resolvedOptions: TokenRevocationOptions;

if (typeof optionsOrCallbackArg === 'function') {
callbackArg = optionsOrCallbackArg;
resolvedOptions = {};
} else {
resolvedOptions = optionsOrCallbackArg ?? {};
}

if (callbackArg === undefined) {
if (this.client.options.promises) {
return Utils.promisify(this, 'revokeTokens', [specifiers, resolvedOptions]);
}
callbackArg = noop;
}

const callback = callbackArg;

const requestBodyDTO = {
targets: specifiers.map((specifier) => `${specifier.type}:${specifier.value}`),
...resolvedOptions,
};

const format = this.client.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json,
headers = Utils.defaultPostHeaders(this.client.options, format);

if (this.client.options.headers) Utils.mixin(headers, this.client.options.headers);

const requestBody = Utils.encodeBody(requestBodyDTO, format);
Resource.post(
this.client,
`/keys/${keyName}/revokeTokens`,
requestBody,
headers,
{ newBatchResponse: 'true' },
null,
(err, body, headers, unpacked) => {
if (err) {
// TODO remove this type assertion after fixing https://github.com/ably/ably-js/issues/1405
callback(err as API.Types.ErrorInfo);
return;
}

const batchResult = (unpacked ? body : Utils.decodeBody(body, format)) as TokenRevocationResult;

callback(null, batchResult);
}
);
}
}

export default Auth;
115 changes: 115 additions & 0 deletions src/common/lib/client/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,21 @@ import { ChannelOptions } from '../../types/channel';
import { PaginatedResultCallback, StandardCallback } from '../../types/utils';
import { ErrnoException, IHttp, RequestParams } from '../../types/http';
import ClientOptions, { DeprecatedClientOptions, NormalisedClientOptions } from '../../types/ClientOptions';
import * as API from '../../../../ably';

import Platform from '../../platform';
import Message from '../types/message';
import PresenceMessage from '../types/presencemessage';
import Resource from './resource';

type BatchResult<T> = API.Types.BatchResult<T>;
type BatchPublishSpec = API.Types.BatchPublishSpec;
type BatchPublishSuccessResult = API.Types.BatchPublishSuccessResult;
type BatchPublishFailureResult = API.Types.BatchPublishFailureResult;
type BatchPublishResult = BatchResult<BatchPublishSuccessResult | BatchPublishFailureResult>;
type BatchPresenceSuccessResult = API.Types.BatchPresenceSuccessResult;
type BatchPresenceFailureResult = API.Types.BatchPresenceFailureResult;
type BatchPresenceResult = BatchResult<BatchPresenceSuccessResult | BatchPresenceFailureResult>;

const noop = function () {};
class Rest {
Expand Down Expand Up @@ -228,6 +239,110 @@ class Rest {
}
}

batchPublish<T extends BatchPublishSpec | BatchPublishSpec[]>(
specOrSpecs: T,
callback: API.Types.StandardCallback<T extends BatchPublishSpec ? BatchPublishResult : BatchPublishResult[]>
): void;
batchPublish<T extends BatchPublishSpec | BatchPublishSpec[]>(
specOrSpecs: T
): Promise<T extends BatchPublishSpec ? BatchPublishResult : BatchPublishResult[]>;
batchPublish<T extends BatchPublishSpec | BatchPublishSpec[]>(
specOrSpecs: T,
callbackArg?: API.Types.StandardCallback<T extends BatchPublishSpec ? BatchPublishResult : BatchPublishResult[]>
): void | Promise<T extends BatchPublishSpec ? BatchPublishResult : BatchPublishResult[]> {
if (callbackArg === undefined) {
if (this.options.promises) {
return Utils.promisify(this, 'batchPublish', [specOrSpecs]);
}
callbackArg = noop;
}

const callback = callbackArg;

let requestBodyDTO: BatchPublishSpec[];
let singleSpecMode: boolean;
if (Utils.isArray(specOrSpecs)) {
requestBodyDTO = specOrSpecs;
singleSpecMode = false;
} else {
requestBodyDTO = [specOrSpecs];
singleSpecMode = true;
}

const format = this.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json,
headers = Utils.defaultPostHeaders(this.options, format);

if (this.options.headers) Utils.mixin(headers, this.options.headers);

const requestBody = Utils.encodeBody(requestBodyDTO, format);
Resource.post(
this,
'/messages',
requestBody,
headers,
{ newBatchResponse: 'true' },
null,
(err, body, headers, unpacked) => {
if (err) {
// TODO remove this type assertion after fixing https://github.com/ably/ably-js/issues/1405
callback(err as API.Types.ErrorInfo);
return;
}

const batchResults = (unpacked ? body : Utils.decodeBody(body, format)) as BatchPublishResult[];

// I don't love the below type assertions for `callback` but not sure how to avoid them
if (singleSpecMode) {
(callback as API.Types.StandardCallback<BatchPublishResult>)(null, batchResults[0]);
} else {
(callback as API.Types.StandardCallback<BatchPublishResult[]>)(null, batchResults);
}
}
);
}

batchPresence(channels: string[], callback: API.Types.StandardCallback<BatchPresenceResult>): void;
batchPresence(channels: string[]): Promise<BatchPresenceResult>;
batchPresence(
channels: string[],
callbackArg?: API.Types.StandardCallback<BatchPresenceResult>
): void | Promise<BatchPresenceResult> {
if (callbackArg === undefined) {
if (this.options.promises) {
return Utils.promisify(this, 'batchPresence', [channels]);
}
callbackArg = noop;
}

const callback = callbackArg;

const format = this.options.useBinaryProtocol ? Utils.Format.msgpack : Utils.Format.json,
headers = Utils.defaultPostHeaders(this.options, format);

if (this.options.headers) Utils.mixin(headers, this.options.headers);

const channelsParam = channels.join(',');

Resource.get(
this,
'/presence',
headers,
{ newBatchResponse: 'true', channels: channelsParam },
null,
(err, body, headers, unpacked) => {
if (err) {
// TODO remove this type assertion after fixing https://github.com/ably/ably-js/issues/1405
callback(err as API.Types.ErrorInfo);
return;
}

const batchResult = (unpacked ? body : Utils.decodeBody(body, format)) as BatchPresenceResult;

callback(null, batchResult);
}
);
}

setLog(logOptions: LoggerOptions): void {
Logger.setLog(logOptions.level, logOptions.handler);
}
Expand Down
5 changes: 5 additions & 0 deletions test/common/modules/shared_helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ define([
return res;
};

function randomString() {
return Math.random().toString().slice(2);
}

return (module.exports = {
setupApp: testAppModule.setup,
tearDownApp: testAppModule.tearDown,
Expand Down Expand Up @@ -242,5 +246,6 @@ define([
unroutableAddress: unroutableAddress,
arrFind: arrFind,
arrFilter: arrFilter,
randomString: randomString,
});
});
6 changes: 1 addition & 5 deletions test/realtime/channel.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,7 @@ define(['ably', 'shared_helper', 'async', 'chai'], function (Ably, helper, async
var monitorConnection = helper.monitorConnection;
var createPM = Ably.Realtime.ProtocolMessage.fromDeserialized;
var testOnAllTransports = helper.testOnAllTransports;

/* Helpers */
function randomString() {
return Math.random().toString().slice(2);
}
var randomString = helper.randomString;

function checkCanSubscribe(channel, testChannel) {
return function (callback) {
Expand Down
Loading

0 comments on commit 53ad2ec

Please sign in to comment.