Home > OS >  Parse sendBeacon data in Express
Parse sendBeacon data in Express

Time:07-20

I'm trying to start logging my own WebVitals. The simple use-case in their example docs looks like this:

function sendToAnalytics(metric) {
  // Replace with whatever serialization method you prefer.
  // Note: JSON.stringify will likely include more data than you need.
  const body = JSON.stringify(metric);

  // Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
  (navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) ||
      fetch('/analytics', {body, method: 'POST', keepalive: true});
}

That all seems simple enough. Here's my actual implementation:

function sendToLog(metric) {
    // Replace with whatever serialization method you prefer.
    // Note: JSON.stringify will likely include more data than you need.
    const body = JSON.stringify(metric);
    console.log(`Sending body ${body}`);
  
    // Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
    // (navigator.sendBeacon && navigator.sendBeacon('https://localho  st:9292/api/log', body)) ||
        fetch('https://localhost:9292/api/log', {body: body,   method: 'POST', headers: { 'Content-Type': 'application/json' }, keepalive: true});
}

I had to modify the fetch to include the "body" property name and add headers to get it to work, but that is now working. However, if I uncomment the navigator.sendBeacon line, I just get {} for the body.

I'm using NodeJS and Express on the backend. My starting point was this:

const app = express();
app.use(
    express.json({
        // We need the raw body to verify webhook signatures.
        // Let's compute it only when hitting the Stripe webhook endpoint.
        verify: function (req, res, buf) {
            if (req.originalUrl.startsWith(API_PREFIX   '/webhook')) {
            req.rawBody = buf.toString();
            }
        },
    })
);
app.use(cors());

// Expose an endpoint for client logging.
app.post(API_PREFIX   '/log', async (req, res) => {
  const message = req.body;

  console.log('Got message');
  console.dir(message, { depth: null });
  logger.info(message);

  res.sendStatus(200);
});

There's this similar question, where the accepted answer suggests that adding body-parser and app.use(bodyParser.raw()) should do the trick, and then in the comments there's discussion of using bodyParser.json()) instead.

I've tried both of those:

import bodyParser from 'body-parser';
...
//app.use(bodyParser.raw());
//app.use(bodyParser.json());
app.use(
    express.json({
        // We need the raw body to verify webhook signatures.
        // Let's compute it only when hitting the Stripe webhook endpoint.
        verify: function (req, res, buf) {
            if (req.originalUrl.startsWith(API_PREFIX   '/webhook')) {
            req.rawBody = buf.toString();
            }
        },
    })
);
app.use(cors());

(i.e., uncommenting either of the two lines at the beginning), and in all 3 cases I still get an empty body when sendBeacon is used.

The Express documentation says,

[express.json] is a built-in middleware function in Express. It parses incoming requests with JSON payloads and is based on body-parser....A new body object containing the parsed data is populated on the request object after the middleware (i.e. req.body), or an empty object ({}) if there was no body to parse, the Content-Type was not matched, or an error occurred.

So, a) I guess I shouldn't need the body-parser, since express.json is just doing that anyhow; and b) I'm hitting one of those three conditions (there's no body to parse, the Content-Type was not matched, or an error occurred). Assuming that's the case, how do I diagnose which one it is, and then fix it (in such a way that the fetch fallback continues to work). Or, if there's some other problem, back to the original question, how do I get this to work? :)

CodePudding user response:

navigator.sendBeacon sends Content-Type: text/plain whereas express.json() only parses the body if Content-Type: application/json.

Use express.text() in combination with JSON.parse(req.body) instead, or additionally:

app.post(API_PREFIX   '/log',
  express.json(), express.text(), function(req, res) {
  if (typeof req.body === "string") req.body = JSON.parse(req.body);
  ...
});
  • Related