import Fastify from 'fastify'; import fastifyStatic from '@fastify/static'; import { join } from 'node:path'; import WebTorrent, { Torrent } from 'webtorrent'; import createTorrent from 'create-torrent' // @ts-ignore import TrackerServer from 'bittorrent-tracker/server' import fs, { existsSync, mkdirSync, writeFileSync } from 'node:fs'; // Configuration const API_PORT = 6080; const TRACKER_PORT = 6081; // Get local IP address for broader accessibility const LOCAL_IP = '127.0.0.1'; const TRACKER_URL = `http://${LOCAL_IP}:${TRACKER_PORT}/announce`; const API_BASE_URL = `http://${LOCAL_IP}:${API_PORT}/api/static/`; // Initialize Fastify instance const app = Fastify({ logger: true }); // Mount static file service, mapping ./workspace directory to /api/static route app.register(fastifyStatic, { root: join(process.cwd(), 'workspace'), prefix: '/api/static', }); const tracker = new TrackerServer({ udp: true, // enable udp server? [default=true] http: true, // enable http server? [default=true] ws: true, // enable websocket server? [default=true] stats: true, // enable web-based statistics? [default=true] trustProxy: true, // enable trusting x-forwarded-for header for remote IP [default=false] }); // Define request and response type definitions interface FileItem { path: string; size: number; } interface RequestSchema { id: string; fileList: FileItem[]; } interface ResponseSchema { torrentUrl: string; magnetUrl: string; } // Start local Tracker async function startTracker(): Promise { return new Promise((resolve, reject) => { tracker.listen(TRACKER_PORT, "localhost", () => { console.log(`Tracker listening on port ${TRACKER_PORT}`); resolve(); }); tracker.on('error', (err: any) => { console.error(`Tracker error: ${err}`) reject(`Tracker error: ${err}`); }); tracker.on('warning', (warn: any) => console.warn(`Tracker warning: ${warn}`)); // Log tracked torrents tracker.on('update', (addr: any, params: any) => { console.log(`Tracker update: ${params.info_hash} from ${addr}`); }); tracker.on('stop', function () { reject(`Tracker stopped`); }) }); } // Tracker and WebTorrent client const webTorrent = new WebTorrent({}); // Generate mock file async function generateMockFile(filePath: string, size: number) { const dir = join(filePath, '..'); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }) }; fs.writeFileSync(filePath, 'w'); fs.truncateSync(filePath, size); } // Generate torrent file function generateTorrent(folderPath: string, torrentPath: string): Promise { return new Promise((resolve, reject) => { createTorrent( folderPath, { announceList: [[TRACKER_URL]], // Specify tracker URL private: false, createdBy: 'WebTorrent', comment: 'Generated by WebTorrent server', urlList: [API_BASE_URL] }, (err, torrent) => { if (err) { reject(new Error(`Failed to create torrent: ${err}`)); return; } writeFileSync(torrentPath, torrent); if (!existsSync(torrentPath)) { reject(new Error(`Torrent file ${torrentPath} was not created`)); return; } console.log(`Generated torrent with tracker: ${TRACKER_URL}`); resolve(); } ); }); } // Add torrent and seed async function seedTorrent(torrentPath: string): Promise { return new Promise((resolve) => { const torrent = webTorrent.seed(torrentPath, { announce: [TRACKER_URL], }, (t) => { resolve(t); }); torrent.on('error', (err) => console.error(`Torrent error: ${err}`)); torrent.on('wire', (wire) => console.log(`Connected to peer: ${wire.peerId}`)); torrent.on('done', () => console.log(`Torrent ${torrent.infoHash} fully seeded`)); }) } // Handle POST request to /api/torrents/mock app.post<{ Body: RequestSchema }>('/api/torrents/mock', async (req, _reply) => { const { id, fileList } = req.body; const idFolder = join('./workspace', id); if (!existsSync(idFolder)) { mkdirSync(idFolder, { recursive: true }) }; for (const fileItem of fileList) { const filePath = join(idFolder, fileItem.path); await generateMockFile(filePath, fileItem.size); } const torrentPath = join('./workspace', `${id}.torrent`); await generateTorrent(idFolder, torrentPath); const torrent = await seedTorrent(torrentPath); const magnetUrl = `magnet:?xt=urn:btih:${torrent.infoHash}&tr=${TRACKER_URL}`; return { torrentUrl: `${API_BASE_URL}${id}.torrent`, magnetUrl, } as ResponseSchema; }); // Main program entry async function main() { try { await startTracker(); await app.listen({ port: API_PORT, host: '0.0.0.0' }); console.log(`Fastify running on http://0.0.0.0:${API_PORT}`); } catch (err) { console.error('Startup error:', err); webTorrent.destroy(); tracker.close(); process.exit(1); } } main(); // Graceful shutdown process.on('SIGINT', () => { console.log('Shutting down...'); tracker.close(); webTorrent.destroy(); process.exit(0); });