Skip to content

Commit

Permalink
(WIP) Add tree-shakable Vcdiff plugin
Browse files Browse the repository at this point in the history
TODO update README and error message

TODO update to use 40019:
https://github.com/ably/ably-common/blob/main/protocol/errors.json
  • Loading branch information
lawrence-forooghian committed Nov 21, 2023
1 parent 5f060cd commit 9b11adb
Show file tree
Hide file tree
Showing 20 changed files with 313 additions and 238 deletions.
10 changes: 0 additions & 10 deletions ably.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,16 +565,6 @@ declare namespace Types {
* @defaultValue 10s
*/
realtimeRequestTimeout?: number;

/**
* A map between a plugin type and a plugin object.
*/
plugins?: {
/**
* A plugin capable of decoding `vcdiff`-encoded messages. For more information on how to configure a channel to use delta encoding, see the [documentation for the `@ably-forks/vcdiff-decoder` package](https://github.com/ably-forks/vcdiff-decoder#usage).
*/
vcdiff?: any;
};
}

/**
Expand Down
10 changes: 10 additions & 0 deletions modules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,11 @@ export declare const MessageInteractions: unknown;
*/
export declare const RealtimePublishing: unknown;

/**
* TODO
*/
export declare const Vcdiff: unknown;

/**
* Pass a `ModulesMap` to { @link BaseRest.constructor | the constructor of BaseRest } or {@link BaseRealtime.constructor | that of BaseRealtime} to specify which functionality should be made available to that client.
*/
Expand Down Expand Up @@ -232,6 +237,11 @@ export interface ModulesMap {
* See {@link RealtimePublishing | documentation for the `RealtimePublishing` module}.
*/
RealtimePublishing?: typeof RealtimePublishing;

/**
* See {@link Vcdiff | documentation for the `Vcdiff` module}.
*/
Vcdiff?: typeof Vcdiff;
}

