@signalwire/js 3.7.1-dev.202201131750.6d234cc.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/dist/core/src/BaseComponent.d.ts +29 -31
  2. package/dist/core/src/BaseComponent.d.ts.map +1 -1
  3. package/dist/core/src/BaseJWTSession.d.ts +1 -0
  4. package/dist/core/src/BaseJWTSession.d.ts.map +1 -1
  5. package/dist/core/src/BaseSession.d.ts +1 -1
  6. package/dist/core/src/BaseSession.d.ts.map +1 -1
  7. package/dist/core/src/chat/BaseChat.d.ts +4 -3
  8. package/dist/core/src/chat/BaseChat.d.ts.map +1 -1
  9. package/dist/core/src/chat/ChatMember.d.ts +6 -0
  10. package/dist/core/src/chat/ChatMember.d.ts.map +1 -1
  11. package/dist/core/src/chat/ChatMessage.d.ts +9 -0
  12. package/dist/core/src/chat/ChatMessage.d.ts.map +1 -1
  13. package/dist/core/src/chat/index.d.ts +1 -0
  14. package/dist/core/src/chat/index.d.ts.map +1 -1
  15. package/dist/core/src/chat/methods.d.ts +18 -15
  16. package/dist/core/src/chat/methods.d.ts.map +1 -1
  17. package/dist/core/src/chat/workers.d.ts +2 -1
  18. package/dist/core/src/chat/workers.d.ts.map +1 -1
  19. package/dist/core/src/index.d.ts +3 -3
  20. package/dist/core/src/index.d.ts.map +1 -1
  21. package/dist/core/src/internal/BaseBackendSession.d.ts.map +1 -1
  22. package/dist/core/src/redux/actions.d.ts +17 -14
  23. package/dist/core/src/redux/actions.d.ts.map +1 -1
  24. package/dist/core/src/redux/features/component/componentSlice.d.ts +3952 -10
  25. package/dist/core/src/redux/features/component/componentSlice.d.ts.map +1 -1
  26. package/dist/core/src/redux/features/executeQueue/executeQueueSlice.d.ts +44 -7
  27. package/dist/core/src/redux/features/executeQueue/executeQueueSlice.d.ts.map +1 -1
  28. package/dist/core/src/redux/features/pubSub/pubSubSaga.d.ts.map +1 -1
  29. package/dist/core/src/redux/features/session/sessionSlice.d.ts +74 -8
  30. package/dist/core/src/redux/features/session/sessionSlice.d.ts.map +1 -1
  31. package/dist/core/src/redux/index.d.ts +9 -25
  32. package/dist/core/src/redux/index.d.ts.map +1 -1
  33. package/dist/core/src/redux/interfaces.d.ts +5 -4
  34. package/dist/core/src/redux/interfaces.d.ts.map +1 -1
  35. package/dist/core/src/redux/rootReducer.d.ts +417 -3
  36. package/dist/core/src/redux/rootReducer.d.ts.map +1 -1
  37. package/dist/core/src/redux/rootSaga.d.ts +23 -5
  38. package/dist/core/src/redux/rootSaga.d.ts.map +1 -1
  39. package/dist/core/src/redux/toolkit/configureStore.d.ts +77 -0
  40. package/dist/core/src/redux/toolkit/configureStore.d.ts.map +1 -0
  41. package/dist/core/src/redux/toolkit/createAction.d.ts +180 -0
  42. package/dist/core/src/redux/toolkit/createAction.d.ts.map +1 -0
  43. package/dist/core/src/redux/toolkit/createReducer.d.ts +42 -0
  44. package/dist/core/src/redux/toolkit/createReducer.d.ts.map +1 -0
  45. package/dist/core/src/redux/toolkit/createSlice.d.ts +142 -0
  46. package/dist/core/src/redux/toolkit/createSlice.d.ts.map +1 -0
  47. package/dist/core/src/redux/toolkit/devtoolsExtension.d.ts +185 -0
  48. package/dist/core/src/redux/toolkit/devtoolsExtension.d.ts.map +1 -0
  49. package/dist/core/src/redux/toolkit/getDefaultMiddleware.d.ts +12 -0
  50. package/dist/core/src/redux/toolkit/getDefaultMiddleware.d.ts.map +1 -0
  51. package/dist/core/src/redux/toolkit/index.d.ts +13 -0
  52. package/dist/core/src/redux/toolkit/index.d.ts.map +1 -0
  53. package/dist/core/src/redux/toolkit/isPlainObject.d.ts +12 -0
  54. package/dist/core/src/redux/toolkit/isPlainObject.d.ts.map +1 -0
  55. package/dist/core/src/redux/toolkit/mapBuilders.d.ts +38 -0
  56. package/dist/core/src/redux/toolkit/mapBuilders.d.ts.map +1 -0
  57. package/dist/core/src/redux/toolkit/tsHelpers.d.ts +51 -0
  58. package/dist/core/src/redux/toolkit/tsHelpers.d.ts.map +1 -0
  59. package/dist/core/src/redux/toolkit/utils.d.ts +11 -0
  60. package/dist/core/src/redux/toolkit/utils.d.ts.map +1 -0
  61. package/dist/core/src/redux/utils/createDestroyableSlice.d.ts +3 -2
  62. package/dist/core/src/redux/utils/createDestroyableSlice.d.ts.map +1 -1
  63. package/dist/core/src/rooms/methods.d.ts +31 -21
  64. package/dist/core/src/rooms/methods.d.ts.map +1 -1
  65. package/dist/core/src/testUtils.d.ts +8 -25
  66. package/dist/core/src/testUtils.d.ts.map +1 -1
  67. package/dist/core/src/types/chat.d.ts +12 -12
  68. package/dist/core/src/types/chat.d.ts.map +1 -1
  69. package/dist/core/src/types/utils.d.ts +7 -0
  70. package/dist/core/src/types/utils.d.ts.map +1 -1
  71. package/dist/core/src/types/videoMember.d.ts +10 -1
  72. package/dist/core/src/types/videoMember.d.ts.map +1 -1
  73. package/dist/core/src/utils/constants.d.ts +1 -0
  74. package/dist/core/src/utils/constants.d.ts.map +1 -1
  75. package/dist/core/src/utils/index.d.ts +4 -1
  76. package/dist/core/src/utils/index.d.ts.map +1 -1
  77. package/dist/core/src/utils/interfaces.d.ts +19 -6
  78. package/dist/core/src/utils/interfaces.d.ts.map +1 -1
  79. package/dist/index.esm.js +179 -49
  80. package/dist/index.esm.js.map +3 -3
  81. package/dist/index.js +223 -100
  82. package/dist/index.js.map +3 -3
  83. package/dist/index.umd.js +2 -2
  84. package/dist/index.umd.js.map +1 -1
  85. package/dist/js/src/BaseRoomSession.d.ts +7 -0
  86. package/dist/js/src/BaseRoomSession.d.ts.map +1 -1
  87. package/dist/js/src/JWTSession.d.ts.map +1 -1
  88. package/dist/js/src/RoomSession.d.ts.map +1 -1
  89. package/dist/js/src/RoomSession.docs.d.ts +9 -1
  90. package/dist/js/src/RoomSession.docs.d.ts.map +1 -1
  91. package/dist/js/src/chat/Client.d.ts +37 -3
  92. package/dist/js/src/chat/Client.d.ts.map +1 -1
  93. package/dist/js/src/chat/Client.docs.d.ts +238 -0
  94. package/dist/js/src/chat/Client.docs.d.ts.map +1 -0
  95. package/dist/js/src/chat/index.d.ts +5 -0
  96. package/dist/js/src/chat/index.d.ts.map +1 -1
  97. package/dist/js/src/features/actions.d.ts +1 -1
  98. package/dist/js/src/features/actions.d.ts.map +1 -1
  99. package/dist/js/src/index.d.ts +5 -2
  100. package/dist/js/src/index.d.ts.map +1 -1
  101. package/dist/js/src/testUtils.d.ts +16 -50
  102. package/dist/js/src/testUtils.d.ts.map +1 -1
  103. package/dist/js/src/utils/interfaces.d.ts +16 -2
  104. package/dist/js/src/utils/interfaces.d.ts.map +1 -1
  105. package/dist/js/src/video/memberListUpdatedWorker.d.ts +14 -0
  106. package/dist/js/src/video/memberListUpdatedWorker.d.ts.map +1 -0
  107. package/dist/js/src/video/workers.d.ts +2 -0
  108. package/dist/js/src/video/workers.d.ts.map +1 -0
  109. package/dist/js/tsconfig.build.tsbuildinfo +1 -1
  110. package/dist/webrtc/src/BaseConnection.d.ts +7 -0
  111. package/dist/webrtc/src/BaseConnection.d.ts.map +1 -1
  112. package/dist/webrtc/src/RTCPeer.d.ts.map +1 -1
  113. package/package.json +3 -3
  114. package/src/BaseRoomSession.test.ts +2 -2
  115. package/src/BaseRoomSession.ts +15 -0
  116. package/src/RoomSession.docs.ts +10 -1
  117. package/src/RoomSession.ts +16 -4
  118. package/src/chat/Client.docs.ts +259 -0
  119. package/src/chat/Client.test.ts +4 -4
  120. package/src/chat/Client.ts +39 -3
  121. package/src/chat/index.ts +10 -0
  122. package/src/index.ts +6 -3
  123. package/src/utils/interfaces.ts +22 -2
  124. package/src/video/memberListUpdatedWorker.ts +217 -0
  125. package/src/video/workers.ts +1 -0
@@ -0,0 +1,259 @@
1
+ import type { ConsumerContract, Chat } from '@signalwire/core'
2
+
3
+ import type { ClientApiEvents, ClientFullState } from './Client'
4
+
5
+ export type PagingCursor =
6
+ | {
7
+ before: string
8
+ after?: never
9
+ }
10
+ | {
11
+ before?: never
12
+ after: string
13
+ }
14
+
15
+ export interface ClientDocs
16
+ extends Omit<
17
+ ConsumerContract<ClientApiEvents, ClientFullState>,
18
+ 'subscribe'
19
+ > {
20
+ /**
21
+ * Creates a new Chat client.
22
+ *
23
+ * @example
24
+ *
25
+ * ```js
26
+ * import { Chat } from '@signalwire/js'
27
+ *
28
+ * const chatClient = new Chat.Client({
29
+ * token: '<your_chat_token>',
30
+ * })
31
+ * ```
32
+ */
33
+ new (chatOptions: {
34
+ /** SignalWire Chat token (you can get one with the REST APIs) */
35
+ token: string
36
+ /** @ignore */
37
+ logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent'
38
+ }): this
39
+
40
+ /**
41
+ * Replaces the token used by the client with a new one. You can use this
42
+ * method to replace the token when for example it is expiring, in order to
43
+ * keep the session alive.
44
+ *
45
+ * The new token can contain different channels from the previous one. In that
46
+ * case, you will need to subscribe to the new channels if you want to receive
47
+ * messages for those. Channels that were in the previous token but are not in
48
+ * the new one will get unsubscribed automatically.
49
+ *
50
+ * @param token the new token.
51
+ *
52
+ * @example
53
+ * ```js
54
+ * const chatClient = new Chat.Client({
55
+ * token: '<your chat token>'
56
+ * })
57
+ *
58
+ * chatClient.on('session.expiring', async () => {
59
+ * const newToken = await fetchNewToken(..)
60
+ *
61
+ * await chatClient.updateToken(newToken)
62
+ * })
63
+ * ```
64
+ */
65
+ updateToken(token: string): Promise<void>
66
+
67
+ /**
68
+ * List of channels for which you want to receive messages. You can only
69
+ * subscribe to those channels for which your token has read permission.
70
+ *
71
+ * Note that the `subscribe` function is idempotent, and calling it again with
72
+ * a different set of channels _will not_ unsubscribe you from the old ones.
73
+ * To unsubscribe, use {@link unsubscribe}.
74
+ *
75
+ * @param channels the channels to subscribe to, either in the form of a
76
+ * string (for one channel) or an array of strings.
77
+ *
78
+ * @example
79
+ * ```js
80
+ * const chatClient = new Chat.Client({
81
+ * token: '<your chat token>'
82
+ * })
83
+ *
84
+ * chatClient.on('message', m => console.log(m))
85
+ *
86
+ * await chatClient.subscribe("my-channel")
87
+ * await chatClient.subscribe(["chan-2", "chan-3"])
88
+ * ```
89
+ */
90
+ subscribe(channels: string | string[]): Promise<any>
91
+
92
+ /**
93
+ * List of channels from which you want to unsubscribe.
94
+ *
95
+ * @param channels the channels to unsubscribe from, either in the form of a
96
+ * string (for one channel) or an array of strings.
97
+ *
98
+ * @example
99
+ * ```js
100
+ * await chatClient.unsubscribe("my-channel")
101
+ * await chatClient.unsubscribe(["chan-2", "chan-3"])
102
+ * ```
103
+ */
104
+ unsubscribe(channels: string | string[]): Promise<any>
105
+
106
+ /**
107
+ * Publish a message into the specified channel.
108
+ *
109
+ * @example Publishing a message as a string:
110
+ * ```js
111
+ * await chatClient.publish({
112
+ * channel: 'my-channel',
113
+ * message: 'Hello, world.'
114
+ * })
115
+ * ```
116
+ *
117
+ * @example Publishing a message as an object:
118
+ * ```js
119
+ * await chatClient.publish({
120
+ * channel: 'my-channel',
121
+ * message: {
122
+ * field_one: 'value_one',
123
+ * field_two: 'value_two',
124
+ * }
125
+ * })
126
+ * ```
127
+ */
128
+ publish(params: {
129
+ /** The message to send. This can be any JSON-serializable object. */
130
+ content: any
131
+ /** Channel in which to send the message. */
132
+ channel: string
133
+ /**
134
+ * Metadata associated with the message. There are no requirements on the
135
+ * content of metadata.
136
+ */
137
+ meta?: Record<any, any>
138
+ }): Promise<any>
139
+
140
+ /**
141
+ * Returns the list of messages that were sent to the specified channel.
142
+ *
143
+ * @example
144
+ * ```js
145
+ * const m = await chatClient.getMessages({ channel: 'chan1' })
146
+ *
147
+ * m.messages.length; // 23
148
+ * m.messages[0]; // the most recent message
149
+ * m.messages[0].member; // the sender
150
+ * m.messages[0].content; // the content
151
+ * m.messages[0].meta; // the metadata (if any)
152
+ *
153
+ * m.cursor.next; // if not null, there are more messages.
154
+ *
155
+ * // Get the next page using the cursor
156
+ * const next = await chatClient.getMessages({
157
+ * channel: 'chan1',
158
+ * cursor: {
159
+ * after: m.cursor.after
160
+ * }
161
+ * })
162
+ * ```
163
+ */
164
+ getMessages(params: {
165
+ /** Channel for which to retrieve the messages. */
166
+ channel: string
167
+ /** Cursor for pagination. */
168
+ cursor?: PagingCursor
169
+ }): Promise<any>
170
+
171
+ /**
172
+ * Returns the list of members in the given channel.
173
+ *
174
+ * @example
175
+ * ```js
176
+ * const m = await chatClient.getMembers({ channel: 'my-channel' })
177
+ *
178
+ * m.members.length; // 7
179
+ * m.members[0]; // { id: ..., channel: ..., state: ... }
180
+ * ```
181
+ */
182
+ getMembers(params: {
183
+ /** The channel for which to get the list of members. */
184
+ channel: string
185
+ }): Promise<any>
186
+
187
+ /**
188
+ * Sets a state object for the current member, for the specified channels. The
189
+ * previous state object will be completely replaced.
190
+ *
191
+ * @example
192
+ * ```js
193
+ * await chatClient.setMemberState({
194
+ * channels: ['chan1', 'chan2'],
195
+ * state: {
196
+ * online: true,
197
+ * typing: false
198
+ * }
199
+ * })
200
+ * ```
201
+ */
202
+ setMemberState(params: {
203
+ /** Channels for which to set the state. */
204
+ channels: string | string[]
205
+ /**
206
+ * The state to set. There are no requirements on the content of the state.
207
+ */
208
+ state: Record<any, any>
209
+ }): Promise<any>
210
+
211
+ /**
212
+ * Returns the states of a member in the specified channels.
213
+ *
214
+ * @example
215
+ * ```js
216
+ * const s = await chatClient.getMemberState({
217
+ * channels: ['chan1', 'chan2'],
218
+ * memberId: 'my-member-id'
219
+ * })
220
+ *
221
+ * s.channels.length; // 2
222
+ * s.channels.chan1.state; // the state object for chan1
223
+ * ```
224
+ */
225
+ getMemberState(params: {
226
+ /** Channels for which to get the state. */
227
+ channels: string | string[]
228
+ /** Id of the member for which to get the state. */
229
+ memberId: string
230
+ }): Promise<any>
231
+ }
232
+
233
+ export interface ClientApiEventsDocs {
234
+ /**
235
+ * The session is going to expire.
236
+ * Use the `updateToken` method to refresh your token.
237
+ */
238
+ 'session.expiring': () => void
239
+
240
+ /**
241
+ * A new message has been received.
242
+ */
243
+ message: (message: Chat.ChatMessage) => void
244
+
245
+ /**
246
+ * A new member joined the chat.
247
+ */
248
+ 'member.joined': (member: Chat.ChatMember) => void
249
+
250
+ /**
251
+ * A member updated its state.
252
+ */
253
+ 'member.updated': (member: Chat.ChatMember) => void
254
+
255
+ /**
256
+ * A member left the chat.
257
+ */
258
+ 'member.left': (member: Chat.ChatMember) => void
259
+ }
@@ -128,7 +128,7 @@ describe('ChatClient Object', () => {
128
128
  await chat.subscribe(['test'])
129
129
  await chat.publish({
130
130
  channel: 'test',
131
- message: 'test',
131
+ content: 'test',
132
132
  })
133
133
 
134
134
  const connectMsg = JSON.parse(server.messages[0].toString())
@@ -147,7 +147,7 @@ describe('ChatClient Object', () => {
147
147
  chat.subscribe(['test']),
148
148
  chat.publish({
149
149
  channel: 'test',
150
- message: 'test',
150
+ content: 'test',
151
151
  }),
152
152
  ])
153
153
 
@@ -390,7 +390,7 @@ describe('ChatClient Object', () => {
390
390
 
391
391
  const params = {
392
392
  channel: 'test',
393
- message: 'test',
393
+ content: 'test',
394
394
  }
395
395
 
396
396
  await chat.publish(params)
@@ -476,7 +476,7 @@ describe('ChatClient Object', () => {
476
476
  // calling unsubscribe()
477
477
  await chat.publish({
478
478
  channel: 'test',
479
- message: 'test',
479
+ content: 'test',
480
480
  })
481
481
 
482
482
  await expect(() => chat.unsubscribe(['test1_error'])).rejects.toBeTruthy()
@@ -7,18 +7,54 @@ import type {
7
7
  } from '@signalwire/core'
8
8
  import { getLogger } from '@signalwire/core'
9
9
  import { createClient } from '../createClient'
10
+ import {
11
+ ClientApiEventsDocs,
12
+ ClientDocs,
13
+ } from './Client.docs'
10
14
 
11
- export interface ClientApiEvents extends ChatNamespace.BaseChatApiEvents {}
15
+ interface ClientApiEventsMain extends ChatNamespace.BaseChatApiEvents {}
16
+ export interface ClientApiEvents extends AssertSameType<ClientApiEventsMain, ClientApiEventsDocs> {}
12
17
 
18
+ /** @ignore */
13
19
  export interface ClientFullState extends Client {}
14
20
  interface ClientMain
15
21
  extends ChatContract,
16
22
  Omit<ConsumerContract<ClientApiEvents, ClientFullState>, 'subscribe'> {}
17
23
 
18
- interface ClientDocs extends ClientMain {}
19
-
24
+ /**
25
+ * You can use the Client object to build a messaging system into the browser.
26
+ *
27
+ * Example usage:
28
+ *
29
+ * ```js
30
+ * import { Chat } from '@signalwire/js'
31
+ *
32
+ * const chatClient = new Chat.Client({
33
+ * token: '<your_chat_token>', // get this from the REST APIs
34
+ * })
35
+ *
36
+ * await chatClient.subscribe([ 'mychannel1', 'mychannel2' ])
37
+ *
38
+ * chatClient.on('message', (message) => {
39
+ * console.log("Received", message.content,
40
+ * "on", message.channel,
41
+ * "at", message.publishedAt)
42
+ * })
43
+ *
44
+ * await chatClient.publish({
45
+ * channel: 'mychannel1',
46
+ * message: 'hello world'
47
+ * })
48
+ * ```
49
+ *
50
+ * ## Events
51
+ *
52
+ * Please see {@link ClientApiEvents} for the list of events emitted by a chat
53
+ * Client object.
54
+ */
20
55
  export interface Client extends AssertSameType<ClientMain, ClientDocs> {}
21
56
 
57
+ /** @ignore */
22
58
  export interface ClientOptions extends UserOptions {}
23
59
 
24
60
  export const Client = function (chatOptions: ClientOptions) {
package/src/chat/index.ts CHANGED
@@ -1 +1,11 @@
1
+ import { Chat } from '@signalwire/core'
2
+ import ChatMember = Chat.ChatMember
3
+ import ChatMessage = Chat.ChatMessage
4
+
1
5
  export * from './Client'
6
+ export { PagingCursor } from './Client.docs'
7
+
8
+ export {
9
+ ChatMember,
10
+ ChatMessage,
11
+ }
package/src/index.ts CHANGED
@@ -46,6 +46,12 @@ export type RoomScreenShare = VideoMemberEntity & { type: 'screen' }
46
46
  /** @ignore @deprecated */
47
47
  export type RoomDevice = VideoMemberEntity & { type: 'device' }
48
48
 
49
+ /**
50
+ * The Chat namespace contains the classes and functions that you need to
51
+ * create a real-time chat application.
52
+ */
53
+ export * as Chat from './chat'
54
+
49
55
  /**
50
56
  * The Video namespace contains the classes and functions that you need to
51
57
  * create a video conferencing application.
@@ -96,6 +102,3 @@ export type {
96
102
  RoomSessionObjectEventsHandlerMap as RoomObjectEventsHandlerMap,
97
103
  RoomSessionObjectEvents as RoomObjectEvents,
98
104
  } from './utils/interfaces'
99
-
100
- /** @internal */
101
- export * as __sw__Chat from './chat'
@@ -5,7 +5,6 @@ import type {
5
5
  VideoLayoutEventNames,
6
6
  VideoRoomSessionEventNames,
7
7
  VideoRoomEventParams,
8
- VideoMemberEntity,
9
8
  InternalVideoMemberEntity,
10
9
  VideoMemberEventNames,
11
10
  MemberUpdated,
@@ -22,6 +21,7 @@ import type {
22
21
  VideoRoomSessionContract,
23
22
  OnlyFunctionProperties,
24
23
  AssertSameType,
24
+ MemberListUpdated,
25
25
  } from '@signalwire/core'
26
26
  import { INTERNAL_MEMBER_UPDATABLE_PROPS } from '@signalwire/core'
27
27
  import type { RoomSession } from '../RoomSession'
@@ -29,6 +29,18 @@ import type { RoomSessionDevice } from '../RoomSessionDevice'
29
29
  import type { RoomSessionScreenShare } from '../RoomSessionScreenShare'
30
30
  import { RoomMemberSelfMethodsInterfaceDocs } from './interfaces.docs'
31
31
 
32
+ /**
33
+ * @privateRemarks
34
+ * Every other package exposing a `VideoMemberEntity` is
35
+ * transforming the server payload into something else, with
36
+ * the most significant change being converting properties
37
+ * from snake to camel case. The `js` package, on the other
38
+ * hand, exposes the server payload pretty much as is (as as
39
+ * v3) so what we consider internal (sdk and server) in
40
+ * other packages is external (user facing) for `js`.
41
+ */
42
+ type VideoMemberEntity = InternalVideoMemberEntity
43
+
32
44
  const INTERNAL_MEMBER_UPDATED_EVENTS = Object.keys(
33
45
  INTERNAL_MEMBER_UPDATABLE_PROPS
34
46
  ).map((key) => {
@@ -47,19 +59,27 @@ export type VideoMemberHandlerParams = { member: VideoMemberEntity }
47
59
  export type VideoMemberUpdatedHandlerParams = {
48
60
  member: VideoMemberEntityUpdated
49
61
  }
62
+ export type VideoMemberListUpdatedParams = { members: VideoMemberEntity[] }
50
63
 
51
64
  export type RoomSessionObjectEventsHandlerMap = Record<
52
65
  VideoLayoutEventNames,
53
66
  (params: { layout: VideoLayout }) => void
54
67
  > &
55
68
  Record<
56
- Exclude<VideoMemberEventNames, MemberUpdated | MemberUpdatedEventNames>,
69
+ Exclude<
70
+ VideoMemberEventNames,
71
+ MemberUpdated | MemberUpdatedEventNames | MemberListUpdated
72
+ >,
57
73
  (params: VideoMemberHandlerParams) => void
58
74
  > &
59
75
  Record<
60
76
  Extract<VideoMemberEventNames, MemberUpdated | MemberUpdatedEventNames>,
61
77
  (params: VideoMemberUpdatedHandlerParams) => void
62
78
  > &
79
+ Record<
80
+ Extract<VideoMemberEventNames, MemberListUpdated>,
81
+ (params: VideoMemberListUpdatedParams) => void
82
+ > &
63
83
  Record<
64
84
  DeprecatedMemberUpdatableProps,
65
85
  (params: DeprecatedVideoMemberHandlerParams) => void
@@ -0,0 +1,217 @@
1
+ import {
2
+ sagaEffects,
3
+ SagaIterator,
4
+ SDKWorker,
5
+ toSyntheticEvent,
6
+ validateEventsToSubscribe,
7
+ toInternalEventName,
8
+ PubSubChannel,
9
+ InternalVideoMemberEntity,
10
+ InternalVideoMemberUpdatedEvent,
11
+ VideoMemberJoinedEvent,
12
+ VideoMemberLeftEvent,
13
+ VideoMemberUpdatedEvent,
14
+ InternalVideoRoomJoinedEvent,
15
+ MapToPubSubShape,
16
+ } from '@signalwire/core'
17
+ import type { RoomSession } from '../RoomSession'
18
+ import type { VideoMemberListUpdatedParams } from '../utils/interfaces'
19
+
20
+ const noop = () => {}
21
+
22
+ const EXTERNAL_MEMBER_LIST_UPDATED_EVENT = 'video.memberList.updated'
23
+
24
+ const INTERNAL_MEMBER_LIST_UPDATED_EVENT = toInternalEventName({
25
+ event: EXTERNAL_MEMBER_LIST_UPDATED_EVENT,
26
+ })
27
+
28
+ const SYNTHETIC_MEMBER_LIST_UPDATED_EVENT = toSyntheticEvent(
29
+ INTERNAL_MEMBER_LIST_UPDATED_EVENT
30
+ )
31
+
32
+ /**
33
+ * List of action types this worker cares about.
34
+ */
35
+ type MemberListUpdatedTargetActions = MapToPubSubShape<
36
+ | InternalVideoRoomJoinedEvent
37
+ | InternalVideoMemberUpdatedEvent
38
+ | VideoMemberJoinedEvent
39
+ | VideoMemberLeftEvent
40
+ | VideoMemberUpdatedEvent
41
+ >
42
+
43
+ const MEMBER_LIST_EVENTS: Array<MemberListUpdatedTargetActions['type']> = [
44
+ /** Alias to `video.room.subscribed` */
45
+ 'video.room.joined',
46
+ 'video.member.joined',
47
+ 'video.member.left',
48
+ 'video.member.updated',
49
+ ]
50
+
51
+ type MemberList = Map<string, InternalVideoMemberEntity>
52
+
53
+ const isMemberListEvent = (
54
+ event: string
55
+ ): event is MemberListUpdatedTargetActions['type'] => {
56
+ // @ts-expect-error
57
+ return MEMBER_LIST_EVENTS.includes(event)
58
+ }
59
+
60
+ const getMemberListEventsToSubscribe = (subscriptions: MemberListUpdatedTargetActions['type'][]) => {
61
+ return validateEventsToSubscribe(MEMBER_LIST_EVENTS).filter((event) => {
62
+ return !subscriptions.includes(event)
63
+ })
64
+ }
65
+
66
+ const shouldHandleMemberList = (subscriptions: string[]) => {
67
+ return subscriptions.some((event) =>
68
+ event.includes(INTERNAL_MEMBER_LIST_UPDATED_EVENT)
69
+ )
70
+ }
71
+
72
+ const getMembersFromAction = (action: MemberListUpdatedTargetActions) => {
73
+ if (action.type === 'video.room.joined') {
74
+ return action.payload.room_session.members
75
+ }
76
+
77
+ return [action.payload.member]
78
+ }
79
+
80
+ export const getUpdatedMembers = ({
81
+ action,
82
+ memberList,
83
+ }: {
84
+ action: MemberListUpdatedTargetActions
85
+ memberList: MemberList
86
+ }) => {
87
+ const actionMembers = getMembersFromAction(action)
88
+
89
+ switch (action.type) {
90
+ case 'video.member.left':
91
+ actionMembers.forEach((member: InternalVideoMemberEntity) => {
92
+ memberList.delete(member.id)
93
+ })
94
+ break
95
+ default:
96
+ actionMembers.forEach((member: InternalVideoMemberEntity) => {
97
+ memberList.set(member.id, member)
98
+ })
99
+ }
100
+
101
+ return Array.from(memberList.values())
102
+ }
103
+
104
+ const initMemberListSubscriptions = (
105
+ room: RoomSession,
106
+ subscriptions: MemberListUpdatedTargetActions['type'][]
107
+ ) => {
108
+ const events = getMemberListEventsToSubscribe(subscriptions)
109
+
110
+ events.forEach((event) => {
111
+ /**
112
+ * Params to `subscribe` come from the event handlers
113
+ * the user has attached so to make sure we subscribe to
114
+ * all the appropiate events needed for
115
+ * `memberList.updated` to work, we must subscribe to
116
+ * the required events. We don't need to act upon the
117
+ * event (that's why we attach a `noop`), just to
118
+ * register it (`subscribe` gets its values from
119
+ * `BaseComponent.getSubscriptions`, which gets
120
+ * populated by each of the event handlers the user
121
+ * attached).
122
+ */
123
+ room.once(event as any, noop)
124
+ })
125
+
126
+ /**
127
+ * This handler will act as a simple bridge between
128
+ * synthetic events and external events.
129
+ */
130
+ const eventBridgeHandler = ({ members }: VideoMemberListUpdatedParams) => {
131
+ // @ts-expect-error
132
+ room.emit(EXTERNAL_MEMBER_LIST_UPDATED_EVENT, { members })
133
+ }
134
+
135
+ // @ts-expect-error
136
+ room.on(SYNTHETIC_MEMBER_LIST_UPDATED_EVENT, eventBridgeHandler)
137
+
138
+ /**
139
+ * Any events attached by the saga should be specified
140
+ * here so it can be cleaned up when needed.
141
+ */
142
+ const cleanup = () => {
143
+ // @ts-expect-error
144
+ room.off(SYNTHETIC_MEMBER_LIST_UPDATED_EVENT, eventBridgeHandler)
145
+ }
146
+
147
+ return {
148
+ cleanup,
149
+ }
150
+ }
151
+
152
+ function* membersListUpdatedWatcher({
153
+ pubSubChannel,
154
+ }: {
155
+ pubSubChannel: PubSubChannel
156
+ }): SagaIterator {
157
+ const memberList: MemberList = new Map()
158
+
159
+ function* worker(pubSubAction: MemberListUpdatedTargetActions) {
160
+ const roomSessionId =
161
+ pubSubAction.type === 'video.room.joined'
162
+ ? pubSubAction.payload.room_session.id
163
+ : pubSubAction.payload.room_session_id
164
+
165
+ const members = getUpdatedMembers({ action: pubSubAction, memberList })
166
+ const memberListPayload = {
167
+ /**
168
+ * At this point it's needed to send the
169
+ * `room_session_id` so the pubSubSaga can properly
170
+ * infer the namespace for emitting the events to the
171
+ * appropiate room.
172
+ */
173
+ room_session_id: roomSessionId,
174
+ members,
175
+ }
176
+
177
+ // TODO: add typings
178
+ yield sagaEffects.put(pubSubChannel, {
179
+ type: SYNTHETIC_MEMBER_LIST_UPDATED_EVENT as any,
180
+ payload: memberListPayload as any,
181
+ })
182
+ }
183
+
184
+ while (true) {
185
+ const pubSubAction: MemberListUpdatedTargetActions = yield sagaEffects.take(
186
+ pubSubChannel,
187
+ ({ type }: any) => {
188
+ return isMemberListEvent(type)
189
+ }
190
+ )
191
+
192
+ yield sagaEffects.fork(worker, pubSubAction)
193
+ }
194
+ }
195
+
196
+ export const memberListUpdatedWorker: SDKWorker<RoomSession> =
197
+ function* membersChangedWorker({
198
+ channels: { pubSubChannel },
199
+ instance,
200
+ }): SagaIterator {
201
+ // @ts-expect-error
202
+ const subscriptions = instance.getSubscriptions()
203
+
204
+ if (!shouldHandleMemberList(subscriptions)) {
205
+ return
206
+ }
207
+
208
+ const { cleanup } = initMemberListSubscriptions(instance, subscriptions)
209
+
210
+ yield sagaEffects.fork(membersListUpdatedWatcher, {
211
+ pubSubChannel,
212
+ })
213
+
214
+ instance.once('destroy', () => {
215
+ cleanup()
216
+ })
217
+ }
@@ -0,0 +1 @@
1
+ export * from "./memberListUpdatedWorker"