@voltkit-rtc/client 0.6.0 → 0.7.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/CHANGELOG.md +75 -1
- package/README.md +222 -11
- package/dist/cjs/chat.d.ts +0 -9
- package/dist/cjs/chat.d.ts.map +1 -1
- package/dist/cjs/chat.js +16 -14
- package/dist/cjs/chat.js.map +1 -1
- package/dist/cjs/errors.d.ts +571 -0
- package/dist/cjs/errors.d.ts.map +1 -0
- package/dist/cjs/errors.js +155 -0
- package/dist/cjs/errors.js.map +1 -0
- package/dist/cjs/index.d.ts +298 -16
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +663 -124
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/media-permissions.d.ts +101 -0
- package/dist/cjs/media-permissions.d.ts.map +1 -0
- package/dist/cjs/media-permissions.js +166 -0
- package/dist/cjs/media-permissions.js.map +1 -0
- package/dist/cjs/network.d.ts +80 -0
- package/dist/cjs/network.d.ts.map +1 -0
- package/dist/cjs/network.js +190 -0
- package/dist/cjs/network.js.map +1 -0
- package/dist/cjs/stats.d.ts +16 -2
- package/dist/cjs/stats.d.ts.map +1 -1
- package/dist/cjs/stats.js +53 -5
- package/dist/cjs/stats.js.map +1 -1
- package/dist/cjs/token.d.ts +17 -0
- package/dist/cjs/token.d.ts.map +1 -0
- package/dist/cjs/token.js +36 -0
- package/dist/cjs/token.js.map +1 -0
- package/dist/esm/chat.d.ts +0 -9
- package/dist/esm/chat.d.ts.map +1 -1
- package/dist/esm/chat.js +16 -14
- package/dist/esm/chat.js.map +1 -1
- package/dist/esm/errors.d.ts +571 -0
- package/dist/esm/errors.d.ts.map +1 -0
- package/dist/esm/errors.js +149 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/index.d.ts +298 -16
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +642 -115
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/media-permissions.d.ts +101 -0
- package/dist/esm/media-permissions.d.ts.map +1 -0
- package/dist/esm/media-permissions.js +160 -0
- package/dist/esm/media-permissions.js.map +1 -0
- package/dist/esm/network.d.ts +80 -0
- package/dist/esm/network.d.ts.map +1 -0
- package/dist/esm/network.js +183 -0
- package/dist/esm/network.js.map +1 -0
- package/dist/esm/stats.d.ts +16 -2
- package/dist/esm/stats.d.ts.map +1 -1
- package/dist/esm/stats.js +53 -5
- package/dist/esm/stats.js.map +1 -1
- package/dist/esm/token.d.ts +17 -0
- package/dist/esm/token.d.ts.map +1 -0
- package/dist/esm/token.js +31 -0
- package/dist/esm/token.js.map +1 -0
- package/package.json +1 -1
- package/src/chat.ts +7 -14
- package/src/errors.ts +162 -0
- package/src/index.ts +785 -115
- package/src/media-permissions.ts +220 -0
- package/src/network.ts +212 -0
- package/src/stats.ts +51 -6
- package/src/token.ts +31 -0
package/src/chat.ts
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* nothing else -- for an audience that is watching a broadcast over a CDN and
|
|
8
8
|
* has no reason to be on an SFU.
|
|
9
9
|
*/
|
|
10
|
+
import { isTerminalError } from './errors.js';
|
|
10
11
|
import type { Role } from './index.js';
|
|
12
|
+
import { socketUrl } from './token.js';
|
|
11
13
|
|
|
12
14
|
export interface ChatMessage {
|
|
13
15
|
/** Monotonic within a channel, so the oldest id held is a paging cursor. */
|
|
@@ -95,16 +97,6 @@ interface Pending {
|
|
|
95
97
|
|
|
96
98
|
const RPC_TIMEOUT_MS = 15_000;
|
|
97
99
|
const MAX_RECONNECT_ATTEMPTS = 8;
|
|
98
|
-
/** A token that is wrong will be wrong next time too; retrying only adds noise. */
|
|
99
|
-
const TERMINAL_JOIN_ERRORS = new Set([
|
|
100
|
-
'bad-token',
|
|
101
|
-
'no-token',
|
|
102
|
-
// The account cannot use the service until it is topped up or restored.
|
|
103
|
-
'quota-exhausted',
|
|
104
|
-
'daily-limit-reached',
|
|
105
|
-
'monthly-limit-reached',
|
|
106
|
-
'tenant-suspended',
|
|
107
|
-
]);
|
|
108
100
|
|
|
109
101
|
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
|
|
110
102
|
|
|
@@ -179,9 +171,7 @@ export class ChatClient {
|
|
|
179
171
|
private openSocket(): Promise<void> {
|
|
180
172
|
this.detachSocket();
|
|
181
173
|
return new Promise((resolve, reject) => {
|
|
182
|
-
const
|
|
183
|
-
url.searchParams.set('token', this.token);
|
|
184
|
-
const ws = new WebSocket(url.toString());
|
|
174
|
+
const ws = new WebSocket(socketUrl(this.wsUrl, this.token));
|
|
185
175
|
this.ws = ws;
|
|
186
176
|
|
|
187
177
|
const onError = () => reject(new Error('websocket failed to open'));
|
|
@@ -246,7 +236,10 @@ export class ChatClient {
|
|
|
246
236
|
return;
|
|
247
237
|
} catch (err) {
|
|
248
238
|
this.emit('error', err as Error);
|
|
249
|
-
|
|
239
|
+
// A token that is wrong will be wrong next time too, and an account
|
|
240
|
+
// that ran out stays out until topped up: the registry's terminal
|
|
241
|
+
// codes, shared with StreamClient. Retrying only adds noise.
|
|
242
|
+
if (err instanceof ChatRpcError && isTerminalError(err.code)) {
|
|
250
243
|
this.closedByUs = true;
|
|
251
244
|
this.emit('closed', 'gave-up');
|
|
252
245
|
return;
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One error registry for every VoltKit SDK (docs/sdk-contract.json): the codes
|
|
3
|
+
* the controller and the media nodes answer with, passed through unchanged, and
|
|
4
|
+
* the ones the SDKs raise themselves. Switch on `error.code`, never on the
|
|
5
|
+
* message.
|
|
6
|
+
*
|
|
7
|
+
* Kept as a literal here, since the package cannot read the repository's docs
|
|
8
|
+
* at runtime, and checked against the contract by
|
|
9
|
+
* scripts/test/sdk-contract-browser.sh and test/contract.test.mjs. One code per
|
|
10
|
+
* line, so that check can read it.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Who raises a code: the server (controller or media node) or the SDK itself. */
|
|
14
|
+
export type ErrorOrigin = 'server' | 'sdk';
|
|
15
|
+
|
|
16
|
+
export interface ErrorInfo {
|
|
17
|
+
origin: ErrorOrigin;
|
|
18
|
+
/** A join (or reconnect) refused with it must not be retried with the same token. */
|
|
19
|
+
terminal: boolean;
|
|
20
|
+
/** Trying the same call again later can succeed. */
|
|
21
|
+
retryable: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const ERROR_CODES = {
|
|
25
|
+
'bad-json': { origin: 'server', terminal: false, retryable: false },
|
|
26
|
+
'bad-params': { origin: 'server', terminal: false, retryable: false },
|
|
27
|
+
'unknown-method': { origin: 'server', terminal: false, retryable: false },
|
|
28
|
+
'too-many-requests': { origin: 'server', terminal: false, retryable: true },
|
|
29
|
+
'not-joined': { origin: 'server', terminal: false, retryable: false },
|
|
30
|
+
'already-joined': { origin: 'server', terminal: false, retryable: false },
|
|
31
|
+
'internal': { origin: 'server', terminal: false, retryable: true },
|
|
32
|
+
'node-error': { origin: 'server', terminal: false, retryable: true },
|
|
33
|
+
'no-token': { origin: 'server', terminal: true, retryable: false },
|
|
34
|
+
'bad-token': { origin: 'server', terminal: true, retryable: false },
|
|
35
|
+
'wrong-tenant': { origin: 'server', terminal: true, retryable: false },
|
|
36
|
+
'token-mismatch': { origin: 'server', terminal: false, retryable: false },
|
|
37
|
+
'no-room': { origin: 'server', terminal: true, retryable: false },
|
|
38
|
+
'room-ended': { origin: 'server', terminal: true, retryable: false },
|
|
39
|
+
'kicked': { origin: 'server', terminal: true, retryable: false },
|
|
40
|
+
'no-node': { origin: 'server', terminal: false, retryable: true },
|
|
41
|
+
'no-capacity': { origin: 'server', terminal: false, retryable: true },
|
|
42
|
+
'stage-full': { origin: 'server', terminal: false, retryable: true },
|
|
43
|
+
'participant-quota': { origin: 'server', terminal: false, retryable: true },
|
|
44
|
+
'viewer-quota': { origin: 'server', terminal: false, retryable: true },
|
|
45
|
+
'quota-exhausted': { origin: 'server', terminal: true, retryable: false },
|
|
46
|
+
'daily-limit-reached': { origin: 'server', terminal: true, retryable: false },
|
|
47
|
+
'monthly-limit-reached': { origin: 'server', terminal: true, retryable: false },
|
|
48
|
+
'tenant-suspended': { origin: 'server', terminal: true, retryable: false },
|
|
49
|
+
'billing-unavailable': { origin: 'server', terminal: true, retryable: false },
|
|
50
|
+
'forbidden': { origin: 'server', terminal: true, retryable: false },
|
|
51
|
+
'not-in-room': { origin: 'server', terminal: false, retryable: false },
|
|
52
|
+
'no-participant': { origin: 'server', terminal: false, retryable: false },
|
|
53
|
+
'bad-transport': { origin: 'server', terminal: false, retryable: false },
|
|
54
|
+
'no-transport': { origin: 'server', terminal: false, retryable: false },
|
|
55
|
+
'no-producer': { origin: 'server', terminal: false, retryable: false },
|
|
56
|
+
'no-consumer': { origin: 'server', terminal: false, retryable: false },
|
|
57
|
+
'cannot-consume': { origin: 'server', terminal: false, retryable: false },
|
|
58
|
+
'consumer-cap': { origin: 'server', terminal: false, retryable: true },
|
|
59
|
+
'producer-cap': { origin: 'server', terminal: false, retryable: true },
|
|
60
|
+
'room-cap': { origin: 'server', terminal: false, retryable: true },
|
|
61
|
+
'draining': { origin: 'server', terminal: false, retryable: true },
|
|
62
|
+
'bad-request': { origin: 'server', terminal: false, retryable: false },
|
|
63
|
+
'source-already-published': { origin: 'server', terminal: false, retryable: false },
|
|
64
|
+
'force-muted': { origin: 'server', terminal: false, retryable: false },
|
|
65
|
+
'media-disabled': { origin: 'server', terminal: false, retryable: false },
|
|
66
|
+
'frame-rate-not-allowed': { origin: 'server', terminal: false, retryable: false },
|
|
67
|
+
'video-quality-not-allowed': { origin: 'server', terminal: false, retryable: false },
|
|
68
|
+
'audio-quality-not-allowed': { origin: 'server', terminal: false, retryable: false },
|
|
69
|
+
'video-not-allowed': { origin: 'server', terminal: false, retryable: false },
|
|
70
|
+
'audio-not-allowed': { origin: 'server', terminal: false, retryable: false },
|
|
71
|
+
'video-subscription-limit': { origin: 'server', terminal: false, retryable: false },
|
|
72
|
+
'slot-taken': { origin: 'server', terminal: false, retryable: false },
|
|
73
|
+
'slot-blocked': { origin: 'server', terminal: false, retryable: false },
|
|
74
|
+
'slot-occupied': { origin: 'server', terminal: false, retryable: false },
|
|
75
|
+
'chat-muted': { origin: 'server', terminal: false, retryable: false },
|
|
76
|
+
'rate-limited': { origin: 'server', terminal: false, retryable: true },
|
|
77
|
+
'no-message': { origin: 'server', terminal: false, retryable: false },
|
|
78
|
+
'chat-unavailable': { origin: 'server', terminal: false, retryable: false },
|
|
79
|
+
'closed': { origin: 'server', terminal: false, retryable: true },
|
|
80
|
+
'unauthorized': { origin: 'server', terminal: false, retryable: false },
|
|
81
|
+
'bad-signature': { origin: 'server', terminal: false, retryable: false },
|
|
82
|
+
'operator-only': { origin: 'server', terminal: false, retryable: false },
|
|
83
|
+
'bad-role': { origin: 'server', terminal: false, retryable: false },
|
|
84
|
+
'room-exists': { origin: 'server', terminal: false, retryable: false },
|
|
85
|
+
'room-quota': { origin: 'server', terminal: false, retryable: true },
|
|
86
|
+
'no-nodes': { origin: 'server', terminal: false, retryable: true },
|
|
87
|
+
'capacity-exhausted': { origin: 'server', terminal: false, retryable: true },
|
|
88
|
+
'unavailable': { origin: 'server', terminal: false, retryable: true },
|
|
89
|
+
'not-found': { origin: 'server', terminal: false, retryable: false },
|
|
90
|
+
'broadcast-disabled': { origin: 'server', terminal: false, retryable: false },
|
|
91
|
+
'chat-disabled': { origin: 'server', terminal: false, retryable: false },
|
|
92
|
+
'no-stream': { origin: 'server', terminal: false, retryable: false },
|
|
93
|
+
'stream-exists': { origin: 'server', terminal: false, retryable: false },
|
|
94
|
+
'stream-quota': { origin: 'server', terminal: false, retryable: true },
|
|
95
|
+
'network': { origin: 'sdk', terminal: false, retryable: true },
|
|
96
|
+
'rates-unconfigured': { origin: 'sdk', terminal: false, retryable: false },
|
|
97
|
+
'redirected': { origin: 'sdk', terminal: false, retryable: false },
|
|
98
|
+
'not-connected': { origin: 'sdk', terminal: false, retryable: true },
|
|
99
|
+
'timeout': { origin: 'sdk', terminal: false, retryable: true },
|
|
100
|
+
'connect-failed': { origin: 'sdk', terminal: false, retryable: true },
|
|
101
|
+
'replaced': { origin: 'sdk', terminal: true, retryable: false },
|
|
102
|
+
'reconnect-gave-up': { origin: 'sdk', terminal: true, retryable: false },
|
|
103
|
+
'devices-gone': { origin: 'sdk', terminal: false, retryable: false },
|
|
104
|
+
'unpublished': { origin: 'sdk', terminal: false, retryable: false },
|
|
105
|
+
'frame-rate-fallback': { origin: 'sdk', terminal: false, retryable: false },
|
|
106
|
+
'codec-fallback': { origin: 'sdk', terminal: false, retryable: false },
|
|
107
|
+
'quality-unavailable': { origin: 'sdk', terminal: false, retryable: false },
|
|
108
|
+
'screen-share-active': { origin: 'sdk', terminal: false, retryable: false },
|
|
109
|
+
'screen-share-unsupported': { origin: 'sdk', terminal: false, retryable: false },
|
|
110
|
+
'screen-share-no-video': { origin: 'sdk', terminal: false, retryable: false },
|
|
111
|
+
'disconnected': { origin: 'sdk', terminal: false, retryable: true },
|
|
112
|
+
'produce-failed': { origin: 'sdk', terminal: false, retryable: true },
|
|
113
|
+
'consume-failed': { origin: 'sdk', terminal: false, retryable: true },
|
|
114
|
+
'media-unavailable': { origin: 'sdk', terminal: false, retryable: false },
|
|
115
|
+
'music-unavailable': { origin: 'sdk', terminal: false, retryable: false },
|
|
116
|
+
'no-camera': { origin: 'sdk', terminal: false, retryable: false },
|
|
117
|
+
'camera-failed': { origin: 'sdk', terminal: false, retryable: true },
|
|
118
|
+
'unknown-producer': { origin: 'sdk', terminal: false, retryable: false },
|
|
119
|
+
'screen-share-denied': { origin: 'sdk', terminal: false, retryable: true },
|
|
120
|
+
'screen-share-service-required': { origin: 'sdk', terminal: false, retryable: false },
|
|
121
|
+
'screen-share-source-required': { origin: 'sdk', terminal: false, retryable: false },
|
|
122
|
+
'screen-share-setup-required': { origin: 'sdk', terminal: false, retryable: false },
|
|
123
|
+
'screen-share-start-timeout': { origin: 'sdk', terminal: false, retryable: true },
|
|
124
|
+
'screen-share-ended': { origin: 'sdk', terminal: false, retryable: true },
|
|
125
|
+
'screen-share-failed': { origin: 'sdk', terminal: false, retryable: true },
|
|
126
|
+
'screen-share-size-unavailable': { origin: 'sdk', terminal: false, retryable: true },
|
|
127
|
+
'screen-audio-unavailable': { origin: 'sdk', terminal: false, retryable: false },
|
|
128
|
+
'screen-stop-failed': { origin: 'sdk', terminal: false, retryable: false },
|
|
129
|
+
'degradation-preference-failed': { origin: 'sdk', terminal: false, retryable: false },
|
|
130
|
+
} as const satisfies Record<string, ErrorInfo>;
|
|
131
|
+
|
|
132
|
+
/** Every code in the registry. A newer server may still send one this SDK predates. */
|
|
133
|
+
export type ErrorCode = keyof typeof ERROR_CODES;
|
|
134
|
+
|
|
135
|
+
/** The registry entry for a code, or undefined for one this SDK does not know. */
|
|
136
|
+
export function errorInfo(code: string): ErrorInfo | undefined {
|
|
137
|
+
return Object.prototype.hasOwnProperty.call(ERROR_CODES, code) ? ERROR_CODES[code as ErrorCode] : undefined;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Whether reconnecting cannot fix a join refused with this code: a room that
|
|
142
|
+
* has ended is not coming back, and a token that is wrong will be wrong next
|
|
143
|
+
* time too. Unknown codes are not terminal, so a newer server's code never
|
|
144
|
+
* stops a reconnect that could succeed.
|
|
145
|
+
*/
|
|
146
|
+
export function isTerminalError(code: string): boolean {
|
|
147
|
+
return errorInfo(code)?.terminal === true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* An error with a machine-readable code: returned by the server (its code
|
|
152
|
+
* passed through unchanged, known to this SDK or not) or raised by the SDK
|
|
153
|
+
* itself with a registry code. `data` is what the server attached so the app
|
|
154
|
+
* can act on a refusal rather than merely report it: `{ slots }` for
|
|
155
|
+
* `slot-occupied`, for instance.
|
|
156
|
+
*/
|
|
157
|
+
export class RpcError extends Error {
|
|
158
|
+
constructor(readonly code: ErrorCode | (string & {}), message: string, readonly data?: unknown) {
|
|
159
|
+
super(`${code}: ${message}`);
|
|
160
|
+
this.name = 'RpcError';
|
|
161
|
+
}
|
|
162
|
+
}
|