diff --git a/.changeset/funny-lamps-carry.md b/.changeset/funny-lamps-carry.md new file mode 100644 index 00000000..5f13d0e0 --- /dev/null +++ b/.changeset/funny-lamps-carry.md @@ -0,0 +1,5 @@ +--- +"@livekit/rtc-node": patch +--- + +Add chat API support diff --git a/packages/livekit-rtc/rust-sdks b/packages/livekit-rtc/rust-sdks index 5d1f91ee..0e4b9834 160000 --- a/packages/livekit-rtc/rust-sdks +++ b/packages/livekit-rtc/rust-sdks @@ -1 +1 @@ -Subproject commit 5d1f91eed163d9badb296ff605cb0a500235a741 +Subproject commit 0e4b9834598c32c52db0928f37e2d04db6c4cb45 diff --git a/packages/livekit-rtc/src/index.ts b/packages/livekit-rtc/src/index.ts index 402d9825..e8063d5b 100644 --- a/packages/livekit-rtc/src/index.ts +++ b/packages/livekit-rtc/src/index.ts @@ -43,3 +43,4 @@ export { StreamState, TrackKind, TrackSource } from './proto/track_pb.js'; export { VideoBufferType, VideoRotation, VideoCodec } from './proto/video_frame_pb.js'; export { ParticipantKind } from './proto/participant_pb.js'; export { dispose } from './ffi_client.js'; +export type { ChatMessage } from './types.js'; diff --git a/packages/livekit-rtc/src/napi/native.d.ts b/packages/livekit-rtc/src/napi/native.d.ts index 0355e240..f3ba192f 100644 --- a/packages/livekit-rtc/src/napi/native.d.ts +++ b/packages/livekit-rtc/src/napi/native.d.ts @@ -3,11 +3,14 @@ /* auto-generated by NAPI-RS */ -export function livekitInitialize(callback: (data: Uint8Array) => void, captureLogs: boolean): void; -export function livekitFfiRequest(data: Uint8Array): Uint8Array; -export function livekitRetrievePtr(handle: Uint8Array): bigint; -export function livekitCopyBuffer(ptr: bigint, len: number): Uint8Array; -export function livekitDispose(): Promise; +export declare function livekitInitialize( + callback: (data: Uint8Array) => void, + captureLogs: boolean, +): void; +export declare function livekitFfiRequest(data: Uint8Array): Uint8Array; +export declare function livekitRetrievePtr(handle: Uint8Array): bigint; +export declare function livekitCopyBuffer(ptr: bigint, len: number): Uint8Array; +export declare function livekitDispose(): Promise; export declare class FfiHandle { constructor(handle: bigint); dispose(): void; diff --git a/packages/livekit-rtc/src/participant.ts b/packages/livekit-rtc/src/participant.ts index 63e24266..2dda98b6 100644 --- a/packages/livekit-rtc/src/participant.ts +++ b/packages/livekit-rtc/src/participant.ts @@ -3,7 +3,8 @@ // SPDX-License-Identifier: Apache-2.0 import { FfiClient, FfiHandle } from './ffi_client.js'; import type { OwnedParticipant, ParticipantInfo, ParticipantKind } from './proto/participant_pb.js'; -import type { +import { + ChatMessage as ChatMessageModel, PublishDataCallback, PublishDataResponse, PublishSipDtmfCallback, @@ -12,6 +13,8 @@ import type { PublishTrackResponse, PublishTranscriptionCallback, PublishTranscriptionResponse, + SendChatMessageCallback, + SendChatMessageResponse, SetLocalAttributesCallback, SetLocalAttributesResponse, SetLocalMetadataCallback, @@ -23,11 +26,13 @@ import type { UnpublishTrackResponse, } from './proto/room_pb.js'; import { + EditChatMessageRequest, TranscriptionSegment as ProtoTranscriptionSegment, PublishDataRequest, PublishSipDtmfRequest, PublishTrackRequest, PublishTranscriptionRequest, + SendChatMessageRequest, SetLocalAttributesRequest, SetLocalMetadataRequest, SetLocalNameRequest, @@ -37,6 +42,7 @@ import type { LocalTrack } from './track.js'; import type { RemoteTrackPublication, TrackPublication } from './track_publication.js'; import { LocalTrackPublication } from './track_publication.js'; import type { Transcription } from './transcription.js'; +import { ChatMessage } from './types.js'; export abstract class Participant { /** @internal */ @@ -185,6 +191,77 @@ export class LocalParticipant extends Participant { }); } + /** + * + */ + async sendChatMessage( + text: string, + destinationIdentities?: Array, + senderIdentity?: string, + ): Promise { + const req = new SendChatMessageRequest({ + localParticipantHandle: this.ffi_handle.handle, + message: text, + destinationIdentities, + senderIdentity, + }); + + const res = FfiClient.instance.request({ + message: { case: 'sendChatMessage', value: req }, + }); + + const cb = await FfiClient.instance.waitFor((ev) => { + return ev.message.case == 'chatMessage' && ev.message.value.asyncId == res.asyncId; + }); + + if (cb.error) { + throw new Error(cb.error); + } + const { id, timestamp, editTimestamp, message } = cb.chatMessage; + return { id, timestamp: Number(timestamp), editTimestamp: Number(editTimestamp), message }; + } + + /** + * Sends a chat message to participants in the room + * + * @param text - The text content of the chat message. + * @param destinationIdentities - An optional array of recipient identities to whom the message will be sent. If omitted, the message is broadcast to all participants. + * @param senderIdentity - An optional identity of the sender. If omitted, the default sender identity is used. + * + */ + async editChatMessage( + editText: string, + originalMessage: ChatMessage, + destinationIdentities?: Array, + senderIdentity?: string, + ): Promise { + const req = new EditChatMessageRequest({ + localParticipantHandle: this.ffi_handle.handle, + editText, + originalMessage: new ChatMessageModel({ + ...originalMessage, + timestamp: BigInt(originalMessage.timestamp), + editTimestamp: BigInt(originalMessage.editTimestamp), + }), + destinationIdentities, + senderIdentity, + }); + + const res = FfiClient.instance.request({ + message: { case: 'editChatMessage', value: req }, + }); + + const cb = await FfiClient.instance.waitFor((ev) => { + return ev.message.case == 'chatMessage' && ev.message.value.asyncId == res.asyncId; + }); + + if (cb.error) { + throw new Error(cb.error); + } + const { id, timestamp, editTimestamp, message } = cb.chatMessage; + return { id, timestamp: Number(timestamp), editTimestamp: Number(editTimestamp), message }; + } + async updateName(name: string) { const req = new SetLocalNameRequest({ localParticipantHandle: this.ffi_handle.handle, diff --git a/packages/livekit-rtc/src/proto/ffi_pb.ts b/packages/livekit-rtc/src/proto/ffi_pb.ts index c65cb535..56924f6e 100644 --- a/packages/livekit-rtc/src/proto/ffi_pb.ts +++ b/packages/livekit-rtc/src/proto/ffi_pb.ts @@ -19,7 +19,7 @@ import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; import { Message, proto3, protoInt64 } from "@bufbuild/protobuf"; -import { ConnectCallback, ConnectRequest, ConnectResponse, DisconnectCallback, DisconnectRequest, DisconnectResponse, GetSessionStatsCallback, GetSessionStatsRequest, GetSessionStatsResponse, PublishDataCallback, PublishDataRequest, PublishDataResponse, PublishSipDtmfCallback, PublishSipDtmfRequest, PublishSipDtmfResponse, PublishTrackCallback, PublishTrackRequest, PublishTrackResponse, PublishTranscriptionCallback, PublishTranscriptionRequest, PublishTranscriptionResponse, RoomEvent, SetLocalAttributesCallback, SetLocalAttributesRequest, SetLocalAttributesResponse, SetLocalMetadataCallback, SetLocalMetadataRequest, SetLocalMetadataResponse, SetLocalNameCallback, SetLocalNameRequest, SetLocalNameResponse, SetSubscribedRequest, SetSubscribedResponse, UnpublishTrackCallback, UnpublishTrackRequest, UnpublishTrackResponse } from "./room_pb.js"; +import { ConnectCallback, ConnectRequest, ConnectResponse, DisconnectCallback, DisconnectRequest, DisconnectResponse, EditChatMessageRequest, GetSessionStatsCallback, GetSessionStatsRequest, GetSessionStatsResponse, PublishDataCallback, PublishDataRequest, PublishDataResponse, PublishSipDtmfCallback, PublishSipDtmfRequest, PublishSipDtmfResponse, PublishTrackCallback, PublishTrackRequest, PublishTrackResponse, PublishTranscriptionCallback, PublishTranscriptionRequest, PublishTranscriptionResponse, RoomEvent, SendChatMessageCallback, SendChatMessageRequest, SendChatMessageResponse, SetLocalAttributesCallback, SetLocalAttributesRequest, SetLocalAttributesResponse, SetLocalMetadataCallback, SetLocalMetadataRequest, SetLocalMetadataResponse, SetLocalNameCallback, SetLocalNameRequest, SetLocalNameResponse, SetSubscribedRequest, SetSubscribedResponse, UnpublishTrackCallback, UnpublishTrackRequest, UnpublishTrackResponse } from "./room_pb.js"; import { CreateAudioTrackRequest, CreateAudioTrackResponse, CreateVideoTrackRequest, CreateVideoTrackResponse, EnableRemoteTrackRequest, EnableRemoteTrackResponse, GetStatsCallback, GetStatsRequest, GetStatsResponse, LocalTrackMuteRequest, LocalTrackMuteResponse, TrackEvent } from "./track_pb.js"; import { CaptureVideoFrameRequest, CaptureVideoFrameResponse, NewVideoSourceRequest, NewVideoSourceResponse, NewVideoStreamRequest, NewVideoStreamResponse, VideoConvertRequest, VideoConvertResponse, VideoStreamEvent, VideoStreamFromParticipantRequest, VideoStreamFromParticipantResponse } from "./video_frame_pb.js"; import { AudioStreamEvent, AudioStreamFromParticipantRequest, AudioStreamFromParticipantResponse, CaptureAudioFrameCallback, CaptureAudioFrameRequest, CaptureAudioFrameResponse, ClearAudioBufferRequest, ClearAudioBufferResponse, FlushSoxResamplerRequest, FlushSoxResamplerResponse, NewAudioResamplerRequest, NewAudioResamplerResponse, NewAudioSourceRequest, NewAudioSourceResponse, NewAudioStreamRequest, NewAudioStreamResponse, NewSoxResamplerRequest, NewSoxResamplerResponse, PushSoxResamplerRequest, PushSoxResamplerResponse, RemixAndResampleRequest, RemixAndResampleResponse } from "./audio_frame_pb.js"; @@ -285,6 +285,18 @@ export class FfiRequest extends Message { */ value: FlushSoxResamplerRequest; case: "flushSoxResampler"; + } | { + /** + * @generated from field: livekit.proto.SendChatMessageRequest send_chat_message = 36; + */ + value: SendChatMessageRequest; + case: "sendChatMessage"; + } | { + /** + * @generated from field: livekit.proto.EditChatMessageRequest edit_chat_message = 37; + */ + value: EditChatMessageRequest; + case: "editChatMessage"; } | { case: undefined; value?: undefined } = { case: undefined }; constructor(data?: PartialMessage) { @@ -329,6 +341,8 @@ export class FfiRequest extends Message { { no: 33, name: "new_sox_resampler", kind: "message", T: NewSoxResamplerRequest, oneof: "message" }, { no: 34, name: "push_sox_resampler", kind: "message", T: PushSoxResamplerRequest, oneof: "message" }, { no: 35, name: "flush_sox_resampler", kind: "message", T: FlushSoxResamplerRequest, oneof: "message" }, + { no: 36, name: "send_chat_message", kind: "message", T: SendChatMessageRequest, oneof: "message" }, + { no: 37, name: "edit_chat_message", kind: "message", T: EditChatMessageRequest, oneof: "message" }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): FfiRequest { @@ -569,6 +583,12 @@ export class FfiResponse extends Message { */ value: FlushSoxResamplerResponse; case: "flushSoxResampler"; + } | { + /** + * @generated from field: livekit.proto.SendChatMessageResponse send_chat_message = 36; + */ + value: SendChatMessageResponse; + case: "sendChatMessage"; } | { case: undefined; value?: undefined } = { case: undefined }; constructor(data?: PartialMessage) { @@ -613,6 +633,7 @@ export class FfiResponse extends Message { { no: 33, name: "new_sox_resampler", kind: "message", T: NewSoxResamplerResponse, oneof: "message" }, { no: 34, name: "push_sox_resampler", kind: "message", T: PushSoxResamplerResponse, oneof: "message" }, { no: 35, name: "flush_sox_resampler", kind: "message", T: FlushSoxResamplerResponse, oneof: "message" }, + { no: 36, name: "send_chat_message", kind: "message", T: SendChatMessageResponse, oneof: "message" }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): FfiResponse { @@ -763,6 +784,12 @@ export class FfiEvent extends Message { */ value: PublishSipDtmfCallback; case: "publishSipDtmf"; + } | { + /** + * @generated from field: livekit.proto.SendChatMessageCallback chat_message = 22; + */ + value: SendChatMessageCallback; + case: "chatMessage"; } | { case: undefined; value?: undefined } = { case: undefined }; constructor(data?: PartialMessage) { @@ -793,6 +820,7 @@ export class FfiEvent extends Message { { no: 19, name: "get_session_stats", kind: "message", T: GetSessionStatsCallback, oneof: "message" }, { no: 20, name: "panic", kind: "message", T: Panic, oneof: "message" }, { no: 21, name: "publish_sip_dtmf", kind: "message", T: PublishSipDtmfCallback, oneof: "message" }, + { no: 22, name: "chat_message", kind: "message", T: SendChatMessageCallback, oneof: "message" }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): FfiEvent { diff --git a/packages/livekit-rtc/src/proto/room_pb.ts b/packages/livekit-rtc/src/proto/room_pb.ts index fefc54b7..219f431c 100644 --- a/packages/livekit-rtc/src/proto/room_pb.ts +++ b/packages/livekit-rtc/src/proto/room_pb.ts @@ -1375,6 +1375,208 @@ export class SetLocalMetadataCallback extends Message } } +/** + * @generated from message livekit.proto.SendChatMessageRequest + */ +export class SendChatMessageRequest extends Message { + /** + * @generated from field: uint64 local_participant_handle = 1; + */ + localParticipantHandle = protoInt64.zero; + + /** + * @generated from field: string message = 2; + */ + message = ""; + + /** + * @generated from field: repeated string destination_identities = 3; + */ + destinationIdentities: string[] = []; + + /** + * @generated from field: optional string sender_identity = 4; + */ + senderIdentity?: string; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "livekit.proto.SendChatMessageRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "local_participant_handle", kind: "scalar", T: 4 /* ScalarType.UINT64 */ }, + { no: 2, name: "message", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "destination_identities", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, + { no: 4, name: "sender_identity", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): SendChatMessageRequest { + return new SendChatMessageRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): SendChatMessageRequest { + return new SendChatMessageRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): SendChatMessageRequest { + return new SendChatMessageRequest().fromJsonString(jsonString, options); + } + + static equals(a: SendChatMessageRequest | PlainMessage | undefined, b: SendChatMessageRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(SendChatMessageRequest, a, b); + } +} + +/** + * @generated from message livekit.proto.EditChatMessageRequest + */ +export class EditChatMessageRequest extends Message { + /** + * @generated from field: uint64 local_participant_handle = 1; + */ + localParticipantHandle = protoInt64.zero; + + /** + * @generated from field: string edit_text = 2; + */ + editText = ""; + + /** + * @generated from field: livekit.proto.ChatMessage original_message = 3; + */ + originalMessage?: ChatMessage; + + /** + * @generated from field: repeated string destination_identities = 4; + */ + destinationIdentities: string[] = []; + + /** + * @generated from field: optional string sender_identity = 5; + */ + senderIdentity?: string; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "livekit.proto.EditChatMessageRequest"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "local_participant_handle", kind: "scalar", T: 4 /* ScalarType.UINT64 */ }, + { no: 2, name: "edit_text", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 3, name: "original_message", kind: "message", T: ChatMessage }, + { no: 4, name: "destination_identities", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true }, + { no: 5, name: "sender_identity", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): EditChatMessageRequest { + return new EditChatMessageRequest().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): EditChatMessageRequest { + return new EditChatMessageRequest().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): EditChatMessageRequest { + return new EditChatMessageRequest().fromJsonString(jsonString, options); + } + + static equals(a: EditChatMessageRequest | PlainMessage | undefined, b: EditChatMessageRequest | PlainMessage | undefined): boolean { + return proto3.util.equals(EditChatMessageRequest, a, b); + } +} + +/** + * @generated from message livekit.proto.SendChatMessageResponse + */ +export class SendChatMessageResponse extends Message { + /** + * @generated from field: uint64 async_id = 1; + */ + asyncId = protoInt64.zero; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "livekit.proto.SendChatMessageResponse"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "async_id", kind: "scalar", T: 4 /* ScalarType.UINT64 */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): SendChatMessageResponse { + return new SendChatMessageResponse().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): SendChatMessageResponse { + return new SendChatMessageResponse().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): SendChatMessageResponse { + return new SendChatMessageResponse().fromJsonString(jsonString, options); + } + + static equals(a: SendChatMessageResponse | PlainMessage | undefined, b: SendChatMessageResponse | PlainMessage | undefined): boolean { + return proto3.util.equals(SendChatMessageResponse, a, b); + } +} + +/** + * @generated from message livekit.proto.SendChatMessageCallback + */ +export class SendChatMessageCallback extends Message { + /** + * @generated from field: uint64 async_id = 1; + */ + asyncId = protoInt64.zero; + + /** + * @generated from field: optional string error = 2; + */ + error?: string; + + /** + * @generated from field: optional livekit.proto.ChatMessage chat_message = 3; + */ + chatMessage?: ChatMessage; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "livekit.proto.SendChatMessageCallback"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "async_id", kind: "scalar", T: 4 /* ScalarType.UINT64 */ }, + { no: 2, name: "error", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true }, + { no: 3, name: "chat_message", kind: "message", T: ChatMessage, opt: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): SendChatMessageCallback { + return new SendChatMessageCallback().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): SendChatMessageCallback { + return new SendChatMessageCallback().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): SendChatMessageCallback { + return new SendChatMessageCallback().fromJsonString(jsonString, options); + } + + static equals(a: SendChatMessageCallback | PlainMessage | undefined, b: SendChatMessageCallback | PlainMessage | undefined): boolean { + return proto3.util.equals(SendChatMessageCallback, a, b); + } +} + /** * Change the local participant's attributes * @@ -2491,6 +2693,12 @@ export class RoomEvent extends Message { */ value: TranscriptionReceived; case: "transcriptionReceived"; + } | { + /** + * @generated from field: livekit.proto.ChatMessageReceived chat_message = 29; + */ + value: ChatMessageReceived; + case: "chatMessage"; } | { case: undefined; value?: undefined } = { case: undefined }; constructor(data?: PartialMessage) { @@ -2529,6 +2737,7 @@ export class RoomEvent extends Message { { no: 26, name: "eos", kind: "message", T: RoomEOS, oneof: "message" }, { no: 27, name: "data_packet_received", kind: "message", T: DataPacketReceived, oneof: "message" }, { no: 28, name: "transcription_received", kind: "message", T: TranscriptionReceived, oneof: "message" }, + { no: 29, name: "chat_message", kind: "message", T: ChatMessageReceived, oneof: "message" }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): RoomEvent { @@ -3517,6 +3726,116 @@ export class UserPacket extends Message { } } +/** + * @generated from message livekit.proto.ChatMessage + */ +export class ChatMessage extends Message { + /** + * @generated from field: string id = 1; + */ + id = ""; + + /** + * @generated from field: int64 timestamp = 2; + */ + timestamp = protoInt64.zero; + + /** + * @generated from field: string message = 3; + */ + message = ""; + + /** + * @generated from field: optional int64 edit_timestamp = 4; + */ + editTimestamp?: bigint; + + /** + * @generated from field: optional bool deleted = 5; + */ + deleted?: boolean; + + /** + * @generated from field: optional bool generated = 6; + */ + generated?: boolean; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "livekit.proto.ChatMessage"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "id", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 2, name: "timestamp", kind: "scalar", T: 3 /* ScalarType.INT64 */ }, + { no: 3, name: "message", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 4, name: "edit_timestamp", kind: "scalar", T: 3 /* ScalarType.INT64 */, opt: true }, + { no: 5, name: "deleted", kind: "scalar", T: 8 /* ScalarType.BOOL */, opt: true }, + { no: 6, name: "generated", kind: "scalar", T: 8 /* ScalarType.BOOL */, opt: true }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): ChatMessage { + return new ChatMessage().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): ChatMessage { + return new ChatMessage().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): ChatMessage { + return new ChatMessage().fromJsonString(jsonString, options); + } + + static equals(a: ChatMessage | PlainMessage | undefined, b: ChatMessage | PlainMessage | undefined): boolean { + return proto3.util.equals(ChatMessage, a, b); + } +} + +/** + * @generated from message livekit.proto.ChatMessageReceived + */ +export class ChatMessageReceived extends Message { + /** + * @generated from field: livekit.proto.ChatMessage message = 1; + */ + message?: ChatMessage; + + /** + * @generated from field: string participant_identity = 2; + */ + participantIdentity = ""; + + constructor(data?: PartialMessage) { + super(); + proto3.util.initPartial(data, this); + } + + static readonly runtime: typeof proto3 = proto3; + static readonly typeName = "livekit.proto.ChatMessageReceived"; + static readonly fields: FieldList = proto3.util.newFieldList(() => [ + { no: 1, name: "message", kind: "message", T: ChatMessage }, + { no: 2, name: "participant_identity", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + ]); + + static fromBinary(bytes: Uint8Array, options?: Partial): ChatMessageReceived { + return new ChatMessageReceived().fromBinary(bytes, options); + } + + static fromJson(jsonValue: JsonValue, options?: Partial): ChatMessageReceived { + return new ChatMessageReceived().fromJson(jsonValue, options); + } + + static fromJsonString(jsonString: string, options?: Partial): ChatMessageReceived { + return new ChatMessageReceived().fromJsonString(jsonString, options); + } + + static equals(a: ChatMessageReceived | PlainMessage | undefined, b: ChatMessageReceived | PlainMessage | undefined): boolean { + return proto3.util.equals(ChatMessageReceived, a, b); + } +} + /** * @generated from message livekit.proto.SipDTMF */ diff --git a/packages/livekit-rtc/src/room.ts b/packages/livekit-rtc/src/room.ts index fa2ae9f2..369ddaab 100644 --- a/packages/livekit-rtc/src/room.ts +++ b/packages/livekit-rtc/src/room.ts @@ -32,6 +32,7 @@ import type { LocalTrack, RemoteTrack } from './track.js'; import { RemoteAudioTrack, RemoteVideoTrack } from './track.js'; import type { LocalTrackPublication, TrackPublication } from './track_publication.js'; import { RemoteTrackPublication } from './track_publication.js'; +import type { ChatMessage } from './types.js'; export interface RtcConfiguration { iceTransportType: IceTransportType; @@ -262,6 +263,17 @@ export class Room extends (EventEmitter as new () => TypedEmitter } else if (ev.case == 'connectionQualityChanged') { const participant = this.retrieveParticipantByIdentity(ev.value.participantIdentity); this.emit(RoomEvent.ConnectionQualityChanged, ev.value.quality, participant); + } 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, + message: messageText, + timestamp: Number(timestamp), + editTimestamp: Number(editTimestamp), + generated, + }; + this.emit(RoomEvent.ChatMessage, message, participant); } else if (ev.case == 'dataPacketReceived') { // Can be undefined if the data is sent from a Server SDK const participant = this.remoteParticipants.get(ev.value.participantIdentity); @@ -285,7 +297,6 @@ export class Room extends (EventEmitter as new () => TypedEmitter const { code, digit } = dataPacket.value; this.emit(RoomEvent.DtmfReceived, code, digit, participant); break; - default: break; } @@ -376,6 +387,7 @@ export type RoomCallbacks = { kind?: DataPacketKind, topic?: string, ) => void; + chatMessage: (message: ChatMessage, participant?: Participant) => void; dtmfReceived: (code: number, digit: string, participant: RemoteParticipant) => void; encryptionError: (error: Error) => void; connectionStateChanged: (state: ConnectionState) => void; @@ -405,6 +417,7 @@ export enum RoomEvent { ParticipantAttributesChanged = 'participantAttributesChanged', ConnectionQualityChanged = 'connectionQualityChanged', DataReceived = 'dataReceived', + ChatMessage = 'chatMessage', DtmfReceived = 'dtmfReceived', EncryptionError = 'encryptionError', ConnectionStateChanged = 'connectionStateChanged', diff --git a/packages/livekit-rtc/src/types.ts b/packages/livekit-rtc/src/types.ts new file mode 100644 index 00000000..0f98f0b4 --- /dev/null +++ b/packages/livekit-rtc/src/types.ts @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2024 LiveKit, Inc. +// +// SPDX-License-Identifier: Apache-2.0 +export interface ChatMessage { + id: string; + timestamp: number; + message: string; + editTimestamp?: number; + generated?: boolean; +}