Initial commit: Shelfless – alternative Audiobookshelf frontend
React + Vite + TypeScript SPA covering the full ABS feature set (library browsing, item detail, metadata/cover editing, podcasts, player with session sync, admin: users/libraries/scanner/server settings). Dev uses a dynamic CORS proxy; production is served by server/index.mjs (static + reverse proxy to ABS_URL). Includes systemd unit and installer under deploy/. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
73
vite.config.ts
Normal file
73
vite.config.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { defineConfig, type Plugin } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import path from 'node:path'
|
||||
import http from 'node:http'
|
||||
import https from 'node:https'
|
||||
|
||||
/**
|
||||
* Dev-only dynamic proxy. The browser can't call an arbitrary ABS server directly
|
||||
* (it sends no CORS headers), so in dev the client routes every ABS request through
|
||||
* `/__abs/<url-encoded-server>/<path>`. This middleware decodes the target from the
|
||||
* path and forwards the request server-side — no CORS, and the target can change at
|
||||
* runtime (just log in to a different server). Production needs a real reverse proxy.
|
||||
*/
|
||||
function dynamicAbsProxy(): Plugin {
|
||||
return {
|
||||
name: 'shelfless-dynamic-abs-proxy',
|
||||
configureServer(server) {
|
||||
server.middlewares.use((req, res, next) => {
|
||||
if (!req.url?.startsWith('/__abs/')) return next()
|
||||
const m = req.url.match(/^\/__abs\/([^/]+)(.*)$/)
|
||||
if (!m) {
|
||||
res.statusCode = 400
|
||||
return res.end('Bad proxy path')
|
||||
}
|
||||
let target: URL
|
||||
try {
|
||||
target = new URL(decodeURIComponent(m[1]))
|
||||
} catch {
|
||||
res.statusCode = 400
|
||||
return res.end('Bad proxy target')
|
||||
}
|
||||
const isHttps = target.protocol === 'https:'
|
||||
const lib = isHttps ? https : http
|
||||
const headers = { ...req.headers, host: target.host }
|
||||
delete headers.origin
|
||||
delete headers.referer
|
||||
|
||||
const proxyReq = lib.request(
|
||||
{
|
||||
protocol: target.protocol,
|
||||
hostname: target.hostname,
|
||||
port: target.port || (isHttps ? 443 : 80),
|
||||
method: req.method,
|
||||
path: m[2] || '/',
|
||||
headers,
|
||||
},
|
||||
(proxyRes) => {
|
||||
res.writeHead(proxyRes.statusCode || 502, proxyRes.headers)
|
||||
proxyRes.pipe(res)
|
||||
},
|
||||
)
|
||||
proxyReq.on('error', (e) => {
|
||||
res.statusCode = 502
|
||||
res.end(`Proxy error: ${e.message}`)
|
||||
})
|
||||
req.pipe(proxyReq)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), dynamicAbsProxy()],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 5173,
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user