diff --git a/CHANGELOG.md b/CHANGELOG.md index 39e1f7b36..21f3eb89c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ This contains only the most important and/or user-facing changes; for a full changelog, see the commit history. +## [2.4.1](https://github.com/ably/ably-js/tree/2.4.1) (2024-10-04) + +- Fix `usePresence` hook wasn't leaving presence if component unmounted during channel attaching state [\#1884](https://github.com/ably/ably-js/pull/1884) + ## [2.4.0](https://github.com/ably/ably-js/tree/2.4.0) (2024-09-11) - Add `wsConnectivityCheckUrl` client option [\#1862](https://github.com/ably/ably-js/pull/1862) diff --git a/ably.d.ts b/ably.d.ts index b8e85c6a4..a09f5aa49 100644 --- a/ably.d.ts +++ b/ably.d.ts @@ -2335,12 +2335,99 @@ export interface Message { * Timestamp of when the message was received by Ably, as milliseconds since the Unix epoch. */ timestamp?: number; + /** + * The action type of the message, one of the {@link MessageAction} enum values. + */ + action?: MessageAction; + /** + * This message's unique serial. + */ + serial?: string; + /** + * The serial of the message that this message is a reference to. + */ + refSerial?: string; + /** + * The type of reference this message is, in relation to the message it references. + */ + refType?: string; + /** + * If an `update` operation was applied to this message, this will be the timestamp the update occurred. + */ + updatedAt?: number; + /** + * If a `deletion` operation was applied to this message, this will be the timestamp the deletion occurred. + */ + deletedAt?: number; + /** + * If this message resulted from an operation, this will contain the operation details. + */ + operation?: Operation; } +/** + * Contains the details of an operation, such as update of deletion, supplied by the actioning client. + */ +export interface Operation { + /** + * The client ID of the client that initiated the operation. + */ + clientId?: string; + /** + * The description provided by the client that initiated the operation. + */ + description?: string; + /** + * A JSON object of string key-value pairs that may contain metadata associated with the operation. + */ + metadata?: Record; +} + +/** + * The namespace containing the different types of message actions. + */ +declare namespace MessageActions { + /** + * Message action has not been set. + */ + type MESSAGE_UNSET = 'MESSAGE_UNSET'; + /** + * Message action for a newly created message. + */ + type MESSAGE_CREATE = 'MESSAGE_CREATE'; + /** + * Message action for an updated message. + */ + type MESSAGE_UPDATE = 'MESSAGE_UPDATE'; + /** + * Message action for a deleted message. + */ + type MESSAGE_DELETE = 'MESSAGE_DELETE'; + /** + * Message action for a newly created annotation. + */ + type MESSAGE_ANNOTATION_CREATE = 'MESSAGE_ANNOTATION_CREATE'; + /** + * Message action for a deleted annotation. + */ + type MESSAGE_ANNOTATION_DELETE = 'MESSAGE_ANNOTATION_DELETE'; +} + +/** + * Describes the possible action types used on an {@link Message}. + */ +export type MessageAction = + | MessageActions.MESSAGE_UNSET + | MessageActions.MESSAGE_CREATE + | MessageActions.MESSAGE_UPDATE + | MessageActions.MESSAGE_DELETE + | MessageActions.MESSAGE_ANNOTATION_CREATE + | MessageActions.MESSAGE_ANNOTATION_DELETE; + /** * A message received from Ably. */ -export type InboundMessage = Message & Required>; +export type InboundMessage = Message & Required>; /** * Static utilities related to messages. diff --git a/package-lock.json b/package-lock.json index 63325af50..c5f3dea2d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ably", - "version": "2.4.0", + "version": "2.4.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ably", - "version": "2.4.0", + "version": "2.4.1", "license": "Apache-2.0", "dependencies": { "@ably/msgpack-js": "^0.4.0", diff --git a/package.json b/package.json index 5bb15dd54..66df4b236 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "ably", "description": "Realtime client library for Ably, the realtime messaging service", - "version": "2.4.0", + "version": "2.5.0", "license": "Apache-2.0", "bugs": { "url": "https://github.com/ably/ably-js/issues", diff --git a/src/common/lib/types/defaultmessage.ts b/src/common/lib/types/defaultmessage.ts index dfc4a02b1..33246b30e 100644 --- a/src/common/lib/types/defaultmessage.ts +++ b/src/common/lib/types/defaultmessage.ts @@ -1,10 +1,11 @@ import Message, { CipherOptions, - fromEncoded, - fromEncodedArray, - encode, decode, + encode, EncodingDecodingContext, + fromEncoded, + fromEncodedArray, + fromValues, } from './message'; import * as API from '../../../../ably'; import Platform from 'common/platform'; @@ -25,8 +26,8 @@ export class DefaultMessage extends Message { } // Used by tests - static fromValues(values: unknown): Message { - return Object.assign(new Message(), values); + static fromValues(values: Message | Record, stringifyAction?: boolean): Message { + return fromValues(values, stringifyAction); } // Used by tests diff --git a/src/common/lib/types/message.ts b/src/common/lib/types/message.ts index 7cc8b80ac..152b9805b 100644 --- a/src/common/lib/types/message.ts +++ b/src/common/lib/types/message.ts @@ -9,6 +9,32 @@ import * as API from '../../../../ably'; import { IUntypedCryptoStatic } from 'common/types/ICryptoStatic'; import { MsgPack } from 'common/types/msgpack'; +const MessageActionNumberMap: Record = { + 0: 'MESSAGE_UNSET', + 1: 'MESSAGE_CREATE', + 2: 'MESSAGE_UPDATE', + 3: 'MESSAGE_DELETE', + 4: 'MESSAGE_ANNOTATION_CREATE', + 5: 'MESSAGE_ANNOTATION_DELETE', +}; + +function toMessageActionString(actionNumber: number): API.MessageAction { + if (actionNumber in MessageActionNumberMap) { + return MessageActionNumberMap[actionNumber]; + } else { + throw new ErrorInfo(`Unsupported action number: ${actionNumber}`, 40000, 400); + } +} + +function toMessageActionNumber(messageAction: API.MessageAction): number { + for (const [key, value] of Object.entries(MessageActionNumberMap)) { + if (value === messageAction) { + return parseInt(key); + } + } + throw new ErrorInfo(`Unsupported action string: ${messageAction}`, 40000, 400); +} + export type CipherOptions = { channelCipher: { encrypt: Function; @@ -82,7 +108,7 @@ export async function fromEncoded( encoded: unknown, inputOptions?: API.ChannelOptions, ): Promise { - const msg = fromValues(encoded); + const msg = fromValues(encoded as Message | Record, true); const options = normalizeCipherOptions(Crypto, logger, inputOptions ?? null); /* if decoding fails at any point, catch and return the message decoded to * the fullest extent possible */ @@ -260,7 +286,7 @@ export async function fromResponseBody( } for (let i = 0; i < body.length; i++) { - const msg = (body[i] = fromValues(body[i])); + const msg = (body[i] = fromValues(body[i], true)); try { await decode(msg, options); } catch (e) { @@ -270,14 +296,17 @@ export async function fromResponseBody( return body; } -export function fromValues(values: unknown): Message { +export function fromValues(values: Message | Record, stringifyAction?: boolean): Message { + if (stringifyAction) { + values.action = toMessageActionString(values.action as number); + } 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]); + for (let i = 0; i < count; i++) result[i] = fromValues(values[i] as Record); return result; } @@ -304,6 +333,13 @@ class Message { encoding?: string | null; extras?: any; size?: number; + action?: API.MessageAction | number; + serial?: string; + refSerial?: string; + refType?: string; + updatedAt?: number; + deletedAt?: number; + operation?: API.Operation; /** * Overload toJSON() to intercept JSON.stringify() @@ -334,6 +370,13 @@ class Message { connectionId: this.connectionId, connectionKey: this.connectionKey, extras: this.extras, + serial: this.serial, + action: toMessageActionNumber(this.action as API.MessageAction), + refSerial: this.refSerial, + refType: this.refType, + updatedAt: this.updatedAt, + deletedAt: this.deletedAt, + operation: this.operation, encoding, data, }; @@ -355,6 +398,14 @@ class Message { else result += '; data (json)=' + JSON.stringify(this.data); } if (this.extras) result += '; extras=' + JSON.stringify(this.extras); + + if (this.action) result += '; action=' + this.action; + if (this.serial) result += '; serial=' + this.serial; + if (this.refSerial) result += '; refSerial=' + this.refSerial; + if (this.refType) result += '; refType=' + this.refType; + if (this.updatedAt) result += '; updatedAt=' + this.updatedAt; + if (this.deletedAt) result += '; deletedAt=' + this.deletedAt; + if (this.operation) result += '; operation=' + JSON.stringify(this.operation); result += ']'; return result; } diff --git a/src/platform/react-hooks/src/AblyReactHooks.ts b/src/platform/react-hooks/src/AblyReactHooks.ts index 9874cd8f7..aa421f28c 100644 --- a/src/platform/react-hooks/src/AblyReactHooks.ts +++ b/src/platform/react-hooks/src/AblyReactHooks.ts @@ -12,7 +12,7 @@ export type ChannelNameAndOptions = { export type ChannelNameAndAblyId = Pick; export type ChannelParameters = string | ChannelNameAndOptions; -export const version = '2.4.0'; +export const version = '2.4.1'; export function channelOptionsWithAgent(options?: Ably.ChannelOptions) { return {