Skip to content

Commit 0d5a64b

Browse files
refactor(astro): streamline cookie handling and improve type definitions in index.ts
1 parent 344f7c9 commit 0d5a64b

File tree

1 file changed

+63
-130
lines changed

1 file changed

+63
-130
lines changed

src/astro/index.ts

Lines changed: 63 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,28 @@
11
import { betterFetch } from "@better-fetch/fetch";
2-
import type { betterAuth } from "better-auth";
2+
import { betterAuth } from "better-auth";
33
import { createCookieGetter } from "better-auth/cookies";
4+
import type { APIContext, AstroCookies } from "astro";
45
import { ConvexHttpClient } from "convex/browser";
56
import type {
67
FunctionReference,
78
FunctionReturnType,
89
GenericActionCtx,
910
GenericDataModel,
1011
} from "convex/server";
11-
import type { APIContext, AstroCookies } from "astro";
12-
import { type CreateAuth, getStaticAuth } from "../client";
1312
import { JWT_COOKIE_NAME } from "../plugins/convex";
13+
import { type CreateAuth, getStaticAuth } from "../client";
1414

15-
type CookieReader = (name: string) => string | undefined;
16-
type CookieSource = AstroCookies | CookieReader | undefined;
1715
type AstroRequestContext =
1816
| Pick<APIContext, "request" | "cookies">
19-
| {
20-
request: Request;
21-
cookies?: CookieSource;
22-
};
23-
type CookieInput =
24-
| CookieSource
17+
| { request: Request; cookies?: AstroCookies };
18+
19+
type CookieSource =
2520
| AstroRequestContext
21+
| AstroCookies
2622
| Request
2723
| Headers
2824
| string
29-
| {
30-
request?: Request;
31-
headers?: Headers | string;
32-
cookies?: CookieSource | string;
33-
cookie?: string;
34-
};
25+
| undefined;
3526

