Chataway Developers
Reference

Bridge API reference

The typed client your panel uses to talk to Chataway — context, files, host media, accounts, cards, badges and events — plus the postMessage protocol underneath.

bash
npm install @chataway/apps
ts
import { connect } from '@chataway/apps/bridge';
import '@chataway/apps/theme.css';

const app = await connect();

@chataway/apps/bridge has no dependencies and works with any framework. Everything is promise-based; errors are BridgeErrors.

#Connecting

#connect(options?)

Handshakes with the host and resolves with a ChatawayApp. Calling it again returns the same connection.

OptionTypeDefault
mockboolean | MockHostOptionsautotrue forces the mock host, false forbids it. Default: mock only when the page isn't embedded in Chataway.
timeoutMsnumber30000Default per-request timeout. Pickers, connect and media use longer ones.
connectTimeoutMsnumber10000How long to wait for the host's first answer.
applyThemebooleantrueApply the host theme to <html> (palette, light/dark, exact colours) and keep it updated.

#isEmbedded()

true when the page runs inside a Chataway panel or card.

#Context

#app.context

What the host sent on connect. Updated automatically on view and theme events.

FieldType
pluginIdstringYour app id.
projectIdstringThe current project (a local id — not the pseudonym your server sees).
chatIdstring | nullThe active chat, if any.
cardIdstring | nullSet when this page is a webview card.
paramsobjectCard params (webview cards).
viewstring | nullThe selected header view.
apiBasestringPath prefix for host URLs. files.url() and host.media() already apply it.
themeHostThemeThe host palette as hex: bg, surface, hover, text, muted, dim, border, primary, primaryFg, danger, success, dark, palette.

#app.getContext()

Re-reads the context from the host. Promise<AppContext>.

#Files

#app.pickFiles(options?)

Opens Chataway's file picker: Media library, project files, or upload from the device. Resolves with only the files the user chose. Needs files:receive.

OptionType
acceptstring[]MIME types or extensions: ['image/*', '.pdf'].
multiplebooleanAllow several files. Default false.

Returns Promise<PickedFile[]>:

PickedFile
refOpaque plugin-file: ref — pass it to host.media and files.*.
nameFile name.
typeMIME type.
sizeBytes.
blob()Promise<Blob> — the bytes.
url()Promise<string> — a URL you can use in <img>, <video> or fetch.

Rejects with cancelled when the user closes the picker. Default timeout: 15 minutes.

ts
const files = await app.pickFiles({ accept: ['image/png', 'image/jpeg'], multiple: true });
const form = new FormData();
for (const f of files) form.append('file', await f.blob(), f.name);
await fetch('https://api.example.com/upload', { method: 'POST', body: form, headers: { Authorization: `Bearer ${token}` } });

#app.files.url(ref)

A URL for a ref your app may see (a picked file, a host media result, a file you returned). Promise<string>.

#app.files.addToLibrary(ref)

Copies a file your app owns into the project's Media library. Promise<{ ref }> — the library copy's ref. Needs files:return.

#Host media

#app.host.media(op, ref, params?)

Runs a host media operation on a file and returns a new file. Needs host:media.

ts
const out = await app.host.media('resize', photo.ref, { width: 512, height: 512, fit: 'cover', format: 'png' });
// out = { ref, url, info? }
Result
refThe new file's ref. The source is never modified.
urlLoadable URL of the result.
infoFor probe: { width, height, duration, codecs }.

Default timeout: 11 minutes (jobs queue, at most 2 per app at a time).

#Accounts

#app.accounts.list()

Your declared accounts and whether each is connected. Promise<AccountState[]>: { id, label, connected, expiresAt }.

#app.accounts.connect(id)

Starts the OAuth flow. Resolves with the account state once connected; rejects with cancelled if the user gives up. Must be called from a user gesture (a click handler).

#app.accounts.getToken(id)

A fresh access token for calling your own API from the panel. Promise<{ accessToken, expiresAt }>. The host refreshes it when needed — ask again rather than caching it. Rejects with denied if the account isn't connected.

#Cards and jobs

#app.listCards(options?)

Your app's cards in this project. options.status: pending · resolved · expired · cancelled · info. Promise<PluginCard[]>.

#app.respondCard(cardId, actionId, values?)

Answers one of your cards from the panel — for example, approving a pending upload from a queue view. values carries form answers. Promise<PluginCard>. Only your own cards; others reject with denied.

#app.listJobs() · app.cancelJob(jobId)

Your app's recent jobs in this project (for example host media jobs), and cancelling one that's running.

#UI

#app.setBadge(view, count)

Shows a count on one of your header views. 0 clears it.

#Events

ts
const off = app.on('card', card => updateQueue(card));
off(); // unsubscribe
EventPayloadWhen
viewstringThe user switched the header view.
themeHostThemePalette or light/dark changed. Applied to <html> automatically unless applyTheme: false.
cardPluginCardOne of your cards was created or changed (answered, expired…).
jobPluginJobOne of your jobs progressed or finished.

app.off(event, handler) also unsubscribes. app.close() stops listening; pending requests reject with closed.

#Errors

Every failure is a BridgeError with a code and the method that failed:

codeMeaning
cancelledThe user closed a picker or the connect flow. Usually not worth an error message.
deniedA grant is missing, or it's not your card, job or file.
timeoutNo answer in time.
unsupportedThis version of Chataway doesn't know the method — ask the user to update.
closedapp.close() was called.
hostAnything else; see message.
ts
import { BridgeError } from '@chataway/apps/bridge';

try {
  await app.pickFiles();
} catch (e) {
  if (e instanceof BridgeError && e.code === 'cancelled') return;
  throw e;
}

#Mock host

Opened directly in a browser (npm run ui with Vite), connect() uses a mock host so you can build the UI without Chataway. A small floating toolbar switches palette, light/dark and view.

ts
const app = await connect({
  mock: {
    views: [{ id: 'library', label: 'Library' }, { id: 'uploads', label: 'Uploads' }],
    accounts: [{ id: 'acme', label: 'Acme', connected: true }],
    files: [{ name: 'test.png', type: 'image/png', bytes: pngBytes }],
    dark: false,
  },
});
MockHostOptions
pluginId, projectId, chatIdContext values.
views, viewHeader views and the selected one.
palette, darkInitial theme.
accountsAccounts to pretend are declared.
cards, jobsInitial cards and jobs.
filesFiles pickFiles returns without a chooser.
toolbarShow the dev toolbar (default true in a browser).
latencyMsSimulated latency.

app.isMock is true against the mock host, and app.mockHost lets you emit events from your dev tools.

#Protocol

The client is a thin layer over postMessage. You don't need this unless you're writing your own client.

js
// page → host
{ type: 'claw:request', id: 'r1', method: 'pickFiles', params: { accept: ['image/*'], multiple: true } }
// host → page
{ type: 'claw:response', id: 'r1', result: [ /* files */ ] }
{ type: 'claw:response', id: 'r1', error: 'cancelled by the user' }
{ type: 'claw:event', event: 'theme', payload: { bg: '#0b0b0c', /* … */ } }

Methods: getContext, pickFiles, host.media, accounts.list, accounts.connect, accounts.getToken, files.url, files.addToLibrary, listCards, respondCard, listJobs, cancelJob, setBadge. The page posts to window.parent with target '*' (the sandboxed frame has an opaque origin); the host only accepts messages from your frame.