// Enhanced PartyKit server with session renewal support
export default class SessionValidatedPlayHTML implements PartyKitServer {
private validSessions = new Map<string, ValidatedSession>();
private pendingChallenges = new Map<string, SessionChallenge>();
private usedNonces = new Set<string>();
// Session establishment with renewal support
async handleSessionEstablishment(request: Request): Response<Response> {
const { challenge, signature, publicKey } = await request.json();
// Validate challenge exists and signature is correct (ONLY crypto verification)
const storedChallenge = this.pendingChallenges.get(challenge.challenge);
if (!storedChallenge || storedChallenge.expiresAt < Date.now()) {
return new Response('Invalid or expired challenge', { status: 400 });
}
const isValidSignature = await verifySignature(
JSON.stringify(challenge),
signature,
publicKey
);
if (!isValidSignature) {
return new Response('Invalid signature', { status: 400 });
}
// Check if this is a renewal (user already has active session)
const existingSession = this.findExistingSession(publicKey);
if (existingSession) {
// Extend existing session instead of creating new one
existingSession.expiresAt = Date.now() + (24 * 60 * 60 * 1000);
console.log(`🔄 Renewed session for ${publicKey}`);
return new Response(JSON.stringify({
sessionId: existingSession.sessionId, // Keep same session ID
publicKey: existingSession.publicKey,
expiresAt: existingSession.expiresAt,
renewed: true
}));
} else {
// Create new session
const session: ValidatedSession = {
sessionId: crypto.randomUUID(),
publicKey,
domain: challenge.domain,
establishedAt: Date.now(),
expiresAt: Date.now() + (24 * 60 * 60 * 1000), // 24 hours
permissions: await this.getUserPermissions(publicKey, challenge.domain)
};
this.validSessions.set(session.sessionId, session);
this.pendingChallenges.delete(challenge.challenge);
console.log(`✅ New session established for ${publicKey}`);
return new Response(JSON.stringify({
sessionId: session.sessionId,
publicKey: session.publicKey,
expiresAt: session.expiresAt,
renewed: false
}));
}
}
private findExistingSession(publicKey: string): ValidatedSession | null {
for (const session of this.validSessions.values()) {
if (session.publicKey === publicKey && session.expiresAt > Date.now()) {
return session;
}
}
return null;
}
// Message handling with simplified session validation
async onMessage(message: string, sender: Party.Connection) {
try {
const parsed = JSON.parse(message);
if (parsed.type === "session_action") {
await this.handleSessionAction(parsed.action, sender);
} else {
await this.handleAnonymousAction(parsed, sender);
}
} catch (error) {
console.error("Message processing error:", error);
sender.send(JSON.stringify({
type: "action_rejected",
reason: error.message
}));
}
}
private async handleSessionAction(action: SimpleAction, sender: Party.Connection) {
// 1. Validate session exists and is not expired