// ========================================== // Chat Switchboard — SDK Entry Point // ========================================== // Assembles all SDK modules into the `sw` object // and runs the boot sequence. // // Usage: // import { boot } from './js/sw/sdk/index.js'; // const sw = await boot(); // // Exports: boot() → window.sw // ========================================== import { createRestClient } from './rest-client.js'; import { createAuth } from './auth.js'; import { createCan } from './can.js'; import { createDomains } from './api-domains.js'; import { createEvents } from './events.js'; import { createPipe } from './pipe.js'; import { createTheme } from './theme.js'; import { confirm } from '../primitives/confirm.js'; import { prompt } from '../primitives/prompt.js'; /** * Boot the SDK. Assembles modules, loads auth state, connects WebSocket. * Idempotent — returns the same sw object on subsequent calls. * * @returns {Promise} sw */ export async function boot() { // Idempotent if (window.sw?._sdk) return window.sw; // 1. Auth — owns token state const auth = createAuth(); // 2. REST client — uses auth for token injection + refresh const restClient = createRestClient( () => auth._getToken(), () => auth._onRefresh(), () => auth._on401Failure() ); // 3. Wire rest client into auth (auth needs it for login/refresh/permissions calls) auth._setRestClient(restClient); // 4. Events — with WS auth callback const events = createEvents(async () => { // Prefer ticket exchange try { const data = await restClient.post('/api/v1/ws/ticket', {}); if (data?.ticket) return `ticket=${encodeURIComponent(data.ticket)}`; } catch (_) { /* fall through to legacy */ } // Legacy fallback: JWT in query param const token = auth._getToken(); return token ? `token=${encodeURIComponent(token)}` : null; }); // 5. Wire emit into auth auth._setEmit(events.emit.bind(events)); // 6. Remaining modules const { can, isAdmin, isTeamAdmin } = createCan(auth); const api = createDomains(restClient); const pipe = createPipe(); const theme = createTheme(events.emit.bind(events)); // 7. Assemble sw object const sw = Object.create(null); // Auth sw.auth = auth; // API domains sw.api = api; // RBAC sw.can = can; Object.defineProperty(sw, 'isAdmin', { get() { return isAdmin(); }, enumerable: true, }); sw.isTeamAdmin = isTeamAdmin; // Events (expose as top-level convenience + events namespace for connect/disconnect) sw.on = events.on.bind(events); sw.once = events.once.bind(events); sw.off = events.off.bind(events); sw.emit = events.emit.bind(events); sw.events = events; // Pipe & Theme sw.pipe = pipe; sw.theme = theme; // Shell helpers — imperative confirm/prompt backed by primitives sw.confirm = confirm; sw.prompt = prompt; // Toast — dynamic import to avoid module resolution issues try { const toastMod = await import('../primitives/toast.js'); sw.toast = toastMod.toast; events.on('toast', ({ message, variant, duration }) => { toastMod.toast(message, variant, duration); }); } catch (e) { console.warn('[sw] Toast import failed:', e.message); } // UserMenu render helper — surfaces call sw.userMenu(container, opts) sw.userMenu = function (container, opts = {}) { import('../shell/user-menu.js').then(({ UserMenu }) => { const { render } = preact; render(html`<${UserMenu} ...${opts} />`, container); }); }; // Marker for idempotency sw._sdk = '0.37.5'; // 8. Expose globally window.sw = sw; // 9. Boot sequence theme.init(); try { await auth.boot(); } catch (e) { console.warn('[sw] Auth boot failed (expected without backend):', e.message); } if (auth.isAuthenticated) { const BASE = window.__BASE__ || ''; events.connect(BASE + '/ws'); } // 10. Signal ready events.emit('sdk.ready', {}, { localOnly: true }); document.dispatchEvent(new CustomEvent('sw:ready', { detail: { sw } })); console.log('[sw] SDK v0.37.5 ready'); return sw; }