@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 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
- or πŸͺ IPFS – all using the same API.
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 "serverless" strategies for connecting peers
68
- (currently BitTorrent, Nostr, MQTT, Supabase, Firebase, and IPFS).
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(peerId => console.log(`${peerId} joined`))
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(peerId => console.log(`${peerId} left`))
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
- (stream, peerId) => (peerElements[peerId].video.srcObject = stream)
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 [sendDrink, getDrink] = room.makeAction('drink')
190
+ const drink = room.makeAction('drink')
188
191
 
189
192
  // buy drink for a friend
190
- sendDrink({drink: 'negroni', withIce: true}, friendId)
193
+ drink.send({drink: 'negroni', withIce: true}, {target: friendId})
191
194
 
192
- // buy round for the house (second argument omitted)
193
- sendDrink({drink: 'mezcal', withIce: false})
195
+ // buy round for the house
196
+ drink.send({drink: 'mezcal', withIce: false})
194
197
 
195
198
  // listen for drinks sent to you
196
- getDrink((data, peerId) =>
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 [sendCursor, getCursor] = room.makeAction<CursorMove>('cursor-move')
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 [sendPic, getPic] = room.makeAction('pic')
258
+ const pic = room.makeAction('pic')
215
259
 
216
260
  // blobs are automatically handled, as are any form of TypedArray
217
- canvas.toBlob(blob => sendPic(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
- getPic(
222
- (data, peerId) => (imgs[peerId].src = URL.createObjectURL(new Blob([data])))
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 [sendName, getName] = room.makeAction('name')
273
+ const name = room.makeAction('name')
231
274
 
232
275
  // tell new peers your name when they connect
233
- room.onPeerJoin(peerId => sendName('Oedipa', peerId))
276
+ room.onPeerJoin = peerId => name.send('Oedipa', {target: peerId})
234
277
 
235
278
  // listen for peers naming themselves
236
- getName((name, peerId) => (idsToNames[peerId] = name))
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 => sendName(e.target.value))
282
+ nameInput.addEventListener('change', e => name.send(e.target.value))
240
283
 
241
- room.onPeerLeave(peerId =>
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(peerId => room.addStream(selfStream, peerId))
311
+ room.onPeerJoin = peerId => room.addStream(selfStream, {target: peerId})
270
312
 
271
313
  // handle streams from other peers
272
- room.onPeerStream((stream, peerId) => {
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((stream, peerId) => {
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 [sendFile, getFile] = room.makeAction('file')
360
+ const file = room.makeAction('file')
319
361
 
320
- getFile((data, peerId, metadata) =>
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
- // to send metadata, pass a third argument
328
- // to broadcast to the whole room, set the second peer ID argument to null
329
- sendFile(buffer, null, {name: 'The CourierΚΌs Tragedy', type: 'application/pdf'})
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 sendFile(amplePayload)
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
- sendFile(
352
- payload,
353
- // notice the peer target argument for any action sender can be a single peer
354
- // ID, an array of IDs, or null (meaning send to all peers in the room)
355
- [peerIdA, peerIdB, peerIdC],
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 [sendFile, getFile, onFileProgress] = room.makeAction('file')
402
+ const file = room.makeAction('file')
369
403
 
370
- onFileProgress((percent, peerId, metadata) =>
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 [sendColor, getColor] = room.makeAction('color')
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(peer => sendColor(myColor, peer))
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
- getColor((color, peer) =>
430
- setPeerColors(peerColors => ({...peerColors, [peer]: color}))
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
- sendColor(value)
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 `createStrategy`.
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 {createStrategy, selfId, toJson} from '@trystero-p2p/core'
630
+ import {createTopicStrategy, toJson} from '@trystero-p2p/core'
564
631
 
565
- export const joinRoom = createStrategy({
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.relayUrl)
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 takes 5 arguments:
578
- // 1. One of the relay clients you defined in init.
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 {topic, payload} = JSON.parse(String(event.data))
647
+ const message = JSON.parse(String(event.data))
592
648
 
593
- if (!topics.includes(topic)) {
649
+ if (message.topic !== topic) {
594
650
  return
595
651
  }
596
652
 
597
- onMessage(topic, payload, (peerTopic, signal) =>
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
- topics.forEach(topic => client.send(toJson({type: 'subscribe', topic})))
657
+ client.send(toJson({type: 'subscribe', topic}))
606
658
 
607
659
  return () => {
608
- topics.forEach(topic => client.send(toJson({type: 'unsubscribe', topic})))
660
+ client.send(toJson({type: 'unsubscribe', topic}))
609
661
  client.removeEventListener('message', onWsMessage)
610
662
  }
611
663
  },
612
664
 
613
- // announce takes a relay client and a rootTopic
614
- announce: (client, rootTopic) =>
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: rootTopic,
619
- payload: toJson({peerId: selfId})
670
+ topic,
671
+ payload
620
672
  })
621
673
  )
622
674
  })
623
675
 
624
676
  const room = joinRoom(
625
- {appId: 'my-app-id', relayUrl: 'wss://my-relay.example'},
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 Trystero config
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 Firebase
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
- - `relayUrls` - **(optional, 🌊 BitTorrent, 🐦 Nostr, πŸ“‘ MQTT only)** Custom
697
- list of URLs for the strategy to use to bootstrap P2P connections. These
698
- would be BitTorrent trackers, Nostr relays, and MQTT brokers, respectively.
699
- They must support secure WebSocket connections.
700
-
701
- - `relayRedundancy` - **(optional, 🌊 BitTorrent, 🐦 Nostr, πŸ“‘ MQTT only)**
702
- Integer specifying how many relay endpoints to connect to simultaneously.
703
- Passing a `relayUrls` option will cause this option to be ignored as the
704
- entire list will be used.
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 or when handshake admission fails (including timeout). `details` is
754
- an object containing `appId`, `roomId`, `peerId`, and `error` describing the
755
- failure.
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
- - ### `addStream(stream, [targetPeers], [metadata])`
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
- - `targetPeers` - **(optional)** If specified, the stream is sent only to the
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 type) to
824
- be sent with the stream. This is useful when sending multiple streams so
825
- recipients know which is which (e.g. a webcam versus a screen capture). If
826
- you want to broadcast a stream to all peers in the room with a metadata
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, [targetPeers])`
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
- - `targetPeers` - **(optional)** If specified, the stream is removed only from
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, [targetPeers], [metadata])`
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
- - `targetPeers` - **(optional)** If specified, the track is sent only to the
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 type) to
848
- be sent with the track. See `metadata` notes for `addStream()` above for
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, [targetPeers])`
926
+ - ### `removeTrack(track, [options])`
852
927
 
853
928
  Removes a media track.
854
929
  - `track` - The `MediaStreamTrack` to remove.
855
930
 
856
- - `targetPeers` - **(optional)** If specified, the track is removed only from
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, [targetPeers], [metadata])`
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
- - `targetPeers` - **(optional)** If specified, the track is replaced only for
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 type) to
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(callback)`
947
+ - ### `onPeerJoin`
873
948
 
874
- Registers a callback function that will be called when a peer joins the room.
875
- If called more than once, only the latest callback registered is ever called.
876
- Existing active peers are immediately replayed to a newly registered callback.
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(peerId => console.log(`${peerId} joined`))
958
+ room.onPeerJoin = peerId => console.log(`${peerId} joined`)
884
959
  ```
885
960
 
886
- - ### `onPeerLeave(callback)`
961
+ - ### `onPeerLeave`
887
962
 
888
- Registers a callback function that will be called when a peer leaves the room.
889
- If called more than once, only the latest callback registered is ever called.
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(peerId => console.log(`${peerId} left`))
971
+ room.onPeerLeave = peerId => console.log(`${peerId} left`)
897
972
  ```
898
973
 
899
- - ### `onPeerStream(callback)`
974
+ - ### `onPeerStream`
900
975
 
901
- Registers a callback function that will be called when a peer sends a media
902
- stream. If called more than once, only the latest callback registered is ever
903
- called.
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((stream, peerId) =>
986
+ room.onPeerStream = (stream, peerId) =>
912
987
  console.log(`got stream from ${peerId}`, stream)
913
- )
914
988
  ```
915
989
 
916
- - ### `onPeerTrack(callback)`
990
+ - ### `onPeerTrack`
917
991
 
918
- Registers a callback function that will be called when a peer sends a media
919
- track. If called more than once, only the latest callback registered is ever
920
- called.
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((track, stream, peerId) =>
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
- Returns an array of three functions:
939
- 1. #### Sender
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
- - `onProgress` - **(optional)** A callback function that will be called
956
- as every chunk for every peer is transmitted. The function will be
957
- called with a value between 0 and 1 and a peer ID. See
958
- [Progress updates](#progress-updates) for an example.
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
- 2. #### Receiver
961
- - Registers a callback function that runs when data for this action is
962
- received from other peers.
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
- - `(data, peerId, metadata)`
965
- - `data` - The value transmitted by the sending peer. Deserialization is
966
- handled automatically, i.e. a number will be received as a number, an
967
- object as an object, etc.
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 [sendCursor, getCursor] = room.makeAction('cursormove')
1035
+ const cursor = room.makeAction('cursormove')
992
1036
 
993
- window.addEventListener('mousemove', e => sendCursor([e.clientX, e.clientY]))
1037
+ window.addEventListener('mousemove', e => cursor.send([e.clientX, e.clientY]))
994
1038
 
995
- getCursor(([x, y], peerId) => {
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(peerId =>
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 object of relay URL keys
1028
- mapped to their WebSocket connections. This can be useful for determining the
1029
- state of the user's connection to the relays and handling any connection
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 `manualRelayReconnection: true` is set in the
1048
- room config. Calling this function stops relay reconnection attempts until
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 { BaseRoomConfig, JoinRoom, RelayConfig, pauseRelayReconnection, resumeRelayReconnection, selfId } from "@trystero-p2p/core";
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 = BaseRoomConfig & RelayConfig;
5
+ type TorrentRoomConfig = JoinRoomConfig;
6
6
  declare const joinRoom: JoinRoom<TorrentRoomConfig>;
7
7
  declare const getRelaySockets: any;
8
8
  declare const defaultRelayUrls: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;KA+CY,iBAAA,GAAoB,cAAA,GAAiB,WAAA;AAAA,cA6VpC,QAAA,EAAU,QAAA,CAAS,iBAAA;AAAA,cAcnB,eAAA;AAAA,cAIA,gBAAA"}
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 announce = async () => {
160
- if (activeTokens[rootTopic] !== subscriptionToken) return;
161
- send(client, rootTopic, {
162
- numwant: offerPoolSize,
163
- offers: entries(await ensureOutstandingOffers(rootTopic, getOffers)).map(([id, { offer }]) => ({
164
- offer_id: id,
165
- offer: {
166
- type: "offer",
167
- sdp: offer
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
- relayIntervals[rootTopic] = setInterval(announce, trackerAnnounceMs[client.url]);
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]) reclaimAllOutstandingOffers(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) => trackerAnnounceMs[client.url]
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,
@@ -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.23.1",
4
- "description": "Torrent tracker strategy for Trystero",
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.23.1"
38
+ "@trystero-p2p/core": "0.25.0"
38
39
  }
39
40
  }