@ -16,6 +16,7 @@ import (
"crypto/x509"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"log"
"math/rand/v2"
@ -589,11 +590,29 @@ func (i *jsIPN) dial(network, addr string) js.Value {
func ( i * jsIPN ) listen ( network , addr string ) js . Value {
return makePromise ( func ( ) ( any , error ) {
pc , err := i . ns . ListenPacket ( network , addr )
if err != nil {
return nil , err
switch network {
case "tcp" , "tcp4" , "tcp6" :
// netstack.ListenTCP only accepts tcp4/tcp6; bare "tcp"
// defaults to IPv4 to match net.Listen's typical behavior
// when given an unspecified address.
n := network
if n == "tcp" {
n = "tcp4"
}
ln , err := i . ns . ListenTCP ( n , addr )
if err != nil {
return nil , err
}
return wrapTCPListener ( ln ) , nil
case "udp" , "udp4" , "udp6" :
pc , err := i . ns . ListenPacket ( network , addr )
if err != nil {
return nil , err
}
return wrapPacketConn ( pc ) , nil
default :
return nil , fmt . Errorf ( "unsupported network %q" , network )
}
return wrapPacketConn ( pc ) , nil
} )
}
@ -711,6 +730,54 @@ func wrapConn(conn net.Conn) map[string]any {
}
}
// wrapTCPListener exposes a net.Listener to JavaScript as an object with
// accept/close/addr methods plus a Symbol.asyncIterator implementation, so
// callers can write `for await (const conn of listener)`.
func wrapTCPListener ( ln net . Listener ) js . Value {
obj := js . Global ( ) . Get ( "Object" ) . New ( )
obj . Set ( "accept" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) any {
return makePromise ( func ( ) ( any , error ) {
conn , err := ln . Accept ( )
if err != nil {
return nil , err
}
return wrapConn ( conn ) , nil
} )
} ) )
obj . Set ( "close" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) any {
return ln . Close ( ) != nil
} ) )
obj . Set ( "addr" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) any {
return ln . Addr ( ) . String ( )
} ) )
asyncIterSym := js . Global ( ) . Get ( "Symbol" ) . Get ( "asyncIterator" )
iterFactory := js . FuncOf ( func ( this js . Value , args [ ] js . Value ) any {
iter := js . Global ( ) . Get ( "Object" ) . New ( )
iter . Set ( "next" , js . FuncOf ( func ( this js . Value , args [ ] js . Value ) any {
return makePromise ( func ( ) ( any , error ) {
conn , err := ln . Accept ( )
if err != nil {
if errors . Is ( err , net . ErrClosed ) {
return map [ string ] any {
"value" : js . Undefined ( ) ,
"done" : true ,
} , nil
}
return nil , err
}
return map [ string ] any {
"value" : wrapConn ( conn ) ,
"done" : false ,
} , nil
} )
} ) )
return iter
} )
js . Global ( ) . Get ( "Reflect" ) . Call ( "set" , obj , asyncIterSym , iterFactory )
return obj
}
// wrapPacketConn exposes a net.PacketConn to JavaScript with binary (Uint8Array) I/O.
func wrapPacketConn ( pc net . PacketConn ) map [ string ] any {
return map [ string ] any {