@alignable/bifrost-fastify 1.0.22 → 1.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -2
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -19,14 +19,13 @@ declare module "fastify" {
|
|
|
19
19
|
bifrostProxyLayout?: Vike.ProxyLayoutInfo | null;
|
|
20
20
|
vikePageContext?: Partial<PageContextServer> | null;
|
|
21
21
|
getLayout: GetLayout | null;
|
|
22
|
-
customPageContextInit: Partial<Omit<PageContextServer, "headers">> | null;
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
interface ViteProxyPluginOptions extends Omit<FastifyHttpProxyOptions, "upstream" | "preHandler" | "replyOptions"> {
|
|
26
25
|
upstream: URL;
|
|
27
26
|
host: URL;
|
|
28
27
|
onError?: (error: any, pageContext: RenderedPageContext) => void;
|
|
29
|
-
buildPageContextInit?: (req: FastifyRequest) => Promise<Partial<Omit<PageContextServer, "headers">>>;
|
|
28
|
+
buildPageContextInit?: (req: FastifyRequest<RequestGenericInterface, RawServerBase>) => Promise<Partial<Omit<PageContextServer, "headers">>>;
|
|
30
29
|
beforeWrappedRender?: (req: FastifyRequest<RequestGenericInterface, RawServerBase>, reply: FastifyReply<RouteGenericInterface, RawServerBase, IncomingMessage | Http2ServerRequest>) => void;
|
|
31
30
|
}
|
|
32
31
|
/**
|
package/dist/index.js
CHANGED
|
@@ -57,18 +57,17 @@ var viteProxyPlugin = async (fastify, opts) => {
|
|
|
57
57
|
fastify.decorateRequest("bifrostSentProxyHeaders", false);
|
|
58
58
|
fastify.decorateRequest("vikePageContext", null);
|
|
59
59
|
fastify.decorateRequest("getLayout", null);
|
|
60
|
-
fastify.decorateRequest("customPageContextInit", null);
|
|
61
60
|
await fastify.register(proxy, {
|
|
62
61
|
...opts,
|
|
63
62
|
upstream: upstream.href,
|
|
64
63
|
websocket: true,
|
|
65
64
|
async preHandler(req, reply) {
|
|
66
65
|
if ((req.method === "GET" || req.method === "HEAD") && req.accepts().type(["html"]) === "html") {
|
|
67
|
-
|
|
66
|
+
const customPageContextInit = buildPageContextInit ? await buildPageContextInit(req) : {};
|
|
68
67
|
const pageContextInit = {
|
|
69
68
|
urlOriginal: req.url,
|
|
70
69
|
headersOriginal: req.headers,
|
|
71
|
-
...
|
|
70
|
+
...customPageContextInit
|
|
72
71
|
};
|
|
73
72
|
const pageContext = await renderPage(pageContextInit);
|
|
74
73
|
let proxyMode = pageContext.config?.proxyMode;
|
|
@@ -171,6 +170,7 @@ var viteProxyPlugin = async (fastify, opts) => {
|
|
|
171
170
|
`Error in beforeWrappedRender: ${e.message}`
|
|
172
171
|
);
|
|
173
172
|
}
|
|
173
|
+
const customPageContextInit = buildPageContextInit ? await buildPageContextInit(req) : {};
|
|
174
174
|
const pageContextInit = {
|
|
175
175
|
urlOriginal: reply.request.url,
|
|
176
176
|
headersOriginal: req.headers,
|
|
@@ -182,7 +182,7 @@ var viteProxyPlugin = async (fastify, opts) => {
|
|
|
182
182
|
headInnerHtml,
|
|
183
183
|
proxyLayoutInfo
|
|
184
184
|
},
|
|
185
|
-
...
|
|
185
|
+
...customPageContextInit
|
|
186
186
|
};
|
|
187
187
|
const pageContext = await renderPage(pageContextInit);
|
|
188
188
|
req.vikePageContext = pageContext;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../index.ts","../lib/extractDomElements.ts"],"sourcesContent":["// Note that this file isn't processed by Vite, see https://github.com/brillout/vike/issues/562\nimport {\n FastifyReply,\n RawServerBase,\n FastifyPluginAsync,\n RouteGenericInterface,\n} from \"fastify\";\nimport { FastifyRequest, RequestGenericInterface } from \"fastify/types/request\";\nimport proxy, { type FastifyHttpProxyOptions } from \"@fastify/http-proxy\";\nimport accepts from \"@fastify/accepts\";\nimport forwarded from \"@fastify/forwarded\";\nimport type { GetLayout, WrappedServerOnly } from \"@alignable/bifrost/config\";\nimport { renderPage } from \"vike/server\";\nimport { PageContextServer } from \"vike/types\";\nimport { extractDomElements } from \"./lib/extractDomElements\";\nimport { Http2ServerRequest } from \"http2\";\nimport { text } from \"node:stream/consumers\";\nimport { parse as parseContentType } from \"fast-content-type-parse\";\nimport { IncomingMessage } from \"http\";\n\ntype RenderedPageContext = Awaited<\n ReturnType<\n typeof renderPage<\n {\n isClientSideNavigation?: boolean;\n },\n { urlOriginal: string }\n >\n >\n>;\n\ndeclare module \"fastify\" {\n interface FastifyRequest {\n /// Actual ProxyMode after processing backend server results, which can tell us to fallback to passthru or redirect\n bifrostProxyMode?: Vike.Config[\"proxyMode\"];\n /// whether we sent proxy headers to legacy backend\n bifrostSentProxyHeaders?: boolean;\n bifrostProxyLayout?: Vike.ProxyLayoutInfo | null;\n /// Only set when proxy mode is false or wrapped\n vikePageContext?: Partial<PageContextServer> | null;\n getLayout: GetLayout | null;\n customPageContextInit: Partial<Omit<PageContextServer, \"headers\">> | null;\n }\n}\n\ntype RawRequestExtendedWithProxy = FastifyRequest<\n RequestGenericInterface,\n RawServerBase\n>[\"raw\"] & {\n _bfproxy?: boolean;\n};\n\ninterface ViteProxyPluginOptions extends Omit<\n FastifyHttpProxyOptions,\n \"upstream\" | \"preHandler\" | \"replyOptions\"\n> {\n upstream: URL;\n host: URL;\n onError?: (error: any, pageContext: RenderedPageContext) => void;\n buildPageContextInit?: (\n req: FastifyRequest\n ) => Promise<Partial<Omit<PageContextServer, \"headers\">>>;\n beforeWrappedRender?: (\n req: FastifyRequest<RequestGenericInterface, RawServerBase>,\n reply: FastifyReply<\n RouteGenericInterface,\n RawServerBase,\n IncomingMessage | Http2ServerRequest\n >\n ) => void;\n}\n/**\n * Fastify plugin that wraps @fasitfy/http-proxy to proxy Rails/Turbolinks server into a vike site.\n */\nexport const viteProxyPlugin: FastifyPluginAsync<\n ViteProxyPluginOptions\n> = async (fastify, opts) => {\n const { upstream, host, onError, buildPageContextInit, beforeWrappedRender } =\n opts;\n async function replyWithPage(\n reply: FastifyReply<RouteGenericInterface, RawServerBase>,\n pageContext: RenderedPageContext\n ): Promise<FastifyReply> {\n const { httpResponse } = pageContext;\n\n if (\n onError &&\n httpResponse?.statusCode === 500 &&\n pageContext.errorWhileRendering\n ) {\n onError(pageContext.errorWhileRendering, pageContext);\n }\n\n if (!httpResponse) {\n return reply.code(404).type(\"text/html\").send(\"Not Found\");\n }\n\n const { statusCode, headers, getBody } = httpResponse;\n return (\n reply\n .status(statusCode)\n .headers(Object.fromEntries(headers))\n // This disables any possibility of real streaming. To re-enable streaming we should adopt vike-photon and rewrite wrapped proxy as a Vike middleware.\n // Why not pipe? Because Vike gives us `pipe` which sends data into a Writable, but Fastify's reply.send only accepts a ReadableStream. Passthrough can convert but causes race conditions\n // We would have to pipe into reply.raw, but that skips Fastify's reply handling (like onSend hooks)\n // Photon/universal-middleware solves this with some hacks around reply.body\n .send(await getBody())\n );\n }\n await fastify.register(accepts);\n fastify.decorateRequest(\"bifrostProxyMode\", false);\n fastify.decorateRequest(\"bifrostProxyLayout\", null);\n fastify.decorateRequest(\"bifrostSentProxyHeaders\", false);\n fastify.decorateRequest(\"vikePageContext\", null);\n fastify.decorateRequest(\"getLayout\", null);\n fastify.decorateRequest(\"customPageContextInit\", null);\n await fastify.register(proxy, {\n ...opts,\n upstream: upstream.href,\n websocket: true,\n async preHandler(req, reply) {\n if (\n (req.method === \"GET\" || req.method === \"HEAD\") &&\n req.accepts().type([\"html\"]) === \"html\"\n ) {\n req.customPageContextInit = buildPageContextInit\n ? await buildPageContextInit(req)\n : {};\n\n const pageContextInit = {\n urlOriginal: req.url,\n headersOriginal: req.headers,\n ...req.customPageContextInit,\n };\n\n const pageContext = await renderPage(pageContextInit);\n\n let proxyMode = pageContext.config?.proxyMode;\n if (!proxyMode) {\n req.vikePageContext = pageContext;\n req.log.info(`bifrost: rendering page ${pageContext.pageId}`);\n return replyWithPage(reply, pageContext);\n }\n\n req.bifrostProxyMode = \"passthru\";\n switch (proxyMode) {\n case \"passthru\": {\n req.log.info(`bifrost: passthru proxy to backend`);\n break;\n }\n case \"wrapped\": {\n req.log.info(`bifrost: proxy route matched, proxying to backend`);\n if (!!pageContext.isClientSideNavigation) {\n // This should never happen because wrapped proxy routes have no onBeforeRender. onRenderClient should make a request to the legacy backend.\n req.log.error(\n \"Wrapped proxy route is requesting index.pageContext.json. Something is wrong with the client.\"\n );\n return reply.redirect(\n req.url.replace(\"/index.pageContext.json\", \"\")\n );\n }\n if (pageContext.config?.getLayout) {\n let proxyHeadersAlreadySet = true;\n for (const [key, val] of Object.entries(\n pageContext.config?.proxyHeaders || {}\n )) {\n proxyHeadersAlreadySet &&=\n req.headers[key.toLowerCase()] == val;\n req.headers[key.toLowerCase()] = val;\n }\n // If proxy headers set, this is a client navigation meant to go direct to legacy backend.\n // ALB CANNOT be used for this. see `onBeforeRenderClient` for details\n // Only set getLayout and _bfproxy if we didn't already set proxy headers\n if (!proxyHeadersAlreadySet) {\n // setting _bfproxy tells onResponse we're in wrapped mode\n (req.raw as RawRequestExtendedWithProxy)._bfproxy = true;\n req.getLayout = pageContext.config.getLayout;\n req.bifrostSentProxyHeaders = true;\n }\n } else {\n req.log.error(\n \"Config missing getLayout on wrapped route! Falling back to passthru proxy\"\n );\n }\n break;\n }\n }\n\n if (pageContext.urlParsed) {\n const { options } = reply.fromParameters(pageContext.urlParsed.href);\n return reply.from(pageContext.urlParsed.href, options as any);\n }\n }\n },\n replyOptions: {\n rewriteRequestHeaders(request, headers) {\n if (!(request.raw instanceof Http2ServerRequest)) {\n const fwd = forwarded(request.raw).reverse();\n headers[\"X-Forwarded-For\"] = fwd.join(\", \");\n headers[\"X-Forwarded-Host\"] = host.host;\n headers[\"X-Forwarded-Proto\"] = host.protocol;\n }\n\n if ((request.raw as RawRequestExtendedWithProxy)._bfproxy) {\n // Proxying and wrapping\n\n // Delete cache headers\n delete headers[\"if-modified-since\"];\n delete headers[\"if-none-match\"];\n delete headers[\"if-unmodified-since\"];\n delete headers[\"if-none-match\"];\n delete headers[\"if-range\"];\n }\n return headers;\n },\n async onResponse(req, reply, res) {\n if ([301, 302, 303, 307, 308].includes(reply.statusCode)) {\n const location = reply.getHeader(\"location\") as string;\n if (location) {\n const url = new URL(location, host.href);\n if (url.host === upstream.host || url.host === host.host) {\n // rewrite redirect on upstream's host to the proxy host\n url.host = host.host;\n url.protocol = host.protocol;\n }\n reply.header(\"location\", url);\n return reply.send(\"stream\" in res ? res.stream : res);\n }\n }\n\n const proxyLayoutInfo = req.getLayout?.(reply.getHeaders());\n req.bifrostProxyLayout = proxyLayoutInfo;\n if (!proxyLayoutInfo) {\n return reply.send(\"stream\" in res ? res.stream : res);\n }\n\n const contentType = reply.getHeader(\"content-type\") as\n | string\n | undefined;\n\n if (\n !contentType ||\n parseContentType(contentType).type !== \"text/html\"\n ) {\n return reply.send(\"stream\" in res ? res.stream : res);\n }\n\n const html = await text(res.stream);\n\n const { bodyAttributes, bodyInnerHtml, headInnerHtml } =\n extractDomElements(html);\n\n if (!bodyInnerHtml || !headInnerHtml) {\n return reply.send(html);\n }\n\n try {\n beforeWrappedRender?.(req, reply);\n } catch (e) {\n req.log.error(\n `Error in beforeWrappedRender: ${(e as Error).message}`\n );\n }\n\n const pageContextInit = {\n urlOriginal: reply.request.url,\n headersOriginal: req.headers,\n // Critical that we don't set any passToClient values in pageContextInit\n // If we do, Vike re-requests pageContext on client navigation. This breaks wrapped proxy.\n _wrappedServerOnly: {\n bodyAttributes,\n bodyInnerHtml,\n headInnerHtml,\n proxyLayoutInfo,\n } satisfies WrappedServerOnly,\n ...req.customPageContextInit,\n };\n const pageContext = await renderPage(pageContextInit);\n req.vikePageContext = pageContext;\n req.bifrostProxyMode = \"wrapped\";\n return replyWithPage(reply, pageContext);\n },\n },\n });\n};\n","import { Parser } from \"htmlparser2\";\n\nexport function extractDomElements(html: string): {\n bodyInnerHtml: string | null;\n headInnerHtml: string | null;\n bodyAttributes: Record<string, string>;\n} {\n let headInnerHtml: string | null = null;\n let bodyInnerHtml: string | null = null;\n let bodyAttributes: Record<string, string> | null = null;\n\n let headStart = -1;\n let bodyStart = -1;\n\n const parser = new Parser({\n onopentag(name, attribs) {\n if (name === \"head\" && headStart < 0) {\n headStart = parser.endIndex! + 1;\n } else if (name === \"body\" && bodyStart < 0) {\n bodyStart = parser.endIndex! + 1;\n bodyAttributes = attribs;\n }\n },\n onclosetag(name) {\n if (name === \"head\" && headStart >= 0 && headInnerHtml === null) {\n headInnerHtml = html.slice(headStart, parser.startIndex!);\n } else if (name === \"body\" && bodyStart >= 0 && bodyInnerHtml === null) {\n bodyInnerHtml = html.slice(bodyStart, parser.startIndex!);\n }\n },\n });\n\n parser.write(html);\n parser.end();\n\n return { headInnerHtml, bodyInnerHtml, bodyAttributes: bodyAttributes ?? {} };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../index.ts","../lib/extractDomElements.ts"],"sourcesContent":["// Note that this file isn't processed by Vite, see https://github.com/brillout/vike/issues/562\nimport {\n FastifyReply,\n RawServerBase,\n FastifyPluginAsync,\n RouteGenericInterface,\n} from \"fastify\";\nimport { FastifyRequest, RequestGenericInterface } from \"fastify/types/request\";\nimport proxy, { type FastifyHttpProxyOptions } from \"@fastify/http-proxy\";\nimport accepts from \"@fastify/accepts\";\nimport forwarded from \"@fastify/forwarded\";\nimport type { GetLayout, WrappedServerOnly } from \"@alignable/bifrost/config\";\nimport { renderPage } from \"vike/server\";\nimport { PageContextServer } from \"vike/types\";\nimport { extractDomElements } from \"./lib/extractDomElements\";\nimport { Http2ServerRequest } from \"http2\";\nimport { text } from \"node:stream/consumers\";\nimport { parse as parseContentType } from \"fast-content-type-parse\";\nimport { IncomingMessage } from \"http\";\n\ntype RenderedPageContext = Awaited<\n ReturnType<\n typeof renderPage<\n {\n isClientSideNavigation?: boolean;\n },\n { urlOriginal: string }\n >\n >\n>;\n\ndeclare module \"fastify\" {\n interface FastifyRequest {\n /// Actual ProxyMode after processing backend server results, which can tell us to fallback to passthru or redirect\n bifrostProxyMode?: Vike.Config[\"proxyMode\"];\n /// whether we sent proxy headers to legacy backend\n bifrostSentProxyHeaders?: boolean;\n bifrostProxyLayout?: Vike.ProxyLayoutInfo | null;\n /// Only set when proxy mode is false or wrapped\n vikePageContext?: Partial<PageContextServer> | null;\n getLayout: GetLayout | null;\n }\n}\n\ntype RawRequestExtendedWithProxy = FastifyRequest<\n RequestGenericInterface,\n RawServerBase\n>[\"raw\"] & {\n _bfproxy?: boolean;\n};\n\ninterface ViteProxyPluginOptions extends Omit<\n FastifyHttpProxyOptions,\n \"upstream\" | \"preHandler\" | \"replyOptions\"\n> {\n upstream: URL;\n host: URL;\n onError?: (error: any, pageContext: RenderedPageContext) => void;\n buildPageContextInit?: (\n req: FastifyRequest<RequestGenericInterface, RawServerBase>\n ) => Promise<Partial<Omit<PageContextServer, \"headers\">>>;\n beforeWrappedRender?: (\n req: FastifyRequest<RequestGenericInterface, RawServerBase>,\n reply: FastifyReply<\n RouteGenericInterface,\n RawServerBase,\n IncomingMessage | Http2ServerRequest\n >\n ) => void;\n}\n/**\n * Fastify plugin that wraps @fasitfy/http-proxy to proxy Rails/Turbolinks server into a vike site.\n */\nexport const viteProxyPlugin: FastifyPluginAsync<\n ViteProxyPluginOptions\n> = async (fastify, opts) => {\n const { upstream, host, onError, buildPageContextInit, beforeWrappedRender } =\n opts;\n async function replyWithPage(\n reply: FastifyReply<RouteGenericInterface, RawServerBase>,\n pageContext: RenderedPageContext\n ): Promise<FastifyReply> {\n const { httpResponse } = pageContext;\n\n if (\n onError &&\n httpResponse?.statusCode === 500 &&\n pageContext.errorWhileRendering\n ) {\n onError(pageContext.errorWhileRendering, pageContext);\n }\n\n if (!httpResponse) {\n return reply.code(404).type(\"text/html\").send(\"Not Found\");\n }\n\n const { statusCode, headers, getBody } = httpResponse;\n return (\n reply\n .status(statusCode)\n .headers(Object.fromEntries(headers))\n // This disables any possibility of real streaming. To re-enable streaming we should adopt vike-photon and rewrite wrapped proxy as a Vike middleware.\n // Why not pipe? Because Vike gives us `pipe` which sends data into a Writable, but Fastify's reply.send only accepts a ReadableStream. Passthrough can convert but causes race conditions\n // We would have to pipe into reply.raw, but that skips Fastify's reply handling (like onSend hooks)\n // Photon/universal-middleware solves this with some hacks around reply.body\n .send(await getBody())\n );\n }\n await fastify.register(accepts);\n fastify.decorateRequest(\"bifrostProxyMode\", false);\n fastify.decorateRequest(\"bifrostProxyLayout\", null);\n fastify.decorateRequest(\"bifrostSentProxyHeaders\", false);\n fastify.decorateRequest(\"vikePageContext\", null);\n fastify.decorateRequest(\"getLayout\", null);\n await fastify.register(proxy, {\n ...opts,\n upstream: upstream.href,\n websocket: true,\n async preHandler(req, reply) {\n if (\n (req.method === \"GET\" || req.method === \"HEAD\") &&\n req.accepts().type([\"html\"]) === \"html\"\n ) {\n const customPageContextInit = buildPageContextInit\n ? await buildPageContextInit(req)\n : {};\n\n const pageContextInit = {\n urlOriginal: req.url,\n headersOriginal: req.headers,\n ...customPageContextInit,\n };\n\n const pageContext = await renderPage(pageContextInit);\n\n let proxyMode = pageContext.config?.proxyMode;\n if (!proxyMode) {\n req.vikePageContext = pageContext;\n req.log.info(`bifrost: rendering page ${pageContext.pageId}`);\n return replyWithPage(reply, pageContext);\n }\n\n req.bifrostProxyMode = \"passthru\";\n switch (proxyMode) {\n case \"passthru\": {\n req.log.info(`bifrost: passthru proxy to backend`);\n break;\n }\n case \"wrapped\": {\n req.log.info(`bifrost: proxy route matched, proxying to backend`);\n if (!!pageContext.isClientSideNavigation) {\n // This should never happen because wrapped proxy routes have no onBeforeRender. onRenderClient should make a request to the legacy backend.\n req.log.error(\n \"Wrapped proxy route is requesting index.pageContext.json. Something is wrong with the client.\"\n );\n return reply.redirect(\n req.url.replace(\"/index.pageContext.json\", \"\")\n );\n }\n if (pageContext.config?.getLayout) {\n let proxyHeadersAlreadySet = true;\n for (const [key, val] of Object.entries(\n pageContext.config?.proxyHeaders || {}\n )) {\n proxyHeadersAlreadySet &&=\n req.headers[key.toLowerCase()] == val;\n req.headers[key.toLowerCase()] = val;\n }\n // If proxy headers set, this is a client navigation meant to go direct to legacy backend.\n // ALB CANNOT be used for this. see `onBeforeRenderClient` for details\n // Only set getLayout and _bfproxy if we didn't already set proxy headers\n if (!proxyHeadersAlreadySet) {\n // setting _bfproxy tells onResponse we're in wrapped mode\n (req.raw as RawRequestExtendedWithProxy)._bfproxy = true;\n req.getLayout = pageContext.config.getLayout;\n req.bifrostSentProxyHeaders = true;\n }\n } else {\n req.log.error(\n \"Config missing getLayout on wrapped route! Falling back to passthru proxy\"\n );\n }\n break;\n }\n }\n\n if (pageContext.urlParsed) {\n const { options } = reply.fromParameters(pageContext.urlParsed.href);\n return reply.from(pageContext.urlParsed.href, options as any);\n }\n }\n },\n replyOptions: {\n rewriteRequestHeaders(request, headers) {\n if (!(request.raw instanceof Http2ServerRequest)) {\n const fwd = forwarded(request.raw).reverse();\n headers[\"X-Forwarded-For\"] = fwd.join(\", \");\n headers[\"X-Forwarded-Host\"] = host.host;\n headers[\"X-Forwarded-Proto\"] = host.protocol;\n }\n\n if ((request.raw as RawRequestExtendedWithProxy)._bfproxy) {\n // Proxying and wrapping\n\n // Delete cache headers\n delete headers[\"if-modified-since\"];\n delete headers[\"if-none-match\"];\n delete headers[\"if-unmodified-since\"];\n delete headers[\"if-none-match\"];\n delete headers[\"if-range\"];\n }\n return headers;\n },\n async onResponse(req, reply, res) {\n if ([301, 302, 303, 307, 308].includes(reply.statusCode)) {\n const location = reply.getHeader(\"location\") as string;\n if (location) {\n const url = new URL(location, host.href);\n if (url.host === upstream.host || url.host === host.host) {\n // rewrite redirect on upstream's host to the proxy host\n url.host = host.host;\n url.protocol = host.protocol;\n }\n reply.header(\"location\", url);\n return reply.send(\"stream\" in res ? res.stream : res);\n }\n }\n\n const proxyLayoutInfo = req.getLayout?.(reply.getHeaders());\n req.bifrostProxyLayout = proxyLayoutInfo;\n if (!proxyLayoutInfo) {\n return reply.send(\"stream\" in res ? res.stream : res);\n }\n\n const contentType = reply.getHeader(\"content-type\") as\n | string\n | undefined;\n\n if (\n !contentType ||\n parseContentType(contentType).type !== \"text/html\"\n ) {\n return reply.send(\"stream\" in res ? res.stream : res);\n }\n\n const html = await text(res.stream);\n\n const { bodyAttributes, bodyInnerHtml, headInnerHtml } =\n extractDomElements(html);\n\n if (!bodyInnerHtml || !headInnerHtml) {\n return reply.send(html);\n }\n\n try {\n beforeWrappedRender?.(req, reply);\n } catch (e) {\n req.log.error(\n `Error in beforeWrappedRender: ${(e as Error).message}`\n );\n }\n\n const customPageContextInit = buildPageContextInit\n ? await buildPageContextInit(req)\n : {};\n\n const pageContextInit = {\n urlOriginal: reply.request.url,\n headersOriginal: req.headers,\n // Critical that we don't set any passToClient values in pageContextInit\n // If we do, Vike re-requests pageContext on client navigation. This breaks wrapped proxy.\n _wrappedServerOnly: {\n bodyAttributes,\n bodyInnerHtml,\n headInnerHtml,\n proxyLayoutInfo,\n } satisfies WrappedServerOnly,\n ...customPageContextInit,\n };\n const pageContext = await renderPage(pageContextInit);\n req.vikePageContext = pageContext;\n req.bifrostProxyMode = \"wrapped\";\n return replyWithPage(reply, pageContext);\n },\n },\n });\n};\n","import { Parser } from \"htmlparser2\";\n\nexport function extractDomElements(html: string): {\n bodyInnerHtml: string | null;\n headInnerHtml: string | null;\n bodyAttributes: Record<string, string>;\n} {\n let headInnerHtml: string | null = null;\n let bodyInnerHtml: string | null = null;\n let bodyAttributes: Record<string, string> | null = null;\n\n let headStart = -1;\n let bodyStart = -1;\n\n const parser = new Parser({\n onopentag(name, attribs) {\n if (name === \"head\" && headStart < 0) {\n headStart = parser.endIndex! + 1;\n } else if (name === \"body\" && bodyStart < 0) {\n bodyStart = parser.endIndex! + 1;\n bodyAttributes = attribs;\n }\n },\n onclosetag(name) {\n if (name === \"head\" && headStart >= 0 && headInnerHtml === null) {\n headInnerHtml = html.slice(headStart, parser.startIndex!);\n } else if (name === \"body\" && bodyStart >= 0 && bodyInnerHtml === null) {\n bodyInnerHtml = html.slice(bodyStart, parser.startIndex!);\n }\n },\n });\n\n parser.write(html);\n parser.end();\n\n return { headInnerHtml, bodyInnerHtml, bodyAttributes: bodyAttributes ?? {} };\n}\n"],"mappings":";AAQA,OAAO,WAA6C;AACpD,OAAO,aAAa;AACpB,OAAO,eAAe;AAEtB,SAAS,kBAAkB;;;ACZ3B,SAAS,cAAc;AAEhB,SAAS,mBAAmB,MAIjC;AACA,MAAI,gBAA+B;AACnC,MAAI,gBAA+B;AACnC,MAAI,iBAAgD;AAEpD,MAAI,YAAY;AAChB,MAAI,YAAY;AAEhB,QAAM,SAAS,IAAI,OAAO;AAAA,IACxB,UAAU,MAAM,SAAS;AACvB,UAAI,SAAS,UAAU,YAAY,GAAG;AACpC,oBAAY,OAAO,WAAY;AAAA,MACjC,WAAW,SAAS,UAAU,YAAY,GAAG;AAC3C,oBAAY,OAAO,WAAY;AAC/B,yBAAiB;AAAA,MACnB;AAAA,IACF;AAAA,IACA,WAAW,MAAM;AACf,UAAI,SAAS,UAAU,aAAa,KAAK,kBAAkB,MAAM;AAC/D,wBAAgB,KAAK,MAAM,WAAW,OAAO,UAAW;AAAA,MAC1D,WAAW,SAAS,UAAU,aAAa,KAAK,kBAAkB,MAAM;AACtE,wBAAgB,KAAK,MAAM,WAAW,OAAO,UAAW;AAAA,MAC1D;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,MAAM,IAAI;AACjB,SAAO,IAAI;AAEX,SAAO,EAAE,eAAe,eAAe,gBAAgB,kBAAkB,CAAC,EAAE;AAC9E;;;ADrBA,SAAS,0BAA0B;AACnC,SAAS,YAAY;AACrB,SAAS,SAAS,wBAAwB;AAwDnC,IAAM,kBAET,OAAO,SAAS,SAAS;AAC3B,QAAM,EAAE,UAAU,MAAM,SAAS,sBAAsB,oBAAoB,IACzE;AACF,iBAAe,cACb,OACA,aACuB;AACvB,UAAM,EAAE,aAAa,IAAI;AAEzB,QACE,WACA,cAAc,eAAe,OAC7B,YAAY,qBACZ;AACA,cAAQ,YAAY,qBAAqB,WAAW;AAAA,IACtD;AAEA,QAAI,CAAC,cAAc;AACjB,aAAO,MAAM,KAAK,GAAG,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW;AAAA,IAC3D;AAEA,UAAM,EAAE,YAAY,SAAS,QAAQ,IAAI;AACzC,WACE,MACG,OAAO,UAAU,EACjB,QAAQ,OAAO,YAAY,OAAO,CAAC,EAKnC,KAAK,MAAM,QAAQ,CAAC;AAAA,EAE3B;AACA,QAAM,QAAQ,SAAS,OAAO;AAC9B,UAAQ,gBAAgB,oBAAoB,KAAK;AACjD,UAAQ,gBAAgB,sBAAsB,IAAI;AAClD,UAAQ,gBAAgB,2BAA2B,KAAK;AACxD,UAAQ,gBAAgB,mBAAmB,IAAI;AAC/C,UAAQ,gBAAgB,aAAa,IAAI;AACzC,QAAM,QAAQ,SAAS,OAAO;AAAA,IAC5B,GAAG;AAAA,IACH,UAAU,SAAS;AAAA,IACnB,WAAW;AAAA,IACX,MAAM,WAAW,KAAK,OAAO;AAC3B,WACG,IAAI,WAAW,SAAS,IAAI,WAAW,WACxC,IAAI,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,QACjC;AACA,cAAM,wBAAwB,uBAC1B,MAAM,qBAAqB,GAAG,IAC9B,CAAC;AAEL,cAAM,kBAAkB;AAAA,UACtB,aAAa,IAAI;AAAA,UACjB,iBAAiB,IAAI;AAAA,UACrB,GAAG;AAAA,QACL;AAEA,cAAM,cAAc,MAAM,WAAW,eAAe;AAEpD,YAAI,YAAY,YAAY,QAAQ;AACpC,YAAI,CAAC,WAAW;AACd,cAAI,kBAAkB;AACtB,cAAI,IAAI,KAAK,2BAA2B,YAAY,QAAQ;AAC5D,iBAAO,cAAc,OAAO,WAAW;AAAA,QACzC;AAEA,YAAI,mBAAmB;AACvB,gBAAQ,WAAW;AAAA,UACjB,KAAK,YAAY;AACf,gBAAI,IAAI,KAAK,oCAAoC;AACjD;AAAA,UACF;AAAA,UACA,KAAK,WAAW;AACd,gBAAI,IAAI,KAAK,mDAAmD;AAChE,gBAAI,CAAC,CAAC,YAAY,wBAAwB;AAExC,kBAAI,IAAI;AAAA,gBACN;AAAA,cACF;AACA,qBAAO,MAAM;AAAA,gBACX,IAAI,IAAI,QAAQ,2BAA2B,EAAE;AAAA,cAC/C;AAAA,YACF;AACA,gBAAI,YAAY,QAAQ,WAAW;AACjC,kBAAI,yBAAyB;AAC7B,yBAAW,CAAC,KAAK,GAAG,KAAK,OAAO;AAAA,gBAC9B,YAAY,QAAQ,gBAAgB,CAAC;AAAA,cACvC,GAAG;AACD,oEACE,IAAI,QAAQ,IAAI,YAAY,CAAC,KAAK;AACpC,oBAAI,QAAQ,IAAI,YAAY,CAAC,IAAI;AAAA,cACnC;AAIA,kBAAI,CAAC,wBAAwB;AAE3B,gBAAC,IAAI,IAAoC,WAAW;AACpD,oBAAI,YAAY,YAAY,OAAO;AACnC,oBAAI,0BAA0B;AAAA,cAChC;AAAA,YACF,OAAO;AACL,kBAAI,IAAI;AAAA,gBACN;AAAA,cACF;AAAA,YACF;AACA;AAAA,UACF;AAAA,QACF;AAEA,YAAI,YAAY,WAAW;AACzB,gBAAM,EAAE,QAAQ,IAAI,MAAM,eAAe,YAAY,UAAU,IAAI;AACnE,iBAAO,MAAM,KAAK,YAAY,UAAU,MAAM,OAAc;AAAA,QAC9D;AAAA,MACF;AAAA,IACF;AAAA,IACA,cAAc;AAAA,MACZ,sBAAsB,SAAS,SAAS;AACtC,YAAI,EAAE,QAAQ,eAAe,qBAAqB;AAChD,gBAAM,MAAM,UAAU,QAAQ,GAAG,EAAE,QAAQ;AAC3C,kBAAQ,iBAAiB,IAAI,IAAI,KAAK,IAAI;AAC1C,kBAAQ,kBAAkB,IAAI,KAAK;AACnC,kBAAQ,mBAAmB,IAAI,KAAK;AAAA,QACtC;AAEA,YAAK,QAAQ,IAAoC,UAAU;AAIzD,iBAAO,QAAQ,mBAAmB;AAClC,iBAAO,QAAQ,eAAe;AAC9B,iBAAO,QAAQ,qBAAqB;AACpC,iBAAO,QAAQ,eAAe;AAC9B,iBAAO,QAAQ,UAAU;AAAA,QAC3B;AACA,eAAO;AAAA,MACT;AAAA,MACA,MAAM,WAAW,KAAK,OAAO,KAAK;AAChC,YAAI,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG,EAAE,SAAS,MAAM,UAAU,GAAG;AACxD,gBAAM,WAAW,MAAM,UAAU,UAAU;AAC3C,cAAI,UAAU;AACZ,kBAAM,MAAM,IAAI,IAAI,UAAU,KAAK,IAAI;AACvC,gBAAI,IAAI,SAAS,SAAS,QAAQ,IAAI,SAAS,KAAK,MAAM;AAExD,kBAAI,OAAO,KAAK;AAChB,kBAAI,WAAW,KAAK;AAAA,YACtB;AACA,kBAAM,OAAO,YAAY,GAAG;AAC5B,mBAAO,MAAM,KAAK,YAAY,MAAM,IAAI,SAAS,GAAG;AAAA,UACtD;AAAA,QACF;AAEA,cAAM,kBAAkB,IAAI,YAAY,MAAM,WAAW,CAAC;AAC1D,YAAI,qBAAqB;AACzB,YAAI,CAAC,iBAAiB;AACpB,iBAAO,MAAM,KAAK,YAAY,MAAM,IAAI,SAAS,GAAG;AAAA,QACtD;AAEA,cAAM,cAAc,MAAM,UAAU,cAAc;AAIlD,YACE,CAAC,eACD,iBAAiB,WAAW,EAAE,SAAS,aACvC;AACA,iBAAO,MAAM,KAAK,YAAY,MAAM,IAAI,SAAS,GAAG;AAAA,QACtD;AAEA,cAAM,OAAO,MAAM,KAAK,IAAI,MAAM;AAElC,cAAM,EAAE,gBAAgB,eAAe,cAAc,IACnD,mBAAmB,IAAI;AAEzB,YAAI,CAAC,iBAAiB,CAAC,eAAe;AACpC,iBAAO,MAAM,KAAK,IAAI;AAAA,QACxB;AAEA,YAAI;AACF,gCAAsB,KAAK,KAAK;AAAA,QAClC,SAAS,GAAP;AACA,cAAI,IAAI;AAAA,YACN,iCAAkC,EAAY;AAAA,UAChD;AAAA,QACF;AAEA,cAAM,wBAAwB,uBAC1B,MAAM,qBAAqB,GAAG,IAC9B,CAAC;AAEL,cAAM,kBAAkB;AAAA,UACtB,aAAa,MAAM,QAAQ;AAAA,UAC3B,iBAAiB,IAAI;AAAA;AAAA;AAAA,UAGrB,oBAAoB;AAAA,YAClB;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,UACA,GAAG;AAAA,QACL;AACA,cAAM,cAAc,MAAM,WAAW,eAAe;AACpD,YAAI,kBAAkB;AACtB,YAAI,mBAAmB;AACvB,eAAO,cAAc,OAAO,WAAW;AAAA,MACzC;AAAA,IACF;AAAA,EACF,CAAC;AACH;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alignable/bifrost-fastify",
|
|
3
3
|
"repository": "https://github.com/Alignable/bifrost.git",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.23",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"htmlparser2": "^10.0.0"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
|
-
"@alignable/bifrost": "1.0.
|
|
17
|
+
"@alignable/bifrost": "1.0.23",
|
|
18
18
|
"fastify": "^5.0.0",
|
|
19
19
|
"vike": ">=0.4.251",
|
|
20
20
|
"vite": ">=6"
|