feat: make authentication optional — no tokens = no auth
Lint & Test / test (push) Successful in 10s
Lint & Test / test (push) Successful in 10s
When no api_tokens are configured (the new default), all endpoints are accessible without authentication. The frontend detects this via /api/health's auth_required field and skips the login form. - Backend: auth.py skips verification when api_tokens is empty - Frontend: shared getAuthHeaders()/hasCredentials() helpers replace scattered token logic across all JS modules - Health endpoint exposes auth_required for frontend discovery - config.example.yaml ships with tokens commented out - CLI --show-token and startup log reflect disabled state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
||||
togglePlayPause, nextTrack, previousTrack, toggleMute,
|
||||
VOLUME_THROTTLE_MS, VOLUME_RELEASE_DELAY_MS,
|
||||
changeLocale, t,
|
||||
setAuthRequired,
|
||||
} from './core.js';
|
||||
|
||||
// Layer 1: Player (tabs, theme, accent, vinyl, visualizer, UI)
|
||||
@@ -160,8 +161,25 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
// Load version from health endpoint
|
||||
fetchVersion();
|
||||
|
||||
// Check if authentication is required
|
||||
let authReq = true;
|
||||
try {
|
||||
const healthResp = await fetch('/api/health');
|
||||
const healthData = await healthResp.json();
|
||||
authReq = healthData.auth_required !== false;
|
||||
} catch { /* assume auth required on error */ }
|
||||
setAuthRequired(authReq);
|
||||
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
if (token) {
|
||||
if (!authReq) {
|
||||
// No auth required — connect directly without token
|
||||
connectWebSocket('');
|
||||
loadScripts();
|
||||
loadScriptsTable();
|
||||
loadCallbacksTable();
|
||||
loadLinksTable();
|
||||
loadAudioDevices();
|
||||
} else if (token) {
|
||||
connectWebSocket(token);
|
||||
loadScripts();
|
||||
loadScriptsTable();
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import {
|
||||
t, showToast, escapeHtml, closeDialog,
|
||||
SEARCH_DEBOUNCE_MS, EMPTY_SVG_FILE, EMPTY_SVG_FOLDER, emptyStateHtml,
|
||||
getAuthHeaders, hasCredentials,
|
||||
} from './core.js';
|
||||
|
||||
// Browser state
|
||||
@@ -24,14 +25,10 @@ const THUMBNAIL_CACHE_MAX = 200;
|
||||
// Load media folders on page load
|
||||
export async function loadMediaFolders() {
|
||||
try {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
if (!token) {
|
||||
console.error('No API token found');
|
||||
return;
|
||||
}
|
||||
if (!hasCredentials()) return;
|
||||
|
||||
const response = await fetch('/api/browser/folders', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Failed to load folders');
|
||||
@@ -119,11 +116,7 @@ async function browsePath(folderId, path, offset = 0, nocache = false) {
|
||||
showBrowserSearch(false);
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
if (!token) {
|
||||
console.error('No API token found');
|
||||
return;
|
||||
}
|
||||
if (!hasCredentials()) return;
|
||||
|
||||
// Show loading spinner
|
||||
const container = document.getElementById('browserGrid');
|
||||
@@ -135,7 +128,7 @@ async function browsePath(folderId, path, offset = 0, nocache = false) {
|
||||
if (nocache) url += '&nocache=true';
|
||||
const response = await fetch(
|
||||
url,
|
||||
{ headers: { 'Authorization': `Bearer ${token}` } }
|
||||
{ headers: getAuthHeaders() }
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -487,11 +480,7 @@ function formatBitrate(bps) {
|
||||
|
||||
async function loadThumbnail(imgElement, fileName) {
|
||||
try {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
if (!token) {
|
||||
console.error('No API token found');
|
||||
return;
|
||||
}
|
||||
if (!hasCredentials()) return;
|
||||
|
||||
const absolutePath = buildAbsolutePath(currentFolderId, currentPath, fileName);
|
||||
|
||||
@@ -510,7 +499,7 @@ async function loadThumbnail(imgElement, fileName) {
|
||||
|
||||
const response = await fetch(
|
||||
`/api/browser/thumbnail?path=${encodedPath}&size=medium`,
|
||||
{ headers: { 'Authorization': `Bearer ${token}` } }
|
||||
{ headers: getAuthHeaders() }
|
||||
);
|
||||
|
||||
if (response.status === 200) {
|
||||
@@ -576,20 +565,13 @@ async function playMediaFile(fileName) {
|
||||
if (playInProgress) return;
|
||||
playInProgress = true;
|
||||
try {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
if (!token) {
|
||||
console.error('No API token found');
|
||||
return;
|
||||
}
|
||||
if (!hasCredentials()) return;
|
||||
|
||||
const absolutePath = buildAbsolutePath(currentFolderId, currentPath, fileName);
|
||||
|
||||
const response = await fetch('/api/browser/play', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
body: JSON.stringify({ path: absolutePath })
|
||||
});
|
||||
|
||||
@@ -610,15 +592,11 @@ export async function playAllFolder() {
|
||||
const btn = document.getElementById('playAllBtn');
|
||||
if (btn) btn.disabled = true;
|
||||
try {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
if (!token || !currentFolderId) return;
|
||||
if (!hasCredentials() || !currentFolderId) return;
|
||||
|
||||
const response = await fetch('/api/browser/play-folder', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
body: JSON.stringify({ folder_id: currentFolderId, path: currentPath })
|
||||
});
|
||||
|
||||
@@ -640,8 +618,7 @@ export async function playAllFolder() {
|
||||
|
||||
export async function downloadFile(fileName, event) {
|
||||
if (event) event.stopPropagation();
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
if (!token) return;
|
||||
if (!hasCredentials()) return;
|
||||
|
||||
const fullPath = currentPath === '/'
|
||||
? '/' + fileName
|
||||
@@ -651,7 +628,7 @@ export async function downloadFile(fileName, event) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/browser/download?folder_id=${currentFolderId}&path=${encodedPath}`,
|
||||
{ headers: { 'Authorization': `Bearer ${token}` } }
|
||||
{ headers: getAuthHeaders() }
|
||||
);
|
||||
if (!response.ok) throw new Error('Download failed');
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Callbacks: CRUD management
|
||||
// ============================================================
|
||||
|
||||
import { t, showToast, escapeHtml, closeDialog, showConfirm } from './core.js';
|
||||
import { t, showToast, escapeHtml, closeDialog, showConfirm, getAuthHeaders, hasCredentials } from './core.js';
|
||||
|
||||
export let callbackFormDirty = false;
|
||||
export function setCallbackFormDirty(value) { callbackFormDirty = value; }
|
||||
@@ -16,12 +16,11 @@ export async function loadCallbacksTable() {
|
||||
}
|
||||
|
||||
async function _loadCallbacksTableImpl() {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
const tbody = document.getElementById('callbacksTableBody');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/callbacks/list', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -79,13 +78,12 @@ export function showAddCallbackDialog() {
|
||||
}
|
||||
|
||||
export async function showEditCallbackDialog(callbackName) {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
const dialog = document.getElementById('callbackDialog');
|
||||
const title = document.getElementById('callbackDialogTitle');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/callbacks/list', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -137,7 +135,6 @@ export async function saveCallback(event) {
|
||||
const submitBtn = event.target.querySelector('button[type="submit"]');
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
const isEdit = document.getElementById('callbackIsEdit').value === 'true';
|
||||
const callbackName = document.getElementById('callbackName').value;
|
||||
|
||||
@@ -157,10 +154,7 @@ export async function saveCallback(event) {
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
@@ -187,14 +181,10 @@ export async function deleteCallbackConfirm(callbackName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/callbacks/delete/${callbackName}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
@@ -300,8 +300,7 @@ function updateAllText() {
|
||||
document.getElementById('sourceIcon').innerHTML = initSrc?.icon || '';
|
||||
}
|
||||
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
if (token) {
|
||||
if (hasCredentials()) {
|
||||
if (_loadScriptsTable) _loadScriptsTable();
|
||||
if (_loadCallbacksTable) _loadCallbacksTable();
|
||||
if (_loadLinksTable) _loadLinksTable();
|
||||
@@ -396,19 +395,39 @@ export function showConfirm(message) {
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Auth Helpers
|
||||
// ============================================================
|
||||
|
||||
// Set to false when server reports auth_required: false
|
||||
export let authRequired = true;
|
||||
export function setAuthRequired(value) { authRequired = value; }
|
||||
|
||||
/**
|
||||
* Build Authorization headers for API requests.
|
||||
* Returns empty object when auth is disabled or no token is stored.
|
||||
*/
|
||||
export function getAuthHeaders() {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
return token ? { 'Authorization': `Bearer ${token}` } : {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we have sufficient credentials to call the API.
|
||||
* True when auth is disabled OR a token is stored.
|
||||
*/
|
||||
export function hasCredentials() {
|
||||
return !authRequired || !!localStorage.getItem('media_server_token');
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// API Commands
|
||||
// ============================================================
|
||||
|
||||
export async function sendCommand(endpoint, body = null) {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
};
|
||||
|
||||
if (body) {
|
||||
|
||||
@@ -2,21 +2,20 @@
|
||||
// Display Brightness & Power Control + Links Management
|
||||
// ============================================================
|
||||
|
||||
import { t, showToast, escapeHtml, closeDialog, showConfirm, resolveMdiIcons, fetchMdiIcon } from './core.js';
|
||||
import { t, showToast, escapeHtml, closeDialog, showConfirm, resolveMdiIcons, fetchMdiIcon, getAuthHeaders, hasCredentials } from './core.js';
|
||||
|
||||
let displayBrightnessTimers = {};
|
||||
const DISPLAY_THROTTLE_MS = 50;
|
||||
|
||||
export async function loadDisplayMonitors() {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
if (!token) return;
|
||||
if (!hasCredentials()) return;
|
||||
|
||||
const container = document.getElementById('displayMonitors');
|
||||
if (!container) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/display/monitors?refresh=true', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -108,14 +107,10 @@ export function onDisplayBrightnessChange(monitorId, value) {
|
||||
}
|
||||
|
||||
async function sendDisplayBrightness(monitorId, brightness) {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
try {
|
||||
await fetch(`/api/display/brightness/${monitorId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
body: JSON.stringify({ brightness })
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -128,14 +123,10 @@ export async function toggleDisplayPower(monitorId, monitorName) {
|
||||
const isOn = btn && btn.classList.contains('on');
|
||||
const newState = !isOn;
|
||||
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
try {
|
||||
const response = await fetch(`/api/display/power/${monitorId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
body: JSON.stringify({ on: newState })
|
||||
});
|
||||
const data = await response.json();
|
||||
@@ -160,15 +151,14 @@ export async function toggleDisplayPower(monitorId, monitorName) {
|
||||
// ============================================================
|
||||
|
||||
export async function loadHeaderLinks() {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
if (!token) return;
|
||||
if (!hasCredentials()) return;
|
||||
|
||||
const container = document.getElementById('headerLinks');
|
||||
if (!container) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/links/list', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
if (!response.ok) return;
|
||||
@@ -210,12 +200,11 @@ export async function loadLinksTable() {
|
||||
}
|
||||
|
||||
async function _loadLinksTableImpl() {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
const tbody = document.getElementById('linksTableBody');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/links/list', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -273,13 +262,12 @@ export function showAddLinkDialog() {
|
||||
}
|
||||
|
||||
export async function showEditLinkDialog(linkName) {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
const dialog = document.getElementById('linkDialog');
|
||||
const title = document.getElementById('linkDialogTitle');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/links/list', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -342,7 +330,6 @@ export async function saveLink(event) {
|
||||
const submitBtn = event.target.querySelector('button[type="submit"]');
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
const isEdit = document.getElementById('linkIsEdit').value === 'true';
|
||||
const linkName = isEdit ?
|
||||
document.getElementById('linkOriginalName').value :
|
||||
@@ -364,10 +351,7 @@ export async function saveLink(event) {
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
@@ -393,14 +377,10 @@ export async function deleteLinkConfirm(linkName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/links/delete/${linkName}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
currentPosition, setCurrentPosition, isUserAdjustingVolume,
|
||||
lastStatus, setLastStatus, currentPlayState, setCurrentPlayState,
|
||||
POSITION_INTERPOLATION_MS, seek,
|
||||
getAuthHeaders, hasCredentials,
|
||||
} from './core.js';
|
||||
import { updateBackgroundColors } from './background.js';
|
||||
import { loadDisplayMonitors } from './links.js';
|
||||
@@ -282,9 +283,8 @@ const VISUALIZER_SMOOTHING = 0.15;
|
||||
|
||||
export async function checkVisualizerAvailability() {
|
||||
try {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
const resp = await fetch('/api/media/visualizer/status', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
@@ -428,14 +428,12 @@ export async function loadAudioDevices() {
|
||||
if (!section || !select) return;
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
|
||||
const [devicesResp, statusResp] = await Promise.all([
|
||||
fetch('/api/media/visualizer/devices', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: getAuthHeaders()
|
||||
}),
|
||||
fetch('/api/media/visualizer/status', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: getAuthHeaders()
|
||||
})
|
||||
]);
|
||||
|
||||
@@ -495,15 +493,11 @@ export async function onAudioDeviceChanged() {
|
||||
if (!select) return;
|
||||
|
||||
const deviceName = select.value || null;
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/media/visualizer/device', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
body: JSON.stringify({ device_name: deviceName })
|
||||
});
|
||||
|
||||
@@ -612,9 +606,8 @@ export function updateUI(status) {
|
||||
const placeholderArt = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Crect fill='%23282828' width='300' height='300'/%3E%3Cpath fill='%236a6a6a' d='M150 80c-38.66 0-70 31.34-70 70s31.34 70 70 70 70-31.34 70-70-31.34-70-70-70zm0 20c27.614 0 50 22.386 50 50s-22.386 50-50 50-50-22.386-50-50 22.386-50 50-50zm0 30a20 20 0 100 40 20 20 0 000-40z'/%3E%3C/svg%3E";
|
||||
const placeholderGlow = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Crect fill='%23282828' width='300' height='300'/%3E%3C/svg%3E";
|
||||
if (artworkSource) {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
fetch(`/api/media/artwork?_=${Date.now()}`, {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: getAuthHeaders()
|
||||
})
|
||||
.then(r => r.ok ? r.blob() : null)
|
||||
.then(blob => {
|
||||
|
||||
@@ -6,19 +6,16 @@ import {
|
||||
t, showToast, escapeHtml, closeDialog, showConfirm,
|
||||
resolveMdiIcons, fetchMdiIcon,
|
||||
scripts, setScripts,
|
||||
getAuthHeaders, hasCredentials,
|
||||
} from './core.js';
|
||||
|
||||
export let scriptFormDirty = false;
|
||||
export function setScriptFormDirty(value) { scriptFormDirty = value; }
|
||||
|
||||
export async function loadScripts() {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/scripts/list', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
@@ -67,10 +64,9 @@ export async function displayQuickAccess() {
|
||||
});
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
if (token) {
|
||||
if (hasCredentials()) {
|
||||
const response = await fetch('/api/links/list', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
if (gen !== _quickAccessGen) return;
|
||||
if (response.ok) {
|
||||
@@ -124,16 +120,12 @@ export async function displayQuickAccess() {
|
||||
}
|
||||
|
||||
async function executeScript(scriptName, buttonElement) {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
buttonElement.classList.add('executing');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/scripts/execute/${scriptName}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
body: JSON.stringify({ args: [] })
|
||||
});
|
||||
|
||||
@@ -165,12 +157,11 @@ export async function loadScriptsTable() {
|
||||
}
|
||||
|
||||
async function _loadScriptsTableImpl() {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
const tbody = document.getElementById('scriptsTableBody');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/scripts/list', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -232,13 +223,12 @@ export function showAddScriptDialog() {
|
||||
}
|
||||
|
||||
export async function showEditScriptDialog(scriptName) {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
const dialog = document.getElementById('scriptDialog');
|
||||
const title = document.getElementById('dialogTitle');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/scripts/list', {
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -300,7 +290,6 @@ export async function saveScript(event) {
|
||||
const submitBtn = event.target.querySelector('button[type="submit"]');
|
||||
if (submitBtn) submitBtn.disabled = true;
|
||||
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
const isEdit = document.getElementById('scriptIsEdit').value === 'true';
|
||||
const scriptName = isEdit ?
|
||||
document.getElementById('scriptOriginalName').value :
|
||||
@@ -324,10 +313,7 @@ export async function saveScript(event) {
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
@@ -353,14 +339,10 @@ export async function deleteScriptConfirm(scriptName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/scripts/delete/${scriptName}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
@@ -443,7 +425,6 @@ function showExecutionResult(name, result, type = 'script') {
|
||||
}
|
||||
|
||||
export async function executeScriptDebug(scriptName) {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
const dialog = document.getElementById('executionDialog');
|
||||
const title = document.getElementById('executionDialogTitle');
|
||||
const statusDiv = document.getElementById('executionStatus');
|
||||
@@ -463,10 +444,7 @@ export async function executeScriptDebug(scriptName) {
|
||||
try {
|
||||
const response = await fetch(`/api/scripts/execute/${scriptName}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() },
|
||||
body: JSON.stringify({ args: [] })
|
||||
});
|
||||
|
||||
@@ -494,7 +472,6 @@ export async function executeScriptDebug(scriptName) {
|
||||
}
|
||||
|
||||
export async function executeCallbackDebug(callbackName) {
|
||||
const token = localStorage.getItem('media_server_token');
|
||||
const dialog = document.getElementById('executionDialog');
|
||||
const title = document.getElementById('executionDialogTitle');
|
||||
const statusDiv = document.getElementById('executionStatus');
|
||||
@@ -514,10 +491,7 @@ export async function executeCallbackDebug(callbackName) {
|
||||
try {
|
||||
const response = await fetch(`/api/callbacks/execute/${callbackName}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() }
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
dom, t, showToast, setWs,
|
||||
WS_BACKOFF_BASE_MS, WS_BACKOFF_MAX_MS,
|
||||
WS_MAX_RECONNECT_ATTEMPTS, WS_PING_INTERVAL_MS,
|
||||
authRequired,
|
||||
} from './core.js';
|
||||
import { updateUI, visualizerEnabled, visualizerAvailable, setFrequencyData, stopPositionInterpolation, loadAudioDevices } from './player.js';
|
||||
import { loadScripts, loadScriptsTable, displayQuickAccess } from './scripts.js';
|
||||
@@ -62,7 +63,8 @@ export function connectWebSocket(token) {
|
||||
}
|
||||
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsUrl = `${protocol}//${window.location.host}/api/media/ws?token=${encodeURIComponent(token)}`;
|
||||
const wsBase = `${protocol}//${window.location.host}/api/media/ws`;
|
||||
const wsUrl = token ? `${wsBase}?token=${encodeURIComponent(token)}` : wsBase;
|
||||
|
||||
const newWs = new WebSocket(wsUrl);
|
||||
setWs(newWs);
|
||||
@@ -134,8 +136,8 @@ export function connectWebSocket(token) {
|
||||
|
||||
reconnectTimeout = setTimeout(() => {
|
||||
const savedToken = localStorage.getItem('media_server_token');
|
||||
if (savedToken) {
|
||||
connectWebSocket(savedToken);
|
||||
if (savedToken || !authRequired) {
|
||||
connectWebSocket(savedToken || '');
|
||||
}
|
||||
}, delay);
|
||||
} else {
|
||||
@@ -175,9 +177,9 @@ function hideConnectionBanner() {
|
||||
|
||||
export function manualReconnect() {
|
||||
const savedToken = localStorage.getItem('media_server_token');
|
||||
if (savedToken) {
|
||||
if (savedToken || !authRequired) {
|
||||
wsReconnectAttempts = 0;
|
||||
hideConnectionBanner();
|
||||
connectWebSocket(savedToken);
|
||||
connectWebSocket(savedToken || '');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user