fix: Replace connect-gzip-static with express-static-gzip to become compatible with Node 23 (#24619)

Old versions of connect-gzip-static are not compatible with node 23,
new versions of connect-gzip-static are not compatible with node 18,
but we want to support both. express-static-gzip is compatible with
all versions.
This commit is contained in:
Boris Dolgov
2024-11-04 20:57:25 +01:00
committed by GitHub
parent 0c2cc10ad8
commit 87cee1aea6
5 changed files with 83 additions and 84 deletions
+10 -6
View File
@@ -9,7 +9,7 @@ import {posix} from 'path';
import {parse} from 'url';
import bind from 'bind-decorator';
import gzipStatic, {RequestHandler} from 'connect-gzip-static';
import expressStaticGzip, {RequestHandler} from 'express-static-gzip';
import finalhandler from 'finalhandler';
import stringify from 'json-stable-stringify-without-jsonify';
import WebSocket from 'ws';
@@ -73,13 +73,16 @@ export default class Frontend extends Extension {
override async start(): Promise<void> {
/* istanbul ignore next */
const options = {
setHeaders: (res: ServerResponse, path: string): void => {
if (path.endsWith('index.html')) {
res.setHeader('Cache-Control', 'no-store');
}
enableBrotli: true,
serveStatic: {
setHeaders: (res: ServerResponse, path: string): void => {
if (path.endsWith('index.html')) {
res.setHeader('Cache-Control', 'no-store');
}
},
},
};
this.fileServer = gzipStatic(frontend.getPath(), options);
this.fileServer = expressStaticGzip(frontend.getPath(), options);
this.wss = new WebSocket.Server({noServer: true, path: posix.join(this.baseUrl, 'api')});
this.wss.on('connection', this.onWebSocketConnection);
@@ -133,6 +136,7 @@ export default class Frontend extends Extension {
// This is necessary for the browser to resolve relative assets paths correctly.
request.originalUrl = request.url;
request.url = '/' + newUrl;
request.path = request.url;
this.fileServer(request, response, fin);
}
+3 -2
View File
@@ -5,11 +5,12 @@ declare module 'zigbee2mqtt-frontend' {
declare module 'http' {
interface IncomingMessage {
originalUrl?: string;
path?: string;
}
}
declare module 'connect-gzip-static' {
declare module 'express-static-gzip' {
import {IncomingMessage, ServerResponse} from 'http';
export type RequestHandler = (req: IncomingMessage, res: ServerResponse, finalhandler: (err: unknown) => void) => void;
export default function gzipStatic(root: string, options?: Record<string, unknown>): RequestHandler;
export default function expressStaticGzip(root: string, options?: Record<string, unknown>): RequestHandler;
}
+49 -68
View File
@@ -11,8 +11,8 @@
"dependencies": {
"ajv": "^8.17.1",
"bind-decorator": "^1.0.11",
"connect-gzip-static": "3.0.1",
"debounce": "^2.2.0",
"express-static-gzip": "^2.1.8",
"fast-deep-equal": "^3.1.3",
"finalhandler": "^1.3.1",
"git-last-commit": "^1.0.1",
@@ -57,6 +57,7 @@
"@types/object-assign-deep": "^0.4.3",
"@types/readable-stream": "4.0.18",
"@types/sd-notify": "^2.8.2",
"@types/serve-static": "^1.15.7",
"@types/ws": "8.5.13",
"babel-jest": "^29.7.0",
"eslint": "^9.14.0",
@@ -68,7 +69,7 @@
"typescript-eslint": "^8.12.2"
},
"engines": {
"node": "^18 || ^20 || ^22"
"node": "^18 || ^20 || ^22 || ^23"
},
"optionalDependencies": {
"sd-notify": "^2.8.0"
@@ -2149,20 +2150,6 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@folder/readdir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@folder/readdir/-/readdir-3.1.0.tgz",
"integrity": "sha512-mqkVdQ77BcCOWur/dxoPxjJS2eJg8Eqqx1Dgdc/qbTeGI5UMPDLmT0peGEjxLdlnD9SvhMnpJuiyaYl2btdT6A==",
"funding": [
"https://github.com/sponsors/jonschlinkert",
"https://paypal.me/jonathanschlinkert",
"https://jonschlinkert.dev/sponsor"
],
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -3056,6 +3043,13 @@
"@types/node": "*"
}
},
"node_modules/@types/http-errors": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/humanize-duration": {
"version": "3.27.4",
"resolved": "https://registry.npmjs.org/@types/humanize-duration/-/humanize-duration-3.27.4.tgz",
@@ -3115,6 +3109,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.8.6",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.6.tgz",
@@ -3148,6 +3149,29 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/send": {
"version": "0.17.4",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
"integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/mime": "^1",
"@types/node": "*"
}
},
"node_modules/@types/serve-static": {
"version": "1.15.7",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
"integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/http-errors": "*",
"@types/node": "*",
"@types/send": "*"
}
},
"node_modules/@types/stack-utils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
@@ -4182,19 +4206,6 @@
"node": ">= 6"
}
},
"node_modules/connect-gzip-static": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/connect-gzip-static/-/connect-gzip-static-3.0.1.tgz",
"integrity": "sha512-VVqi1fJ4uZK8aIrb1BN9n7tsRAbc3BhkIo/mz1VuNhYC2KDuP1ZgCDapjYIfT3mcGgWzyUr04kv7nmnGOnCvWg==",
"license": "MIT",
"dependencies": {
"@folder/readdir": "^3.1.0",
"debug": "~2||~3||~4",
"parseurl": "~1",
"send": "~0",
"serve-static": "~1"
}
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
@@ -4813,6 +4824,15 @@
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/express-static-gzip": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/express-static-gzip/-/express-static-gzip-2.1.8.tgz",
"integrity": "sha512-g8tiJuI9Y9Ffy59ehVXvqb0hhP83JwZiLxzanobPaMbkB5qBWA8nuVgd+rcd5qzH3GkgogTALlc0BaADYwnMbQ==",
"license": "MIT",
"dependencies": {
"serve-static": "^1.16.2"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -7563,45 +7583,6 @@
"node": ">=10"
}
},
"node_modules/send": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.1.tgz",
"integrity": "sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "2.4.1",
"range-parser": "~1.2.1",
"statuses": "2.0.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/send/node_modules/debug/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/serve-static": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+3 -2
View File
@@ -8,7 +8,7 @@
"url": "git+https://github.com/Koenkk/zigbee2mqtt.git"
},
"engines": {
"node": "^18 || ^20 || ^22"
"node": "^18 || ^20 || ^22 || ^23"
},
"keywords": [
"xiaomi",
@@ -39,8 +39,8 @@
"dependencies": {
"ajv": "^8.17.1",
"bind-decorator": "^1.0.11",
"connect-gzip-static": "3.0.1",
"debounce": "^2.2.0",
"express-static-gzip": "^2.1.8",
"fast-deep-equal": "^3.1.3",
"finalhandler": "^1.3.1",
"git-last-commit": "^1.0.1",
@@ -82,6 +82,7 @@
"@types/object-assign-deep": "^0.4.3",
"@types/readable-stream": "4.0.18",
"@types/sd-notify": "^2.8.2",
"@types/serve-static": "^1.15.7",
"@types/ws": "8.5.13",
"babel-jest": "^29.7.0",
"eslint": "^9.14.0",
+18 -6
View File
@@ -86,7 +86,7 @@ jest.mock('https', () => ({
Agent: jest.fn(),
}));
jest.mock('connect-gzip-static', () =>
jest.mock('express-static-gzip', () =>
jest.fn().mockImplementation((path) => {
mockNodeStatic.variables.path = path;
return mockNodeStatic.implementation;
@@ -321,7 +321,11 @@ describe('Frontend', () => {
mockHTTP.variables.onRequest({url: '/file.txt'}, 2);
expect(mockNodeStatic.implementation).toHaveBeenCalledTimes(1);
expect(mockNodeStatic.implementation).toHaveBeenCalledWith({originalUrl: '/file.txt', url: '/file.txt'}, 2, expect.any(Function));
expect(mockNodeStatic.implementation).toHaveBeenCalledWith(
{originalUrl: '/file.txt', url: '/file.txt', path: '/file.txt'},
2,
expect.any(Function),
);
});
it('Static server', async () => {
@@ -367,14 +371,18 @@ describe('Frontend', () => {
mockHTTP.variables.onRequest({url: '/z2m'}, 2);
expect(mockNodeStatic.implementation).toHaveBeenCalledTimes(1);
expect(mockNodeStatic.implementation).toHaveBeenCalledWith({originalUrl: '/z2m', url: '/'}, 2, expect.any(Function));
expect(mockNodeStatic.implementation).toHaveBeenCalledWith({originalUrl: '/z2m', url: '/', path: '/'}, 2, expect.any(Function));
expect(mockFinalHandler.implementation).not.toHaveBeenCalledWith();
mockNodeStatic.implementation.mockReset();
expect(mockFinalHandler.implementation).not.toHaveBeenCalledWith();
mockHTTP.variables.onRequest({url: '/z2m/file.txt'}, 2);
expect(mockNodeStatic.implementation).toHaveBeenCalledTimes(1);
expect(mockNodeStatic.implementation).toHaveBeenCalledWith({originalUrl: '/z2m/file.txt', url: '/file.txt'}, 2, expect.any(Function));
expect(mockNodeStatic.implementation).toHaveBeenCalledWith(
{originalUrl: '/z2m/file.txt', url: '/file.txt', path: '/file.txt'},
2,
expect.any(Function),
);
expect(mockFinalHandler.implementation).not.toHaveBeenCalledWith();
mockNodeStatic.implementation.mockReset();
@@ -393,7 +401,11 @@ describe('Frontend', () => {
mockHTTP.variables.onRequest({url: '/z2m-more++/c0mplex.url'}, 2);
expect(mockNodeStatic.implementation).toHaveBeenCalledTimes(1);
expect(mockNodeStatic.implementation).toHaveBeenCalledWith({originalUrl: '/z2m-more++/c0mplex.url', url: '/'}, 2, expect.any(Function));
expect(mockNodeStatic.implementation).toHaveBeenCalledWith(
{originalUrl: '/z2m-more++/c0mplex.url', url: '/', path: '/'},
2,
expect.any(Function),
);
expect(mockFinalHandler.implementation).not.toHaveBeenCalledWith();
mockNodeStatic.implementation.mockReset();
@@ -401,7 +413,7 @@ describe('Frontend', () => {
mockHTTP.variables.onRequest({url: '/z2m-more++/c0mplex.url/file.txt'}, 2);
expect(mockNodeStatic.implementation).toHaveBeenCalledTimes(1);
expect(mockNodeStatic.implementation).toHaveBeenCalledWith(
{originalUrl: '/z2m-more++/c0mplex.url/file.txt', url: '/file.txt'},
{originalUrl: '/z2m-more++/c0mplex.url/file.txt', url: '/file.txt', path: '/file.txt'},
2,
expect.any(Function),
);