@@ -15,6 +15,9 @@ import type {
1515 NetworkHttpServerListenRawBridgeRef ,
1616 RegisterHandleBridgeFn ,
1717 UnregisterHandleBridgeFn ,
18+ UpgradeSocketWriteRawBridgeRef ,
19+ UpgradeSocketEndRawBridgeRef ,
20+ UpgradeSocketDestroyRawBridgeRef ,
1821} from "../shared/bridge-contract.js" ;
1922
2023// Declare host bridge References
@@ -32,6 +35,18 @@ declare const _networkHttpServerCloseRaw:
3235 | NetworkHttpServerCloseRawBridgeRef
3336 | undefined ;
3437
38+ declare const _upgradeSocketWriteRaw :
39+ | UpgradeSocketWriteRawBridgeRef
40+ | undefined ;
41+
42+ declare const _upgradeSocketEndRaw :
43+ | UpgradeSocketEndRawBridgeRef
44+ | undefined ;
45+
46+ declare const _upgradeSocketDestroyRaw :
47+ | UpgradeSocketDestroyRawBridgeRef
48+ | undefined ;
49+
3550declare const _registerHandle :
3651 | RegisterHandleBridgeFn
3752 | undefined ;
@@ -745,15 +760,27 @@ export class ClientRequest {
745760 statusText ?: string ;
746761 body ?: string ;
747762 trailers ?: Record < string , string > ;
763+ upgradeSocketId ?: number ;
748764 } ;
749765
750766 this . finished = true ;
751767
752768 // 101 Switching Protocols → fire 'upgrade' event
753769 if ( response . status === 101 ) {
754770 const res = new IncomingMessage ( response ) ;
755- const head = typeof Buffer !== "undefined" ? Buffer . alloc ( 0 ) : new Uint8Array ( 0 ) ;
756- this . _emit ( "upgrade" , res , this . socket , head ) ;
771+ // Use UpgradeSocket for bidirectional data relay when socketId is available
772+ let socket : FakeSocket | UpgradeSocket = this . socket ;
773+ if ( response . upgradeSocketId != null ) {
774+ socket = new UpgradeSocket ( response . upgradeSocketId , {
775+ host : this . _options . hostname as string ,
776+ port : Number ( this . _options . port ) || 80 ,
777+ } ) ;
778+ upgradeSocketInstances . set ( response . upgradeSocketId , socket ) ;
779+ }
780+ const head = typeof Buffer !== "undefined"
781+ ? ( response . body ? Buffer . from ( response . body , "base64" ) : Buffer . alloc ( 0 ) )
782+ : new Uint8Array ( 0 ) ;
783+ this . _emit ( "upgrade" , res , socket , head ) ;
757784 return ;
758785 }
759786
@@ -1020,6 +1047,8 @@ const serverRequestListeners = new Map<
10201047 number ,
10211048 ( incoming : ServerIncomingMessage , outgoing : ServerResponseBridge ) => unknown
10221049> ( ) ;
1050+ // Server instances indexed by serverId — used by upgrade dispatch to emit 'upgrade' events
1051+ const serverInstances = new Map < number , Server > ( ) ;
10231052
10241053class ServerIncomingMessage {
10251054 headers : Record < string , string > ;
@@ -1344,9 +1373,11 @@ class Server {
13441373 } else {
13451374 serverRequestListeners . set ( this . _serverId , ( ) => undefined ) ;
13461375 }
1376+ serverInstances . set ( this . _serverId , this ) ;
13471377 }
13481378
1349- private _emit ( event : string , ...args : unknown [ ] ) : void {
1379+ /** @internal Emit an event — used by upgrade dispatch to fire 'upgrade' events. */
1380+ _emit ( event : string , ...args : unknown [ ] ) : void {
13501381 const listeners = this . _listeners [ event ] ;
13511382 if ( ! listeners || listeners . length === 0 ) return ;
13521383 listeners . slice ( ) . forEach ( ( listener ) => listener ( ...args ) ) ;
@@ -1415,6 +1446,7 @@ class Server {
14151446 }
14161447 this . listening = false ;
14171448 this . _address = null ;
1449+ serverInstances . delete ( this . _serverId ) ;
14181450 if ( this . _handleId && typeof _unregisterHandle === "function" ) {
14191451 _unregisterHandle ( this . _handleId ) ;
14201452 }
@@ -1534,6 +1566,203 @@ async function dispatchServerRequest(
15341566 return JSON . stringify ( outgoing . serialize ( ) ) ;
15351567}
15361568
1569+ // Upgrade socket for bidirectional data relay through the host bridge
1570+ const upgradeSocketInstances = new Map < number , UpgradeSocket > ( ) ;
1571+
1572+ class UpgradeSocket {
1573+ remoteAddress : string ;
1574+ remotePort : number ;
1575+ localAddress = "127.0.0.1" ;
1576+ localPort = 0 ;
1577+ connecting = false ;
1578+ destroyed = false ;
1579+ writable = true ;
1580+ readable = true ;
1581+ readyState = "open" ;
1582+ bytesWritten = 0 ;
1583+ private _listeners : Record < string , EventListener [ ] > = { } ;
1584+ private _socketId : number ;
1585+
1586+ // Readable stream state stub for ws compatibility (socketOnClose checks _readableState.endEmitted)
1587+ _readableState = { endEmitted : false } ;
1588+ _writableState = { finished : false , errorEmitted : false } ;
1589+
1590+ constructor ( socketId : number , options ?: { host ?: string ; port ?: number } ) {
1591+ this . _socketId = socketId ;
1592+ this . remoteAddress = options ?. host || "127.0.0.1" ;
1593+ this . remotePort = options ?. port || 80 ;
1594+ }
1595+
1596+ setTimeout ( _ms : number , _cb ?: ( ) => void ) : this { return this ; }
1597+ setNoDelay ( _noDelay ?: boolean ) : this { return this ; }
1598+ setKeepAlive ( _enable ?: boolean , _delay ?: number ) : this { return this ; }
1599+ ref ( ) : this { return this ; }
1600+ unref ( ) : this { return this ; }
1601+ cork ( ) : void { }
1602+ uncork ( ) : void { }
1603+ pause ( ) : this { return this ; }
1604+ resume ( ) : this { return this ; }
1605+ address ( ) : { address : string ; family : string ; port : number } {
1606+ return { address : this . localAddress , family : "IPv4" , port : this . localPort } ;
1607+ }
1608+
1609+ on ( event : string , listener : EventListener ) : this {
1610+ if ( ! this . _listeners [ event ] ) this . _listeners [ event ] = [ ] ;
1611+ this . _listeners [ event ] . push ( listener ) ;
1612+ return this ;
1613+ }
1614+
1615+ addListener ( event : string , listener : EventListener ) : this {
1616+ return this . on ( event , listener ) ;
1617+ }
1618+
1619+ once ( event : string , listener : EventListener ) : this {
1620+ const wrapper = ( ...args : unknown [ ] ) : void => {
1621+ this . off ( event , wrapper ) ;
1622+ listener ( ...args ) ;
1623+ } ;
1624+ return this . on ( event , wrapper ) ;
1625+ }
1626+
1627+ off ( event : string , listener : EventListener ) : this {
1628+ if ( this . _listeners [ event ] ) {
1629+ const idx = this . _listeners [ event ] . indexOf ( listener ) ;
1630+ if ( idx !== - 1 ) this . _listeners [ event ] . splice ( idx , 1 ) ;
1631+ }
1632+ return this ;
1633+ }
1634+
1635+ removeListener ( event : string , listener : EventListener ) : this {
1636+ return this . off ( event , listener ) ;
1637+ }
1638+
1639+ removeAllListeners ( event ?: string ) : this {
1640+ if ( event ) {
1641+ delete this . _listeners [ event ] ;
1642+ } else {
1643+ this . _listeners = { } ;
1644+ }
1645+ return this ;
1646+ }
1647+
1648+ emit ( event : string , ...args : unknown [ ] ) : boolean {
1649+ const handlers = this . _listeners [ event ] ;
1650+ if ( handlers ) handlers . slice ( ) . forEach ( ( fn ) => fn . call ( this , ...args ) ) ;
1651+ return handlers !== undefined && handlers . length > 0 ;
1652+ }
1653+
1654+ listenerCount ( event : string ) : number {
1655+ return this . _listeners [ event ] ?. length || 0 ;
1656+ }
1657+
1658+ // Allow arbitrary property assignment (used by ws for Symbol properties)
1659+ [ key : string | symbol ] : unknown ;
1660+
1661+ write ( data : unknown , encodingOrCb ?: string | ( ( ) => void ) , cb ?: ( ( ) => void ) ) : boolean {
1662+ if ( this . destroyed ) return false ;
1663+ const callback = typeof encodingOrCb === "function" ? encodingOrCb : cb ;
1664+ if ( typeof _upgradeSocketWriteRaw !== "undefined" ) {
1665+ let base64 : string ;
1666+ if ( typeof Buffer !== "undefined" && Buffer . isBuffer ( data ) ) {
1667+ base64 = data . toString ( "base64" ) ;
1668+ } else if ( typeof data === "string" ) {
1669+ base64 = typeof Buffer !== "undefined" ? Buffer . from ( data ) . toString ( "base64" ) : btoa ( data ) ;
1670+ } else if ( data instanceof Uint8Array ) {
1671+ base64 = typeof Buffer !== "undefined" ? Buffer . from ( data ) . toString ( "base64" ) : btoa ( String . fromCharCode ( ...data ) ) ;
1672+ } else {
1673+ base64 = typeof Buffer !== "undefined" ? Buffer . from ( String ( data ) ) . toString ( "base64" ) : btoa ( String ( data ) ) ;
1674+ }
1675+ this . bytesWritten += base64 . length ;
1676+ _upgradeSocketWriteRaw . applySync ( undefined , [ this . _socketId , base64 ] ) ;
1677+ }
1678+ if ( callback ) callback ( ) ;
1679+ return true ;
1680+ }
1681+
1682+ end ( data ?: unknown ) : this {
1683+ if ( data ) this . write ( data ) ;
1684+ if ( typeof _upgradeSocketEndRaw !== "undefined" && ! this . destroyed ) {
1685+ _upgradeSocketEndRaw . applySync ( undefined , [ this . _socketId ] ) ;
1686+ }
1687+ this . writable = false ;
1688+ this . emit ( "finish" ) ;
1689+ return this ;
1690+ }
1691+
1692+ destroy ( err ?: Error ) : this {
1693+ if ( this . destroyed ) return this ;
1694+ this . destroyed = true ;
1695+ this . writable = false ;
1696+ this . readable = false ;
1697+ this . _readableState . endEmitted = true ;
1698+ this . _writableState . finished = true ;
1699+ if ( typeof _upgradeSocketDestroyRaw !== "undefined" ) {
1700+ _upgradeSocketDestroyRaw . applySync ( undefined , [ this . _socketId ] ) ;
1701+ }
1702+ upgradeSocketInstances . delete ( this . _socketId ) ;
1703+ if ( err ) this . emit ( "error" , err ) ;
1704+ this . emit ( "close" , false ) ;
1705+ return this ;
1706+ }
1707+
1708+ // Push data received from the host into this socket
1709+ _pushData ( data : Buffer | Uint8Array ) : void {
1710+ this . emit ( "data" , data ) ;
1711+ }
1712+
1713+ // Signal end-of-stream from the host
1714+ _pushEnd ( ) : void {
1715+ this . readable = false ;
1716+ this . _readableState . endEmitted = true ;
1717+ this . _writableState . finished = true ;
1718+ this . emit ( "end" ) ;
1719+ this . emit ( "close" , false ) ;
1720+ upgradeSocketInstances . delete ( this . _socketId ) ;
1721+ }
1722+ }
1723+
1724+ /** Route an incoming HTTP upgrade to the server's 'upgrade' event listeners. */
1725+ function dispatchUpgradeRequest (
1726+ serverId : number ,
1727+ requestJson : string ,
1728+ headBase64 : string ,
1729+ socketId : number
1730+ ) : void {
1731+ const server = serverInstances . get ( serverId ) ;
1732+ if ( ! server ) {
1733+ throw new Error ( `Unknown HTTP server for upgrade: ${ serverId } ` ) ;
1734+ }
1735+
1736+ const request = JSON . parse ( requestJson ) as SerializedServerRequest ;
1737+ const incoming = new ServerIncomingMessage ( request ) ;
1738+ const head = typeof Buffer !== "undefined" ? Buffer . from ( headBase64 , "base64" ) : new Uint8Array ( 0 ) ;
1739+
1740+ const socket = new UpgradeSocket ( socketId , {
1741+ host : incoming . headers [ "host" ] ?. split ( ":" ) [ 0 ] || "127.0.0.1" ,
1742+ } ) ;
1743+ upgradeSocketInstances . set ( socketId , socket ) ;
1744+
1745+ // Emit 'upgrade' on the server — ws.WebSocketServer listens for this
1746+ server . _emit ( "upgrade" , incoming , socket , head ) ;
1747+ }
1748+
1749+ /** Push data from host to an upgrade socket. */
1750+ function onUpgradeSocketData ( socketId : number , dataBase64 : string ) : void {
1751+ const socket = upgradeSocketInstances . get ( socketId ) ;
1752+ if ( socket ) {
1753+ const data = typeof Buffer !== "undefined" ? Buffer . from ( dataBase64 , "base64" ) : new Uint8Array ( 0 ) ;
1754+ socket . _pushData ( data ) ;
1755+ }
1756+ }
1757+
1758+ /** Signal end-of-stream from host to an upgrade socket. */
1759+ function onUpgradeSocketEnd ( socketId : number ) : void {
1760+ const socket = upgradeSocketInstances . get ( socketId ) ;
1761+ if ( socket ) {
1762+ socket . _pushEnd ( ) ;
1763+ }
1764+ }
1765+
15371766// Function-based ServerResponse constructor — allows .call() inheritance
15381767// used by light-my-request (Fastify's inject), which does
15391768// http.ServerResponse.call(this, req) + util.inherits(Response, http.ServerResponse)
@@ -1693,6 +1922,9 @@ exposeCustomGlobal("_httpsModule", https);
16931922exposeCustomGlobal ( "_http2Module" , http2 ) ;
16941923exposeCustomGlobal ( "_dnsModule" , dns ) ;
16951924exposeCustomGlobal ( "_httpServerDispatch" , dispatchServerRequest ) ;
1925+ exposeCustomGlobal ( "_httpServerUpgradeDispatch" , dispatchUpgradeRequest ) ;
1926+ exposeCustomGlobal ( "_upgradeSocketData" , onUpgradeSocketData ) ;
1927+ exposeCustomGlobal ( "_upgradeSocketEnd" , onUpgradeSocketEnd ) ;
16961928
16971929// Harden fetch API globals (non-writable, non-configurable)
16981930exposeCustomGlobal ( "fetch" , fetch ) ;
0 commit comments