/**
Expand Down
1 change: 1 addition & 0 deletions scripts/moduleReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const moduleNames = [
'FetchRequest',
'MessageInteractions',
'RealtimePublishing',
'Vcdiff',
];

// List of all free-standing functions exported by the library along with the
Expand Down
9 changes: 8 additions & 1 deletion src/common/lib/client/baserealtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ import ClientOptions from '../../types/ClientOptions';
import * as API from '../../../../ably';
import { ModulesMap, RealtimePresenceModule } from './modulesmap';
import { TransportNames } from 'common/constants/TransportName';
import { TransportImplementations } from 'common/platform';
import Platform, { TransportImplementations } from 'common/platform';
import { RealtimePublishing } from './realtimepublishing';
import { decode as decodeVcdiff } from '@ably/vcdiff-decoder';

/**
`BaseRealtime` is an export of the tree-shakable version of the SDK, and acts as the base class for the `DefaultRealtime` class exported by the non tree-shakable version.
*/
class BaseRealtime extends BaseClient {
readonly _RealtimePresence: RealtimePresenceModule | null;
readonly __RealtimePublishing: typeof RealtimePublishing | null;
readonly _decodeVcdiff: typeof decodeVcdiff | null;
// Extra transport implementations available to this client, in addition to those in Platform.Transports.bundledImplementations
readonly _additionalTransportImplementations: TransportImplementations;
_channels: any;
Expand All @@ -31,6 +33,11 @@ class BaseRealtime extends BaseClient {
this._additionalTransportImplementations = BaseRealtime.transportImplementationsFromModules(modules);
this._RealtimePresence = modules.RealtimePresence ?? null;
this.__RealtimePublishing = modules.RealtimePublishing ?? null;
if (Platform.Vcdiff.supported) {
this._decodeVcdiff = Platform.Vcdiff.bundledDecode ?? modules.Vcdiff;
} else {
this._decodeVcdiff = null;
}
this.connection = new Connection(this, this.options);
this._channels = new Channels(this);
if (options.autoConnect !== false) this.connect();
Expand Down
1 change: 1 addition & 0 deletions src/common/lib/client/modulesmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export interface ModulesMap {
FetchRequest?: typeof fetchRequest;
MessageInteractions?: typeof FilteredSubscriptions;
RealtimePublishing?: typeof RealtimePublishing;
Vcdiff?: any; // TODO
}

export const allCommonModules: ModulesMap = { Rest };
2 changes: 1 addition & 1 deletion src/common/lib/client/realtimechannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class RealtimeChannel extends EventEmitter {
this._attachResume = false;
this._decodingContext = {
channelOptions: this.channelOptions,
plugins: client.options.plugins || {},
decodeVcdiff: client._decodeVcdiff ?? undefined,
baseEncodedPreviousPayload: undefined,
};
this._lastPayload = {
Expand Down
14 changes: 6 additions & 8 deletions src/common/lib/types/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,14 @@ export type CipherOptions = {

export type EncodingDecodingContext = {
channelOptions: ChannelOptions;
plugins: {
vcdiff?: {
decode: (delta: Uint8Array, source: Uint8Array) => Uint8Array;
};
};
decodeVcdiff?: (delta: Uint8Array, source: Uint8Array) => Uint8Array;
baseEncodedPreviousPayload?: Buffer | BrowserBufferlike;
};

function normaliseContext(context: CipherOptions | EncodingDecodingContext | ChannelOptions): EncodingDecodingContext {
if (!context || !(context as EncodingDecodingContext).channelOptions) {
return {
channelOptions: context as ChannelOptions,
plugins: {},
baseEncodedPreviousPayload: undefined,
};
}
Expand Down Expand Up @@ -216,7 +211,10 @@ export async function decode(
throw new Error('Unable to decrypt message; not an encrypted channel');
}
case 'vcdiff':
if (!context.plugins || !context.plugins.vcdiff) {
// static Vcdiff: { supported: false } | { supported: true; bundledDecode: typeof decodeVcdiff | null };
// TODO does the platform support it?
// is it missing because of the user or because the platform doesn't support it
if (!context.decodeVcdiff) {
throw new ErrorInfo('Missing Vcdiff decoder (https://github.com/ably-forks/vcdiff-decoder)', 40019, 400);
}
if (typeof Uint8Array === 'undefined') {
Expand All @@ -236,7 +234,7 @@ export async function decode(
const deltaBaseBuffer = Platform.BufferUtils.toBuffer(deltaBase as Buffer);
data = Platform.BufferUtils.toBuffer(data);

data = Platform.BufferUtils.arrayBufferViewToBuffer(context.plugins.vcdiff.decode(data, deltaBaseBuffer));
data = Platform.BufferUtils.arrayBufferViewToBuffer(context.decodeVcdiff(data, deltaBaseBuffer));
lastPayload = data;
} catch (e) {
throw new ErrorInfo('Vcdiff delta decode failed with ' + e, 40018, 400);
Expand Down
3 changes: 3 additions & 0 deletions src/common/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as WebBufferUtils from '../platform/web/lib/util/bufferutils';
import * as NodeBufferUtils from '../platform/nodejs/lib/util/bufferutils';
import { IUntypedCryptoStatic } from '../common/types/ICryptoStatic';
import TransportName from './constants/TransportName';
import { decode as decodeVcdiff } from '@ably/vcdiff-decoder';

type Bufferlike = WebBufferUtils.Bufferlike | NodeBufferUtils.Bufferlike;
type BufferUtilsOutput = WebBufferUtils.Output | NodeBufferUtils.Output;
Expand Down Expand Up @@ -39,4 +40,6 @@ export default class Platform {
};
static Defaults: IDefaults;
static WebStorage: IWebStorage | null;
// { supported: true, bundledDecode: null } means that the decode implementation can be provided via ModulesMap
static Vcdiff: { supported: false } | { supported: true; bundledDecode: typeof decodeVcdiff | null };
}
2 changes: 2 additions & 0 deletions src/platform/nativescript/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DefaultRest } from '../../common/lib/client/defaultrest';
import { DefaultRealtime } from '../../common/lib/client/defaultrealtime';
import Platform from '../../common/platform';
import ErrorInfo from '../../common/lib/types/errorinfo';
import { decode as decodeVcdiff } from '@ably/vcdiff-decoder';

// Platform Specific
import BufferUtils from '../web/lib/util/bufferutils';
Expand All @@ -29,6 +30,7 @@ Platform.Http = Http;
Platform.Config = Config;
Platform.Transports = Transports;
Platform.WebStorage = WebStorage;
Platform.Vcdiff = { supported: true, bundledDecode: decodeVcdiff };

for (const clientClass of [DefaultRest, DefaultRealtime]) {
clientClass.Crypto = Crypto;
Expand Down
2 changes: 2 additions & 0 deletions src/platform/nodejs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DefaultRest } from '../../common/lib/client/defaultrest';
import { DefaultRealtime } from '../../common/lib/client/defaultrealtime';
import Platform from '../../common/platform';
import ErrorInfo from '../../common/lib/types/errorinfo';
import { decode as decodeVcdiff } from '@ably/vcdiff-decoder';

// Platform Specific
import BufferUtils from './lib/util/bufferutils';
Expand All @@ -25,6 +26,7 @@ Platform.Http = Http;
Platform.Config = Config;
Platform.Transports = Transports;
Platform.WebStorage = null;
Platform.Vcdiff = { supported: true, bundledDecode: decodeVcdiff };

for (const clientClass of [DefaultRest, DefaultRealtime]) {
clientClass.Crypto = Crypto;
Expand Down
2 changes: 2 additions & 0 deletions src/platform/react-native/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { DefaultRest } from '../../common/lib/client/defaultrest';
import { DefaultRealtime } from '../../common/lib/client/defaultrealtime';
import Platform from '../../common/platform';
import ErrorInfo from '../../common/lib/types/errorinfo';
import { decode as decodeVcdiff } from '@ably/vcdiff-decoder';

// Platform Specific
import BufferUtils from '../web/lib/util/bufferutils';
Expand All @@ -29,6 +30,7 @@ Platform.Http = Http;
Platform.Config = Config;
Platform.Transports = Transports;
Platform.WebStorage = WebStorage;
Platform.Vcdiff = { supported: true, bundledDecode: decodeVcdiff };

for (const clientClass of [DefaultRest, DefaultRealtime]) {
clientClass.Crypto = Crypto;
Expand Down
2 changes: 2 additions & 0 deletions src/platform/web/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Platform.Http = Http;
Platform.Config = Config;
Platform.Transports = Transports;
Platform.WebStorage = WebStorage;
// To use vcdiff on web you must use the modular variant of the library
Platform.Vcdiff = { supported: false };

for (const clientClass of [DefaultRest, DefaultRealtime]) {
clientClass.Crypto = Crypto;
Expand Down
2 changes: 2 additions & 0 deletions src/platform/web/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Platform.Http = Http;
Platform.Config = Config;
Platform.Transports = ModulesTransports;
Platform.WebStorage = WebStorage;
Platform.Vcdiff = { supported: true, bundledDecode: null };

Http.bundledRequestImplementations = modulesBundledRequestImplementations;

Expand Down Expand Up @@ -49,6 +50,7 @@ export * from './modules/msgpack';
export * from './modules/realtimepresence';
export * from './modules/transports';
export * from './modules/http';
export * from './modules/vcdiff';
export { Rest } from '../../common/lib/client/rest';
export { FilteredSubscriptions as MessageInteractions } from '../../common/lib/client/filteredsubscriptions';
export { RealtimePublishing } from '../../common/lib/client/realtimepublishing';
Expand Down
1 change: 1 addition & 0 deletions src/platform/web/modules/vcdiff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { decode as Vcdiff } from '@ably/vcdiff-decoder';
38 changes: 35 additions & 3 deletions test/browser/modules.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ import {
XHRRequest,
MessageInteractions,
RealtimePublishing,
Vcdiff,
} from '../../build/modules/index.js';

function registerAblyModulesTests(helper) {
function registerAblyModulesTests(helper, registerDeltaTests) {
describe('browser/modules', function () {
this.timeout(10 * 1000);
const expect = chai.expect;
Expand Down Expand Up @@ -751,14 +752,45 @@ function registerAblyModulesTests(helper) {
});
});
});

// Tests for the Vcdiff module
//
// Note: Unlike the other tests in this file, which only test how the
// absence or presence of a module affects the client, assuming that the
// underlying functionality is tested in detail in the test suite for the
// default variant of the library, the tests for the Vcdiff module actually
// test the library’s delta encoding functionality. This is because on web,
// delta encoding functionality is only available in the modular variant of
// the library.
(() => {
const config = {
createRealtimeWithDeltaPlugin: (options) => {
return new BaseRealtime(options, {
WebSocketTransport,
FetchRequest,
RealtimePublishing,
Vcdiff,
});
},
createRealtimeWithoutDeltaPlugin: (options) => {
return new BaseRealtime(options, {
WebSocketTransport,
FetchRequest,
RealtimePublishing,
});
},
};

registerDeltaTests('Vcdiff', config);
})();
});
}

// This function is called by browser_setup.js once `require` is available
window.registerAblyModulesTests = async () => {
return new Promise((resolve) => {
require(['shared_helper'], (helper) => {
registerAblyModulesTests(helper);
require(['shared_helper', 'delta_tests'], (helper, registerDeltaTests) => {
registerAblyModulesTests(helper, registerDeltaTests);
resolve();
});
});
Expand Down
4 changes: 0 additions & 4 deletions test/common/globals/named_dependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ define(function () {
// Ably modules
ably: { browser: 'build/ably', node: 'build/ably-node' },
'ably.noencryption': { browser: 'build/ably.noencryption' },
'vcdiff-decoder': {
browser: 'node_modules/@ably/vcdiff-decoder/dist/vcdiff-decoder',
node: 'node_modules/@ably/vcdiff-decoder',
},

// test modules
globals: { browser: 'test/common/globals/environment', node: 'test/common/globals/environment' },
Expand Down
28 changes: 26 additions & 2 deletions test/realtime/delta.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
'use strict';

define(['delta_tests'], function (registerDeltaTests) {
registerDeltaTests('realtime/delta');
define(['shared_helper', 'delta_tests'], function (helper, runDeltaTests) {
const Platform = helper.Ably.Realtime.Platform;

let config;

if (Platform.Vcdiff.supported) {
if (Platform.Vcdiff.bundledDecode) {
config = {
createRealtimeWithDeltaPlugin: (options) => {
return helper.AblyRealtime(options);
},
};
} else {
throw new Error(
'vcdiff is supported but not bundled; this should only be the case for the modular variant of the library, which this test doesn’t exercise'
);
}
} else {
config = {
createRealtimeWithoutDeltaPlugin: (options) => {
return new helper.AblyRealtime(options);
},
};
}

runDeltaTests('realtime/delta', config);
});
Loading

0 comments on commit 9b11adb

Please sign in to comment.