@gjsify/http 0.51.1 → 0.53.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.
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{IncomingMessage as e}from"./incoming-message.js";import{OutgoingMessage as t}from"./server.js";import n from"@girs/glib-2.0";import r from"@girs/soup-3.0";import i from"@girs/gio-2.0";import{Buffer as a}from"node:buffer";import{URL as o}from"node:url";import{readBytesAsync as s}from"@gjsify/utils";var ClientRequest=class extends t{method;path;protocol;host;hostname;port;aborted=!1;reusedSocket=!1;maxHeadersCount=2e3;_chunks=[];_session;_message;_cancellable;_timeout=0;_timeoutTimer=null;_responseCallback;constructor(e,t,s){super();let c;if(typeof e==`string`||e instanceof o){let n=typeof e==`string`?new o(e):e;c={protocol:n.protocol,hostname:n.hostname,port:n.port?Number(n.port):void 0,path:n.pathname+n.search,...typeof t==`object`?t:{}},typeof t==`function`&&(s=t)}else c=e,typeof t==`function`&&(s=t);if(this.method=(c.method||`GET`).toUpperCase(),this.protocol=c.protocol||`http:`,this.hostname=c.hostname||c.host?.split(`:`)[0]||`localhost`,this.port=Number(c.port)||(this.protocol===`https:`?443:80),this.path=c.path||`/`,this.host=c.host||`${this.hostname}:${this.port}`,this._timeout=c.timeout||0,s&&(this._responseCallback=s,this.once(`response`,s)),c.headers)for(let[e,t]of Object.entries(c.headers))this.setHeader(e,t);if(c.setHost!==!1&&!this._headers.has(`host`)){let e=this.protocol===`https:`?443:80,t=this.port===e?this.hostname:`${this.hostname}:${this.port}`;this.setHeader(`Host`,t)}c.auth&&!this._headers.has(`authorization`)&&this.setHeader(`Authorization`,`Basic `+a.from(c.auth).toString(`base64`)),c.signal&&(c.signal.aborted?this.abort():c.signal.addEventListener(`abort`,()=>this.abort(),{once:!0}));let l=n.Uri.parse(this._buildUrl(),n.UriFlags.NONE);this._session=new r.Session,this._message=new r.Message({method:this.method,uri:l}),this._cancellable=new i.Cancellable,this._timeout>0&&(this._session.timeout=Math.ceil(this._timeout/1e3),this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},this._timeout))}_buildUrl(){let e=this.protocol.endsWith(`:`)?this.protocol:this.protocol+`:`,t=e===`https:`?443:80,n=this.port===t?``:`:${this.port}`;return`${e}//${this.hostname}${n}${this.path}`}getRawHeaderNames(){return Array.from(this._headers.keys())}flushHeaders(){this.headersSent||this._applyHeaders()}setTimeout(e,t){return this._timeout=e,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),t&&this.once(`timeout`,t),e>0&&(this._session.timeout=Math.ceil(e/1e3),this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},e)),this}abort(){this.aborted||(this.aborted=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this._cancellable.cancel(),this.emit(`abort`),this.destroy())}_write(e,t,n){let r=a.isBuffer(e)?e:typeof e==`string`?a.from(e,t):a.from(e);this._chunks.push(r),n()}_final(e){this._sendRequest().then(()=>e()).catch(t=>e(t))}_applyHeaders(){if(this.headersSent)return;this.headersSent=!0;let e=this._message.get_request_headers();for(let[t,n]of this._headers)if(Array.isArray(n))for(let r of n)e.append(t,r);else e.replace(t,n)}async _sendRequest(){this._applyHeaders();let t=a.concat(this._chunks);if(t.length>0){let e=this._headers.get(`content-type`)||`application/octet-stream`;this._message.set_request_body_from_bytes(e,new n.Bytes(t))}try{let t=await new Promise((e,t)=>{this._session.send_async(this._message,n.PRIORITY_DEFAULT,this._cancellable,(n,r)=>{try{e(this._session.send_finish(r))}catch(e){t(e)}})}),r=[];try{let e;for(;(e=await s(t,4096,n.PRIORITY_DEFAULT,this._cancellable))!==null;)r.push(a.from(e))}catch{}let i=new e;i.statusCode=this._message.status_code,i.statusMessage=this._message.get_reason_phrase(),i.httpVersion=`1.1`,this._message.get_response_headers().foreach((e,t)=>{let n=e.toLowerCase();if(i.rawHeaders.push(e,t),n in i.headers){let e=i.headers[n];Array.isArray(e)?e.push(t):i.headers[n]=[e,t]}else i.headers[n]=t}),this.finished=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this.emit(`response`,i),setTimeout(()=>{for(let e of r)i.push(e);i.push(null),i.complete=!0},0)}catch(e){this.aborted?this.emit(`abort`):this.emit(`error`,e instanceof Error?e:Error(String(e)))}}};export{ClientRequest};
1
+ import"./_virtual/_rolldown/runtime.js";import{IncomingMessage as e}from"./incoming-message.js";import{OutgoingMessage as t}from"./server.js";import n from"@girs/glib-2.0";import r from"@girs/soup-3.0";import i from"@girs/gio-2.0";import{Buffer as a}from"node:buffer";import{URL as o}from"node:url";import{readBytesAsync as s}from"@gjsify/utils";import{createPeerVerifier as c,createSecureContext as l}from"@gjsify/tls/verify";var ClientRequest=class extends t{method;path;protocol;host;hostname;port;aborted=!1;reusedSocket=!1;maxHeadersCount=2e3;_chunks=[];_session;_message;_cancellable;_timeout=0;_timeoutTimer=null;_responseCallback;_tlsRejection=null;constructor(e,t,s){super();let c;if(typeof e==`string`||e instanceof o){let n=typeof e==`string`?new o(e):e;c={protocol:n.protocol,hostname:n.hostname,port:n.port?Number(n.port):void 0,path:n.pathname+n.search,...typeof t==`object`?t:{}},typeof t==`function`&&(s=t)}else c=e,typeof t==`function`&&(s=t);if(this.method=(c.method||`GET`).toUpperCase(),this.protocol=c.protocol||`http:`,this.hostname=c.hostname||c.host?.split(`:`)[0]||`localhost`,this.port=Number(c.port)||(this.protocol===`https:`?443:80),this.path=c.path||`/`,this.host=c.host||`${this.hostname}:${this.port}`,this._timeout=c.timeout||0,s&&(this._responseCallback=s,this.once(`response`,s)),c.headers)for(let[e,t]of Object.entries(c.headers))this.setHeader(e,t);if(c.setHost!==!1&&!this._headers.has(`host`)){let e=this.protocol===`https:`?443:80,t=this.port===e?this.hostname:`${this.hostname}:${this.port}`;this.setHeader(`Host`,t)}c.auth&&!this._headers.has(`authorization`)&&this.setHeader(`Authorization`,`Basic `+a.from(c.auth).toString(`base64`)),c.signal&&(c.signal.aborted?this.abort():c.signal.addEventListener(`abort`,()=>this.abort(),{once:!0}));let l=n.Uri.parse(this._buildUrl(),n.UriFlags.NONE);this._session=new r.Session,this._message=new r.Message({method:this.method,uri:l}),this._cancellable=new i.Cancellable,this.protocol===`https:`&&this._setupTls(c),this._timeout>0&&(this._session.timeout=Math.ceil(this._timeout/1e3),this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},this._timeout))}_setupTls(e){let t=e.agent===void 0?e._defaultAgent:e.agent,n=t&&typeof t==`object`?t.options:void 0,r={...e,...n},i=r.secureContext??l(r),a=c({caCertificates:i.caCertificates,rejectUnauthorized:r.rejectUnauthorized,host:r.servername||this.hostname,checkServerIdentity:r.checkServerIdentity});this._session.set_tls_database(null),this._message.connect(`accept-certificate`,(e,t,n)=>{let r=a.accept(t);return this._tlsRejection=r?null:a.error,r}),i.certificate&&this._message.set_tls_client_certificate(i.certificate)}_buildUrl(){let e=this.protocol.endsWith(`:`)?this.protocol:this.protocol+`:`,t=e===`https:`?443:80,n=this.port===t?``:`:${this.port}`;return`${e}//${this.hostname}${n}${this.path}`}getRawHeaderNames(){return Array.from(this._headers.keys())}flushHeaders(){this.headersSent||this._applyHeaders()}setTimeout(e,t){return this._timeout=e,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),t&&this.once(`timeout`,t),e>0&&(this._session.timeout=Math.ceil(e/1e3),this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},e)),this}abort(){this.aborted||(this.aborted=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this._cancellable.cancel(),this.emit(`abort`),this.destroy())}_write(e,t,n){let r=a.isBuffer(e)?e:typeof e==`string`?a.from(e,t):a.from(e);this._chunks.push(r),n()}_final(e){this._sendRequest().then(()=>e()).catch(t=>e(t))}_applyHeaders(){if(this.headersSent)return;this.headersSent=!0;let e=this._message.get_request_headers();for(let[t,n]of this._headers)if(Array.isArray(n))for(let r of n)e.append(t,r);else e.replace(t,n)}async _sendRequest(){this._applyHeaders();let t=a.concat(this._chunks);if(t.length>0){let e=this._headers.get(`content-type`)||`application/octet-stream`;this._message.set_request_body_from_bytes(e,new n.Bytes(t))}try{let t=await new Promise((e,t)=>{this._session.send_async(this._message,n.PRIORITY_DEFAULT,this._cancellable,(n,r)=>{try{e(this._session.send_finish(r))}catch(e){t(e)}})}),r=[];try{let e;for(;(e=await s(t,4096,n.PRIORITY_DEFAULT,this._cancellable))!==null;)r.push(a.from(e))}catch{}let i=new e;i.statusCode=this._message.status_code,i.statusMessage=this._message.get_reason_phrase(),i.httpVersion=`1.1`,this._message.get_response_headers().foreach((e,t)=>{let n=e.toLowerCase();if(i.rawHeaders.push(e,t),n in i.headers){let e=i.headers[n];Array.isArray(e)?e.push(t):i.headers[n]=[e,t]}else i.headers[n]=t}),this.finished=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),this.emit(`response`,i),setTimeout(()=>{for(let e of r)i.push(e);i.push(null),i.complete=!0},0)}catch(e){this.aborted?this.emit(`abort`):this._tlsRejection?this.emit(`error`,this._tlsRejection):this.emit(`error`,e instanceof Error?e:Error(String(e)))}}};export{ClientRequest};
package/lib/esm/index.js CHANGED
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{validateHeaderName as e,validateHeaderValue as t}from"./validators.js";import{METHODS as n,STATUS_CODES as r}from"./constants.js";import{IncomingMessage as i}from"./incoming-message.js";import{OutgoingMessage as a,Server as o,ServerResponse as s}from"./server.js";import{ClientRequest as c}from"./client-request.js";import{URL as l}from"node:url";var Agent=class{defaultPort=80;protocol=`http:`;maxSockets;maxTotalSockets;maxFreeSockets;keepAliveMsecs;keepAlive;scheduling;requests={};sockets={};freeSockets={};constructor(e){this.keepAlive=e?.keepAlive??!1,this.keepAliveMsecs=e?.keepAliveMsecs??1e3,this.maxSockets=e?.maxSockets??1/0,this.maxTotalSockets=e?.maxTotalSockets??1/0,this.maxFreeSockets=e?.maxFreeSockets??256,this.scheduling=e?.scheduling??`lifo`}destroy(){}getName(e){let t=e.host||`localhost`;return e.port&&(t+=`:`+e.port),e.localAddress&&(t+=`:`+e.localAddress),(e.family===4||e.family===6)&&(t+=`:`+e.family),t}};const u=new Agent;function createServer(e,t){return typeof e==`function`?new o(e):new o(t)}function request(e,t,n){return new c(e,t,n)}function get(e,t,n){let r,i=n;typeof e==`string`||e instanceof l?(r=typeof t==`object`?{...t,method:`GET`}:{method:`GET`},typeof t==`function`&&(i=t)):(r={...e,method:`GET`},typeof t==`function`&&(i=t),e=r);let a=typeof e==`string`||e instanceof l?new c(e,{...r,method:`GET`},i):new c({...r,method:`GET`},i);return a.end(),a}const d=16384;function setMaxIdleHTTPParsers(e){}var f={STATUS_CODES:r,METHODS:n,Server:o,IncomingMessage:i,OutgoingMessage:a,ServerResponse:s,ClientRequest:c,Agent,globalAgent:u,createServer,request,get,validateHeaderName:e,validateHeaderValue:t,maxHeaderSize:d,setMaxIdleHTTPParsers};export{Agent,c as ClientRequest,i as IncomingMessage,n as METHODS,a as OutgoingMessage,r as STATUS_CODES,o as Server,s as ServerResponse,createServer,f as default,get,u as globalAgent,d as maxHeaderSize,request,setMaxIdleHTTPParsers,e as validateHeaderName,t as validateHeaderValue};
1
+ import"./_virtual/_rolldown/runtime.js";import{validateHeaderName as e,validateHeaderValue as t}from"./validators.js";import{METHODS as n,STATUS_CODES as r}from"./constants.js";import{IncomingMessage as i}from"./incoming-message.js";import{OutgoingMessage as a,Server as o,ServerResponse as s}from"./server.js";import{ClientRequest as c}from"./client-request.js";import{URL as l}from"node:url";var Agent=class{defaultPort=80;protocol=`http:`;maxSockets;maxTotalSockets;maxFreeSockets;keepAliveMsecs;keepAlive;scheduling;requests={};sockets={};freeSockets={};options;constructor(e){this.options={...e},this.keepAlive=e?.keepAlive??!1,this.keepAliveMsecs=e?.keepAliveMsecs??1e3,this.maxSockets=e?.maxSockets??1/0,this.maxTotalSockets=e?.maxTotalSockets??1/0,this.maxFreeSockets=e?.maxFreeSockets??256,this.scheduling=e?.scheduling??`lifo`}destroy(){}getName(e){let t=e.host||`localhost`;return e.port&&(t+=`:`+e.port),e.localAddress&&(t+=`:`+e.localAddress),(e.family===4||e.family===6)&&(t+=`:`+e.family),t}};const u=new Agent;function createServer(e,t){return typeof e==`function`?new o(e):new o(t)}function request(e,t,n){return new c(e,t,n)}function get(e,t,n){let r,i=n;typeof e==`string`||e instanceof l?(r=typeof t==`object`?{...t,method:`GET`}:{method:`GET`},typeof t==`function`&&(i=t)):(r={...e,method:`GET`},typeof t==`function`&&(i=t),e=r);let a=typeof e==`string`||e instanceof l?new c(e,{...r,method:`GET`},i):new c({...r,method:`GET`},i);return a.end(),a}const d=16384;function setMaxIdleHTTPParsers(e){}var f={STATUS_CODES:r,METHODS:n,Server:o,IncomingMessage:i,OutgoingMessage:a,ServerResponse:s,ClientRequest:c,Agent,globalAgent:u,createServer,request,get,validateHeaderName:e,validateHeaderValue:t,maxHeaderSize:d,setMaxIdleHTTPParsers};export{Agent,c as ClientRequest,i as IncomingMessage,n as METHODS,a as OutgoingMessage,r as STATUS_CODES,o as Server,s as ServerResponse,createServer,f as default,get,u as globalAgent,d as maxHeaderSize,request,setMaxIdleHTTPParsers,e as validateHeaderName,t as validateHeaderValue};
@@ -0,0 +1,2 @@
1
+ import"./_virtual/_rolldown/runtime.js";import e from"@girs/gio-2.0";import{createSecureContext as t}from"@gjsify/tls";function resolveServerTls(n){let r=t(n);if(n.cert&&!r.certificate)throw Error(`https.Server: cannot parse the key/cert pair as PEM`);if(!r.certificate)return null;let i=e.TlsAuthenticationMode.NONE;return n.requestCert&&(i=n.rejectUnauthorized===!1?e.TlsAuthenticationMode.REQUESTED:e.TlsAuthenticationMode.REQUIRED),{certificate:r.certificate,authenticationMode:i,clientCa:n.requestCert?r.caCertificates:[]}}function createTrustDatabase(t){let[n,r]=e.File.new_tmp(`gjsify-https-ca-XXXXXX.pem`);try{let i=t.map(e=>e.certificate_pem).join(`
2
+ `);return r.get_output_stream().write_all(new TextEncoder().encode(i),null),r.close(null),{database:e.TlsFileDatabase.new(n.get_path()),file:n}}catch(e){throw removeFile(n),e}}function removeFile(e){try{e.delete(null)}catch{}}export{createTrustDatabase,removeFile,resolveServerTls};
package/lib/esm/server.js CHANGED
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{ServerRequestSocket as e}from"./server-request-socket.js";import{STATUS_CODES as t}from"./constants.js";import{IncomingMessage as n}from"./incoming-message.js";import{Buffer as r}from"node:buffer";import{EventEmitter as i}from"node:events";import{Writable as a}from"node:stream";import{Socket as o}from"@gjsify/net/socket";import{Server as s}from"@gjsify/http-soup-bridge";import{createNodeError as c,deferEmit as l,ensureMainLoop as u}from"@gjsify/utils/core";var OutgoingMessage=class extends a{headersSent=!1;sendDate=!0;finished=!1;socket=null;_headers=new Map;setHeader(e,t){return this._headers.set(e.toLowerCase(),typeof t==`number`?String(t):t),this}getHeader(e){return this._headers.get(e.toLowerCase())}removeHeader(e){this._headers.delete(e.toLowerCase())}hasHeader(e){return this._headers.has(e.toLowerCase())}getHeaderNames(){return Array.from(this._headers.keys())}getHeaders(){let e={};for(let[t,n]of this._headers)e[t]=n;return e}appendHeader(e,t){let n=e.toLowerCase(),r=this._headers.get(n);return r===void 0?this._headers.set(n,t):Array.isArray(r)?Array.isArray(t)?r.push(...t):r.push(t):Array.isArray(t)?this._headers.set(n,[r,...t]):this._headers.set(n,[r,t]),this}flushHeaders(){this.headersSent=!0}_write(e,t,n){n()}},ServerResponse=class extends OutgoingMessage{statusCode=200;statusMessage=``;_streaming=!1;_bridge;_timeoutTimer=null;constructor(e){super(),this._bridge=e,e.connect(`close`,()=>{this.emit(`close`)})}setTimeout(e,t){return this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),t&&this.once(`timeout`,t),e>0&&(this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},e)),this}writeHead(e,n,r){if(this.statusCode=e,typeof n==`object`&&(r=n,n=void 0),this.statusMessage=n||t[e]||``,r)for(let[e,t]of Object.entries(r))this.setHeader(e,t);return this}writeContinue(e){e&&Promise.resolve().then(e)}writeProcessing(e){e&&Promise.resolve().then(e)}flushHeaders(){this.headersSent||=!0}addTrailers(e){for(let[t,n]of Object.entries(e))this._headers.set(`trailer-`+t.toLowerCase(),n)}_startStreaming(){if(!this._streaming){this._streaming=!0,this.headersSent=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null);for(let[e,t]of this._headers)if(Array.isArray(t))for(let n of t)this._bridge.append_header(e,n);else this._bridge.set_header(e,t);this._bridge.write_head(this.statusCode,this.statusMessage||null)}}_write(e,t,n){let i=r.isBuffer(e)?e:typeof e==`string`?r.from(e,t):r.from(e);this._startStreaming(),this._bridge.write_chunk(new Uint8Array(i.buffer,i.byteOffset,i.byteLength)),n()}_final(e){if(!this._streaming){for(let[e,t]of this._headers)if(Array.isArray(t))for(let n of t)this._bridge.append_header(e,n);else this._bridge.set_header(e,t);this._bridge.write_head(this.statusCode,this.statusMessage||null)}this._bridge.end(),this.finished=!0,e()}end(e,t,n){return typeof e==`function`?(n=e,e=void 0):typeof t==`function`&&(n=t,t=void 0),e!=null&&this.write(e,t),super.end(n),this}};const d=new Set;var Server=class extends i{listening=!1;maxHeadersCount=2e3;timeout=0;keepAliveTimeout=5e3;headersTimeout=6e4;requestTimeout=3e5;_bridge=null;_address=null;get soupServer(){return this._bridge?.soup_server??null}constructor(e,t){super();let n=typeof e==`function`?e:t;n&&this.on(`request`,n)}listen(...e){let t=0,n=`0.0.0.0`,r;for(let i of e)typeof i==`number`?t=i:typeof i==`string`?n=i:typeof i==`function`&&(r=i);r&&this.once(`listening`,r);try{this._bridge=new s,this._bridge.connect(`request-received`,(e,t,n)=>{this._handleRequest(t,n)}),this._bridge.connect(`upgrade`,(e,t,n,r)=>{this._handleUpgrade(t,n)}),this._bridge.connect(`error-occurred`,(e,t)=>{this.emit(`error`,Error(t))}),this._bridge.listen(t,n),u(),this.listening=!0,this._address={port:this._bridge.port,family:`IPv4`,address:this._bridge.address||n},d.add(this),l(this,`listening`)}catch(e){let r=c(e,`listen`,{address:n,port:t});l(this,`error`,r)}return this}_handleRequest(t,r){let i=new n,a=new ServerResponse(r);i.method=t.method,i.url=t.url,i.httpVersion=`1.1`;let o=t.header_pairs??[];for(let e=0;e+1<o.length;e+=2){let t=o[e],n=o[e+1],r=t.toLowerCase();if(i.rawHeaders.push(t,n),r in i.headers){let e=i.headers[r];Array.isArray(e)?e.push(n):i.headers[r]=[e,n]}else i.headers[r]=n}i.socket=new e(t.remote_address??`127.0.0.1`,t.remote_port??0,this._address?.address??`127.0.0.1`,this._address?.port??0,r);let s=t.get_body();s.length>0?i._pushBody(s):i._pushBody(null),t.connect(`aborted_signal`,()=>{i.aborted||(i.aborted=!0,i.emit(`aborted`))}),t.connect(`close`,()=>{i.emit(`close`)});try{let e=this.emit(`request`,i,a);(e instanceof Promise||typeof e==`object`&&e&&typeof e.then==`function`)&&e.catch(e=>{if(console.error(`[HTTP] Unhandled error in async request handler:`,e),!a.headersSent)try{a.writeHead(500),a.end(`Internal Server Error`)}catch{}})}catch(e){if(console.error(`[HTTP] Unhandled error in request handler:`,e),!a.headersSent)try{a.writeHead(500),a.end(`Internal Server Error`)}catch{}}}_handleUpgrade(e,t){let i=new n;i.method=e.method,i.url=e.url,i.httpVersion=`1.1`;let a=e.header_pairs??[];for(let e=0;e+1<a.length;e+=2){let t=a[e],n=a[e+1],r=t.toLowerCase();i.rawHeaders.push(t,n),i.headers[r]=n}if(this.listenerCount(`upgrade`)>0){let e=new o;e._attachOutputOnly(t),this.emit(`upgrade`,i,e,r.alloc(0))}}address(){return this._address}addWebSocketHandler(e,t){if(!this._bridge)throw Error(`Server must be listening before adding WebSocket handlers. Call listen() first.`);this._bridge.soup_server.add_websocket_handler(e,null,null,(e,n,r,i)=>{t(i)})}close(e){return e&&this.once(`close`,e),this._bridge&&=(this._bridge.close(),null),this.listening=!1,d.delete(this),l(this,`close`),this}setTimeout(e,t){return this.timeout=e,t&&this.on(`timeout`,t),this}};export{OutgoingMessage,Server,ServerResponse};
1
+ import"./_virtual/_rolldown/runtime.js";import{ServerRequestSocket as e}from"./server-request-socket.js";import{STATUS_CODES as t}from"./constants.js";import{IncomingMessage as n}from"./incoming-message.js";import{createTrustDatabase as r,removeFile as i,resolveServerTls as a}from"./server-tls.js";import o from"@girs/soup-3.0";import s from"@girs/gio-2.0";import{Buffer as c}from"node:buffer";import{EventEmitter as l}from"node:events";import{Writable as u}from"node:stream";import{Socket as d}from"@gjsify/net/socket";import{Server as f}from"@gjsify/http-soup-bridge";import{createNodeError as p,deferEmit as m,ensureMainLoop as h}from"@gjsify/utils/core";var OutgoingMessage=class extends u{headersSent=!1;sendDate=!0;finished=!1;socket=null;_headers=new Map;setHeader(e,t){return this._headers.set(e.toLowerCase(),typeof t==`number`?String(t):t),this}getHeader(e){return this._headers.get(e.toLowerCase())}removeHeader(e){this._headers.delete(e.toLowerCase())}hasHeader(e){return this._headers.has(e.toLowerCase())}getHeaderNames(){return Array.from(this._headers.keys())}getHeaders(){let e={};for(let[t,n]of this._headers)e[t]=n;return e}appendHeader(e,t){let n=e.toLowerCase(),r=this._headers.get(n);return r===void 0?this._headers.set(n,t):Array.isArray(r)?Array.isArray(t)?r.push(...t):r.push(t):Array.isArray(t)?this._headers.set(n,[r,...t]):this._headers.set(n,[r,t]),this}flushHeaders(){this.headersSent=!0}_write(e,t,n){n()}},ServerResponse=class extends OutgoingMessage{statusCode=200;statusMessage=``;_streaming=!1;_bridge;_timeoutTimer=null;constructor(e){super(),this._bridge=e,e.connect(`close`,()=>{this.emit(`close`)})}setTimeout(e,t){return this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null),t&&this.once(`timeout`,t),e>0&&(this._timeoutTimer=setTimeout(()=>{this._timeoutTimer=null,this.emit(`timeout`)},e)),this}writeHead(e,n,r){if(this.statusCode=e,typeof n==`object`&&(r=n,n=void 0),this.statusMessage=n||t[e]||``,r)for(let[e,t]of Object.entries(r))this.setHeader(e,t);return this}writeContinue(e){e&&Promise.resolve().then(e)}writeProcessing(e){e&&Promise.resolve().then(e)}flushHeaders(){this.headersSent||=!0}addTrailers(e){for(let[t,n]of Object.entries(e))this._headers.set(`trailer-`+t.toLowerCase(),n)}_startStreaming(){if(!this._streaming){this._streaming=!0,this.headersSent=!0,this._timeoutTimer&&=(clearTimeout(this._timeoutTimer),null);for(let[e,t]of this._headers)if(Array.isArray(t))for(let n of t)this._bridge.append_header(e,n);else this._bridge.set_header(e,t);this._bridge.write_head(this.statusCode,this.statusMessage||null)}}_write(e,t,n){let r=c.isBuffer(e)?e:typeof e==`string`?c.from(e,t):c.from(e);this._startStreaming(),this._bridge.write_chunk(new Uint8Array(r.buffer,r.byteOffset,r.byteLength)),n()}_final(e){if(!this._streaming){for(let[e,t]of this._headers)if(Array.isArray(t))for(let n of t)this._bridge.append_header(e,n);else this._bridge.set_header(e,t);this._bridge.write_head(this.statusCode,this.statusMessage||null)}this._bridge.end(),this.finished=!0,e()}end(e,t,n){return typeof e==`function`?(n=e,e=void 0):typeof t==`function`&&(n=t,t=void 0),e!=null&&this.write(e,t),super.end(n),this}};const g=new Set;var Server=class extends l{listening=!1;maxHeadersCount=2e3;timeout=0;keepAliveTimeout=5e3;headersTimeout=6e4;requestTimeout=3e5;_bridge=null;_tls=null;_tlsRequested=!1;_trustFile=null;_address=null;get soupServer(){return this._bridge?.soup_server??null}constructor(e,t){super();let n=typeof e==`function`?e:t;n&&this.on(`request`,n)}_useTls(e){this._tlsRequested=!0,this._tls=a(e)}listen(...e){let t=0,n=`0.0.0.0`,r;for(let i of e)typeof i==`number`?t=i:typeof i==`string`?n=i:typeof i==`function`&&(r=i);if(this._tlsRequested&&!this._tls)return m(this,`error`,Error(`https.Server: no certificate — pass key and cert (PEM)`)),this;r&&this.once(`listening`,r);try{this._bridge=new f,this._bridge.connect(`request-received`,(e,t,n)=>{this._handleRequest(t,n)}),this._bridge.connect(`upgrade`,(e,t,n,r)=>{this._handleUpgrade(t,n)}),this._bridge.connect(`error-occurred`,(e,t)=>{this.emit(`error`,Error(t))});let e=this._tls?this._listenTls(this._bridge,t):this._listenPlain(this._bridge,t,n);h(),this.listening=!0,this._address={port:e,family:`IPv4`,address:this._bridge.address||n},g.add(this),m(this,`listening`)}catch(e){let r=p(e,`listen`,{address:n,port:t});m(this,`error`,r)}return this}_listenPlain(e,t,n){return e.listen(t,n),e.port}_listenTls(e,t){let n=this._tls,a=e.soup_server;if(a.tls_certificate=n.certificate,a.tls_auth_mode=n.authenticationMode,n.clientCa.length>0){let e=r(n.clientCa);a.tls_database=e.database,this._trustFile&&i(this._trustFile),this._trustFile=e.file}if(n.authenticationMode!==s.TlsAuthenticationMode.NONE){let e=n.authenticationMode===s.TlsAuthenticationMode.REQUIRED;a.connect(`request-started`,(t,n)=>{n.connect(`accept-certificate`,(t,n,r)=>!e||r===s.TlsCertificateFlags.NO_FLAGS)})}try{a.listen_local(t,o.ServerListenOptions.IPV4_ONLY|o.ServerListenOptions.HTTPS)}catch(e){throw this._trustFile&&i(this._trustFile),this._trustFile=null,e}return(a.get_listeners()[0]?.get_local_address())?.get_port()??t}_handleRequest(t,r){let i=new n,a=new ServerResponse(r);i.method=t.method,i.url=t.url,i.httpVersion=`1.1`;let o=t.header_pairs??[];for(let e=0;e+1<o.length;e+=2){let t=o[e],n=o[e+1],r=t.toLowerCase();if(i.rawHeaders.push(t,n),r in i.headers){let e=i.headers[r];Array.isArray(e)?e.push(n):i.headers[r]=[e,n]}else i.headers[r]=n}i.socket=new e(t.remote_address??`127.0.0.1`,t.remote_port??0,this._address?.address??`127.0.0.1`,this._address?.port??0,r,this._tls!==null);let s=t.get_body();s.length>0?i._pushBody(s):i._pushBody(null),t.connect(`aborted_signal`,()=>{i.aborted||(i.aborted=!0,i.emit(`aborted`))}),t.connect(`close`,()=>{i.emit(`close`)});try{let e=this.emit(`request`,i,a);(e instanceof Promise||typeof e==`object`&&e&&typeof e.then==`function`)&&e.catch(e=>{if(console.error(`[HTTP] Unhandled error in async request handler:`,e),!a.headersSent)try{a.writeHead(500),a.end(`Internal Server Error`)}catch{}})}catch(e){if(console.error(`[HTTP] Unhandled error in request handler:`,e),!a.headersSent)try{a.writeHead(500),a.end(`Internal Server Error`)}catch{}}}_handleUpgrade(e,t){let r=new n;r.method=e.method,r.url=e.url,r.httpVersion=`1.1`;let i=e.header_pairs??[];for(let e=0;e+1<i.length;e+=2){let t=i[e],n=i[e+1],a=t.toLowerCase();r.rawHeaders.push(t,n),r.headers[a]=n}if(this.listenerCount(`upgrade`)>0){let n=new d;n._attachOutputOnly(t),r.socket=n,n.remoteAddress??=e.remote_address||void 0,n.remotePort??=e.remote_port??void 0,n.remoteFamily??=n.remoteAddress?.includes(`:`)?`IPv6`:`IPv4`,n.localAddress??=this._address?.address,n.localPort??=this._address?.port,this.emit(`upgrade`,r,n,c.alloc(0))}}address(){return this._address}addWebSocketHandler(e,t){if(!this._bridge)throw Error(`Server must be listening before adding WebSocket handlers. Call listen() first.`);this._bridge.soup_server.add_websocket_handler(e,null,null,(e,n,r,i)=>{t(i)})}close(e){return e&&this.once(`close`,e),this._bridge&&=(this._tls?this._bridge.soup_server.disconnect():this._bridge.close(),null),this._trustFile&&=(i(this._trustFile),null),this.listening=!1,g.delete(this),m(this,`close`),this}setTimeout(e,t){return this.timeout=e,t&&this.on(`timeout`,t),this}};export{OutgoingMessage,Server,ServerResponse};
@@ -1,8 +1,21 @@
1
+ import { Buffer } from 'node:buffer';
1
2
  import { URL } from 'node:url';
