1. 'use strict';
    
  2. 
    
  3. // This is a server to host data-local resources like databases and RSC
    
  4. 
    
  5. const path = require('path');
    
  6. const url = require('url');
    
  7. 
    
  8. if (typeof fetch === 'undefined') {
    
  9.   // Patch fetch for earlier Node versions.
    
  10.   global.fetch = require('undici').fetch;
    
  11. }
    
  12. 
    
  13. const express = require('express');
    
  14. const bodyParser = require('body-parser');
    
  15. const busboy = require('busboy');
    
  16. const app = express();
    
  17. const compress = require('compression');
    
  18. const {Readable} = require('node:stream');
    
  19. 
    
  20. app.use(compress());
    
  21. 
    
  22. // Application
    
  23. 
    
  24. const {readFile} = require('fs').promises;
    
  25. 
    
  26. const React = require('react');
    
  27. 
    
  28. const moduleBasePath = new URL('../src', url.pathToFileURL(__filename)).href;
    
  29. 
    
  30. async function renderApp(res, returnValue) {
    
  31.   const {renderToPipeableStream} = await import('react-server-dom-esm/server');
    
  32.   const m = await import('../src/App.js');
    
  33. 
    
  34.   const App = m.default;
    
  35.   const root = React.createElement(App);
    
  36.   // For client-invoked server actions we refresh the tree and return a return value.
    
  37.   const payload = returnValue ? {returnValue, root} : root;
    
  38.   const {pipe} = renderToPipeableStream(payload, moduleBasePath);
    
  39.   pipe(res);
    
  40. }
    
  41. 
    
  42. app.get('/', async function (req, res) {
    
  43.   await renderApp(res, null);
    
  44. });
    
  45. 
    
  46. app.post('/', bodyParser.text(), async function (req, res) {
    
  47.   const {
    
  48.     renderToPipeableStream,
    
  49.     decodeReply,
    
  50.     decodeReplyFromBusboy,
    
  51.     decodeAction,
    
  52.   } = await import('react-server-dom-esm/server');
    
  53.   const serverReference = req.get('rsc-action');
    
  54.   if (serverReference) {
    
  55.     // This is the client-side case
    
  56.     const [filepath, name] = serverReference.split('#');
    
  57.     const action = (await import(filepath))[name];
    
  58.     // Validate that this is actually a function we intended to expose and
    
  59.     // not the client trying to invoke arbitrary functions. In a real app,
    
  60.     // you'd have a manifest verifying this before even importing it.
    
  61.     if (action.$$typeof !== Symbol.for('react.server.reference')) {
    
  62.       throw new Error('Invalid action');
    
  63.     }
    
  64. 
    
  65.     let args;
    
  66.     if (req.is('multipart/form-data')) {
    
  67.       // Use busboy to streamingly parse the reply from form-data.
    
  68.       const bb = busboy({headers: req.headers});
    
  69.       const reply = decodeReplyFromBusboy(bb, moduleBasePath);
    
  70.       req.pipe(bb);
    
  71.       args = await reply;
    
  72.     } else {
    
  73.       args = await decodeReply(req.body, moduleBasePath);
    
  74.     }
    
  75.     const result = action.apply(null, args);
    
  76.     try {
    
  77.       // Wait for any mutations
    
  78.       await result;
    
  79.     } catch (x) {
    
  80.       // We handle the error on the client
    
  81.     }
    
  82.     // Refresh the client and return the value
    
  83.     renderApp(res, result);
    
  84.   } else {
    
  85.     // This is the progressive enhancement case
    
  86.     const UndiciRequest = require('undici').Request;
    
  87.     const fakeRequest = new UndiciRequest('http://localhost', {
    
  88.       method: 'POST',
    
  89.       headers: {'Content-Type': req.headers['content-type']},
    
  90.       body: Readable.toWeb(req),
    
  91.       duplex: 'half',
    
  92.     });
    
  93.     const formData = await fakeRequest.formData();
    
  94.     const action = await decodeAction(formData, moduleBasePath);
    
  95.     try {
    
  96.       // Wait for any mutations
    
  97.       await action();
    
  98.     } catch (x) {
    
  99.       const {setServerState} = await import('../src/ServerState.js');
    
  100.       setServerState('Error: ' + x.message);
    
  101.     }
    
  102.     renderApp(res, null);
    
  103.   }
    
  104. });
    
  105. 
    
  106. app.get('/todos', function (req, res) {
    
  107.   res.json([
    
  108.     {
    
  109.       id: 1,
    
  110.       text: 'Shave yaks',
    
  111.     },
    
  112.     {
    
  113.       id: 2,
    
  114.       text: 'Eat kale',
    
  115.     },
    
  116.   ]);
    
  117. });
    
  118. 
    
  119. app.listen(3001, () => {
    
  120.   console.log('Regional Flight Server listening on port 3001...');
    
  121. });
    
  122. 
    
  123. app.on('error', function (error) {
    
  124.   if (error.syscall !== 'listen') {
    
  125.     throw error;
    
  126.   }
    
  127. 
    
  128.   switch (error.code) {
    
  129.     case 'EACCES':
    
  130.       console.error('port 3001 requires elevated privileges');
    
  131.       process.exit(1);
    
  132.       break;
    
  133.     case 'EADDRINUSE':
    
  134.       console.error('Port 3001 is already in use');
    
  135.       process.exit(1);
    
  136.       break;
    
  137.     default:
    
  138.       throw error;
    
  139.   }
    
  140. });