Skip to content

Repository files navigation

Mitra Platform SDK

Quality Gate Status Coverage

JavaScript and TypeScript SDK for browser applications built on the Mitra Platform. Applications generated by Code Studio use this package to authenticate users, access Data Manager entities, execute Server Functions and custom queries, call integrations, and run live Agent tasks.

The browser transport uses standard Web APIs only, including fetch, WebSocket, ReadableStream, AbortController, browser storage, URL, Proxy, crypto, and atob. Shared contracts and API modules come from @mitralab.io/sdk-core, without bringing browser authentication into the Core package.

Installation

npm install @mitralab.io/platform-sdk

Node.js 18 or newer is required for development and server-side tooling. The runtime application must provide the browser Web APIs used by the SDK.

Quick start

import { createClient } from "@mitralab.io/platform-sdk"

export const mitra = createClient({
  appId: import.meta.env.VITE_MITRA_APP_ID,
  apiUrl: import.meta.env.VITE_MITRA_API_URL,
  onError: (error) => console.error(error.status, error.code, error.message),
})

await mitra.init()

init() resolves the application's public Code Studio configuration, including nullable dataSourceId and allowSignup. The Data Source value remains part of the Platform 1.x compatibility flow; native Entities and Custom Queries resolve the current app through the authenticated request. Call init() during application startup before the compatibility sign-up method needs allowSignup.

Configuration

Field Required Description
appId yes ID of the published Code Studio application.
apiUrl yes Base URL of the Mitra API gateway.
authPageUrl no Absolute URL of sdk-auth.html. Falls back to window.__mitraEnv.authPageUrl, then /sdk-auth.html on the apiUrl origin.
onError no Global callback for API errors.

The client derives service endpoints from apiUrl: /iam, /data-manager, /functions, /integration, /copilot, and /code-studio.

Boundary

The Platform SDK owns:

  • browser Google SSO, logout, and session refresh
  • trusted session adoption for the embedded app preview
  • session persistence in localStorage
  • auth-state listeners
  • proactive token refresh before authenticated native requests
  • one retry after reactive 401 recovery with the current session
  • browser HTTP transport and public application initialization
  • Agent WebSocket and HTTP/SSE live channels

@mitralab.io/sdk-core owns the shared entities, custom queries, Functions, integrations, Agent task lifecycle, auth.me, safe paths, and structural response validation. Server Function code should use @mitralab.io/functions-sdk instead of this browser SDK.

Authentication

const user = await mitra.auth.signInWithGoogle({ mode: "popup" })

const unsubscribe = mitra.auth.onAuthStateChange((currentUser) => {
  console.log(currentUser?.email)
})

mitra.auth.signOut("/login")
unsubscribe()

Authentication state is stored under mitra_auth_{appId}. Before each authenticated native request, the SDK checks a JWT's exp claim with a 30-second safety window and refreshes directly through IAM when needed. Opaque tokens, malformed JWTs, and JWTs without a numeric exp remain server-authoritative and proceed to the request. A 401 still triggers reactive recovery and at most one retry. If another login or bridged session replaced the token while the request was in flight, the retry uses that current token without refreshing its session. If sign-out cleared the token, the old 401 neither refreshes nor retries.

The generated-application authentication flow is Google SSO. The old native signIn and signUp names fail locally with UNSUPPORTED_AUTH_METHOD because IAM has no email/password endpoints. Deprecated login bindings remain available only through the legacy reexports.

An embedded preview can adopt the app-scoped session it receives from the platform without exchanging it:

mitra.auth.setSession({ accessToken, refreshToken })
await mitra.auth.checkAuth()

Proactive and reactive callers share one in-flight refresh. Successful refresh rotates and persists both tokens and updates the legacy session bridge without calling auth.me() or notifying public auth-state listeners. A refresh response that arrives after sign-out or after another login/session replacement is discarded and cannot restore or overwrite that newer state. Network failures, 408, 429, and 5xx responses preserve the current session; the original HTTP request proceeds with the current token so a later 401 can use the reactive fallback. auth.me() also preserves that retained session when the fallback refresh is transient. Other IAM 4xx responses are definitive and clear the session.

JWT decoding is not user authentication. It schedules refresh and prevents cross-app session adoption. Every decodable access and refresh token must contain app_id exactly equal to the client's configured appId; opaque tokens are kept for rollout compatibility and remain validated by the server.

Custom WebSocket or Server-Sent Events boundaries can refresh explicitly before connecting:

const fresh = await mitra.auth.ensureFreshSession(30_000)

if (!fresh) {
  // A required refresh failed. The SDK may still retain the session after a
  // transient failure, but this boundary can choose whether to connect.
}

ensureFreshSession() returns true when the token does not need refresh or refresh succeeds. It returns false when a required refresh fails, including a transient failure that intentionally preserves the current session.

Google SSO uses a popup by default. The SDK opens sdk-auth.html, validates the popup source, origin, and one-time state, exchanges the returned code directly with IAM, stores both tokens, and fetches the current user:

const user = await mitra.auth.signInWithGoogle({ mode: "popup" })

