@gjsify/fetch 0.0.4 → 0.1.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.
Files changed (88) hide show
  1. package/README.md +27 -2
  2. package/globals.mjs +12 -0
  3. package/lib/body.d.ts +69 -0
  4. package/lib/body.js +375 -0
  5. package/lib/errors/abort-error.d.ts +7 -0
  6. package/lib/errors/abort-error.js +9 -0
  7. package/lib/errors/base.d.ts +6 -0
  8. package/lib/errors/base.js +17 -0
  9. package/lib/errors/fetch-error.d.ts +16 -0
  10. package/lib/errors/fetch-error.js +23 -0
  11. package/lib/esm/body.js +104 -56
  12. package/lib/esm/errors/base.js +3 -1
  13. package/lib/esm/headers.js +116 -131
  14. package/lib/esm/index.js +145 -190
  15. package/lib/esm/request.js +42 -41
  16. package/lib/esm/response.js +19 -4
  17. package/lib/esm/utils/blob-from.js +2 -98
  18. package/lib/esm/utils/data-uri.js +23 -0
  19. package/lib/esm/utils/is.js +7 -3
  20. package/lib/esm/utils/multipart-parser.js +5 -2
  21. package/lib/esm/utils/referrer.js +10 -10
  22. package/lib/esm/utils/soup-helpers.js +22 -0
  23. package/lib/headers.d.ts +33 -0
  24. package/lib/headers.js +195 -0
  25. package/lib/index.d.ts +18 -0
  26. package/lib/index.js +205 -0
  27. package/lib/request.d.ts +101 -0
  28. package/lib/request.js +308 -0
  29. package/lib/response.d.ts +73 -0
  30. package/lib/response.js +158 -0
  31. package/lib/types/index.d.ts +1 -0
  32. package/lib/types/index.js +1 -0
  33. package/lib/types/system-error.d.ts +11 -0
  34. package/lib/types/system-error.js +2 -0
  35. package/lib/utils/blob-from.d.ts +2 -0
  36. package/lib/utils/blob-from.js +4 -0
  37. package/lib/utils/data-uri.d.ts +10 -0
  38. package/lib/utils/data-uri.js +27 -0
  39. package/lib/utils/get-search.d.ts +1 -0
  40. package/lib/utils/get-search.js +8 -0
  41. package/lib/utils/is-redirect.d.ts +7 -0
  42. package/lib/utils/is-redirect.js +10 -0
  43. package/lib/utils/is.d.ts +35 -0
  44. package/lib/utils/is.js +74 -0
  45. package/lib/utils/multipart-parser.d.ts +2 -0
  46. package/lib/utils/multipart-parser.js +396 -0
  47. package/lib/utils/referrer.d.ts +76 -0
  48. package/lib/utils/referrer.js +283 -0
  49. package/lib/utils/soup-helpers.d.ts +12 -0
  50. package/lib/utils/soup-helpers.js +25 -0
  51. package/package.json +23 -27
  52. package/src/body.ts +181 -169
  53. package/src/errors/base.ts +3 -1
  54. package/src/headers.ts +155 -202
  55. package/src/index.spec.ts +268 -3
  56. package/src/index.ts +199 -312
  57. package/src/request.ts +84 -75
  58. package/src/response.ts +48 -18
  59. package/src/test.mts +1 -1
  60. package/src/utils/blob-from.ts +4 -164
  61. package/src/utils/data-uri.ts +29 -0
  62. package/src/utils/is.ts +15 -15
  63. package/src/utils/multipart-parser.ts +3 -3
  64. package/src/utils/referrer.ts +11 -11
  65. package/src/utils/soup-helpers.ts +37 -0
  66. package/tsconfig.json +4 -4
  67. package/tsconfig.tsbuildinfo +1 -0
  68. package/lib/cjs/body.js +0 -255
  69. package/lib/cjs/errors/abort-error.js +0 -9
  70. package/lib/cjs/errors/base.js +0 -17
  71. package/lib/cjs/errors/fetch-error.js +0 -21
  72. package/lib/cjs/headers.js +0 -202
  73. package/lib/cjs/index.js +0 -224
  74. package/lib/cjs/request.js +0 -281
  75. package/lib/cjs/response.js +0 -133
  76. package/lib/cjs/types/index.js +0 -1
  77. package/lib/cjs/types/system-error.js +0 -1
  78. package/lib/cjs/utils/blob-from.js +0 -101
  79. package/lib/cjs/utils/get-search.js +0 -11
  80. package/lib/cjs/utils/is-redirect.js +0 -7
  81. package/lib/cjs/utils/is.js +0 -28
  82. package/lib/cjs/utils/multipart-parser.js +0 -353
  83. package/lib/cjs/utils/referrer.js +0 -153
  84. package/test.gjs.js +0 -34758
  85. package/test.gjs.mjs +0 -53172
  86. package/test.node.js +0 -1226
  87. package/test.node.mjs +0 -6273
  88. package/tsconfig.types.json +0 -8
package/lib/esm/index.js CHANGED
@@ -1,210 +1,169 @@
1
- import Soup from "@girs/soup-3.0";
2
- import Request from "./request.js";
3
- import zlib from "zlib";
4
- import Stream, { PassThrough, pipeline as pump } from "stream";
5
- import dataUriToBuffer from "data-uri-to-buffer";
6
- import { writeToStream, clone } from "./body.js";
1
+ import Stream from "node:stream";
2
+ import { parseDataUri } from "./utils/data-uri.js";
3
+ import { clone } from "./body.js";
7
4
  import Response from "./response.js";
8
5
  import Headers from "./headers.js";
9
- import { getSoupRequestOptions } from "./request.js";
6
+ import Request, { getSoupRequestOptions } from "./request.js";
10
7
  import { FetchError } from "./errors/fetch-error.js";
11
8
  import { AbortError } from "./errors/abort-error.js";
12
9
  import { isRedirect } from "./utils/is-redirect.js";
13
- import { FormData } from "formdata-polyfill/esm.min.js";
10
+ import { FormData } from "@gjsify/formdata";
14
11
  import { isDomainOrSubdomain, isSameProtocol } from "./utils/is.js";
15
12
  import { parseReferrerPolicyFromHeader } from "./utils/referrer.js";
16
13
  import {
17
14
  Blob,
18
- File,
19
- fileFromSync,
20
- fileFrom,
21
- blobFromSync,
22
- blobFrom
15
+ File
23
16
  } from "./utils/blob-from.js";
24
- import { URL } from "@gjsify/deno-runtime/ext/url/00_url";
17
+ import { URL } from "@gjsify/url";
25
18
  const supportedSchemas = /* @__PURE__ */ new Set(["data:", "http:", "https:"]);
26
19
  async function fetch(url, init = {}) {
27
- return new Promise(async (resolve, reject) => {
28
- const request = new Request(url, init);
29
- const { parsedURL, options } = getSoupRequestOptions(request);
30
- if (!supportedSchemas.has(parsedURL.protocol)) {
31
- throw new TypeError(`@gjsify/fetch cannot load ${url}. URL scheme "${parsedURL.protocol.replace(/:$/, "")}" is not supported.`);
32
- }
33
- if (parsedURL.protocol === "data:") {
34
- const data = dataUriToBuffer(request.url);
35
- const response2 = new Response(data, { headers: { "Content-Type": data.typeFull } });
36
- resolve(response2);
37
- return;
38
- }
39
- const { signal } = request;
40
- let response = null;
41
- const abort = () => {
42
- const error = new AbortError("The operation was aborted.");
43
- reject(error);
44
- if (request.body && request.body instanceof Stream.Readable) {
45
- request.body.destroy(error);
46
- }
47
- if (!response || !response.body) {
48
- return;
49
- }
50
- response.body.emit("error", error);
51
- };
52
- if (signal && signal.aborted) {
53
- abort();
54
- return;
55
- }
56
- const abortAndFinalize = () => {
57
- abort();
58
- finalize();
59
- };
60
- let readable;
61
- let cancellable;
62
- try {
63
- const sendRes = await request._send(options);
64
- readable = sendRes.readable;
65
- cancellable = sendRes.cancellable;
66
- } catch (error) {
67
- reject(error);
68
- }
20
+ const request = new Request(url, init);
21
+ const { parsedURL, options } = getSoupRequestOptions(request);
22
+ if (!supportedSchemas.has(parsedURL.protocol)) {
23
+ throw new TypeError(`@gjsify/fetch cannot load ${url}. URL scheme "${parsedURL.protocol.replace(/:$/, "")}" is not supported.`);
24
+ }
25
+ if (parsedURL.protocol === "data:") {
26
+ const { buffer, typeFull } = parseDataUri(request.url);
27
+ const response = new Response(Buffer.from(buffer), { headers: { "Content-Type": typeFull } });
28
+ return response;
29
+ }
30
+ const { signal } = request;
31
+ if (signal && signal.aborted) {
32
+ throw new AbortError("The operation was aborted.");
33
+ }
34
+ let readable;
35
+ let cancellable;
36
+ try {
37
+ const sendRes = await request._send(options);
38
+ readable = sendRes.readable;
39
+ cancellable = sendRes.cancellable;
40
+ } catch (error) {
41
+ const err = error instanceof Error ? error : new Error(String(error));
42
+ throw new FetchError(`request to ${request.url} failed, reason: ${err.message}`, "system", err);
43
+ }
44
+ const abortHandler = () => {
45
+ cancellable.cancel();
46
+ };
47
+ if (signal) {
48
+ signal.addEventListener("abort", abortHandler, { once: true });
49
+ }
50
+ const finalize = () => {
69
51
  if (signal) {
70
- signal.addEventListener("abort", abortAndFinalize);
52
+ signal.removeEventListener("abort", abortHandler);
71
53
  }
72
- const cancelledSignalId = cancellable.connect("cancelled", () => {
73
- abortAndFinalize();
74
- });
75
- const finalize = () => {
76
- cancellable.cancel();
77
- if (signal) {
78
- signal.removeEventListener("abort", abortAndFinalize);
54
+ };
55
+ cancellable.connect("cancelled", () => {
56
+ readable.destroy(new AbortError("The operation was aborted."));
57
+ });
58
+ readable.on("error", (error) => {
59
+ finalize();
60
+ });
61
+ const message = request._message;
62
+ const headers = Headers._newFromSoupMessage(message);
63
+ const statusCode = message.status_code;
64
+ const statusMessage = message.get_reason_phrase();
65
+ if (isRedirect(statusCode)) {
66
+ const location = headers.get("Location");
67
+ let locationURL = null;
68
+ try {
69
+ locationURL = location === null ? null : new URL(location, request.url);
70
+ } catch {
71
+ if (request.redirect !== "manual") {
72
+ finalize();
73
+ throw new FetchError(`uri requested responds with an invalid redirect URL: ${location}`, "invalid-redirect");
79
74
  }
80
- cancellable.disconnect(cancelledSignalId);
81
- };
82
- const message = request._message;
83
- readable.on("error", (error) => {
84
- reject(new FetchError(`request to ${request.url} failed, reason: ${error.message}`, "system", error));
85
- finalize();
86
- });
87
- message.connect("finished", (message2) => {
88
- const headers = Headers._newFromSoupMessage(request._message, Soup.MessageHeadersType.RESPONSE);
89
- const statusCode = message2.status_code;
90
- const statusMessage = message2.get_reason_phrase();
91
- if (isRedirect(statusCode)) {
92
- const location = headers.get("Location");
93
- let locationURL = null;
94
- try {
95
- locationURL = location === null ? null : new URL(location, request.url);
96
- } catch {
97
- if (request.redirect !== "manual") {
98
- reject(new FetchError(`uri requested responds with an invalid redirect URL: ${location}`, "invalid-redirect"));
99
- finalize();
100
- return;
101
- }
75
+ }
76
+ switch (request.redirect) {
77
+ case "error":
78
+ finalize();
79
+ throw new FetchError(`uri requested responds with a redirect, redirect mode is set to error: ${request.url}`, "no-redirect");
80
+ case "manual":
81
+ break;
82
+ case "follow": {
83
+ if (locationURL === null) {
84
+ break;
102
85
  }
103
- switch (request.redirect) {
104
- case "error":
105
- reject(new FetchError(`uri requested responds with a redirect, redirect mode is set to error: ${request.url}`, "no-redirect"));
106
- finalize();
107
- return;
108
- case "manual":
109
- break;
110
- case "follow": {
111
- if (locationURL === null) {
112
- break;
113
- }
114
- if (request.counter >= request.follow) {
115
- reject(new FetchError(`maximum redirect reached at: ${request.url}`, "max-redirect"));
116
- finalize();
117
- return;
118
- }
119
- const requestOptions = {
120
- headers: new Headers(request.headers),
121
- follow: request.follow,
122
- counter: request.counter + 1,
123
- agent: request.agent,
124
- compress: request.compress,
125
- method: request.method,
126
- body: clone(request),
127
- signal: request.signal,
128
- size: request.size,
129
- referrer: request.referrer,
130
- referrerPolicy: request.referrerPolicy
131
- };
132
- if (!isDomainOrSubdomain(request.url, locationURL) || !isSameProtocol(request.url, locationURL)) {
133
- for (const name of ["authorization", "www-authenticate", "cookie", "cookie2"]) {
134
- requestOptions.headers.delete(name);
135
- }
136
- }
137
- if (statusCode !== 303 && request.body && init.body instanceof Stream.Readable) {
138
- reject(new FetchError("Cannot follow redirect with body being a readable stream", "unsupported-redirect"));
139
- finalize();
140
- return;
141
- }
142
- if (statusCode === 303 || (statusCode === 301 || statusCode === 302) && request.method === "POST") {
143
- requestOptions.method = "GET";
144
- requestOptions.body = void 0;
145
- requestOptions.headers.delete("content-length");
146
- }
147
- const responseReferrerPolicy = parseReferrerPolicyFromHeader(headers);
148
- if (responseReferrerPolicy) {
149
- requestOptions.referrerPolicy = responseReferrerPolicy;
150
- }
151
- resolve(fetch(new Request(locationURL, requestOptions)));
152
- finalize();
153
- return;
86
+ if (request.counter >= request.follow) {
87
+ finalize();
88
+ throw new FetchError(`maximum redirect reached at: ${request.url}`, "max-redirect");
89
+ }
90
+ const requestOptions = {
91
+ headers: new Headers(request.headers),
92
+ follow: request.follow,
93
+ counter: request.counter + 1,
94
+ agent: request.agent,
95
+ compress: request.compress,
96
+ method: request.method,
97
+ body: clone(request),
98
+ signal: request.signal,
99
+ size: request.size,
100
+ referrer: request.referrer,
101
+ referrerPolicy: request.referrerPolicy
102
+ };
103
+ if (!isDomainOrSubdomain(request.url, locationURL) || !isSameProtocol(request.url, locationURL)) {
104
+ for (const name of ["authorization", "www-authenticate", "cookie", "cookie2"]) {
105
+ requestOptions.headers.delete(name);
154
106
  }
155
- default:
156
- return reject(new TypeError(`Redirect option '${request.redirect}' is not a valid value of RequestRedirect`));
157
107
  }
158
- }
159
- let body = pump(response_, new PassThrough(), (error) => {
160
- if (error) {
161
- reject(error);
108
+ if (statusCode !== 303 && request.body && init.body instanceof Stream.Readable) {
109
+ finalize();
110
+ throw new FetchError("Cannot follow redirect with body being a readable stream", "unsupported-redirect");
162
111
  }
163
- });
164
- const responseOptions = {
165
- url: request.url,
166
- status: statusCode,
167
- statusText: statusMessage,
168
- headers,
169
- size: request.size,
170
- counter: request.counter,
171
- highWaterMark: request.highWaterMark
172
- };
173
- const codings = headers.get("Content-Encoding");
174
- if (!request.compress || request.method === "HEAD" || codings === null || statusCode === 204 || statusCode === 304) {
175
- response = new Response(body, responseOptions);
176
- resolve(response);
177
- return;
178
- }
179
- const zlibOptions = {
180
- flush: zlib.Z_SYNC_FLUSH,
181
- finishFlush: zlib.Z_SYNC_FLUSH
182
- };
183
- if (codings === "gzip" || codings === "x-gzip") {
184
- body = pump(body, zlib.createGunzip(zlibOptions), (error) => {
185
- if (error) {
186
- reject(error);
187
- }
188
- });
189
- response = new Response(body, responseOptions);
190
- resolve(response);
191
- return;
112
+ if (statusCode === 303 || (statusCode === 301 || statusCode === 302) && request.method === "POST") {
113
+ requestOptions.method = "GET";
114
+ requestOptions.body = void 0;
115
+ requestOptions.headers.delete("content-length");
116
+ }
117
+ const responseReferrerPolicy = parseReferrerPolicyFromHeader(headers);
118
+ if (responseReferrerPolicy) {
119
+ requestOptions.referrerPolicy = responseReferrerPolicy;
120
+ }
121
+ finalize();
122
+ return fetch(new Request(locationURL, requestOptions));
192
123
  }
193
- if (codings === "br") {
194
- body = pump(body, zlib.createBrotliDecompress(), (error) => {
195
- if (error) {
196
- reject(error);
197
- }
198
- });
199
- response = new Response(body, responseOptions);
200
- resolve(response);
201
- return;
124
+ default:
125
+ throw new TypeError(`Redirect option '${request.redirect}' is not a valid value of RequestRedirect`);
126
+ }
127
+ }
128
+ const responseOptions = {
129
+ url: request.url,
130
+ status: statusCode,
131
+ statusText: statusMessage,
132
+ headers,
133
+ size: request.size,
134
+ counter: request.counter,
135
+ highWaterMark: request.highWaterMark
136
+ };
137
+ const codings = headers.get("Content-Encoding");
138
+ if (!request.compress || request.method === "HEAD" || codings === null || statusCode === 204 || statusCode === 304) {
139
+ finalize();
140
+ return new Response(readable, responseOptions);
141
+ }
142
+ if (typeof DecompressionStream !== "undefined") {
143
+ let format = null;
144
+ if (codings === "gzip" || codings === "x-gzip") {
145
+ format = "gzip";
146
+ } else if (codings === "deflate" || codings === "x-deflate") {
147
+ format = "deflate";
148
+ }
149
+ if (format) {
150
+ const webBody = new Response(readable, responseOptions).body;
151
+ if (webBody) {
152
+ const decompressed = webBody.pipeThrough(new DecompressionStream(format));
153
+ finalize();
154
+ return new Response(decompressed, responseOptions);
202
155
  }
203
- response = new Response(body, responseOptions);
204
- resolve(response);
205
- });
206
- writeToStream(inputStream, request).catch(reject);
207
- });
156
+ }
157
+ }
158
+ finalize();
159
+ return new Response(readable, responseOptions);
160
+ }
161
+ const _isGJS = typeof globalThis.imports !== "undefined";
162
+ if (_isGJS) {
163
+ globalThis.fetch = fetch;
164
+ globalThis.Headers = Headers;
165
+ globalThis.Request = Request;
166
+ globalThis.Response = Response;
208
167
  }
209
168
  export {
210
169
  AbortError,
@@ -215,10 +174,6 @@ export {
215
174
  Headers,
216
175
  Request,
217
176
  Response,
218
- blobFrom,
219
- blobFromSync,
220
177
  fetch as default,
221
- fileFrom,
222
- fileFromSync,
223
178
  isRedirect
224
179
  };
@@ -1,10 +1,8 @@
1
- import "@girs/gjs";
2
- import "@girs/gio-2.0";
3
1
  import GLib from "@girs/glib-2.0";
4
2
  import Soup from "@girs/soup-3.0";
5
3
  import Gio from "@girs/gio-2.0";
6
- import * as SoupExt from "@gjsify/soup-3.0";
7
- import { URL } from "@gjsify/deno-runtime/ext/url/00_url";
4
+ import { soupSendAsync, inputStreamToReadable } from "./utils/soup-helpers.js";
5
+ import { URL } from "@gjsify/url";
8
6
  import Headers from "./headers.js";
9
7
  import Body, { clone, extractContentType, getTotalBytes } from "./body.js";
10
8
  import { isAbortSignal } from "./utils/is.js";
@@ -13,7 +11,7 @@ import {
13
11
  determineRequestsReferrer,
14
12
  DEFAULT_REFERRER_POLICY
15
13
  } from "./utils/referrer.js";
16
- const INTERNALS = Symbol("Request internals");
14
+ const INTERNALS = /* @__PURE__ */ Symbol("Request internals");
17
15
  const isRequest = (obj) => {
18
16
  return typeof obj === "object" && typeof obj.url === "string";
19
17
  };
@@ -99,43 +97,45 @@ class Request extends Body {
99
97
  highWaterMark = 16384;
100
98
  insecureHTTPParser = false;
101
99
  constructor(input, init) {
100
+ const inputRL = input;
101
+ const initRL = init || {};
102
102
  let parsedURL;
103
103
  let requestObj = {};
104
104
  if (isRequest(input)) {
105
- parsedURL = new URL(input.url);
106
- requestObj = input;
105
+ parsedURL = new URL(inputRL.url);
106
+ requestObj = inputRL;
107
107
  } else {
108
108
  parsedURL = new URL(input);
109
109
  }
110
110
  if (parsedURL.username !== "" || parsedURL.password !== "") {
111
111
  throw new TypeError(`${parsedURL} is an url with embedded credentials.`);
112
112
  }
113
- let method = init.method || requestObj.method || "GET";
113
+ let method = initRL.method || requestObj.method || "GET";
114
114
  if (/^(delete|get|head|options|post|put)$/i.test(method)) {
115
115
  method = method.toUpperCase();
116
116
  }
117
- if ((init.body != null || isRequest(input) && input.body !== null) && (method === "GET" || method === "HEAD")) {
117
+ if ((init?.body != null || isRequest(input) && inputRL.body !== null) && (method === "GET" || method === "HEAD")) {
118
118
  throw new TypeError("Request with GET/HEAD method cannot have body");
119
119
  }
120
- const inputBody = init.body ? init.body : isRequest(input) && input.body !== null ? clone(input) : null;
120
+ const inputBody = init?.body ? init.body : isRequest(input) && inputRL.body !== null ? clone(input) : null;
121
121
  super(inputBody, {
122
- size: init.size || init.size || 0
122
+ size: initRL.size || 0
123
123
  });
124
- const headers = new Headers(init.headers || input.headers || {});
124
+ const headers = new Headers(init?.headers || inputRL.headers || {});
125
125
  if (inputBody !== null && !headers.has("Content-Type")) {
126
126
  const contentType = extractContentType(inputBody, this);
127
127
  if (contentType) {
128
128
  headers.set("Content-Type", contentType);
129
129
  }
130
130
  }
131
- let signal = isRequest(input) ? input.signal : null;
132
- if ("signal" in init) {
131
+ let signal = isRequest(input) ? inputRL.signal : null;
132
+ if (init && "signal" in init) {
133
133
  signal = init.signal;
134
134
  }
135
135
  if (signal != null && !isAbortSignal(signal)) {
136
136
  throw new TypeError("Expected signal to be an instanceof AbortSignal or EventTarget");
137
137
  }
138
- let referrer = init.referrer == null ? input.referrer : init.referrer;
138
+ let referrer = init?.referrer == null ? inputRL.referrer : init.referrer;
139
139
  if (referrer === "") {
140
140
  referrer = "no-referrer";
141
141
  } else if (referrer) {
@@ -144,14 +144,19 @@ class Request extends Body {
144
144
  } else {
145
145
  referrer = void 0;
146
146
  }
147
- const session = SoupExt.ExtSession.new();
148
- const message = new Soup.Message({
149
- method,
150
- uri: this._uri
151
- });
147
+ const scheme = parsedURL.protocol;
148
+ let session = null;
149
+ let message = null;
150
+ if (scheme === "http:" || scheme === "https:") {
151
+ session = new Soup.Session();
152
+ message = new Soup.Message({
153
+ method,
154
+ uri: GLib.Uri.parse(parsedURL.toString(), GLib.UriFlags.NONE)
155
+ });
156
+ }
152
157
  this[INTERNALS] = {
153
158
  method,
154
- redirect: init.redirect || input.redirect || "follow",
159
+ redirect: init?.redirect || inputRL.redirect || "follow",
155
160
  headers,
156
161
  parsedURL,
157
162
  signal,
@@ -160,24 +165,26 @@ class Request extends Body {
160
165
  session,
161
166
  message
162
167
  };
163
- this.follow = init.follow === void 0 ? input.follow === void 0 ? 20 : input.follow : init.follow;
164
- this.compress = init.compress === void 0 ? input.compress === void 0 ? true : input.compress : init.compress;
165
- this.counter = init.counter || input.counter || 0;
166
- this.agent = init.agent || input.agent;
167
- this.highWaterMark = init.highWaterMark || input.highWaterMark || 16384;
168
- this.insecureHTTPParser = init.insecureHTTPParser || input.insecureHTTPParser || false;
169
- this.referrerPolicy = init.referrerPolicy || input.referrerPolicy || "";
168
+ this.follow = initRL.follow === void 0 ? inputRL.follow === void 0 ? 20 : inputRL.follow : initRL.follow;
169
+ this.compress = initRL.compress === void 0 ? inputRL.compress === void 0 ? true : inputRL.compress : initRL.compress;
170
+ this.counter = initRL.counter || inputRL.counter || 0;
171
+ this.agent = initRL.agent || inputRL.agent;
172
+ this.highWaterMark = initRL.highWaterMark || inputRL.highWaterMark || 16384;
173
+ this.insecureHTTPParser = initRL.insecureHTTPParser || inputRL.insecureHTTPParser || false;
174
+ this.referrerPolicy = init?.referrerPolicy || inputRL.referrerPolicy || "";
170
175
  }
171
176
  /**
172
- * Custom send method using Soup, used in fetch to send the request
173
- * @param options
174
- * @returns
177
+ * Send the request using Soup.
175
178
  */
176
179
  async _send(options) {
177
- options.headers._appendToSoupMessage(this._message);
180
+ const { session, message } = this[INTERNALS];
181
+ if (!session || !message) {
182
+ throw new Error("Cannot send request: no Soup session (non-HTTP URL?)");
183
+ }
184
+ options.headers._appendToSoupMessage(message);
178
185
  const cancellable = new Gio.Cancellable();
179
- this[INTERNALS].inputStream = await this._session.sendAsync(this._message, GLib.PRIORITY_DEFAULT, cancellable);
180
- this[INTERNALS].readable = this[INTERNALS].inputStream.toReadable({});
186
+ this[INTERNALS].inputStream = await soupSendAsync(session, message, GLib.PRIORITY_DEFAULT, cancellable);
187
+ this[INTERNALS].readable = inputStreamToReadable(this[INTERNALS].inputStream);
181
188
  return {
182
189
  inputStream: this[INTERNALS].inputStream,
183
190
  readable: this[INTERNALS].readable,
@@ -248,7 +255,7 @@ const getSoupRequestOptions = (request) => {
248
255
  headers.set("Referer", request.referrer);
249
256
  }
250
257
  if (!headers.has("User-Agent")) {
251
- headers.set("User-Agent", "node-fetch");
258
+ headers.set("User-Agent", "gjsify-fetch");
252
259
  }
253
260
  if (request.compress && !headers.has("Accept-Encoding")) {
254
261
  headers.set("Accept-Encoding", "gzip, deflate, br");
@@ -261,13 +268,7 @@ const getSoupRequestOptions = (request) => {
261
268
  headers.set("Connection", "close");
262
269
  }
263
270
  const options = {
264
- // Overwrite search to retain trailing ? (issue #776)
265
- // path: parsedURL.pathname + search,
266
- // The following options are not expressed in the URL
267
- // method: request.method,
268
271
  headers
269
- // insecureHTTPParser: request.insecureHTTPParser,
270
- // agent
271
272
  };
272
273
  return {
273
274
  parsedURL,
@@ -3,8 +3,8 @@ import Gio from "@girs/gio-2.0";
3
3
  import Headers from "./headers.js";
4
4
  import Body, { clone, extractContentType } from "./body.js";
5
5
  import { isRedirect } from "./utils/is-redirect.js";
6
- import { URL } from "@gjsify/deno-runtime/ext/url/00_url";
7
- const INTERNALS = Symbol("Response internals");
6
+ import { URL } from "@gjsify/url";
7
+ const INTERNALS = /* @__PURE__ */ Symbol("Response internals");
8
8
  class Response extends Body {
9
9
  [INTERNALS];
10
10
  _inputStream = null;
@@ -60,7 +60,6 @@ class Response extends Body {
60
60
  *
61
61
  * @return Response
62
62
  */
63
- // @ts-ignore
64
63
  clone() {
65
64
  return new Response(clone(this, this.highWaterMark), {
66
65
  type: this.type,
@@ -95,6 +94,22 @@ class Response extends Body {
95
94
  response[INTERNALS].type = "error";
96
95
  return response;
97
96
  }
97
+ /**
98
+ * Create a Response with a JSON body.
99
+ * @param data The data to serialize as JSON.
100
+ * @param init Optional response init options.
101
+ * @returns A Response with the JSON body and appropriate content-type header.
102
+ */
103
+ static json(data, init) {
104
+ const body = JSON.stringify(data);
105
+ const options = { ...init };
106
+ const headers = new Headers(options.headers);
107
+ if (!headers.has("content-type")) {
108
+ headers.set("content-type", "application/json");
109
+ }
110
+ options.headers = headers;
111
+ return new Response(body, options);
112
+ }
98
113
  get [Symbol.toStringTag]() {
99
114
  return "Response";
100
115
  }
@@ -104,7 +119,7 @@ class Response extends Body {
104
119
  }
105
120
  const outputStream = Gio.MemoryOutputStream.new_resizable();
106
121
  await new Promise((resolve, reject) => {
107
- outputStream.splice_async(this._inputStream, Gio.OutputStreamSpliceFlags.CLOSE_TARGET | Gio.OutputStreamSpliceFlags.CLOSE_SOURCE, GLib.PRIORITY_DEFAULT, null, (self, res) => {
122
+ outputStream.splice_async(this._inputStream, Gio.OutputStreamSpliceFlags.CLOSE_TARGET | Gio.OutputStreamSpliceFlags.CLOSE_SOURCE, GLib.PRIORITY_DEFAULT, null, (_self, res) => {
108
123
  try {
109
124
  resolve(outputStream.splice_finish(res));
110
125
  } catch (error) {