@trystero-p2p/torrent 0.23.1 β 0.25.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 +272 -229
- package/dist/index.d.mts +3 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +64 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Trystero makes browsers discover each other and communicate directly. No
|
|
|
8
8
|
accounts. No deploying infrastructure. Just import and connect.
|
|
9
9
|
|
|
10
10
|
Peers can connect via π BitTorrent, π¦ Nostr, π‘ MQTT, β‘οΈ Supabase, π₯Firebase,
|
|
11
|
-
|
|
11
|
+
πͺ IPFS, or a π self-hosted WebSocket relay β all using the same API.
|
|
12
12
|
|
|
13
13
|
Besides making peer matching automatic, Trystero offers some nice abstractions
|
|
14
14
|
on top of WebRTC:
|
|
@@ -42,6 +42,7 @@ You can see what people are building with Trystero
|
|
|
42
42
|
- [React hooks](#react-hooks)
|
|
43
43
|
- [Troubleshooting connection issues](#troubleshooting-connection-issues)
|
|
44
44
|
- [Running server-side (Node, Bun)](#running-server-side-node-bun)
|
|
45
|
+
- [Self-hosted WebSocket relay](#self-hosted-websocket-relay)
|
|
45
46
|
- [Write your own strategy](#write-your-own-strategy)
|
|
46
47
|
- [Supabase setup](#supabase-setup)
|
|
47
48
|
- [Firebase setup](#firebase-setup)
|
|
@@ -64,8 +65,9 @@ To establish a direct peer-to-peer connection with WebRTC, a signalling channel
|
|
|
64
65
|
is needed to exchange peer information
|
|
65
66
|
([SDP](https://en.wikipedia.org/wiki/Session_Description_Protocol)). Typically
|
|
66
67
|
this involves running your own matchmaking server but Trystero abstracts this
|
|
67
|
-
away for you and offers multiple
|
|
68
|
-
|
|
68
|
+
away for you and offers multiple strategies for connecting peers (currently
|
|
69
|
+
BitTorrent, Nostr, MQTT, Supabase, Firebase, IPFS, and self-hosted WebSocket
|
|
70
|
+
relay).
|
|
69
71
|
|
|
70
72
|
The important point to remember is this:
|
|
71
73
|
|
|
@@ -112,6 +114,8 @@ import {joinRoom} from '@trystero-p2p/supabase'
|
|
|
112
114
|
import {joinRoom} from '@trystero-p2p/firebase'
|
|
113
115
|
// or
|
|
114
116
|
import {joinRoom} from '@trystero-p2p/ipfs'
|
|
117
|
+
// or
|
|
118
|
+
import {joinRoom} from '@trystero-p2p/ws-relay'
|
|
115
119
|
```
|
|
116
120
|
|
|
117
121
|
Next, join the user to a room with an ID:
|
|
@@ -138,21 +142,20 @@ Supabase, it should be your project URL.
|
|
|
138
142
|
Listen for peers joining the room:
|
|
139
143
|
|
|
140
144
|
```js
|
|
141
|
-
room.onPeerJoin
|
|
145
|
+
room.onPeerJoin = peerId => console.log(`${peerId} joined`)
|
|
142
146
|
```
|
|
143
147
|
|
|
144
148
|
Listen for peers leaving the room:
|
|
145
149
|
|
|
146
150
|
```js
|
|
147
|
-
room.onPeerLeave
|
|
151
|
+
room.onPeerLeave = peerId => console.log(`${peerId} left`)
|
|
148
152
|
```
|
|
149
153
|
|
|
150
154
|
Listen for peers sending their audio/video streams:
|
|
151
155
|
|
|
152
156
|
```js
|
|
153
|
-
room.onPeerStream(
|
|
154
|
-
(
|
|
155
|
-
)
|
|
157
|
+
room.onPeerStream = (stream, peerId) =>
|
|
158
|
+
(peerElements[peerId].video.srcObject = stream)
|
|
156
159
|
```
|
|
157
160
|
|
|
158
161
|
To unsubscribe from events, leave the room:
|
|
@@ -184,20 +187,61 @@ room.addStream(stream)
|
|
|
184
187
|
Send and subscribe to custom peer-to-peer actions:
|
|
185
188
|
|
|
186
189
|
```js
|
|
187
|
-
const
|
|
190
|
+
const drink = room.makeAction('drink')
|
|
188
191
|
|
|
189
192
|
// buy drink for a friend
|
|
190
|
-
|
|
193
|
+
drink.send({drink: 'negroni', withIce: true}, {target: friendId})
|
|
191
194
|
|
|
192
|
-
// buy round for the house
|
|
193
|
-
|
|
195
|
+
// buy round for the house
|
|
196
|
+
drink.send({drink: 'mezcal', withIce: false})
|
|
194
197
|
|
|
195
198
|
// listen for drinks sent to you
|
|
196
|
-
|
|
199
|
+
drink.onMessage = (data, {peerId}) =>
|
|
197
200
|
console.log(
|
|
198
201
|
`got a ${data.drink} with${data.withIce ? '' : 'out'} ice from ${peerId}`
|
|
199
202
|
)
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Actions can also use request/response semantics:
|
|
206
|
+
|
|
207
|
+
```js
|
|
208
|
+
const isEven = room.makeAction('is-even', {
|
|
209
|
+
kind: 'request',
|
|
210
|
+
onRequest: n => n % 2 === 0
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
const result = await isEven.request(42, {
|
|
214
|
+
target: friendId,
|
|
215
|
+
timeoutMs: 1000
|
|
216
|
+
})
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
To ask multiple peers at once, use `requestMany()`. It resolves with a
|
|
220
|
+
peer-labeled result for every target, while `onResult` lets you react as each
|
|
221
|
+
peer answers:
|
|
222
|
+
|
|
223
|
+
```js
|
|
224
|
+
const availability = room.makeAction('availability', {
|
|
225
|
+
kind: 'request',
|
|
226
|
+
onRequest: ({date}) => calendar.isFree(date)
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const results = await availability.requestMany(
|
|
230
|
+
{date: '2026-05-04'},
|
|
231
|
+
{
|
|
232
|
+
targets: teammateIds,
|
|
233
|
+
timeoutMs: 1000,
|
|
234
|
+
onResult: result => {
|
|
235
|
+
if (result.status === 'fulfilled') {
|
|
236
|
+
updateAvailabilityBadge(result.peerId, result.value)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
200
240
|
)
|
|
241
|
+
|
|
242
|
+
const freePeers = results
|
|
243
|
+
.filter(result => result.status === 'fulfilled' && result.value)
|
|
244
|
+
.map(result => result.peerId)
|
|
201
245
|
```
|
|
202
246
|
|
|
203
247
|
If you're using TypeScript, you can add a type hint to the action:
|
|
@@ -205,42 +249,40 @@ If you're using TypeScript, you can add a type hint to the action:
|
|
|
205
249
|
```typescript
|
|
206
250
|
type CursorMove = {x: number; y: number}
|
|
207
251
|
|
|
208
|
-
const
|
|
252
|
+
const cursor = room.makeAction<CursorMove>('cursor-move')
|
|
209
253
|
```
|
|
210
254
|
|
|
211
255
|
You can also use actions to send binary data, like images:
|
|
212
256
|
|
|
213
257
|
```js
|
|
214
|
-
const
|
|
258
|
+
const pic = room.makeAction('pic')
|
|
215
259
|
|
|
216
260
|
// blobs are automatically handled, as are any form of TypedArray
|
|
217
|
-
canvas.toBlob(blob =>
|
|
261
|
+
canvas.toBlob(blob => pic.send(blob))
|
|
218
262
|
|
|
219
263
|
// binary data is received as raw ArrayBuffers so your handling code should
|
|
220
264
|
// interpret it in a way that makes sense
|
|
221
|
-
|
|
222
|
-
(
|
|
223
|
-
)
|
|
265
|
+
pic.onMessage = (data, {peerId}) =>
|
|
266
|
+
(imgs[peerId].src = URL.createObjectURL(new Blob([data])))
|
|
224
267
|
```
|
|
225
268
|
|
|
226
269
|
Let's say we want users to be able to name themselves:
|
|
227
270
|
|
|
228
271
|
```js
|
|
229
272
|
const idsToNames = {}
|
|
230
|
-
const
|
|
273
|
+
const name = room.makeAction('name')
|
|
231
274
|
|
|
232
275
|
// tell new peers your name when they connect
|
|
233
|
-
room.onPeerJoin
|
|
276
|
+
room.onPeerJoin = peerId => name.send('Oedipa', {target: peerId})
|
|
234
277
|
|
|
235
278
|
// listen for peers naming themselves
|
|
236
|
-
|
|
279
|
+
name.onMessage = (value, {peerId}) => (idsToNames[peerId] = value)
|
|
237
280
|
|
|
238
281
|
// tell all peers at once when your name changes
|
|
239
|
-
nameInput.addEventListener('change', e =>
|
|
282
|
+
nameInput.addEventListener('change', e => name.send(e.target.value))
|
|
240
283
|
|
|
241
|
-
room.onPeerLeave
|
|
284
|
+
room.onPeerLeave = peerId =>
|
|
242
285
|
console.log(`${idsToNames[peerId] || 'a weird stranger'} left`)
|
|
243
|
-
)
|
|
244
286
|
```
|
|
245
287
|
|
|
246
288
|
> Actions are smart and handle serialization and chunking for you behind the
|
|
@@ -266,10 +308,10 @@ const selfStream = await navigator.mediaDevices.getUserMedia({
|
|
|
266
308
|
room.addStream(selfStream)
|
|
267
309
|
|
|
268
310
|
// send stream to peers who join later
|
|
269
|
-
room.onPeerJoin
|
|
311
|
+
room.onPeerJoin = peerId => room.addStream(selfStream, {target: peerId})
|
|
270
312
|
|
|
271
313
|
// handle streams from other peers
|
|
272
|
-
room.onPeerStream(
|
|
314
|
+
room.onPeerStream = (stream, peerId) => {
|
|
273
315
|
// create an audio instance and set the incoming stream
|
|
274
316
|
const audio = new Audio()
|
|
275
317
|
audio.srcObject = stream
|
|
@@ -278,7 +320,7 @@ room.onPeerStream((stream, peerId) => {
|
|
|
278
320
|
// add the audio to peerAudios object if you want to address it for something
|
|
279
321
|
// later (volume, etc.)
|
|
280
322
|
peerAudios[peerId] = audio
|
|
281
|
-
}
|
|
323
|
+
}
|
|
282
324
|
```
|
|
283
325
|
|
|
284
326
|
Doing the same with video is similar, just be sure to add incoming streams to
|
|
@@ -288,7 +330,7 @@ video elements in the DOM:
|
|
|
288
330
|
const peerVideos = {}
|
|
289
331
|
const videoContainer = document.getElementById('videos')
|
|
290
332
|
|
|
291
|
-
room.onPeerStream(
|
|
333
|
+
room.onPeerStream = (stream, peerId) => {
|
|
292
334
|
let video = peerVideos[peerId]
|
|
293
335
|
|
|
294
336
|
// if this peer hasn't sent a stream before, create a video element
|
|
@@ -302,7 +344,7 @@ room.onPeerStream((stream, peerId) => {
|
|
|
302
344
|
|
|
303
345
|
video.srcObject = stream
|
|
304
346
|
peerVideos[peerId] = video
|
|
305
|
-
}
|
|
347
|
+
}
|
|
306
348
|
```
|
|
307
349
|
|
|
308
350
|
## Advanced
|
|
@@ -315,18 +357,17 @@ interpreted. Instead of manually adding metadata bytes to the buffer you can
|
|
|
315
357
|
simply pass a metadata argument in the sender action for your binary payload:
|
|
316
358
|
|
|
317
359
|
```js
|
|
318
|
-
const
|
|
360
|
+
const file = room.makeAction('file')
|
|
319
361
|
|
|
320
|
-
|
|
362
|
+
file.onMessage = (data, {peerId, metadata}) =>
|
|
321
363
|
console.log(
|
|
322
364
|
`got a file (${metadata.name}) from ${peerId} with type ${metadata.type}`,
|
|
323
365
|
data
|
|
324
366
|
)
|
|
325
|
-
)
|
|
326
367
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
368
|
+
file.send(buffer, {
|
|
369
|
+
metadata: {name: 'The CourierΚΌs Tragedy', type: 'application/pdf'}
|
|
370
|
+
})
|
|
330
371
|
```
|
|
331
372
|
|
|
332
373
|
### Action promises
|
|
@@ -336,7 +377,7 @@ sending. You can optionally use this to indicate to the user when a large
|
|
|
336
377
|
transfer is done.
|
|
337
378
|
|
|
338
379
|
```js
|
|
339
|
-
await
|
|
380
|
+
await file.send(amplePayload)
|
|
340
381
|
console.log('done sending to all peers')
|
|
341
382
|
```
|
|
342
383
|
|
|
@@ -348,30 +389,22 @@ a progress bar to the sender for large transfers. The callback is called with a
|
|
|
348
389
|
percentage value between 0 and 1 and the receiving peer's ID:
|
|
349
390
|
|
|
350
391
|
```js
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
// metadata, which can also be null if you're only interested in the
|
|
357
|
-
// progress handler
|
|
358
|
-
{filename: 'paranoids.flac'},
|
|
359
|
-
// assuming each peer has a loading bar added to the DOM, its value is
|
|
360
|
-
// updated here
|
|
361
|
-
(percent, peerId) => (loadingBars[peerId].value = percent)
|
|
362
|
-
)
|
|
392
|
+
file.send(payload, {
|
|
393
|
+
target: [peerIdA, peerIdB, peerIdC],
|
|
394
|
+
metadata: {filename: 'paranoids.flac'},
|
|
395
|
+
onProgress: (percent, {peerId}) => (loadingBars[peerId].value = percent)
|
|
396
|
+
})
|
|
363
397
|
```
|
|
364
398
|
|
|
365
399
|
Similarly you can listen for progress events as a receiver like this:
|
|
366
400
|
|
|
367
401
|
```js
|
|
368
|
-
const
|
|
402
|
+
const file = room.makeAction('file')
|
|
369
403
|
|
|
370
|
-
|
|
404
|
+
file.onReceiveProgress = (percent, {peerId, metadata}) =>
|
|
371
405
|
console.log(
|
|
372
406
|
`${percent * 100}% done receiving ${metadata.filename} from ${peerId}`
|
|
373
407
|
)
|
|
374
|
-
)
|
|
375
408
|
```
|
|
376
409
|
|
|
377
410
|
Notice that any metadata is sent with progress events so you can show the
|
|
@@ -418,23 +451,22 @@ const trysteroConfig = {appId: 'thurn-und-taxis'}
|
|
|
418
451
|
|
|
419
452
|
export default function App({roomId}) {
|
|
420
453
|
const room = joinRoom(trysteroConfig, roomId)
|
|
421
|
-
const
|
|
454
|
+
const colorAction = room.makeAction('color')
|
|
422
455
|
const [myColor, setMyColor] = useState('#c0ffee')
|
|
423
456
|
const [peerColors, setPeerColors] = useState({})
|
|
424
457
|
|
|
425
458
|
// whenever new peers join the room, send my color to them:
|
|
426
|
-
room.onPeerJoin
|
|
459
|
+
room.onPeerJoin = peer => colorAction.send(myColor, {target: peer})
|
|
427
460
|
|
|
428
461
|
// listen for peers sending their colors and update the state accordingly:
|
|
429
|
-
|
|
430
|
-
setPeerColors(peerColors => ({...peerColors, [
|
|
431
|
-
)
|
|
462
|
+
colorAction.onMessage = (color, {peerId}) =>
|
|
463
|
+
setPeerColors(peerColors => ({...peerColors, [peerId]: color}))
|
|
432
464
|
|
|
433
465
|
const updateColor = e => {
|
|
434
466
|
const {value} = e.target
|
|
435
467
|
|
|
436
468
|
// when updating my own color, broadcast it to all peers:
|
|
437
|
-
|
|
469
|
+
colorAction.send(value)
|
|
438
470
|
setMyColor(value)
|
|
439
471
|
}
|
|
440
472
|
|
|
@@ -548,10 +580,42 @@ const room = joinRoom(
|
|
|
548
580
|
)
|
|
549
581
|
```
|
|
550
582
|
|
|
583
|
+
### Self-hosted WebSocket relay
|
|
584
|
+
|
|
585
|
+
If you want a tiny relay that you control, use the WebSocket relay package.
|
|
586
|
+
Start the relay on Node or Bun:
|
|
587
|
+
|
|
588
|
+
```js
|
|
589
|
+
import {createWsRelayServer} from '@trystero-p2p/ws-relay/server'
|
|
590
|
+
|
|
591
|
+
createWsRelayServer({port: 8080})
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
Then in the browser:
|
|
595
|
+
|
|
596
|
+
```js
|
|
597
|
+
import {joinRoom} from '@trystero-p2p/ws-relay'
|
|
598
|
+
|
|
599
|
+
const room = joinRoom(
|
|
600
|
+
{
|
|
601
|
+
appId: 'app-id',
|
|
602
|
+
relayConfig: {
|
|
603
|
+
urls: ['wss://localhost:8080']
|
|
604
|
+
}
|
|
605
|
+
},
|
|
606
|
+
'room-id'
|
|
607
|
+
)
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
The `relayConfig.urls` config option is required for this strategy because there
|
|
611
|
+
are no public default servers. You can pass multiple.
|
|
612
|
+
|
|
551
613
|
### Write your own strategy
|
|
552
614
|
|
|
553
615
|
If you want to provide your own signaling backend, you can build a custom
|
|
554
|
-
strategy with `
|
|
616
|
+
strategy with `createTopicStrategy`. This is the recommended helper for simple
|
|
617
|
+
pub/sub relays because Trystero handles room lifecycle details like passive
|
|
618
|
+
mode, announce scheduling, and peer-specific signaling topics.
|
|
555
619
|
|
|
556
620
|
The example below assumes a WebSocket relay that does simple pub/sub routing:
|
|
557
621
|
|
|
@@ -559,74 +623,65 @@ The example below assumes a WebSocket relay that does simple pub/sub routing:
|
|
|
559
623
|
`{"type": "subscribe" | "unsubscribe" | "publish", "topic", "payload"}`
|
|
560
624
|
- Server broadcasts `{"topic", "payload"}` to subscribers of each topic
|
|
561
625
|
|
|
626
|
+
This is just to show you how it works; if you want a simple self-hosted
|
|
627
|
+
solution, use the `ws-relay` package explained above.
|
|
628
|
+
|
|
562
629
|
```js
|
|
563
|
-
import {
|
|
630
|
+
import {createTopicStrategy, toJson} from '@trystero-p2p/core'
|
|
564
631
|
|
|
565
|
-
export const joinRoom =
|
|
632
|
+
export const joinRoom = createTopicStrategy({
|
|
566
633
|
// Define init as a function that returns a promise of your signaling client.
|
|
567
634
|
// Resolve the promise when your client is ready to send messages.
|
|
568
635
|
// You can also return an array of client promises for redundancy.
|
|
569
636
|
// In this case, the client is a single WebSocket.
|
|
570
637
|
init: config =>
|
|
571
638
|
new Promise((resolve, reject) => {
|
|
572
|
-
const ws = new WebSocket(config.
|
|
639
|
+
const ws = new WebSocket(config.relayConfig.urls[0])
|
|
573
640
|
ws.addEventListener('open', () => resolve(ws), {once: true})
|
|
574
641
|
ws.addEventListener('error', reject, {once: true})
|
|
575
642
|
}),
|
|
576
643
|
|
|
577
|
-
// Subscribe
|
|
578
|
-
|
|
579
|
-
// 2. rootTopic is the channel where peers in a room announce their presence.
|
|
580
|
-
// 3. selfTopic is the current peer's own channel where messages specific to
|
|
581
|
-
// it should flow (i.e. offers and answers from external peers).
|
|
582
|
-
// 4. onMessage is a function that should be called when your relay client
|
|
583
|
-
// gets a message. Call it with the topic, data payload, and a callback
|
|
584
|
-
// function that takes another topic and payload.
|
|
585
|
-
// 5. getOffers is an optional helper for precomputed offers (only needed if
|
|
586
|
-
// the strategy needs to send offers in bulk, like torrents).
|
|
587
|
-
// It should return a cleanup function that unsubscribes
|
|
588
|
-
subscribe: (client, rootTopic, selfTopic, onMessage, _getOffers) => {
|
|
589
|
-
const topics = [rootTopic, selfTopic]
|
|
644
|
+
// Subscribe to one topic. Trystero decides which topics are needed and when.
|
|
645
|
+
subscribeTopic: (client, topic, onMessage) => {
|
|
590
646
|
const onWsMessage = event => {
|
|
591
|
-
const
|
|
647
|
+
const message = JSON.parse(String(event.data))
|
|
592
648
|
|
|
593
|
-
if (
|
|
649
|
+
if (message.topic !== topic) {
|
|
594
650
|
return
|
|
595
651
|
}
|
|
596
652
|
|
|
597
|
-
onMessage(topic, payload
|
|
598
|
-
client.send(
|
|
599
|
-
toJson({type: 'publish', topic: peerTopic, payload: signal})
|
|
600
|
-
)
|
|
601
|
-
)
|
|
653
|
+
onMessage(message.topic, message.payload)
|
|
602
654
|
}
|
|
603
655
|
|
|
604
656
|
client.addEventListener('message', onWsMessage)
|
|
605
|
-
|
|
657
|
+
client.send(toJson({type: 'subscribe', topic}))
|
|
606
658
|
|
|
607
659
|
return () => {
|
|
608
|
-
|
|
660
|
+
client.send(toJson({type: 'unsubscribe', topic}))
|
|
609
661
|
client.removeEventListener('message', onWsMessage)
|
|
610
662
|
}
|
|
611
663
|
},
|
|
612
664
|
|
|
613
|
-
//
|
|
614
|
-
|
|
665
|
+
// Publish a payload to one topic.
|
|
666
|
+
publishTopic: (client, topic, payload) =>
|
|
615
667
|
client.send(
|
|
616
668
|
toJson({
|
|
617
669
|
type: 'publish',
|
|
618
|
-
topic
|
|
619
|
-
payload
|
|
670
|
+
topic,
|
|
671
|
+
payload
|
|
620
672
|
})
|
|
621
673
|
)
|
|
622
674
|
})
|
|
623
675
|
|
|
624
676
|
const room = joinRoom(
|
|
625
|
-
{appId: 'my-app-id',
|
|
677
|
+
{appId: 'my-app-id', relayConfig: {urls: ['wss://my-relay.example']}},
|
|
626
678
|
'my-room-id'
|
|
627
679
|
)
|
|
628
680
|
```
|
|
629
681
|
|
|
682
|
+
For non-pub/sub signaling protocols, such as trackers that exchange offers in
|
|
683
|
+
bulk, `createStrategy` is available as a lower-level advanced API.
|
|
684
|
+
|
|
630
685
|
### Supabase setup
|
|
631
686
|
|
|
632
687
|
To use the Supabase strategy:
|
|
@@ -634,7 +689,8 @@ To use the Supabase strategy:
|
|
|
634
689
|
1. Create a [Supabase](https://supabase.com) project or use an existing one
|
|
635
690
|
2. On the dashboard, go to Project Settings -> API
|
|
636
691
|
3. Copy the Project URL and set that as the `appId` in the Trystero config, copy
|
|
637
|
-
the `anon public` API key and set it as `supabaseKey` in the
|
|
692
|
+
the `anon public` API key and set it as `relayConfig.supabaseKey` in the
|
|
693
|
+
Trystero config
|
|
638
694
|
|
|
639
695
|
### Firebase setup
|
|
640
696
|
|
|
@@ -684,8 +740,8 @@ the same namespace will return the same room instance.
|
|
|
684
740
|
Supabase, this should be set to your project URL (see
|
|
685
741
|
[Supabase setup instructions](#supabase-setup)). If using Firebase, this
|
|
686
742
|
should be the `databaseURL` from your Firebase config (also see
|
|
687
|
-
`firebaseApp` below for an alternative way of configuring the
|
|
688
|
-
strategy).
|
|
743
|
+
`relayConfig.firebaseApp` below for an alternative way of configuring the
|
|
744
|
+
Firebase strategy).
|
|
689
745
|
|
|
690
746
|
- `password` - **(optional)** A string to encrypt session descriptions via
|
|
691
747
|
AES-GCM as they are passed through the peering medium. If not set, session
|
|
@@ -693,15 +749,46 @@ the same namespace will return the same room instance.
|
|
|
693
749
|
name. A custom password must match between any peers in the room for them to
|
|
694
750
|
connect. See [encryption](#encryption) for more details.
|
|
695
751
|
|
|
696
|
-
- `
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
752
|
+
- `passive` - **(optional)** Boolean for backup or relay peers that should
|
|
753
|
+
listen for active peers without announcing themselves while a room is
|
|
754
|
+
dormant. Passive peers activate only after hearing a non-passive peer and
|
|
755
|
+
include a passive flag in their signaling so passive peers do not connect to
|
|
756
|
+
each other. For BitTorrent, dormant passive rooms announce as seeders
|
|
757
|
+
(`left: 0`) without offers, which avoids passive-to-passive discovery while
|
|
758
|
+
keeping tracker load low.
|
|
759
|
+
|
|
760
|
+
- `relayConfig` - **(optional unless required by your chosen strategy)**
|
|
761
|
+
Object containing strategy-specific relay settings:
|
|
762
|
+
- `urls` - **(optional for π BitTorrent, π¦ Nostr, π‘ MQTT; required for π
|
|
763
|
+
WebSocket relay)** Custom list of URLs for the strategy to use to
|
|
764
|
+
bootstrap P2P connections. These would be BitTorrent trackers, Nostr
|
|
765
|
+
relays, MQTT brokers, and WebSocket relays, respectively. They must
|
|
766
|
+
support secure WebSocket connections.
|
|
767
|
+
|
|
768
|
+
- `redundancy` - **(optional, π BitTorrent, π¦ Nostr, π‘ MQTT only)**
|
|
769
|
+
Integer specifying how many default relay endpoints to connect to
|
|
770
|
+
simultaneously. Passing a `urls` option will cause this option to be
|
|
771
|
+
ignored as the entire list will be used.
|
|
772
|
+
|
|
773
|
+
- `manualReconnection` - **(optional, π¦ Nostr and π BitTorrent only)**
|
|
774
|
+
Boolean (default: `false`) that when set to `true` disables automatically
|
|
775
|
+
pausing and resuming reconnection attempts when the browser goes offline
|
|
776
|
+
and comes back online. This is useful if you want to manage this behavior
|
|
777
|
+
yourself.
|
|
778
|
+
|
|
779
|
+
- `supabaseKey` - **(required, β‘οΈ Supabase only)** Your Supabase project's
|
|
780
|
+
`anon public` API key.
|
|
781
|
+
|
|
782
|
+
- `firebaseApp` - **(optional, π₯ Firebase only)** You can pass an already
|
|
783
|
+
initialized Firebase app instance instead of an `appId`. Normally Trystero
|
|
784
|
+
will initialize a Firebase app based on the `appId` but this will fail if
|
|
785
|
+
you've already initialized it for use elsewhere.
|
|
786
|
+
|
|
787
|
+
- `firebasePath` - **(optional, π₯ Firebase only)** String specifying path
|
|
788
|
+
where Trystero writes its matchmaking data in your database
|
|
789
|
+
(`'__trystero__'` by default). Changing this is useful if you want to run
|
|
790
|
+
multiple apps using the same database and don't want to worry about
|
|
791
|
+
namespace collisions.
|
|
705
792
|
|
|
706
793
|
- `rtcConfig` - **(optional)** Specifies a custom
|
|
707
794
|
[`RTCConfiguration`](https://developer.mozilla.org/en-US/docs/Web/API/RTCConfiguration)
|
|
@@ -727,32 +814,16 @@ the same namespace will return the same room instance.
|
|
|
727
814
|
constructor. This is useful for running outside of a browser, such as in
|
|
728
815
|
Node (still experimental).
|
|
729
816
|
|
|
730
|
-
- `supabaseKey` - **(required, β‘οΈ Supabase only)** Your Supabase project's
|
|
731
|
-
`anon public` API key.
|
|
732
|
-
|
|
733
|
-
- `firebaseApp` - **(optional, π₯ Firebase only)** You can pass an already
|
|
734
|
-
initialized Firebase app instance instead of an `appId`. Normally Trystero
|
|
735
|
-
will initialize a Firebase app based on the `appId` but this will fail if
|
|
736
|
-
you've already initialized it for use elsewhere.
|
|
737
|
-
|
|
738
|
-
- `rootPath` - **(optional, π₯ Firebase only)** String specifying path where
|
|
739
|
-
Trystero writes its matchmaking data in your database (`'__trystero__'` by
|
|
740
|
-
default). Changing this is useful if you want to run multiple apps using the
|
|
741
|
-
same database and don't want to worry about namespace collisions.
|
|
742
|
-
|
|
743
|
-
- `manualRelayReconnection` - **(optional, π¦ Nostr and π BitTorrent only)**
|
|
744
|
-
Boolean (default: `false`) that when set to `true` disables automatically
|
|
745
|
-
pausing and resuming reconnection attempts when the browser goes offline and
|
|
746
|
-
comes back online. This is useful if you want to manage this behavior
|
|
747
|
-
yourself.
|
|
748
|
-
|
|
749
817
|
- `roomId` - A string to namespace peers and events within a room.
|
|
750
818
|
|
|
751
819
|
- `callbacks` - **(optional)** Callback config object containing:
|
|
752
820
|
- `onJoinError(details)` - Called when room join fails due to an incorrect
|
|
753
|
-
password
|
|
754
|
-
|
|
755
|
-
|
|
821
|
+
password, when handshake admission fails (including timeout), or when peers
|
|
822
|
+
exchange SDP but WebRTC still cannot establish a direct connection. This
|
|
823
|
+
last case usually means TURN servers are needed or misconfigured (see
|
|
824
|
+
[Troubleshooting connection issues](#troubleshooting-connection-issues)).
|
|
825
|
+
`details` is an object containing `appId`, `roomId`, `peerId`, and `error`
|
|
826
|
+
describing the failure.
|
|
756
827
|
|
|
757
828
|
- `onPeerHandshake(peerId, send, receive, isInitiator)` - Async predicate that
|
|
758
829
|
runs after the transport connects but before the peer becomes active.
|
|
@@ -811,96 +882,100 @@ Returns an object with the following methods:
|
|
|
811
882
|
for the peers present in room (not including the local user). The keys of this
|
|
812
883
|
object are the respective peers' IDs.
|
|
813
884
|
|
|
814
|
-
- ### `
|
|
885
|
+
- ### `isPassive()`
|
|
886
|
+
|
|
887
|
+
Returns whether the room was joined with `passive: true`.
|
|
888
|
+
|
|
889
|
+
- ### `addStream(stream, [options])`
|
|
815
890
|
|
|
816
891
|
Broadcasts media stream to other peers.
|
|
817
892
|
- `stream` - A `MediaStream` with audio and/or video to send to peers in the
|
|
818
893
|
room.
|
|
819
894
|
|
|
820
|
-
- `
|
|
821
|
-
target peer ID (string) or list of peer IDs (array).
|
|
895
|
+
- `options.target` - **(optional)** If specified, the stream is sent only to
|
|
896
|
+
the target peer ID (string) or list of peer IDs (array). Passing `null` or
|
|
897
|
+
omitting this option sends to all peers in the room.
|
|
822
898
|
|
|
823
|
-
- `metadata` - **(optional)** Additional metadata (any serializable
|
|
824
|
-
be sent with the stream. This is useful when sending multiple
|
|
825
|
-
recipients know which is which (e.g. a webcam versus a screen
|
|
826
|
-
|
|
827
|
-
argument, pass `null` as the second argument.
|
|
899
|
+
- `options.metadata` - **(optional)** Additional metadata (any serializable
|
|
900
|
+
type) to be sent with the stream. This is useful when sending multiple
|
|
901
|
+
streams so recipients know which is which (e.g. a webcam versus a screen
|
|
902
|
+
capture).
|
|
828
903
|
|
|
829
|
-
- ### `removeStream(stream, [
|
|
904
|
+
- ### `removeStream(stream, [options])`
|
|
830
905
|
|
|
831
906
|
Stops sending previously sent media stream to other peers.
|
|
832
907
|
- `stream` - A previously sent `MediaStream` to stop sending.
|
|
833
908
|
|
|
834
|
-
- `
|
|
835
|
-
the target peer ID (string) or list of peer IDs (array).
|
|
909
|
+
- `options.target` - **(optional)** If specified, the stream is removed only
|
|
910
|
+
from the target peer ID (string) or list of peer IDs (array).
|
|
836
911
|
|
|
837
|
-
- ### `addTrack(track, stream, [
|
|
912
|
+
- ### `addTrack(track, stream, [options])`
|
|
838
913
|
|
|
839
914
|
Adds a new media track to a stream.
|
|
840
915
|
- `track` - A `MediaStreamTrack` to add to an existing stream.
|
|
841
916
|
|
|
842
917
|
- `stream` - The target `MediaStream` to attach the new track to.
|
|
843
918
|
|
|
844
|
-
- `
|
|
845
|
-
target peer ID (string) or list of peer IDs (array).
|
|
919
|
+
- `options.target` - **(optional)** If specified, the track is sent only to
|
|
920
|
+
the target peer ID (string) or list of peer IDs (array).
|
|
846
921
|
|
|
847
|
-
- `metadata` - **(optional)** Additional metadata (any serializable
|
|
848
|
-
be sent with the track. See `metadata` notes for `addStream()`
|
|
849
|
-
more details.
|
|
922
|
+
- `options.metadata` - **(optional)** Additional metadata (any serializable
|
|
923
|
+
type) to be sent with the track. See `metadata` notes for `addStream()`
|
|
924
|
+
above for more details.
|
|
850
925
|
|
|
851
|
-
- ### `removeTrack(track, [
|
|
926
|
+
- ### `removeTrack(track, [options])`
|
|
852
927
|
|
|
853
928
|
Removes a media track.
|
|
854
929
|
- `track` - The `MediaStreamTrack` to remove.
|
|
855
930
|
|
|
856
|
-
- `
|
|
857
|
-
the target peer ID (string) or list of peer IDs (array).
|
|
931
|
+
- `options.target` - **(optional)** If specified, the track is removed only
|
|
932
|
+
from the target peer ID (string) or list of peer IDs (array).
|
|
858
933
|
|
|
859
|
-
- ### `replaceTrack(oldTrack, newTrack, [
|
|
934
|
+
- ### `replaceTrack(oldTrack, newTrack, [options])`
|
|
860
935
|
|
|
861
936
|
Replaces a media track with a new one.
|
|
862
937
|
- `oldTrack` - The `MediaStreamTrack` to remove.
|
|
863
938
|
|
|
864
939
|
- `newTrack` - A `MediaStreamTrack` to attach.
|
|
865
940
|
|
|
866
|
-
- `
|
|
867
|
-
the target peer ID (string) or list of peer IDs (array).
|
|
941
|
+
- `options.target` - **(optional)** If specified, the track is replaced only
|
|
942
|
+
for the target peer ID (string) or list of peer IDs (array).
|
|
868
943
|
|
|
869
|
-
- `metadata` - **(optional)** Additional metadata (any serializable
|
|
870
|
-
be sent with the replacement track.
|
|
944
|
+
- `options.metadata` - **(optional)** Additional metadata (any serializable
|
|
945
|
+
type) to be sent with the replacement track.
|
|
871
946
|
|
|
872
|
-
- ### `onPeerJoin
|
|
947
|
+
- ### `onPeerJoin`
|
|
873
948
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
Existing active peers are immediately replayed to a newly
|
|
949
|
+
A callback property that will be called when a peer joins the room. Assigning
|
|
950
|
+
a new function replaces the previous handler; assigning `null` clears it.
|
|
951
|
+
Existing active peers are immediately replayed to a newly assigned handler.
|
|
877
952
|
- `callback(peerId)` - Function to run whenever a peer joins, called with the
|
|
878
953
|
peer's ID.
|
|
879
954
|
|
|
880
955
|
Example:
|
|
881
956
|
|
|
882
957
|
```js
|
|
883
|
-
onPeerJoin
|
|
958
|
+
room.onPeerJoin = peerId => console.log(`${peerId} joined`)
|
|
884
959
|
```
|
|
885
960
|
|
|
886
|
-
- ### `onPeerLeave
|
|
961
|
+
- ### `onPeerLeave`
|
|
887
962
|
|
|
888
|
-
|
|
889
|
-
|
|
963
|
+
A callback property that will be called when a peer leaves the room. Assigning
|
|
964
|
+
a new function replaces the previous handler; assigning `null` clears it.
|
|
890
965
|
- `callback(peerId)` - Function to run whenever a peer leaves, called with the
|
|
891
966
|
peer's ID.
|
|
892
967
|
|
|
893
968
|
Example:
|
|
894
969
|
|
|
895
970
|
```js
|
|
896
|
-
onPeerLeave
|
|
971
|
+
room.onPeerLeave = peerId => console.log(`${peerId} left`)
|
|
897
972
|
```
|
|
898
973
|
|
|
899
|
-
- ### `onPeerStream
|
|
974
|
+
- ### `onPeerStream`
|
|
900
975
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
976
|
+
A callback property that will be called when a peer sends a media stream.
|
|
977
|
+
Assigning a new function replaces the previous handler; assigning `null`
|
|
978
|
+
clears it.
|
|
904
979
|
- `callback(stream, peerId, metadata)` - Function to run whenever a peer sends
|
|
905
980
|
a media stream, called with the the peer's stream, ID, and optional metadata
|
|
906
981
|
(see `addStream()` above for details).
|
|
@@ -908,16 +983,15 @@ Returns an object with the following methods:
|
|
|
908
983
|
Example:
|
|
909
984
|
|
|
910
985
|
```js
|
|
911
|
-
onPeerStream(
|
|
986
|
+
room.onPeerStream = (stream, peerId) =>
|
|
912
987
|
console.log(`got stream from ${peerId}`, stream)
|
|
913
|
-
)
|
|
914
988
|
```
|
|
915
989
|
|
|
916
|
-
- ### `onPeerTrack
|
|
990
|
+
- ### `onPeerTrack`
|
|
917
991
|
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
992
|
+
A callback property that will be called when a peer sends a media track.
|
|
993
|
+
Assigning a new function replaces the previous handler; assigning `null`
|
|
994
|
+
clears it.
|
|
921
995
|
- `callback(track, stream, peerId, metadata)` - Function to run whenever a
|
|
922
996
|
peer sends a media track, called with the the peer's track, attached stream,
|
|
923
997
|
ID, and optional metadata (see `addTrack()` above for details).
|
|
@@ -925,78 +999,48 @@ Returns an object with the following methods:
|
|
|
925
999
|
Example:
|
|
926
1000
|
|
|
927
1001
|
```js
|
|
928
|
-
onPeerTrack(
|
|
1002
|
+
room.onPeerTrack = (track, stream, peerId) =>
|
|
929
1003
|
console.log(`got track from ${peerId}`, track)
|
|
930
|
-
)
|
|
931
1004
|
```
|
|
932
1005
|
|
|
933
|
-
- ### `makeAction(actionId)`
|
|
1006
|
+
- ### `makeAction(actionId, [config])`
|
|
934
1007
|
|
|
935
1008
|
Listen for and send custom data actions.
|
|
936
1009
|
- `actionId` - A string to register this action consistently among all peers.
|
|
937
1010
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
- Sends data to peers and returns a promise that resolves when all target
|
|
941
|
-
peers are finished sending locally.
|
|
942
|
-
|
|
943
|
-
- `(data, [targetPeers], [metadata], [onProgress])`
|
|
944
|
-
- `data` - Any value to send (primitive, object, binary). Serialization
|
|
945
|
-
and chunking is handled automatically. Binary data (e.g. `Blob`,
|
|
946
|
-
`TypedArray`) is received by other peer as an agnostic `ArrayBuffer`.
|
|
947
|
-
|
|
948
|
-
- `targetPeers` - **(optional)** Either a peer ID (string), an array of
|
|
949
|
-
peer IDs, or `null` (indicating to send to all peers in the room).
|
|
950
|
-
|
|
951
|
-
- `metadata` - **(optional)** If the data is binary, you can send an
|
|
952
|
-
optional metadata object describing it (see
|
|
953
|
-
[Binary metadata](#binary-metadata)).
|
|
1011
|
+
If `config.kind` is omitted, the action is a one-way message action. Passing
|
|
1012
|
+
`kind: 'request'` creates a request/response action.
|
|
954
1013
|
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1014
|
+
Message actions expose:
|
|
1015
|
+
- `send(data, [options])` - Sends data to peers and resolves when local
|
|
1016
|
+
sending is complete.
|
|
1017
|
+
- `onMessage` - Nullable callback property for received messages.
|
|
1018
|
+
- `onReceiveProgress` - Nullable callback property for inbound progress.
|
|
959
1019
|
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1020
|
+
Request actions expose:
|
|
1021
|
+
- `request(data, options)` - Sends to one peer and resolves with its response.
|
|
1022
|
+
- `requestMany(data, options)` - Sends to many peers and resolves with
|
|
1023
|
+
peer-labeled settled results.
|
|
1024
|
+
- `onRequest` - Nullable callback property that returns the response.
|
|
1025
|
+
- `onReceiveProgress` - Nullable callback property for inbound progress.
|
|
963
1026
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
- `peerId` - The ID string of the sending peer.
|
|
970
|
-
|
|
971
|
-
- `metadata` - **(optional)** Optional metadata object supplied by the
|
|
972
|
-
sender if `data` is binary, e.g. a filename.
|
|
973
|
-
|
|
974
|
-
3. #### Progress handler
|
|
975
|
-
- Registers a callback function that runs when partial data is received
|
|
976
|
-
from peers. You can use this for tracking large binary transfers. See
|
|
977
|
-
[Progress updates](#progress-updates) for an example.
|
|
978
|
-
|
|
979
|
-
- `(percent, peerId, metadata)`
|
|
980
|
-
- `percent` - A number between 0 and 1 indicating the percentage complete
|
|
981
|
-
of the transfer.
|
|
982
|
-
|
|
983
|
-
- `peerId` - The ID string of the sending peer.
|
|
984
|
-
|
|
985
|
-
- `metadata` - **(optional)** Optional metadata object supplied by the
|
|
986
|
-
sender.
|
|
1027
|
+
Send options use `target`, `metadata`, `onProgress`, and `signal`. Request
|
|
1028
|
+
options use `target`, `metadata`, `timeoutMs`, `onProgress`, and `signal`.
|
|
1029
|
+
`requestMany()` uses `targets` plus optional `onResult` for each peer result
|
|
1030
|
+
as it arrives.
|
|
987
1031
|
|
|
988
1032
|
Example:
|
|
989
1033
|
|
|
990
1034
|
```js
|
|
991
|
-
const
|
|
1035
|
+
const cursor = room.makeAction('cursormove')
|
|
992
1036
|
|
|
993
|
-
window.addEventListener('mousemove', e =>
|
|
1037
|
+
window.addEventListener('mousemove', e => cursor.send([e.clientX, e.clientY]))
|
|
994
1038
|
|
|
995
|
-
|
|
1039
|
+
cursor.onMessage = ([x, y], {peerId}) => {
|
|
996
1040
|
const peerCursor = cursorMap[peerId]
|
|
997
1041
|
peerCursor.style.left = x + 'px'
|
|
998
1042
|
peerCursor.style.top = y + 'px'
|
|
999
|
-
}
|
|
1043
|
+
}
|
|
1000
1044
|
```
|
|
1001
1045
|
|
|
1002
1046
|
- ### `ping(peerId)`
|
|
@@ -1009,12 +1053,11 @@ Returns an object with the following methods:
|
|
|
1009
1053
|
|
|
1010
1054
|
```js
|
|
1011
1055
|
// log round-trip time every 2 seconds
|
|
1012
|
-
room.onPeerJoin
|
|
1056
|
+
room.onPeerJoin = peerId =>
|
|
1013
1057
|
setInterval(
|
|
1014
1058
|
async () => console.log(`took ${await room.ping(peerId)}ms`),
|
|
1015
1059
|
2000
|
|
1016
1060
|
)
|
|
1017
|
-
)
|
|
1018
1061
|
```
|
|
1019
1062
|
|
|
1020
1063
|
### `selfId`
|
|
@@ -1024,10 +1067,10 @@ rooms.
|
|
|
1024
1067
|
|
|
1025
1068
|
### `getRelaySockets()`
|
|
1026
1069
|
|
|
1027
|
-
**(π BitTorrent, π¦ Nostr, π‘ MQTT only)** Returns an
|
|
1028
|
-
mapped to their WebSocket connections. This can be
|
|
1029
|
-
state of the user's connection to the relays and
|
|
1030
|
-
failures.
|
|
1070
|
+
**(π BitTorrent, π¦ Nostr, π‘ MQTT, π WebSocket relay only)** Returns an
|
|
1071
|
+
object of relay URL keys mapped to their WebSocket connections. This can be
|
|
1072
|
+
useful for determining the state of the user's connection to the relays and
|
|
1073
|
+
handling any connection failures.
|
|
1031
1074
|
|
|
1032
1075
|
Example:
|
|
1033
1076
|
|
|
@@ -1044,9 +1087,9 @@ console.log(getRelaySockets())
|
|
|
1044
1087
|
### `pauseRelayReconnection()`
|
|
1045
1088
|
|
|
1046
1089
|
**(π¦ Nostr, π BitTorrent only)** Normally Trystero will try to automatically
|
|
1047
|
-
reconnect to relay sockets unless `
|
|
1048
|
-
room config. Calling this function stops relay reconnection attempts
|
|
1049
|
-
`resumeRelayReconnection()` is called.
|
|
1090
|
+
reconnect to relay sockets unless `relayConfig.manualReconnection: true` is set
|
|
1091
|
+
in the room config. Calling this function stops relay reconnection attempts
|
|
1092
|
+
until `resumeRelayReconnection()` is called.
|
|
1050
1093
|
|
|
1051
1094
|
### `resumeRelayReconnection()`
|
|
1052
1095
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export * from "@trystero-p2p/core";
|
|
1
|
+
import { JoinRoom, JoinRoomConfig, pauseRelayReconnection, resumeRelayReconnection, selfId } from "@trystero-p2p/core";
|
|
2
|
+
export type * from "@trystero-p2p/core";
|
|
3
3
|
|
|
4
4
|
//#region src/index.d.ts
|
|
5
|
-
type TorrentRoomConfig =
|
|
5
|
+
type TorrentRoomConfig = JoinRoomConfig;
|
|
6
6
|
declare const joinRoom: JoinRoom<TorrentRoomConfig>;
|
|
7
7
|
declare const getRelaySockets: any;
|
|
8
8
|
declare const defaultRelayUrls: string[];
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;KAmDY,iBAAA,GAAoB,cAAc;AAAA,cA6ZjC,QAAA,EAAU,QAAQ,CAAC,iBAAA;AAAA,cAcnB,eAAA;AAAA,cAIA,gBAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -9,6 +9,7 @@ const subscriptionTokens = relayManager.scoped();
|
|
|
9
9
|
const trackerAnnounceMs = {};
|
|
10
10
|
const handledSignals = {};
|
|
11
11
|
const msgHandlers = relayManager.scoped();
|
|
12
|
+
const topicStates = relayManager.scoped();
|
|
12
13
|
const roomOutstandingOffers = {};
|
|
13
14
|
const roomOfferGenerationPromises = {};
|
|
14
15
|
const roomSubscriberCounts = {};
|
|
@@ -16,6 +17,7 @@ const trackerAction = "announce";
|
|
|
16
17
|
const hashLimit = 20;
|
|
17
18
|
const offerPoolSize = 3;
|
|
18
19
|
const defaultAnnounceMs = 1e4;
|
|
20
|
+
const dormantAnnounceMs = 12e4;
|
|
19
21
|
const maxAnnounceMs = 2e4;
|
|
20
22
|
const offerRetentionMs = 12e4;
|
|
21
23
|
const signalDedupeWindowMs = 4e3;
|
|
@@ -125,11 +127,12 @@ const joinRoomStrategy = createStrategy({
|
|
|
125
127
|
}));
|
|
126
128
|
return client.ready;
|
|
127
129
|
}),
|
|
128
|
-
subscribe: (client, rootTopic, _, onMessage, getOffers) => {
|
|
130
|
+
subscribe: (client, rootTopic, _, onMessage, getOffers, context) => {
|
|
129
131
|
const handlers = msgHandlers.forRelay(client);
|
|
130
132
|
const relayFns = announceFns.forRelay(client);
|
|
131
133
|
const relayIntervals = announceIntervals.forRelay(client);
|
|
132
134
|
const activeTokens = subscriptionTokens.forRelay(client);
|
|
135
|
+
const states = topicStates.forRelay(client);
|
|
133
136
|
const subscriptionToken = Symbol(rootTopic);
|
|
134
137
|
activeTokens[rootTopic] = subscriptionToken;
|
|
135
138
|
roomSubscriberCounts[rootTopic] = (roomSubscriberCounts[rootTopic] ?? 0) + 1;
|
|
@@ -156,28 +159,45 @@ const joinRoomStrategy = createStrategy({
|
|
|
156
159
|
}
|
|
157
160
|
};
|
|
158
161
|
handlers[rootTopic] = topicHandler;
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
162
|
+
const topicState = {
|
|
163
|
+
announce: async () => {
|
|
164
|
+
if (activeTokens[rootTopic] !== subscriptionToken) return;
|
|
165
|
+
if (!topicState.isActive) {
|
|
166
|
+
send(client, rootTopic, {
|
|
167
|
+
left: 0,
|
|
168
|
+
numwant: offerPoolSize,
|
|
169
|
+
offers: []
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
send(client, rootTopic, {
|
|
174
|
+
numwant: offerPoolSize,
|
|
175
|
+
offers: entries(await ensureOutstandingOffers(rootTopic, getOffers)).map(([id, { offer }]) => ({
|
|
176
|
+
offer_id: id,
|
|
177
|
+
offer: {
|
|
178
|
+
type: "offer",
|
|
179
|
+
sdp: offer
|
|
180
|
+
}
|
|
181
|
+
}))
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
isActive: !context?.isPassive
|
|
171
185
|
};
|
|
172
186
|
trackerAnnounceMs[client.url] = defaultAnnounceMs;
|
|
187
|
+
const { announce } = topicState;
|
|
173
188
|
relayFns[rootTopic] = announce;
|
|
174
|
-
|
|
189
|
+
states[rootTopic] = topicState;
|
|
190
|
+
const initialInterval = topicState.isActive ? trackerAnnounceMs[client.url] : dormantAnnounceMs;
|
|
191
|
+
relayIntervals[rootTopic] = setInterval(announce, initialInterval);
|
|
175
192
|
announce();
|
|
176
193
|
return () => {
|
|
177
194
|
roomSubscriberCounts[rootTopic] = Math.max(0, (roomSubscriberCounts[rootTopic] ?? 1) - 1);
|
|
178
195
|
if (!roomSubscriberCounts[rootTopic]) delete roomSubscriberCounts[rootTopic];
|
|
179
196
|
if (activeTokens[rootTopic] !== subscriptionToken) {
|
|
180
|
-
if (!roomSubscriberCounts[rootTopic])
|
|
197
|
+
if (!roomSubscriberCounts[rootTopic]) {
|
|
198
|
+
reclaimAllOutstandingOffers(rootTopic);
|
|
199
|
+
delete states[rootTopic];
|
|
200
|
+
}
|
|
181
201
|
return;
|
|
182
202
|
}
|
|
183
203
|
const interval = relayIntervals[rootTopic];
|
|
@@ -188,10 +208,38 @@ const joinRoomStrategy = createStrategy({
|
|
|
188
208
|
if (handlers[rootTopic] === topicHandler) delete handlers[rootTopic];
|
|
189
209
|
if (relayFns[rootTopic] === announce) delete relayFns[rootTopic];
|
|
190
210
|
delete activeTokens[rootTopic];
|
|
211
|
+
if (states[rootTopic] === topicState) delete states[rootTopic];
|
|
191
212
|
if (!roomSubscriberCounts[rootTopic]) reclaimAllOutstandingOffers(rootTopic);
|
|
192
213
|
};
|
|
193
214
|
},
|
|
194
|
-
announce: (client) =>
|
|
215
|
+
announce: (client, rootTopic) => {
|
|
216
|
+
const state = topicStates.forRelay(client)[rootTopic];
|
|
217
|
+
const relayIntervals = announceIntervals.forRelay(client);
|
|
218
|
+
const fn = announceFns.forRelay(client)[rootTopic];
|
|
219
|
+
if (state) state.isActive = true;
|
|
220
|
+
if (fn && relayIntervals[rootTopic]) {
|
|
221
|
+
clearInterval(relayIntervals[rootTopic]);
|
|
222
|
+
relayIntervals[rootTopic] = setInterval(() => {
|
|
223
|
+
fn();
|
|
224
|
+
}, trackerAnnounceMs[client.url]);
|
|
225
|
+
fn();
|
|
226
|
+
}
|
|
227
|
+
return trackerAnnounceMs[client.url];
|
|
228
|
+
},
|
|
229
|
+
deactivate: (client, rootTopic) => {
|
|
230
|
+
const state = topicStates.forRelay(client)[rootTopic];
|
|
231
|
+
const relayIntervals = announceIntervals.forRelay(client);
|
|
232
|
+
const fn = announceFns.forRelay(client)[rootTopic];
|
|
233
|
+
if (state) state.isActive = false;
|
|
234
|
+
reclaimAllOutstandingOffers(rootTopic);
|
|
235
|
+
if (fn && relayIntervals[rootTopic]) {
|
|
236
|
+
clearInterval(relayIntervals[rootTopic]);
|
|
237
|
+
relayIntervals[rootTopic] = setInterval(() => {
|
|
238
|
+
fn();
|
|
239
|
+
}, dormantAnnounceMs);
|
|
240
|
+
fn();
|
|
241
|
+
}
|
|
242
|
+
}
|
|
195
243
|
});
|
|
196
244
|
const joinRoom = (config, roomId, callbacks) => joinRoomStrategy({
|
|
197
245
|
...config,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n createRelayManager,\n createStrategy,\n entries,\n fromJson,\n genId,\n getRelays,\n keys,\n libName,\n makeSocket,\n pauseRelayReconnection,\n resumeRelayReconnection,\n selfId,\n sha1,\n toJson,\n type BaseRoomConfig,\n type JoinRoom,\n type OfferRecord,\n type RelayConfig,\n type SocketClient\n} from '@trystero-p2p/core'\n\nconst relayManager = createRelayManager<SocketClient>(client => client.socket)\nconst topicToInfoHash: Record<string, string> = {}\nconst infoHashToTopic: Record<string, string> = {}\nconst announceIntervals = relayManager.scoped<ReturnType<typeof setInterval>>()\nconst announceFns = relayManager.scoped<() => void | Promise<void>>()\nconst subscriptionTokens = relayManager.scoped<symbol>()\nconst trackerAnnounceMs: Record<string, number> = {}\nconst handledSignals: Record<string, number> = {}\nconst msgHandlers = relayManager.scoped<(data: TrackerMessage) => void>()\nconst roomOutstandingOffers: Record<\n string,\n Record<string, OfferRecord & {createdAt: number}>\n> = {}\nconst roomOfferGenerationPromises: Record<string, Promise<void> | undefined> =\n {}\nconst roomSubscriberCounts: Record<string, number> = {}\nconst trackerAction = 'announce'\nconst hashLimit = 20\nconst offerPoolSize = 3\nconst defaultAnnounceMs = 10_000\nconst maxAnnounceMs = 20_000\nconst offerRetentionMs = 120_000\nconst signalDedupeWindowMs = 4_000\nconst defaultRedundancy = 3\n\nexport type TorrentRoomConfig = BaseRoomConfig & RelayConfig\n\ntype TrackerMessage = {\n offer?: {\n type: 'offer'\n sdp: string\n }\n answer?: {\n type: 'answer'\n sdp: string\n }\n offer_id?: string\n peer_id?: string\n info_hash?: string\n interval?: number\n ['failure reason']?: string\n ['warning message']?: string\n}\n\nconst getInfoHash = async (topic: string): Promise<string> => {\n if (topicToInfoHash[topic]) {\n return topicToInfoHash[topic]\n }\n\n const hash = (await sha1(topic)).slice(0, hashLimit)\n topicToInfoHash[topic] = hash\n infoHashToTopic[hash] = topic\n\n return hash\n}\n\nconst send = async (\n client: SocketClient,\n topic: string,\n payload: Record<string, unknown>\n): Promise<void> =>\n client.send(\n toJson({\n action: trackerAction,\n info_hash: await getInfoHash(topic),\n peer_id: selfId,\n ...payload\n })\n )\n\nconst warn = (url: string, msg: string, didFail = false): void =>\n console.warn(\n `${libName}: torrent tracker ${didFail ? 'failure' : 'warning'} from ${url} - ${msg}`\n )\n\nconst getRoomOutstandingOffers = (\n rootTopic: string\n): Record<string, OfferRecord & {createdAt: number}> =>\n (roomOutstandingOffers[rootTopic] ??= {})\n\nconst deleteRoomOfferBookkeeping = (rootTopic: string): void => {\n delete roomOutstandingOffers[rootTopic]\n delete roomOfferGenerationPromises[rootTopic]\n}\n\nconst claimOutstandingOffer = (\n rootTopic: string,\n offerId: string\n): OfferRecord | undefined => {\n const outstandingOffers = roomOutstandingOffers[rootTopic]\n const offer = outstandingOffers?.[offerId]\n\n if (!offer) {\n return\n }\n\n delete outstandingOffers[offerId]\n offer.claim?.()\n\n if (!keys(outstandingOffers).length && !roomSubscriberCounts[rootTopic]) {\n deleteRoomOfferBookkeeping(rootTopic)\n }\n\n return offer\n}\n\nconst reclaimOutstandingOffer = (rootTopic: string, offerId: string): void => {\n const outstandingOffers = roomOutstandingOffers[rootTopic]\n const offer = outstandingOffers?.[offerId]\n\n if (!offer) {\n return\n }\n\n delete outstandingOffers[offerId]\n offer.reclaim?.()\n\n if (!keys(outstandingOffers).length && !roomSubscriberCounts[rootTopic]) {\n deleteRoomOfferBookkeeping(rootTopic)\n }\n}\n\nconst reclaimAllOutstandingOffers = (rootTopic: string): void => {\n keys(getRoomOutstandingOffers(rootTopic)).forEach(offerId =>\n reclaimOutstandingOffer(rootTopic, offerId)\n )\n deleteRoomOfferBookkeeping(rootTopic)\n}\n\nconst pruneOutstandingOffers = (rootTopic: string): void => {\n const now = Date.now()\n\n entries(getRoomOutstandingOffers(rootTopic)).forEach(([offerId, offer]) => {\n if (now - offer.createdAt > offerRetentionMs) {\n reclaimOutstandingOffer(rootTopic, offerId)\n }\n })\n}\n\nconst ensureOutstandingOffers = async (\n rootTopic: string,\n getOffers: (n: number) => Promise<OfferRecord[]>\n): Promise<Record<string, OfferRecord & {createdAt: number}>> => {\n while (roomOfferGenerationPromises[rootTopic]) {\n await roomOfferGenerationPromises[rootTopic]\n }\n\n const nextPromise = (async () => {\n pruneOutstandingOffers(rootTopic)\n\n const outstandingOffers = getRoomOutstandingOffers(rootTopic)\n const outstandingCount = keys(outstandingOffers).length\n const missingOffers = Math.max(0, offerPoolSize - outstandingCount)\n\n if (missingOffers > 0) {\n ;(await getOffers(missingOffers)).forEach(peerAndOffer => {\n outstandingOffers[genId(hashLimit)] = {\n ...peerAndOffer,\n createdAt: Date.now()\n }\n })\n }\n })().finally(() => {\n if (roomOfferGenerationPromises[rootTopic] === nextPromise) {\n delete roomOfferGenerationPromises[rootTopic]\n }\n })\n\n roomOfferGenerationPromises[rootTopic] = nextPromise\n await nextPromise\n\n return getRoomOutstandingOffers(rootTopic)\n}\n\nconst joinRoomStrategy: JoinRoom<TorrentRoomConfig> = createStrategy({\n init: config =>\n getRelays(config, defaultRelayUrls, defaultRedundancy).map(rawUrl => {\n const client = relayManager.register(\n rawUrl,\n makeSocket(rawUrl, rawData => {\n const data = fromJson<TrackerMessage>(rawData)\n const errMsg = data['failure reason']\n const warnMsg = data['warning message']\n const {interval} = data\n const topic = data.info_hash\n ? infoHashToTopic[data.info_hash]\n : undefined\n\n if (errMsg) {\n warn(client.url, errMsg, true)\n return\n }\n\n if (warnMsg) {\n warn(client.url, warnMsg)\n }\n\n if (\n interval &&\n interval * 1000 >\n (trackerAnnounceMs[client.url] ?? defaultAnnounceMs) &&\n topic &&\n announceFns.forKey(rawUrl)[topic]\n ) {\n const nextInterval = Math.min(interval * 1000, maxAnnounceMs)\n const relayIntervals = announceIntervals.forKey(rawUrl)\n const relayFns = announceFns.forKey(rawUrl)\n\n if (relayIntervals[topic]) {\n clearInterval(relayIntervals[topic])\n }\n trackerAnnounceMs[client.url] = nextInterval\n const relayFn = relayFns[topic]\n\n if (relayFn) {\n relayIntervals[topic] = setInterval(() => {\n void relayFn()\n }, nextInterval)\n }\n }\n\n if ((data.offer || data.answer) && topic && data.offer_id) {\n if (data.peer_id === selfId) {\n return\n }\n\n const signalType = data.offer ? 'offer' : 'answer'\n const signalKey = `${topic}:${signalType}:${data.offer_id}:${data.peer_id ?? ''}`\n const nowMs = Date.now()\n const lastHandledMs = handledSignals[signalKey]\n\n if (\n typeof lastHandledMs === 'number' &&\n nowMs - lastHandledMs < signalDedupeWindowMs\n ) {\n return\n }\n\n handledSignals[signalKey] = nowMs\n\n entries(handledSignals).forEach(([key, handledAtMs]) => {\n if (nowMs - handledAtMs > signalDedupeWindowMs * 6) {\n delete handledSignals[key]\n }\n })\n\n msgHandlers.forKey(rawUrl)[topic]?.(data)\n }\n })\n )\n\n return client.ready\n }),\n\n subscribe: (client, rootTopic, _, onMessage, getOffers) => {\n const handlers = msgHandlers.forRelay(client)\n const relayFns = announceFns.forRelay(client)\n const relayIntervals = announceIntervals.forRelay(client)\n const activeTokens = subscriptionTokens.forRelay(client)\n const subscriptionToken = Symbol(rootTopic)\n\n activeTokens[rootTopic] = subscriptionToken\n roomSubscriberCounts[rootTopic] = (roomSubscriberCounts[rootTopic] ?? 0) + 1\n\n const topicHandler = (data: TrackerMessage): void => {\n if (data.offer && data.peer_id && data.offer_id) {\n void onMessage(\n rootTopic,\n {\n offer: data.offer.sdp,\n peerId: data.peer_id,\n hasOutgoingOffer:\n keys(getRoomOutstandingOffers(rootTopic)).length > 0\n },\n (_, signal) =>\n void send(client, rootTopic, {\n answer: {\n type: 'answer',\n sdp: fromJson<{answer: string}>(signal).answer\n },\n offer_id: data.offer_id,\n to_peer_id: data.peer_id\n })\n )\n } else if (data.answer && data.offer_id && data.peer_id) {\n const offer = claimOutstandingOffer(rootTopic, data.offer_id)\n\n if (offer) {\n void onMessage(\n rootTopic,\n {\n answer: data.answer.sdp,\n peerId: data.peer_id,\n peer: offer.peer\n },\n () => {}\n )\n }\n }\n }\n\n handlers[rootTopic] = topicHandler\n\n const announce = async (): Promise<void> => {\n if (activeTokens[rootTopic] !== subscriptionToken) {\n return\n }\n\n const outstandingOffers = await ensureOutstandingOffers(\n rootTopic,\n getOffers\n )\n const offers = entries(outstandingOffers).map(([id, {offer}]) => ({\n offer_id: id,\n offer: {type: 'offer', sdp: offer}\n }))\n\n void send(client, rootTopic, {\n numwant: offerPoolSize,\n offers\n })\n }\n\n trackerAnnounceMs[client.url] = defaultAnnounceMs\n relayFns[rootTopic] = announce\n relayIntervals[rootTopic] = setInterval(\n announce,\n trackerAnnounceMs[client.url]\n )\n void announce()\n\n return () => {\n roomSubscriberCounts[rootTopic] = Math.max(\n 0,\n (roomSubscriberCounts[rootTopic] ?? 1) - 1\n )\n\n if (!roomSubscriberCounts[rootTopic]) {\n delete roomSubscriberCounts[rootTopic]\n }\n\n if (activeTokens[rootTopic] !== subscriptionToken) {\n if (!roomSubscriberCounts[rootTopic]) {\n reclaimAllOutstandingOffers(rootTopic)\n }\n\n return\n }\n\n const interval = relayIntervals[rootTopic]\n if (interval) {\n clearInterval(interval)\n delete relayIntervals[rootTopic]\n }\n\n if (handlers[rootTopic] === topicHandler) {\n delete handlers[rootTopic]\n }\n\n if (relayFns[rootTopic] === announce) {\n delete relayFns[rootTopic]\n }\n\n delete activeTokens[rootTopic]\n\n if (!roomSubscriberCounts[rootTopic]) {\n reclaimAllOutstandingOffers(rootTopic)\n }\n }\n },\n\n announce: client => trackerAnnounceMs[client.url]\n})\n\nexport const joinRoom: JoinRoom<TorrentRoomConfig> = (\n config,\n roomId,\n callbacks\n) =>\n joinRoomStrategy(\n {\n ...config,\n trickleIce: config.trickleIce ?? false\n },\n roomId,\n callbacks\n )\n\nexport const getRelaySockets = relayManager.getSockets\n\nexport {pauseRelayReconnection, resumeRelayReconnection, selfId}\n\nexport const defaultRelayUrls = [\n 'tracker.webtorrent.dev',\n 'tracker.openwebtorrent.com',\n 'tracker.btorrent.xyz',\n 'tracker.files.fm:7073/announce'\n].map(url => 'wss://' + url)\n\nexport type * from '@trystero-p2p/core'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n createRelayManager,\n createStrategy,\n entries,\n fromJson,\n genId,\n getRelays,\n keys,\n libName,\n makeSocket,\n pauseRelayReconnection,\n resumeRelayReconnection,\n selfId,\n sha1,\n toJson,\n type JoinRoom,\n type JoinRoomConfig,\n type OfferRecord,\n type SocketClient\n} from '@trystero-p2p/core'\n\nconst relayManager = createRelayManager<SocketClient>(client => client.socket)\nconst topicToInfoHash: Record<string, string> = {}\nconst infoHashToTopic: Record<string, string> = {}\nconst announceIntervals = relayManager.scoped<ReturnType<typeof setInterval>>()\nconst announceFns = relayManager.scoped<() => void | Promise<void>>()\nconst subscriptionTokens = relayManager.scoped<symbol>()\nconst trackerAnnounceMs: Record<string, number> = {}\nconst handledSignals: Record<string, number> = {}\nconst msgHandlers = relayManager.scoped<(data: TrackerMessage) => void>()\nconst topicStates = relayManager.scoped<{\n announce: () => void | Promise<void>\n isActive: boolean\n}>()\nconst roomOutstandingOffers: Record<\n string,\n Record<string, OfferRecord & {createdAt: number}>\n> = {}\nconst roomOfferGenerationPromises: Record<string, Promise<void> | undefined> =\n {}\nconst roomSubscriberCounts: Record<string, number> = {}\nconst trackerAction = 'announce'\nconst hashLimit = 20\nconst offerPoolSize = 3\nconst defaultAnnounceMs = 10_000\nconst dormantAnnounceMs = 120_000\nconst maxAnnounceMs = 20_000\nconst offerRetentionMs = 120_000\nconst signalDedupeWindowMs = 4_000\nconst defaultRedundancy = 3\n\nexport type TorrentRoomConfig = JoinRoomConfig\n\ntype TrackerMessage = {\n offer?: {\n type: 'offer'\n sdp: string\n }\n answer?: {\n type: 'answer'\n sdp: string\n }\n offer_id?: string\n peer_id?: string\n info_hash?: string\n interval?: number\n ['failure reason']?: string\n ['warning message']?: string\n}\n\nconst getInfoHash = async (topic: string): Promise<string> => {\n if (topicToInfoHash[topic]) {\n return topicToInfoHash[topic]\n }\n\n const hash = (await sha1(topic)).slice(0, hashLimit)\n topicToInfoHash[topic] = hash\n infoHashToTopic[hash] = topic\n\n return hash\n}\n\nconst send = async (\n client: SocketClient,\n topic: string,\n payload: Record<string, unknown>\n): Promise<void> =>\n client.send(\n toJson({\n action: trackerAction,\n info_hash: await getInfoHash(topic),\n peer_id: selfId,\n ...payload\n })\n )\n\nconst warn = (url: string, msg: string, didFail = false): void =>\n console.warn(\n `${libName}: torrent tracker ${didFail ? 'failure' : 'warning'} from ${url} - ${msg}`\n )\n\nconst getRoomOutstandingOffers = (\n rootTopic: string\n): Record<string, OfferRecord & {createdAt: number}> =>\n (roomOutstandingOffers[rootTopic] ??= {})\n\nconst deleteRoomOfferBookkeeping = (rootTopic: string): void => {\n delete roomOutstandingOffers[rootTopic]\n delete roomOfferGenerationPromises[rootTopic]\n}\n\nconst claimOutstandingOffer = (\n rootTopic: string,\n offerId: string\n): OfferRecord | undefined => {\n const outstandingOffers = roomOutstandingOffers[rootTopic]\n const offer = outstandingOffers?.[offerId]\n\n if (!offer) {\n return\n }\n\n delete outstandingOffers[offerId]\n offer.claim?.()\n\n if (!keys(outstandingOffers).length && !roomSubscriberCounts[rootTopic]) {\n deleteRoomOfferBookkeeping(rootTopic)\n }\n\n return offer\n}\n\nconst reclaimOutstandingOffer = (rootTopic: string, offerId: string): void => {\n const outstandingOffers = roomOutstandingOffers[rootTopic]\n const offer = outstandingOffers?.[offerId]\n\n if (!offer) {\n return\n }\n\n delete outstandingOffers[offerId]\n offer.reclaim?.()\n\n if (!keys(outstandingOffers).length && !roomSubscriberCounts[rootTopic]) {\n deleteRoomOfferBookkeeping(rootTopic)\n }\n}\n\nconst reclaimAllOutstandingOffers = (rootTopic: string): void => {\n keys(getRoomOutstandingOffers(rootTopic)).forEach(offerId =>\n reclaimOutstandingOffer(rootTopic, offerId)\n )\n deleteRoomOfferBookkeeping(rootTopic)\n}\n\nconst pruneOutstandingOffers = (rootTopic: string): void => {\n const now = Date.now()\n\n entries(getRoomOutstandingOffers(rootTopic)).forEach(([offerId, offer]) => {\n if (now - offer.createdAt > offerRetentionMs) {\n reclaimOutstandingOffer(rootTopic, offerId)\n }\n })\n}\n\nconst ensureOutstandingOffers = async (\n rootTopic: string,\n getOffers: (n: number) => Promise<OfferRecord[]>\n): Promise<Record<string, OfferRecord & {createdAt: number}>> => {\n while (roomOfferGenerationPromises[rootTopic]) {\n await roomOfferGenerationPromises[rootTopic]\n }\n\n const nextPromise = (async () => {\n pruneOutstandingOffers(rootTopic)\n\n const outstandingOffers = getRoomOutstandingOffers(rootTopic)\n const outstandingCount = keys(outstandingOffers).length\n const missingOffers = Math.max(0, offerPoolSize - outstandingCount)\n\n if (missingOffers > 0) {\n ;(await getOffers(missingOffers)).forEach(peerAndOffer => {\n outstandingOffers[genId(hashLimit)] = {\n ...peerAndOffer,\n createdAt: Date.now()\n }\n })\n }\n })().finally(() => {\n if (roomOfferGenerationPromises[rootTopic] === nextPromise) {\n delete roomOfferGenerationPromises[rootTopic]\n }\n })\n\n roomOfferGenerationPromises[rootTopic] = nextPromise\n await nextPromise\n\n return getRoomOutstandingOffers(rootTopic)\n}\n\nconst joinRoomStrategy: JoinRoom<TorrentRoomConfig> = createStrategy({\n init: config =>\n getRelays(config, defaultRelayUrls, defaultRedundancy).map(rawUrl => {\n const client = relayManager.register(\n rawUrl,\n makeSocket(rawUrl, rawData => {\n const data = fromJson<TrackerMessage>(rawData)\n const errMsg = data['failure reason']\n const warnMsg = data['warning message']\n const {interval} = data\n const topic = data.info_hash\n ? infoHashToTopic[data.info_hash]\n : undefined\n\n if (errMsg) {\n warn(client.url, errMsg, true)\n return\n }\n\n if (warnMsg) {\n warn(client.url, warnMsg)\n }\n\n if (\n interval &&\n interval * 1000 >\n (trackerAnnounceMs[client.url] ?? defaultAnnounceMs) &&\n topic &&\n announceFns.forKey(rawUrl)[topic]\n ) {\n const nextInterval = Math.min(interval * 1000, maxAnnounceMs)\n const relayIntervals = announceIntervals.forKey(rawUrl)\n const relayFns = announceFns.forKey(rawUrl)\n\n if (relayIntervals[topic]) {\n clearInterval(relayIntervals[topic])\n }\n trackerAnnounceMs[client.url] = nextInterval\n const relayFn = relayFns[topic]\n\n if (relayFn) {\n relayIntervals[topic] = setInterval(() => {\n void relayFn()\n }, nextInterval)\n }\n }\n\n if ((data.offer || data.answer) && topic && data.offer_id) {\n if (data.peer_id === selfId) {\n return\n }\n\n const signalType = data.offer ? 'offer' : 'answer'\n const signalKey = `${topic}:${signalType}:${data.offer_id}:${data.peer_id ?? ''}`\n const nowMs = Date.now()\n const lastHandledMs = handledSignals[signalKey]\n\n if (\n typeof lastHandledMs === 'number' &&\n nowMs - lastHandledMs < signalDedupeWindowMs\n ) {\n return\n }\n\n handledSignals[signalKey] = nowMs\n\n entries(handledSignals).forEach(([key, handledAtMs]) => {\n if (nowMs - handledAtMs > signalDedupeWindowMs * 6) {\n delete handledSignals[key]\n }\n })\n\n msgHandlers.forKey(rawUrl)[topic]?.(data)\n }\n })\n )\n\n return client.ready\n }),\n\n subscribe: (client, rootTopic, _, onMessage, getOffers, context) => {\n const handlers = msgHandlers.forRelay(client)\n const relayFns = announceFns.forRelay(client)\n const relayIntervals = announceIntervals.forRelay(client)\n const activeTokens = subscriptionTokens.forRelay(client)\n const states = topicStates.forRelay(client)\n const subscriptionToken = Symbol(rootTopic)\n\n activeTokens[rootTopic] = subscriptionToken\n roomSubscriberCounts[rootTopic] = (roomSubscriberCounts[rootTopic] ?? 0) + 1\n\n const topicHandler = (data: TrackerMessage): void => {\n if (data.offer && data.peer_id && data.offer_id) {\n void onMessage(\n rootTopic,\n {\n offer: data.offer.sdp,\n peerId: data.peer_id,\n hasOutgoingOffer:\n keys(getRoomOutstandingOffers(rootTopic)).length > 0\n },\n (_, signal) =>\n void send(client, rootTopic, {\n answer: {\n type: 'answer',\n sdp: fromJson<{answer: string}>(signal).answer\n },\n offer_id: data.offer_id,\n to_peer_id: data.peer_id\n })\n )\n } else if (data.answer && data.offer_id && data.peer_id) {\n const offer = claimOutstandingOffer(rootTopic, data.offer_id)\n\n if (offer) {\n void onMessage(\n rootTopic,\n {\n answer: data.answer.sdp,\n peerId: data.peer_id,\n peer: offer.peer\n },\n () => {}\n )\n }\n }\n }\n\n handlers[rootTopic] = topicHandler\n\n const topicState: {\n announce: () => void | Promise<void>\n isActive: boolean\n } = {\n announce: async () => {\n if (activeTokens[rootTopic] !== subscriptionToken) {\n return\n }\n\n if (!topicState.isActive) {\n void send(client, rootTopic, {\n left: 0,\n numwant: offerPoolSize,\n offers: []\n })\n return\n }\n\n const outstandingOffers = await ensureOutstandingOffers(\n rootTopic,\n getOffers\n )\n const offers = entries(outstandingOffers).map(([id, {offer}]) => ({\n offer_id: id,\n offer: {type: 'offer', sdp: offer}\n }))\n\n void send(client, rootTopic, {\n numwant: offerPoolSize,\n offers\n })\n },\n isActive: !context?.isPassive\n }\n\n trackerAnnounceMs[client.url] = defaultAnnounceMs\n const {announce} = topicState\n relayFns[rootTopic] = announce\n states[rootTopic] = topicState\n const initialInterval = topicState.isActive\n ? trackerAnnounceMs[client.url]\n : dormantAnnounceMs\n\n relayIntervals[rootTopic] = setInterval(announce, initialInterval)\n void announce()\n\n return () => {\n roomSubscriberCounts[rootTopic] = Math.max(\n 0,\n (roomSubscriberCounts[rootTopic] ?? 1) - 1\n )\n\n if (!roomSubscriberCounts[rootTopic]) {\n delete roomSubscriberCounts[rootTopic]\n }\n\n if (activeTokens[rootTopic] !== subscriptionToken) {\n if (!roomSubscriberCounts[rootTopic]) {\n reclaimAllOutstandingOffers(rootTopic)\n delete states[rootTopic]\n }\n\n return\n }\n\n const interval = relayIntervals[rootTopic]\n if (interval) {\n clearInterval(interval)\n delete relayIntervals[rootTopic]\n }\n\n if (handlers[rootTopic] === topicHandler) {\n delete handlers[rootTopic]\n }\n\n if (relayFns[rootTopic] === announce) {\n delete relayFns[rootTopic]\n }\n\n delete activeTokens[rootTopic]\n\n if (states[rootTopic] === topicState) {\n delete states[rootTopic]\n }\n\n if (!roomSubscriberCounts[rootTopic]) {\n reclaimAllOutstandingOffers(rootTopic)\n }\n }\n },\n\n announce: (client, rootTopic) => {\n const state = topicStates.forRelay(client)[rootTopic]\n const relayIntervals = announceIntervals.forRelay(client)\n const relayFns = announceFns.forRelay(client)\n const fn = relayFns[rootTopic]\n\n if (state) {\n state.isActive = true\n }\n\n if (fn && relayIntervals[rootTopic]) {\n clearInterval(relayIntervals[rootTopic])\n relayIntervals[rootTopic] = setInterval(() => {\n void fn()\n }, trackerAnnounceMs[client.url])\n void fn()\n }\n\n return trackerAnnounceMs[client.url]\n },\n\n deactivate: (client, rootTopic) => {\n const state = topicStates.forRelay(client)[rootTopic]\n const relayIntervals = announceIntervals.forRelay(client)\n const relayFns = announceFns.forRelay(client)\n const fn = relayFns[rootTopic]\n\n if (state) {\n state.isActive = false\n }\n\n reclaimAllOutstandingOffers(rootTopic)\n\n if (fn && relayIntervals[rootTopic]) {\n clearInterval(relayIntervals[rootTopic])\n relayIntervals[rootTopic] = setInterval(() => {\n void fn()\n }, dormantAnnounceMs)\n void fn()\n }\n }\n})\n\nexport const joinRoom: JoinRoom<TorrentRoomConfig> = (\n config,\n roomId,\n callbacks\n) =>\n joinRoomStrategy(\n {\n ...config,\n trickleIce: config.trickleIce ?? false\n },\n roomId,\n callbacks\n )\n\nexport const getRelaySockets = relayManager.getSockets\n\nexport {pauseRelayReconnection, resumeRelayReconnection, selfId}\n\nexport const defaultRelayUrls = [\n 'tracker.webtorrent.dev',\n 'tracker.openwebtorrent.com',\n 'tracker.btorrent.xyz',\n 'tracker.files.fm:7073/announce'\n].map(url => 'wss://' + url)\n\nexport type * from '@trystero-p2p/core'\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trystero-p2p/torrent",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.25.0",
|
|
4
|
+
"description": "Simple, serverless WebRTC (via Torrent)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bittorrent",
|
|
7
7
|
"chat",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"serverless",
|
|
16
16
|
"signaling",
|
|
17
17
|
"torrent",
|
|
18
|
+
"trystero",
|
|
18
19
|
"webrtc",
|
|
19
20
|
"webtorrent"
|
|
20
21
|
],
|
|
@@ -34,6 +35,6 @@
|
|
|
34
35
|
"./package.json": "./package.json"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
|
-
"@trystero-p2p/core": "0.
|
|
38
|
+
"@trystero-p2p/core": "0.25.0"
|
|
38
39
|
}
|
|
39
40
|
}
|