The public Google options contain only mode. Account creation and locale are producer concerns, so the SDK does not send create or language to IAM. During rollout, the popup also accepts the older auth page token response.

Deprecated LoginOptions still preserve returnTo and title for source compatibility. They are not copied into the new Google API: the legacy runtime never read title, did not pass caller returnTo into popup login, and hardcoded the current URL for redirect login.

Redirect mode stores the one-time state and options in sessionStorage. Complete it during application startup before rendering authenticated routes:

const redirectedUser = await mitra.auth.completeGoogleSignInRedirect()

if (!redirectedUser) {
  await mitra.auth.signInWithGoogle({ mode: "redirect" })
}

Redirect errors are accepted only when stateMitra matches the stored one-time state. A missing or mismatched state leaves the fragment and redirect context untouched and does not expose errorMitra. The current alpha sdk-auth.html error redirect omits stateMitra, so those error redirects are intentionally rejected until that producer echoes the state; popup errors already carry state and are unaffected.

Configure the auth page explicitly when it is hosted outside the API gateway origin:

const mitra = createClient({
  appId,
  apiUrl,
  authPageUrl: "https://app.example.com/sdk-auth.html",
})

Entities

type Task = {
  id: string
  title: string
  status: "pending" | "done"
}

const { data: tasks } = await mitra.entities.getTable<Task>("Task").list({
  sort: "-created_at",
  limit: 10,
  fields: ["id", "title", "status"],
})

const pending = await mitra.entities.Task.filter({ status: "pending" })
const created = await mitra.entities.Task.create({ title: "New task" })
await mitra.entities.Task.update(created.id, { status: "done" })
await mitra.entities.Task.delete(created.id)

Table names are case-sensitive and must match the Data Manager table name. Record operations use /api/v1/tables/{table}/records. Application and tenant scope come from the authenticated context, not from a data source in the path.

The native producer does not accept jdbcConnectionConfigId or dataSourceId on public record requests. The deprecated record helpers still expose their legacy arguments, but there is no native equivalent until Data Manager defines an app-safe backend contract. The SDK does not translate those arguments into query parameters or SQL.

update(id, fields) sends the Data Manager PUT contract, which applies the supplied fields as a partial record update and preserves omitted fields. A separate patch alias would duplicate that producer behavior, so the native surface keeps one method.

Server Functions

const execution = await mitra.functions.execute("function-id", {
  orderId: "order-123",
})

console.log(execution.id, execution.status)

execute sends X-Invocation-Type: sync and waits for the terminal result. executeAsync sends async and returns the initial execution for polling or cancellation.

The complete native lifecycle is also available. getExecution is kept because the Functions producer exposes execution polling and Agent consumers use it:

const queued = await mitra.functions.executeAsync("function-id", { orderId: "order-123" })
const current = await mitra.functions.getExecution(queued.id)
await mitra.functions.cancelExecution(current.id)

Public Functions use a separate anonymous transport. It never adds Authorization or X-App-Id:

const result = await mitra.publicFunctions.execute("public-function-id", { sku: "A-1" })
const queuedPublic = await mitra.publicFunctions.executeAsync("public-function-id", { sku: "A-1" })

Public async execution is fire-and-forget. The public API does not expose anonymous polling or cancellation. Use synchronous publicFunctions.execute when the screen needs the result, or the authenticated functions.executeAsync plus functions.getExecution flow after login.

Agent tasks and credentials

The browser-safe Copilot modules call the native service directly. agentTasks provides list, read, create, rename, archive, HTTP input, history, and live sessions. These direct primitives match the Copilot producer and MCP contract. Core owns session state, queueing, recovery, and reconciliation; Platform supplies only authenticated browser WebSocket/SSE channels. agentCredentials provides safe credential status, model discovery, API key, OAuth, and device authorization flows. Raw credentials are write-only.

const credentials = await mitra.agentCredentials.list()
const models = await mitra.agentCredentials.listModels()
const chats = await mitra.agentTasks.list({ archived: false, size: 20 })

Create a live task lazily on the first message:

const session = mitra.agentTasks.session({
  create: true,
  agentType: models[0].agentType,
  reasoningEffort: models[0].reasoningOptions[0],
})

const unsubscribe = session.on("delta", ({ delta, kind }) => {
  console.log(kind, delta)
})

session.send("Analyze this application")
session.respondApproval(true)
await session.cancel()
unsubscribe()
session.close()

Open an existing task with session({ taskId }). The default auto transport refreshes before connecting, opens /copilot/ws/tasks/{taskId}, and performs at most one safe recovery through persisted history plus the HTTP/SSE channel. Set transport: "http" when WebSockets are unavailable. Messages sent during a turn enter a FIFO queue with a maximum of 10 items; the session also exposes edit, remove, clear, approval, cancel, history, close, and typed events.

API keys and removal accept ANTHROPIC or OPENAI. OAuth accepts only ANTHROPIC; device authorization accepts only OPENAI. The facade enforces those producer-supported pairs in TypeScript and at runtime.

