Preserve community data on onboarding write failure

Co-authored-by: npub1cl47vfhsqpqy9pwndphpm36vcp7vvz5h2js4qpqm5yewzj7nutkq7xyw8c <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
Signed-off-by: npub1cl47vfhsqpqy9pwndphpm36vcp7vvz5h2js4qpqm5yewzj7nutkq7xyw8c <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
This commit is contained in:
npub1cl47vfhsqpqy9pwndphpm36vcp7vvz5h2js4qpqm5yewzj7nutkq7xyw8c
2026-07-23 11:06:42 -07:00
parent dc8d7b824f
commit a57ed65d3b
2 changed files with 43 additions and 7 deletions

View File

@@ -3,6 +3,7 @@ import test from "node:test";
import {
clearCommunityStorage,
initFirstCommunity,
migrateLegacyCommunityStorage,
shouldAutoConnectDefaultRelay,
} from "./communityStorage.ts";
@@ -10,6 +11,7 @@ import {
function createMemoryStorage(initial = {}) {
const values = new Map(Object.entries(initial));
return {
values,
getItem: (key) => values.get(key) ?? null,
setItem: (key, value) => values.set(key, String(value)),
removeItem: (key) => values.delete(key),
@@ -59,6 +61,28 @@ test("signed-build relay defaults auto-connect during first-run onboarding", ()
assert.equal(shouldAutoConnectDefaultRelay("not a valid relay"), false);
});
test("failed first-community write preserves existing community data", () => {
const storage = createMemoryStorage({
"buzz-communities": '[{"id":"existing"}]',
"buzz-workspaces": '[{"id":"legacy"}]',
"buzz-active-workspace-id": "legacy",
});
storage.setItem = (key, value) => {
if (key === "buzz-communities") {
throw new Error("QuotaExceededError");
}
storage.values.set(key, String(value));
};
globalThis.localStorage = storage;
globalThis.window = { localStorage: storage };
assert.equal(initFirstCommunity("wss://relay.example.com", "pubkey"), null);
assert.equal(storage.getItem("buzz-communities"), '[{"id":"existing"}]');
assert.equal(storage.getItem("buzz-active-community-id"), null);
assert.equal(storage.getItem("buzz-workspaces"), '[{"id":"legacy"}]');
assert.equal(storage.getItem("buzz-active-workspace-id"), "legacy");
});
test("clearCommunityStorage removes new and legacy state", () => {
const storage = createMemoryStorage({
"buzz-communities": "new",

View File

@@ -160,14 +160,26 @@ export function initFirstCommunity(
pubkey,
addedAt: new Date().toISOString(),
};
const didSaveCommunities = saveCommunities([community]);
const didSaveActiveCommunity =
didSaveCommunities && saveActiveCommunityId(community.id);
const previousActiveCommunityId = localStorage.getItem(ACTIVE_COMMUNITY_KEY);
const didSaveActiveCommunity = saveActiveCommunityId(community.id);
if (!didSaveActiveCommunity) {
return null;
}
// The quota-recovery helper reports failure instead of throwing. Do not let
// callers reload unless both writes landed; otherwise first launch can loop.
if (!didSaveCommunities || !didSaveActiveCommunity) {
clearCommunityStorage();
if (!saveCommunities([community])) {
// A failed setItem leaves the existing communities value untouched. Roll
// back only the active-ID write so inconsistent pre-existing data is never
// destroyed while recovering from a quota failure.
try {
if (previousActiveCommunityId === null) {
localStorage.removeItem(ACTIVE_COMMUNITY_KEY);
} else {
localStorage.setItem(ACTIVE_COMMUNITY_KEY, previousActiveCommunityId);
}
} catch {
// Best effort: persistence is already unavailable, and callers will stay
// on setup instead of reloading.
}
return null;
}