@gjsify/http2 0.1.15 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,615 +1,107 @@
1
- // Reference: Node.js lib/http2.js, lib/internal/http2/core.js, lib/internal/http2/util.js
2
- // Reimplemented for GJS constants and settings are fully implemented,
3
- // createServer/connect are stubs (Soup 3.0 handles HTTP/2 transparently but
4
- // does not expose multiplexed streams needed for the full Node.js API)
5
-
6
- import { EventEmitter } from 'node:events';
7
-
8
- export const constants = {
9
- // NGHTTP2 Error Codes (RFC 7540 §7)
10
- NGHTTP2_NO_ERROR: 0x00,
11
- NGHTTP2_PROTOCOL_ERROR: 0x01,
12
- NGHTTP2_INTERNAL_ERROR: 0x02,
13
- NGHTTP2_FLOW_CONTROL_ERROR: 0x03,
14
- NGHTTP2_SETTINGS_TIMEOUT: 0x04,
15
- NGHTTP2_STREAM_CLOSED: 0x05,
16
- NGHTTP2_FRAME_SIZE_ERROR: 0x06,
17
- NGHTTP2_REFUSED_STREAM: 0x07,
18
- NGHTTP2_CANCEL: 0x08,
19
- NGHTTP2_COMPRESSION_ERROR: 0x09,
20
- NGHTTP2_CONNECT_ERROR: 0x0a,
21
- NGHTTP2_ENHANCE_YOUR_CALM: 0x0b,
22
- NGHTTP2_INADEQUATE_SECURITY: 0x0c,
23
- NGHTTP2_HTTP_1_1_REQUIRED: 0x0d,
24
- NGHTTP2_ERR_FRAME_SIZE_ERROR: -522,
25
- NGHTTP2_ERR_DEFERRED: -508,
26
- NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE: -509,
27
- NGHTTP2_ERR_INVALID_ARGUMENT: -501,
28
- NGHTTP2_ERR_STREAM_CLOSED: -510,
29
- NGHTTP2_ERR_NOMEM: -901,
30
- NGHTTP2_SESSION_SERVER: 0,
31
- NGHTTP2_SESSION_CLIENT: 1,
32
-
33
- NGHTTP2_STREAM_STATE_IDLE: 1,
34
- NGHTTP2_STREAM_STATE_OPEN: 2,
35
- NGHTTP2_STREAM_STATE_RESERVED_LOCAL: 3,
36
- NGHTTP2_STREAM_STATE_RESERVED_REMOTE: 4,
37
- NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL: 5,
38
- NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE: 6,
39
- NGHTTP2_STREAM_STATE_CLOSED: 7,
40
-
41
- NGHTTP2_FLAG_NONE: 0,
42
- NGHTTP2_FLAG_END_STREAM: 0x01,
43
- NGHTTP2_FLAG_END_HEADERS: 0x04,
44
- NGHTTP2_FLAG_ACK: 0x01,
45
- NGHTTP2_FLAG_PADDED: 0x08,
46
- NGHTTP2_FLAG_PRIORITY: 0x20,
47
-
48
- NGHTTP2_HCAT_REQUEST: 0,
49
- NGHTTP2_HCAT_RESPONSE: 1,
50
- NGHTTP2_HCAT_PUSH_RESPONSE: 2,
51
- NGHTTP2_HCAT_HEADERS: 3,
52
-
53
- NGHTTP2_NV_FLAG_NONE: 0,
54
- NGHTTP2_NV_FLAG_NO_INDEX: 0x01,
55
-
56
- NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: 0x01,
57
- NGHTTP2_SETTINGS_ENABLE_PUSH: 0x02,
58
- NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: 0x03,
59
- NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: 0x04,
60
- NGHTTP2_SETTINGS_MAX_FRAME_SIZE: 0x05,
61
- NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: 0x06,
62
- NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: 0x08,
63
-
64
- DEFAULT_SETTINGS_HEADER_TABLE_SIZE: 4096,
65
- DEFAULT_SETTINGS_ENABLE_PUSH: 1,
66
- DEFAULT_SETTINGS_MAX_CONCURRENT_STREAMS: 0xffffffff,
67
- DEFAULT_SETTINGS_INITIAL_WINDOW_SIZE: 65535,
68
- DEFAULT_SETTINGS_MAX_FRAME_SIZE: 16384,
69
- DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE: 65535,
70
- DEFAULT_SETTINGS_ENABLE_CONNECT_PROTOCOL: 0,
71
-
72
- MAX_MAX_FRAME_SIZE: 16777215,
73
- MIN_MAX_FRAME_SIZE: 16384,
74
- MAX_INITIAL_WINDOW_SIZE: 2147483647,
75
- NGHTTP2_DEFAULT_WEIGHT: 16,
76
-
77
- PADDING_STRATEGY_NONE: 0,
78
- PADDING_STRATEGY_ALIGNED: 1,
79
- PADDING_STRATEGY_MAX: 2,
80
- PADDING_STRATEGY_CALLBACK: 1,
81
-
82
- HTTP2_HEADER_STATUS: ':status',
83
- HTTP2_HEADER_METHOD: ':method',
84
- HTTP2_HEADER_AUTHORITY: ':authority',
85
- HTTP2_HEADER_SCHEME: ':scheme',
86
- HTTP2_HEADER_PATH: ':path',
87
- HTTP2_HEADER_PROTOCOL: ':protocol',
88
-
89
- HTTP2_HEADER_ACCEPT: 'accept',
90
- HTTP2_HEADER_ACCEPT_CHARSET: 'accept-charset',
91
- HTTP2_HEADER_ACCEPT_ENCODING: 'accept-encoding',
92
- HTTP2_HEADER_ACCEPT_LANGUAGE: 'accept-language',
93
- HTTP2_HEADER_ACCEPT_RANGES: 'accept-ranges',
94
- HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS: 'access-control-allow-credentials',
95
- HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS: 'access-control-allow-headers',
96
- HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS: 'access-control-allow-methods',
97
- HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN: 'access-control-allow-origin',
98
- HTTP2_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS: 'access-control-expose-headers',
99
- HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE: 'access-control-max-age',
100
- HTTP2_HEADER_ACCESS_CONTROL_REQUEST_HEADERS: 'access-control-request-headers',
101
- HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD: 'access-control-request-method',
102
- HTTP2_HEADER_AGE: 'age',
103
- HTTP2_HEADER_ALLOW: 'allow',
104
- HTTP2_HEADER_ALT_SVC: 'alt-svc',
105
- HTTP2_HEADER_AUTHORIZATION: 'authorization',
106
- HTTP2_HEADER_CACHE_CONTROL: 'cache-control',
107
- HTTP2_HEADER_CONNECTION: 'connection',
108
- HTTP2_HEADER_CONTENT_DISPOSITION: 'content-disposition',
109
- HTTP2_HEADER_CONTENT_ENCODING: 'content-encoding',
110
- HTTP2_HEADER_CONTENT_LANGUAGE: 'content-language',
111
- HTTP2_HEADER_CONTENT_LENGTH: 'content-length',
112
- HTTP2_HEADER_CONTENT_LOCATION: 'content-location',
113
- HTTP2_HEADER_CONTENT_MD5: 'content-md5',
114
- HTTP2_HEADER_CONTENT_RANGE: 'content-range',
115
- HTTP2_HEADER_CONTENT_SECURITY_POLICY: 'content-security-policy',
116
- HTTP2_HEADER_CONTENT_TYPE: 'content-type',
117
- HTTP2_HEADER_COOKIE: 'cookie',
118
- HTTP2_HEADER_DATE: 'date',
119
- HTTP2_HEADER_DNT: 'dnt',
120
- HTTP2_HEADER_EARLY_DATA: 'early-data',
121
- HTTP2_HEADER_ETAG: 'etag',
122
- HTTP2_HEADER_EXPECT: 'expect',
123
- HTTP2_HEADER_EXPECT_CT: 'expect-ct',
124
- HTTP2_HEADER_EXPIRES: 'expires',
125
- HTTP2_HEADER_FORWARDED: 'forwarded',
126
- HTTP2_HEADER_FROM: 'from',
127
- HTTP2_HEADER_HOST: 'host',
128
- HTTP2_HEADER_HTTP2_SETTINGS: 'http2-settings',
129
- HTTP2_HEADER_IF_MATCH: 'if-match',
130
- HTTP2_HEADER_IF_MODIFIED_SINCE: 'if-modified-since',
131
- HTTP2_HEADER_IF_NONE_MATCH: 'if-none-match',
132
- HTTP2_HEADER_IF_RANGE: 'if-range',
133
- HTTP2_HEADER_IF_UNMODIFIED_SINCE: 'if-unmodified-since',
134
- HTTP2_HEADER_KEEP_ALIVE: 'keep-alive',
135
- HTTP2_HEADER_LAST_MODIFIED: 'last-modified',
136
- HTTP2_HEADER_LINK: 'link',
137
- HTTP2_HEADER_LOCATION: 'location',
138
- HTTP2_HEADER_MAX_FORWARDS: 'max-forwards',
139
- HTTP2_HEADER_ORIGIN: 'origin',
140
- HTTP2_HEADER_PREFER: 'prefer',
141
- HTTP2_HEADER_PRIORITY: 'priority',
142
- HTTP2_HEADER_PROXY_AUTHENTICATE: 'proxy-authenticate',
143
- HTTP2_HEADER_PROXY_AUTHORIZATION: 'proxy-authorization',
144
- HTTP2_HEADER_PROXY_CONNECTION: 'proxy-connection',
145
- HTTP2_HEADER_RANGE: 'range',
146
- HTTP2_HEADER_REFERER: 'referer',
147
- HTTP2_HEADER_REFRESH: 'refresh',
148
- HTTP2_HEADER_RETRY_AFTER: 'retry-after',
149
- HTTP2_HEADER_SERVER: 'server',
150
- HTTP2_HEADER_SET_COOKIE: 'set-cookie',
151
- HTTP2_HEADER_STRICT_TRANSPORT_SECURITY: 'strict-transport-security',
152
- HTTP2_HEADER_TE: 'te',
153
- HTTP2_HEADER_TIMING_ALLOW_ORIGIN: 'timing-allow-origin',
154
- HTTP2_HEADER_TRAILER: 'trailer',
155
- HTTP2_HEADER_TRANSFER_ENCODING: 'transfer-encoding',
156
- HTTP2_HEADER_TK: 'tk',
157
- HTTP2_HEADER_UPGRADE: 'upgrade',
158
- HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS: 'upgrade-insecure-requests',
159
- HTTP2_HEADER_USER_AGENT: 'user-agent',
160
- HTTP2_HEADER_VARY: 'vary',
161
- HTTP2_HEADER_VIA: 'via',
162
- HTTP2_HEADER_WARNING: 'warning',
163
- HTTP2_HEADER_WWW_AUTHENTICATE: 'www-authenticate',
164
- HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS: 'x-content-type-options',
165
- HTTP2_HEADER_X_FORWARDED_FOR: 'x-forwarded-for',
166
- HTTP2_HEADER_X_FRAME_OPTIONS: 'x-frame-options',
167
- HTTP2_HEADER_X_XSS_PROTECTION: 'x-xss-protection',
168
- HTTP2_HEADER_PURPOSE: 'purpose',
169
-
170
- HTTP2_METHOD_ACL: 'ACL',
171
- HTTP2_METHOD_BASELINE_CONTROL: 'BASELINE-CONTROL',
172
- HTTP2_METHOD_BIND: 'BIND',
173
- HTTP2_METHOD_CHECKIN: 'CHECKIN',
174
- HTTP2_METHOD_CHECKOUT: 'CHECKOUT',
175
- HTTP2_METHOD_CONNECT: 'CONNECT',
176
- HTTP2_METHOD_COPY: 'COPY',
177
- HTTP2_METHOD_DELETE: 'DELETE',
178
- HTTP2_METHOD_GET: 'GET',
179
- HTTP2_METHOD_HEAD: 'HEAD',
180
- HTTP2_METHOD_LABEL: 'LABEL',
181
- HTTP2_METHOD_LINK: 'LINK',
182
- HTTP2_METHOD_LOCK: 'LOCK',
183
- HTTP2_METHOD_MERGE: 'MERGE',
184
- HTTP2_METHOD_MKACTIVITY: 'MKACTIVITY',
185
- HTTP2_METHOD_MKCALENDAR: 'MKCALENDAR',
186
- HTTP2_METHOD_MKCOL: 'MKCOL',
187
- HTTP2_METHOD_MKREDIRECTREF: 'MKREDIRECTREF',
188
- HTTP2_METHOD_MKWORKSPACE: 'MKWORKSPACE',
189
- HTTP2_METHOD_MOVE: 'MOVE',
190
- HTTP2_METHOD_OPTIONS: 'OPTIONS',
191
- HTTP2_METHOD_ORDERPATCH: 'ORDERPATCH',
192
- HTTP2_METHOD_PATCH: 'PATCH',
193
- HTTP2_METHOD_POST: 'POST',
194
- HTTP2_METHOD_PRI: 'PRI',
195
- HTTP2_METHOD_PROPFIND: 'PROPFIND',
196
- HTTP2_METHOD_PROPPATCH: 'PROPPATCH',
197
- HTTP2_METHOD_PUT: 'PUT',
198
- HTTP2_METHOD_REBIND: 'REBIND',
199
- HTTP2_METHOD_REPORT: 'REPORT',
200
- HTTP2_METHOD_SEARCH: 'SEARCH',
201
- HTTP2_METHOD_TRACE: 'TRACE',
202
- HTTP2_METHOD_UNBIND: 'UNBIND',
203
- HTTP2_METHOD_UNCHECKOUT: 'UNCHECKOUT',
204
- HTTP2_METHOD_UNLINK: 'UNLINK',
205
- HTTP2_METHOD_UNLOCK: 'UNLOCK',
206
- HTTP2_METHOD_UPDATE: 'UPDATE',
207
- HTTP2_METHOD_UPDATEREDIRECTREF: 'UPDATEREDIRECTREF',
208
- HTTP2_METHOD_VERSION_CONTROL: 'VERSION-CONTROL',
209
-
210
- HTTP_STATUS_CONTINUE: 100,
211
- HTTP_STATUS_SWITCHING_PROTOCOLS: 101,
212
- HTTP_STATUS_PROCESSING: 102,
213
- HTTP_STATUS_EARLY_HINTS: 103,
214
- HTTP_STATUS_OK: 200,
215
- HTTP_STATUS_CREATED: 201,
216
- HTTP_STATUS_ACCEPTED: 202,
217
- HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION: 203,
218
- HTTP_STATUS_NO_CONTENT: 204,
219
- HTTP_STATUS_RESET_CONTENT: 205,
220
- HTTP_STATUS_PARTIAL_CONTENT: 206,
221
- HTTP_STATUS_MULTI_STATUS: 207,
222
- HTTP_STATUS_ALREADY_REPORTED: 208,
223
- HTTP_STATUS_IM_USED: 226,
224
- HTTP_STATUS_MULTIPLE_CHOICES: 300,
225
- HTTP_STATUS_MOVED_PERMANENTLY: 301,
226
- HTTP_STATUS_FOUND: 302,
227
- HTTP_STATUS_SEE_OTHER: 303,
228
- HTTP_STATUS_NOT_MODIFIED: 304,
229
- HTTP_STATUS_USE_PROXY: 305,
230
- HTTP_STATUS_TEMPORARY_REDIRECT: 307,
231
- HTTP_STATUS_PERMANENT_REDIRECT: 308,
232
- HTTP_STATUS_BAD_REQUEST: 400,
233
- HTTP_STATUS_UNAUTHORIZED: 401,
234
- HTTP_STATUS_PAYMENT_REQUIRED: 402,
235
- HTTP_STATUS_FORBIDDEN: 403,
236
- HTTP_STATUS_NOT_FOUND: 404,
237
- HTTP_STATUS_METHOD_NOT_ALLOWED: 405,
238
- HTTP_STATUS_NOT_ACCEPTABLE: 406,
239
- HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED: 407,
240
- HTTP_STATUS_REQUEST_TIMEOUT: 408,
241
- HTTP_STATUS_CONFLICT: 409,
242
- HTTP_STATUS_GONE: 410,
243
- HTTP_STATUS_LENGTH_REQUIRED: 411,
244
- HTTP_STATUS_PRECONDITION_FAILED: 412,
245
- HTTP_STATUS_PAYLOAD_TOO_LARGE: 413,
246
- HTTP_STATUS_URI_TOO_LONG: 414,
247
- HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: 415,
248
- HTTP_STATUS_RANGE_NOT_SATISFIABLE: 416,
249
- HTTP_STATUS_EXPECTATION_FAILED: 417,
250
- HTTP_STATUS_TEAPOT: 418,
251
- HTTP_STATUS_MISDIRECTED_REQUEST: 421,
252
- HTTP_STATUS_UNPROCESSABLE_ENTITY: 422,
253
- HTTP_STATUS_LOCKED: 423,
254
- HTTP_STATUS_FAILED_DEPENDENCY: 424,
255
- HTTP_STATUS_TOO_EARLY: 425,
256
- HTTP_STATUS_UPGRADE_REQUIRED: 426,
257
- HTTP_STATUS_PRECONDITION_REQUIRED: 428,
258
- HTTP_STATUS_TOO_MANY_REQUESTS: 429,
259
- HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
260
- HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS: 451,
261
- HTTP_STATUS_INTERNAL_SERVER_ERROR: 500,
262
- HTTP_STATUS_NOT_IMPLEMENTED: 501,
263
- HTTP_STATUS_BAD_GATEWAY: 502,
264
- HTTP_STATUS_SERVICE_UNAVAILABLE: 503,
265
- HTTP_STATUS_GATEWAY_TIMEOUT: 504,
266
- HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED: 505,
267
- HTTP_STATUS_VARIANT_ALSO_NEGOTIATES: 506,
268
- HTTP_STATUS_INSUFFICIENT_STORAGE: 507,
269
- HTTP_STATUS_LOOP_DETECTED: 508,
270
- HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED: 509,
271
- HTTP_STATUS_NOT_EXTENDED: 510,
272
- HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED: 511,
273
- } as const;
274
-
275
-
276
- export interface Http2Settings {
277
- headerTableSize?: number;
278
- enablePush?: boolean;
279
- maxConcurrentStreams?: number;
280
- initialWindowSize?: number;
281
- maxFrameSize?: number;
282
- maxHeaderListSize?: number;
283
- enableConnectProtocol?: boolean;
284
- }
285
-
286
- const SETTINGS_MAP: [number, keyof Http2Settings, boolean][] = [
287
- [0x01, 'headerTableSize', false],
288
- [0x02, 'enablePush', true],
289
- [0x03, 'maxConcurrentStreams', false],
290
- [0x04, 'initialWindowSize', false],
291
- [0x05, 'maxFrameSize', false],
292
- [0x06, 'maxHeaderListSize', false],
293
- [0x08, 'enableConnectProtocol', true],
294
- ];
295
-
296
- // RFC 7540 §6.5
297
- export function getDefaultSettings(): Http2Settings {
298
- return {
299
- headerTableSize: 4096,
300
- enablePush: true,
301
- maxConcurrentStreams: 0xffffffff,
302
- initialWindowSize: 65535,
303
- maxFrameSize: 16384,
304
- maxHeaderListSize: 65535,
305
- enableConnectProtocol: false,
306
- };
307
- }
308
-
309
- // RFC 7540 §6.5.1 — each setting: 2-byte ID + 4-byte value (big-endian)
310
- export function getPackedSettings(settings?: Http2Settings): Uint8Array {
311
- if (!settings) return new Uint8Array(0);
312
-
313
- const pairs: [number, number][] = [];
314
- for (const [id, key, isBool] of SETTINGS_MAP) {
315
- const val = settings[key];
316
- if (val !== undefined) {
317
- pairs.push([id, isBool ? (val ? 1 : 0) : val as number]);
318
- }
319
- }
320
-
321
- const buf = new Uint8Array(pairs.length * 6);
322
- const view = new DataView(buf.buffer);
323
- for (let i = 0; i < pairs.length; i++) {
324
- const offset = i * 6;
325
- view.setUint16(offset, pairs[i][0], false);
326
- view.setUint32(offset + 2, pairs[i][1], false);
327
- }
328
- return buf;
329
- }
330
-
331
- export function getUnpackedSettings(buf: Uint8Array | ArrayBuffer): Http2Settings {
332
- const data = buf instanceof ArrayBuffer ? new Uint8Array(buf) : buf;
333
- if (data.byteLength % 6 !== 0) {
334
- throw new RangeError('Invalid packed settings length');
335
- }
336
-
337
- const result: Http2Settings = {};
338
- const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
339
-
340
- for (let i = 0; i < data.byteLength; i += 6) {
341
- const id = view.getUint16(i, false);
342
- const value = view.getUint32(i + 2, false);
343
-
344
- for (const [settingId, key, isBool] of SETTINGS_MAP) {
345
- if (id === settingId) {
346
- (result as Record<string, unknown>)[key] = isBool ? value !== 0 : value;
347
- break;
348
- }
349
- }
350
- }
351
- return result;
352
- }
353
-
354
- // Stub — Soup 3.0 handles HTTP/2 transparently but lacks multiplexed stream API
355
- export class Http2Session extends EventEmitter {
356
- readonly alpnProtocol: string | undefined = undefined;
357
- readonly encrypted: boolean = false;
358
- readonly type: number = constants.NGHTTP2_SESSION_CLIENT;
359
-
360
- private _closed = false;
361
- private _destroyed = false;
362
- private _settings: Http2Settings;
363
-
364
- constructor() {
365
- super();
366
- this._settings = getDefaultSettings();
367
- }
368
-
369
- get closed(): boolean { return this._closed; }
370
- get destroyed(): boolean { return this._destroyed; }
371
- get connecting(): boolean { return false; }
372
- get pendingSettingsAck(): boolean { return false; }
373
-
374
- get localSettings(): Http2Settings { return { ...this._settings }; }
375
- get remoteSettings(): Http2Settings { return getDefaultSettings(); }
376
-
377
- settings(settings: Http2Settings, callback?: () => void): void {
378
- Object.assign(this._settings, settings);
379
- if (callback) Promise.resolve().then(callback);
380
- }
381
-
382
- goaway(code?: number, _lastStreamId?: number, _data?: Uint8Array): void {
383
- this.emit('goaway', code ?? constants.NGHTTP2_NO_ERROR);
384
- }
385
-
386
- ping(payload?: Uint8Array, callback?: (err: Error | null, duration: number, payload: Uint8Array) => void): boolean {
387
- const buf = payload || new Uint8Array(8);
388
- if (callback) Promise.resolve().then(() => callback(null, 0, buf));
389
- return true;
390
- }
391
-
392
- close(callback?: () => void): void {
393
- if (this._closed) return;
394
- this._closed = true;
395
- this.emit('close');
396
- if (callback) callback();
397
- }
398
-
399
- destroy(error?: Error, code?: number): void {
400
- if (this._destroyed) return;
401
- this._destroyed = true;
402
- this._closed = true;
403
- if (error) this.emit('error', error);
404
- this.emit('close');
405
- if (code !== undefined) {
406
- this.goaway(code);
407
- }
408
- }
409
-
410
- ref(): void {}
411
- unref(): void {}
412
- }
413
-
414
- export class ServerHttp2Session extends Http2Session {
415
- readonly type = constants.NGHTTP2_SESSION_SERVER;
416
-
417
- altsvc(_alt: string, _originOrStream: string | number): void {}
418
-
419
- origin(..._origins: string[]): void {}
420
- }
421
-
422
- export class ClientHttp2Session extends Http2Session {
423
- request(_headers?: Record<string, string | string[]>, _options?: unknown): Http2Stream {
424
- throw new Error('http2 client requests are not yet implemented in GJS');
425
- }
426
- }
427
-
428
- export class Http2Stream extends EventEmitter {
429
- readonly id: number = 0;
430
- readonly session: Http2Session | null = null;
431
- readonly sentHeaders: Record<string, string | string[]> = {};
432
- readonly sentInfoHeaders: Record<string, string | string[]>[] = [];
433
-
434
- private _closed = false;
435
- private _destroyed = false;
436
- private _state: number = constants.NGHTTP2_STREAM_STATE_IDLE;
437
-
438
- get closed(): boolean { return this._closed; }
439
- get destroyed(): boolean { return this._destroyed; }
440
- get pending(): boolean { return this.id === 0; }
441
- get state(): number { return this._state; }
442
- get endAfterHeaders(): boolean { return false; }
443
- get bufferSize(): number { return 0; }
444
-
445
- get rstCode(): number { return constants.NGHTTP2_NO_ERROR; }
446
-
447
- close(code?: number, callback?: () => void): void {
448
- if (this._closed) return;
449
- this._closed = true;
450
- this._state = constants.NGHTTP2_STREAM_STATE_CLOSED;
451
- this.emit('close', code ?? constants.NGHTTP2_NO_ERROR);
452
- if (callback) callback();
453
- }
454
-
455
- destroy(error?: Error): void {
456
- if (this._destroyed) return;
457
- this._destroyed = true;
458
- this._closed = true;
459
- this._state = constants.NGHTTP2_STREAM_STATE_CLOSED;
460
- if (error) this.emit('error', error);
461
- this.emit('close');
462
- }
463
-
464
- priority(_options: { exclusive?: boolean; parent?: number; weight?: number; silent?: boolean }): void {}
465
-
466
- setTimeout(msecs: number, callback?: () => void): void {
467
- if (callback) setTimeout(callback, msecs);
468
- }
469
- }
470
-
471
- export class ServerHttp2Stream extends Http2Stream {
472
- readonly headersSent: boolean = false;
473
- readonly pushAllowed: boolean = false;
474
-
475
- respond(_headers?: Record<string, string | string[] | number>, _options?: unknown): void {
476
- throw new Error('http2 server respond is not yet implemented in GJS');
477
- }
478
-
479
- respondWithFD(_fd: number | unknown, _headers?: Record<string, string | string[]>, _options?: unknown): void {
480
- throw new Error('http2 respondWithFD is not yet implemented in GJS');
481
- }
482
-
483
- respondWithFile(_path: string, _headers?: Record<string, string | string[]>, _options?: unknown): void {
484
- throw new Error('http2 respondWithFile is not yet implemented in GJS');
485
- }
486
-
487
- pushStream(
488
- _headers: Record<string, string | string[]>,
489
- _options: unknown,
490
- _callback: (err: Error | null, pushStream: ServerHttp2Stream, headers: Record<string, string | string[]>) => void,
491
- ): void {
492
- throw new Error('http2 server push is not yet implemented in GJS');
493
- }
494
-
495
- additionalHeaders(_headers: Record<string, string | string[]>): void {}
496
- }
497
-
498
- export class ClientHttp2Stream extends Http2Stream {}
499
-
500
- export class Http2ServerRequest extends EventEmitter {
501
- readonly headers: Record<string, string | string[] | undefined> = {};
502
- readonly httpVersion: string = '2.0';
503
- readonly method: string = 'GET';
504
- readonly url: string = '/';
505
- readonly stream: Http2Stream | null = null;
506
- readonly authority: string = '';
507
- readonly scheme: string = 'https';
508
-
509
- get complete(): boolean { return true; }
510
-
511
- setTimeout(msecs: number, callback?: () => void): this {
512
- if (callback) setTimeout(callback, msecs);
513
- return this;
514
- }
515
- }
516
-
517
- export class Http2ServerResponse extends EventEmitter {
518
- statusCode: number = 200;
519
- readonly stream: Http2Stream | null = null;
520
- readonly headersSent: boolean = false;
521
-
522
- private _headers: Record<string, string | string[] | number> = {};
523
-
524
- setHeader(name: string, value: string | string[] | number): this {
525
- this._headers[name.toLowerCase()] = value;
526
- return this;
527
- }
528
-
529
- getHeader(name: string): string | string[] | number | undefined {
530
- return this._headers[name.toLowerCase()];
531
- }
1
+ // Reference: Node.js lib/http2.js, lib/internal/http2/core.js, lib/internal/http2/compat.js
2
+ // Reimplemented for GJS using Soup 3.0 (HTTP/2 transparently via ALPN when TLS is active)
3
+ //
4
+ // Phase 1: Compat layer backed by Soup.Server + Soup.Session.
5
+ // createServer() → HTTP/1.1 only (Soup does not support h2c/cleartext HTTP/2)
6
+ // createSecureServer() HTTP/2 via ALPN when TLS cert is provided, else HTTP/1.1
7
+ // connect() → HTTP/2 over HTTPS automatically, HTTP/1.1 over plain HTTP
8
+ //
9
+ // Phase 2 (future, requires Vala/nghttp2): pushStream, stream IDs, flow control, GOAWAY
10
+
11
+ // ─── Protocol constants & settings ───────────────────────────────────────────
12
+
13
+ export {
14
+ constants,
15
+ getDefaultSettings,
16
+ getPackedSettings,
17
+ getUnpackedSettings,
18
+ type Http2Settings,
19
+ } from './protocol.js';
532
20
 
533
- getHeaders(): Record<string, string | string[] | number> {
534
- return { ...this._headers };
535
- }
21
+ import { constants, getDefaultSettings, getPackedSettings, getUnpackedSettings, type Http2Settings } from './protocol.js';
536
22
 
537
- removeHeader(name: string): void {
538
- delete this._headers[name.toLowerCase()];
539
- }
23
+ // ─── Server-side classes ──────────────────────────────────────────────────────
540
24
 
541
- hasHeader(name: string): boolean {
542
- return name.toLowerCase() in this._headers;
543
- }
25
+ export {
26
+ Http2ServerRequest,
27
+ Http2ServerResponse,
28
+ ServerHttp2Stream,
29
+ ServerHttp2Session,
30
+ Http2Server,
31
+ Http2SecureServer,
32
+ type ServerOptions,
33
+ type SecureServerOptions,
34
+ } from './server.js';
544
35
 
545
- writeHead(statusCode: number, headers?: Record<string, string | string[] | number>): this {
546
- this.statusCode = statusCode;
547
- if (headers) {
548
- for (const [name, value] of Object.entries(headers)) {
549
- this._headers[name.toLowerCase()] = value;
550
- }
551
- }
552
- return this;
553
- }
36
+ import {
37
+ Http2ServerRequest,
38
+ Http2ServerResponse,
39
+ ServerHttp2Stream,
40
+ ServerHttp2Session,
41
+ Http2Server,
42
+ Http2SecureServer,
43
+ type ServerOptions,
44
+ type SecureServerOptions,
45
+ } from './server.js';
554
46
 
555
- end(_data?: string | Uint8Array, _encoding?: string, _callback?: () => void): this {
556
- this.emit('finish');
557
- return this;
558
- }
47
+ // ─── Client-side classes ──────────────────────────────────────────────────────
559
48
 
560
- write(_chunk: string | Uint8Array, _encoding?: string, _callback?: () => void): boolean {
561
- return true;
562
- }
49
+ export {
50
+ Http2Session,
51
+ ClientHttp2Session,
52
+ ClientHttp2Stream,
53
+ type ClientSessionOptions,
54
+ type ClientStreamOptions,
55
+ } from './client-session.js';
563
56
 
564
- createPushResponse(_headers: Record<string, string | string[]>, _callback: (err: Error | null, res: Http2ServerResponse) => void): void {
565
- throw new Error('http2 server push is not yet implemented in GJS');
566
- }
57
+ import {
58
+ Http2Session,
59
+ ClientHttp2Session,
60
+ ClientHttp2Stream,
61
+ type ClientSessionOptions,
62
+ } from './client-session.js';
567
63
 
568
- setTimeout(msecs: number, callback?: () => void): this {
569
- if (callback) setTimeout(callback, msecs);
570
- return this;
571
- }
572
- }
64
+ // ─── Factory functions ────────────────────────────────────────────────────────
573
65
 
