A build your own ATProto adventure, OAuth already figured out for you.
0
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 }