feat(tsconnect): add TCP listening to ipn.listen
Extend ipn.listen to also accept "tcp"/"tcp4"/"tcp6" and return a
TCPListener bound to a netstack gonet.TCPListener. The listener
exposes accept/close/addr like a Go net.Listener and additionally
implements Symbol.asyncIterator so JS callers can write:
for await (const conn of listener) { ... }
The async iterator returns done when the listener is closed (via
errors.Is(net.ErrClosed)) and rejects on any other accept error.
Symbol-keyed properties are set via Reflect.set since syscall/js
only exposes string-keyed Set.
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"math/rand/v2"
|
"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 {
|
func (i *jsIPN) listen(network, addr string) js.Value {
|
||||||
return makePromise(func() (any, error) {
|
return makePromise(func() (any, error) {
|
||||||
pc, err := i.ns.ListenPacket(network, addr)
|
switch network {
|
||||||
if err != nil {
|
case "tcp", "tcp4", "tcp6":
|
||||||
return nil, err
|
// 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.
|
// wrapPacketConn exposes a net.PacketConn to JavaScript with binary (Uint8Array) I/O.
|
||||||
func wrapPacketConn(pc net.PacketConn) map[string]any {
|
func wrapPacketConn(pc net.PacketConn) map[string]any {
|
||||||
return map[string]any{
|
return map[string]any{
|
||||||
|
|||||||
Reference in New Issue
Block a user