3
+ import { type PeerCertificate, type SecureContext } from '@gjsify/tls/verify';
2
4
  import { OutgoingMessage } from './server.js';
3
5
  import { IncomingMessage } from './incoming-message.js';
4
6
  import type { Agent } from 'node:http';
5
- export interface ClientRequestOptions {
7
+ /** The `https:` options of Node's request / `https.Agent` (tls.connect's), honoured here. */
8
+ export interface ClientTlsOptions {
9
+ ca?: string | Buffer | Uint8Array | Array<string | Buffer | Uint8Array>;
10
+ cert?: string | Buffer | Uint8Array | Array<string | Buffer | Uint8Array>;
11
+ key?: string | Buffer | Uint8Array | Array<string | Buffer | Uint8Array>;
12
+ passphrase?: string;
13
+ rejectUnauthorized?: boolean;
14
+ servername?: string;
15
+ checkServerIdentity?: (host: string, cert: PeerCertificate) => Error | undefined;
16
+ secureContext?: SecureContext;
17
+ }
18
+ export interface ClientRequestOptions extends ClientTlsOptions {
6
19
  protocol?: string;
7
20
  hostname?: string;
8
21
  host?: string;
@@ -12,6 +25,8 @@ export interface ClientRequestOptions {
12
25
  headers?: Record<string, string | number | string[]>;
13
26
  timeout?: number;
14
27
  agent?: Agent | boolean;
28
+ /** Agent used when `agent` is undefined — `https.request` passes `https.globalAgent` (Node's name). */
29
+ _defaultAgent?: Agent;
15
30
  setHost?: boolean;
16
31
  /** Basic authentication string in the format 'user:password'. */
17
32
  auth?: string;
@@ -47,7 +62,16 @@ export declare class ClientRequest extends OutgoingMessage {
47
62
  private _timeout;
48
63
  private _timeoutTimer;
49
64
  private _responseCallback?;
65
+ /** Set when the peer verifier refused the server certificate: the error Node reports. */
66
+ private _tlsRejection;
50
67
  constructor(url: string | URL | ClientRequestOptions, options?: ClientRequestOptions | ((res: IncomingMessage) => void), callback?: (res: IncomingMessage) => void);
68
+ /**
69
+ * Apply the TLS options, merged the way Node's Agent does (`{...request, ...agent.options}` —
70
+ * the agent's win). The session gets NO trust database so every peer reaches the shared
71
+ * Node-semantics verifier in @gjsify/tls (why: `verify.ts`); its verdict is the error a
72
+ * rejected request emits.
73
+ */
74
+ private _setupTls;
51
75
  private _buildUrl;
52
76
  /** Get raw header names and values as a flat array. */
53
77
  getRawHeaderNames(): string[];
@@ -1,6 +1,7 @@
1
1
  export { STATUS_CODES, METHODS } from './constants.js';
2
2
  export { IncomingMessage } from './incoming-message.js';
3
3
  export { OutgoingMessage, Server, ServerResponse } from './server.js';
4
+ export type { ServerTlsOptions } from './server-tls.js';
4
5
  export { ClientRequest } from './client-request.js';
5
6
  export { validateHeaderName, validateHeaderValue } from './validators.js';
6
7
  import { validateHeaderName, validateHeaderValue } from './validators.js';
@@ -38,6 +39,11 @@ export declare class Agent {
38
39
  readonly sockets: Record<string, unknown[]>;
39
40
  /** Idle sockets per host (compatibility — Soup manages internally). */
40
41
  readonly freeSockets: Record<string, unknown[]>;
42
+ /**
43
+ * The constructor options, as Node keeps them: connection options (the TLS ones on an
44
+ * `https.Agent`) are merged over each request's by ClientRequest.
45
+ */
46
+ readonly options: AgentOptions & Record<string, unknown>;
41
47
  constructor(options?: AgentOptions);
42
48
  /** Destroy the agent and close idle connections. */
43
49
  destroy(): void;
@@ -0,0 +1,41 @@
1
+ import Gio from '@girs/gio-2.0';
2
+ type PemInput = string | Uint8Array | Array<string | Uint8Array>;
3
+ /**
4
+ * The `https.createServer()` options that shape the listener.
5
+ *
6
+ * No `passphrase`: `Gio.TlsCertificate.new_from_pem` takes no passphrase, so declaring one here
7
+ * would claim a decryption nobody performs. `secure-context.ts` accepts the field and ignores it
8
+ * for the same reason; the honest place to name the gap is the https README's list.
9
+ */
10
+ export interface ServerTlsOptions {
11
+ key?: PemInput;
12
+ cert?: PemInput;
13
+ ca?: PemInput;
14
+ requestCert?: boolean;
15
+ rejectUnauthorized?: boolean;
16
+ }
17
+ /** What Soup.Server needs, derived from ServerTlsOptions. */
18
+ export interface ServerTlsSettings {
19
+ certificate: Gio.TlsCertificate;
20
+ authenticationMode: Gio.TlsAuthenticationMode;
21
+ /** Anchors a client certificate must chain to (`ca` with `requestCert`); may be empty. */
22
+ clientCa: Gio.TlsCertificate[];
23
+ }
24
+ /**
25
+ * Translates Node's options. Returns null without a certificate; throws on PEM that does not
26
+ * parse, as Node's createSecureContext (and so its https.Server) does.
27
+ */
28
+ export declare function resolveServerTls(options: ServerTlsOptions): ServerTlsSettings | null;
29
+ /**
30
+ * Builds the database that verifies client certificates against `ca`. `Gio.TlsFileDatabase` is
31
+ * GIO's only public database constructor, and it reads its file when it verifies — not when it
32
+ * is created (measured: a file deleted right after `new()` makes every client certificate fail).
33
+ * So the anchors live in a temporary file for as long as the listener does; the caller removes
34
+ * it on close. The file holds only the public CA certificates.
35
+ */
36
+ export declare function createTrustDatabase(anchors: Gio.TlsCertificate[]): {
37
+ database: Gio.TlsDatabase;
38
+ file: Gio.File;
39
+ };
40
+ export declare function removeFile(file: Gio.File): void;
41
+ export {};
@@ -3,6 +3,7 @@ import { EventEmitter } from 'node:events';
3
3
  import { Writable } from 'node:stream';
4
4
  import { type Response as BridgeResponse } from '@gjsify/http-soup-bridge';
5
5
  import { IncomingMessage } from './incoming-message.js';
6
+ import { type ServerTlsOptions } from './server-tls.js';
6
7
  /**
7
8
  * OutgoingMessage — Base class for ServerResponse and ClientRequest.
8
9
  * Reference: Node.js lib/_http_outgoing.js
@@ -85,6 +86,12 @@ export declare class Server extends EventEmitter {
85
86
  headersTimeout: number;
86
87
  requestTimeout: number;
87
88
  private _bridge;
89
+ /** Set through `_useTls()` by `https.Server`; null serves plain HTTP. */
90
+ private _tls;
91
+ /** An https.Server without a certificate must not fall back to plain HTTP. */
92
+ private _tlsRequested;
93
+ /** Backs the client-certificate database while listening; see createTrustDatabase. */
94
+ private _trustFile;
88
95
  private _address;
89
96
  /** Exposes the underlying Soup.Server so consumers (e.g. WebSocketServer
90
97
  * in `@gjsify/ws`) can register additional handlers (websocket, path-
@@ -92,9 +99,24 @@ export declare class Server extends EventEmitter {
92
99
  get soupServer(): unknown;
93
100
  constructor(requestListener?: ((req: IncomingMessage, res: ServerResponse) => void) | Record<string, unknown>);
94
101
  constructor(options: Record<string, unknown>, requestListener?: (req: IncomingMessage, res: ServerResponse) => void);
102
+ /**
103
+ * @internal `https.Server`'s hand-off, not part of Node's http.Server: from now on
104
+ * `listen()` terminates TLS with these `https.createServer()` options.
105
+ */
106
+ _useTls(options: ServerTlsOptions): void;
95
107
  listen(port?: number, hostname?: string, backlog?: number, callback?: () => void): this;
96
108
  listen(port?: number, hostname?: string, callback?: () => void): this;
97
109
  listen(port?: number, callback?: () => void): this;
110
+ private _listenPlain;
111
+ /**
112
+ * Listens with TLS on the bridge's Soup.Server. The bridge's own `listen()` has no HTTPS
113
+ * option, so this drives the Soup.Server it exposes for exactly this kind of caller (see
114
+ * server.vala): Soup.Server is a GObject, and setting its TLS properties or listening on it
115
+ * passes no libsoup boxed type through JS — the GC race the bridge exists to avoid stays
116
+ * C-side, since requests still arrive through the bridge's handler. The client-certificate
117
+ * hook below sees each connection's first Soup.ServerMessage, which is a GObject as well.
118
+ */
119
+ private _listenTls;
98
120
  private _handleRequest;
99
121
  private _handleUpgrade;
100
122
  address(): {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/http",
3
- "version": "0.51.1",
3
+ "version": "0.53.0",
4
4
  "description": "Node.js http module for Gjs",
5
5
  "module": "lib/esm/index.js",
6
6
  "types": "lib/types/index.d.ts",
@@ -42,22 +42,23 @@
42
42
  "http"
43
43
  ],
44
44
  "devDependencies": {
45
- "@gjsify/cli": "^0.51.1",
46
- "@gjsify/unit": "^0.51.1",
45
+ "@gjsify/cli": "^0.53.0",
46
+ "@gjsify/unit": "^0.53.0",
47
47
  "@types/node": "^25.9.2",
48
48
  "typescript": "^6.0.3"
49
49
  },
50
50
  "dependencies": {
51
- "@girs/gio-2.0": "5.0.0",
52
- "@girs/glib-2.0": "5.0.0",
53
- "@girs/soup-3.0": "5.0.0",
54
- "@gjsify/buffer": "^0.51.1",
55
- "@gjsify/events": "^0.51.1",
56
- "@gjsify/http-soup-bridge": "^0.51.1",
57
- "@gjsify/net": "^0.51.1",
58
- "@gjsify/stream": "^0.51.1",
59
- "@gjsify/url": "^0.51.1",
60
- "@gjsify/utils": "^0.51.1"
51
+ "@girs/gio-2.0": "5.4.0",
52
+ "@girs/glib-2.0": "5.4.0",
53
+ "@girs/soup-3.0": "5.4.0",
54
+ "@gjsify/buffer": "^0.53.0",
55
+ "@gjsify/events": "^0.53.0",
56
+ "@gjsify/http-soup-bridge": "^0.53.0",
57
+ "@gjsify/net": "^0.53.0",
58
+ "@gjsify/stream": "^0.53.0",
59
+ "@gjsify/tls": "^0.53.0",
60
+ "@gjsify/url": "^0.53.0",
61
+ "@gjsify/utils": "^0.53.0"
61
62
  },
62
63
  "gjsify": {
63
64
  "runtimes": {