Skip to content

Commit d3819bf

Browse files
authored
Merge pull request #142 from vrtmrz/prevent-reconnection
A new option or function to stay offline when disconnected during specific situations
2 parents 65fa48e + 9bcca2d commit d3819bf

File tree

8 files changed

+125
-25
lines changed

8 files changed

+125
-25
lines changed

README.md

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,24 +35,30 @@ You can see what people are building with Trystero [here](https://github.com/jer
3535

3636
## Contents
3737

38-
- [How it works](#how-it-works)
39-
- [Get started](#get-started)
40-
- [Listen for events](#listen-for-events)
41-
- [Broadcast events](#broadcast-events)
42-
- [Audio and video](#audio-and-video)
43-
- [Advanced](#advanced)
44-
- [Binary metadata](#binary-metadata)
45-
- [Action promises](#action-promises)
46-
- [Progress updates](#progress-updates)
47-
- [Encryption](#encryption)
48-
- [React hooks](#react-hooks)
49-
- [Connection issues](#connection-issues)
50-
- [Running server-side (Node, Deno, Bun)](#running-server-side-node-deno-bun)
51-
- [Supabase setup](#supabase-setup)
52-
- [Firebase setup](#firebase-setup)
53-
- [API](#api)
54-
- [Strategy comparison](#strategy-comparison)
55-
- [How to choose](#how-to-choose)
38+
- [✨🤝✨ Trystero](#-trystero)
39+
- [Contents](#contents)
40+
- [How it works](#how-it-works)
41+
- [Get started](#get-started)
42+
- [Listen for events](#listen-for-events)
43+
- [Broadcast events](#broadcast-events)
44+
- [Audio and video](#audio-and-video)
45+
- [Advanced](#advanced)
46+
- [Binary metadata](#binary-metadata)
47+
- [Action promises](#action-promises)
48+
- [Progress updates](#progress-updates)
49+
- [Encryption](#encryption)
50+
- [React hooks](#react-hooks)
51+
- [Connection issues](#connection-issues)
52+
- [Running server-side (Node, Deno, Bun)](#running-server-side-node-deno-bun)
53+
- [Supabase setup](#supabase-setup)
54+
- [Firebase setup](#firebase-setup)
55+
- [API](#api)
56+
- [`joinRoom(config, roomId, [onJoinError])`](#joinroomconfig-roomid-onjoinerror)
57+
- [`selfId`](#selfid)
58+
- [`getRelaySockets()`](#getrelaysockets)
59+
- [`getOccupants(config, roomId)`](#getoccupantsconfig-roomid)
60+
- [Strategy comparison](#strategy-comparison)
61+
- [How to choose](#how-to-choose)
5662

5763
---
5864

@@ -649,6 +655,12 @@ the same namespace will return the same room instance.
649655
[`Libp2pOptions`](https://libp2p.github.io/js-libp2p/types/libp2p.index.Libp2pOptions.html)
650656
where you can specify a list of static peers for bootstrapping.
651657

658+
- `manualRelayReconnection` - **(optional, 🐦 Nostr and 🌊 BitTorrent only)**
659+
Boolean (default: `false`) that when set to `true` disables
660+
automatically pausing and resuming reconnection attempts when the browser
661+
goes offline and comes back online. This is useful if you want to manage
662+
this behavior yourself.
663+
652664
- `roomId` - A string to namespace peers and events within a room.
653665

654666
- `onJoinError(details)` - **(optional)** A callback function that will be
@@ -893,6 +905,18 @@ console.log(trystero.getRelaySockets())
893905
// }
894906
```
895907

908+
### `pauseRelayReconnection()`
909+
910+
**(🐦 Nostr, 🌊 BitTorrent only)** Normally Trystero will try to automatically
911+
reconnect to relay sockets unless `manualRelayReconnection: true` is set in
912+
the room config. Calling this function stops relay reconnection attempts until
913+
`resumeRelayReconnection()` is called.
914+
915+
### `resumeRelayReconnection()`
916+
917+
**(🐦 Nostr, 🌊 BitTorrent, only)** Allows relay reconnection attempts to resume.
918+
(See `pauseRelayReconnection()` above).
919+
896920
### `getOccupants(config, roomId)`
897921

898922
**(🔥 Firebase only)** Returns a promise that resolves to a list of user IDs

src/index.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
export {getRelaySockets, joinRoom, selfId} from './nostr.js'
1+
export {
2+
getRelaySockets,
3+
joinRoom,
4+
selfId,
5+
pauseRelayReconnection,
6+
resumeRelayReconnection
7+
} from './nostr.js'

src/nostr.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ declare module 'trystero/nostr' {
33

44
export function joinRoom(
55
config: BaseRoomConfig & RelayConfig,
6-
roomId: string
6+
roomId: string,
7+
manualRelayReconnection?: boolean
78
): Room
89

910
export function getRelaySockets(): Record<string, WebSocket>
11+
export function pauseRelayReconnection(): void
12+
export function resumeRelayReconnection(): void
1013

1114
export * from 'trystero'
1215
}

src/nostr.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,11 @@ export const joinRoom = strategy({
129129

130130
export const getRelaySockets = socketGetter(clients)
131131

132-
export {selfId} from './utils.js'
132+
export {
133+
selfId,
134+
pauseRelayReconnection,
135+
resumeRelayReconnection
136+
} from './utils.js'
133137

134138
export const defaultRelayUrls = [
135139
'black.nostrcity.club',

src/strategy.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import {
1010
noOp,
1111
selfId,
1212
toJson,
13-
topicPath
13+
topicPath,
14+
watchOnline
1415
} from './utils.js'
1516

1617
const poolSize = 20
@@ -24,6 +25,7 @@ export default ({init, subscribe, announce}) => {
2425
let initPromises
2526
let offerPool
2627
let offerCleanupTimer
28+
let cleanupWatchOnline
2729

2830
return (config, roomId, onJoinError) => {
2931
const {appId} = config
@@ -237,6 +239,7 @@ export default ({init, subscribe, announce}) => {
237239
})),
238240
offerTtl * 1.03
239241
)
242+
cleanupWatchOnline = config.manualRelayReconnection ? noOp : watchOnline()
240243
}
241244

242245
const announceIntervals = initPromises.map(() => announceIntervalMs)
@@ -284,6 +287,8 @@ export default ({init, subscribe, announce}) => {
284287
announceTimeouts.forEach(clearTimeout)
285288
unsubFns.forEach(async f => (await f)())
286289
clearInterval(offerCleanupTimer)
290+
cleanupWatchOnline()
291+
didInit = false
287292
}
288293
))
289294
}

src/torrent.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@ declare module 'trystero/torrent' {
33

44
export function joinRoom(
55
config: BaseRoomConfig & RelayConfig,
6-
roomId: string
6+
roomId: string,
7+
manualRelayReconnection?: boolean
78
): Room
89

910
export function getRelaySockets(): Record<string, WebSocket>
11+
export function pauseRelayReconnection(): void
12+
export function resumeRelayReconnection(): void
1013

1114
export * from 'trystero'
1215
}

src/torrent.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,11 @@ export const joinRoom = strategy({
174174

175175
export const getRelaySockets = socketGetter(clients)
176176

177-
export {selfId} from './utils.js'
177+
export {
178+
selfId,
179+
pauseRelayReconnection,
180+
resumeRelayReconnection
181+
} from './utils.js'
178182

179183
export const defaultRelayUrls = [
180184
'tracker.webtorrent.dev',

src/utils.js

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,47 @@ export const strToNum = (str, limit = Number.MAX_SAFE_INTEGER) =>
7373
const defaultRetryMs = 3333
7474
const socketRetryPeriods = {}
7575

76+
// Prevents reconnection to the socket until the promise resolves
77+
let reconnectionLockingPromise = null
78+
// Resolver for the promise that pauses reconnection
79+
let resolver = null
80+
81+
/**
82+
* Pauses reconnection attempts until resumed.
83+
* If already paused, no new promise is created.
84+
*/
85+
export const pauseRelayReconnection = () => {
86+
if (!reconnectionLockingPromise) {
87+
reconnectionLockingPromise = new Promise(resolve => {
88+
resolver = resolve
89+
}).finally(() => {
90+
resolver = null
91+
reconnectionLockingPromise = null
92+
})
93+
}
94+
}
95+
96+
/**
97+
* Resumes reconnection attempts if they are currently paused.
98+
* If not paused, this function has no effect.
99+
*/
100+
export const resumeRelayReconnection = () =>
101+
// resolver will be set to null after resolving.
102+
// Do not change here to avoid multiple calls to _resolver and creating new locker-promise
103+
// If not paused, do nothing
104+
resolver?.()
105+
76106
export const makeSocket = (url, onMessage) => {
77107
const client = {}
78108

79109
const init = () => {
80110
const socket = new WebSocket(url)
81-
82111
socket.onclose = () => {
112+
if (reconnectionLockingPromise) {
113+
// If reconnect is paused, wait for the promise to resolve
114+
reconnectionLockingPromise.then(init)
115+
return
116+
}
83117
socketRetryPeriods[url] ??= defaultRetryMs
84118
setTimeout(init, socketRetryPeriods[url])
85119
socketRetryPeriods[url] *= 2
@@ -109,3 +143,20 @@ export const makeSocket = (url, onMessage) => {
109143

110144
export const socketGetter = clientMap => () =>
111145
fromEntries(entries(clientMap).map(([url, client]) => [url, client.socket]))
146+
147+
export const watchOnline = () => {
148+
if (isBrowser) {
149+
const controller = new AbortController()
150+
151+
addEventListener('online', resumeRelayReconnection, {
152+
signal: controller.signal
153+
})
154+
addEventListener('offline', pauseRelayReconnection, {
155+
signal: controller.signal
156+
})
157+
158+
return () => controller.abort()
159+
}
160+
161+
return noOp
162+
}

0 commit comments

Comments
 (0)