3627
const safeDecode = (value: string) => {
3728
try {
@@ -41,127 +32,74 @@ const safeDecode = (value: string) => {
4132
}
4233
};
4334

44-
const cookieReaderFromString = (cookieHeader: string): CookieReader => {
35+
const isAstroCookies = (value: unknown): value is AstroCookies =>
36+
!!value &&
37+
typeof value === "object" &&
38+
"get" in value &&
39+
typeof (value as { get?: unknown }).get === "function";
40+
41+
const readCookieFromHeader = (
42+
cookieHeader: string | null | undefined,
43+
name: string
44+
) => {
4545
if (!cookieHeader) {
46-
return () => undefined;
46+
return undefined;
4747
}
48-
const pairs = cookieHeader.split(/;\s*/).filter(Boolean);
49-
const store = new Map<string, string>();
50-
for (const pair of pairs) {
48+
for (const pair of cookieHeader.split(/;\s*/)) {
49+
if (!pair) {
50+
continue;
51+
}
5152
const separatorIndex = pair.indexOf("=");
5253
if (separatorIndex === -1) {
5354
continue;
5455
}
5556
const key = safeDecode(pair.slice(0, separatorIndex).trim());
56-
const value = safeDecode(pair.slice(separatorIndex + 1));
57-
if (!store.has(key)) {
58-
store.set(key, value);
59-
}
60-
}
61-
if (store.size === 0) {
62-
return () => undefined;
63-
}
64-
return (name) => {
65-
if (!name) {
66-
return undefined;
57+
if (key !== name) {
58+
continue;
6759
}
68-
return store.get(name);
69-
};
70-
};
71-
72-
const isAstroCookies = (source: unknown): source is AstroCookies => {
73-
if (!source || typeof source !== "object") {
74-
return false;
75-
}
76-
return (
77-
"get" in source &&
78-
typeof (source as { get?: unknown }).get === "function" &&
79-
"has" in source &&
80-
typeof (source as { has?: unknown }).has === "function" &&
81-
"merge" in source &&
82-
typeof (source as { merge?: unknown }).merge === "function"
83-
);
84-
};
85-
86-
const isHeadersLike = (source: unknown): source is Headers => {
87-
return typeof Headers !== "undefined" && source instanceof Headers;
88-
};
89-
90-
const isRequestLike = (source: unknown): source is Request => {
91-
if (typeof Request !== "undefined" && source instanceof Request) {
92-
return true;
60+
const rawValue = pair.slice(separatorIndex + 1);
61+
return safeDecode(rawValue);
9362
}
94-
if (!source || typeof source !== "object") {
95-
return false;
96-
}
97-
return (
98-
"headers" in source &&
99-
typeof (source as { headers?: unknown }).headers !== "undefined" &&
100-
"method" in source
101-
);
63+
return undefined;
10264
};
10365

104-
const normalizeCookieSource = (source: CookieInput): CookieSource => {
66+
const readCookie = (source: CookieSource, name: string): string | undefined => {
10567
if (!source) {
10668
return undefined;
10769
}
108-
if (typeof source === "function") {
109-
return source;
110-
}
111-
if (isAstroCookies(source)) {
112-
return source;
70+
if (typeof source === "string") {
71+
return readCookieFromHeader(source, name);
11372
}
114-
if (isRequestLike(source)) {
115-
return normalizeCookieSource((source as Request).headers);
73+
if (source instanceof Headers) {
74+
return readCookieFromHeader(source.get("cookie"), name);
11675
}
117-
if (isHeadersLike(source)) {
118-
const cookieHeader = (source as Headers).get("cookie") ?? "";
119-
return cookieHeader ? cookieReaderFromString(cookieHeader) : undefined;
76+
if (source instanceof Request) {
77+
return readCookie(source.headers, name);
12078
}
121-
if (typeof source === "string") {
122-
return cookieReaderFromString(source);
79+
if (isAstroCookies(source)) {
80+
const cookie = source.get(name);
81+
return typeof cookie?.value === "string" ? cookie.value : undefined;
12382
}
124-
if (typeof source === "object") {
125-
if ("cookies" in source && source.cookies) {
126-
const normalized = normalizeCookieSource(source.cookies as CookieInput);
127-
if (normalized) {
128-
return normalized;
129-
}
130-
}
131-
if ("request" in source && source.request) {
132-
const normalized = normalizeCookieSource(source.request as CookieInput);
133-
if (normalized) {
134-
return normalized;
135-
}
136-
}
137-
if ("headers" in source && source.headers) {
138-
const normalized = normalizeCookieSource(source.headers as CookieInput);
139-
if (normalized) {
140-
return normalized;
141-
}
83+
if (
84+
typeof source === "object" &&
85+
source !== null &&
86+
"cookies" in source &&
87+
source.cookies
88+
) {
89+
const fromStore = readCookie(source.cookies, name);
90+
if (fromStore) {
91+
return fromStore;
14292
}
143-
if ("cookie" in source && typeof source.cookie === "string") {
144-
const normalized = normalizeCookieSource(source.cookie);
145-
if (normalized) {
146-
return normalized;
147-
}
148-
}
149-
}
150-
return undefined;
151-
};
152-
153-
const createCookieReader = (source: CookieInput): CookieReader => {
154-
const normalized = normalizeCookieSource(source);
155-
if (typeof normalized === "function") {
156-
return normalized;
15793
}
158-
if (normalized && typeof normalized.get === "function") {
159-
return (name) => {
160-
const cookie = normalized.get(name) as { value?: string } | undefined;
161-
return typeof cookie?.value === "string" ? cookie.value : undefined;
162-
};
94+
if (
95+
typeof source === "object" &&
96+
source !== null &&
97+
"request" in source &&
98+
source.request
99+
) {
100+
return readCookie(source.request, name);
163101
}
164-
return () => undefined;
102+
return undefined;
165103
};
166104

167105
export const getCookieName = <DataModel extends GenericDataModel>(
@@ -174,24 +112,23 @@ export const getCookieName = <DataModel extends GenericDataModel>(
174112

175113
export const getToken = <DataModel extends GenericDataModel>(
176114
createAuth: CreateAuth<DataModel>,
177-
cookies?: CookieInput
115+
cookies?: CookieSource
178116
) => {
179117
const sessionCookieName = getCookieName(createAuth);
180-
const readCookie = createCookieReader(cookies);
181-
const token = readCookie(sessionCookieName);
118+
const token = readCookie(cookies, sessionCookieName);
182119

183120
if (!token) {
184121
const isSecure = sessionCookieName.startsWith("__Secure-");
185122
const insecureCookieName = sessionCookieName.replace("__Secure-", "");
186123
const secureCookieName = isSecure
187124
? sessionCookieName
188125
: `__Secure-${insecureCookieName}`;
189-
const secureToken = readCookie(secureCookieName);
190-
const insecureToken = readCookie(insecureCookieName);
126+
const secureToken = readCookie(cookies, secureCookieName);
127+
const insecureToken = readCookie(cookies, insecureCookieName);
191128

192129
if (isSecure && insecureToken) {
193130
console.warn(
194-
`Looking for secure cookie ${sessionCookieName} but found insecure cookie ${sessionCookieName.replace("__Secure-", "")}`
131+
`Looking for secure cookie ${sessionCookieName} but found insecure cookie ${insecureCookieName}`
195132
);
196133
}
197134
if (!isSecure && secureToken) {
@@ -206,18 +143,16 @@ export const getToken = <DataModel extends GenericDataModel>(
206143

207144
export const setupFetchClient = async <DataModel extends GenericDataModel>(
208145
createAuth: CreateAuth<DataModel>,
209-
cookies?: CookieInput,
146+
cookies?: CookieSource,
210147
opts?: { convexUrl?: string }
211148
) => {
212-
const readCookie = createCookieReader(cookies);
213149
const createClient = () => {
214150
const convexUrl = opts?.convexUrl ?? process.env.VITE_CONVEX_URL;
215151
if (!convexUrl) {
216152
throw new Error("VITE_CONVEX_URL is not set");
217153
}
218-
const sessionCookieName = getCookieName(createAuth);
219-
const token = readCookie(sessionCookieName);
220154
const client = new ConvexHttpClient(convexUrl);
155+
const token = getToken(createAuth, cookies);
221156
if (token) {
222157
client.setAuth(token);
223158
}
@@ -295,9 +230,7 @@ export const getAuth = async <DataModel extends GenericDataModel>(
295230
if (!request) {
296231
throw new Error("No request found");
297232
}
298-
const readCookie = createCookieReader(context);
299-
const sessionCookieName = getCookieName(createAuth);
300-
const token = readCookie(sessionCookieName);
233+
const token = getToken(createAuth, context);
301234
const { session } = await fetchSession(request, opts);
302235
return {
303236
userId: session?.user.id,

0 commit comments

Comments
 (0)