Browse Source

Rename torrent client service to just torrent. Use IPC package and include IPC API

dev
David Ludwig 4 years ago
parent
commit
4bc8698f34
21 changed files with 4610 additions and 0 deletions
  1. +5
    -0
      services/torrent/.env.example
  2. +3
    -0
      services/torrent/README.md
  3. +6
    -0
      services/torrent/nodemon.json
  4. +7
    -0
      services/torrent/ormconfig.json
  5. +44
    -0
      services/torrent/package.json
  6. +26
    -0
      services/torrent/patches/webtorrent+0.116.0.patch
  7. +29
    -0
      services/torrent/src/common.ts
  8. +73
    -0
      services/torrent/src/database/entities/Torrent.ts
  9. +5
    -0
      services/torrent/src/database/entities/index.ts
  10. +18
    -0
      services/torrent/src/database/index.ts
  11. +11
    -0
      services/torrent/src/index.ts
  12. +36
    -0
      services/torrent/src/services/Database.ts
  13. +114
    -0
      services/torrent/src/services/IpcInterface.ts
  14. +298
    -0
      services/torrent/src/services/TorrentClient.ts
  15. +9
    -0
      services/torrent/src/services/index.ts
  16. +31
    -0
      services/torrent/src/typings/magnet-uri/index.d.ts
  17. +350
    -0
      services/torrent/src/typings/node-ipc/index.d.ts
  18. +38
    -0
      services/torrent/src/typings/rimraf/index.t.ts
  19. +232
    -0
      services/torrent/src/typings/webtorrent-hybrid/index.d.ts
  20. +71
    -0
      services/torrent/tsconfig.json
  21. +3204
    -0
      services/torrent/yarn.lock

+ 5
- 0
services/torrent/.env.example View File

@ -0,0 +1,5 @@
DB_HOST = database
DB_PORT = 3306
DB_USER = root
DB_PASSWORD_FILE = /run/secrets/mysql_root_password
DB_DATABASE = autoplex_torrent

+ 3
- 0
services/torrent/README.md View File

@ -0,0 +1,3 @@
# Autoplex Torrent Client
The torrent client for Autoplex.

+ 6
- 0
services/torrent/nodemon.json View File

@ -0,0 +1,6 @@
{
"watch": ["src"],
"ext": "ts,json",
"ignore": ["src/**/*.spec.ts"],
"exec": "node --inspect=0.0.0.0:9229 -r ts-node/register src/index.ts"
}

+ 7
- 0
services/torrent/ormconfig.json View File

@ -0,0 +1,7 @@
{
"cli": {
"entitiesDir": "src/database/entities",
"migrationsDir": "src/database/migrations",
"subscribersDir": "src/database/subscribers"
}
}

+ 44
- 0
services/torrent/package.json View File

@ -0,0 +1,44 @@
{
"name": "@autoplex-service/torrent",
"version": "0.0.1",
"description": "A dedicated torrent client for Autoplex",
"main": "./dist/index.js",
"scripts": {
"clean": "rimraf ./dist",
"build": "tsc",
"start": "NODE_ENV=production; node .",
"start:dev": "nodemon",
"test": "echo \"Error: no test specified\" && exit 1",
"postinstall": "patch-package"
},
"keywords": [],
"author": "David Ludwig",
"license": "ISC",
"devDependencies": {
"@types/glob": "^7.1.3",
"@types/node": "^14.14.37",
"@types/parse-torrent": "^5.8.3",
"@types/rimraf": "^3.0.0",
"@types/websocket": "^1.0.2",
"nodemon": "^2.0.7",
"postinstall-postinstall": "^2.1.0",
"ts-node": "^9.1.1",
"typescript": "^4.2.3"
},
"dependencies": {
"@autoplex-api/torrent": "^0.0.0",
"@autoplex/ipc": "^0.0.0",
"@autoplex/microservice": "^0.0.0",
"@autoplex/utils": "^0.0.0",
"bitfield": "^4.0.0",
"mysql": "^2.18.1",
"node-ipc": "^9.1.4",
"parse-torrent": "^9.1.3",
"patch-package": "^6.4.7",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"typeorm": "^0.2.32",
"websocket": "^1.0.33",
"webtorrent-hybrid": "^4.0.3"
}
}

+ 26
- 0
services/torrent/patches/webtorrent+0.116.0.patch View File

