@gjsify/fetch 0.25.1 → 0.26.1
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/lib/esm/index.js +1 -1
- package/lib/esm/register/xhr.js +1 -1
- package/lib/esm/request.js +1 -1
- package/lib/esm/utils/root-relative-system.js +1 -0
- package/lib/esm/utils/root-relative.js +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +26 -32
- package/lib/register/xhr.js +3 -6
- package/lib/request.js +2 -6
- package/lib/root-relative.spec.d.ts +2 -0
- package/lib/root-relative.spec.js +56 -0
- package/lib/utils/root-relative-system.d.ts +8 -0
- package/lib/utils/root-relative-system.js +25 -0
- package/lib/utils/root-relative.d.ts +23 -0
- package/lib/utils/root-relative.js +49 -0
- package/package.json +8 -8
package/lib/esm/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{Blob as e,File as t}from"./utils/blob-from.js";import{FetchError as n}from"./errors/fetch-error.js";import{isDomainOrSubdomain as r,isSameProtocol as i}from"./utils/is.js";import{clone as a}from"./body.js";import o from"./headers.js";import{parseDataUri as
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{Blob as e,File as t}from"./utils/blob-from.js";import{FetchError as n}from"./errors/fetch-error.js";import{isDomainOrSubdomain as r,isSameProtocol as i}from"./utils/is.js";import{clone as a}from"./body.js";import{AbortError as o}from"./errors/abort-error.js";import s from"./headers.js";import{parseDataUri as c}from"./utils/data-uri.js";import{resolveRootRelativeUrl as l}from"./utils/root-relative-system.js";import{isRedirect as u}from"./utils/is-redirect.js";import{Response as d}from"./response.js";import{parseReferrerPolicyFromHeader as f}from"./utils/referrer.js";import{Request as p,getSoupRequestOptions as m}from"./request.js";import{XMLHttpRequest as h,XMLHttpRequestUpload as g}from"./xhr.js";import{URL as _}from"@gjsify/url";import v from"node:stream";import{FormData as y}from"@gjsify/formdata";import b from"@girs/glib-2.0";const x=new Set([`data:`,`http:`,`https:`,`file:`]);function rewriteFetchInput(e){if(typeof e!=`string`)return e;let t=l(e);return t!==e&&globalThis.__GJSIFY_DEBUG_FETCH===!0&&console.log(`[fetch] rewrite ${e} → ${t}`),t}async function fetch(t,l={}){t=rewriteFetchInput(t);let h=new p(t,l),{parsedURL:g,options:y}=m(h);if(!x.has(g.protocol))throw TypeError(`@gjsify/fetch cannot load ${t}. URL scheme "${g.protocol.replace(/:$/,``)}" is not supported.`);if(g.protocol===`data:`){let{buffer:e,typeFull:t}=c(h.url);return new d(Buffer.from(e),{headers:{"Content-Type":t}})}if(g.protocol===`file:`){let e=globalThis.__GJSIFY_DEBUG_FETCH===!0;e&&console.log(`[fetch] file:// ${h.url}`);try{let t=b.filename_from_uri(h.url)[0];e&&console.log(`[fetch] file:// path=${t}`);let[r,i]=b.file_get_contents(t);if(e&&console.log(`[fetch] file:// ok=${r} bytes=${i?.byteLength??`?`}`),!r)throw new n(`Failed to read file: ${t}`,`system`);let a=i,o=new Uint8Array(a.byteLength);o.set(a);let s=new d(o);return e&&console.log(`[fetch] file:// response created`),s}catch(t){let r=t instanceof Error?t:Error(String(t));throw e&&console.warn(`[fetch] file:// FAIL: ${r.message}`),new n(`request to ${h.url} failed, reason: ${r.message}`,`system`,r)}}let{signal:S}=h;if(S&&S.aborted)throw new o(`The operation was aborted.`);let C,w;try{let e=await h._send(y);C=e.readable,w=e.cancellable}catch(e){if(S&&S.aborted)throw new o(`The operation was aborted.`);let t=e instanceof Error?e:Error(String(e));throw new n(`request to ${h.url} failed, reason: ${t.message}`,`system`,t)}let abortHandler=()=>{w.cancel()};S&&S.addEventListener(`abort`,abortHandler,{once:!0});let finalize=()=>{S&&S.removeEventListener(`abort`,abortHandler)};w.connect(()=>{C.destroy(new o(`The operation was aborted.`))}),C.on(`error`,e=>{finalize()});let T=h._message,E=s._newFromSoupMessage(T),D=T.status_code,O=T.get_reason_phrase();if(u(D)){let e=E.get(`Location`),t=null;try{t=e===null?null:new _(e,h.url)}catch{if(h.redirect!==`manual`)throw finalize(),new n(`uri requested responds with an invalid redirect URL: ${e}`,`invalid-redirect`)}switch(h.redirect){case`error`:throw finalize(),new n(`uri requested responds with a redirect, redirect mode is set to error: ${h.url}`,`no-redirect`);case`manual`:break;case`follow`:{if(t===null)break;if(h.counter>=h.follow)throw finalize(),new n(`maximum redirect reached at: ${h.url}`,`max-redirect`);let e={headers:new s(h.headers),follow:h.follow,counter:h.counter+1,agent:h.agent,compress:h.compress,method:h.method,body:a(h),signal:h.signal,size:h.size,referrer:h.referrer,referrerPolicy:h.referrerPolicy};if(!r(h.url,t)||!i(h.url,t))for(let t of[`authorization`,`www-authenticate`,`cookie`,`cookie2`])e.headers.delete(t);if(D!==303&&h.body&&l.body instanceof v.Readable)throw finalize(),new n(`Cannot follow redirect with body being a readable stream`,`unsupported-redirect`);(D===303||(D===301||D===302)&&h.method===`POST`)&&(e.method=`GET`,e.body=void 0,e.headers.delete(`content-length`));let o=f(E);return o&&(e.referrerPolicy=o),finalize(),fetch(new p(t,e))}default:throw TypeError(`Redirect option '${h.redirect}' is not a valid value of RequestRedirect`)}}let k={url:h.url,status:D,statusText:O,headers:E,size:h.size,counter:h.counter,highWaterMark:h.highWaterMark},A=E.get(`Content-Encoding`);if(!h.compress||h.method===`HEAD`||A===null||D===204||D===304)return finalize(),new d(C,k);if(typeof DecompressionStream<`u`){let t=null;if(A===`gzip`||A===`x-gzip`?t=`gzip`:(A===`deflate`||A===`x-deflate`)&&(t=`deflate`),t){let n=new e([await new d(C,k).arrayBuffer()]).stream().pipeThrough(new DecompressionStream(t));return finalize(),new d(n,k)}}return finalize(),new d(C,k)}export{o as AbortError,e as Blob,n as FetchError,t as File,y as FormData,s as Headers,p as Request,d as Response,h as XMLHttpRequest,g as XMLHttpRequestUpload,fetch as default,u as isRedirect,l as resolveRootRelativeUrl};
|
package/lib/esm/register/xhr.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{XMLHttpRequest as e,XMLHttpRequestUpload as t}from"../xhr.js";import n from"gi://GLib?version=2.0";globalThis.XMLHttpRequest===void 0&&(globalThis.XMLHttpRequest=e),globalThis.XMLHttpRequestUpload===void 0&&(globalThis.XMLHttpRequestUpload=t);const r=globalThis.URL;if(r&&r.__gjsify_objecturl!==!0){let e=new Map,t=r.createObjectURL?.bind(r),i=r.revokeObjectURL?.bind(r);r.createObjectURL=n=>{let r=n?._tmpPath;if(typeof r==`string`&&r.length>0){let t=`file://${r}`;return e.set(t,r),t}return t?t(n):`file:///dev/null`},r.revokeObjectURL=t=>{let r=e.get(t);
|
|
1
|
+
import{XMLHttpRequest as e,XMLHttpRequestUpload as t}from"../xhr.js";import n from"gi://GLib?version=2.0";globalThis.XMLHttpRequest===void 0&&(globalThis.XMLHttpRequest=e),globalThis.XMLHttpRequestUpload===void 0&&(globalThis.XMLHttpRequestUpload=t);const r=globalThis.URL;if(r&&r.__gjsify_objecturl!==!0){let e=new Map,t=r.createObjectURL?.bind(r),i=r.revokeObjectURL?.bind(r);r.createObjectURL=n=>{let r=n?._tmpPath;if(typeof r==`string`&&r.length>0){let t=`file://${r}`;return e.set(t,r),t}return t?t(n):`file:///dev/null`},r.revokeObjectURL=t=>{let r=e.get(t);r?(n.unlink(r),e.delete(t)):i&&i(t)},r.__gjsify_objecturl=!0}
|
package/lib/esm/request.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./_virtual/_rolldown/runtime.js";import{isAbortSignal as e}from"./utils/is.js";import t,{clone as n,extractContentType as r,getTotalBytes as i}from"./body.js";import a from"./headers.js";import{inputStreamToReadable as o,soupSendAsync as s}from"./utils/soup-helpers.js";import{DEFAULT_REFERRER_POLICY as c,determineRequestsReferrer as l,validateReferrerPolicy as u}from"./utils/referrer.js";import{URL as d}from"@gjsify/url";import f from"@girs/soup-3.0";import p from"@girs/glib-2.0";import m from"@girs/gio-2.0";const h=Symbol(`Request internals`);let g=null;function envPositiveInt(e,t){let n=p.getenv(e);if(!n)return t;let r=Number.parseInt(n,10);return Number.isFinite(r)&&r>0?r:t}function shouldAcceptAnyCert(e){return e===!1?!0:e===!0?!1:p.getenv(`NODE_TLS_REJECT_UNAUTHORIZED`)===`0`}function getSharedSession(){if(g===null){let e=envPositiveInt(`GJSIFY_FETCH_MAX_CONNS_PER_HOST`,16),t=Math.max(envPositiveInt(`GJSIFY_FETCH_MAX_CONNS`,64),e);g=new f.Session({maxConns:t,maxConnsPerHost:e})
|
|
1
|
+
import"./_virtual/_rolldown/runtime.js";import{isAbortSignal as e}from"./utils/is.js";import t,{clone as n,extractContentType as r,getTotalBytes as i}from"./body.js";import a from"./headers.js";import{inputStreamToReadable as o,soupSendAsync as s}from"./utils/soup-helpers.js";import{DEFAULT_REFERRER_POLICY as c,determineRequestsReferrer as l,validateReferrerPolicy as u}from"./utils/referrer.js";import{URL as d}from"@gjsify/url";import f from"@girs/soup-3.0";import p from"@girs/glib-2.0";import m from"@girs/gio-2.0";const h=Symbol(`Request internals`);let g=null;function envPositiveInt(e,t){let n=p.getenv(e);if(!n)return t;let r=Number.parseInt(n,10);return Number.isFinite(r)&&r>0?r:t}function shouldAcceptAnyCert(e){return e===!1?!0:e===!0?!1:p.getenv(`NODE_TLS_REJECT_UNAUTHORIZED`)===`0`}function getSharedSession(){if(g===null){let e=envPositiveInt(`GJSIFY_FETCH_MAX_CONNS_PER_HOST`,16),t=Math.max(envPositiveInt(`GJSIFY_FETCH_MAX_CONNS`,64),e);g=new f.Session({maxConns:t,maxConnsPerHost:e}),g.remove_feature_by_type(f.ContentDecoder.$gtype)}return g}const isRequest=e=>typeof e==`object`&&typeof e.url==`string`;var _=class Request extends t{cache;credentials;destination;get headers(){return this[h].headers}integrity;keepalive;get method(){return this[h].method}mode;get redirect(){return this[h].redirect}get referrer(){if(this[h].referrer===`no-referrer`)return``;if(this[h].referrer===`client`)return`about:client`;if(this[h].referrer)return this[h].referrer.toString()}get referrerPolicy(){return this[h].referrerPolicy}set referrerPolicy(e){this[h].referrerPolicy=u(e)}get signal(){return this[h].signal}get url(){return this[h].parsedURL.toString()}get _uri(){return p.Uri.parse(this.url,p.UriFlags.ENCODED)}get _session(){return this[h].session}get _message(){return this[h].message}get _inputStream(){return this[h].inputStream}get[Symbol.toStringTag](){return`Request`}[h];follow;compress=!1;counter=0;agent=``;highWaterMark=16384;insecureHTTPParser=!1;rejectUnauthorized;constructor(t,i){let o=t,s=i||{},c,l={};if(isRequest(t)?(c=new d(o.url),l=o):c=new d(t),c.username!==``||c.password!==``)throw TypeError(`${c} is an url with embedded credentials.`);let u=s.method||l.method||`GET`;if(/^(delete|get|head|options|post|put)$/i.test(u)&&(u=u.toUpperCase()),(i?.body!=null||isRequest(t)&&o.body!==null)&&(u===`GET`||u===`HEAD`))throw TypeError(`Request with GET/HEAD method cannot have body`);let m=i?.body?i.body:isRequest(t)&&o.body!==null?n(t):null;super(m,{size:s.size||0});let g=new a(i?.headers||o.headers||{});if(m!==null&&!g.has(`Content-Type`)){let e=r(m,this);e&&g.set(`Content-Type`,e)}let _=isRequest(t)?o.signal:null;if(i&&`signal`in i&&(_=i.signal),_!=null&&!e(_))throw TypeError(`Expected signal to be an instanceof AbortSignal or EventTarget`);let v=i?.referrer==null?o.referrer:i.referrer;if(v===``)v=`no-referrer`;else if(v){let e=new d(v);v=/^about:(\/\/)?client$/.test(e.toString())?`client`:e}else v=void 0;let y=c.protocol,b=null,x=null;(y===`http:`||y===`https:`)&&(b=getSharedSession(),x=new f.Message({method:u,uri:p.Uri.parse(c.toString(),p.UriFlags.ENCODED)})),this[h]={method:u,redirect:i?.redirect||o.redirect||`follow`,headers:g,parsedURL:c,signal:_,referrer:v,referrerPolicy:``,session:b,message:x},this.follow=s.follow===void 0?o.follow===void 0?20:o.follow:s.follow,this.compress=s.compress===void 0?o.compress===void 0?!0:o.compress:s.compress,this.counter=s.counter||o.counter||0,this.agent=s.agent||o.agent,this.highWaterMark=s.highWaterMark||o.highWaterMark||16384,this.insecureHTTPParser=s.insecureHTTPParser||o.insecureHTTPParser||!1,this.rejectUnauthorized=s.rejectUnauthorized??o.rejectUnauthorized,this.referrerPolicy=i?.referrerPolicy||o.referrerPolicy||``}async _send(e){let{session:t,message:n,signal:r,parsedURL:i}=this[h];if(!t||!n)throw Error(`Cannot send request: no Soup session (non-HTTP URL?)`);e.headers._appendToSoupMessage(n);let a=this._rawBodyBuffer;if(a!==null&&a.byteLength>0){let t=e.headers.get(`content-type`)||null;n.set_request_body_from_bytes(t,new p.Bytes(a))}i.protocol===`https:`&&shouldAcceptAnyCert(this.rejectUnauthorized)&&n.connect(`accept-certificate`,(e,t,n)=>!0);let c=new m.Cancellable;return r&&(r.aborted?c.cancel():r.addEventListener(`abort`,()=>c.cancel(),{once:!0})),this[h].inputStream=await s(t,n,p.PRIORITY_DEFAULT,c),this[h].readable=o(this[h].inputStream),{inputStream:this[h].inputStream,readable:this[h].readable,cancellable:c}}clone(){return new Request(this)}async arrayBuffer(){return super.arrayBuffer()}async blob(){return super.blob()}async formData(){return super.formData()}async json(){return super.json()}async text(){return super.text()}};Object.defineProperties(_.prototype,{method:{enumerable:!0},url:{enumerable:!0},headers:{enumerable:!0},redirect:{enumerable:!0},clone:{enumerable:!0},signal:{enumerable:!0},referrer:{enumerable:!0},referrerPolicy:{enumerable:!0}});const getSoupRequestOptions=e=>{let{parsedURL:t}=e[h],n=new a(e[h].headers);n.has(`Accept`)||n.set(`Accept`,`*/*`);let r=null;if(e.body===null&&/^(post|put)$/i.test(e.method)&&(r=`0`),e.body!==null){let t=i(e);typeof t==`number`&&!Number.isNaN(t)&&(r=String(t))}r&&n.set(`Content-Length`,r),e.referrerPolicy===``&&(e.referrerPolicy=c),e.referrer&&e.referrer!==`no-referrer`?e[h].referrer=l(e):e[h].referrer=`no-referrer`,e[h].referrer instanceof d&&n.set(`Referer`,e.referrer),n.has(`User-Agent`)||n.set(`User-Agent`,`gjsify-fetch`),e.compress&&!n.has(`Accept-Encoding`)&&n.set(`Accept-Encoding`,`gzip, deflate`);let{agent:o}=e;return typeof o==`function`&&(o=o(t)),!n.has(`Connection`)&&!o&&n.set(`Connection`,`close`),{parsedURL:t,options:{headers:n}}};export{_ as Request,_ as default,getSoupRequestOptions};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";import{rewriteRootRelativeUrl as e}from"./root-relative.js";import t from"system";function resolveRootRelativeUrl(n){return e(n,t.programPath??t.programInvocationName??``)}export{resolveRootRelativeUrl};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"../_virtual/_rolldown/runtime.js";function rewriteRootRelativeUrl(e,t){if(!e.startsWith(`/`)||e.startsWith(`//`))return e;let n=t.lastIndexOf(`/`);return n<=0?e:`file://${t.slice(0,n)}${e}`}export{rewriteRootRelativeUrl};
|
package/lib/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { URL } from '@gjsify/url';
|
|
|
10
10
|
export { FormData, Headers, Request, Response, FetchError, AbortError, isRedirect };
|
|
11
11
|
export { Blob, File };
|
|
12
12
|
export { XMLHttpRequest, XMLHttpRequestUpload } from './xhr.js';
|
|
13
|
+
export { resolveRootRelativeUrl } from './utils/root-relative-system.js';
|
|
13
14
|
/**
|
|
14
15
|
* Fetch function
|
|
15
16
|
*
|
package/lib/index.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
// Copyright (c) node-fetch contributors. MIT license.
|
|
4
4
|
// Modifications: Rewritten for GJS using libsoup 3.0 (Soup.Session)
|
|
5
5
|
import GLib from '@girs/glib-2.0';
|
|
6
|
-
import System from 'system';
|
|
7
6
|
import Stream from 'node:stream';
|
|
8
7
|
import { parseDataUri } from './utils/data-uri.js';
|
|
8
|
+
import { resolveRootRelativeUrl } from './utils/root-relative-system.js';
|
|
9
9
|
import { clone } from './body.js';
|
|
10
10
|
import Response from './response.js';
|
|
11
11
|
import Headers from './headers.js';
|
|
@@ -21,39 +21,31 @@ import { URL } from '@gjsify/url';
|
|
|
21
21
|
export { FormData, Headers, Request, Response, FetchError, AbortError, isRedirect };
|
|
22
22
|
export { Blob, File };
|
|
23
23
|
export { XMLHttpRequest, XMLHttpRequestUpload } from './xhr.js';
|
|
24
|
+
// The shared program-dir rewrite — consumed by @gjsify/xmlhttprequest so fetch
|
|
25
|
+
// and XHR cannot drift apart again (see utils/root-relative.ts for the pure
|
|
26
|
+
// logic + utils/root-relative-system.ts for the `system`-reading wrapper).
|
|
27
|
+
export { resolveRootRelativeUrl } from './utils/root-relative-system.js';
|
|
24
28
|
const supportedSchemas = new Set(['data:', 'http:', 'https:', 'file:']);
|
|
25
|
-
|
|
29
|
+
/**
|
|
30
|
+
* String inputs go through the shared root-relative rewrite
|
|
31
|
+
* (`utils/root-relative.ts` — one copy for fetch AND XHR; two drifting copies
|
|
32
|
+
* are what #869 was). Non-string inputs pass through untouched.
|
|
33
|
+
*/
|
|
34
|
+
function rewriteFetchInput(input) {
|
|
26
35
|
if (typeof input !== 'string')
|
|
27
36
|
return input;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// unchanged, and every root-relative `fetch()` died with
|
|
39
|
-
// `ERR_INVALID_URL` — while XHR-loaded assets in the same app resolved
|
|
40
|
-
// fine, because those two siblings import the module. That asymmetry is
|
|
41
|
-
// what made the excalibur-jelly-jumper showcase render an empty level on
|
|
42
|
-
// node/bun/deno: its Tiled map is the one asset fetched rather than XHR'd.
|
|
43
|
-
const programPath = System.programPath ?? System.programInvocationName ?? '';
|
|
44
|
-
if (!programPath)
|
|
45
|
-
return input;
|
|
46
|
-
const dir = GLib.path_get_dirname(programPath);
|
|
47
|
-
const rewritten = `file://${dir}${input}`;
|
|
48
|
-
if (DEBUG)
|
|
49
|
-
console.log(`[fetch] rewrite ${input} → ${rewritten}`);
|
|
50
|
-
return rewritten;
|
|
51
|
-
}
|
|
52
|
-
catch (err) {
|
|
53
|
-
if (DEBUG)
|
|
54
|
-
console.warn(`[fetch] rewrite FAILED: ${err instanceof Error ? err.message : String(err)}`);
|
|
55
|
-
return input;
|
|
37
|
+
// No try/catch: `resolveRootRelativeUrl` reads two properties off the
|
|
38
|
+
// `system` built-in and calls a pure function — there is no throw path, so
|
|
39
|
+
// a catch here could only ever SWALLOW a bug. That is precisely how #869
|
|
40
|
+
// hid: the previous spelling reached `globalThis.imports.system`, which is
|
|
41
|
+
// `undefined` off gjs, and the catch turned the resulting TypeError into a
|
|
42
|
+
// silently unrewritten URL. If this ever does throw, the caller should see
|
|
43
|
+
// it.
|
|
44
|
+
const rewritten = resolveRootRelativeUrl(input);
|
|
45
|
+
if (rewritten !== input && globalThis.__GJSIFY_DEBUG_FETCH === true) {
|
|
46
|
+
console.log(`[fetch] rewrite ${input} → ${rewritten}`);
|
|
56
47
|
}
|
|
48
|
+
return rewritten;
|
|
57
49
|
}
|
|
58
50
|
/**
|
|
59
51
|
* Fetch function
|
|
@@ -63,7 +55,7 @@ function rewriteRootRelativeUrl(input) {
|
|
|
63
55
|
*/
|
|
64
56
|
export default async function fetch(url, init = {}) {
|
|
65
57
|
// Rewrite root-relative URLs before Request constructor parses them
|
|
66
|
-
url =
|
|
58
|
+
url = rewriteFetchInput(url);
|
|
67
59
|
// Build request object
|
|
68
60
|
const request = new Request(url, init);
|
|
69
61
|
const { parsedURL, options } = getSoupRequestOptions(request);
|
|
@@ -271,7 +263,9 @@ export default async function fetch(url, init = {}) {
|
|
|
271
263
|
// the streaming decode is fragile. Buffer first, then decompress the
|
|
272
264
|
// complete in-memory blob — the same pattern @gjsify/tar uses for .tgz.
|
|
273
265
|
const rawBuffer = await new Response(readable, responseOptions).arrayBuffer();
|
|
274
|
-
const decompressed = new Blob([rawBuffer])
|
|
266
|
+
const decompressed = new Blob([rawBuffer])
|
|
267
|
+
.stream()
|
|
268
|
+
.pipeThrough(new DecompressionStream(format));
|
|
275
269
|
finalize();
|
|
276
270
|
return new Response(decompressed, responseOptions);
|
|
277
271
|
}
|
package/lib/register/xhr.js
CHANGED
|
@@ -36,12 +36,9 @@ if (urlCtor && urlCtor.__gjsify_objecturl !== true) {
|
|
|
36
36
|
urlCtor.revokeObjectURL = (url) => {
|
|
37
37
|
const path = objectUrlPaths.get(url);
|
|
38
38
|
if (path) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
catch {
|
|
43
|
-
// best-effort temp-file cleanup
|
|
44
|
-
}
|
|
39
|
+
// Best-effort temp-file cleanup — GLib.unlink reports failure via
|
|
40
|
+
// its return value (-1); it has no throw path (no `throws` in GIR).
|
|
41
|
+
GLib.unlink(path);
|
|
45
42
|
objectUrlPaths.delete(url);
|
|
46
43
|
}
|
|
47
44
|
else if (nativeRevoke) {
|
package/lib/request.js
CHANGED
|
@@ -101,12 +101,8 @@ function getSharedSession() {
|
|
|
101
101
|
// body without removing the Content-Encoding header, causing
|
|
102
102
|
// double-decompression when index.ts also runs DecompressionStream.
|
|
103
103
|
// Remove it once here so our JS-level decompression handles everything.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
/* not present */
|
|
109
|
-
}
|
|
104
|
+
// (No throw path: an absent feature is a documented silent no-op.)
|
|
105
|
+
sharedSession.remove_feature_by_type(Soup.ContentDecoder.$gtype);
|
|
110
106
|
}
|
|
111
107
|
return sharedSession;
|
|
112
108
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Coverage for the SHARED root-relative rewrite (utils/root-relative.ts).
|
|
2
|
+
//
|
|
3
|
+
// The regression it pins: fetch and XHR used to carry two hand-written copies
|
|
4
|
+
// of this rewrite, and the fetch copy drifted (reading the GJS-only
|
|
5
|
+
// `globalThis.imports.system`) — every root-relative `fetch()` then died with
|
|
6
|
+
// `ERR_INVALID_URL` on the node-gi reverse bridge while XHR kept working
|
|
7
|
+
// (#869). One function, consumed by both, tested here on gjs AND node.
|
|
8
|
+
//
|
|
9
|
+
// The PURE half (`rewriteRootRelativeUrl(url, programPath)`) is imported
|
|
10
|
+
// statically and covered on BOTH runtimes. The `system`-reading wrapper
|
|
11
|
+
// (`utils/root-relative-system.ts`) is deliberately NOT imported statically:
|
|
12
|
+
// on `--app node` the bare `system` built-in is aliased to
|
|
13
|
+
// `@gjsify/node-gi/system` and kept external, so a static import here would
|
|
14
|
+
// make the node test bundle hard-require the node-gi bridge — the exact
|
|
15
|
+
// cross-platform loadability this split exists to protect. The wrapper is
|
|
16
|
+
// instead exercised on gjs via the aliased package root (`import('fetch')`),
|
|
17
|
+
// which on node resolves to the harmless native-globals re-export.
|
|
18
|
+
import { describe, expect, it, on } from '@gjsify/unit';
|
|
19
|
+
import { rewriteRootRelativeUrl } from './utils/root-relative.js';
|
|
20
|
+
export default async () => {
|
|
21
|
+
await describe('fetch.rewriteRootRelativeUrl (pure)', async () => {
|
|
22
|
+
await it('maps a root-relative URL to file:// under the program dir', async () => {
|
|
23
|
+
expect(rewriteRootRelativeUrl('/res/tilemaps/level1.tmx', '/opt/app/dist/main.js')).toBe('file:///opt/app/dist/res/tilemaps/level1.tmx');
|
|
24
|
+
});
|
|
25
|
+
await it('leaves a protocol-relative URL (//host/...) untouched', async () => {
|
|
26
|
+
expect(rewriteRootRelativeUrl('//cdn.example.com/x.png', '/opt/app/dist/main.js')).toBe('//cdn.example.com/x.png');
|
|
27
|
+
});
|
|
28
|
+
await it('leaves absolute and relative URLs untouched', async () => {
|
|
29
|
+
expect(rewriteRootRelativeUrl('https://example.com/a', '/opt/app/dist/main.js')).toBe('https://example.com/a');
|
|
30
|
+
expect(rewriteRootRelativeUrl('res/local.png', '/opt/app/dist/main.js')).toBe('res/local.png');
|
|
31
|
+
});
|
|
32
|
+
await it('returns the input when no usable program dir exists', async () => {
|
|
33
|
+
// '' (no program path at all) and a bare name (no directory part).
|
|
34
|
+
expect(rewriteRootRelativeUrl('/res/x.png', '')).toBe('/res/x.png');
|
|
35
|
+
expect(rewriteRootRelativeUrl('/res/x.png', 'main.js')).toBe('/res/x.png');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
// The `system`-reading wrapper, end to end on gjs — the runtime where the
|
|
39
|
+
// program path is real. Reached through the aliased package root so the
|
|
40
|
+
// node test bundle never links the `system` external (see file header).
|
|
41
|
+
await on('Gjs', async () => {
|
|
42
|
+
await describe('fetch.resolveRootRelativeUrl (system wrapper)', async () => {
|
|
43
|
+
await it('maps a root-relative URL to file:// under the real program dir', async () => {
|
|
44
|
+
// Namespace access (not a destructured binding): on the node
|
|
45
|
+
// build this module is the globals re-export without the
|
|
46
|
+
// wrapper, and only the gjs path ever reaches this line.
|
|
47
|
+
const fetchModule = await import('fetch');
|
|
48
|
+
const rewritten = fetchModule.resolveRootRelativeUrl('/res/tilemaps/level1.tmx');
|
|
49
|
+
// The program path is the test bundle's own path — assert the
|
|
50
|
+
// CONTRACT, not the host.
|
|
51
|
+
expect(rewritten.startsWith('file:///')).toBe(true);
|
|
52
|
+
expect(rewritten.endsWith('/res/tilemaps/level1.tmx')).toBe(true);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrite a root-relative URL to `file://` under the running program's
|
|
3
|
+
* directory (see `rewriteRootRelativeUrl` for the contract). Reads the program
|
|
4
|
+
* path from the `system` built-in — `programPath` on gjs, with
|
|
5
|
+
* `programInvocationName` as the fallback (on the node bridge that is
|
|
6
|
+
* `process.argv[1]`, the bundle itself — the right base).
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveRootRelativeUrl(url: string): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// The GJS-host binding of the shared root-relative rewrite: reads the running
|
|
2
|
+
// program's path from the `system` built-in and delegates to the PURE
|
|
3
|
+
// `rewriteRootRelativeUrl` (utils/root-relative.ts — the #869 single copy).
|
|
4
|
+
//
|
|
5
|
+
// This wrapper is a SEPARATE module on purpose. The bare `system` built-in is
|
|
6
|
+
// external on the gjs target and aliased to `@gjsify/node-gi/system` (kept
|
|
7
|
+
// external) on `--app node`, so whichever module imports it drags a hard
|
|
8
|
+
// node-gi requirement into the emitted bundle. Keeping the import here confines
|
|
9
|
+
// that to the GJS-reachable path (fetch's own Soup-backed impl + XHR), while
|
|
10
|
+
// the pure half stays testable — and loadable — on plain Node.
|
|
11
|
+
// The bare `system` built-in — NOT `globalThis.imports.system`: the probe shape
|
|
12
|
+
// is `undefined` on the node-gi reverse bridge unless the globals shim happens
|
|
13
|
+
// to be injected, which is exactly how #869's silent `ERR_INVALID_URL` hid.
|
|
14
|
+
import System from 'system';
|
|
15
|
+
import { rewriteRootRelativeUrl } from './root-relative.js';
|
|
16
|
+
/**
|
|
17
|
+
* Rewrite a root-relative URL to `file://` under the running program's
|
|
18
|
+
* directory (see `rewriteRootRelativeUrl` for the contract). Reads the program
|
|
19
|
+
* path from the `system` built-in — `programPath` on gjs, with
|
|
20
|
+
* `programInvocationName` as the fallback (on the node bridge that is
|
|
21
|
+
* `process.argv[1]`, the bundle itself — the right base).
|
|
22
|
+
*/
|
|
23
|
+
export function resolveRootRelativeUrl(url) {
|
|
24
|
+
return rewriteRootRelativeUrl(url, System.programPath ?? System.programInvocationName ?? '');
|
|
25
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rewrite a root-relative URL (starts with `/` but not `//`, e.g.
|
|
3
|
+
* `/res/images/foo.png`) to a `file://` URL under the given program path's
|
|
4
|
+
* directory, so a GJS/GTK app loads bundled assets with the same paths a
|
|
5
|
+
* browser would resolve against its origin.
|
|
6
|
+
*
|
|
7
|
+
* Returns the input unchanged when it is not root-relative or when no usable
|
|
8
|
+
* program path is given. The security implications (arbitrary file reads) are
|
|
9
|
+
* acceptable for the current use cases — revisit if these packages ever handle
|
|
10
|
+
* untrusted input.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately no `GLib.path_get_dirname`: `@girs/glib-2.0` is emptied to `{}`
|
|
13
|
+
* in a plain-Node bundle (only the gjs target and the reverse bridge resolve
|
|
14
|
+
* it), so a GLib call here only ever "worked" behind a swallowing catch. The
|
|
15
|
+
* program path is `/`-separated on every runtime this rewrite serves, making
|
|
16
|
+
* dirname a string slice.
|
|
17
|
+
*
|
|
18
|
+
* @param url The URL to rewrite.
|
|
19
|
+
* @param programPath The running program's path (`System.programPath ??
|
|
20
|
+
* System.programInvocationName` on gjs; `process.argv[1]` — the bundle
|
|
21
|
+
* itself — on the node-gi reverse bridge).
|
|
22
|
+
*/
|
|
23
|
+
export declare function rewriteRootRelativeUrl(url: string, programPath: string): string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Root-relative URL → `file://` under the program directory.
|
|
2
|
+
//
|
|
3
|
+
// THE one copy of this rewrite. `@gjsify/fetch` and `@gjsify/xmlhttprequest`
|
|
4
|
+
// used to carry two hand-written spellings of it, and the copies drifting is
|
|
5
|
+
// exactly what broke: the fetch copy read `globalThis.imports.system` (GJS-only)
|
|
6
|
+
// while the XHR copy imported the `system` built-in, so on the node-gi reverse
|
|
7
|
+
// bridge every XHR-loaded asset resolved and every root-relative `fetch()` died
|
|
8
|
+
// with `ERR_INVALID_URL` (#869 — the excalibur-jelly-jumper showcase rendered an
|
|
9
|
+
// empty level because its Tiled map is the one asset fetched rather than XHR'd).
|
|
10
|
+
//
|
|
11
|
+
// This module is deliberately PURE — the program path is a PARAMETER, and the
|
|
12
|
+
// `system` built-in is read one module over (`root-relative-system.ts`). The
|
|
13
|
+
// split is load-bearing for cross-platform loadability: on `--app node` the
|
|
14
|
+
// bare `system` specifier is aliased to `@gjsify/node-gi/system` and kept
|
|
15
|
+
// EXTERNAL, so any module that imports it makes the emitted bundle hard-require
|
|
16
|
+
// the node-gi bridge at load. `@gjsify/fetch` is a cross-platform package whose
|
|
17
|
+
// node test bundle must load on plain Node — its spec therefore covers THIS
|
|
18
|
+
// pure function on gjs AND node, while the `system` read stays confined to the
|
|
19
|
+
// GJS-reachable wrapper.
|
|
20
|
+
/**
|
|
21
|
+
* Rewrite a root-relative URL (starts with `/` but not `//`, e.g.
|
|
22
|
+
* `/res/images/foo.png`) to a `file://` URL under the given program path's
|
|
23
|
+
* directory, so a GJS/GTK app loads bundled assets with the same paths a
|
|
24
|
+
* browser would resolve against its origin.
|
|
25
|
+
*
|
|
26
|
+
* Returns the input unchanged when it is not root-relative or when no usable
|
|
27
|
+
* program path is given. The security implications (arbitrary file reads) are
|
|
28
|
+
* acceptable for the current use cases — revisit if these packages ever handle
|
|
29
|
+
* untrusted input.
|
|
30
|
+
*
|
|
31
|
+
* Deliberately no `GLib.path_get_dirname`: `@girs/glib-2.0` is emptied to `{}`
|
|
32
|
+
* in a plain-Node bundle (only the gjs target and the reverse bridge resolve
|
|
33
|
+
* it), so a GLib call here only ever "worked" behind a swallowing catch. The
|
|
34
|
+
* program path is `/`-separated on every runtime this rewrite serves, making
|
|
35
|
+
* dirname a string slice.
|
|
36
|
+
*
|
|
37
|
+
* @param url The URL to rewrite.
|
|
38
|
+
* @param programPath The running program's path (`System.programPath ??
|
|
39
|
+
* System.programInvocationName` on gjs; `process.argv[1]` — the bundle
|
|
40
|
+
* itself — on the node-gi reverse bridge).
|
|
41
|
+
*/
|
|
42
|
+
export function rewriteRootRelativeUrl(url, programPath) {
|
|
43
|
+
if (!url.startsWith('/') || url.startsWith('//'))
|
|
44
|
+
return url;
|
|
45
|
+
const slash = programPath.lastIndexOf('/');
|
|
46
|
+
if (slash <= 0)
|
|
47
|
+
return url; // no usable program dir ('' or a bare name)
|
|
48
|
+
return `file://${programPath.slice(0, slash)}${url}`;
|
|
49
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/fetch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.1",
|
|
4
4
|
"description": "Web and Node.js fetch module for Gjs",
|
|
5
5
|
"module": "lib/esm/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"fetch"
|
|
52
52
|
],
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@gjsify/cli": "^0.
|
|
55
|
-
"@gjsify/unit": "^0.
|
|
54
|
+
"@gjsify/cli": "^0.26.1",
|
|
55
|
+
"@gjsify/unit": "^0.26.1",
|
|
56
56
|
"@types/node": "^25.9.2",
|
|
57
57
|
"typescript": "^6.0.3"
|
|
58
58
|
},
|
|
@@ -61,11 +61,11 @@
|
|
|
61
61
|
"@girs/gjs": "^4.1.0",
|
|
62
62
|
"@girs/glib-2.0": "^4.1.0",
|
|
63
63
|
"@girs/soup-3.0": "^4.1.0",
|
|
64
|
-
"@gjsify/dom-events": "^0.
|
|
65
|
-
"@gjsify/formdata": "^0.
|
|
66
|
-
"@gjsify/http": "^0.
|
|
67
|
-
"@gjsify/url": "^0.
|
|
68
|
-
"@gjsify/utils": "^0.
|
|
64
|
+
"@gjsify/dom-events": "^0.26.1",
|
|
65
|
+
"@gjsify/formdata": "^0.26.1",
|
|
66
|
+
"@gjsify/http": "^0.26.1",
|
|
67
|
+
"@gjsify/url": "^0.26.1",
|
|
68
|
+
"@gjsify/utils": "^0.26.1"
|
|
69
69
|
},
|
|
70
70
|
"gjsify": {
|
|
71
71
|
"runtimes": {
|