multi-scrobbler/src/backend/server/index.ts
2026-06-12 17:57:54 +00:00

137 lines
No EOL
5 KiB
TypeScript

import express, { Router } from 'express';
import { childLogger, LogDataPretty, Logger } from "@foxxmd/logging";
import bodyParser from 'body-parser';
import { stripIndents } from "common-tags";
import session from 'express-session';
import { PassThrough } from "node:stream";
import passport from 'passport';
import path from "path";
import ViteExpress from "vite-express";
import { projectDir } from "../common/index.js";
import { getRoot } from "../ioc.js";
import { parseBool } from "../utils.js";
import { getAddress } from "../utils/NetworkUtils.js";
import { setupApi } from "./api.js";
import ScrobbleSources from '../sources/ScrobbleSources.js';
import ScrobbleClients from '../scrobblers/ScrobbleClients.js';
const app = express();
const router = Router();
export const initServer = async (parentLogger: Logger, appLoggerStream: PassThrough, initialOutput: LogDataPretty[] = [], sources: ScrobbleSources, clients: ScrobbleClients) => {
const logger = childLogger(parentLogger, 'API'); // parentLogger.child({labels: ['API']}, mergeArr);
try {
app.use(router);
app.use(bodyParser.json());
app.use(
bodyParser.urlencoded({
extended: true,
})
);
//app.use(express.static(buildDir));
app.use(session({secret: 'keyboard cat', resave: false, saveUninitialized: false}));
app.use(passport.initialize());
app.use(passport.session());
const root = getRoot();
if(root.get('disableWeb')) {
logger.warn('API and Dashboard have been DISABLED. Note that any ingress sources (Webscrobbler, Listenbrainz/Lastfm Endpoint Sources, etc...) will be unusable');
return;
}
const isProd = root.get('isProd');
const port = root.get('port');
const local = root.get('localUrl');
const localDefined = root.get('hasDefinedBaseUrl');
setupApi(app, logger, appLoggerStream, initialOutput, sources, clients);
const addy = getAddress();
const addresses: string[] = [];
let dockerHint = '';
if (parseBool(process.env.IS_DOCKER) && addy.v4 !== undefined && addy.v4.includes('172')) {
dockerHint = stripIndents`
--- HINT ---
MS is likely being run in a container with BRIDGE networking which means the above addresses are not accessible from outside this container.
To ensure the container is accessible make sure you have mapped the *container* port ${port} to a *host* port. https://docs.multi-scrobbler.app/installation/?dockerSetting=networking#recommended-settings
The container will then be accessible at http://HOST_MACHINE_IP:HOST_PORT${localDefined ? ` (or ${local} since you defined this!)` : ''}
--- HINT ---
`;
}
for (const [k, v] of Object.entries(addy)) {
if (v !== undefined) {
switch (k) {
case 'host':
case 'v4':
addresses.push(`---> ${k === 'host' ? 'Local'.padEnd(14, ' ') : 'Network'.padEnd(14, ' ')} http://${v}:${port}`);
break;
case 'v6':
addresses.push(`---> Network (IPv6) http://[${v}]:${port}`);
}
}
}
app.use('/docs', express.static(path.resolve(projectDir, `./docsite/build`)));
if(process.env.USE_HASH_ROUTER === undefined) {
process.env.USE_HASH_ROUTER = root.get('isSubPath').toString();
}
app.get(/^\/next$/, (_, res) => {
res.redirect("/next/")
});
const viteExpressOptions: Parameters<typeof ViteExpress.config>[0] = {
mode: isProd ? 'production' : 'development',
inlineViteConfig: {
base: localDefined && local.pathname !== '/' ? local.toString() : '/'
}
};
if(!isProd) {
const conf = await ViteExpress.getViteConfig();
viteExpressOptions.inlineViteConfig = {
...viteExpressOptions.inlineViteConfig,
...conf
};
}
ViteExpress.config(viteExpressOptions);
try {
ViteExpress.listen(app, port, () => {
const start = stripIndents`\n
Server started:
${addresses.join('\n')}${dockerHint !== '' ? `\n${dockerHint}` : ''}`
logger.info(start);
if(localDefined) {
logger.info(`User-defined base URL for UI and redirect URLs (spotify, deezer, lastfm): ${local}`)
}
}).on('error', (err) => {
throw new Error('Server encountered unrecoverable error', {cause: err});
});
} catch (e) {
throw new Error('Server encountered unrecoverable error', {cause: e});
}
} catch (e) {
throw new Error('Server crashed with uncaught exception', {cause: e});
}
}
type ViteConfig = {
root: string;
base: string;
build: {
outDir: string;
};
server?: {
hmr?: boolean;
};
};