// --------------------------------------------------------------------------- // TygaRun Client — ready-made SaaS APIs for generated sites // --------------------------------------------------------------------------- // Universal client for ALL sdk.tyga.run modules: auth, scheduling (bookings/ // eventtypes/availability), crm, ecommerce, billing, notifications, webhooks... // Where Lovable gives you raw tables, tyga.run gives you ready-made APIs. // // This stub ships unconfigured. When the project connects tyga.run, the // builder replaces TYGARUN_CONFIG with real values (like lib/anydb.ts). // Until then isConfigured() is false and hooks fall back to seed data. // --------------------------------------------------------------------------- const TYGARUN_CONFIG = { baseUrl: "__TYGARUN_SDK_URL__", // e.g. https://sdk.tyga.run // Browser-safe `tc_pub_` publishable key (Supabase-anon model): origin-locked, // sandbox-pinned, module-restricted server-side. Safe to ship in public code. publishableKey: "__TYGARUN_ANON_KEY__", sandbox: "__TYGARUN_SANDBOX__", // "true" | "false" }; export function isConfigured(): boolean { return !TYGARUN_CONFIG.publishableKey.startsWith("__"); } const TOKEN_KEY = "tygarun_token"; export function getToken(): string | null { try { return localStorage.getItem(TOKEN_KEY); } catch { return null; } } export function setToken(token: string | null) { try { if (token) localStorage.setItem(TOKEN_KEY, token); else localStorage.removeItem(TOKEN_KEY); } catch { /* SSR / private mode */ } } export interface TygaRunError extends Error { statusCode?: number; body?: any; } /** * Universal call to any tyga.run module endpoint. * tygarun("scheduling", "/bookings", { query: { page: 1 } }) * tygarun("auth", "/signin", { method: "POST", body: { email, password } }) * tygarun("crm", "/contacts", { method: "POST", body: contact }) */ export async function tygarun( module: string, endpoint = "/", opts: { method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; body?: any; query?: Record; headers?: Record; } = {} ): Promise { if (!isConfigured()) { const err = new Error("tyga.run not connected") as TygaRunError; err.statusCode = 0; throw err; } const { method = "GET", body, query = {}, headers = {} } = opts; const path = endpoint.startsWith("/") ? endpoint : `/${endpoint}`; const url = new URL(`${TYGARUN_CONFIG.baseUrl.replace(/\/+$/, "")}/${module}${path}`); Object.entries(query).forEach(([k, v]) => { if (v !== null && v !== undefined) url.searchParams.append(k, String(v)); }); const token = getToken(); const res = await fetch(url.toString(), { method, headers: { "Content-Type": "application/json", Accept: "application/json", // Publishable key ALWAYS authenticates the app (never the user). Authorization: `Bearer ${TYGARUN_CONFIG.publishableKey}`, // End-user identity (their JWT from auth.signin) rides separately. ...(token ? { "X-User-Token": token } : {}), "X-Sandbox-Mode": TYGARUN_CONFIG.sandbox, ...headers, }, body: body && method !== "GET" && method !== "DELETE" ? JSON.stringify(body) : undefined, }); const text = await res.text(); let json: any = {}; try { json = text ? JSON.parse(text) : {}; } catch { /* non-JSON error body */ } if (!res.ok) { const err = new Error(json.error || json.message || `tyga.run ${module} error ${res.status}`) as TygaRunError; err.statusCode = res.status; err.body = json; throw err; } return json as T; } // ── Auth convenience (JWT stored for user-scoped calls) ───────────────────── export const auth = { async signup(data: { email: string; password: string; firstName?: string; lastName?: string; [k: string]: any }) { const res = await tygarun("auth", "/signup", { method: "POST", body: data }); if (res.token) setToken(res.token); return res; }, async signin(email: string, password: string, rememberMe = false) { const res = await tygarun("auth", "/signin", { method: "POST", body: { email, password, rememberMe } }); if (res.token) setToken(res.token); return res; }, signout() { setToken(null); }, isSignedIn(): boolean { return Boolean(getToken()); }, }; export default tygarun;