Users, friends and presence
This page covers everything about people: the user's own profile, other users' profiles, friends and friend requests, the blocklist, presence, and the user's signed-in devices.
- Profiles (
im.users): the user's own profile comes with sign-in and is saved locally; the user can change their nickname, avatar and custom attributes. Other users' profiles are fetched in batches as needed and cached, so list UIs can read them synchronously. - Friends (
im.friends): the friend list and blocklist are stored locally in full and exposed as live lists, with changes from other devices synced in real time. Friend requests are queried page by page. - Presence (
im.presence): subscribe to the people you're showing and get an event when their status changes, or query once. - Signed-in devices (
im.devices): see the user's devices that are online now and all signed-in devices, and sign other devices out.
Call these methods after signing in (see Initialization and login). The general rules for live lists, synchronous state and events are in Events and errors.
| You want to | See |
|---|---|
| Show and change the user's own nickname and avatar | Your own profile |
| Show other users' nicknames and avatars in a list | Show nicknames and avatars in lists |
| Show the friend list, or check whether someone is a friend | Show the friend list, Check your relationship with someone |
| Add friends and handle incoming requests | Send a request, Received requests and unread count |
| Block someone | Blocklist |
| Show online or offline | Subscribe to the people you show |
| See and sign out the user's other devices | Your signed-in devices |
Your own profile
Read your profile
im.users.me() returns the locally saved profile synchronously, or null when signed out. When the profile is changed on another device or by your server, the SDK updates the local copy and emits me.changed.
const me = im.users.me();
render(me?.nickname || me?.username);
im.on('me.changed', (profile) => {
render(profile?.nickname || profile?.username);
});To make sure you have the latest profile from the server, call im.users.refreshMe().
Change nickname, avatar and custom attributes
im.users.updateMe() sends only the fields you pass; the rest stay unchanged. Pass an empty string to clear the nickname or avatar. Custom attributes are merged key by key: keys set to null are deleted, and setting the whole attributes to null clears them all. On success the local profile updates and me.changed is emitted.
const updated = await im.users.updateMe({
nickname: '张三',
attributes: { sign: '今天也要加油', city: null }, // 设置 sign,删除 city
});
console.log(updated.version);Nicknames and avatars go through content moderation. If they fail, the call rejects with content_rejected and details.field is nickname or avatar_url. For field formats, see User management in the server docs.
If several devices may edit the profile at once, pass the version you read. If the profile has been changed elsewhere, the call rejects with version_conflict and nothing is changed:
const current = im.users.me();
try {
await im.users.updateMe({ nickname: '张三', version: current?.version });
} catch (err) {
if (err instanceof DRError && err.code === 'version_conflict') {
await im.users.refreshMe(); // 取得最新的资料,让用户重新确认
}
}Set an avatar
Upload the avatar first with purpose user_avatar, then write the returned public URL to the profile. Uploaded images are scaled so the long side is at most 640 pixels; see Files.
async function changeAvatar(file: File) {
const info = await im.files.upload(file, { purpose: 'user_avatar' });
await im.users.updateMe({ avatar_url: info.url });
}If the app turns on media_url_only in its runtime policies, only avatar URLs uploaded by this app (or https URLs on allowed hosts) are accepted. Other URLs reject with invalid_argument and details.reason is url_not_allowed.
Whether the user is muted
Your server can mute users globally. im.users.myMute gives the user's mute in three scopes: one-to-one (chat), groups (group) and chat rooms (room), each null if not muted. me.muteChanged is emitted when it changes:
im.on('me.muteChanged', ({ scope, muted, expires_at }) => {
if (muted) showToast(`你已被禁言${expires_at ? `,到 ${new Date(expires_at).toLocaleString()} 解除` : ''}`);
});Display only
The server has no API for querying your own mute, so myMute only reflects changes received during the current connection. After a reload it's empty even if the user is still muted. Don't disable the input box based on it; rely on the user_muted error the server returns when sending.
Other users' profiles
Show nicknames and avatars in lists
im.users.get(username) returns the cached profile synchronously. If it isn't cached or has expired (10 minutes by default; change it with the userCacheTtlMs option), it returns whatever it has (possibly undefined) and fetches in the background. Once fetched, users.changed is emitted with the usernames whose profiles changed.
For UIs that show many people at once, such as the conversation list or a member list, use get and re-render on users.changed instead of awaiting each user. Background fetches within 50 ms are merged into one request of up to 100 users.
function nameOf(username: string): string {
const profile = im.users.get(username);
return profile?.nickname || username;
}
im.on('users.changed', ({ usernames }) => {
render(usernames.map(nameOf));
});To show names in the order "friend remark, group nickname, nickname, username", use displayName(im, username) from @deeprespond/im-web/render. It also reads synchronously and updates through the event once profiles arrive.
Query profiles
When you really need to wait for the result (for example when opening someone's profile page), call im.users.fetch(). The result is a Map keyed by lowercase username; users that don't exist or were deleted are left out. Unexpired cache entries are returned directly; with force: true the server is always queried.
const profiles = await im.users.fetch(['lisi', 'wangwu']);
const lisi = profiles.get('lisi');
if (!lisi) showToast('用户不存在');Profiles are cached locally and survive a reload. The server doesn't push friends' profile changes, so other users' profiles update at most one cache period later.
Friends
Show the friend list
im.friends.list() returns a live list of all friends, sorted by the first non-empty value of remark, nickname and username (Chinese sorted by pinyin). The SDK syncs the friend list after sign-in, and later additions, removals and edits (including those made on other devices) show up in the list in real time. Call dispose() when you no longer show it.
const friends = im.friends.list();
const unsubscribe = friends.subscribe(() => {
const { items, loading } = friends.getSnapshot();
if (!loading) render(items.map((f: Friend) => f.remark || f.nickname || f.username));
});
// 页面离开时
unsubscribe();
friends.dispose();To just check whether someone is a friend or read their remark, use the synchronous im.friends.get(username), which returns undefined for non-friends. im.friends.listInfo gives the friend count and limit:
const info = im.friends.listInfo;
if (info) render(`好友 ${info.count} / ${info.max_count}`);In React, use useLiveList:
import type { Friend } from '@deeprespond/im-web';
import { useLiveList } from '@deeprespond/im-web-react';
export function FriendList() {
const { snapshot } = useLiveList<Friend>((im) => im.friends.list(), []);
if (snapshot.loading) return <p>加载中…</p>;
return (
<ul>
{snapshot.items.map((f) => (
<li key={f.username}>{f.remark || f.nickname || f.username}</li>
))}
</ul>
);
}Check your relationship with someone
To show states like "Add friend", "Awaiting approval" or "Blocked" on a profile page, use im.friends.check(), with up to 100 users per call. The user themself and nonexistent users are left out of the result.
const [relation] = await im.friends.check(['lisi']);
if (relation?.is_friend) render('发消息');
else if (relation?.request === 'sent') render('等待验证');
else if (relation?.request === 'received') render('对方已向你发出申请');
else render('加好友');blocked only says whether the user has blocked the other person; whether the other person has blocked the user is never revealed.
Set remarks and custom attributes
Remarks and custom attributes belong only to the user's side; the friend can't see them. Custom attributes can be used for starred friends or friend groups. They're merged key by key, and keys set to null are deleted.
await im.friends.update('lisi', { remark: '老李', attributes: { star: '1', group: '同事' } });Remove a friend
After removal, neither side is a friend of the other, and the user is also removed from the other person's friend list. Removing someone who isn't a friend also succeeds.
await im.friends.remove('lisi');Friend requests
Send a request
const result = await im.friends.requests.send({
username: 'lisi',
message: '我是张三,项目组的',
remark: '李四', // 成为好友后自动设置的备注
add_source: 'search', // 添加来源,由你定义,会展示给对方
});
if (result.status === 'accepted') showToast('已成为好友');
else showToast('已发送,等待对方验证');The result depends on the other user's friend add mode. If they require approval, status is pending. If they allow anyone to add them, or they had already sent the user a request, the two become friends right away: status is accepted and friend is the new friend. If they reject everyone, the call rejects with friend_add_denied.
Common errors:
code | Description |
|---|---|
already_exists | Already friends. The SDK treats this as success and returns status: 'accepted'; it never rejects with this error |
friend_add_denied | The other user rejects all friend requests |
user_blocked | One of the two has blocked the other |
limit_exceeded | The user's friend list is full, or the other user allows anyone to add them and their friend list is full |
rate_limited | Too many requests. details.reason is request_rate (20 per minute, 500 per day), pair_daily_limit (5 per day to the same person) or declined_cooldown (within 24 hours of being declined) |
content_rejected | The message or add source failed content moderation |
permission_denied | Your server rejected the request through the before-add-friend callback. details.reason is app_rejected or callback_unavailable |
timeout | Network error or timeout, and the SDK checked and confirmed the request wasn't sent |
Sending a request is never retried automatically. After a network error or timeout, the SDK checks the relationship first: if the two are already friends or the request went through, it returns success; if it confirms the request wasn't sent, it rejects with timeout and the user decides whether to send again. For rate_limited, tell the user based on details.reason.
A sent request can't be withdrawn. For the full rules (requests expire after 30 days, sending again updates the existing request, and so on), see Friend requests in the server docs.
Received requests and unread count
im.friends.requests.unreadCount gives the number of received requests the user hasn't viewed yet, synchronously (counted up to 100, so you can show "99+" above 99). friendRequests.changed is emitted when it changes:
im.on('friendRequests.changed', ({ unread_count }) => {
render(unread_count > 99 ? '99+' : String(unread_count || ''));
});When the user opens the "New friends" page, query received requests and mark them as read once shown:
const page = await im.friends.requests.received();
render(page.items.map((r: FriendRequest) => `${r.nickname || r.username}:${r.message}(${r.status})`));
await im.friends.requests.markRead();- Without
cursorandstatus, it returns the first page held in SDK memory without sending a request. The SDK refreshes that page on sign-in, on reconnect and when request notifications arrive, so it's current. Filtering bystatusor paging queries the server. markRead()without arguments marks the unread requests on the current first page as read. You can also passread_until(therequested_atof the newest request shown).
Query sent requests with im.friends.requests.sent(), which works the same way. A request not handled within 30 days of the last send expires and its status becomes expired.
Accept or decline
const friend = await im.friends.requests.accept('wangwu', { remark: '王五' });
await im.friends.requests.decline('zhaoliu');Accepting makes the two friends and the friend list updates right away. After declining, the other user can't send the user another request for 24 hours. Expired requests reject with not_found. If either side's friend list is full when accepting, the call rejects with limit_exceeded.
Blocklist
Once the user blocks someone, that person can't send them one-to-one messages, send them friend requests or invite them to groups, and any pending friend request the user sent them is withdrawn. Blocking doesn't remove the friendship and doesn't notify the other person. See Blocklist in the server docs.
await im.friends.blacklist.add('spammer');
if (im.friends.blacklist.has('spammer')) render('已拉黑');
const blacklist = im.friends.blacklist.list(); // 按拉黑时间从新到旧
blacklist.subscribe(() => render(blacklist.getSnapshot().items));
await im.friends.blacklist.remove('spammer');The blocklist holds up to 1000 people. If the app turns off blocklist enforcement (user_blacklist_enabled) in its runtime policies, users can no longer block others and add rejects with permission_denied; viewing and unblocking still work.
Friend add mode
Each user can choose how friend requests sent to them are handled:
| Value | Description |
|---|---|
need_confirm | Approval required: a pending request is created, and the two become friends once the user accepts |
allow_any | Anyone can add: the two become friends right away, without approval |
deny_any | Nobody can add: requests fail with friend_add_denied |
const { friend_add_mode, effective_friend_add_mode } = await im.friends.settings.get();
render(friend_add_mode === null ? `跟随应用默认(${effective_friend_add_mode})` : friend_add_mode);
await im.friends.settings.set('need_confirm');
await im.friends.settings.set(null); // 清除本人的设置,改用应用的默认值If the user hasn't set it, the default from the app's runtime policies applies (need_confirm by default).
Presence
Presence requires the app to turn on presence_enabled in its runtime policies (off by default). The policies also control whose status can be seen: with presence_scope set to friends (the default) only friends' status is visible; with all, anyone's. When presence_last_seen_visible is off, last-seen times aren't shown (last_seen_at is null). Read these from im.config to decide whether to show presence in the UI:
const config = im.config;
const showPresence = config?.presence_enabled === true;When presence is off, subscribe and query fail with permission_denied (details.reason is presence_disabled).
Subscribe to the people you show
im.presence.subscribe() registers people to follow and returns a function that cancels this subscription. presence.changed is emitted when a status changes; then read it with the synchronous im.presence.get(username):
const visible = ['lisi', 'wangwu'];
const stop = im.presence.subscribe(visible);
im.on('presence.changed', ({ items, reset }) => {
for (const username of visible) {
const state = im.presence.get(username);
render(username, state === undefined ? '未知' : state.online ? `在线(${state.platforms.join('、')})` : '离线');
}
});
// 不再显示时
stop();- Subscribe only to what's visible, such as friends visible in the conversation list or the other user in an open chat. Subscribing to the same person several times is reference-counted; they're only unsubscribed once every subscription is cancelled.
- Delays. Subscription changes are batched for 2 seconds before being sent to the server, and unsubscribing waits 30 seconds (skipped if the person is needed again), so fast scrolling doesn't flood the server.
- Limit. One browser can subscribe to at most 3000 people at once.
- Multiple tabs. Tabs in the same browser share one connection; the SDK merges subscriptions, and each tab's
subscribeis independent. - Reconnecting. After a reconnect the SDK resubscribes automatically.
presence.changedthen hasreset: true, meaning all previous status was cleared.
get returns undefined in these cases, and the UI should show "Unknown" rather than "Offline": no subscription result yet; the person doesn't exist or isn't visible; resubscribing after a reconnect hasn't finished; or presence_scope is friends and the person is no longer a friend.
People who couldn't be subscribed are reported with presence.subscribeFailed:
im.on('presence.subscribeFailed', ({ usernames, reason }) => {
// reason:denied(不存在或看不到)、limit_exceeded(超出上限)、presence_disabled(应用关闭了在线状态)
console.warn('在线状态订阅失败', reason, usernames);
});Server rate limits (30 subscription calls per user per minute) are handled by the SDK, which waits and retries without emitting an event.
In React, use usePresence:
import { usePresence } from '@deeprespond/im-web-react';
export function OnlineDot({ username }: { username: string }) {
const states = usePresence([username]);
const state = states.get(username);
return <span className={state === undefined ? 'unknown' : state.online ? 'online' : 'offline'} />;
}Query once
When you don't need live updates (for example showing status once on a profile page), use im.presence.query(), with up to 100 users per call. Query results don't update what get returns.
const [state] = await im.presence.query(['lisi']);
if (state && !state.online && state.last_seen_at) render(`最近在线:${new Date(state.last_seen_at).toLocaleString()}`);Going offline is detected with a delay: when a device loses its network abruptly, the server only marks it offline after the connection's idle timeout. See Presence in the server docs.
Your signed-in devices
Devices online now
im.devices.online() returns the user's devices that currently hold a connection (including this one), synchronously. devices.changed is emitted when they change. Use it to show "Signed in on desktop":
im.on('devices.changed', ({ devices }) => {
const others = devices.filter((d) => !d.current);
render(others.length > 0 ? `${others.map((d) => d.platform).join('、')} 已登录` : '');
});When the user signs in on a new device, devices.newLogin is emitted, and you can notify the user:
im.on('devices.newLogin', ({ device_name, platform, created_at }) => {
showToast(`你的账号于 ${new Date(created_at).toLocaleString()} 在 ${device_name || platform} 上登录`);
});All signed-in devices and signing them out
im.devices.sessions() returns all of the user's active sessions (including devices not connected right now), most recently active first, with current marking this device. im.devices.kick() signs out a given device:
const sessions = await im.devices.sessions();
render(sessions.map((s: LoginSession) => `${s.device_name || s.platform}${s.current ? '(本机)' : ''}`));
const target = sessions.find((s) => !s.current);
if (target) await im.devices.kick(target.session_id);The kicked device goes offline at once and has to sign in again. Kicking the current device is the same as signing out. The number of devices signed in at once is limited by the runtime policies, and the oldest device is signed out when the limit is exceeded; see Sign-in and tickets in the server docs.
API reference
im.users.me()
Returns the locally saved profile synchronously.
Returns: SelfProfile | null, null when signed out. me.changed is emitted when it changes.
im.users.refreshMe()
Fetches the user's profile from the server and updates the local copy.
Returns: Promise<SelfProfile>.
im.users.updateMe()
Changes the user's profile, sending only the fields passed.
| Parameter | Type | Required | Description |
|---|---|---|---|
patch.nickname | string | No | Nickname, up to 64 characters. An empty string clears it |
patch.avatar_url | string | No | Avatar URL. An empty string clears it |
patch.attributes | Record<string, string | null> | null | No | Custom attributes, merged key by key: keys set to null are deleted, and null for the whole field clears all. Up to 32 keys and 4 KB in total |
patch.version | number | No | The profile version you read. Nothing is changed if it doesn't match the server |
Returns: Promise<SelfProfile>, the updated profile.
Errors:
code | Description |
|---|---|
local_validation | nickname or avatar_url isn't a string |
invalid_argument | A field is malformed, or the avatar URL isn't allowed (details.reason is url_not_allowed) |
content_rejected | The nickname or avatar failed content moderation |
version_conflict | version doesn't match the server |
im.users.myMute
Read-only: the user's global mute in three scopes, { chat, group, room }, each GlobalMute | null. Only reflects changes received during the current connection; me.muteChanged is emitted when it changes.
im.users.get()
Returns a cached user profile synchronously. If it's missing or expired, fetches it in the background and emits users.changed once fetched.
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Username |
Returns: UserProfile | undefined.
im.users.fetch()
Queries user profiles in a batch, using unexpired cache entries directly.
| Parameter | Type | Required | Description |
|---|---|---|---|
usernames | string[] | Yes | Usernames. More than 100 are queried in batches |
opts.force | boolean | No | When true, the cache isn't used |
Returns: Promise<Map<string, UserProfile>> keyed by lowercase username. Users that don't exist or were deleted are left out.
im.friends.list()
Returns a live list of all friends (LiveList<Friend>), sorted by the first non-empty value of remark, nickname and username. The snapshot's hasMore is always false. Call dispose() when you're done with it.
Throws not_signed_in when signed out.
im.friends.get()
Returns one friend synchronously, or undefined if not a friend. friends.changed is emitted on changes.
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Username |
Returns: Friend | undefined.
im.friends.listInfo
Read-only: { count: number; max_count: number } | null, the friend count and limit. null until the friend list has synced.
im.friends.check()
Checks the user's relationship with other users.
| Parameter | Type | Required | Description |
|---|---|---|---|
usernames | string[] | Yes | 1 to 100 usernames |
Returns: Promise<FriendCheck[]> in request order. The user themself and nonexistent users are left out.
Errors: local_validation (usernames is empty or has more than 100).
im.friends.update()
Changes a friend's remark and custom attributes.
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | The friend's username |
p.remark | string | No | Remark, up to 64 characters. An empty string clears it |
p.attributes | Record<string, string | null> | null | No | Custom attributes, merged key by key; null for the whole field clears all. Up to 16 keys and 1 KB in total |
Pass at least one of remark and attributes.
Returns: Promise<Friend>.
Errors: local_validation (neither was given), not_found (not a friend), invalid_argument (remark or attributes break the rules).
im.friends.remove()
Removes a friend from both sides' friend lists. Succeeds even if not a friend.
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | The friend's username |
Returns: Promise<void>.
im.friends.requests.send()
Sends a friend request. Never retried automatically; see Send a request for the rules.
| Parameter | Type | Required | Description |
|---|---|---|---|
p.username | string | Yes | The other user's username |
p.message | string | No | Message, up to 256 characters |
p.remark | string | No | Remark to set for the other user once you're friends, up to 64 characters |
p.add_source | string | No | Where the request came from, such as search or qrcode |
Returns: Promise<{ status: 'pending' | 'accepted'; friend?: Friend }>.
Errors: friend_add_denied, user_blocked, limit_exceeded, rate_limited, content_rejected, permission_denied, not_found (the user doesn't exist), timeout. See Send a request.
im.friends.requests.received()
Queries received friend requests, newest first.
| Parameter | Type | Required | Description |
|---|---|---|---|
p.status | RequestStatus | No | List only requests with this status: pending, accepted, declined, expired |
p.cursor | string | null | No | Cursor for the next page, from the previous page's next_cursor |
p.limit | number | No | Items per page |
Returns: Promise<Page<FriendRequest>>. Without cursor and status, returns the first page held in SDK memory.
im.friends.requests.sent()
Queries friend requests the user sent. Parameters and return value are the same as im.friends.requests.received().
im.friends.requests.unreadCount
Read-only: the number of unread received requests, up to 100. friendRequests.changed is emitted when it changes.
im.friends.requests.markRead()
Marks received requests as read.
| Parameter | Type | Required | Description |
|---|---|---|---|
p.read_until | string | No | The requested_at of the newest request shown; requests at or before this time are marked as read. If omitted, the unread requests on the first page in SDK memory are used |
Returns: Promise<void>.
im.friends.requests.accept()
Accepts a friend request; the two become friends.
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | The requester's username |
p.remark | string | No | Remark to set for the requester |
Returns: Promise<Friend>, the new friend.
Errors: not_found (the request doesn't exist or has expired), limit_exceeded (friend list full), permission_denied (rejected by the before-add-friend callback).
im.friends.requests.decline()
Declines a friend request. The requester can't send the user another request for 24 hours.
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | The requester's username |
Returns: Promise<void>.
Errors: not_found (the request doesn't exist, was already accepted, or has expired).
im.friends.blacklist.list()
Returns a live list of the blocklist (LiveList<BlacklistEntry>), most recently blocked first. Call dispose() when you're done with it.
im.friends.blacklist.has()
Checks synchronously whether the user has blocked someone. blacklist.changed is emitted on changes.
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Username |
Returns: boolean.
im.friends.blacklist.add()
Blocks a user. Succeeds if already blocked.
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Username |
Returns: Promise<void>.
Errors: permission_denied (the app turned off blocklist enforcement), limit_exceeded (the blocklist already has 1000 people; details.reason is blacklist_limit), not_found (the user doesn't exist), invalid_argument (blocking yourself).
im.friends.blacklist.remove()
Unblocks a user. Succeeds if not blocked.
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Username |
Returns: Promise<void>.
im.friends.settings.get()
Gets the user's friend add mode.
Returns: Promise<{ friend_add_mode: FriendAddMode | null; effective_friend_add_mode: FriendAddMode }>. friend_add_mode is the user's own setting, null if never set; effective_friend_add_mode is the mode actually in effect.
im.friends.settings.set()
Sets the user's friend add mode.
| Parameter | Type | Required | Description |
|---|---|---|---|
mode | FriendAddMode | null | Yes | need_confirm, allow_any or deny_any. null clears the setting and uses the app default |
Returns: Promise<void>.
im.presence.subscribe()
Registers people whose presence to follow. Runs synchronously without waiting for the server.
| Parameter | Type | Required | Description |
|---|---|---|---|
usernames | string[] | Yes | Usernames |
Returns: () => void, a function that cancels this subscription. Safe to call more than once.
Errors (thrown synchronously): not_signed_in, permission_denied (presence_disabled). After the client is destroyed it does nothing, and the returned function does nothing either.
im.presence.get()
Returns the presence of a subscribed person synchronously. presence.changed is emitted on changes.
| Parameter | Type | Required | Description |
|---|---|---|---|
username | string | Yes | Username |
Returns: PresenceState | undefined, undefined when unknown.
im.presence.query()
Queries presence once without subscribing.
| Parameter | Type | Required | Description |
|---|---|---|---|
usernames | string[] | Yes | 1 to 100 usernames |
Returns: Promise<PresenceState[]>. Users that aren't visible are left out.
Errors: local_validation (empty or more than 100), permission_denied (presence_disabled), rate_limited.
im.devices.online()
Returns the user's devices that are online now, synchronously. devices.changed is emitted on changes.
Returns: readonly OnlineDevice[].
im.devices.sessions()
Queries all of the user's active signed-in devices.
Returns: Promise<LoginSession[]>, most recently active first.
im.devices.kick()
Signs out one of the user's devices. Kicking the current device is the same as signing out. Succeeds if the device is already signed out.
| Parameter | Type | Required | Description |
|---|---|---|---|
session_id | string | Yes | The device's session_id |
Returns: Promise<void>.
Errors: local_validation (session_id is empty), not_found (not one of the user's devices).
Related events
| Event | Payload | Description |
|---|---|---|
me.changed | SelfProfile | null | The user's profile changed |
me.muteChanged | { scope, muted, expires_at } | The user's global mute changed; scope is chat, group or room |
users.changed | { usernames: string[] } | Cached profiles of these users changed |
friends.changed | { upserted: string[]; removed: string[] } | The friend list changed |
blacklist.changed | { upserted: string[]; removed: string[] } | The blocklist changed |
friendRequests.changed | { lists: Array<'received' | 'sent'>; unread_count: number } | Friend requests or the unread count changed; the first page in SDK memory has been refreshed |
presence.changed | { items: PresenceState[]; reset: boolean; cleared?: string[] } | Presence changed. When reset is true, all status was cleared; status for people in cleared was cleared |
presence.subscribeFailed | { usernames: string[]; reason } | Subscription failed; reason is denied, limit_exceeded or presence_disabled |
devices.changed | { devices: OnlineDevice[] } | The user's online devices changed |
devices.newLogin | { session_id, device_name, platform, login_method, created_at } | The user signed in on a new device |
Types
SelfProfile
The user's own profile.
| Field | Type | Description |
|---|---|---|
username | string | Username |
nickname | string | Nickname; empty string if none |
avatar_url | string | Avatar URL; empty string if none |
attributes | Record<string, string> | null | Custom attributes; null if none |
has_password | boolean | Whether a password is set |
version | number | Profile version, incremented on every change |
Other fields returned by the server are kept as is.
UserProfile
Another user's public profile.
| Field | Type | Description |
|---|---|---|
username | string | Username |
nickname | string | Nickname; empty string if none |
avatar_url | string | Avatar URL; empty string if none |
attributes | Record<string, string> | null | Custom attributes |
GlobalMute
| Field | Type | Description |
|---|---|---|
expires_at | string | null | When the mute ends; null for a permanent mute |
Friend
A friend, from the user's point of view.
| Field | Type | Description |
|---|---|---|
username | string | The friend's username |
nickname | string | The friend's nickname |
avatar_url | string | The friend's avatar URL |
remark | string | Remark the user set for the friend; empty string if none |
attributes | Record<string, string> | null | Custom attributes the user set for the friend |
add_source | string | Where the friend was added from; empty string if none |
created_at | string | When they became friends |
FriendRequest
A friend request from the user's point of view: in received requests "the other user" is the requester, in sent requests it's the recipient.
| Field | Type | Description |
|---|---|---|
username | string | The other user's username |
nickname | string | The other user's nickname |
avatar_url | string | The other user's avatar URL |
message | string | The message; empty string if none |
add_source | string | Where the request came from |
status | RequestStatus | pending, accepted (now friends), declined or expired |
unread | boolean | Received requests only: whether the user hasn't viewed it yet |
remark | string | Sent requests only: the remark preset for the other user |
requested_at | string | When it was last sent |
expires_at | string | null | When it expires; only set for pending requests |
handled_at | string | null | When it was accepted, declined or the two became friends |
FriendCheck
| Field | Type | Description |
|---|---|---|
username | string | The other user's username |
is_friend | boolean | Whether they're friends |
blocked | boolean | Whether the user has blocked the other user |
request | 'sent' | 'received' | null | An unexpired pending request between them: sent by the user, or received from the other user |
BlacklistEntry
| Field | Type | Description |
|---|---|---|
username | string | The blocked username |
nickname | string | Nickname |
avatar_url | string | Avatar URL |
created_at | string | When they were blocked |
FriendAddMode
'need_confirm' | 'allow_any' | 'deny_any'; see Friend add mode.
PresenceState
| Field | Type | Description |
|---|---|---|
username | string | Username |
online | boolean | Whether online |
platforms | string[] | Platforms online, such as web, ios, windows; empty when offline |
last_seen_at | string | null | When last online. null when online, not seen in 30 days, or the app doesn't allow viewing it |
version | number | Status version |
OnlineDevice
One of the user's devices that's online now.
| Field | Type | Description |
|---|---|---|
session_id | string | The device's session ID, same as in LoginSession |
platform | string | Platform |
connected_at | string | When it connected |
current | boolean | Whether it's this device |
LoginSession
One of the user's active sessions.
| Field | Type | Description |
|---|---|---|
session_id | string | Session ID, used with kick() |
device_id | string | Device ID |
device_name | string | Device name; empty string if none |
platform | string | Platform: ios, android, harmony, windows, macos, linux, web, mini_program or other |
current | boolean | Whether it's this device |
sdk_version | string | Client version |
login_method | string | How the user signed in: ticket or password |
ip | string | IP at sign-in |
created_at | string | When the user signed in |
last_active_at | string | Last sign-in or token refresh |
expires_at | string | null | When the session expires; null means never |
The TypeScript type declares only the first five fields. The others are present too and can be read by index.
