forked from
atpota.to/flushes.app
The 1st decentralized social network for sharing when you're on the toilet. Post a "flush" today! Powered by the AT Protocol.
1'use client';
2
3import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
4
5interface AuthContextType {
6 isAuthenticated: boolean;
7 accessToken: string | null;
8 refreshToken: string | null;
9 did: string | null;
10 handle: string | null;
11 serializedKeyPair: string | null;
12 dpopNonce: string | null;
13 pdsEndpoint: string | null;
14 setAuth: (auth: {
15 accessToken: string;
16 refreshToken: string;
17 did: string;
18 handle: string;
19 serializedKeyPair: string;
20 dpopNonce?: string | null;
21 pdsEndpoint?: string | null;
22 }) => void;
23 clearAuth: () => void;
24}
25
26const AuthContext = createContext<AuthContextType | undefined>(undefined);
27
28interface AuthProviderProps {
29 children: ReactNode;
30}
31
32export function AuthProvider({ children }: AuthProviderProps) {
33 const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
34 const [accessToken, setAccessToken] = useState<string | null>(null);
35 const [refreshToken, setRefreshToken] = useState<string | null>(null);
36 const [did, setDid] = useState<string | null>(null);
37 const [handle, setHandle] = useState<string | null>(null);
38 const [serializedKeyPair, setSerializedKeyPair] = useState<string | null>(null);
39 const [dpopNonce, setDpopNonce] = useState<string | null>(null);
40 const [pdsEndpoint, setPdsEndpoint] = useState<string | null>(null);
41 const [isClient, setIsClient] = useState(false);
42 const [lastTokenRefresh, setLastTokenRefresh] = useState<number>(0);
43
44 // Function to check token and refresh if needed
45 const checkAndRefreshToken = async () => {
46 if (!accessToken || !refreshToken || !serializedKeyPair || !did || !pdsEndpoint) {
47 return;
48 }
49
50 try {
51 // TEMPORARILY DISABLED TOKEN REFRESH
52 // This fixes issues with third-party PDSs like geese.blue
53 console.log('[AUTH CONTEXT] Token refresh temporarily disabled for third-party PDS compatibility');
54
55 // Still update the nonce from localStorage if available
56 if (typeof localStorage !== 'undefined') {
57 const latestNonce = localStorage.getItem('dpopNonce');
58 if (latestNonce && latestNonce !== dpopNonce) {
59 console.log('[AUTH CONTEXT] Updating nonce from localStorage:', latestNonce);
60 setDpopNonce(latestNonce);
61 }
62 }
63 } catch (error) {
64 console.error('[AUTH CONTEXT] Error in checkAndRefreshToken:', error);
65 }
66 };
67
68 useEffect(() => {
69 // Set isClient to true once the component mounts
70 setIsClient(true);
71
72 // Load auth data from localStorage on initial mount
73 if (typeof window !== 'undefined') {
74 const storedAccessToken = localStorage.getItem('accessToken');
75 const storedRefreshToken = localStorage.getItem('refreshToken');
76 const storedDid = localStorage.getItem('did');
77 const storedHandle = localStorage.getItem('handle');
78 const storedKeyPair = localStorage.getItem('keyPair');
79 const storedDpopNonce = localStorage.getItem('dpopNonce');
80
81 // Special handling for PDS endpoint - check all possible storage locations
82 let storedPdsEndpoint = localStorage.getItem('pdsEndpoint');
83
84 // If not found, try our auth-prefixed format
85 if (!storedPdsEndpoint) {
86 storedPdsEndpoint = localStorage.getItem('bsky_auth_pdsEndpoint');
87 }
88
89 // Last resort - check sessionStorage
90 if (!storedPdsEndpoint && typeof sessionStorage !== 'undefined') {
91 try {
92 storedPdsEndpoint = sessionStorage.getItem('pdsEndpoint');
93 } catch (e) {
94 console.warn('Failed to check sessionStorage for PDS endpoint:', e);
95 }
96 }
97
98 if (storedAccessToken && storedDid && storedKeyPair) {
99 setAccessToken(storedAccessToken);
100 setRefreshToken(storedRefreshToken);
101 setDid(storedDid);
102 setHandle(storedHandle);
103 setSerializedKeyPair(storedKeyPair);
104 setDpopNonce(storedDpopNonce);
105 setPdsEndpoint(storedPdsEndpoint);
106 setIsAuthenticated(true);
107 }
108 }
109 }, []);
110
111 // Effect to check token expiration periodically
112 useEffect(() => {
113 if (!isAuthenticated) return;
114
115 // Check token immediately after login
116 checkAndRefreshToken();
117
118 // Check token every 10 minutes
119 const tokenCheckInterval = setInterval(() => {
120 checkAndRefreshToken();
121 }, 10 * 60 * 1000); // 10 minutes
122
123 return () => clearInterval(tokenCheckInterval);
124 }, [isAuthenticated, accessToken, refreshToken, serializedKeyPair, did, pdsEndpoint]);
125
126 const setAuth = ({
127 accessToken,
128 refreshToken,
129 did,
130 handle,
131 serializedKeyPair,
132 dpopNonce = null,
133 pdsEndpoint = null
134 }: {
135 accessToken: string;
136 refreshToken: string;
137 did: string;
138 handle: string;
139 serializedKeyPair: string;
140 dpopNonce?: string | null;
141 pdsEndpoint?: string | null;
142 }) => {
143 // Store auth data in state
144 setAccessToken(accessToken);
145 setRefreshToken(refreshToken);
146 setDid(did);
147 setHandle(handle);
148 setSerializedKeyPair(serializedKeyPair);
149 setDpopNonce(dpopNonce);
150 setPdsEndpoint(pdsEndpoint);
151 setIsAuthenticated(true);
152
153 // Store auth data in localStorage (only on client)
154 if (typeof window !== 'undefined') {
155 localStorage.setItem('accessToken', accessToken);
156 localStorage.setItem('refreshToken', refreshToken);
157 localStorage.setItem('did', did);
158 localStorage.setItem('handle', handle);
159 localStorage.setItem('keyPair', serializedKeyPair);
160 if (dpopNonce) {
161 localStorage.setItem('dpopNonce', dpopNonce);
162 }
163 if (pdsEndpoint) {
164 localStorage.setItem('pdsEndpoint', pdsEndpoint);
165 }
166 }
167 };
168
169 const clearAuth = () => {
170 // Clear auth data from state
171 setAccessToken(null);
172 setRefreshToken(null);
173 setDid(null);
174 setHandle(null);
175 setSerializedKeyPair(null);
176 setDpopNonce(null);
177 setPdsEndpoint(null);
178 setIsAuthenticated(false);
179
180 // Clear auth data from localStorage (only on client)
181 if (typeof window !== 'undefined') {
182 localStorage.removeItem('accessToken');
183 localStorage.removeItem('refreshToken');
184 localStorage.removeItem('did');
185 localStorage.removeItem('handle');
186 localStorage.removeItem('keyPair');
187 localStorage.removeItem('dpopNonce');
188 localStorage.removeItem('pdsEndpoint');
189 }
190 };
191
192 return (
193 <AuthContext.Provider
194 value={{
195 isAuthenticated,
196 accessToken,
197 refreshToken,
198 did,
199 handle,
200 serializedKeyPair,
201 dpopNonce,
202 pdsEndpoint,
203 setAuth,
204 clearAuth
205 }}
206 >
207 {children}
208 </AuthContext.Provider>
209 );
210}
211
212export function useAuth() {
213 const context = useContext(AuthContext);
214 if (context === undefined) {
215 throw new Error('useAuth must be used within an AuthProvider');
216 }
217 return context;
218}