feat: Chrome extension download + install guide in admin panel

- GET /api/extension/download — streams chrome-extension/ as a .zip using archiver
- Admin → 🧩 Extension tab with:
  - One-click download button (fetches zip via auth'd API, triggers browser save)
  - 5-step install guide: unzip, chrome://extensions, developer mode, load unpacked, configure
  - UDP port-forwarding reminder note
- Added archiver dependency to package.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Zac Gaetano 2026-04-05 20:33:56 -04:00
parent fbec32dcc4
commit 895145e1ed
3 changed files with 99 additions and 1 deletions

View file

@ -12,6 +12,7 @@
"@aws-sdk/lib-storage": "^3.650.0",
"@aws-sdk/s3-request-presigner": "^3.650.0",
"@smithy/node-http-handler": "^3.2.0",
"archiver": "^7.0.1",
"express": "^4.21.0",
"multer": "^1.4.5-lts.1"
},

View file

@ -320,6 +320,15 @@ body:not([data-theme="light"]) .header-wordmark{filter:invert(1)}
.admin-tab.active{color:var(--blue-bright);border-bottom-color:var(--blue-bright)}
.admin-panel{display:none}
.admin-panel.active{display:block}
/* EXTENSION INSTALL GUIDE */
.ext-steps{display:flex;flex-direction:column;gap:.88rem;margin-bottom:1.5rem}
.ext-step{display:flex;gap:1rem;align-items:flex-start}
.ext-step-num{flex-shrink:0;width:28px;height:28px;border-radius:50%;background:var(--blue);color:#fff;font-size:.78rem;font-weight:700;display:flex;align-items:center;justify-content:center;margin-top:.1rem}
.ext-step-body{flex:1}
.ext-step-body strong{display:block;font-size:.88rem;color:var(--text-primary);margin-bottom:.25rem}
.ext-step-body p{font-size:.82rem;color:var(--text-secondary);margin:0;line-height:1.55}
.ext-step-body code{background:var(--bg-secondary);border:1px solid var(--border);border-radius:4px;padding:.1rem .4rem;font-family:'Courier New',monospace;font-size:.8rem;color:var(--blue-bright)}
.ext-note{background:var(--bg-secondary);border:1px solid var(--border);border-left:3px solid var(--dragon);border-radius:8px;padding:1rem 1.1rem;font-size:.82rem;color:var(--text-secondary);line-height:1.6}
/* FORMS */
.form-group{margin-bottom:1rem}
@ -520,6 +529,7 @@ body:not([data-theme="light"]) .header-wordmark{filter:invert(1)}
<div class="admin-tab active" onclick="switchAdminTab('s3')">S3 Storage</div>
<div class="admin-tab" onclick="switchAdminTab('relay')">UDP Relay</div>
<div class="admin-tab" onclick="switchAdminTab('ampp')">AMPP</div>
<div class="admin-tab" onclick="switchAdminTab('extension')">🧩 Extension</div>
<div class="admin-tab" onclick="switchAdminTab('users')">Users</div>
<div class="admin-tab" onclick="switchAdminTab('folders')">Folders</div>
</div>
@ -578,6 +588,58 @@ body:not([data-theme="light"]) .header-wordmark{filter:invert(1)}
<div class="status-msg" id="ampp-status"></div>
</div>
<!-- Extension -->
<div class="admin-panel" id="admin-extension">
<div class="section-title">Chrome Extension — UDP Upload</div>
<p style="color:var(--text-secondary);margin-bottom:1.5rem;line-height:1.6">The Dragon Wind Chrome extension enables UDP-accelerated uploads directly from your browser. Download the extension package below, then follow the steps to install it.</p>
<button class="btn-primary" style="margin-bottom:2rem" onclick="downloadExtension()">⬇️ Download Extension (.zip)</button>
<div class="section-title" style="font-size:.8rem;margin-bottom:1rem">Installation Steps</div>
<div class="ext-steps">
<div class="ext-step">
<div class="ext-step-num">1</div>
<div class="ext-step-body">
<strong>Unzip the downloaded file</strong>
<p>Extract <code>dragon-wind-extension.zip</code> to a permanent folder on your computer — Chrome needs this folder to stay in place.</p>
</div>
</div>
<div class="ext-step">
<div class="ext-step-num">2</div>
<div class="ext-step-body">
<strong>Open Chrome Extensions</strong>
<p>In Chrome, navigate to <code>chrome://extensions</code> or open the menu → More Tools → Extensions.</p>
</div>
</div>
<div class="ext-step">
<div class="ext-step-num">3</div>
<div class="ext-step-body">
<strong>Enable Developer Mode</strong>
<p>Toggle the <strong>Developer mode</strong> switch in the top-right corner of the Extensions page.</p>
</div>
</div>
<div class="ext-step">
<div class="ext-step-num">4</div>
<div class="ext-step-body">
<strong>Load the extension</strong>
<p>Click <strong>Load unpacked</strong>, then navigate to and select the <code>dragon-wind-extension</code> folder you unzipped in step 1.</p>
</div>
</div>
<div class="ext-step">
<div class="ext-step-num">5</div>
<div class="ext-step-body">
<strong>Configure the extension</strong>
<p>Click the 🐉 Dragon Wind icon in Chrome's toolbar. Enter this server's URL and log in with your credentials. UDP uploads will now be available from any page.</p>
</div>
</div>
</div>
<div class="ext-note">
<strong>📡 UDP Port Forwarding</strong><br>
For uploads from outside your local network, make sure your UDP relay port is forwarded in your router. You can find your assigned UDP port in the <strong>.env</strong> file (<code>RELAY_UDP_PORT</code>) or check the UDP Relay tab above.
</div>
</div>
<!-- Users -->
<div class="admin-panel" id="admin-users">
<div class="section-title">User Management</div>
@ -753,7 +815,7 @@ function switchPage(name) {
function switchAdminTab(name) {
document.querySelectorAll('.admin-tab').forEach((t,i) => {
t.classList.toggle('active', ['s3','relay','ampp','users','folders'][i] === name);
t.classList.toggle('active', ['s3','relay','ampp','extension','users','folders'][i] === name);
});
document.querySelectorAll('.admin-panel').forEach(p => p.classList.toggle('active', p.id === `admin-${name}`));
if (name === 'users') loadUsers();
@ -761,6 +823,25 @@ function switchAdminTab(name) {
if (name === 'ampp') loadAmppConfig();
}
async function downloadExtension() {
const btn = event.target;
btn.disabled = true; btn.textContent = '⏳ Preparing download…';
try {
const res = await fetch('/api/extension/download', { headers: { 'Authorization': 'Bearer ' + authToken } });
if (!res.ok) throw new Error(`Server error ${res.status}`);
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url; a.download = 'dragon-wind-extension.zip'; a.click();
URL.revokeObjectURL(url);
btn.textContent = '✅ Downloaded!';
setTimeout(() => { btn.disabled = false; btn.textContent = '⬇️ Download Extension (.zip)'; }, 3000);
} catch(e) {
btn.disabled = false; btn.textContent = '⬇️ Download Extension (.zip)';
showToast('Download failed: ' + e.message, 'error');
}
}
// ============================================================
// UPLOAD MODE
// ============================================================

View file

@ -10,6 +10,7 @@ const { NodeHttpHandler } = require("@smithy/node-http-handler");
const fs = require("fs");
const https = require("https");
const http = require("http");
const archiver = require("archiver");
require("events").defaultMaxListeners = 50;
@ -640,6 +641,21 @@ app.get("/api/ampp/jobs/:jobId", requireAuth, async (req, res) => {
} catch (err) { res.status(500).json({ success: false, error: err.message }); }
});
// ==================== CHROME EXTENSION DOWNLOAD ====================
app.get("/api/extension/download", requireAdmin, (req, res) => {
const extDir = path.join(__dirname, "chrome-extension");
if (!fs.existsSync(extDir)) {
return res.status(404).json({ success: false, error: "chrome-extension directory not found" });
}
res.setHeader("Content-Type", "application/zip");
res.setHeader("Content-Disposition", "attachment; filename=\"dragon-wind-extension.zip\"");
const archive = archiver("zip", { zlib: { level: 6 } });
archive.on("error", (err) => { console.error("[Extension ZIP]", err.message); res.end(); });
archive.pipe(res);
archive.directory(extDir, "dragon-wind-extension");
archive.finalize();
});
// ==================== HEALTH ====================
app.get("/api/health", (req, res) => {
res.json({