diff --git a/examples/white-label-minimal/components/useBuilderSocket.ts b/examples/white-label-minimal/components/useBuilderSocket.ts index 1ff0a3a..284c0d8 100644 --- a/examples/white-label-minimal/components/useBuilderSocket.ts +++ b/examples/white-label-minimal/components/useBuilderSocket.ts @@ -1,6 +1,6 @@ "use client"; import { useCallback, useEffect, useState } from "react"; -import { getApp } from "../lib/chat/builder-api"; +import { ApiError, getApp } from "../lib/chat/builder-api"; import { watchApp } from "../lib/chat/live-updates"; import type { App, Message } from "../lib/types"; @@ -22,11 +22,12 @@ export function useBuilderSocket(appId: string | null) { let stopped = false; let stop: (() => void) | undefined; const readApp = () => getApp(appId).then((next) => { if (!stopped) setApp(next); }); - const fail = () => { + const fail = (error?: unknown) => { if (stopped) return; stopped = true; stop?.(); - setError("Live updates paused. Reconnect to continue."); + // Our server explains configuration problems; socket errors get the generic message. + setError(error instanceof ApiError ? error.message : "Live updates paused. Reconnect to continue."); setLoaded(key); }; diff --git a/examples/white-label-minimal/lib/base44/socket-session.ts b/examples/white-label-minimal/lib/base44/socket-session.ts index 3be572f..88b5723 100644 --- a/examples/white-label-minimal/lib/base44/socket-session.ts +++ b/examples/white-label-minimal/lib/base44/socket-session.ts @@ -12,9 +12,13 @@ export async function openSocketSession(appId: string) { body: JSON.stringify({ app_ids: [appId] }), cache: "no-store", }); - if (response.status === 401 || response.status === 403) - throw new Base44Error("Live updates need a workspace key with the apps:watch scope.", 503); - if (!response.ok) throw new Base44Error(`Base44 returned ${response.status} opening live updates.`); + if (!response.ok) { + // Base44 says why: a key without apps:watch, a workspace without white-label + // sockets, or an app the key isn't granted. + const reason = (await response.json().catch(() => null))?.error?.message; + throw new Base44Error(`Live updates unavailable: ${reason ?? `Base44 returned ${response.status}`}.`, + response.status === 401 || response.status === 403 ? 503 : 502); + } const { socket_url, session_token } = await response.json(); return { serverUrl: new URL(socket_url).origin, sessionToken: String(session_token) }; } diff --git a/examples/white-label-minimal/tests/server.test.ts b/examples/white-label-minimal/tests/server.test.ts index 5458f9f..0ed055d 100644 --- a/examples/white-label-minimal/tests/server.test.ts +++ b/examples/white-label-minimal/tests/server.test.ts @@ -207,8 +207,8 @@ test('the live-updates session opens with the workspace key only after sign-in a assert.equal(calls.length, 1); }); -test('a workspace key without the apps:watch scope is reported as configuration', async () => { - setup({ error: { code: 'scope_required' } }, 403); +test('Base44 rejecting the key is reported as configuration, with its reason', async () => { + setup({ error: { code: 'scope_required', message: 'The key needs the apps:watch scope' } }, 403); const response = await request({ action: 'openBuilderSession', appId: 'app_1' }); assert.equal(response.status, 503); assert.match((await response.json()).error, /apps:watch/);