@trystero-p2p/torrent 0.25.3 → 0.26.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 +55 -6
- package/dist/index.d.mts +5 -6
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +83 -114
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -36,6 +36,7 @@ You can see what people are building with Trystero
|
|
|
36
36
|
- [Audio and video](#audio-and-video)
|
|
37
37
|
- [Advanced](#advanced)
|
|
38
38
|
- [Binary metadata](#binary-metadata)
|
|
39
|
+
- [Receiving files](#receiving-files)
|
|
39
40
|
- [Action promises](#action-promises)
|
|
40
41
|
- [Progress updates](#progress-updates)
|
|
41
42
|
- [Encryption](#encryption)
|
|
@@ -288,7 +289,9 @@ room.onPeerLeave = peerId =>
|
|
|
288
289
|
> Actions are smart and handle serialization and chunking for you behind the
|
|
289
290
|
> scenes. This means you can send very large files and whatever data you send
|
|
290
291
|
> will be received on the other side as the same type (a number as a number, a
|
|
291
|
-
> string as a string, an object as an object, binary as binary, etc.).
|
|
292
|
+
> string as a string, an object as an object, binary as binary, etc.). See
|
|
293
|
+
> [receiving files](#receiving-files) for optional size limits and acceptance
|
|
294
|
+
> callbacks.
|
|
292
295
|
|
|
293
296
|
## Audio and video
|
|
294
297
|
|
|
@@ -370,11 +373,38 @@ file.send(buffer, {
|
|
|
370
373
|
})
|
|
371
374
|
```
|
|
372
375
|
|
|
376
|
+
### Receiving files
|
|
377
|
+
|
|
378
|
+
Incoming data is accepted automatically. Use `onReceive` to accept or decline
|
|
379
|
+
payloads based on their size, sender, or metadata:
|
|
380
|
+
|
|
381
|
+
```js
|
|
382
|
+
const file = room.makeAction('file', {
|
|
383
|
+
onReceive: ({byteLength}) => byteLength <= 100 * 1024 ** 2,
|
|
384
|
+
onMessage: (data, {metadata}) => saveFile(data, metadata.name)
|
|
385
|
+
})
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
Return `true` or `false`, or a promise of either. Large payloads wait for
|
|
389
|
+
approval before sending their contents; declined small messages are discarded
|
|
390
|
+
after arrival. You can also assign `file.onReceive` later or set it to `null` to
|
|
391
|
+
restore automatic acceptance.
|
|
392
|
+
|
|
393
|
+
`byteLength` is the payload size in bytes (UTF-8 for strings/JSON), excluding
|
|
394
|
+
metadata. The context's `signal` aborts when the transfer ends; use it to
|
|
395
|
+
dismiss pending approval UI.
|
|
396
|
+
|
|
397
|
+
Each room defaults to a 256 MiB receive budget. Set `maxReceiveBytes` in the
|
|
398
|
+
room config to allow larger files or reduce memory use. Concurrent transfers
|
|
399
|
+
queue automatically when the budget is occupied. Received files must fit in
|
|
400
|
+
memory.
|
|
401
|
+
|
|
373
402
|
### Action promises
|
|
374
403
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
404
|
+
Sender promises resolve when local sending finishes; they do not confirm
|
|
405
|
+
delivery or application handling. If a receiver declines a large transfer, the
|
|
406
|
+
promise rejects with `error.kind === 'rejected'`. Use request actions when you
|
|
407
|
+
need a response.
|
|
378
408
|
|
|
379
409
|
```js
|
|
380
410
|
await file.send(amplePayload)
|
|
@@ -679,6 +709,11 @@ const room = joinRoom(
|
|
|
679
709
|
)
|
|
680
710
|
```
|
|
681
711
|
|
|
712
|
+
`createTopicStrategy` uses a short startup announcement burst followed by a
|
|
713
|
+
60-second steady interval and repeats the burst after a peer drops. Set
|
|
714
|
+
`reannounceOnDisconnect: false` when the signaling backend stores peer presence
|
|
715
|
+
rather than delivering announcements as transient events.
|
|
716
|
+
|
|
682
717
|
For non-pub/sub signaling protocols, such as trackers that exchange offers in
|
|
683
718
|
bulk, `createStrategy` is available as a lower-level advanced API.
|
|
684
719
|
|
|
@@ -749,6 +784,11 @@ the same namespace will return the same room instance.
|
|
|
749
784
|
name. A custom password must match between any peers in the room for them to
|
|
750
785
|
connect. See [encryption](#encryption) for more details.
|
|
751
786
|
|
|
787
|
+
- `maxReceiveBytes` - **(optional)** Maximum payload size and budget for
|
|
788
|
+
concurrent large incoming transfers per room, in bytes. Defaults to
|
|
789
|
+
`256 * 1024 ** 2` (256 MiB). Must be a positive integer. Raise it for larger
|
|
790
|
+
files or lower it to reduce memory use.
|
|
791
|
+
|
|
752
792
|
- `passive` - **(optional)** Boolean for backup or relay peers that should
|
|
753
793
|
listen for active peers without announcing themselves while a room is
|
|
754
794
|
dormant. Passive peers activate only after hearing a non-passive peer and
|
|
@@ -1018,7 +1058,9 @@ Returns an object with the following methods:
|
|
|
1018
1058
|
|
|
1019
1059
|
Message actions expose:
|
|
1020
1060
|
- `send(data, [options])` - Sends data to peers and resolves when local
|
|
1021
|
-
sending is complete.
|
|
1061
|
+
sending is complete. To cancel an in-progress send, pass an
|
|
1062
|
+
`AbortController`'s `signal` in the options (e.g.
|
|
1063
|
+
`send(data, {signal: controller.signal})`) and call `controller.abort()`.
|
|
1022
1064
|
- `onMessage` - Nullable callback property for received messages.
|
|
1023
1065
|
- `onReceiveProgress` - Nullable callback property for inbound progress.
|
|
1024
1066
|
|
|
@@ -1029,10 +1071,17 @@ Returns an object with the following methods:
|
|
|
1029
1071
|
- `onRequest` - Nullable callback property that returns the response.
|
|
1030
1072
|
- `onReceiveProgress` - Nullable callback property for inbound progress.
|
|
1031
1073
|
|
|
1074
|
+
Both kinds expose `onReceive`, a nullable callback accepting
|
|
1075
|
+
`{byteLength, peerId, metadata, kind, signal}` and returning a boolean or a
|
|
1076
|
+
promise of a boolean. It can also be provided in `config`. `kind` is
|
|
1077
|
+
`'message'`, `'request'`, or `'response'`; request actions apply it to both
|
|
1078
|
+
requests and responses. Responses have no metadata. See
|
|
1079
|
+
[receiving files](#receiving-files).
|
|
1080
|
+
|
|
1032
1081
|
Send options use `target`, `metadata`, `onProgress`, and `signal`. Request
|
|
1033
1082
|
options use `target`, `metadata`, `timeoutMs`, `onProgress`, and `signal`.
|
|
1034
1083
|
`requestMany()` uses `targets` plus optional `onResult` for each peer result
|
|
1035
|
-
as it arrives.
|
|
1084
|
+
as it arrives. Request timeouts cover approval, sending, and the response.
|
|
1036
1085
|
|
|
1037
1086
|
Example:
|
|
1038
1087
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { JoinRoom, JoinRoomConfig, pauseRelayReconnection, resumeRelayReconnection, selfId } from "@trystero-p2p/core";
|
|
2
2
|
export type * from "@trystero-p2p/core";
|
|
3
|
-
|
|
4
3
|
//#region src/index.d.ts
|
|
5
|
-
type TorrentRoomConfig = JoinRoomConfig;
|
|
6
|
-
declare const joinRoom: JoinRoom<TorrentRoomConfig>;
|
|
7
|
-
declare const getRelaySockets: any;
|
|
8
|
-
declare const defaultRelayUrls: string[];
|
|
4
|
+
export type TorrentRoomConfig = JoinRoomConfig;
|
|
5
|
+
export declare const joinRoom: JoinRoom<TorrentRoomConfig>;
|
|
6
|
+
export declare const getRelaySockets: any;
|
|
7
|
+
export declare const defaultRelayUrls: string[];
|
|
9
8
|
//#endregion
|
|
10
|
-
export {
|
|
9
|
+
export { pauseRelayReconnection, resumeRelayReconnection, selfId };
|
|
11
10
|
//# sourceMappingURL=index.d.mts.map
|
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":";;;YAqDY,oBAAoB;qBAsXnB,UAAU,SAAS;qBAcnB;qBAIA"}
|
package/dist/index.mjs
CHANGED
|
@@ -3,22 +3,17 @@ import { createRelayManager, createStrategy, entries, fromJson, genId, getRelays
|
|
|
3
3
|
const relayManager = createRelayManager((client) => client.socket);
|
|
4
4
|
const topicToInfoHash = {};
|
|
5
5
|
const infoHashToTopic = {};
|
|
6
|
-
const announceIntervals = relayManager.scoped();
|
|
7
|
-
const announceFns = relayManager.scoped();
|
|
8
|
-
const subscriptionTokens = relayManager.scoped();
|
|
9
|
-
const trackerAnnounceMs = {};
|
|
10
6
|
const handledSignals = {};
|
|
11
|
-
const msgHandlers = relayManager.scoped();
|
|
12
7
|
const topicStates = relayManager.scoped();
|
|
13
8
|
const roomOutstandingOffers = {};
|
|
14
9
|
const roomOfferGenerationPromises = {};
|
|
15
10
|
const roomSubscriberCounts = {};
|
|
16
11
|
const trackerAction = "announce";
|
|
17
12
|
const hashLimit = 20;
|
|
18
|
-
const
|
|
13
|
+
const offersPerAnnounce = 1;
|
|
14
|
+
const requestedPeers = 3;
|
|
19
15
|
const defaultAnnounceMs = 1e4;
|
|
20
16
|
const dormantAnnounceMs = 12e4;
|
|
21
|
-
const maxAnnounceMs = 2e4;
|
|
22
17
|
const offerRetentionMs = 12e4;
|
|
23
18
|
const signalDedupeWindowMs = 4e3;
|
|
24
19
|
const defaultRedundancy = 3;
|
|
@@ -41,31 +36,23 @@ const deleteRoomOfferBookkeeping = (rootTopic) => {
|
|
|
41
36
|
delete roomOutstandingOffers[rootTopic];
|
|
42
37
|
delete roomOfferGenerationPromises[rootTopic];
|
|
43
38
|
};
|
|
44
|
-
const
|
|
39
|
+
const takeOutstandingOffer = (rootTopic, offerId, action) => {
|
|
45
40
|
const outstandingOffers = roomOutstandingOffers[rootTopic];
|
|
46
41
|
const offer = outstandingOffers?.[offerId];
|
|
47
42
|
if (!offer) return;
|
|
48
43
|
delete outstandingOffers[offerId];
|
|
49
|
-
offer
|
|
44
|
+
offer[action]?.();
|
|
50
45
|
if (!keys(outstandingOffers).length && !roomSubscriberCounts[rootTopic]) deleteRoomOfferBookkeeping(rootTopic);
|
|
51
46
|
return offer;
|
|
52
47
|
};
|
|
53
|
-
const reclaimOutstandingOffer = (rootTopic, offerId) => {
|
|
54
|
-
const outstandingOffers = roomOutstandingOffers[rootTopic];
|
|
55
|
-
const offer = outstandingOffers?.[offerId];
|
|
56
|
-
if (!offer) return;
|
|
57
|
-
delete outstandingOffers[offerId];
|
|
58
|
-
offer.reclaim?.();
|
|
59
|
-
if (!keys(outstandingOffers).length && !roomSubscriberCounts[rootTopic]) deleteRoomOfferBookkeeping(rootTopic);
|
|
60
|
-
};
|
|
61
48
|
const reclaimAllOutstandingOffers = (rootTopic) => {
|
|
62
|
-
keys(getRoomOutstandingOffers(rootTopic)).forEach((offerId) =>
|
|
49
|
+
keys(getRoomOutstandingOffers(rootTopic)).forEach((offerId) => takeOutstandingOffer(rootTopic, offerId, "reclaim"));
|
|
63
50
|
deleteRoomOfferBookkeeping(rootTopic);
|
|
64
51
|
};
|
|
65
52
|
const pruneOutstandingOffers = (rootTopic) => {
|
|
66
53
|
const now = Date.now();
|
|
67
54
|
entries(getRoomOutstandingOffers(rootTopic)).forEach(([offerId, offer]) => {
|
|
68
|
-
if (now - offer.createdAt > offerRetentionMs)
|
|
55
|
+
if (now - offer.createdAt > offerRetentionMs) takeOutstandingOffer(rootTopic, offerId, "reclaim");
|
|
69
56
|
});
|
|
70
57
|
};
|
|
71
58
|
const ensureOutstandingOffers = async (rootTopic, getOffers) => {
|
|
@@ -74,7 +61,7 @@ const ensureOutstandingOffers = async (rootTopic, getOffers) => {
|
|
|
74
61
|
pruneOutstandingOffers(rootTopic);
|
|
75
62
|
const outstandingOffers = getRoomOutstandingOffers(rootTopic);
|
|
76
63
|
const outstandingCount = keys(outstandingOffers).length;
|
|
77
|
-
const missingOffers = Math.max(0,
|
|
64
|
+
const missingOffers = Math.max(0, offersPerAnnounce - outstandingCount);
|
|
78
65
|
if (missingOffers > 0) (await getOffers(missingOffers)).forEach((peerAndOffer) => {
|
|
79
66
|
outstandingOffers[genId(hashLimit)] = {
|
|
80
67
|
...peerAndOffer,
|
|
@@ -101,17 +88,8 @@ const joinRoomStrategy = createStrategy({
|
|
|
101
88
|
return;
|
|
102
89
|
}
|
|
103
90
|
if (warnMsg && config.relayConfig?.warnOnRelayFailure !== false) warn(client.url, warnMsg);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const relayIntervals = announceIntervals.forKey(rawUrl);
|
|
107
|
-
const relayFns = announceFns.forKey(rawUrl);
|
|
108
|
-
if (relayIntervals[topic]) clearInterval(relayIntervals[topic]);
|
|
109
|
-
trackerAnnounceMs[client.url] = nextInterval;
|
|
110
|
-
const relayFn = relayFns[topic];
|
|
111
|
-
if (relayFn) relayIntervals[topic] = setInterval(() => {
|
|
112
|
-
relayFn();
|
|
113
|
-
}, nextInterval);
|
|
114
|
-
}
|
|
91
|
+
const state = topic ? topicStates.forKey(rawUrl)[topic] : void 0;
|
|
92
|
+
if (interval && state) state.announceMs = Math.min(Math.max(interval * 1e3, defaultAnnounceMs), offerRetentionMs);
|
|
115
93
|
if ((data.offer || data.answer) && topic && data.offer_id) {
|
|
116
94
|
if (data.peer_id === selfId) return;
|
|
117
95
|
const signalKey = `${topic}:${data.offer ? "offer" : "answer"}:${data.offer_id}:${data.peer_id ?? ""}`;
|
|
@@ -122,123 +100,114 @@ const joinRoomStrategy = createStrategy({
|
|
|
122
100
|
entries(handledSignals).forEach(([key, handledAtMs]) => {
|
|
123
101
|
if (nowMs - handledAtMs > signalDedupeWindowMs * 6) delete handledSignals[key];
|
|
124
102
|
});
|
|
125
|
-
|
|
103
|
+
state?.handler(data);
|
|
126
104
|
}
|
|
127
|
-
}));
|
|
105
|
+
}, () => Object.values(topicStates.forKey(rawUrl)).forEach((state) => void state.announce())));
|
|
128
106
|
return client.ready;
|
|
129
107
|
}),
|
|
130
108
|
subscribe: (client, rootTopic, _, onMessage, getOffers, context) => {
|
|
131
|
-
const handlers = msgHandlers.forRelay(client);
|
|
132
|
-
const relayFns = announceFns.forRelay(client);
|
|
133
|
-
const relayIntervals = announceIntervals.forRelay(client);
|
|
134
|
-
const activeTokens = subscriptionTokens.forRelay(client);
|
|
135
109
|
const states = topicStates.forRelay(client);
|
|
136
110
|
const subscriptionToken = Symbol(rootTopic);
|
|
137
|
-
activeTokens[rootTopic] = subscriptionToken;
|
|
138
111
|
roomSubscriberCounts[rootTopic] = (roomSubscriberCounts[rootTopic] ?? 0) + 1;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
hasOutgoingOffer: keys(getRoomOutstandingOffers(rootTopic)).length > 0
|
|
144
|
-
}, (_, signal) => void send(client, rootTopic, {
|
|
145
|
-
answer: {
|
|
146
|
-
type: "answer",
|
|
147
|
-
sdp: fromJson(signal).answer
|
|
148
|
-
},
|
|
149
|
-
offer_id: data.offer_id,
|
|
150
|
-
to_peer_id: data.peer_id
|
|
151
|
-
}));
|
|
152
|
-
else if (data.answer && data.offer_id && data.peer_id) {
|
|
153
|
-
const offer = claimOutstandingOffer(rootTopic, data.offer_id);
|
|
154
|
-
if (offer) onMessage(rootTopic, {
|
|
155
|
-
answer: data.answer.sdp,
|
|
156
|
-
peerId: data.peer_id,
|
|
157
|
-
peer: offer.peer
|
|
158
|
-
}, () => {});
|
|
159
|
-
}
|
|
112
|
+
let passiveAnnounceTimeout;
|
|
113
|
+
const stopPassiveAnnouncements = () => {
|
|
114
|
+
clearTimeout(passiveAnnounceTimeout);
|
|
115
|
+
passiveAnnounceTimeout = void 0;
|
|
160
116
|
};
|
|
161
|
-
handlers[rootTopic] = topicHandler;
|
|
162
117
|
const topicState = {
|
|
163
118
|
announce: async () => {
|
|
164
|
-
if (
|
|
119
|
+
if (states[rootTopic]?.token !== subscriptionToken) return;
|
|
165
120
|
if (!topicState.isActive) {
|
|
166
121
|
send(client, rootTopic, {
|
|
167
122
|
left: 0,
|
|
168
|
-
numwant:
|
|
123
|
+
numwant: requestedPeers,
|
|
169
124
|
offers: []
|
|
170
125
|
});
|
|
171
126
|
return;
|
|
172
127
|
}
|
|
128
|
+
let outstandingOffers;
|
|
129
|
+
try {
|
|
130
|
+
outstandingOffers = await ensureOutstandingOffers(rootTopic, getOffers);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (states[rootTopic]?.token !== subscriptionToken) return;
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
if (states[rootTopic]?.token !== subscriptionToken || !topicState.isActive) return;
|
|
136
|
+
const offers = entries(outstandingOffers).map(([id, { offer }]) => ({
|
|
137
|
+
offer_id: id,
|
|
138
|
+
offer: {
|
|
139
|
+
type: "offer",
|
|
140
|
+
sdp: offer
|
|
141
|
+
}
|
|
142
|
+
}));
|
|
173
143
|
send(client, rootTopic, {
|
|
174
|
-
numwant:
|
|
175
|
-
offers
|
|
176
|
-
offer_id: id,
|
|
177
|
-
offer: {
|
|
178
|
-
type: "offer",
|
|
179
|
-
sdp: offer
|
|
180
|
-
}
|
|
181
|
-
}))
|
|
144
|
+
numwant: requestedPeers,
|
|
145
|
+
offers
|
|
182
146
|
});
|
|
183
147
|
},
|
|
184
|
-
|
|
148
|
+
announceMs: defaultAnnounceMs,
|
|
149
|
+
handler: (data) => {
|
|
150
|
+
if (data.offer && data.peer_id && data.offer_id) onMessage(rootTopic, {
|
|
151
|
+
offer: data.offer.sdp,
|
|
152
|
+
offerId: data.offer_id,
|
|
153
|
+
peerId: data.peer_id
|
|
154
|
+
}, (_, signal) => void send(client, rootTopic, {
|
|
155
|
+
answer: {
|
|
156
|
+
type: "answer",
|
|
157
|
+
sdp: fromJson(signal).answer
|
|
158
|
+
},
|
|
159
|
+
offer_id: data.offer_id,
|
|
160
|
+
to_peer_id: data.peer_id
|
|
161
|
+
}));
|
|
162
|
+
else if (data.answer && data.offer_id && data.peer_id) {
|
|
163
|
+
const offer = takeOutstandingOffer(rootTopic, data.offer_id, "claim");
|
|
164
|
+
if (offer) {
|
|
165
|
+
onMessage(rootTopic, {
|
|
166
|
+
answer: data.answer.sdp,
|
|
167
|
+
offerId: data.offer_id,
|
|
168
|
+
peerId: data.peer_id,
|
|
169
|
+
peer: offer.peer
|
|
170
|
+
}, () => {});
|
|
171
|
+
topicState.announce();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
isActive: !context?.isPassive,
|
|
176
|
+
startPassiveAnnouncements: () => {
|
|
177
|
+
stopPassiveAnnouncements();
|
|
178
|
+
if (states[rootTopic]?.token !== subscriptionToken || topicState.isActive) return;
|
|
179
|
+
topicState.announce();
|
|
180
|
+
passiveAnnounceTimeout = setTimeout(topicState.startPassiveAnnouncements, Math.max(topicState.announceMs, dormantAnnounceMs));
|
|
181
|
+
},
|
|
182
|
+
stopPassiveAnnouncements,
|
|
183
|
+
token: subscriptionToken
|
|
185
184
|
};
|
|
186
|
-
trackerAnnounceMs[client.url] = defaultAnnounceMs;
|
|
187
|
-
const { announce } = topicState;
|
|
188
|
-
relayFns[rootTopic] = announce;
|
|
189
185
|
states[rootTopic] = topicState;
|
|
190
|
-
|
|
191
|
-
relayIntervals[rootTopic] = setInterval(announce, initialInterval);
|
|
192
|
-
announce();
|
|
186
|
+
if (!topicState.isActive) topicState.startPassiveAnnouncements();
|
|
193
187
|
return () => {
|
|
188
|
+
topicState.stopPassiveAnnouncements();
|
|
194
189
|
roomSubscriberCounts[rootTopic] = Math.max(0, (roomSubscriberCounts[rootTopic] ?? 1) - 1);
|
|
195
|
-
if (!roomSubscriberCounts[rootTopic])
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
reclaimAllOutstandingOffers(rootTopic);
|
|
199
|
-
delete states[rootTopic];
|
|
200
|
-
}
|
|
201
|
-
return;
|
|
190
|
+
if (!roomSubscriberCounts[rootTopic]) {
|
|
191
|
+
delete roomSubscriberCounts[rootTopic];
|
|
192
|
+
reclaimAllOutstandingOffers(rootTopic);
|
|
202
193
|
}
|
|
203
|
-
|
|
204
|
-
if (interval) {
|
|
205
|
-
clearInterval(interval);
|
|
206
|
-
delete relayIntervals[rootTopic];
|
|
207
|
-
}
|
|
208
|
-
if (handlers[rootTopic] === topicHandler) delete handlers[rootTopic];
|
|
209
|
-
if (relayFns[rootTopic] === announce) delete relayFns[rootTopic];
|
|
210
|
-
delete activeTokens[rootTopic];
|
|
211
|
-
if (states[rootTopic] === topicState) delete states[rootTopic];
|
|
212
|
-
if (!roomSubscriberCounts[rootTopic]) reclaimAllOutstandingOffers(rootTopic);
|
|
194
|
+
if (states[rootTopic]?.token === subscriptionToken) delete states[rootTopic];
|
|
213
195
|
};
|
|
214
196
|
},
|
|
215
|
-
announce: (client, rootTopic) => {
|
|
197
|
+
announce: async (client, rootTopic) => {
|
|
216
198
|
const state = topicStates.forRelay(client)[rootTopic];
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
clearInterval(relayIntervals[rootTopic]);
|
|
222
|
-
relayIntervals[rootTopic] = setInterval(() => {
|
|
223
|
-
fn();
|
|
224
|
-
}, trackerAnnounceMs[client.url]);
|
|
225
|
-
fn();
|
|
199
|
+
if (state) {
|
|
200
|
+
state.stopPassiveAnnouncements();
|
|
201
|
+
state.isActive = true;
|
|
202
|
+
await state.announce();
|
|
226
203
|
}
|
|
227
|
-
return
|
|
204
|
+
return state?.announceMs ?? defaultAnnounceMs;
|
|
228
205
|
},
|
|
229
206
|
deactivate: (client, rootTopic) => {
|
|
230
207
|
const state = topicStates.forRelay(client)[rootTopic];
|
|
231
|
-
const relayIntervals = announceIntervals.forRelay(client);
|
|
232
|
-
const fn = announceFns.forRelay(client)[rootTopic];
|
|
233
208
|
if (state) state.isActive = false;
|
|
234
209
|
reclaimAllOutstandingOffers(rootTopic);
|
|
235
|
-
|
|
236
|
-
clearInterval(relayIntervals[rootTopic]);
|
|
237
|
-
relayIntervals[rootTopic] = setInterval(() => {
|
|
238
|
-
fn();
|
|
239
|
-
}, dormantAnnounceMs);
|
|
240
|
-
fn();
|
|
241
|
-
}
|
|
210
|
+
state?.startPassiveAnnouncements();
|
|
242
211
|
}
|
|
243
212
|
});
|
|
244
213
|
const joinRoom = (config, roomId, callbacks) => joinRoomStrategy({
|
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 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(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 if (config.relayConfig?.warnOnRelayFailure !== false) {\n warn(client.url, errMsg, true)\n }\n\n return\n }\n\n if (warnMsg && config.relayConfig?.warnOnRelayFailure !== false) {\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 'open.ftorrent.com',\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> = {}\ntype TopicState = {\n announce: () => void | Promise<void>\n announceMs: number\n handler: (data: TrackerMessage) => void\n isActive: boolean\n startPassiveAnnouncements: () => void\n stopPassiveAnnouncements: () => void\n token: symbol\n}\n\nconst handledSignals: Record<string, number> = {}\nconst topicStates = relayManager.scoped<TopicState>()\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 offersPerAnnounce = 1\nconst requestedPeers = 3\nconst defaultAnnounceMs = 10_000\nconst dormantAnnounceMs = 120_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 takeOutstandingOffer = (\n rootTopic: string,\n offerId: string,\n action: 'claim' | 'reclaim'\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[action]?.()\n\n if (!keys(outstandingOffers).length && !roomSubscriberCounts[rootTopic]) {\n deleteRoomOfferBookkeeping(rootTopic)\n }\n\n return offer\n}\n\nconst reclaimAllOutstandingOffers = (rootTopic: string): void => {\n keys(getRoomOutstandingOffers(rootTopic)).forEach(offerId =>\n takeOutstandingOffer(rootTopic, offerId, 'reclaim')\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 takeOutstandingOffer(rootTopic, offerId, 'reclaim')\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, offersPerAnnounce - 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(rawUrl, () =>\n makeSocket(\n rawUrl,\n 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 if (config.relayConfig?.warnOnRelayFailure !== false) {\n warn(client.url, errMsg, true)\n }\n\n return\n }\n\n if (warnMsg && config.relayConfig?.warnOnRelayFailure !== false) {\n warn(client.url, warnMsg)\n }\n\n const state = topic ? topicStates.forKey(rawUrl)[topic] : undefined\n\n if (interval && state) {\n state.announceMs = Math.min(\n Math.max(interval * 1000, defaultAnnounceMs),\n offerRetentionMs\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 state?.handler(data)\n }\n },\n () =>\n Object.values(topicStates.forKey(rawUrl)).forEach(\n state => void state.announce()\n )\n )\n )\n\n return client.ready\n }),\n\n subscribe: (client, rootTopic, _, onMessage, getOffers, context) => {\n const states = topicStates.forRelay(client)\n const subscriptionToken = Symbol(rootTopic)\n\n roomSubscriberCounts[rootTopic] = (roomSubscriberCounts[rootTopic] ?? 0) + 1\n\n let passiveAnnounceTimeout: ReturnType<typeof setTimeout> | undefined\n const stopPassiveAnnouncements = (): void => {\n clearTimeout(passiveAnnounceTimeout)\n passiveAnnounceTimeout = undefined\n }\n const topicState: TopicState = {\n announce: async () => {\n if (states[rootTopic]?.token !== subscriptionToken) {\n return\n }\n\n if (!topicState.isActive) {\n void send(client, rootTopic, {\n left: 0,\n numwant: requestedPeers,\n offers: []\n })\n return\n }\n\n let outstandingOffers: Record<string, OfferRecord & {createdAt: number}>\n\n try {\n outstandingOffers = await ensureOutstandingOffers(\n rootTopic,\n getOffers\n )\n } catch (error) {\n if (states[rootTopic]?.token !== subscriptionToken) {\n return\n }\n\n throw error\n }\n\n if (\n states[rootTopic]?.token !== subscriptionToken ||\n !topicState.isActive\n ) {\n return\n }\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: requestedPeers,\n offers\n })\n },\n announceMs: defaultAnnounceMs,\n handler: (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 offerId: data.offer_id,\n peerId: data.peer_id\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 = takeOutstandingOffer(rootTopic, data.offer_id, 'claim')\n\n if (offer) {\n void onMessage(\n rootTopic,\n {\n answer: data.answer.sdp,\n offerId: data.offer_id,\n peerId: data.peer_id,\n peer: offer.peer\n },\n () => {}\n )\n void topicState.announce()\n }\n }\n },\n isActive: !context?.isPassive,\n startPassiveAnnouncements: (): void => {\n stopPassiveAnnouncements()\n\n if (\n states[rootTopic]?.token !== subscriptionToken ||\n topicState.isActive\n ) {\n return\n }\n\n void topicState.announce()\n passiveAnnounceTimeout = setTimeout(\n topicState.startPassiveAnnouncements,\n Math.max(topicState.announceMs, dormantAnnounceMs)\n )\n },\n stopPassiveAnnouncements,\n token: subscriptionToken\n }\n\n states[rootTopic] = topicState\n\n if (!topicState.isActive) {\n topicState.startPassiveAnnouncements()\n }\n\n return () => {\n topicState.stopPassiveAnnouncements()\n roomSubscriberCounts[rootTopic] = Math.max(\n 0,\n (roomSubscriberCounts[rootTopic] ?? 1) - 1\n )\n\n if (!roomSubscriberCounts[rootTopic]) {\n delete roomSubscriberCounts[rootTopic]\n reclaimAllOutstandingOffers(rootTopic)\n }\n\n if (states[rootTopic]?.token === subscriptionToken) {\n delete states[rootTopic]\n }\n }\n },\n\n announce: async (client, rootTopic) => {\n const state = topicStates.forRelay(client)[rootTopic]\n\n if (state) {\n state.stopPassiveAnnouncements()\n state.isActive = true\n await state.announce()\n }\n\n return state?.announceMs ?? defaultAnnounceMs\n },\n\n deactivate: (client, rootTopic) => {\n const state = topicStates.forRelay(client)[rootTopic]\n\n if (state) {\n state.isActive = false\n }\n\n reclaimAllOutstandingOffers(rootTopic)\n\n state?.startPassiveAnnouncements()\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 'open.ftorrent.com',\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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trystero-p2p/torrent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Simple, serverless WebRTC (via Torrent)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bittorrent",
|
|
@@ -35,6 +35,6 @@
|
|
|
35
35
|
"./package.json": "./package.json"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@trystero-p2p/core": "0.
|
|
38
|
+
"@trystero-p2p/core": "0.26.0"
|
|
39
39
|
}
|
|
40
40
|
}
|