mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Part of #5418 (Phase 1, lane B). ## What Adds a periodic, whitelist-driven TTL sweep for disposable localStorage caches so a desktop session left open for days converges to the same storage state as one restarted nightly. - New `desktop/src/shared/lib/localStorageSweep.ts`: declarative `LOCAL_STORAGE_SWEEP_RULES` table — six repaintable pure-cache prefixes (matching `PURE_CACHE_KEY_PREFIXES` in `localStorageQuota.ts`), all 14-day TTL, keyed on each payload's `updatedAt` (user-label buckets use their newest nested per-profile timestamp). - Entries with no trustworthy timestamp are retained, never guessed stale. `buzz-self-profile.v1:` is deliberately excluded — it is the load-bearing offline identity fallback (guard comment in the table). - Scheduler: first sweep deferred off the boot critical path via `requestIdleCallback` (1.5s timeout) with a 250ms timer fallback, then hourly and on return-to-visible, debounced to 5 minutes. Throw-safe throughout (failures `console.warn`, never crash — per `safeStorage.ts` conventions / #5078). - Wired in `desktop/src/main.tsx` beside `recoverLocalStorageQuotaOnStartup()`. ## Validation - Focused node test 7/7 at HEAD; pre-push gate green (desktop-check, desktop-typecheck, full desktop-test 4542/4542). - Manual Playwright (not covered by push hooks): `relay-connectivity.spec.ts -g "04"` (offline cached identity) passes 1/1 at HEAD — this spec caught and now guards the v1 regression. - Independent adversarial review: FULL REVIEW (REQUEST CHANGES) then VERIFIED — PASS at exactly this commit, including whitelist containment against the 58-site inventory, scheduler tracing, and smoke E2E. Authored by Summer (agent), reviewed by Beth (agent), integrated by Rick (agent). Discussion: Buzz channel time-based-localstorage-eviction, thread 0d85a73ca43e54748128f89c3512a4726131bf5473253395d46bf8f3a7b58bd4. Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Summer <1fdd3cc104e2911eb3b2da6f97d1b25f4a7f3550ded4492b24ff1d95acd66766@buzz.block.builderlab.xyz>
133 lines
4.6 KiB
TypeScript
133 lines
4.6 KiB
TypeScript
import React from "react";
|
|
import ReactDOM from "react-dom/client";
|
|
import { App } from "@/app/App";
|
|
import { RootErrorBoundary } from "@/app/RootErrorBoundary";
|
|
import { NostrBindConsentDialog } from "@/features/profile/ui/NostrBindConsentDialog";
|
|
import "@fontsource-variable/inter/wght.css";
|
|
import "@fontsource/jetbrains-mono/400.css";
|
|
import "@fontsource/jetbrains-mono/700.css";
|
|
import "@/shared/styles/globals.css";
|
|
import { UpdaterProvider } from "@/features/settings/hooks/UpdaterProvider";
|
|
import { migrateLegacyCommunityStorageBeforeRender } from "@/features/communities/legacyCommunityStorage";
|
|
import { CommunitiesProvider } from "@/features/communities/useCommunities";
|
|
import { huddleWindowChannelId } from "@/features/huddle/lib/huddleWindow";
|
|
import { CommunityOnboardingProvider } from "@/features/onboarding/communityOnboarding";
|
|
import { ThemeProvider } from "@/shared/theme/ThemeProvider";
|
|
import { EmojiBurstProvider } from "@/shared/ui/EmojiBurstProvider";
|
|
import { PoofBurstProvider } from "@/shared/ui/PoofBurstProvider";
|
|
import { Toaster } from "@/shared/ui/sonner";
|
|
import { TooltipProvider } from "@/shared/ui/tooltip";
|
|
import { recoverLocalStorageQuotaOnStartup } from "@/shared/lib/localStorageQuota";
|
|
import { startLocalStorageSweep } from "@/shared/lib/localStorageSweep";
|
|
|
|
type E2eWindow = Window & {
|
|
__BUZZ_E2E__?: unknown;
|
|
};
|
|
|
|
const E2E_DEFAULT_PUBKEY = "deadbeef".repeat(8);
|
|
const E2E_COMMUNITY_ID = "e2e-default-community";
|
|
const ONBOARDING_COMPLETION_STORAGE_KEY_PREFIX = "buzz-onboarding-complete.v1:";
|
|
const DEV_STATE_RESET_PARAM = "resetDevState";
|
|
|
|
function resetDevWebviewStateFromUrl() {
|
|
if (!import.meta.env.DEV) {
|
|
return;
|
|
}
|
|
|
|
const url = new URL(window.location.href);
|
|
if (url.searchParams.get(DEV_STATE_RESET_PARAM) !== "1") {
|
|
return;
|
|
}
|
|
|
|
// WebKit groups every Buzz binary under one disk directory, but storage is
|
|
// isolated by origin. Clearing here resets only this dev server's origin;
|
|
// deleting the shared WebKit directory would also destroy installed-app state.
|
|
window.localStorage.clear();
|
|
window.sessionStorage.clear();
|
|
url.searchParams.delete(DEV_STATE_RESET_PARAM);
|
|
window.history.replaceState(window.history.state, "", url);
|
|
}
|
|
|
|
function configureDevE2eBridgeFromUrl() {
|
|
if (!import.meta.env.DEV) {
|
|
return;
|
|
}
|
|
|
|
const url = new URL(window.location.href);
|
|
if (url.searchParams.get("e2e") !== "mock") {
|
|
return;
|
|
}
|
|
|
|
const e2eWindow = window as E2eWindow;
|
|
e2eWindow.__BUZZ_E2E__ ??= { mode: "mock" };
|
|
|
|
const community = {
|
|
addedAt: new Date().toISOString(),
|
|
id: E2E_COMMUNITY_ID,
|
|
name: "E2E Test",
|
|
relayUrl: "ws://localhost:3000",
|
|
};
|
|
window.localStorage.setItem("buzz-communities", JSON.stringify([community]));
|
|
window.localStorage.setItem("buzz-active-community-id", E2E_COMMUNITY_ID);
|
|
window.localStorage.setItem(
|
|
`${ONBOARDING_COMPLETION_STORAGE_KEY_PREFIX}${E2E_DEFAULT_PUBKEY}`,
|
|
"true",
|
|
);
|
|
}
|
|
|
|
function renderApp() {
|
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
|
<React.StrictMode>
|
|
{/* block/buzz#5078 — catch any uncaught render error so a WebKit
|
|
SecurityError from localStorage can't blank the whole window. */}
|
|
<RootErrorBoundary>
|
|
<CommunitiesProvider>
|
|
<CommunityOnboardingProvider
|
|
enabled={huddleWindowChannelId() === null}
|
|
>
|
|
<ThemeProvider defaultTheme="buzz">
|
|
<TooltipProvider delayDuration={300}>
|
|
<EmojiBurstProvider>
|
|
<PoofBurstProvider>
|
|
<UpdaterProvider>
|
|
<App />
|
|
<NostrBindConsentDialog />
|
|
</UpdaterProvider>
|
|
<Toaster />
|
|
</PoofBurstProvider>
|
|
</EmojiBurstProvider>
|
|
</TooltipProvider>
|
|
</ThemeProvider>
|
|
</CommunityOnboardingProvider>
|
|
</CommunitiesProvider>
|
|
</RootErrorBoundary>
|
|
</React.StrictMode>,
|
|
);
|
|
}
|
|
|
|
async function installE2eBridgeIfConfigured() {
|
|
// The mock bridge is compiled only into dev and explicit E2E builds. A
|
|
// pre-bootstrap global alone must never activate mock IPC in production.
|
|
if (
|
|
!(import.meta.env.DEV || import.meta.env.MODE === "e2e") ||
|
|
!(window as E2eWindow).__BUZZ_E2E__
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const { maybeInstallE2eTauriMocks } = await import("@/testing/e2eBridge");
|
|
maybeInstallE2eTauriMocks();
|
|
}
|
|
|
|
async function bootstrap() {
|
|
resetDevWebviewStateFromUrl();
|
|
configureDevE2eBridgeFromUrl();
|
|
recoverLocalStorageQuotaOnStartup();
|
|
startLocalStorageSweep();
|
|
await installE2eBridgeIfConfigured();
|
|
await migrateLegacyCommunityStorageBeforeRender();
|
|
renderApp();
|
|
}
|
|
|
|
void bootstrap();
|