Skip to content

Commit

Permalink
Add middlewares for FetchAPI, add Edge register handler
Browse files Browse the repository at this point in the history
  • Loading branch information
witoszekdev committed Mar 8, 2024
1 parent b2947b2 commit eca6911
Show file tree
Hide file tree
Showing 13 changed files with 496 additions and 0 deletions.
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@
"import": "./settings-manager/index.mjs",
"require": "./settings-manager/index.js"
},
"./fetch-middleware": {
"types": "./fetch-middleware/index.d.ts",
"import": "./fetch-middleware/index.mjs",
"require": "./fetch-middleware/index.js"
},
"./middleware": {
"types": "./middleware/index.d.ts",
"import": "./middleware/index.mjs",
Expand Down
6 changes: 6 additions & 0 deletions src/fetch-middleware/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from "./to-next-edge-handler";
export * from "./with-auth-token-required";
export * from "./with-method";
export * from "./with-registered-saleor-domain-header";
export * from "./with-saleor-app";
export * from "./with-saleor-domain-present";
4 changes: 4 additions & 0 deletions src/fetch-middleware/middleware-debug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createDebug } from "../debug";

export const createFetchMiddlewareDebug = (middleware: string) =>
createDebug(`FetchMiddleware:${middleware}`);
20 changes: 20 additions & 0 deletions src/fetch-middleware/to-next-edge-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { FetchHandler, FetchPipeline, ReveredFetchPipeline } from "./types";

const isPipeline = (maybePipeline: unknown): maybePipeline is FetchPipeline =>
Array.isArray(maybePipeline);

const compose =
<T extends Function>(...functions: T[]) =>
(args: any) =>
functions.reduce((arg, fn) => fn(arg), args);

const preparePipeline = (pipeline: FetchPipeline): FetchHandler => {
const [action, ...middleware] = pipeline.reverse() as ReveredFetchPipeline;
return compose(...middleware)(action);
};

export const toNextEdgeHandler = (flow: FetchHandler | FetchPipeline): FetchHandler => {
const handler = isPipeline(flow) ? preparePipeline(flow) : flow;

return async (request: Request) => handler(request);
};
5 changes: 5 additions & 0 deletions src/fetch-middleware/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type SaleorRequest = Request & { context?: Record<string, any> };
export type FetchHandler = (req: SaleorRequest) => Response | Promise<Response>;
export type FetchMiddleware = (handler: FetchHandler) => FetchHandler;
export type FetchPipeline = [...FetchMiddleware[], FetchHandler];
export type ReveredFetchPipeline = [FetchHandler, ...FetchMiddleware[]];
32 changes: 32 additions & 0 deletions src/fetch-middleware/with-auth-token-required.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { createFetchMiddlewareDebug } from "./middleware-debug";
import { FetchMiddleware } from "./types";

const debug = createFetchMiddlewareDebug("withAuthTokenRequired");

export const withAuthTokenRequired: FetchMiddleware = (handler) => async (request) => {
debug("Middleware called");

try {
// If we read `request.json()` without cloning it will throw an error
// next time we run request.json()
const clone = request.clone();
const json = await clone.json();
const authToken = json.auth_token;

if (!authToken) {
debug("Found missing authToken param");

return Response.json(
{
success: false,
message: "Missing auth token.",
},
{ status: 400 }
);
}
} catch {
return Response.json({ success: false, message: "Invalid request body" }, { status: 400 });
}

return handler(request);
};
25 changes: 25 additions & 0 deletions src/fetch-middleware/with-method.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { FetchMiddleware } from "./types";

export const HTTPMethod = {
GET: "GET",
POST: "POST",
PUT: "PUT",
PATH: "PATCH",
HEAD: "HEAD",
OPTIONS: "OPTIONS",
DELETE: "DELETE",
} as const;
export type HTTPMethod = typeof HTTPMethod[keyof typeof HTTPMethod];

export const withMethod =
(...methods: HTTPMethod[]): FetchMiddleware =>
(handler) =>
async (request) => {
if (!methods.includes(request.method as HTTPMethod)) {
return new Response("Method not allowed", { status: 405 });
}

const response = await handler(request);

return response;
};
51 changes: 51 additions & 0 deletions src/fetch-middleware/with-registered-saleor-domain-header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { getSaleorHeadersFetchAPI } from "../headers";
import { createFetchMiddlewareDebug } from "./middleware-debug";
import { FetchMiddleware } from "./types";
import { getSaleorAppFromRequest } from "./with-saleor-app";

const debug = createFetchMiddlewareDebug("withRegisteredSaleorDomainHeader");

export const withRegisteredSaleorDomainHeader: FetchMiddleware = (handler) => async (request) => {
const { saleorApiUrl } = getSaleorHeadersFetchAPI(request.headers);

if (!saleorApiUrl) {
return Response.json(
{ success: false, message: "saleorApiUrl header missing" },
{ status: 400 }
);
}

debug("Middleware called with saleorApiUrl: \"%s\"", saleorApiUrl);

const saleorApp = getSaleorAppFromRequest(request);

if (!saleorApp) {
console.error(
"SaleorApp not found in request context. Ensure your API handler is wrapped with withSaleorApp middleware"
);

return Response.json(
{
success: false,
message: "SaleorApp is misconfigured",
},
{ status: 500 }
);
}

const authData = await saleorApp?.apl.get(saleorApiUrl);

if (!authData) {
debug("Auth was not found in APL, will respond with Forbidden status");

return Response.json(
{
success: false,
message: `Saleor: ${saleorApiUrl} not registered.`,
},
{ status: 403 }
);
}

return handler(request);
};
20 changes: 20 additions & 0 deletions src/fetch-middleware/with-saleor-app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { SaleorApp } from "../saleor-app";
import { createFetchMiddlewareDebug } from "./middleware-debug";
import { FetchMiddleware, SaleorRequest } from "./types";

const debug = createFetchMiddlewareDebug("withSaleorApp");

export const withSaleorApp =
(saleorApp: SaleorApp): FetchMiddleware =>
(handler) =>
async (request: SaleorRequest) => {
debug("Middleware called");

request.context ??= {};
request.context.saleorApp = saleorApp;

return handler(request);
};

export const getSaleorAppFromRequest = (request: SaleorRequest): SaleorApp | undefined =>
request.context?.saleorApp;
25 changes: 25 additions & 0 deletions src/fetch-middleware/with-saleor-domain-present.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { getSaleorHeadersFetchAPI } from "../headers";
import { createFetchMiddlewareDebug } from "./middleware-debug";
import { FetchMiddleware } from "./types";

const debug = createFetchMiddlewareDebug("withSaleorDomainPresent");

export const withSaleorDomainPresent: FetchMiddleware = (handler) => async (request) => {
const { domain } = getSaleorHeadersFetchAPI(request.headers);

debug("Middleware called with domain in header: %s", domain);

if (!domain) {
debug("Domain not found in header, will respond with Bad Request");

return Response.json(
{
success: false,
message: "Missing Saleor domain header.",
},
{ status: 400 }
);
}

return handler(request);
};
Loading

0 comments on commit eca6911

Please sign in to comment.