livekit-client 2.5.10 → 2.6.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.
- package/README.md +54 -0
- package/dist/livekit-client.esm.mjs +418 -32
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/room/PCTransport.d.ts +2 -0
- package/dist/src/room/PCTransport.d.ts.map +1 -1
- package/dist/src/room/Room.d.ts.map +1 -1
- package/dist/src/room/participant/LocalParticipant.d.ts +56 -0
- package/dist/src/room/participant/LocalParticipant.d.ts.map +1 -1
- package/dist/src/room/rpc.d.ts +96 -0
- package/dist/src/room/rpc.d.ts.map +1 -0
- package/dist/src/room/track/utils.d.ts +2 -2
- package/dist/src/room/track/utils.d.ts.map +1 -1
- package/dist/ts4.2/src/index.d.ts +2 -0
- package/dist/ts4.2/src/room/PCTransport.d.ts +2 -0
- package/dist/ts4.2/src/room/participant/LocalParticipant.d.ts +56 -0
- package/dist/ts4.2/src/room/rpc.d.ts +96 -0
- package/dist/ts4.2/src/room/track/utils.d.ts +2 -2
- package/package.json +2 -2
- package/src/index.ts +2 -0
- package/src/room/PCTransport.ts +42 -29
- package/src/room/Room.ts +1 -0
- package/src/room/participant/LocalParticipant.test.ts +304 -0
- package/src/room/participant/LocalParticipant.ts +340 -1
- package/src/room/rpc.ts +172 -0
- package/src/room/track/utils.ts +1 -6
@@ -9,6 +9,9 @@ import {
|
|
9
9
|
ParticipantPermission,
|
10
10
|
RequestResponse,
|
11
11
|
RequestResponse_Reason,
|
12
|
+
RpcAck,
|
13
|
+
RpcRequest,
|
14
|
+
RpcResponse,
|
12
15
|
SimulcastCodec,
|
13
16
|
SipDTMF,
|
14
17
|
SubscribedQualityUpdate,
|
@@ -29,6 +32,13 @@ import {
|
|
29
32
|
UnexpectedConnectionState,
|
30
33
|
} from '../errors';
|
31
34
|
import { EngineEvent, ParticipantEvent, TrackEvent } from '../events';
|
35
|
+
import {
|
36
|
+
MAX_PAYLOAD_BYTES,
|
37
|
+
type PerformRpcParams,
|
38
|
+
RpcError,
|
39
|
+
type RpcInvocationData,
|
40
|
+
byteLength,
|
41
|
+
} from '../rpc';
|
32
42
|
import LocalAudioTrack from '../track/LocalAudioTrack';
|
33
43
|
import LocalTrack from '../track/LocalTrack';
|
34
44
|
import LocalTrackPublication from '../track/LocalTrackPublication';
|
@@ -54,6 +64,7 @@ import {
|
|
54
64
|
import type { ChatMessage, DataPublishOptions } from '../types';
|
55
65
|
import {
|
56
66
|
Future,
|
67
|
+
compareVersions,
|
57
68
|
isE2EESimulcastSupported,
|
58
69
|
isFireFox,
|
59
70
|
isSVCCodec,
|
@@ -119,6 +130,18 @@ export default class LocalParticipant extends Participant {
|
|
119
130
|
|
120
131
|
private enabledPublishVideoCodecs: Codec[] = [];
|
121
132
|
|
133
|
+
private rpcHandlers: Map<string, (data: RpcInvocationData) => Promise<string>> = new Map();
|
134
|
+
|
135
|
+
private pendingAcks = new Map<string, { resolve: () => void; participantIdentity: string }>();
|
136
|
+
|
137
|
+
private pendingResponses = new Map<
|
138
|
+
string,
|
139
|
+
{
|
140
|
+
resolve: (payload: string | null, error: RpcError | null) => void;
|
141
|
+
participantIdentity: string;
|
142
|
+
}
|
143
|
+
>();
|
144
|
+
|
122
145
|
/** @internal */
|
123
146
|
constructor(sid: string, identity: string, engine: RTCEngine, options: InternalRoomOptions) {
|
124
147
|
super(sid, identity, undefined, undefined, {
|
@@ -187,7 +210,8 @@ export default class LocalParticipant extends Participant {
|
|
187
210
|
.on(EngineEvent.LocalTrackUnpublished, this.handleLocalTrackUnpublished)
|
188
211
|
.on(EngineEvent.SubscribedQualityUpdate, this.handleSubscribedQualityUpdate)
|
189
212
|
.on(EngineEvent.Disconnected, this.handleDisconnected)
|
190
|
-
.on(EngineEvent.SignalRequestResponse, this.handleSignalRequestResponse)
|
213
|
+
.on(EngineEvent.SignalRequestResponse, this.handleSignalRequestResponse)
|
214
|
+
.on(EngineEvent.DataPacketReceived, this.handleDataPacket);
|
191
215
|
}
|
192
216
|
|
193
217
|
private handleReconnecting = () => {
|
@@ -221,6 +245,38 @@ export default class LocalParticipant extends Participant {
|
|
221
245
|
}
|
222
246
|
};
|
223
247
|
|
248
|
+
private handleDataPacket = (packet: DataPacket) => {
|
249
|
+
switch (packet.value.case) {
|
250
|
+
case 'rpcRequest':
|
251
|
+
let rpcRequest = packet.value.value as RpcRequest;
|
252
|
+
this.handleIncomingRpcRequest(
|
253
|
+
packet.participantIdentity,
|
254
|
+
rpcRequest.id,
|
255
|
+
rpcRequest.method,
|
256
|
+
rpcRequest.payload,
|
257
|
+
rpcRequest.responseTimeoutMs,
|
258
|
+
rpcRequest.version,
|
259
|
+
);
|
260
|
+
break;
|
261
|
+
case 'rpcResponse':
|
262
|
+
let rpcResponse = packet.value.value as RpcResponse;
|
263
|
+
let payload: string | null = null;
|
264
|
+
let error: RpcError | null = null;
|
265
|
+
|
266
|
+
if (rpcResponse.value.case === 'payload') {
|
267
|
+
payload = rpcResponse.value.value;
|
268
|
+
} else if (rpcResponse.value.case === 'error') {
|
269
|
+
error = RpcError.fromProto(rpcResponse.value.value);
|
270
|
+
}
|
271
|
+
this.handleIncomingRpcResponse(rpcResponse.requestId, payload, error);
|
272
|
+
break;
|
273
|
+
case 'rpcAck':
|
274
|
+
let rpcAck = packet.value.value as RpcAck;
|
275
|
+
this.handleIncomingRpcAck(rpcAck.requestId);
|
276
|
+
break;
|
277
|
+
}
|
278
|
+
};
|
279
|
+
|
224
280
|
/**
|
225
281
|
* Sets and updates the metadata of the local participant.
|
226
282
|
* Note: this requires `canUpdateOwnMetadata` permission.
|
@@ -1415,6 +1471,121 @@ export default class LocalParticipant extends Participant {
|
|
1415
1471
|
return msg;
|
1416
1472
|
}
|
1417
1473
|
|
1474
|
+
/**
|
1475
|
+
* Initiate an RPC call to a remote participant
|
1476
|
+
* @param params - Parameters for initiating the RPC call, see {@link PerformRpcParams}
|
1477
|
+
* @returns A promise that resolves with the response payload or rejects with an error.
|
1478
|
+
* @throws Error on failure. Details in `message`.
|
1479
|
+
*/
|
1480
|
+
async performRpc({
|
1481
|
+
destinationIdentity,
|
1482
|
+
method,
|
1483
|
+
payload,
|
1484
|
+
responseTimeout = 10000,
|
1485
|
+
}: PerformRpcParams): Promise<string> {
|
1486
|
+
const maxRoundTripLatency = 2000;
|
1487
|
+
|
1488
|
+
return new Promise(async (resolve, reject) => {
|
1489
|
+
if (byteLength(payload) > MAX_PAYLOAD_BYTES) {
|
1490
|
+
reject(RpcError.builtIn('REQUEST_PAYLOAD_TOO_LARGE'));
|
1491
|
+
return;
|
1492
|
+
}
|
1493
|
+
|
1494
|
+
if (
|
1495
|
+
this.engine.latestJoinResponse?.serverInfo?.version &&
|
1496
|
+
compareVersions(this.engine.latestJoinResponse?.serverInfo?.version, '1.8.0') < 0
|
1497
|
+
) {
|
1498
|
+
reject(RpcError.builtIn('UNSUPPORTED_SERVER'));
|
1499
|
+
return;
|
1500
|
+
}
|
1501
|
+
|
1502
|
+
const id = crypto.randomUUID();
|
1503
|
+
await this.publishRpcRequest(
|
1504
|
+
destinationIdentity,
|
1505
|
+
id,
|
1506
|
+
method,
|
1507
|
+
payload,
|
1508
|
+
responseTimeout - maxRoundTripLatency,
|
1509
|
+
);
|
1510
|
+
|
1511
|
+
const ackTimeoutId = setTimeout(() => {
|
1512
|
+
this.pendingAcks.delete(id);
|
1513
|
+
reject(RpcError.builtIn('CONNECTION_TIMEOUT'));
|
1514
|
+
this.pendingResponses.delete(id);
|
1515
|
+
clearTimeout(responseTimeoutId);
|
1516
|
+
}, maxRoundTripLatency);
|
1517
|
+
|
1518
|
+
this.pendingAcks.set(id, {
|
1519
|
+
resolve: () => {
|
1520
|
+
clearTimeout(ackTimeoutId);
|
1521
|
+
},
|
1522
|
+
participantIdentity: destinationIdentity,
|
1523
|
+
});
|
1524
|
+
|
1525
|
+
const responseTimeoutId = setTimeout(() => {
|
1526
|
+
this.pendingResponses.delete(id);
|
1527
|
+
reject(RpcError.builtIn('RESPONSE_TIMEOUT'));
|
1528
|
+
}, responseTimeout);
|
1529
|
+
|
1530
|
+
this.pendingResponses.set(id, {
|
1531
|
+
resolve: (responsePayload: string | null, responseError: RpcError | null) => {
|
1532
|
+
clearTimeout(responseTimeoutId);
|
1533
|
+
if (this.pendingAcks.has(id)) {
|
1534
|
+
console.warn('RPC response received before ack', id);
|
1535
|
+
this.pendingAcks.delete(id);
|
1536
|
+
clearTimeout(ackTimeoutId);
|
1537
|
+
}
|
1538
|
+
|
1539
|
+
if (responseError) {
|
1540
|
+
reject(responseError);
|
1541
|
+
} else {
|
1542
|
+
resolve(responsePayload ?? '');
|
1543
|
+
}
|
1544
|
+
},
|
1545
|
+
participantIdentity: destinationIdentity,
|
1546
|
+
});
|
1547
|
+
});
|
1548
|
+
}
|
1549
|
+
|
1550
|
+
/**
|
1551
|
+
* Establishes the participant as a receiver for calls of the specified RPC method.
|
1552
|
+
* Will overwrite any existing callback for the same method.
|
1553
|
+
*
|
1554
|
+
* @param method - The name of the indicated RPC method
|
1555
|
+
* @param handler - Will be invoked when an RPC request for this method is received
|
1556
|
+
* @returns A promise that resolves when the method is successfully registered
|
1557
|
+
*
|
1558
|
+
* @example
|
1559
|
+
* ```typescript
|
1560
|
+
* room.localParticipant?.registerRpcMethod(
|
1561
|
+
* 'greet',
|
1562
|
+
* async (data: RpcInvocationData) => {
|
1563
|
+
* console.log(`Received greeting from ${data.callerIdentity}: ${data.payload}`);
|
1564
|
+
* return `Hello, ${data.callerIdentity}!`;
|
1565
|
+
* }
|
1566
|
+
* );
|
1567
|
+
* ```
|
1568
|
+
*
|
1569
|
+
* The handler should return a Promise that resolves to a string.
|
1570
|
+
* If unable to respond within `responseTimeout`, the request will result in an error on the caller's side.
|
1571
|
+
*
|
1572
|
+
* You may throw errors of type `RpcError` with a string `message` in the handler,
|
1573
|
+
* and they will be received on the caller's side with the message intact.
|
1574
|
+
* Other errors thrown in your handler will not be transmitted as-is, and will instead arrive to the caller as `1500` ("Application Error").
|
1575
|
+
*/
|
1576
|
+
registerRpcMethod(method: string, handler: (data: RpcInvocationData) => Promise<string>) {
|
1577
|
+
this.rpcHandlers.set(method, handler);
|
1578
|
+
}
|
1579
|
+
|
1580
|
+
/**
|
1581
|
+
* Unregisters a previously registered RPC method.
|
1582
|
+
*
|
1583
|
+
* @param method - The name of the RPC method to unregister
|
1584
|
+
*/
|
1585
|
+
unregisterRpcMethod(method: string) {
|
1586
|
+
this.rpcHandlers.delete(method);
|
1587
|
+
}
|
1588
|
+
|
1418
1589
|
/**
|
1419
1590
|
* Control who can subscribe to LocalParticipant's published tracks.
|
1420
1591
|
*
|
@@ -1443,6 +1614,174 @@ export default class LocalParticipant extends Participant {
|
|
1443
1614
|
}
|
1444
1615
|
}
|
1445
1616
|
|
1617
|
+
private handleIncomingRpcAck(requestId: string) {
|
1618
|
+
const handler = this.pendingAcks.get(requestId);
|
1619
|
+
if (handler) {
|
1620
|
+
handler.resolve();
|
1621
|
+
this.pendingAcks.delete(requestId);
|
1622
|
+
} else {
|
1623
|
+
console.error('Ack received for unexpected RPC request', requestId);
|
1624
|
+
}
|
1625
|
+
}
|
1626
|
+
|
1627
|
+
private handleIncomingRpcResponse(
|
1628
|
+
requestId: string,
|
1629
|
+
payload: string | null,
|
1630
|
+
error: RpcError | null,
|
1631
|
+
) {
|
1632
|
+
const handler = this.pendingResponses.get(requestId);
|
1633
|
+
if (handler) {
|
1634
|
+
handler.resolve(payload, error);
|
1635
|
+
this.pendingResponses.delete(requestId);
|
1636
|
+
} else {
|
1637
|
+
console.error('Response received for unexpected RPC request', requestId);
|
1638
|
+
}
|
1639
|
+
}
|
1640
|
+
|
1641
|
+
private async handleIncomingRpcRequest(
|
1642
|
+
callerIdentity: string,
|
1643
|
+
requestId: string,
|
1644
|
+
method: string,
|
1645
|
+
payload: string,
|
1646
|
+
responseTimeout: number,
|
1647
|
+
version: number,
|
1648
|
+
) {
|
1649
|
+
await this.publishRpcAck(callerIdentity, requestId);
|
1650
|
+
|
1651
|
+
if (version !== 1) {
|
1652
|
+
await this.publishRpcResponse(
|
1653
|
+
callerIdentity,
|
1654
|
+
requestId,
|
1655
|
+
null,
|
1656
|
+
RpcError.builtIn('UNSUPPORTED_VERSION'),
|
1657
|
+
);
|
1658
|
+
return;
|
1659
|
+
}
|
1660
|
+
|
1661
|
+
const handler = this.rpcHandlers.get(method);
|
1662
|
+
|
1663
|
+
if (!handler) {
|
1664
|
+
await this.publishRpcResponse(
|
1665
|
+
callerIdentity,
|
1666
|
+
requestId,
|
1667
|
+
null,
|
1668
|
+
RpcError.builtIn('UNSUPPORTED_METHOD'),
|
1669
|
+
);
|
1670
|
+
return;
|
1671
|
+
}
|
1672
|
+
|
1673
|
+
let responseError: RpcError | null = null;
|
1674
|
+
let responsePayload: string | null = null;
|
1675
|
+
|
1676
|
+
try {
|
1677
|
+
const response = await handler({
|
1678
|
+
requestId,
|
1679
|
+
callerIdentity,
|
1680
|
+
payload,
|
1681
|
+
responseTimeout,
|
1682
|
+
});
|
1683
|
+
if (byteLength(response) > MAX_PAYLOAD_BYTES) {
|
1684
|
+
responseError = RpcError.builtIn('RESPONSE_PAYLOAD_TOO_LARGE');
|
1685
|
+
console.warn(`RPC Response payload too large for ${method}`);
|
1686
|
+
} else {
|
1687
|
+
responsePayload = response;
|
1688
|
+
}
|
1689
|
+
} catch (error) {
|
1690
|
+
if (error instanceof RpcError) {
|
1691
|
+
responseError = error;
|
1692
|
+
} else {
|
1693
|
+
console.warn(
|
1694
|
+
`Uncaught error returned by RPC handler for ${method}. Returning APPLICATION_ERROR instead.`,
|
1695
|
+
error,
|
1696
|
+
);
|
1697
|
+
responseError = RpcError.builtIn('APPLICATION_ERROR');
|
1698
|
+
}
|
1699
|
+
}
|
1700
|
+
await this.publishRpcResponse(callerIdentity, requestId, responsePayload, responseError);
|
1701
|
+
}
|
1702
|
+
|
1703
|
+
/** @internal */
|
1704
|
+
private async publishRpcRequest(
|
1705
|
+
destinationIdentity: string,
|
1706
|
+
requestId: string,
|
1707
|
+
method: string,
|
1708
|
+
payload: string,
|
1709
|
+
responseTimeout: number,
|
1710
|
+
) {
|
1711
|
+
const packet = new DataPacket({
|
1712
|
+
destinationIdentities: [destinationIdentity],
|
1713
|
+
kind: DataPacket_Kind.RELIABLE,
|
1714
|
+
value: {
|
1715
|
+
case: 'rpcRequest',
|
1716
|
+
value: new RpcRequest({
|
1717
|
+
id: requestId,
|
1718
|
+
method,
|
1719
|
+
payload,
|
1720
|
+
responseTimeoutMs: responseTimeout,
|
1721
|
+
version: 1,
|
1722
|
+
}),
|
1723
|
+
},
|
1724
|
+
});
|
1725
|
+
|
1726
|
+
await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
|
1727
|
+
}
|
1728
|
+
|
1729
|
+
/** @internal */
|
1730
|
+
private async publishRpcResponse(
|
1731
|
+
destinationIdentity: string,
|
1732
|
+
requestId: string,
|
1733
|
+
payload: string | null,
|
1734
|
+
error: RpcError | null,
|
1735
|
+
) {
|
1736
|
+
const packet = new DataPacket({
|
1737
|
+
destinationIdentities: [destinationIdentity],
|
1738
|
+
kind: DataPacket_Kind.RELIABLE,
|
1739
|
+
value: {
|
1740
|
+
case: 'rpcResponse',
|
1741
|
+
value: new RpcResponse({
|
1742
|
+
requestId,
|
1743
|
+
value: error
|
1744
|
+
? { case: 'error', value: error.toProto() }
|
1745
|
+
: { case: 'payload', value: payload ?? '' },
|
1746
|
+
}),
|
1747
|
+
},
|
1748
|
+
});
|
1749
|
+
|
1750
|
+
await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
|
1751
|
+
}
|
1752
|
+
|
1753
|
+
/** @internal */
|
1754
|
+
private async publishRpcAck(destinationIdentity: string, requestId: string) {
|
1755
|
+
const packet = new DataPacket({
|
1756
|
+
destinationIdentities: [destinationIdentity],
|
1757
|
+
kind: DataPacket_Kind.RELIABLE,
|
1758
|
+
value: {
|
1759
|
+
case: 'rpcAck',
|
1760
|
+
value: new RpcAck({
|
1761
|
+
requestId,
|
1762
|
+
}),
|
1763
|
+
},
|
1764
|
+
});
|
1765
|
+
|
1766
|
+
await this.engine.sendDataPacket(packet, DataPacket_Kind.RELIABLE);
|
1767
|
+
}
|
1768
|
+
|
1769
|
+
/** @internal */
|
1770
|
+
handleParticipantDisconnected(participantIdentity: string) {
|
1771
|
+
for (const [id, { participantIdentity: pendingIdentity }] of this.pendingAcks) {
|
1772
|
+
if (pendingIdentity === participantIdentity) {
|
1773
|
+
this.pendingAcks.delete(id);
|
1774
|
+
}
|
1775
|
+
}
|
1776
|
+
|
1777
|
+
for (const [id, { participantIdentity: pendingIdentity, resolve }] of this.pendingResponses) {
|
1778
|
+
if (pendingIdentity === participantIdentity) {
|
1779
|
+
resolve(null, RpcError.builtIn('RECIPIENT_DISCONNECTED'));
|
1780
|
+
this.pendingResponses.delete(id);
|
1781
|
+
}
|
1782
|
+
}
|
1783
|
+
}
|
1784
|
+
|
1446
1785
|
/** @internal */
|
1447
1786
|
setEnabledPublishCodecs(codecs: Codec[]) {
|
1448
1787
|
this.enabledPublishVideoCodecs = codecs.filter(
|
package/src/room/rpc.ts
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
// SPDX-FileCopyrightText: 2024 LiveKit, Inc.
|
2
|
+
//
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
4
|
+
import { RpcError as RpcError_Proto } from '@livekit/protocol';
|
5
|
+
|
6
|
+
/** Parameters for initiating an RPC call */
|
7
|
+
export interface PerformRpcParams {
|
8
|
+
/** The `identity` of the destination participant */
|
9
|
+
destinationIdentity: string;
|
10
|
+
/** The method name to call */
|
11
|
+
method: string;
|
12
|
+
/** The method payload */
|
13
|
+
payload: string;
|
14
|
+
/** Timeout for receiving a response after initial connection (milliseconds). Default: 10000 */
|
15
|
+
responseTimeout?: number;
|
16
|
+
}
|
17
|
+
|
18
|
+
/**
|
19
|
+
* Data passed to method handler for incoming RPC invocations
|
20
|
+
*/
|
21
|
+
export interface RpcInvocationData {
|
22
|
+
/**
|
23
|
+
* The unique request ID. Will match at both sides of the call, useful for debugging or logging.
|
24
|
+
*/
|
25
|
+
requestId: string;
|
26
|
+
|
27
|
+
/**
|
28
|
+
* The unique participant identity of the caller.
|
29
|
+
*/
|
30
|
+
callerIdentity: string;
|
31
|
+
|
32
|
+
/**
|
33
|
+
* The payload of the request. User-definable format, typically JSON.
|
34
|
+
*/
|
35
|
+
payload: string;
|
36
|
+
|
37
|
+
/**
|
38
|
+
* The maximum time the caller will wait for a response.
|
39
|
+
*/
|
40
|
+
responseTimeout: number;
|
41
|
+
}
|
42
|
+
|
43
|
+
/**
|
44
|
+
* Specialized error handling for RPC methods.
|
45
|
+
*
|
46
|
+
* Instances of this type, when thrown in a method handler, will have their `message`
|
47
|
+
* serialized and sent across the wire. The sender will receive an equivalent error on the other side.
|
48
|
+
*
|
49
|
+
* Built-in types are included but developers may use any string, with a max length of 256 bytes.
|
50
|
+
*/
|
51
|
+
|
52
|
+
export class RpcError extends Error {
|
53
|
+
static MAX_MESSAGE_BYTES = 256;
|
54
|
+
|
55
|
+
static MAX_DATA_BYTES = 15360; // 15 KB
|
56
|
+
|
57
|
+
code: number;
|
58
|
+
|
59
|
+
data?: string;
|
60
|
+
|
61
|
+
/**
|
62
|
+
* Creates an error object with the given code and message, plus an optional data payload.
|
63
|
+
*
|
64
|
+
* If thrown in an RPC method handler, the error will be sent back to the caller.
|
65
|
+
*
|
66
|
+
* Error codes 1001-1999 are reserved for built-in errors (see RpcError.ErrorCode for their meanings).
|
67
|
+
*/
|
68
|
+
constructor(code: number, message: string, data?: string) {
|
69
|
+
super(message);
|
70
|
+
this.code = code;
|
71
|
+
this.message = truncateBytes(message, RpcError.MAX_MESSAGE_BYTES);
|
72
|
+
this.data = data ? truncateBytes(data, RpcError.MAX_DATA_BYTES) : undefined;
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* @internal
|
77
|
+
*/
|
78
|
+
static fromProto(proto: RpcError_Proto) {
|
79
|
+
return new RpcError(proto.code, proto.message, proto.data);
|
80
|
+
}
|
81
|
+
|
82
|
+
/**
|
83
|
+
* @internal
|
84
|
+
*/
|
85
|
+
toProto() {
|
86
|
+
return new RpcError_Proto({
|
87
|
+
code: this.code as number,
|
88
|
+
message: this.message,
|
89
|
+
data: this.data,
|
90
|
+
});
|
91
|
+
}
|
92
|
+
|
93
|
+
static ErrorCode = {
|
94
|
+
APPLICATION_ERROR: 1500,
|
95
|
+
CONNECTION_TIMEOUT: 1501,
|
96
|
+
RESPONSE_TIMEOUT: 1502,
|
97
|
+
RECIPIENT_DISCONNECTED: 1503,
|
98
|
+
RESPONSE_PAYLOAD_TOO_LARGE: 1504,
|
99
|
+
SEND_FAILED: 1505,
|
100
|
+
|
101
|
+
UNSUPPORTED_METHOD: 1400,
|
102
|
+
RECIPIENT_NOT_FOUND: 1401,
|
103
|
+
REQUEST_PAYLOAD_TOO_LARGE: 1402,
|
104
|
+
UNSUPPORTED_SERVER: 1403,
|
105
|
+
UNSUPPORTED_VERSION: 1404,
|
106
|
+
} as const;
|
107
|
+
|
108
|
+
/**
|
109
|
+
* @internal
|
110
|
+
*/
|
111
|
+
static ErrorMessage: Record<keyof typeof RpcError.ErrorCode, string> = {
|
112
|
+
APPLICATION_ERROR: 'Application error in method handler',
|
113
|
+
CONNECTION_TIMEOUT: 'Connection timeout',
|
114
|
+
RESPONSE_TIMEOUT: 'Response timeout',
|
115
|
+
RECIPIENT_DISCONNECTED: 'Recipient disconnected',
|
116
|
+
RESPONSE_PAYLOAD_TOO_LARGE: 'Response payload too large',
|
117
|
+
SEND_FAILED: 'Failed to send',
|
118
|
+
|
119
|
+
UNSUPPORTED_METHOD: 'Method not supported at destination',
|
120
|
+
RECIPIENT_NOT_FOUND: 'Recipient not found',
|
121
|
+
REQUEST_PAYLOAD_TOO_LARGE: 'Request payload too large',
|
122
|
+
UNSUPPORTED_SERVER: 'RPC not supported by server',
|
123
|
+
UNSUPPORTED_VERSION: 'Unsupported RPC version',
|
124
|
+
} as const;
|
125
|
+
|
126
|
+
/**
|
127
|
+
* Creates an error object from the code, with an auto-populated message.
|
128
|
+
*
|
129
|
+
* @internal
|
130
|
+
*/
|
131
|
+
static builtIn(key: keyof typeof RpcError.ErrorCode, data?: string): RpcError {
|
132
|
+
return new RpcError(RpcError.ErrorCode[key], RpcError.ErrorMessage[key], data);
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
/*
|
137
|
+
* Maximum payload size for RPC requests and responses. If a payload exceeds this size,
|
138
|
+
* the RPC call will fail with a REQUEST_PAYLOAD_TOO_LARGE(1402) or RESPONSE_PAYLOAD_TOO_LARGE(1504) error.
|
139
|
+
*/
|
140
|
+
export const MAX_PAYLOAD_BYTES = 15360; // 15 KB
|
141
|
+
|
142
|
+
/**
|
143
|
+
* @internal
|
144
|
+
*/
|
145
|
+
export function byteLength(str: string): number {
|
146
|
+
const encoder = new TextEncoder();
|
147
|
+
return encoder.encode(str).length;
|
148
|
+
}
|
149
|
+
|
150
|
+
/**
|
151
|
+
* @internal
|
152
|
+
*/
|
153
|
+
export function truncateBytes(str: string, maxBytes: number): string {
|
154
|
+
if (byteLength(str) <= maxBytes) {
|
155
|
+
return str;
|
156
|
+
}
|
157
|
+
|
158
|
+
let low = 0;
|
159
|
+
let high = str.length;
|
160
|
+
const encoder = new TextEncoder();
|
161
|
+
|
162
|
+
while (low < high) {
|
163
|
+
const mid = Math.floor((low + high + 1) / 2);
|
164
|
+
if (encoder.encode(str.slice(0, mid)).length <= maxBytes) {
|
165
|
+
low = mid;
|
166
|
+
} else {
|
167
|
+
high = mid - 1;
|
168
|
+
}
|
169
|
+
}
|
170
|
+
|
171
|
+
return str.slice(0, low);
|
172
|
+
}
|
package/src/room/track/utils.ts
CHANGED
@@ -9,7 +9,6 @@ import {
|
|
9
9
|
type ScreenShareCaptureOptions,
|
10
10
|
type VideoCaptureOptions,
|
11
11
|
type VideoCodec,
|
12
|
-
videoCodecs,
|
13
12
|
} from './options';
|
14
13
|
import type { AudioTrack } from './types';
|
15
14
|
|
@@ -188,11 +187,7 @@ export function screenCaptureToDisplayMediaStreamOptions(
|
|
188
187
|
}
|
189
188
|
|
190
189
|
export function mimeTypeToVideoCodecString(mimeType: string) {
|
191
|
-
|
192
|
-
if (!videoCodecs.includes(codec)) {
|
193
|
-
throw Error(`Video codec not supported: ${codec}`);
|
194
|
-
}
|
195
|
-
return codec;
|
190
|
+
return mimeType.split('/')[1].toLowerCase() as VideoCodec;
|
196
191
|
}
|
197
192
|
|
198
193
|
export function getTrackPublicationInfo<T extends TrackPublication>(
|