MuteSky uses Bluesky's OAuth implementation through the @atproto/oauth-client-browser
library. The system ensures proper session management, token refresh, and state persistence across user sessions.
- Initial Setup
this.client = await BrowserOAuthClient.load({
clientId: 'https://mutesky.app/client-metadata.json',
handleResolver: 'https://bsky.social/'
});
- Sign In Process
- User enters Bluesky handle
- App initiates OAuth flow with
client.signIn(handle)
- User redirects to Bluesky for authorization
- Bluesky redirects to callback page
- Callback processes response and establishes session
-
AuthService (js/auth.js)
- Handles OAuth client initialization
- Manages sign in/out operations
- Provides session refresh capabilities
-
BlueskyService (js/bluesky.js)
- Coordinates between services
- Handles session state changes
- Manages session refresh events
-
Individual Services
- Maintain own session references
- Handle API calls with current session
- Dispatch refresh events when needed
-
No Session
- Initial app load
- After sign out
- After failed authentication
-
Active Session
- After successful sign in
- After successful token refresh
- Contains valid access token
-
Expired Session
- Token has expired
- Triggers refresh flow
- Temporary state during refresh
- Detection
if (error.status === 401) {
// Dispatch event for session refresh
const refreshEvent = new CustomEvent('mutesky:session:refresh:needed');
window.dispatchEvent(refreshEvent);
}
- Handling
setupSessionRefreshHandler() {
window.addEventListener('mutesky:session:refresh:needed', async () => {
if (this.isRefreshing) return; // Prevent multiple refreshes
try {
this.isRefreshing = true;
const result = await this.auth.refreshSession();
if (result.success) {
// Update services with new session
this.profile.setSession(result.session);
this.mute.setSession(result.session);
} else {
await this.signOut();
}
} finally {
this.isRefreshing = false;
}
});
}
<div class="callback-container">
<h2>Authentication Successful</h2>
<p class="status-text">✨ Rendering keywords</p>
<div class="progress-container">
<div class="progress-bar"></div>
</div>
<p id="error" class="error-message"></p>
<a href="/" class="home-link">Return to app</a>
</div>
mutesky:auth:complete
: Fired when OAuth callback processing finishesmutesky:setup:complete
: Fired when initial data loading finishes
showError(message) {
this.container.classList.add('error');
this.titleElement.textContent = 'Authentication Failed';
this.errorElement.textContent = message;
this.homeLink.style.display = 'block';
}
-
Token Expiration
- Detected through 401 responses
- Triggers automatic refresh attempt
- Retries failed operation if refresh succeeds
- Signs out user if refresh fails
-
Network Issues
- Services clear cached data on errors
- Errors are logged with context
- User-friendly error messages displayed
-
Race Conditions
- Prevents multiple simultaneous refresh attempts
- Maintains consistent session state across services
- Cleans up properly on sign out
-
Session Storage
- OAuth client handles token storage
- Services maintain runtime session references
- No sensitive data stored in localStorage
-
Service Coordination
// Example: Updating services with new session
this.profile.setSession(result.session);
this.mute.setSession(result.session);
this.ui.updateLoginState(true);
- Error Recovery
try {
await operation();
} catch (error) {
if (error.status === 401) {
// Trigger refresh flow
window.dispatchEvent(new CustomEvent('mutesky:session:refresh:needed'));
}
// Clear any cached data
this.cachedData = null;
}
-
Session Management
- Clear cached data when session changes
- Handle 401 errors at service level
- Use event system for refresh coordination
- Prevent multiple simultaneous refreshes
-
Error Handling
- Provide clear user feedback
- Log errors with context
- Clean up state on failures
- Handle race conditions
-
Security
- No sensitive data in localStorage
- Clear all state on logout
- Validate session before operations
- Handle token refresh securely