cleanup of app.js, wwww

This commit is contained in:
zadam 2023-05-07 15:23:46 +02:00
parent 9f5f0aeddd
commit 64db5e2542
8 changed files with 535 additions and 459 deletions

705
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -57,18 +57,18 @@
"express-rate-limit": "6.7.0",
"express-session": "1.17.3",
"fs-extra": "11.1.1",
"helmet": "6.1.5",
"helmet": "7.0.0",
"html": "1.0.0",
"html2plaintext": "2.1.4",
"http-proxy-agent": "5.0.0",
"https-proxy-agent": "5.0.1",
"http-proxy-agent": "6.0.1",
"https-proxy-agent": "6.1.0",
"image-type": "4.1.0",
"ini": "3.0.1",
"is-animated": "2.0.2",
"is-svg": "4.3.2",
"jimp": "0.22.7",
"joplin-turndown-plugin-gfm": "1.0.12",
"jsdom": "21.1.1",
"jsdom": "22.0.0",
"mime-types": "2.1.35",
"multer": "1.4.5-lts.1",
"node-abi": "3.40.0",
@ -97,28 +97,28 @@
},
"devDependencies": {
"cross-env": "7.0.3",
"electron": "25.0.0-beta.1",
"electron": "25.0.0-beta.2",
"electron-builder": "23.6.0",
"electron-packager": "17.1.1",
"electron-rebuild": "3.2.9",
"eslint": "8.39.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsonc": "^2.7.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint": "8.40.0",
"eslint-config-airbnb-base": "15.0.0",
"eslint-config-prettier": "8.8.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-jsonc": "2.7.0",
"eslint-plugin-prettier": "4.2.1",
"esm": "3.2.25",
"husky": "8.0.3",
"jsonc-eslint-parser": "^2.2.0",
"lint-staged": "^13.2.1",
"jsonc-eslint-parser": "2.2.0",
"lint-staged": "13.2.2",
"jasmine": "4.6.0",
"jsdoc": "4.0.2",
"lorem-ipsum": "2.0.8",
"prettier": "2.8.8",
"nodemon": "2.0.22",
"rcedit": "3.0.1",
"webpack": "5.81.0",
"webpack-cli": "5.0.2"
"webpack": "5.82.0",
"webpack-cli": "5.1.0"
},
"optionalDependencies": {
"electron-installer-debian": "3.1.0"

View File

@ -1,17 +1,12 @@
const log = require('./services/log');
const express = require('express');
const path = require('path');
const favicon = require('serve-favicon');
const cookieParser = require('cookie-parser');
const helmet = require('helmet');
const session = require('express-session');
const compression = require('compression');
const FileStore = require('session-file-store')(session);
const sessionSecret = require('./services/session_secret');
const dataDir = require('./services/data_dir');
const sessionParser = require('./routes/session_parser');
const utils = require('./services/utils');
const assetPath = require('./services/asset_path');
const env = require('./services/env');
require('./services/handlers');
require('./becca/becca_loader');
@ -31,99 +26,21 @@ app.use(helmet({
crossOriginEmbedderPolicy: false
}));
const persistentCacheStatic = (root, options) => {
if (!env.isDev()) {
options = {
maxAge: '1y',
...options
};
}
return express.static(root, options);
};
app.use(express.text({limit: '500mb'}));
app.use(express.json({limit: '500mb'}));
app.use(express.raw({limit: '500mb'}));
app.use(express.urlencoded({extended: false}));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public/root')));
app.use(`/${assetPath}/app`, persistentCacheStatic(path.join(__dirname, 'public/app')));
app.use(`/${assetPath}/app-dist`, persistentCacheStatic(path.join(__dirname, 'public/app-dist')));
app.use(`/${assetPath}/fonts`, persistentCacheStatic(path.join(__dirname, 'public/fonts')));
app.use(`/assets/vX/fonts`, express.static(path.join(__dirname, 'public/fonts')));
app.use(`/${assetPath}/stylesheets`, persistentCacheStatic(path.join(__dirname, 'public/stylesheets')));
app.use(`/assets/vX/stylesheets`, express.static(path.join(__dirname, 'public/stylesheets')));
app.use(`/${assetPath}/libraries`, persistentCacheStatic(path.join(__dirname, '..', 'libraries')));
app.use(`/assets/vX/libraries`, express.static(path.join(__dirname, '..', 'libraries')));
// excalidraw-view mode in shared notes
app.use(`/${assetPath}/node_modules/react/umd/react.production.min.js`, persistentCacheStatic(path.join(__dirname, '..', 'node_modules/react/umd/react.production.min.js')));
app.use(`/${assetPath}/node_modules/react-dom/umd/react-dom.production.min.js`, persistentCacheStatic(path.join(__dirname, '..', 'node_modules/react-dom/umd/react-dom.production.min.js')));
// expose whole dist folder since complete assets are needed in edit and share
app.use(`/node_modules/@excalidraw/excalidraw/dist/`, express.static(path.join(__dirname, '..', 'node_modules/@excalidraw/excalidraw/dist/')));
app.use(`/${assetPath}/node_modules/@excalidraw/excalidraw/dist/`, persistentCacheStatic(path.join(__dirname, '..', 'node_modules/@excalidraw/excalidraw/dist/')));
app.use(`/${assetPath}/images`, persistentCacheStatic(path.join(__dirname, '..', 'images')));
app.use(`/assets/vX/images`, express.static(path.join(__dirname, '..', 'images')));
app.use(`/manifest.webmanifest`, express.static(path.join(__dirname, 'public/manifest.webmanifest')));
app.use(`/robots.txt`, express.static(path.join(__dirname, 'public/robots.txt')));
const sessionParser = session({
secret: sessionSecret,
resave: false, // true forces the session to be saved back to the session store, even if the session was never modified during the request.
saveUninitialized: false, // true forces a session that is "uninitialized" to be saved to the store. A session is uninitialized when it is new but not modified.
cookie: {
// path: "/",
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // in milliseconds
},
name: 'trilium.sid',
store: new FileStore({
ttl: 30 * 24 * 3600,
path: `${dataDir.TRILIUM_DATA_DIR}/sessions`
})
});
app.use(sessionParser);
app.use(favicon(`${__dirname}/../images/app-icons/win/icon.ico`));
require('./routes/assets').register(app);
require('./routes/routes').register(app);
require('./routes/custom').register(app);
app.use((err, req, res, next) => {
if (err.code !== 'EBADCSRFTOKEN') {
return next(err);
}
log.error(`Invalid CSRF token: ${req.headers['x-csrf-token']}, secret: ${req.cookies['_csrf']}`);
err = new Error('Invalid CSRF token');
err.status = 403;
next(err);
});
// catch 404 and forward to error handler
app.use((req, res, next) => {
const err = new Error(`Router not found for request ${req.url}`);
err.status = 404;
next(err);
});
// error handler
app.use((err, req, res, next) => {
if (err && err.message && (
(err.message.includes("Router not found for request") && err.message.includes(".js.map"))
|| (err.message.includes("Router not found for request") && err.message.includes(".css.map"))
)) {
// ignore
}
else {
log.info(err);
}
res.status(err.status || 500);
res.send({
message: err.message
});
});
require('./routes/error_handlers').register(app);
// triggers sync timer
require('./services/sync');
@ -140,7 +57,4 @@ if (utils.isElectron()) {
require('@electron/remote/main').initialize();
}
module.exports = {
app,
sessionParser
};
module.exports = app;

38
src/routes/assets.js Normal file
View File

@ -0,0 +1,38 @@
const assetPath = require("../services/asset_path.js");
const path = require("path");
const express = require("express");
const env = require("../services/env.js");
const persistentCacheStatic = (root, options) => {
if (!env.isDev()) {
options = {
maxAge: '1y',
...options
};
}
return express.static(root, options);
};
function register(app) {
const srcRoot = path.join(__dirname, '..');
app.use(`/${assetPath}/app`, persistentCacheStatic(path.join(srcRoot, 'public/app')));
app.use(`/${assetPath}/app-dist`, persistentCacheStatic(path.join(srcRoot, 'public/app-dist')));
app.use(`/${assetPath}/fonts`, persistentCacheStatic(path.join(srcRoot, 'public/fonts')));
app.use(`/assets/vX/fonts`, express.static(path.join(srcRoot, 'public/fonts')));
app.use(`/${assetPath}/stylesheets`, persistentCacheStatic(path.join(srcRoot, 'public/stylesheets')));
app.use(`/assets/vX/stylesheets`, express.static(path.join(srcRoot, 'public/stylesheets')));
app.use(`/${assetPath}/libraries`, persistentCacheStatic(path.join(srcRoot, '..', 'libraries')));
app.use(`/assets/vX/libraries`, express.static(path.join(srcRoot, '..', 'libraries')));
// excalidraw-view mode in shared notes
app.use(`/${assetPath}/node_modules/react/umd/react.production.min.js`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/react/umd/react.production.min.js')));
app.use(`/${assetPath}/node_modules/react-dom/umd/react-dom.production.min.js`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/react-dom/umd/react-dom.production.min.js')));
// expose the whole dist folder since complete assets are needed in edit and share
app.use(`/node_modules/@excalidraw/excalidraw/dist/`, express.static(path.join(srcRoot, '..', 'node_modules/@excalidraw/excalidraw/dist/')));
app.use(`/${assetPath}/node_modules/@excalidraw/excalidraw/dist/`, persistentCacheStatic(path.join(srcRoot, '..', 'node_modules/@excalidraw/excalidraw/dist/')));
app.use(`/${assetPath}/images`, persistentCacheStatic(path.join(srcRoot, '..', 'images')));
app.use(`/assets/vX/images`, express.static(path.join(srcRoot, '..', 'images')));
}
module.exports = {
register
};

View File

@ -0,0 +1,43 @@
const log = require("../services/log");
function register(app) {
app.use((err, req, res, next) => {
if (err.code !== 'EBADCSRFTOKEN') {
return next(err);
}
log.error(`Invalid CSRF token: ${req.headers['x-csrf-token']}, secret: ${req.cookies['_csrf']}`);
err = new Error('Invalid CSRF token');
err.status = 403;
next(err);
});
// catch 404 and forward to error handler
app.use((req, res, next) => {
const err = new Error(`Router not found for request ${req.url}`);
err.status = 404;
next(err);
});
// error handler
app.use((err, req, res, next) => {
if (err && err.message && (
(err.message.includes("Router not found for request") && err.message.includes(".js.map"))
|| (err.message.includes("Router not found for request") && err.message.includes(".css.map"))
)) {
// ignore
} else {
log.info(err);
}
res.status(err.status || 500);
res.send({
message: err.message
});
});
}
module.exports = {
register
};

View File

@ -0,0 +1,22 @@
const session = require("express-session");
const sessionSecret = require("../services/session_secret.js");
const dataDir = require("../services/data_dir.js");
const FileStore = require('session-file-store')(session);
const sessionParser = session({
secret: sessionSecret,
resave: false, // true forces the session to be saved back to the session store, even if the session was never modified during the request.
saveUninitialized: false, // true forces a session that is "uninitialized" to be saved to the store. A session is uninitialized when it is new but not modified.
cookie: {
// path: "/",
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // in milliseconds
},
name: 'trilium.sid',
store: new FileStore({
ttl: 30 * 24 * 3600,
path: `${dataDir.TRILIUM_DATA_DIR}/sessions`
})
});
module.exports = sessionParser;

View File

@ -99,8 +99,8 @@ async function createInitialDatabase() {
await zipImportService.importZip(dummyTaskContext, demoFile, rootNote);
sql.transactional(() => {
// this needs to happen after ZIP import
// previous solution was to move option initialization here but then the important parts of initialization
// this needs to happen after ZIP import,
// the previous solution was to move option initialization here, but then the important parts of initialization
// are not all in one transaction (because ZIP import is async and thus not transactional)
const startNoteId = sql.getValue("SELECT noteId FROM branches WHERE parentNoteId = 'root' AND isDeleted = 0 ORDER BY notePosition");

56
src/www
View File

@ -1,5 +1,7 @@
#!/usr/bin/env node
// setup basic error handling even before requiring dependencies, since those can produce errors as well
process.on('unhandledRejection', error => {
// this makes sure that stacktrace of failed promise is printed out
console.log(error);
@ -16,7 +18,8 @@ function exit() {
process.on('SIGINT', exit);
process.on('SIGTERM', exit);
const { app, sessionParser } = require('./app');
const app = require('./app');
const sessionParser = require('./routes/session_parser');
const fs = require('fs');
const http = require('http');
const https = require('https');
@ -25,7 +28,6 @@ const log = require('./services/log');
const appInfo = require('./services/app_info');
const ws = require('./services/ws');
const utils = require('./services/utils');
const sqlInit = require('./services/sql_init');
const port = require('./services/port');
const host = require('./services/host');
const semver = require('semver');
@ -35,9 +37,9 @@ if (!semver.satisfies(process.version, ">=10.5.0")) {
process.exit(1);
}
let httpServer;
startTrilium();
async function startTrilium() {
function startTrilium() {
/**
* The intended behavior is to detect when a second instance is running, in that case open the old instance
* instead of the new one. This is complicated by the fact that it is possible to run multiple instances of Trilium
@ -48,13 +50,29 @@ async function startTrilium() {
*
* A bit weird is that "second-instance" is triggered also on the valid usecases (different port/data dir) and
* focuses the existing window. But the new process is start as well and will steal the focus too, it will win, because
* its startup is slower than focusing the existing process/window. So in the end it works out without having
* to do complex evaluation.
* its startup is slower than focusing the existing process/window. So in the end, it works out without having
* to do a complex evaluation.
*/
if (utils.isElectron()) {
require("electron").app.requestSingleInstanceLock();
}
log.info(JSON.stringify(appInfo, null, 2));
const cpuInfos = require('os').cpus();
log.info(`CPU model: ${cpuInfos[0].model}, logical cores: ${cpuInfos.length} freq: ${cpuInfos[0].speed} Mhz`); // for perf. issues it's good to know the rough configuration
const httpServer = startHttpServer();
ws.init(httpServer, sessionParser);
if (utils.isElectron()) {
const electronRouting = require('./routes/electron');
electronRouting(app);
}
}
function startHttpServer() {
app.set('port', port);
app.set('host', host);
@ -64,8 +82,11 @@ async function startTrilium() {
app.set('trust proxy', config['Network']['trustedReverseProxy'])
}
}
log.info(`Trusted reverse proxy: ${app.get('trust proxy')}`)
let httpServer;
if (config['Network']['https']) {
if (!config['Network']['keyPath'] || !config['Network']['keyPath'].trim().length) {
throw new Error("keyPath in config.ini is required when https=true, but it's empty");
@ -83,29 +104,25 @@ async function startTrilium() {
httpServer = https.createServer(options, app);
log.info(`App HTTPS server starting up at port ${port}`);
}
else {
} else {
httpServer = http.createServer(app);
log.info(`App HTTP server starting up at port ${port}`);
}
log.info(JSON.stringify(appInfo, null, 2));
const cpuInfos = require('os').cpus();
log.info(`CPU model: ${cpuInfos[0].model}, logical cores: ${cpuInfos.length} freq: ${cpuInfos[0].speed} Mhz`); // for perf. issues it's good to know the rough configuration
/**
* Listen on provided port, on all network interfaces.
*/
httpServer.keepAliveTimeout = 120000 * 5;
const listenOnTcp = port !== 0;
if (listenOnTcp) {
httpServer.listen(port, host); // TCP socket.
httpServer.listen(port, host); // TCP socket.
} else {
httpServer.listen(host); // Unix socket.
httpServer.listen(host); // Unix socket.
}
httpServer.on('error', error => {
if (!listenOnTcp || error.syscall !== 'listen') {
throw error;
@ -137,12 +154,5 @@ async function startTrilium() {
}
});
ws.init(httpServer, sessionParser);
if (utils.isElectron()) {
const electronRouting = require('./routes/electron');
electronRouting(app);
}
return httpServer;
}
startTrilium();