574
66
  export function createServer(
575
- _options?: Record<string, unknown>,
576
- _onRequestHandler?: (request: Http2ServerRequest, response: Http2ServerResponse) => void,
577
- ): unknown {
578
- throw new Error(
579
- 'http2.createServer() is not yet implemented in GJS. ' +
580
- 'Soup 3.0 handles HTTP/2 transparently but does not expose multiplexed streams. ' +
581
- 'Use http.createServer() for HTTP/1.1 or consider a future nghttp2-based implementation.'
582
- );
67
+ options?: ServerOptions | ((req: Http2ServerRequest, res: Http2ServerResponse) => void),
68
+ handler?: (req: Http2ServerRequest, res: Http2ServerResponse) => void,
69
+ ): Http2Server {
70
+ return new Http2Server(options, handler);
583
71
  }
584
72
 
585
73
  export function createSecureServer(
586
- _options?: Record<string, unknown>,
587
- _onRequestHandler?: (request: Http2ServerRequest, response: Http2ServerResponse) => void,
588
- ): unknown {
589
- throw new Error(
590
- 'http2.createSecureServer() is not yet implemented in GJS. ' +
591
- 'Requires TLS server support combined with HTTP/2 multiplexing.'
592
- );
74
+ options: SecureServerOptions,
75
+ handler?: (req: Http2ServerRequest, res: Http2ServerResponse) => void,
76
+ ): Http2SecureServer {
77
+ return new Http2SecureServer(options, handler);
593
78
  }
