diff --git a/packages/astro/src/core/session/vite-plugin.ts b/packages/astro/src/core/session/vite-plugin.ts index bcec01d8b3cc..d37499bc79c4 100644 --- a/packages/astro/src/core/session/vite-plugin.ts +++ b/packages/astro/src/core/session/vite-plugin.ts @@ -1,6 +1,10 @@ +import { fileURLToPath } from 'node:url'; + import { type BuiltinDriverName, builtinDrivers } from 'unstorage'; import type { Plugin as VitePlugin } from 'vite'; import type { AstroSettings } from '../../types/astro.js'; +import { SessionStorageInitError } from '../errors/errors-data.js'; +import { AstroError } from '../errors/index.js'; const VIRTUAL_SESSION_DRIVER_ID = 'virtual:astro:session-driver'; const RESOLVED_VIRTUAL_SESSION_DRIVER_ID = '\0' + VIRTUAL_SESSION_DRIVER_ID; @@ -12,25 +16,42 @@ export function vitePluginSessionDriver({ settings }: { settings: AstroSettings async resolveId(id) { if (id === VIRTUAL_SESSION_DRIVER_ID) { + return RESOLVED_VIRTUAL_SESSION_DRIVER_ID; + } + }, + + async load(id) { + if (id === RESOLVED_VIRTUAL_SESSION_DRIVER_ID) { if (settings.config.session) { + let sessionDriver: string; if (settings.config.session.driver === 'fs') { - return await this.resolve(builtinDrivers.fsLite); + sessionDriver = builtinDrivers.fsLite; + } else if ( + settings.config.session.driver && + settings.config.session.driver in builtinDrivers + ) { + sessionDriver = builtinDrivers[settings.config.session.driver as BuiltinDriverName]; + } else { + return { code: 'export default null;' }; } - if (settings.config.session.driver && settings.config.session.driver in builtinDrivers) { - return await this.resolve( - builtinDrivers[settings.config.session.driver as BuiltinDriverName], - ); + const importerPath = fileURLToPath(import.meta.url); + const resolved = await this.resolve(sessionDriver, importerPath); + if (!resolved) { + throw new AstroError({ + ...SessionStorageInitError, + message: SessionStorageInitError.message( + `Failed to resolve session driver: ${sessionDriver}`, + settings.config.session.driver, + ), + }); } + return { + code: `import { default as _default } from '${resolved.id}';\nexport * from '${resolved.id}';\nexport default _default;`, + }; } else { - return RESOLVED_VIRTUAL_SESSION_DRIVER_ID; + return { code: 'export default null;' }; } } }, - - async load(id) { - if (id === RESOLVED_VIRTUAL_SESSION_DRIVER_ID) { - return { code: 'export default null;' }; - } - }, }; } diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts index 9c9bfc6e35fb..53578f5fb5bc 100644 --- a/packages/integrations/cloudflare/src/index.ts +++ b/packages/integrations/cloudflare/src/index.ts @@ -27,6 +27,7 @@ import { import { createGetEnv } from './utils/env.js'; import { createRoutesFile, getParts } from './utils/generate-routes-json.js'; import { type ImageService, setImageConfig } from './utils/image-config.js'; +import { createConfigPlugin } from './vite-plugin-config.js'; export type { Runtime } from './utils/handler.js'; @@ -247,6 +248,9 @@ export default function createIntegration(args?: Options): AstroIntegration { } }, }, + createConfigPlugin({ + sessionKVBindingName: SESSION_KV_BINDING_NAME, + }), ], }, image: setImageConfig(args?.imageService ?? 'compile', config.image, command, logger), @@ -316,9 +320,6 @@ export default function createIntegration(args?: Options): AstroIntegration { setProcessEnv(_config, platformProxy.env); - globalThis.__env__ ??= {}; - globalThis.__env__[SESSION_KV_BINDING_NAME] = platformProxy.env[SESSION_KV_BINDING_NAME]; - const clientLocalsSymbol = Symbol.for('astro.locals'); server.middlewares.use(async function middleware(req, _res, next) { @@ -385,10 +386,6 @@ export default function createIntegration(args?: Options): AstroIntegration { // in a global way, so we shim their access as `process.env.*`. This is not the recommended way for users to access environment variables. But we'll add this for compatibility for chosen variables. Mainly to support `@astrojs/db` vite.define = { 'process.env': 'process.env', - // Allows the request handler to know what the binding name is - 'globalThis.__ASTRO_SESSION_BINDING_NAME': JSON.stringify( - args?.sessionKVBindingName ?? 'SESSION', - ), 'globalThis.__ASTRO_IMAGES_BINDING_NAME': JSON.stringify( args?.imagesBindingName ?? 'IMAGES', ), diff --git a/packages/integrations/cloudflare/src/utils/handler.ts b/packages/integrations/cloudflare/src/utils/handler.ts index d5f4bea6b84d..ed86826fd962 100644 --- a/packages/integrations/cloudflare/src/utils/handler.ts +++ b/packages/integrations/cloudflare/src/utils/handler.ts @@ -1,5 +1,6 @@ // @ts-expect-error - It is safe to expect the error here. import { env as globalEnv } from 'cloudflare:workers'; +import { sessionKVBindingName } from 'virtual:astro-cloudflare:config'; import type { Response as CfResponse, CacheStorage as CloudflareCacheStorage, @@ -27,14 +28,8 @@ export interface Runtime { } declare global { - // This is not a real global, but is injected using Vite define to allow us to specify the session binding name in the config. - var __ASTRO_SESSION_BINDING_NAME: string; - // This is not a real global, but is injected using Vite define to allow us to specify the Images binding name in the config. var __ASTRO_IMAGES_BINDING_NAME: string; - - // Just used to pass the KV binding to unstorage. - var __env__: Partial; } export async function handle( @@ -44,11 +39,13 @@ export async function handle( ): Promise { const app = createApp(import.meta.env.DEV); const { pathname } = new URL(request.url); - const bindingName = globalThis.__ASTRO_SESSION_BINDING_NAME; - // Assigning the KV binding to globalThis allows unstorage to access it for session storage. - // unstorage checks in globalThis and globalThis.__env__ for the binding. - globalThis.__env__ ??= {}; - globalThis.__env__[bindingName] = env[bindingName]; + + if (env[sessionKVBindingName]) { + const sessionConfigOptions = app.manifest.sessionConfig?.options ?? {}; + Object.assign(sessionConfigOptions, { + binding: env[sessionKVBindingName], + }); + } // static assets fallback, in case default _routes.json is not used if (app.manifest.assets.has(pathname)) { diff --git a/packages/integrations/cloudflare/src/vite-plugin-config.ts b/packages/integrations/cloudflare/src/vite-plugin-config.ts new file mode 100644 index 000000000000..b2d20b99599b --- /dev/null +++ b/packages/integrations/cloudflare/src/vite-plugin-config.ts @@ -0,0 +1,24 @@ +import type { PluginOption } from 'vite'; + +const VIRTUAL_CONFIG_ID = 'virtual:astro-cloudflare:config'; +const RESOLVED_VIRTUAL_CONFIG_ID = '\0' + VIRTUAL_CONFIG_ID; + +interface CloudflareConfig { + sessionKVBindingName: string; +} + +export function createConfigPlugin(config: CloudflareConfig): PluginOption { + return { + name: 'vite:astro-cloudflare-config', + resolveId(id) { + if (id === VIRTUAL_CONFIG_ID) { + return RESOLVED_VIRTUAL_CONFIG_ID; + } + }, + load(id) { + if (id === RESOLVED_VIRTUAL_CONFIG_ID) { + return `export const sessionKVBindingName = ${JSON.stringify(config.sessionKVBindingName)};`; + } + }, + }; +} diff --git a/packages/integrations/cloudflare/test/fixtures/vite-plugin/src/pages/sessions.astro b/packages/integrations/cloudflare/test/fixtures/vite-plugin/src/pages/sessions.astro index bab9ec21456b..4f711ad2148e 100644 --- a/packages/integrations/cloudflare/test/fixtures/vite-plugin/src/pages/sessions.astro +++ b/packages/integrations/cloudflare/test/fixtures/vite-plugin/src/pages/sessions.astro @@ -1,25 +1,44 @@ --- export const prerender = false; // Not needed with 'server' output const cart = await Astro.session?.get('cart'); -const user = await Astro.session?.get('user'); +let user = await Astro.session?.get('user'); + +if(Astro.request.method === 'POST') { + const data = await Astro.request.formData(); + const newId = data.get('id'); + const newName = data.get('name'); + + if(newId && newName) { + user = { + id: newId, + name: newName + }; + Astro.session?.set('user', user); + } +} --- -

- Sessions -

-

- Cart -

-

- 🛒 {cart?.length ?? 0} items -

+ + + + Sessions + + +

Sessions

+

Cart

+

+ 🛒 {cart?.length ?? 0} items +

+ +

User

+

Id: {user?.id}

+

Name: {user?.name}

-

- User -

-

- Id: {user?.id} -

-

- Name: {user?.name} -

+
+

Change

+ + + +
+ + diff --git a/packages/integrations/cloudflare/test/fixtures/vite-plugin/wrangler.jsonc b/packages/integrations/cloudflare/test/fixtures/vite-plugin/wrangler.jsonc index 43b8c3a8e78d..52ede3921909 100644 --- a/packages/integrations/cloudflare/test/fixtures/vite-plugin/wrangler.jsonc +++ b/packages/integrations/cloudflare/test/fixtures/vite-plugin/wrangler.jsonc @@ -11,5 +11,11 @@ }, "images": { "binding": "IMAGES" - } + }, + "kv_namespaces": [ + { + "binding": "SESSION", + "id": "SESSION" + } + ] } diff --git a/packages/integrations/cloudflare/tsconfig.json b/packages/integrations/cloudflare/tsconfig.json index 1504b4b6dfa4..ea1c17c2952b 100644 --- a/packages/integrations/cloudflare/tsconfig.json +++ b/packages/integrations/cloudflare/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "../../../tsconfig.base.json", - "include": ["src"], + "include": ["src", "virtual.d.ts"], "compilerOptions": { "outDir": "./dist" } diff --git a/packages/integrations/cloudflare/virtual.d.ts b/packages/integrations/cloudflare/virtual.d.ts new file mode 100644 index 000000000000..afc8ce716a0a --- /dev/null +++ b/packages/integrations/cloudflare/virtual.d.ts @@ -0,0 +1,4 @@ +declare module 'virtual:astro-cloudflare:config' { + export const sessionKVBindingName: string; + // Additional exports can be added here in the future +}