2025-10-08 12:28:02 +02:00
|
|
|
"use strict";
|
|
|
|
|
|
2025-10-09 13:08:24 +02:00
|
|
|
class trserver extends EventTarget {
|
2025-10-08 12:28:02 +02:00
|
|
|
#rpcurl;
|
|
|
|
|
#authHeader;
|
|
|
|
|
#sessionHeader;
|
|
|
|
|
#requests = [];
|
|
|
|
|
#torrents = {};
|
|
|
|
|
#isOnline = false;
|
|
|
|
|
|
|
|
|
|
#logger = function(msg) {};
|
2025-10-09 13:08:24 +02:00
|
|
|
#torrentCallback = function(hash, torrentInfo) {
|
|
|
|
|
let e = new CustomEvent('torrent-update', {
|
|
|
|
|
'detail': {
|
|
|
|
|
'torrentHash': hash,
|
|
|
|
|
'torrentInfo': torrentInfo
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.dispatchEvent(e);
|
|
|
|
|
};
|
2025-10-08 12:28:02 +02:00
|
|
|
|
2025-10-09 13:08:24 +02:00
|
|
|
constructor (initdata, logger = null, tcb = null) {
|
|
|
|
|
super();
|
2025-10-08 12:28:02 +02:00
|
|
|
this.#rpcurl = initdata.rpcurl;
|
|
|
|
|
if (logger != null) {
|
|
|
|
|
this.#logger = logger;
|
|
|
|
|
}
|
|
|
|
|
if (tcb != null) {
|
|
|
|
|
this.#torrentCallback = tcb;
|
|
|
|
|
}
|
|
|
|
|
this.#authHeader = initdata.auth;
|
|
|
|
|
this.#sessionHeader = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#rpccall_prepare(method, args) {
|
|
|
|
|
let request = {
|
|
|
|
|
'payload': {
|
|
|
|
|
'method': method,
|
|
|
|
|
'arguments': args
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let tag = this.#requests.push(request) - 1;
|
|
|
|
|
return tag;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#rpccall(tag) {
|
|
|
|
|
let request = this.#requests[tag];
|
|
|
|
|
//this.#logger(`[${tag}] >> ${request.payload.method}`);
|
|
|
|
|
let rpc = new window.XMLHttpRequest();
|
|
|
|
|
rpc.tag = tag;
|
|
|
|
|
rpc.open('POST', this.#rpcurl);
|
|
|
|
|
if (this.#authHeader != null) {
|
|
|
|
|
rpc.setRequestHeader('authorization', this.#authHeader);
|
|
|
|
|
}
|
|
|
|
|
if (this.#sessionHeader != null) {
|
|
|
|
|
rpc.setRequestHeader('X-Transmission-Session-Id', this.#sessionHeader);
|
|
|
|
|
}
|
|
|
|
|
rpc.setRequestHeader('content-type', 'application/json');
|
2025-10-09 11:35:35 +02:00
|
|
|
rpc.addEventListener('readystatechange', this);
|
|
|
|
|
rpc.addEventListener('timeout', this);
|
|
|
|
|
rpc.addEventListener('error', this);
|
2025-10-08 12:28:02 +02:00
|
|
|
rpc.timeout = 10000;
|
|
|
|
|
try {
|
|
|
|
|
rpc.send(JSON.stringify(request.payload));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
this.#logger(error);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rpccall_debug(method, args) {
|
|
|
|
|
var tag = this.#rpccall_prepare(method, args);
|
|
|
|
|
let request = this.#requests[tag];
|
|
|
|
|
request.success = console.info;
|
|
|
|
|
this.#rpccall(tag);
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-09 11:35:35 +02:00
|
|
|
handleEvent(e) {
|
2025-10-08 12:28:02 +02:00
|
|
|
switch (e.type) {
|
|
|
|
|
case 'readystatechange':
|
|
|
|
|
if (e.target.readyState == 4) {
|
|
|
|
|
let tag = e.target.tag;
|
|
|
|
|
let request = this.#requests[tag];
|
|
|
|
|
switch (e.target.status) {
|
|
|
|
|
case 200:
|
|
|
|
|
if (!this.#isOnline) {
|
|
|
|
|
this.#logger('Server API connection estabilished');
|
|
|
|
|
this.#isOnline = true;
|
|
|
|
|
this.#forceUpdateTorrents();
|
|
|
|
|
}
|
|
|
|
|
//request.response = JSON.parse(e.target.responseText);
|
|
|
|
|
if (request.success != null) {
|
|
|
|
|
request.success(JSON.parse(e.target.responseText).arguments);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
//this.#logger(e.target.responseText);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 409:
|
|
|
|
|
this.#sessionHeader = e.target.getResponseHeader('X-Transmission-Session-Id');
|
|
|
|
|
this.#rpccall(tag);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
request.response = {'code': e.target.status, 'content': e.target.responseText};
|
|
|
|
|
//this.#logger(JSON.stringify(request.response));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'timeout':
|
|
|
|
|
if (this.#isOnline) {
|
|
|
|
|
this.#logger('Server API connection timed out');
|
|
|
|
|
this.#isOnline = false;
|
|
|
|
|
this.#forceUpdateTorrents();
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'error':
|
|
|
|
|
if (this.#isOnline) {
|
|
|
|
|
this.#logger('Server API refuses connection');
|
|
|
|
|
this.#isOnline = false;
|
|
|
|
|
this.#forceUpdateTorrents();
|
|
|
|
|
}
|
|
|
|
|
this.#logger(JSON.stringify(e));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
refreshSession (){
|
|
|
|
|
//let tag = this.#rpccall_prepare('session-get', null);
|
|
|
|
|
//this.#rpccall(tag);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#parseTorrents(response) {
|
|
|
|
|
// mark everything as deleted but clean
|
|
|
|
|
for (const [key, value] of Object.entries(this.#torrents)) {
|
|
|
|
|
value.wasDeleted = value.deleted;
|
|
|
|
|
value.deleted = true;
|
|
|
|
|
value.dirty = false;
|
|
|
|
|
}
|
|
|
|
|
// Update torrent data
|
|
|
|
|
for (const torrent of response.torrents) {
|
|
|
|
|
let hash = torrent.hashString;
|
|
|
|
|
if (!(hash in this.#torrents)) {
|
|
|
|
|
this.#torrents[hash] = {'dirty': true};
|
|
|
|
|
}
|
|
|
|
|
let knownTorrent = this.#torrents[hash];
|
|
|
|
|
for (const [key, value] of Object.entries(torrent)) {
|
|
|
|
|
let parsedValue = undefined;
|
|
|
|
|
switch (key) {
|
|
|
|
|
case 'hashString':
|
|
|
|
|
break;
|
|
|
|
|
case 'percentDone':
|
|
|
|
|
parsedValue = Math.floor(value * 100);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
parsedValue = value;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parsedValue != undefined && knownTorrent[key] !== parsedValue) {
|
|
|
|
|
knownTorrent[key] = parsedValue;
|
|
|
|
|
knownTorrent.dirty = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
knownTorrent.deleted = false;
|
|
|
|
|
}
|
|
|
|
|
//check for changes
|
|
|
|
|
for (const [key, value] of Object.entries(this.#torrents)) {
|
|
|
|
|
if (value.deleted != value.wasDeleted || value.dirty) {
|
|
|
|
|
// call UI update
|
|
|
|
|
this.#torrentCallback(key, value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#forceUpdateTorrents() {
|
|
|
|
|
for (const [key, value] of Object.entries(this.#torrents)) {
|
|
|
|
|
// call UI update
|
|
|
|
|
this.#torrentCallback(key, value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
refreshTorrentList() {
|
|
|
|
|
let tag = this.#rpccall_prepare('torrent-get', {'fields': ['id', 'hashString', 'name', 'status', 'percentDone', 'comment', 'magnetLink']});
|
|
|
|
|
this.#requests[tag].success = this.#parseTorrents.bind(this);
|
|
|
|
|
this.#rpccall(tag);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
torrent_add_url(url, dontstart, path) {
|
|
|
|
|
let args = {
|
|
|
|
|
'filename': url,
|
|
|
|
|
'paused': dontstart
|
|
|
|
|
};
|
|
|
|
|
if (path != null) {
|
|
|
|
|
args['download-dir'] = path;
|
|
|
|
|
}
|
|
|
|
|
let tag = this.#rpccall_prepare('torrent-add', args);
|
|
|
|
|
this.#rpccall(tag);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
torrent_start(hash) {
|
|
|
|
|
let tag = this.#rpccall_prepare('torrent-start', {'ids':[hash]});
|
|
|
|
|
this.#rpccall(tag);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
torrent_pause(hash) {
|
|
|
|
|
let tag = this.#rpccall_prepare('torrent-stop', {'ids':[hash]});
|
|
|
|
|
this.#rpccall(tag);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getInstanceData() {
|
|
|
|
|
return {
|
|
|
|
|
'rpcurl': this.#rpcurl,
|
|
|
|
|
'auth': this.#authHeader
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isOnline() {
|
|
|
|
|
return this.#isOnline;
|
|
|
|
|
}
|
|
|
|
|
};
|