Use forms with or without client-side JavaScript--no code duplication required!
Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

185 linhas
4.7 KiB

  1. import {
  2. GetServerSideProps,
  3. NextApiHandler,
  4. NextApiRequest as DefaultNextApiRequest,
  5. NextApiResponse as DefaultNextApiResponse,
  6. PageConfig,
  7. } from 'next';
  8. import { deserialize, serialize } from 'seroval';
  9. import {
  10. NextApiResponse,
  11. NextApiRequest,
  12. ENCTYPE_APPLICATION_JSON,
  13. ENCTYPE_APPLICATION_OCTET_STREAM,
  14. } from './common';
  15. import { getBody } from './utils/body';
  16. import {
  17. CookieManager,
  18. BODY_COOKIE_KEY,
  19. STATUS_CODE_COOKIE_KEY,
  20. STATUS_MESSAGE_COOKIE_KEY,
  21. CONTENT_TYPE_COOKIE_KEY,
  22. } from './utils/cookies';
  23. import { deserializeBody, EncTypeDeserializerMap } from './utils/serialization';
  24. export namespace destination {
  25. export const getServerSideProps = (
  26. gspFn?: GetServerSideProps,
  27. ): GetServerSideProps => async (ctx) => {
  28. const req: NextApiRequest = {
  29. query: ctx.query,
  30. body: null,
  31. };
  32. const { method = 'GET' } = ctx.req;
  33. if (!['GET', 'HEAD'].includes(method.toUpperCase())) {
  34. const body = await getBody(ctx.req);
  35. req.body = body.toString('utf-8');
  36. }
  37. const cookieManager = new CookieManager(ctx);
  38. // TODO how to properly remove cookies without leftovers?
  39. if (cookieManager.hasCookie(STATUS_CODE_COOKIE_KEY)) {
  40. ctx.res.statusCode = Number(cookieManager.getCookie(STATUS_CODE_COOKIE_KEY) || '200');
  41. cookieManager.unsetCookie(STATUS_CODE_COOKIE_KEY);
  42. }
  43. if (cookieManager.hasCookie(STATUS_MESSAGE_COOKIE_KEY)) {
  44. ctx.res.statusMessage = cookieManager.getCookie(STATUS_MESSAGE_COOKIE_KEY) || '';
  45. cookieManager.unsetCookie(STATUS_MESSAGE_COOKIE_KEY);
  46. }
  47. const res: NextApiResponse = {};
  48. if (cookieManager.hasCookie(BODY_COOKIE_KEY)) {
  49. const resBody = cookieManager.getCookie(BODY_COOKIE_KEY);
  50. const contentType = cookieManager.getCookie(CONTENT_TYPE_COOKIE_KEY);
  51. if (contentType === ENCTYPE_APPLICATION_JSON) {
  52. res.body = deserialize(resBody);
  53. } else if (contentType === ENCTYPE_APPLICATION_OCTET_STREAM) {
  54. res.body = deserialize(resBody);
  55. } else {
  56. const c = console;
  57. c.warn('Could not parse response body, returning nothing');
  58. res.body = null;
  59. }
  60. cookieManager.unsetCookie(BODY_COOKIE_KEY);
  61. cookieManager.unsetCookie(CONTENT_TYPE_COOKIE_KEY);
  62. }
  63. let gspResult;
  64. if (gspFn) {
  65. gspResult = await gspFn(ctx);
  66. } else {
  67. gspResult = {
  68. props: {},
  69. };
  70. }
  71. if ('props' in gspResult) {
  72. return {
  73. ...gspResult,
  74. props: {
  75. ...gspResult.props,
  76. req,
  77. res,
  78. },
  79. };
  80. }
  81. // redirect/not found will be treated as default behavior
  82. return gspResult;
  83. };
  84. }
  85. export namespace action {
  86. export const getApiConfig = (customConfig = {} as PageConfig) => ({
  87. api: {
  88. ...(customConfig.api ?? {}),
  89. bodyParser: false,
  90. },
  91. });
  92. export interface ActionWrapperOptions {
  93. fn: NextApiHandler,
  94. deserializers?: EncTypeDeserializerMap,
  95. }
  96. export const wrapApiHandler = (
  97. options: ActionWrapperOptions,
  98. ): NextApiHandler => async (req, res) => {
  99. const body = await deserializeBody({
  100. req,
  101. deserializers: options.deserializers,
  102. });
  103. const reqMut = req as unknown as Record<string, unknown>;
  104. reqMut.body = body;
  105. return options.fn(reqMut as unknown as DefaultNextApiRequest, res);
  106. };
  107. export const getServerSideProps = (
  108. options: ActionWrapperOptions,
  109. ): GetServerSideProps => async (ctx) => {
  110. const { referer } = ctx.req.headers;
  111. const mockReq = {
  112. ...ctx.req,
  113. body: await deserializeBody({
  114. req: ctx.req,
  115. deserializers: options.deserializers,
  116. }),
  117. } as DefaultNextApiRequest;
  118. let data: unknown = null;
  119. let contentType: string | undefined;
  120. const mockRes = {
  121. // todo handle other nextapiresponse methods (e.g. setting headers, writeHead, etc.)
  122. statusMessage: '',
  123. statusCode: 200,
  124. status(code: number) {
  125. // should we mask error status code to Bad Gateway?
  126. this.statusCode = code;
  127. return mockRes;
  128. },
  129. send: (raw?: unknown) => {
  130. // xtodo: how to transfer binary response in a more compact way?
  131. // > we let seroval handle this for now
  132. if (typeof raw === 'undefined' || raw === null) {
  133. return;
  134. }
  135. if (raw instanceof Buffer) {
  136. contentType = ENCTYPE_APPLICATION_OCTET_STREAM;
  137. }
  138. data = serialize(raw);
  139. },
  140. json: (raw: unknown) => {
  141. contentType = ENCTYPE_APPLICATION_JSON;
  142. data = serialize(raw);
  143. },
  144. } as DefaultNextApiResponse;
  145. await options.fn(mockReq, mockRes);
  146. const cookieManager = new CookieManager(ctx);
  147. cookieManager.setCookie(STATUS_CODE_COOKIE_KEY, mockRes.statusCode.toString());
  148. cookieManager.setCookie(STATUS_MESSAGE_COOKIE_KEY, mockRes.statusMessage);
  149. if (data) {
  150. cookieManager.setCookie(BODY_COOKIE_KEY, data as string);
  151. if (contentType) {
  152. cookieManager.setCookie(CONTENT_TYPE_COOKIE_KEY, contentType);
  153. }
  154. }
  155. return {
  156. redirect: {
  157. destination: referer,
  158. statusCode: 307,
  159. },
  160. props: {
  161. query: ctx.query,
  162. body: data,
  163. },
  164. };
  165. };
  166. }