feat: Update PWA service worker for authentication
- Add authentication pages to static cache (/auth/login, /auth/register, /auth/error) - Implement special handling for authentication API requests (network-only with offline error) - Add authentication page caching strategy (cache-first with network fallback) - Prevent caching of authentication errors (401, 403) - Enhance offline page with online detection and auto-redirect - Add authentication notice to offline page - Update cache version to v2 for proper invalidation - Create PWA_AUTH_GUIDE.md documentation Task: 19. Update PWA service worker for authentication Requirements: 4.4
This commit is contained in:
@@ -264,7 +264,7 @@
|
||||
- [ ] 18. Checkpoint - Ensure all tests pass
|
||||
- Ensure all tests pass, ask the user if questions arise.
|
||||
|
||||
- [ ] 19. Update PWA service worker for authentication
|
||||
- [x] 19. Update PWA service worker for authentication
|
||||
- Handle authentication errors in offline mode
|
||||
- Cache authentication-related pages appropriately
|
||||
- Update manifest if needed
|
||||
|
||||
189
PWA_AUTH_GUIDE.md
Normal file
189
PWA_AUTH_GUIDE.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# PWA Authentication Guide
|
||||
|
||||
## Overview
|
||||
|
||||
The Ultimate Lists App PWA has been updated to handle authentication properly in offline scenarios. This guide explains how the service worker manages authentication-related requests and what users can expect when offline.
|
||||
|
||||
## Service Worker Updates
|
||||
|
||||
### Cache Version
|
||||
|
||||
The service worker cache has been updated to version 2 (`ultimate-lists-v2`) to ensure proper cache invalidation and updates.
|
||||
|
||||
### Authentication Pages Caching
|
||||
|
||||
The following authentication-related pages are now cached during service worker installation:
|
||||
|
||||
- `/auth/login` - Login page
|
||||
- `/auth/register` - Registration page
|
||||
- `/auth/error` - Authentication error page
|
||||
|
||||
This allows users to view these pages even when offline, though actual authentication requires an internet connection.
|
||||
|
||||
### Authentication Request Handling
|
||||
|
||||
#### API Authentication Requests (`/api/auth/*`)
|
||||
|
||||
Authentication API calls (login, register, session validation) always require a network connection. When offline:
|
||||
|
||||
- The service worker returns a 503 Service Unavailable response
|
||||
- A JSON error message explains that authentication requires internet connectivity
|
||||
- The error message: "Authentication requires an internet connection. Please check your connection and try again."
|
||||
|
||||
#### Authentication Pages (`/auth/*`)
|
||||
|
||||
Authentication pages use a cache-first strategy with network fallback:
|
||||
|
||||
1. Try to fetch the latest version from the network
|
||||
2. Cache successful responses for offline access
|
||||
3. If offline, serve the cached version
|
||||
4. If no cached version exists, redirect to the offline page
|
||||
|
||||
### Authentication Error Handling
|
||||
|
||||
The service worker now properly handles authentication errors (401, 403):
|
||||
|
||||
- Authentication errors are **not cached**
|
||||
- This prevents stale authentication states from being served
|
||||
- Users always get fresh authentication status from the server
|
||||
|
||||
### Offline Page Enhancements
|
||||
|
||||
The offline page (`/app/offline/page.tsx`) has been enhanced with:
|
||||
|
||||
1. **Online Detection**: Automatically detects when the connection is restored
|
||||
2. **Auto-redirect**: Redirects to the home page when back online
|
||||
3. **Authentication Notice**: Displays a clear message that login/registration requires internet
|
||||
4. **Visual Feedback**: Shows different states for offline vs. back online
|
||||
|
||||
## User Experience
|
||||
|
||||
### When Online
|
||||
|
||||
- All authentication features work normally
|
||||
- Login, registration, and session management function as expected
|
||||
- Pages are cached for faster subsequent loads
|
||||
|
||||
### When Offline
|
||||
|
||||
#### Authenticated Users
|
||||
|
||||
- Can view cached lists and tasks
|
||||
- Can make changes that will sync when back online
|
||||
- Session remains valid (until expiration)
|
||||
- Cannot perform new authentication actions
|
||||
|
||||
#### Unauthenticated Users
|
||||
|
||||
- Can view cached authentication pages (login/register forms)
|
||||
- Cannot actually log in or register (requires internet)
|
||||
- See clear error messages explaining the need for connectivity
|
||||
- Are directed to the offline page with authentication guidance
|
||||
|
||||
## Testing Offline Behavior
|
||||
|
||||
### Manual Testing
|
||||
|
||||
1. **Test Offline Login Attempt**:
|
||||
- Open the app while online
|
||||
- Navigate to `/auth/login`
|
||||
- Disconnect from the internet (or use DevTools offline mode)
|
||||
- Try to submit login credentials
|
||||
- Verify error message appears
|
||||
|
||||
2. **Test Cached Authentication Pages**:
|
||||
- Visit `/auth/login` while online
|
||||
- Disconnect from the internet
|
||||
- Refresh the page
|
||||
- Verify the login page loads from cache
|
||||
|
||||
3. **Test Online Detection**:
|
||||
- Navigate to `/offline` while offline
|
||||
- Reconnect to the internet
|
||||
- Verify automatic redirect to home page
|
||||
|
||||
### Chrome DevTools Testing
|
||||
|
||||
1. Open Chrome DevTools (F12)
|
||||
2. Go to Application tab → Service Workers
|
||||
3. Check "Offline" to simulate offline mode
|
||||
4. Test authentication flows
|
||||
5. Verify cache contents in Application → Cache Storage
|
||||
|
||||
## Technical Implementation
|
||||
|
||||
### Service Worker Strategy
|
||||
|
||||
```javascript
|
||||
// Authentication API calls - Network only
|
||||
if (url.pathname.startsWith('/api/auth/')) {
|
||||
// Always try network, return 503 if offline
|
||||
}
|
||||
|
||||
// Authentication pages - Cache-first with network fallback
|
||||
if (url.pathname.startsWith('/auth/')) {
|
||||
// Try network, fall back to cache, then offline page
|
||||
}
|
||||
```
|
||||
|
||||
### Error Response Format
|
||||
|
||||
When authentication API calls fail offline:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Authentication requires an internet connection. Please check your connection and try again."
|
||||
}
|
||||
```
|
||||
|
||||
### Cache Strategy Summary
|
||||
|
||||
| Resource Type | Strategy | Offline Behavior |
|
||||
|--------------|----------|------------------|
|
||||
| Authentication API | Network only | Return 503 error |
|
||||
| Authentication Pages | Cache-first | Serve cached version |
|
||||
| Static Assets | Cache-first | Serve cached version |
|
||||
| API Responses | Stale-while-revalidate | Serve cached, update in background |
|
||||
| Regular Pages | Stale-while-revalidate | Serve cached or offline page |
|
||||
|
||||
## Security Considerations
|
||||
|
||||
1. **No Credential Caching**: User credentials are never cached by the service worker
|
||||
2. **Session Validation**: Sessions are validated server-side on each request
|
||||
3. **Error State Handling**: Authentication errors (401, 403) are not cached
|
||||
4. **Secure Token Storage**: Session tokens are managed by NextAuth.js, not the service worker
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential improvements for authentication in offline scenarios:
|
||||
|
||||
- Queue authentication attempts for when connection is restored
|
||||
- Show estimated time until session expiration
|
||||
- Provide offline-specific UI hints for authenticated users
|
||||
- Implement background sync for authentication state updates
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Service Worker Not Updating
|
||||
|
||||
If changes aren't reflected:
|
||||
|
||||
1. Unregister the old service worker in DevTools
|
||||
2. Clear cache storage
|
||||
3. Hard refresh the page (Ctrl+Shift+R)
|
||||
|
||||
### Authentication Not Working Offline
|
||||
|
||||
This is expected behavior. Authentication requires:
|
||||
|
||||
- Active internet connection
|
||||
- Server communication for credential validation
|
||||
- Session token generation and validation
|
||||
|
||||
### Cached Pages Showing Old Content
|
||||
|
||||
The service worker updates cached pages when online. To force an update:
|
||||
|
||||
1. Ensure you're online
|
||||
2. The service worker checks for updates every minute
|
||||
3. Or manually update in DevTools → Application → Service Workers → Update
|
||||
@@ -1,8 +1,30 @@
|
||||
'use client';
|
||||
|
||||
import { WifiOff } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
export default function OfflinePage() {
|
||||
const [isOnline, setIsOnline] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
// Check if we're back online
|
||||
const handleOnline = () => {
|
||||
setIsOnline(true);
|
||||
// Automatically redirect after a short delay
|
||||
setTimeout(() => {
|
||||
router.push('/');
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
window.addEventListener('online', handleOnline);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('online', handleOnline);
|
||||
};
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50 px-4">
|
||||
<div className="text-center max-w-md">
|
||||
@@ -13,36 +35,52 @@ export default function OfflinePage() {
|
||||
</div>
|
||||
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-4">
|
||||
You're Offline
|
||||
{isOnline ? "You're Back Online!" : "You're Offline"}
|
||||
</h1>
|
||||
|
||||
<p className="text-gray-600 mb-8">
|
||||
It looks like you've lost your internet connection. Don't worry, your data is safe and will sync when you're back online.
|
||||
</p>
|
||||
{isOnline ? (
|
||||
<p className="text-gray-600 mb-8">
|
||||
Your connection has been restored. Redirecting...
|
||||
</p>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-gray-600 mb-8">
|
||||
It looks like you've lost your internet connection. Don't worry, your data is safe and will sync when you're back online.
|
||||
</p>
|
||||
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 text-left">
|
||||
<h2 className="font-semibold text-gray-900 mb-3">What you can do:</h2>
|
||||
<ul className="space-y-2 text-gray-600">
|
||||
<li className="flex items-start">
|
||||
<span className="text-blue-600 mr-2">•</span>
|
||||
<span>View your cached lists and tasks</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-blue-600 mr-2">•</span>
|
||||
<span>Make changes that will sync later</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-blue-600 mr-2">•</span>
|
||||
<span>Check your connection and try again</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6 text-left mb-6">
|
||||
<h2 className="font-semibold text-gray-900 mb-3">What you can do:</h2>
|
||||
<ul className="space-y-2 text-gray-600">
|
||||
<li className="flex items-start">
|
||||
<span className="text-blue-600 mr-2">•</span>
|
||||
<span>View your cached lists and tasks</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-blue-600 mr-2">•</span>
|
||||
<span>Make changes that will sync later</span>
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="text-blue-600 mr-2">•</span>
|
||||
<span>Check your connection and try again</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="bg-amber-50 rounded-lg border border-amber-200 p-4 text-left mb-6">
|
||||
<h3 className="font-semibold text-amber-900 mb-2 text-sm">Note about authentication:</h3>
|
||||
<p className="text-amber-800 text-sm">
|
||||
Login and registration require an internet connection. If you need to sign in, please connect to the internet first.
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="mt-8 bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors"
|
||||
className="bg-blue-600 text-white px-6 py-3 rounded-lg font-medium hover:bg-blue-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled={isOnline}
|
||||
>
|
||||
Try Again
|
||||
{isOnline ? 'Redirecting...' : 'Try Again'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
78
public/sw.js
78
public/sw.js
@@ -1,13 +1,16 @@
|
||||
// Service Worker for Ultimate Lists App
|
||||
// Implements caching strategies for offline functionality
|
||||
|
||||
const CACHE_NAME = 'ultimate-lists-v1';
|
||||
const RUNTIME_CACHE = 'ultimate-lists-runtime-v1';
|
||||
const CACHE_NAME = 'ultimate-lists-v2';
|
||||
const RUNTIME_CACHE = 'ultimate-lists-runtime-v2';
|
||||
|
||||
// Static assets to cache on install
|
||||
const STATIC_ASSETS = [
|
||||
'/',
|
||||
'/offline',
|
||||
'/auth/login',
|
||||
'/auth/register',
|
||||
'/auth/error',
|
||||
'/manifest.json',
|
||||
'/icon-192.svg',
|
||||
'/icon-512.svg',
|
||||
@@ -58,6 +61,12 @@ self.addEventListener('fetch', (event) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle authentication-related requests
|
||||
if (isAuthRequest(url)) {
|
||||
event.respondWith(handleAuthRequest(request));
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache-first strategy for static assets
|
||||
if (isStaticAsset(url)) {
|
||||
event.respondWith(cacheFirst(request));
|
||||
@@ -90,6 +99,58 @@ function isPageRequest(url) {
|
||||
return url.origin === self.location.origin && !url.pathname.includes('.');
|
||||
}
|
||||
|
||||
// Helper: Check if request is authentication-related
|
||||
function isAuthRequest(url) {
|
||||
return url.pathname.startsWith('/auth/') ||
|
||||
url.pathname.startsWith('/api/auth/');
|
||||
}
|
||||
|
||||
// Strategy: Handle authentication requests
|
||||
async function handleAuthRequest(request) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
// For authentication API calls, always try network first
|
||||
if (url.pathname.startsWith('/api/auth/')) {
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Authentication API request failed (offline):', error);
|
||||
// Return a JSON error response for API calls
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: 'Authentication requires an internet connection. Please check your connection and try again.'
|
||||
}),
|
||||
{
|
||||
status: 503,
|
||||
statusText: 'Service Unavailable',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// For authentication pages, use cache-first with network fallback
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
const cached = await cache.match(request);
|
||||
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
if (response.ok) {
|
||||
cache.put(request, response.clone());
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Authentication page request failed:', error);
|
||||
// Return cached version if available
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
// Otherwise redirect to offline page
|
||||
return caches.match('/offline');
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy: Cache-first (for static assets)
|
||||
async function cacheFirst(request) {
|
||||
const cache = await caches.open(CACHE_NAME);
|
||||
@@ -117,6 +178,12 @@ async function staleWhileRevalidate(request) {
|
||||
const cached = await cache.match(request);
|
||||
|
||||
const fetchPromise = fetch(request).then((response) => {
|
||||
// Handle authentication errors (401, 403)
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
// Don't cache authentication errors
|
||||
return response;
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
cache.put(request, response.clone());
|
||||
}
|
||||
@@ -141,6 +208,13 @@ async function staleWhileRevalidate(request) {
|
||||
async function networkFirst(request) {
|
||||
try {
|
||||
const response = await fetch(request);
|
||||
|
||||
// Handle authentication errors (401, 403)
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
// Don't cache authentication errors
|
||||
return response;
|
||||
}
|
||||
|
||||
if (response.ok) {
|
||||
const cache = await caches.open(RUNTIME_CACHE);
|
||||
cache.put(request, response.clone());
|
||||
|
||||
Reference in New Issue
Block a user