+84
-28
netlify/functions/session.ts
+84
-28
netlify/functions/session.ts
···
13
13
return key;
14
14
}
15
15
16
-
// In-memory cache for profile data (lives for the function instance lifetime)
16
+
// ENHANCED: Two-tier cache system
17
+
// Tier 1: In-memory cache for profile data (lives for function instance)
17
18
const profileCache = new Map<string, { data: any; timestamp: number }>();
18
-
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
19
+
const PROFILE_CACHE_TTL = 5 * 60 * 1000; // 5 minutes
20
+
21
+
// Tier 2: Session metadata cache (DID -> basic info, faster than full OAuth restore)
22
+
const sessionMetadataCache = new Map<string, {
23
+
did: string;
24
+
lastSeen: number;
25
+
profileFetchNeeded: boolean;
26
+
}>();
19
27
20
28
export const handler: Handler = async (event: HandlerEvent): Promise<HandlerResponse> => {
21
29
try {
···
30
38
};
31
39
}
32
40
33
-
// Get the DID from our simple session store
34
-
const userSession = await userSessions.get(sessionId);
35
-
if (!userSession) {
36
-
return {
37
-
statusCode: 401,
38
-
headers: { 'Content-Type': 'application/json' },
39
-
body: JSON.stringify({ error: 'Invalid or expired session' }),
40
-
};
41
+
// OPTIMIZATION: Check session metadata cache first (avoids DB query)
42
+
const cachedMetadata = sessionMetadataCache.get(sessionId);
43
+
const now = Date.now();
44
+
45
+
let did: string;
46
+
47
+
if (cachedMetadata && (now - cachedMetadata.lastSeen < 60000)) {
48
+
// Session seen within last minute, trust the cache
49
+
did = cachedMetadata.did;
50
+
console.log('Session metadata from cache');
51
+
} else {
52
+
// Need to verify session from database
53
+
const userSession = await userSessions.get(sessionId);
54
+
if (!userSession) {
55
+
// Clear stale cache entry
56
+
sessionMetadataCache.delete(sessionId);
57
+
return {
58
+
statusCode: 401,
59
+
headers: { 'Content-Type': 'application/json' },
60
+
body: JSON.stringify({ error: 'Invalid or expired session' }),
61
+
};
62
+
}
63
+
64
+
did = userSession.did;
65
+
66
+
// Update session metadata cache
67
+
sessionMetadataCache.set(sessionId, {
68
+
did,
69
+
lastSeen: now,
70
+
profileFetchNeeded: true
71
+
});
72
+
73
+
// Cleanup: Remove old session metadata entries
74
+
if (sessionMetadataCache.size > 200) {
75
+
for (const [sid, meta] of sessionMetadataCache.entries()) {
76
+
if (now - meta.lastSeen > 300000) { // 5 minutes
77
+
sessionMetadataCache.delete(sid);
78
+
}
79
+
}
80
+
}
41
81
}
42
82
43
-
// Check cache first
44
-
const cached = profileCache.get(userSession.did);
45
-
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
46
-
console.log('Returning cached profile for', userSession.did);
83
+
// Check profile cache (Tier 1)
84
+
const cached = profileCache.get(did);
85
+
if (cached && now - cached.timestamp < PROFILE_CACHE_TTL) {
86
+
console.log('Returning cached profile for', did);
87
+
88
+
// Update session metadata last seen
89
+
const meta = sessionMetadataCache.get(sessionId);
90
+
if (meta) {
91
+
meta.lastSeen = now;
92
+
}
93
+
47
94
return {
48
95
statusCode: 200,
49
96
headers: {
50
97
'Content-Type': 'application/json',
51
98
'Access-Control-Allow-Origin': '*',
52
99
'Cache-Control': 'private, max-age=300', // Browser can cache for 5 minutes
100
+
'X-Cache-Status': 'HIT'
53
101
},
54
102
body: JSON.stringify(cached.data),
55
103
};
56
104
}
57
105
58
-
// If not in cache, fetch full profile
106
+
// Cache miss - fetch full profile
59
107
try {
60
108
const config = getOAuthConfig();
61
109
const normalizedKey = normalizePrivateKey(process.env.OAUTH_PRIVATE_KEY!);
···
82
130
});
83
131
84
132
// Restore OAuth session
85
-
const oauthSession = await client.restore(userSession.did);
133
+
const oauthSession = await client.restore(did);
86
134
87
135
// Create agent from OAuth session
88
136
const agent = new Agent(oauthSession);
89
137
90
138
// Get profile
91
-
const profile = await agent.getProfile({ actor: userSession.did });
139
+
const profile = await agent.getProfile({ actor: did });
92
140
93
141
const profileData = {
94
-
did: userSession.did,
142
+
did: did,
95
143
handle: profile.data.handle,
96
144
displayName: profile.data.displayName,
97
145
avatar: profile.data.avatar,
98
146
description: profile.data.description,
99
147
};
100
148
101
-
// Cache the profile data
102
-
profileCache.set(userSession.did, {
149
+
// Cache the profile data (Tier 1)
150
+
profileCache.set(did, {
103
151
data: profileData,
104
-
timestamp: Date.now(),
152
+
timestamp: now,
105
153
});
106
154
107
-
// Clean up old cache entries (simple cleanup)
155
+
// Update session metadata (Tier 2)
156
+
const meta = sessionMetadataCache.get(sessionId);
157
+
if (meta) {
158
+
meta.lastSeen = now;
159
+
meta.profileFetchNeeded = false;
160
+
}
161
+
162
+
// Clean up old profile cache entries
108
163
if (profileCache.size > 100) {
109
-
const now = Date.now();
110
-
for (const [did, entry] of profileCache.entries()) {
111
-
if (now - entry.timestamp > CACHE_TTL) {
112
-
profileCache.delete(did);
164
+
for (const [cachedDid, entry] of profileCache.entries()) {
165
+
if (now - entry.timestamp > PROFILE_CACHE_TTL) {
166
+
profileCache.delete(cachedDid);
113
167
}
114
168
}
115
169
}
···
119
173
headers: {
120
174
'Content-Type': 'application/json',
121
175
'Access-Control-Allow-Origin': '*',
122
-
'Cache-Control': 'private, max-age=300', // Browser can cache for 5 minutes
176
+
'Cache-Control': 'private, max-age=300',
177
+
'X-Cache-Status': 'MISS'
123
178
},
124
179
body: JSON.stringify(profileData),
125
180
};
···
132
187
headers: {
133
188
'Content-Type': 'application/json',
134
189
'Access-Control-Allow-Origin': '*',
190
+
'X-Cache-Status': 'ERROR'
135
191
},
136
192
body: JSON.stringify({
137
-
did: userSession.did,
193
+
did: did,
138
194
// Profile data unavailable
139
195
}),
140
196
};