feat: enhance service worker functionality with improved caching strategies, client communication, and service worker registration options
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m42s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 3m42s
This commit is contained in:
@@ -1,14 +1,137 @@
|
||||
import logger from "@/lib/logger";
|
||||
|
||||
export const registerServiceWorker = async () => {
|
||||
interface ServiceWorkerRegistrationOptions {
|
||||
onUpdate?: (registration: ServiceWorkerRegistration) => void;
|
||||
onSuccess?: (registration: ServiceWorkerRegistration) => void;
|
||||
onError?: (error: Error) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the service worker with optional callbacks for update and success events
|
||||
*/
|
||||
export const registerServiceWorker = async (
|
||||
options: ServiceWorkerRegistrationOptions = {}
|
||||
): Promise<ServiceWorkerRegistration | null> => {
|
||||
if (typeof window === "undefined" || !("serviceWorker" in navigator)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
const { onUpdate, onSuccess, onError } = options;
|
||||
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.register("/sw.js", {
|
||||
scope: "/",
|
||||
});
|
||||
|
||||
// Check for updates immediately
|
||||
registration.update().catch(() => {
|
||||
// Ignore update check errors
|
||||
});
|
||||
|
||||
// Handle updates
|
||||
registration.addEventListener("updatefound", () => {
|
||||
const newWorker = registration.installing;
|
||||
|
||||
if (!newWorker) return;
|
||||
|
||||
newWorker.addEventListener("statechange", () => {
|
||||
if (newWorker.state === "installed") {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// New service worker available
|
||||
logger.info("New service worker available");
|
||||
onUpdate?.(registration);
|
||||
} else {
|
||||
// First install
|
||||
logger.info("Service worker installed for the first time");
|
||||
onSuccess?.(registration);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// If already active, call success
|
||||
if (registration.active) {
|
||||
onSuccess?.(registration);
|
||||
}
|
||||
|
||||
return registration;
|
||||
} catch (error) {
|
||||
const err = error instanceof Error ? error : new Error(String(error));
|
||||
logger.error({ err }, "Service Worker registration failed");
|
||||
onError?.(err);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Unregister all service workers
|
||||
*/
|
||||
export const unregisterServiceWorker = async (): Promise<boolean> => {
|
||||
if (typeof window === "undefined" || !("serviceWorker" in navigator)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
await navigator.serviceWorker.register("/sw.js");
|
||||
// logger.info("Service Worker registered with scope:", registration.scope);
|
||||
const registrations = await navigator.serviceWorker.getRegistrations();
|
||||
await Promise.all(registrations.map((reg) => reg.unregister()));
|
||||
logger.info("All service workers unregistered");
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, "Service Worker registration failed:");
|
||||
logger.error({ err: error }, "Failed to unregister service workers");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a message to the active service worker
|
||||
*/
|
||||
export const sendMessageToSW = <T = unknown>(message: unknown): Promise<T | null> => {
|
||||
return new Promise((resolve) => {
|
||||
if (!navigator.serviceWorker.controller) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const messageChannel = new MessageChannel();
|
||||
|
||||
messageChannel.port1.onmessage = (event) => {
|
||||
resolve(event.data as T);
|
||||
};
|
||||
|
||||
navigator.serviceWorker.controller.postMessage(message, [messageChannel.port2]);
|
||||
|
||||
// Timeout after 5 seconds
|
||||
setTimeout(() => {
|
||||
resolve(null);
|
||||
}, 5000);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the app is running as a PWA (standalone mode)
|
||||
*/
|
||||
export const isPWA = (): boolean => {
|
||||
if (typeof window === "undefined") return false;
|
||||
|
||||
return (
|
||||
window.matchMedia("(display-mode: standalone)").matches ||
|
||||
// iOS Safari
|
||||
("standalone" in window.navigator &&
|
||||
(window.navigator as { standalone?: boolean }).standalone === true)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current service worker registration
|
||||
*/
|
||||
export const getServiceWorkerRegistration = async (): Promise<ServiceWorkerRegistration | null> => {
|
||||
if (typeof window === "undefined" || !("serviceWorker" in navigator)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return await navigator.serviceWorker.ready;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user