diff --git a/app/components/common/Cookies.tsx b/app/components/common/Cookies.tsx new file mode 100644 index 0000000..e69de29 diff --git a/app/fastify-entry.ts b/app/fastify-entry.ts index 1bf2846..be74630 100644 --- a/app/fastify-entry.ts +++ b/app/fastify-entry.ts @@ -1,6 +1,9 @@ +import type { Theme } from "@/contexts/ThemeContext"; + import { createHandler } from "@universal-middleware/fastify"; import { telefuncHandler } from "./server/telefunc-handler"; import { vikeHandler } from "./server/vike-handler"; +import fastifyCookie from "@fastify/cookie"; import { fileURLToPath } from "node:url"; import { dirname } from "node:path"; import Fastify from "fastify"; @@ -12,9 +15,26 @@ const hmrPort = process.env.HMR_PORT ? parseInt(process.env.HMR_PORT, 10) : 2467 const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; const root = __dirname; +declare global { + namespace Vike { + interface PageContext { + cookies: Partial<{ + consent: boolean; + theme: Theme; + }>; + } + } +} + async function startServer() { const app = Fastify(); + app.register(fastifyCookie, { + secret: "todo", + hook: "onRequest", + parseOptions: {}, + }); + // Avoid pre-parsing body, otherwise it will cause issue with universal handlers // This will probably change in the future though, you can follow https://github.com/magne4000/universal-middleware for updates app.removeAllContentTypeParsers(); diff --git a/app/package.json b/app/package.json index 01afe5a..a6e5eba 100644 --- a/app/package.json +++ b/app/package.json @@ -7,6 +7,7 @@ "prod": "npm-run-all build preview" }, "dependencies": { + "@fastify/cookie": "^11.0.2", "@fastify/middie": "^9.0.3", "@fastify/static": "^8.1.1", "@fontsource-variable/inter": "^5.2.5", diff --git a/app/pages/+Head.tsx b/app/pages/+Head.tsx index c9fb777..ba67683 100644 --- a/app/pages/+Head.tsx +++ b/app/pages/+Head.tsx @@ -1,7 +1,12 @@ +import { usePageContext } from "vike-react/usePageContext"; import logoUrl from "@/assets/logo.svg"; import React from "react"; export default function HeadDefault() { + const pageContext = usePageContext(); + + console.log("pageContext", pageContext.cookies); + return ( <> diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index ebbe8bb..0057572 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@fastify/cookie': + specifier: ^11.0.2 + version: 11.0.2 '@fastify/middie': specifier: ^9.0.3 version: 9.0.3 @@ -94,7 +97,7 @@ importers: version: 1.2.1 telefunc: specifier: ^0.1.87 - version: 0.1.87(@babel/core@7.26.10)(@babel/parser@7.27.0)(@babel/types@7.27.0)(react-streaming@0.3.50(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) + version: 0.1.87(@babel/core@7.7.4)(@babel/parser@7.27.0)(@babel/types@7.27.0)(react-streaming@0.3.50(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0) unplugin-fonts: specifier: ^1.3.1 version: 1.3.1(vite@6.2.6(@types/node@18.19.86)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.3)) @@ -1301,6 +1304,9 @@ packages: '@fastify/ajv-compiler@4.0.2': resolution: {integrity: sha512-Rkiu/8wIjpsf46Rr+Fitd3HRP+VsxUFDDeag0hs9L0ksfnwx2g7SPQQTFL0E8Qv+rfXzQOxBJnjUB9ITUDjfWQ==} + '@fastify/cookie@11.0.2': + resolution: {integrity: sha512-GWdwdGlgJxyvNv+QcKiGNevSspMQXncjMZ1J8IvuDQk0jvkzgWWZFNC2En3s+nHndZBGV8IbLwOI/sxCZw/mzA==} + '@fastify/error@4.1.0': resolution: {integrity: sha512-KeFcciOr1eo/YvIXHP65S94jfEEqn1RxTRBT1aJaHxY5FK0/GDXYozsQMMWlZoHgi8i0s+YtrLsgj/JkUUjSkQ==} @@ -9754,6 +9760,11 @@ snapshots: ajv-formats: 3.0.1(ajv@8.17.1) fast-uri: 3.0.6 + '@fastify/cookie@11.0.2': + dependencies: + cookie: 1.0.2 + fastify-plugin: 5.0.1 + '@fastify/error@4.1.0': {} '@fastify/fast-json-stringify-compiler@5.0.3': @@ -10506,10 +10517,10 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@2.34.0(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@2.34.0(@typescript-eslint/parser@2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@typescript-eslint/experimental-utils': 2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/parser': 8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.24.0(jiti@2.4.2) functional-red-black-tree: 1.0.1 regexpp: 3.2.0 @@ -12828,13 +12839,13 @@ snapshots: eslint-config-react-app@5.2.1(@typescript-eslint/eslint-plugin@2.34.0(@typescript-eslint/parser@2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(@typescript-eslint/parser@2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(babel-eslint@10.0.3(eslint@9.24.0(jiti@2.4.2)))(eslint-plugin-flowtype@3.13.0(eslint@9.24.0(jiti@2.4.2)))(eslint-plugin-import@2.18.2(@typescript-eslint/parser@2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint-plugin-jsx-a11y@6.2.3(eslint@9.24.0(jiti@2.4.2)))(eslint-plugin-react-hooks@1.7.0(eslint@9.24.0(jiti@2.4.2)))(eslint-plugin-react@7.16.0(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3): dependencies: - '@typescript-eslint/eslint-plugin': 2.34.0(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 2.34.0(@typescript-eslint/parser@2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) '@typescript-eslint/parser': 2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) babel-eslint: 10.0.3(eslint@9.24.0(jiti@2.4.2)) confusing-browser-globals: 1.0.11 eslint: 9.24.0(jiti@2.4.2) eslint-plugin-flowtype: 3.13.0(eslint@9.24.0(jiti@2.4.2)) - eslint-plugin-import: 2.18.2(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)) + eslint-plugin-import: 2.18.2(@typescript-eslint/parser@2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-jsx-a11y: 6.2.3(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-react: 7.16.0(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-react-hooks: 1.7.0(eslint@9.24.0(jiti@2.4.2)) @@ -12859,11 +12870,11 @@ snapshots: schema-utils: 2.7.1 webpack: 4.41.2 - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.24.0(jiti@2.4.2)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.24.0(jiti@2.4.2)): dependencies: debug: 3.2.7(supports-color@6.1.0) optionalDependencies: - '@typescript-eslint/parser': 8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.24.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: @@ -12874,7 +12885,7 @@ snapshots: eslint: 9.24.0(jiti@2.4.2) lodash: 4.17.21 - eslint-plugin-import@2.18.2(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)): + eslint-plugin-import@2.18.2(@typescript-eslint/parser@2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)): dependencies: array-includes: 3.1.8 contains-path: 0.1.0 @@ -12882,14 +12893,14 @@ snapshots: doctrine: 1.5.0 eslint: 9.24.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.24.0(jiti@2.4.2)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint@9.24.0(jiti@2.4.2)) has: 1.0.4 minimatch: 3.1.2 object.values: 1.2.1 read-pkg-up: 2.0.0 resolve: 1.22.10 optionalDependencies: - '@typescript-eslint/parser': 8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -16363,7 +16374,7 @@ snapshots: dependencies: '@babel/core': 7.7.4 '@svgr/webpack': 4.3.3 - '@typescript-eslint/eslint-plugin': 2.34.0(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 2.34.0(@typescript-eslint/parser@2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) '@typescript-eslint/parser': 2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) babel-eslint: 10.0.3(eslint@9.24.0(jiti@2.4.2)) babel-jest: 24.9.0(@babel/core@7.7.4) @@ -16379,7 +16390,7 @@ snapshots: eslint-config-react-app: 5.2.1(@typescript-eslint/eslint-plugin@2.34.0(@typescript-eslint/parser@2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(@typescript-eslint/parser@2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(babel-eslint@10.0.3(eslint@9.24.0(jiti@2.4.2)))(eslint-plugin-flowtype@3.13.0(eslint@9.24.0(jiti@2.4.2)))(eslint-plugin-import@2.18.2(@typescript-eslint/parser@2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)))(eslint-plugin-jsx-a11y@6.2.3(eslint@9.24.0(jiti@2.4.2)))(eslint-plugin-react-hooks@1.7.0(eslint@9.24.0(jiti@2.4.2)))(eslint-plugin-react@7.16.0(eslint@9.24.0(jiti@2.4.2)))(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3) eslint-loader: 3.0.2(eslint@9.24.0(jiti@2.4.2))(webpack@4.41.2) eslint-plugin-flowtype: 3.13.0(eslint@9.24.0(jiti@2.4.2)) - eslint-plugin-import: 2.18.2(@typescript-eslint/parser@8.29.1(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)) + eslint-plugin-import: 2.18.2(@typescript-eslint/parser@2.34.0(eslint@9.24.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-jsx-a11y: 6.2.3(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-react: 7.16.0(eslint@9.24.0(jiti@2.4.2)) eslint-plugin-react-hooks: 1.7.0(eslint@9.24.0(jiti@2.4.2)) @@ -17435,7 +17446,7 @@ snapshots: tapable@2.2.1: {} - telefunc@0.1.87(@babel/core@7.26.10)(@babel/parser@7.27.0)(@babel/types@7.27.0)(react-streaming@0.3.50(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0): + telefunc@0.1.87(@babel/core@7.7.4)(@babel/parser@7.27.0)(@babel/types@7.27.0)(react-streaming@0.3.50(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0): dependencies: '@brillout/import': 0.2.6 '@brillout/json-serializer': 0.5.15 @@ -17444,7 +17455,7 @@ snapshots: es-module-lexer: 1.6.0 ts-morph: 19.0.0 optionalDependencies: - '@babel/core': 7.26.10 + '@babel/core': 7.7.4 '@babel/parser': 7.27.0 '@babel/types': 7.27.0 react: 19.1.0 diff --git a/app/server/vike-handler.ts b/app/server/vike-handler.ts index 5d1ae96..66e5728 100644 --- a/app/server/vike-handler.ts +++ b/app/server/vike-handler.ts @@ -1,10 +1,24 @@ /// -import { renderPage } from "vike/server"; + // TODO: stop using universal-middleware and directly integrate server middlewares instead. (Bati generates boilerplates that use universal-middleware https://github.com/magne4000/universal-middleware to make Bati's internal logic easier. This is temporary and will be removed soon.) import type { Get, UniversalHandler } from "@universal-middleware/core"; +import { CookieParser } from "@/services/CookieParser"; +import { renderPage } from "vike/server"; + export const vikeHandler: Get<[], UniversalHandler> = () => async (request, context, runtime) => { - const pageContextInit = { ...context, ...runtime, urlOriginal: request.url, headersOriginal: request.headers }; + const cookies = new CookieParser(request.headers.get("cookie") || ""); + + const pageContextInit = { + ...context, + ...runtime, + urlOriginal: request.url, + headersOriginal: request.headers, + cookies: { + consent: cookies.get("consent", Boolean) || false, + theme: cookies.get("theme") || "light", + }, + }; const pageContext = await renderPage(pageContextInit); const response = pageContext.httpResponse; diff --git a/app/services/CookieParser.ts b/app/services/CookieParser.ts new file mode 100644 index 0000000..12f95eb --- /dev/null +++ b/app/services/CookieParser.ts @@ -0,0 +1,28 @@ +export class CookieParser { + private rawCookies: string; + private cookies: Record; + + constructor(rawCookies: string) { + this.rawCookies = rawCookies; + this.cookies = {}; + this.parse(); + } + + parse(): Record { + return this.rawCookies.split("; ").reduce( + (acc, cookie) => { + const [key, value] = cookie.split("="); + acc[key] = decodeURIComponent(value); + return acc; + }, + {} as Record, + ); + } + + get(key: string, formatter?: Function): string | undefined { + const value = this.cookies[key]; + + if (formatter) return formatter(value); + return value; + } +}