From 4589351740f3913624ec27ba170e2a7fdaf13e8b Mon Sep 17 00:00:00 2001 From: JAD3N Date: Mon, 8 Jan 2024 15:50:41 +0000 Subject: [PATCH 1/4] Add Session Locks --- src/types.ts | 1 + src/utils/session.ts | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/types.ts b/src/types.ts index 1c664df3..386fc3a6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -44,6 +44,7 @@ export interface H3EventContext extends Record { matchedRoute?: RouteNode; /* Cached session data */ sessions?: Record; + sessionLocks?: Record | undefined>; /* Trusted IP Address of client */ clientAddress?: string; } diff --git a/src/utils/session.ts b/src/utils/session.ts index 6178ca97..c55ca025 100644 --- a/src/utils/session.ts +++ b/src/utils/session.ts @@ -74,6 +74,13 @@ export async function getSession( if (!event.context.sessions) { event.context.sessions = Object.create(null); } + if (!event.context.sessionLocks) { + event.context.sessionLocks = Object.create(null); + } + // Wait for existing session to load + if (event.context.sessionLocks![sessionName]) { + await event.context.sessionLocks![sessionName]; + } if (event.context.sessions![sessionName]) { return event.context.sessions![sessionName] as Session; } @@ -105,10 +112,16 @@ export async function getSession( } if (sealedSession) { // Unseal session data from cookie - const unsealed = await unsealSession(event, config, sealedSession).catch( - () => {}, - ); - Object.assign(session, unsealed); + const lock = unsealSession(event, config, sealedSession) + .catch(() => {}) + .then((unsealed) => { + Object.assign(session, unsealed); + // make sure deletion occurs before promise resolves + delete event.context.sessionLocks![sessionName]; + }); + + event.context.sessionLocks![sessionName] = lock; + await lock; } // New session store in response cookies From 020df646cd37ad7a99f3fcaada688797bf5c7825 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Mon, 15 Jan 2024 21:39:54 +0100 Subject: [PATCH 2/4] add test --- test/session.test.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/session.test.ts b/test/session.test.ts index e9832b86..b4c19fa8 100644 --- a/test/session.test.ts +++ b/test/session.test.ts @@ -72,4 +72,27 @@ describe("session", () => { session: { id: "1", data: { foo: "bar" } }, }); }); + + it("gets same session back (concurrent)", async () => { + router.use( + "/concurrent", + eventHandler(async (event) => { + const sessions = await Promise.all( + [1, 2, 3].map(() => + useSession(event, sessionConfig).then((s) => ({ + id: s.id, + data: s.data, + })), + ), + ); + return { + sessions, + }; + }), + ); + const result = await request.get("/concurrent").set("Cookie", cookie); + expect(result.body).toMatchObject({ + sessions: [1, 2, 3].map(() => ({ id: "1", data: { foo: "bar" } })), + }); + }); }); From cef84591123a583b99a6c9b2dfc1219e92a8624d Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Mon, 15 Jan 2024 22:04:19 +0100 Subject: [PATCH 3/4] refactor: reuse same context object --- src/types.ts | 1 - src/utils/session.ts | 26 ++++++++++++-------------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/types.ts b/src/types.ts index 386fc3a6..1c664df3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -44,7 +44,6 @@ export interface H3EventContext extends Record { matchedRoute?: RouteNode; /* Cached session data */ sessions?: Record; - sessionLocks?: Record | undefined>; /* Trusted IP Address of client */ clientAddress?: string; } diff --git a/src/utils/session.ts b/src/utils/session.ts index c55ca025..c22bb8ed 100644 --- a/src/utils/session.ts +++ b/src/utils/session.ts @@ -8,10 +8,13 @@ import { getCookie, setCookie } from "./cookie"; type SessionDataT = Record; export type SessionData = T; +const getSessionPromise = Symbol("getSession"); + export interface Session { id: string; createdAt: number; data: SessionData; + [getSessionPromise]?: Promise>; } export interface SessionConfig { @@ -74,15 +77,11 @@ export async function getSession( if (!event.context.sessions) { event.context.sessions = Object.create(null); } - if (!event.context.sessionLocks) { - event.context.sessionLocks = Object.create(null); - } // Wait for existing session to load - if (event.context.sessionLocks![sessionName]) { - await event.context.sessionLocks![sessionName]; - } - if (event.context.sessions![sessionName]) { - return event.context.sessions![sessionName] as Session; + const existingSession = event.context.sessions![sessionName] as Session; + if (existingSession) { + await existingSession[getSessionPromise]; + return existingSession; } // Prepare an empty session object and store in context @@ -112,16 +111,15 @@ export async function getSession( } if (sealedSession) { // Unseal session data from cookie - const lock = unsealSession(event, config, sealedSession) + const promise = unsealSession(event, config, sealedSession) .catch(() => {}) .then((unsealed) => { Object.assign(session, unsealed); - // make sure deletion occurs before promise resolves - delete event.context.sessionLocks![sessionName]; + delete event.context.sessions![sessionName][getSessionPromise]; + return session as Session; }); - - event.context.sessionLocks![sessionName] = lock; - await lock; + event.context.sessions![sessionName][getSessionPromise] = promise; + await promise; } // New session store in response cookies From cfdacf3291f3762a8034a2e60fedcbd9a73c6275 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Mon, 15 Jan 2024 22:05:16 +0100 Subject: [PATCH 4/4] reuse promise --- src/utils/session.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/utils/session.ts b/src/utils/session.ts index c22bb8ed..e3b726ed 100644 --- a/src/utils/session.ts +++ b/src/utils/session.ts @@ -80,8 +80,7 @@ export async function getSession( // Wait for existing session to load const existingSession = event.context.sessions![sessionName] as Session; if (existingSession) { - await existingSession[getSessionPromise]; - return existingSession; + return existingSession[getSessionPromise] || existingSession; } // Prepare an empty session object and store in context