diff --git a/.changeset/chat-message-edit-timestamp.md b/.changeset/chat-message-edit-timestamp.md new file mode 100644 index 00000000..09e58511 --- /dev/null +++ b/.changeset/chat-message-edit-timestamp.md @@ -0,0 +1,5 @@ +--- +'@livekit/rtc-node': patch +--- + +Fix `ChatMessage.editTimestamp` being `NaN` instead of `undefined` for a message that was never edited. diff --git a/packages/livekit-rtc/src/participant.ts b/packages/livekit-rtc/src/participant.ts index 07ed90a4..a7d7bbc6 100644 --- a/packages/livekit-rtc/src/participant.ts +++ b/packages/livekit-rtc/src/participant.ts @@ -104,7 +104,7 @@ import type { RemoteTrackPublication, TrackPublication } from './track_publicati import { LocalTrackPublication } from './track_publication.js'; import type { Transcription } from './transcription.js'; import type { ChatMessage } from './types.js'; -import { byteStreamInfoFromProto, textStreamInfoFromProto } from './utils.js'; +import { byteStreamInfoFromProto, chatMessageFromProto, textStreamInfoFromProto } from './utils.js'; import { numberToBigInt } from './utils.js'; export abstract class Participant { @@ -651,13 +651,7 @@ export class LocalParticipant extends Participant { switch (cb.message.case) { case 'chatMessage': - const { id, timestamp, editTimestamp, message } = cb.message.value!; - return { - id: id!, - timestamp: Number(timestamp), - editTimestamp: Number(editTimestamp), - message: message!, - }; + return chatMessageFromProto(cb.message.value!); case 'error': default: throw new Error(cb.message.value); @@ -698,13 +692,7 @@ export class LocalParticipant extends Participant { switch (cb.message.case) { case 'chatMessage': - const { id, timestamp, editTimestamp, message } = cb.message.value!; - return { - id: id!, - timestamp: Number(timestamp), - editTimestamp: Number(editTimestamp), - message: message!, - }; + return chatMessageFromProto(cb.message.value!); case 'error': default: throw new Error(cb.message.value); diff --git a/packages/livekit-rtc/src/room.ts b/packages/livekit-rtc/src/room.ts index 4ed47938..173dcdc4 100644 --- a/packages/livekit-rtc/src/room.ts +++ b/packages/livekit-rtc/src/room.ts @@ -55,6 +55,7 @@ import type { ChatMessage } from './types.js'; import { bigIntToNumber, byteStreamInfoFromProto, + chatMessageFromProto, numberToBigInt, textStreamInfoFromProto, } from './utils.js'; @@ -825,14 +826,7 @@ export class Room extends (EventEmitter as new () => TypedEmitter } } else if (ev.case == 'chatMessage') { const participant = this.retrieveParticipantByIdentity(ev.value.participantIdentity!); - const { id, message: messageText, timestamp, editTimestamp, generated } = ev.value.message!; - const message: ChatMessage = { - id: id!, - message: messageText!, - timestamp: Number(timestamp), - editTimestamp: Number(editTimestamp), - generated, - }; + const message: ChatMessage = chatMessageFromProto(ev.value.message!); this.emit(RoomEvent.ChatMessage, message, participant); } else if (ev.case == 'dataPacketReceived') { // Can be undefined if the data is sent from a Server SDK diff --git a/packages/livekit-rtc/src/utils.test.ts b/packages/livekit-rtc/src/utils.test.ts index 35ac904b..deb7f3ca 100644 --- a/packages/livekit-rtc/src/utils.test.ts +++ b/packages/livekit-rtc/src/utils.test.ts @@ -1,8 +1,9 @@ // SPDX-FileCopyrightText: 2024 LiveKit, Inc. // // SPDX-License-Identifier: Apache-2.0 +import { ChatMessage as ProtoChatMessage } from '@livekit/rtc-ffi-bindings'; import { describe, expect, it } from 'vitest'; -import { splitUtf8 } from './utils.js'; +import { chatMessageFromProto, splitUtf8 } from './utils.js'; describe('splitUtf8', () => { it('splits a string into chunks of the given size', () => { @@ -49,3 +50,34 @@ describe('splitUtf8', () => { expect(splitUtf8('', 5)).toEqual([]); }); }); + +describe('chatMessageFromProto', () => { + it('leaves editTimestamp undefined for a message that was never edited', () => { + const message = chatMessageFromProto( + new ProtoChatMessage({ id: 'msg-1', message: 'hello', timestamp: 1700000000000n }), + ); + + expect(message).toEqual({ + id: 'msg-1', + message: 'hello', + timestamp: 1700000000000, + editTimestamp: undefined, + generated: undefined, + }); + }); + + it('converts editTimestamp and generated for an edited message', () => { + const message = chatMessageFromProto( + new ProtoChatMessage({ + id: 'msg-2', + message: 'hello again', + timestamp: 1700000000000n, + editTimestamp: 1700000005000n, + generated: true, + }), + ); + + expect(message.editTimestamp).toBe(1700000005000); + expect(message.generated).toBe(true); + }); +}); diff --git a/packages/livekit-rtc/src/utils.ts b/packages/livekit-rtc/src/utils.ts index 1c274d59..1babb4ba 100644 --- a/packages/livekit-rtc/src/utils.ts +++ b/packages/livekit-rtc/src/utils.ts @@ -3,9 +3,11 @@ // SPDX-License-Identifier: Apache-2.0 import type { ByteStreamInfo as ProtoByteStreamInfo, + ChatMessage as ProtoChatMessage, TextStreamInfo as ProtoTextStreamInfo, } from '@livekit/rtc-ffi-bindings'; import type { ByteStreamInfo, TextStreamInfo } from './data_streams/types.js'; +import type { ChatMessage } from './types.js'; /** convert bigints to numbers preserving undefined values */ export function bigIntToNumber( @@ -70,3 +72,14 @@ export function byteStreamInfoFromProto(info: ProtoByteStreamInfo): ByteStreamIn attributes: info.attributes, }; } + +/** @internal */ +export function chatMessageFromProto(info: ProtoChatMessage): ChatMessage { + return { + id: info.id!, + message: info.message!, + timestamp: bigIntToNumber(info.timestamp!), + editTimestamp: bigIntToNumber(info.editTimestamp), + generated: info.generated, + }; +}