@gjsify/http2 0.1.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 ADDED
@@ -0,0 +1,631 @@
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
+ }
532
+
533
+ getHeaders(): Record<string, string | string[] | number> {
534
+ return { ...this._headers };
535
+ }
536
+
537
+ removeHeader(name: string): void {
538
+ delete this._headers[name.toLowerCase()];
539
+ }
540
+
541
+ hasHeader(name: string): boolean {
542
+ return name.toLowerCase() in this._headers;
543
+ }
544
+
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
+ }
554
+
555
+ end(_data?: string | Uint8Array, _encoding?: string, _callback?: () => void): this {
556
+ this.emit('finish');
557
+ return this;
558
+ }
559
+
560
+ write(_chunk: string | Uint8Array, _encoding?: string, _callback?: () => void): boolean {
561
+ return true;
562
+ }
563
+
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
+ }
567
+
568
+ setTimeout(msecs: number, callback?: () => void): this {
569
+ if (callback) setTimeout(callback, msecs);
570
+ return this;
571
+ }
572
+ }
573
+
574
+ 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
+ );
583
+ }
584
+
585
+ 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
+ );
593
+ }
594
+
595
+ export function connect(
596
+ _authority: string | URL,
597
+ _options?: Record<string, unknown>,
598
+ _listener?: (session: ClientHttp2Session) => void,
599
+ ): 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
+ );
605
+ }
606
+
607
+ export const sensitiveHeaders = Symbol('http2.sensitiveHeaders');
608
+
609
+ export function performServerHandshake(_socket: unknown): unknown {
610
+ throw new Error('http2.performServerHandshake() is not yet implemented in GJS');
611
+ }
612
+
613
+ export default {
614
+ constants,
615
+ createServer,
616
+ createSecureServer,
617
+ connect,
618
+ getDefaultSettings,
619
+ getPackedSettings,
620
+ getUnpackedSettings,
621
+ sensitiveHeaders,
622
+ performServerHandshake,
623
+ Http2Session,
624
+ Http2Stream,
625
+ ServerHttp2Session,
626
+ ClientHttp2Session,
627
+ ServerHttp2Stream,
628
+ ClientHttp2Stream,
629
+ Http2ServerRequest,
630
+ Http2ServerResponse,
631
+ };
package/src/test.mts ADDED
@@ -0,0 +1,3 @@
1
+ import { run } from '@gjsify/unit';
2
+ import testSuite from './index.spec.js';
3
+ run({ testSuite });
package/tsconfig.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "ESNext",
4
+ "target": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "types": [
7
+ "node"
8
+ ],
9
+ "experimentalDecorators": true,
10
+ "emitDeclarationOnly": true,
11
+ "declaration": true,
12
+ "allowImportingTsExtensions": true,
13
+ "outDir": "lib",
14
+ "rootDir": "src",
15
+ "declarationDir": "lib/types",
16
+ "composite": true,
17
+ "skipLibCheck": true,
18
+ "allowJs": true,
19
+ "checkJs": false,
20
+ "strict": false
21
+ },
22
+ "include": [
23
+ "src/**/*.ts"
24
+ ],
25
+ "exclude": [
26
+ "src/test.ts",
27
+ "src/test.mts",
28
+ "src/**/*.spec.ts",
29
+ "src/**/*.spec.mts"
30
+ ]
31
+ }