170 lines
4.5 KiB
TypeScript
170 lines
4.5 KiB
TypeScript
import fastifyStatic from '@fastify/static';
|
|
import Fastify from 'fastify';
|
|
|
|
import fs from 'node:fs';
|
|
import fsp from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
// @ts-ignore
|
|
import TrackerServer from 'bittorrent-tracker/server';
|
|
import WebTorrent, { type Torrent } from 'webtorrent';
|
|
|
|
// Configuration
|
|
const API_PORT = 6080;
|
|
const TRACKER_PORT = 6081;
|
|
const SEEDING_PORT = 6082;
|
|
const STATIC_API_PATH = '/api/static';
|
|
const LOCAL_IP = '127.0.0.1';
|
|
const WORKSPACE_PATH = 'workspace';
|
|
const TRACKER_URL = `http://${LOCAL_IP}:${TRACKER_PORT}/announce`;
|
|
const API_BASE_URL = `http://${LOCAL_IP}:${API_PORT}${STATIC_API_PATH}/`;
|
|
|
|
// Initialize Fastify instance
|
|
const app = Fastify({ logger: true });
|
|
|
|
// Mount static file service, mapping ./workspace directory to /api/static route
|
|
app.register(fastifyStatic, {
|
|
root: path.join(process.cwd(), WORKSPACE_PATH),
|
|
prefix: STATIC_API_PATH,
|
|
});
|
|
|
|
const tracker = new TrackerServer({
|
|
udp: false, // enable udp server? [default=true]
|
|
http: true, // enable http server? [default=true]
|
|
ws: false, // 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;
|
|
hash: string;
|
|
}
|
|
|
|
// Start local Tracker
|
|
async function startTracker(): Promise<void> {
|
|
return new Promise<void>((resolve, reject) => {
|
|
tracker.listen(TRACKER_PORT, '0.0.0.0', () => {
|
|
console.log(`Tracker listening on port ${TRACKER_PORT}`);
|
|
resolve();
|
|
});
|
|
tracker.on('error', (err: any) => {
|
|
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 and WebTorrent client
|
|
const webTorrent = new WebTorrent({
|
|
// @ts-ignore
|
|
torrentPort: SEEDING_PORT,
|
|
});
|
|
|
|
// Generate mock file
|
|
async function generateMockFile(filePath: string, size: number) {
|
|
const dir = path.dirname(filePath);
|
|
if (!fs.existsSync(dir)) {
|
|
await fsp.mkdir(dir, { recursive: true });
|
|
}
|
|
|
|
await fsp.writeFile(filePath, Buffer.alloc(0));
|
|
await fsp.truncate(filePath, size);
|
|
}
|
|
|
|
// Add bittorrent and seed
|
|
async function seedTorrent(
|
|
torrentPath: string,
|
|
contentFolder: string
|
|
): Promise<Torrent> {
|
|
return new Promise((resolve) => {
|
|
const torrent = webTorrent.seed(
|
|
contentFolder,
|
|
{
|
|
announceList: [[TRACKER_URL]], // Specify tracker URL
|
|
private: false,
|
|
createdBy: 'Konobangu Testing Torrents',
|
|
urlList: [API_BASE_URL],
|
|
},
|
|
async (t) => {
|
|
await fsp.writeFile(torrentPath, t.torrentFile);
|
|
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 = path.join(WORKSPACE_PATH, id);
|
|
if (!fs.existsSync(idFolder)) {
|
|
await fsp.mkdir(idFolder, { recursive: true });
|
|
}
|
|
|
|
for (const fileItem of fileList) {
|
|
const filePath = path.join(idFolder, fileItem.path);
|
|
await generateMockFile(filePath, fileItem.size);
|
|
}
|
|
|
|
const torrentPath = path.join(WORKSPACE_PATH, `${id}.torrent`);
|
|
|
|
const torrent = await seedTorrent(torrentPath, idFolder);
|
|
const magnetUrl = `magnet:?xt=urn:btih:${torrent.infoHash}&tr=${TRACKER_URL}`;
|
|
|
|
return {
|
|
torrentUrl: `${API_BASE_URL}${id}.torrent`,
|
|
magnetUrl,
|
|
hash: torrent.infoHash,
|
|
} as ResponseSchema;
|
|
});
|
|
|
|
// Main program entry
|
|
async function main() {
|
|
try {
|
|
await startTracker();
|
|
const address = await app.listen({ port: API_PORT, host: '0.0.0.0' });
|
|
console.log('Listening on:', address);
|
|
} 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);
|
|
});
|