This repository has no description
0

Configure Feed

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

switch to client side auth

+317 -715
+87 -59
package-lock.json
··· 11 11 "@atcute/client": "^2.0.6", 12 12 "@atcute/oauth-browser-client": "^1.0.7", 13 13 "@atproto/api": "^0.13.22", 14 - "@atproto/oauth-client-browser": "^0.3.12", 14 + "@atproto/oauth-client-browser": "^0.3.15", 15 15 "@atproto/oauth-client-node": "^0.2.4", 16 16 "@fortawesome/free-solid-svg-icons": "^6.7.2", 17 17 "@fortawesome/react-fontawesome": "^0.2.2", ··· 201 201 } 202 202 }, 203 203 "node_modules/@atproto/common-web": { 204 - "version": "0.4.0", 205 - "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.0.tgz", 206 - "integrity": "sha512-ZYL0P9myHybNgwh/hBY0HaBzqiLR1B5/ie5bJpLQAg0whRzNA28t8/nU2vh99tbsWcAF0LOD29M8++LyENJLNQ==", 204 + "version": "0.4.1", 205 + "resolved": "https://registry.npmjs.org/@atproto/common-web/-/common-web-0.4.1.tgz", 206 + "integrity": "sha512-Ghh+djHYMAUCktLKwr2IuGgtjcwSWGudp+K7+N7KBA9pDDloOXUEY8Agjc5SHSo9B1QIEFkegClU5n+apn2e0w==", 207 207 "license": "MIT", 208 208 "dependencies": { 209 209 "graphemer": "^1.4.0", ··· 253 253 } 254 254 }, 255 255 "node_modules/@atproto/lexicon": { 256 - "version": "0.4.9", 257 - "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.9.tgz", 258 - "integrity": "sha512-/tmEuHQFr51V2V7EAVJzaA40sqJ7ylAZpR962VbOsPtmcdOHvezbjVHYEMXgfb927hS+xqbVyzBTbu5w9v8prA==", 256 + "version": "0.4.10", 257 + "resolved": "https://registry.npmjs.org/@atproto/lexicon/-/lexicon-0.4.10.tgz", 258 + "integrity": "sha512-uDbP20vetBgtXPuxoyRcvOGBt2gNe1dFc9yYKcb6jWmXfseHiGTnIlORJOLBXIT2Pz15Eap4fLxAu6zFAykD5A==", 259 259 "license": "MIT", 260 260 "dependencies": { 261 - "@atproto/common-web": "^0.4.0", 261 + "@atproto/common-web": "^0.4.1", 262 262 "@atproto/syntax": "^0.4.0", 263 263 "iso-datestring-validator": "^2.2.2", 264 264 "multiformats": "^9.9.0", ··· 292 292 } 293 293 }, 294 294 "node_modules/@atproto/oauth-client-browser": { 295 - "version": "0.3.12", 296 - "resolved": "https://registry.npmjs.org/@atproto/oauth-client-browser/-/oauth-client-browser-0.3.12.tgz", 297 - "integrity": "sha512-VmpBoNIMOzfRZyAlimhBZ5WZftIf/Ysyi77cytxL9gHKIyB/tTUgB1oB4zSv3Oid01bPQHx73sMMIVfYEZ1Fpw==", 295 + "version": "0.3.15", 296 + "resolved": "https://registry.npmjs.org/@atproto/oauth-client-browser/-/oauth-client-browser-0.3.15.tgz", 297 + "integrity": "sha512-sYfjpBsiiWiQTJbPZU234uOKI3613Pi5X4Z0KLIbDpBdH+HtvcjmsVRrYH4d6WBJUqezxsPCuW/oj5uA87yk1Q==", 298 298 "license": "MIT", 299 299 "dependencies": { 300 - "@atproto-labs/did-resolver": "0.1.11", 301 - "@atproto-labs/handle-resolver": "0.1.7", 302 - "@atproto-labs/simple-store": "0.1.2", 300 + "@atproto-labs/did-resolver": "0.1.12", 301 + "@atproto-labs/handle-resolver": "0.1.8", 302 + "@atproto-labs/simple-store": "0.2.0", 303 303 "@atproto/did": "0.1.5", 304 - "@atproto/jwk": "0.1.4", 305 - "@atproto/jwk-webcrypto": "0.1.5", 306 - "@atproto/oauth-client": "0.3.12", 307 - "@atproto/oauth-types": "0.2.4" 304 + "@atproto/jwk": "0.1.5", 305 + "@atproto/jwk-webcrypto": "0.1.6", 306 + "@atproto/oauth-client": "0.3.15", 307 + "@atproto/oauth-types": "0.2.6" 308 308 } 309 309 }, 310 310 "node_modules/@atproto/oauth-client-browser/node_modules/@atproto-labs/did-resolver": { 311 - "version": "0.1.11", 312 - "resolved": "https://registry.npmjs.org/@atproto-labs/did-resolver/-/did-resolver-0.1.11.tgz", 313 - "integrity": "sha512-qXNzIX2GPQnxT1gl35nv/8ErDdc4Fj/+RlJE7oyE7JGkFAPUyuY03TvKJ79SmWFsWE8wyTXEpLuphr9Da1Vhkw==", 311 + "version": "0.1.12", 312 + "resolved": "https://registry.npmjs.org/@atproto-labs/did-resolver/-/did-resolver-0.1.12.tgz", 313 + "integrity": "sha512-criWN7o21C5TFsauB+bGTlkqqerOU6gT2TbxdQVgZUWqNcfazUmUjT4gJAY02i+O4d3QmZa27fv9CcaRKWkSug==", 314 314 "license": "MIT", 315 315 "dependencies": { 316 316 "@atproto-labs/fetch": "0.2.2", 317 317 "@atproto-labs/pipe": "0.1.0", 318 - "@atproto-labs/simple-store": "0.1.2", 319 - "@atproto-labs/simple-store-memory": "0.1.2", 318 + "@atproto-labs/simple-store": "0.2.0", 319 + "@atproto-labs/simple-store-memory": "0.1.3", 320 320 "@atproto/did": "0.1.5", 321 321 "zod": "^3.23.8" 322 322 } ··· 330 330 "@atproto-labs/pipe": "0.1.0" 331 331 } 332 332 }, 333 + "node_modules/@atproto/oauth-client-browser/node_modules/@atproto-labs/handle-resolver": { 334 + "version": "0.1.8", 335 + "resolved": "https://registry.npmjs.org/@atproto-labs/handle-resolver/-/handle-resolver-0.1.8.tgz", 336 + "integrity": "sha512-Y0ckccoCGDo/3g4thPkgp9QcORmc+qqEaCBCYCZYtfLIQp4775u22wd+4fyEyJP4DqoReKacninkICgRGfs3dQ==", 337 + "license": "MIT", 338 + "dependencies": { 339 + "@atproto-labs/simple-store": "0.2.0", 340 + "@atproto-labs/simple-store-memory": "0.1.3", 341 + "@atproto/did": "0.1.5", 342 + "zod": "^3.23.8" 343 + } 344 + }, 333 345 "node_modules/@atproto/oauth-client-browser/node_modules/@atproto-labs/identity-resolver": { 334 - "version": "0.1.15", 335 - "resolved": "https://registry.npmjs.org/@atproto-labs/identity-resolver/-/identity-resolver-0.1.15.tgz", 336 - "integrity": "sha512-3ABob5iUDoFL85I8/pJE4wncz3148fADoxNVAdksyACxxjpH1GNhSYNyIpRpdMCJ/kjj69DM9rggumTHqnD/Xg==", 346 + "version": "0.1.16", 347 + "resolved": "https://registry.npmjs.org/@atproto-labs/identity-resolver/-/identity-resolver-0.1.16.tgz", 348 + "integrity": "sha512-pFrtKT49cYBhCDd2U1t/CcUBiMmQzaNQxh8oSkDUlGs/K3P8rJFTAGAMm8UjokfGEKwF4hX9oo7O8Kn+GkyExw==", 337 349 "license": "MIT", 338 350 "dependencies": { 339 - "@atproto-labs/did-resolver": "0.1.11", 340 - "@atproto-labs/handle-resolver": "0.1.7", 351 + "@atproto-labs/did-resolver": "0.1.12", 352 + "@atproto-labs/handle-resolver": "0.1.8", 341 353 "@atproto/syntax": "0.4.0" 342 354 } 343 355 }, 356 + "node_modules/@atproto/oauth-client-browser/node_modules/@atproto-labs/simple-store": { 357 + "version": "0.2.0", 358 + "resolved": "https://registry.npmjs.org/@atproto-labs/simple-store/-/simple-store-0.2.0.tgz", 359 + "integrity": "sha512-0bRbAlI8Ayh03wRwncAMEAyUKtZ+AuTS1jgPrfym1WVOAOiottI/ZmgccqLl6w5MbxVcClNQF7WYGKvGwGoIhA==", 360 + "license": "MIT" 361 + }, 362 + "node_modules/@atproto/oauth-client-browser/node_modules/@atproto-labs/simple-store-memory": { 363 + "version": "0.1.3", 364 + "resolved": "https://registry.npmjs.org/@atproto-labs/simple-store-memory/-/simple-store-memory-0.1.3.tgz", 365 + "integrity": "sha512-jkitT9+AtU+0b28DoN92iURLaCt/q/q4yX8q6V+9LSwYlUTqKoj/5NFKvF7x6EBuG+gpUdlcycbH7e60gjOhRQ==", 366 + "license": "MIT", 367 + "dependencies": { 368 + "@atproto-labs/simple-store": "0.2.0", 369 + "lru-cache": "^10.2.0" 370 + } 371 + }, 344 372 "node_modules/@atproto/oauth-client-browser/node_modules/@atproto/jwk": { 345 - "version": "0.1.4", 346 - "resolved": "https://registry.npmjs.org/@atproto/jwk/-/jwk-0.1.4.tgz", 347 - "integrity": "sha512-dSRuEi0FbxL5ln6hEFHp5ZW01xbQH9yJi5odZaEYpcA6beZHf/bawlU12CQy/CDsbC3FxSqrBw7Q2t7mvdSBqw==", 373 + "version": "0.1.5", 374 + "resolved": "https://registry.npmjs.org/@atproto/jwk/-/jwk-0.1.5.tgz", 375 + "integrity": "sha512-OzZFLhX41TOcMeanP3aZlL5bLeaUIZT15MI4aU5cwflNq/rwpGOpz3uwDjZc8ytgUjuTQ8LabSz5jMmwoTSWFg==", 348 376 "license": "MIT", 349 377 "dependencies": { 350 378 "multiformats": "^9.9.0", ··· 352 380 } 353 381 }, 354 382 "node_modules/@atproto/oauth-client-browser/node_modules/@atproto/jwk-jose": { 355 - "version": "0.1.5", 356 - "resolved": "https://registry.npmjs.org/@atproto/jwk-jose/-/jwk-jose-0.1.5.tgz", 357 - "integrity": "sha512-piYZ3ohKhRiGlD6/bZCV/Ed3lIi7CVd6txbofEHik22EkYWK0nWKoEriCUSTssSylwFzeOq2r31Ut16WcJoghw==", 383 + "version": "0.1.6", 384 + "resolved": "https://registry.npmjs.org/@atproto/jwk-jose/-/jwk-jose-0.1.6.tgz", 385 + "integrity": "sha512-r4DGMvvmazy6CxqAcnplpUxvp6Vd8UwKxQBZRpmm1aNsVonf5qj1yeDkECTiwoe/FPbvtdamlzClB3UZc7Yb5w==", 358 386 "license": "MIT", 359 387 "dependencies": { 360 - "@atproto/jwk": "0.1.4", 388 + "@atproto/jwk": "0.1.5", 361 389 "jose": "^5.2.0" 362 390 } 363 391 }, 364 392 "node_modules/@atproto/oauth-client-browser/node_modules/@atproto/jwk-webcrypto": { 365 - "version": "0.1.5", 366 - "resolved": "https://registry.npmjs.org/@atproto/jwk-webcrypto/-/jwk-webcrypto-0.1.5.tgz", 367 - "integrity": "sha512-xsX8cJO6rBakLz8zNKenuKIbjoTeaiMi/ETOFFYGtlMlk1grdxDOe6OGpCmUDXaOiYWu3x5K5Hc4J9ooI/4nRg==", 393 + "version": "0.1.6", 394 + "resolved": "https://registry.npmjs.org/@atproto/jwk-webcrypto/-/jwk-webcrypto-0.1.6.tgz", 395 + "integrity": "sha512-mxWHOvlg+HGohldfiaon1fNsr7iDvKrTrkV0/ZvymWRzxsDFPCon1hu8OtKLXUVgLh+IzDJT1D79I4fBSo4pog==", 368 396 "license": "MIT", 369 397 "dependencies": { 370 - "@atproto/jwk": "0.1.4", 371 - "@atproto/jwk-jose": "0.1.5", 398 + "@atproto/jwk": "0.1.5", 399 + "@atproto/jwk-jose": "0.1.6", 372 400 "zod": "^3.23.8" 373 401 } 374 402 }, 375 403 "node_modules/@atproto/oauth-client-browser/node_modules/@atproto/oauth-client": { 376 - "version": "0.3.12", 377 - "resolved": "https://registry.npmjs.org/@atproto/oauth-client/-/oauth-client-0.3.12.tgz", 378 - "integrity": "sha512-tDQzq/6PNspWeHBrYGZ+LU7t7PM/+0Ealk8V2PUfncGObLz0iPDS4RbWAzy0GH1foee1qIfRdiiKiEymMGIZDw==", 404 + "version": "0.3.15", 405 + "resolved": "https://registry.npmjs.org/@atproto/oauth-client/-/oauth-client-0.3.15.tgz", 406 + "integrity": "sha512-kBv+sk2P5nqGjVSxF26ZswsGKbCfWmxCRpd90TRlNtxo29ZJrYxWySTZrnReXjMellMrTqJEUuTBIasGrXk5Jg==", 379 407 "license": "MIT", 380 408 "dependencies": { 381 - "@atproto-labs/did-resolver": "0.1.11", 409 + "@atproto-labs/did-resolver": "0.1.12", 382 410 "@atproto-labs/fetch": "0.2.2", 383 - "@atproto-labs/handle-resolver": "0.1.7", 384 - "@atproto-labs/identity-resolver": "0.1.15", 385 - "@atproto-labs/simple-store": "0.1.2", 386 - "@atproto-labs/simple-store-memory": "0.1.2", 411 + "@atproto-labs/handle-resolver": "0.1.8", 412 + "@atproto-labs/identity-resolver": "0.1.16", 413 + "@atproto-labs/simple-store": "0.2.0", 414 + "@atproto-labs/simple-store-memory": "0.1.3", 387 415 "@atproto/did": "0.1.5", 388 - "@atproto/jwk": "0.1.4", 389 - "@atproto/oauth-types": "0.2.4", 390 - "@atproto/xrpc": "0.6.11", 416 + "@atproto/jwk": "0.1.5", 417 + "@atproto/oauth-types": "0.2.6", 418 + "@atproto/xrpc": "0.6.12", 391 419 "multiformats": "^9.9.0", 392 420 "zod": "^3.23.8" 393 421 } 394 422 }, 395 423 "node_modules/@atproto/oauth-client-browser/node_modules/@atproto/oauth-types": { 396 - "version": "0.2.4", 397 - "resolved": "https://registry.npmjs.org/@atproto/oauth-types/-/oauth-types-0.2.4.tgz", 398 - "integrity": "sha512-V2LnlXi1CSmBQWTQgDm8l4oN7xYxlftVwM7hrvYNP+Jxo3Ozfe0QLK1Wy/CH6/ZqzrBBhYvcbf4DJYTUwPA+hw==", 424 + "version": "0.2.6", 425 + "resolved": "https://registry.npmjs.org/@atproto/oauth-types/-/oauth-types-0.2.6.tgz", 426 + "integrity": "sha512-6rUmV7T1YKCgVYLLjm+FGv+dYC8S0+0AHji/azVGDEhTsiadSrlC0H9Pgxix1y89zI1FIf0piBqecBcPewdrJg==", 399 427 "license": "MIT", 400 428 "dependencies": { 401 - "@atproto/jwk": "0.1.4", 429 + "@atproto/jwk": "0.1.5", 402 430 "zod": "^3.23.8" 403 431 } 404 432 }, ··· 409 437 "license": "MIT" 410 438 }, 411 439 "node_modules/@atproto/oauth-client-browser/node_modules/@atproto/xrpc": { 412 - "version": "0.6.11", 413 - "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.6.11.tgz", 414 - "integrity": "sha512-J2cZP8FjoDN0UkyTYBlCvKvxwBbDm4dld47u6FQK30RJy9YpSiUkdxJJ10NYqpi7JVny3M0qWQgpWJDV94+PdA==", 440 + "version": "0.6.12", 441 + "resolved": "https://registry.npmjs.org/@atproto/xrpc/-/xrpc-0.6.12.tgz", 442 + "integrity": "sha512-Ut3iISNLujlmY9Gu8sNU+SPDJDvqlVzWddU8qUr0Yae5oD4SguaUFjjhireMGhQ3M5E0KljQgDbTmnBo1kIZ3w==", 415 443 "license": "MIT", 416 444 "dependencies": { 417 - "@atproto/lexicon": "^0.4.9", 445 + "@atproto/lexicon": "^0.4.10", 418 446 "zod": "^3.23.8" 419 447 } 420 448 },
+1 -1
package.json
··· 6 6 "@atcute/client": "^2.0.6", 7 7 "@atcute/oauth-browser-client": "^1.0.7", 8 8 "@atproto/api": "^0.13.22", 9 - "@atproto/oauth-client-browser": "^0.3.12", 9 + "@atproto/oauth-client-browser": "^0.3.15", 10 10 "@atproto/oauth-client-node": "^0.2.4", 11 11 "@fortawesome/free-solid-svg-icons": "^6.7.2", 12 12 "@fortawesome/react-fontawesome": "^0.2.2",
+40 -92
src/components/Login/Login.js
··· 1 - import React, { useState, useEffect, useRef } from 'react'; 2 - import { useNavigate, useLocation } from 'react-router-dom'; 1 + import React, { useState, useEffect } from 'react'; 2 + import { useLocation, useNavigate } from 'react-router-dom'; 3 3 import { useAuth } from '../../contexts/AuthContext'; 4 4 import './Login.css'; 5 5 6 6 const Login = () => { 7 7 const [handle, setHandle] = useState(''); 8 - const [isLoading, setIsLoading] = useState(false); 9 - const [error, setError] = useState(''); 10 - const [returnUrl, setReturnUrl] = useState(''); 11 - const { login, isAuthenticated, loading } = useAuth(); 8 + const { login, loading, error, isAuthenticated } = useAuth(); 12 9 const navigate = useNavigate(); 13 10 const location = useLocation(); 14 - const hasRedirected = useRef(false); 15 11 16 - // Extract returnUrl from query params 17 - useEffect(() => { 18 - const searchParams = new URLSearchParams(location.search); 19 - const returnPath = searchParams.get('returnUrl'); 20 - if (returnPath) { 21 - setReturnUrl(returnPath); 22 - } 23 - }, [location]); 12 + const queryParams = new URLSearchParams(location.search); 13 + const returnUrl = queryParams.get('returnUrl') || '/'; 24 14 25 - // Redirect if already authenticated 26 15 useEffect(() => { 27 - // Skip redirection if loading is still true 28 - if (loading) return; 29 - 30 - // Only redirect once to prevent loop 31 - if (isAuthenticated && !hasRedirected.current) { 32 - hasRedirected.current = true; 33 - // Navigate to return URL if it exists, otherwise to home 34 - navigate(returnUrl || '/'); 35 - } 36 - }, [isAuthenticated, navigate, returnUrl, loading]); 37 - 38 - const handleSubmit = async (e) => { 39 - e.preventDefault(); 40 - 41 - // Don't allow login attempt if already authenticated 42 16 if (isAuthenticated) { 43 - navigate(returnUrl || '/'); 44 - return; 17 + console.log('Already authenticated, redirecting from Login page to:', returnUrl); 18 + navigate(returnUrl); 45 19 } 46 - 47 - if (!handle) { 48 - setError('Please enter your Bluesky handle'); 49 - return; 50 - } 51 - 52 - // Don't allow multiple concurrent login attempts 53 - if (isLoading) return; 54 - 55 - setIsLoading(true); 56 - setError(''); 57 - 58 - try { 59 - // Pass returnUrl to login function 60 - await login(handle, returnUrl); 61 - // Note: This code won't run because login redirects to Bluesky OAuth page 62 - } catch (err) { 63 - setError('Authentication failed. Please try again.'); 64 - setIsLoading(false); 65 - } 20 + }, [isAuthenticated, navigate, returnUrl]); 21 + 22 + const handleInputChange = (event) => { 23 + setHandle(event.target.value); 66 24 }; 67 25 26 + const handleSubmit = async (event) => { 27 + event.preventDefault(); 28 + console.log(`Login attempt for handle: ${handle || 'default PDS'}, returnUrl: ${returnUrl}`); 29 + await login(handle || null, returnUrl); 30 + }; 31 + 32 + if (isAuthenticated) { 33 + return <div>Redirecting...</div>; 34 + } 35 + 68 36 return ( 69 37 <div className="login-container"> 70 - <div className="login-card"> 71 - <h2>Login with Bluesky</h2> 72 - <p>Sign in with your Bluesky handle to access protected features.</p> 73 - 74 - {returnUrl && ( 75 - <div className="return-notice"> 76 - <p>You'll be redirected back to the page you were trying to access after logging in.</p> 77 - </div> 78 - )} 79 - 80 - {error && <div className="login-error">{error}</div>} 81 - 82 - <form onSubmit={handleSubmit} className="login-form"> 83 - <input 84 - id="handle" 85 - type="text" 86 - value={handle} 87 - onChange={(e) => setHandle(e.target.value)} 88 - placeholder="yourhandle.bsky.social" 89 - disabled={isLoading || isAuthenticated} 90 - autoFocus 91 - /> 92 - 93 - <button 94 - type="submit" 95 - className="login-button" 96 - disabled={isLoading || isAuthenticated} 97 - > 98 - {isLoading ? 'Connecting...' : 'Login with Bluesky'} 99 - </button> 100 - </form> 101 - 102 - <div className="login-info"> 103 - <p> 104 - We use Bluesky's authentication service to verify your identity. 105 - No passwords are stored by cred.blue. 106 - </p> 107 - </div> 108 - </div> 38 + <h1>Login to Cred Blue</h1> 39 + <p>Enter your Bluesky handle (e.g., yourname.bsky.social) or leave blank to use bsky.social.</p> 40 + <form onSubmit={handleSubmit}> 41 + <input 42 + type="text" 43 + value={handle} 44 + onChange={handleInputChange} 45 + placeholder="yourname.bsky.social (optional)" 46 + aria-label="Bluesky Handle (optional)" 47 + /> 48 + {loading && <p>Processing...</p>} 49 + {error && <p className="error-message">Login failed: {error}</p>} 50 + <button type="submit" disabled={loading}> 51 + {loading ? 'Processing...' : 'Login with Bluesky'} 52 + </button> 53 + </form> 54 + <p className="privacy-note"> 55 + We use official Bluesky authentication. We don't see or store your password. 56 + </p> 109 57 </div> 110 58 ); 111 59 };
+112 -136
src/components/Login/LoginCallback.js
··· 1 - import React, { useEffect, useState, useRef } from 'react'; 2 - import { Navigate, useLocation } from 'react-router-dom'; 1 + import React, { useEffect, useState } from 'react'; 2 + import { useNavigate } from 'react-router-dom'; 3 3 import { useAuth } from '../../contexts/AuthContext'; 4 - import Loading from '../Loading/Loading'; 4 + import './LoginCallback.css'; // Optional: Add styles if needed 5 5 6 - // This component handles the callback redirect from the Bluesky OAuth process 7 6 const LoginCallback = () => { 8 - const { loading, isAuthenticated, session, checkAuthStatus } = useAuth(); 7 + const { client } = useAuth(); // Get the client instance from context 8 + const navigate = useNavigate(); 9 + const [status, setStatus] = useState('Processing login...'); 9 10 const [error, setError] = useState(null); 10 - const [returnUrl, setReturnUrl] = useState('/'); 11 - const [processingComplete, setProcessingComplete] = useState(false); 12 - const [isRedirecting, setIsRedirecting] = useState(false); 13 - const location = useLocation(); 14 - const callbackAttempts = useRef(0); 15 - const maxAttempts = 2; 16 - 17 - // First effect: Check if we're already authenticated 11 + 18 12 useEffect(() => { 19 - // If we already have authentication, we can skip the processing 20 - if (isAuthenticated && session) { 21 - console.log('Already authenticated in initial check, preparing immediate redirect'); 22 - setProcessingComplete(true); 23 - setIsRedirecting(true); 24 - } 25 - }, []); 26 - 27 - // Second effect: Process the callback if needed 28 - useEffect(() => { 29 - // Skip processing if: 30 - // 1. We've already determined we should redirect 31 - // 2. Processing is already complete 32 - // 3. We've reached the maximum number of attempts 33 - if (isRedirecting || processingComplete || callbackAttempts.current >= maxAttempts) { 34 - return; 35 - } 36 - 37 - callbackAttempts.current += 1; 38 - console.log(`Processing callback attempt ${callbackAttempts.current}`); 39 - 40 13 const handleCallback = async () => { 14 + // Client might not be initialized immediately on page load 15 + if (!client) { 16 + setStatus('Waiting for authentication client...'); 17 + // Optionally add a small delay and retry or rely on AuthContext re-render 18 + const timeoutId = setTimeout(() => { 19 + if (!client) { // Check again after delay 20 + console.error('OAuth client not available after delay.'); 21 + setError('Authentication client failed to load. Please try logging in again.'); 22 + setStatus(''); // Clear status message 23 + } 24 + // If client became available, the effect will re-run anyway 25 + }, 2000); // Wait 2 seconds 26 + return () => clearTimeout(timeoutId); 27 + } 28 + 41 29 try { 42 - // Extract return URL from state or session storage 43 - const sessionReturnUrl = sessionStorage.getItem('returnUrl'); 44 - const params = new URLSearchParams(location.search); 45 - const stateParam = params.get('state'); 46 - 47 - // Attempt to decode state if available 48 - if (stateParam) { 49 - try { 50 - const decodedState = JSON.parse(atob(stateParam)); 51 - if (decodedState && decodedState.returnUrl) { 52 - setReturnUrl(decodedState.returnUrl); 53 - } 54 - } catch (e) { 55 - console.error('Failed to decode state parameter:', e); 56 - } 57 - } 58 - 59 - // Use sessionStorage return URL if available (it takes precedence) 60 - if (sessionReturnUrl) { 61 - setReturnUrl(sessionReturnUrl); 62 - sessionStorage.removeItem('returnUrl'); 63 - } 64 - 65 - // Check for error in URL parameters 66 - const errorParam = params.get('error'); 67 - if (errorParam) { 68 - // Only set error if we don't have a session 69 - if (!isAuthenticated || !session) { 70 - setError(errorParam); 71 - setProcessingComplete(true); 72 - } else { 73 - // We have a session despite the error param, so redirect 74 - setIsRedirecting(true); 75 - setProcessingComplete(true); 76 - } 77 - return; 78 - } 30 + console.log('(LoginCallback) Client available, attempting to handle callback...'); 31 + // The client.init() in AuthContext likely already handled the callback. 32 + // We might not need client.callback() here if init handles it. 33 + // However, keeping client.callback() as a fallback or direct handler can be robust. 34 + // Check if init() already processed it by seeing if session exists 35 + // This check might be complex depending on AuthContext's exact init timing. 79 36 80 - // If already authenticated, skip further processing 81 - if (isAuthenticated && session) { 82 - console.log('Already authenticated during callback processing, skipping auth check'); 83 - setIsRedirecting(true); 84 - setProcessingComplete(true); 85 - return; 86 - } 37 + // Let's assume AuthContext's init() handles the primary callback logic. 38 + // This component might just need to redirect based on the resulting session state. 87 39 88 - // If we get here, we need to check server authentication 89 - const authResult = await checkAuthStatus(); 90 - 91 - if (authResult || isAuthenticated) { 92 - console.log('Auth check successful, preparing redirect'); 93 - setIsRedirecting(true); 94 - setProcessingComplete(true); 95 - } else { 96 - console.error('Auth check failed, but checking for session one more time'); 97 - 98 - // Double-check session state before showing error 99 - if (session) { 100 - console.log('Found session after auth check failed, still redirecting'); 101 - setIsRedirecting(true); 102 - setProcessingComplete(true); 103 - } else { 104 - setError('Authentication failed. Could not establish a valid session.'); 105 - setProcessingComplete(true); 106 - } 107 - } 40 + // Check AuthContext state directly (may require exposing session explicitly) 41 + // Or simply redirect - AuthContext should have set the session if successful 42 + 43 + setStatus('Login successful! Redirecting...'); 44 + 45 + // Attempt to retrieve the original intended URL from state if passed during login 46 + // Note: client.init() doesn't directly return state here. We might need 47 + // AuthContext to store the state temporarily or parse it from the URL hash/query. 48 + // For simplicity, we'll redirect to home. Implement state parsing if needed. 49 + 50 + // Retrieve returnUrl from state saved during login (requires state handling) 51 + // const stateParam = new URLSearchParams(window.location.hash.substring(1)).get('state') || new URLSearchParams(window.location.search).get('state'); 52 + let returnUrl = '/'; 53 + // if (stateParam) { 54 + // try { 55 + // const decodedState = JSON.parse(atob(stateParam)); // Adjust decoding if needed 56 + // returnUrl = decodedState.returnUrl || '/'; 57 + // } catch (e) { 58 + // console.error("Error parsing state parameter:", e); 59 + // } 60 + // } 61 + 62 + // Redirect after a short delay to show the success message 63 + setTimeout(() => { 64 + navigate(returnUrl); 65 + }, 1500); 66 + 108 67 } catch (err) { 109 - console.error('Error handling login callback:', err); 110 - 111 - // If we have a session despite the error, still redirect 112 - if (session && isAuthenticated) { 113 - console.log('Error occurred but session exists, proceeding with redirect'); 114 - setIsRedirecting(true); 115 - setProcessingComplete(true); 116 - } else { 117 - setError('Failed to complete login process'); 118 - setProcessingComplete(true); 119 - } 68 + console.error('(LoginCallback) Error processing callback:', err); 69 + setError(`Login failed: ${err.message || 'Unknown error'}`); 70 + setStatus(''); 120 71 } 121 72 }; 122 73 123 74 handleCallback(); 124 - }, [location, checkAuthStatus, isAuthenticated, session, processingComplete, isRedirecting]); 125 75 126 - // Always prioritize redirecting if we're authenticated 127 - if (isAuthenticated && session) { 128 - console.log(`Redirecting to ${returnUrl} with valid session`); 129 - return <Navigate to={returnUrl} replace />; 130 - } 131 - 132 - // Show loading while still processing 133 - if (loading || (!processingComplete && !isRedirecting)) { 134 - return <Loading message="Processing login..." />; 135 - } 136 - 137 - // Show error only if we've completed processing, have an error, and don't have a valid session 138 - if (processingComplete && error && (!isAuthenticated || !session)) { 139 - return ( 140 - <div className="login-callback"> 141 - <div className="error"> 142 - <h3>Authentication Error</h3> 143 - <p>{error}</p> 144 - <a href="/login">Return to login</a> 145 - </div> 146 - </div> 147 - ); 148 - } 76 + }, [client, navigate]); // Depend on client availability 149 77 150 - // Fallback redirect when processing is complete 151 - return <Navigate to={returnUrl} replace />; 78 + return ( 79 + <div className="login-callback-container"> 80 + <h2>Authentication Callback</h2> 81 + {status && <p className="status-message">{status}</p>} 82 + {error && <p className="error-message">{error}</p>} 83 + {!status && !error && <p>Verifying authentication...</p>} 84 + {/* Add a button to retry or go home if stuck */} 85 + {(error || (!client && !status && !error)) && ( 86 + <button onClick={() => navigate('/')} className="home-button">Go to Homepage</button> 87 + )} 88 + </div> 89 + ); 152 90 }; 153 91 154 - export default LoginCallback; 92 + export default LoginCallback; 93 + 94 + // --- Basic CSS (LoginCallback.css) --- 95 + /* 96 + .login-callback-container { 97 + padding: 40px; 98 + text-align: center; 99 + max-width: 600px; 100 + margin: 40px auto; 101 + background-color: var(--navbar-bg); 102 + border: 1px solid var(--card-border); 103 + border-radius: 8px; 104 + color: var(--text); 105 + } 106 + 107 + .status-message { 108 + color: var(--text-muted); // Adjust variable if needed 109 + font-weight: bold; 110 + } 111 + 112 + .error-message { 113 + color: var(--error); // Use theme error color 114 + font-weight: bold; 115 + } 116 + 117 + .home-button { 118 + margin-top: 20px; 119 + background-color: var(--button-bg); 120 + color: var(--button-text); 121 + border: none; 122 + padding: 10px 20px; 123 + border-radius: 5px; 124 + cursor: pointer; 125 + } 126 + 127 + .home-button:hover { 128 + background-color: var(--button-hover-bg); // Adjust variable if needed 129 + } 130 + */
+77 -427
src/contexts/AuthContext.js
··· 30 30 const [session, setSession] = useState(null); 31 31 const [loading, setLoading] = useState(true); 32 32 const [error, setError] = useState(null); 33 - const lastAuthCheck = useRef(0); 34 - const authCheckInProgress = useRef(false); 35 - const didInitialCheck = useRef(false); 33 + const initializing = useRef(false); 36 34 37 - // Initialize the OAuth client 35 + // Updated initializeAuth for BrowserOAuthClient 38 36 useEffect(() => { 39 37 const initializeAuth = async () => { 40 - if (didInitialCheck.current) return; 41 - didInitialCheck.current = true; 42 - 38 + if (initializing.current || client) return; // Prevent multiple initializations 39 + initializing.current = true; 40 + setLoading(true); 41 + setError(null); 42 + console.log('(AuthProvider) Initializing BrowserOAuthClient...'); 43 + 43 44 try { 44 - // First check server-side authentication status 45 - console.log('Checking server authentication status'); 46 - const serverAuthResponse = await fetch('/api/auth/status', { 47 - credentials: 'include' 48 - }); 49 - 50 - if (!serverAuthResponse.ok) { 51 - console.error('Server auth check failed with status:', serverAuthResponse.status); 52 - } else { 53 - const serverAuthData = await serverAuthResponse.json(); 54 - console.log('Server auth status:', serverAuthData); 55 - 56 - if (serverAuthData.isAuthenticated || serverAuthData.authenticated) { 57 - console.log('Already authenticated on server, setting session'); 58 - setSession(serverAuthData.user); 59 - setLoading(false); 60 - return; 61 - } 62 - } 63 - 64 - // If not authenticated on the server, check client OAuth 65 - console.log('Not authenticated on server, initializing OAuth client'); 45 + // Create the client instance 66 46 const oauthClient = new BrowserOAuthClient({ 67 - clientMetadata, 68 - handleResolver: 'https://bsky.social', 47 + clientMetadata: clientMetadata, 48 + handleResolver: 'https://bsky.social', // Use Bluesky's resolver or your own 69 49 }); 70 50 71 - try { 72 - // Initialize the client and check for existing sessions 73 - const result = await oauthClient.init(); 74 - console.log('OAuth client initialized:', oauthClient); 75 - setClient(oauthClient); 76 - 77 - if (result?.session) { 78 - console.log('Found existing OAuth session:', result.session); 79 - 80 - // Check if atproto_session exists in localStorage as a backup 81 - const atprotoSession = localStorage.getItem('atproto_session'); 82 - console.log('atproto_session in localStorage:', atprotoSession ? 'exists' : 'not found'); 83 - 84 - // Try to extract handle from session 85 - let handle = result.session.handle; 86 - 87 - // If handle is missing, try to extract it from other sources 88 - if (!handle) { 89 - try { 90 - // Try server login data 91 - if (result.session.server && result.session.server.login && result.session.server.login.handle) { 92 - handle = result.session.server.login.handle; 93 - } 94 - // Try atproto_session in localStorage if it exists 95 - else if (atprotoSession) { 96 - try { 97 - const parsedSession = JSON.parse(atprotoSession); 98 - if (parsedSession && parsedSession.handle) { 99 - handle = parsedSession.handle; 100 - } 101 - } catch (e) { 102 - console.error('Error parsing atproto_session:', e); 103 - } 104 - } 105 - } catch (e) { 106 - console.error('Error extracting handle:', e); 107 - } 108 - } 109 - 110 - // Format session data for our internal use and sync with server 111 - const sessionData = { 112 - did: result.session.sub, 113 - handle: handle || 'unknown' // Ensure we always have a handle value 114 - }; 115 - 116 - console.log('Syncing session with server:', sessionData); 117 - 118 - // Try to sync with server 119 - try { 120 - const syncResponse = await fetch('/api/sync-session', { 121 - method: 'POST', 122 - headers: { 123 - 'Content-Type': 'application/json' 124 - }, 125 - body: JSON.stringify(sessionData), 126 - credentials: 'include' 127 - }); 128 - 129 - if (syncResponse.ok) { 130 - const syncData = await syncResponse.json(); 131 - console.log('Initial session sync successful:', syncData); 132 - setSession(syncData.user); 133 - } else { 134 - console.warn('Initial session sync failed:', await syncResponse.text()); 135 - 136 - // If sync fails, extract what we can from the result.session 137 - const handle = result.session.handle; 138 - 139 - // Try to get the handle from other properties if undefined 140 - let fallbackHandle = handle; 141 - if (!fallbackHandle) { 142 - // Check if we can extract handle from other properties 143 - try { 144 - // If we have additional properties in the session that might contain the handle 145 - if (result.session.server && result.session.server.login && result.session.server.login.handle) { 146 - fallbackHandle = result.session.server.login.handle; 147 - } else if (result.session.displayName && result.session.displayName.includes('@')) { 148 - // Sometimes displayName has the handle 149 - fallbackHandle = result.session.displayName.replace('@', ''); 150 - } else { 151 - // Fallback to 'unknown' if we can't find a handle 152 - fallbackHandle = 'unknown'; 153 - } 154 - } catch (e) { 155 - console.error('Error extracting handle from session:', e); 156 - fallbackHandle = 'unknown'; 157 - } 158 - } 159 - 160 - // Log what we're doing 161 - console.log(`Using client session as fallback with handle: ${fallbackHandle}`); 162 - 163 - // Use client session in our internal format 164 - setSession({ 165 - did: result.session.sub, 166 - handle: fallbackHandle, 167 - displayName: result.session.displayName || fallbackHandle 168 - }); 169 - } 170 - } catch (syncError) { 171 - console.error('Error syncing initial session:', syncError); 172 - 173 - // Extract handle with fallbacks similar to above 174 - const handle = result.session.handle; 175 - let fallbackHandle = handle; 176 - 177 - if (!fallbackHandle) { 178 - try { 179 - if (result.session.server && result.session.server.login && result.session.server.login.handle) { 180 - fallbackHandle = result.session.server.login.handle; 181 - } else if (result.session.displayName && result.session.displayName.includes('@')) { 182 - fallbackHandle = result.session.displayName.replace('@', ''); 183 - } else { 184 - fallbackHandle = 'unknown'; 185 - } 186 - } catch (e) { 187 - console.error('Error extracting handle from session:', e); 188 - fallbackHandle = 'unknown'; 189 - } 190 - } 191 - 192 - console.log(`Using client session after error with handle: ${fallbackHandle}`); 193 - 194 - // If sync fails, still use client session in our internal format 195 - setSession({ 196 - did: result.session.sub, 197 - handle: fallbackHandle, 198 - displayName: result.session.displayName || fallbackHandle 199 - }); 200 - } 201 - } else { 202 - console.log('No existing OAuth session found'); 51 + setClient(oauthClient); // Store the client instance 52 + 53 + // Initialize the client - this handles callbacks and session restoration 54 + const initResult = await oauthClient.init(); 55 + 56 + if (initResult?.session) { 57 + setSession(initResult.session); 58 + console.log(`(AuthProvider) Session ${initResult.state ? 'established via callback' : 'restored'}:`, initResult.session.did); 59 + if (initResult.state) { 60 + console.log('(AuthProvider) Original state from callback:', initResult.state); 61 + // Optionally, redirect based on state if needed, e.g., using navigate 62 + // const returnUrl = initResult.state.returnUrl || '/'; // Example state usage 63 + // window.location.href = returnUrl; // Or use React Router navigate 203 64 } 204 - 205 - // Listen for session deletion events 206 - oauthClient.addEventListener('deleted', (event) => { 207 - console.log('Session deletion event received:', event.data); 208 - 209 - // Get current session DID at the time of event 210 - const currentSession = session; 211 - const sessionDid = currentSession?.did || currentSession?.sub; 212 - 213 - if (event.data.did === sessionDid) { 214 - console.log('Current session was deleted, logging out'); 215 - setSession(null); 216 - 217 - // Also logout from server 218 - fetch('/api/logout', { 219 - method: 'POST', 220 - credentials: 'include' 221 - }).catch(err => { 222 - console.error('Error during server logout after deletion:', err); 223 - }); 224 - } 225 - }); 226 - } catch (oauthError) { 227 - console.error('OAuth client initialization error:', oauthError); 65 + } else { 66 + setSession(null); 67 + console.log('(AuthProvider) No active session found or callback processed.'); 228 68 } 229 - 230 - setLoading(false); 231 69 } catch (err) { 232 - console.error('Auth initialization error:', err); 233 - setError(err.message); 70 + console.error('(AuthProvider) Error initializing client or handling callback:', err); 71 + setError('Initialization failed. Please try refreshing.'); 72 + setSession(null); 73 + } finally { 234 74 setLoading(false); 75 + initializing.current = false; 76 + console.log('(AuthProvider) Initialization complete.'); 235 77 } 236 78 }; 237 79 238 80 initializeAuth(); 239 - }, []); 81 + }, [client]); // Dependency on client ensures it runs only once after client is potentially set 240 82 241 - // Initiate the login process 242 - const login = async (handle, returnUrl) => { 243 - if (!client) return; 244 - 245 - try { 246 - // Save returnUrl to session storage if provided 247 - if (returnUrl) { 248 - sessionStorage.setItem('returnUrl', returnUrl); 249 - } 250 - 251 - // Create state parameter with returnUrl 252 - const state = returnUrl ? 253 - btoa(JSON.stringify({ returnUrl })) : 254 - undefined; 255 - 256 - // Pass state parameter to signIn method 257 - await client.signIn(handle, { state }); 258 - } catch (err) { 259 - console.error('Login failed:', err); 260 - setError(err.message); 83 + // Updated login function - uses client.signIn() 84 + const login = useCallback(async (handle, returnUrl = '/') => { 85 + if (!client) { 86 + setError("Client not initialized."); 87 + return; 261 88 } 262 - }; 263 - 264 - // Logout the user 265 - const logout = async () => { 89 + console.log(`(AuthProvider) Initiating client-side login for handle: ${handle || 'none specified'}`); 266 90 try { 267 - console.log('Starting logout process'); 268 - 269 - // First, try to logout from the server 270 - try { 271 - const response = await fetch('/api/logout', { 272 - method: 'POST', 273 - credentials: 'include' 274 - }); 275 - 276 - if (response.ok) { 277 - console.log('Server logout successful'); 278 - } else { 279 - console.warn('Server logout failed, continuing with client logout'); 280 - } 281 - } catch (serverLogoutErr) { 282 - console.error('Error during server logout:', serverLogoutErr); 283 - // Continue with client-side logout even if server logout fails 284 - } 285 - 286 - // Clear the session state immediately 287 - setSession(null); 288 - 289 - // If we have a client, try to clear its session too 290 - if (client) { 291 - try { 292 - console.log('Attempting to clear OAuth client session'); 293 - 294 - // Clear OAuth-specific storage items 295 - localStorage.removeItem('atproto_session'); 296 - localStorage.removeItem('atproto_state'); 297 - localStorage.removeItem('atproto_refresh_token'); 298 - 299 - // Check if there are any other localStorage items with 'atproto' in the key 300 - Object.keys(localStorage).forEach(key => { 301 - if (key.includes('atproto')) { 302 - console.log(`Removing localStorage item: ${key}`); 303 - localStorage.removeItem(key); 304 - } 305 - }); 306 - } catch (clientErr) { 307 - console.error('Error clearing client storage:', clientErr); 308 - } 309 - } else { 310 - console.warn('No OAuth client available for logout'); 311 - } 312 - 313 - // Force a reload to ensure all state is cleared 314 - console.log('Completing logout - reloading page'); 315 - window.location.href = '/'; 91 + // The state can be used to pass information through the redirect, like the return URL 92 + const stateData = JSON.stringify({ returnUrl }); 93 + // signIn redirects the browser, so code execution stops here if successful 94 + await client.signIn(handle || 'https://bsky.social', { // Use handle or default PDS 95 + state: stateData, 96 + // prompt: 'none', // Uncomment for silent sign-in attempt 97 + }); 316 98 } catch (err) { 317 - console.error('Logout process error:', err); 318 - // Still try to reload even if there are errors 319 - window.location.href = '/'; 320 - } 321 - }; 322 - 323 - // Check server-side authentication status with debounce 324 - const checkAuthStatus = useCallback(async () => { 325 - // Avoid concurrent auth checks 326 - if (authCheckInProgress.current) { 327 - return !!session; 328 - } 329 - 330 - // Rate limiting - prevent checks more frequently than every 3 seconds 331 - const now = Date.now(); 332 - if (now - lastAuthCheck.current < 3000) { 333 - return !!session; // Return current auth state if called too frequently 99 + // This catch might run if the user navigates back or cancels 100 + console.error('(AuthProvider) Error during signIn initiation or cancellation:', err); 101 + setError('Login initiation failed or was cancelled.'); 334 102 } 335 - 336 - authCheckInProgress.current = true; 337 - lastAuthCheck.current = now; 103 + }, [client]); 338 104 339 - try { 340 - console.log('Checking auth status...'); 341 - 342 - const controller = new AbortController(); 343 - // Set a timeout for the fetch to prevent hanging requests 344 - const timeoutId = setTimeout(() => controller.abort(), 5000); 345 - 346 - const response = await fetch('/api/auth/status', { 347 - credentials: 'include', 348 - signal: controller.signal 349 - }); 350 - 351 - clearTimeout(timeoutId); 352 - 353 - if (!response.ok) { 354 - console.error('Auth status check failed with status:', response.status); 355 - // If we get a server error, we should still rely on client-side session 356 - // to prevent users from getting logged out due to temporary server issues 357 - authCheckInProgress.current = false; 358 - return !!session; 359 - } 360 - 361 - const data = await response.json(); 362 - console.log('Auth status check response:', data); 363 - 364 - const isAuthenticated = data.isAuthenticated || data.authenticated; 365 - 366 - if (isAuthenticated && data.user) { 367 - // If server session is different from current session, update it 368 - const currentSessionJSON = session ? JSON.stringify(session) : ''; 369 - const newSessionJSON = JSON.stringify(data.user); 370 - 371 - if (currentSessionJSON !== newSessionJSON) { 372 - console.log('Updating session from server data'); 373 - setSession(data.user); 374 - } 375 - 376 - authCheckInProgress.current = false; 377 - return true; 378 - } else { 379 - // If server says not authenticated but we have a client session, 380 - // try to synchronize sessions 381 - if (session && client) { 382 - try { 383 - console.log('Server says not authenticated but we have a client session, trying to sync'); 384 - 385 - // Extract handle from current session 386 - let handle = session.handle; 387 - if (!handle) { 388 - // If our session doesn't have a handle, try to extract from other properties 389 - try { 390 - if (session.displayName && typeof session.displayName === 'string') { 391 - // Sometimes the handle might be in the displayName 392 - handle = session.displayName.includes('@') ? 393 - session.displayName.replace('@', '') : 394 - session.displayName; 395 - } 396 - } catch (e) { 397 - console.error('Error extracting handle from session:', e); 398 - } 399 - } 400 - 401 - // Format session data properly 402 - const sessionData = { 403 - did: session.did || session.sub, 404 - handle: handle || 'unknown' // Always provide a handle value 405 - }; 406 - 407 - console.log('Syncing with data:', sessionData); 408 - 409 - // Add a timeout for sync request 410 - const syncController = new AbortController(); 411 - const syncTimeoutId = setTimeout(() => syncController.abort(), 5000); 412 - 413 - try { 414 - // Try to sync one more time 415 - const syncResponse = await fetch('/api/sync-session', { 416 - method: 'POST', 417 - headers: { 418 - 'Content-Type': 'application/json' 419 - }, 420 - body: JSON.stringify(sessionData), 421 - credentials: 'include', 422 - signal: syncController.signal 423 - }); 424 - 425 - clearTimeout(syncTimeoutId); 426 - 427 - if (syncResponse.ok) { 428 - console.log('Session sync successful during status check'); 429 - const syncData = await syncResponse.json(); 430 - setSession(syncData.user); 431 - authCheckInProgress.current = false; 432 - return true; 433 - } else { 434 - console.error('Sync response was not ok:', syncResponse.status); 435 - // If server explicitly rejects our sync attempt, we should clear session 436 - setSession(null); 437 - authCheckInProgress.current = false; 438 - return false; 439 - } 440 - } catch (syncFetchError) { 441 - clearTimeout(syncTimeoutId); 442 - console.error('Network error during sync request:', syncFetchError); 443 - 444 - // On network errors, we should keep the current session state to prevent 445 - // users from being logged out due to temporary connectivity issues 446 - authCheckInProgress.current = false; 447 - return !!session; 448 - } 449 - } catch (syncError) { 450 - console.error('Error in sync logic during status check:', syncError); 451 - // If there's an error in our sync logic, keep current session 452 - authCheckInProgress.current = false; 453 - return !!session; 454 - } 455 - } 456 - 457 - // If all attempts failed and the server says we're not authenticated 458 - console.log('Server says not authenticated, clearing session'); 105 + // Updated Logout function - uses session.signOut() 106 + const logout = useCallback(async () => { 107 + if (!session) return; 108 + console.log('(AuthProvider) Logging out...'); 109 + try { 110 + await session.signOut(); // Use session's signOut method 459 111 setSession(null); 460 - authCheckInProgress.current = false; 461 - return false; 462 - } 463 - } catch (err) { 464 - console.error('Error checking auth status:', err); 465 - // For network errors, don't log out the user 466 - if (err.name === 'AbortError') { 467 - console.warn('Auth status check timed out'); 468 - } else if (err.name === 'TypeError' && err.message.includes('Network request failed')) { 469 - console.warn('Network request failed during auth check - keeping current session state'); 470 - } 471 - 472 - authCheckInProgress.current = false; 473 - return !!session; // Fall back to current session state on errors 474 - } 475 - }, [session, client]); 112 + // Optionally clear other app state here 113 + console.log('(AuthProvider) Logout complete.'); 114 + // Redirect to home or login page 115 + window.location.href = '/'; // Force reload to ensure clean state 116 + } catch (err) { 117 + console.error('(AuthProvider) Error during logout:', err); 118 + // Still attempt to clear local session on error 119 + setSession(null); 120 + window.location.href = '/'; // Force reload 121 + } 122 + }, [session]); 476 123 477 124 return ( 478 - <AuthContext.Provider 479 - value={{ 480 - session, 481 - loading, 125 + <AuthContext.Provider 126 + value={{ 127 + client, // Export the client instance if needed by components (e.g., LoginCallback) 128 + session, 129 + loading, // Renamed from authLoading for clarity 482 130 error, 483 131 isAuthenticated: !!session, 484 - login, 132 + login, 485 133 logout, 486 - checkAuthStatus 134 + // Remove checkAuthStatus export 487 135 }} 488 136 > 489 137 {children} ··· 497 145 if (context === null) { 498 146 throw new Error('useAuth must be used within an AuthProvider'); 499 147 } 148 + // Ensure components using the hook get the updated context value 149 + // (React handles this, but good to be mindful) 500 150 return context; 501 151 };