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 / workspacehost.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,nextordestinationargument. Passing one is refused withinvalid_argsrather 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
| Answer | What 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
idis guaranteed.username,displayName, andavatarUrlare 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.
Related
- host.agents: connected agent identities, distinct from the human viewer
- host.rpc: how to reach
ui.signInand any other op without a typed wrapper - Security model: why the sandbox never exposes sensitive identifiers