This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/src/js/sw/sdk/index.js
gobha 2695bb3bdc Changeset 0.37.8 (#220)
Co-authored-by: gobha <jasafpro@gmail.com>
Co-committed-by: gobha <jasafpro@gmail.com>
2026-03-21 18:50:04 +00:00

157 lines
4.9 KiB
JavaScript

// ==========================================
// 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<object>} 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);
});
};
// ChatPane render helper — surfaces call sw.chatPane(container, opts)
// Returns Promise<imperative handle> for backward-compat with old ChatPane API.
sw.chatPane = function (container, opts = {}) {
return import('../components/chat-pane/index.js').then(({ ChatPane }) => {
const handleRef = { current: null };
const { render } = preact;
render(html`<${ChatPane} handleRef=${handleRef} ...${opts} />`, container);
return handleRef.current;
});
};
// Marker for idempotency
sw._sdk = '0.37.8';
// 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.8 ready');
return sw;
}