@trystero-p2p/torrent 0.25.3 → 0.25.4
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 +5 -0
- package/dist/index.d.mts +0 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +35 -51
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -679,6 +679,11 @@ const room = joinRoom(
|
|
|
679
679
|
)
|
|
680
680
|
```
|
|
681
681
|
|
|
682
|
+
`createTopicStrategy` uses a short startup announcement burst followed by a
|
|
683
|
+
60-second steady interval and repeats the burst after a peer drops. Set
|
|
684
|
+
`reannounceOnDisconnect: false` when the signaling backend stores peer presence
|
|
685
|
+
rather than delivering announcements as transient events.
|
|
686
|
+
|
|
682
687
|
For non-pub/sub signaling protocols, such as trackers that exchange offers in
|
|
683
688
|
bulk, `createStrategy` is available as a lower-level advanced API.
|
|
684
689
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
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
4
|
type TorrentRoomConfig = JoinRoomConfig;
|
|
6
5
|
declare const joinRoom: JoinRoom<TorrentRoomConfig>;
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;KAmDY,oBAAoB;cAyZnB,UAAU,SAAS;cAcnB;cAIA"}
|
package/dist/index.mjs
CHANGED
|
@@ -3,10 +3,9 @@ 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
6
|
const announceFns = relayManager.scoped();
|
|
8
7
|
const subscriptionTokens = relayManager.scoped();
|
|
9
|
-
const trackerAnnounceMs =
|
|
8
|
+
const trackerAnnounceMs = relayManager.scoped();
|
|
10
9
|
const handledSignals = {};
|
|
11
10
|
const msgHandlers = relayManager.scoped();
|
|
12
11
|
const topicStates = relayManager.scoped();
|
|
@@ -18,7 +17,6 @@ const hashLimit = 20;
|
|
|
18
17
|
const offerPoolSize = 3;
|
|
19
18
|
const defaultAnnounceMs = 1e4;
|
|
20
19
|
const dormantAnnounceMs = 12e4;
|
|
21
|
-
const maxAnnounceMs = 2e4;
|
|
22
20
|
const offerRetentionMs = 12e4;
|
|
23
21
|
const signalDedupeWindowMs = 4e3;
|
|
24
22
|
const defaultRedundancy = 3;
|
|
@@ -101,17 +99,7 @@ const joinRoomStrategy = createStrategy({
|
|
|
101
99
|
return;
|
|
102
100
|
}
|
|
103
101
|
if (warnMsg && config.relayConfig?.warnOnRelayFailure !== false) warn(client.url, warnMsg);
|
|
104
|
-
if (interval &&
|
|
105
|
-
const nextInterval = Math.min(interval * 1e3, maxAnnounceMs);
|
|
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
|
-
}
|
|
102
|
+
if (interval && topic && announceFns.forKey(rawUrl)[topic]) trackerAnnounceMs.forKey(rawUrl)[topic] = Math.min(Math.max(interval * 1e3, defaultAnnounceMs), offerRetentionMs);
|
|
115
103
|
if ((data.offer || data.answer) && topic && data.offer_id) {
|
|
116
104
|
if (data.peer_id === selfId) return;
|
|
117
105
|
const signalKey = `${topic}:${data.offer ? "offer" : "answer"}:${data.offer_id}:${data.peer_id ?? ""}`;
|
|
@@ -124,13 +112,13 @@ const joinRoomStrategy = createStrategy({
|
|
|
124
112
|
});
|
|
125
113
|
msgHandlers.forKey(rawUrl)[topic]?.(data);
|
|
126
114
|
}
|
|
127
|
-
}));
|
|
115
|
+
}, () => Object.values(announceFns.forKey(rawUrl)).forEach((announce) => void announce())));
|
|
128
116
|
return client.ready;
|
|
129
117
|
}),
|
|
130
118
|
subscribe: (client, rootTopic, _, onMessage, getOffers, context) => {
|
|
131
119
|
const handlers = msgHandlers.forRelay(client);
|
|
132
120
|
const relayFns = announceFns.forRelay(client);
|
|
133
|
-
const
|
|
121
|
+
const relayAnnounceMs = trackerAnnounceMs.forRelay(client);
|
|
134
122
|
const activeTokens = subscriptionTokens.forRelay(client);
|
|
135
123
|
const states = topicStates.forRelay(client);
|
|
136
124
|
const subscriptionToken = Symbol(rootTopic);
|
|
@@ -159,6 +147,11 @@ const joinRoomStrategy = createStrategy({
|
|
|
159
147
|
}
|
|
160
148
|
};
|
|
161
149
|
handlers[rootTopic] = topicHandler;
|
|
150
|
+
let passiveAnnounceTimeout;
|
|
151
|
+
const stopPassiveAnnouncements = () => {
|
|
152
|
+
clearTimeout(passiveAnnounceTimeout);
|
|
153
|
+
passiveAnnounceTimeout = void 0;
|
|
154
|
+
};
|
|
162
155
|
const topicState = {
|
|
163
156
|
announce: async () => {
|
|
164
157
|
if (activeTokens[rootTopic] !== subscriptionToken) return;
|
|
@@ -170,27 +163,34 @@ const joinRoomStrategy = createStrategy({
|
|
|
170
163
|
});
|
|
171
164
|
return;
|
|
172
165
|
}
|
|
166
|
+
const offers = entries(await ensureOutstandingOffers(rootTopic, getOffers)).map(([id, { offer }]) => ({
|
|
167
|
+
offer_id: id,
|
|
168
|
+
offer: {
|
|
169
|
+
type: "offer",
|
|
170
|
+
sdp: offer
|
|
171
|
+
}
|
|
172
|
+
}));
|
|
173
173
|
send(client, rootTopic, {
|
|
174
174
|
numwant: offerPoolSize,
|
|
175
|
-
offers
|
|
176
|
-
offer_id: id,
|
|
177
|
-
offer: {
|
|
178
|
-
type: "offer",
|
|
179
|
-
sdp: offer
|
|
180
|
-
}
|
|
181
|
-
}))
|
|
175
|
+
offers
|
|
182
176
|
});
|
|
183
177
|
},
|
|
184
|
-
isActive: !context?.isPassive
|
|
178
|
+
isActive: !context?.isPassive,
|
|
179
|
+
startPassiveAnnouncements: () => {
|
|
180
|
+
stopPassiveAnnouncements();
|
|
181
|
+
if (activeTokens[rootTopic] !== subscriptionToken || topicState.isActive) return;
|
|
182
|
+
topicState.announce();
|
|
183
|
+
passiveAnnounceTimeout = setTimeout(topicState.startPassiveAnnouncements, Math.max(relayAnnounceMs[rootTopic] ?? dormantAnnounceMs, dormantAnnounceMs));
|
|
184
|
+
},
|
|
185
|
+
stopPassiveAnnouncements
|
|
185
186
|
};
|
|
186
|
-
|
|
187
|
+
relayAnnounceMs[rootTopic] = defaultAnnounceMs;
|
|
187
188
|
const { announce } = topicState;
|
|
188
189
|
relayFns[rootTopic] = announce;
|
|
189
190
|
states[rootTopic] = topicState;
|
|
190
|
-
|
|
191
|
-
relayIntervals[rootTopic] = setInterval(announce, initialInterval);
|
|
192
|
-
announce();
|
|
191
|
+
if (!topicState.isActive) topicState.startPassiveAnnouncements();
|
|
193
192
|
return () => {
|
|
193
|
+
topicState.stopPassiveAnnouncements();
|
|
194
194
|
roomSubscriberCounts[rootTopic] = Math.max(0, (roomSubscriberCounts[rootTopic] ?? 1) - 1);
|
|
195
195
|
if (!roomSubscriberCounts[rootTopic]) delete roomSubscriberCounts[rootTopic];
|
|
196
196
|
if (activeTokens[rootTopic] !== subscriptionToken) {
|
|
@@ -200,13 +200,9 @@ const joinRoomStrategy = createStrategy({
|
|
|
200
200
|
}
|
|
201
201
|
return;
|
|
202
202
|
}
|
|
203
|
-
const interval = relayIntervals[rootTopic];
|
|
204
|
-
if (interval) {
|
|
205
|
-
clearInterval(interval);
|
|
206
|
-
delete relayIntervals[rootTopic];
|
|
207
|
-
}
|
|
208
203
|
if (handlers[rootTopic] === topicHandler) delete handlers[rootTopic];
|
|
209
204
|
if (relayFns[rootTopic] === announce) delete relayFns[rootTopic];
|
|
205
|
+
delete relayAnnounceMs[rootTopic];
|
|
210
206
|
delete activeTokens[rootTopic];
|
|
211
207
|
if (states[rootTopic] === topicState) delete states[rootTopic];
|
|
212
208
|
if (!roomSubscriberCounts[rootTopic]) reclaimAllOutstandingOffers(rootTopic);
|
|
@@ -214,31 +210,19 @@ const joinRoomStrategy = createStrategy({
|
|
|
214
210
|
},
|
|
215
211
|
announce: (client, rootTopic) => {
|
|
216
212
|
const state = topicStates.forRelay(client)[rootTopic];
|
|
217
|
-
const relayIntervals = announceIntervals.forRelay(client);
|
|
218
213
|
const fn = announceFns.forRelay(client)[rootTopic];
|
|
219
|
-
if (state)
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
relayIntervals[rootTopic] = setInterval(() => {
|
|
223
|
-
fn();
|
|
224
|
-
}, trackerAnnounceMs[client.url]);
|
|
225
|
-
fn();
|
|
214
|
+
if (state) {
|
|
215
|
+
state.stopPassiveAnnouncements();
|
|
216
|
+
state.isActive = true;
|
|
226
217
|
}
|
|
227
|
-
|
|
218
|
+
if (fn) fn();
|
|
219
|
+
return trackerAnnounceMs.forRelay(client)[rootTopic] ?? defaultAnnounceMs;
|
|
228
220
|
},
|
|
229
221
|
deactivate: (client, rootTopic) => {
|
|
230
222
|
const state = topicStates.forRelay(client)[rootTopic];
|
|
231
|
-
const relayIntervals = announceIntervals.forRelay(client);
|
|
232
|
-
const fn = announceFns.forRelay(client)[rootTopic];
|
|
233
223
|
if (state) state.isActive = false;
|
|
234
224
|
reclaimAllOutstandingOffers(rootTopic);
|
|
235
|
-
|
|
236
|
-
clearInterval(relayIntervals[rootTopic]);
|
|
237
|
-
relayIntervals[rootTopic] = setInterval(() => {
|
|
238
|
-
fn();
|
|
239
|
-
}, dormantAnnounceMs);
|
|
240
|
-
fn();
|
|
241
|
-
}
|
|
225
|
+
state?.startPassiveAnnouncements();
|
|
242
226
|
}
|
|
243
227
|
});
|
|
244
228
|
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> = {}\nconst announceFns = relayManager.scoped<() => void | Promise<void>>()\nconst subscriptionTokens = relayManager.scoped<symbol>()\nconst trackerAnnounceMs = relayManager.scoped<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 startPassiveAnnouncements: () => void\n stopPassiveAnnouncements: () => void\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 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(\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 if (interval && topic && announceFns.forKey(rawUrl)[topic]) {\n trackerAnnounceMs.forKey(rawUrl)[topic] = 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 msgHandlers.forKey(rawUrl)[topic]?.(data)\n }\n },\n () =>\n Object.values(announceFns.forKey(rawUrl)).forEach(\n announce => void announce()\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 relayAnnounceMs = trackerAnnounceMs.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 let passiveAnnounceTimeout: ReturnType<typeof setTimeout> | undefined\n const stopPassiveAnnouncements = (): void => {\n clearTimeout(passiveAnnounceTimeout)\n passiveAnnounceTimeout = undefined\n }\n const topicState = {\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 startPassiveAnnouncements: (): void => {\n stopPassiveAnnouncements()\n\n if (\n activeTokens[rootTopic] !== subscriptionToken ||\n topicState.isActive\n ) {\n return\n }\n\n void topicState.announce()\n passiveAnnounceTimeout = setTimeout(\n topicState.startPassiveAnnouncements,\n Math.max(\n relayAnnounceMs[rootTopic] ?? dormantAnnounceMs,\n dormantAnnounceMs\n )\n )\n },\n stopPassiveAnnouncements\n }\n\n relayAnnounceMs[rootTopic] = defaultAnnounceMs\n const {announce} = topicState\n relayFns[rootTopic] = announce\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 }\n\n if (activeTokens[rootTopic] !== subscriptionToken) {\n if (!roomSubscriberCounts[rootTopic]) {\n reclaimAllOutstandingOffers(rootTopic)\n delete states[rootTopic]\n }\n\n return\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 relayAnnounceMs[rootTopic]\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 relayFns = announceFns.forRelay(client)\n const fn = relayFns[rootTopic]\n\n if (state) {\n state.stopPassiveAnnouncements()\n state.isActive = true\n }\n\n if (fn) {\n void fn()\n }\n\n return trackerAnnounceMs.forRelay(client)[rootTopic] ?? 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.25.
|
|
3
|
+
"version": "0.25.4",
|
|
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.25.
|
|
38
|
+
"@trystero-p2p/core": "0.25.4"
|
|
39
39
|
}
|
|
40
40
|
}
|