diff --git a/desktop/src/features/communities/communityStorage.test.mjs b/desktop/src/features/communities/communityStorage.test.mjs index e55c1d7b7c..170ae11882 100644 --- a/desktop/src/features/communities/communityStorage.test.mjs +++ b/desktop/src/features/communities/communityStorage.test.mjs @@ -54,6 +54,8 @@ test("signed-build relay defaults auto-connect during first-run onboarding", () ); assert.equal(shouldAutoConnectDefaultRelay("ws://localhost:3000"), false); assert.equal(shouldAutoConnectDefaultRelay("ws://127.0.0.1:3000"), false); + assert.equal(shouldAutoConnectDefaultRelay("ws://[::1]:3000"), false); + assert.equal(shouldAutoConnectDefaultRelay("ws://0.0.0.0:3000"), false); assert.equal(shouldAutoConnectDefaultRelay("not a valid relay"), false); }); diff --git a/desktop/src/features/communities/communityStorage.ts b/desktop/src/features/communities/communityStorage.ts index 3dc43bddd8..9f4d79eb47 100644 --- a/desktop/src/features/communities/communityStorage.ts +++ b/desktop/src/features/communities/communityStorage.ts @@ -81,8 +81,11 @@ export function loadCommunities(): Community[] { } } -export function saveCommunities(communities: Community[]): void { - setLocalStorageItemWithRecovery(COMMUNITIES_KEY, JSON.stringify(communities)); +export function saveCommunities(communities: Community[]): boolean { + return setLocalStorageItemWithRecovery( + COMMUNITIES_KEY, + JSON.stringify(communities), + ); } export function clearCommunityStorage(storage: Storage = localStorage): void { @@ -97,8 +100,8 @@ export function loadActiveCommunityId(): string | null { return localStorage.getItem(ACTIVE_COMMUNITY_KEY); } -export function saveActiveCommunityId(id: string): void { - setLocalStorageItemWithRecovery(ACTIVE_COMMUNITY_KEY, id); +export function saveActiveCommunityId(id: string): boolean { + return setLocalStorageItemWithRecovery(ACTIVE_COMMUNITY_KEY, id); } export function normalizeRelayUrl(url: string): string { @@ -111,7 +114,9 @@ export function normalizeRelayUrl(url: string): string { export function shouldAutoConnectDefaultRelay(relayUrl: string): boolean { try { const parsed = new URL(normalizeRelayUrl(relayUrl)); - return parsed.hostname !== "localhost" && parsed.hostname !== "127.0.0.1"; + return !["localhost", "127.0.0.1", "[::1]", "0.0.0.0"].includes( + parsed.hostname, + ); } catch { return false; } @@ -145,7 +150,7 @@ export function initFirstCommunity( relayUrl: string, pubkey: string, name?: string, -): Community { +): Community | null { const normalizedUrl = normalizeRelayUrl(relayUrl); const trimmedName = name?.trim(); const community: Community = { @@ -155,7 +160,16 @@ export function initFirstCommunity( pubkey, addedAt: new Date().toISOString(), }; - saveCommunities([community]); - saveActiveCommunityId(community.id); + const didSaveCommunities = saveCommunities([community]); + const didSaveActiveCommunity = + didSaveCommunities && saveActiveCommunityId(community.id); + + // 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(); + return null; + } + return community; } diff --git a/desktop/src/features/communities/useCommunityInit.ts b/desktop/src/features/communities/useCommunityInit.ts index b44a56cc19..b1e2251c3c 100644 --- a/desktop/src/features/communities/useCommunityInit.ts +++ b/desktop/src/features/communities/useCommunityInit.ts @@ -107,9 +107,20 @@ export function useCommunityInit( ) { const identity = await getIdentity(); if (cancelled) return; - initFirstCommunity(defaultRelayUrl, identity.pubkey); - if (!cancelled) { + const community = initFirstCommunity( + defaultRelayUrl, + identity.pubkey, + ); + if (community && !cancelled) { window.location.reload(); + return; + } + if (!cancelled) { + setResult({ + isReady: false, + needsSetup: true, + defaultRelayUrl, + }); } return; } diff --git a/desktop/tests/e2e/onboarding.spec.ts b/desktop/tests/e2e/onboarding.spec.ts index c3c716bec3..e1d87951a6 100644 --- a/desktop/tests/e2e/onboarding.spec.ts +++ b/desktop/tests/e2e/onboarding.spec.ts @@ -571,7 +571,7 @@ test("first-community choices route join, create, owner, and member intents", as ); }, BLANK_TYLER_IDENTITY.pubkey); await installMockBridge(page, undefined, { - relayWsUrl: "wss://default.example.com", + relayWsUrl: "ws://localhost:3000", skipOnboardingSeed: true, skipCommunitySeed: true, }); @@ -657,7 +657,7 @@ test("first-community owner can connect an existing hosted community", async ({ ], }, { - relayWsUrl: "wss://default.example.com", + relayWsUrl: "ws://localhost:3000", skipOnboardingSeed: true, skipCommunitySeed: true, }, @@ -715,7 +715,7 @@ test("first-community owner can create and connect a hosted community", async ({ page, {}, { - relayWsUrl: "wss://default.example.com", + relayWsUrl: "ws://localhost:3000", skipOnboardingSeed: true, skipCommunitySeed: true, }, @@ -789,7 +789,7 @@ test("hosted community address line stays within the card for a long name", asyn page, {}, { - relayWsUrl: "wss://default.example.com", + relayWsUrl: "ws://localhost:3000", skipOnboardingSeed: true, skipCommunitySeed: true, }, @@ -860,7 +860,7 @@ test("first-community reports a created community without a relay address", asyn }, }, { - relayWsUrl: "wss://default.example.com", + relayWsUrl: "ws://localhost:3000", skipOnboardingSeed: true, skipCommunitySeed: true, }, @@ -891,7 +891,7 @@ test("first-community X cancels a pending sign-in", async ({ page }) => { page, { builderlabLoginDelayMs: 5_000 }, { - relayWsUrl: "wss://default.example.com", + relayWsUrl: "ws://localhost:3000", skipOnboardingSeed: true, skipCommunitySeed: true, }, @@ -933,7 +933,7 @@ test("first-community owner can replace a mismatched account identity", async ({ builderlabIdentity: { pubkey_hex: "f".repeat(64) }, }, { - relayWsUrl: "wss://default.example.com", + relayWsUrl: "ws://localhost:3000", skipOnboardingSeed: true, skipCommunitySeed: true, }, @@ -983,7 +983,7 @@ test("first-community explains when the local identity belongs to another accoun builderlabBindError: { code: "pubkey_already_bound" }, }, { - relayWsUrl: "wss://default.example.com", + relayWsUrl: "ws://localhost:3000", skipOnboardingSeed: true, skipCommunitySeed: true, }, @@ -1024,7 +1024,7 @@ test("back clears Builderlab auth before returning to first-community choices", builderlabIdentity: { pubkey_hex: BLANK_TYLER_IDENTITY.pubkey }, }, { - relayWsUrl: "wss://default.example.com", + relayWsUrl: "ws://localhost:3000", skipOnboardingSeed: true, skipCommunitySeed: true, }, @@ -1112,7 +1112,7 @@ test("first-community direct join reaches profile", async ({ page }) => { ); }, BLANK_TYLER_IDENTITY.pubkey); await installMockBridge(page, undefined, { - relayWsUrl: "wss://onboarding.communities.buzz.xyz", + relayWsUrl: "ws://localhost:3000", skipOnboardingSeed: true, skipCommunitySeed: true, }); @@ -1167,7 +1167,7 @@ test("first-community direct join cancel returns to request access", async ({ page, { applyCommunityDelayMs: 5_000 }, { - relayWsUrl: "wss://onboarding.communities.buzz.xyz", + relayWsUrl: "ws://localhost:3000", skipOnboardingSeed: true, skipCommunitySeed: true, },