The browser token roles expose Agent tasks, credential status, and model discovery. Administrative business-Agent CRUD is intentionally not exposed by this adapter because the USE app role does not carry AGENT_* authority.

Custom queries

const result = await mitra.queries.execute("query-id", {
  status: "active",
})

console.log(result.rows, result.affectedRows)

Custom Query execution sends only parameters. Data Manager resolves its Data Source from the authenticated app, so Queries work without a caller-selected dataSourceId and do not depend on init().

Integrations

List the current app's saved configs without exposing the Core admin module:

const configs = await mitra.integration.list({ page: 0, size: 20, sort: "alias,asc" })

The Integration producer derives the app from the authenticated token, so this list is app-scoped.

Execute a predefined resource:

const result = await mitra.integration.executeResource("resource-id", {
  description: "Notebook",
  limit: 10,
})

Or execute an integration config directly:

const result = await mitra.integration.execute("config-id", {
  method: "GET",
  endpoint: "/users",
  queryParams: { limit: "10" },
})

console.log(result.status, result.body)

An app can also address a saved config by its app-scoped alias:

const result = await mitra.integration.executeByAlias("billing", {
  method: "POST",
  endpoint: "/invoices",
  body: { customerId: "customer-1" },
})

Integration credentials are injected by the Integration service. Do not pass provider credentials through browser input.

Legacy surface

The public surface of mitra-interactions-sdk is re-exported from this package so an application can replace the legacy dependency without rewriting its call sites. Runtime re-exports are marked @deprecated and name their replacement, or state that no replacement exists yet. Every legacy type is an identity-preserving alias marked as a deprecated compatibility type.

import { createClient, loginWithGoogleMitra } from "@mitralab.io/platform-sdk"

export const mitra = createClient({ appId, apiUrl })

await loginWithGoogleMitra()
console.log(mitra.auth.accessToken)

Google SSO is available through mitra.auth.signInWithGoogle and mitra.auth.completeGoogleSignInRedirect. Agent tasks, Agent credentials, public Functions, entities, custom queries, Function execution, and integrations now have native replacements. Microsoft SSO remains available only through the complete deprecated re-export surface.

createClient configures the legacy SDK with both baseURL and authUrl set to ${apiUrl}/legacy, after removing trailing slashes, plus projectId: appId. This keeps deprecated calls and legacy login routed through the BFF while the new modules call their native APIs directly. Its authPageUrl uses the same precedence as native Google SSO: explicit client config, window.__mitraEnv.authPageUrl, then /sdk-auth.html on the apiUrl origin. Existing query parameters are preserved.

The bridge shares the session in both directions. A session persisted under mitra_auth_{appId} is handed to the legacy SDK at startup; native sign-in, Google SSO, proactive or reactive refresh, manual token changes, and sign-out update its active configuration. Sessions produced by legacy login or refresh are persisted back under the same new storage key. The bridge only propagates sessions the two SDKs produce: it never starts a login and never triggers a refresh of its own.

The legacy package exposes no sign-out API for deleting the refresh token stored in its private mitra-session entry. Native sign-out safely removes both credentials from the active legacy configuration, so deprecated calls cannot authenticate or refresh. A later direct call to configureSdkMitra can restore that private persisted refresh token; applications should keep configuration ownership in createClient during the migration.

The legacy SDK does not return a user, so auth.currentUser stays empty after a legacy login. Call mitra.auth.me() to populate it. Calling configureSdkMitra directly replaces the legacy configuration and its refresh hook until the next bridged session change, so it should not be mixed with a client-managed migration.

Errors and request behavior

API failures throw MitraApiError:

import { MitraApiError } from "@mitralab.io/platform-sdk"

try {
  await mitra.entities.Task.get("missing-id")
} catch (error) {
  if (error instanceof MitraApiError) {
    console.error(error.status, error.code, error.message)
  }
}

The transport refuses HTTP redirects. Statuses 307 and 308, opaque redirects, and responses already marked as redirected fail without replay. The only automatic replay is the single request attempted after reactive 401 recovery with either a refreshed token or a session that changed while the original request was in flight.

Before constructing MitraApiError, the SDK recursively redacts the token used by the request and credentials in Bearer format from the error message, code, details, arrays, values, and object keys. Values under credential fields such as accessToken, refreshToken, apiKey, password, authorization, secret, and clientSecret are also replaced with [REDACTED].

Development

npm install
npm run check

Platform 1.1.0-beta.0 targets exactly @mitralab.io/sdk-core@0.2.0-beta.0. Until that Core prerelease is published, local validation uses its matching tarball through MITRA_SDK_CORE_TARBALL. The manifest and lock keep the registry spec and the tarball's verified integrity; root npm ci becomes available after Core is published. Do not commit a file: dependency.

The build produces ESM, CommonJS, .d.ts, and .d.cts artifacts. Package checks inspect the public tarball with Are The Types Wrong, install it into an isolated consumer, and validate ESM, CommonJS, and TypeScript resolution.

See CHANGELOG.md for release history and LICENSE for license terms.

About

JavaScript/TypeScript SDK for building apps on the platform

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages