A build your own ATProto adventure, OAuth already figured out for you. demo.atpoke.xyz
41
fork

Configure Feed

Select the types of activity you want to include in your feed.

Handle session timeouts and clearing t he cookie session

+54 -17
+29 -13
src/hooks.server.ts
··· 7 7 import { STATE_STORE } from '$lib/server/cache'; 8 8 import { logger } from '$lib/server/logger'; 9 9 import { HOUR } from '@atproto/common'; 10 - import { getSessionManager } from '$lib/server/session'; 10 + import { getSessionManager, SessionRestorationError } from '$lib/server/session'; 11 11 12 12 const clearExpiredStates = async () => { 13 13 try { ··· 50 50 event.locals.atpAgent = null; 51 51 return resolve(event); 52 52 } 53 + 53 54 const sessionManager = await getSessionManager(); 54 - const { atpAgent, did, handle } = await sessionManager.getSessionFromRequest(event); 55 55 56 - if(atpAgent == null){ 56 + try { 57 + const { atpAgent, did, handle } = await sessionManager.getSessionFromRequest(event); 58 + 59 + if(atpAgent == null){ 60 + event.locals.session = null; 61 + event.locals.atpAgent = null; 62 + return resolve(event); 63 + } 64 + 65 + // Store atpAgent in locals (server-side only, not serialized) 66 + event.locals.atpAgent = atpAgent; 67 + 68 + // Store only serializable data in session (gets passed to client via load functions) 69 + event.locals.session = { 70 + did, 71 + handle 72 + }; 73 + } catch (err) { 74 + if (err instanceof SessionRestorationError) { 75 + //You can propagate this error to the frontend to let your users know their session unexpectedly ended 76 + //I opted out of not completely implementing this since everyone may have a different idea of what to do in their apps 77 + //For instance I would use the cache to create a flash message that when loaded it deletes and show it on the layout 78 + } else { 79 + // Unexpected error, re-throw 80 + throw err; 81 + } 82 + 57 83 event.locals.session = null; 58 84 event.locals.atpAgent = null; 59 - return resolve(event); 60 85 } 61 - 62 - // Store atpAgent in locals (server-side only, not serialized) 63 - event.locals.atpAgent = atpAgent; 64 - 65 - // Store only serializable data in session (gets passed to client via load functions) 66 - event.locals.session = { 67 - did, 68 - handle 69 - }; 70 86 71 87 return resolve(event); 72 88 };
+25 -4
src/lib/server/session.ts
··· 13 13 import type { NodeOAuthClient } from '@atproto/oauth-client-node'; 14 14 import { logger } from '$lib/server/logger'; 15 15 16 + export class SessionRestorationError extends Error { 17 + constructor(message: string) { 18 + super(message); 19 + this.name = 'SessionRestorationError'; 20 + } 21 + } 16 22 17 23 // This is a sliding expiration for the cookie session. Can change it if you want it to be less or more. 18 24 // The actual atproto session goes for a while if it's a confidential client as long as it's refreshed ··· 52 58 session.expiresAt = new Date(Date.now() + DEFAULT_EXPIRY); 53 59 await this.db.update(sessionStore).set(session).where(eq(sessionStore.id, sessionId)); 54 60 } 61 + try{ 62 + const oAuthSession = await this.atpOAuthClient.restore(session.did); 55 63 56 - const oAuthSession = await this.atpOAuthClient.restore(session.did); 57 - const agent = new Agent(oAuthSession); 58 - return { atpAgent: agent, did: session.did, handle: session.handle }; 64 + const agent = new Agent(oAuthSession); 65 + return { atpAgent: agent, did: session.did, handle: session.handle }; 66 + }catch (err){ 67 + const errorMessage = (err as Error).message; 68 + logger.warn(`Error restoring session for did: ${session.did}, error: ${errorMessage}`); 69 + //Counting any error when restoring a session as a failed session resume and deleting the users web browser session 70 + //You can go further and capture different types of errors 71 + await this.invalidateUserSessions(session.did); 72 + throw new SessionRestorationError(`Failed to restore your session: ${errorMessage}. Please log in again.`); 73 + } 59 74 } 60 75 61 76 private setSessionTokenCookie(event: RequestEvent, token: string, expiresAt: Date): void { ··· 117 132 if (!token) { 118 133 return NULL_SESSION_RESPONSE; 119 134 } 120 - return this.validateSessionToken(token); 135 + try { 136 + return await this.validateSessionToken(token); 137 + } catch (err) { 138 + //We delete the cookie on any error and pass along the error 139 + this.deleteSessionTokenCookie(event); 140 + throw err; 141 + } 121 142 } 122 143 123 144 }