@@ -19,6 +19,7 @@ const {
1919 PromiseResolve,
2020 PromiseWithResolvers,
2121 SafeSet,
22+ StringPrototypeStartsWith,
2223 Symbol,
2324 SymbolAsyncDispose,
2425 SymbolAsyncIterator,
@@ -407,6 +408,8 @@ const endpointRegistry = new SafeSet();
407408 * @property {string|string[] } [alpn] The ALPN protocol identifier(s).
408409 * For client sessions, a single string. For server sessions, an array
409410 * of protocol names in preference order.
411+ * @property {boolean } [autoWrap] Whether to provide the session wrapped in the
412+ * application matching its ALPN (e.g. an Http3Session for 'h3').
410413 * @property {string } [ciphers] The TLS ciphers
411414 * @property {string } [groups] The TLS key-exchange groups
412415 * @property {Array<'zlib'|'brotli'|'zstd'> } [certificateCompression] The
@@ -1320,6 +1323,21 @@ function updateHeaderInterest(handle, inner) {
13201323 ) ;
13211324}
13221325
1326+ /**
1327+ * Wraps a new session in the application matching its ALPN, if any. The
1328+ * http3 module is loaded lazily, as it depends on this one.
1329+ * @param {QuicSession } session
1330+ * @param {string } alpn
1331+ * @returns {QuicSession|Http3Session }
1332+ */
1333+ function autoWrapSession ( session , alpn ) {
1334+ if ( alpn === 'h3' || StringPrototypeStartsWith ( alpn , 'h3-' ) ) {
1335+ const { Http3Session } = require ( 'internal/quic/http3' ) ;
1336+ return Http3Session . from ( session ) ;
1337+ }
1338+ return session ;
1339+ }
1340+
13231341/**
13241342 * Applies session and stream callbacks from an options object to a session.
13251343 * @param {QuicSession } session
@@ -4385,6 +4403,7 @@ class QuicEndpoint {
43854403 stats : undefined ,
43864404 truncatedReads : undefined ,
43874405 onsession : undefined ,
4406+ autoWrap : undefined ,
43884407 sessionCallbacks : undefined ,
43894408 } ;
43904409
@@ -4690,10 +4709,12 @@ class QuicEndpoint {
46904709 onwanttrailers,
46914710 // Stored on the endpoint and applied to each incoming session.
46924711 truncatedReads,
4712+ autoWrap,
46934713 ...rest
46944714 } = options ;
46954715
46964716 inner . truncatedReads = truncatedReads ;
4717+ inner . autoWrap = autoWrap ;
46974718
46984719 // Store session and stream callbacks to apply to each new incoming session.
46994720 inner . sessionCallbacks = {
@@ -4725,15 +4746,17 @@ class QuicEndpoint {
47254746 * Initiates a session with a remote endpoint.
47264747 * @param {object } address
47274748 * @param {SessionOptions } [options]
4728- * @returns {QuicSession }
4749+ * @param {string } alpn The client's offered ALPN
4750+ * @returns {QuicSession|Http3Session }
47294751 */
4730- [ kConnect ] ( address , options ) {
4752+ [ kConnect ] ( address , options , alpn ) {
47314753 assertEndpointNotClosedOrClosing ( this ) ;
47324754 assertEndpointIsNotBusy ( this ) ;
47334755 validateObject ( options , 'options' ) ;
47344756 const {
47354757 sessionTicket,
47364758 truncatedReads,
4759+ autoWrap,
47374760 ...rest
47384761 } = options ;
47394762
@@ -4750,7 +4773,7 @@ class QuicEndpoint {
47504773 if ( options . verifyPeer !== undefined ) {
47514774 session [ kVerifyPeer ] = options . verifyPeer ;
47524775 }
4753- return session ;
4776+ return autoWrap ? autoWrapSession ( session , alpn ) : session ;
47544777 }
47554778
47564779 /**
@@ -4984,6 +5007,8 @@ class QuicEndpoint {
49845007 if ( inner . sessionCallbacks ) {
49855008 applyCallbacks ( session , inner . sessionCallbacks ) ;
49865009 }
5010+ const wrapped = inner . autoWrap ?
5011+ autoWrapSession ( session , session . alpnProtocol ) : session ;
49875012 if ( onEndpointServerSessionChannel . hasSubscribers ) {
49885013 onEndpointServerSessionChannel . publish ( {
49895014 __proto__ : null ,
@@ -4997,7 +5022,7 @@ class QuicEndpoint {
49975022 // endpoint with the error rather than surfacing as an unhandled
49985023 // exception or unhandled rejection coming out of the C++ -> JS
49995024 // boundary.
5000- safeCallbackInvoke ( inner . onsession , this , session ) ;
5025+ safeCallbackInvoke ( inner . onsession , this , wrapped ) ;
50015026 }
50025027
50035028 // Called by the QuicSession when it closes to remove itself from
@@ -5472,6 +5497,7 @@ function processSessionOptions(options, config = kEmptyObject) {
54725497 streamIdleTimeout,
54735498 verifyPeer = 'auto' ,
54745499 truncatedReads = 'error' ,
5500+ autoWrap,
54755501 // Session callbacks that can be set at construction time to avoid
54765502 // race conditions with events that fire during or immediately
54775503 // after the handshake.
@@ -5554,6 +5580,8 @@ function processSessionOptions(options, config = kEmptyObject) {
55545580
55555581 const tls = processTlsOptions ( options , forServer ) ;
55565582
5583+ if ( autoWrap !== undefined ) validateBoolean ( autoWrap , 'options.autoWrap' ) ;
5584+
55575585 const actualEndpoint = processEndpointOption ( endpoint ,
55585586 reuseEndpoint ,
55595587 forServer ,
@@ -5584,6 +5612,7 @@ function processSessionOptions(options, config = kEmptyObject) {
55845612 } ,
55855613 verifyPeer,
55865614 truncatedReads,
5615+ autoWrap : autoWrap ?? true ,
55875616 qlog,
55885617 maxPayloadSize,
55895618 unacknowledgedPacketThreshold,
@@ -5675,7 +5704,8 @@ async function connect(address, options = kEmptyObject) {
56755704 } ) ;
56765705 }
56775706
5678- const session = endpoint [ kConnect ] ( address [ kSocketAddressHandle ] , rest ) ;
5707+ const session = endpoint [ kConnect ] ( address [ kSocketAddressHandle ] , rest ,
5708+ options . alpn ) ;
56795709
56805710 if ( onEndpointClientSessionChannel . hasSubscribers ) {
56815711 onEndpointClientSessionChannel . publish ( {
0 commit comments