Sync working tree before initial Gitea push
- File reorg: popup/options/bulk-check moved to src/ subdirs - Shared modules: src/shared/id-extract.js, src/options/options-shared.js - Host updates: rcjav-host.py + register/install scripts - .gitignore expanded
This commit is contained in:
@@ -0,0 +1,343 @@
|
||||
// ---------- diagnostics ----------
|
||||
|
||||
// Extension ID display + copy button (added when Transfer Assistant was deleted).
|
||||
// Diagnostics is the canonical home for "what's my extension ID?" info now.
|
||||
(() => {
|
||||
const idEl = document.getElementById("diag-extension-id");
|
||||
const copyBtn = document.getElementById("diag-copy-extension-id");
|
||||
if (idEl) idEl.textContent = chrome.runtime.id;
|
||||
if (copyBtn) {
|
||||
copyBtn.addEventListener("click", async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(chrome.runtime.id);
|
||||
copyBtn.textContent = "Copied";
|
||||
setTimeout(() => { copyBtn.textContent = "Copy ID"; }, 1200);
|
||||
} catch (_) {
|
||||
copyBtn.textContent = "Copy failed";
|
||||
setTimeout(() => { copyBtn.textContent = "Copy ID"; }, 1200);
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
document.getElementById("run-diag").addEventListener("click", (event) =>
|
||||
keepActionViewport(event.currentTarget, runDiagnostics)
|
||||
);
|
||||
|
||||
// ---------- native messaging RPC log ----------
|
||||
const NATIVE_LOG_KEY = "rclonejavNativeLog";
|
||||
|
||||
function _fmtNativeLogTime(ts) {
|
||||
if (!Number.isFinite(ts)) return "?";
|
||||
const d = new Date(ts);
|
||||
const pad = (n) => String(n).padStart(2, "0");
|
||||
return `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}.${String(d.getMilliseconds()).padStart(3, "0")}`;
|
||||
}
|
||||
|
||||
function _fmtBytes(n) {
|
||||
if (!Number.isFinite(n) || n < 0) return "?";
|
||||
if (n < 1024) return `${n} B`;
|
||||
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KiB`;
|
||||
return `${(n / 1024 / 1024).toFixed(2)} MiB`;
|
||||
}
|
||||
|
||||
async function renderNativeLog() {
|
||||
const out = document.getElementById("native-log-results");
|
||||
if (!out) return;
|
||||
const errOnly = document.getElementById("native-log-errors-only")?.checked;
|
||||
let entries = [];
|
||||
try {
|
||||
const got = await chrome.storage.local.get(NATIVE_LOG_KEY);
|
||||
entries = Array.isArray(got[NATIVE_LOG_KEY]) ? got[NATIVE_LOG_KEY] : [];
|
||||
} catch (e) {
|
||||
out.innerHTML = `<span style="color:#faa;">error reading log:</span> ${escapeHtml(e.message || String(e))}`;
|
||||
return;
|
||||
}
|
||||
if (errOnly) entries = entries.filter((e) => !e.ok);
|
||||
if (!entries.length) {
|
||||
out.innerHTML = `<span style="color:#777;">${errOnly ? "no errors recorded" : "no RPC calls recorded yet"}</span>`;
|
||||
return;
|
||||
}
|
||||
out.innerHTML = entries.slice(0, 80).map((e) => {
|
||||
const ok = !!e.ok;
|
||||
const color = ok ? "#9be3b3" : "#ff9097";
|
||||
const action = e.action || "?";
|
||||
const latency = Number.isFinite(e.latency_ms) ? `${e.latency_ms}ms` : "?";
|
||||
const size = e.resp_bytes != null ? ` · ${_fmtBytes(e.resp_bytes)}` : "";
|
||||
const truncated = e.truncated ? ` · <span style="color:#ffd784;">TRUNCATED${e.truncated_reason ? " (" + escapeHtml(e.truncated_reason) + ")" : ""}</span>` : "";
|
||||
const inflight = e.inflight != null ? ` · ${e.inflight} inflight` : "";
|
||||
const head = `<div><span style="color:#888;">${escapeHtml(_fmtNativeLogTime(e.ts))}</span> <span style="color:${color};">${ok ? "✓" : "✗"} ${escapeHtml(action)}</span> · ${escapeHtml(latency)}${size}${truncated}${inflight}</div>`;
|
||||
const tail = !ok
|
||||
? `<div style="color:#aaa;margin-left:14px;"><span style="color:#888;">${escapeHtml(e.error_kind || "error")}:</span> ${escapeHtml(e.error || "")}</div>`
|
||||
: "";
|
||||
return `<div class="activity-entry">${head}${tail}</div>`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
document.getElementById("native-log-run")?.addEventListener("click", renderNativeLog);
|
||||
document.getElementById("native-log-errors-only")?.addEventListener("change", renderNativeLog);
|
||||
document.getElementById("native-log-clear")?.addEventListener("click", async () => {
|
||||
if (!confirm("Clear extension-side native messaging log?")) return;
|
||||
await chrome.storage.local.remove(NATIVE_LOG_KEY);
|
||||
renderNativeLog();
|
||||
});
|
||||
document.getElementById("host-events-clear")?.addEventListener("click", async (e) => {
|
||||
if (!confirm("Truncate host/logs/rcjav-host-events.log on disk?")) return;
|
||||
const btn = e.currentTarget;
|
||||
const original = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = "Clearing…";
|
||||
try {
|
||||
const r = await chrome.runtime.sendMessage({ type: "clear-events-log" });
|
||||
btn.textContent = r?.ok ? "Cleared" : `Failed: ${r?.error || "no response"}`;
|
||||
} catch (err) {
|
||||
btn.textContent = `Failed: ${err.message || err}`;
|
||||
} finally {
|
||||
setTimeout(() => { btn.disabled = false; btn.textContent = original; }, 1500);
|
||||
}
|
||||
});
|
||||
// Live update if SW writes new entries while the page is open.
|
||||
chrome.storage.onChanged.addListener((changes, area) => {
|
||||
if (area === "local" && NATIVE_LOG_KEY in changes) renderNativeLog();
|
||||
});
|
||||
|
||||
document.getElementById("host-status-run").addEventListener("click", (event) =>
|
||||
keepActionViewport(event.currentTarget, runHostStatus)
|
||||
);
|
||||
document.getElementById("host-repair-run").addEventListener("click", (event) =>
|
||||
keepActionViewport(event.currentTarget, runHostRepair)
|
||||
);
|
||||
document.getElementById("host-verify-run").addEventListener("click", (event) =>
|
||||
keepActionViewport(event.currentTarget, runHostStatus)
|
||||
);
|
||||
document.getElementById("run-all-diag").addEventListener("click", (event) =>
|
||||
keepActionViewport(event.currentTarget, async () => {
|
||||
clearNativeRepairCard();
|
||||
const runtime = await runDiagnostics();
|
||||
if (runtime && runtime.nativeBlocked) {
|
||||
renderBlockedByNativeIssue(document.getElementById("host-status-results"), "Host registration");
|
||||
return;
|
||||
}
|
||||
await runHostStatus();
|
||||
})
|
||||
);
|
||||
|
||||
function renderDiagRows(out, checks, emptyLabel) {
|
||||
out.innerHTML = "";
|
||||
if (!checks || checks.length === 0) {
|
||||
out.innerHTML = `<div class="diag-row warn"><span class="icon">!</span><span class="name">${escapeHtml(emptyLabel)}</span><span class="detail">no checks returned</span></div>`;
|
||||
return;
|
||||
}
|
||||
const counts = checks.reduce((acc, c) => {
|
||||
const status = c.status || "warn";
|
||||
acc[status] = (acc[status] || 0) + 1;
|
||||
return acc;
|
||||
}, {});
|
||||
const summary = document.createElement("div");
|
||||
summary.className = "diag-row " + ((counts.fail || 0) ? "fail" : (counts.warn || 0) ? "warn" : "ok");
|
||||
summary.innerHTML = `<span class="icon">#</span><span class="name">summary</span><span class="detail">${checks.length} checks · ok ${counts.ok || 0} · info ${counts.info || 0} · warn ${counts.warn || 0} · fail ${counts.fail || 0}</span>`;
|
||||
out.appendChild(summary);
|
||||
for (const c of checks) {
|
||||
const row = document.createElement("div");
|
||||
row.className = "diag-row " + (c.status || "warn");
|
||||
const status = c.status || "warn";
|
||||
const icon = status === "ok" ? "✓" : status === "info" ? "i" : status === "warn" ? "!" : "✗";
|
||||
row.innerHTML = `<span class="icon">${icon}</span><span class="name">${escapeHtml(c.name)}</span><span class="detail">${formatDiagDetail(c.detail || "")}</span>`;
|
||||
out.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
function formatDiagDetail(detail) {
|
||||
const text = String(detail || "");
|
||||
if (!text) return "";
|
||||
const shouldCollapse = text.length > 120 || text.includes("\n") || (text.match(/[;|]/g) || []).length > 2;
|
||||
if (!shouldCollapse) return escapeHtml(text);
|
||||
const first = text.split(/\r?\n/)[0].slice(0, 110);
|
||||
return `<details><summary>${escapeHtml(first)}${text.length > first.length ? "…" : ""}</summary><pre>${escapeHtml(text)}</pre></details>`;
|
||||
}
|
||||
|
||||
async function runDiagnostics() {
|
||||
const out = document.getElementById("diag-results");
|
||||
clearNativeRepairCard();
|
||||
out.innerHTML = '<div class="diag-row warn"><span class="icon">…</span><span class="name">running…</span><span class="detail">waiting for native host</span></div>';
|
||||
try {
|
||||
const r = await chrome.runtime.sendMessage({ type: "diagnostics" });
|
||||
if (!r || !r.ok) {
|
||||
await renderNativeMessagingFailure(r);
|
||||
renderBlockedByNativeIssue(out, "Runtime diagnostics");
|
||||
return { nativeBlocked: true };
|
||||
}
|
||||
renderDiagRows(out, r.checks || [], "runtime");
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
out.innerHTML = `<div class="diag-row fail"><span class="icon">✗</span><span class="name">runtime</span><span class="detail">${escapeHtml(err.message || String(err))}</span></div>`;
|
||||
return { ok: false };
|
||||
}
|
||||
}
|
||||
|
||||
async function runHostStatus() {
|
||||
const out = document.getElementById("host-status-results");
|
||||
clearNativeRepairCard();
|
||||
out.innerHTML = '<div class="diag-row warn"><span class="icon">…</span><span class="name">checking…</span><span class="detail">reading manifest and registry state</span></div>';
|
||||
try {
|
||||
const r = await chrome.runtime.sendMessage({ type: "host-status" });
|
||||
if (!r || !r.ok) {
|
||||
await renderNativeMessagingFailure(r);
|
||||
renderBlockedByNativeIssue(out, "Native host checks");
|
||||
return { nativeBlocked: true };
|
||||
}
|
||||
renderDiagRows(out, r.checks || [], "host status");
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
out.innerHTML = `<div class="diag-row fail"><span class="icon">✗</span><span class="name">host status</span><span class="detail">${escapeHtml(err.message || String(err))}</span></div>`;
|
||||
return { ok: false };
|
||||
}
|
||||
}
|
||||
|
||||
async function runHostRepair() {
|
||||
const out = document.getElementById("host-status-results");
|
||||
clearNativeRepairCard();
|
||||
out.innerHTML = '<div class="diag-row warn"><span class="icon">…</span><span class="name">repairing…</span><span class="detail">launching install-host.ps1 (UAC prompt will appear)</span></div>';
|
||||
try {
|
||||
const r = await chrome.runtime.sendMessage({ type: "repair-host" });
|
||||
if (!r || !r.ok) {
|
||||
if (r?.error_kind) {
|
||||
await renderNativeMessagingFailure(r);
|
||||
renderBlockedByNativeIssue(out, "Registration repair");
|
||||
} else {
|
||||
out.innerHTML = `<div class="diag-row fail"><span class="icon">✗</span><span class="name">Registration repair</span><span class="detail">${escapeHtml(r?.error || "repair failed")}</span></div>`;
|
||||
}
|
||||
return { ok: false };
|
||||
}
|
||||
out.innerHTML = "";
|
||||
renderCompletedNativeRepair(r);
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
out.innerHTML = `<div class="diag-row fail"><span class="icon">✗</span><span class="name">Registration repair</span><span class="detail">${escapeHtml(err.message || String(err))}</span></div>`;
|
||||
return { ok: false };
|
||||
}
|
||||
}
|
||||
|
||||
function clearNativeRepairCard() {
|
||||
const card = document.getElementById("native-repair-card");
|
||||
const out = document.getElementById("native-repair-results");
|
||||
const title = document.getElementById("native-repair-title");
|
||||
if (card) card.style.display = "none";
|
||||
if (out) out.innerHTML = "";
|
||||
if (title) title.textContent = "Native host setup";
|
||||
}
|
||||
|
||||
function renderCompletedNativeRepair(response) {
|
||||
const card = document.getElementById("native-repair-card");
|
||||
const out = document.getElementById("native-repair-results");
|
||||
if (!card || !out) return;
|
||||
card.style.display = "";
|
||||
const title = document.getElementById("native-repair-title");
|
||||
if (title) title.textContent = "install-host.ps1 launched";
|
||||
out.innerHTML = `
|
||||
<div class="diag-row ok"><span class="icon">✓</span><span class="name">Launcher started</span><span class="detail">${escapeHtml(response.message || "install-host.ps1 launched")}</span></div>
|
||||
<div class="diag-row info"><span class="icon">i</span><span class="name">Script</span><span class="detail">${escapeHtml(response.script_path || "")}</span></div>
|
||||
<div class="diag-row info"><span class="icon">i</span><span class="name">Manifest target</span><span class="detail">${escapeHtml(response.manifest_path || "")}</span></div>
|
||||
<div class="diag-row warn"><span class="icon">!</span><span class="name">Next steps</span><span class="detail">1) Approve the UAC prompt. 2) Wait for the PowerShell window to print "Press Enter to close" and press Enter. 3) Fully close and reopen Brave. 4) Click Verify Registration.</span></div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderBlockedByNativeIssue(out, title) {
|
||||
out.innerHTML = `<div class="diag-row info"><span class="icon">i</span><span class="name">${escapeHtml(title)}</span><span class="detail">Blocked until this PC registers the native host for the current extension ID. Use the setup card above.</span></div>`;
|
||||
}
|
||||
|
||||
async function getPackagedHostPaths() {
|
||||
try {
|
||||
const resp = await fetch(chrome.runtime.getURL("host/com.rcjav.host.json"));
|
||||
if (!resp.ok) return {};
|
||||
const manifest = await resp.json();
|
||||
const bat = manifest.path || "";
|
||||
const hostDir = bat.replace(/[\\/][^\\/]+$/, "");
|
||||
return {
|
||||
hostBat: bat,
|
||||
hostDir,
|
||||
registerBat: hostDir ? hostDir + "\\register-host.bat" : "",
|
||||
installPs1: hostDir ? hostDir + "\\install-host.ps1" : "",
|
||||
};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async function renderNativeMessagingFailure(response) {
|
||||
const card = document.getElementById("native-repair-card");
|
||||
const out = document.getElementById("native-repair-results");
|
||||
if (!card || !out) return;
|
||||
card.style.display = "";
|
||||
const title = document.getElementById("native-repair-title");
|
||||
if (title) title.textContent = "Register host on this PC";
|
||||
const error = response?.error || "no response";
|
||||
const kind = response?.error_kind || (/forbidden/i.test(error) ? "forbidden" : "unknown");
|
||||
const extensionId = response?.extension_id || chrome.runtime.id;
|
||||
const paths = await getPackagedHostPaths();
|
||||
const installCommand = paths.installPs1
|
||||
? `pwsh -ExecutionPolicy Bypass -File "${paths.installPs1}"`
|
||||
: `pwsh -ExecutionPolicy Bypass -File ".\\host\\install-host.ps1"`;
|
||||
const registerCommand = paths.registerBat ? `"${paths.registerBat}"` : ".\\host\\register-host.bat";
|
||||
const hostDir = paths.hostDir || "";
|
||||
const hostFolderUrl = hostDir ? "file:///" + hostDir.replace(/\\/g, "/").replace(/^([A-Za-z]:)/, "$1") : "";
|
||||
let cause = "This extension cannot launch the native messaging host yet.";
|
||||
let fix = "Run register-host.bat once on this PC, fully restart Brave, then verify registration.";
|
||||
if (kind === "forbidden") {
|
||||
cause = "Brave found the native host but the extension ID is not in its allowlist on this PC.";
|
||||
fix = "Run register-host.bat to refresh the manifest from allowed-extension-ids.json.";
|
||||
} else if (kind === "not_found") {
|
||||
cause = "Brave could not find a registered native messaging host for com.rcjav.host on this PC.";
|
||||
fix = "Run register-host.bat from the extension host folder.";
|
||||
} else if (kind === "disconnected") {
|
||||
cause = "The native host started and then disconnected or crashed.";
|
||||
fix = "After registration is fixed, run Runtime diagnostics again to check Python, rc-jav, and rclone.";
|
||||
} else if (kind === "timeout") {
|
||||
cause = "The native host did not respond before the timeout.";
|
||||
fix = "Restart Brave and check whether a scan or rclone command is stuck.";
|
||||
}
|
||||
const openFolderBtn = hostFolderUrl
|
||||
? `<button type="button" data-open-folder="${escapeHtml(hostFolderUrl)}" data-folder-path="${escapeHtml(hostDir)}">Open Host Folder</button>`
|
||||
: "";
|
||||
out.innerHTML = `
|
||||
<div class="diag-row warn"><span class="icon">!</span><span class="name">Setup required</span><span class="detail">Native host registration must be fixed before cache, runtime, and host checks can run.</span></div>
|
||||
<div class="diag-row warn"><span class="icon">!</span><span class="name">Likely cause</span><span class="detail">${escapeHtml(cause)}</span></div>
|
||||
<div class="diag-row info"><span class="icon">i</span><span class="name">Host message</span><span class="detail">${escapeHtml(error)}</span></div>
|
||||
<div class="diag-row ok"><span class="icon">→</span><span class="name">Fix on this PC</span><span class="detail">${escapeHtml(fix)}</span></div>
|
||||
<div class="diag-row info"><span class="icon">1</span><span class="name">Run register-host</span><span class="detail">
|
||||
<details open><summary>${escapeHtml(registerCommand)}</summary><pre>${escapeHtml(`Double-click ${registerCommand}\nor run the PowerShell alternative:\n${installCommand}\n\nThe script reads the extension ID from allowed-extension-ids.json — no paste step.`)}</pre></details>
|
||||
<span class="diag-action">${openFolderBtn}<button type="button" data-copy="${escapeHtml(hostDir)}" data-copy-label="Copy Folder Path">Copy Folder Path</button><button type="button" data-copy="${escapeHtml(registerCommand)}" data-copy-label="Copy Script Path">Copy Script Path</button><button type="button" data-copy="${escapeHtml(installCommand)}" data-copy-label="Copy PowerShell Alternative">Copy PowerShell Alternative</button></span>
|
||||
</span></div>
|
||||
<div class="diag-row info"><span class="icon">2</span><span class="name">Restart Brave</span><span class="detail">Close every Brave window/process, reopen Brave, then reload the extension.</span></div>
|
||||
<div class="diag-row info"><span class="icon">3</span><span class="name">Verify</span><span class="detail"><span class="diag-action"><button type="button" data-verify-registration>Verify Registration</button></span></span></div>
|
||||
`;
|
||||
for (const btn of out.querySelectorAll("button[data-copy]")) {
|
||||
btn.addEventListener("click", async () => {
|
||||
await navigator.clipboard.writeText(btn.dataset.copy || "");
|
||||
btn.textContent = "Copied";
|
||||
setTimeout(() => { btn.textContent = btn.dataset.copyLabel || "Copy"; }, 1200);
|
||||
});
|
||||
}
|
||||
for (const btn of out.querySelectorAll("button[data-open-folder]")) {
|
||||
btn.addEventListener("click", async () => {
|
||||
const url = btn.dataset.openFolder;
|
||||
const folderPath = btn.dataset.folderPath || "";
|
||||
try {
|
||||
// file:// URLs require "Allow access to file URLs" toggled on for the
|
||||
// extension. If Brave silently blocks it (no tab opens), fall back to
|
||||
// clipboard so the user can paste into File Explorer (Win+E).
|
||||
await chrome.tabs.create({ url });
|
||||
btn.textContent = "Opening…";
|
||||
setTimeout(() => { btn.textContent = "Open Host Folder"; }, 1500);
|
||||
} catch (_) {
|
||||
try { await navigator.clipboard.writeText(folderPath); } catch {}
|
||||
btn.textContent = "Blocked — path copied";
|
||||
setTimeout(() => { btn.textContent = "Open Host Folder"; }, 2500);
|
||||
}
|
||||
});
|
||||
}
|
||||
for (const btn of out.querySelectorAll("button[data-verify-registration]")) {
|
||||
btn.addEventListener("click", runHostStatus);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user