| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Copyright 2017 Google Inc. All rights reserved. | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |  * Modifications copyright (c) Microsoft Corporation. | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  |  * you may not use this file except in compliance with the License. | 
					
						
							|  |  |  |  * You may obtain a copy of the License at | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *     http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  |  * distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  | import fs from 'fs'; | 
					
						
							|  |  |  | import http from 'http'; | 
					
						
							|  |  |  | import https from 'https'; | 
					
						
							|  |  |  | import mime from 'mime'; | 
					
						
							|  |  |  | import type net from 'net'; | 
					
						
							|  |  |  | import path from 'path'; | 
					
						
							|  |  |  | import url from 'url'; | 
					
						
							|  |  |  | import util from 'util'; | 
					
						
							|  |  |  | import ws from 'ws'; | 
					
						
							|  |  |  | import zlib, { gzip } from 'zlib'; | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | const fulfillSymbol = Symbol('fullfil callback'); | 
					
						
							|  |  |  | const rejectSymbol = Symbol('reject callback'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  | const gzipAsync = util.promisify(gzip.bind(zlib)); | 
					
						
							| 
									
										
										
										
											2020-12-10 09:47:06 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  | export class TestServer { | 
					
						
							|  |  |  |   private _server: http.Server; | 
					
						
							|  |  |  |   private _wsServer: ws.WebSocketServer; | 
					
						
							|  |  |  |   private _dirPath: string; | 
					
						
							|  |  |  |   readonly debugServer: any; | 
					
						
							|  |  |  |   private _startTime: Date; | 
					
						
							|  |  |  |   private _cachedPathPrefix: string | null; | 
					
						
							|  |  |  |   private _sockets = new Set<net.Socket>(); | 
					
						
							|  |  |  |   private _routes = new Map<string, (arg0: http.IncomingMessage, arg1: http.ServerResponse) => any>(); | 
					
						
							|  |  |  |   private _auths = new Map<string, { username: string; password: string; }>(); | 
					
						
							|  |  |  |   private _csp = new Map<string, string>(); | 
					
						
							|  |  |  |   private _extraHeaders = new Map<string, object>(); | 
					
						
							|  |  |  |   private _gzipRoutes = new Set<string>(); | 
					
						
							|  |  |  |   private _requestSubscribers = new Map<string, Promise<any>>(); | 
					
						
							|  |  |  |   readonly PORT: number; | 
					
						
							|  |  |  |   readonly PREFIX: string; | 
					
						
							|  |  |  |   readonly CROSS_PROCESS_PREFIX: string; | 
					
						
							|  |  |  |   readonly EMPTY_PAGE: string; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   static async create(dirPath: string, port: number, loopback?: string): Promise<TestServer> { | 
					
						
							| 
									
										
										
										
											2021-04-09 07:59:09 -07:00
										 |  |  |     const server = new TestServer(dirPath, port, loopback); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |     await new Promise(x => server._server.once('listening', x)); | 
					
						
							|  |  |  |     return server; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |   static async createHTTPS(dirPath: string, port: number, loopback?: string): Promise<TestServer> { | 
					
						
							| 
									
										
										
										
											2021-04-09 07:59:09 -07:00
										 |  |  |     const server = new TestServer(dirPath, port, loopback, { | 
					
						
							| 
									
										
										
										
											2021-06-03 09:55:33 -07:00
										 |  |  |       key: await fs.promises.readFile(path.join(__dirname, 'key.pem')), | 
					
						
							|  |  |  |       cert: await fs.promises.readFile(path.join(__dirname, 'cert.pem')), | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |       passphrase: 'aaaa', | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     await new Promise(x => server._server.once('listening', x)); | 
					
						
							|  |  |  |     return server; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |   constructor(dirPath: string, port: number, loopback?: string, sslOptions?: object) { | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |     if (sslOptions) | 
					
						
							|  |  |  |       this._server = https.createServer(sslOptions, this._onRequest.bind(this)); | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       this._server = http.createServer(this._onRequest.bind(this)); | 
					
						
							|  |  |  |     this._server.on('connection', socket => this._onSocket(socket)); | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |     this._wsServer = new ws.WebSocketServer({ noServer: true }); | 
					
						
							| 
									
										
										
										
											2021-09-22 17:08:49 -07:00
										 |  |  |     this._server.on('upgrade', async (request, socket, head) => { | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |       const pathname = url.parse(request.url!).path; | 
					
						
							| 
									
										
										
										
											2022-09-29 13:04:19 -08:00
										 |  |  |       if (pathname === '/ws-401') { | 
					
						
							|  |  |  |         socket.write('HTTP/1.1 401 Unauthorized\r\n\r\nUnauthorized body'); | 
					
						
							|  |  |  |         socket.destroy(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2021-09-22 17:08:49 -07:00
										 |  |  |       if (pathname === '/ws-slow') | 
					
						
							|  |  |  |         await new Promise(f => setTimeout(f, 2000)); | 
					
						
							| 
									
										
										
										
											2021-12-22 11:17:34 -08:00
										 |  |  |       if (!['/ws', '/ws-slow'].includes(pathname)) { | 
					
						
							| 
									
										
										
										
											2021-09-22 17:08:49 -07:00
										 |  |  |         socket.write('HTTP/1.1 400 Bad Request\r\n\r\n'); | 
					
						
							|  |  |  |         socket.destroy(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       this._wsServer.handleUpgrade(request, socket, head, ws => { | 
					
						
							|  |  |  |         // Next emit is only for our internal 'connection' listeners.
 | 
					
						
							|  |  |  |         this._wsServer.emit('connection', ws, request); | 
					
						
							|  |  |  |       }); | 
					
						
							| 
									
										
										
										
											2021-05-26 15:18:52 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |     this._server.listen(port); | 
					
						
							|  |  |  |     this._dirPath = dirPath; | 
					
						
							| 
									
										
										
										
											2022-02-10 16:36:23 -08:00
										 |  |  |     this.debugServer = require('debug')('pw:testserver'); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     this._startTime = new Date(); | 
					
						
							|  |  |  |     this._cachedPathPrefix = null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-09 07:59:09 -07:00
										 |  |  |     const cross_origin = loopback || '127.0.0.1'; | 
					
						
							|  |  |  |     const same_origin = loopback || 'localhost'; | 
					
						
							| 
									
										
										
										
											2020-08-10 16:48:34 -07:00
										 |  |  |     const protocol = sslOptions ? 'https' : 'http'; | 
					
						
							|  |  |  |     this.PORT = port; | 
					
						
							| 
									
										
										
										
											2021-04-09 07:59:09 -07:00
										 |  |  |     this.PREFIX = `${protocol}://${same_origin}:${port}`; | 
					
						
							|  |  |  |     this.CROSS_PROCESS_PREFIX = `${protocol}://${cross_origin}:${port}`; | 
					
						
							|  |  |  |     this.EMPTY_PAGE = `${protocol}://${same_origin}:${port}/empty.html`; | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |   _onSocket(socket: net.Socket) { | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |     this._sockets.add(socket); | 
					
						
							| 
									
										
										
										
											2020-11-04 10:24:01 -08:00
										 |  |  |     // ECONNRESET and HPE_INVALID_EOF_STATE are legit errors given
 | 
					
						
							|  |  |  |     // that tab closing aborts outgoing connections to the server.
 | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |     socket.on('error', error => { | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |       if ((error as any).code !== 'ECONNRESET' && (error as any).code !== 'HPE_INVALID_EOF_STATE') | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |         throw error; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     socket.once('close', () => this._sockets.delete(socket)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |   enableHTTPCache(pathPrefix: string) { | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |     this._cachedPathPrefix = pathPrefix; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |   setAuth(path: string, username: string, password: string) { | 
					
						
							| 
									
										
										
										
											2020-03-24 09:11:56 -07:00
										 |  |  |     this.debugServer(`set auth for ${path} to ${username}:${password}`); | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |     this._auths.set(path, { username, password }); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |   enableGzip(path: string) { | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |     this._gzipRoutes.add(path); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |   setCSP(path: string, csp: string) { | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |     this._csp.set(path, csp); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |   setExtraHeaders(path: string, object: Record<string, string>) { | 
					
						
							| 
									
										
										
										
											2021-08-30 09:58:44 -07:00
										 |  |  |     this._extraHeaders.set(path, object); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |   async stop() { | 
					
						
							|  |  |  |     this.reset(); | 
					
						
							|  |  |  |     for (const socket of this._sockets) | 
					
						
							|  |  |  |       socket.destroy(); | 
					
						
							|  |  |  |     this._sockets.clear(); | 
					
						
							|  |  |  |     await new Promise(x => this._server.close(x)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |   setRoute(path: string, handler: (arg0: http.IncomingMessage & { postBody: Promise<Buffer> }, arg1: http.ServerResponse) => any) { | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |     this._routes.set(path, handler); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |   setRedirect(from: string, to: string) { | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |     this.setRoute(from, (req, res) => { | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |       const headers = this._extraHeaders.get(req.url!) || {}; | 
					
						
							| 
									
										
										
										
											2021-08-30 09:58:44 -07:00
										 |  |  |       res.writeHead(302, { ...headers, location: to }); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |       res.end(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |   waitForRequest(path: string): Promise<http.IncomingMessage & { postBody: Promise<Buffer> }> { | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |     let promise = this._requestSubscribers.get(path); | 
					
						
							|  |  |  |     if (promise) | 
					
						
							|  |  |  |       return promise; | 
					
						
							|  |  |  |     let fulfill, reject; | 
					
						
							|  |  |  |     promise = new Promise((f, r) => { | 
					
						
							|  |  |  |       fulfill = f; | 
					
						
							|  |  |  |       reject = r; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     promise[fulfillSymbol] = fulfill; | 
					
						
							|  |  |  |     promise[rejectSymbol] = reject; | 
					
						
							|  |  |  |     this._requestSubscribers.set(path, promise); | 
					
						
							|  |  |  |     return promise; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   reset() { | 
					
						
							|  |  |  |     this._routes.clear(); | 
					
						
							|  |  |  |     this._auths.clear(); | 
					
						
							|  |  |  |     this._csp.clear(); | 
					
						
							| 
									
										
										
										
											2021-08-30 09:58:44 -07:00
										 |  |  |     this._extraHeaders.clear(); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |     this._gzipRoutes.clear(); | 
					
						
							|  |  |  |     const error = new Error('Static Server has been reset'); | 
					
						
							|  |  |  |     for (const subscriber of this._requestSubscribers.values()) | 
					
						
							|  |  |  |       subscriber[rejectSymbol].call(null, error); | 
					
						
							|  |  |  |     this._requestSubscribers.clear(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |   _onRequest(request: http.IncomingMessage, response: http.ServerResponse) { | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |     request.on('error', error => { | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |       if ((error as any).code === 'ECONNRESET') | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |         response.end(); | 
					
						
							|  |  |  |       else | 
					
						
							|  |  |  |         throw error; | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |     (request as any).postBody = new Promise(resolve => { | 
					
						
							|  |  |  |       const chunks: Buffer[] = []; | 
					
						
							| 
									
										
										
										
											2022-03-18 09:00:52 -07:00
										 |  |  |       request.on('data', chunk => { | 
					
						
							|  |  |  |         chunks.push(chunk); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       request.on('end', () => resolve(Buffer.concat(chunks))); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |     const path = url.parse(request.url!).path; | 
					
						
							| 
									
										
										
										
											2021-09-13 15:38:27 -07:00
										 |  |  |     this.debugServer(`request ${request.method} ${path}`); | 
					
						
							|  |  |  |     if (this._auths.has(path)) { | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |       const auth = this._auths.get(path)!; | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |       const credentials = Buffer.from((request.headers.authorization || '').split(' ')[1] || '', 'base64').toString(); | 
					
						
							| 
									
										
										
										
											2020-03-24 09:11:56 -07:00
										 |  |  |       this.debugServer(`request credentials ${credentials}`); | 
					
						
							|  |  |  |       this.debugServer(`actual credentials ${auth.username}:${auth.password}`); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |       if (credentials !== `${auth.username}:${auth.password}`) { | 
					
						
							| 
									
										
										
										
											2020-03-24 09:11:56 -07:00
										 |  |  |         this.debugServer(`request write www-auth`); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |         response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Secure Area"' }); | 
					
						
							|  |  |  |         response.end('HTTP Error 401 Unauthorized: Access is denied'); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     // Notify request subscriber.
 | 
					
						
							| 
									
										
										
										
											2021-09-13 15:38:27 -07:00
										 |  |  |     if (this._requestSubscribers.has(path)) { | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |       this._requestSubscribers.get(path)![fulfillSymbol].call(null, request); | 
					
						
							| 
									
										
										
										
											2021-09-13 15:38:27 -07:00
										 |  |  |       this._requestSubscribers.delete(path); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2021-09-13 15:38:27 -07:00
										 |  |  |     const handler = this._routes.get(path); | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |     if (handler) | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |       handler.call(null, request, response); | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |     else | 
					
						
							| 
									
										
										
										
											2020-08-31 08:43:14 -07:00
										 |  |  |       this.serveFile(request, response); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |   async serveFile(request: http.IncomingMessage, response: http.ServerResponse, filePath?: string) { | 
					
						
							|  |  |  |     let pathName = url.parse(request.url!).path; | 
					
						
							| 
									
										
										
										
											2020-08-31 08:43:14 -07:00
										 |  |  |     if (!filePath) { | 
					
						
							|  |  |  |       if (pathName === '/') | 
					
						
							|  |  |  |         pathName = '/index.html'; | 
					
						
							|  |  |  |       filePath = path.join(this._dirPath, pathName.substring(1)); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (this._cachedPathPrefix !== null && filePath.startsWith(this._cachedPathPrefix)) { | 
					
						
							|  |  |  |       if (request.headers['if-modified-since']) { | 
					
						
							|  |  |  |         response.statusCode = 304; // not modified
 | 
					
						
							|  |  |  |         response.end(); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2020-01-03 15:34:51 -08:00
										 |  |  |       response.setHeader('Cache-Control', 'public, max-age=31536000, no-cache'); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |       response.setHeader('Last-Modified', this._startTime.toISOString()); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       response.setHeader('Cache-Control', 'no-cache, no-store'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (this._csp.has(pathName)) | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |       response.setHeader('Content-Security-Policy', this._csp.get(pathName)!); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-08-30 09:58:44 -07:00
										 |  |  |     if (this._extraHeaders.has(pathName)) { | 
					
						
							|  |  |  |       const object = this._extraHeaders.get(pathName); | 
					
						
							|  |  |  |       for (const key in object) | 
					
						
							|  |  |  |         response.setHeader(key, object[key]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |     const { err, data } = await fs.promises.readFile(filePath).then(data => ({ data, err: undefined })).catch(err => ({ data: undefined, err })); | 
					
						
							| 
									
										
										
										
											2020-12-10 09:47:06 -08:00
										 |  |  |     // The HTTP transaction might be already terminated after async hop here - do nothing in this case.
 | 
					
						
							|  |  |  |     if (response.writableEnded) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     if (err) { | 
					
						
							|  |  |  |       response.statusCode = 404; | 
					
						
							| 
									
										
										
										
											2022-07-29 11:46:48 -07:00
										 |  |  |       response.setHeader('Content-Type', 'text/plain'); | 
					
						
							| 
									
										
										
										
											2020-12-10 09:47:06 -08:00
										 |  |  |       response.end(`File not found: ${filePath}`); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const extension = filePath.substring(filePath.lastIndexOf('.') + 1); | 
					
						
							| 
									
										
										
										
											2021-08-25 11:18:35 -07:00
										 |  |  |     const mimeType = mime.getType(extension) || 'application/octet-stream'; | 
					
						
							| 
									
										
										
										
											2020-12-10 09:47:06 -08:00
										 |  |  |     const isTextEncoding = /^text\/|^application\/(javascript|json)/.test(mimeType); | 
					
						
							|  |  |  |     const contentType = isTextEncoding ? `${mimeType}; charset=utf-8` : mimeType; | 
					
						
							|  |  |  |     response.setHeader('Content-Type', contentType); | 
					
						
							|  |  |  |     if (this._gzipRoutes.has(pathName)) { | 
					
						
							|  |  |  |       response.setHeader('Content-Encoding', 'gzip'); | 
					
						
							|  |  |  |       const result = await gzipAsync(data); | 
					
						
							|  |  |  |       // The HTTP transaction might be already terminated after async hop here.
 | 
					
						
							|  |  |  |       if (!response.writableEnded) | 
					
						
							|  |  |  |         response.end(result); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       response.end(data); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-03 17:52:22 -07:00
										 |  |  |   onceWebSocketConnection(handler) { | 
					
						
							|  |  |  |     this._wsServer.once('connection', handler); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-23 14:52:27 -07:00
										 |  |  |   waitForWebSocketConnectionRequest() { | 
					
						
							| 
									
										
										
										
											2022-09-28 16:01:13 -08:00
										 |  |  |     return new Promise<http.IncomingMessage & { headers: http.IncomingHttpHeaders }>(fullfil => { | 
					
						
							| 
									
										
										
										
											2021-04-23 14:52:27 -07:00
										 |  |  |       this._wsServer.once('connection', (ws, req) => fullfil(req)); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-26 15:18:52 -07:00
										 |  |  |   sendOnWebSocketConnection(data) { | 
					
						
							| 
									
										
										
										
											2021-12-22 11:17:34 -08:00
										 |  |  |     this.onceWebSocketConnection(ws => ws.send(data)); | 
					
						
							| 
									
										
										
										
											2019-11-18 18:18:28 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | } |