@jskit-ai/http-runtime 0.1.90 → 0.1.91
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/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
"packageVersion": 1,
|
|
3
3
|
"packageId": "@jskit-ai/http-runtime",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.91",
|
|
5
5
|
"kind": "runtime",
|
|
6
6
|
"dependsOn": [],
|
|
7
7
|
"capabilities": {
|
|
@@ -67,7 +67,7 @@ export default Object.freeze({
|
|
|
67
67
|
"mutations": {
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"runtime": {
|
|
70
|
-
"@jskit-ai/kernel": "0.1.
|
|
70
|
+
"@jskit-ai/kernel": "0.1.92"
|
|
71
71
|
},
|
|
72
72
|
"dev": {}
|
|
73
73
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/http-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.91",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"./shared/validators/operationValidation": "./src/shared/validators/operationValidation.js"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@jskit-ai/kernel": "0.1.
|
|
21
|
+
"@jskit-ai/kernel": "0.1.92",
|
|
22
22
|
"json-rest-schema": "1.x.x"
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -33,6 +33,14 @@ function resolveFetch() {
|
|
|
33
33
|
throw new Error("createHttpClient requires fetchImpl when global fetch is unavailable.");
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function normalizeResolvedRequestUrl(value, fallback = "") {
|
|
37
|
+
if (value === undefined || value === null || value === "") {
|
|
38
|
+
return fallback;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return String(value);
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
function isObjectBody(value) {
|
|
37
45
|
return Boolean(value) && typeof value === "object" && !(value instanceof FormData);
|
|
38
46
|
}
|
|
@@ -164,6 +172,8 @@ async function readNdjsonStream(response, handlers = {}) {
|
|
|
164
172
|
|
|
165
173
|
function createHttpClient(options = {}) {
|
|
166
174
|
const configuredFetchImpl = typeof options.fetchImpl === "function" ? options.fetchImpl : null;
|
|
175
|
+
const configuredResolveRequestUrl =
|
|
176
|
+
typeof options.resolveRequestUrl === "function" ? options.resolveRequestUrl : null;
|
|
167
177
|
const unsafeMethods = resolveUnsafeMethods(options.unsafeMethods);
|
|
168
178
|
const hooks = options?.hooks && typeof options.hooks === "object" ? options.hooks : {};
|
|
169
179
|
|
|
@@ -189,9 +199,19 @@ function createHttpClient(options = {}) {
|
|
|
189
199
|
|
|
190
200
|
async function fetchSessionForCsrf() {
|
|
191
201
|
const activeFetch = configuredFetchImpl || resolveFetch();
|
|
202
|
+
const requestUrl = await resolveRequestUrl(csrf.sessionPath, {
|
|
203
|
+
originalUrl: csrf.sessionPath,
|
|
204
|
+
method: "GET",
|
|
205
|
+
requestOptions: {
|
|
206
|
+
method: "GET"
|
|
207
|
+
},
|
|
208
|
+
state: null,
|
|
209
|
+
stream: false,
|
|
210
|
+
csrfSession: true
|
|
211
|
+
});
|
|
192
212
|
let response;
|
|
193
213
|
try {
|
|
194
|
-
response = await activeFetch(
|
|
214
|
+
response = await activeFetch(requestUrl, {
|
|
195
215
|
method: "GET",
|
|
196
216
|
credentials: String(options?.credentials || "same-origin")
|
|
197
217
|
});
|
|
@@ -255,6 +275,19 @@ function createHttpClient(options = {}) {
|
|
|
255
275
|
}
|
|
256
276
|
}
|
|
257
277
|
|
|
278
|
+
async function resolveRequestUrl(url, context = {}) {
|
|
279
|
+
const normalizedUrl = String(url || "").trim();
|
|
280
|
+
if (!configuredResolveRequestUrl) {
|
|
281
|
+
return normalizedUrl;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const resolved = await configuredResolveRequestUrl(normalizedUrl, {
|
|
285
|
+
...context,
|
|
286
|
+
url: normalizedUrl
|
|
287
|
+
});
|
|
288
|
+
return normalizeResolvedRequestUrl(resolved, normalizedUrl);
|
|
289
|
+
}
|
|
290
|
+
|
|
258
291
|
async function maybeRetry({ response, method, state, data, stream }) {
|
|
259
292
|
if (!csrf.enabled) {
|
|
260
293
|
return false;
|
|
@@ -321,11 +354,19 @@ function createHttpClient(options = {}) {
|
|
|
321
354
|
...forwardedRequestOptions
|
|
322
355
|
} = requestOptions && typeof requestOptions === "object" ? requestOptions : {};
|
|
323
356
|
const requestUrl = appendRequestQueryToUrl(url, requestQuery, transport);
|
|
357
|
+
const resolvedRequestUrl = await resolveRequestUrl(requestUrl, {
|
|
358
|
+
originalUrl: url,
|
|
359
|
+
method,
|
|
360
|
+
requestOptions,
|
|
361
|
+
state: resolvedState,
|
|
362
|
+
stream: Boolean(stream),
|
|
363
|
+
csrfSession: false
|
|
364
|
+
});
|
|
324
365
|
const headers =
|
|
325
366
|
requestOptions.headers && typeof requestOptions.headers === "object" ? { ...requestOptions.headers } : {};
|
|
326
367
|
|
|
327
368
|
const decorateHeadersResult = decorateHeaders({
|
|
328
|
-
url:
|
|
369
|
+
url: resolvedRequestUrl,
|
|
329
370
|
method,
|
|
330
371
|
headers,
|
|
331
372
|
requestOptions,
|
|
@@ -369,7 +410,7 @@ function createHttpClient(options = {}) {
|
|
|
369
410
|
config,
|
|
370
411
|
state: resolvedState,
|
|
371
412
|
transport,
|
|
372
|
-
url:
|
|
413
|
+
url: resolvedRequestUrl
|
|
373
414
|
};
|
|
374
415
|
}
|
|
375
416
|
|
package/test/client.test.js
CHANGED
|
@@ -56,6 +56,67 @@ test("request serializes json body and injects csrf token for unsafe methods", a
|
|
|
56
56
|
assert.equal(calls[1][1].body, JSON.stringify({ demo: true }));
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
+
test("request resolves request urls after query encoding and before fetch", async () => {
|
|
60
|
+
const calls = [];
|
|
61
|
+
const contexts = [];
|
|
62
|
+
const client = createHttpClient({
|
|
63
|
+
csrf: {
|
|
64
|
+
enabled: false
|
|
65
|
+
},
|
|
66
|
+
resolveRequestUrl(url, context = {}) {
|
|
67
|
+
contexts.push({ url, context });
|
|
68
|
+
return url.replace(/^\/api\//u, "/api/app/beepollen/");
|
|
69
|
+
},
|
|
70
|
+
fetchImpl: async (url, options) => {
|
|
71
|
+
calls.push([url, options]);
|
|
72
|
+
return mockResponse({
|
|
73
|
+
data: {
|
|
74
|
+
ok: true
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const payload = await client.request("/api/vibe64/sessions", {
|
|
81
|
+
method: "GET",
|
|
82
|
+
query: {
|
|
83
|
+
cursor: "next page"
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
assert.deepEqual(payload, { ok: true });
|
|
88
|
+
assert.equal(calls[0][0], "/api/app/beepollen/vibe64/sessions?cursor=next+page");
|
|
89
|
+
assert.equal(contexts[0].url, "/api/vibe64/sessions?cursor=next+page");
|
|
90
|
+
assert.equal(contexts[0].context.originalUrl, "/api/vibe64/sessions");
|
|
91
|
+
assert.equal(contexts[0].context.method, "GET");
|
|
92
|
+
assert.equal(contexts[0].context.stream, false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("requestStream resolves request urls before fetch", async () => {
|
|
96
|
+
const calls = [];
|
|
97
|
+
const client = createHttpClient({
|
|
98
|
+
csrf: {
|
|
99
|
+
enabled: false
|
|
100
|
+
},
|
|
101
|
+
resolveRequestUrl(url) {
|
|
102
|
+
return url.replace(/^\/api\//u, "/api/app/beepollen/");
|
|
103
|
+
},
|
|
104
|
+
fetchImpl: async (url, options) => {
|
|
105
|
+
calls.push([url, options]);
|
|
106
|
+
return mockResponse({
|
|
107
|
+
contentType: "text/plain; charset=utf-8",
|
|
108
|
+
text: ""
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
await client.requestStream("/api/vibe64/events", {
|
|
114
|
+
method: "GET"
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
assert.equal(calls[0][0], "/api/app/beepollen/vibe64/events");
|
|
118
|
+
});
|
|
119
|
+
|
|
59
120
|
test("request parses json:api responses as json payloads", async () => {
|
|
60
121
|
const fetchImpl = async () =>
|
|
61
122
|
mockResponse({
|
|
@@ -69,6 +69,33 @@ test("createTransientRetryHttpClient retries transient GET request failures", as
|
|
|
69
69
|
});
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
+
test("createTransientRetryHttpClient resolves request urls before fetch", async () => {
|
|
73
|
+
const calls = [];
|
|
74
|
+
const client = createTransientRetryHttpClient({
|
|
75
|
+
csrf: {
|
|
76
|
+
enabled: false
|
|
77
|
+
},
|
|
78
|
+
resolveRequestUrl(url) {
|
|
79
|
+
return url.replace(/^\/api\//u, "/api/app/beepollen/");
|
|
80
|
+
},
|
|
81
|
+
fetchImpl: async (url, options) => {
|
|
82
|
+
calls.push([url, options]);
|
|
83
|
+
return mockResponse({
|
|
84
|
+
data: {
|
|
85
|
+
ok: true
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const payload = await client.request("/api/vibe64/sessions", {
|
|
92
|
+
method: "GET"
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
assert.deepEqual(payload, { ok: true });
|
|
96
|
+
assert.equal(calls[0][0], "/api/app/beepollen/vibe64/sessions");
|
|
97
|
+
});
|
|
98
|
+
|
|
72
99
|
test("createTransientRetryHttpClient does not retry unsafe transient failures", async () => {
|
|
73
100
|
await withImmediateTimers(async () => {
|
|
74
101
|
let callCount = 0;
|