From ed5ff7d530e73f8a2173c69f28f0791ac1c3abab Mon Sep 17 00:00:00 2001 From: Lawrence Forooghian Date: Tue, 14 Nov 2023 17:33:22 -0300 Subject: [PATCH] Remove static stuff from *Message classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace them by standalone exports, which can be tree-shaken. (My eye was in particular caught by the Message.encrypt function, which I was thinking it would be good to — at some point — bundle only if the Crypto module is used. This change isn’t made here though.) --- src/common/lib/client/defaultrealtime.ts | 4 +- src/common/lib/client/realtimechannel.ts | 48 +- src/common/lib/client/realtimepresence.ts | 12 +- src/common/lib/client/restchannel.ts | 21 +- src/common/lib/client/restchannelmixin.ts | 4 +- src/common/lib/client/restpresence.ts | 4 +- src/common/lib/client/restpresencemixin.ts | 4 +- src/common/lib/transport/comettransport.ts | 12 +- src/common/lib/transport/connectionmanager.ts | 17 +- src/common/lib/transport/protocol.ts | 6 +- src/common/lib/transport/transport.ts | 17 +- .../lib/transport/websockettransport.ts | 9 +- src/common/lib/types/defaultmessage.ts | 29 +- .../lib/types/defaultprotocolmessage.ts | 10 + src/common/lib/types/message.ts | 414 +++++++++--------- src/common/lib/types/presencemessage.ts | 98 ++--- src/common/lib/types/protocolmessage.ts | 127 +++--- 17 files changed, 452 insertions(+), 384 deletions(-) create mode 100644 src/common/lib/types/defaultprotocolmessage.ts diff --git a/src/common/lib/client/defaultrealtime.ts b/src/common/lib/client/defaultrealtime.ts index 1dd3d402dd..a4ea6c2965 100644 --- a/src/common/lib/client/defaultrealtime.ts +++ b/src/common/lib/client/defaultrealtime.ts @@ -3,7 +3,7 @@ import ClientOptions from '../../types/ClientOptions'; import { allCommonModules } from './modulesmap'; import * as Utils from '../util/utils'; import ConnectionManager from '../transport/connectionmanager'; -import ProtocolMessage from '../types/protocolmessage'; +import { DefaultProtocolMessage } from '../types/defaultprotocolmessage'; import Platform from 'common/platform'; import { DefaultMessage } from '../types/defaultmessage'; import { MsgPack } from 'common/types/msgpack'; @@ -34,7 +34,7 @@ export class DefaultRealtime extends BaseRealtime { static Utils = Utils; static ConnectionManager = ConnectionManager; - static ProtocolMessage = ProtocolMessage; + static ProtocolMessage = DefaultProtocolMessage; private static _Crypto: typeof Platform.Crypto = null; static get Crypto() { diff --git a/src/common/lib/client/realtimechannel.ts b/src/common/lib/client/realtimechannel.ts index e3d054ca15..6a4694690d 100644 --- a/src/common/lib/client/realtimechannel.ts +++ b/src/common/lib/client/realtimechannel.ts @@ -1,12 +1,27 @@ -import ProtocolMessage from '../types/protocolmessage'; +import ProtocolMessage, { + actions, + channelModes, + fromValues as protocolMessageFromValues, +} from '../types/protocolmessage'; import EventEmitter from '../util/eventemitter'; import * as Utils from '../util/utils'; import Logger from '../util/logger'; import RealtimePresence from './realtimepresence'; -import Message, { CipherOptions } from '../types/message'; +import Message, { + fromValues as messageFromValues, + fromValuesArray as messagesFromValuesArray, + encodeArray as encodeMessagesArray, + decode as decodeMessage, + getMessagesSize, + CipherOptions, +} from '../types/message'; import ChannelStateChange from './channelstatechange'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; -import PresenceMessage, { fromValues as presenceMessageFromValues } from '../types/presencemessage'; +import PresenceMessage, { + fromValues as presenceMessageFromValues, + fromValuesArray as presenceMessagesFromValuesArray, + decode as decodePresenceMessage, +} from '../types/presencemessage'; import ConnectionErrors from '../transport/connectionerrors'; import * as API from '../../../../ably'; import ConnectionManager from '../transport/connectionmanager'; @@ -25,7 +40,6 @@ interface RealtimeHistoryParams { from_serial?: string; } -const actions = ProtocolMessage.Action; const noop = function () {}; function validateChannelOptions(options?: API.Types.ChannelOptions) { @@ -41,7 +55,7 @@ function validateChannelOptions(options?: API.Types.ChannelOptions) { if ( !currentMode || typeof currentMode !== 'string' || - !Utils.arrIn(ProtocolMessage.channelModes, String.prototype.toUpperCase.call(currentMode)) + !Utils.arrIn(channelModes, String.prototype.toUpperCase.call(currentMode)) ) { return new ErrorInfo('Invalid channel mode: ' + currentMode, 40000, 400); } @@ -230,8 +244,8 @@ class RealtimeChannel extends EventEmitter { return; } if (argCount == 2) { - if (Utils.isObject(messages)) messages = [Message.fromValues(messages)]; - else if (Utils.isArray(messages)) messages = Message.fromValuesArray(messages); + if (Utils.isObject(messages)) messages = [messageFromValues(messages)]; + else if (Utils.isArray(messages)) messages = messagesFromValuesArray(messages); else throw new ErrorInfo( 'The single-argument form of publish() expects a message object or an array of message objects', @@ -239,16 +253,16 @@ class RealtimeChannel extends EventEmitter { 400 ); } else { - messages = [Message.fromValues({ name: args[0], data: args[1] })]; + messages = [messageFromValues({ name: args[0], data: args[1] })]; } const maxMessageSize = this.client.options.maxMessageSize; - Message.encodeArray(messages, this.channelOptions as CipherOptions, (err: Error | null) => { + encodeMessagesArray(messages, this.channelOptions as CipherOptions, (err: Error | null) => { if (err) { callback(err); return; } /* RSL1i */ - const size = Message.getMessagesSize(messages); + const size = getMessagesSize(messages); if (size > maxMessageSize) { callback( new ErrorInfo( @@ -354,7 +368,7 @@ class RealtimeChannel extends EventEmitter { attachImpl(): void { Logger.logAction(Logger.LOG_MICRO, 'RealtimeChannel.attachImpl()', 'sending ATTACH message'); - const attachMsg = ProtocolMessage.fromValues({ + const attachMsg = protocolMessageFromValues({ action: actions.ATTACH, channel: this.name, params: this.channelOptions.params, @@ -424,7 +438,7 @@ class RealtimeChannel extends EventEmitter { detachImpl(callback?: ErrCallback): void { Logger.logAction(Logger.LOG_MICRO, 'RealtimeChannel.detach()', 'sending DETACH message'); - const msg = ProtocolMessage.fromValues({ action: actions.DETACH, channel: this.name }); + const msg = protocolMessageFromValues({ action: actions.DETACH, channel: this.name }); this.sendMessage(msg, callback || noop); } @@ -479,7 +493,7 @@ class RealtimeChannel extends EventEmitter { } /* send sync request */ - const syncMessage = ProtocolMessage.fromValues({ action: actions.SYNC, channel: this.name }); + const syncMessage = protocolMessageFromValues({ action: actions.SYNC, channel: this.name }); if (this.syncChannelSerial) { syncMessage.channelSerial = this.syncChannelSerial; } @@ -491,11 +505,11 @@ class RealtimeChannel extends EventEmitter { } sendPresence(presence: PresenceMessage | PresenceMessage[], callback?: ErrCallback): void { - const msg = ProtocolMessage.fromValues({ + const msg = protocolMessageFromValues({ action: actions.PRESENCE, channel: this.name, presence: Utils.isArray(presence) - ? PresenceMessage.fromValuesArray(presence) + ? presenceMessagesFromValuesArray(presence) : [presenceMessageFromValues(presence)], }); this.sendMessage(msg, callback); @@ -579,7 +593,7 @@ class RealtimeChannel extends EventEmitter { for (let i = 0; i < presence.length; i++) { try { presenceMsg = presence[i]; - await PresenceMessage.decode(presenceMsg, options); + await decodePresenceMessage(presenceMsg, options); if (!presenceMsg.connectionId) presenceMsg.connectionId = connectionId; if (!presenceMsg.timestamp) presenceMsg.timestamp = timestamp; if (!presenceMsg.id) presenceMsg.id = id + ':' + i; @@ -635,7 +649,7 @@ class RealtimeChannel extends EventEmitter { for (let i = 0; i < messages.length; i++) { const msg = messages[i]; try { - await Message.decode(msg, this._decodingContext); + await decodeMessage(msg, this._decodingContext); } catch (e) { /* decrypt failed .. the most likely cause is that we have the wrong key */ Logger.logAction(Logger.LOG_ERROR, 'RealtimeChannel.processMessage()', (e as Error).toString()); diff --git a/src/common/lib/client/realtimepresence.ts b/src/common/lib/client/realtimepresence.ts index 0027e22e20..51f460a618 100644 --- a/src/common/lib/client/realtimepresence.ts +++ b/src/common/lib/client/realtimepresence.ts @@ -1,7 +1,11 @@ import * as Utils from '../util/utils'; import EventEmitter from '../util/eventemitter'; import Logger from '../util/logger'; -import PresenceMessage, { fromValues as presenceMessageFromValues } from '../types/presencemessage'; +import PresenceMessage, { + fromValues as presenceMessageFromValues, + fromData as presenceMessageFromData, + encode as encodePresenceMessage, +} from '../types/presencemessage'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from '../types/errorinfo'; import RealtimeChannel from './realtimechannel'; import Multicaster from '../util/multicaster'; @@ -147,7 +151,7 @@ class RealtimePresence extends EventEmitter { 'channel = ' + channel.name + ', id = ' + id + ', client = ' + (clientId || '(implicit) ' + getClientId(this)) ); - const presence = PresenceMessage.fromData(data); + const presence = presenceMessageFromData(data); presence.action = action; if (id) { presence.id = id; @@ -156,7 +160,7 @@ class RealtimePresence extends EventEmitter { presence.clientId = clientId; } - PresenceMessage.encode(presence, channel.channelOptions as CipherOptions, (err: IPartialErrorInfo) => { + encodePresenceMessage(presence, channel.channelOptions as CipherOptions, (err: IPartialErrorInfo) => { if (err) { callback(err); return; @@ -214,7 +218,7 @@ class RealtimePresence extends EventEmitter { 'RealtimePresence.leaveClient()', 'leaving; channel = ' + this.channel.name + ', client = ' + clientId ); - const presence = PresenceMessage.fromData(data); + const presence = presenceMessageFromData(data); presence.action = 'leave'; if (clientId) { presence.clientId = clientId; diff --git a/src/common/lib/client/restchannel.ts b/src/common/lib/client/restchannel.ts index 6197c65191..95fe706227 100644 --- a/src/common/lib/client/restchannel.ts +++ b/src/common/lib/client/restchannel.ts @@ -1,7 +1,14 @@ import * as Utils from '../util/utils'; import Logger from '../util/logger'; import RestPresence from './restpresence'; -import Message, { CipherOptions } from '../types/message'; +import Message, { + fromValues as messageFromValues, + fromValuesArray as messagesFromValuesArray, + encodeArray as encodeMessagesArray, + serialize as serializeMessage, + getMessagesSize, + CipherOptions, +} from '../types/message'; import ErrorInfo from '../types/errorinfo'; import { PaginatedResult } from './paginatedresource'; import Resource, { ResourceCallback } from './resource'; @@ -70,13 +77,13 @@ class RestChannel { if (typeof first === 'string' || first === null) { /* (name, data, ...) */ - messages = [Message.fromValues({ name: first, data: second })]; + messages = [messageFromValues({ name: first, data: second })]; params = arguments[2]; } else if (Utils.isObject(first)) { - messages = [Message.fromValues(first)]; + messages = [messageFromValues(first)]; params = arguments[1]; } else if (Utils.isArray(first)) { - messages = Message.fromValuesArray(first); + messages = messagesFromValuesArray(first); params = arguments[1]; } else { throw new ErrorInfo( @@ -106,14 +113,14 @@ class RestChannel { }); } - Message.encodeArray(messages, this.channelOptions as CipherOptions, (err: Error) => { + encodeMessagesArray(messages, this.channelOptions as CipherOptions, (err: Error) => { if (err) { callback(err); return; } /* RSL1i */ - const size = Message.getMessagesSize(messages), + const size = getMessagesSize(messages), maxMessageSize = options.maxMessageSize; if (size > maxMessageSize) { callback( @@ -130,7 +137,7 @@ class RestChannel { return; } - this._publish(Message.serialize(messages, client._MsgPack, format), headers, params, callback); + this._publish(serializeMessage(messages, client._MsgPack, format), headers, params, callback); }); } diff --git a/src/common/lib/client/restchannelmixin.ts b/src/common/lib/client/restchannelmixin.ts index 9986dc4e74..2e3ffc84f1 100644 --- a/src/common/lib/client/restchannelmixin.ts +++ b/src/common/lib/client/restchannelmixin.ts @@ -3,7 +3,7 @@ import RestChannel from './restchannel'; import RealtimeChannel from './realtimechannel'; import * as Utils from '../util/utils'; import { PaginatedResultCallback, StandardCallback } from '../../types/utils'; -import Message from '../types/message'; +import Message, { fromResponseBody as messageFromResponseBody } from '../types/message'; import Defaults from '../util/defaults'; import PaginatedResource from './paginatedresource'; import Resource from './resource'; @@ -40,7 +40,7 @@ export class RestChannelMixin { headers, unpacked ) { - return await Message.fromResponseBody(body as Message[], options, client._MsgPack, unpacked ? undefined : format); + return await messageFromResponseBody(body as Message[], options, client._MsgPack, unpacked ? undefined : format); }).get(params as Record, callback); } diff --git a/src/common/lib/client/restpresence.ts b/src/common/lib/client/restpresence.ts index 7aec14dcb2..8997f53526 100644 --- a/src/common/lib/client/restpresence.ts +++ b/src/common/lib/client/restpresence.ts @@ -1,7 +1,7 @@ import * as Utils from '../util/utils'; import Logger from '../util/logger'; import PaginatedResource, { PaginatedResult } from './paginatedresource'; -import PresenceMessage from '../types/presencemessage'; +import PresenceMessage, { fromResponseBody as presenceMessageFromResponseBody } from '../types/presencemessage'; import { CipherOptions } from '../types/message'; import { PaginatedResultCallback } from '../../types/utils'; import RestChannel from './restchannel'; @@ -39,7 +39,7 @@ class RestPresence { headers, envelope, async function (body, headers, unpacked) { - return await PresenceMessage.fromResponseBody( + return await presenceMessageFromResponseBody( body as Record[], options as CipherOptions, client._MsgPack, diff --git a/src/common/lib/client/restpresencemixin.ts b/src/common/lib/client/restpresencemixin.ts index 7e600fdee3..296a1ec6ff 100644 --- a/src/common/lib/client/restpresencemixin.ts +++ b/src/common/lib/client/restpresencemixin.ts @@ -4,7 +4,7 @@ import * as Utils from '../util/utils'; import { PaginatedResultCallback } from '../../types/utils'; import Defaults from '../util/defaults'; import PaginatedResource, { PaginatedResult } from './paginatedresource'; -import PresenceMessage from '../types/presencemessage'; +import PresenceMessage, { fromResponseBody as presenceMessageFromResponseBody } from '../types/presencemessage'; import { CipherOptions } from '../types/message'; import { RestChannelMixin } from './restchannelmixin'; @@ -41,7 +41,7 @@ export class RestPresenceMixin { headers, unpacked ) { - return await PresenceMessage.fromResponseBody( + return await presenceMessageFromResponseBody( body as Record[], options as CipherOptions, client._MsgPack, diff --git a/src/common/lib/transport/comettransport.ts b/src/common/lib/transport/comettransport.ts index c2c68fa3da..2a68701bd1 100644 --- a/src/common/lib/transport/comettransport.ts +++ b/src/common/lib/transport/comettransport.ts @@ -1,5 +1,9 @@ import * as Utils from '../util/utils'; -import ProtocolMessage from '../types/protocolmessage'; +import ProtocolMessage, { + actions, + fromValues as protocolMessageFromValues, + fromDeserialized as protocolMessageFromDeserialized, +} from '../types/protocolmessage'; import Transport from './transport'; import Logger from '../util/logger'; import Defaults from '../util/defaults'; @@ -29,9 +33,9 @@ function protocolMessageFromRawError(err: ErrorInfo) { /* err will be either a legacy (non-protocolmessage) comet error response * (which will have an err.code), or a xhr/network error (which won't). */ if (shouldBeErrorAction(err)) { - return [ProtocolMessage.fromValues({ action: ProtocolMessage.Action.ERROR, error: err })]; + return [protocolMessageFromValues({ action: actions.ERROR, error: err })]; } else { - return [ProtocolMessage.fromValues({ action: ProtocolMessage.Action.DISCONNECTED, error: err })]; + return [protocolMessageFromValues({ action: actions.DISCONNECTED, error: err })]; } } @@ -344,7 +348,7 @@ abstract class CometTransport extends Transport { try { const items = this.decodeResponse(responseData); if (items && items.length) - for (let i = 0; i < items.length; i++) this.onProtocolMessage(ProtocolMessage.fromDeserialized(items[i])); + for (let i = 0; i < items.length; i++) this.onProtocolMessage(protocolMessageFromDeserialized(items[i])); } catch (e) { Logger.logAction( Logger.LOG_ERROR, diff --git a/src/common/lib/transport/connectionmanager.ts b/src/common/lib/transport/connectionmanager.ts index 335e2a01d4..8e38f79f79 100644 --- a/src/common/lib/transport/connectionmanager.ts +++ b/src/common/lib/transport/connectionmanager.ts @@ -1,4 +1,8 @@ -import ProtocolMessage from 'common/lib/types/protocolmessage'; +import ProtocolMessage, { + actions, + stringify as stringifyProtocolMessage, + fromValues as protocolMessageFromValues, +} from 'common/lib/types/protocolmessage'; import * as Utils from 'common/lib/util/utils'; import Protocol, { PendingMessage } from './protocol'; import Defaults, { getAgentString } from 'common/lib/util/defaults'; @@ -10,7 +14,7 @@ import ConnectionStateChange from 'common/lib/client/connectionstatechange'; import ConnectionErrors, { isRetriable } from './connectionerrors'; import ErrorInfo, { IPartialErrorInfo, PartialErrorInfo } from 'common/lib/types/errorinfo'; import Auth from 'common/lib/client/auth'; -import Message from 'common/lib/types/message'; +import Message, { getMessagesSize } from 'common/lib/types/message'; import Multicaster, { MulticasterInstance } from 'common/lib/util/multicaster'; import Transport, { TransportCtor } from './transport'; import * as API from '../../../../ably'; @@ -24,7 +28,6 @@ let globalObject = typeof global !== 'undefined' ? global : typeof window !== 'u const haveWebStorage = () => typeof Platform.WebStorage !== 'undefined' && Platform.WebStorage?.localSupported; const haveSessionStorage = () => typeof Platform.WebStorage !== 'undefined' && Platform.WebStorage?.sessionSupported; -const actions = ProtocolMessage.Action; const noop = function () {}; const transportPreferenceName = 'ably-transport-preference'; @@ -62,7 +65,7 @@ function bundleWith(dest: ProtocolMessage, src: ProtocolMessage, maxSize: number } const kind = action === actions.PRESENCE ? 'presence' : 'messages', proposed = (dest as Record)[kind].concat((src as Record)[kind]), - size = Message.getMessagesSize(proposed); + size = getMessagesSize(proposed); if (size > maxSize) { /* RTL6d1 */ return false; @@ -732,7 +735,7 @@ class ConnectionManager extends EventEmitter { // Send ACTIVATE to tell the server to make this transport the // active transport, which suspends channels until we re-attach. transport.send( - ProtocolMessage.fromValues({ + protocolMessageFromValues({ action: actions.ACTIVATE, }) ); @@ -1777,7 +1780,7 @@ class ConnectionManager extends EventEmitter { activeTransport.onAuthUpdated(tokenDetails); } - const authMsg = ProtocolMessage.fromValues({ + const authMsg = protocolMessageFromValues({ action: actions.AUTH, auth: { accessToken: tokenDetails.token, @@ -1911,7 +1914,7 @@ class ConnectionManager extends EventEmitter { return; } if (Logger.shouldLog(Logger.LOG_MICRO)) { - Logger.logAction(Logger.LOG_MICRO, 'ConnectionManager.send()', 'queueing msg; ' + ProtocolMessage.stringify(msg)); + Logger.logAction(Logger.LOG_MICRO, 'ConnectionManager.send()', 'queueing msg; ' + stringifyProtocolMessage(msg)); } this.queue(msg, callback); } diff --git a/src/common/lib/transport/protocol.ts b/src/common/lib/transport/protocol.ts index 630dc70ac3..7725cd478a 100644 --- a/src/common/lib/transport/protocol.ts +++ b/src/common/lib/transport/protocol.ts @@ -1,4 +1,4 @@ -import ProtocolMessage from '../types/protocolmessage'; +import ProtocolMessage, { actions, stringify as stringifyProtocolMessage } from '../types/protocolmessage'; import * as Utils from '../util/utils'; import EventEmitter from '../util/eventemitter'; import Logger from '../util/logger'; @@ -7,8 +7,6 @@ import ErrorInfo from '../types/errorinfo'; import Transport from './transport'; import { ErrCallback } from '../../types/utils'; -const actions = ProtocolMessage.Action; - export class PendingMessage { message: ProtocolMessage; callback?: ErrCallback; @@ -76,7 +74,7 @@ class Protocol extends EventEmitter { Logger.logAction( Logger.LOG_MICRO, 'Protocol.send()', - 'sending msg; ' + ProtocolMessage.stringify(pendingMessage.message) + 'sending msg; ' + stringifyProtocolMessage(pendingMessage.message) ); } pendingMessage.sendAttempted = true; diff --git a/src/common/lib/transport/transport.ts b/src/common/lib/transport/transport.ts index 593de97b85..3c83644aba 100644 --- a/src/common/lib/transport/transport.ts +++ b/src/common/lib/transport/transport.ts @@ -1,4 +1,8 @@ -import ProtocolMessage from '../types/protocolmessage'; +import ProtocolMessage, { + actions, + fromValues as protocolMessageFromValues, + stringify as stringifyProtocolMessage, +} from '../types/protocolmessage'; import * as Utils from '../util/utils'; import EventEmitter from '../util/eventemitter'; import Logger from '../util/logger'; @@ -21,9 +25,8 @@ export type TransportCtor = new ( forceJsonProtocol?: boolean ) => Transport; -const actions = ProtocolMessage.Action; -const closeMessage = ProtocolMessage.fromValues({ action: actions.CLOSE }); -const disconnectMessage = ProtocolMessage.fromValues({ action: actions.DISCONNECT }); +const closeMessage = protocolMessageFromValues({ action: actions.CLOSE }); +const disconnectMessage = protocolMessageFromValues({ action: actions.DISCONNECT }); /* * Transport instances inherit from EventEmitter and emit the following events: @@ -120,7 +123,7 @@ abstract class Transport extends EventEmitter { 'received on ' + this.shortName + ': ' + - ProtocolMessage.stringify(message) + + stringifyProtocolMessage(message) + '; connectionId = ' + this.connectionManager.connectionId ); @@ -239,9 +242,9 @@ abstract class Transport extends EventEmitter { } ping(id: string): void { - const msg: Record = { action: ProtocolMessage.Action.HEARTBEAT }; + const msg: Record = { action: actions.HEARTBEAT }; if (id) msg.id = id; - this.send(ProtocolMessage.fromValues(msg)); + this.send(protocolMessageFromValues(msg)); } dispose(): void { diff --git a/src/common/lib/transport/websockettransport.ts b/src/common/lib/transport/websockettransport.ts index 60f2cc061a..43c53c986b 100644 --- a/src/common/lib/transport/websockettransport.ts +++ b/src/common/lib/transport/websockettransport.ts @@ -3,7 +3,10 @@ import * as Utils from '../util/utils'; import Transport from './transport'; import Defaults from '../util/defaults'; import Logger from '../util/logger'; -import ProtocolMessage from '../types/protocolmessage'; +import ProtocolMessage, { + serialize as serializeProtocolMessage, + deserialize as deserializeProtocolMessage, +} from '../types/protocolmessage'; import ErrorInfo from '../types/errorinfo'; import NodeWebSocket from 'ws'; import ConnectionManager, { TransportParams, TransportStorage } from './connectionmanager'; @@ -104,7 +107,7 @@ class WebSocketTransport extends Transport { } try { (wsConnection as NodeWebSocket).send( - ProtocolMessage.serialize(message, this.connectionManager.realtime._MsgPack, this.params.format) + serializeProtocolMessage(message, this.connectionManager.realtime._MsgPack, this.params.format) ); } catch (e) { const msg = 'Exception from ws connection when trying to send: ' + Utils.inspectError(e); @@ -122,7 +125,7 @@ class WebSocketTransport extends Transport { 'data received; length = ' + data.length + '; type = ' + typeof data ); try { - this.onProtocolMessage(ProtocolMessage.deserialize(data, this.connectionManager.realtime._MsgPack, this.format)); + this.onProtocolMessage(deserializeProtocolMessage(data, this.connectionManager.realtime._MsgPack, this.format)); } catch (e) { Logger.logAction( Logger.LOG_ERROR, diff --git a/src/common/lib/types/defaultmessage.ts b/src/common/lib/types/defaultmessage.ts index 3ef7dab056..34135869fd 100644 --- a/src/common/lib/types/defaultmessage.ts +++ b/src/common/lib/types/defaultmessage.ts @@ -1,6 +1,15 @@ -import Message, { fromEncoded, fromEncodedArray } from './message'; +import Message, { + CipherOptions, + fromEncoded, + fromEncodedArray, + encode, + decode, + EncodingDecodingContext, +} from './message'; import * as API from '../../../../ably'; import Platform from 'common/platform'; +import PresenceMessage from './presencemessage'; +import { ChannelOptions } from 'common/types/channel'; /** `DefaultMessage` is the class returned by `DefaultRest` and `DefaultRealtime`’s `Message` static property. It introduces the static methods described in the `MessageStatic` interface of the public API of the non tree-shakable version of the library. @@ -13,4 +22,22 @@ export class DefaultMessage extends Message { static async fromEncodedArray(encodedArray: Array, options?: API.Types.ChannelOptions): Promise { return fromEncodedArray(Platform.Crypto, encodedArray, options); } + + // Used by tests + static fromValues(values: unknown): Message { + return Object.assign(new Message(), values); + } + + // Used by tests + static encode(msg: Message | PresenceMessage, options: CipherOptions, callback: Function): void { + encode(msg, options, callback); + } + + // Used by tests + static async decode( + message: Message | PresenceMessage, + inputContext: CipherOptions | EncodingDecodingContext | ChannelOptions + ): Promise { + return decode(message, inputContext); + } } diff --git a/src/common/lib/types/defaultprotocolmessage.ts b/src/common/lib/types/defaultprotocolmessage.ts new file mode 100644 index 0000000000..4c2c6f92aa --- /dev/null +++ b/src/common/lib/types/defaultprotocolmessage.ts @@ -0,0 +1,10 @@ +import ProtocolMessage, { fromDeserialized } from './protocolmessage'; + +/** + `DefaultProtocolMessage` is the class returned by `DefaultRealtime`’s `ProtocolMessage` static property. It exposes the static methods that are used by the tests. + */ +export class DefaultProtocolMessage extends ProtocolMessage { + static fromDeserialized(deserialized: Record): ProtocolMessage { + return fromDeserialized(deserialized); + } +} diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 48285adae3..75fd1e33fe 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -22,7 +22,7 @@ export type CipherOptions = { }; }; -type EncodingDecodingContext = { +export type EncodingDecodingContext = { channelOptions: ChannelOptions; plugins: { vcdiff?: { @@ -81,12 +81,12 @@ export async function fromEncoded( encoded: unknown, inputOptions?: API.Types.ChannelOptions ): Promise { - const msg = Message.fromValues(encoded); + const msg = fromValues(encoded); const options = normalizeCipherOptions(Crypto, inputOptions ?? null); /* if decoding fails at any point, catch and return the message decoded to * the fullest extent possible */ try { - await Message.decode(msg, options); + await decode(msg, options); } catch (e) { Logger.logAction(Logger.LOG_ERROR, 'Message.fromEncoded()', (e as Error).toString()); } @@ -105,6 +105,208 @@ export async function fromEncodedArray( ); } +function encrypt(msg: Message | PresenceMessage, options: CipherOptions, callback: Function) { + let data = msg.data, + encoding = msg.encoding, + cipher = options.channelCipher; + + encoding = encoding ? encoding + '/' : ''; + if (!Platform.BufferUtils.isBuffer(data)) { + data = Platform.BufferUtils.utf8Encode(String(data)); + encoding = encoding + 'utf-8/'; + } + cipher.encrypt(data, function (err: Error, data: unknown) { + if (err) { + callback(err); + return; + } + msg.data = data; + msg.encoding = encoding + 'cipher+' + cipher.algorithm; + callback(null, msg); + }); +} + +export function encode(msg: Message | PresenceMessage, options: CipherOptions, callback: Function): void { + const data = msg.data; + const nativeDataType = + typeof data == 'string' || Platform.BufferUtils.isBuffer(data) || data === null || data === undefined; + + if (!nativeDataType) { + if (Utils.isObject(data) || Utils.isArray(data)) { + msg.data = JSON.stringify(data); + msg.encoding = msg.encoding ? msg.encoding + '/json' : 'json'; + } else { + throw new ErrorInfo('Data type is unsupported', 40013, 400); + } + } + + if (options != null && options.cipher) { + encrypt(msg, options, callback); + } else { + callback(null, msg); + } +} + +export function encodeArray(messages: Array, options: CipherOptions, callback: Function): void { + let processed = 0; + for (let i = 0; i < messages.length; i++) { + encode(messages[i], options, function (err: Error) { + if (err) { + callback(err); + return; + } + processed++; + if (processed == messages.length) { + callback(null, messages); + } + }); + } +} + +export const serialize = Utils.encodeBody; + +export async function decode( + message: Message | PresenceMessage, + inputContext: CipherOptions | EncodingDecodingContext | ChannelOptions +): Promise { + const context = normaliseContext(inputContext); + + let lastPayload = message.data; + const encoding = message.encoding; + if (encoding) { + const xforms = encoding.split('/'); + let lastProcessedEncodingIndex, + encodingsToProcess = xforms.length, + data = message.data; + + let xform = ''; + try { + while ((lastProcessedEncodingIndex = encodingsToProcess) > 0) { + // eslint-disable-next-line security/detect-unsafe-regex + const match = xforms[--encodingsToProcess].match(/([-\w]+)(\+([\w-]+))?/); + if (!match) break; + xform = match[1]; + switch (xform) { + case 'base64': + data = Platform.BufferUtils.base64Decode(String(data)); + if (lastProcessedEncodingIndex == xforms.length) { + lastPayload = data; + } + continue; + case 'utf-8': + data = Platform.BufferUtils.utf8Decode(data); + continue; + case 'json': + data = JSON.parse(data); + continue; + case 'cipher': + if ( + context.channelOptions != null && + context.channelOptions.cipher && + context.channelOptions.channelCipher + ) { + const xformAlgorithm = match[3], + cipher = context.channelOptions.channelCipher; + /* don't attempt to decrypt unless the cipher params are compatible */ + if (xformAlgorithm != cipher.algorithm) { + throw new Error('Unable to decrypt message with given cipher; incompatible cipher params'); + } + data = await cipher.decrypt(data); + continue; + } else { + throw new Error('Unable to decrypt message; not an encrypted channel'); + } + case 'vcdiff': + if (!context.plugins || !context.plugins.vcdiff) { + throw new ErrorInfo('Missing Vcdiff decoder (https://github.com/ably-forks/vcdiff-decoder)', 40019, 400); + } + if (typeof Uint8Array === 'undefined') { + throw new ErrorInfo( + 'Delta decoding not supported on this browser (need ArrayBuffer & Uint8Array)', + 40020, + 400 + ); + } + try { + let deltaBase = context.baseEncodedPreviousPayload; + if (typeof deltaBase === 'string') { + deltaBase = Platform.BufferUtils.utf8Encode(deltaBase); + } + + // vcdiff expects Uint8Arrays, can't copy with ArrayBuffers. + deltaBase = Platform.BufferUtils.toBuffer(deltaBase as Buffer); + data = Platform.BufferUtils.toBuffer(data); + + data = Platform.BufferUtils.arrayBufferViewToBuffer(context.plugins.vcdiff.decode(data, deltaBase)); + lastPayload = data; + } catch (e) { + throw new ErrorInfo('Vcdiff delta decode failed with ' + e, 40018, 400); + } + continue; + default: + throw new Error('Unknown encoding'); + } + } + } catch (e) { + const err = e as ErrorInfo; + throw new ErrorInfo( + 'Error processing the ' + xform + ' encoding, decoder returned ‘' + err.message + '’', + err.code || 40013, + 400 + ); + } finally { + message.encoding = + (lastProcessedEncodingIndex as number) <= 0 ? null : xforms.slice(0, lastProcessedEncodingIndex).join('/'); + message.data = data; + } + } + context.baseEncodedPreviousPayload = lastPayload; +} + +export async function fromResponseBody( + body: Array, + options: ChannelOptions | EncodingDecodingContext, + MsgPack: MsgPack | null, + format?: Utils.Format +): Promise { + if (format) { + body = Utils.decodeBody(body, MsgPack, format); + } + + for (let i = 0; i < body.length; i++) { + const msg = (body[i] = fromValues(body[i])); + try { + await decode(msg, options); + } catch (e) { + Logger.logAction(Logger.LOG_ERROR, 'Message.fromResponseBody()', (e as Error).toString()); + } + } + return body; +} + +export function fromValues(values: unknown): Message { + return Object.assign(new Message(), values); +} + +export function fromValuesArray(values: unknown[]): Message[] { + const count = values.length, + result = new Array(count); + for (let i = 0; i < count; i++) result[i] = fromValues(values[i]); + return result; +} + +/* This should be called on encode()d (and encrypt()d) Messages (as it + * assumes the data is a string or buffer) */ +export function getMessagesSize(messages: Message[]): number { + let msg, + total = 0; + for (let i = 0; i < messages.length; i++) { + msg = messages[i]; + total += msg.size || (msg.size = getMessageSize(msg)); + } + return total; +} + class Message { name?: string; id?: string; @@ -170,212 +372,6 @@ class Message { result += ']'; return result; } - - static encrypt(msg: Message | PresenceMessage, options: CipherOptions, callback: Function) { - let data = msg.data, - encoding = msg.encoding, - cipher = options.channelCipher; - - encoding = encoding ? encoding + '/' : ''; - if (!Platform.BufferUtils.isBuffer(data)) { - data = Platform.BufferUtils.utf8Encode(String(data)); - encoding = encoding + 'utf-8/'; - } - cipher.encrypt(data, function (err: Error, data: unknown) { - if (err) { - callback(err); - return; - } - msg.data = data; - msg.encoding = encoding + 'cipher+' + cipher.algorithm; - callback(null, msg); - }); - } - - static encode(msg: Message | PresenceMessage, options: CipherOptions, callback: Function): void { - const data = msg.data; - const nativeDataType = - typeof data == 'string' || Platform.BufferUtils.isBuffer(data) || data === null || data === undefined; - - if (!nativeDataType) { - if (Utils.isObject(data) || Utils.isArray(data)) { - msg.data = JSON.stringify(data); - msg.encoding = msg.encoding ? msg.encoding + '/json' : 'json'; - } else { - throw new ErrorInfo('Data type is unsupported', 40013, 400); - } - } - - if (options != null && options.cipher) { - Message.encrypt(msg, options, callback); - } else { - callback(null, msg); - } - } - - static encodeArray(messages: Array, options: CipherOptions, callback: Function): void { - let processed = 0; - for (let i = 0; i < messages.length; i++) { - Message.encode(messages[i], options, function (err: Error) { - if (err) { - callback(err); - return; - } - processed++; - if (processed == messages.length) { - callback(null, messages); - } - }); - } - } - - static serialize = Utils.encodeBody; - - static async decode( - message: Message | PresenceMessage, - inputContext: CipherOptions | EncodingDecodingContext | ChannelOptions - ): Promise { - const context = normaliseContext(inputContext); - - let lastPayload = message.data; - const encoding = message.encoding; - if (encoding) { - const xforms = encoding.split('/'); - let lastProcessedEncodingIndex, - encodingsToProcess = xforms.length, - data = message.data; - - let xform = ''; - try { - while ((lastProcessedEncodingIndex = encodingsToProcess) > 0) { - // eslint-disable-next-line security/detect-unsafe-regex - const match = xforms[--encodingsToProcess].match(/([-\w]+)(\+([\w-]+))?/); - if (!match) break; - xform = match[1]; - switch (xform) { - case 'base64': - data = Platform.BufferUtils.base64Decode(String(data)); - if (lastProcessedEncodingIndex == xforms.length) { - lastPayload = data; - } - continue; - case 'utf-8': - data = Platform.BufferUtils.utf8Decode(data); - continue; - case 'json': - data = JSON.parse(data); - continue; - case 'cipher': - if ( - context.channelOptions != null && - context.channelOptions.cipher && - context.channelOptions.channelCipher - ) { - const xformAlgorithm = match[3], - cipher = context.channelOptions.channelCipher; - /* don't attempt to decrypt unless the cipher params are compatible */ - if (xformAlgorithm != cipher.algorithm) { - throw new Error('Unable to decrypt message with given cipher; incompatible cipher params'); - } - data = await cipher.decrypt(data); - continue; - } else { - throw new Error('Unable to decrypt message; not an encrypted channel'); - } - case 'vcdiff': - if (!context.plugins || !context.plugins.vcdiff) { - throw new ErrorInfo( - 'Missing Vcdiff decoder (https://github.com/ably-forks/vcdiff-decoder)', - 40019, - 400 - ); - } - if (typeof Uint8Array === 'undefined') { - throw new ErrorInfo( - 'Delta decoding not supported on this browser (need ArrayBuffer & Uint8Array)', - 40020, - 400 - ); - } - try { - let deltaBase = context.baseEncodedPreviousPayload; - if (typeof deltaBase === 'string') { - deltaBase = Platform.BufferUtils.utf8Encode(deltaBase); - } - - // vcdiff expects Uint8Arrays, can't copy with ArrayBuffers. - deltaBase = Platform.BufferUtils.toBuffer(deltaBase as Buffer); - data = Platform.BufferUtils.toBuffer(data); - - data = Platform.BufferUtils.arrayBufferViewToBuffer(context.plugins.vcdiff.decode(data, deltaBase)); - lastPayload = data; - } catch (e) { - throw new ErrorInfo('Vcdiff delta decode failed with ' + e, 40018, 400); - } - continue; - default: - throw new Error('Unknown encoding'); - } - } - } catch (e) { - const err = e as ErrorInfo; - throw new ErrorInfo( - 'Error processing the ' + xform + ' encoding, decoder returned ‘' + err.message + '’', - err.code || 40013, - 400 - ); - } finally { - message.encoding = - (lastProcessedEncodingIndex as number) <= 0 ? null : xforms.slice(0, lastProcessedEncodingIndex).join('/'); - message.data = data; - } - } - context.baseEncodedPreviousPayload = lastPayload; - } - - static async fromResponseBody( - body: Array, - options: ChannelOptions | EncodingDecodingContext, - MsgPack: MsgPack | null, - format?: Utils.Format - ): Promise { - if (format) { - body = Utils.decodeBody(body, MsgPack, format); - } - - for (let i = 0; i < body.length; i++) { - const msg = (body[i] = Message.fromValues(body[i])); - try { - await Message.decode(msg, options); - } catch (e) { - Logger.logAction(Logger.LOG_ERROR, 'Message.fromResponseBody()', (e as Error).toString()); - } - } - return body; - } - - static fromValues(values: unknown): Message { - return Object.assign(new Message(), values); - } - - static fromValuesArray(values: unknown[]): Message[] { - const count = values.length, - result = new Array(count); - for (let i = 0; i < count; i++) result[i] = Message.fromValues(values[i]); - return result; - } - - /* This should be called on encode()d (and encrypt()d) Messages (as it - * assumes the data is a string or buffer) */ - static getMessagesSize(messages: Message[]): number { - let msg, - total = 0; - for (let i = 0; i < messages.length; i++) { - msg = messages[i]; - total += msg.size || (msg.size = getMessageSize(msg)); - } - return total; - } } export default Message; diff --git a/src/common/lib/types/presencemessage.ts b/src/common/lib/types/presencemessage.ts index c1fa4db450..b9969df241 100644 --- a/src/common/lib/types/presencemessage.ts +++ b/src/common/lib/types/presencemessage.ts @@ -1,12 +1,14 @@ import Logger from '../util/logger'; import Platform from 'common/platform'; -import Message, { CipherOptions } from './message'; +import { encode as encodeMessage, decode as decodeMessage, getMessagesSize, CipherOptions } from './message'; import * as Utils from '../util/utils'; import * as API from '../../../../ably'; import { MsgPack } from 'common/types/msgpack'; +const actions = ['absent', 'present', 'enter', 'leave', 'update']; + function toActionValue(actionString: string) { - return PresenceMessage.Actions.indexOf(actionString); + return actions.indexOf(actionString); } export async function fromEncoded(encoded: unknown, options?: API.Types.ChannelOptions): Promise { @@ -14,7 +16,7 @@ export async function fromEncoded(encoded: unknown, options?: API.Types.ChannelO /* if decoding fails at any point, catch and return the message decoded to * the fullest extent possible */ try { - await PresenceMessage.decode(msg, options ?? {}); + await decode(msg, options ?? {}); } catch (e) { Logger.logAction(Logger.LOG_ERROR, 'PresenceMessage.fromEncoded()', (e as Error).toString()); } @@ -37,11 +39,54 @@ export function fromValues( stringifyAction?: boolean ): PresenceMessage { if (stringifyAction) { - values.action = PresenceMessage.Actions[values.action as number]; + values.action = actions[values.action as number]; } return Object.assign(new PresenceMessage(), values); } +export { encodeMessage as encode }; +export const decode = decodeMessage; + +export async function fromResponseBody( + body: Record[], + options: CipherOptions, + MsgPack: MsgPack | null, + format?: Utils.Format +): Promise { + const messages: PresenceMessage[] = []; + if (format) { + body = Utils.decodeBody(body, MsgPack, format); + } + + for (let i = 0; i < body.length; i++) { + const msg = (messages[i] = fromValues(body[i], true)); + try { + await decode(msg, options); + } catch (e) { + Logger.logAction(Logger.LOG_ERROR, 'PresenceMessage.fromResponseBody()', (e as Error).toString()); + } + } + return messages; +} + +export function fromValuesArray(values: unknown[]): PresenceMessage[] { + const count = values.length, + result = new Array(count); + for (let i = 0; i < count; i++) result[i] = fromValues(values[i] as Record); + return result; +} + +export function fromData(data: unknown): PresenceMessage { + if (data instanceof PresenceMessage) { + return data; + } + return fromValues({ + data, + }); +} + +export { getMessagesSize }; + class PresenceMessage { action?: string | number; id?: string; @@ -53,8 +98,6 @@ class PresenceMessage { extras?: any; size?: number; - static Actions = ['absent', 'present', 'enter', 'leave', 'update']; - /* Returns whether this presenceMessage is synthesized, i.e. was not actually * sent by the connection (usually means a leave event sent 15s after a * disconnection). This is useful because synthesized messages cannot be @@ -138,49 +181,6 @@ class PresenceMessage { result += ']'; return result; } - - static encode = Message.encode; - static decode = Message.decode; - - static async fromResponseBody( - body: Record[], - options: CipherOptions, - MsgPack: MsgPack | null, - format?: Utils.Format - ): Promise { - const messages: PresenceMessage[] = []; - if (format) { - body = Utils.decodeBody(body, MsgPack, format); - } - - for (let i = 0; i < body.length; i++) { - const msg = (messages[i] = fromValues(body[i], true)); - try { - await PresenceMessage.decode(msg, options); - } catch (e) { - Logger.logAction(Logger.LOG_ERROR, 'PresenceMessage.fromResponseBody()', (e as Error).toString()); - } - } - return messages; - } - - static fromValuesArray(values: unknown[]): PresenceMessage[] { - const count = values.length, - result = new Array(count); - for (let i = 0; i < count; i++) result[i] = fromValues(values[i] as Record); - return result; - } - - static fromData(data: unknown): PresenceMessage { - if (data instanceof PresenceMessage) { - return data; - } - return fromValues({ - data, - }); - } - - static getMessagesSize = Message.getMessagesSize; } export default PresenceMessage; diff --git a/src/common/lib/types/protocolmessage.ts b/src/common/lib/types/protocolmessage.ts index a8b2f360ea..fdcedf6c7d 100644 --- a/src/common/lib/types/protocolmessage.ts +++ b/src/common/lib/types/protocolmessage.ts @@ -2,10 +2,13 @@ import { MsgPack } from 'common/types/msgpack'; import { Types } from '../../../../ably'; import * as Utils from '../util/utils'; import ErrorInfo from './errorinfo'; -import Message from './message'; -import PresenceMessage, { fromValues as presenceMessageFromValues } from './presencemessage'; +import Message, { fromValues as messageFromValues, fromValuesArray as messagesFromValuesArray } from './message'; +import PresenceMessage, { + fromValues as presenceMessageFromValues, + fromValuesArray as presenceMessagesFromValuesArray, +} from './presencemessage'; -const actions = { +export const actions = { HEARTBEAT: 0, ACK: 1, NACK: 2, @@ -27,7 +30,7 @@ const actions = { ACTIVATE: 18, }; -const ActionName: string[] = []; +export const ActionName: string[] = []; Object.keys(actions).forEach(function (name) { ActionName[(actions as { [key: string]: number })[name]] = name; }); @@ -58,6 +61,61 @@ function toStringArray(array?: any[]): string { return '[ ' + result.join(', ') + ' ]'; } +export const channelModes = ['PRESENCE', 'PUBLISH', 'SUBSCRIBE', 'PRESENCE_SUBSCRIBE']; + +export const serialize = Utils.encodeBody; + +export function deserialize(serialized: unknown, MsgPack: MsgPack | null, format?: Utils.Format): ProtocolMessage { + const deserialized = Utils.decodeBody>(serialized, MsgPack, format); + return fromDeserialized(deserialized); +} + +export function fromDeserialized(deserialized: Record): ProtocolMessage { + const error = deserialized.error; + if (error) deserialized.error = ErrorInfo.fromValues(error as ErrorInfo); + const messages = deserialized.messages as Message[]; + if (messages) for (let i = 0; i < messages.length; i++) messages[i] = messageFromValues(messages[i]); + const presence = deserialized.presence as PresenceMessage[]; + if (presence) for (let i = 0; i < presence.length; i++) presence[i] = presenceMessageFromValues(presence[i], true); + return Object.assign(new ProtocolMessage(), deserialized); +} + +export function fromValues(values: unknown): ProtocolMessage { + return Object.assign(new ProtocolMessage(), values); +} + +export function stringify(msg: any): string { + let result = '[ProtocolMessage'; + if (msg.action !== undefined) result += '; action=' + ActionName[msg.action] || msg.action; + + const simpleAttributes = ['id', 'channel', 'channelSerial', 'connectionId', 'count', 'msgSerial', 'timestamp']; + let attribute; + for (let attribIndex = 0; attribIndex < simpleAttributes.length; attribIndex++) { + attribute = simpleAttributes[attribIndex]; + if (msg[attribute] !== undefined) result += '; ' + attribute + '=' + msg[attribute]; + } + + if (msg.messages) result += '; messages=' + toStringArray(messagesFromValuesArray(msg.messages)); + if (msg.presence) result += '; presence=' + toStringArray(presenceMessagesFromValuesArray(msg.presence)); + if (msg.error) result += '; error=' + ErrorInfo.fromValues(msg.error).toString(); + if (msg.auth && msg.auth.accessToken) result += '; token=' + msg.auth.accessToken; + if (msg.flags) result += '; flags=' + flagNames.filter(msg.hasFlag).join(','); + if (msg.params) { + let stringifiedParams = ''; + Utils.forInOwnNonNullProperties(msg.params, function (prop: string) { + if (stringifiedParams.length > 0) { + stringifiedParams += '; '; + } + stringifiedParams += prop + '=' + msg.params[prop]; + }); + if (stringifiedParams.length > 0) { + result += '; params=[' + stringifiedParams + ']'; + } + } + result += ']'; + return result; +} + class ProtocolMessage { action?: number; flags?: number; @@ -74,12 +132,6 @@ class ProtocolMessage { auth?: unknown; connectionDetails?: Record; - static Action = actions; - - static channelModes = ['PRESENCE', 'PUBLISH', 'SUBSCRIBE', 'PRESENCE_SUBSCRIBE']; - - static ActionName = ActionName; - hasFlag = (flag: string): boolean => { return ((this.flags as number) & flags[flag]) > 0; }; @@ -98,66 +150,13 @@ class ProtocolMessage { decodeModesFromFlags(): string[] | undefined { const modes: string[] = []; - ProtocolMessage.channelModes.forEach((mode) => { + channelModes.forEach((mode) => { if (this.hasFlag(mode)) { modes.push(mode); } }); return modes.length > 0 ? modes : undefined; } - - static serialize = Utils.encodeBody; - - static deserialize = function (serialized: unknown, MsgPack: MsgPack | null, format?: Utils.Format): ProtocolMessage { - const deserialized = Utils.decodeBody>(serialized, MsgPack, format); - return ProtocolMessage.fromDeserialized(deserialized); - }; - - static fromDeserialized = function (deserialized: Record): ProtocolMessage { - const error = deserialized.error; - if (error) deserialized.error = ErrorInfo.fromValues(error as ErrorInfo); - const messages = deserialized.messages as Message[]; - if (messages) for (let i = 0; i < messages.length; i++) messages[i] = Message.fromValues(messages[i]); - const presence = deserialized.presence as PresenceMessage[]; - if (presence) for (let i = 0; i < presence.length; i++) presence[i] = presenceMessageFromValues(presence[i], true); - return Object.assign(new ProtocolMessage(), deserialized); - }; - - static fromValues(values: unknown): ProtocolMessage { - return Object.assign(new ProtocolMessage(), values); - } - - static stringify = function (msg: any): string { - let result = '[ProtocolMessage'; - if (msg.action !== undefined) result += '; action=' + ProtocolMessage.ActionName[msg.action] || msg.action; - - const simpleAttributes = ['id', 'channel', 'channelSerial', 'connectionId', 'count', 'msgSerial', 'timestamp']; - let attribute; - for (let attribIndex = 0; attribIndex < simpleAttributes.length; attribIndex++) { - attribute = simpleAttributes[attribIndex]; - if (msg[attribute] !== undefined) result += '; ' + attribute + '=' + msg[attribute]; - } - - if (msg.messages) result += '; messages=' + toStringArray(Message.fromValuesArray(msg.messages)); - if (msg.presence) result += '; presence=' + toStringArray(PresenceMessage.fromValuesArray(msg.presence)); - if (msg.error) result += '; error=' + ErrorInfo.fromValues(msg.error).toString(); - if (msg.auth && msg.auth.accessToken) result += '; token=' + msg.auth.accessToken; - if (msg.flags) result += '; flags=' + flagNames.filter(msg.hasFlag).join(','); - if (msg.params) { - let stringifiedParams = ''; - Utils.forInOwnNonNullProperties(msg.params, function (prop: string) { - if (stringifiedParams.length > 0) { - stringifiedParams += '; '; - } - stringifiedParams += prop + '=' + msg.params[prop]; - }); - if (stringifiedParams.length > 0) { - result += '; params=[' + stringifiedParams + ']'; - } - } - result += ']'; - return result; - }; } export default ProtocolMessage;