1
1
/* eslint-disable camelcase */
2
- import type { Participant , Room } from 'livekit-client' ;
2
+ import type { Participant , Room , ChatMessage } from 'livekit-client' ;
3
3
import { 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' ;
6
11
7
12
/** @public */
8
- export interface ChatMessage {
9
- id : string ;
10
- timestamp : number ;
11
- message : string ;
12
- }
13
+ export type { ChatMessage } ;
13
14
14
15
/** @public */
15
16
export interface ReceivedChatMessage extends ChatMessage {
16
17
from ?: Participant ;
17
- editTimestamp ?: number ;
18
18
}
19
19
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 ;
24
38
/** @public */
25
39
export 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 */
28
45
channelTopic ?: string ;
46
+ /** @deprecated the new chat API doesn't rely on topics anymore and uses a dedicated chat API instead */
29
47
updateChannelTopic ?: string ;
30
48
} ;
31
49
@@ -40,9 +58,10 @@ const decoder = new TextDecoder();
40
58
41
59
const topicSubjectMap : Map < Room , Map < string , Subject < RawMessage > > > = new Map ( ) ;
42
60
43
- const encode = ( message : ChatMessage ) => encoder . encode ( JSON . stringify ( message ) ) ;
61
+ const encode = ( message : LegacyReceivedChatMessage ) => encoder . encode ( JSON . stringify ( message ) ) ;
44
62
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 ;
46
65
47
66
export function setupChat ( room : Room , options ?: ChatOptions ) {
48
67
const onDestroyObservable = new Subject < void > ( ) ;
@@ -67,17 +86,33 @@ export function setupChat(room: Room, options?: ChatOptions) {
67
86
const { messageObservable } = setupDataMessageHandler ( room , [ topic , updateTopic ] ) ;
68
87
messageObservable . pipe ( takeUntil ( onDestroyObservable ) ) . subscribe ( messageSubject ) ;
69
88
}
89
+ const { chatObservable, send : sendChatMessage } = setupChatMessageHandler ( room ) ;
70
90
71
91
const finalMessageDecoder = messageDecoder ?? decode ;
72
92
73
93
/** 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
+ }
81
116
// handle message updates
82
117
if (
83
118
'id' in value &&
@@ -89,7 +124,7 @@ export function setupChat(room: Room, options?: ChatOptions) {
89
124
acc [ replaceIndex ] = {
90
125
...value ,
91
126
timestamp : originalMsg . timestamp ,
92
- editTimestamp : value . timestamp ,
127
+ editTimestamp : value . editTimestamp ?? value . timestamp ,
93
128
} ;
94
129
}
95
130
@@ -105,43 +140,35 @@ export function setupChat(room: Room, options?: ChatOptions) {
105
140
const finalMessageEncoder = messageEncoder ?? encode ;
106
141
107
142
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 ) ;
112
143
isSending$ . next ( true ) ;
113
144
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 , {
115
148
reliable : true ,
116
149
topic,
117
150
} ) ;
118
- messageSubject . next ( {
119
- payload : encodedMsg ,
120
- topic : topic ,
121
- from : room . localParticipant ,
122
- } ) ;
123
151
return chatMessage ;
124
152
} finally {
125
153
isSending$ . next ( false ) ;
126
154
}
127
155
} ;
128
156
129
- const update = async ( message : string , messageId : string ) => {
157
+ const update = async ( message : string , originalMessageOrId : string | ChatMessage ) => {
130
158
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 ;
133
163
isSending$ . next ( true ) ;
134
164
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 , {
136
168
topic : updateTopic ,
137
169
reliable : true ,
138
170
} ) ;
139
- messageSubject . next ( {
140
- payload : encodedMsg ,
141
- topic : topic ,
142
- from : room . localParticipant ,
143
- } ) ;
144
- return chatMessage ;
171
+ return editedMessage ;
145
172
} finally {
146
173
isSending$ . next ( false ) ;
147
174
}
@@ -154,5 +181,16 @@ export function setupChat(room: Room, options?: ChatOptions) {
154
181
}
155
182
room . once ( RoomEvent . Disconnected , destroy ) ;
156
183
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 ;
158
196
}
0 commit comments