Skip to content
This repository was archived by the owner on Jun 24, 2025. It is now read-only.

Commit cedf6cc

Browse files
authored
Merge pull request #1888 from TriliumNext/renovate/express-5.x
fix(deps): update dependency express to v5
2 parents 70cdc10 + 4def18e commit cedf6cc

7 files changed

Lines changed: 349 additions & 78 deletions

File tree

_regroup/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"devDependencies": {
3838
"@playwright/test": "1.52.0",
3939
"@stylistic/eslint-plugin": "4.4.1",
40-
"@types/express": "5.0.1",
40+
"@types/express": "5.0.3",
4141
"@types/node": "22.15.30",
4242
"@types/yargs": "17.0.33",
4343
"@vitest/coverage-v8": "3.2.2",

apps/server/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
"electron-debug": "4.1.0",
6464
"electron-window-state": "5.0.3",
6565
"escape-html": "1.0.3",
66-
"express": "4.21.2",
66+
"express": "5.1.0",
6767
"express-openid-connect": "^2.17.1",
6868
"express-rate-limit": "7.5.0",
6969
"express-session": "1.18.1",

apps/server/src/routes/custom.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,26 @@ import type { Request, Response, Router } from "express";
88
import { safeExtractMessageAndStackFromError } from "../services/utils.js";
99

1010
function handleRequest(req: Request, res: Response) {
11-
// express puts content after first slash into 0 index element
1211

13-
const path = req.params.path + req.params[0];
12+
// handle path from "*path" route wildcard
13+
// in express v4, you could just add
14+
// req.params.path + req.params[0], but with v5
15+
// we get a split array that we have to join ourselves again
16+
17+
// @TriliumNextTODO: remove typecasting once express types are fixed
18+
// they currently only treat req.params as string, while in reality
19+
// it can also be a string[], when using wildcards
20+
const splitPath = req.params.path as unknown as string[];
21+
22+
//const path = splitPath.map(segment => encodeURIComponent(segment)).join("/")
23+
// naively join the "decoded" paths using a slash
24+
// this is to mimick handleRequest behaviour
25+
// as with the previous express v4.
26+
// @TriliumNextTODO: using something like =>
27+
// splitPath.map(segment => encodeURIComponent(segment)).join("/")
28+
// might be safer
29+
30+
const path = splitPath.join("/")
1431

1532
const attributeIds = sql.getColumn<string>("SELECT attributeId FROM attributes WHERE isDeleted = 0 AND type = 'label' AND name IN ('customRequestHandler', 'customResourceProvider')");
1633

@@ -70,7 +87,7 @@ function handleRequest(req: Request, res: Response) {
7087
function register(router: Router) {
7188
// explicitly no CSRF middleware since it's meant to allow integration from external services
7289

73-
router.all("/custom/:path*", (req: Request, res: Response, _next) => {
90+
router.all("/custom/*path", (req: Request, res: Response, _next) => {
7491
cls.namespace.bindEmitter(req);
7592
cls.namespace.bindEmitter(res);
7693

apps/server/src/routes/electron.ts

Lines changed: 115 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,123 @@
11
import electron from "electron";
2+
import type { Application } from "express";
3+
import type { ParamsDictionary, Request, Response } from "express-serve-static-core";
4+
import type QueryString from "qs";
5+
import { Session, SessionData } from "express-session";
6+
import { parse as parseQuery } from "qs";
7+
import EventEmitter from "events";
28

3-
interface Response {
4-
statusCode: number;
5-
getHeader: (name: string) => string;
6-
setHeader: (name: string, value: string) => Response;
7-
header: (name: string, value: string) => Response;
8-
status: (statusCode: number) => Response;
9-
send: (obj: {}) => void; // eslint-disable-line @typescript-eslint/no-empty-object-type
10-
}
9+
type MockedResponse = Response<any, Record<string, any>, number>;
1110

12-
function init(app: Express.Application) {
11+
function init(app: Application) {
1312
electron.ipcMain.on("server-request", (event, arg) => {
14-
const req = {
15-
url: arg.url,
16-
method: arg.method,
17-
body: arg.data,
18-
headers: arg.headers,
19-
session: {
20-
loggedIn: true
21-
}
22-
};
23-
24-
const respHeaders: Record<string, string> = {};
25-
26-
const res: Response = {
27-
statusCode: 200,
28-
getHeader: (name) => respHeaders[name],
29-
setHeader: (name, value) => {
30-
respHeaders[name] = value.toString();
31-
return res;
32-
},
33-
header: (name, value) => {
34-
respHeaders[name] = value.toString();
35-
return res;
36-
},
37-
status: (statusCode) => {
38-
res.statusCode = statusCode;
39-
return res;
40-
},
41-
send: (obj) => {
42-
event.sender.send("server-response", {
43-
url: arg.url,
44-
method: arg.method,
45-
requestId: arg.requestId,
46-
statusCode: res.statusCode,
47-
headers: respHeaders,
48-
body: obj
49-
});
50-
}
51-
};
52-
53-
return (app as any)._router.handle(req, res, () => {});
13+
const req = new FakeRequest(arg);
14+
const res = new FakeResponse(event, arg);
15+
16+
return app.router(req as any, res as any, () => {});
5417
});
5518
}
5619

20+
const fakeSession: Session & Partial<SessionData> = {
21+
id: "session-id", // Placeholder for session ID
22+
cookie: {
23+
originalMaxAge: 3600000, // 1 hour
24+
},
25+
loggedIn: true,
26+
regenerate(callback) {
27+
callback?.(null);
28+
return fakeSession;
29+
},
30+
destroy(callback) {
31+
callback?.(null);
32+
return fakeSession;
33+
},
34+
reload(callback) {
35+
callback?.(null);
36+
return fakeSession;
37+
},
38+
save(callback) {
39+
callback?.(null);
40+
return fakeSession;
41+
},
42+
resetMaxAge: () => fakeSession,
43+
touch: () => fakeSession
44+
};
45+
46+
interface Arg {
47+
url: string;
48+
method: string;
49+
data: any;
50+
headers: Record<string, string>
51+
}
52+
53+
class FakeRequest extends EventEmitter implements Pick<Request<ParamsDictionary, any, any, QueryString.ParsedQs, Record<string, any>>, "url" | "method" | "body" | "headers" | "session" | "query"> {
54+
url: string;
55+
method: string;
56+
body: any;
57+
headers: Record<string, string>;
58+
session: Session & Partial<SessionData>;
59+
query: Record<string, any>;
60+
61+
constructor(arg: Arg) {
62+
super();
63+
this.url = arg.url;
64+
this.method = arg.method;
65+
this.body = arg.data;
66+
this.headers = arg.headers;
67+
this.session = fakeSession;
68+
this.query = parseQuery(arg.url.split("?")[1] || "", { ignoreQueryPrefix: true });
69+
}
70+
}
71+
72+
class FakeResponse extends EventEmitter implements Pick<Response<any, Record<string, any>, number>, "status" | "send" | "json" | "setHeader"> {
73+
private respHeaders: Record<string, string | string[]> = {};
74+
private event: Electron.IpcMainEvent;
75+
private arg: Arg & { requestId: string; };
76+
statusCode: number = 200;
77+
headers: Record<string, string> = {};
78+
locals: Record<string, any> = {};
79+
80+
constructor(event: Electron.IpcMainEvent, arg: Arg & { requestId: string; }) {
81+
super();
82+
this.event = event;
83+
this.arg = arg;
84+
}
85+
86+
getHeader(name) {
87+
return this.respHeaders[name];
88+
}
89+
90+
setHeader(name, value) {
91+
this.respHeaders[name] = value.toString();
92+
return this as unknown as MockedResponse;
93+
}
94+
95+
header(name: string, value?: string | string[]) {
96+
this.respHeaders[name] = value ?? "";
97+
return this as unknown as MockedResponse;
98+
}
99+
100+
status(statusCode) {
101+
this.statusCode = statusCode;
102+
return this as unknown as MockedResponse;
103+
}
104+
105+
send(obj) {
106+
this.event.sender.send("server-response", {
107+
url: this.arg.url,
108+
method: this.arg.method,
109+
requestId: this.arg.requestId,
110+
statusCode: this.statusCode,
111+
headers: this.respHeaders,
112+
body: obj
113+
});
114+
return this as unknown as MockedResponse;
115+
}
116+
117+
json(obj) {
118+
this.send(JSON.stringify(obj));
119+
return this as unknown as MockedResponse;
120+
}
121+
}
122+
57123
export default init;

apps/server/src/routes/routes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ function register(app: express.Application) {
216216

217217
apiRoute(GET, "/api/options", optionsApiRoute.getOptions);
218218
// FIXME: possibly change to sending value in the body to avoid host of HTTP server issues with slashes
219-
apiRoute(PUT, "/api/options/:name/:value*", optionsApiRoute.updateOption);
219+
apiRoute(PUT, "/api/options/:name/:value", optionsApiRoute.updateOption);
220220
apiRoute(PUT, "/api/options", optionsApiRoute.updateOptions);
221221
apiRoute(GET, "/api/options/user-themes", optionsApiRoute.getUserThemes);
222222
apiRoute(GET, "/api/options/locales", optionsApiRoute.getSupportedLocales);

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"@nx/web": "21.1.3",
4040
"@playwright/test": "^1.36.0",
4141
"@triliumnext/server": "workspace:*",
42-
"@types/express": "^4.17.21",
42+
"@types/express": "^5.0.0",
4343
"@types/node": "22.15.30",
4444
"@vitest/coverage-v8": "^3.0.5",
4545
"@vitest/ui": "^3.0.0",
@@ -81,7 +81,7 @@
8181
"homepage": "https://github.com/TriliumNext/Notes#readme",
8282
"dependencies": {
8383
"axios": "^1.6.0",
84-
"express": "^4.21.2"
84+
"express": "^5.0.0"
8585
},
8686
"packageManager": "pnpm@10.11.1+sha512.e519b9f7639869dc8d5c3c5dfef73b3f091094b0a006d7317353c72b124e80e1afd429732e28705ad6bfa1ee879c1fce46c128ccebd3192101f43dd67c667912",
8787
"pnpm": {

0 commit comments

Comments
 (0)