-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Test #69
Test #69
Conversation
- Add web app manifest and service worker - Create PWA installation prompts and update notifications - Add offline fallback page with pending trades display - Implement offline detection and visual indicators - Create background sync for offline trades - Add IndexedDB storage for offline persistence - Update build configuration for PWA assets 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
Reviewer's Guide by SourceryThis pull request introduces Progressive Web App (PWA) support with offline capabilities. It includes a service worker for caching and background synchronization, a manifest file for PWA metadata, and components for prompting installation and update notifications. Additionally, it implements offline trade queuing using Zustand and IndexedDB, ensuring trades are processed when the app regains connectivity. No diagrams generated as the changes look simple and do not need a visual representation. File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @ashkan-deriv - I've reviewed your changes - here's some feedback:
Overall Comments:
- Consider adding a comment explaining the purpose of the PWAProvider component.
- The service worker file is quite large; consider using a build tool to minimize it.
Here's what I looked at during the review
- 🟡 General issues: 2 issues found
- 🟢 Security: all looks good
- 🟢 Testing: all looks good
- 🟡 Complexity: 1 issue found
- 🟢 Documentation: all looks good
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
@@ -41,7 +63,7 @@ export const TradeButton: React.FC<TradeButtonProps> = ({ | |||
className | |||
)} | |||
variant="default" | |||
onClick={onClick} | |||
onClick={handleClick} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Consider replacing alert with a non-blocking notification.
Using alert can disrupt the user experience. If possible, integrate a more seamless and non-blocking mechanism to notify users when offline.
Suggested implementation:
import React from 'react';
import { toast } from 'react-toastify';
if (!navigator.onLine) {
toast.error('You are offline, please check your connection.');
return;
}
- Offline functionality | ||
- Install prompts for adding to home screen | ||
- Background synchronization for offline trades | ||
- Push notifications support |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Provide details about push notification implementation and usage
The documentation mentions push notification support, but lacks details about how it's implemented and used within the app. Adding this information would be beneficial.
- Push notifications support | |
- Push notifications support | |
- Implemented with the Web Push API, allowing real-time notifications. | |
- A dedicated service worker listens for push events and displays notifications even when the app is in the background. | |
- The server triggers notifications via a configured push service, ensuring users are alerted to relevant trading updates. |
@@ -0,0 +1,223 @@ | |||
// Cache name with version for easy updating |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
issue (complexity): Consider extracting caching strategies, background sync/IndexedDB interactions, and push notification handling into separate modules to improve maintainability and scalability of the service worker.
You’re combining several distinct responsibilities in one file. This can make it harder to maintain or extend things later. For example, you might extract caching logic, background sync/IndexedDB interactions, and push notification handling into separate modules. Consider the following approach:
-
Extract caching strategies into a module (e.g.,
sw-cache.js
):// sw-cache.js export const CACHE_NAME = "champion-trader-v1"; export const STATIC_ASSETS = ["/", "/index.html", "/offline.html", "/manifest.json", "/favicon.ico", "/logo.png", "/logo192.png", "/logo512.png", "/icons/trade.png"]; export async function cacheFirstStrategy(request) { const cachedResponse = await caches.match(request); if (cachedResponse) return cachedResponse; try { const networkResponse = await fetch(request); if (networkResponse && networkResponse.status === 200) { const cache = await caches.open(CACHE_NAME); cache.put(request, networkResponse.clone()); } return networkResponse; } catch (error) { if (request.headers.get("accept").includes("text/html")) { return caches.match("/offline.html"); } throw error; } } export async function networkFirstStrategy(request) { try { const networkResponse = await fetch(request); if (networkResponse && networkResponse.status === 200) { const cache = await caches.open(CACHE_NAME); cache.put(request, networkResponse.clone()); } return networkResponse; } catch (error) { const cachedResponse = await caches.match(request); if (cachedResponse) return cachedResponse; throw error; } }
-
Extract background sync and IndexedDB logic (e.g.,
sw-sync.js
):// sw-sync.js async function openDatabase() { return new Promise((resolve, reject) => { const request = indexedDB.open("ChampionTraderDB", 1); request.onupgradeneeded = (event) => { const db = event.target.result; if (!db.objectStoreNames.contains("pendingTrades")) { db.createObjectStore("pendingTrades", { keyPath: "id" }); } }; request.onsuccess = (event) => { const db = event.target.result; resolve({ getAll: (storeName) => { return new Promise((resolve, reject) => { const transaction = db.transaction(storeName, "readonly"); const store = transaction.objectStore(storeName); const request = store.getAll(); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); }, delete: (storeName, id) => { return new Promise((resolve, reject) => { const transaction = db.transaction(storeName, "readwrite"); const store = transaction.objectStore(storeName); const request = store.delete(id); request.onsuccess = () => resolve(); request.onerror = () => reject(request.error); }); }, }); }; request.onerror = () => reject(request.error); }); } export async function syncPendingTrades() { try { const db = await openDatabase(); const pendingTrades = await db.getAll("pendingTrades"); for (const trade of pendingTrades) { try { const response = await fetch("/api/trade", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(trade), }); if (response.ok) await db.delete("pendingTrades", trade.id); } catch (error) { console.error("Failed to sync trade:", error); } } } catch (error) { console.error("Sync failed:", error); } }
-
Extract push notification handlers (e.g.,
sw-push.js
):// sw-push.js export function handlePushEvent(event) { if (!event.data) return; const data = event.data.json(); const options = { body: data.body, icon: "/logo192.png", badge: "/logo192.png", vibrate: [100, 50, 100], data: { url: data.url || "/" }, }; event.waitUntil(self.registration.showNotification(data.title, options)); } export function handleNotificationClick(event) { event.notification.close(); event.waitUntil( clients.matchAll({ type: "window" }).then((clientList) => { for (const client of clientList) { if (client.url === event.notification.data.url && "focus" in client) { return client.focus(); } } if (clients.openWindow) { return clients.openWindow(event.notification.data.url); } }) ); }
-
Refactor the main Service Worker (
sw.js
) to import and use these modules:importScripts('sw-cache.js', 'sw-sync.js', 'sw-push.js'); self.addEventListener("install", (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS)) ); self.skipWaiting(); }); self.addEventListener("activate", (event) => { event.waitUntil( caches.keys().then((cacheNames) => Promise.all( cacheNames.filter((name) => name !== CACHE_NAME) .map((name) => caches.delete(name)) ) ) ); self.clients.claim(); }); self.addEventListener("fetch", (event) => { if (!event.request.url.startsWith(self.location.origin)) return; if (event.request.url.includes("/api/") || event.request.url.includes("/sse/") || event.request.headers.get("accept").includes("application/json")) { event.respondWith(networkFirstStrategy(event.request)); } else { event.respondWith(cacheFirstStrategy(event.request)); } }); self.addEventListener("sync", (event) => { if (event.tag === "sync-trades") event.waitUntil(syncPendingTrades()); }); self.addEventListener("push", handlePushEvent); self.addEventListener("notificationclick", handleNotificationClick);
This modular approach keeps different PWA concerns isolated, making each part easier to test, maintain, and extend—without altering functionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've completed a thorough code review of the Pull Request that implements Progressive Web App (PWA) functionality for the Champion Trader application. The implementation adds important features like service worker for offline capabilities, PWA installation prompt, update notifications, offline indicator, background sync for trades, and proper manifest configuration. I've identified 12 issues that should be addressed before merging, ranging from variable naming improvements to more critical issues like missing message handlers and type safety concerns. Addressing these issues will make the PWA implementation more robust and aligned with best practices.
I'm available to help with any questions or follow-up tasks related to this review or other aspects of the repository.
Follow-up suggestions: @devloai help implement the missing SKIP_WAITING message handler in the service worker
import UpdateNotification from "./UpdateNotification"; | ||
|
||
export const PWAProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { | ||
const [registration, setRegistration] = useState<ServiceWorkerRegistration | null>(null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using a more descriptive name for the registration state, such as serviceWorkerRegistration
to make it clear what kind of registration this is.
const CACHE_NAME = "champion-trader-v1"; | ||
|
||
// Assets to cache on install | ||
const STATIC_ASSETS = [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The STATIC_ASSETS
array is not complete. You are missing the actual JavaScript and CSS files that would be generated during build. Consider using a build-time plugin to generate this list automatically, or at minimum add a comment to explain that additional assets need to be added after build.
|
||
interface OfflineState { | ||
isOnline: boolean; | ||
pendingTrades: any[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using any[]
for pendingTrades
is not type-safe. Consider creating a proper interface for the trade objects that will be stored offline.
}); | ||
|
||
// Show a notification that the trade will be processed when online | ||
// This would typically be handled by a notification system |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't use alert()
for user notifications as it's not a good user experience. Instead, integrate with the application's notification system.
// Store trade for later synchronization | ||
addPendingTrade({ | ||
type: title, | ||
instrument: label, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The trade object structure being stored here does not match what the IndexedDB is expecting. Ensure consistent data structure between components.
}); | ||
|
||
// Fetch event - serve from cache, fallback to network | ||
self.addEventListener("fetch", (event) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The API detection logic might miss some API calls if they don't include /api/
or /sse/
in the URL. Consider a more robust detection method or ensure all API endpoints follow this pattern consistently throughout the application.
}); | ||
|
||
// Background sync for pending trades | ||
async function syncPendingTrades() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The background sync implementation assumes a hardcoded endpoint /api/trade
for all trade synchronizations. This should match the actual API endpoint used in the application. Consider making this configurable or consistent with the rest of the app.
// Tell waiting service worker to take control immediately | ||
waitingWorker.postMessage({ type: "SKIP_WAITING" }); | ||
|
||
// Reload once the new service worker has taken control |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a potential race condition here. The service worker might take control before the event listener is registered. Consider registering the event listener before sending the message to the service worker.
@@ -0,0 +1,223 @@ | |||
// Cache name with version for easy updating |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The service worker doesn't listen for the SKIP_WAITING message that UpdateNotification.tsx sends. Add a message event listener to handle this message type. You'll need to add code like: self.addEventListener("message", (event) => { if (event.data && event.data.type === "SKIP_WAITING") { self.skipWaiting(); } });
navigator.serviceWorker.ready.then((registration) => { | ||
// Trigger background sync if available | ||
if ("sync" in registration) { | ||
(registration as any).sync |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The type casting (registration as any).sync
suggests TypeScript is missing proper typings for the Background Sync API. Consider adding a proper type definition or interface for this API to maintain type safety.
Summary by Sourcery
Implements Progressive Web App (PWA) functionality, including offline support, install prompts, and background synchronization for trades. It also adds an offline indicator and improves the user experience when the application is offline.
New Features: