Skip to content

Commit

Permalink
Merge pull request #118 from getkirby/fiber/steps/request
Browse files Browse the repository at this point in the history
Request module
  • Loading branch information
bastianallgeier authored Apr 5, 2023
2 parents 8f6ce94 + 63b78b6 commit abf94e4
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 0 deletions.
26 changes: 26 additions & 0 deletions panel/src/panel/request.body.test.js
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" }));
});
});
23 changes: 23 additions & 0 deletions panel/src/panel/request.globals.test.js
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);
});
});
59 changes: 59 additions & 0 deletions panel/src/panel/request.headers.test.js
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);
});
});
142 changes: 142 additions & 0 deletions panel/src/panel/request.js
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;

0 comments on commit abf94e4

Please sign in to comment.