Files
rclone_gui/webgui/web/assets/js/rc.js
T
2026-06-20 12:10:32 +08:00

106 lines
3.4 KiB
JavaScript

// rc.js — minimal client for rclone's RC HTTP API.
// The RC base URL is discovered from the ?url= query param that the
// webgui command appends to the GUI URL on launch. If absent (e.g. when
// developing the SPA from another server), fall back to same-origin.
const params = new URLSearchParams(location.search);
const QUERY_URL = params.get("url");
const RC_BASE = QUERY_URL
? QUERY_URL.replace(/\/$/, "")
: location.origin;
const AUTH_USER = params.get("user");
const AUTH_PASS = params.get("pass");
let authHeader = null;
if (AUTH_USER && AUTH_PASS) {
authHeader = "Basic " + btoa(`${AUTH_USER}:${AUTH_PASS}`);
}
export function rcURL() {
return RC_BASE;
}
// Are we running with basic auth configured from the launch URL?
export function hasAuth() {
return authHeader !== null;
}
export function isNoAuth() {
return !authHeader;
}
// POST JSON to an RC endpoint. Returns the parsed JSON response, or throws.
export async function post(path, body = {}, options = {}) {
const headers = { "Content-Type": "application/json" };
if (authHeader) headers["Authorization"] = authHeader;
const res = await fetch(RC_BASE + "/" + path.replace(/^\//, ""), {
method: "POST",
headers,
body: JSON.stringify(body),
});
return parseResponse(res, path, options);
}
// POST JSON and request an async job. Returns { jobid, executeId }.
export async function postAsync(path, body = {}) {
return post(path, { ...body, _async: true });
}
// Upload one or more files via multipart form-data.
// Matches operations/uploadfile: form fields `fs`, `remote`, and one
// file part per uploaded file. The server uses the part's filename.
export async function uploadFile(fs, remote, files) {
const form = new FormData();
form.set("fs", fs);
form.set("remote", remote);
for (const file of files) {
form.append("file", file, file.name);
}
const headers = {};
if (authHeader) headers["Authorization"] = authHeader;
// Do NOT set Content-Type — the browser sets multipart boundary.
const res = await fetch(RC_BASE + "/operations/uploadfile", {
method: "POST",
headers,
body: form,
});
return parseResponse(res, "/operations/uploadfile");
}
// Build a download URL for a file. Requires opt.Serve = true on the rc server.
export function downloadURL(remoteFs, remotePath, fileName) {
const base = RC_BASE.replace(/\/$/, "");
// rc server serves remote files at /<remote>:<path>
const trimmed = (remotePath || "").replace(/^\/+|\/+$/g, "");
const path = trimmed ? `${remoteFs}/${trimmed}/${fileName}` : `${remoteFs}/${fileName}`;
let url = `${base}/${path}`;
if (authHeader) {
// Embed basic auth into the URL so the browser can fetch it directly.
url = url.replace(/^(https?:\/\/)/, `$1${encodeURIComponent(AUTH_USER)}:${encodeURIComponent(AUTH_PASS)}@`);
}
return url;
}
async function parseResponse(res, path, { allowError = false } = {}) {
let body = null;
const ct = res.headers.get("Content-Type") || "";
if (ct.includes("application/json")) {
body = await res.json();
} else {
const text = await res.text();
body = text ? { raw: text } : {};
}
if (!res.ok) {
const msg = (body && (body.error || body.message)) || `HTTP ${res.status}`;
const err = new Error(`${path}: ${msg}`);
err.status = res.status;
err.body = body;
throw err;
}
if (body && body.error && !allowError) {
throw new Error(`${path}: ${body.error}`);
}
return body;
}