Skip to content

Commit

Permalink
feat: prototype session support (#12471)
Browse files Browse the repository at this point in the history
* feat: add session object

* Add tests and fix logic

* Fixes

* Allow string as cookie option

* wip: implement sessions (#12478)

* feat: implement sessions

* Add middleware

* Action middleware test

* Support URLs

* Remove comment

* Changes from review

* Update test

* Ensure test file is run
  • Loading branch information
ascorbic authored Nov 21, 2024
1 parent 31aa5eb commit d96c507
Show file tree
Hide file tree
Showing 23 changed files with 1,138 additions and 55 deletions.
9 changes: 9 additions & 0 deletions packages/astro/src/core/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { RenderContext } from '../render-context.js';
import { createAssetLink } from '../render/ssr-element.js';
import { createDefaultRoutes, injectDefaultRoutes } from '../routing/default.js';
import { matchRoute } from '../routing/match.js';
import { type AstroSession, PERSIST_SYMBOL } from '../session.js';
import { AppPipeline } from './pipeline.js';

export { deserializeManifest } from './common.js';
Expand Down Expand Up @@ -277,6 +278,7 @@ export class App {
const defaultStatus = this.#getDefaultStatusCode(routeData, pathname);

let response;
let session: AstroSession | undefined;
try {
// Load route module. We also catch its error here if it fails on initialization
const mod = await this.#pipeline.getModuleForRoute(routeData);
Expand All @@ -289,10 +291,13 @@ export class App {
routeData,
status: defaultStatus,
});
session = renderContext.session;
response = await renderContext.render(await mod.page());
} catch (err: any) {
this.#logger.error(null, err.stack || err.message || String(err));
return this.#renderError(request, { locals, status: 500, error: err });
} finally {
session?.[PERSIST_SYMBOL]();
}

if (
Expand Down Expand Up @@ -376,6 +381,7 @@ export class App {
}
}
const mod = await this.#pipeline.getModuleForRoute(errorRouteData);
let session: AstroSession | undefined;
try {
const renderContext = await RenderContext.create({
locals,
Expand All @@ -387,6 +393,7 @@ export class App {
status,
props: { error },
});
session = renderContext.session;
const response = await renderContext.render(await mod.page());
return this.#mergeResponses(response, originalResponse);
} catch {
Expand All @@ -399,6 +406,8 @@ export class App {
skipMiddleware: true,
});
}
} finally {
session?.[PERSIST_SYMBOL]();
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/astro/src/core/app/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { RoutingStrategies } from '../../i18n/utils.js';
import type { ComponentInstance, SerializedRouteData } from '../../types/astro.js';
import type { AstroMiddlewareInstance } from '../../types/public/common.js';
import type { Locales } from '../../types/public/config.js';
import type { Locales, SessionConfig } from '../../types/public/config.js';
import type {
RouteData,
SSRComponentMetadata,
Expand Down Expand Up @@ -70,6 +70,7 @@ export type SSRManifest = {
middleware?: () => Promise<AstroMiddlewareInstance> | AstroMiddlewareInstance;
checkOrigin: boolean;
envGetSecretEnabled: boolean;
sessionConfig?: SessionConfig<any>;
};

export type SSRManifestI18n = {
Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/build/plugins/plugin-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,5 +277,6 @@ function buildManifest(
envGetSecretEnabled:
(unwrapSupportKind(settings.adapter?.supportedAstroFeatures.envGetSecret) ??
'unsupported') !== 'unsupported',
sessionConfig: settings.config.experimental.session,
};
}
28 changes: 17 additions & 11 deletions packages/astro/src/core/config/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -541,17 +541,23 @@ export const AstroConfigSchema = z.object({
.object({
driver: z.string(),
options: z.record(z.any()).optional(),
cookieName: z.string().optional(),
cookieOptions: z
.object({
domain: z.string().optional(),
path: z.string().optional(),
expires: z.string().optional(),
maxAge: z.number().optional(),
httpOnly: z.boolean().optional(),
sameSite: z.string().optional(),
secure: z.boolean().optional(),
encode: z.string().optional(),
cookie: z
.union([
z.object({
name: z.string().optional(),
domain: z.string().optional(),
path: z.string().optional(),
maxAge: z.number().optional(),
sameSite: z.union([z.enum(['strict', 'lax', 'none']), z.boolean()]).optional(),
secure: z.boolean().optional(),
}),
z.string(),
])
.transform((val) => {
if (typeof val === 'string') {
return { name: val };
}
return val;
})
.optional(),
})
Expand Down
30 changes: 30 additions & 0 deletions packages/astro/src/core/errors/errors-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,36 @@ export const AstroResponseHeadersReassigned = {
hint: 'Consider using `Astro.response.headers.add()`, and `Astro.response.headers.delete()`.',
} satisfies ErrorData;

/**
* @docs
* @see
* - [experimental.session](https://5-0-0-beta.docs.astro.build/en/reference/configuration-reference/#experimentalsession)
* @description
* Thrown when the session storage could not be initialized.
*/
export const SessionStorageInitError = {
name: 'SessionStorageInitError',
title: 'Session storage could not be initialized.',
message: (error: string, driver?: string) =>
`Error when initializing session storage${driver ? ` with driver ${driver}` : ''}. ${error ?? ''}`,
hint: 'For more information, see https://5-0-0-beta.docs.astro.build/en/reference/configuration-reference/#experimentalsession',
} satisfies ErrorData;

/**
* @docs
* @see
* - [experimental.session](https://5-0-0-beta.docs.astro.build/en/reference/configuration-reference/#experimentalsession)
* @description
* Thrown when the session data could not be saved.
*/
export const SessionStorageSaveError = {
name: 'SessionStorageSaveError',
title: 'Session data could not be saved.',
message: (error: string, driver?: string) =>
`Error when saving session data${driver ? ` with driver ${driver}` : ''}. ${error ?? ''}`,
hint: 'For more information, see https://5-0-0-beta.docs.astro.build/en/reference/configuration-reference/#experimentalsession',
} satisfies ErrorData;

/**
* @docs
* @description
Expand Down
10 changes: 8 additions & 2 deletions packages/astro/src/core/render-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { renderRedirect } from './redirects/render.js';
import { type Pipeline, Slots, getParams, getProps } from './render/index.js';
import { copyRequest, getOriginPathname, setOriginPathname } from './routing/rewrite.js';
import { SERVER_ISLAND_COMPONENT } from './server-islands/endpoint.js';
import { AstroSession } from './session.js';

export const apiContextRoutesSymbol = Symbol.for('context.routes');

Expand All @@ -52,6 +53,9 @@ export class RenderContext {
protected url = new URL(request.url),
public props: Props = {},
public partial: undefined | boolean = undefined,
public session: AstroSession | undefined = pipeline.manifest.sessionConfig
? new AstroSession(cookies, pipeline.manifest.sessionConfig)
: undefined,
) {}

/**
Expand Down Expand Up @@ -296,7 +300,7 @@ export class RenderContext {

createActionAPIContext(): ActionAPIContext {
const renderContext = this;
const { cookies, params, pipeline, url } = this;
const { cookies, params, pipeline, url, session } = this;
const generator = `Astro v${ASTRO_VERSION}`;

const rewrite = async (reroutePayload: RewritePayload) => {
Expand Down Expand Up @@ -334,6 +338,7 @@ export class RenderContext {
get originPathname() {
return getOriginPathname(renderContext.request);
},
session,
};
}

Expand Down Expand Up @@ -466,7 +471,7 @@ export class RenderContext {
astroStaticPartial: AstroGlobalPartial,
): Omit<AstroGlobal, 'props' | 'self' | 'slots'> {
const renderContext = this;
const { cookies, locals, params, pipeline, url } = this;
const { cookies, locals, params, pipeline, url, session } = this;
const { response } = result;
const redirect = (path: string, status = 302) => {
// If the response is already sent, error as we cannot proceed with the redirect.
Expand All @@ -488,6 +493,7 @@ export class RenderContext {
routePattern: this.routeData.route,
isPrerendered: this.routeData.prerender,
cookies,
session,
get clientAddress() {
return renderContext.clientAddress();
},
Expand Down
Loading

0 comments on commit d96c507

Please sign in to comment.