webpack-dev-server 5.0.2 → 5.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/Server.js CHANGED
@@ -18,9 +18,6 @@ const schema = require("./options.json");
18
18
  /** @typedef {import("webpack").Stats} Stats */
19
19
  /** @typedef {import("webpack").MultiStats} MultiStats */
20
20
  /** @typedef {import("os").NetworkInterfaceInfo} NetworkInterfaceInfo */
21
- /** @typedef {import("express").NextFunction} NextFunction */
22
- /** @typedef {import("express").RequestHandler} ExpressRequestHandler */
23
- /** @typedef {import("express").ErrorRequestHandler} ExpressErrorRequestHandler */
24
21
  /** @typedef {import("chokidar").WatchOptions} WatchOptions */
25
22
  /** @typedef {import("chokidar").FSWatcher} FSWatcher */
26
23
  /** @typedef {import("connect-history-api-fallback").Options} ConnectHistoryApiFallbackOptions */
@@ -34,14 +31,32 @@ const schema = require("./options.json");
34
31
  /** @typedef {import("ipaddr.js").IPv4} IPv4 */
35
32
  /** @typedef {import("ipaddr.js").IPv6} IPv6 */
36
33
  /** @typedef {import("net").Socket} Socket */
34
+ /** @typedef {import("http").Server} HTTPServer*/
37
35
  /** @typedef {import("http").IncomingMessage} IncomingMessage */
38
36
  /** @typedef {import("http").ServerResponse} ServerResponse */
39
37
  /** @typedef {import("open").Options} OpenOptions */
38
+ /** @typedef {import("express").Application} ExpressApplication */
39
+ /** @typedef {import("express").RequestHandler} ExpressRequestHandler */
40
+ /** @typedef {import("express").ErrorRequestHandler} ExpressErrorRequestHandler */
41
+ /** @typedef {import("express").Request} ExpressRequest */
42
+ /** @typedef {import("express").Response} ExpressResponse */
43
+
44
+ /** @typedef {(err?: any) => void} NextFunction */
45
+ /** @typedef {(req: IncomingMessage, res: ServerResponse) => void} SimpleHandleFunction */
46
+ /** @typedef {(req: IncomingMessage, res: ServerResponse, next: NextFunction) => void} NextHandleFunction */
47
+ /** @typedef {(err: any, req: IncomingMessage, res: ServerResponse, next: NextFunction) => void} ErrorHandleFunction */
48
+ /** @typedef {SimpleHandleFunction | NextHandleFunction | ErrorHandleFunction} HandleFunction */
40
49
 
41
50
  /** @typedef {import("https").ServerOptions & { spdy?: { plain?: boolean | undefined, ssl?: boolean | undefined, 'x-forwarded-for'?: string | undefined, protocol?: string | undefined, protocols?: string[] | undefined }}} ServerOptions */
42
51
 
43
- /** @typedef {import("express").Request} Request */
44
- /** @typedef {import("express").Response} Response */
52
+ /**
53
+ * @template {BasicApplication} [T=ExpressApplication]
54
+ * @typedef {T extends ExpressApplication ? ExpressRequest : IncomingMessage} Request
55
+ */
56
+ /**
57
+ * @template {BasicApplication} [T=ExpressApplication]
58
+ * @typedef {T extends ExpressApplication ? ExpressResponse : ServerResponse} Response
59
+ */
45
60
 
46
61
  /**
47
62
  * @template {Request} T
@@ -88,8 +103,16 @@ const schema = require("./options.json");
88
103
  */
89
104
 
90
105
  /**
106
+ * @template {BasicApplication} [A=ExpressApplication]
107
+ * @template {BasicServer} [S=import("http").Server]
108
+ * @typedef {"http" | "https" | "spdy" | "http2" | string | function(ServerOptions, A): S} ServerType
109
+ */
110
+
111
+ /**
112
+ * @template {BasicApplication} [A=ExpressApplication]
113
+ * @template {BasicServer} [S=import("http").Server]
91
114
  * @typedef {Object} ServerConfiguration
92
- * @property {"http" | "https" | "spdy" | string} [type]
115
+ * @property {ServerType<A, S>} [type]
93
116
  * @property {ServerOptions} [options]
94
117
  */
95
118
 
@@ -126,10 +149,6 @@ const schema = require("./options.json");
126
149
  * @typedef {(ProxyConfigArrayItem | ((req?: Request | undefined, res?: Response | undefined, next?: NextFunction | undefined) => ProxyConfigArrayItem))[]} ProxyConfigArray
127
150
  */
128
151
 
129
- /**
130
- * @typedef {{ [url: string]: string | ProxyConfigArrayItem }} ProxyConfigMap
131
- */
132
-
133
152
  /**
134
153
  * @typedef {Object} OpenApp
135
154
  * @property {string} [name]
@@ -177,10 +196,23 @@ const schema = require("./options.json");
177
196
  */
178
197
 
179
198
  /**
180
- * @typedef {{ name?: string, path?: string, middleware: ExpressRequestHandler | ExpressErrorRequestHandler } | ExpressRequestHandler | ExpressErrorRequestHandler} Middleware
199
+ * @template {BasicApplication} [T=ExpressApplication]
200
+ * @typedef {T extends ExpressApplication ? ExpressRequestHandler | ExpressErrorRequestHandler : HandleFunction} MiddlewareHandler
201
+ */
202
+
203
+ /**
204
+ * @typedef {{ name?: string, path?: string, middleware: MiddlewareHandler }} MiddlewareObject
205
+ */
206
+
207
+ /**
208
+ * @typedef {MiddlewareObject | MiddlewareHandler } Middleware
181
209
  */
182
210
 
211
+ /** @typedef {import("net").Server | import("tls").Server} BasicServer */
212
+
183
213
  /**
214
+ * @template {BasicApplication} [A=ExpressApplication]
215
+ * @template {BasicServer} [S=import("http").Server]
184
216
  * @typedef {Object} Configuration
185
217
  * @property {boolean | string} [ipc]
186
218
  * @property {Host} [host]
@@ -194,17 +226,16 @@ const schema = require("./options.json");
194
226
  * @property {boolean | Record<string, never> | BonjourOptions} [bonjour]
195
227
  * @property {string | string[] | WatchFiles | Array<string | WatchFiles>} [watchFiles]
196
228
  * @property {boolean | string | Static | Array<string | Static>} [static]
197
- * @property {boolean | ServerOptions} [https]
198
- * @property {boolean} [http2]
199
- * @property {"http" | "https" | "spdy" | string | ServerConfiguration} [server]
229
+ * @property {ServerType<A, S> | ServerConfiguration<A, S>} [server]
230
+ * @property {() => Promise<A>} [app]
200
231
  * @property {boolean | "sockjs" | "ws" | string | WebSocketServerConfiguration} [webSocketServer]
201
- * @property {ProxyConfigMap | ProxyConfigArrayItem | ProxyConfigArray} [proxy]
232
+ * @property {ProxyConfigArray} [proxy]
202
233
  * @property {boolean | string | Open | Array<string | Open>} [open]
203
234
  * @property {boolean} [setupExitSignals]
204
235
  * @property {boolean | ClientConfiguration} [client]
205
- * @property {Headers | ((req: Request, res: Response, context: DevMiddlewareContext<Request, Response>) => Headers)} [headers]
206
- * @property {(devServer: Server) => void} [onListening]
207
- * @property {(middlewares: Middleware[], devServer: Server) => Middleware[]} [setupMiddlewares]
236
+ * @property {Headers | ((req: Request, res: Response, context: DevMiddlewareContext<Request, Response> | undefined) => Headers)} [headers]
237
+ * @property {(devServer: Server<A, S>) => void} [onListening]
238
+ * @property {(middlewares: Middleware[], devServer: Server<A, S>) => Middleware[]} [setupMiddlewares]
208
239
  */
209
240
 
