+84
-28
netlify/functions/session.ts
+84
-28
netlify/functions/session.ts
···
13
return key;
14
}
15
16
-
// In-memory cache for profile data (lives for the function instance lifetime)
17
const profileCache = new Map<string, { data: any; timestamp: number }>();
18
-
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
19
20
export const handler: Handler = async (event: HandlerEvent): Promise<HandlerResponse> => {
21
try {
···
30
};
31
}
32
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
}
42
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);
47
return {
48
statusCode: 200,
49
headers: {
50
'Content-Type': 'application/json',
51
'Access-Control-Allow-Origin': '*',
52
'Cache-Control': 'private, max-age=300', // Browser can cache for 5 minutes
53
},
54
body: JSON.stringify(cached.data),
55
};
56
}
57
58
-
// If not in cache, fetch full profile
59
try {
60
const config = getOAuthConfig();
61
const normalizedKey = normalizePrivateKey(process.env.OAUTH_PRIVATE_KEY!);
···
82
});
83
84
// Restore OAuth session
85
-
const oauthSession = await client.restore(userSession.did);
86
87
// Create agent from OAuth session
88
const agent = new Agent(oauthSession);
89
90
// Get profile
91
-
const profile = await agent.getProfile({ actor: userSession.did });
92
93
const profileData = {
94
-
did: userSession.did,
95
handle: profile.data.handle,
96
displayName: profile.data.displayName,
97
avatar: profile.data.avatar,
98
description: profile.data.description,
99
};
100
101
-
// Cache the profile data
102
-
profileCache.set(userSession.did, {
103
data: profileData,
104
-
timestamp: Date.now(),
105
});
106
107
-
// Clean up old cache entries (simple cleanup)
108
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);
113
}
114
}
115
}
···
119
headers: {
120
'Content-Type': 'application/json',
121
'Access-Control-Allow-Origin': '*',
122
-
'Cache-Control': 'private, max-age=300', // Browser can cache for 5 minutes
123
},
124
body: JSON.stringify(profileData),
125
};
···
132
headers: {
133
'Content-Type': 'application/json',
134
'Access-Control-Allow-Origin': '*',
135
},
136
body: JSON.stringify({
137
-
did: userSession.did,
138
// Profile data unavailable
139
}),
140
};
···
13
return key;
14
}
15
16
+
// ENHANCED: Two-tier cache system
17
+
// Tier 1: In-memory cache for profile data (lives for function instance)
18
const profileCache = new Map<string, { data: any; timestamp: number }>();
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
+
}>();
27
28
export const handler: Handler = async (event: HandlerEvent): Promise<HandlerResponse> => {
29
try {
···
38
};
39
}
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
+
}
81
}
82
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
+
94
return {
95
statusCode: 200,
96
headers: {
97
'Content-Type': 'application/json',
98
'Access-Control-Allow-Origin': '*',
99
'Cache-Control': 'private, max-age=300', // Browser can cache for 5 minutes
100
+
'X-Cache-Status': 'HIT'
101
},
102
body: JSON.stringify(cached.data),
103
};
104
}
105
106
+
// Cache miss - fetch full profile
107
try {
108
const config = getOAuthConfig();
109
const normalizedKey = normalizePrivateKey(process.env.OAUTH_PRIVATE_KEY!);
···
130
});
131
132
// Restore OAuth session
133
+
const oauthSession = await client.restore(did);
134
135
// Create agent from OAuth session
136
const agent = new Agent(oauthSession);
137
138
// Get profile
139
+
const profile = await agent.getProfile({ actor: did });
140
141
const profileData = {
142
+
did: did,
143
handle: profile.data.handle,
144
displayName: profile.data.displayName,
145
avatar: profile.data.avatar,
146
description: profile.data.description,
147
};
148
149
+
// Cache the profile data (Tier 1)
150
+
profileCache.set(did, {
151
data: profileData,
152
+
timestamp: now,
153
});
154
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
163
if (profileCache.size > 100) {
164
+
for (const [cachedDid, entry] of profileCache.entries()) {
165
+
if (now - entry.timestamp > PROFILE_CACHE_TTL) {
166
+
profileCache.delete(cachedDid);
167
}
168
}
169
}
···
173
headers: {
174
'Content-Type': 'application/json',
175
'Access-Control-Allow-Origin': '*',
176
+
'Cache-Control': 'private, max-age=300',
177
+
'X-Cache-Status': 'MISS'
178
},
179
body: JSON.stringify(profileData),
180
};
···
187
headers: {
188
'Content-Type': 'application/json',
189
'Access-Control-Allow-Origin': '*',
190
+
'X-Cache-Status': 'ERROR'
191
},
192
body: JSON.stringify({
193
+
did: did,
194
// Profile data unavailable
195
}),
196
};