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);
...
});