|
- import {
- GetServerSideProps,
- NextApiHandler,
- NextApiRequest as DefaultNextApiRequest,
- NextApiResponse as DefaultNextApiResponse,
- PageConfig,
- } from 'next';
- import { deserialize, serialize } from 'seroval';
- import {
- NextApiResponse,
- NextApiRequest,
- ENCTYPE_APPLICATION_JSON,
- ENCTYPE_APPLICATION_OCTET_STREAM,
- } from './common';
- import { getBody } from './utils/request';
- import {
- CookieManager,
- BODY_COOKIE_KEY,
- STATUS_CODE_COOKIE_KEY,
- STATUS_MESSAGE_COOKIE_KEY,
- CONTENT_TYPE_COOKIE_KEY,
- } from './utils/cookies';
- import { deserializeBody, EncTypeDeserializerMap } from './utils/serialization';
-
- export namespace destination {
- export const getServerSideProps = (
- gspFn?: GetServerSideProps,
- ): GetServerSideProps => async (ctx) => {
- const req: NextApiRequest = {
- query: ctx.query,
- body: null,
- };
- const { method = 'GET' } = ctx.req;
-
- if (!['GET', 'HEAD'].includes(method.toUpperCase())) {
- const body = await getBody(ctx.req);
- req.body = body.toString('utf-8');
- }
-
- const cookieManager = new CookieManager(ctx);
- // TODO how to properly remove cookies without leftovers?
- if (cookieManager.hasCookie(STATUS_CODE_COOKIE_KEY)) {
- ctx.res.statusCode = Number(cookieManager.getCookie(STATUS_CODE_COOKIE_KEY) || '200');
- cookieManager.unsetCookie(STATUS_CODE_COOKIE_KEY);
- }
-
- if (cookieManager.hasCookie(STATUS_MESSAGE_COOKIE_KEY)) {
- ctx.res.statusMessage = cookieManager.getCookie(STATUS_MESSAGE_COOKIE_KEY) || '';
- cookieManager.unsetCookie(STATUS_MESSAGE_COOKIE_KEY);
- }
-
- const res: NextApiResponse = {};
- if (cookieManager.hasCookie(BODY_COOKIE_KEY)) {
- const resBody = cookieManager.getCookie(BODY_COOKIE_KEY);
- const contentType = cookieManager.getCookie(CONTENT_TYPE_COOKIE_KEY);
- if (contentType === ENCTYPE_APPLICATION_JSON) {
- res.body = deserialize(resBody);
- } else if (contentType === ENCTYPE_APPLICATION_OCTET_STREAM) {
- res.body = deserialize(resBody);
- } else {
- const c = console;
- c.warn('Could not parse response body, returning nothing');
- res.body = null;
- }
- cookieManager.unsetCookie(BODY_COOKIE_KEY);
- cookieManager.unsetCookie(CONTENT_TYPE_COOKIE_KEY);
- }
-
- let gspResult;
- if (gspFn) {
- gspResult = await gspFn(ctx);
- } else {
- gspResult = {
- props: {},
- };
- }
- if ('props' in gspResult) {
- return {
- ...gspResult,
- props: {
- ...gspResult.props,
- req,
- res,
- },
- };
- }
-
- // redirect/not found will be treated as default behavior
- return gspResult;
- };
- }
-
- export namespace action {
- export const getApiConfig = (customConfig = {} as PageConfig) => ({
- api: {
- ...(customConfig.api ?? {}),
- bodyParser: false,
- },
- });
-
- export interface ActionWrapperOptions {
- fn: NextApiHandler,
- deserializers?: EncTypeDeserializerMap,
- }
-
- export const wrapApiHandler = (
- options: ActionWrapperOptions,
- ): NextApiHandler => async (req, res) => {
- const body = await deserializeBody({
- req,
- deserializers: options.deserializers,
- });
- const reqMut = req as unknown as Record<string, unknown>;
- reqMut.body = body;
- return options.fn(reqMut as unknown as DefaultNextApiRequest, res);
- };
-
- export const getServerSideProps = (
- options: ActionWrapperOptions,
- ): GetServerSideProps => async (ctx) => {
- const { referer } = ctx.req.headers;
-
- const mockReq = {
- ...ctx.req,
- body: await deserializeBody({
- req: ctx.req,
- deserializers: options.deserializers,
- }),
- } as DefaultNextApiRequest;
-
- let data: unknown = null;
- let contentType: string | undefined;
- const mockRes = {
- // todo handle other nextapiresponse methods (e.g. setting headers, writeHead, etc.)
- statusMessage: '',
- statusCode: 200,
- status(code: number) {
- // should we mask error status code to Bad Gateway?
- this.statusCode = code;
- return mockRes;
- },
- send: (raw?: unknown) => {
- // xtodo: how to transfer binary response in a more compact way?
- // > we let seroval handle this for now
- if (typeof raw === 'undefined' || raw === null) {
- return;
- }
-
- if (raw instanceof Buffer) {
- contentType = ENCTYPE_APPLICATION_OCTET_STREAM;
- }
-
- data = serialize(raw);
- },
- json: (raw: unknown) => {
- contentType = ENCTYPE_APPLICATION_JSON;
- data = serialize(raw);
- },
- } as DefaultNextApiResponse;
-
- await options.fn(mockReq, mockRes);
-
- const cookieManager = new CookieManager(ctx);
- cookieManager.setCookie(STATUS_CODE_COOKIE_KEY, mockRes.statusCode.toString());
- cookieManager.setCookie(STATUS_MESSAGE_COOKIE_KEY, mockRes.statusMessage);
- if (data) {
- cookieManager.setCookie(BODY_COOKIE_KEY, data as string);
- if (contentType) {
- cookieManager.setCookie(CONTENT_TYPE_COOKIE_KEY, contentType);
- }
- }
-
- return {
- redirect: {
- destination: referer,
- statusCode: 307,
- },
- props: {
- query: ctx.query,
- body: data,
- },
- };
- };
- }
|