diff --git a/scripts/moduleReport.js b/scripts/moduleReport.js index c346615d36..4aa41cd949 100644 --- a/scripts/moduleReport.js +++ b/scripts/moduleReport.js @@ -12,6 +12,8 @@ const functions = [ { name: 'decodeEncryptedMessage', transitiveImports: ['Crypto'] }, { name: 'decodeMessages', transitiveImports: [] }, { name: 'decodeEncryptedMessages', transitiveImports: ['Crypto'] }, + { name: 'decodePresenceMessage', transitiveImports: [] }, + { name: 'decodePresenceMessages', transitiveImports: [] }, ]; function formatBytes(bytes) { diff --git a/src/common/lib/client/baseclient.ts b/src/common/lib/client/baseclient.ts index fcdab7cdd5..f22864fb08 100644 --- a/src/common/lib/client/baseclient.ts +++ b/src/common/lib/client/baseclient.ts @@ -9,7 +9,6 @@ import { IHttp, RequestParams } from '../../types/http'; import ClientOptions, { NormalisedClientOptions } from '../../types/ClientOptions'; import Platform from '../../platform'; -import PresenceMessage from '../types/presencemessage'; import { ModulesMap } from './modulesmap'; import { Rest } from './rest'; import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic'; @@ -133,7 +132,6 @@ class BaseClient { } static Platform = Platform; - static PresenceMessage = PresenceMessage; } export default BaseClient; diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index 68073accf0..242e612288 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -8,6 +8,7 @@ import Platform from 'common/platform'; import { DefaultMessage } from '../types/defaultmessage'; import { MsgPack } from 'common/types/msgpack'; import RealtimePresence from './realtimepresence'; +import { DefaultPresenceMessage } from '../types/defaultpresencemessage'; /** `DefaultRealtime` is the class that the non tree-shakable version of the SDK exports as `Realtime`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. @@ -39,6 +40,7 @@ export class DefaultRealtime extends BaseRealtime { } static Message = DefaultMessage; + static PresenceMessage = DefaultPresenceMessage; static _MsgPack: MsgPack | null = null; } diff --git a/src/common/lib/client/defaultrest.ts b/src/common/lib/client/defaultrest.ts index e70a9eecf6..c03849af37 100644 --- a/src/common/lib/client/defaultrest.ts +++ b/src/common/lib/client/defaultrest.ts @@ -4,6 +4,7 @@ import { allCommonModules } from './modulesmap'; import Platform from 'common/platform'; import { DefaultMessage } from '../types/defaultmessage'; import { MsgPack } from 'common/types/msgpack'; +import { DefaultPresenceMessage } from '../types/defaultpresencemessage'; /** `DefaultRest` is the class that the non tree-shakable version of the SDK exports as `Rest`. It ensures that this version of the SDK includes all of the functionality which is optionally available in the tree-shakable version. @@ -35,6 +36,7 @@ export class DefaultRest extends BaseRest { } static Message = DefaultMessage; + static PresenceMessage = DefaultPresenceMessage; static _MsgPack: MsgPack | null = null; } diff --git a/src/common/lib/types/defaultpresencemessage.ts b/src/common/lib/types/defaultpresencemessage.ts new file mode 100644 index 0000000000..4cba6d4302 --- /dev/null +++ b/src/common/lib/types/defaultpresencemessage.ts @@ -0,0 +1,18 @@ +import * as API from '../../../../ably'; +import PresenceMessage, { fromEncoded, fromEncodedArray } from './presencemessage'; + +/** + `DefaultPresenceMessage` is the class returned by `DefaultRest` and `DefaultRealtime`’s `PresenceMessage` static property. It introduces the static methods described in the `PresenceMessageStatic` interface of the public API of the non tree-shakable version of the library. + */ +export class DefaultPresenceMessage extends PresenceMessage { + static async fromEncoded(encoded: unknown, inputOptions?: API.Types.ChannelOptions): Promise { + return fromEncoded(encoded, inputOptions); + } + + static async fromEncodedArray( + encodedArray: Array, + options?: API.Types.ChannelOptions + ): Promise { + return fromEncodedArray(encodedArray, options); + } +} diff --git a/src/common/lib/types/presencemessage.ts b/src/common/lib/types/presencemessage.ts index 48b6ef5a1b..37d59f69d6 100644 --- a/src/common/lib/types/presencemessage.ts +++ b/src/common/lib/types/presencemessage.ts @@ -9,6 +9,29 @@ function toActionValue(actionString: string) { return PresenceMessage.Actions.indexOf(actionString); } +export async function fromEncoded(encoded: unknown, options?: API.Types.ChannelOptions): Promise { + const msg = PresenceMessage.fromValues(encoded as PresenceMessage | Record, true); + /* if decoding fails at any point, catch and return the message decoded to + * the fullest extent possible */ + try { + await PresenceMessage.decode(msg, options ?? {}); + } catch (e) { + Logger.logAction(Logger.LOG_ERROR, 'PresenceMessage.fromEncoded()', (e as Error).toString()); + } + return msg; +} + +export async function fromEncodedArray( + encodedArray: unknown[], + options?: API.Types.ChannelOptions +): Promise { + return Promise.all( + encodedArray.map(function (encoded) { + return PresenceMessage.fromEncoded(encoded, options); + }) + ); +} + class PresenceMessage { action?: string | number; id?: string; @@ -140,26 +163,14 @@ class PresenceMessage { } static async fromEncoded(encoded: unknown, options?: API.Types.ChannelOptions): Promise { - const msg = PresenceMessage.fromValues(encoded as PresenceMessage | Record, true); - /* if decoding fails at any point, catch and return the message decoded to - * the fullest extent possible */ - try { - await PresenceMessage.decode(msg, options ?? {}); - } catch (e) { - Logger.logAction(Logger.LOG_ERROR, 'PresenceMessage.fromEncoded()', (e as Error).toString()); - } - return msg; + return fromEncoded(encoded, options); } static async fromEncodedArray( encodedArray: unknown[], options?: API.Types.ChannelOptions ): Promise { - return Promise.all( - encodedArray.map(function (encoded) { - return PresenceMessage.fromEncoded(encoded, options); - }) - ); + return fromEncodedArray(encodedArray, options); } static getMessagesSize = Message.getMessagesSize; diff --git a/src/platform/web/modules.ts b/src/platform/web/modules.ts index 35076c8b36..16cd8c30f9 100644 --- a/src/platform/web/modules.ts +++ b/src/platform/web/modules.ts @@ -40,6 +40,7 @@ if (Platform.Config.noUpgrade) { export * from './modules/crypto'; export * from './modules/message'; +export * from './modules/presencemessage'; export * from './modules/msgpack'; export * from './modules/realtimepresence'; export { Rest } from '../../common/lib/client/rest'; diff --git a/src/platform/web/modules/presencemessage.ts b/src/platform/web/modules/presencemessage.ts index e69de29bb2..3f56016d39 100644 --- a/src/platform/web/modules/presencemessage.ts +++ b/src/platform/web/modules/presencemessage.ts @@ -0,0 +1,12 @@ +import * as API from '../../../../ably'; +import { fromEncoded, fromEncodedArray } from '../../../common/lib/types/presencemessage'; + +// The type assertions for the decode* functions below are due to https://github.com/ably/ably-js/issues/1421 + +export const decodePresenceMessage = ((obj, options) => { + return fromEncoded(obj, options); +}) as API.Types.PresenceMessageStatic['fromEncoded']; + +export const decodePresenceMessages = ((obj, options) => { + return fromEncodedArray(obj, options); +}) as API.Types.PresenceMessageStatic['fromEncodedArray']; diff --git a/test/browser/modules.test.js b/test/browser/modules.test.js index 079ca359aa..be5b3d6dd3 100644 --- a/test/browser/modules.test.js +++ b/test/browser/modules.test.js @@ -11,6 +11,8 @@ import { Crypto, MsgPack, RealtimePresence, + decodePresenceMessage, + decodePresenceMessages, } from '../../build/modules/index.js'; describe('browser/modules', function () { @@ -349,4 +351,37 @@ describe('browser/modules', function () { }); }); }); + + describe('PresenceMessage standalone functions', () => { + describe('decodePresenceMessage', () => { + it('decodes a presence message’s data', async () => { + const buffer = BufferUtils.utf8Encode('foo'); + const encodedMessage = { data: BufferUtils.base64Encode(buffer), encoding: 'base64' }; + + const decodedMessage = await decodePresenceMessage(encodedMessage); + + expect(BufferUtils.areBuffersEqual(decodedMessage.data, buffer)).to.be.true; + expect(decodedMessage.encoding).to.be.null; + }); + }); + + describe('decodeMessages', () => { + it('decodes presence messages’ data', async () => { + const buffers = ['foo', 'bar'].map((data) => BufferUtils.utf8Encode(data)); + const encodedMessages = buffers.map((buffer) => ({ + data: BufferUtils.base64Encode(buffer), + encoding: 'base64', + })); + + const decodedMessages = await decodePresenceMessages(encodedMessages); + + for (let i = 0; i < decodedMessages.length; i++) { + const decodedMessage = decodedMessages[i]; + + expect(BufferUtils.areBuffersEqual(decodedMessage.data, buffers[i])).to.be.true; + expect(decodedMessage.encoding).to.be.null; + } + }); + }); + }); });