import { renderSSRHead } from '@unhead/vue/server'; import { Hono } from 'hono'; import { contextStorage } from 'hono/context-storage'; import { cors } from "hono/cors"; import { streamText } from 'hono/streaming'; import isMobile from 'is-mobile'; import { renderToWebStream } from 'vue/server-renderer'; import { buildBootstrapScript } from './lib/manifest'; import { styleTags } from './lib/primePassthrough'; import { createApp } from './main'; import { useAuthStore } from './stores/auth'; // @ts-ignore import Base from '@primevue/core/base'; const app = new Hono() const defaultNames = ['primitive', 'semantic', 'global', 'base', 'ripple-directive'] // app.use(renderer) app.use('*', contextStorage()); app.use(cors(), async (c, next) => { c.set("fetch", app.request.bind(app)); const ua = c.req.header("User-Agent") if (!ua) { return c.json({ error: "User-Agent header is missing" }, 400); }; c.set("isMobile", isMobile({ ua })); await next(); }, async (c, next) => { const path = c.req.path if (path !== '/r' && !path.startsWith('/r/')) { return await next() } const url = new URL(c.req.url) url.host = 'api.pipic.fun' url.protocol = 'https:' url.pathname = path.replace(/^\/r/, '') || '/' url.port = '' // console.log("url", url.toString()) // console.log("c.req.raw", c.req.raw) const headers = new Headers(c.req.header()); headers.delete("host"); headers.delete("connection"); const response = await fetch(url.toString(), { method: c.req.method, headers: headers, body: c.req.raw.body, // @ts-ignore duplex: 'half', credentials: 'include' }); const newHeaders = new Headers(response.headers); // Rewrite Set-Cookie to remove Domain attribute if (typeof response.headers.getSetCookie === 'function') { newHeaders.delete('set-cookie'); const cookies = response.headers.getSetCookie(); for (const cookie of cookies) { // Remove Domain=...; or Domain=... ending const newCookie = cookie.replace(/Domain=[^;]+;?/gi, ''); newHeaders.append('set-cookie', newCookie); } } else { // Fallback for environments without getSetCookie const cookie = response.headers.get('set-cookie'); if (cookie) { newHeaders.set('set-cookie', cookie.replace(/Domain=[^;]+;?/gi, '')); } } return new Response(response.body, { status: response.status, statusText: response.statusText, headers: newHeaders }); }); app.get("/.well-known/*", (c) => { return c.json({ ok: true }); }); app.get("*", async (c) => { const nonce = crypto.randomUUID(); const url = new URL(c.req.url); const { app, router, head, pinia, bodyClass } = createApp(); app.provide("honoContext", c); const auth = useAuthStore(); auth.$reset(); // auth.initialized = false; await auth.init(); await router.push(url.pathname); await router.isReady(); let usedStyles = new Set(); Base.setLoadedStyleName = async (name: string) => usedStyles.add(name) return streamText(c, async (stream) => { c.header("Content-Type", "text/html; charset=utf-8"); c.header("Content-Encoding", "Identity"); const ctx: Record = {}; const appStream = renderToWebStream(app, ctx); // console.log("ctx: ", ); await stream.write(""); await stream.write(""); await renderSSRHead(head).then((headString) => stream.write(headString.headTags.replace(/\n/g, ""))); await stream.write(``); await stream.write(''); await stream.write(buildBootstrapScript()); if (usedStyles.size > 0) { defaultNames.forEach(name => usedStyles.add(name)); } await Promise.all(styleTags.filter(tag => usedStyles.has(tag.name.replace(/-(variables|style)$/, ""))).map(tag => stream.write(``))); await stream.write(``); await stream.pipe(appStream); Object.assign(ctx, { $p: pinia.state.value }); await stream.write(``); await stream.write(""); }); }) const ESCAPE_LOOKUP: { [match: string]: string } = { "&": "\\u0026", ">": "\\u003e", "<": "\\u003c", "\u2028": "\\u2028", "\u2029": "\\u2029", }; const ESCAPE_REGEX = /[&><\u2028\u2029]/g; function htmlEscape(str: string): string { return str.replace(ESCAPE_REGEX, (match) => ESCAPE_LOOKUP[match]); } export default app