Alerts, push settings and reports
This page covers three things: deciding whether to alert the user when a new message arrives, and which tab does it; reading and changing the user's push settings and conversation mutes with im.push; and reporting messages, users, groups and chat rooms with im.reports.
| You want to | See |
|---|---|
| Show a browser notification or play a sound for new messages | Alerts for new messages |
| Alert only once across several tabs | Which tab alerts |
| Do not disturb, quiet hours, what notifications show | Push settings |
| Mute one chat or group | Conversation mutes |
| Report a message, user, group or chat room | Reports |
Alerts for new messages
The Web SDK never shows browser notifications or plays sounds by itself; you decide when you receive the message.received event. Its payload already includes two decisions:
alert: whether this tab is the one responsible for alerts (see Which tab alerts).notify: whether this message should alert given the user's push settings and conversation mutes, plus the notification title and body (NotifyDecision).
import type { NotifyDecision } from '@deeprespond/im-web';
im.on('message.received', ({ message, notify, alert }) => {
const decision = notify as NotifyDecision | null;
if (!alert || !decision?.notify) return;
if (document.visibilityState === 'visible') {
showToast(`${decision.title}:${decision.body}`); // 页面可见时用页面内的提示
return;
}
if (Notification.permission === 'granted') {
const n = new Notification(decision.title || '新消息', { body: decision.body, tag: message.conversation_id });
n.onclick = () => {
window.focus();
// 打开这个会话
};
}
});
// 在用户的操作中(如点击“开启通知”)请求权限
async function enableNotification(): Promise<void> {
await Notification.requestPermission();
}message.receivedis only emitted for messages from others received in real time over the connection. Messages from while offline are filled in by sync after reconnecting and don't emit this event, so the user doesn't get a burst of notifications on coming online.notifyis declared asunknown; use it asNotifyDecision | null.- The conversation the user is looking at usually doesn't need a notification. The SDK's decision doesn't take this into account, so check it yourself (for example skip when that conversation's message list is open and the page is visible).
- Once the page is closed, no alerts arrive at all: the Web SDK has no Web Push, and new messages are only known through the connection.
Which tab alerts
When the user has several pages open in one browser, every tab receives message.received. If they all showed notifications, the user would get duplicates and overlapping sounds. The SDK picks one "alert tab":
- If any tab is visible, the most recently focused one.
- If none is visible (for example the browser is minimized), the tab that holds the connection for that browser.
Only alert in the tab whose event payload has alert: true. im.connection.isAlertTab says whether this tab is currently the alert tab, and tabs.roleChanged is emitted when it changes, which is useful for showing status. For notifications, rely on alert in the event payload, which is the value at the moment the event was emitted. Incoming calls work the same way; see call.incoming in Audio and video calls.
The alert decision
notify is the result of im.push.shouldNotify(message). You can also call it yourself for any message, for example to alert later from a list:
const decision = im.push.shouldNotify(message);
if (decision.notify) {
console.log(decision.title, decision.body);
} else {
console.log('不提醒', decision.reason);
}When it shouldn't alert, reason says why:
reason | Description |
|---|---|
self | The user's own message |
tip | A group notice. Being removed, group dissolution and so on are known from group events; see Groups |
call | A call record. Incoming calls are alerted separately through call.incoming |
excluded | Sent with exclude_from_unread |
not_loaded | Push settings haven't synced yet (right after sign-in); decide for yourself whether to alert |
dnd | The user turned on do not disturb |
quiet_hours | It's currently within quiet hours |
muted | This conversation is muted |
not_mentioned | The group is set to "alert only when mentioned", and this message doesn't mention the user or everyone |
When it should alert, title and body follow the user's preview mode (effective_preview) and match the push the user would get on their phone. The SDK uses its built-in Chinese templates:
| Preview mode | One-to-one | Group |
|---|---|---|
full | Title is the sender; body is the message summary | Title is the group name; body is "sender: summary", prefixed with "[有人@我]" (someone mentioned you) when the user is mentioned |
sender_only | Title is the sender; body is "发来一条新消息" (sent a new message) | Title is the group name; body is "sender 发来一条新消息" |
none | Title is empty; body is "你收到了一条新消息" (you received a new message) | Same |
The sender is shown by nickname (username if there's no nickname); friend remarks and group nicknames aren't used. Message summaries follow the same rules as server pushes: the first 100 characters of text, "[图片]" for images, "[文件] filename" for files, and "[消息]" for custom messages.
This decision approximates the server's offline push rules, with these differences:
push.disabledandpush.forceset when sending aren't delivered with the message, so they're treated as unset.- Mentioning everyone is treated as breaking through "alert only when mentioned" (the server's default).
- Title and body only use the built-in Chinese templates, not the notification templates you override in the console.
Push settings
A user's push settings are do not disturb, quiet hours and preview mode. They decide the offline pushes the user gets in mobile apps, and they also drive the web alert decision above. Settings are stored on the server and synced across all of the user's devices: change them on the web and phone pushes follow. Web pages themselves get no offline pushes; see Offline push.
im.push.settings() reads the locally synced settings synchronously, and push.changed is emitted when they change:
function renderSettings(): void {
const s = im.push.settings();
if (!s) return; // 还没有同步到
render({
dnd: s.dnd ? (s.dnd.until ? `免打扰至 ${s.dnd.until}` : '一直免打扰') : '关闭',
quietHours: s.quiet_hours ? `${s.quiet_hours.start}~${s.quiet_hours.end}` : '关闭',
preview: s.effective_preview, // full / sender_only / none
});
}
im.on('push.changed', ({ settings }) => {
if (settings) renderSettings();
});
renderSettings();When updating, pass only what you're changing:
// 8 小时内免打扰
await im.push.updateSettings({ dnd: { duration_seconds: 8 * 3600 } });
// 一直免打扰;关闭免打扰
await im.push.updateSettings({ dnd: { duration_seconds: null } });
await im.push.updateSettings({ dnd: null });
// 每天 22:00 到次日 07:30 免打扰,按上海时间
await im.push.updateSettings({ quiet_hours: { start: '22:00', end: '07:30', timezone: 'Asia/Shanghai' } });
await im.push.updateSettings({ quiet_hours: null });
// 通知只显示发送者;恢复为应用的默认方式
await im.push.updateSettings({ preview: 'sender_only' });
await im.push.updateSettings({ preview: null });- Do not disturb. No pushes for a period (1 second to 10 years) or indefinitely. When it ends, the SDK clears it locally and emits
push.changed. - Quiet hours. A daily time range. If
endis earlier thanstart, the range crosses midnight; the start time is inside the range and the end time isn't. Times areHH:MM(24-hour). The time zone is an IANA name; get the browser's withIntl.DateTimeFormat().resolvedOptions().timeZone. The time zone doesn't follow the device, so users who change time zones need to set it again. - Preview mode.
fullshows the sender and content,sender_onlyonly the sender, andnoneneither.previewis the user's own choice, andnullmeans the app default;effective_previewis the mode actually in effect.
Conversation mutes
Mute a one-to-one chat or a group:
// 单聊:不提醒
await im.push.mute({ conversation_type: 'single', target: 'lisi' }, { mode: 'none' });
// 群:只在有人 @ 我时提醒,24 小时后自动恢复
await im.push.mute({ conversation_type: 'group', target: groupId }, { mode: 'mention_only', duration_seconds: 86400 });
// 取消
await im.push.unmute({ conversation_type: 'group', target: groupId });
// 读取(同步)
const mute = im.push.getMute({ conversation_type: 'group', target: groupId });
console.log(mute?.mode, mute?.until);targetis the other user's username or the group ID.modeisnone(no alerts) ormention_only(alert only when the user or everyone is mentioned; groups only).- One-to-one chats only allow
none, and you can't mute yourself. The SDK rejects both locally withlocal_validation(reasoninvalid_modeorself_conversation). - Without
duration_secondsthe mute lasts indefinitely. With one (1 second to 10 years), it ends automatically; the SDK updates the conversation list and emitspush.changedwhen it expires locally. - Once set, the conversation's
mutedfield in the conversation list changes (see Conversations), andim.conversations.getUnreadTotal({ excludeMuted: true })stops counting it. - Mutes only affect pushes and alerts, not message delivery or unread counts. Leaving or being removed from a group, or the group being dissolved, deletes that group's mute. Each user can have up to 10000 conversation mutes.
The push.changed payload is { settings, mutes }: settings: true means the push settings changed, and mutes lists conversations whose mute changed ({ conversation_type, target }). Changes made on other devices sync in real time too.
Reports
Users can report a message, a user, a group, a chat room or a chat room message. Reports go into the moderation records in your console for reviewers to handle; see Content moderation.
import type { ReportReason } from '@deeprespond/im-web';
if (im.config?.report_enabled !== false) {
// 举报一条消息
const { duplicate } = await im.reports.submit({ target_type: 'message', message_id: '99584445836689408', reason: 'fraud' });
showToast(duplicate ? '你已举报过' : '已举报,我们会尽快处理');
}
// 举报一个用户,附上他发来的消息作为证据(最多 5 条)
const reason: ReportReason = 'abuse';
await im.reports.submit({
target_type: 'user',
username: 'lisi',
reason,
description: '多次发送辱骂信息',
evidence_message_ids: ['99584445836689408', '99584445836689409'],
});
// 本人 90 天内的举报
const page = await im.reports.mine({ limit: 20 });
for (const r of page.items) console.log(r.target_type, r.reason, r.status === 'pending' ? '处理中' : '已处理');- Entry point. When
report_enabledin the runtime configuration isfalse, the app has turned off client reports; hide the report option. Calls then reject withpermission_denied(report_disabled). - Reasons. Choose from 10 fixed reasons; see ReportReason. The description is up to 500 characters.
- Evidence. When reporting a user or group, attach up to 5 message
message_ids. Evidence for a user report must be sent by that user, and for a group report must be from that group; recalled messages aren't allowed. The SDK checks against locally stored messages and rejects withlocal_validation(reasoninvalid_evidence) if they don't qualify. In your UI, only let users select qualifying messages. - Only what the user can see. Group messages from before joining or after leaving, and messages the user deleted or that were recalled, can't be reported; the server returns
not_found. Chat room messages can only be reported within 24 hours; if one can't be read, report the chat room or the sender instead. Users can't report themselves (local_validation,reasonself_report). - Duplicate reports. One user reporting the same target counts once. Reporting again returns the original report with
duplicate: true; still tell the user it was reported. - Rate. 5 per user per minute and 30 per day. Beyond that, calls reject with
rate_limitedand aren't retried. - Outcome. Reporters only see "pending" (
pending) or "handled" (closed), not the decision.
API reference
Methods that return a Promise reject with a DRError on failure: not_signed_in when signed out, and network_error or timeout for network errors. For error codes, see Events and errors and the server error codes.
im.push.settings()
Reads the local push settings synchronously. If do not disturb has expired, dnd is null. push.changed (with settings: true) is emitted when they change.
Returns: PushSettings | null, null until synced. See PushSettings.
im.push.updateSettings()
Changes push settings, only the items given.
| Parameter | Type | Required | Description |
|---|---|---|---|
patch.dnd | { duration_seconds: number | null } | null | No | Do not disturb: duration_seconds is 1 to 315360000 seconds, null for indefinitely; dnd: null turns it off |
patch.quiet_hours | { start: string; end: string; timezone: string } | null | No | Quiet hours: start and end are HH:MM, timezone is an IANA name; null turns it off |
patch.preview | PushPreview | null | No | Preview mode: 'full', 'sender_only' or 'none'; null uses the app default |
Give at least one.
Returns: Promise<PushSettings>, the full updated settings.
Errors: local_validation (reason is required: nothing given or time zone missing; out_of_range: duration out of range; invalid_format: bad time format; invalid_value: bad preview mode), invalid_argument (invalid_quiet_hours: start equals end; invalid_timezone: not an IANA time zone name).
im.push.mute()
Mutes a conversation.
| Parameter | Type | Required | Description |
|---|---|---|---|
target.conversation_type | 'single' | 'group' | Yes | One-to-one or group |
target.target | string | Yes | The other user's username or the group ID |
p.mode | 'none' | 'mention_only' | Yes | none: no alerts. mention_only: alert only when the user is mentioned; groups only |
p.duration_seconds | number | No | Duration, 1 to 315360000 seconds. Indefinite if omitted |
Returns: Promise<void>.
Errors: local_validation (reason is invalid_mode: mention_only on a one-to-one chat; self_conversation: muting yourself; out_of_range: duration out of range; required, invalid_value: missing or bad parameters), limit_exceeded (10000 conversation mutes reached).
im.push.unmute()
Unmutes a conversation.
| Parameter | Type | Required | Description |
|---|---|---|---|
target | MuteTarget | Yes | See MuteTarget |
Returns: Promise<void>.
Errors: local_validation (missing or bad parameters).
im.push.getMute()
Reads one conversation's mute synchronously. push.changed (with this conversation in mutes) is emitted when it changes.
| Parameter | Type | Required | Description |
|---|---|---|---|
target | MuteTarget | Yes | See MuteTarget |
Returns: MuteInfo | null, null if not set or expired. MuteInfo is { mode, until }; see Conversations.
im.push.shouldNotify()
Decides, from the local push settings and conversation mutes, whether a new message should alert, and gives the notification title and body. Runs synchronously.
| Parameter | Type | Required | Description |
|---|---|---|---|
message | MessageView | Yes | The message; see Messages |
Returns: NotifyDecision; see NotifyDecision.
im.reports.submit()
Submits a report. Never retried automatically.
| Parameter | Type | Required | Description |
|---|---|---|---|
p | ReportInput | Yes | The target, reason and description; see ReportInput |
Returns: Promise<{ report: Report; duplicate: boolean }>. duplicate: true means a repeat report, and report is the original one.
Errors: local_validation (reason is invalid_value: bad target type or reason; required: target ID missing; too_long: description over 500 characters; control_characters: control characters in the description; self_report: reporting yourself; invalid_evidence: evidence doesn't qualify; too_many_items: more than 5 pieces of evidence), permission_denied (report_disabled), not_found (the target doesn't exist or isn't visible to the user), invalid_argument (invalid_evidence), rate_limited.
im.reports.mine()
Queries the user's reports from the last 90 days, page by page.
| Parameter | Type | Required | Description |
|---|---|---|---|
page.cursor | string | null | No | next_cursor from the previous page |
page.limit | number | No | Items per page |
Returns: Promise<Page<Report>>, i.e. { items: Report[]; next_cursor: string | null }; next_cursor is null when there's no next page.
Types
NotifyDecision
| Field | Type | Description |
|---|---|---|
notify | boolean | Whether to alert |
title | string | Optional; given when alerting. The notification title; empty when the preview mode is none |
body | string | Optional; given when alerting. The notification body |
reason | string | Optional; given when not alerting: self, tip, call, excluded, not_loaded, dnd, quiet_hours, muted, not_mentioned |
PushSettings
Same as the server's push settings.
| Field | Type | Description |
|---|---|---|
dnd | { until: string | null } | null | Do not disturb; null if off. until is when it ends, null for indefinitely |
quiet_hours | { start: string; end: string; timezone: string } | null | Quiet hours; null if not set |
preview | PushPreview | null | The user's chosen preview mode; null if not chosen |
effective_preview | PushPreview | The preview mode in effect |
settings_version | number | Settings version, used by the SDK for sync |
PushPreview
'full' | 'sender_only' | 'none': show sender and content, show only the sender, or show neither.
MuteTarget
| Field | Type | Description |
|---|---|---|
conversation_type | 'single' | 'group' | One-to-one or group |
target | string | The other user's username or the group ID |
ReportInput
Five forms by target_type, all with reason (required) and description (optional, up to 500 characters, line breaks allowed):
target_type | Other fields | Description |
|---|---|---|
message | message_id | Report a one-to-one or group message |
user | username; evidence_message_ids? | Report a user, optionally with up to 5 messages they sent |
group | group_id; evidence_message_ids? | Report a group, optionally with up to 5 messages from it |
chatroom | room_id | Report a chat room |
chatroom_message | room_id; message_id | Report a chat room message |
ReportReason
| Value | Meaning |
|---|---|
ad | Ads or harassment |
fraud | Fraud |
porn | Sexual or vulgar content |
politics | Political content |
terrorism | Violence or terrorism |
abuse | Abuse or attacks |
illegal | Illegal or prohibited content |
infringement | Infringement |
minor | Involves minors |
other | Other |
Report
| Field | Type | Description |
|---|---|---|
report_id | string | Report ID |
target_type | string | Target type, as in ReportInput |
username | string | null | The reported user (for user reports) |
message_id | string | null | The reported message |
group_id | string | null | The reported group |
room_id | string | null | The reported chat room |
reason | ReportReason | Reason |
status | 'pending' | 'closed' | Pending or handled |
created_at | string | When it was reported |
