diff --git a/docs/4.examples/handle-session.md b/docs/4.examples/handle-session.md index 8713ab7c..025ddd21 100644 --- a/docs/4.examples/handle-session.md +++ b/docs/4.examples/handle-session.md @@ -37,7 +37,7 @@ This will initialize a session and return an header `Set-Cookie` with a cookie n If the request contains a cookie named `h3` or a header named `x-h3-session`, the session will be initialized with the content of the cookie or the header. > [!NOTE] -> The header take precedence over the cookie. +> The header takes precedence over the cookie. ## Get Data from a Session @@ -86,6 +86,8 @@ We try to get a session from the request. If there is no session, a new one will Try to visit the page multiple times and you will see the number of times you visited the page. +If a `maxAge` is configured, the expiration date of the session will not be updated with a call to `update` on the session object, nor with a call using the `updateSession` utility. + > [!NOTE] > If you use a CLI tool like `curl` to test this example, you will not see the number of times you visited the page because the CLI tool does not save cookies. You must get the cookie from the response and send it back to the server. diff --git a/src/utils/session.ts b/src/utils/session.ts index 30efe037..50a24b7f 100644 --- a/src/utils/session.ts +++ b/src/utils/session.ts @@ -8,6 +8,27 @@ import { } from "./internal/session"; import { EmptyObject } from "./internal/obj"; +/** + * Get the session name from the config. + */ +function getSessionName(config: SessionConfig) { + return config.name || DEFAULT_SESSION_NAME; +} + +/** + * Generate the session id from the config. + */ +function generateId(config: SessionConfig) { + return config.generateId?.() ?? (config.crypto || crypto).randomUUID(); +} + +/** + * Get the max age TTL in ms from the config. + */ +function getMaxAgeTTL(config: SessionConfig) { + return config.maxAge ? config.maxAge * 1000 : 0; +} + /** * Create a session manager for the current request. * @@ -17,7 +38,7 @@ export async function useSession( config: SessionConfig, ) { // Create a synced wrapper around the session - const sessionName = config.name || DEFAULT_SESSION_NAME; + const sessionName = getSessionName(config); await getSession(event, config); // Force init const sessionManager = { get id() { @@ -45,7 +66,7 @@ export async function getSession( event: H3Event, config: SessionConfig, ): Promise> { - const sessionName = config.name || DEFAULT_SESSION_NAME; + const sessionName = getSessionName(config); // Return existing session if available if (!event.context.sessions) { @@ -97,8 +118,7 @@ export async function getSession( // New session store in response cookies if (!session.id) { - session.id = - config.generateId?.() ?? (config.crypto || crypto).randomUUID(); + session.id = generateId(config); session.createdAt = Date.now(); await updateSession(event, config); } @@ -118,12 +138,12 @@ export async function updateSession( config: SessionConfig, update?: SessionUpdate, ): Promise> { - const sessionName = config.name || DEFAULT_SESSION_NAME; + const sessionName = getSessionName(config); // Access current session const session: Session = (event.context.sessions?.[sessionName] as Session) || - (await getSession(event, config)); + (await getSession(event, config)); // Update session data if provided if (typeof update === "function") { @@ -139,7 +159,7 @@ export async function updateSession( setCookie(event, sessionName, sealed, { ...DEFAULT_SESSION_COOKIE, expires: config.maxAge - ? new Date(session.createdAt + config.maxAge * 1000) + ? new Date(session.createdAt + getMaxAgeTTL(config)) : undefined, ...config.cookie, }); @@ -155,7 +175,7 @@ export async function sealSession( event: H3Event, config: SessionConfig, ) { - const sessionName = config.name || DEFAULT_SESSION_NAME; + const sessionName = getSessionName(config); // Access current session const session: Session = @@ -164,7 +184,7 @@ export async function sealSession( const sealed = await seal(session, config.password, { ...sealDefaults, - ttl: config.maxAge ? config.maxAge * 1000 : 0, + ttl: getMaxAgeTTL(config), ...config.seal, }); @@ -181,12 +201,12 @@ export async function unsealSession( ) { const unsealed = (await unseal(sealed, config.password, { ...sealDefaults, - ttl: config.maxAge ? config.maxAge * 1000 : 0, + ttl: getMaxAgeTTL(config), ...config.seal, })) as Partial; if (config.maxAge) { const age = Date.now() - (unsealed.createdAt || Number.NEGATIVE_INFINITY); - if (age > config.maxAge * 1000) { + if (age > getMaxAgeTTL(config)) { throw new Error("Session expired!"); } } @@ -198,12 +218,21 @@ export async function unsealSession( */ export function clearSession( event: H3Event, - config: Partial, + config: SessionConfig, ): Promise { - const sessionName = config.name || DEFAULT_SESSION_NAME; - if (event.context.sessions?.[sessionName]) { - delete event.context.sessions![sessionName]; + const sessionName = getSessionName(config); + if (!event.context.sessions) { + event.context.sessions = new EmptyObject(); } + + // Clobber the original session with a new session + event.context.sessions![sessionName] = { + id: generateId(config), + createdAt: Date.now(), + data: new EmptyObject(), + } satisfies Session; + + // Set a cleared session cookie setCookie(event, sessionName, "", { ...DEFAULT_SESSION_COOKIE, ...config.cookie,