diff --git a/README.md b/README.md index 9a2c32fc..c7d7a9b8 100644 --- a/README.md +++ b/README.md @@ -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" } ] } diff --git a/cspell.json b/cspell.json index 43d0895c..1c161fc8 100644 --- a/cspell.json +++ b/cspell.json @@ -5,8 +5,6 @@ "maxNumberOfProblems": 100, "words": [ "realtime", - "timeserial", - "timeserials", "inband", "typers", "msgpack", diff --git a/demo/src/containers/Chat/Chat.tsx b/demo/src/containers/Chat/Chat.tsx index cd31652f..8dba2d31 100644 --- a/demo/src/containers/Chat/Chat.tsx +++ b/demo/src/containers/Chat/Chat.tsx @@ -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; @@ -192,15 +192,15 @@ export const Chat = (props: { roomId: string; setRoomId: (roomId: string) => voi > {messages.map((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; }); }); }); diff --git a/src/core/chat-api.ts b/src/core/chat-api.ts index 9c0aa604..62850bc6 100644 --- a/src/core/chat-api.ts +++ b/src/core/chat-api.ts @@ -11,9 +11,9 @@ 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) */ @@ -21,7 +21,7 @@ export interface GetMessagesQueryParams { } export interface CreateMessageResponse { - timeserial: string; + serial: string; createdAt: number; } @@ -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, @@ -85,17 +85,13 @@ export class ChatApi { }); } - async deleteMessage( - roomId: string, - timeserial: string, - params?: DeleteMessageParams, - ): Promise { + async deleteMessage(roomId: string, serial: string, params?: DeleteMessageParams): Promise { const body: { description?: string; metadata?: MessageActionMetadata } = { description: params?.description, metadata: params?.metadata, }; return this._makeAuthorizedRequest( - `/chat/v2/rooms/${roomId}/messages/${timeserial}/delete`, + `/chat/v2/rooms/${roomId}/messages/${serial}/delete`, 'POST', body, {}, @@ -114,7 +110,6 @@ export class ChatApi { if (params.headers) { body.headers = params.headers; } - return this._makeAuthorizedRequest(`/chat/v2/rooms/${roomId}/messages`, 'POST', body); } diff --git a/src/core/message-parser.ts b/src/core/message-parser.ts index ef1c7b39..1424a84c 100644 --- a/src/core/message-parser.ts +++ b/src/core/message-parser.ts @@ -22,7 +22,7 @@ interface MessagePayload { } interface ChatMessageFields { - timeserial: string; + serial: string; clientId: string; roomId: string; text: string; @@ -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, @@ -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, diff --git a/src/core/message.ts b/src/core/message.ts index f5f9abd1..17276d7d 100644 --- a/src/core/message.ts +++ b/src/core/message.ts @@ -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. @@ -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. @@ -146,7 +146,7 @@ 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; @@ -154,7 +154,7 @@ export interface Message { * 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; @@ -162,7 +162,7 @@ export interface Message { * 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; @@ -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; @@ -182,7 +182,7 @@ 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; @@ -190,7 +190,7 @@ export interface Message { * 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; } @@ -198,33 +198,32 @@ export interface Message { /** * 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); @@ -249,7 +248,7 @@ 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); } @@ -257,7 +256,7 @@ export class DefaultMessage implements Message { 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); } @@ -265,20 +264,20 @@ export class DefaultMessage implements Message { 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); } } diff --git a/src/core/messages.ts b/src/core/messages.ts index a2ebcb23..1c0e7de2 100644 --- a/src/core/messages.ts +++ b/src/core/messages.ts @@ -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'; /** @@ -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`, @@ -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; @@ -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 { this._logger.trace('Messages.send();', { params }); @@ -476,7 +474,7 @@ 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, @@ -484,7 +482,7 @@ export class DefaultMessages metadata ?? {}, headers ?? {}, ChatMessageActions.MessageCreate, - response.timeserial, + response.serial, ); } @@ -493,9 +491,9 @@ export class DefaultMessages */ async delete(message: Message, params?: DeleteMessageParams): Promise { 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, @@ -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, diff --git a/src/core/serial.ts b/src/core/serial.ts new file mode 100644 index 00000000..1d651050 --- /dev/null +++ b/src/core/serial.ts @@ -0,0 +1,152 @@ +import * as Ably from 'ably'; + +/** + * Represents a parsed serial. + */ +export interface Serial { + /** + * The series ID of the serial. + */ + readonly seriesId: string; + + /** + * The timestamp of the serial. + */ + readonly timestamp: number; + + /** + * The counter of the serial. + */ + readonly counter: number; + + /** + * The index of the serial. + */ + readonly index?: number; + + toString(): string; + + before(serial: Serial | string): boolean; + + after(serial: Serial | string): boolean; + + equal(serial: Serial | string): boolean; +} + +/** + * Default implementation of the Serial interface. Used internally to parse and compare serials. + * + * @internal + */ +export class DefaultSerial implements Serial { + public readonly seriesId: string; + public readonly timestamp: number; + public readonly counter: number; + public readonly index?: number; + + private constructor(seriesId: string, timestamp: number, counter: number, index?: number) { + this.seriesId = seriesId; + this.timestamp = timestamp; + this.counter = counter; + this.index = index; + } + + /** + * Returns the string representation of the serial object. + * @returns The serial string. + */ + toString(): string { + return `${this.seriesId}@${this.timestamp.toString()}-${this.counter.toString()}${this.index ? `:${this.index.toString()}` : ''}`; + } + + /** + * Calculate the serial object from a serial string. + * + * @param serial The serial string to parse. + * @returns The parsed serial object. + * @throws {@link ErrorInfo} if serial is invalid. + */ + public static calculateSerial(serial: string): Serial { + const [seriesId, rest] = serial.split('@'); + if (!seriesId || !rest) { + throw new Ably.ErrorInfo('invalid serial', 50000, 500); + } + + const [timestamp, counterAndIndex] = rest.split('-'); + if (!timestamp || !counterAndIndex) { + throw new Ably.ErrorInfo('invalid serial', 50000, 500); + } + + const [counter, index] = counterAndIndex.split(':'); + if (!counter) { + throw new Ably.ErrorInfo('invalid serial', 50000, 500); + } + + return new DefaultSerial(seriesId, Number(timestamp), Number(counter), index ? Number(index) : undefined); + } + + /** + * Compares this serial to the supplied serial, returning a number indicating their relative order. + * @param serialToCompare The serial to compare against. Can be a string or a Serial object. + * @returns 0 if the serials are equal, <0 if the first serial is less than the second, >0 if the first serial is greater than the second. + * @throws {@link ErrorInfo} if comparison serial is invalid. + */ + private _serialCompare(serialToCompare: string | Serial): number { + const secondSerial = + typeof serialToCompare === 'string' ? DefaultSerial.calculateSerial(serialToCompare) : serialToCompare; + + // Compare the timestamp + const timestampDiff = this.timestamp - secondSerial.timestamp; + if (timestampDiff) { + return timestampDiff; + } + + // Compare the counter + const counterDiff = this.counter - secondSerial.counter; + if (counterDiff) { + return counterDiff; + } + + // Compare the seriesId lexicographically + const seriesIdDiff = this.seriesId === secondSerial.seriesId ? 0 : this.seriesId < secondSerial.seriesId ? -1 : 1; + + if (seriesIdDiff) { + return seriesIdDiff; + } + + // Compare the index, if present + return this.index !== undefined && secondSerial.index !== undefined ? this.index - secondSerial.index : 0; + } + + /** + * Determines if this serial occurs logically before the given serial. + * + * @param serial The serial to compare against. Can be a string or a Serial object. + * @returns true if this serial precedes the given serial, in global order. + * @throws {@link ErrorInfo} if the given serial is invalid. + */ + before(serial: Serial | string): boolean { + return this._serialCompare(serial) < 0; + } + + /** + * Determines if this serial occurs logically after the given serial. + * + * @param serial The serial to compare against. Can be a string or a Serial object. + * @returns true if this serial follows the given serial, in global order. + * @throws {@link ErrorInfo} if the given serial is invalid. + */ + after(serial: Serial | string): boolean { + return this._serialCompare(serial) > 0; + } + + /** + * Determines if this serial is equal to the given serial. + * @param serial The serial to compare against. Can be a string or a Serial object. + * @returns true if this serial is equal to the given serial. + * @throws {@link ErrorInfo} if the given serial is invalid. + */ + equal(serial: Serial | string): boolean { + return this._serialCompare(serial) === 0; + } +} diff --git a/src/core/timeserial.ts b/src/core/timeserial.ts deleted file mode 100644 index dc1f7878..00000000 --- a/src/core/timeserial.ts +++ /dev/null @@ -1,155 +0,0 @@ -import * as Ably from 'ably'; - -/** - * Represents a parsed timeserial. - */ -export interface Timeserial { - /** - * The series ID of the timeserial. - */ - readonly seriesId: string; - - /** - * The timestamp of the timeserial. - */ - readonly timestamp: number; - - /** - * The counter of the timeserial. - */ - readonly counter: number; - - /** - * The index of the timeserial. - */ - readonly index?: number; - - toString(): string; - - before(timeserial: Timeserial | string): boolean; - - after(timeserial: Timeserial | string): boolean; - - equal(timeserial: Timeserial | string): boolean; -} - -/** - * Default implementation of the Timeserial interface. Used internally to parse and compare timeserials. - * - * @internal - */ -export class DefaultTimeserial implements Timeserial { - public readonly seriesId: string; - public readonly timestamp: number; - public readonly counter: number; - public readonly index?: number; - - private constructor(seriesId: string, timestamp: number, counter: number, index?: number) { - this.seriesId = seriesId; - this.timestamp = timestamp; - this.counter = counter; - this.index = index; - } - - /** - * Returns the string representation of the timeserial object. - * @returns The timeserial string. - */ - toString(): string { - return `${this.seriesId}@${this.timestamp.toString()}-${this.counter.toString()}${this.index ? `:${this.index.toString()}` : ''}`; - } - - /** - * Calculate the timeserial object from a timeserial string. - * - * @param timeserial The timeserial string to parse. - * @returns The parsed timeserial object. - * @throws {@link ErrorInfo} if timeserial is invalid. - */ - public static calculateTimeserial(timeserial: string): Timeserial { - const [seriesId, rest] = timeserial.split('@'); - if (!seriesId || !rest) { - throw new Ably.ErrorInfo('invalid timeserial', 50000, 500); - } - - const [timestamp, counterAndIndex] = rest.split('-'); - if (!timestamp || !counterAndIndex) { - throw new Ably.ErrorInfo('invalid timeserial', 50000, 500); - } - - const [counter, index] = counterAndIndex.split(':'); - if (!counter) { - throw new Ably.ErrorInfo('invalid timeserial', 50000, 500); - } - - return new DefaultTimeserial(seriesId, Number(timestamp), Number(counter), index ? Number(index) : undefined); - } - - /** - * Compares this timeserial to the supplied timeserial, returning a number indicating their relative order. - * @param timeserialToCompare The timeserial to compare against. Can be a string or a Timeserial object. - * @returns 0 if the timeserials are equal, <0 if the first timeserial is less than the second, >0 if the first timeserial is greater than the second. - * @throws {@link ErrorInfo} if comparison timeserial is invalid. - */ - private _timeserialCompare(timeserialToCompare: string | Timeserial): number { - const secondTimeserial = - typeof timeserialToCompare === 'string' - ? DefaultTimeserial.calculateTimeserial(timeserialToCompare) - : timeserialToCompare; - - // Compare the timestamp - const timestampDiff = this.timestamp - secondTimeserial.timestamp; - if (timestampDiff) { - return timestampDiff; - } - - // Compare the counter - const counterDiff = this.counter - secondTimeserial.counter; - if (counterDiff) { - return counterDiff; - } - - // Compare the seriesId lexicographically - const seriesIdDiff = - this.seriesId === secondTimeserial.seriesId ? 0 : this.seriesId < secondTimeserial.seriesId ? -1 : 1; - - if (seriesIdDiff) { - return seriesIdDiff; - } - - // Compare the index, if present - return this.index !== undefined && secondTimeserial.index !== undefined ? this.index - secondTimeserial.index : 0; - } - - /** - * Determines if this timeserial occurs logically before the given timeserial. - * - * @param timeserial The timeserial to compare against. Can be a string or a Timeserial object. - * @returns true if this timeserial precedes the given timeserial, in global order. - * @throws {@link ErrorInfo} if the given timeserial is invalid. - */ - before(timeserial: Timeserial | string): boolean { - return this._timeserialCompare(timeserial) < 0; - } - - /** - * Determines if this timeserial occurs logically after the given timeserial. - * - * @param timeserial The timeserial to compare against. Can be a string or a Timeserial object. - * @returns true if this timeserial follows the given timeserial, in global order. - * @throws {@link ErrorInfo} if the given timeserial is invalid. - */ - after(timeserial: Timeserial | string): boolean { - return this._timeserialCompare(timeserial) > 0; - } - - /** - * Determines if this timeserial is equal to the given timeserial. - * @param timeserial The timeserial to compare against. Can be a string or a Timeserial object. - * @returns true if this timeserial is equal to the given timeserial. - * @throws {@link ErrorInfo} if the given timeserial is invalid. - */ - equal(timeserial: Timeserial | string): boolean { - return this._timeserialCompare(timeserial) === 0; - } -} diff --git a/test/core/message-parser.test.ts b/test/core/message-parser.test.ts index 02c01927..63756aa5 100644 --- a/test/core/message-parser.test.ts +++ b/test/core/message-parser.test.ts @@ -187,7 +187,7 @@ describe('parseMessage', () => { const result = parseMessage('room1', message); expect(result).toBeInstanceOf(DefaultMessage); - expect(result.timeserial).toBe('cbfkKvEYgBhDaZ38195418@1728402074206-0:0'); + expect(result.serial).toBe('cbfkKvEYgBhDaZ38195418@1728402074206-0:0'); expect(result.clientId).toBe('client1'); expect(result.roomId).toBe('room1'); expect(result.text).toBe('hello'); @@ -226,7 +226,7 @@ describe('parseMessage', () => { const result = parseMessage('room1', message); expect(result).toBeInstanceOf(DefaultMessage); - expect(result.timeserial).toBe('cbfkKvEYgBhDaZ38195418@1728402074206-0:0'); + expect(result.serial).toBe('cbfkKvEYgBhDaZ38195418@1728402074206-0:0'); expect(result.clientId).toBe('client1'); expect(result.roomId).toBe('room1'); expect(result.text).toBe('hello'); @@ -271,7 +271,7 @@ describe('parseMessage', () => { const result = parseMessage('room1', message); expect(result).toBeInstanceOf(DefaultMessage); - expect(result.timeserial).toBe('cbfkKvEYgBhDaZ38195418@1728402074206-0:0'); + expect(result.serial).toBe('cbfkKvEYgBhDaZ38195418@1728402074206-0:0'); expect(result.clientId).toBe('client1'); expect(result.roomId).toBe('room1'); expect(result.text).toBe('hello'); diff --git a/test/core/message.test.ts b/test/core/message.test.ts index cf6ed69d..db5c77ec 100644 --- a/test/core/message.test.ts +++ b/test/core/message.test.ts @@ -6,11 +6,11 @@ import { DefaultMessage } from '../../src/core/message.ts'; describe('ChatMessage', () => { it('is the same as another message', () => { - const firstTimeserial = 'abcdefghij@1672531200000-123'; - const secondTimeserial = 'abcdefghij@1672531200000-123'; + const firstSerial = 'abcdefghij@1672531200000-123'; + const secondSerial = 'abcdefghij@1672531200000-123'; const firstMessage = new DefaultMessage( - firstTimeserial, + firstSerial, 'clientId', 'roomId', 'hello there', @@ -18,10 +18,10 @@ describe('ChatMessage', () => { {}, {}, ChatMessageActions.MessageCreate, - firstTimeserial, + firstSerial, ); const secondMessage = new DefaultMessage( - secondTimeserial, + secondSerial, 'clientId', 'roomId', 'hello there', @@ -29,18 +29,18 @@ describe('ChatMessage', () => { {}, {}, ChatMessageActions.MessageCreate, - secondTimeserial, + secondSerial, ); expect(firstMessage.equal(secondMessage)).toBe(true); }); it('is not the same as another message', () => { - const firstTimeserial = 'abcdefghij@1672531200000-123'; - const secondTimeserial = 'abcdefghij@1672531200000-124'; + const firstSerial = 'abcdefghij@1672531200000-123'; + const secondSerial = 'abcdefghij@1672531200000-124'; const firstMessage = new DefaultMessage( - firstTimeserial, + firstSerial, 'clientId', 'roomId', 'hello there', @@ -48,10 +48,10 @@ describe('ChatMessage', () => { {}, {}, ChatMessageActions.MessageCreate, - firstTimeserial, + firstSerial, ); const secondMessage = new DefaultMessage( - secondTimeserial, + secondSerial, 'clientId', 'roomId', 'hello there', @@ -59,18 +59,18 @@ describe('ChatMessage', () => { {}, {}, ChatMessageActions.MessageCreate, - secondTimeserial, + secondSerial, ); expect(firstMessage.equal(secondMessage)).toBe(false); }); it('is before another message', () => { - const firstTimeserial = 'abcdefghij@1672531200000-123'; - const secondTimeserial = 'abcdefghij@1672531200000-124'; + const firstSerial = 'abcdefghij@1672531200000-123'; + const secondSerial = 'abcdefghij@1672531200000-124'; const firstMessage = new DefaultMessage( - firstTimeserial, + firstSerial, 'clientId', 'roomId', 'hello there', @@ -78,10 +78,10 @@ describe('ChatMessage', () => { {}, {}, ChatMessageActions.MessageCreate, - firstTimeserial, + firstSerial, ); const secondMessage = new DefaultMessage( - secondTimeserial, + secondSerial, 'clientId', 'roomId', 'hello there', @@ -89,17 +89,17 @@ describe('ChatMessage', () => { {}, {}, ChatMessageActions.MessageCreate, - secondTimeserial, + secondSerial, ); expect(firstMessage.before(secondMessage)).toBe(true); }); it('is after another message', () => { - const firstTimeserial = 'abcdefghij@1672531200000-124'; - const secondTimeserial = 'abcdefghij@1672531200000-123'; + const firstSerial = 'abcdefghij@1672531200000-124'; + const secondSerial = 'abcdefghij@1672531200000-123'; const firstMessage = new DefaultMessage( - firstTimeserial, + firstSerial, 'clientId', 'roomId', 'hello there', @@ -107,10 +107,10 @@ describe('ChatMessage', () => { {}, {}, ChatMessageActions.MessageCreate, - firstTimeserial, + firstSerial, ); const secondMessage = new DefaultMessage( - secondTimeserial, + secondSerial, 'clientId', 'roomId', 'hello there', @@ -118,16 +118,16 @@ describe('ChatMessage', () => { {}, {}, ChatMessageActions.MessageCreate, - secondTimeserial, + secondSerial, ); expect(firstMessage.after(secondMessage)).toBe(true); }); - it('throws an error with an invalid timeserial', () => { + it('throws an error with an invalid serial', () => { expect(() => { new DefaultMessage( - 'not a valid timeserial', + 'not a valid serial', 'clientId', 'roomId', 'hello there', @@ -135,19 +135,19 @@ describe('ChatMessage', () => { {}, {}, ChatMessageActions.MessageCreate, - 'not a valid timeserial', + 'not a valid serial', ); }).toThrowErrorInfo({ code: 50000, - message: 'invalid timeserial', + message: 'invalid serial', }); }); describe('message actions', () => { it('is deleted', () => { - const firstTimeserial = 'abcdefghij@1672531200000-124:0'; + const firstSerial = 'abcdefghij@1672531200000-124:0'; const firstMessage = new DefaultMessage( - firstTimeserial, + firstSerial, 'clientId', 'roomId', 'hello there', @@ -167,9 +167,9 @@ describe('ChatMessage', () => { }); it('is updated', () => { - const firstTimeserial = 'abcdefghij@1672531200000-124'; + const firstSerial = 'abcdefghij@1672531200000-124'; const firstMessage = new DefaultMessage( - firstTimeserial, + firstSerial, 'clientId', 'roomId', 'hello there', @@ -187,14 +187,14 @@ describe('ChatMessage', () => { }); it(`throws an error when trying to compare actions belonging to different origin messages`, () => { - const firstTimeserial = 'abcdefghij@1672531200000-124'; - const secondTimeserial = 'abcdefghij@1672531200000-123'; + const firstSerial = 'abcdefghij@1672531200000-124'; + const secondSerial = 'abcdefghij@1672531200000-123'; const firstActionSerial = 'abcdefghij@1672531200000-123:0'; const secondActionSerial = 'abcdefghij@1672531200000-123:0'; const firstMessage = new DefaultMessage( - firstTimeserial, + firstSerial, 'clientId', 'roomId', 'hello there', @@ -205,7 +205,7 @@ describe('ChatMessage', () => { firstActionSerial, ); const secondMessage = new DefaultMessage( - secondTimeserial, + secondSerial, 'clientId', 'roomId', 'hello there', @@ -218,7 +218,7 @@ describe('ChatMessage', () => { expect(() => firstMessage.actionEqual(secondMessage)).toThrowErrorInfo({ code: 50000, - message: 'actionEqual(): Cannot compare actions, message timeserials must be equal', + message: 'actionEqual(): Cannot compare actions, message serials must be equal', }); }); @@ -269,9 +269,9 @@ describe('ChatMessage', () => { ], ])('compare message action serials', (name, { firstActionSerial, secondActionSerial, expected }) => { it(name, () => { - const messageTimeserial = 'abcdefghij@1672531200000-123'; + const messageSerial = 'abcdefghij@1672531200000-123'; const firstMessage = new DefaultMessage( - messageTimeserial, + messageSerial, 'clientId', 'roomId', 'hello there', @@ -282,7 +282,7 @@ describe('ChatMessage', () => { firstActionSerial, ); const secondMessage = new DefaultMessage( - messageTimeserial, + messageSerial, 'clientId', 'roomId', 'hello there', diff --git a/test/core/messages.integration.test.ts b/test/core/messages.integration.test.ts index f1458af3..dcb953cd 100644 --- a/test/core/messages.integration.test.ts +++ b/test/core/messages.integration.test.ts @@ -71,12 +71,12 @@ describe('messages integration', () => { expect.objectContaining({ text: 'Hello there!', clientId: chat.clientId, - timeserial: message1.timeserial, + serial: message1.serial, }), expect.objectContaining({ text: 'I have the high ground!', clientId: chat.clientId, - timeserial: message2.timeserial, + serial: message2.serial, }), ]); }); @@ -124,7 +124,7 @@ describe('messages integration', () => { expect.objectContaining({ text: 'Hello there!', clientId: chat.clientId, - timeserial: message1.timeserial, + serial: message1.serial, }), ]); // Check that the deletion was received @@ -132,7 +132,7 @@ describe('messages integration', () => { expect.objectContaining({ text: 'Hello there!', clientId: chat.clientId, - timeserial: deletedMessage1.timeserial, + serial: deletedMessage1.serial, deletedAt: deletedMessage1.deletedAt, deletedBy: chat.clientId, latestAction: ChatMessageActions.MessageDelete, @@ -158,17 +158,17 @@ describe('messages integration', () => { expect.objectContaining({ text: 'Hello there!', clientId: chat.clientId, - timeserial: message1.timeserial, + serial: message1.serial, }), expect.objectContaining({ text: 'I have the high ground!', clientId: chat.clientId, - timeserial: message2.timeserial, + serial: message2.serial, }), expect.objectContaining({ text: 'You underestimate my power!', clientId: chat.clientId, - timeserial: message3.timeserial, + serial: message3.serial, }), ]); @@ -195,7 +195,7 @@ describe('messages integration', () => { expect.objectContaining({ text: 'Hello there!', clientId: chat.clientId, - timeserial: deletedMessage1.timeserial, + serial: deletedMessage1.serial, deletedAt: deletedMessage1.deletedAt, deletedBy: chat.clientId, }), @@ -223,17 +223,17 @@ describe('messages integration', () => { expect.objectContaining({ text: 'Hello there!', clientId: chat.clientId, - timeserial: message1.timeserial, + serial: message1.serial, }), expect.objectContaining({ text: 'I have the high ground!', clientId: chat.clientId, - timeserial: message2.timeserial, + serial: message2.serial, }), expect.objectContaining({ text: 'You underestimate my power!', clientId: chat.clientId, - timeserial: message3.timeserial, + serial: message3.serial, }), ]); @@ -247,7 +247,7 @@ describe('messages integration', () => { expect.objectContaining({ text: "Don't try it!", clientId: chat.clientId, - timeserial: message4.timeserial, + serial: message4.serial, }), ]); @@ -273,17 +273,17 @@ describe('messages integration', () => { expect.objectContaining({ text: "Don't try it!", clientId: chat.clientId, - timeserial: message4.timeserial, + serial: message4.serial, }), expect.objectContaining({ text: 'You underestimate my power!', clientId: chat.clientId, - timeserial: message3.timeserial, + serial: message3.serial, }), expect.objectContaining({ text: 'I have the high ground!', clientId: chat.clientId, - timeserial: message2.timeserial, + serial: message2.serial, }), ]); @@ -297,7 +297,7 @@ describe('messages integration', () => { expect.objectContaining({ text: 'Hello there!', clientId: chat.clientId, - timeserial: message1.timeserial, + serial: message1.serial, }), ]); @@ -336,14 +336,14 @@ describe('messages integration', () => { { text: 'Hello there!', clientId: chat.clientId, - timeserial: message1.timeserial, + serial: message1.serial, headers: { key1: 'val1', key2: 22 }, metadata: { hello: { name: 'world' } }, }, { text: 'I have the high ground!', clientId: chat.clientId, - timeserial: message2.timeserial, + serial: message2.serial, headers: { key1: 'second key 1 value', key2: 99, greeting: 'yo' }, metadata: { hello: { name: 'second' } }, }, @@ -364,7 +364,7 @@ describe('messages integration', () => { ]); }); - it('should be able to get history for listener from attached timeserial', async (context) => { + it('should be able to get history for listener from attached serial', async (context) => { const { chat } = context; const room = await getRandomRoom(chat); @@ -389,12 +389,12 @@ describe('messages integration', () => { expect.objectContaining({ text: 'I have the high ground!', clientId: chat.clientId, - timeserial: message2.timeserial, + serial: message2.serial, }), expect.objectContaining({ text: 'Hello there!', clientId: chat.clientId, - timeserial: message1.timeserial, + serial: message1.serial, }), ]); @@ -405,21 +405,21 @@ describe('messages integration', () => { // Try and get history again const historyPreSubscription2 = await getPreviousMessages({ limit: 50 }); - // It should not contain the new messages since we should be getting messages based on initial attach timeserial + // It should not contain the new messages since we should be getting messages based on initial attach serial expect(historyPreSubscription2.items).toEqual([ expect.objectContaining({ text: 'I have the high ground!', clientId: chat.clientId, - timeserial: message2.timeserial, + serial: message2.serial, }), expect.objectContaining({ text: 'Hello there!', clientId: chat.clientId, - timeserial: message1.timeserial, + serial: message1.serial, }), ]); }); - it('should be able to get history for listener with latest message timeserial', async (context) => { + it('should be able to get history for listener with latest message serial', async (context) => { const { chat } = context; const room = await getRandomRoom(chat); @@ -434,10 +434,10 @@ describe('messages integration', () => { const message1 = await room.messages.send({ text: 'Hello there!' }); const message2 = await room.messages.send({ text: 'I have the high ground!' }); - // Do a history request which should use attach timeserial + // Do a history request which should use attach serial const historyPreSubscription1 = await getPreviousMessages({ limit: 50 }); - // Should have no items since we are using attach timeserial + // Should have no items since we are using attach serial expect(historyPreSubscription1.items).toEqual([]); const { getPreviousMessages: getPreviousMessagesListener2 } = room.messages.subscribe(() => {}); @@ -450,12 +450,12 @@ describe('messages integration', () => { expect.objectContaining({ text: 'I have the high ground!', clientId: chat.clientId, - timeserial: message2.timeserial, + serial: message2.serial, }), expect.objectContaining({ text: 'Hello there!', clientId: chat.clientId, - timeserial: message1.timeserial, + serial: message1.serial, }), ]); }); diff --git a/test/core/messages.test.ts b/test/core/messages.test.ts index fa1f5594..976e3aad 100644 --- a/test/core/messages.test.ts +++ b/test/core/messages.test.ts @@ -66,7 +66,7 @@ describe('Messages', () => { const { chatApi } = context; const timestamp = Date.now(); vi.spyOn(chatApi, 'sendMessage').mockResolvedValue({ - timeserial: 'abcdefghij@1672531200000-123', + serial: 'abcdefghij@1672531200000-123', createdAt: timestamp, }); @@ -76,7 +76,7 @@ describe('Messages', () => { expect(message).toEqual( expect.objectContaining({ - timeserial: 'abcdefghij@1672531200000-123', + serial: 'abcdefghij@1672531200000-123', text: 'hello there', clientId: 'clientId', createdAt: new Date(timestamp), @@ -89,7 +89,7 @@ describe('Messages', () => { const { chatApi } = context; const sendTimestamp = Date.now(); vi.spyOn(chatApi, 'sendMessage').mockResolvedValue({ - timeserial: 'abcdefghij@1672531200000-123', + serial: 'abcdefghij@1672531200000-123', createdAt: sendTimestamp, }); @@ -104,7 +104,7 @@ describe('Messages', () => { expect(deleteMessage1).toEqual( expect.objectContaining({ - timeserial: 'abcdefghij@1672531200000-123', + serial: 'abcdefghij@1672531200000-123', text: 'hello there', clientId: 'clientId', deletedAt: new Date(deleteTimestamp), @@ -120,7 +120,7 @@ describe('Messages', () => { const { chatApi, realtime } = context; const timestamp = Date.now(); vi.spyOn(chatApi, 'sendMessage').mockResolvedValue({ - timeserial: 'abcdefghij@1672531200000-123', + serial: 'abcdefghij@1672531200000-123', createdAt: timestamp, }); @@ -135,7 +135,7 @@ describe('Messages', () => { expect(message).toEqual( expect.objectContaining({ - timeserial: 'abcdefghij@1672531200000-123', + serial: 'abcdefghij@1672531200000-123', text: 'hello there', clientId: 'clientId', createdAt: new Date(timestamp), @@ -674,7 +674,7 @@ describe('Messages', () => { }; }; - // Set the timeserial of the channel attach + // Set the serial of the channel attach channel.properties.attachSerial = testAttachSerial; vi.spyOn(channel, 'whenState').mockImplementation(function () { @@ -738,13 +738,13 @@ describe('Messages', () => { // Mock the channel state to be attached so we should query with the channel serial vi.spyOn(channel, 'state', 'get').mockReturnValue('attached'); - // Set the timeserial of the channel (attachment serial) + // Set the serial of the channel (attachment serial) channel.properties.channelSerial = latestChannelSerial; // Subscribe to the messages const { getPreviousMessages } = room.messages.subscribe(() => {}); - // Run a history query for the listener and check the chat api call is made with the channel timeserial + // Run a history query for the listener and check the chat api call is made with the channel serial await expect(getPreviousMessages({ limit: 50 })).resolves.toBeTruthy(); }); @@ -770,7 +770,7 @@ describe('Messages', () => { }; }; - // Set the timeserials for before attachment testing + // Set the serials for before attachment testing channel.properties.attachSerial = firstAttachmentSerial; const { getPreviousMessages } = room.messages.subscribe(() => {}); @@ -871,7 +871,7 @@ describe('Messages', () => { return Promise.resolve(null); }); - // Set the timeserials for the channel + // Set the serials for the channel channel.properties.channelSerial = firstChannelSerial; channel.properties.attachSerial = firstAttachSerial; @@ -960,7 +960,7 @@ describe('Messages', () => { return Promise.resolve(null); }); - // Set the timeserials for the channel + // Set the serials for the channel channel.properties.channelSerial = firstChannelSerial; channel.properties.attachSerial = firstAttachSerial; @@ -1046,7 +1046,7 @@ describe('Messages', () => { await expect(getPreviousMessages({ limit: 50 })).resolves.toBeTruthy(); }); - it('should throw an error if listener query end time is later than query timeserial', async (context) => { + it('should throw an error if listener query end time is later than query serial', async (context) => { // Create a room instance const { room } = context; @@ -1058,7 +1058,7 @@ describe('Messages', () => { }; }; - // Set the timeserials for the channel + // Set the serials for the channel channel.properties.channelSerial = '108uyDJAgBOihn12345678@1772531200000-1'; channel.properties.attachSerial = '108uyDJAgBOihn12345678@1772531200000-1'; diff --git a/test/core/timeserial.test.ts b/test/core/timeserial.test.ts index 24e1201f..cd23eece 100644 --- a/test/core/timeserial.test.ts +++ b/test/core/timeserial.test.ts @@ -1,11 +1,11 @@ import { describe, expect, it } from 'vitest'; -import { DefaultTimeserial } from '../../src/core/timeserial.ts'; +import { DefaultSerial } from '../../src/core/serial.ts'; -describe('calculateTimeserial', () => { - it('parses a valid timeserial', () => { - const timeserial = 'abcdefghij@1672531200000-123:1'; - const result = DefaultTimeserial.calculateTimeserial(timeserial); +describe('calculateSerial', () => { + it('parses a valid serial', () => { + const serial = 'abcdefghij@1672531200000-123:1'; + const result = DefaultSerial.calculateSerial(serial); expect(result).toEqual({ seriesId: 'abcdefghij', timestamp: 1672531200000, @@ -18,19 +18,19 @@ describe('calculateTimeserial', () => { ['abcdefghij@1672531200000'], // No counter ['abcdefghij@'], // No timestamp ['abcdefghij'], // No series id - ])('throws an error with an invalid timeserial %s', (timeserial) => { + ])('throws an error with an invalid serial %s', (serial) => { expect(() => { - DefaultTimeserial.calculateTimeserial(timeserial); + DefaultSerial.calculateSerial(serial); }).toThrowErrorInfo({ code: 50000, - message: 'invalid timeserial', + message: 'invalid serial', }); }); - it('should be equal to the same timeserial', () => { - const timeserial = 'abcdefghij@1672531200000-123:1'; - const result = DefaultTimeserial.calculateTimeserial(timeserial); - expect(result.equal(timeserial)).toBe(true); + it('should be equal to the same serial', () => { + const serial = 'abcdefghij@1672531200000-123:1'; + const result = DefaultSerial.calculateSerial(serial); + expect(result.equal(serial)).toBe(true); }); it.each([ @@ -46,11 +46,11 @@ describe('calculateTimeserial', () => { ['abcdefghi@1672531200000-123', 'abcdefghij@1672531200001-123', true], // Earlier timestamp ['abcdefghij@1672531200001-123', 'abcdefghij@1672531200000-123', false], // Later timestamp ['abcdefghij@1672531200000-123', 'abcdefghij@1672531200000-123', false], // Same timestamp] - ])(`is before another timeserial %s, %s -> %o`, (firstTimeserialString, secondTimeserialString, expected) => { - const firstTimeserial = DefaultTimeserial.calculateTimeserial(firstTimeserialString); - const secondTimeserial = DefaultTimeserial.calculateTimeserial(secondTimeserialString); + ])(`is before another serial %s, %s -> %o`, (firstSerialString, secondSerialString, expected) => { + const firstSerial = DefaultSerial.calculateSerial(firstSerialString); + const secondSerial = DefaultSerial.calculateSerial(secondSerialString); - expect(firstTimeserial.before(secondTimeserial)).toBe(expected); + expect(firstSerial.before(secondSerial)).toBe(expected); }); it.each([ @@ -66,15 +66,15 @@ describe('calculateTimeserial', () => { ['abcdefghij@1672531200000-123', 'abcdefghij@1672531200001-123', false], // Earlier timestamp ['abcdefghij@1672531200001-123', 'abcdefghij@1672531200000-123', true], // Later timestamp ['abcdefghij@1672531200000-123', 'abcdefghij@1672531200000-123', false], // Same timestamp - ])('is after another timeserial %s, %s -> %o', (firstTimeserialString, secondTimeserialString, expected) => { - const firstTimeserial = DefaultTimeserial.calculateTimeserial(firstTimeserialString); - const secondTimeserial = DefaultTimeserial.calculateTimeserial(secondTimeserialString); - expect(firstTimeserial.after(secondTimeserial)).toBe(expected); + ])('is after another serial %s, %s -> %o', (firstSerialString, secondSerialString, expected) => { + const firstSerial = DefaultSerial.calculateSerial(firstSerialString); + const secondSerial = DefaultSerial.calculateSerial(secondSerialString); + expect(firstSerial.after(secondSerial)).toBe(expected); }); - it('should return the original timeserial as a string', () => { - const timeserial = 'abcdefghij@1672531200000-123:1'; - const result = DefaultTimeserial.calculateTimeserial(timeserial); - expect(result.toString()).toBe(timeserial); + it('should return the original serial as a string', () => { + const serial = 'abcdefghij@1672531200000-123:1'; + const result = DefaultSerial.calculateSerial(serial); + expect(result.toString()).toBe(serial); }); }); diff --git a/test/react/hooks/use-messages.test.tsx b/test/react/hooks/use-messages.test.tsx index 1d0221fd..2653956b 100644 --- a/test/react/hooks/use-messages.test.tsx +++ b/test/react/hooks/use-messages.test.tsx @@ -112,8 +112,7 @@ describe('useMessages', () => { message: { timestamp: new Date(), text: 'test message', - timeserial: '123', - action: ChatMessageActions.MessageCreate, + serial: '123', clientId: '123', roomId: '123', createdAt: new Date(),