@voltkit-rtc/client 0.2.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 +38 -0
- package/LICENSE +21 -0
- package/README.md +296 -0
- package/dist/cjs/broadcast.d.ts +130 -0
- package/dist/cjs/broadcast.d.ts.map +1 -0
- package/dist/cjs/broadcast.js +320 -0
- package/dist/cjs/broadcast.js.map +1 -0
- package/dist/cjs/chat.d.ts +138 -0
- package/dist/cjs/chat.d.ts.map +1 -0
- package/dist/cjs/chat.js +267 -0
- package/dist/cjs/chat.js.map +1 -0
- package/dist/cjs/index.d.ts +285 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +798 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/music.d.ts +76 -0
- package/dist/cjs/music.d.ts.map +1 -0
- package/dist/cjs/music.js +176 -0
- package/dist/cjs/music.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/broadcast.d.ts +130 -0
- package/dist/esm/broadcast.d.ts.map +1 -0
- package/dist/esm/broadcast.js +313 -0
- package/dist/esm/broadcast.js.map +1 -0
- package/dist/esm/chat.d.ts +138 -0
- package/dist/esm/chat.d.ts.map +1 -0
- package/dist/esm/chat.js +262 -0
- package/dist/esm/chat.js.map +1 -0
- package/dist/esm/index.d.ts +285 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +785 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/music.d.ts +76 -0
- package/dist/esm/music.d.ts.map +1 -0
- package/dist/esm/music.js +171 -0
- package/dist/esm/music.js.map +1 -0
- package/dist/esm/package.json +1 -0
- package/package.json +60 -0
- package/src/broadcast.ts +352 -0
- package/src/chat.ts +352 -0
- package/src/index.ts +945 -0
- package/src/music.ts +200 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Publishing a broadcast stream.
|
|
4
|
+
*
|
|
5
|
+
* A broadcast is one publisher and an audience of any size, delivered over
|
|
6
|
+
* LL-HLS and WebRTC. Publishing is WHIP: one HTTP exchange of SDP, then media
|
|
7
|
+
* straight to the media server. Viewers need nothing from this package -- they
|
|
8
|
+
* play the stream's LL-HLS URL in any HLS player.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.BroadcastPublisher = exports.WhipError = void 0;
|
|
12
|
+
exports.whipOffer = whipOffer;
|
|
13
|
+
exports.whipEnd = whipEnd;
|
|
14
|
+
const music_js_1 = require("./music.js");
|
|
15
|
+
/** The media server refused, or could not be reached. */
|
|
16
|
+
class WhipError extends Error {
|
|
17
|
+
status;
|
|
18
|
+
/**
|
|
19
|
+
* @param status HTTP status, or 0 when the request never got an answer.
|
|
20
|
+
*/
|
|
21
|
+
constructor(status, message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.status = status;
|
|
24
|
+
this.name = 'WhipError';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The publish URL was not accepted: its token is missing, expired, for a
|
|
28
|
+
* different stream, or the account behind it cannot go on air. The same URL
|
|
29
|
+
* will be refused again -- get a new one from your backend.
|
|
30
|
+
*/
|
|
31
|
+
get isRefused() {
|
|
32
|
+
return this.status === 401 || this.status === 403;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.WhipError = WhipError;
|
|
36
|
+
const WHIP_TIMEOUT_MS = 15_000;
|
|
37
|
+
/** A browser that never reports "complete" still publishes with what it has. */
|
|
38
|
+
const ICE_GATHER_TIMEOUT_MS = 3_000;
|
|
39
|
+
/** Helper formats that ride along with a codec. Not codecs in their own right. */
|
|
40
|
+
const HELPER_FORMATS = new Set(['video/rtx', 'video/red', 'video/ulpfec', 'video/flexfec-03']);
|
|
41
|
+
/**
|
|
42
|
+
* Send an SDP offer to a WHIP endpoint and return the answer, plus the session
|
|
43
|
+
* resource the server names for ending it.
|
|
44
|
+
*/
|
|
45
|
+
async function whipOffer(whipUrl, offerSdp, timeoutMs = WHIP_TIMEOUT_MS) {
|
|
46
|
+
let res;
|
|
47
|
+
try {
|
|
48
|
+
res = await fetch(whipUrl, {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
headers: { 'Content-Type': 'application/sdp', Accept: 'application/sdp' },
|
|
51
|
+
body: offerSdp,
|
|
52
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
// Never echo the URL: it carries the publish token.
|
|
57
|
+
const timedOut = err?.name === 'TimeoutError';
|
|
58
|
+
throw new WhipError(0, timedOut
|
|
59
|
+
? 'the media server did not answer in time'
|
|
60
|
+
: 'could not reach the media server');
|
|
61
|
+
}
|
|
62
|
+
const body = await res.text();
|
|
63
|
+
if (res.status !== 201 && res.status !== 200) {
|
|
64
|
+
const line = body.split('\n')[0]?.trim().slice(0, 200);
|
|
65
|
+
throw new WhipError(res.status, line || 'the media server refused the stream');
|
|
66
|
+
}
|
|
67
|
+
const location = res.headers.get('Location');
|
|
68
|
+
// Location may be relative; it is relative to the URL that was posted to.
|
|
69
|
+
return { answerSdp: body, resource: location ? new URL(location, whipUrl).toString() : null };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Tell the server the session is over. Best effort: the media server also
|
|
73
|
+
* notices the connection going away, so a failure here is not an error.
|
|
74
|
+
*/
|
|
75
|
+
async function whipEnd(resource) {
|
|
76
|
+
try {
|
|
77
|
+
// keepalive lets it finish when called while the page is being closed.
|
|
78
|
+
await fetch(resource, { method: 'DELETE', keepalive: true, signal: AbortSignal.timeout(5_000) });
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* the session ends when the connection does */
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Publishes camera and microphone to one broadcast stream.
|
|
86
|
+
*
|
|
87
|
+
* ```ts
|
|
88
|
+
* const publisher = new BroadcastPublisher(stream.publish.whipUrl);
|
|
89
|
+
* publisher.on('state', (s) => setState(s));
|
|
90
|
+
* const media = await publisher.start();
|
|
91
|
+
* video.srcObject = media;
|
|
92
|
+
* // ...
|
|
93
|
+
* await publisher.stop();
|
|
94
|
+
* ```
|
|
95
|
+
*
|
|
96
|
+
* The `whipUrl` comes from your backend (`POST /v1/streams`) and already
|
|
97
|
+
* carries the publish token. Treat it like a password.
|
|
98
|
+
*/
|
|
99
|
+
class BroadcastPublisher {
|
|
100
|
+
whipUrl;
|
|
101
|
+
pc = null;
|
|
102
|
+
media = null;
|
|
103
|
+
ownsMedia = false;
|
|
104
|
+
resource = null;
|
|
105
|
+
audioSender = null;
|
|
106
|
+
micTrack = null;
|
|
107
|
+
/** Present only while music is in use: the broadcast carries one audio track, so the two are mixed. */
|
|
108
|
+
mixer = null;
|
|
109
|
+
micOn = true;
|
|
110
|
+
current = 'idle';
|
|
111
|
+
listeners = new Map();
|
|
112
|
+
constructor(whipUrl) {
|
|
113
|
+
this.whipUrl = whipUrl;
|
|
114
|
+
}
|
|
115
|
+
get state() {
|
|
116
|
+
return this.current;
|
|
117
|
+
}
|
|
118
|
+
/** What is being published, for a local preview. */
|
|
119
|
+
get localStream() {
|
|
120
|
+
return this.media;
|
|
121
|
+
}
|
|
122
|
+
on(event, fn) {
|
|
123
|
+
let set = this.listeners.get(event);
|
|
124
|
+
if (!set) {
|
|
125
|
+
set = new Set();
|
|
126
|
+
this.listeners.set(event, set);
|
|
127
|
+
}
|
|
128
|
+
set.add(fn);
|
|
129
|
+
return () => set.delete(fn);
|
|
130
|
+
}
|
|
131
|
+
emit(event, ...args) {
|
|
132
|
+
for (const fn of this.listeners.get(event) ?? []) {
|
|
133
|
+
try {
|
|
134
|
+
fn(...args);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
console.error(`listener for ${event} threw`, err);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
set(state) {
|
|
142
|
+
if (this.current === state)
|
|
143
|
+
return;
|
|
144
|
+
this.current = state;
|
|
145
|
+
this.emit('state', state);
|
|
146
|
+
}
|
|
147
|
+
/** Go on air. Resolves once the media server has accepted the stream. */
|
|
148
|
+
async start(options = {}) {
|
|
149
|
+
if (this.pc)
|
|
150
|
+
throw new Error('already publishing; call stop() first');
|
|
151
|
+
this.set('connecting');
|
|
152
|
+
try {
|
|
153
|
+
this.ownsMedia = !options.stream;
|
|
154
|
+
const media = options.stream ?? await navigator.mediaDevices.getUserMedia(options.constraints ?? { audio: true, video: { width: 1280, height: 720 } });
|
|
155
|
+
this.media = media;
|
|
156
|
+
const pc = new RTCPeerConnection();
|
|
157
|
+
this.pc = pc;
|
|
158
|
+
pc.addEventListener('connectionstatechange', () => {
|
|
159
|
+
if (this.pc !== pc)
|
|
160
|
+
return;
|
|
161
|
+
if (pc.connectionState === 'connected')
|
|
162
|
+
this.set('live');
|
|
163
|
+
else if (pc.connectionState === 'disconnected')
|
|
164
|
+
this.set('interrupted');
|
|
165
|
+
else if (pc.connectionState === 'failed')
|
|
166
|
+
this.set('failed');
|
|
167
|
+
});
|
|
168
|
+
this.micTrack = media.getAudioTracks()[0] ?? null;
|
|
169
|
+
if (options.music) {
|
|
170
|
+
this.mixer = new music_js_1.AudioMixer(this.micTrack);
|
|
171
|
+
this.mixer.setMusic(options.music, options.musicOptions);
|
|
172
|
+
}
|
|
173
|
+
for (const track of media.getVideoTracks()) {
|
|
174
|
+
this.preferH264(pc.addTransceiver(track, { direction: 'sendonly', streams: [media] }));
|
|
175
|
+
}
|
|
176
|
+
// Always an audio line, even with no microphone, so music can be added
|
|
177
|
+
// later without renegotiating with the media server.
|
|
178
|
+
const audio = this.mixer?.track ?? this.micTrack;
|
|
179
|
+
const transceiver = audio
|
|
180
|
+
? pc.addTransceiver(audio, { direction: 'sendonly', streams: [media] })
|
|
181
|
+
: pc.addTransceiver('audio', { direction: 'sendonly' });
|
|
182
|
+
this.audioSender = transceiver.sender;
|
|
183
|
+
await pc.setLocalDescription(await pc.createOffer());
|
|
184
|
+
await iceGathered(pc);
|
|
185
|
+
const offerSdp = pc.localDescription?.sdp;
|
|
186
|
+
if (!offerSdp)
|
|
187
|
+
throw new WhipError(0, 'could not create an offer in this browser');
|
|
188
|
+
const answer = await whipOffer(this.whipUrl, offerSdp);
|
|
189
|
+
this.resource = answer.resource;
|
|
190
|
+
await pc.setRemoteDescription({ type: 'answer', sdp: answer.answerSdp });
|
|
191
|
+
return media;
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
this.teardown();
|
|
195
|
+
this.set('failed');
|
|
196
|
+
throw err;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Put H.264 first, and offer nothing else for video.
|
|
201
|
+
*
|
|
202
|
+
* The media server repackages H.264 into HLS untouched. Any other codec
|
|
203
|
+
* makes it decode and re-encode every frame, which is the entire cost of a
|
|
204
|
+
* stream -- so a publisher that negotiates VP8 works, and quietly costs many
|
|
205
|
+
* times more.
|
|
206
|
+
*/
|
|
207
|
+
preferH264(transceiver) {
|
|
208
|
+
try {
|
|
209
|
+
const all = RTCRtpSender.getCapabilities('video')?.codecs ?? [];
|
|
210
|
+
const h264 = all.filter((c) => c.mimeType.toLowerCase() === 'video/h264');
|
|
211
|
+
if (h264.length === 0) {
|
|
212
|
+
this.emit('warning', 'H.264 is not available in this browser; the stream will be transcoded');
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const helpers = all.filter((c) => HELPER_FORMATS.has(c.mimeType.toLowerCase()));
|
|
216
|
+
transceiver.setCodecPreferences([...h264, ...helpers]);
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
this.emit('warning', `could not prefer H.264 (${err.message}); the stream may be transcoded`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/** Mute or unmute the microphone without leaving the air. Music keeps playing. */
|
|
223
|
+
setMicEnabled(enabled) {
|
|
224
|
+
this.micOn = enabled;
|
|
225
|
+
if (this.mixer)
|
|
226
|
+
this.mixer.setMicEnabled(enabled);
|
|
227
|
+
else if (this.micTrack)
|
|
228
|
+
this.micTrack.enabled = enabled;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Put music from inside the page on air, mixed with the microphone.
|
|
232
|
+
*
|
|
233
|
+
* A broadcast carries a single audio track, so the two are mixed here, in
|
|
234
|
+
* the browser, before they are sent. The music never passes through the
|
|
235
|
+
* microphone, so the audience hears it clean, and each has its own switch:
|
|
236
|
+
* `setMusicEnabled` and `setMicEnabled`.
|
|
237
|
+
*/
|
|
238
|
+
async setMusic(input, options) {
|
|
239
|
+
if (!this.pc || !this.audioSender)
|
|
240
|
+
throw new Error('start() the broadcast before adding music');
|
|
241
|
+
if (!this.mixer) {
|
|
242
|
+
this.mixer = new music_js_1.AudioMixer(this.micTrack);
|
|
243
|
+
// The raw track was being sent until now; its own switch no longer applies.
|
|
244
|
+
if (this.micTrack)
|
|
245
|
+
this.micTrack.enabled = true;
|
|
246
|
+
this.mixer.setMicEnabled(this.micOn);
|
|
247
|
+
await this.audioSender.replaceTrack(this.mixer.track);
|
|
248
|
+
}
|
|
249
|
+
this.mixer.setMusic(input, options);
|
|
250
|
+
}
|
|
251
|
+
/** Switch the music off or on for the audience. The microphone is untouched. */
|
|
252
|
+
setMusicEnabled(enabled) {
|
|
253
|
+
this.mixer?.setMusicEnabled(enabled);
|
|
254
|
+
}
|
|
255
|
+
/** 0 to 1, for the audience. */
|
|
256
|
+
setMusicVolume(volume) {
|
|
257
|
+
this.mixer?.setMusicVolume(volume);
|
|
258
|
+
}
|
|
259
|
+
/** Take the music off air. The microphone carries on. */
|
|
260
|
+
clearMusic() {
|
|
261
|
+
this.mixer?.clearMusic();
|
|
262
|
+
}
|
|
263
|
+
get musicEnabled() {
|
|
264
|
+
return this.mixer?.musicEnabled ?? false;
|
|
265
|
+
}
|
|
266
|
+
get micEnabled() {
|
|
267
|
+
return this.micOn && this.micTrack !== null;
|
|
268
|
+
}
|
|
269
|
+
/** Turn the camera on or off without leaving the air. */
|
|
270
|
+
setCamEnabled(enabled) {
|
|
271
|
+
for (const t of this.media?.getVideoTracks() ?? [])
|
|
272
|
+
t.enabled = enabled;
|
|
273
|
+
}
|
|
274
|
+
/** Go off air and release the camera and microphone. Idempotent. */
|
|
275
|
+
async stop() {
|
|
276
|
+
const resource = this.resource;
|
|
277
|
+
this.teardown();
|
|
278
|
+
if (resource)
|
|
279
|
+
await whipEnd(resource);
|
|
280
|
+
this.set('stopped');
|
|
281
|
+
}
|
|
282
|
+
teardown() {
|
|
283
|
+
const { pc, media } = this;
|
|
284
|
+
this.pc = null;
|
|
285
|
+
this.media = null;
|
|
286
|
+
this.resource = null;
|
|
287
|
+
this.audioSender = null;
|
|
288
|
+
this.micTrack = null;
|
|
289
|
+
this.mixer?.close();
|
|
290
|
+
this.mixer = null;
|
|
291
|
+
this.micOn = true;
|
|
292
|
+
pc?.close();
|
|
293
|
+
if (this.ownsMedia && media)
|
|
294
|
+
for (const t of media.getTracks())
|
|
295
|
+
t.stop();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
exports.BroadcastPublisher = BroadcastPublisher;
|
|
299
|
+
/**
|
|
300
|
+
* WHIP sends one complete offer, so the candidates have to be in it before it
|
|
301
|
+
* is sent.
|
|
302
|
+
*/
|
|
303
|
+
function iceGathered(pc) {
|
|
304
|
+
if (pc.iceGatheringState === 'complete')
|
|
305
|
+
return Promise.resolve();
|
|
306
|
+
return new Promise((resolve) => {
|
|
307
|
+
const done = () => {
|
|
308
|
+
clearTimeout(timer);
|
|
309
|
+
pc.removeEventListener('icegatheringstatechange', check);
|
|
310
|
+
resolve();
|
|
311
|
+
};
|
|
312
|
+
const check = () => {
|
|
313
|
+
if (pc.iceGatheringState === 'complete')
|
|
314
|
+
done();
|
|
315
|
+
};
|
|
316
|
+
const timer = setTimeout(done, ICE_GATHER_TIMEOUT_MS);
|
|
317
|
+
pc.addEventListener('icegatheringstatechange', check);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
//# sourceMappingURL=broadcast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"broadcast.js","sourceRoot":"","sources":["../../src/broadcast.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chat for the orchestrator's signaling protocol.
|
|
3
|
+
*
|
|
4
|
+
* A channel shares its name with the room of the same name, so there are two
|
|
5
|
+
* ways into one conversation: `StreamClient` carries chat over the socket it
|
|
6
|
+
* already holds for media, and `ChatClient` here opens a socket that carries
|
|
7
|
+
* nothing else -- for an audience that is watching a broadcast over a CDN and
|
|
8
|
+
* has no reason to be on an SFU.
|
|
9
|
+
*/
|
|
10
|
+
import type { Role } from './index.js';
|
|
11
|
+
export interface ChatMessage {
|
|
12
|
+
/** Monotonic within a channel, so the oldest id held is a paging cursor. */
|
|
13
|
+
id: string;
|
|
14
|
+
channel: string;
|
|
15
|
+
from: string;
|
|
16
|
+
/** Present on a direct message, which only its two ends receive. */
|
|
17
|
+
to?: string;
|
|
18
|
+
/** "text" unless the sender said otherwise. Anything else is the app's own. */
|
|
19
|
+
type: string;
|
|
20
|
+
/**
|
|
21
|
+
* Delivered exactly as sent. Render it as text, never as HTML: escaping is
|
|
22
|
+
* the renderer's job and the server does not do it for you.
|
|
23
|
+
*/
|
|
24
|
+
text?: string;
|
|
25
|
+
metadata?: unknown;
|
|
26
|
+
/** Echoed from the sender, to match an optimistic message to the real one. */
|
|
27
|
+
clientMsgId?: string;
|
|
28
|
+
ts: number;
|
|
29
|
+
}
|
|
30
|
+
export interface SendMessageOptions {
|
|
31
|
+
type?: string;
|
|
32
|
+
/** Make it a direct message to one identity. */
|
|
33
|
+
to?: string;
|
|
34
|
+
metadata?: unknown;
|
|
35
|
+
clientMsgId?: string;
|
|
36
|
+
/** Delivered and not stored: reactions, hearts, anything not worth replaying. */
|
|
37
|
+
ephemeral?: boolean;
|
|
38
|
+
}
|
|
39
|
+
export interface ChatState {
|
|
40
|
+
channel: string;
|
|
41
|
+
/** Distinct identities connected, in the room or only in its chat. */
|
|
42
|
+
members: number;
|
|
43
|
+
/** Whether a moderator has muted THIS identity. */
|
|
44
|
+
muted: boolean;
|
|
45
|
+
/** Only present for moderators. */
|
|
46
|
+
mutedIdentities?: string[];
|
|
47
|
+
}
|
|
48
|
+
export interface ChatJoinResult {
|
|
49
|
+
channel: string;
|
|
50
|
+
identity: string;
|
|
51
|
+
role: Role;
|
|
52
|
+
members: number;
|
|
53
|
+
muted: boolean;
|
|
54
|
+
/** The most recent messages, oldest first. */
|
|
55
|
+
messages: ChatMessage[];
|
|
56
|
+
}
|
|
57
|
+
export interface ChatEvents {
|
|
58
|
+
message: (m: ChatMessage) => void;
|
|
59
|
+
messageDeleted: (e: {
|
|
60
|
+
id: string;
|
|
61
|
+
by: string;
|
|
62
|
+
}) => void;
|
|
63
|
+
chatMuted: (e: {
|
|
64
|
+
identity: string;
|
|
65
|
+
muted: boolean;
|
|
66
|
+
by: string;
|
|
67
|
+
}) => void;
|
|
68
|
+
typing: (e: {
|
|
69
|
+
identity: string;
|
|
70
|
+
typing: boolean;
|
|
71
|
+
}) => void;
|
|
72
|
+
}
|
|
73
|
+
export interface ChatClientEvents extends ChatEvents {
|
|
74
|
+
connected: () => void;
|
|
75
|
+
disconnected: (reason: string) => void;
|
|
76
|
+
joined: (result: ChatJoinResult) => void;
|
|
77
|
+
error: (err: Error) => void;
|
|
78
|
+
/** The session ended and will not recover. Nothing further will be emitted. */
|
|
79
|
+
closed: (reason: 'gave-up' | 'left' | 'ended') => void;
|
|
80
|
+
/** The account behind this connection ran out; see StreamClient's event. */
|
|
81
|
+
usageLimit: (e: {
|
|
82
|
+
reason: string;
|
|
83
|
+
graceSeconds: number;
|
|
84
|
+
endsAt: number;
|
|
85
|
+
}) => void;
|
|
86
|
+
}
|
|
87
|
+
/** An error returned by the server, carrying its machine-readable code. */
|
|
88
|
+
export declare class ChatRpcError extends Error {
|
|
89
|
+
readonly code: string;
|
|
90
|
+
constructor(code: string, message: string);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* A chat-only connection. Takes the `wsUrl` and `token` returned by
|
|
94
|
+
* `POST /v1/chat/{channel}/tokens`.
|
|
95
|
+
*/
|
|
96
|
+
export declare class ChatClient {
|
|
97
|
+
private readonly wsUrl;
|
|
98
|
+
private ws;
|
|
99
|
+
private readonly pending;
|
|
100
|
+
private readonly listeners;
|
|
101
|
+
private nextId;
|
|
102
|
+
private token;
|
|
103
|
+
private joinResult;
|
|
104
|
+
private closedByUs;
|
|
105
|
+
private reconnecting;
|
|
106
|
+
private reconnectAttempt;
|
|
107
|
+
constructor(wsUrl: string, token: string);
|
|
108
|
+
on<K extends keyof ChatClientEvents>(event: K, fn: ChatClientEvents[K]): () => void;
|
|
109
|
+
private emit;
|
|
110
|
+
/** What join returned, including the history the channel had at that moment. */
|
|
111
|
+
get info(): ChatJoinResult | null;
|
|
112
|
+
connect(): Promise<ChatJoinResult>;
|
|
113
|
+
private openSocket;
|
|
114
|
+
private detachSocket;
|
|
115
|
+
/** Single-flight, with backoff: the same shape as the room client's. */
|
|
116
|
+
private scheduleReconnect;
|
|
117
|
+
close(): void;
|
|
118
|
+
private rpc;
|
|
119
|
+
private failAllPending;
|
|
120
|
+
private onMessage;
|
|
121
|
+
sendMessage(text: string, opts?: SendMessageOptions): Promise<{
|
|
122
|
+
id: string;
|
|
123
|
+
ts: number;
|
|
124
|
+
}>;
|
|
125
|
+
/** Older messages, oldest first. Pass the oldest id you hold as `before`. */
|
|
126
|
+
getMessages(opts?: {
|
|
127
|
+
limit?: number;
|
|
128
|
+
before?: string;
|
|
129
|
+
}): Promise<ChatMessage[]>;
|
|
130
|
+
/** Your own message, or anyone's if you are a moderator. */
|
|
131
|
+
deleteMessage(messageId: string): Promise<unknown>;
|
|
132
|
+
/** Moderators only. A mute outlasts the muted socket reconnecting. */
|
|
133
|
+
muteChat(identity: string, muted?: boolean): Promise<unknown>;
|
|
134
|
+
/** Advisory, and throttled by the server; safe to call on every keystroke. */
|
|
135
|
+
setTyping(typing: boolean): Promise<unknown>;
|
|
136
|
+
getChatState(): Promise<ChatState>;
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=chat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/chat.ts"],"names":[],"mappings":"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"}
|