Witbitz docs HomeTrustAll docs

People — sign-in, who may enter, who runs the room

A room is open to anyone with its link, or gated: only signed-in people on its list may read or write it. This page is signing people in, gating a room, its admins and owners, direct chats, and changing who is in it.

Part of the SDK · every call: the reference.


Sign in

Your app needs no accounts of its own. The SDK signs people in the way the Spaces app's Email me a code does: a six-digit code to any address, from any origin.

javascript
const wb = new Witbitz()
const { ticket } = await wb.signIn({ email })      // a code is emailed
const session = await wb.signIn({ email, code })   // → { email, idToken, exp, sign }
localStorage.setItem('wb:session', JSON.stringify(session))
const renewed = await wb.refreshSession()          // before session.exp, with no new code

Gated rooms

A room's gate is chosen when it is made, with wb.createSpace or space.privateLane; a room with none is open to anyone holding its link.

javascript
const space = await wb.createSpace({
  agent: { name: 'Warden' },
  admission: { emails: ['dana@example.com'], domains: ['keepwarden.com'] },
})

Reading the gate

A member holds the room key, so your app can read the gate the server enforces instead of keeping a list of its own:

javascript
await space.admission()
// → { gated: true, emails: ['lead@keepwarden.com'], domains: ['keepwarden.com'], hidden: 3,
//     admins: ['lead@keepwarden.com'], owners: [] }
await space.admits('dana@example.com')   // → true / false, against the sealed list

Who did what

In a gated room every write needs an allowed sign-in, and the server marks what a signed-in member wrote:

On The server sets
a message verified; user, their @username (absent when the room set usernames: false); spk, the key of the device that sent it
a row _verified; _email, their verified email; _user, their @username (absent when the room set usernames: false)

A message does not carry the email, so its author's address is not readable by every member. from and a row's _by stay the display name the writer chose; lane.approve(id, { from }) sets the one a lane's message shows.

Admins

A gated room can name admins, and the room itself — not your app's interface — enforces what only they may do:

javascript
const team = await wb.createSpace({
  admission: { emails: ['lead@keepwarden.com', 'dana@example.com'], admins: ['lead@keepwarden.com'] },
  data: {
    tasks:  { fields: { title: 'text', done: 'boolean' } },                    // every member
    budget: { fields: { item: 'text', amount: 'number' }, write: 'admins' },   // admins only
  },
})
await team.isAdmin('dana@example.com')   // → false

In a room with admins, the room refuses a member who is not one (not_admin) when they:

Posting, reading, the other collections and a member's own lane stay open to every member.

Owners

Admins are equal, and any admin can change the room's members — so on its own, any admin could change the list to one without the other admins, or without the person who made the room. Owners are a level above, enforced by the room too:

javascript
const team = await wb.createSpace({
  admission: { emails: ['founder@keepwarden.com', 'lead@keepwarden.com', 'dana@example.com'],
               owners: ['founder@keepwarden.com'], admins: ['lead@keepwarden.com'] },
})

Direct chats

A direct chat is the Spaces app's one-to-one chat: a gated room for exactly you and one other person. Each of you sees it under the other's name, nobody renames it, and the assistant is in it but answers only when @-mentioned.

javascript
const chat = await wb.createSpace({ agent: { name: 'Helper' }, direct: 'sam@example.com' })   // needs wb.signIn
// give Sam chat.shareLink; Sam opens it signed in as sam@example.com
await chat.directPeer()   // → { email: 'sam@example.com' } (on Sam's side: you)

Changing members in place

A gated room changes who is in it without becoming a new room: the same id, link, history, store and lanes, and, from that moment, a new key that only the people still on the list receive.

javascript
const r = await team.changeMembers({ emails: ['lead@keepwarden.com', 'dana@example.com'], admins: ['lead@keepwarden.com'] })
// r → { inPlace: true, epoch, link, removed: ['eve@example.com'], added: [], unreachable: [] }
await team.rotateKey()   // the same members, a new key — for a lost phone, or a link sent to the wrong place

team.on('rekeyed', ({ epoch, link }) => saveLink(link))
team.on('removed', () => showGone())
team.on('pending', ({ reason }) => askForTheLink())

changeMembers takes the whole new list. Every member's open copy of the room takes the new key by itself, on its next request: 'rekeyed' gives the new link (null in a vault), 'removed' means this account is no longer on the list, and 'pending' means it is on the list but has no key this device can open yet.

@usernames

javascript
await wb.usernames.claim('dana', { publish: true, name: 'Dana' })   // → { username, published }
await wb.usernames.mine()                                          // → { username, published }; throws when signed out
await wb.usernames.resolve('dana')                                 // → { username, exists }; no sign-in needed
await wb.usernames.release()

One @username means one person across all of Witbitz, not per app. Verified messages and rows carry it (user, _user), and the Spaces app can admit a person to a room by it; the SDK's admission takes emails and domains.

Rooms an older client moved

A room changes its members only in place (above). space.fork() makes nothing and throws use_change_members, and plan() refuses a change that names a history with the same code. For a fresh start, make a new room with wb.createSpace({ admission }); nothing is carried into it.

An older client (a 2.2.x page) could still move a room to a new one, and the SDK follows such a move:

javascript
team.on('moved', ({ room, link }) => switchTo(wb.openSpace(link)))
team.on('moveRefused', ({ reason }) => showWhy(reason))

Machine-readable source: sdk-people.md · Generated 2026-10-27T07:05:42Z · build f301388c · every doc in one fetch: llms-full.txt (HTML)