594
79
 
595
80
  export function connect(
596
- _authority: string | URL,
597
- _options?: Record<string, unknown>,
598
- _listener?: (session: ClientHttp2Session) => void,
81
+ authority: string | URL,
82
+ options?: ClientSessionOptions | ((session: ClientHttp2Session, socket: any) => void),
83
+ listener?: (session: ClientHttp2Session, socket: any) => void,
599
84
  ): ClientHttp2Session {
600
- throw new Error(
601
- 'http2.connect() is not yet implemented in GJS. ' +
602
- 'Soup 3.0 can negotiate HTTP/2 transparently via Soup.Session, ' +
603
- 'but does not expose the session/stream API needed for http2.connect().'
604
- );
85
+ const authorityStr = typeof authority === 'string' ? authority : authority.toString();
86
+ if (typeof options === 'function') {
87
+ listener = options;
88
+ options = {};
89
+ }
90
+ const session = new ClientHttp2Session(authorityStr, (options ?? {}) as ClientSessionOptions);
91
+ if (listener) session.once('connect', listener);
92
+ return session;
605
93
  }
606
94
 
607
- export const sensitiveHeaders = Symbol('http2.sensitiveHeaders');
95
+ // ─── Misc ─────────────────────────────────────────────────────────────────────
96
+
97
+ export const sensitiveHeaders = Symbol.for('nodejs.http2.sensitiveHeaders');
608
98
 
609
99
  export function performServerHandshake(_socket: unknown): unknown {
610
100
  throw new Error('http2.performServerHandshake() is not yet implemented in GJS');
611
101
  }
612
102
 
103
+ // ─── Default export ───────────────────────────────────────────────────────────
104
+
613
105
  export default {
614
106
  constants,
615
107
  createServer,
@@ -621,11 +113,12 @@ export default {
621
113
  sensitiveHeaders,
622
114
  performServerHandshake,
623
115
  Http2Session,
624
- Http2Stream,
116
+ Http2Server,
117
+ Http2SecureServer,
118
+ Http2ServerRequest,
119
+ Http2ServerResponse,
625
120
  ServerHttp2Session,
626
- ClientHttp2Session,
627
121
  ServerHttp2Stream,
122
+ ClientHttp2Session,
628
123
  ClientHttp2Stream,
629
- Http2ServerRequest,
630
- Http2ServerResponse,
631
124
  };