fix web memo detail loading
This commit is contained in:
1 parent
e1a2b2d08b
commit
a800bcc64e
9 files changed
+276
-49
No files matched your search
@@ -19,6 +19,7 @@ import {
|
||||
saveDesktopApiBaseUrl,
|
||||
} from "@/lib/api";
|
||||
import { EVERNOTE_MIGRATION_PATH } from "@/lib/routes";
|
||||
import { isBrowserOffline } from "@/lib/network-status";
|
||||
import type { AuthSession } from "@edgeever/shared";
|
||||
|
||||
const EvernoteImportGuidePane = lazy(() =>
|
||||
@@ -113,7 +114,7 @@ const AuthenticatedWorkspace = () => {
|
||||
return session;
|
||||
} catch (error) {
|
||||
const cached = getCachedDesktopSession();
|
||||
if (cached?.authenticated && typeof navigator !== "undefined" && !navigator.onLine) return cached;
|
||||
if (cached?.authenticated && isBrowserOffline()) return cached;
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -108,6 +108,7 @@ import { fetchLatestRelease, isVersionOutdated } from "@/lib/version-check";
|
||||
import { RELEASE_STATUS_EVENT } from "@/lib/release-notice";
|
||||
import { downloadMarkdownFile } from "@/lib/note-markdown-export";
|
||||
import { openNotePrintPreview, serializeNoteDocumentForPrint } from "@/lib/note-print";
|
||||
import { isBrowserOffline } from "@/lib/network-status";
|
||||
|
||||
const SUPPORTED_PASTE_IMAGE_TYPES = new Set(["image/png", "image/jpeg", "image/gif", "image/webp", "image/avif"]);
|
||||
const MOBILE_EDITOR_QUERY = "(max-width: 639px)";
|
||||
@@ -125,7 +126,7 @@ const createLocalEditSession = (memo: MemoDetail): MemoEditSession => ({
|
||||
const requiresLocalEditSession = (memo: MemoDetail) =>
|
||||
isDesktopResourceRuntime() ||
|
||||
isLocalMemoId(memo.id) ||
|
||||
(typeof navigator !== "undefined" && !navigator.onLine);
|
||||
isBrowserOffline();
|
||||
|
||||
const IconTooltip = ({ label, children }: { label: string; children: ReactNode }) => (
|
||||
<TooltipProvider delayDuration={0} skipDelayDuration={0}>
|
||||
@@ -2414,7 +2415,7 @@ const RichEditorPane = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
if (isLoading && !memo) {
|
||||
return (
|
||||
<div className="flex h-full min-w-0 flex-col bg-white">
|
||||
{selectionActionBar}
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
type MobileEditorReturnPreview,
|
||||
} from "@/lib/mobile-editor";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { isBrowserOffline, isBrowserOnline } from "@/lib/network-status";
|
||||
import { createExcerpt, docToText, getNotebookDescendantIds, type Notebook, type AuthUser, type MemoSummary, type MemoDetail, type Resource, type MemoTemplate as SavedMemoTemplate } from "@edgeever/shared";
|
||||
import { toggleMobileMemoSelection } from "@edgeever/shared/mobile-ui";
|
||||
import type {
|
||||
@@ -727,7 +728,7 @@ export const WorkspaceApp = ({
|
||||
const [mobileEditorReturnPreview, setMobileEditorReturnPreview] = useState<MobileEditorReturnPreview | null>(() =>
|
||||
readMobileEditorReturnPreview(getMobileEditorReturnMemoId(location.search))
|
||||
);
|
||||
const [isOnline, setIsOnline] = useState(() => (typeof navigator === "undefined" ? true : navigator.onLine));
|
||||
const [isOnline, setIsOnline] = useState(isBrowserOnline);
|
||||
const [isDesktop, setIsDesktop] = useState(isDesktopViewport);
|
||||
const [isSyncingQueuedChanges, setIsSyncingQueuedChanges] = useState(false);
|
||||
const [isManualMemoSyncing, setIsManualMemoSyncing] = useState(false);
|
||||
@@ -769,7 +770,7 @@ export const WorkspaceApp = ({
|
||||
};
|
||||
|
||||
const runQueuedSync = useCallback(async () => {
|
||||
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
||||
if (isBrowserOffline()) {
|
||||
setIsOnline(false);
|
||||
return;
|
||||
}
|
||||
@@ -873,7 +874,7 @@ export const WorkspaceApp = ({
|
||||
}, [isOnline, queryClient]);
|
||||
|
||||
const refreshLatestMemos = useCallback(async () => {
|
||||
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
||||
if (isBrowserOffline()) {
|
||||
setIsOnline(false);
|
||||
setPullToRefreshDistance(0);
|
||||
return;
|
||||
@@ -894,7 +895,7 @@ export const WorkspaceApp = ({
|
||||
}, [isOnline, localDataScope, queryClient]);
|
||||
|
||||
const syncMemosManually = useCallback(async () => {
|
||||
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
||||
if (isBrowserOffline()) {
|
||||
setIsOnline(false);
|
||||
return;
|
||||
}
|
||||
@@ -1363,7 +1364,7 @@ export const WorkspaceApp = ({
|
||||
|
||||
useEffect(() => {
|
||||
const updateOnlineState = () => {
|
||||
const online = navigator.onLine;
|
||||
const online = isBrowserOnline();
|
||||
setIsOnline(online);
|
||||
if (online) {
|
||||
void runQueuedSync();
|
||||
@@ -1394,7 +1395,7 @@ export const WorkspaceApp = ({
|
||||
}
|
||||
|
||||
const refreshWorkspaceQueries = () => {
|
||||
if (document.visibilityState === "hidden" || (typeof navigator !== "undefined" && !navigator.onLine)) {
|
||||
if (document.visibilityState === "hidden" || isBrowserOffline()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1480,6 +1481,7 @@ export const WorkspaceApp = ({
|
||||
const previousMemoId = selectedMemoIndex > 0 ? memos[selectedMemoIndex - 1]?.id : null;
|
||||
const nextMemoId =
|
||||
selectedMemoIndex >= 0 && selectedMemoIndex < memos.length - 1 ? memos[selectedMemoIndex + 1]?.id : null;
|
||||
const detailMemoId = selectedMemoId ?? memos[0]?.id ?? null;
|
||||
|
||||
useEffect(() => {
|
||||
const selectedMemoInList = selectedMemoId ? memos.some((memo) => memo.id === selectedMemoId) : false;
|
||||
@@ -1502,11 +1504,25 @@ export const WorkspaceApp = ({
|
||||
}, [createdMemoEditId, memos, selectedMemoId]);
|
||||
|
||||
const memoQuery = useQuery({
|
||||
queryKey: selectedMemoId ? memoDetailQueryKey(selectedMemoId, memoView) : ["memo", selectedMemoId, memoView],
|
||||
queryFn: () => repository.getMemo(selectedMemoId as string, memoView === "trash"),
|
||||
enabled: Boolean(selectedMemoId),
|
||||
queryKey: detailMemoId ? memoDetailQueryKey(detailMemoId, memoView) : ["memo", detailMemoId, memoView],
|
||||
queryFn: () => repository.getMemo(detailMemoId as string, memoView === "trash"),
|
||||
enabled: Boolean(detailMemoId),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleMemoDetailRefreshed = (event: Event) => {
|
||||
const memo = (event as CustomEvent<MemoDetail>).detail;
|
||||
if (!memo?.id) return;
|
||||
|
||||
queryClient.setQueryData(memoDetailQueryKey(memo.id, "notebook"), { memo });
|
||||
queryClient.setQueryData(memoDetailQueryKey(memo.id, "trash"), { memo });
|
||||
updateMemoSummaryInLists(queryClient, memoToSummary(memo));
|
||||
};
|
||||
|
||||
window.addEventListener("edgeever:memo-detail-refreshed", handleMemoDetailRefreshed);
|
||||
return () => window.removeEventListener("edgeever:memo-detail-refreshed", handleMemoDetailRefreshed);
|
||||
}, [queryClient]);
|
||||
|
||||
const createNotebookMutation = useMutation({
|
||||
mutationFn: repository.createNotebook,
|
||||
onSuccess: async (data) => {
|
||||
@@ -1895,8 +1911,8 @@ export const WorkspaceApp = ({
|
||||
});
|
||||
|
||||
const selectedNotebook = notebooks.find((notebook) => notebook.id === selectedNotebookId) ?? null;
|
||||
const cachedSelectedMemo = selectedMemoId
|
||||
? queryClient.getQueryData<{ memo: MemoDetail }>(memoDetailQueryKey(selectedMemoId, memoView))?.memo ?? null
|
||||
const cachedSelectedMemo = detailMemoId
|
||||
? queryClient.getQueryData<{ memo: MemoDetail }>(memoDetailQueryKey(detailMemoId, memoView))?.memo ?? null
|
||||
: null;
|
||||
const selectedMemo = memoQuery.data?.memo ?? cachedSelectedMemo;
|
||||
const desktopFocusModeActive = Boolean(
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { MemoFilterMode, MemoSortMode } from "@/lib/app-helpers";
|
||||
import { api, type SyncChangesResponse } from "@/lib/api";
|
||||
import { localDb, type LocalMemo, type LocalNotebook, type LocalResource, type LocalRevision } from "@/lib/local-db";
|
||||
import { cacheLocalResourceBytes, localResourceUrl, removeCachedLocalResourceBytes } from "@/lib/local-resource-cache";
|
||||
import { isBrowserOffline } from "@/lib/network-status";
|
||||
|
||||
export type LocalMemoListParams = {
|
||||
notebookId?: string | null;
|
||||
@@ -122,7 +123,7 @@ const bootstrapScope = async (scope: string) => {
|
||||
};
|
||||
|
||||
const performSyncLocalMirror = async (scope: string) => {
|
||||
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
||||
if (isBrowserOffline()) {
|
||||
return { bootstrapped: false, changed: 0 };
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
const LOOPBACK_HOSTNAMES = new Set(["localhost", "127.0.0.1", "::1"]);
|
||||
|
||||
const isLoopbackOrigin = () =>
|
||||
typeof window !== "undefined" && LOOPBACK_HOSTNAMES.has(window.location.hostname);
|
||||
|
||||
export const isBrowserOffline = () =>
|
||||
typeof navigator !== "undefined" && navigator.onLine === false && !isLoopbackOrigin();
|
||||
|
||||
export const isBrowserOnline = () => !isBrowserOffline();
|
||||
@@ -22,6 +22,91 @@ afterEach(async () => {
|
||||
});
|
||||
|
||||
describe("web repository offline boundaries", () => {
|
||||
test("falls back to the remote detail when the local database read is blocked", async () => {
|
||||
const previousOnline = globalThis.navigator?.onLine;
|
||||
if (globalThis.navigator) Object.defineProperty(globalThis.navigator, "onLine", { configurable: true, value: true });
|
||||
const scope = "https://demo.edgeever.org|user-1";
|
||||
const remoteMemo = {
|
||||
id: "memo-blocked",
|
||||
notebookId: "nb-1",
|
||||
title: "Remote detail",
|
||||
excerpt: "Remote excerpt",
|
||||
tags: [],
|
||||
isPinned: false,
|
||||
isArchived: false,
|
||||
isDeleted: false,
|
||||
revision: 1,
|
||||
createdAt: "2026-01-01T00:00:00.000Z",
|
||||
updatedAt: "2026-01-01T00:00:00.000Z",
|
||||
deletedAt: null,
|
||||
contentJson: { type: "doc", content: [] },
|
||||
contentMarkdown: "remote",
|
||||
contentText: "remote",
|
||||
contentHash: "remote",
|
||||
sourceMemoIds: [],
|
||||
};
|
||||
const originalLocalGet = localDb.memos.get;
|
||||
const originalLocalPut = localDb.memos.put;
|
||||
const originalApiGetMemo = api.getMemo;
|
||||
localDb.memos.get = async () => new Promise(() => {});
|
||||
localDb.memos.put = async () => new Promise(() => {});
|
||||
api.getMemo = async () => ({ memo: remoteMemo });
|
||||
|
||||
try {
|
||||
const repository = createWebRepository(scope);
|
||||
const startedAt = Date.now();
|
||||
expect((await repository.getMemo("memo-blocked")).memo.title).toBe("Remote detail");
|
||||
expect(Date.now() - startedAt).toBeLessThan(1_000);
|
||||
} finally {
|
||||
localDb.memos.get = originalLocalGet;
|
||||
localDb.memos.put = originalLocalPut;
|
||||
api.getMemo = originalApiGetMemo;
|
||||
if (globalThis.navigator) Object.defineProperty(globalThis.navigator, "onLine", { configurable: true, value: previousOnline });
|
||||
}
|
||||
});
|
||||
|
||||
test("returns cached detail immediately and refreshes it in the background", async () => {
|
||||
const previousOnline = globalThis.navigator?.onLine;
|
||||
if (globalThis.navigator) Object.defineProperty(globalThis.navigator, "onLine", { configurable: true, value: true });
|
||||
const scope = "https://demo.edgeever.org|user-1";
|
||||
const localMemo = {
|
||||
id: "memo-1",
|
||||
notebookId: "nb-1",
|
||||
title: "Cached title",
|
||||
excerpt: "Cached excerpt",
|
||||
tags: [],
|
||||
isPinned: false,
|
||||
isArchived: false,
|
||||
isDeleted: false,
|
||||
revision: 1,
|
||||
createdAt: "2026-01-01T00:00:00.000Z",
|
||||
updatedAt: "2026-01-01T00:00:00.000Z",
|
||||
deletedAt: null,
|
||||
contentJson: { type: "doc", content: [] },
|
||||
contentMarkdown: "cached",
|
||||
contentText: "cached",
|
||||
contentHash: "cached",
|
||||
sourceMemoIds: [],
|
||||
};
|
||||
const remoteMemo = { ...localMemo, title: "Remote title", contentMarkdown: "remote", contentText: "remote", revision: 2 };
|
||||
await localDb.memos.put({ ...localMemo, scope });
|
||||
const originalGetMemo = api.getMemo;
|
||||
api.getMemo = async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
return { memo: remoteMemo };
|
||||
};
|
||||
|
||||
try {
|
||||
const repository = createWebRepository(scope);
|
||||
expect((await repository.getMemo("memo-1")).memo.title).toBe("Cached title");
|
||||
await new Promise((resolve) => setTimeout(resolve, 30));
|
||||
expect((await localDb.memos.get([scope, "memo-1"])).title).toBe("Remote title");
|
||||
} finally {
|
||||
api.getMemo = originalGetMemo;
|
||||
if (globalThis.navigator) Object.defineProperty(globalThis.navigator, "onLine", { configurable: true, value: previousOnline });
|
||||
}
|
||||
});
|
||||
|
||||
test("returns empty initialized collections without cloud fallbacks", async () => {
|
||||
const previousOnline = globalThis.navigator?.onLine;
|
||||
if (globalThis.navigator) Object.defineProperty(globalThis.navigator, "onLine", { configurable: true, value: false });
|
||||
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
import { queueLocalAction, queueMemoCreate, queueMemoDelete, queueMemoRestore, queueMemoUpdate } from "@/lib/sync-queue";
|
||||
import type { MemoUpdateSyncPayload } from "@/lib/local-db";
|
||||
import { createDesktopRepository } from "@/lib/desktop-repository";
|
||||
import { isBrowserOffline } from "@/lib/network-status";
|
||||
|
||||
export type EdgeEverRepository = {
|
||||
listNotebooks(): Promise<{ notebooks: Notebook[] }>;
|
||||
@@ -73,8 +74,24 @@ export type EdgeEverRepository = {
|
||||
sync(): Promise<{ bootstrapped: boolean; changed: number }>;
|
||||
};
|
||||
|
||||
const LOCAL_MEMO_READ_TIMEOUT_MS = 300;
|
||||
|
||||
const getLocalMemoWithoutBlocking = (scope: string, memoId: string) =>
|
||||
Promise.race([
|
||||
getLocalMemo(scope, memoId),
|
||||
new Promise<null>((resolve) => {
|
||||
globalThis.setTimeout(() => resolve(null), LOCAL_MEMO_READ_TIMEOUT_MS);
|
||||
}),
|
||||
]);
|
||||
|
||||
const cacheMemoWithoutBlocking = (scope: string, memo: MemoDetail) => {
|
||||
void putLocalMemo(scope, memo).catch(() => {
|
||||
// Cache persistence must never prevent a remote detail from rendering.
|
||||
});
|
||||
};
|
||||
|
||||
export const createWebRepository = (scope: string): EdgeEverRepository => {
|
||||
const isOffline = () => typeof navigator !== "undefined" && !navigator.onLine;
|
||||
const isOffline = isBrowserOffline;
|
||||
|
||||
return ({
|
||||
async listNotebooks() {
|
||||
@@ -185,7 +202,7 @@ export const createWebRepository = (scope: string): EdgeEverRepository => {
|
||||
return { memo };
|
||||
},
|
||||
uploadMemoResource: async (memoId, file) => {
|
||||
if (typeof navigator !== "undefined" && !navigator.onLine) {
|
||||
if (isOffline()) {
|
||||
const resource = await createLocalResource(scope, memoId, file);
|
||||
await queueLocalAction(scope, "resource.create", resource.id, {
|
||||
resourceId: resource.id,
|
||||
@@ -282,12 +299,52 @@ export const createWebRepository = (scope: string): EdgeEverRepository => {
|
||||
},
|
||||
|
||||
async getMemo(memoId, includeDeleted = false) {
|
||||
const local = await getLocalMemo(scope, memoId);
|
||||
if (local && (includeDeleted || !local.isDeleted)) return { memo: local };
|
||||
const localPromise = getLocalMemoWithoutBlocking(scope, memoId);
|
||||
|
||||
if (isOffline()) throw new Error("Memo is not available in the local mirror");
|
||||
const remote = await api.getMemo(memoId, { includeDeleted });
|
||||
await putLocalMemo(scope, remote.memo);
|
||||
if (isOffline()) {
|
||||
const local = await localPromise;
|
||||
const usableLocal = local && (includeDeleted || !local.isDeleted) ? local : null;
|
||||
if (usableLocal) return { memo: usableLocal };
|
||||
throw new Error("Memo is not available in the local mirror");
|
||||
}
|
||||
|
||||
const remotePromise = api.getMemo(memoId, { includeDeleted });
|
||||
const firstResult = await Promise.race([
|
||||
localPromise.then((local) => ({ source: "local" as const, local })),
|
||||
remotePromise.then(
|
||||
(remote) => ({ source: "remote" as const, remote }),
|
||||
(error: unknown) => ({ source: "remote-error" as const, error }),
|
||||
),
|
||||
]);
|
||||
|
||||
if (firstResult.source === "remote") {
|
||||
cacheMemoWithoutBlocking(scope, firstResult.remote.memo);
|
||||
return firstResult.remote;
|
||||
}
|
||||
|
||||
const local = firstResult.source === "local" ? firstResult.local : await localPromise;
|
||||
const usableLocal = local && (includeDeleted || !local.isDeleted) ? local : null;
|
||||
|
||||
if (usableLocal) {
|
||||
if (firstResult.source === "local") {
|
||||
void remotePromise
|
||||
.then((remote) => {
|
||||
cacheMemoWithoutBlocking(scope, remote.memo);
|
||||
window.dispatchEvent(new CustomEvent("edgeever:memo-detail-refreshed", { detail: remote.memo }));
|
||||
})
|
||||
.catch(() => {
|
||||
// The local detail remains usable when the background refresh fails.
|
||||
});
|
||||
}
|
||||
return { memo: usableLocal };
|
||||
}
|
||||
|
||||
if (firstResult.source === "remote-error") {
|
||||
throw firstResult.error;
|
||||
}
|
||||
|
||||
const remote = await remotePromise;
|
||||
cacheMemoWithoutBlocking(scope, remote.memo);
|
||||
return remote;
|
||||
},
|
||||
|
||||
|
||||
+53
-26
@@ -10,38 +10,65 @@ import { initializeTheme, ThemeProvider } from "./components/ThemeProvider";
|
||||
import "./styles/globals.css";
|
||||
|
||||
const PWA_UPDATE_CHECK_INTERVAL_MS = 10 * 60 * 1_000;
|
||||
const DEVELOPMENT_PWA_RELOAD_KEY = "edgeever.dev-pwa-reset";
|
||||
|
||||
let updateServiceWorker: ReturnType<typeof registerSW>;
|
||||
const clearDevelopmentPwaState = async () => {
|
||||
if (!("serviceWorker" in navigator)) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateServiceWorker = registerSW({
|
||||
immediate: true,
|
||||
onNeedRefresh() {
|
||||
emitPwaUpdateNotice({ buildLabel: __EDGEEVER_BUILD_LABEL__, kind: "checking" });
|
||||
void updateServiceWorker(true);
|
||||
},
|
||||
onNeedReload() {
|
||||
markPwaUpdateReloadPending();
|
||||
const registrations = await navigator.serviceWorker.getRegistrations();
|
||||
await Promise.all(registrations.map((registration) => registration.unregister()));
|
||||
|
||||
if ("caches" in window) {
|
||||
const cacheNames = await window.caches.keys();
|
||||
await Promise.all(cacheNames.map((cacheName) => window.caches.delete(cacheName)));
|
||||
}
|
||||
|
||||
if (navigator.serviceWorker.controller && window.sessionStorage.getItem(DEVELOPMENT_PWA_RELOAD_KEY) !== "1") {
|
||||
window.sessionStorage.setItem(DEVELOPMENT_PWA_RELOAD_KEY, "1");
|
||||
window.location.reload();
|
||||
},
|
||||
onRegisteredSW(_swScriptUrl, registration) {
|
||||
if (!registration) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const checkForUpdate = () => {
|
||||
if (document.visibilityState === "visible") {
|
||||
void registration.update().catch(() => undefined);
|
||||
window.sessionStorage.removeItem(DEVELOPMENT_PWA_RELOAD_KEY);
|
||||
};
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
void clearDevelopmentPwaState();
|
||||
} else {
|
||||
let updateServiceWorker: ReturnType<typeof registerSW>;
|
||||
|
||||
updateServiceWorker = registerSW({
|
||||
immediate: true,
|
||||
onNeedRefresh() {
|
||||
emitPwaUpdateNotice({ buildLabel: __EDGEEVER_BUILD_LABEL__, kind: "checking" });
|
||||
void updateServiceWorker(true);
|
||||
},
|
||||
onNeedReload() {
|
||||
markPwaUpdateReloadPending();
|
||||
window.location.reload();
|
||||
},
|
||||
onRegisteredSW(_swScriptUrl, registration) {
|
||||
if (!registration) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const updateInterval = window.setInterval(checkForUpdate, PWA_UPDATE_CHECK_INTERVAL_MS);
|
||||
window.addEventListener("beforeunload", () => window.clearInterval(updateInterval), { once: true });
|
||||
document.addEventListener("visibilitychange", checkForUpdate);
|
||||
},
|
||||
onRegisterError(error) {
|
||||
console.warn("PWA service worker registration failed", error);
|
||||
},
|
||||
});
|
||||
const checkForUpdate = () => {
|
||||
if (document.visibilityState === "visible") {
|
||||
void registration.update().catch(() => undefined);
|
||||
}
|
||||
};
|
||||
|
||||
const updateInterval = window.setInterval(checkForUpdate, PWA_UPDATE_CHECK_INTERVAL_MS);
|
||||
window.addEventListener("beforeunload", () => window.clearInterval(updateInterval), { once: true });
|
||||
document.addEventListener("visibilitychange", checkForUpdate);
|
||||
},
|
||||
onRegisterError(error) {
|
||||
console.warn("PWA service worker registration failed", error);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
|
||||
+31
-1
@@ -2,7 +2,7 @@ import react from "@vitejs/plugin-react";
|
||||
import { execSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath, URL } from "node:url";
|
||||
import { defineConfig } from "vite";
|
||||
import { defineConfig, type Plugin } from "vite";
|
||||
import { VitePWA } from "vite-plugin-pwa";
|
||||
import { resolveAppVersion, resolveDeploymentMethod, resolveDeploymentTrigger, resolveReleaseTimestamp } from "./build-metadata";
|
||||
|
||||
@@ -71,6 +71,35 @@ const deploymentMethod = resolveDeploymentMethod(
|
||||
: undefined)
|
||||
);
|
||||
const isDesktopBuild = process.env.EDGE_EVER_DESKTOP_BUILD === "1";
|
||||
const developmentServiceWorkerReset: Plugin = {
|
||||
name: "edgeever-development-service-worker-reset",
|
||||
apply: "serve",
|
||||
configureServer(server) {
|
||||
server.middlewares.use((request, response, next) => {
|
||||
if (request.url?.split("?")[0] !== "/sw.js") {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
response.statusCode = 200;
|
||||
response.setHeader("Content-Type", "application/javascript; charset=utf-8");
|
||||
response.setHeader("Cache-Control", "no-store");
|
||||
response.end(`
|
||||
self.addEventListener("install", () => self.skipWaiting());
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil((async () => {
|
||||
const cacheNames = await caches.keys();
|
||||
await Promise.all(cacheNames.map((cacheName) => caches.delete(cacheName)));
|
||||
await self.clients.claim();
|
||||
await self.registration.unregister();
|
||||
const clients = await self.clients.matchAll({ type: "window" });
|
||||
await Promise.all(clients.map((client) => client.navigate(client.url)));
|
||||
})());
|
||||
});
|
||||
`);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
root: "apps/web",
|
||||
@@ -86,6 +115,7 @@ export default defineConfig({
|
||||
__EDGEEVER_DEPLOYMENT_METHOD__: JSON.stringify(deploymentMethod),
|
||||
},
|
||||
plugins: [
|
||||
developmentServiceWorkerReset,
|
||||
react(),
|
||||
VitePWA({
|
||||
registerType: "autoUpdate",
|
||||
|
||||
Reference in new issue
Block a user