1- import type { IncomingMessage , Server as NodeHttpServer , ServerResponse } from 'node:http'
1+ import type { IncomingMessage , ServerResponse } from 'node:http'
22import type { Http2SecureServer } from 'node:http2'
3+ import type { Socket } from 'node:net'
34import type { ViteDevServer } from 'vite'
4- import { mkdtempSync } from 'node:fs '
5- import { createServer } from 'node:http '
5+ import { execFileSync } from 'node:child_process '
6+ import { mkdtempSync , readFileSync } from 'node:fs '
67import { createSecureServer } from 'node:http2'
8+ import { request } from 'node:https'
79import { tmpdir } from 'node:os'
810import { join } from 'node:path'
9- import { getPort } from 'get-port-please '
10- import { afterEach , describe , expect , it } from 'vitest '
11+ import { afterEach , beforeAll , describe , expect , it } from 'vitest '
12+ import { WebSocket } from 'ws '
1113import { viteDevframeHub } from '../src/hub'
1214
1315type ConnectMiddleware = ( req : IncomingMessage , res : ServerResponse , next : ( ) => void ) => void
1416
17+ function hasOpenssl ( ) : boolean {
18+ try {
19+ execFileSync ( 'openssl' , [ 'version' ] , { stdio : 'ignore' } )
20+ return true
21+ }
22+ catch {
23+ return false
24+ }
25+ }
26+
27+ const opensslAvailable = hasOpenssl ( )
28+ if ( ! opensslAvailable )
29+ console . warn ( '[vite hub test] openssl not found, skipping the https dev server test' )
30+
1531/**
16- * Vite on `server.https` hands plugins an `Http2SecureServer` (with
17- * `allowHTTP1`), which is not a `node:http` `Server`. Requests are served
18- * here over a plain HTTP server running the same middleware stack, so the
19- * test needs no certificate.
32+ * Vite on `server.https` hands plugins an `Http2SecureServer` with
33+ * `allowHTTP1`, which is not a `node:http` `Server`. This one runs the
34+ * plugin's connect middlewares like Vite does.
2035 */
21- function fakeHttpsViteServer ( ) {
36+ function fakeHttpsViteServer ( tls : { key : string , cert : string } ) {
2237 const stack : ConnectMiddleware [ ] = [ ]
23- const requestServer : NodeHttpServer = createServer ( ( req , res ) => {
38+ const httpServer : Http2SecureServer = createSecureServer ( { ...tls , allowHTTP1 : true } )
39+ httpServer . on ( 'request' , ( req : IncomingMessage , res : ServerResponse ) => {
2440 let i = 0
2541 const next = ( ) : void => {
2642 const handler = stack [ i ++ ]
@@ -33,46 +49,99 @@ function fakeHttpsViteServer() {
3349 }
3450 next ( )
3551 } )
36- const httpServer : Http2SecureServer = createSecureServer ( { allowHTTP1 : true } )
52+ const sockets = new Set < Socket > ( )
53+ httpServer . on ( 'secureConnection' , ( socket : Socket ) => {
54+ sockets . add ( socket )
55+ socket . once ( 'close' , ( ) => sockets . delete ( socket ) )
56+ } )
3757 const server = {
3858 httpServer,
3959 resolvedUrls : null ,
4060 middlewares : { use : ( handler : ConnectMiddleware ) => stack . push ( handler ) } ,
4161 }
42- return { server, httpServer, requestServer }
62+ const close = async ( ) : Promise < void > => {
63+ for ( const socket of sockets )
64+ socket . destroy ( )
65+ await new Promise < void > ( resolve => httpServer . close ( ( ) => resolve ( ) ) )
66+ }
67+ return { server, httpServer, close }
68+ }
69+
70+ function getInsecure ( url : string ) : Promise < { status : number , body : string } > {
71+ return new Promise ( ( resolve , reject ) => {
72+ request ( url , { rejectUnauthorized : false } , ( res ) => {
73+ let body = ''
74+ res . setEncoding ( 'utf8' )
75+ res . on ( 'data' , ( chunk : string ) => body += chunk )
76+ res . on ( 'end' , ( ) => resolve ( { status : res . statusCode ?? 0 , body } ) )
77+ } ) . on ( 'error' , reject ) . end ( )
78+ } )
79+ }
80+
81+ function openWs ( url : string ) : Promise < WebSocket > {
82+ return new Promise ( ( resolve , reject ) => {
83+ const ws = new WebSocket ( url , { rejectUnauthorized : false } )
84+ ws . once ( 'open' , ( ) => resolve ( ws ) )
85+ ws . once ( 'error' , reject )
86+ } )
4387}
4488
45- describe ( 'viteDevframeHub' , ( ) => {
89+ describe . skipIf ( ! opensslAvailable ) ( 'viteDevframeHub' , ( ) => {
90+ let tls : { key : string , cert : string }
4691 let cleanup : ( ( ) => Promise < void > ) | undefined
4792
93+ beforeAll ( ( ) => {
94+ const dir = mkdtempSync ( join ( tmpdir ( ) , 'devframe-vite-hub-tls-' ) )
95+ const keyPath = join ( dir , 'key.pem' )
96+ const certPath = join ( dir , 'cert.pem' )
97+ execFileSync ( 'openssl' , [
98+ 'req' ,
99+ '-x509' ,
100+ '-newkey' ,
101+ 'rsa:2048' ,
102+ '-nodes' ,
103+ '-keyout' ,
104+ keyPath ,
105+ '-out' ,
106+ certPath ,
107+ '-days' ,
108+ '1' ,
109+ '-subj' ,
110+ '/CN=localhost' ,
111+ ] , { stdio : 'ignore' } )
112+ tls = { key : readFileSync ( keyPath , 'utf8' ) , cert : readFileSync ( certPath , 'utf8' ) }
113+ } )
114+
48115 afterEach ( async ( ) => {
49116 await cleanup ?.( )
50117 cleanup = undefined
51118 } )
52119
53120 it ( 'shares an https (http2) dev server for the WebSocket upgrade' , async ( ) => {
54121 const host = '127.0.0.1'
55- const port = await getPort ( { port : 19800 , host } )
56- const { server , httpServer, requestServer } = fakeHttpsViteServer ( )
57- await new Promise < void > ( resolve => requestServer . listen ( port , host , resolve ) )
122+ const { server , httpServer , close } = fakeHttpsViteServer ( tls )
123+ await new Promise < void > ( resolve => httpServer . listen ( 0 , host , resolve ) )
124+ const { port } = httpServer . address ( ) as { port : number }
58125
59126 const plugin = viteDevframeHub ( {
60127 ui : false ,
61128 auth : false ,
62129 quiet : true ,
63130 cwd : mkdtempSync ( join ( tmpdir ( ) , 'devframe-vite-hub-' ) ) ,
64131 } )
132+ let ws : WebSocket | undefined
65133 cleanup = async ( ) => {
66- httpServer . emit ( 'close' )
134+ ws ?. terminate ( )
67135 await ( plugin . closeBundle as ( ) => Promise < void > ) ( )
68- requestServer . close ( )
69- requestServer . closeAllConnections ( )
136+ await close ( )
70137 }
71138 await ( plugin . configureServer as ( s : ViteDevServer ) => Promise < void > ) ( server as any )
72139
73- const res = await fetch ( `http://${ host } :${ port } /__devframes/__connection.json` )
74- const meta = await res . json ( ) as { websocket ?: unknown }
75- expect ( meta . websocket ) . toEqual ( { path : '/__devframes/__ws' } )
76- expect ( httpServer . listenerCount ( 'upgrade' ) ) . toBe ( 1 )
140+ const res = await getInsecure ( `https://${ host } :${ port } /__devframes/__connection.json` )
141+ expect ( res . status ) . toBe ( 200 )
142+ expect ( ( JSON . parse ( res . body ) as { websocket ?: unknown } ) . websocket ) . toEqual ( { path : '/__devframes/__ws' } )
143+
144+ ws = await openWs ( `wss://${ host } :${ port } /__devframes/__ws` )
145+ expect ( ws . readyState ) . toBe ( WebSocket . OPEN )
77146 } )
78147} )
0 commit comments