livekit-client 2.18.9 → 2.19.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 (217) hide show
  1. package/dist/livekit-client.e2ee.worker.js +1 -1
  2. package/dist/livekit-client.e2ee.worker.js.map +1 -1
  3. package/dist/livekit-client.e2ee.worker.mjs +5609 -644
  4. package/dist/livekit-client.e2ee.worker.mjs.map +1 -1
  5. package/dist/livekit-client.esm.mjs +3553 -2813
  6. package/dist/livekit-client.esm.mjs.map +1 -1
  7. package/dist/livekit-client.pt.worker.js +2 -0
  8. package/dist/livekit-client.pt.worker.js.map +1 -0
  9. package/dist/livekit-client.pt.worker.mjs +5834 -0
  10. package/dist/livekit-client.pt.worker.mjs.map +1 -0
  11. package/dist/livekit-client.umd.js +1 -1
  12. package/dist/livekit-client.umd.js.map +1 -1
  13. package/dist/src/api/SignalClient.d.ts +2 -1
  14. package/dist/src/api/SignalClient.d.ts.map +1 -1
  15. package/dist/src/e2ee/E2eeManager.d.ts +8 -7
  16. package/dist/src/e2ee/E2eeManager.d.ts.map +1 -1
  17. package/dist/src/e2ee/types.d.ts +35 -8
  18. package/dist/src/e2ee/types.d.ts.map +1 -1
  19. package/dist/src/e2ee/utils.d.ts +5 -5
  20. package/dist/src/e2ee/utils.d.ts.map +1 -1
  21. package/dist/src/e2ee/worker/DataCryptor.d.ts +5 -5
  22. package/dist/src/e2ee/worker/DataCryptor.d.ts.map +1 -1
  23. package/dist/src/e2ee/worker/FrameCryptor.d.ts +21 -4
  24. package/dist/src/e2ee/worker/FrameCryptor.d.ts.map +1 -1
  25. package/dist/src/e2ee/worker/naluUtils.d.ts +1 -1
  26. package/dist/src/e2ee/worker/naluUtils.d.ts.map +1 -1
  27. package/dist/src/e2ee/worker/sifPayload.d.ts +7 -7
  28. package/dist/src/e2ee/worker/sifPayload.d.ts.map +1 -1
  29. package/dist/src/index.d.ts +4 -1
  30. package/dist/src/index.d.ts.map +1 -1
  31. package/dist/src/options.d.ts +7 -0
  32. package/dist/src/options.d.ts.map +1 -1
  33. package/dist/src/packetTrailer/PacketTrailerManager.d.ts +49 -0
  34. package/dist/src/packetTrailer/PacketTrailerManager.d.ts.map +1 -0
  35. package/dist/src/packetTrailer/packetTrailer.d.ts +32 -0
  36. package/dist/src/packetTrailer/packetTrailer.d.ts.map +1 -0
  37. package/dist/src/packetTrailer/types.d.ts +57 -0
  38. package/dist/src/packetTrailer/types.d.ts.map +1 -0
  39. package/dist/src/packetTrailer/utils.d.ts +9 -0
  40. package/dist/src/packetTrailer/utils.d.ts.map +1 -0
  41. package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts +2 -0
  42. package/dist/src/packetTrailer/worker/packetTrailer.worker.d.ts.map +1 -0
  43. package/dist/src/room/RTCEngine.d.ts +2 -4
  44. package/dist/src/room/RTCEngine.d.ts.map +1 -1
  45. package/dist/src/room/Room.d.ts +7 -3
  46. package/dist/src/room/Room.d.ts.map +1 -1
  47. package/dist/src/room/data-track/RemoteDataTrack.d.ts +5 -1
  48. package/dist/src/room/data-track/RemoteDataTrack.d.ts.map +1 -1
  49. package/dist/src/room/data-track/depacketizer.d.ts +12 -4
  50. package/dist/src/room/data-track/depacketizer.d.ts.map +1 -1
  51. package/dist/src/room/data-track/frame.d.ts +3 -3
  52. package/dist/src/room/data-track/frame.d.ts.map +1 -1
  53. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
  54. package/dist/src/room/data-track/incoming/IncomingDataTrackManager.d.ts.map +1 -1
  55. package/dist/src/room/data-track/incoming/pipeline.d.ts +4 -1
  56. package/dist/src/room/data-track/incoming/pipeline.d.ts.map +1 -1
  57. package/dist/src/room/data-track/outgoing/types.d.ts +2 -2
  58. package/dist/src/room/data-track/outgoing/types.d.ts.map +1 -1
  59. package/dist/src/room/data-track/packet/extensions.d.ts +4 -4
  60. package/dist/src/room/data-track/packet/extensions.d.ts.map +1 -1
  61. package/dist/src/room/data-track/packet/index.d.ts +5 -5
  62. package/dist/src/room/data-track/packet/index.d.ts.map +1 -1
  63. package/dist/src/room/data-track/packet/serializable.d.ts +1 -1
  64. package/dist/src/room/data-track/packet/serializable.d.ts.map +1 -1
  65. package/dist/src/room/data-track/types.d.ts +7 -0
  66. package/dist/src/room/data-track/types.d.ts.map +1 -1
  67. package/dist/src/room/events.d.ts +2 -2
  68. package/dist/src/room/participant/LocalParticipant.d.ts +8 -14
  69. package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
  70. package/dist/src/room/participant/Participant.d.ts +1 -1
  71. package/dist/src/room/participant/Participant.d.ts.map +1 -1
  72. package/dist/src/room/participant/RemoteParticipant.d.ts +5 -1
  73. package/dist/src/room/participant/RemoteParticipant.d.ts.map +1 -1
  74. package/dist/src/room/rpc/client/RpcClientManager.d.ts +39 -0
  75. package/dist/src/room/rpc/client/RpcClientManager.d.ts.map +1 -0
  76. package/dist/src/room/rpc/client/events.d.ts +8 -0
  77. package/dist/src/room/rpc/client/events.d.ts.map +1 -0
  78. package/dist/src/room/rpc/index.d.ts +6 -0
  79. package/dist/src/room/rpc/index.d.ts.map +1 -0
  80. package/dist/src/room/rpc/server/RpcServerManager.d.ts +44 -0
  81. package/dist/src/room/rpc/server/RpcServerManager.d.ts.map +1 -0
  82. package/dist/src/room/rpc/server/events.d.ts +8 -0
  83. package/dist/src/room/rpc/server/events.d.ts.map +1 -0
  84. package/dist/src/room/{rpc.d.ts → rpc/utils.d.ts} +34 -4
  85. package/dist/src/room/rpc/utils.d.ts.map +1 -0
  86. package/dist/src/room/track/PacketTrailerExtractor.d.ts +19 -0
  87. package/dist/src/room/track/PacketTrailerExtractor.d.ts.map +1 -0
  88. package/dist/src/room/track/RemoteVideoTrack.d.ts +16 -0
  89. package/dist/src/room/track/RemoteVideoTrack.d.ts.map +1 -1
  90. package/dist/src/room/track/Track.d.ts +1 -1
  91. package/dist/src/room/track/Track.d.ts.map +1 -1
  92. package/dist/src/room/track/create.d.ts.map +1 -1
  93. package/dist/src/room/track/options.d.ts +10 -0
  94. package/dist/src/room/track/options.d.ts.map +1 -1
  95. package/dist/src/room/track/utils.d.ts.map +1 -1
  96. package/dist/src/room/utils.d.ts +4 -3
  97. package/dist/src/room/utils.d.ts.map +1 -1
  98. package/dist/src/test/MockMediaStreamTrack.d.ts.map +1 -1
  99. package/dist/src/utils/dataPacketBuffer.d.ts +1 -1
  100. package/dist/src/utils/dataPacketBuffer.d.ts.map +1 -1
  101. package/dist/src/version.d.ts +9 -1
  102. package/dist/src/version.d.ts.map +1 -1
  103. package/dist/ts4.2/api/SignalClient.d.ts +2 -1
  104. package/dist/ts4.2/e2ee/E2eeManager.d.ts +8 -7
  105. package/dist/ts4.2/e2ee/types.d.ts +35 -8
  106. package/dist/ts4.2/e2ee/utils.d.ts +5 -5
  107. package/dist/ts4.2/e2ee/worker/DataCryptor.d.ts +5 -5
  108. package/dist/ts4.2/e2ee/worker/FrameCryptor.d.ts +21 -4
  109. package/dist/ts4.2/e2ee/worker/naluUtils.d.ts +1 -1
  110. package/dist/ts4.2/e2ee/worker/sifPayload.d.ts +7 -7
  111. package/dist/ts4.2/index.d.ts +5 -1
  112. package/dist/ts4.2/options.d.ts +7 -0
  113. package/dist/ts4.2/packetTrailer/PacketTrailerManager.d.ts +49 -0
  114. package/dist/ts4.2/packetTrailer/packetTrailer.d.ts +32 -0
  115. package/dist/ts4.2/packetTrailer/types.d.ts +57 -0
  116. package/dist/ts4.2/packetTrailer/utils.d.ts +9 -0
  117. package/dist/ts4.2/packetTrailer/worker/packetTrailer.worker.d.ts +2 -0
  118. package/dist/ts4.2/room/RTCEngine.d.ts +2 -4
  119. package/dist/ts4.2/room/Room.d.ts +7 -3
  120. package/dist/ts4.2/room/data-track/RemoteDataTrack.d.ts +5 -1
  121. package/dist/ts4.2/room/data-track/depacketizer.d.ts +12 -4
  122. package/dist/ts4.2/room/data-track/frame.d.ts +3 -3
  123. package/dist/ts4.2/room/data-track/incoming/IncomingDataTrackManager.d.ts +3 -1
  124. package/dist/ts4.2/room/data-track/incoming/pipeline.d.ts +4 -1
  125. package/dist/ts4.2/room/data-track/outgoing/types.d.ts +2 -2
  126. package/dist/ts4.2/room/data-track/packet/extensions.d.ts +4 -4
  127. package/dist/ts4.2/room/data-track/packet/index.d.ts +5 -5
  128. package/dist/ts4.2/room/data-track/packet/serializable.d.ts +1 -1
  129. package/dist/ts4.2/room/data-track/types.d.ts +7 -0
  130. package/dist/ts4.2/room/events.d.ts +2 -2
  131. package/dist/ts4.2/room/participant/LocalParticipant.d.ts +8 -14
  132. package/dist/ts4.2/room/participant/Participant.d.ts +1 -1
  133. package/dist/ts4.2/room/participant/RemoteParticipant.d.ts +5 -1
  134. package/dist/ts4.2/room/rpc/client/RpcClientManager.d.ts +43 -0
  135. package/dist/ts4.2/room/rpc/client/events.d.ts +8 -0
  136. package/dist/ts4.2/room/rpc/index.d.ts +7 -0
  137. package/dist/ts4.2/room/rpc/server/RpcServerManager.d.ts +44 -0
  138. package/dist/ts4.2/room/rpc/server/events.d.ts +8 -0
  139. package/dist/ts4.2/room/{rpc.d.ts → rpc/utils.d.ts} +34 -4
  140. package/dist/ts4.2/room/track/PacketTrailerExtractor.d.ts +19 -0
  141. package/dist/ts4.2/room/track/RemoteVideoTrack.d.ts +16 -0
  142. package/dist/ts4.2/room/track/Track.d.ts +1 -1
  143. package/dist/ts4.2/room/track/options.d.ts +10 -0
  144. package/dist/ts4.2/room/utils.d.ts +4 -3
  145. package/dist/ts4.2/utils/dataPacketBuffer.d.ts +1 -1
  146. package/dist/ts4.2/version.d.ts +9 -1
  147. package/package.json +24 -16
  148. package/src/api/SignalClient.test.ts +102 -10
  149. package/src/api/SignalClient.ts +4 -2
  150. package/src/api/WebSocketStream.test.ts +0 -1
  151. package/src/e2ee/E2eeManager.ts +82 -30
  152. package/src/e2ee/types.ts +37 -8
  153. package/src/e2ee/utils.ts +7 -6
  154. package/src/e2ee/worker/DataCryptor.ts +6 -6
  155. package/src/e2ee/worker/FrameCryptor.test.ts +177 -4
  156. package/src/e2ee/worker/FrameCryptor.ts +94 -14
  157. package/src/e2ee/worker/ParticipantKeyHandler.test.ts +4 -4
  158. package/src/e2ee/worker/e2ee.worker.ts +13 -5
  159. package/src/e2ee/worker/naluUtils.ts +4 -4
  160. package/src/e2ee/worker/sifPayload.ts +10 -8
  161. package/src/index.ts +7 -0
  162. package/src/options.ts +8 -0
  163. package/src/packetTrailer/PacketTrailerManager.test.ts +172 -0
  164. package/src/packetTrailer/PacketTrailerManager.ts +250 -0
  165. package/src/packetTrailer/packetTrailer.test.ts +174 -0
  166. package/src/packetTrailer/packetTrailer.ts +276 -0
  167. package/src/packetTrailer/types.ts +75 -0
  168. package/src/packetTrailer/utils.test.ts +105 -0
  169. package/src/packetTrailer/utils.ts +50 -0
  170. package/src/packetTrailer/worker/packetTrailer.worker.ts +155 -0
  171. package/src/packetTrailer/worker/tsconfig.json +14 -0
  172. package/src/room/BackOffStrategy.test.ts +1 -1
  173. package/src/room/RTCEngine.test.ts +219 -0
  174. package/src/room/RTCEngine.ts +86 -46
  175. package/src/room/Room.test.ts +62 -1
  176. package/src/room/Room.ts +111 -86
  177. package/src/room/data-track/RemoteDataTrack.ts +8 -1
  178. package/src/room/data-track/depacketizer.test.ts +433 -1
  179. package/src/room/data-track/depacketizer.ts +79 -61
  180. package/src/room/data-track/frame.ts +2 -2
  181. package/src/room/data-track/incoming/IncomingDataTrackManager.test.ts +194 -0
  182. package/src/room/data-track/incoming/IncomingDataTrackManager.ts +21 -1
  183. package/src/room/data-track/incoming/pipeline.ts +13 -2
  184. package/src/room/data-track/outgoing/types.ts +3 -2
  185. package/src/room/data-track/packet/extensions.ts +2 -2
  186. package/src/room/data-track/packet/index.ts +6 -6
  187. package/src/room/data-track/packet/serializable.ts +1 -1
  188. package/src/room/data-track/types.ts +8 -0
  189. package/src/room/events.ts +2 -2
  190. package/src/room/participant/LocalParticipant.test.ts +81 -0
  191. package/src/room/participant/LocalParticipant.ts +64 -187
  192. package/src/room/participant/Participant.ts +1 -1
  193. package/src/room/participant/RemoteParticipant.ts +9 -0
  194. package/src/room/participant/publishUtils.ts +1 -1
  195. package/src/room/rpc/client/RpcClientManager.test.ts +430 -0
  196. package/src/room/rpc/client/RpcClientManager.ts +269 -0
  197. package/src/room/rpc/client/events.ts +9 -0
  198. package/src/room/rpc/index.ts +14 -0
  199. package/src/room/rpc/server/RpcServerManager.test.ts +471 -0
  200. package/src/room/rpc/server/RpcServerManager.ts +293 -0
  201. package/src/room/rpc/server/events.ts +9 -0
  202. package/src/room/{rpc.ts → rpc/utils.ts} +49 -8
  203. package/src/room/track/PacketTrailerExtractor.ts +43 -0
  204. package/src/room/track/RemoteVideoTrack.ts +23 -2
  205. package/src/room/track/Track.ts +1 -1
  206. package/src/room/track/create.ts +0 -4
  207. package/src/room/track/options.ts +11 -0
  208. package/src/room/track/record.ts +1 -1
  209. package/src/room/track/utils.ts +4 -1
  210. package/src/room/utils.test.ts +14 -1
  211. package/src/room/utils.ts +19 -4
  212. package/src/test/MockMediaStreamTrack.ts +0 -1
  213. package/src/type-polyfills/non-shared-typed-arrays.d.ts +6 -0
  214. package/src/utils/dataPacketBuffer.ts +1 -1
  215. package/src/version.ts +11 -1
  216. package/dist/src/room/rpc.d.ts.map +0 -1
  217. package/src/room/rpc.test.ts +0 -301
