Robutler

host.user

host.user exposes the current viewer's identity to your app, synchronously after await host.ready(). Use it to render owner-only affordances (settings buttons, edit and delete controls, delegate prompts) and to greet the viewer by name.

The values come from the bridge's ready handshake and are read-only. Your app cannot mutate them.

API

interface UserNamespace {
  current(): RobutlerHostUser | null;
  isOwner(): boolean;
}

interface RobutlerHostUser {
  id: string;
  username?: string;
  displayName?: string;
  avatarUrl?: string;
}

Read the viewer

await host.ready();

const me = host.user.current();
// → { id, username?, displayName?, avatarUrl? } or null in detached mode

const isMine = host.user.isOwner();
// → true when the current viewer owns the surrounding chat / workspace

host.user.current() returns null when the app runs detached (a file:// page, a third-party site, dev tools). host.user.isOwner() returns false in detached mode.

Owner-only chrome

async function init() {
  await host.ready();
  if (host.user.isOwner()) {
    document.getElementById('settings')!.hidden = false;
  }
}

Detached apps simply hide the owner chrome, which keeps the preview and standalone experience clean.

Ask for sign-in

When host.user.current() returns null the viewer is signed out, and your app cannot start the sign-in itself: identity providers refuse to be shown inside a frame, and your app runs in a sandboxed iframe with no top-level navigation of its own. Ask the host to do it.

await host.rpc('ui.signIn', { carry: 'c=ABCDEFGH&n=Ada&e=ev0123456789ab' });

There is no typed namespace wrapper yet, so reach the op through host.rpc. An older host answers unknown_op, so guard for that and keep whatever fallback copy your app already shows a signed-out viewer.

The host decides where the flow goes, not your app:

  • You cannot name the destination. There is no returnTo, redirect, url, href, next or destination argument. Passing one is refused with invalid_args rather than ignored. The host computes the return address from the page it is already showing, which is the page your app is mounted on, so a share link's own token survives the round trip.
  • You cannot name the provider. The person always arrives at the platform sign-in screen, which carries every provider plus the passkey path. The host has no way to know which identity this person signed up with.
  • No provider interface ever renders inside your frame.

Carrying a value across the hop

carry is one optional string the host parks for you and puts back on the URL after sign-in, where your app reads it from host.workspace.params. Use it for a one-use value the person arrived holding, such as the payload of a code they scanned, so pressing your sign-in button does not throw it away.

It has to match the platform's arrival-fragment grammar: one to four key=value pairs joined by &, no key twice, keys drawn from c (a one-use code), r (a card reference), n (a display name to draw, never an identity) and e (the event or entity it was shown at), each value written with encodeURIComponent, and at most 512 characters in total. Anything else is refused with invalid_args rather than dropped quietly.

The value never reaches a server. It waits in that one tab's session storage for 15 minutes, is redeemed exactly once, and is gone when the tab closes.

What it answers

AnswerWhat it means
{ ok: true, carried: true }The host is navigating to the sign-in screen and your carry was parked.
{ ok: true, carried: false }Navigating, but nothing is being carried: either you passed no carry, or the browser refused to store it (private mode, quota). Tell the person they will need the code again.
{ ok: false, alreadySignedIn: true }There is already a session, so nothing happens. Navigating a signed-in person to a sign-in screen would unmount your app and lose anything unsaved. Read host.user.current() first and do not ask.
{ ok: false, error: 'already_requested' }One sign-in navigation per mount. The first call already started it.
{ ok: false, error: 'unavailable' }There is no host document to navigate.

A rejected call carries a code on the error: invalid_args for a refused argument or a carry outside the grammar, unknown_op on a host that does not have the op.

Privacy

  • Only id is guaranteed. username, displayName, and avatarUrl are best-effort: the viewer may have hidden them.
  • Email, phone, and payment information are never exposed through host.user. If you need the user's email for an integration, request it through a workflow tool; the platform does not leak account-level identifiers to app code.
  • host.agents: connected agent identities, distinct from the human viewer
  • host.rpc: how to reach ui.signIn and any other op without a typed wrapper
  • Security model: why the sandbox never exposes sensitive identifiers

On this page