Skip to content

Commit

Permalink
fix: Replace connect-gzip-static with express-static-gzip to become c…
Browse files Browse the repository at this point in the history
…ompatible 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.
  • Loading branch information
bdolgov authored Nov 4, 2024
1 parent 0c2cc10 commit 87cee1a
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 84 deletions.
16 changes: 10 additions & 6 deletions lib/extension/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down
5 changes: 3 additions & 2 deletions lib/types/zigbee2mqtt-frontend.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
117 changes: 49 additions & 68 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"url": "git+https://github.com/Koenkk/zigbee2mqtt.git"
},
"engines": {
"node": "^18 || ^20 || ^22"
"node": "^18 || ^20 || ^22 || ^23"
},
"keywords": [
"xiaomi",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
24 changes: 18 additions & 6 deletions test/frontend.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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();
Expand All @@ -393,15 +401,19 @@ 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();
expect(mockFinalHandler.implementation).not.toHaveBeenCalledWith();
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),
);
Expand Down

0 comments on commit 87cee1a

Please sign in to comment.