@@ -0,0 +1,9 @@
1
+ import type { DataPacket } from '@livekit/protocol';
2
+
3
+ export type EventSendDataPacket = {
4
+ packet: DataPacket;
5
+ };
6
+
7
+ export type RpcClientManagerCallbacks = {
8
+ sendDataPacket: (event: EventSendDataPacket) => void;
9
+ };
@@ -0,0 +1,14 @@
1
+ export { default as RpcClientManager } from './client/RpcClientManager';
2
+ export type { RpcClientManagerCallbacks } from './client/events';
3
+ export { default as RpcServerManager } from './server/RpcServerManager';
4
+ export type { RpcServerManagerCallbacks } from './server/events';
5
+ export {
6
+ type PerformRpcParams,
7
+ RPC_REQUEST_DATA_STREAM_TOPIC,
8
+ RPC_RESPONSE_DATA_STREAM_TOPIC,
9
+ RpcRequestAttrs,
10
+ RpcError,
11
+ type RpcInvocationData,
12
+ byteLength,
13
+ truncateBytes,
14
+ } from './utils';
@@ -0,0 +1,471 @@
1
+ import { RpcRequest } from '@livekit/protocol';
2
+ import { assert, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import log from '../../../logger';
4
+ import { subscribeToEvents } from '../../../utils/subscribeToEvents';
5
+ import { CLIENT_PROTOCOL_DATA_STREAM_RPC, CLIENT_PROTOCOL_DEFAULT } from '../../../version';
6
+ import type RTCEngine from '../../RTCEngine';
7
+ import OutgoingDataStreamManager from '../../data-stream/outgoing/OutgoingDataStreamManager';
8
+ import { RPC_RESPONSE_DATA_STREAM_TOPIC, RpcError, RpcRequestAttrs } from '../utils';
9
+ import RpcServerManager from './RpcServerManager';
10
+ import type { RpcServerManagerCallbacks } from './events';
11
+
12
+ describe('RpcServerManager', () => {
13
+ describe('v1 -> v1', () => {
14
+ let rpcServerManager: RpcServerManager;
15
+
16
+ beforeEach(() => {
17
+ const outgoingDataStreamManager = new OutgoingDataStreamManager(
18
+ {} as unknown as RTCEngine,
19
+ log,
20
+ );
21
+
22
+ rpcServerManager = new RpcServerManager(
23
+ log,
24
+ outgoingDataStreamManager,
25
+ (_identity) => CLIENT_PROTOCOL_DEFAULT,
26
+ );
27
+ });
28
+
29
+ it('should receive a rpc message from a participant', async () => {
30
+ const managerEvents = subscribeToEvents<RpcServerManagerCallbacks>(rpcServerManager, [
31
+ 'sendDataPacket',
32
+ ]);
33
+
34
+ const handler = async () => 'response payload';
35
+ rpcServerManager.registerRpcMethod('test-method', handler);
36
+
37
+ const requestId = crypto.randomUUID();
38
+ const responseTimeoutMs = 10_000;
39
+ await rpcServerManager.handleIncomingRpcRequest(
40
+ 'caller-identity',
41
+ new RpcRequest({
42
+ id: requestId,
43
+ method: 'test-method',
44
+ payload: 'request payload',
45
+ responseTimeoutMs,
46
+ version: 1,
47
+ }),
48
+ );
49
+
50
+ // The first event is an acknowledgement of the request
51
+ const ackEvent = await managerEvents.waitFor('sendDataPacket');
52
+ assert(ackEvent.packet.value.case === 'rpcAck');
53
+ expect(ackEvent.packet.value.value.requestId).toStrictEqual(requestId);
54
+
55
+ // And the second being the actual response
56
+ const responseEvent = await managerEvents.waitFor('sendDataPacket');
57
+ assert(responseEvent.packet.value.case === 'rpcResponse');
58
+ const rpcResponse = responseEvent.packet.value.value;
59
+ expect(rpcResponse.requestId).toStrictEqual(requestId);
60
+ assert(rpcResponse.value.case === 'payload');
61
+ expect(rpcResponse.value.value).toStrictEqual('response payload');
62
+
63
+ expect(managerEvents.areThereBufferedEvents('sendDataPacket')).toBe(false);
64
+ });
65
+
66
+ it('should register a RPC method handler', async () => {
67
+ const managerEvents = subscribeToEvents<RpcServerManagerCallbacks>(rpcServerManager, [
68
+ 'sendDataPacket',
69
+ ]);
70
+
71
+ const methodName = 'testMethod';
72
+ const handler = vi.fn().mockResolvedValue('test response');
73
+
74
+ rpcServerManager.registerRpcMethod(methodName, handler);
75
+
76
+ await rpcServerManager.handleIncomingRpcRequest(
77
+ 'remote-identity',
78
+ new RpcRequest({
79
+ id: 'test-request-id',
80
+ method: methodName,
81
+ payload: 'test payload',
82
+ responseTimeoutMs: 5000,
83
+ version: 1,
84
+ }),
85
+ );
86
+
87
+ expect(handler).toHaveBeenCalledWith({
88
+ requestId: 'test-request-id',
89
+ callerIdentity: 'remote-identity',
90
+ payload: 'test payload',
91
+ responseTimeout: 5000,
92
+ });
93
+
94
+ // Ensure the first event was for the ack
95
+ const ackEvent = await managerEvents.waitFor('sendDataPacket');
96
+ expect(ackEvent.packet.value.case).toStrictEqual('rpcAck');
97
+
98
+ // And the second event was for the response
99
+ const responseEvent = await managerEvents.waitFor('sendDataPacket');
100
+ expect(responseEvent.packet.value.case).toStrictEqual('rpcResponse');
101
+
102
+ expect(managerEvents.areThereBufferedEvents('sendDataPacket')).toBe(false);
103
+ });
104
+
105
+ it('should catch and transform unhandled errors in the RPC method handler', async () => {
106
+ const managerEvents = subscribeToEvents<RpcServerManagerCallbacks>(rpcServerManager, [
107
+ 'sendDataPacket',
108
+ ]);
109
+
110
+ const methodName = 'errorMethod';
111
+ const errorMessage = 'Test error';
112
+ const handler = async () => {
113
+ throw new Error(errorMessage);
114
+ };
115
+
116
+ rpcServerManager.registerRpcMethod(methodName, handler);
117
+
118
+ await rpcServerManager.handleIncomingRpcRequest(
119
+ 'remote-identity',
120
+ new RpcRequest({
121
+ id: 'test-error-request-id',
122
+ method: methodName,
123
+ payload: 'test payload',
124
+ responseTimeoutMs: 5000,
125
+ version: 1,
126
+ }),
127
+ );
128
+
129
+ // Ensure the first event was for the ack
130
+ const ackEvent = await managerEvents.waitFor('sendDataPacket');
131
+ assert(ackEvent.packet.value.case === 'rpcAck');
132
+
133
+ // And the second event was for the error response
134
+ const errorEvent = await managerEvents.waitFor('sendDataPacket');
135
+ assert(errorEvent.packet.value.case === 'rpcResponse');
136
+ assert(errorEvent.packet.value.value.value.case === 'error');
137
+ const errorResponse = errorEvent.packet.value.value.value.value;
138
+ expect(errorResponse.code).toStrictEqual(RpcError.ErrorCode.APPLICATION_ERROR);
139
+
140
+ expect(managerEvents.areThereBufferedEvents('sendDataPacket')).toBe(false);
141
+ });
142
+
143
+ it('should pass through RpcError thrown by the RPC method handler', async () => {
144
+ const managerEvents = subscribeToEvents<RpcServerManagerCallbacks>(rpcServerManager, [
145
+ 'sendDataPacket',
146
+ ]);
147
+
148
+ const methodName = 'rpcErrorMethod';
149
+ const errorCode = 101;
150
+ const errorMessage = 'some-error-message';
151
+ const handler = async () => {
152
+ throw new RpcError(errorCode, errorMessage);
153
+ };
154
+
155
+ rpcServerManager.registerRpcMethod(methodName, handler);
156
+
157
+ await rpcServerManager.handleIncomingRpcRequest(
158
+ 'remote-identity',
159
+ new RpcRequest({
160
+ id: 'test-rpc-error-request-id',
161
+ method: methodName,
162
+ payload: 'test payload',
163
+ responseTimeoutMs: 5000,
164
+ version: 1,
165
+ }),
166
+ );
167
+
168
+ // Ensure the first event was for the ack
169
+ const ackEvent = await managerEvents.waitFor('sendDataPacket');
170
+ assert(ackEvent.packet.value.case === 'rpcAck');
171
+
172
+ // And the second event was for the error response
173
+ const errorEvent = await managerEvents.waitFor('sendDataPacket');
174
+ assert(errorEvent.packet.value.case === 'rpcResponse');
175
+ assert(errorEvent.packet.value.value.value.case === 'error');
176
+ const errorResponse = errorEvent.packet.value.value.value.value;
177
+ expect(errorResponse.code).toStrictEqual(errorCode);
178
+ expect(errorResponse.message).toStrictEqual(errorMessage);
179
+
180
+ expect(managerEvents.areThereBufferedEvents('sendDataPacket')).toBe(false);
181
+ });
182
+ });
183
+
184
+ describe('v2 -> v2', () => {
185
+ let rpcServerManager: RpcServerManager;
186
+ let outgoingDataStreamManager: OutgoingDataStreamManager;
187
+ let mockStreamTextWriter: {
188
+ write: ReturnType<typeof vi.fn>;
189
+ close: ReturnType<typeof vi.fn>;
190
+ };
191
+
192
+ beforeEach(() => {
193
+ outgoingDataStreamManager = new OutgoingDataStreamManager({} as unknown as RTCEngine, log);
194
+
195
+ mockStreamTextWriter = {
196
+ write: vi.fn().mockResolvedValue(undefined),
197
+ close: vi.fn().mockResolvedValue(undefined),
198
+ };
199
+ vi.spyOn(outgoingDataStreamManager, 'streamText').mockResolvedValue(
200
+ mockStreamTextWriter as any,
201
+ );
202
+
203
+ rpcServerManager = new RpcServerManager(
204
+ log,
205
+ outgoingDataStreamManager,
206
+ (_identity) => CLIENT_PROTOCOL_DATA_STREAM_RPC,
207
+ );
208
+ });
209
+
210
+ function makeDataStreamAttrs(requestId: string, method: string, responseTimeout: number) {
211
+ return {
212
+ [RpcRequestAttrs.RPC_REQUEST_ID]: requestId,
213
+ [RpcRequestAttrs.RPC_REQUEST_METHOD]: method,
214
+ [RpcRequestAttrs.RPC_REQUEST_RESPONSE_TIMEOUT_MS]: `${responseTimeout}`,
215
+ [RpcRequestAttrs.RPC_REQUEST_VERSION]: '2',
216
+ };
217
+ }
218
+
219
+ function mockTextStreamReader(payload: string) {
220
+ return { readAll: vi.fn().mockResolvedValue(payload) } as any;
221
+ }
222
+
223
+ it('should receive a small rpc request (< 15kb) and send a small response via data stream from a participant', async () => {
224
+ const managerEvents = subscribeToEvents<RpcServerManagerCallbacks>(rpcServerManager, [
225
+ 'sendDataPacket',
226
+ ]);
227
+
228
+ const handler = async () => 'response payload';
229
+ rpcServerManager.registerRpcMethod('test-method', handler);
230
+
231
+ const requestId = crypto.randomUUID();
232
+ const responseTimeoutMs = 10_000;
233
+ await rpcServerManager.handleIncomingDataStream(
234
+ mockTextStreamReader('request payload'),
235
+ 'caller-identity',
236
+ makeDataStreamAttrs(requestId, 'test-method', responseTimeoutMs),
237
+ );
238
+
239
+ // The first event is an acknowledgement of the request
240
+ const ackEvent = await managerEvents.waitFor('sendDataPacket');
241
+ assert(ackEvent.packet.value.case === 'rpcAck');
242
+ expect(ackEvent.packet.value.value.requestId).toStrictEqual(requestId);
243
+
244
+ // The response should have been sent via data stream, not packet
245
+ expect(managerEvents.areThereBufferedEvents('sendDataPacket')).toBe(false);
246
+ expect(outgoingDataStreamManager.streamText).toHaveBeenCalledWith(
247
+ expect.objectContaining({
248
+ topic: RPC_RESPONSE_DATA_STREAM_TOPIC,
249
+ destinationIdentities: ['caller-identity'],
250
+ attributes: { [RpcRequestAttrs.RPC_REQUEST_ID]: requestId },
251
+ }),
252
+ );
253
+ expect(mockStreamTextWriter.write).toHaveBeenCalledWith('response payload');
254
+ expect(mockStreamTextWriter.close).toHaveBeenCalled();
255
+ });
256
+
257
+ it('should receive a large rpc request (> 15kb) and send a large response via data stream from a participant', async () => {
258
+ const managerEvents = subscribeToEvents<RpcServerManagerCallbacks>(rpcServerManager, [
259
+ 'sendDataPacket',
260
+ ]);
261
+
262
+ const handler = async () => new Array(20_000).fill('B').join('');
263
+ rpcServerManager.registerRpcMethod('test-method', handler);
264
+
265
+ const requestId = crypto.randomUUID();
266
+ const responseTimeoutMs = 10_000;
267
+ await rpcServerManager.handleIncomingDataStream(
268
+ mockTextStreamReader(new Array(20_000).fill('A').join('')),
269
+ 'caller-identity',
270
+ makeDataStreamAttrs(requestId, 'test-method', responseTimeoutMs),
271
+ );
272
+
273
+ // The first event is an acknowledgement of the request
274
+ const ackEvent = await managerEvents.waitFor('sendDataPacket');
275
+ assert(ackEvent.packet.value.case === 'rpcAck');
276
+ expect(ackEvent.packet.value.value.requestId).toStrictEqual(requestId);
277
+
278
+ // The response should have been sent via data stream, not packet
279
+ expect(managerEvents.areThereBufferedEvents('sendDataPacket')).toBe(false);
280
+ expect(outgoingDataStreamManager.streamText).toHaveBeenCalledWith(
281
+ expect.objectContaining({
282
+ topic: RPC_RESPONSE_DATA_STREAM_TOPIC,
283
+ destinationIdentities: ['caller-identity'],
284
+ attributes: { [RpcRequestAttrs.RPC_REQUEST_ID]: requestId },
285
+ }),
286
+ );
287
+ expect(mockStreamTextWriter.write).toHaveBeenCalledWith(new Array(20_000).fill('B').join(''));
288
+ expect(mockStreamTextWriter.close).toHaveBeenCalled();
289
+ });
290
+
291
+ it('should register an RPC method handler', async () => {
292
+ const managerEvents = subscribeToEvents<RpcServerManagerCallbacks>(rpcServerManager, [
293
+ 'sendDataPacket',
294
+ ]);
295
+
296
+ const methodName = 'testMethod';
297
+ const handler = vi.fn().mockResolvedValue('test response');
298
+
299
+ rpcServerManager.registerRpcMethod(methodName, handler);
300
+
301
+ await rpcServerManager.handleIncomingDataStream(
302
+ mockTextStreamReader('test payload'),
303
+ 'remote-identity',
304
+ makeDataStreamAttrs('test-request-id', methodName, 5000),
305
+ );
306
+
307
+ expect(handler).toHaveBeenCalledWith({
308
+ requestId: 'test-request-id',
309
+ callerIdentity: 'remote-identity',
310
+ payload: 'test payload',
311
+ responseTimeout: 5000,
312
+ });
313
+
314
+ // Ensure the ack was sent
315
+ const ackEvent = await managerEvents.waitFor('sendDataPacket');
316
+ expect(ackEvent.packet.value.case).toStrictEqual('rpcAck');
317
+
318
+ // Response goes via data stream, not packet
319
+ expect(managerEvents.areThereBufferedEvents('sendDataPacket')).toBe(false);
320
+ expect(outgoingDataStreamManager.streamText).toHaveBeenCalled();
321
+ });
322
+
323
+ it('should catch and transform unhandled errors in the RPC method handler', async () => {
324
+ const managerEvents = subscribeToEvents<RpcServerManagerCallbacks>(rpcServerManager, [
325
+ 'sendDataPacket',
326
+ ]);
327
+
328
+ const methodName = 'errorMethod';
329
+ const errorMessage = 'Test error';
330
+ const handler = async () => {
331
+ throw new Error(errorMessage);
332
+ };
333
+
334
+ rpcServerManager.registerRpcMethod(methodName, handler);
335
+
336
+ await rpcServerManager.handleIncomingDataStream(
337
+ mockTextStreamReader('test payload'),
338
+ 'remote-identity',
339
+ makeDataStreamAttrs('test-error-request-id', methodName, 5000),
340
+ );
341
+
342
+ // Ensure the first event was for the ack
343
+ const ackEvent = await managerEvents.waitFor('sendDataPacket');
344
+ assert(ackEvent.packet.value.case === 'rpcAck');
345
+
346
+ // Error responses always go via packet, even for v2 callers
347
+ const errorEvent = await managerEvents.waitFor('sendDataPacket');
348
+ assert(errorEvent.packet.value.case === 'rpcResponse');
349
+ assert(errorEvent.packet.value.value.value.case === 'error');
350
+ const errorResponse = errorEvent.packet.value.value.value.value;
351
+ expect(errorResponse.code).toStrictEqual(RpcError.ErrorCode.APPLICATION_ERROR);
352
+
353
+ expect(managerEvents.areThereBufferedEvents('sendDataPacket')).toBe(false);
354
+ });
355
+
356
+ it('should pass through RpcError thrown by the RPC method handler', async () => {
357
+ const managerEvents = subscribeToEvents<RpcServerManagerCallbacks>(rpcServerManager, [
358
+ 'sendDataPacket',
359
+ ]);
360
+
361
+ const methodName = 'rpcErrorMethod';
362
+ const errorCode = 101;
363
+ const errorMessage = 'some-error-message';
364
+ const handler = async () => {
365
+ throw new RpcError(errorCode, errorMessage);
366
+ };
367
+
368
+ rpcServerManager.registerRpcMethod(methodName, handler);
369
+
370
+ await rpcServerManager.handleIncomingDataStream(
371
+ mockTextStreamReader('test payload'),
372
+ 'remote-identity',
373
+ makeDataStreamAttrs('test-rpc-error-request-id', methodName, 5000),
374
+ );
375
+
376
+ // Ensure the first event was for the ack
377
+ const ackEvent = await managerEvents.waitFor('sendDataPacket');
378
+ assert(ackEvent.packet.value.case === 'rpcAck');
379
+
380
+ // Error responses always go via packet, even for v2 callers
381
+ const errorEvent = await managerEvents.waitFor('sendDataPacket');
382
+ assert(errorEvent.packet.value.case === 'rpcResponse');
383
+ assert(errorEvent.packet.value.value.value.case === 'error');
384
+ const errorResponse = errorEvent.packet.value.value.value.value;
385
+ expect(errorResponse.code).toStrictEqual(errorCode);
386
+ expect(errorResponse.message).toStrictEqual(errorMessage);
387
+
388
+ expect(managerEvents.areThereBufferedEvents('sendDataPacket')).toBe(false);
389
+ });
390
+
391
+ it('should ack and respond with UNSUPPORTED_METHOD for an unregistered method', async () => {
392
+ const managerEvents = subscribeToEvents<RpcServerManagerCallbacks>(rpcServerManager, [
393
+ 'sendDataPacket',
394
+ ]);
395
+
396
+ // Intentionally do not call registerRpcMethod for "unknown-method".
397
+
398
+ const requestId = crypto.randomUUID();
399
+ await rpcServerManager.handleIncomingDataStream(
400
+ mockTextStreamReader('request payload'),
401
+ 'caller-identity',
402
+ makeDataStreamAttrs(requestId, 'unknown-method', 5000),
403
+ );
404
+
405
+ // Ack must be sent first so the caller knows the handler is alive.
406
+ const ackEvent = await managerEvents.waitFor('sendDataPacket');
407
+ assert(ackEvent.packet.value.case === 'rpcAck');
408
+ expect(ackEvent.packet.value.value.requestId).toStrictEqual(requestId);
409
+
410
+ // Then a v1 RpcResponse packet with UNSUPPORTED_METHOD - never a data stream.
411
+ const errorEvent = await managerEvents.waitFor('sendDataPacket');
412
+ assert(errorEvent.packet.value.case === 'rpcResponse');
413
+ assert(errorEvent.packet.value.value.value.case === 'error');
414
+ const errorResponse = errorEvent.packet.value.value.value.value;
415
+ expect(errorResponse.code).toStrictEqual(RpcError.ErrorCode.UNSUPPORTED_METHOD);
416
+
417
+ expect(outgoingDataStreamManager.streamText).not.toHaveBeenCalled();
418
+ expect(managerEvents.areThereBufferedEvents('sendDataPacket')).toBe(false);
419
+ });
420
+ });
421
+
422
+ describe('v1 -> v2', () => {
423
+ it('should use v1 protocol (RpcResponse packet) when responding to a v1 caller', async () => {
424
+ const outgoingDataStreamManager = new OutgoingDataStreamManager(
425
+ {} as unknown as RTCEngine,
426
+ log,
427
+ );
428
+ const streamTextSpy = vi.spyOn(outgoingDataStreamManager, 'streamText');
429
+
430
+ const rpcServerManager = new RpcServerManager(
431
+ log,
432
+ outgoingDataStreamManager,
433
+ (_identity) => CLIENT_PROTOCOL_DEFAULT,
434
+ );
435
+
436
+ const managerEvents = subscribeToEvents<RpcServerManagerCallbacks>(rpcServerManager, [
437
+ 'sendDataPacket',
438
+ ]);
439
+
440
+ const handler = async () => 'response payload';
441
+ rpcServerManager.registerRpcMethod('test-method', handler);
442
+
443
+ const requestId = crypto.randomUUID();
444
+ await rpcServerManager.handleIncomingRpcRequest(
445
+ 'v1-caller',
446
+ new RpcRequest({
447
+ id: requestId,
448
+ method: 'test-method',
449
+ payload: 'request payload',
450
+ responseTimeoutMs: 10_000,
451
+ version: 1,
452
+ }),
453
+ );
454
+
455
+ // Ack via packet
456
+ const ackEvent = await managerEvents.waitFor('sendDataPacket');
457
+ assert(ackEvent.packet.value.case === 'rpcAck');
458
+
459
+ // Response should be a v1 RpcResponse packet, not a data stream
460
+ expect(streamTextSpy).not.toHaveBeenCalled();
461
+ const responseEvent = await managerEvents.waitFor('sendDataPacket');
462
+ assert(responseEvent.packet.value.case === 'rpcResponse');
463
+ const rpcResponse = responseEvent.packet.value.value;
464
+ expect(rpcResponse.requestId).toStrictEqual(requestId);
465
+ assert(rpcResponse.value.case === 'payload');
466
+ expect(rpcResponse.value.value).toStrictEqual('response payload');
467
+
468
+ expect(managerEvents.areThereBufferedEvents('sendDataPacket')).toBe(false);
469
+ });
470
+ });
471
+ });