11/* eslint-disable camelcase */
2- import type { Participant , Room } from 'livekit-client' ;
2+ import type { Participant , Room , ChatMessage } from 'livekit-client' ;
33import { RoomEvent } from 'livekit-client' ;
4- import { BehaviorSubject , Subject , scan , map , takeUntil } from 'rxjs' ;
5- import { DataTopic , sendMessage , setupDataMessageHandler } from '../observables/dataChannel' ;
4+ import { BehaviorSubject , Subject , scan , map , takeUntil , merge } from 'rxjs' ;
5+ import {
6+ DataTopic ,
7+ sendMessage ,
8+ setupChatMessageHandler ,
9+ setupDataMessageHandler ,
10+ } from '../observables/dataChannel' ;
611
712/** @public */
8- export interface ChatMessage {
9- id : string ;
10- timestamp : number ;
11- message : string ;
12- }
13+ export type { ChatMessage } ;
1314
1415/** @public */
1516export interface ReceivedChatMessage extends ChatMessage {
1617 from ?: Participant ;
17- editTimestamp ?: number ;
1818}
1919
20- /** @public */
21- export type MessageEncoder = ( message : ChatMessage ) => Uint8Array ;
22- /** @public */
23- export type MessageDecoder = ( message : Uint8Array ) => ReceivedChatMessage ;
20+ export interface LegacyChatMessage extends ChatMessage {
21+ ignore ?: true ;
22+ }
23+
24+ export interface LegacyReceivedChatMessage extends ReceivedChatMessage {
25+ ignore ?: true ;
26+ }
27+
28+ /**
29+ * @public
30+ * @deprecated the new chat API doesn't rely on encoders and decoders anymore and uses a dedicated chat API instead
31+ */
32+ export type MessageEncoder = ( message : LegacyChatMessage ) => Uint8Array ;
33+ /**
34+ * @public
35+ * @deprecated the new chat API doesn't rely on encoders and decoders anymore and uses a dedicated chat API instead
36+ */
37+ export type MessageDecoder = ( message : Uint8Array ) => LegacyReceivedChatMessage ;
2438/** @public */
2539export type ChatOptions = {
26- messageEncoder ?: ( message : ChatMessage ) => Uint8Array ;
27- messageDecoder ?: ( message : Uint8Array ) => ReceivedChatMessage ;
40+ /** @deprecated the new chat API doesn't rely on encoders and decoders anymore and uses a dedicated chat API instead */
41+ messageEncoder ?: ( message : LegacyChatMessage ) => Uint8Array ;
42+ /** @deprecated the new chat API doesn't rely on encoders and decoders anymore and uses a dedicated chat API instead */
43+ messageDecoder ?: ( message : Uint8Array ) => LegacyReceivedChatMessage ;
44+ /** @deprecated the new chat API doesn't rely on topics anymore and uses a dedicated chat API instead */
2845 channelTopic ?: string ;
46+ /** @deprecated the new chat API doesn't rely on topics anymore and uses a dedicated chat API instead */
2947 updateChannelTopic ?: string ;
3048} ;
3149
@@ -40,9 +58,10 @@ const decoder = new TextDecoder();
4058
4159const topicSubjectMap : Map < Room , Map < string , Subject < RawMessage > > > = new Map ( ) ;
4260
43- const encode = ( message : ChatMessage ) => encoder . encode ( JSON . stringify ( message ) ) ;
61+ const encode = ( message : LegacyReceivedChatMessage ) => encoder . encode ( JSON . stringify ( message ) ) ;
4462
45- const decode = ( message : Uint8Array ) => JSON . parse ( decoder . decode ( message ) ) as ReceivedChatMessage ;
63+ const decode = ( message : Uint8Array ) =>
64+ JSON . parse ( decoder . decode ( message ) ) as LegacyReceivedChatMessage | ReceivedChatMessage ;
4665
4766export function setupChat ( room : Room , options ?: ChatOptions ) {
4867 const onDestroyObservable = new Subject < void > ( ) ;
@@ -67,17 +86,33 @@ export function setupChat(room: Room, options?: ChatOptions) {
6786 const { messageObservable } = setupDataMessageHandler ( room , [ topic , updateTopic ] ) ;
6887 messageObservable . pipe ( takeUntil ( onDestroyObservable ) ) . subscribe ( messageSubject ) ;
6988 }
89+ const { chatObservable, send : sendChatMessage } = setupChatMessageHandler ( room ) ;
7090
7191 const finalMessageDecoder = messageDecoder ?? decode ;
7292
7393 /** Build up the message array over time. */
74- const messagesObservable = messageSubject . pipe (
75- map ( ( msg ) => {
76- const parsedMessage = finalMessageDecoder ( msg . payload ) ;
77- const newMessage : ReceivedChatMessage = { ...parsedMessage , from : msg . from } ;
78- return newMessage ;
79- } ) ,
80- scan < ReceivedChatMessage , ReceivedChatMessage [ ] > ( ( acc , value ) => {
94+ const messagesObservable = merge (
95+ messageSubject . pipe (
96+ map ( ( msg ) => {
97+ const parsedMessage = finalMessageDecoder ( msg . payload ) ;
98+ const newMessage = { ...parsedMessage , from : msg . from } ;
99+ if ( isIgnorableChatMessage ( newMessage ) ) {
100+ return undefined ;
101+ }
102+ return newMessage ;
103+ } ) ,
104+ ) ,
105+ chatObservable . pipe (
106+ map ( ( [ msg , participant ] ) => {
107+ return { ...msg , from : participant } ;
108+ } ) ,
109+ ) ,
110+ ) . pipe (
111+ scan < ReceivedChatMessage | undefined , ReceivedChatMessage [ ] > ( ( acc , value ) => {
112+ // ignore legacy message updates
113+ if ( ! value ) {
114+ return acc ;
115+ }
81116 // handle message updates
82117 if (
83118 'id' in value &&
@@ -89,7 +124,7 @@ export function setupChat(room: Room, options?: ChatOptions) {
89124 acc [ replaceIndex ] = {
90125 ...value ,
91126 timestamp : originalMsg . timestamp ,
92- editTimestamp : value . timestamp ,
127+ editTimestamp : value . editTimestamp ?? value . timestamp ,
93128 } ;
94129 }
95130
@@ -105,43 +140,35 @@ export function setupChat(room: Room, options?: ChatOptions) {
105140 const finalMessageEncoder = messageEncoder ?? encode ;
106141
107142 const send = async ( message : string ) => {
108- const timestamp = Date . now ( ) ;
109- const id = crypto . randomUUID ( ) ;
110- const chatMessage : ChatMessage = { id, message, timestamp } ;
111- const encodedMsg = finalMessageEncoder ( chatMessage ) ;
112143 isSending$ . next ( true ) ;
113144 try {
114- await sendMessage ( room . localParticipant , encodedMsg , {
145+ const chatMessage = await sendChatMessage ( message ) ;
146+ const encodedLegacyMsg = finalMessageEncoder ( { ...chatMessage , ignore : true } ) ;
147+ await sendMessage ( room . localParticipant , encodedLegacyMsg , {
115148 reliable : true ,
116149 topic,
117150 } ) ;
118- messageSubject . next ( {
119- payload : encodedMsg ,
120- topic : topic ,
121- from : room . localParticipant ,
122- } ) ;
123151 return chatMessage ;
124152 } finally {
125153 isSending$ . next ( false ) ;
126154 }
127155 } ;
128156
129- const update = async ( message : string , messageId : string ) => {
157+ const update = async ( message : string , originalMessageOrId : string | ChatMessage ) => {
130158 const timestamp = Date . now ( ) ;
131- const chatMessage : ChatMessage = { id : messageId , message, timestamp } ;
132- const encodedMsg = finalMessageEncoder ( chatMessage ) ;
159+ const originalMessage : ChatMessage =
160+ typeof originalMessageOrId === 'string'
161+ ? { id : originalMessageOrId , message : '' , timestamp }
162+ : originalMessageOrId ;
133163 isSending$ . next ( true ) ;
134164 try {
135- await sendMessage ( room . localParticipant , encodedMsg , {
165+ const editedMessage = await room . localParticipant . editChatMessage ( message , originalMessage ) ;
166+ const encodedLegacyMessage = finalMessageEncoder ( editedMessage ) ;
167+ await sendMessage ( room . localParticipant , encodedLegacyMessage , {
136168 topic : updateTopic ,
137169 reliable : true ,
138170 } ) ;
139- messageSubject . next ( {
140- payload : encodedMsg ,
141- topic : topic ,
142- from : room . localParticipant ,
143- } ) ;
144- return chatMessage ;
171+ return editedMessage ;
145172 } finally {
146173 isSending$ . next ( false ) ;
147174 }
@@ -154,5 +181,16 @@ export function setupChat(room: Room, options?: ChatOptions) {
154181 }
155182 room . once ( RoomEvent . Disconnected , destroy ) ;
156183
157- return { messageObservable : messagesObservable , isSendingObservable : isSending$ , send, update } ;
184+ return {
185+ messageObservable : messagesObservable ,
186+ isSendingObservable : isSending$ ,
187+ send,
188+ update,
189+ } ;
190+ }
191+
192+ function isIgnorableChatMessage (
193+ msg : ReceivedChatMessage | LegacyReceivedChatMessage ,
194+ ) : msg is ReceivedChatMessage {
195+ return ( msg as LegacyChatMessage ) . ignore == true ;
158196}
0 commit comments