文件
hyb-oa/client/src/pwa.ts
T

131 行
3.8 KiB
TypeScript

import { registerSW } from "virtual:pwa-register";
import { canUseWindow, isEnterpriseMessengerContainer, matchesMedia } from "./lib/runtimeContext";
import { PWA_UPDATE_READY_EVENT } from "./lib/pwaUpdateEvents";
const MOBILE_APPROVAL_TASK_PATH = /^\/workflow\/tasks\/([^/]+)$/i;
const OFFLINE_ALLOWED_PATHS = ["/mobile/approvals"];
const CLEARABLE_CACHE_PREFIXES = ["app-api-runtime", "app-static-runtime", "workbox-precache"];
const AUTH_INPUT_PATHS = ["/login", "/change-password", "/forgot-password", "/reset-password"];
function isAuthInputPath(pathname: string) {
return AUTH_INPUT_PATHS.some((path) => pathname === path || pathname.startsWith(`${path}/`));
}
function bindServiceWorkerUpdateCleanup(registration: ServiceWorkerRegistration | undefined) {
if (!canUseWindow() || !registration) {
return;
}
const watchInstallingWorker = (worker: ServiceWorker | null) => {
if (!worker) {
return;
}
worker.addEventListener("statechange", () => {
if (worker.state === "installed" && window.navigator.serviceWorker.controller) {
void clearPwaRuntimeCaches();
}
});
};
watchInstallingWorker(registration.installing);
registration.addEventListener("updatefound", () => {
watchInstallingWorker(registration.installing);
});
}
export function registerPwaIfEnabled() {
if (import.meta.env.DEV) {
return;
}
if (import.meta.env.VITE_PWA_ENABLED === "false") {
return;
}
let updateServiceWorker: ((reloadPage?: boolean) => Promise<void>) | null = null;
updateServiceWorker = registerSW({
immediate: true,
onNeedRefresh() {
const applyUpdate = () => {
void clearPwaRuntimeCaches()
.catch(() => undefined)
.finally(() => {
void updateServiceWorker?.(true);
});
};
if (canUseWindow() && isAuthInputPath(window.location.pathname)) {
window.dispatchEvent(new CustomEvent(PWA_UPDATE_READY_EVENT, { detail: { applyUpdate } }));
return;
}
applyUpdate();
},
onOfflineReady() {
if (canUseWindow()) {
window.dispatchEvent(new CustomEvent("hyb-oa:pwa-offline-ready"));
}
},
onRegisteredSW(_swScriptUrl, registration) {
bindServiceWorkerUpdateCleanup(registration);
},
});
}
export function buildMobileApprovalsPath(taskId?: string | null) {
const normalizedTaskId = String(taskId ?? "").trim();
if (!normalizedTaskId) {
return "/mobile/approvals";
}
return `/mobile/approvals/${encodeURIComponent(normalizedTaskId)}`;
}
export function extractWorkflowTaskId(pathname: string) {
const match = pathname.match(MOBILE_APPROVAL_TASK_PATH);
return match?.[1] ?? null;
}
export function isOfflineSnapshotAllowedPath(pathname: string) {
if (OFFLINE_ALLOWED_PATHS.includes(pathname)) {
return true;
}
if (/^\/mobile\/approvals\/[^/]+$/.test(pathname)) {
return true;
}
return Boolean(extractWorkflowTaskId(pathname));
}
export function shouldPreferMobileApprovalExperience() {
if (!canUseWindow()) {
return false;
}
const isStandalone = matchesMedia("(display-mode: standalone)") || (window.navigator as Navigator & { standalone?: boolean }).standalone === true;
const isTouchSmallScreen = window.navigator.maxTouchPoints > 0 && matchesMedia("(max-width: 1024px)");
return isStandalone || isEnterpriseMessengerContainer() || isTouchSmallScreen;
}
export async function clearPwaRuntimeCaches() {
if (!canUseWindow() || !("caches" in window)) {
return;
}
const cacheNames = await window.caches.keys();
const cachesToDelete = cacheNames.filter((cacheName) =>
CLEARABLE_CACHE_PREFIXES.some((prefix) => cacheName.startsWith(prefix)),
);
await Promise.all(cachesToDelete.map(async (cacheName) => {
try {
await window.caches.delete(cacheName);
} catch {
}
}));
}