@ -0,0 +1,26 @@
diff --git a/node_modules/webtorrent/lib/file.js b/node_modules/webtorrent/lib/file.js
index caa395d..80234de 100644
--- a/node_modules/webtorrent/lib/file.js
+++ b/node_modules/webtorrent/lib/file.js
@@ -21,6 +21,7 @@ class File extends EventEmitter {
this.length = file.length
this.offset = file.offset
+ this.isSelected = true
this.done = false
const start = file.offset
@@ -85,11 +86,13 @@ class File extends EventEmitter {
select (priority) {
if (this.length === 0) return
+ this.isSelected = true
this._torrent.select(this._startPiece, this._endPiece, priority)
}
deselect () {
if (this.length === 0) return
+ this.isSelected = false
this._torrent.deselect(this._startPiece, this._endPiece, false)
}

+ 29
- 0
services/torrent/src/common.ts View File

@ -0,0 +1,29 @@
export interface ISerializedFile {
path : string;
size : number;
downloaded: number;
progress : number;
selected : boolean;
}
export interface ISerializedTorrent {
name : string;
infoHash : string;
downloaded : number;
uploaded : number;
ratio : number;
size : number;
downloadSpeed: number;
uploadSpeed : number;
numPeers : number;
progress : number;
path : string;
state : TorrentState;
files : ISerializedFile[];
}
export enum TorrentState {
Ready = 0x1,
Paused = 0x2,
Done = 0x4
}

+ 73
- 0
services/torrent/src/database/entities/Torrent.ts View File

@ -0,0 +1,73 @@
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from "typeorm";
import WebTorrent from "webtorrent-hybrid";
@Entity()
export default class Torrent extends BaseEntity
{
static fromWebTorrent(torrent: WebTorrent.Torrent) {
let entity = new Torrent();
entity.infoHash = torrent.infoHash;
entity.torrentFile = torrent.torrentFile;
entity.setSelectedFiles(torrent.files);
entity.downloadPath = torrent.path;
return entity;
}
@PrimaryGeneratedColumn()
id!: number;
@Column()
infoHash!: string;
@Column("mediumblob")
torrentFile!: Buffer;
@Column({nullable: true})
selectOnly?: string;
@Column()
downloadPath!: string;
/**
* Get the list of selected files
*/
selectedFiles() {
let result: number[] = [];
for (let range of (this.selectOnly ?? "").split(',')) {
// @TODO Check this map function... it's weird
let [index, end] = range.split('-').map(num => parseInt(num));
do {
result.push(index);
} while(index++ < end);
}
return result;
}
/**
* Update the selected files from the torrent
*/
setSelectedFiles(files: WebTorrent.TorrentFile[]) {
let ranges: string[] = [];
let offset: number | null = null;
let range: number = 0;
files.forEach((file, i) => {
if (file.isSelected) {
if (offset === null) {
offset = i;
range = 0;
} else {
range++;
}
} else {
if (offset !== null) {
ranges.push(offset + (range > 0 ? `-${offset + range}` : ""));
offset = null;
}
}
});
if (offset !== null) {
ranges.push(offset + (range > 0 ? `-${offset + range}` : ""));
}
this.selectOnly = ranges.join(',');
}
}

+ 5
- 0
services/torrent/src/database/entities/index.ts View File

@ -0,0 +1,5 @@
import Torrent from "./Torrent";
export default [
Torrent
];

+ 18
- 0
services/torrent/src/database/index.ts View File

@ -0,0 +1,18 @@
import { createConnection } from "typeorm";
import entities from "./entities";
export default async function connectToDatabase(host: string, port: number, username: string,
password: string, database: string)
{
return createConnection({
type: "mysql",
host,
port,
username,
password,
database,
synchronize: true,
entities,
migrations: ["src/migrations/*.ts"]
});
}

+ 11
- 0
services/torrent/src/index.ts View File

@ -0,0 +1,11 @@
import { Microservice } from "@autoplex/microservice";
import * as services from "./services";
// Create the application
let app = new Microservice();
// Install the services
app.installServices(Object.values(services));
// Execute the app
app.exec().then(process.exit);

+ 36
- 0
services/torrent/src/services/Database.ts View File

@ -0,0 +1,36 @@
import { Connection } from "typeorm";
import connectToDatabase from "../database";
import { env, secret } from "@autoplex/utils";
import { InternalService } from "@autoplex/microservice";
export default class Database extends InternalService
{
/**
* The active database connection
*/
public connection!: Connection;
/**
* The service name
*/
public readonly NAME = "Database";
/**
* Boot the database service
*/
public async boot() {
let host = env("DB_HOST");
let port = parseInt(env("DB_PORT"));
let username = env("DB_USER");
let password = await secret(env("DB_PASSWORD_FILE"));
let database = env("DB_DATABASE");
this.connection = await connectToDatabase(host, port, username, password, database);
}
/**
* Shutdown the database service
*/
public async shutdown() {
await this.connection.close();
}
}

+ 114
- 0
services/torrent/src/services/IpcInterface.ts View File

@ -0,0 +1,114 @@
import assert from "assert";
import WebTorrent from "webtorrent-hybrid";
import TorrentClient from "./TorrentClient";
import { IpcServerService } from "@autoplex/ipc";
import { SOCKET_PATH } from "@autoplex-api/torrent";
type IAddTorrent = string | {
type: "Buffer",
data: number[]
}
export default class IpcInterface extends IpcServerService
{
/**
* The torrent client instance
*/
protected torrentClient!: TorrentClient;
/**
* The service name
*/
public readonly NAME = "IPC";
/**
* The path to the socket file
*/
protected readonly SOCKET_PATH = SOCKET_PATH;
/**
* Boot the IPC interface
*/
public async boot() {
this.torrentClient = this.app.service<TorrentClient>("Torrent Client");
await super.boot();
}
/**
* Install the the event handlers
*/
protected installMessageHandlers() {
this.addMessageHandler("add", this.addTorrent);
this.addMessageHandler("remove", this.removeTorrent);
this.addMessageHandler("list", this.listTorrents);
this.addMessageHandler("details", this.torrentDetails);
this.torrentClient.on("torrent_finished", this.torrentFinished.bind(this));
}
// Interface Methods ---------------------------------------------------------------------------
/**
* Add a torrent to the client
*/
// protected async addTorrent(torrentInfo: IAddTorrent, downloadPath?: string) {
protected async addTorrent(payload: { torrent: IAddTorrent, downloadPath?: string }) {
let torrent: WebTorrent.Torrent;
if (typeof payload.torrent == "string") {
torrent = await this.torrentClient.add(payload.torrent, { downloadPath: payload.downloadPath });
} else {
torrent = await this.torrentClient.add(Buffer.from(payload.torrent.data), { downloadPath: payload.downloadPath });
}
return torrent.infoHash;
}
/**
* Remove a torrent from the
* @param message Add
*/
protected async removeTorrent(torrentId: string) {
try {
await this.torrentClient.remove(torrentId);
} catch(e) {}
}
protected async listTorrents() {
return this.torrentClient.torrents.map(torrent => Object({
name: torrent.name,
infoHash: torrent.infoHash,
progress: torrent.progress,
state: this.torrentClient.torrentState(torrent)
}));
}
protected async torrentDetails(torrentIds: string[]) {
let torrents: WebTorrent.Torrent[];
if (torrentIds.length == 0) {
torrents = this.torrentClient.torrents;
} else {
torrents = torrentIds.map(torrentId => {
let torrent = this.torrentClient.get(torrentId);
assert(torrent != null, `Unknown torrent ID provided: ${torrentId}`);
return torrent;
});
}
return torrents.map(torrent => this.torrentClient.serializeTorrent(torrent));
}
// Subscription Interface Methods --------------------------------------------------------------
/**
* Broadcast a message to the connected clients
*/
// protected broadcast(method: string, ...message: any[]) {
// for (let socket of <Socket[]>(<any>ipc.server).sockets) {
// this.server.emit(socket, method, message);
// }
// }
/**
* Notify connected clients that a torrent has finished
*/
public torrentFinished(torrent: WebTorrent.Torrent) {
this.broadcast("torrent_finished", torrent.infoHash);
}
}

+ 298
- 0
services/torrent/src/services/TorrentClient.ts View File

@ -0,0 +1,298 @@
import { InternalService } from "@autoplex/microservice";
import assert from "assert";
import MagnetUri from "magnet-uri";
import WebTorrent from "webtorrent-hybrid";
import parseTorrent from "parse-torrent";
import { extname, join, sep } from "path";
import Torrent from "../database/entities/Torrent";
import rimraf from "rimraf";
import { ISerializedTorrent, TorrentState } from "../common";
import { Database } from ".";
interface IAddOptions {
downloadPath?: string;
extensions?: string | string[];
}
/**
* Available events in the torrent client
*/
interface TorrentClientEvents {
"torrent_finished": (torrent: WebTorrent.Torrent) => void;
}
/**
* Declare the event types in the torrent client
*/
export declare interface TorrentClient {
emit<U extends keyof TorrentClientEvents>(
event: U, ...args: Parameters<TorrentClientEvents[U]>
): boolean;
on<U extends keyof TorrentClientEvents>(
event: U, listener: TorrentClientEvents[U]
): this;
}
export class TorrentClient extends InternalService
{
/**
* The current WebTorrent instance (available after boot)
*/
private __webtorrent!: WebTorrent.Instance;
// ---------------------------------------------------------------------------------------------
/**
* The service name
*/
public readonly NAME = "Torrent Client";
/**
* Boot the service
*/
public async boot() {
this.__webtorrent = new WebTorrent();
this.__webtorrent.on("error", error => this.onError(error));
this.__webtorrent.on("torrent", torrent => {
torrent.on("done", () => this.onTorrentFinish(torrent));
// torrent.on("download", (...args) => this.onTorrentDownload(torrent, ...args));
// torrent.on("upload", (...args) => this.onTorrentUpload(torrent, ...args));
torrent.on("error", (...args) => this.onTorrentError(torrent, ...args));
torrent.on("noPeers", (...args) => this.onTorrentNoPeers(torrent, ...args));
torrent.on("wire", (...args) => this.onTorrentWire(torrent, ...args));
});
}
/**
* @TODO really this is kinda bad putting this here...
* Start the torrent client
*/
public start() {
this.loadTorrents();
}
// Event Handling ------------------------------------------------------------------------------
/**
* Invoked when a WebTorrent client error occurs
*/
protected onError(error: string | Error) {
console.error("A Webtorrent client error occurred:", error);
}
/**
* Invoked when a torrent error occurs
*/
protected onTorrentError(torrent: WebTorrent.Torrent, error: string | Error) {
console.error("Torrent error occurred:", torrent.name, error);
Torrent.delete({ infoHash: torrent.infoHash });
}
/**
* Invoked when a torrent has finished
*/
protected onTorrentFinish(torrent: WebTorrent.Torrent) {
console.log("Torrent finished:", torrent.name);
this.emit("torrent_finished", torrent);
}
/**
* @NOTE: Ignoring these two events for performance purposes
*/
// protected onTorrentDownload(torrent: WebTorrent.Torrent, bytes: number) {}
// protected onTorrentUpload(torrent: WebTorrent.Torrent, bytes: number) {}
protected onTorrentNoPeers(torrent: WebTorrent.Torrent, announceTypes: "tracker" | "dht") {
}
protected onTorrentWire(torrent: WebTorrent.Torrent, wire: any, address: string | undefined) {
}
// Torrent Storage -----------------------------------------------------------------------------
/**
* Load the torrents from the database
*/
protected async loadTorrents() {
let torrents = await Torrent.find();
for (let torrent of torrents) {
try {
let torrentInfo = parseTorrent(torrent.torrentFile);
this.addTorrent(torrentInfo, torrent.downloadPath, torrent.selectedFiles());
} catch(e) {
torrent.remove();
continue;
}
}
}
/**
* Delete a torrent from storage
*/
protected async deleteTorrent(torrent: WebTorrent.Torrent) {
return await Torrent.delete({ infoHash: torrent.infoHash });
}
/**
* Store a torrent in the database
*/
protected async storeTorrent(torrent: WebTorrent.Torrent) {
return await Torrent.fromWebTorrent(torrent).save();
}
/**
* Delete the download files from a torrent
*/
protected async removeTorrentFiles(torrent: WebTorrent.Torrent) {
return new Promise<void>((resolve, reject) => {
let toRemove = new Set<string>();
torrent.files.forEach(file => {
toRemove.add(file.path.split(sep)[0]);
});
toRemove.forEach(path => {
rimraf(join(torrent.path, path), (err) => {
if (err) {
return reject(err);
}
resolve();
});
});
});
}
// Torrent Handling ----------------------------------------------------------------------------
/**
* Add a torrent to the client
*/
protected addTorrent(torrent: MagnetUri.Instance, downloadPath: string, selectOnly?: number[]) {
// Select only: Select no files by default due to broken selection mechanism in Webtorrent
torrent.so = selectOnly ?? [];
console.log("Torrent added:", torrent.infoHash);
return this.__webtorrent.add(torrent, { path: downloadPath });
}
/**
* Remove a torrent from the client
*/
protected removeTorrent(torrent: WebTorrent.Torrent) {
this.deleteTorrent(torrent);
// console.log("Torrent removed:", torrent.infoHash);
this.__webtorrent.remove(torrent);
}
/**
* Select the initial list of files to download on a newly added torrent
*/
protected filterSelectFiles(torrent: WebTorrent.Torrent, extensions?: string | string[]) {
if (extensions === undefined) {
torrent.files.forEach(file => file.select());
return;
}
let exts = new Set(typeof extensions === "string" ? extensions.split(',') : extensions);
torrent.files.forEach(file => {
if (exts.has(extname(file.path).slice(1))) {
file.select();
} else {
file.deselect();
}
});
}
// Torrent Client Interface --------------------------------------------------------------------
/**
* Add a torrent to the client
*/
public async add(info: string | Buffer, options: IAddOptions = {}) {
// Parse the torrent
let torrentInfo = parseTorrent(info);
assert(typeof torrentInfo.infoHash === "string" && torrentInfo.infoHash.length > 0, "Invalid magnet link provided");
// If the torrent already exists, skip it
assert(this.__webtorrent.get(torrentInfo.infoHash) === null, "Torrent has already been added");
// Add the torrent to the client
let torrent = this.addTorrent(torrentInfo, options.downloadPath ?? "/mnt/storage/Downloads");
// When the metadata has beened fetched, select the files to download and store the torrent
torrent.once("metadata", () => {
this.filterSelectFiles(torrent, options.extensions);
this.storeTorrent(torrent);
console.log(torrent.path);
});
return torrent;
}
/**
* Remove a torrent from the client
*/
public async remove(info: string | Buffer, withData: boolean = false) {
// Parse the torrent
let torrentInfo = parseTorrent(info);
assert(typeof torrentInfo.infoHash === "string");
// Get the torrent and ensure it exists it exists
let torrent = this.__webtorrent.get(torrentInfo.infoHash);
assert(torrent !== null, "Torrent has not been added");
// Remove the torrent
this.removeTorrent(torrent);
// Delete data if necessary
if (withData) {
this.removeTorrentFiles(torrent);
}
}
public has(infoHash: string) {
return this.__webtorrent.get(infoHash) != null;
}
public get(infoHash: string) {
return this.__webtorrent.get(infoHash);
}
public torrentState(torrent: WebTorrent.Torrent) {
let state = 0;
state |= <number>(torrent.ready && TorrentState.Ready);
state |= <number>(torrent.paused && TorrentState.Paused);
state |= <number>(torrent.done && TorrentState.Done);
return <TorrentState>state;
}
public serializeTorrent(torrent: WebTorrent.Torrent) {
return <ISerializedTorrent>{
name: torrent.name,
infoHash: torrent.infoHash,
downloaded: torrent.downloaded,
uploaded: torrent.uploaded,
ratio: torrent.ratio,
size: torrent.length,
downloadSpeed: torrent.downloadSpeed,
uploadSpeed: torrent.uploadSpeed,
numPeers: torrent.numPeers,
progress: torrent.progress,
path: torrent.path,
state: this.torrentState(torrent),
files: torrent.files.map(file => Object({
path: file.path,
size: file.length,
downloaded: file.downloaded,
progress: file.progress,
selected: file.isSelected
}))
};
}
get torrents() {
return this.__webtorrent.torrents;
}
}
export default TorrentClient;

+ 9
- 0
services/torrent/src/services/index.ts View File

@ -0,0 +1,9 @@
import Database from "./Database";
import IpcInterface from "./IpcInterface";
import TorrentClient from "./TorrentClient";
export {
Database,
IpcInterface,
TorrentClient
}

+ 31
- 0
services/torrent/src/typings/magnet-uri/index.d.ts View File

@ -0,0 +1,31 @@
declare const MagnetUri: MagnetUri.MagnetUri;
declare module "magnet-uri" {
declare namespace MagnetUri {
interface MagnetUri {
(uri: string): Instance;
decode(uri: string): Instance;
encode(parsed: Instance): string;
}
interface Instance extends Object {
dn?: string | string[];
tr?: string | string[];
xs?: string | string[];
as?: string | string[];
ws?: string | string[];
kt?: string[];
ix?: number | number[];
xt?: string | string[];
so?: number[];
infoHash?: string;
infoHashBuffer?: Buffer;
name?: string | string[];
keywords?: string | string[];
announce?: string[];
urlList?: string[];
}
}
export = MagnetUri;
}

+ 350
- 0
services/torrent/src/typings/node-ipc/index.d.ts View File

@ -0,0 +1,350 @@
/// <reference types="node"/>
declare module "node-ipc" {
import { Socket } from "net";
declare const NodeIPC: NodeIPC.NodeIPC;
declare namespace NodeIPC {
interface NodeIPC extends IPC
{}
interface IPC {
/**
* Set these variables in the ipc.config scope to overwrite or set default values
*/
config: Config;
/**
* https://www.npmjs.com/package/node-ipc#log
*/
log(...args: any[]): void;
/**
* https://www.npmjs.com/package/node-ipc#connectto
* Used for connecting as a client to local Unix Sockets and Windows Sockets.
* This is the fastest way for processes on the same machine to communicate
* because it bypasses the network card which TCP and UDP must both use.
* @param id is the string id of the socket being connected to.
* The socket with this id is added to the ipc.of object when created.
* @param path is the path of the Unix Domain Socket File, if the System is Windows,
* this will automatically be converted to an appropriate pipe with the same information as the Unix Domain Socket File.
* If not set this will default to ipc.config.socketRoot+ipc.config.appspace+id
* @param callback this is the function to execute when the socket has been created
*/
connectTo(id: string, path?: string, callback?: () => void): void;
/**
* https://www.npmjs.com/package/node-ipc#connectto
* Used for connecting as a client to local Unix Sockets and Windows Sockets.
* This is the fastest way for processes on the same machine to communicate
* because it bypasses the network card which TCP and UDP must both use.
* @param id is the string id of the socket being connected to.
* The socket with this id is added to the ipc.of object when created.
* @param callback this is the function to execute when the socket has been created
*/
connectTo(id: string, callback?: () => void): void;
/**
* https://www.npmjs.com/package/node-ipc#connecttonet
* Used to connect as a client to a TCP or TLS socket via the network card.
* This can be local or remote, if local, it is recommended that you use the Unix
* and Windows Socket Implementaion of connectTo instead as it is much faster since it avoids the network card altogether.
* For TLS and SSL Sockets see the node-ipc TLS and SSL docs.
* They have a few additional requirements, and things to know about and so have their own doc.
* @param id is the string id of the socket being connected to. For TCP & TLS sockets,
* this id is added to the ipc.of object when the socket is created with a reference to the socket
* @param host is the host on which the TCP or TLS socket resides.
* This will default to ipc.config.networkHost if not specified
* @param port the port on which the TCP or TLS socket resides
* @param callback this is the function to execute when the socket has been created
*/
connectToNet(id: string, host?: string, port?: number, callback?: () => void): void;
/**
* https://www.npmjs.com/package/node-ipc#connecttonet
* Used to connect as a client to a TCP or TLS socket via the network card.
* This can be local or remote, if local, it is recommended that you use the Unix
* and Windows Socket Implementaion of connectTo instead as it is much faster since it avoids the network card altogether.
* For TLS and SSL Sockets see the node-ipc TLS and SSL docs.
* They have a few additional requirements, and things to know about and so have their own doc.
* @param id is the string id of the socket being connected to. For TCP & TLS sockets,
* this id is added to the ipc.of object when the socket is created with a reference to the socket
* @param callback this is the function to execute when the socket has been created
*/
connectToNet(id: string, callback?: () => void): void;
/**
* https://www.npmjs.com/package/node-ipc#connecttonet
* Used to connect as a client to a TCP or TLS socket via the network card.
* This can be local or remote, if local, it is recommended that you use the Unix
* and Windows Socket Implementaion of connectTo instead as it is much faster since it avoids the network card altogether.
* For TLS and SSL Sockets see the node-ipc TLS and SSL docs.
* They have a few additional requirements, and things to know about and so have their own doc.
* @param id is the string id of the socket being connected to.
* For TCP & TLS sockets, this id is added to the ipc.of object when the socket is created with a reference to the socket
* @param host is the host on which the TCP or TLS socket resides. This will default to ipc.config.networkHost if not specified
* @param port the port on which the TCP or TLS socket resides
* @param callback this is the function to execute when the socket has been created
*/
connectToNet(id: string, hostOrPort: number | string, callback?: () => void): void;
/**
* https://www.npmjs.com/package/node-ipc#disconnect
* Used to disconnect a client from a Unix, Windows, TCP or TLS socket.
* The socket and its refrence will be removed from memory and the ipc.of scope.
* This can be local or remote. UDP clients do not maintain connections and so there are no Clients and this method has no value to them
* @param id is the string id of the socket from which to disconnect
*/
disconnect(id: string): void;
/**
* https://www.npmjs.com/package/node-ipc#serve
* Used to create local Unix Socket Server or Windows Socket Server to which Clients can bind.
* The server can emit events to specific Client Sockets, or broadcast events to all known Client Sockets
* @param path This is the path of the Unix Domain Socket File, if the System is Windows,
* this will automatically be converted to an appropriate pipe with the same information as the Unix Domain Socket File.
* If not set this will default to ipc.config.socketRoot+ipc.config.appspace+id
* @param callback This is a function to be called after the Server has started.
* This can also be done by binding an event to the start event like ipc.server.on('start',function(){});
*/
serve(path: string, callback?: () => void): void;
/**
* https://www.npmjs.com/package/node-ipc#serve
* Used to create local Unix Socket Server or Windows Socket Server to which Clients can bind.
* The server can emit events to specific Client Sockets, or broadcast events to all known Client Sockets
* @param callback This is a function to be called after the Server has started.
* This can also be done by binding an event to the start event like ipc.server.on('start',function(){});
*/
serve(callback?: () => void): void;
/**
* https://www.npmjs.com/package/node-ipc#serve
* Used to create local Unix Socket Server or Windows Socket Server to which Clients can bind.
* The server can emit events to specific Client Sockets, or broadcast events to all known Client Sockets
*/
serve(callback: null): void;
/**
* https://www.npmjs.com/package/node-ipc#servenet
* @param host If not specified this defaults to the first address in os.networkInterfaces().
* For TCP, TLS & UDP servers this is most likely going to be 127.0.0.1 or ::1
* @param port The port on which the TCP, UDP, or TLS Socket server will be bound, this defaults to 8000 if not specified
* @param UDPType If set this will create the server as a UDP socket. 'udp4' or 'udp6' are valid values.
* This defaults to not being set. When using udp6 make sure to specify a valid IPv6 host, like ::1
* @param callback Function to be called when the server is created
*/
serveNet(host?: string, port?: number, UDPType?: "udp4" | "udp6", callback?: () => void): void;
/**
* https://www.npmjs.com/package/node-ipc#servenet
* @param UDPType If set this will create the server as a UDP socket. 'udp4' or 'udp6' are valid values.
* This defaults to not being set. When using udp6 make sure to specify a valid IPv6 host, like ::1
* @param callback Function to be called when the server is created
*/
serveNet(UDPType: "udp4" | "udp6", callback?: () => void): void;
/**
* https://www.npmjs.com/package/node-ipc#servenet
* @param callback Function to be called when the server is created
* @param port The port on which the TCP, UDP, or TLS Socket server will be bound, this defaults to 8000 if not specified
*/
serveNet(callbackOrPort: EmptyCallback | number): void;
/**
* https://www.npmjs.com/package/node-ipc#servenet
* @param host If not specified this defaults to the first address in os.networkInterfaces().
* For TCP, TLS & UDP servers this is most likely going to be 127.0.0.1 or ::1
* @param port The port on which the TCP, UDP, or TLS Socket server will be bound, this defaults to 8000 if not specified
* @param callback Function to be called when the server is created
*/
serveNet(host: string, port: number, callback?: () => void): void;
/**
* This is where socket connection refrences will be stored when connecting to them as a client via the ipc.connectTo
* or iupc.connectToNet. They will be stored based on the ID used to create them, eg : ipc.of.mySocket
*/
of: any;
/**
* This is a refrence to the server created by ipc.serve or ipc.serveNet
*/
server: Server;
}
type EmptyCallback = () => void;
interface Client {
/**
* triggered when a JSON message is received. The event name will be the type string from your message
* and the param will be the data object from your message eg : { type:'myEvent',data:{a:1}}
*/
on(event: string, callback: (message: any, socket: Socket) => void): Client;
/**
* triggered when an error has occured
*/
on(event: "error", callback: (err: any) => void): Client;
/**
* connect - triggered when socket connected
* disconnect - triggered by client when socket has disconnected from server
* destroy - triggered when socket has been totally destroyed, no further auto retries will happen and all references are gone
*/
on(event: "connect" | "disconnect" | "destroy", callback: () => void): Client;
/**
* triggered by server when a client socket has disconnected
*/
on(event: "socket.disconnected", callback: (socket: Socket, destroyedSocketID: string) => void): Client;
/**
* triggered when ipc.config.rawBuffer is true and a message is received
*/
on(event: "data", callback: (buffer: Buffer) => void): Client;
emit(event: string, value?: any): Client;
/**
* Unbind subscribed events
*/
off(event: string, handler: any): Client;
}
interface Server extends Client {
/**
* start serving need top call serve or serveNet first to set up the server
*/
start(): void;
/**
* close the server and stop serving
*/
stop(): void;
emit(value: any): Client;
emit(event: string, value: any): Client;
emit(socket: Socket | SocketConfig, event: string, value?: any): Server;
emit(socketConfig: Socket | SocketConfig, value?: any): Server;
}
interface SocketConfig {
address?: string;
port?: number;
}
interface Config {
/**
* Default: 'app.'
* Used for Unix Socket (Unix Domain Socket) namespacing.
* If not set specifically, the Unix Domain Socket will combine the socketRoot, appspace,
* and id to form the Unix Socket Path for creation or binding.
* This is available incase you have many apps running on your system, you may have several sockets with the same id,
* but if you change the appspace, you will still have app specic unique sockets
*/
appspace: string;
/**
* Default: '/tmp/'
* The directory in which to create or bind to a Unix Socket
*/
socketRoot: string;
/**
* Default: os.hostname()
* The id of this socket or service
*/
id: string;
/**
* Default: 'localhost'
* The local or remote host on which TCP, TLS or UDP Sockets should connect
* Should resolve to 127.0.0.1 or ::1 see the table below related to this
*/
networkHost: string;
/**
* Default: 8000
* The default port on which TCP, TLS, or UDP sockets should connect
*/
networkPort: number;
/**
* Default: 'utf8'
* the default encoding for data sent on sockets. Mostly used if rawBuffer is set to true.
* Valid values are : ascii utf8 utf16le ucs2 base64 hex
*/
encoding: "ascii" | "utf8" | "utf16le" | "ucs2" | "base64" | "hex";
/**
* Default: false
* If true, data will be sent and received as a raw node Buffer NOT an Object as JSON.
* This is great for Binary or hex IPC, and communicating with other processes in languages like C and C++
*/
rawBuffer: boolean;
/**
* Default: false
* Synchronous requests. Clients will not send new requests until the server answers
*/
sync: boolean;
/**
* Default: false
* Turn on/off logging default is false which means logging is on
*/
silent: boolean;
/**
* Default: true
* Turn on/off util.inspect colors for ipc.log
*/
logInColor: boolean;
/**
* Default: 5
* Set the depth for util.inspect during ipc.log
*/
logDepth: number;
/**
* Default: console.log
* The function which receives the output from ipc.log; should take a single string argument
*/
logger(msg: string): void;
/**
* Default: 100
* This is the max number of connections allowed to a socket. It is currently only being set on Unix Sockets.
* Other Socket types are using the system defaults
*/
maxConnections: number;
/**
* Default: 500
* This is the time in milliseconds a client will wait before trying to reconnect to a server if the connection is lost.
* This does not effect UDP sockets since they do not have a client server relationship like Unix Sockets and TCP Sockets
*/
retry: number;
/* */
/**
* Default: false
* if set, it represents the maximum number of retries after each disconnect before giving up
* and completely killing a specific connection
*/
maxRetries: boolean | number;
/**
* Default: false
* Defaults to false meaning clients will continue to retry to connect to servers indefinitely at the retry interval.
* If set to any number the client will stop retrying when that number is exceeded after each disconnect.
* If set to true in real time it will immediately stop trying to connect regardless of maxRetries.
* If set to 0, the client will NOT try to reconnect
*/
stopRetrying: boolean;
/**
* Default: true
* Defaults to true meaning that the module will take care of deleting the IPC socket prior to startup.
* If you use node-ipc in a clustered environment where there will be multiple listeners on the same socket,
* you must set this to false and then take care of deleting the socket in your own code.
*/
unlink: boolean;
/**
* Primarily used when specifying which interface a client should connect through.
* see the socket.connect documentation in the node.js api https://nodejs.org/api/net.html#net_socket_connect_options_connectlistener
*/
interfaces: {
/**
* Default: false
*/
localAddress?: boolean;
/**
* Default: false
*/
localPort?: boolean;
/**
* Default: false
*/
family?: boolean;
/**
* Default: false
*/
hints?: boolean;
/**
* Default: false
*/
lookup?: boolean;
};
tls: {
rejectUnauthorized?: boolean;
public?: string;
private?: string;
};
}
}
export = NodeIPC
// declare const RootIPC: NodeIPC.IPC & { IPC: new () => NodeIPC.IPC };
// export = RootIPC;
}

+ 38
- 0
services/torrent/src/typings/rimraf/index.t.ts View File

@ -0,0 +1,38 @@
import * as glob from "glob";
import * as fs from "fs";
declare function rimraf(path: string, options: rimraf.Options, callback: (error?: Error) => void): void;
declare function rimraf(path: string, callback: (error?: Error) => void): void;
declare namespace rimraf {
/**
* It can remove stuff synchronously, too.
* But that's not so good. Use the async API.
* It's better.
*/
function sync(path: string, options?: Options): void;
/**
* see {@link https://github.com/isaacs/rimraf/blob/79b933fb362b2c51bedfa448be848e1d7ed32d7e/README.md#options}
*/
interface Options {
maxBusyTries?: number;
emfileWait?: number;
/** @default false */
disableGlob?: boolean;
glob?: glob.IOptions | false;
unlink?: typeof fs.unlink;
chmod?: typeof fs.chmod;
stat?: typeof fs.stat;
lstat?: typeof fs.lstat;
rmdir?: typeof fs.rmdir;
readdir?: typeof fs.readdir;
unlinkSync?: typeof fs.unlinkSync;
chmodSync?: typeof fs.chmodSync;
statSync?: typeof fs.statSync;
lstatSync?: typeof fs.lstatSync;
rmdirSync?: typeof fs.rmdirSync;
readdirSync?: typeof fs.readdirSync;
}
}
export = rimraf;

+ 232
- 0
services/torrent/src/typings/webtorrent-hybrid/index.d.ts View File

@ -0,0 +1,232 @@
// declare module "webtorrent-hybrid" {
// declare class WebTorrent {
// }
// declare const WebTorrent: WebTorrent.WebTorrent;
// declare namespace WebTorrent {
// interface WebTorrent {
// new (config?: Options): Instance;
// (config?: Options): Instance;
// WEBRTC_SUPPORT: boolean;
// }
// }
// export = WebTorrent;
// }
// Type definitions for WebTorrent 0.109
// Project: https://github.com/feross/webtorrent, https://webtorrent.io
// Definitions by: Bazyli Brzóska <https://github.com/niieani>
// Tomasz Łaziuk <https://github.com/tlaziuk>
// Gabriel Juchault <https://github.com/gjuchault>
// Adam Crowder <https://github.com/cheeseandcereal>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/// <reference types="node" />
declare module "webtorrent-hybrid" {
import { Instance as MagnetUri } from 'magnet-uri';
import { Instance as ParseTorrent } from 'parse-torrent';
import { Instance as SimplePeer } from 'simple-peer';
import { RequestOptions, Server } from 'http';
import { Wire } from 'bittorrent-protocol';
declare const WebTorrent: WebTorrent.WebTorrent;
declare namespace WebTorrent {
interface WebTorrent {
new (config?: Options): Instance;
(config?: Options): Instance;
WEBRTC_SUPPORT: boolean;
}
interface Options {
maxConns?: number;
nodeId?: string | Buffer;
peerId?: string | Buffer;
tracker?: boolean | {};
dht?: boolean | {};
webSeeds?: boolean;
utp?: boolean;
}
interface TorrentOptions {
announce?: any[];
getAnnounceOpts?(): void;
maxWebConns?: number;
path?: string;
store?(chunkLength: number, storeOpts: { length: number, files: File[], torrent: Torrent, }): any;
private?: boolean;
}
interface TorrentDestroyOptions {
destroyStore?: boolean;
}
interface Instance extends NodeJS.EventEmitter {
on(event: 'torrent', callback: (torrent: Torrent) => void): this;
on(event: 'error', callback: (err: Error | string) => void): this;
add(torrent: string | Buffer | File | ParseTorrent | MagnetUri, opts?: TorrentOptions, cb?: (torrent: Torrent) => any): Torrent;
add(torrent: string | Buffer | File | ParseTorrent | MagnetUri, cb?: (torrent: Torrent) => any): Torrent;
seed(input: string | string[] | File | File[] | FileList | Buffer | Buffer[] | NodeJS.ReadableStream | NodeJS.ReadableStream[], opts?: TorrentOptions, cb?: (torrent: Torrent) => any): Torrent;
seed(input: string | string[] | File | File[] | FileList | Buffer | Buffer[] | NodeJS.ReadableStream | NodeJS.ReadableStream[], cb?: (torrent: Torrent) => any): Torrent;
remove(torrentId: Torrent | string | Buffer, opts?: TorrentDestroyOptions, callback?: (err: Error | string) => void): void;
destroy(callback?: (err: Error | string) => void): void;
readonly torrents: Torrent[];
get(torrentId: Torrent | string | Buffer): Torrent | null;
readonly downloadSpeed: number;
readonly uploadSpeed: number;
readonly progress: number;
readonly ratio: number;
}
interface Torrent extends NodeJS.EventEmitter {
readonly infoHash: string;
readonly magnetURI: string;
readonly torrentFile: Buffer;
readonly torrentFileBlobURL: string;
readonly files: TorrentFile[];
readonly announce: string[];
readonly pieces: Array<TorrentPiece | null>;
readonly timeRemaining: number;
readonly received: number;
readonly downloaded: number;
readonly uploaded: number;
readonly downloadSpeed: number;
readonly uploadSpeed: number;
readonly progress: number;
readonly ratio: number;
readonly length: number;
readonly pieceLength: number;
readonly lastPieceLength: number;
readonly numPeers: number;
readonly path: string;
readonly ready: boolean;
readonly paused: boolean;
readonly done: boolean;
readonly name: string;
readonly created: Date;
readonly createdBy: string;
readonly comment: string;
readonly maxWebConns: number;
destroy(opts?: TorrentDestroyOptions, cb?: (err: Error | string) => void): void;
addPeer(peer: string | SimplePeer): boolean;
addWebSeed(url: string): void;
removePeer(peer: string | SimplePeer): void;
select(start: number, end: number, priority?: number, notify?: () => void): void;
deselect(start: number, end: number, priority: number): void;
createServer(opts?: RequestOptions): Server;
pause(): void;
resume(): void;
rescanFiles(callback?: (err: Error | string | null) => void): void;
on(event: 'infoHash' | 'metadata' | 'ready' | 'done', callback: () => void): this;
on(event: 'warning' | 'error', callback: (err: Error | string) => void): this;
on(event: 'download' | 'upload', callback: (bytes: number) => void): this;
on(event: 'wire', callback: (wire: Wire, addr?: string) => void): this;
on(event: 'noPeers', callback: (announceType: 'tracker' | 'dht') => void): this;
}
interface TorrentFile extends NodeJS.EventEmitter {
// Custom property to check if a file is selected
readonly isSelected: boolean;
readonly name: string;
readonly path: string;
readonly length: number;
readonly downloaded: number;
readonly progress: number;
select(): void;
deselect(): void;
createReadStream(opts?: { start: number, end: number, }): NodeJS.ReadableStream;
getBuffer(callback: (err: string | Error | undefined, buffer?: Buffer) => void): void;
appendTo(
rootElement: HTMLElement | string,
opts?: { autoplay?: boolean, controls?: boolean, maxBlobLength?: number },
callback?: (err: Error | undefined, element: HTMLMediaElement) => void): void;
appendTo(rootElement: HTMLElement | string, callback?: (err: Error | undefined, element: HTMLMediaElement) => void): void;
renderTo(
rootElement: HTMLMediaElement | string,
opts?: { autoplay?: boolean, controls?: boolean, maxBlobLength?: number },
callback?: (err: Error | undefined, element: HTMLMediaElement) => void): void;
renderTo(rootElement: HTMLMediaElement | string, callback?: (err: Error | undefined, element: HTMLMediaElement) => void): void;
getBlob(callback: (err: string | Error | undefined, blob?: Blob) => void): void;
getBlobURL(callback: (err: string | Error | undefined, blobURL?: string) => void): void;
}
interface TorrentPiece {
readonly length: number;
readonly missing: number;
}
}
export = WebTorrent;
}

+ 71
- 0
services/torrent/tsconfig.json View File

@ -0,0 +1,71 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
"typeRoots": ["./src/typings"], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
"sourceRoot": "./src", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

+ 3204
- 0
services/torrent/yarn.lock
File diff suppressed because it is too large
View File


Loading…
Cancel
Save