docs

Ease docs/Concepts/Me

ease.me: the user-self surface

What the signed-in user is: profile and avatar. Read-your-own-state. The doing verbs live elsewhere: sign-up and sign-out on ease.auth, the contact graph on ease.contacts, DMs and groups on ease.chat.

What's there

ease.me is the surface for what the signed-in user is. Five methods, all reading or writing your own state through the server:

  • getProfile() returns your MyProfile, the authenticated user's profile fields.
  • updateProfile(update) writes mutable profile fields back to the server.
  • getAvatar() returns your avatar bytes as a Uint8Array, or null when you have none.
  • setAvatar(input) uploads a new avatar. The input is a Blob, File, or Uint8Array; it returns the canonical bytes the server stored.
  • clearAvatar() removes your avatar. Idempotent.

The natural-language read of "me" is broader than the surface. It owns profile, avatar, and identity, not the room factories. Those live flat on ease.* (ease.chat, ease.contacts), not under ease.me.

Reading and updating your profile

Reads and writes both delegate to the server. getProfile() resolves to your MyProfile; updateProfile takes a ProfileUpdate carrying only the mutable fields you want to change.

TypeScript · Web SDK
const me = await ease.me.getProfile();
console.log(me.name, me.username);

// Update mutable fields only.
await ease.me.updateProfile({ name: 'Alice K.' });

Profile reads do not require a local libsignal identity. The profile surface works whenever you have an active session, even on a device that has not brought its keystore online.

Avatars

Avatars are JPEG bytes within the tenant's size cap. The server rejects an oversized image with avatar_too_large and anything that is not a valid JPEG with invalid_image. Encode on the client. The SDK does not transcode.

Your own avatar

setAvatar accepts a Blob, File, or Uint8Array and returns the canonical bytes the server stored, so you can paint the result without a second fetch. getAvatar returns Uint8Array | null, and clearAvatar is idempotent.

TypeScript · Web SDK
// Upload. Accepts a Blob, File, or Uint8Array of JPEG bytes;
// returns the canonical bytes the server stored.
const file = new File([jpegBytes], 'avatar.jpg', { type: 'image/jpeg' });
const stored = await ease.me.setAvatar(file);

// Read back.
const bytes = await ease.me.getAvatar();
// → Uint8Array or null

// Clear. Idempotent.
await ease.me.clearAvatar();

Like profile reads, avatar reads work even without a local libsignal identity. The bytes come from the server, not the keystore.

Other users' avatars

Other people's avatars are not on ease.me. That surface is yours alone. They live on ease.contacts. Each contact row carries a hasAvatar flag; when it is true, fetch the bytes by user ID.

TypeScript · Web SDK
const contacts = await ease.contacts.list();
const bob = contacts.find((c) => c.name === 'Bob');
if (bob?.hasAvatar) {
  const bytes = await ease.contacts.getAvatar(bob.userId);
  // → Uint8Array or null
}

ease.contacts.getAvatar returns null when hasAvatar is false, the same shape as your own avatar's null.

Observing changes

When a contact swaps their avatar, the SDK fires an avatar-updated push frame carrying the new avatarUpdatedAt timestamp. Subscribe through ease.contacts.onAvatarUpdated to re-fetch on demand instead of polling.

TypeScript · Web SDK
const unsub = ease.contacts.onAvatarUpdated((evt) => {
  // evt.userId, evt.avatarUpdatedAt
  // Drop your cached bytes for this user; re-fetch on next render.
});

Rendering

The SDK hands you raw JPEG bytes. Paint them with your platform's standard primitive:

  • Web: URL.createObjectURL(new Blob([bytes], { type: 'image/jpeg' })), set as the src on an img; revoke the URL when the image unmounts.
  • Apple: UIImage(data: bytes) on iOS, NSImage(data: bytes) on macOS.
  • Android: BitmapFactory.decodeByteArray(bytes, 0, bytes.size).
  • .NET: feed the bytes to your platform's image type (MAUI: ImageSource.FromStream(() => new MemoryStream(bytes))).

Devices live under ease.me

Multi-device management is the sub-surface ease.me.devices.*. These are the devices that are you. That is where the linked-device roster belongs.

The device-link verbs themselves, though, are on ease.auth: startDeviceLink, lookupDeviceLink, approveDeviceLink, completeDeviceLink, getDeviceLinkStatus, and onDeviceLinked. Linking a new device is an act of entering the system, so it sits with the rest of the enter-and-leave lifecycle on ease.auth, not on ease.me.

On the primary, call lookupDeviceLink with the pairing code first: it is a read-only preview returning the new device's self-reported name, platform, and request time, so the user confirms what they are linking before approveDeviceLink grants it access, the pattern Signal, WhatsApp, and WeChat all use at the approval moment.

Why it's separate from ease.auth

ease.auth is how you enter or leave the system: sign-up, log-in, sign-out, session restore. You are not yet "me" until auth succeeds, and you stop being "me" when ease.auth.signOut() completes. ease.me is what is true while you are signed in.

The split keeps the model honest in three directions: ease.me is what you are, ease.auth is entering and leaving the system, and ease.contacts is everyone else. Profile mutations require an active session, which the SDK enforces. Calling ease.me.getProfile() before sign-in throws not_signed_in.

See also