-
-
Notifications
You must be signed in to change notification settings - Fork 173
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #118 from getkirby/fiber/steps/request
Request module
- Loading branch information
Showing
4 changed files
with
250 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { describe, expect, it } from "vitest"; | ||
import { body } from "./request.js"; | ||
|
||
describe.concurrent("request globals", () => { | ||
it("should create body from object", async () => { | ||
const result = body({ | ||
a: "A" | ||
}); | ||
|
||
expect(result).toStrictEqual(JSON.stringify({ a: "A" })); | ||
}); | ||
|
||
it("should create body from string", async () => { | ||
const result = body("test"); | ||
expect(result).toStrictEqual("test"); | ||
}); | ||
|
||
it("should create body from FormData", async () => { | ||
const formData = new FormData(); | ||
formData.append("a", "A"); | ||
|
||
const result = body(formData); | ||
|
||
expect(result).toStrictEqual(JSON.stringify({ a: "A" })); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/** | ||
* @vitest-environment node | ||
*/ | ||
|
||
import { describe, expect, it } from "vitest"; | ||
import { globals } from "./request.js"; | ||
|
||
describe.concurrent("request globals", () => { | ||
it("should create globals from string", async () => { | ||
const result = globals("$language"); | ||
expect(result).toStrictEqual("$language"); | ||
}); | ||
|
||
it("should create globals from array", async () => { | ||
const result = globals(["$language"]); | ||
expect(result).toStrictEqual("$language"); | ||
}); | ||
|
||
it("should skip globals", async () => { | ||
const result = globals(); | ||
expect(result).toStrictEqual(false); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/** | ||
* @vitest-environment node | ||
*/ | ||
|
||
import { describe, expect, it } from "vitest"; | ||
import { headers } from "./request.js"; | ||
|
||
describe.concurrent("request headers", () => { | ||
it("should create default headers", async () => { | ||
const result = headers(); | ||
const expected = { | ||
"content-type": "application/json", | ||
"x-csrf": false, | ||
"x-fiber": true, | ||
"x-fiber-globals": false, | ||
"x-fiber-referrer": false | ||
}; | ||
|
||
expect(result).toStrictEqual(expected); | ||
}); | ||
|
||
it("should add custom headers", async () => { | ||
const result = headers({ | ||
"x-foo": "test" | ||
}); | ||
|
||
const expected = { | ||
"content-type": "application/json", | ||
"x-csrf": false, | ||
"x-fiber": true, | ||
"x-fiber-globals": false, | ||
"x-fiber-referrer": false, | ||
"x-foo": "test" | ||
}; | ||
|
||
expect(result).toStrictEqual(expected); | ||
}); | ||
|
||
it("should set options", async () => { | ||
const result = headers( | ||
{}, | ||
{ | ||
csrf: "dev", | ||
globals: ["$language"], | ||
referrer: "/test" | ||
} | ||
); | ||
|
||
const expected = { | ||
"content-type": "application/json", | ||
"x-csrf": "dev", | ||
"x-fiber": true, | ||
"x-fiber-globals": "$language", | ||
"x-fiber-referrer": "/test" | ||
}; | ||
|
||
expect(result).toStrictEqual(expected); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
import { buildUrl, isSameOrigin, makeAbsolute } from "@/helpers/url.js"; | ||
import { toLowerKeys } from "../helpers/object.js"; | ||
import JsonRequestError from "@/errors/JsonRequestError.js"; | ||
import RequestError from "@/errors/RequestError.js"; | ||
|
||
/** | ||
* Creates a proper request body | ||
* | ||
* @param {String|FormData|Object|Array} | ||
* @returns {String} | ||
*/ | ||
export const body = (body) => { | ||
if (body instanceof HTMLFormElement) { | ||
body = new FormData(body); | ||
} | ||
|
||
if (body instanceof FormData) { | ||
body = Object.fromEntries(body); | ||
} | ||
|
||
if (typeof body === "object") { | ||
return JSON.stringify(body); | ||
} | ||
|
||
return body; | ||
}; | ||
|
||
/** | ||
* Convert globals to comma separated string | ||
* @param {Array|String} globals | ||
* @returns {String|false} | ||
*/ | ||
export const globals = (globals) => { | ||
if (globals) { | ||
if (Array.isArray(globals) === false) { | ||
return String(globals); | ||
} | ||
|
||
return globals.join(","); | ||
} | ||
|
||
return false; | ||
}; | ||
|
||
/** | ||
* Builds all required headers for a request | ||
* | ||
* @param {Object} headers | ||
* @param {Object} options All request options | ||
* @returns {Object} | ||
*/ | ||
export const headers = (headers = {}, options = {}) => { | ||
return { | ||
"content-type": "application/json", | ||
"x-csrf": options.csrf ?? false, | ||
"x-fiber": true, | ||
"x-fiber-globals": globals(options.globals), | ||
"x-fiber-referrer": options.referrer ?? false, | ||
...toLowerKeys(headers) | ||
}; | ||
}; | ||
|
||
/** | ||
* @param {string|URL} url | ||
* @returns false | ||
*/ | ||
export const redirect = (url) => { | ||
window.location.href = makeAbsolute(url); | ||
return false; | ||
}; | ||
|
||
/** | ||
* Sends a Panel request to the backend with | ||
* all the right headers and other options. | ||
* | ||
* It also makes sure to redirect requests, | ||
* which cannot be handled via fetch and | ||
* throws more useful errors. | ||
* | ||
* @param {String} url | ||
* @param {Object} options | ||
* @returns {Object} {request, response} | ||
*/ | ||
export const request = async (url, options = {}) => { | ||
// merge with a few defaults | ||
options = { | ||
cache: "no-store", | ||
credentials: "same-origin", | ||
mode: "same-origin", | ||
...options | ||
}; | ||
|
||
// those need a bit more work | ||
options.body = body(options.body); | ||
options.headers = headers(options.headers, options); | ||
options.url = buildUrl(url, options.query); | ||
|
||
// The request object is a nice way to access all the | ||
// important parts later in errors for example | ||
const request = new Request(options.url, options); | ||
|
||
// Don't even try to request a | ||
// cross-origin url. Redirect instead. | ||
if (isSameOrigin(request.url) === false) { | ||
return redirect(request.url); | ||
} | ||
|
||
const response = await fetch(request); | ||
|
||
// redirect to non-fiber requests | ||
if ( | ||
response.headers.get("Content-Type").includes("application/json") === false | ||
) { | ||
return redirect(response.url); | ||
} | ||
|
||
// try to parse the response. | ||
try { | ||
response.text = await response.text(); | ||
response.json = JSON.parse(response.text); | ||
} catch (error) { | ||
throw new JsonRequestError("Invalid JSON response", { | ||
cause: error, | ||
request, | ||
response | ||
}); | ||
} | ||
|
||
if (response.ok === false) { | ||
throw new RequestError(`The request to ${response.url} failed`, { | ||
request, | ||
response | ||
}); | ||
} | ||
|
||
return { | ||
request, | ||
response | ||
}; | ||
}; | ||
|
||
export default request; |