Skip to content

Commit

Permalink
Materialization: Update to ably-js and support new message fields.
Browse files Browse the repository at this point in the history
Update ably-js to support;
 New MessageActions.
 New Message serial.
 New Operation related fields (Operation, UpdatedAt, UpdateSerial)

Update DefaultMessage;
 Add fields to support deletion and updates in chat.
 Add helper functions to support the above.

Bump send and get endpoints;
 Support new serials and message fields in history response.
 Support the new `RealtimeMessageTypes`.

Introduce `latestAction`, latestActionSerial and `latestActionDetails`.
 These can be used to determine what the latest action of the message is and who performed it, as well as apply global ordering to
 later message actions.

Update demo app to use the latest ably-js build.
  • Loading branch information
splindsay-92 committed Nov 4, 2024
1 parent 6cf4901 commit 1a5c5e4
Show file tree
Hide file tree
Showing 19 changed files with 717 additions and 144 deletions.
Binary file added ably-2.4.1.tgz
Binary file not shown.
Binary file added demo/ably-2.4.1.tgz
Binary file not shown.
9 changes: 5 additions & 4 deletions demo/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"dependencies": {
"@ably/chat": "file:..",
"ably": "^2.4.1",
"ably": "file:./ably-2.4.1.tgz",
"clsx": "^2.1.1",
"nanoid": "^5.0.7",
"react": "^18.3.1",
Expand Down
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
},
"homepage": "https://github.com/ably/ably-chat-js#readme",
"peerDependencies": {
"ably": "^2.3.1"
"ably": "file:./ably-2.4.1.tgz"
},
"devDependencies": {
"@eslint/compat": "^1.2.0",
Expand Down
9 changes: 7 additions & 2 deletions src/core/chat-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export class ChatApi {
}

async getMessages(roomId: string, params: GetMessagesQueryParams): Promise<PaginatedResult<Message>> {
return this._makeAuthorizedPaginatedRequest<Message>(`/chat/v1/rooms/${roomId}/messages`, params).then((data) => {
return this._makeAuthorizedPaginatedRequest<Message>(`/chat/v2/rooms/${roomId}/messages`, params).then((data) => {
data.items = data.items.map((message) => {
const metadata = message.metadata as MessageMetadata | undefined;
const headers = message.headers as MessageHeaders | undefined;
Expand All @@ -57,6 +57,11 @@ export class ChatApi {
new Date(message.createdAt),
metadata ?? {},
headers ?? {},
message.latestAction,
message.latestActionSerial,
message.deletedAt ? new Date(message.deletedAt) : undefined,
message.updatedAt ? new Date(message.updatedAt) : undefined,
message.latestActionDetails,
);
});
return data;
Expand All @@ -76,7 +81,7 @@ export class ChatApi {
body.headers = params.headers;
}

return this._makeAuthorizedRequest<CreateMessageResponse>(`/chat/v1/rooms/${roomId}/messages`, 'POST', body);
return this._makeAuthorizedRequest<CreateMessageResponse>(`/chat/v2/rooms/${roomId}/messages`, 'POST', body);
}

async getOccupancy(roomId: string): Promise<OccupancyEvent> {
Expand Down
40 changes: 40 additions & 0 deletions src/core/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,46 @@
export enum MessageEvents {
/** Fires when a new chat message is received. */
Created = 'message.created',

/** Fires when a chat message is updated. */
Updated = 'message.updated',

/** Fires when a chat message is deleted. */
Deleted = 'message.deleted',
}

/**
* Realtime chat message names.
*/
export enum RealtimeMessageNames {
/** Represents a regular chat message. */
ChatMessage = 'chat.message',
}

/**
* Chat Message Actions.
*/
export enum ChatMessageActions {
/** Represents a message with no action set. */
MessageUnset = 'message.unset',

/** Action applied to a new message. */
MessageCreate = 'message.create',

/** Action applied to an updated message. */
MessageUpdate = 'message.update',

/** Action applied to a deleted message. */
MessageDelete = 'message.delete',

/** Action applied to a new annotation. */
MessageAnnotationCreate = 'annotation.create',

/** Action applied to a deleted annotation. */
MessageAnnotationUpdate = 'annotation.delete',

/** Action applied to a meta occupancy message. */
MessageMetaOccupancy = 'meta.occupancy',
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/core/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as Ably from 'ably';

import { MessageEvents, RoomReactionEvents } from './events.js';
import { RealtimeMessageNames, RoomReactionEvents } from './events.js';
import { Message } from './message.js';
import { parseMessage } from './message-parser.js';
import { Reaction } from './reaction.js';
Expand Down Expand Up @@ -89,7 +89,7 @@ export async function reactionFromEncoded(encoded: unknown): Promise<Reaction> {
*/
export function getEntityTypeFromAblyMessage(message: Ably.InboundMessage): ChatEntityType {
switch (message.name) {
case MessageEvents.Created: {
case RealtimeMessageNames.ChatMessage: {
return ChatEntityType.ChatMessage;
}
case RoomReactionEvents.Reaction: {
Expand Down
5 changes: 3 additions & 2 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* @module chat-js
*/

export type { ActionMetadata } from './action-metadata.js';
export { ChatClient } from './chat.js';
export type { ClientOptions } from './config.js';
export type { Connection } from './connection.js';
Expand All @@ -14,7 +15,7 @@ export type {
export { ConnectionLifecycle } from './connection-status.js';
export type { DiscontinuityListener, OnDiscontinuitySubscriptionResponse } from './discontinuity.js';
export { ErrorCodes, errorInfoIs } from './errors.js';
export { MessageEvents, PresenceEvents } from './events.js';
export { ChatMessageActions, MessageEvents, PresenceEvents } from './events.js';
export type { Headers } from './headers.js';
export {
ChatEntityType,
Expand All @@ -27,7 +28,7 @@ export {
} from './helpers.js';
export type { LogContext, Logger, LogHandler } from './logger.js';
export { LogLevel } from './logger.js';
export type { Message, MessageHeaders, MessageMetadata } from './message.js';
export type { ActionDetails, Message, MessageHeaders, MessageMetadata } from './message.js';
export type {
MessageEventPayload,
MessageListener,
Expand Down
95 changes: 77 additions & 18 deletions src/core/message-parser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as Ably from 'ably';

import { DefaultMessage, Message, MessageHeaders, MessageMetadata } from './message.js';
import { ChatMessageActions } from './events.js';
import { ActionDetails, DefaultMessage, Message, MessageHeaders, MessageMetadata } from './message.js';

interface MessagePayload {
data?: {
Expand All @@ -10,48 +11,106 @@ interface MessagePayload {
clientId?: string;
timestamp: number;
extras?: {
timeserial?: string;
headers?: MessageHeaders;
};

serial: string;
updatedAt?: number;
updateSerial?: string;
action: Ably.MessageAction;
operation?: Ably.Operation;
}

interface ChatMessageFields {
timeserial: string;
clientId: string;
roomId: string;
text: string;
createdAt: Date;
metadata: MessageMetadata;
headers: MessageHeaders;
latestAction: ChatMessageActions;
latestActionSerial: string;
updatedAt?: Date;
deletedAt?: Date;
operation?: ActionDetails;
}

export function parseMessage(roomId: string | undefined, message: Ably.InboundMessage): Message {
const messageCreatedMessage = message as MessagePayload;
export function parseMessage(roomId: string | undefined, inboundMessage: Ably.InboundMessage): Message {
const message = inboundMessage as MessagePayload;
if (!roomId) {
throw new Ably.ErrorInfo(`received incoming message without roomId`, 50000, 500);
}

if (!messageCreatedMessage.data) {
if (!message.data) {
throw new Ably.ErrorInfo(`received incoming message without data`, 50000, 500);
}

if (!messageCreatedMessage.clientId) {
if (!message.clientId) {
throw new Ably.ErrorInfo(`received incoming message without clientId`, 50000, 500);
}

if (!messageCreatedMessage.timestamp) {
if (!message.timestamp) {
throw new Ably.ErrorInfo(`received incoming message without timestamp`, 50000, 500);
}

if (messageCreatedMessage.data.text === undefined) {
if (message.data.text === undefined) {
throw new Ably.ErrorInfo(`received incoming message without text`, 50000, 500);
}

if (!messageCreatedMessage.extras) {
if (!message.extras) {
throw new Ably.ErrorInfo(`received incoming message without extras`, 50000, 500);
}

if (!messageCreatedMessage.extras.timeserial) {
throw new Ably.ErrorInfo(`received incoming message without timeserial`, 50000, 500);
if (!message.serial) {
throw new Ably.ErrorInfo(`received incoming message without serial`, 50000, 500);
}

return new DefaultMessage(
messageCreatedMessage.extras.timeserial,
messageCreatedMessage.clientId,
const newMessage: ChatMessageFields = {
timeserial: message.serial,
clientId: message.clientId,
roomId,
messageCreatedMessage.data.text,
new Date(messageCreatedMessage.timestamp),
messageCreatedMessage.data.metadata ?? {},
messageCreatedMessage.extras.headers ?? {},
text: message.data.text,
createdAt: new Date(message.timestamp),
metadata: message.data.metadata ?? {},
headers: message.extras.headers ?? {},
latestAction: message.action as ChatMessageActions,
latestActionSerial: message.updateSerial ?? message.serial,
updatedAt: message.updatedAt ? new Date(message.updatedAt) : undefined,
deletedAt: message.updatedAt ? new Date(message.updatedAt) : undefined,
operation: message.operation as ActionDetails,
};

switch (message.action) {
case ChatMessageActions.MessageCreate: {
break;
}
case ChatMessageActions.MessageUpdate:
case ChatMessageActions.MessageDelete: {
if (!message.updatedAt) {
throw new Ably.ErrorInfo(`received incoming ${message.action} without updatedAt`, 50000, 500);
}
if (!message.updateSerial) {
throw new Ably.ErrorInfo(`received incoming ${message.action} without updateSerial`, 50000, 500);
}
break;
}
default: {
throw new Ably.ErrorInfo(`received incoming message with unhandled action; ${message.action}`, 50000, 500);
}
}
return new DefaultMessage(
newMessage.timeserial,
newMessage.clientId,
newMessage.roomId,
newMessage.text,
newMessage.createdAt,
newMessage.metadata,
newMessage.headers,
newMessage.latestAction,
newMessage.latestActionSerial,
newMessage.latestAction === ChatMessageActions.MessageDelete ? newMessage.deletedAt : undefined,
newMessage.latestAction === ChatMessageActions.MessageUpdate ? newMessage.updatedAt : undefined,
newMessage.operation,
);
}
Loading

0 comments on commit 1a5c5e4

Please sign in to comment.