Publish screens and use one runtime protocol for display and business actions.
SDK 0.0.1Updated
Start on the host backend
This page describes the current source protocol. Match the SDK, Indiana deployment and database migrations. npm 0.0.1 beta is published; this does not establish publication of later workspace versions. Hosts own verified sessions and persist start input. Close and discard old runs when changing accounts.
1import {2 createFunnelStartClient,3 createGrooveServer,4 createGuestToken,5} from"@tinkerlandai/indiana-sdk/server";67const options = {8 baseUrl: process.env.GROOVE_BASE_URL!,9 apiKey: process.env.GROOVE_SECRET_KEY!,10 scope: { projectId: process.env.GROOVE_PROJECT_ID!, environment: "sandbox" },11} asconst;12const groove = createGrooveServer(options);13const funnels = createFunnelStartClient(options);1415exportasyncfunctionPOST(request: Request) {16if (request.headers.get("origin") !== process.env.APP_ORIGIN)17returnnewResponse("Forbidden", { status: 403 });18const session = awaitrequireHostSession(request);19// Every run has a customer: the signed-in customer, or a guest kept in this session.20let customerToken: string;21if (session.grooveCustomerId) {22 ({ customerToken } = await groove.createCustomerSession(session.grooveCustomerId));23 } else {24 session.grooveGuestToken ??= createGuestToken();25await session.save();26await groove.createGuestCustomer({ guestToken: session.grooveGuestToken });27 ({ customerToken } = await groove.resumeGuestSession({ guestToken: session.grooveGuestToken }));28 }29// Persist before the first request. Retries reuse the whole original input.30if (!session.funnelStart) {31 session.funnelStart = {32 source: { type: "funnel", id: process.env.GROOVE_FUNNEL_ID! },33 requestId: crypto.randomUUID(),34 locale: session.locale,35 commerce: { provider: "stripe", currency: "USD" },36 };37await session.save();38 }39const result = await funnels.start({ ...session.funnelStart, customerToken });40 session.grooveRunToken = result.runToken;41await session.save();42returnResponse.json({ runToken: result.runToken }, {43 headers: { "Cache-Control": "private, no-store" },44 });45}
Display, resume and customer components
obtainCurrentUserCustomerToken first completes host sign-in, then obtains the current user’s token from your backend; cancellation returns null. requireHostSession, session.save and reportError are explicit host integration points. Retain the original start input and remount the same run after returning from checkout.
Account, membership, credits and billing are Funnel screen components. They read the current authenticated customer; purchase and membership buttons must connect to explicit node outputs in the editor.
A Paywall can start with source: { type: 'paywall', id }; the server uses its system-managed Funnel. Separate Page and remote Popup endpoints are unnecessary.
destroy only removes UI; close submits a closure command. Browser caching retains design assets only; customer data and run state remain private.
app/groove-funnel.tsTypeScript
1import { createFunnelRunController, showFunnelRun } from"@tinkerlandai/indiana-sdk";23// Your backend retains the original start request and run credential.4exportasyncfunctionopenFunnel(container: HTMLElement) {5const response = awaitfetch("/api/groove-funnel", {6 method: "POST", credentials: "same-origin", cache: "no-store",7});8if (!response.ok) thrownewError("Funnel bootstrap failed");9const { runToken } = await response.json();10const client = createFunnelRunController({11 baseUrl: "https://grow.your-domain.example",12 scope: { projectId: "your-app-uuid", environment: "sandbox" },13 runToken,14});15const handle = awaitshowFunnelRun(container, {16 client,17 onAuthenticate: async () => obtainCurrentUserCustomerToken(),18 onCheckout: ({ url }) => window.location.assign(url),19 onOpenUrl: ({ url }) => window.location.assign(url),20 onError: reportError,21});22// Keep the host's original bootstrap across navigation and payment returns.23// Destroy removes UI; close is an explicit server command.24return { handle, client, destroy() { handle.destroy(); client.reset(); } };25}26// On route leave: flow.destroy(). On explicit dismissal: await flow.handle.close().