Node.js and Undici collaborator. Member of OpenJS Foundation. Contributing to making the JavaScript ecosystem faster and better for everyone!
\n```\n\nNow, for the heart of the solution, create an [`onSend` hook](https://fastify.dev/docs/latest/Reference/Hooks/#onsend). By default, the hook should pass through the `payload`.\n\n```js\nserver.addHook(\"onSend\", function onSendHook(request, reply, payload, done) {\n return done(null, payload);\n});\n```\n\nNext, add a filter for HTML replies. A reliable way to achieve this is by inspecting the [`content-type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) header.\n\n```js\nserver.addHook(\"onSend\", function onSendHook(request, reply, payload, done) {\n if (reply.getHeader(\"content-type\").startsWith(\"text/html\")) {\n }\n return done(null, payload);\n});\n```\n\nBefore modifying the payload itself, set the appropriate [content-length](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length) header. It is imperative that this value reflects the length of `payload`, otherwise most clients will not read the entire payload.\n\n```js\nserver.addHook(\"onSend\", function onSendHook(request, reply, payload, done) {\n if (reply.getHeader(\"content-type\").startsWith(\"text/html\")) {\n const contentLength = reply.getHeader(\"content-length\");\n reply.header(\"content-length\", contentLength + INJECT_CODE.length);\n }\n return done(null, payload);\n});\n```\n\nThen, pipe the `payload` through a custom Transform stream. By default, the `transform()` method should pass through the `chunk`.\n\n```js\nserver.addHook(\"onSend\", function onSendHook(request, reply, payload, done) {\n if (reply.getHeader(\"content-type\").startsWith(\"text/html\")) {\n const contentLength = reply.getHeader(\"content-length\");\n reply.header(\"content-length\", contentLength + INJECT_CODE.length);\n\n const transformedPayload = payload.pipe(\n new stream.Transform({\n transform(chunk, encoding, callback) {\n return callback(null, chunk);\n },\n }),\n );\n\n return done(null, transformedPayload);\n }\n return done(null, payload);\n});\n```\n\n> [!TIP]\n>\n> **What is a `Transform` stream?**\n>\n> If you're more familiar with Web Streams, a Node.js `Transform` is similar to a Web [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream). The implementations are quite different, but they serve a similar purpose. Essentially, it is a streaming data structure that can be both written to and read from, while simultaneously modifying the data passing through it.\n\nFinally, add an `encoding` check and then the buffer injection logic.\n\n```js\nserver.addHook(\"onSend\", function onSendHook(request, reply, payload, done) {\n if (reply.getHeader(\"content-type\").startsWith(\"text/html\")) {\n const contentLength = reply.getHeader(\"content-length\");\n reply.header(\"content-length\", contentLength + INJECT_CODE.length);\n\n const transformedPayload = payload.pipe(\n new stream.Transform({\n transform(chunk, encoding, callback) {\n if (encoding === \"buffer\") {\n const i = chunk.lastIndexOf(ENCODED_CLOSING_HTML_TAG);\n if (i > -1) {\n const injected = Buffer.alloc(chunk.length + INJECT_CODE.length);\n injected\n .fill(chunk.slice(0, i))\n .fill(INJECT_CODE, i)\n .fill(chunk.slice(i), i + INJECT_CODE.length);\n return callback(null, injected);\n }\n } else {\n console.warn(`Unexpected encoding type ${encoding}`);\n }\n\n return callback(null, chunk);\n },\n }),\n );\n\n return done(null, transformedPayload);\n }\n return done(null, payload);\n});\n```\n\nThe injection logic itself is nothing too special. It finds the last index of the closing HTML tag (`'