···1414 langs?: string[];
1515 createdAt?: string;
1616}) {
1717+ // Ensure we're on the client side
1818+ if (typeof window === 'undefined') {
1919+ throw new Error('API client can only be used on the client side');
2020+ }
2121+1722 try {
1823 // For now, we'll make a direct API call to our existing endpoint
1924 // Later this can be improved to use the OAuth session directly
+47-11
app/src/lib/auth-context.tsx
···2233import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
44import { OAuthSession } from '@atproto/oauth-client-browser';
55-import { initializeOAuthClient, oauthClient, signIn, restoreSession, signOut, onSessionDeleted } from './oauth-client';
6576interface AuthContextType {
87 session: OAuthSession | null;
···2827export function AuthProvider({ children }: AuthProviderProps) {
2928 const [session, setSession] = useState<OAuthSession | null>(null);
3029 const [isLoading, setIsLoading] = useState(true);
3030+ const [isClient, setIsClient] = useState(false);
31313232- // Initialize the OAuth client on mount
3232+ // Track if we're on the client side
3333+ useEffect(() => {
3434+ setIsClient(true);
3535+ }, []);
3636+3737+ // Initialize the OAuth client on mount (client-side only)
3338 useEffect(() => {
3939+ if (!isClient) return;
4040+3441 async function initialize() {
3542 try {
3643 setIsLoading(true);
37444545+ // Dynamic import to ensure client-side only execution
4646+ const { initializeOAuthClient } = await import('./oauth-client');
3847 const result = await initializeOAuthClient();
39484049 if (result) {
···4958 }
50595160 initialize();
5252- }, []);
6161+ }, [isClient]);
53625454- // Set up session deletion listener
6363+ // Set up session deletion listener (client-side only)
5564 useEffect(() => {
5656- const handleSessionDeleted = ({ sub, cause }: { sub: string; cause: any }) => {
5757- console.error(`Session for ${sub} was invalidated:`, cause);
5858- setSession(null);
5959- };
6565+ if (!isClient) return;
60666161- onSessionDeleted(handleSessionDeleted);
6262- }, []);
6767+ async function setupListener() {
6868+ try {
6969+ const { onSessionDeleted } = await import('./oauth-client');
7070+7171+ const handleSessionDeleted = ({ sub, cause }: { sub: string; cause: any }) => {
7272+ console.error(`Session for ${sub} was invalidated:`, cause);
7373+ setSession(null);
7474+ };
7575+7676+ onSessionDeleted(handleSessionDeleted);
7777+ } catch (error) {
7878+ console.error('Failed to set up session listener:', error);
7979+ }
8080+ }
8181+8282+ setupListener();
8383+ }, [isClient]);
63846485 const handleSignIn = async (handle: string) => {
8686+ if (!isClient) {
8787+ throw new Error('Sign in can only be called on the client side');
8888+ }
8989+6590 try {
9191+ const { signIn } = await import('./oauth-client');
6692 await signIn(handle);
6793 // Note: This will redirect, so we won't reach this point
6894 } catch (error) {
···7298 };
739974100 const handleSignOut = async () => {
101101+ if (!isClient) {
102102+ throw new Error('Sign out can only be called on the client side');
103103+ }
104104+75105 try {
106106+ const { signOut } = await import('./oauth-client');
76107 await signOut();
77108 setSession(null);
78109 } catch (error) {
···82113 };
8311484115 const handleRestoreSession = async (did: string) => {
116116+ if (!isClient) {
117117+ throw new Error('Restore session can only be called on the client side');
118118+ }
119119+85120 try {
121121+ const { restoreSession } = await import('./oauth-client');
86122 const restoredSession = await restoreSession(did);
87123 setSession(restoredSession);
88124 return restoredSession;
···95131 const contextValue: AuthContextType = {
96132 session,
97133 isAuthenticated: !!session,
9898- isLoading,
134134+ isLoading: isLoading || !isClient, // Keep loading until client-side hydration
99135 signIn: handleSignIn,
100136 signOut: handleSignOut,
101137 restoreSession: handleRestoreSession,
+70-19
app/src/lib/oauth-client.ts
···1717 "token_endpoint_auth_method": "none" as const
1818}
19192020-// Create the OAuth client instance
2121-export const oauthClient = new BrowserOAuthClient({
2222- clientMetadata: CLIENT_METADATA as any, // Type assertion to avoid strict typing issues
2323- // Use Bluesky's public handle resolver
2424- handleResolver: 'https://bsky.social',
2525- // Use fragment for better SPA support
2626- responseMode: 'fragment'
2727-})
2020+// Lazy OAuth client - only initialize on client side
2121+let _oauthClient: BrowserOAuthClient | null = null
2222+2323+// Get or create the OAuth client instance - client-side only
2424+function getOAuthClient(): BrowserOAuthClient {
2525+ // Ensure we're on the client side
2626+ if (typeof window === 'undefined') {
2727+ throw new Error('OAuth client can only be used on the client side')
2828+ }
2929+3030+ if (!_oauthClient) {
3131+ _oauthClient = new BrowserOAuthClient({
3232+ clientMetadata: CLIENT_METADATA as any,
3333+ handleResolver: 'https://bsky.social',
3434+ responseMode: 'fragment'
3535+ })
3636+ }
3737+3838+ return _oauthClient
3939+}
4040+4141+// Export the getter function instead of the instance
4242+export const oauthClient = {
4343+ get instance() {
4444+ return getOAuthClient()
4545+ }
4646+}
28472948// Initialize the client - this should be called once when the app loads
3049export async function initializeOAuthClient() {
5050+ // Only run on client side
5151+ if (typeof window === 'undefined') {
5252+ console.log('Skipping OAuth client initialization on server side')
5353+ return null
5454+ }
5555+3156 try {
3232- const result = await oauthClient.init()
5757+ const client = getOAuthClient()
5858+ const result = await client.init()
33593460 if (result) {
3561 const { session } = result
···5884 state?: string
5985 signal?: AbortSignal
6086}) {
8787+ // Only run on client side
8888+ if (typeof window === 'undefined') {
8989+ throw new Error('Sign in can only be called on the client side')
9090+ }
9191+6192 try {
6293 console.log(`Initiating OAuth flow for ${handle}`)
63946464- await oauthClient.signIn(handle, {
9595+ const client = getOAuthClient()
9696+ await client.signIn(handle, {
6597 state: options?.state || `signin-${Date.now()}`,
6698 signal: options?.signal
6799 })
···7510776108// Restore a specific session by DID
77109export async function restoreSession(did: string) {
110110+ // Only run on client side
111111+ if (typeof window === 'undefined') {
112112+ throw new Error('Restore session can only be called on the client side')
113113+ }
114114+78115 try {
79116 console.log(`Restoring session for ${did}`)
8080- const session = await oauthClient.restore(did)
117117+ const client = getOAuthClient()
118118+ const session = await client.restore(did)
81119 console.log(`Successfully restored session for ${session.sub}`)
82120 return session
83121 } catch (error) {
···8812689127// Sign out the current session
90128export async function signOut() {
129129+ // Only run on client side
130130+ if (typeof window === 'undefined') {
131131+ throw new Error('Sign out can only be called on the client side')
132132+ }
133133+91134 try {
9292- // The BrowserOAuthClient doesn't expose a direct signOut method
9393- // We need to manually clear the session and redirect
9494- // For now, we'll clear local storage and redirect
95135 console.log('Signing out user')
9613697137 // Clear any remaining localStorage items from the old implementation
···117157118158// Event listener for session deletion/invalidation
119159export function onSessionDeleted(callback: (event: { sub: string, cause: any }) => void) {
120120- oauthClient.addEventListener('deleted', (event: any) => {
121121- const { sub, cause } = event.detail
122122- console.error(`Session for ${sub} was invalidated:`, cause)
123123- callback({ sub, cause })
124124- })
160160+ // Only run on client side
161161+ if (typeof window === 'undefined') {
162162+ console.log('Skipping session deleted listener setup on server side')
163163+ return
164164+ }
165165+166166+ try {
167167+ const client = getOAuthClient()
168168+ client.addEventListener('deleted', (event: any) => {
169169+ const { sub, cause } = event.detail
170170+ console.error(`Session for ${sub} was invalidated:`, cause)
171171+ callback({ sub, cause })
172172+ })
173173+ } catch (error) {
174174+ console.error('Failed to set up session deleted listener:', error)
175175+ }
125176}