Skip to content

Commit

Permalink
Merge pull request #379 from ably/materialisation/rename-message-time…
Browse files Browse the repository at this point in the history
…serial-field

materialisation/rename-message-timeserial-field
  • Loading branch information
splindsay-92 authored Nov 11, 2024
2 parents d6d3b7c + a4bd1ac commit cfdeb29
Show file tree
Hide file tree
Showing 15 changed files with 311 additions and 324 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -635,13 +635,14 @@ For example, if you have the following item reach your integration:
"id": "chat:6TP2sA:some-room:a4534b0ab37bdd5:0",
"clientId": "user1",
"timestamp": 1720954404104,
"serial": "108iZpUxQBe4Vv35120919@1720954404104-0",
"action": 1,
"encoding": "json",
"extras": {
"timeserial": "108iZpUxQBe4Vv35120919@1720954404104-0",
"headers": {}
},
"data": "{\"text\":\"some text data\",\"metadata\":{}}",
"name": "message.created"
"name": "chat.message"
}
]
}
Expand Down
2 changes: 0 additions & 2 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
"maxNumberOfProblems": 100,
"words": [
"realtime",
"timeserial",
"timeserials",
"inband",
"typers",
"msgpack",
Expand Down
8 changes: 4 additions & 4 deletions demo/src/containers/Chat/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const Chat = (props: { roomId: string; setRoomId: (roomId: string) => voi
case MessageEvents.Deleted:
setMessages((prevMessage) => {
return prevMessage.filter((m) => {
return m.timeserial !== message.message.timeserial;
return m.serial !== message.message.serial;
});
});
break;
Expand Down Expand Up @@ -192,15 +192,15 @@ export const Chat = (props: { roomId: string; setRoomId: (roomId: string) => voi
>
{messages.map((msg) => (
<MessageComponent
id={msg.timeserial}
key={msg.timeserial}
id={msg.serial}
key={msg.serial}
self={msg.clientId === clientId}
message={msg}
onMessageDelete={(msg) => {
deleteMessage(msg, { description: 'deleted by user' }).then((deletedMessage: Message) => {
setMessages((prevMessages) => {
return prevMessages.filter((m) => {
return m.timeserial !== deletedMessage.timeserial;
return m.serial !== deletedMessage.serial;
});
});
});
Expand Down
19 changes: 7 additions & 12 deletions src/core/chat-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ export interface GetMessagesQueryParams {
direction?: 'forwards' | 'backwards';
limit?: number;
/**
* Timeserial indicating the starting point for message retrieval.
* This timeserial is specific to the region of the channel the client is connected to. Messages published within
* the same region of the channel are guaranteed to be received in increasing timeserial order.
* Serial indicating the starting point for message retrieval.
* This serial is specific to the region of the channel the client is connected to. Messages published within
* the same region of the channel are guaranteed to be received in increasing serial order.
*
* @defaultValue undefined (not used if not specified)
*/
fromSerial?: string;
}

export interface CreateMessageResponse {
timeserial: string;
serial: string;
createdAt: number;
}

Expand Down Expand Up @@ -67,7 +67,7 @@ export class ChatApi {
const metadata = message.metadata as MessageMetadata | undefined;
const headers = message.headers as MessageHeaders | undefined;
return new DefaultMessage(
message.timeserial,
message.serial,
message.clientId,
message.roomId,
message.text,
Expand All @@ -85,17 +85,13 @@ export class ChatApi {
});
}

async deleteMessage(
roomId: string,
timeserial: string,
params?: DeleteMessageParams,
): Promise<DeleteMessageResponse> {
async deleteMessage(roomId: string, serial: string, params?: DeleteMessageParams): Promise<DeleteMessageResponse> {
const body: { description?: string; metadata?: MessageActionMetadata } = {
description: params?.description,
metadata: params?.metadata,
};
return this._makeAuthorizedRequest<DeleteMessageResponse>(
`/chat/v2/rooms/${roomId}/messages/${timeserial}/delete`,
`/chat/v2/rooms/${roomId}/messages/${serial}/delete`,
'POST',
body,
{},
Expand All @@ -114,7 +110,6 @@ export class ChatApi {
if (params.headers) {
body.headers = params.headers;
}

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

Expand Down
6 changes: 3 additions & 3 deletions src/core/message-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface MessagePayload {
}

interface ChatMessageFields {
timeserial: string;
serial: string;
clientId: string;
roomId: string;
text: string;
Expand Down Expand Up @@ -67,7 +67,7 @@ export function parseMessage(roomId: string | undefined, inboundMessage: Ably.In
}

const newMessage: ChatMessageFields = {
timeserial: message.serial,
serial: message.serial,
clientId: message.clientId,
roomId,
text: message.data.text,
Expand Down Expand Up @@ -100,7 +100,7 @@ export function parseMessage(roomId: string | undefined, inboundMessage: Ably.In
}
}
return new DefaultMessage(
newMessage.timeserial,
newMessage.serial,
newMessage.clientId,
newMessage.roomId,
newMessage.text,
Expand Down
47 changes: 23 additions & 24 deletions src/core/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ActionMetadata } from './action-metadata.js';
import { ChatMessageActions } from './events.js';
import { Headers } from './headers.js';
import { Metadata } from './metadata.js';
import { DefaultTimeserial, Timeserial } from './timeserial.js';
import { DefaultSerial, Serial } from './serial.js';

/**
* {@link Headers} type for chat messages.
Expand Down Expand Up @@ -46,7 +46,7 @@ export interface Message {
/**
* The unique identifier of the message.
*/
readonly timeserial: string;
readonly serial: string;

/**
* The clientId of the user who created the message.
Expand Down Expand Up @@ -146,23 +146,23 @@ export interface Message {
* Determines if the action of this message is before the action of the given message.
* @param message The message to compare against.
* @returns true if the action of this message is before the given message.
* @throws {@link ErrorInfo} if both message timeserials do not match, or if {@link latestActionSerial} of either is invalid.
* @throws {@link ErrorInfo} if both message serials do not match, or if {@link latestActionSerial} of either is invalid.
*/
actionBefore(message: Message): boolean;

/**
* Determines if the action of this message is after the action of the given message.
* @param message The message to compare against.
* @returns true if the action of this message is after the given message.
* @throws {@link ErrorInfo} if both message timeserials do not match, or if {@link latestActionSerial} of either is invalid.
* @throws {@link ErrorInfo} if both message serials do not match, or if {@link latestActionSerial} of either is invalid.
*/
actionAfter(message: Message): boolean;

/**
* Determines if the action of this message is equal to the action of the given message.
* @param message The message to compare against.
* @returns true if the action of this message is equal to the given message.
* @throws {@link ErrorInfo} if both message timeserials do not match, or if {@link latestActionSerial} of either is invalid.
* @throws {@link ErrorInfo} if both message serials do not match, or if {@link latestActionSerial} of either is invalid.
*/
actionEqual(message: Message): boolean;

Expand All @@ -172,7 +172,7 @@ export interface Message {
* from the backend.
* @param message The message to compare against.
* @returns true if this message was created before the given message, in global order.
* @throws {@link ErrorInfo} if timeserial of either message is invalid.
* @throws {@link ErrorInfo} if serials of either message is invalid.
*/
before(message: Message): boolean;

Expand All @@ -182,49 +182,48 @@ export interface Message {
* from the backend.
* @param message The message to compare against.
* @returns true if this message was created after the given message, in global order.
* @throws {@link ErrorInfo} if timeserial of either message is invalid.
* @throws {@link ErrorInfo} if serials of either message is invalid.
*/
after(message: Message): boolean;

/**
* Determines if this message is equal to the given message.
* @param message The message to compare against.
* @returns true if this message is equal to the given message.
* @throws {@link ErrorInfo} if timeserial of either message is invalid.
* @throws {@link ErrorInfo} if serials of either message is invalid.
*/
equal(message: Message): boolean;
}

/**
* An implementation of the Message interface for chat messages.
*
* Allows for comparison of messages based on their timeserials.
* Allows for comparison of messages based on their serials.
*/
export class DefaultMessage implements Message {
private readonly _calculatedOriginTimeserial: Timeserial;
private readonly _calculatedActionSerial: Timeserial;
private readonly _calculatedOriginSerial: Serial;
private readonly _calculatedActionSerial: Serial;

constructor(
public readonly timeserial: string,
public readonly serial: string,
public readonly clientId: string,
public readonly roomId: string,
public readonly text: string,
public readonly createdAt: Date,
public readonly metadata: MessageMetadata,
public readonly headers: MessageHeaders,

public readonly latestAction: ChatMessageActions,
// the latestActionSerial will be set to the original timeserial for new messages,
// else it will be set to the serial corresponding to whatever action

// the `latestActionSerial` will be set to the current message `serial` for new messages,
// else it will be set to the `updateSerial` corresponding to whatever action
// (update/delete) that was just performed.
public readonly latestActionSerial: string,

public readonly deletedAt?: Date,
public readonly updatedAt?: Date,
public readonly latestActionDetails?: MessageActionDetails,
) {
this._calculatedOriginTimeserial = DefaultTimeserial.calculateTimeserial(timeserial);
this._calculatedActionSerial = DefaultTimeserial.calculateTimeserial(latestActionSerial);
this._calculatedOriginSerial = DefaultSerial.calculateSerial(serial);
this._calculatedActionSerial = DefaultSerial.calculateSerial(latestActionSerial);

// The object is frozen after constructing to enforce readonly at runtime too
Object.freeze(this);
Expand All @@ -249,36 +248,36 @@ export class DefaultMessage implements Message {
actionBefore(message: Message): boolean {
// Check to ensure the messages are the same before comparing operation order
if (!this.equal(message)) {
throw new ErrorInfo('actionBefore(): Cannot compare actions, message timeserials must be equal', 50000, 500);
throw new ErrorInfo('actionBefore(): Cannot compare actions, message serials must be equal', 50000, 500);
}
return this._calculatedActionSerial.before(message.latestActionSerial);
}

actionAfter(message: Message): boolean {
// Check to ensure the messages are the same before comparing operation order
if (!this.equal(message)) {
throw new ErrorInfo('actionAfter(): Cannot compare actions, message timeserials must be equal', 50000, 500);
throw new ErrorInfo('actionAfter(): Cannot compare actions, message serials must be equal', 50000, 500);
}
return this._calculatedActionSerial.after(message.latestActionSerial);
}

actionEqual(message: Message): boolean {
// Check to ensure the messages are the same before comparing operation order
if (!this.equal(message)) {
throw new ErrorInfo('actionEqual(): Cannot compare actions, message timeserials must be equal', 50000, 500);
throw new ErrorInfo('actionEqual(): Cannot compare actions, message serials must be equal', 50000, 500);
}
return this._calculatedActionSerial.equal(message.latestActionSerial);
}

before(message: Message): boolean {
return this._calculatedOriginTimeserial.before(message.timeserial);
return this._calculatedOriginSerial.before(message.serial);
}

after(message: Message): boolean {
return this._calculatedOriginTimeserial.after(message.timeserial);
return this._calculatedOriginSerial.after(message.serial);
}

equal(message: Message): boolean {
return this._calculatedOriginTimeserial.equal(message.timeserial);
return this._calculatedOriginSerial.equal(message.serial);
}
}
18 changes: 8 additions & 10 deletions src/core/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { parseMessage } from './message-parser.js';
import { PaginatedResult } from './query.js';
import { addListenerToChannelWithoutAttach } from './realtime-extensions.js';
import { ContributesToRoomLifecycle } from './room-lifecycle-manager.js';
import { DefaultTimeserial } from './timeserial.js';
import { DefaultSerial } from './serial.js';
import EventEmitter from './utils/event-emitter.js';

/**
Expand Down Expand Up @@ -339,7 +339,7 @@ export class DefaultMessages
const subscriptionPointParams = await subscriptionPoint;

// Check the end time does not occur after the fromSerial time
const parseSerial = DefaultTimeserial.calculateTimeserial(subscriptionPointParams.fromSerial);
const parseSerial = DefaultSerial.calculateSerial(subscriptionPointParams.fromSerial);
if (params.end && params.end > parseSerial.timestamp) {
this._logger.error(
`DefaultSubscriptionManager.getBeforeSubscriptionStart(); end time is after the subscription point of the listener`,
Expand Down Expand Up @@ -380,7 +380,7 @@ export class DefaultMessages
}

/**
* Create a promise that resolves with the attachSerial of the channel or the timeserial of the latest message.
* Create a promise that resolves with the attachSerial of the channel or the serial of the latest message.
*/
private async _resolveSubscriptionStart(): Promise<{
fromSerial: string;
Expand Down Expand Up @@ -466,8 +466,6 @@ export class DefaultMessages

/**
* @inheritdoc Messages
* @throws {@link ErrorInfo} if metadata defines reserved keys.
* @throws {@link ErrorInfo} if headers defines any headers prefixed with reserved words.
*/
async send(params: SendMessageParams): Promise<Message> {
this._logger.trace('Messages.send();', { params });
Expand All @@ -476,15 +474,15 @@ export class DefaultMessages

const response = await this._chatApi.sendMessage(this._roomId, { text, headers, metadata });
return new DefaultMessage(
response.timeserial,
response.serial,
this._clientId,
this._roomId,
text,
new Date(response.createdAt),
metadata ?? {},
headers ?? {},
ChatMessageActions.MessageCreate,
response.timeserial,
response.serial,
);
}

Expand All @@ -493,9 +491,9 @@ export class DefaultMessages
*/
async delete(message: Message, params?: DeleteMessageParams): Promise<Message> {
this._logger.trace('Messages.delete();', { params });
const response = await this._chatApi.deleteMessage(this._roomId, message.timeserial, params);
const response = await this._chatApi.deleteMessage(this._roomId, message.serial, params);
const deletedMessage: Message = new DefaultMessage(
message.timeserial,
message.serial,
message.clientId,
message.roomId,
message.text,
Expand All @@ -505,7 +503,7 @@ export class DefaultMessages
ChatMessageActions.MessageDelete,
response.serial,
response.deletedAt ? new Date(response.deletedAt) : undefined,
undefined,
message.updatedAt,
{
clientId: this._clientId,
description: params?.description,
Expand Down
Loading

0 comments on commit cfdeb29

Please sign in to comment.