Skip to content

Commit 3640860

Browse files
committed
fix(hot): require a leading slash in the hot path and serialize publishes once
A path without a leading slash validated but could never match a request, since URL pathnames always start with one — the schema now enforces it. publish() also serializes the payload once per event instead of once per connected client.
1 parent 1220b01 commit 3640860

5 files changed

Lines changed: 33 additions & 5 deletions

File tree

src/hot.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,10 @@ function createEventStream(heartbeat, logger) {
146146
});
147147
},
148148
publish(payload) {
149+
const frame = `data: ${JSON.stringify(payload)}\n\n`;
150+
149151
everyClient((client) => {
150-
client.write(`data: ${JSON.stringify(payload)}\n\n`);
152+
client.write(frame);
151153
});
152154
},
153155
publishTo(res, payload) {

src/options.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,9 @@
190190
"additionalProperties": false,
191191
"properties": {
192192
"path": {
193-
"description": "The path the SSE endpoint is served at.",
193+
"description": "The path the SSE endpoint is served at. Must start with a slash.",
194194
"type": "string",
195-
"minLength": 1
195+
"pattern": "^/"
196196
},
197197
"heartbeat": {
198198
"description": "Heartbeat interval (in milliseconds) used to keep the SSE connection alive.",

test/__snapshots__/validation-options.test.js.snap.webpack5

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,14 @@ exports[`validation should throw an error on the "hot" option with "{"heartbeat"
8989

9090
exports[`validation should throw an error on the "hot" option with "{"path":""}" value 1`] = `
9191
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
92-
- options.hot.path should be a non-empty string.
93-
-> The path the SSE endpoint is served at."
92+
- options.hot.path should match pattern "^/".
93+
-> The path the SSE endpoint is served at. Must start with a slash."
94+
`;
95+
96+
exports[`validation should throw an error on the "hot" option with "{"path":"hmr"}" value 1`] = `
97+
"Invalid options object. Dev Middleware has been initialized using an options object that does not match the API schema.
98+
- options.hot.path should match pattern "^/".
99+
-> The path the SSE endpoint is served at. Must start with a slash."
94100
`;
95101

96102
exports[`validation should throw an error on the "hot" option with "{"statsOptions":"errors-only"}" value 1`] = `

test/hot.test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,23 @@ describe("hot middleware (unit)", () => {
110110
expect(pathMatch("/__webpack_hmr/extra", "/__webpack_hmr")).toBe(false);
111111
});
112112

113+
it("strips a fragment", () => {
114+
expect(pathMatch("/__webpack_hmr#section", "/__webpack_hmr")).toBe(true);
115+
});
116+
117+
it("matches absolute urls by pathname", () => {
118+
expect(
119+
pathMatch(
120+
"http://localhost:3000/__webpack_hmr?name=app",
121+
"/__webpack_hmr",
122+
),
123+
).toBe(true);
124+
});
125+
126+
it("does not mistake a protocol-relative host for a pathname", () => {
127+
expect(pathMatch("//__webpack_hmr", "/__webpack_hmr")).toBe(false);
128+
});
129+
113130
it("returns false when url is undefined", () => {
114131
expect(pathMatch(undefined, "/__webpack_hmr")).toBe(false);
115132
});

test/validation-options.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ describe("validation", () => {
9898
"foo",
9999
0,
100100
{ path: "" },
101+
// Would validate but never match a request: pathnames always start
102+
// with a slash.
103+
{ path: "hmr" },
101104
{ heartbeat: -1 },
102105
// 0 would silently fall back to the default interval — reject it.
103106
{ heartbeat: 0 },

0 commit comments

Comments
 (0)