210
241
  if (!process.env.WEBPACK_SERVE) {
@@ -249,10 +280,48 @@ const encodeOverlaySettings = (setting) =>
249
280
  ? encodeURIComponent(setting.toString())
250
281
  : setting;
251
282
 
283
+ // Working for overload, because typescript doesn't support this yes
284
+ /**
285
+ * @overload
286
+ * @param {NextHandleFunction} fn
287
+ * @returns {BasicApplication}
288
+ */
289
+ /**
290
+ * @overload
291
+ * @param {HandleFunction} fn
292
+ * @returns {BasicApplication}
293
+ */
294
+ /**
295
+ * @overload
296
+ * @param {string} route
297
+ * @param {NextHandleFunction} fn
298
+ * @returns {BasicApplication}
299
+ */
300
+ /**
301
+ * @param {string} route
302
+ * @param {HandleFunction} fn
303
+ * @returns {BasicApplication}
304
+ */
305
+ // eslint-disable-next-line no-unused-vars
306
+ function useFn(route, fn) {
307
+ return /** @type {BasicApplication} */ ({});
308
+ }
309
+
310
+ const DEFAULT_ALLOWED_PROTOCOLS = /^(file|.+-extension):/i;
311
+
312
+ /**
313
+ * @typedef {Object} BasicApplication
314
+ * @property {typeof useFn} use
315
+ */
316
+
317
+ /**
318
+ * @template {BasicApplication} [A=ExpressApplication]
319
+ * @template {BasicServer} [S=HTTPServer]
320
+ */
252
321
  class Server {
253
322
  /**
254
- * @param {Configuration | Compiler | MultiCompiler} options
255
- * @param {Compiler | MultiCompiler | Configuration} compiler
323
+ * @param {Configuration<A, S>} options
324
+ * @param {Compiler | MultiCompiler} compiler
256
325
  */
257
326
  constructor(options = {}, compiler) {
258
327
  validate(/** @type {Schema} */ (schema), options, {
@@ -260,12 +329,12 @@ class Server {
260
329
  baseDataPath: "options",
261
330
  });
262
331
 
263
- this.compiler = /** @type {Compiler | MultiCompiler} */ (compiler);
332
+ this.compiler = compiler;
264
333
  /**
265
334
  * @type {ReturnType<Compiler["getInfrastructureLogger"]>}
266
335
  * */
267
336
  this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server");
268
- this.options = /** @type {Configuration} */ (options);
337
+ this.options = options;
269
338
  /**
270
339
  * @type {FSWatcher[]}
271
340
  */
@@ -328,11 +397,61 @@ class Server {
328
397
  }
329
398
 
330
399
  /**
331
- * @param {string} gateway
400
+ * @param {string} gatewayOrFamily or family
401
+ * @param {boolean} [isInternal] ip should be internal
332
402
  * @returns {string | undefined}
333
403
  */
334
- static findIp(gateway) {
335
- const gatewayIp = ipaddr.parse(gateway);
404
+ static findIp(gatewayOrFamily, isInternal) {
405
+ if (gatewayOrFamily === "v4" || gatewayOrFamily === "v6") {
406
+ let host;
407
+
408
+ const networks = Object.values(os.networkInterfaces())
409
+ // eslint-disable-next-line no-shadow
410
+ .flatMap((networks) => networks ?? [])
411
+ .filter((network) => {
412
+ if (!network || !network.address) {
413
+ return false;
414
+ }
415
+
416
+ if (network.family !== `IP${gatewayOrFamily}`) {
417
+ return false;
418
+ }
419
+
420
+ if (
421
+ typeof isInternal !== "undefined" &&
422
+ network.internal !== isInternal
423
+ ) {
424
+ return false;
425
+ }
426
+
427
+ if (gatewayOrFamily === "v6") {
428
+ const range = ipaddr.parse(network.address).range();
429
+
430
+ if (
431
+ range !== "ipv4Mapped" &&
432
+ range !== "uniqueLocal" &&
433
+ range !== "loopback"
434
+ ) {
435
+ return false;
436
+ }
437
+ }
438
+
439
+ return network.address;
440
+ });
441
+
442
+ if (networks.length > 0) {
443
+ // Take the first network found
444
+ host = networks[0].address;
445
+
446
+ if (host.includes(":")) {
447
+ host = `[${host}]`;
448
+ }
449
+ }
450
+
451
+ return host;
452
+ }
453
+
454
+ const gatewayIp = ipaddr.parse(gatewayOrFamily);
336
455
 
337
456
  // Look for the matching interface in all local interfaces.
338
457
  for (const addresses of Object.values(os.networkInterfaces())) {
@@ -352,32 +471,22 @@ class Server {
352
471
  }
353
472
  }
354
473
 
474
+ // TODO remove me in the next major release, we have `findIp`
355
475
  /**
356
476
  * @param {"v4" | "v6"} family
357
477
  * @returns {Promise<string | undefined>}
358
478
  */
359
479
  static async internalIP(family) {
360
- try {
361
- const { gateway } = await require("default-gateway")[family]();
362
-
363
- return Server.findIp(gateway);
364
- } catch {
365
- // ignore
366
- }
480
+ return Server.findIp(family, false);
367
481
  }
368
482
 
483
+ // TODO remove me in the next major release, we have `findIp`
369
484
  /**
370
485
  * @param {"v4" | "v6"} family
371
486
  * @returns {string | undefined}
372
487
  */
373
488
  static internalIPSync(family) {
374
- try {
375
- const { gateway } = require("default-gateway")[family].sync();
376
-
377
- return Server.findIp(gateway);
378
- } catch {
379
- // ignore
380
- }
489
+ return Server.findIp(family, false);
381
490
  }
382
491
 
383
492
  /**
@@ -387,14 +496,12 @@ class Server {
387
496
  static async getHostname(hostname) {
388
497
  if (hostname === "local-ip") {
389
498
  return (
390
- (await Server.internalIP("v4")) ||
391
- (await Server.internalIP("v6")) ||
392
- "0.0.0.0"
499
+ Server.findIp("v4", false) || Server.findIp("v6", false) || "0.0.0.0"
393
500
  );
394
501
  } else if (hostname === "local-ipv4") {
395
- return (await Server.internalIP("v4")) || "0.0.0.0";
502
+ return Server.findIp("v4", false) || "0.0.0.0";
396
503
  } else if (hostname === "local-ipv6") {
397
- return (await Server.internalIP("v6")) || "::";
504
+ return Server.findIp("v6", false) || "::";
398
505
  }
399
506
 
400
507
  return hostname;
@@ -474,7 +581,11 @@ class Server {
474
581
  * @returns bool
475
582
  */
476
583
  static isWebTarget(compiler) {
477
- // TODO improve for the next major version - we should store `web` and other targets in `compiler.options.environment`
584
+ if (compiler.platform && compiler.platform.web) {
585
+ return compiler.platform.web;
586
+ }
587
+
588
+ // TODO improve for the next major version and keep only `webTargets` to fallback for old versions
478
589
  if (
479
590
  compiler.options.externalsPresets &&
480
591
  compiler.options.externalsPresets.web
@@ -494,6 +605,7 @@ class Server {
494
605
  "webworker",
495
606
  "electron-preload",
496
607
  "electron-renderer",
608
+ "nwjs",
497
609
  "node-webkit",
498
610
  // eslint-disable-next-line no-undefined
499
611
  undefined,
@@ -541,9 +653,7 @@ class Server {
541
653
  if (typeof webSocketURL.protocol !== "undefined") {
542
654
  protocol = webSocketURL.protocol;
543
655
  } else {
544
- protocol =
545
- /** @type {ServerConfiguration} */
546
- (this.options.server).type === "http" ? "ws:" : "wss:";
656
+ protocol = this.isTlsServer ? "wss:" : "ws:";
547
657
  }
548
658
 
549
659
  searchParams.set("protocol", protocol);
@@ -687,15 +797,12 @@ class Server {
687
797
  webSocketURLStr = searchParams.toString();
688
798
  }
689
799
 
690
- additionalEntries.push(
691
- `${require.resolve("../client/index.js")}?${webSocketURLStr}`,
692
- );
800
+ additionalEntries.push(`${this.getClientEntry()}?${webSocketURLStr}`);
693
801
  }
694
802
 
695
- if (this.options.hot === "only") {
696
- additionalEntries.push(require.resolve("webpack/hot/only-dev-server"));
697
- } else if (this.options.hot) {
698
- additionalEntries.push(require.resolve("webpack/hot/dev-server"));
803
+ const clientHotEntry = this.getClientHotEntry();
804
+ if (clientHotEntry) {
805
+ additionalEntries.push(clientHotEntry);
699
806
  }
700
807
 
701
808
  const webpack = compiler.webpack || require("webpack");
@@ -1011,39 +1118,41 @@ class Server {
1011
1118
  ? options.hot
1012
1119
  : true;
1013
1120
 
1014
- options.server = {
1015
- type:
1016
- // eslint-disable-next-line no-nested-ternary
1017
- typeof options.server === "string"
1018
- ? options.server
1019
- : typeof (options.server || {}).type === "string"
1020
- ? /** @type {ServerConfiguration} */ (options.server).type || "http"
1021
- : "http",
1022
- options: {
1023
- .../** @type {ServerOptions} */ (options.https),
1024
- .../** @type {ServerConfiguration} */ (options.server || {}).options,
1025
- },
1026
- };
1121
+ if (
1122
+ typeof options.server === "function" ||
1123
+ typeof options.server === "string"
1124
+ ) {
1125
+ options.server = {
1126
+ type: options.server,
1127
+ options: {},
1128
+ };
1129
+ } else {
1130
+ const serverOptions =
1131
+ /** @type {ServerConfiguration<A, S>} */
1132
+ (options.server || {});
1133
+
1134
+ options.server = {
1135
+ type: serverOptions.type || "http",
1136
+ options: { ...serverOptions.options },
1137
+ };
1138
+ }
1139
+
1140
+ const serverOptions = /** @type {ServerOptions} */ (options.server.options);
1027
1141
 
1028
1142
  if (
1029
1143
  options.server.type === "spdy" &&
1030
- typeof (/** @type {ServerOptions} */ (options.server.options).spdy) ===
1031
- "undefined"
1144
+ typeof serverOptions.spdy === "undefined"
1032
1145
  ) {
1033
- /** @type {ServerOptions} */
1034
- (options.server.options).spdy = {
1035
- protocols: ["h2", "http/1.1"],
1036
- };
1146
+ serverOptions.spdy = { protocols: ["h2", "http/1.1"] };
1037
1147
  }
1038
1148
 
1039
- if (options.server.type === "https" || options.server.type === "spdy") {
1040
- if (
1041
- typeof (
1042
- /** @type {ServerOptions} */ (options.server.options).requestCert
1043
- ) === "undefined"
1044
- ) {
1045
- /** @type {ServerOptions} */
1046
- (options.server.options).requestCert = false;
1149
+ if (
1150
+ options.server.type === "https" ||
1151
+ options.server.type === "http2" ||
1152
+ options.server.type === "spdy"
1153
+ ) {
1154
+ if (typeof serverOptions.requestCert === "undefined") {
1155
+ serverOptions.requestCert = false;
1047
1156
  }
1048
1157
 
1049
1158
  const httpsProperties =
@@ -1051,19 +1160,13 @@ class Server {
1051
1160
  (["ca", "cert", "crl", "key", "pfx"]);
1052
1161
 
1053
1162
  for (const property of httpsProperties) {
1054
- if (
1055
- typeof (
1056
- /** @type {ServerOptions} */ (options.server.options)[property]
1057
- ) === "undefined"
1058
- ) {
1163
+ if (typeof serverOptions[property] === "undefined") {
1059
1164
  // eslint-disable-next-line no-continue
1060
1165
  continue;
1061
1166
  }
1062
1167
 
1063
1168
  /** @type {any} */
1064
- const value =
1065
- /** @type {ServerOptions} */
1066
- (options.server.options)[property];
1169
+ const value = serverOptions[property];
1067
1170
  /**
1068
1171
  * @param {string | Buffer | undefined} item
1069
1172
  * @returns {string | Buffer | undefined}
@@ -1091,17 +1194,14 @@ class Server {
1091
1194
  };
1092
1195
 
1093
1196
  /** @type {any} */
1094
- (options.server.options)[property] = Array.isArray(value)
1197
+ (serverOptions)[property] = Array.isArray(value)
1095
1198
  ? value.map((item) => readFile(item))
1096
1199
  : readFile(value);
1097
1200
  }
1098
1201
 
1099
1202
  let fakeCert;
1100
1203
 
1101
- if (
1102
- !(/** @type {ServerOptions} */ (options.server.options).key) ||
1103
- !(/** @type {ServerOptions} */ (options.server.options).cert)
1104
- ) {
1204
+ if (!serverOptions.key || !serverOptions.cert) {
1105
1205
  const certificateDir = Server.findCacheDir();
1106
1206
  const certificatePath = path.join(certificateDir, "server.pem");
1107
1207
  let certificateExists;
@@ -1120,13 +1220,11 @@ class Server {
1120
1220
 
1121
1221
  // cert is more than 30 days old, kill it with fire
1122
1222
  if ((now - Number(certificateStat.ctime)) / certificateTtl > 30) {
1123
- const { rimraf } = require("rimraf");
1124
-
1125
1223
  this.logger.info(
1126
1224
  "SSL certificate is more than 30 days old. Removing...",
1127
1225
  );
1128
1226
 
1129
- await rimraf(certificatePath);
1227
+ await fs.promises.rm(certificatePath, { recursive: true });
1130
1228
 
1131
1229
  certificateExists = false;
1132
1230
  }
@@ -1135,7 +1233,6 @@ class Server {
1135
1233
  if (!certificateExists) {
1136
1234
  this.logger.info("Generating SSL certificate...");
1137
1235
 
1138
- // @ts-ignore
1139
1236
  const selfsigned = require("selfsigned");
1140
1237
  const attributes = [{ name: "commonName", value: "localhost" }];
1141
1238
  const pems = selfsigned.generate(attributes, {
@@ -1216,14 +1313,8 @@ class Server {
1216
1313
  this.logger.info(`SSL certificate: ${certificatePath}`);
1217
1314
  }
1218
1315
 
1219
- /** @type {ServerOptions} */
1220
- (options.server.options).key =
1221
- /** @type {ServerOptions} */
1222
- (options.server.options).key || fakeCert;
1223
- /** @type {ServerOptions} */
1224
- (options.server.options).cert =
1225
- /** @type {ServerOptions} */
1226
- (options.server.options).cert || fakeCert;
1316
+ serverOptions.key = serverOptions.key || fakeCert;
1317
+ serverOptions.cert = serverOptions.cert || fakeCert;
1227
1318
  }
1228
1319
 
1229
1320
  if (typeof options.ipc === "boolean") {
@@ -1285,15 +1376,15 @@ class Server {
1285
1376
  */
1286
1377
  const result = [];
1287
1378
 
1288
- options.open.forEach((item) => {
1379
+ for (const item of options.open) {
1289
1380
  if (typeof item === "string") {
1290
1381
  result.push({ target: item, options: defaultOpenOptions });
1291
-
1292
- return;
1382
+ // eslint-disable-next-line no-continue
1383
+ continue;
1293
1384
  }
1294
1385
 
1295
1386
  result.push(...getOpenItemsFromObject(item));
1296
- });
1387
+ }
1297
1388
 
1298
1389
  /** @type {NormalizedOpen[]} */
1299
1390
  (options.open) = result;
@@ -1317,48 +1408,45 @@ class Server {
1317
1408
  * }
1318
1409
  */
1319
1410
  if (typeof options.proxy !== "undefined") {
1320
- /** @type {ProxyConfigArray} */
1321
- (options.proxy) =
1322
- /** @type {ProxyConfigArray} */
1323
- (options.proxy).map((item) => {
1324
- if (typeof item === "function") {
1325
- return item;
1326
- }
1411
+ options.proxy = options.proxy.map((item) => {
1412
+ if (typeof item === "function") {
1413
+ return item;
1414
+ }
1327
1415
 
1328
- /**
1329
- * @param {"info" | "warn" | "error" | "debug" | "silent" | undefined | "none" | "log" | "verbose"} level
1330
- * @returns {"info" | "warn" | "error" | "debug" | "silent" | undefined}
1331
- */
1332
- const getLogLevelForProxy = (level) => {
1333
- if (level === "none") {
1334
- return "silent";
1335
- }
1416
+ /**
1417
+ * @param {"info" | "warn" | "error" | "debug" | "silent" | undefined | "none" | "log" | "verbose"} level
1418
+ * @returns {"info" | "warn" | "error" | "debug" | "silent" | undefined}
1419
+ */
1420
+ const getLogLevelForProxy = (level) => {
1421
+ if (level === "none") {
1422
+ return "silent";
1423
+ }
1336
1424
 
1337
- if (level === "log") {
1338
- return "info";
1339
- }
1425
+ if (level === "log") {
1426
+ return "info";
1427
+ }
1340
1428
 
1341
- if (level === "verbose") {
1342
- return "debug";
1343
- }
1429
+ if (level === "verbose") {
1430
+ return "debug";
1431
+ }
1344
1432
 
1345
- return level;
1346
- };
1433
+ return level;
1434
+ };
1347
1435
 
1348
- if (typeof item.logLevel === "undefined") {
1349
- item.logLevel = getLogLevelForProxy(
1350
- compilerOptions.infrastructureLogging
1351
- ? compilerOptions.infrastructureLogging.level
1352
- : "info",
1353
- );
1354
- }
1436
+ if (typeof item.logLevel === "undefined") {
1437
+ item.logLevel = getLogLevelForProxy(
1438
+ compilerOptions.infrastructureLogging
1439
+ ? compilerOptions.infrastructureLogging.level
1440
+ : "info",
1441
+ );
1442
+ }
1355
1443
 
1356
- if (typeof item.logProvider === "undefined") {
1357
- item.logProvider = () => this.logger;
1358
- }
1444
+ if (typeof item.logProvider === "undefined") {
1445
+ item.logProvider = () => this.logger;
1446
+ }
1359
1447
 
1360
- return item;
1361
- });
1448
+ return item;
1449
+ });
1362
1450
  }
1363
1451
 
1364
1452
  if (typeof options.setupExitSignals === "undefined") {
@@ -1529,8 +1617,9 @@ class Server {
1529
1617
  }
1530
1618
 
1531
1619
  /**
1620
+ * @template T
1532
1621
  * @private
1533
- * @returns {string}
1622
+ * @returns {T}
1534
1623
  */
1535
1624
  getServerTransport() {
1536
1625
  let implementation;
@@ -1560,9 +1649,8 @@ class Server {
1560
1649
  try {
1561
1650
  // eslint-disable-next-line import/no-dynamic-require
1562
1651
  implementation = require(
1563
- /** @type {WebSocketServerConfiguration} */ (
1564
- this.options.webSocketServer
1565
- ).type,
1652
+ /** @type {WebSocketServerConfiguration} */
1653
+ (this.options.webSocketServer).type,
1566
1654
  );
1567
1655
  } catch (error) {
1568
1656
  implementationFound = false;
@@ -1570,9 +1658,9 @@ class Server {
1570
1658
  }
1571
1659
  break;
1572
1660
  case "function":
1573
- implementation = /** @type {WebSocketServerConfiguration} */ (
1574
- this.options.webSocketServer
1575
- ).type;
1661
+ implementation =
1662
+ /** @type {WebSocketServerConfiguration} */
1663
+ (this.options.webSocketServer).type;
1576
1664
  break;
1577
1665
  default:
1578
1666
  implementationFound = false;
@@ -1589,6 +1677,25 @@ class Server {
1589
1677
  return implementation;
1590
1678
  }
1591
1679
 
1680
+ /**
1681
+ * @returns {string}
1682
+ */
1683
+ // eslint-disable-next-line class-methods-use-this
1684
+ getClientEntry() {
1685
+ return require.resolve("../client/index.js");
1686
+ }
1687
+
1688
+ /**
1689
+ * @returns {string | void}
1690
+ */
1691
+ getClientHotEntry() {
1692
+ if (this.options.hot === "only") {
1693
+ return require.resolve("webpack/hot/only-dev-server");
1694
+ } else if (this.options.hot) {
1695
+ return require.resolve("webpack/hot/dev-server");
1696
+ }
1697
+ }
1698
+
1592
1699
  /**
1593
1700
  * @private
1594
1701
  * @returns {void}
@@ -1638,12 +1745,22 @@ class Server {
1638
1745
  * @returns {Promise<void>}
1639
1746
  */
1640
1747
  async initialize() {
1748
+ this.setupHooks();
1749
+
1750
+ await this.setupApp();
1751
+ await this.createServer();
1752
+
1641
1753
  if (this.options.webSocketServer) {
1642
1754
  const compilers =
1643
1755
  /** @type {MultiCompiler} */
1644
1756
  (this.compiler).compilers || [this.compiler];
1645
1757
 
1646
- compilers.forEach((compiler) => {
1758
+ for (const compiler of compilers) {
1759
+ if (compiler.options.devServer === false) {
1760
+ // eslint-disable-next-line no-continue
1761
+ continue;
1762
+ }
1763
+
1647
1764
  this.addAdditionalEntries(compiler);
1648
1765
 
1649
1766
  const webpack = compiler.webpack || require("webpack");
@@ -1668,7 +1785,7 @@ class Server {
1668
1785
  plugin.apply(compiler);
1669
1786
  }
1670
1787
  }
1671
- });
1788
+ }
1672
1789
 
1673
1790
  if (
1674
1791
  this.options.client &&
@@ -1678,16 +1795,9 @@ class Server {
1678
1795
  }
1679
1796
  }
1680
1797
 
1681
- this.setupHooks();
1682
- this.setupApp();
1683
- this.setupHostHeaderCheck();
1684
- this.setupDevMiddleware();
1685
- // Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
1686
- this.setupBuiltInRoutes();
1687
1798
  this.setupWatchFiles();
1688
1799
  this.setupWatchStaticFiles();
1689
1800
  this.setupMiddlewares();
1690
- this.createServer();
1691
1801
 
1692
1802
  if (this.options.setupExitSignals) {
1693
1803
  const signals = ["SIGINT", "SIGTERM"];
@@ -1725,24 +1835,30 @@ class Server {
1725
1835
 
1726
1836
  // Proxy WebSocket without the initial http request
1727
1837
  // https://github.com/chimurai/http-proxy-middleware#external-websocket-upgrade
1728
- /** @type {RequestHandler[]} */
1729
- (this.webSocketProxies).forEach((webSocketProxy) => {
1730
- /** @type {import("http").Server} */
1838
+ const webSocketProxies =
1839
+ /** @type {RequestHandler[]} */
1840
+ (this.webSocketProxies);
1841
+
1842
+ for (const webSocketProxy of webSocketProxies) {
1843
+ /** @type {S} */
1731
1844
  (this.server).on(
1732
1845
  "upgrade",
1733
1846
  /** @type {RequestHandler & { upgrade: NonNullable<RequestHandler["upgrade"]> }} */
1734
1847
  (webSocketProxy).upgrade,
1735
1848
  );
1736
- }, this);
1849
+ }
1737
1850
  }
1738
1851
 
1739
1852
  /**
1740
1853
  * @private
1741
- * @returns {void}
1854
+ * @returns {Promise<void>}
1742
1855
  */
1743
- setupApp() {
1744
- /** @type {import("express").Application | undefined}*/
1745
- this.app = new /** @type {any} */ (getExpress())();
1856
+ async setupApp() {
1857
+ /** @type {A | undefined}*/
1858
+ this.app =
1859
+ typeof this.options.app === "function"
1860
+ ? await this.options.app()
1861
+ : getExpress()();
1746
1862
  }
1747
1863
 
1748
1864
  /**
@@ -1796,204 +1912,330 @@ class Server {
1796
1912
  * @private
1797
1913
  * @returns {void}
1798
1914
  */
1799
- setupHostHeaderCheck() {
1800
- /** @type {import("express").Application} */
1801
- (this.app).all(
1802
- "*",
1803
- /**
1804
- * @param {Request} req
1805
- * @param {Response} res
1806
- * @param {NextFunction} next
1807
- * @returns {void}
1808
- */
1809
- (req, res, next) => {
1810
- if (
1811
- this.checkHeader(
1812
- /** @type {{ [key: string]: string | undefined }} */
1813
- (req.headers),
1814
- "host",
1815
- )
1816
- ) {
1817
- return next();
1818
- }
1915
+ setupWatchStaticFiles() {
1916
+ const watchFiles = /** @type {NormalizedStatic[]} */ (this.options.static);
1819
1917
 
1820
- res.send("Invalid Host header");
1821
- },
1822
- );
1918
+ if (watchFiles.length > 0) {
1919
+ for (const item of watchFiles) {
1920
+ if (item.watch) {
1921
+ this.watchFiles(item.directory, item.watch);
1922
+ }
1923
+ }
1924
+ }
1823
1925
  }
1824
1926
 
1825
1927
  /**
1826
1928
  * @private
1827
1929
  * @returns {void}
1828
1930
  */
1829
- setupDevMiddleware() {
1830
- const webpackDevMiddleware = require("webpack-dev-middleware");
1931
+ setupWatchFiles() {
1932
+ const watchFiles = /** @type {WatchFiles[]} */ (this.options.watchFiles);
1831
1933
 
1832
- // middleware for serving webpack bundle
1833
- this.middleware = webpackDevMiddleware(
1834
- this.compiler,
1835
- this.options.devMiddleware,
1836
- );
1934
+ if (watchFiles.length > 0) {
1935
+ for (const item of watchFiles) {
1936
+ this.watchFiles(item.paths, item.options);
1937
+ }
1938
+ }
1837
1939
  }
1838
1940
 
1839
1941
  /**
1840
1942
  * @private
1841
1943
  * @returns {void}
1842
1944
  */
1843
- setupBuiltInRoutes() {
1844
- const { app, middleware } = this;
1945
+ setupMiddlewares() {
1946
+ /**
1947
+ * @type {Array<Middleware>}
1948
+ */
1949
+ let middlewares = [];
1845
1950
 
1846
- /** @type {import("express").Application} */
1847
- (app).get("/__webpack_dev_server__/sockjs.bundle.js", (req, res) => {
1848
- res.setHeader("Content-Type", "application/javascript");
1951
+ // Register setup host header check for security
1952
+ middlewares.push({
1953
+ name: "host-header-check",
1954
+ /**
1955
+ * @param {Request} req
1956
+ * @param {Response} res
1957
+ * @param {NextFunction} next
1958
+ * @returns {void}
1959
+ */
1960
+ middleware: (req, res, next) => {
1961
+ const headers =
1962
+ /** @type {{ [key: string]: string | undefined }} */
1963
+ (req.headers);
1964
+ const headerName = headers[":authority"] ? ":authority" : "host";
1849
1965
 
1850
- const clientPath = path.join(__dirname, "..", "client");
1966
+ if (this.isValidHost(headers, headerName)) {
1967
+ next();
1968
+ return;
1969
+ }
1851
1970
 
1852
- res.sendFile(path.join(clientPath, "modules/sockjs-client/index.js"));
1971
+ res.statusCode = 403;
1972
+ res.end("Invalid Host header");
1973
+ },
1853
1974
  });
1854
1975
 
1855
- /** @type {import("express").Application} */
1856
- (app).get("/webpack-dev-server/invalidate", (_req, res) => {
1857
- this.invalidate();
1976
+ // Register setup cross origin request check for security
1977
+ middlewares.push({
1978
+ name: "cross-origin-header-check",
1979
+ /**
1980
+ * @param {Request} req
1981
+ * @param {Response} res
1982
+ * @param {NextFunction} next
1983
+ * @returns {void}
1984
+ */
1985
+ middleware: (req, res, next) => {
1986
+ const headers =
1987
+ /** @type {{ [key: string]: string | undefined }} */
1988
+ (req.headers);
1989
+ const headerName = headers[":authority"] ? ":authority" : "host";
1990
+
1991
+ if (this.isValidHost(headers, headerName, false)) {
1992
+ next();
1993
+ return;
1994
+ }
1995
+
1996
+ if (
1997
+ headers["sec-fetch-mode"] === "no-cors" &&
1998
+ headers["sec-fetch-site"] === "cross-site"
1999
+ ) {
2000
+ res.statusCode = 403;
2001
+ res.end("Cross-Origin request blocked");
2002
+ return;
2003
+ }
1858
2004
 
1859
- res.end();
2005
+ next();
2006
+ },
1860
2007
  });
1861
2008
 
1862
- /** @type {import("express").Application} */
1863
- (app).get("/webpack-dev-server/open-editor", (req, res) => {
1864
- const fileName = req.query.fileName;
2009
+ const isHTTP2 =
2010
+ /** @type {ServerConfiguration<A, S>} */ (this.options.server).type ===
2011
+ "http2";
1865
2012
 
1866
- if (typeof fileName === "string") {
1867
- // @ts-ignore
1868
- const launchEditor = require("launch-editor");
1869
- launchEditor(fileName);
1870
- }
2013
+ if (isHTTP2) {
2014
+ // TODO patch for https://github.com/pillarjs/finalhandler/pull/45, need remove then will be resolved
2015
+ middlewares.push({
2016
+ name: "http2-status-message-patch",
2017
+ middleware:
2018
+ /** @type {NextHandleFunction} */
2019
+ (_req, res, next) => {
2020
+ Object.defineProperty(res, "statusMessage", {
2021
+ get() {
2022
+ return "";
2023
+ },
2024
+ set() {},
2025
+ });
2026
+
2027
+ next();
2028
+ },
2029
+ });
2030
+ }
2031
+
2032
+ // compress is placed last and uses unshift so that it will be the first middleware used
2033
+ if (this.options.compress && !isHTTP2) {
2034
+ const compression = require("compression");
2035
+
2036
+ middlewares.push({ name: "compression", middleware: compression() });
2037
+ }
2038
+
2039
+ if (typeof this.options.headers !== "undefined") {
2040
+ middlewares.push({
2041
+ name: "set-headers",
2042
+ middleware: this.setHeaders.bind(this),
2043
+ });
2044
+ }
1871
2045
 
1872
- res.end();
2046
+ middlewares.push({
2047
+ name: "webpack-dev-middleware",
2048
+ middleware: /** @type {MiddlewareHandler} */ (this.middleware),
1873
2049
  });
1874
2050
 
1875
- /** @type {import("express").Application} */
1876
- (app).get("/webpack-dev-server", (req, res) => {
1877
- /** @type {import("webpack-dev-middleware").API<Request, Response>}*/
1878
- (middleware).waitUntilValid((stats) => {
1879
- res.setHeader("Content-Type", "text/html");
1880
- // HEAD requests should not return body content
1881
- if (req.method === "HEAD") {
1882
- res.end();
2051
+ // Should be after `webpack-dev-middleware`, otherwise other middlewares might rewrite response
2052
+ middlewares.push({
2053
+ name: "webpack-dev-server-sockjs-bundle",
2054
+ path: "/__webpack_dev_server__/sockjs.bundle.js",
2055
+ /**
2056
+ * @param {Request} req
2057
+ * @param {Response} res
2058
+ * @param {NextFunction} next
2059
+ * @returns {void}
2060
+ */
2061
+ middleware: (req, res, next) => {
2062
+ if (req.method !== "GET" && req.method !== "HEAD") {
2063
+ next();
1883
2064
  return;
1884
2065
  }
1885
- res.write(
1886
- '<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>',
1887
- );
1888
2066
 
1889
- const statsForPrint =
1890
- typeof (/** @type {MultiStats} */ (stats).stats) !== "undefined"
1891
- ? /** @type {MultiStats} */ (stats).toJson().children
1892
- : [/** @type {Stats} */ (stats).toJson()];
1893
-
1894
- res.write(`<h1>Assets Report:</h1>`);
2067
+ const clientPath = path.join(
2068
+ __dirname,
2069
+ "..",
2070
+ "client/modules/sockjs-client/index.js",
2071
+ );
1895
2072
 
1896
- /**
1897
- * @type {StatsCompilation[]}
1898
- */
1899
- (statsForPrint).forEach((item, index) => {
1900
- res.write("<div>");
2073
+ // Express send Etag and other headers by default, so let's keep them for compatibility reasons
2074
+ if (typeof res.sendFile === "function") {
2075
+ res.sendFile(clientPath);
2076
+ return;
2077
+ }
1901
2078
 
1902
- const name =
1903
- // eslint-disable-next-line no-nested-ternary
1904
- typeof item.name !== "undefined"
1905
- ? item.name
1906
- : /** @type {MultiStats} */ (stats).stats
1907
- ? `unnamed[${index}]`
1908
- : "unnamed";
2079
+ let stats;
1909
2080
 
1910
- res.write(`<h2>Compilation: ${name}</h2>`);
1911
- res.write("<ul>");
2081
+ try {
2082
+ // TODO implement `inputFileSystem.createReadStream` in webpack
2083
+ stats = fs.statSync(clientPath);
2084
+ } catch (err) {
2085
+ next();
2086
+ return;
2087
+ }
1912
2088
 
1913
- const publicPath = item.publicPath === "auto" ? "" : item.publicPath;
2089
+ res.setHeader("Content-Type", "application/javascript; charset=UTF-8");
2090
+ res.setHeader("Content-Length", stats.size);
1914
2091
 
1915
- for (const asset of /** @type {NonNullable<StatsCompilation["assets"]>} */ (
1916
- item.assets
1917
- )) {
1918
- const assetName = asset.name;
1919
- const assetURL = `${publicPath}${assetName}`;
2092
+ if (req.method === "HEAD") {
2093
+ res.end();
2094
+ return;
2095
+ }
1920
2096
 
1921
- res.write(
1922
- `<li>
1923
- <strong><a href="${assetURL}" target="_blank">${assetName}</a></strong>
1924
- </li>`,
1925
- );
1926
- }
2097
+ fs.createReadStream(clientPath).pipe(res);
2098
+ },
2099
+ });
1927
2100
 
1928
- res.write("</ul>");
1929
- res.write("</div>");
1930
- });
2101
+ middlewares.push({
2102
+ name: "webpack-dev-server-invalidate",
2103
+ path: "/webpack-dev-server/invalidate",
2104
+ /**
2105
+ * @param {Request} req
2106
+ * @param {Response} res
2107
+ * @param {NextFunction} next
2108
+ * @returns {void}
2109
+ */
2110
+ middleware: (req, res, next) => {
2111
+ if (req.method !== "GET" && req.method !== "HEAD") {
2112
+ next();
2113
+ return;
2114
+ }
1931
2115
 
1932
- res.end("</body></html>");
1933
- });
2116
+ this.invalidate();
2117
+
2118
+ res.end();
2119
+ },
1934
2120
  });
1935
- }
1936
2121
 
1937
- /**
1938
- * @private
1939
- * @returns {void}
1940
- */
1941
- setupWatchStaticFiles() {
1942
- if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
1943
- /** @type {NormalizedStatic[]} */
1944
- (this.options.static).forEach((staticOption) => {
1945
- if (staticOption.watch) {
1946
- this.watchFiles(staticOption.directory, staticOption.watch);
2122
+ middlewares.push({
2123
+ name: "webpack-dev-server-open-editor",
2124
+ path: "/webpack-dev-server/open-editor",
2125
+ /**
2126
+ * @param {Request} req
2127
+ * @param {Response} res
2128
+ * @param {NextFunction} next
2129
+ * @returns {void}
2130
+ */
2131
+ middleware: (req, res, next) => {
2132
+ if (req.method !== "GET" && req.method !== "HEAD") {
2133
+ next();
2134
+ return;
1947
2135
  }
1948
- });
1949
- }
1950
- }
1951
2136
 
1952
- /**
1953
- * @private
1954
- * @returns {void}
1955
- */
1956
- setupWatchFiles() {
1957
- const { watchFiles } = this.options;
1958
-
1959
- if (/** @type {WatchFiles[]} */ (watchFiles).length > 0) {
1960
- /** @type {WatchFiles[]} */
1961
- (watchFiles).forEach((item) => {
1962
- this.watchFiles(item.paths, item.options);
1963
- });
1964
- }
1965
- }
2137
+ if (!req.url) {
2138
+ next();
2139
+ return;
2140
+ }
1966
2141
 
1967
- /**
1968
- * @private
1969
- * @returns {void}
1970
- */
1971
- setupMiddlewares() {
1972
- /**
1973
- * @type {Array<Middleware>}
1974
- */
1975
- let middlewares = [];
2142
+ const resolveUrl = new URL(req.url, `http://${req.headers.host}`);
2143
+ const params = new URLSearchParams(resolveUrl.search);
2144
+ const fileName = params.get("fileName");
1976
2145
 
1977
- // compress is placed last and uses unshift so that it will be the first middleware used
1978
- if (this.options.compress) {
1979
- const compression = require("compression");
2146
+ if (typeof fileName === "string") {
2147
+ // @ts-ignore
2148
+ const launchEditor = require("launch-editor");
1980
2149
 
1981
- middlewares.push({ name: "compression", middleware: compression() });
1982
- }
2150
+ launchEditor(fileName);
2151
+ }
1983
2152
 
1984
- if (typeof this.options.headers !== "undefined") {
1985
- middlewares.push({
1986
- name: "set-headers",
1987
- path: "*",
1988
- middleware: this.setHeaders.bind(this),
1989
- });
1990
- }
2153
+ res.end();
2154
+ },
2155
+ });
1991
2156
 
1992
2157
  middlewares.push({
1993
- name: "webpack-dev-middleware",
1994
- middleware:
1995
- /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
1996
- (this.middleware),
2158
+ name: "webpack-dev-server-assets",
2159
+ path: "/webpack-dev-server",
2160
+ /**
2161
+ * @param {Request} req
2162
+ * @param {Response} res
2163
+ * @param {NextFunction} next
2164
+ * @returns {void}
2165
+ */
2166
+ middleware: (req, res, next) => {
2167
+ if (req.method !== "GET" && req.method !== "HEAD") {
2168
+ next();
2169
+ return;
2170
+ }
2171
+
2172
+ if (!this.middleware) {
2173
+ next();
2174
+ return;
2175
+ }
2176
+
2177
+ this.middleware.waitUntilValid((stats) => {
2178
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
2179
+
2180
+ // HEAD requests should not return body content
2181
+ if (req.method === "HEAD") {
2182
+ res.end();
2183
+ return;
2184
+ }
2185
+
2186
+ res.write(
2187
+ '<!DOCTYPE html><html><head><meta charset="utf-8"/></head><body>',
2188
+ );
2189
+
2190
+ /**
2191
+ * @type {StatsCompilation[]}
2192
+ */
2193
+ const statsForPrint =
2194
+ typeof (/** @type {MultiStats} */ (stats).stats) !== "undefined"
2195
+ ? /** @type {NonNullable<StatsCompilation["children"]>} */
2196
+ (/** @type {MultiStats} */ (stats).toJson().children)
2197
+ : [/** @type {Stats} */ (stats).toJson()];
2198
+
2199
+ res.write(`<h1>Assets Report:</h1>`);
2200
+
2201
+ for (const [index, item] of statsForPrint.entries()) {
2202
+ res.write("<div>");
2203
+
2204
+ const name =
2205
+ // eslint-disable-next-line no-nested-ternary
2206
+ typeof item.name !== "undefined"
2207
+ ? item.name
2208
+ : /** @type {MultiStats} */ (stats).stats
2209
+ ? `unnamed[${index}]`
2210
+ : "unnamed";
2211
+
2212
+ res.write(`<h2>Compilation: ${name}</h2>`);
2213
+ res.write("<ul>");
2214
+
2215
+ const publicPath =
2216
+ item.publicPath === "auto" ? "" : item.publicPath;
2217
+ const assets =
2218
+ /** @type {NonNullable<StatsCompilation["assets"]>} */
2219
+ (item.assets);
2220
+
2221
+ for (const asset of assets) {
2222
+ const assetName = asset.name;
2223
+ const assetURL = `${publicPath}${assetName}`;
2224
+
2225
+ res.write(
2226
+ `<li>
2227
+ <strong><a href="${assetURL}" target="_blank">${assetName}</a></strong>
2228
+ </li>`,
2229
+ );
2230
+ }
2231
+
2232
+ res.write("</ul>");
2233
+ res.write("</div>");
2234
+ }
2235
+
2236
+ res.end("</body></html>");
2237
+ });
2238
+ },
1997
2239
  });
1998
2240
 
1999
2241
  if (this.options.proxy) {
@@ -2045,8 +2287,7 @@ class Server {
2045
2287
  * }
2046
2288
  * ]
2047
2289
  */
2048
- /** @type {ProxyConfigArray} */
2049
- (this.options.proxy).forEach((proxyConfigOrCallback) => {
2290
+ this.options.proxy.forEach((proxyConfigOrCallback) => {
2050
2291
  /**
2051
2292
  * @type {RequestHandler}
2052
2293
  */
@@ -2114,8 +2355,8 @@ class Server {
2114
2355
 
2115
2356
  if (typeof bypassUrl === "boolean") {
2116
2357
  // skip the proxy
2117
- // @ts-ignore
2118
- req.url = null;
2358
+ res.statusCode = 404;
2359
+ req.url = "";
2119
2360
  next();
2120
2361
  } else if (typeof bypassUrl === "string") {
2121
2362
  // byPass to that url
@@ -2132,6 +2373,7 @@ class Server {
2132
2373
  name: "http-proxy-middleware",
2133
2374
  middleware: handler,
2134
2375
  });
2376
+
2135
2377
  // Also forward error requests to the proxy so it can handle them.
2136
2378
  middlewares.push({
2137
2379
  name: "http-proxy-middleware-error-handler",
@@ -2149,16 +2391,17 @@ class Server {
2149
2391
 
2150
2392
  middlewares.push({
2151
2393
  name: "webpack-dev-middleware",
2152
- middleware:
2153
- /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
2154
- (this.middleware),
2394
+ middleware: /** @type {MiddlewareHandler} */ (this.middleware),
2155
2395
  });
2156
2396
  }
2157
2397
 
2158
- if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2398
+ const staticOptions =
2159
2399
  /** @type {NormalizedStatic[]} */
2160
- (this.options.static).forEach((staticOption) => {
2161
- staticOption.publicPath.forEach((publicPath) => {
2400
+ (this.options.static);
2401
+
2402
+ if (staticOptions.length > 0) {
2403
+ for (const staticOption of staticOptions) {
2404
+ for (const publicPath of staticOption.publicPath) {
2162
2405
  middlewares.push({
2163
2406
  name: "express-static",
2164
2407
  path: publicPath,
@@ -2167,8 +2410,8 @@ class Server {
2167
2410
  staticOption.staticOptions,
2168
2411
  ),
2169
2412
  });
2170
- });
2171
- });
2413
+ }
2414
+ }
2172
2415
  }
2173
2416
 
2174
2417
  if (this.options.historyApiFallback) {
@@ -2205,15 +2448,12 @@ class Server {
2205
2448
  // it is able to handle '/index.html' request after redirect
2206
2449
  middlewares.push({
2207
2450
  name: "webpack-dev-middleware",
2208
- middleware:
2209
- /** @type {import("webpack-dev-middleware").Middleware<Request, Response>}*/
2210
- (this.middleware),
2451
+ middleware: /** @type {MiddlewareHandler} */ (this.middleware),
2211
2452
  });
2212
2453
 
2213
- if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2214
- /** @type {NormalizedStatic[]} */
2215
- (this.options.static).forEach((staticOption) => {
2216
- staticOption.publicPath.forEach((publicPath) => {
2454
+ if (staticOptions.length > 0) {
2455
+ for (const staticOption of staticOptions) {
2456
+ for (const publicPath of staticOption.publicPath) {
2217
2457
  middlewares.push({
2218
2458
  name: "express-static",
2219
2459
  path: publicPath,
@@ -2222,17 +2462,16 @@ class Server {
2222
2462
  staticOption.staticOptions,
2223
2463
  ),
2224
2464
  });
2225
- });
2226
- });
2465
+ }
2466
+ }
2227
2467
  }
2228
2468
  }
2229
2469
 
2230
- if (/** @type {NormalizedStatic[]} */ (this.options.static).length > 0) {
2470
+ if (staticOptions.length > 0) {
2231
2471
  const serveIndex = require("serve-index");
2232
2472
 
2233
- /** @type {NormalizedStatic[]} */
2234
- (this.options.static).forEach((staticOption) => {
2235
- staticOption.publicPath.forEach((publicPath) => {
2473
+ for (const staticOption of staticOptions) {
2474
+ for (const publicPath of staticOption.publicPath) {
2236
2475
  if (staticOption.serveIndex) {
2237
2476
  middlewares.push({
2238
2477
  name: "serve-index",
@@ -2257,15 +2496,14 @@ class Server {
2257
2496
  },
2258
2497
  });
2259
2498
  }
2260
- });
2261
- });
2499
+ }
2500
+ }
2262
2501
  }
2263
2502
 
2264
2503
  // Register this middleware always as the last one so that it's only used as a
2265
2504
  // fallback when no other middleware responses.
2266
2505
  middlewares.push({
2267
2506
  name: "options-middleware",
2268
- path: "*",
2269
2507
  /**
2270
2508
  * @param {Request} req
2271
2509
  * @param {Response} res
@@ -2287,37 +2525,93 @@ class Server {
2287
2525
  middlewares = this.options.setupMiddlewares(middlewares, this);
2288
2526
  }
2289
2527
 
2290
- middlewares.forEach((middleware) => {
2528
+ // Lazy init webpack dev middleware
2529
+ const lazyInitDevMiddleware = () => {
2530
+ if (!this.middleware) {
2531
+ const webpackDevMiddleware = require("webpack-dev-middleware");
2532
+
2533
+ // middleware for serving webpack bundle
2534
+ /** @type {import("webpack-dev-middleware").API<Request, Response>} */
2535
+ this.middleware = webpackDevMiddleware(
2536
+ this.compiler,
2537
+ this.options.devMiddleware,
2538
+ );
2539
+ }
2540
+
2541
+ return this.middleware;
2542
+ };
2543
+
2544
+ for (const i of middlewares) {
2545
+ if (i.name === "webpack-dev-middleware") {
2546
+ const item = /** @type {MiddlewareObject} */ (i);
2547
+
2548
+ if (typeof item.middleware === "undefined") {
2549
+ item.middleware = lazyInitDevMiddleware();
2550
+ }
2551
+ }
2552
+ }
2553
+
2554
+ for (const middleware of middlewares) {
2291
2555
  if (typeof middleware === "function") {
2292
- /** @type {import("express").Application} */
2293
- (this.app).use(middleware);
2556
+ /** @type {A} */
2557
+ (this.app).use(
2558
+ /** @type {NextHandleFunction | HandleFunction} */
2559
+ (middleware),
2560
+ );
2294
2561
  } else if (typeof middleware.path !== "undefined") {
2295
- /** @type {import("express").Application} */
2296
- (this.app).use(middleware.path, middleware.middleware);
2562
+ /** @type {A} */
2563
+ (this.app).use(
2564
+ middleware.path,
2565
+ /** @type {SimpleHandleFunction | NextHandleFunction} */
2566
+ (middleware.middleware),
2567
+ );
2297
2568
  } else {
2298
- /** @type {import("express").Application} */
2299
- (this.app).use(middleware.middleware);
2569
+ /** @type {A} */
2570
+ (this.app).use(
2571
+ /** @type {NextHandleFunction | HandleFunction} */
2572
+ (middleware.middleware),
2573
+ );
2300
2574
  }
2301
- });
2575
+ }
2302
2576
  }
2303
2577
 
2304
2578
  /**
2305
2579
  * @private
2306
- * @returns {void}
2580
+ * @returns {Promise<void>}
2307
2581
  */
2308
- createServer() {
2309
- const { type, options } = /** @type {ServerConfiguration} */ (
2310
- this.options.server
2311
- );
2582
+ async createServer() {
2583
+ const { type, options } =
2584
+ /** @type {ServerConfiguration<A, S>} */
2585
+ (this.options.server);
2586
+
2587
+ if (typeof type === "function") {
2588
+ /** @type {S | undefined}*/
2589
+ this.server = await type(
2590
+ /** @type {ServerOptions} */
2591
+ (options),
2592
+ /** @type {A} */
2593
+ (this.app),
2594
+ );
2595
+ } else {
2596
+ // eslint-disable-next-line import/no-dynamic-require
2597
+ const serverType = require(/** @type {string} */ (type));
2312
2598
 
2313
- /** @type {import("http").Server | undefined | null} */
2314
- // eslint-disable-next-line import/no-dynamic-require
2315
- this.server = require(/** @type {string} */ (type)).createServer(
2316
- options,
2317
- this.app,
2318
- );
2599
+ /** @type {S | undefined}*/
2600
+ this.server =
2601
+ type === "http2"
2602
+ ? serverType.createSecureServer(
2603
+ { ...options, allowHTTP1: true },
2604
+ this.app,
2605
+ )
2606
+ : serverType.createServer(options, this.app);
2607
+ }
2319
2608
 
2320
- /** @type {import("http").Server} */
2609
+ this.isTlsServer =
2610
+ typeof (
2611
+ /** @type {import("tls").Server} */ (this.server).setSecureContext
2612
+ ) !== "undefined";
2613
+
2614
+ /** @type {S} */
2321
2615
  (this.server).on(
2322
2616
  "connection",
2323
2617
  /**
@@ -2334,7 +2628,7 @@ class Server {
2334
2628
  },
2335
2629
  );
2336
2630
 
2337
- /** @type {import("http").Server} */
2631
+ /** @type {S} */
2338
2632
  (this.server).on(
2339
2633
  "error",
2340
2634
  /**
@@ -2352,9 +2646,8 @@ class Server {
2352
2646
  */
2353
2647
  createWebSocketServer() {
2354
2648
  /** @type {WebSocketServerImplementation | undefined | null} */
2355
- this.webSocketServer = new /** @type {any} */ (this.getServerTransport())(
2356
- this,
2357
- );
2649
+ this.webSocketServer = new (this.getServerTransport())(this);
2650
+
2358
2651
  /** @type {WebSocketServerImplementation} */
2359
2652
  (this.webSocketServer).implementation.on(
2360
2653
  "connection",
@@ -2384,8 +2677,9 @@ class Server {
2384
2677
 
2385
2678
  if (
2386
2679
  !headers ||
2387
- !this.checkHeader(headers, "host") ||
2388
- !this.checkHeader(headers, "origin")
2680
+ !this.isValidHost(headers, "host") ||
2681
+ !this.isValidHost(headers, "origin") ||
2682
+ !this.isSameOrigin(headers)
2389
2683
  ) {
2390
2684
  this.sendMessage([client], "error", "Invalid Host/Origin header");
2391
2685
 
@@ -2419,7 +2713,8 @@ class Server {
2419
2713
 
2420
2714
  if (
2421
2715
  this.options.client &&
2422
- /** @type {ClientConfiguration} */ (this.options.client).reconnect
2716
+ /** @type {ClientConfiguration} */
2717
+ (this.options.client).reconnect
2423
2718
  ) {
2424
2719
  this.sendMessage(
2425
2720
  [client],
@@ -2434,9 +2729,9 @@ class Server {
2434
2729
  /** @type {ClientConfiguration} */
2435
2730
  (this.options.client).overlay
2436
2731
  ) {
2437
- const overlayConfig = /** @type {ClientConfiguration} */ (
2438
- this.options.client
2439
- ).overlay;
2732
+ const overlayConfig =
2733
+ /** @type {ClientConfiguration} */
2734
+ (this.options.client).overlay;
2440
2735
 
2441
2736
  this.sendMessage(
2442
2737
  [client],
@@ -2521,22 +2816,19 @@ class Server {
2521
2816
  */
2522
2817
  runBonjour() {
2523
2818
  const { Bonjour } = require("bonjour-service");
2819
+ const type = this.isTlsServer ? "https" : "http";
2820
+
2524
2821
  /**
2525
2822
  * @private
2526
2823
  * @type {Bonjour | undefined}
2527
2824
  */
2528
2825
  this.bonjour = new Bonjour();
2529
2826
  this.bonjour.publish({
2530
- // @ts-expect-error
2531
2827
  name: `Webpack Dev Server ${os.hostname()}:${this.options.port}`,
2532
- // @ts-expect-error
2533
2828
  port: /** @type {number} */ (this.options.port),
2534
- // @ts-expect-error
2535
- type:
2536
- /** @type {ServerConfiguration} */
2537
- (this.options.server).type === "http" ? "http" : "https",
2829
+ type,
2538
2830
  subtypes: ["webpack"],
2539
- .../** @type {BonjourOptions} */ (this.options.bonjour),
2831
+ .../** @type {Partial<BonjourOptions>} */ (this.options.bonjour),
2540
2832
  });
2541
2833
  }
2542
2834
 
@@ -2616,23 +2908,15 @@ class Server {
2616
2908
  };
2617
2909
  const useColor = getColorsOption(this.getCompilerOptions());
2618
2910
 
2911
+ const server = /** @type {S} */ (this.server);
2912
+
2619
2913
  if (this.options.ipc) {
2620
- this.logger.info(
2621
- `Project is running at: "${
2622
- /** @type {import("http").Server} */
2623
- (this.server).address()
2624
- }"`,
2625
- );
2914
+ this.logger.info(`Project is running at: "${server.address()}"`);
2626
2915
  } else {
2627
- const protocol =
2628
- /** @type {ServerConfiguration} */
2629
- (this.options.server).type === "http" ? "http" : "https";
2916
+ const protocol = this.isTlsServer ? "https" : "http";
2630
2917
  const { address, port } =
2631
2918
  /** @type {import("net").AddressInfo} */
2632
- (
2633
- /** @type {import("http").Server} */
2634
- (this.server).address()
2635
- );
2919
+ (server.address());
2636
2920
  /**
2637
2921
  * @param {string} newHostname
2638
2922
  * @returns {string}
@@ -2640,7 +2924,7 @@ class Server {
2640
2924
  const prettyPrintURL = (newHostname) =>
2641
2925
  url.format({ protocol, hostname: newHostname, port, pathname: "/" });
2642
2926
 
2643
- let server;
2927
+ let host;
2644
2928
  let localhost;
2645
2929
  let loopbackIPv4;
2646
2930
  let loopbackIPv6;
@@ -2660,7 +2944,7 @@ class Server {
2660
2944
  }
2661
2945
 
2662
2946
  if (!isIP) {
2663
- server = prettyPrintURL(this.options.host);
2947
+ host = prettyPrintURL(this.options.host);
2664
2948
  }
2665
2949
  }
2666
2950
  }
@@ -2669,14 +2953,15 @@ class Server {
2669
2953
 
2670
2954
  if (parsedIP.range() === "unspecified") {
2671
2955
  localhost = prettyPrintURL("localhost");
2956
+ loopbackIPv6 = prettyPrintURL("::1");
2672
2957
 
2673
- const networkIPv4 = await Server.internalIP("v4");
2958
+ const networkIPv4 = Server.findIp("v4", false);
2674
2959
 
2675
2960
  if (networkIPv4) {
2676
2961
  networkUrlIPv4 = prettyPrintURL(networkIPv4);
2677
2962
  }
2678
2963
 
2679
- const networkIPv6 = await Server.internalIP("v6");
2964
+ const networkIPv6 = Server.findIp("v6", false);
2680
2965
 
2681
2966
  if (networkIPv6) {
2682
2967
  networkUrlIPv6 = prettyPrintURL(networkIPv6);
@@ -2705,8 +2990,8 @@ class Server {
2705
2990
 
2706
2991
  this.logger.info("Project is running at:");
2707
2992
 
2708
- if (server) {
2709
- this.logger.info(`Server: ${colors.info(useColor, server)}`);
2993
+ if (host) {
2994
+ this.logger.info(`Server: ${colors.info(useColor, host)}`);
2710
2995
  }
2711
2996
 
2712
2997
  if (localhost || loopbackIPv4 || loopbackIPv6) {
@@ -2778,11 +3063,7 @@ class Server {
2778
3063
  if (this.options.bonjour) {
2779
3064
  const bonjourProtocol =
2780
3065
  /** @type {BonjourOptions} */
2781
- (this.options.bonjour).type ||
2782
- /** @type {ServerConfiguration} */
2783
- (this.options.server).type === "http"
2784
- ? "http"
2785
- : "https";
3066
+ (this.options.bonjour).type || this.isTlsServer ? "https" : "http";
2786
3067
 
2787
3068
  this.logger.info(
2788
3069
  `Broadcasting "${bonjourProtocol}" with subtype of "webpack" via ZeroConf DNS (Bonjour)`,
@@ -2804,8 +3085,8 @@ class Server {
2804
3085
  headers = headers(
2805
3086
  req,
2806
3087
  res,
2807
- /** @type {import("webpack-dev-middleware").API<IncomingMessage, ServerResponse>}*/
2808
- (this.middleware).context,
3088
+ // eslint-disable-next-line no-undefined
3089
+ this.middleware ? this.middleware.context : undefined,
2809
3090
  );
2810
3091
  }
2811
3092
 
@@ -2824,14 +3105,9 @@ class Server {
2824
3105
  headers = allHeaders;
2825
3106
  }
2826
3107
 
2827
- headers.forEach(
2828
- /**
2829
- * @param {{key: string, value: any}} header
2830
- */
2831
- (header) => {
2832
- res.setHeader(header.key, header.value);
2833
- },
2834
- );
3108
+ for (const { key, value } of headers) {
3109
+ res.setHeader(key, value);
3110
+ }
2835
3111
  }
2836
3112
 
2837
3113
  next();
@@ -2839,100 +3115,176 @@ class Server {
2839
3115
 
2840
3116
  /**
2841
3117
  * @private
2842
- * @param {{ [key: string]: string | undefined }} headers
2843
- * @param {string} headerToCheck
3118
+ * @param {string} value
2844
3119
  * @returns {boolean}
2845
3120
  */
2846
- checkHeader(headers, headerToCheck) {
3121
+ isHostAllowed(value) {
3122
+ const { allowedHosts } = this.options;
3123
+
2847
3124
  // allow user to opt out of this security check, at their own risk
2848
3125
  // by explicitly enabling allowedHosts
3126
+ if (allowedHosts === "all") {
3127
+ return true;
3128
+ }
3129
+
3130
+ // always allow localhost host, for convenience
3131
+ // allow if value is in allowedHosts
3132
+ if (Array.isArray(allowedHosts) && allowedHosts.length > 0) {
3133
+ for (const allowedHost of allowedHosts) {
3134
+ if (allowedHost === value) {
3135
+ return true;
3136
+ }
3137
+
3138
+ // support "." as a subdomain wildcard
3139
+ // e.g. ".example.com" will allow "example.com", "www.example.com", "subdomain.example.com", etc
3140
+ if (allowedHost.startsWith(".")) {
3141
+ // "example.com" (value === allowedHost.substring(1))
3142
+ // "*.example.com" (value.endsWith(allowedHost))
3143
+ if (
3144
+ value === allowedHost.substring(1) ||
3145
+ /** @type {string} */
3146
+ (value).endsWith(allowedHost)
3147
+ ) {
3148
+ return true;
3149
+ }
3150
+ }
3151
+ }
3152
+ }
3153
+
3154
+ // Also allow if `client.webSocketURL.hostname` provided
3155
+ if (
3156
+ this.options.client &&
3157
+ typeof (
3158
+ /** @type {ClientConfiguration} */
3159
+ (this.options.client).webSocketURL
3160
+ ) !== "undefined"
3161
+ ) {
3162
+ return (
3163
+ /** @type {WebSocketURL} */
3164
+ (/** @type {ClientConfiguration} */ (this.options.client).webSocketURL)
3165
+ .hostname === value
3166
+ );
3167
+ }
3168
+
3169
+ return false;
3170
+ }
3171
+
3172
+ /**
3173
+ * @private
3174
+ * @param {{ [key: string]: string | undefined }} headers
3175
+ * @param {string} headerToCheck
3176
+ * @param {boolean} validateHost
3177
+ * @returns {boolean}
3178
+ */
3179
+ isValidHost(headers, headerToCheck, validateHost = true) {
2849
3180
  if (this.options.allowedHosts === "all") {
2850
3181
  return true;
2851
3182
  }
2852
3183
 
2853
3184
  // get the Host header and extract hostname
2854
3185
  // we don't care about port not matching
2855
- const hostHeader = headers[headerToCheck];
3186
+ const header = headers[headerToCheck];
2856
3187
 
2857
- if (!hostHeader) {
3188
+ if (!header) {
2858
3189
  return false;
2859
3190
  }
2860
3191
 
2861
- if (/^(file|.+-extension):/i.test(hostHeader)) {
3192
+ if (DEFAULT_ALLOWED_PROTOCOLS.test(header)) {
2862
3193
  return true;
2863
3194
  }
2864
3195
 
2865
3196
  // use the node url-parser to retrieve the hostname from the host-header.
2866
3197
  const hostname = url.parse(
2867
- // if hostHeader doesn't have scheme, add // for parsing.
2868
- /^(.+:)?\/\//.test(hostHeader) ? hostHeader : `//${hostHeader}`,
3198
+ // if header doesn't have scheme, add // for parsing.
3199
+ /^(.+:)?\/\//.test(header) ? header : `//${header}`,
2869
3200
  false,
2870
3201
  true,
2871
3202
  ).hostname;
2872
3203
 
3204
+ if (hostname === null) {
3205
+ return false;
3206
+ }
3207
+
3208
+ if (this.isHostAllowed(hostname)) {
3209
+ return true;
3210
+ }
3211
+
2873
3212
  // always allow requests with explicit IPv4 or IPv6-address.
2874
3213
  // A note on IPv6 addresses:
2875
- // hostHeader will always contain the brackets denoting
3214
+ // header will always contain the brackets denoting
2876
3215
  // an IPv6-address in URLs,
2877
3216
  // these are removed from the hostname in url.parse(),
2878
3217
  // so we have the pure IPv6-address in hostname.
2879
3218
  // For convenience, always allow localhost (hostname === 'localhost')
2880
3219
  // and its subdomains (hostname.endsWith(".localhost")).
2881
3220
  // allow hostname of listening address (hostname === this.options.host)
2882
- const isValidHostname =
2883
- (hostname !== null && ipaddr.IPv4.isValid(hostname)) ||
2884
- (hostname !== null && ipaddr.IPv6.isValid(hostname)) ||
2885
- hostname === "localhost" ||
2886
- (hostname !== null && hostname.endsWith(".localhost")) ||
2887
- hostname === this.options.host;
2888
-
2889
- if (isValidHostname) {
3221
+ const isValidHostname = validateHost
3222
+ ? ipaddr.IPv4.isValid(hostname) ||
3223
+ ipaddr.IPv6.isValid(hostname) ||
3224
+ hostname === "localhost" ||
3225
+ hostname.endsWith(".localhost") ||
3226
+ hostname === this.options.host
3227
+ : false;
3228
+
3229
+ return isValidHostname;
3230
+ }
3231
+
3232
+ /**
3233
+ * @private
3234
+ * @param {{ [key: string]: string | undefined }} headers
3235
+ * @returns {boolean}
3236
+ */
3237
+ isSameOrigin(headers) {
3238
+ if (this.options.allowedHosts === "all") {
2890
3239
  return true;
2891
3240
  }
2892
3241
 
2893
- const { allowedHosts } = this.options;
3242
+ const originHeader = headers.origin;
2894
3243
 
2895
- // always allow localhost host, for convenience
2896
- // allow if hostname is in allowedHosts
2897
- if (Array.isArray(allowedHosts) && allowedHosts.length > 0) {
2898
- for (let hostIdx = 0; hostIdx < allowedHosts.length; hostIdx++) {
2899
- const allowedHost = allowedHosts[hostIdx];
3244
+ if (!originHeader) {
3245
+ return this.options.allowedHosts === "all";
3246
+ }
2900
3247
 
2901
- if (allowedHost === hostname) {
2902
- return true;
2903
- }
3248
+ if (DEFAULT_ALLOWED_PROTOCOLS.test(originHeader)) {
3249
+ return true;
3250
+ }
2904
3251
 
2905
- // support "." as a subdomain wildcard
2906
- // e.g. ".example.com" will allow "example.com", "www.example.com", "subdomain.example.com", etc
2907
- if (allowedHost[0] === ".") {
2908
- // "example.com" (hostname === allowedHost.substring(1))
2909
- // "*.example.com" (hostname.endsWith(allowedHost))
2910
- if (
2911
- hostname === allowedHost.substring(1) ||
2912
- /** @type {string} */ (hostname).endsWith(allowedHost)
2913
- ) {
2914
- return true;
2915
- }
2916
- }
2917
- }
3252
+ const origin = url.parse(originHeader, false, true).hostname;
3253
+
3254
+ if (origin === null) {
3255
+ return false;
2918
3256
  }
2919
3257
 
2920
- // Also allow if `client.webSocketURL.hostname` provided
2921
- if (
2922
- this.options.client &&
2923
- typeof (
2924
- /** @type {ClientConfiguration} */ (this.options.client).webSocketURL
2925
- ) !== "undefined"
2926
- ) {
2927
- return (
2928
- /** @type {WebSocketURL} */
2929
- (/** @type {ClientConfiguration} */ (this.options.client).webSocketURL)
2930
- .hostname === hostname
2931
- );
3258
+ if (this.isHostAllowed(origin)) {
3259
+ return true;
2932
3260
  }
2933
3261
 
2934
- // disallow
2935
- return false;
3262
+ const hostHeader = headers.host;
3263
+
3264
+ if (!hostHeader) {
3265
+ return this.options.allowedHosts === "all";
3266
+ }
3267
+
3268
+ if (DEFAULT_ALLOWED_PROTOCOLS.test(hostHeader)) {
3269
+ return true;
3270
+ }
3271
+
3272
+ const host = url.parse(
3273
+ // if hostHeader doesn't have scheme, add // for parsing.
3274
+ /^(.+:)?\/\//.test(hostHeader) ? hostHeader : `//${hostHeader}`,
3275
+ false,
3276
+ true,
3277
+ ).hostname;
3278
+
3279
+ if (host === null) {
3280
+ return false;
3281
+ }
3282
+
3283
+ if (this.isHostAllowed(host)) {
3284
+ return true;
3285
+ }
3286
+
3287
+ return origin === host;
2936
3288
  }
2937
3289
 
2938
3290
  /**
@@ -3104,7 +3456,7 @@ class Server {
3104
3456
 
3105
3457
  await /** @type {Promise<void>} */ (
3106
3458
  new Promise((resolve) => {
3107
- /** @type {import("http").Server} */
3459
+ /** @type {S} */
3108
3460
  (this.server).listen(listenOptions, () => {
3109
3461
  resolve();
3110
3462
  });
@@ -3190,10 +3542,10 @@ class Server {
3190
3542
  if (this.server) {
3191
3543
  await /** @type {Promise<void>} */ (
3192
3544
  new Promise((resolve) => {
3193
- /** @type {import("http").Server} */
3545
+ /** @type {S} */
3194
3546
  (this.server).close(() => {
3195
- this.server = null;
3196
-
3547
+ // eslint-disable-next-line no-undefined
3548
+ this.server = undefined;
3197
3549
  resolve();
3198
3550
  });
3199
3551
 
@@ -3212,7 +3564,6 @@ class Server {
3212
3564
  (this.middleware).close((error) => {
3213
3565
  if (error) {
3214
3566
  reject(error);
3215
-
3216
3567
  return;
3217
3568
  }
3218
3569
 
@@ -3221,7 +3572,8 @@ class Server {
3221
3572
  })
3222
3573
  );
3223
3574
 
3224
- this.middleware = null;
3575
+ // eslint-disable-next-line no-undefined
3576
+ this.middleware = undefined;
3225
3577
  }
3226
3578
  }
3227
3579