| 
1 | 1 | import _ = require('lodash');  | 
2 |  | -import now = require("performance-now");  | 
 | 2 | +import now = require('performance-now');  | 
3 | 3 | import net = require('net');  | 
4 | 4 | import tls = require('tls');  | 
5 | 5 | import http = require('http');  | 
6 | 6 | import http2 = require('http2');  | 
7 | 7 | import * as streams from 'stream';  | 
 | 8 | + | 
 | 9 | +import * as semver from 'semver';  | 
8 | 10 | import { makeDestroyable, DestroyableServer } from 'destroyable-server';  | 
9 | 11 | import httpolyglot = require('@httptoolkit/httpolyglot');  | 
10 | 12 | import {  | 
@@ -148,17 +150,37 @@ export async function createComboServer(  | 
148 | 150 |         const ca = await getCA(options.https);  | 
149 | 151 |         const defaultCert = ca.generateCertificate(options.https.defaultDomain ?? 'localhost');  | 
150 | 152 | 
 
  | 
 | 153 | +        const serverProtocolPreferences = options.http2 === true  | 
 | 154 | +            ? ['h2', 'http/1.1', 'http 1.1'] // 'http 1.1' is non-standard, but used by https-proxy-agent  | 
 | 155 | +                : options.http2 === 'fallback'  | 
 | 156 | +            ? ['http/1.1', 'http 1.1', 'h2']  | 
 | 157 | +                // options.http2 === false:  | 
 | 158 | +            : ['http/1.1', 'http 1.1'];  | 
 | 159 | + | 
 | 160 | +        const ALPNOption: tls.CommonConnectionOptions = semver.satisfies(process.version, '>=20.4.0')  | 
 | 161 | +            ? {  | 
 | 162 | +                // In modern Node (20+), ALPNProtocols will reject unknown protocols. To allow those (so we can  | 
 | 163 | +                // at least read the request, and hopefully handle HTTP-like cases - not uncommon) we use the new  | 
 | 164 | +                // ALPNCallback feature instead, which lets us dynamically accept unrecognized protocols:  | 
 | 165 | +                ALPNCallback: ({ protocols: clientProtocols }) => {  | 
 | 166 | +                    const preferredProtocol = serverProtocolPreferences.find(p => clientProtocols.includes(p));  | 
 | 167 | + | 
 | 168 | +                    // Wherever possible, we tell the client to use our preferred protocol  | 
 | 169 | +                    if (preferredProtocol) return preferredProtocol;  | 
 | 170 | + | 
 | 171 | +                    // If the client only offers protocols that we don't understand, shrug and accept:  | 
 | 172 | +                    else return clientProtocols[1];  | 
 | 173 | +                }  | 
 | 174 | +            } : {  | 
 | 175 | +                // In Node versions without ALPNCallback, we just set preferences directly:  | 
 | 176 | +                ALPNProtocols: serverProtocolPreferences  | 
 | 177 | +            }  | 
 | 178 | + | 
151 | 179 |         const tlsServer = tls.createServer({  | 
152 | 180 |             key: defaultCert.key,  | 
153 | 181 |             cert: defaultCert.cert,  | 
154 | 182 |             ca: [defaultCert.ca],  | 
155 |  | -            ALPNProtocols: options.http2 === true  | 
156 |  | -                ? ['h2', 'http/1.1', 'http 1.1'] // 'http 1.1' is non-standard, but used by https-proxy-agent  | 
157 |  | -                    : options.http2 === 'fallback'  | 
158 |  | -                ? ['http/1.1', 'http 1.1', 'h2']  | 
159 |  | -                    // false  | 
160 |  | -                : ['http/1.1', 'http 1.1'],  | 
161 |  | - | 
 | 183 | +            ...ALPNOption,  | 
162 | 184 |             SNICallback: (domain: string, cb: Function) => {  | 
163 | 185 |                 if (options.debug) console.log(`Generating certificate for ${domain}`);  | 
164 | 186 | 
 
  | 
 | 
0 commit comments