Created
July 19, 2025 15:06
-
-
Save smhmd/0d4f7d18f9c0b81cb5c2103f3d020bb3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { AnnounceResponse } from "./types"; | |
| import { | |
| decodeHexStringToAscii, | |
| convertBytesToHexString, | |
| generateSecureRandomBytes, | |
| } from "./utils"; | |
| const infoHash = "08ada5a7a6183aae1e09d831df6748d566095a10"; | |
| const peerId = "-WW0109-sC9Dl2bON8bO"; | |
| const announceUrl = "wss://tracker.openwebtorrent.com/"; | |
| const stunServers = [{ urls: "stun:stun.l.google.com:19302" }]; | |
| const numwant = 5; | |
| const socket = new WebSocket(announceUrl); | |
| const peers = new Map<string, RTCPeerConnection>(); | |
| const terminalStates = ["disconnected", "failed", "closed"]; | |
| socket.addEventListener("open", async () => { | |
| const offers = await generateOffers(numwant); | |
| socket.send( | |
| JSON.stringify({ | |
| action: "announce", | |
| event: "started", | |
| info_hash: decodeHexStringToAscii(infoHash), | |
| peer_id: peerId, | |
| numwant, | |
| offers, | |
| }) | |
| ); | |
| }); | |
| socket.addEventListener("message", async (event) => { | |
| const data = JSON.parse(event.data) as AnnounceResponse; | |
| if (data.action !== "announce") return; | |
| if (data.offer && data.peer_id && data.offer_id) { | |
| const peer = new RTCPeerConnection({ | |
| iceServers: stunServers, | |
| iceCandidatePoolSize: 10, | |
| }); | |
| peer.ondatachannel = (event) => { | |
| const channel = event.channel; | |
| channel.onopen = () => { | |
| // Immediately request metadata | |
| channel.send(JSON.stringify({ type: "request-metadata" })); | |
| }; | |
| channel.onmessage = (event) => { | |
| try { | |
| const msg = JSON.parse(event.data); | |
| if (msg.type === "metadata") { | |
| console.log(msg.data); | |
| } | |
| } catch (e) { | |
| console.warn("Invalid message from peer:", event.data); | |
| } | |
| }; | |
| }; | |
| peer.onicecandidate = (event) => { | |
| if (event.candidate === null) { | |
| socket.send( | |
| JSON.stringify({ | |
| action: "announce", | |
| info_hash: decodeHexStringToAscii(infoHash), | |
| peer_id: peerId, | |
| to_peer_id: data.peer_id, | |
| answer: peer.localDescription, | |
| offer_id: data.offer_id, | |
| }) | |
| ); | |
| } | |
| }; | |
| await peer.setRemoteDescription(data.offer); | |
| const answer = await peer.createAnswer(); | |
| await peer.setLocalDescription(answer); | |
| peer.onconnectionstatechange = () => { | |
| if (terminalStates.includes(peer.connectionState)) { | |
| peers.delete(data.offer_id); | |
| } | |
| }; | |
| peers.set(data.offer_id, peer); | |
| } | |
| if (data.answer && data.peer_id && data.offer_id) { | |
| const peer = peers.get(data.offer_id); | |
| if (peer) { | |
| await peer.setRemoteDescription(data.answer); | |
| } | |
| } | |
| }); | |
| async function generateOffers(count: number) { | |
| return await Promise.all( | |
| Array.from({ length: count }, async () => { | |
| const peer = new RTCPeerConnection({ | |
| iceServers: stunServers, | |
| iceCandidatePoolSize: 10, | |
| }); | |
| const channel = peer.createDataChannel("metadata"); | |
| channel.onopen = () => { | |
| // Immediately request metadata | |
| channel.send(JSON.stringify({ type: "request-metadata" })); | |
| }; | |
| channel.onmessage = (event) => { | |
| try { | |
| const msg = JSON.parse(event.data); | |
| if (msg.type === "metadata") { | |
| console.log(msg.data); | |
| } | |
| } catch (e) { | |
| console.warn("Invalid message from peer:", event.data); | |
| } | |
| }; | |
| const offerId = convertBytesToHexString(generateSecureRandomBytes(20)); | |
| const offer = await peer.createOffer(); | |
| await peer.setLocalDescription(offer); | |
| peers.set(offerId, peer); | |
| peer.onconnectionstatechange = () => { | |
| if (terminalStates.includes(peer.connectionState)) { | |
| peers.delete(offerId); | |
| } | |
| }; | |
| return { | |
| offer, | |
| offer_id: offerId, | |
| }; | |
| }) | |
| ); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment