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>
74 lines
2.2 KiB
TypeScript
74 lines
2.2 KiB
TypeScript
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,
|
|
},
|
|
})
|