# Add Channel Subscribers
Source: https://wukong.mintlify.app/en/api/channel/add-subscribers
POST /channel/subscriber_add
Add subscribers (members) to a channel
## Overview
Add subscribers (members) to a channel, supporting batch addition and reset mode.
## Request Body
### Required Parameters
Channel ID
Channel type
* `1` - Personal channel
* `2` - Group channel
List of subscriber user IDs to add
User ID
### Optional Parameters
Whether to reset existing subscribers
* `0` - Do not reset, append new subscribers
* `1` - Reset, replace all existing subscribers
Whether as temporary subscriber
* `0` - Permanent subscriber
* `1` - Temporary subscriber
```bash cURL theme={null}
curl -X POST "http://localhost:5001/channel/subscriber_add" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "group123",
"channel_type": 2,
"subscribers": ["user4", "user5", "user6"],
"reset": 0,
"temp_subscriber": 0
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/channel/subscriber_add', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: 'group123',
channel_type: 2,
subscribers: ['user4', 'user5', 'user6'],
reset: 0,
temp_subscriber: 0
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"channel_id": "group123",
"channel_type": 2,
"subscribers": ["user4", "user5", "user6"],
"reset": 0,
"temp_subscriber": 0
}
response = requests.post('http://localhost:5001/channel/subscriber_add', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"channel_id": "group123",
"channel_type": 2,
"subscribers": []string{"user4", "user5", "user6"},
"reset": 0,
"temp_subscriber": 0,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/channel/subscriber_add",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | ------------------------------ |
| 200 | Subscribers added successfully |
| 400 | Request parameter error |
| 403 | No operation permission |
| 404 | Channel does not exist |
| 500 | Internal server error |
## Parameter Description
### Reset Mode (reset)
| Value | Mode | Description | Use Case |
| ----- | ----------- | ---------------------------------------------- | -------------------------- |
| 0 | Append mode | Add new members to existing members | Invite new members to join |
| 1 | Reset mode | Clear existing members, set as new member list | Rebuild group |
### Temporary Subscriber (temp\_subscriber)
| Value | Type | Features | Applicable Scenario |
| ----- | -------------------- | ------------------------------ | ---------------------------------------- |
| 0 | Permanent subscriber | Persistent member relationship | Official group members |
| 1 | Temporary subscriber | Temporary member relationship | Temporary visitors, meeting participants |
## Use Cases
### Group Management
* **Invite new members**: Group owner or admin invites new users to join the group
* **Batch import**: Batch import member lists from other platforms
* **Rebuild group**: Use reset mode to rebuild group members
# Add Channel Blacklist
Source: https://wukong.mintlify.app/en/api/channel/blacklist
POST /channel/blacklist_add
Add users to channel blacklist
## Overview
Add users to channel blacklist. Users added to the blacklist will be unable to join the channel or send messages.
## Request Body
### Required Parameters
Channel ID
Channel type
* `1` - Personal channel
* `2` - Group channel
List of user IDs to add to blacklist
User ID
```bash cURL theme={null}
curl -X POST "http://localhost:5001/channel/blacklist_add" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["user456", "user789"]
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/channel/blacklist_add', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: 'group123',
channel_type: 2,
uids: ['user456', 'user789']
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"channel_id": "group123",
"channel_type": 2,
"uids": ["user456", "user789"]
}
response = requests.post('http://localhost:5001/channel/blacklist_add', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"channel_id": "group123",
"channel_type": 2,
"uids": []string{"user456", "user789"},
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/channel/blacklist_add",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | ------------------------------ |
| 200 | Blacklist operation successful |
| 400 | Request parameter error |
| 403 | No management permission |
| 404 | Channel does not exist |
| 500 | Internal server error |
## Blacklist Mechanism
### Restriction Scope
Users added to the blacklist will face the following restrictions:
| Operation | Restriction Effect | Description |
| ------------- | ------------------ | ------------------------------------------ |
| Send Messages | Prohibited | Cannot send any messages in the channel |
| Join Channel | Blocked | Cannot join or rejoin the channel |
| View Messages | Limited | May have limited access to channel content |
### Permission Hierarchy
Blacklist has higher priority than other permission settings:
1. **Blacklist** > Whitelist > Regular member permissions
2. **Administrator permissions** > Blacklist (Administrators are not restricted by blacklist)
3. **System users** > Blacklist (System users are not restricted by blacklist)
## Use Cases
### Violation Handling
**Handle Content Violations**:
```javascript theme={null}
// Add users to blacklist for content violations
async function handleContentViolation(channelId, channelType, violatingUsers, reason) {
try {
// Add to blacklist
await addToBlacklist({
channel_id: channelId,
channel_type: channelType,
uids: violatingUsers
});
// Log violation
await logViolation({
channel_id: channelId,
channel_type: channelType,
violating_users: violatingUsers,
reason: reason,
action: 'blacklisted',
timestamp: new Date().toISOString()
});
// Notify users about blacklisting
for (const userId of violatingUsers) {
await notifyUser(userId, {
type: 'blacklisted',
channel_id: channelId,
reason: reason,
appeal_process: 'Contact administrators to appeal'
});
}
console.log(`Added ${violatingUsers.length} users to blacklist for: ${reason}`);
} catch (error) {
console.error('Failed to handle content violation:', error);
}
}
// Usage
await handleContentViolation(
'group123',
2,
['user456', 'user789'],
'Inappropriate content sharing'
);
```
### Spam and Abuse Prevention
**Anti-Spam Management**:
```javascript theme={null}
// Automated spam detection and blacklisting
class SpamDetector {
constructor(channelId, channelType) {
this.channelId = channelId;
this.channelType = channelType;
this.messageHistory = new Map();
this.spamThresholds = {
messagesPerMinute: 10,
duplicateMessages: 3,
linkSpamCount: 5
};
}
async analyzeMessage(userId, messageContent) {
const now = Date.now();
const userHistory = this.messageHistory.get(userId) || {
messages: [],
duplicates: new Map(),
linkCount: 0
};
// Add current message
userHistory.messages.push({
content: messageContent,
timestamp: now
});
// Clean old messages (older than 1 minute)
userHistory.messages = userHistory.messages.filter(
msg => now - msg.timestamp < 60000
);
// Check for spam patterns
const spamDetected = this.detectSpamPatterns(userHistory, messageContent);
if (spamDetected.isSpam) {
await this.handleSpamUser(userId, spamDetected.reasons);
}
this.messageHistory.set(userId, userHistory);
}
detectSpamPatterns(userHistory, messageContent) {
const reasons = [];
// Check message frequency
if (userHistory.messages.length > this.spamThresholds.messagesPerMinute) {
reasons.push('High message frequency');
}
// Check for duplicate messages
const duplicateCount = userHistory.messages.filter(
msg => msg.content === messageContent
).length;
if (duplicateCount > this.spamThresholds.duplicateMessages) {
reasons.push('Duplicate message spam');
}
// Check for link spam
const linkCount = (messageContent.match(/https?:\/\/\S+/g) || []).length;
if (linkCount > 2) {
userHistory.linkCount += linkCount;
if (userHistory.linkCount > this.spamThresholds.linkSpamCount) {
reasons.push('Link spam');
}
}
return {
isSpam: reasons.length > 0,
reasons: reasons
};
}
async handleSpamUser(userId, reasons) {
try {
// Add to blacklist
await addToBlacklist({
channel_id: this.channelId,
channel_type: this.channelType,
uids: [userId]
});
// Log spam incident
await logSpamIncident({
channel_id: this.channelId,
user_id: userId,
reasons: reasons,
action: 'auto_blacklisted',
timestamp: new Date().toISOString()
});
console.log(`Auto-blacklisted user ${userId} for spam: ${reasons.join(', ')}`);
} catch (error) {
console.error(`Failed to blacklist spam user ${userId}:`, error);
}
}
}
// Usage
const spamDetector = new SpamDetector('group123', 2);
// Analyze incoming messages
await spamDetector.analyzeMessage('user456', 'Buy now! Click here: http://spam.com');
```
### Moderation Tools
**Advanced Moderation System**:
```javascript theme={null}
// Comprehensive moderation system
class ChannelModerator {
constructor(channelId, channelType) {
this.channelId = channelId;
this.channelType = channelType;
this.moderationRules = {
profanityFilter: true,
linkRestriction: true,
capsLockLimit: 0.7, // 70% caps lock threshold
mentionSpamLimit: 5
};
}
async moderateUser(userId, violations) {
const severity = this.calculateViolationSeverity(violations);
switch (severity) {
case 'low':
await this.issueWarning(userId, violations);
break;
case 'medium':
await this.temporaryRestriction(userId, violations);
break;
case 'high':
await this.addToBlacklist(userId, violations);
break;
}
}
calculateViolationSeverity(violations) {
const severityScores = {
'profanity': 2,
'spam': 3,
'harassment': 4,
'inappropriate_content': 3,
'link_spam': 2,
'caps_abuse': 1
};
const totalScore = violations.reduce((sum, violation) =>
sum + (severityScores[violation.type] || 1), 0
);
if (totalScore >= 6) return 'high';
if (totalScore >= 3) return 'medium';
return 'low';
}
async addToBlacklist(userId, violations) {
try {
await addToBlacklist({
channel_id: this.channelId,
channel_type: this.channelType,
uids: [userId]
});
// Create moderation record
await createModerationRecord({
channel_id: this.channelId,
user_id: userId,
action: 'blacklisted',
violations: violations,
moderator: 'system',
timestamp: new Date().toISOString(),
appeal_deadline: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString()
});
// Notify user
await notifyUser(userId, {
type: 'blacklisted',
channel_id: this.channelId,
violations: violations,
appeal_process: 'You can appeal this decision within 7 days'
});
console.log(`User ${userId} blacklisted for violations:`, violations);
} catch (error) {
console.error(`Failed to blacklist user ${userId}:`, error);
}
}
async issueWarning(userId, violations) {
await createModerationRecord({
channel_id: this.channelId,
user_id: userId,
action: 'warning',
violations: violations,
moderator: 'system',
timestamp: new Date().toISOString()
});
await notifyUser(userId, {
type: 'warning',
channel_id: this.channelId,
violations: violations,
message: 'Please follow channel guidelines'
});
}
async temporaryRestriction(userId, violations) {
// Implement temporary restriction logic
// This might involve a separate temporary blacklist system
console.log(`Temporary restriction for user ${userId}:`, violations);
}
}
```
### Batch Blacklist Management
**Bulk Blacklist Operations**:
```javascript theme={null}
// Manage blacklists in bulk
async function bulkBlacklistManagement(operations) {
const results = [];
for (const operation of operations) {
try {
switch (operation.action) {
case 'add':
await addToBlacklist({
channel_id: operation.channelId,
channel_type: operation.channelType,
uids: operation.userIds
});
break;
case 'remove':
await removeFromBlacklist({
channel_id: operation.channelId,
channel_type: operation.channelType,
uids: operation.userIds
});
break;
}
results.push({
operation: operation,
success: true
});
} catch (error) {
results.push({
operation: operation,
success: false,
error: error.message
});
}
}
return results;
}
// Usage
const operations = [
{
action: 'add',
channelId: 'group123',
channelType: 2,
userIds: ['spammer1', 'spammer2'],
reason: 'Spam detection'
},
{
action: 'remove',
channelId: 'group456',
channelType: 2,
userIds: ['reformed_user'],
reason: 'Appeal approved'
}
];
const results = await bulkBlacklistManagement(operations);
console.log('Bulk operation results:', results);
```
## Best Practices
1. **Clear Policies**: Establish clear guidelines for blacklisting users
2. **Documentation**: Document all blacklist actions with reasons
3. **Appeal Process**: Provide a clear appeal process for blacklisted users
4. **Graduated Response**: Use warnings before blacklisting when appropriate
5. **Regular Review**: Periodically review blacklists and remove outdated entries
6. **Notification**: Inform users when they are blacklisted and why
7. **Audit Trail**: Maintain detailed logs of all blacklist operations
# Remove Channel Blacklist
Source: https://wukong.mintlify.app/en/api/channel/blacklist-remove
POST /channel/blacklist_remove
Remove specific users from channel blacklist
## Overview
Remove specific users from channel blacklist. Removed users will regain normal permissions and can send and receive channel messages again. Related conversations will be restored if applicable.
## Request Body
### Required Parameters
Channel ID, cannot be empty or contain special characters
Channel type
* `1` - Person channel
* `2` - Group channel
List of user IDs to remove from blacklist
User ID
```bash cURL theme={null}
curl -X POST "http://localhost:5001/channel/blacklist_remove" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["user1", "user2"]
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/channel/blacklist_remove', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: 'group123',
channel_type: 2,
uids: ['user1', 'user2']
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"channel_id": "group123",
"channel_type": 2,
"uids": ["user1", "user2"]
}
response = requests.post('http://localhost:5001/channel/blacklist_remove', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"channel_id": "group123",
"channel_type": 2,
"uids": []string{"user1", "user2"},
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/channel/blacklist_remove",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | ----------------------------------------- |
| 200 | Users removed from blacklist successfully |
| 400 | Request parameter error |
| 403 | No management permission |
| 404 | Channel does not exist |
| 500 | Internal server error |
## Functionality
### Remove Operation
The remove operation performs the following steps:
1. **Verify Users**: Check if specified users are in the blacklist
2. **Remove Entries**: Delete specified users from channel blacklist
3. **Restore Permissions**: Users regain ability to send and receive messages
4. **Restore Conversations**: Restore user's related conversations if applicable
### Permission Restoration
Removed users will regain:
| Permission | Description | Effective Time |
| ------------------- | -------------------------------------------------- | -------------- |
| Send Messages | Can send messages to the channel | Immediately |
| Receive Messages | Can receive channel messages | Immediately |
| Conversation Access | Restore channel conversations (if applicable) | Immediately |
| Normal Interaction | Restore all normal channel interaction permissions | Immediately |
## Special Cases
### Live Channels
* **No Conversation Restoration**: Live channels do not automatically restore conversations
* **Permissions Take Effect Immediately**: Users can still immediately regain send and receive permissions
### Person Channels
* **Blacklist Not Supported**: Person channels do not support blacklist operations
* **Returns Error**: Attempting operations will return corresponding error messages
## Use Cases
### Lift Mistaken Ban
```bash theme={null}
# Remove users mistakenly added to blacklist
curl -X POST "/channel/blacklist_remove" -d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["innocent_user"]
}'
```
### Batch Restoration
```bash theme={null}
# Batch remove multiple users
curl -X POST "/channel/blacklist_remove" -d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["user1", "user2", "user3"]
}'
```
### Temporary Unban
```bash theme={null}
# Temporarily lift user restrictions
curl -X POST "/channel/blacklist_remove" -d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["temp_banned_user"]
}'
```
## Best Practices
1. **Permission Verification**: Ensure operator has sufficient management permissions
2. **Operation Recording**: Record all blacklist change operations
3. **Notification Mechanism**: Notify relevant administrators and users
4. **Progressive Unbanning**: For serious violators, consider progressive permission restoration
5. **Monitoring Mechanism**: Continue monitoring user behavior after removal
6. **Backup Strategy**: Backup current blacklist state before bulk operations
## Error Handling
### Common Errors
| Error Message | Cause | Solution |
| -------------------------- | ------------------------------- | ----------------------------------- |
| Channel ID cannot be empty | No channel ID provided | Ensure valid channel ID is provided |
| Channel type cannot be 0 | Invalid channel type | Use valid channel type (1 or 2) |
| uids cannot be empty | No user list provided | Provide list of user IDs to remove |
| Remove blacklist failed | Remove operation failed | Check if users are in blacklist |
| Add conversation failed | Conversation restoration failed | Check conversation system status |
## Related APIs
* [Add Channel Blacklist](/en/api/channel/blacklist) - Add users to blacklist
* [Set Channel Blacklist](/en/api/channel/blacklist-set) - Set complete blacklist
* [Add Channel Whitelist](/en/api/channel/whitelist) - Manage whitelist users
* [Remove Channel Whitelist](/en/api/channel/whitelist-remove) - Remove whitelist users
# Set Channel Blacklist
Source: https://wukong.mintlify.app/en/api/channel/blacklist-set
POST /channel/blacklist_set
Set (replace) the entire blacklist for a channel
## Overview
Set (replace) the entire blacklist for a channel. This operation will first remove all existing blacklist entries and then add new ones. If UIDs is empty, the entire blacklist will be cleared.
## Request Body
### Required Parameters
Channel ID, cannot be empty or contain special characters
Channel type
* `1` - Person channel
* `2` - Group channel
### Optional Parameters
List of user IDs to set as blacklist. If empty, clears the entire blacklist.
User ID
```bash Set Blacklist theme={null}
curl -X POST "http://localhost:5001/channel/blacklist_set" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["user1", "user2", "user3"]
}'
```
```bash Clear Blacklist theme={null}
curl -X POST "http://localhost:5001/channel/blacklist_set" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "group123",
"channel_type": 2,
"uids": []
}'
```
```javascript JavaScript theme={null}
// Set blacklist
const response = await fetch('http://localhost:5001/channel/blacklist_set', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: 'group123',
channel_type: 2,
uids: ['user1', 'user2', 'user3']
})
});
const data = await response.json();
console.log(data);
// Clear blacklist
const clearResponse = await fetch('http://localhost:5001/channel/blacklist_set', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: 'group123',
channel_type: 2,
uids: []
})
});
```
```python Python theme={null}
import requests
# Set blacklist
data = {
"channel_id": "group123",
"channel_type": 2,
"uids": ["user1", "user2", "user3"]
}
response = requests.post('http://localhost:5001/channel/blacklist_set', json=data)
result = response.json()
print(result)
# Clear blacklist
clear_data = {
"channel_id": "group123",
"channel_type": 2,
"uids": []
}
clear_response = requests.post('http://localhost:5001/channel/blacklist_set', json=clear_data)
clear_result = clear_response.json()
print(clear_result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
// Set blacklist
data := map[string]interface{}{
"channel_id": "group123",
"channel_type": 2,
"uids": []string{"user1", "user2", "user3"},
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/channel/blacklist_set",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
// Clear blacklist
clearData := map[string]interface{}{
"channel_id": "group123",
"channel_type": 2,
"uids": []string{},
}
clearJsonData, _ := json.Marshal(clearData)
clearResp, err := http.Post(
"http://localhost:5001/channel/blacklist_set",
"application/json",
bytes.NewBuffer(clearJsonData),
)
if err != nil {
panic(err)
}
defer clearResp.Body.Close()
var clearResult map[string]interface{}
json.NewDecoder(clearResp.Body).Decode(&clearResult)
fmt.Printf("%+v\n", clearResult)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | -------------------------- |
| 200 | Blacklist set successfully |
| 400 | Request parameter error |
| 403 | No management permission |
| 404 | Channel does not exist |
| 500 | Internal server error |
## Functionality
### Set Operation
The set operation performs the following steps:
1. **Clear Existing Blacklist**: Remove all existing blacklist entries for the channel
2. **Add New Entries**: Add the provided user ID list to the blacklist
3. **Delete Conversations**: Automatically delete related conversations for blacklisted users
4. **Update Permissions**: Take effect immediately, blacklisted users cannot send or receive messages
### Clear Operation
When `uids` is an empty array:
* Clear all blacklist entries for the channel
* Restore normal permissions for all users
* Does not affect existing conversations
## Blacklist Mechanism
### Permission Restrictions
Blacklisted users are subject to the following restrictions:
| Restriction | Description | Scope |
| ----------------------- | ------------------------------------------------ | -------------------- |
| Cannot Send Messages | Blocked from sending any messages to the channel | All message types |
| Cannot Receive Messages | Will not receive messages from the channel | All channel messages |
| Conversation Deletion | Related conversations are automatically deleted | Except live channels |
| Permission Revocation | Lose all channel-related permissions | Complete isolation |
### Special Cases
* **Live Channels**: Do not process conversation deletion
* **Person Channels**: Do not support blacklist operations
* **System Users**: May have special permissions, not affected by blacklist
## Use Cases
### Batch Management
```bash theme={null}
# Set new blacklist, replacing all existing entries
curl -X POST "/channel/blacklist_set" -d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["spammer1", "spammer2", "violator1"]
}'
```
### Clear Blacklist
```bash theme={null}
# Clear all blacklist, restore all user permissions
curl -X POST "/channel/blacklist_set" -d '{
"channel_id": "group123",
"channel_type": 2,
"uids": []
}'
```
### Reset Blacklist
```bash theme={null}
# Completely reset blacklist to new user list
curl -X POST "/channel/blacklist_set" -d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["new_blocked_user"]
}'
```
## Comparison with Other Blacklist Operations
| Operation | Function | Use Case |
| ------------------ | ------------------------------------ | ----------------------------------- |
| `blacklist_add` | Add users to existing blacklist | Incrementally add violating users |
| `blacklist_set` | Replace entire blacklist | Batch management, reset blacklist |
| `blacklist_remove` | Remove specific users from blacklist | Lift restrictions on specific users |
## Best Practices
1. **Use Carefully**: Set operation clears all existing blacklist, ensure this is intended behavior
2. **Backup Existing Data**: Get current blacklist before setting as backup
3. **Permission Verification**: Ensure operator has sufficient management permissions
4. **Log Recording**: Record blacklist changes for auditing
5. **Notification Mechanism**: Consider notifying relevant administrators of blacklist changes
6. **Test Validation**: Thoroughly test before using in production environment
## Error Handling
### Common Errors
| Error Message | Cause | Solution |
| -------------------------------------------- | ------------------------- | ------------------------------------------- |
| Channel ID cannot be empty | No channel ID provided | Ensure valid channel ID is provided |
| Channel type cannot be 0 | Invalid channel type | Use valid channel type (1 or 2) |
| Channel ID cannot contain special characters | Invalid channel ID format | Use alphanumeric characters and underscores |
| Remove all blacklist failed | Clear operation failed | Check channel status and permissions |
| Add blacklist failed | Add operation failed | Check user ID validity |
## Related APIs
* [Add Channel Blacklist](/en/api/channel/blacklist) - Incrementally add blacklist users
* [Remove Channel Blacklist](/en/api/channel/blacklist-remove) - Remove specific blacklist users
* [Add Channel Whitelist](/en/api/channel/whitelist) - Manage whitelist users
# Create Channel
Source: https://wukong.mintlify.app/en/api/channel/create
POST /channel
Create new chat channels
## Overview
Create new chat channels, supporting both personal and group channel creation.
## Request Body
### Required Parameters
Channel ID, must be unique
Channel type
* `1` - Personal channel
* `2` - Group channel
### Optional Parameters
Whether to mute
* `0` - Allow speaking
* `1` - Mute all members
Whether to disband channel
* `1` - Disband channel (irreversible)
Whether to prohibit sending messages (0=not prohibited, 1=prohibited). When prohibited, all members in the channel cannot send messages. Personal channels can receive messages but cannot send messages.
Whether to allow strangers to send messages (0=not allowed, 1=allowed) (this configuration currently only supports personal channels)
Personal channel: If AllowStranger is 1, strangers can send messages to the current user. For example: if the current account needs to accept stranger messages, channel\_id is the current user's uid
Subscriber list
Subscriber user ID
```bash cURL theme={null}
curl -X POST "http://localhost:5001/channel" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "group123",
"channel_type": 2,
"large": 0,
"ban": 0,
"subscribers": ["user1", "user2", "user3"]
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/channel', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: 'group123',
channel_type: 2,
large: 0,
ban: 0,
subscribers: ['user1', 'user2', 'user3']
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"channel_id": "group123",
"channel_type": 2,
"large": 0,
"ban": 0,
"subscribers": ["user1", "user2", "user3"]
}
response = requests.post('http://localhost:5001/channel', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"channel_id": "group123",
"channel_type": 2,
"large": 0,
"ban": 0,
"subscribers": []string{"user1", "user2", "user3"},
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/channel",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | ---------------------------- |
| 200 | Channel created successfully |
| 400 | Request parameter error |
| 409 | Channel ID already exists |
| 500 | Internal server error |
## Best Practices
1. **Channel ID Uniqueness**: Ensure channel ID is unique in the system
2. **Member Management**: Properly set initial subscriber list
3. **Permission Control**: Set mute status as needed
# Delete Channel
Source: https://wukong.mintlify.app/en/api/channel/delete
POST /channel/delete
Delete a specified channel
## Overview
Delete a specified channel, including all related data and member relationships.
## Request Body
### Required Parameters
Channel ID
Channel type
* `1` - Personal channel
* `2` - Group channel
```bash cURL theme={null}
curl -X POST "http://localhost:5001/channel/delete" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "group123",
"channel_type": 2
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/channel/delete', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: 'group123',
channel_type: 2
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"channel_id": "group123",
"channel_type": 2
}
response = requests.post('http://localhost:5001/channel/delete', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"channel_id": "group123",
"channel_type": 2,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/channel/delete",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | ---------------------------- |
| 200 | Channel deleted successfully |
| 400 | Request parameter error |
| 403 | No deletion permission |
| 404 | Channel does not exist |
| 500 | Internal server error |
## Deletion Impact
### Data Cleanup
Deleting a channel will clean up the following related data:
| Data Type | Cleanup Scope | Impact |
| -------------------- | ------------------------------------- | --------------------------------------- |
| Channel Info | Channel basic info, settings | Channel completely disappears |
| Member Relationships | All member subscription relationships | Members can no longer receive messages |
| Message History | All messages in the channel | Message records permanently deleted |
| Conversation Records | Channel in user conversation lists | Channel removed from conversation lists |
### User Impact
* **Member Notification**: All members will receive channel dissolution notification
* **Conversation Cleanup**: Channel removed from all members' conversation lists
* **Message Loss**: Message history in the channel will be unrecoverable
* **Permission Invalidation**: All channel-related permissions immediately invalidated
## Permission Requirements
### Personal Channel (channel\_type = 1)
* **Participants**: Any participant in the channel can delete it
* **System Administrator**: Has permission to delete any personal channel
### Group Channel (channel\_type = 2)
* **Group Owner**: Only the group owner can dissolve the group
* **System Administrator**: Has permission to delete any group
* **Regular Members**: No deletion permission, can only leave the group
## Security Considerations
### Deletion Confirmation
Recommend secondary confirmation before deletion:
1. **Permission Verification**: Confirm the operator has deletion permission
2. **Identity Verification**: Require password or verification code input
3. **Impact Warning**: Clearly inform the scope of deletion impact
4. **Final Confirmation**: Provide a final chance to cancel
### Data Backup
Consider data backup before deletion:
* **Message Export**: Allow export of important messages
* **Member List**: Save member information for rebuilding
* **File Backup**: Backup important files in the channel
* **Operation Logging**: Record detailed information about deletion operations
## Alternative Solutions
### Soft Delete
For important channels, consider soft deletion:
* **Hide Channel**: Hide from user interface but retain data
* **Disable Features**: Prohibit sending messages but retain history
* **Set Expiration**: Set automatic deletion time
* **Permission Revocation**: Remove all member permissions
### Archive Processing
* **Message Archive**: Transfer messages to archive storage
* **Read-only Mode**: Set to read-only status
* **History Viewing**: Allow viewing but no operations
* **Periodic Cleanup**: Regularly clean up archived data
## Best Practices
1. **Permission Control**: Strictly control deletion permissions to prevent misoperations
2. **Operation Logging**: Detailed logging of deletion operations including time, operator, reason
3. **Notification Mechanism**: Timely notification to all related members
4. **Data Backup**: Perform data backup before deleting important channels
5. **Recovery Mechanism**: Provide recovery functionality within a certain time period
6. **Batch Deletion**: Support batch deletion of multiple channels
# Get Channel Whitelist
Source: https://wukong.mintlify.app/en/api/channel/get-whitelist
GET /channel/whitelist
Get the whitelist user list for a channel
## Overview
Get the whitelist user list for a specified channel, returning all user IDs in the whitelist.
## Query Parameters
Channel ID
Channel type
* `1` - Personal channel
* `2` - Group channel
```bash cURL theme={null}
curl -X GET "http://localhost:5001/channel/whitelist?channel_id=group123&channel_type=2"
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/channel/whitelist?channel_id=group123&channel_type=2');
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
params = {
'channel_id': 'group123',
'channel_type': 2
}
response = requests.get('http://localhost:5001/channel/whitelist', params=params)
data = response.json()
print(data)
```
```go Go theme={null}
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
)
func main() {
baseURL := "http://localhost:5001/channel/whitelist"
params := url.Values{}
params.Add("channel_id", "group123")
params.Add("channel_type", "2")
fullURL := baseURL + "?" + params.Encode()
resp, err := http.Get(fullURL)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result []string
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
[
"user456",
"user789",
"user101"
]
```
```json Empty Whitelist theme={null}
[]
```
## Response Fields
List of whitelisted user IDs
User ID
## Status Codes
| Status Code | Description |
| ----------- | -------------------------------- |
| 200 | Successfully retrieved whitelist |
| 400 | Request parameter error |
| 403 | No view permission |
| 404 | Channel does not exist |
| 500 | Internal server error |
## Use Cases
### Whitelist Management Dashboard
**Display Whitelist Members**:
```javascript theme={null}
// Create a whitelist management dashboard
class WhitelistDashboard {
constructor(channelId, channelType) {
this.channelId = channelId;
this.channelType = channelType;
this.whitelistMembers = [];
}
async loadWhitelist() {
try {
const response = await fetch(
`/channel/whitelist?channel_id=${this.channelId}&channel_type=${this.channelType}`
);
this.whitelistMembers = await response.json();
await this.enrichMemberData();
this.renderDashboard();
} catch (error) {
console.error('Failed to load whitelist:', error);
this.showError('Failed to load whitelist members');
}
}
async enrichMemberData() {
// Get additional user information for each whitelisted member
const enrichedMembers = [];
for (const userId of this.whitelistMembers) {
try {
const userInfo = await getUserInfo(userId);
enrichedMembers.push({
userId: userId,
username: userInfo.username || userId,
avatar: userInfo.avatar,
joinedWhitelistAt: userInfo.whitelistJoinDate,
privileges: await this.getUserPrivileges(userId)
});
} catch (error) {
// Fallback for users we can't get info for
enrichedMembers.push({
userId: userId,
username: userId,
avatar: null,
joinedWhitelistAt: null,
privileges: ['basic_whitelist']
});
}
}
this.enrichedMembers = enrichedMembers;
}
async getUserPrivileges(userId) {
// Get specific privileges for this user
const privileges = [];
// Check if user has moderator privileges
const isModerator = await checkModeratorStatus(this.channelId, userId);
if (isModerator) {
privileges.push('moderator');
}
// Check for VIP status
const isVIP = await checkVIPStatus(this.channelId, userId);
if (isVIP) {
privileges.push('vip');
}
// Default whitelist privileges
privileges.push('bypass_mute', 'priority_access');
return privileges;
}
renderDashboard() {
const dashboardHTML = `
${this.renderMemberList()}
`;
document.getElementById('whitelist-dashboard').innerHTML = dashboardHTML;
}
renderMemberList() {
return this.enrichedMembers.map(member => `
${member.username}
${member.userId}
${member.privileges.join(', ')}
`).join('');
}
async addMember() {
const userId = prompt('Enter user ID to add to whitelist:');
if (userId) {
try {
await addToWhitelist({
channel_id: this.channelId,
channel_type: this.channelType,
uids: [userId]
});
await this.loadWhitelist(); // Refresh the list
this.showSuccess(`User ${userId} added to whitelist`);
} catch (error) {
this.showError(`Failed to add user ${userId} to whitelist`);
}
}
}
async removeMember(userId) {
if (confirm(`Remove ${userId} from whitelist?`)) {
try {
await removeFromWhitelist({
channel_id: this.channelId,
channel_type: this.channelType,
uids: [userId]
});
await this.loadWhitelist(); // Refresh the list
this.showSuccess(`User ${userId} removed from whitelist`);
} catch (error) {
this.showError(`Failed to remove user ${userId} from whitelist`);
}
}
}
async refresh() {
await this.loadWhitelist();
this.showSuccess('Whitelist refreshed');
}
exportWhitelist() {
const exportData = {
channel_id: this.channelId,
channel_type: this.channelType,
whitelist_members: this.enrichedMembers,
exported_at: new Date().toISOString(),
total_members: this.enrichedMembers.length
};
const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `whitelist-${this.channelId}-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
}
showSuccess(message) {
console.log(`✓ ${message}`);
// Implement UI notification
}
showError(message) {
console.error(`✗ ${message}`);
// Implement UI error notification
}
}
// Usage
const dashboard = new WhitelistDashboard('group123', 2);
dashboard.loadWhitelist();
```
### Permission Checking System
**Check User Whitelist Status**:
```javascript theme={null}
// Comprehensive permission checking system
class PermissionChecker {
constructor() {
this.whitelistCache = new Map();
this.cacheTimeout = 5 * 60 * 1000; // 5 minutes
}
async isUserWhitelisted(channelId, channelType, userId) {
try {
const cacheKey = `${channelId}:${channelType}`;
const cached = this.whitelistCache.get(cacheKey);
// Check cache first
if (cached && Date.now() - cached.timestamp < this.cacheTimeout) {
return cached.whitelist.includes(userId);
}
// Fetch fresh whitelist
const whitelist = await this.getChannelWhitelist(channelId, channelType);
// Update cache
this.whitelistCache.set(cacheKey, {
whitelist: whitelist,
timestamp: Date.now()
});
return whitelist.includes(userId);
} catch (error) {
console.error('Failed to check whitelist status:', error);
return false;
}
}
async getChannelWhitelist(channelId, channelType) {
const response = await fetch(
`/channel/whitelist?channel_id=${channelId}&channel_type=${channelType}`
);
return await response.json();
}
async checkUserPermissions(channelId, channelType, userId) {
const [isWhitelisted, isBlacklisted, isModerator] = await Promise.all([
this.isUserWhitelisted(channelId, channelType, userId),
this.isUserBlacklisted(channelId, channelType, userId),
this.isUserModerator(channelId, userId)
]);
return {
userId: userId,
isWhitelisted: isWhitelisted,
isBlacklisted: isBlacklisted,
isModerator: isModerator,
effectivePermissions: this.calculateEffectivePermissions({
isWhitelisted,
isBlacklisted,
isModerator
})
};
}
calculateEffectivePermissions({ isWhitelisted, isBlacklisted, isModerator }) {
if (isBlacklisted && !isModerator) {
return {
canSendMessage: false,
canJoinChannel: false,
canBypassMute: false,
level: 'restricted'
};
}
if (isWhitelisted || isModerator) {
return {
canSendMessage: true,
canJoinChannel: true,
canBypassMute: true,
priorityAccess: true,
level: isModerator ? 'moderator' : 'privileged'
};
}
return {
canSendMessage: true,
canJoinChannel: true,
canBypassMute: false,
level: 'regular'
};
}
async isUserBlacklisted(channelId, channelType, userId) {
// Implementation for blacklist checking
try {
const blacklist = await getChannelBlacklist(channelId, channelType);
return blacklist.includes(userId);
} catch (error) {
return false;
}
}
async isUserModerator(channelId, userId) {
// Implementation for moderator checking
try {
const moderators = await getChannelModerators(channelId);
return moderators.includes(userId);
} catch (error) {
return false;
}
}
clearCache() {
this.whitelistCache.clear();
}
clearChannelCache(channelId, channelType) {
const cacheKey = `${channelId}:${channelType}`;
this.whitelistCache.delete(cacheKey);
}
}
// Usage
const permissionChecker = new PermissionChecker();
// Check if user is whitelisted
const isWhitelisted = await permissionChecker.isUserWhitelisted('group123', 2, 'user456');
console.log('User whitelisted:', isWhitelisted);
// Get comprehensive permissions
const permissions = await permissionChecker.checkUserPermissions('group123', 2, 'user456');
console.log('User permissions:', permissions);
```
### Whitelist Analytics
**Analyze Whitelist Patterns**:
```javascript theme={null}
// Whitelist analytics and reporting
class WhitelistAnalytics {
constructor() {
this.analyticsData = new Map();
}
async analyzeChannelWhitelist(channelId, channelType) {
try {
const whitelist = await this.getChannelWhitelist(channelId, channelType);
const analysis = {
channelId: channelId,
channelType: channelType,
totalMembers: whitelist.length,
memberDetails: await this.analyzeMemberDetails(whitelist),
activityAnalysis: await this.analyzeActivity(channelId, whitelist),
privilegeDistribution: await this.analyzePrivileges(channelId, whitelist),
trends: await this.analyzeTrends(channelId, channelType),
recommendations: []
};
// Generate recommendations
analysis.recommendations = this.generateRecommendations(analysis);
this.analyticsData.set(`${channelId}:${channelType}`, analysis);
return analysis;
} catch (error) {
console.error('Failed to analyze whitelist:', error);
return null;
}
}
async getChannelWhitelist(channelId, channelType) {
const response = await fetch(
`/channel/whitelist?channel_id=${channelId}&channel_type=${channelType}`
);
return await response.json();
}
async analyzeMemberDetails(whitelist) {
const memberDetails = {
activeMembers: 0,
inactiveMembers: 0,
newMembers: 0, // Added in last 30 days
longTermMembers: 0, // More than 6 months
memberTypes: {
moderators: 0,
vips: 0,
regular: 0
}
};
for (const userId of whitelist) {
const userInfo = await getUserInfo(userId);
const lastActivity = await getLastActivity(userId);
// Activity analysis
const daysSinceActivity = (Date.now() - lastActivity) / (1000 * 60 * 60 * 24);
if (daysSinceActivity <= 7) {
memberDetails.activeMembers++;
} else {
memberDetails.inactiveMembers++;
}
// Membership duration
const membershipDuration = Date.now() - userInfo.whitelistJoinDate;
const daysMember = membershipDuration / (1000 * 60 * 60 * 24);
if (daysMember <= 30) {
memberDetails.newMembers++;
} else if (daysMember >= 180) {
memberDetails.longTermMembers++;
}
// Member type
if (userInfo.isModerator) {
memberDetails.memberTypes.moderators++;
} else if (userInfo.isVIP) {
memberDetails.memberTypes.vips++;
} else {
memberDetails.memberTypes.regular++;
}
}
return memberDetails;
}
async analyzeActivity(channelId, whitelist) {
const activityData = {
messagesSent: 0,
averageMessagesPerMember: 0,
mostActiveMembers: [],
leastActiveMembers: []
};
const memberActivity = [];
for (const userId of whitelist) {
const messageCount = await getUserMessageCount(channelId, userId, 30); // Last 30 days
memberActivity.push({ userId, messageCount });
activityData.messagesSent += messageCount;
}
activityData.averageMessagesPerMember = activityData.messagesSent / whitelist.length;
// Sort by activity
memberActivity.sort((a, b) => b.messageCount - a.messageCount);
activityData.mostActiveMembers = memberActivity.slice(0, 5);
activityData.leastActiveMembers = memberActivity.slice(-5);
return activityData;
}
async analyzePrivileges(channelId, whitelist) {
const privilegeData = {
bypassMute: 0,
priorityAccess: 0,
rateExempt: 0,
customPrivileges: {}
};
for (const userId of whitelist) {
const privileges = await getUserPrivileges(channelId, userId);
if (privileges.includes('bypass_mute')) privilegeData.bypassMute++;
if (privileges.includes('priority_access')) privilegeData.priorityAccess++;
if (privileges.includes('rate_exempt')) privilegeData.rateExempt++;
// Count custom privileges
privileges.forEach(privilege => {
if (!['bypass_mute', 'priority_access', 'rate_exempt'].includes(privilege)) {
privilegeData.customPrivileges[privilege] =
(privilegeData.customPrivileges[privilege] || 0) + 1;
}
});
}
return privilegeData;
}
async analyzeTrends(channelId, channelType) {
// Analyze whitelist growth/shrinkage trends
const trends = {
growthRate: 0,
additionRate: 0,
removalRate: 0,
seasonality: {}
};
// Implementation would analyze historical data
// This is a simplified version
return trends;
}
generateRecommendations(analysis) {
const recommendations = [];
// Inactive member recommendation
if (analysis.memberDetails.inactiveMembers > analysis.memberDetails.activeMembers) {
recommendations.push({
type: 'cleanup',
priority: 'medium',
message: 'Consider reviewing inactive whitelist members for removal'
});
}
// Privilege distribution recommendation
if (analysis.privilegeDistribution.bypassMute === analysis.totalMembers) {
recommendations.push({
type: 'privilege_review',
priority: 'low',
message: 'All whitelist members have bypass_mute privilege - consider if this is necessary'
});
}
// Growth recommendation
if (analysis.memberDetails.newMembers > analysis.totalMembers * 0.5) {
recommendations.push({
type: 'monitoring',
priority: 'high',
message: 'High rate of new whitelist additions - monitor for abuse'
});
}
return recommendations;
}
generateReport(channelId, channelType) {
const analysis = this.analyticsData.get(`${channelId}:${channelType}`);
if (!analysis) return null;
return {
summary: {
channel: `${channelId} (Type: ${channelType})`,
totalMembers: analysis.totalMembers,
activeMembers: analysis.memberDetails.activeMembers,
recommendations: analysis.recommendations.length
},
details: analysis,
generatedAt: new Date().toISOString()
};
}
}
// Usage
const analytics = new WhitelistAnalytics();
// Analyze channel whitelist
const analysis = await analytics.analyzeChannelWhitelist('group123', 2);
console.log('Whitelist analysis:', analysis);
// Generate report
const report = analytics.generateReport('group123', 2);
console.log('Analytics report:', report);
```
## Best Practices
1. **Regular Monitoring**: Regularly check whitelist membership for accuracy
2. **Access Control**: Ensure only authorized users can view whitelist information
3. **Caching**: Cache whitelist data to improve performance for frequent checks
4. **Analytics**: Use whitelist data for channel management insights
5. **Documentation**: Document the purpose and criteria for whitelist membership
6. **Privacy**: Respect user privacy when displaying whitelist information
7. **Performance**: Optimize whitelist queries for channels with large member counts
# Update Channel Info
Source: https://wukong.mintlify.app/en/api/channel/info
POST /channel/info
Update or add channel basic information
## Overview
Update or add basic information for channels, including channel type, large group identifier, mute status, etc.
## Request Body
### Required Parameters
Channel ID
Channel type
* `1` - Personal channel
* `2` - Group channel
### Optional Parameters
Whether to mute
* `0` - Allow speaking
* `1` - Mute all members
Whether to disband channel
* `1` - Disband channel (irreversible)
Whether to prohibit sending messages (0=not prohibited, 1=prohibited). When prohibited, all members in the channel cannot send messages. Personal channels can receive messages but cannot send messages.
Whether to allow strangers to send messages (0=not allowed, 1=allowed) (this configuration currently only supports personal channels)
Personal channel: If AllowStranger is 1, strangers can send messages to the current user. For example: if the current account needs to accept stranger messages, channel\_id is the current user's uid
```bash cURL theme={null}
curl -X POST "http://localhost:5001/channel/info" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "group123",
"channel_type": 2,
"large": 1,
"ban": 0
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/channel/info', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: 'group123',
channel_type: 2,
large: 1,
ban: 0
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"channel_id": "group123",
"channel_type": 2,
"large": 1,
"ban": 0
}
response = requests.post('http://localhost:5001/channel/info', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"channel_id": "group123",
"channel_type": 2,
"large": 1,
"ban": 0,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/channel/info",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | --------------------------------- |
| 200 | Channel info updated successfully |
| 400 | Request parameter error |
| 404 | Channel does not exist |
| 500 | Internal server error |
## Parameter Details
### Channel Type (channel\_type)
| Value | Type | Description | Features |
| ----- | ---------------- | ----------------------- | ---------------------------------------------------- |
| 1 | Personal channel | One-on-one private chat | Only two members, no group features |
| 2 | Group channel | Multi-user group chat | Supports multiple members, group management features |
### Mute Status (ban)
| Value | Description | Scope | Management Permission |
| ----- | -------------- | -------------------------------------- | --------------------------------- |
| 0 | Allow speaking | All members can send messages normally | Group owner and admins can modify |
| 1 | Mute all | Only admins can send messages | Only group owner can modify |
# Remove Channel Subscribers
Source: https://wukong.mintlify.app/en/api/channel/remove-subscribers
POST /channel/subscriber_remove
Remove subscribers (members) from a channel
## Overview
Remove subscribers (members) from a channel, supporting batch removal operations.
## Request Body
### Required Parameters
Channel ID
Channel type
* `1` - Personal channel
* `2` - Group channel
List of subscriber user IDs to remove
User ID
### Optional Parameters
Whether as temporary subscriber
* `0` - Permanent subscriber
* `1` - Temporary subscriber
```bash cURL theme={null}
curl -X POST "http://localhost:5001/channel/subscriber_remove" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "group123",
"channel_type": 2,
"subscribers": ["user4", "user5"],
"temp_subscriber": 0
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/channel/subscriber_remove', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: 'group123',
channel_type: 2,
subscribers: ['user4', 'user5'],
temp_subscriber: 0
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"channel_id": "group123",
"channel_type": 2,
"subscribers": ["user4", "user5"],
"temp_subscriber": 0
}
response = requests.post('http://localhost:5001/channel/subscriber_remove', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"channel_id": "group123",
"channel_type": 2,
"subscribers": []string{"user4", "user5"},
"temp_subscriber": 0,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/channel/subscriber_remove",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | --------------------------------------------- |
| 200 | Subscribers removed successfully |
| 400 | Request parameter error |
| 403 | No operation permission |
| 404 | Channel does not exist or user not in channel |
| 500 | Internal server error |
## Parameter Description
### Temporary Subscriber (temp\_subscriber)
| Value | Type | Description | Impact |
| ----- | -------------------- | ----------------------- | ------------------------------------- |
| 0 | Permanent subscriber | Remove official member | Completely remove member relationship |
| 1 | Temporary subscriber | Remove temporary member | Remove temporary access permission |
## Use Cases
### Group Management
* **Kick members**: Group owner or admin removes violating members
* **Member leaves**: User voluntarily leaves the group
* **Batch cleanup**: Clean up inactive or invalid members
# Set Temporary Channel Subscribers
Source: https://wukong.mintlify.app/en/api/channel/tmp-subscriber-set
POST /tmpchannel/subscriber_set
Set subscribers for a temporary channel (Internal API)
This interface is primarily used for internal system calls and cluster node communication. External direct use is not recommended.
## Overview
Set subscribers for a temporary channel. This is an internal API used for cluster node communication to manage temporary channel subscriptions. Temporary channels are used for special scenarios such as temporary groups or sessions, and automatically create and manage channel tags.
## Request Body
### Required Parameters
Temporary channel ID, cannot contain special characters
List of user IDs to set as subscribers for the temporary channel
User ID
```bash cURL theme={null}
curl -X POST "http://localhost:5001/tmpchannel/subscriber_set" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "tmp_channel_123",
"uids": ["user1", "user2", "user3"]
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/tmpchannel/subscriber_set', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: 'tmp_channel_123',
uids: ['user1', 'user2', 'user3']
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"channel_id": "tmp_channel_123",
"uids": ["user1", "user2", "user3"]
}
response = requests.post('http://localhost:5001/tmpchannel/subscriber_set', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"channel_id": "tmp_channel_123",
"uids": []string{"user1", "user2", "user3"},
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/tmpchannel/subscriber_set",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | ---------------------------------------------- |
| 200 | Temporary channel subscribers set successfully |
| 400 | Request parameter error |
| 500 | Internal server error |
## Temporary Channel Mechanism
### Temporary Channel Features
* **Temporary Nature**: Used for special scenarios like temporary groups or sessions
* **Auto Management**: System automatically creates and manages channel tags
* **Cluster Communication**: Primarily used for internal communication between cluster nodes
* **Subscription Management**: Supports dynamic setting of subscriber lists
### Use Cases
| Scenario | Description | Applicable Situation |
| ------------------ | ---------------------------------------- | -------------------------------- |
| Temporary Groups | Create temporary discussion groups | Short-term project collaboration |
| Session Migration | Migrate sessions between cluster nodes | Load balancing |
| System Maintenance | Temporarily reorganize channel structure | System upgrades |
## Important Notes
**Internal API Warning**
This interface is designed for internal system calls. Direct use may cause:
* Data inconsistency
* Cluster state anomalies
* Unexpected system behavior
It is recommended to use standard channel management interfaces for regular operations.
### Parameter Restrictions
* **Channel ID**: Cannot be empty, cannot contain special characters
* **User List**: Must contain at least one user ID
* **Character Length**: Channel ID maximum 64 characters
### Error Handling
| Error Message | Cause | Solution |
| -------------------------------------------- | --------------------------- | ------------------------------------------- |
| channel\_id cannot be empty | No channel ID provided | Ensure valid channel ID is provided |
| uids cannot be empty | User list is empty | Provide at least one user ID |
| Channel ID cannot contain special characters | Invalid channel ID format | Use alphanumeric characters and underscores |
| Failed to get channel's cluster node | Cluster communication error | Check cluster status |
| Failed to create tag | Tag system error | Check tag service status |
## Best Practices
1. **Internal Use Only**: Avoid calling directly in client applications
2. **Parameter Validation**: Ensure all parameters are correctly formatted
3. **Error Handling**: Implement comprehensive error handling mechanisms
4. **Monitor Logs**: Record API calls for troubleshooting
5. **Cluster Status**: Ensure cluster status is normal before calling
## Related APIs
* [Create Channel](/en/api/channel/create) - Create standard channels
* [Add Channel Subscribers](/en/api/channel/add-subscribers) - Add standard channel subscribers
* [Remove Channel Subscribers](/en/api/channel/remove-subscribers) - Remove standard channel subscribers
# Add Channel Whitelist
Source: https://wukong.mintlify.app/en/api/channel/whitelist
POST /channel/whitelist_add
Add users to channel whitelist
## Overview
Add users to channel whitelist. Whitelisted users have special privileges and can bypass certain restrictions.
## Request Body
### Required Parameters
Channel ID
Channel type
* `1` - Personal channel
* `2` - Group channel
List of user IDs to add to whitelist
User ID
```bash cURL theme={null}
curl -X POST "http://localhost:5001/channel/whitelist_add" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["user456", "user789"]
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/channel/whitelist_add', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: 'group123',
channel_type: 2,
uids: ['user456', 'user789']
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"channel_id": "group123",
"channel_type": 2,
"uids": ["user456", "user789"]
}
response = requests.post('http://localhost:5001/channel/whitelist_add', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"channel_id": "group123",
"channel_type": 2,
"uids": []string{"user456", "user789"},
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/channel/whitelist_add",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | ------------------------------ |
| 200 | Whitelist operation successful |
| 400 | Request parameter error |
| 403 | No management permission |
| 404 | Channel does not exist |
| 500 | Internal server error |
## Whitelist Mechanism
### Special Privileges
Whitelisted users enjoy the following special privileges:
| Privilege | Description | Use Cases |
| -------------------- | ----------------------------------- | ------------------------------- |
| Bypass Mute | Can speak during channel-wide mute | Administrators, important users |
| Priority Access | Priority access during high traffic | VIP users, moderators |
| Rate Limit Exemption | Exempt from standard rate limits | Trusted users, bots |
### Permission Hierarchy
Whitelist position in the permission system:
1. **Administrator permissions** > Whitelist > Regular member permissions
2. **Blacklist** > Whitelist (Blacklist has higher priority)
3. **System users** > Whitelist
## Use Cases
### VIP User Management
**Grant VIP Privileges**:
```javascript theme={null}
// Add VIP users to whitelist for special privileges
async function grantVIPPrivileges(channelId, channelType, vipUsers) {
try {
await addToWhitelist({
channel_id: channelId,
channel_type: channelType,
uids: vipUsers
});
// Log VIP privilege grant
await logPrivilegeGrant({
channel_id: channelId,
channel_type: channelType,
users: vipUsers,
privilege_type: 'vip_whitelist',
granted_by: 'system',
timestamp: new Date().toISOString()
});
// Notify users about VIP status
for (const userId of vipUsers) {
await notifyUser(userId, {
type: 'vip_granted',
channel_id: channelId,
privileges: ['bypass_mute', 'priority_access'],
message: 'You have been granted VIP privileges in this channel'
});
}
console.log(`Granted VIP privileges to ${vipUsers.length} users`);
} catch (error) {
console.error('Failed to grant VIP privileges:', error);
}
}
// Usage
await grantVIPPrivileges('group123', 2, ['vip_user1', 'vip_user2']);
```
### Moderator Management
**Moderator Whitelist System**:
```javascript theme={null}
// Comprehensive moderator management system
class ModeratorManager {
constructor(channelId, channelType) {
this.channelId = channelId;
this.channelType = channelType;
this.moderatorLevels = {
senior: ['bypass_mute', 'priority_access', 'rate_limit_exempt'],
junior: ['bypass_mute', 'priority_access'],
trainee: ['bypass_mute']
};
}
async promoteModerator(userId, level = 'junior') {
try {
// Add to whitelist
await addToWhitelist({
channel_id: this.channelId,
channel_type: this.channelType,
uids: [userId]
});
// Record moderator status
await this.recordModeratorStatus(userId, level);
// Grant additional permissions based on level
await this.grantModeratorPermissions(userId, level);
// Notify about promotion
await this.notifyModeratorPromotion(userId, level);
console.log(`User ${userId} promoted to ${level} moderator`);
} catch (error) {
console.error(`Failed to promote moderator ${userId}:`, error);
}
}
async recordModeratorStatus(userId, level) {
await createModeratorRecord({
channel_id: this.channelId,
user_id: userId,
level: level,
privileges: this.moderatorLevels[level],
promoted_at: new Date().toISOString(),
status: 'active'
});
}
async grantModeratorPermissions(userId, level) {
const permissions = this.moderatorLevels[level];
for (const permission of permissions) {
await grantChannelPermission(this.channelId, userId, permission);
}
}
async notifyModeratorPromotion(userId, level) {
await notifyUser(userId, {
type: 'moderator_promotion',
channel_id: this.channelId,
level: level,
privileges: this.moderatorLevels[level],
responsibilities: this.getModeratorResponsibilities(level)
});
}
getModeratorResponsibilities(level) {
const responsibilities = {
senior: ['Manage junior moderators', 'Handle appeals', 'Policy enforcement'],
junior: ['Monitor chat', 'Issue warnings', 'Report violations'],
trainee: ['Observe and learn', 'Assist with basic moderation']
};
return responsibilities[level] || [];
}
async demoteModerator(userId) {
try {
// Remove from whitelist
await removeFromWhitelist({
channel_id: this.channelId,
channel_type: this.channelType,
uids: [userId]
});
// Update moderator record
await updateModeratorRecord(this.channelId, userId, {
status: 'inactive',
demoted_at: new Date().toISOString()
});
// Revoke permissions
await this.revokeModeratorPermissions(userId);
console.log(`User ${userId} demoted from moderator`);
} catch (error) {
console.error(`Failed to demote moderator ${userId}:`, error);
}
}
async revokeModeratorPermissions(userId) {
const allPermissions = Object.values(this.moderatorLevels).flat();
const uniquePermissions = [...new Set(allPermissions)];
for (const permission of uniquePermissions) {
await revokeChannelPermission(this.channelId, userId, permission);
}
}
}
// Usage
const moderatorManager = new ModeratorManager('group123', 2);
// Promote user to moderator
await moderatorManager.promoteModerator('user456', 'junior');
// Promote to senior moderator
await moderatorManager.promoteModerator('user789', 'senior');
```
### Event-Based Whitelist Management
**Dynamic Whitelist for Events**:
```javascript theme={null}
// Dynamic whitelist management for special events
class EventWhitelistManager {
constructor() {
this.activeEvents = new Map();
}
async createEventWhitelist(eventId, channelId, channelType, eventConfig) {
try {
const event = {
id: eventId,
channelId: channelId,
channelType: channelType,
config: eventConfig,
participants: [],
startTime: new Date(eventConfig.startTime),
endTime: new Date(eventConfig.endTime),
status: 'scheduled'
};
this.activeEvents.set(eventId, event);
// Schedule automatic whitelist management
await this.scheduleEventWhitelist(event);
console.log(`Event whitelist created for event ${eventId}`);
} catch (error) {
console.error(`Failed to create event whitelist for ${eventId}:`, error);
}
}
async scheduleEventWhitelist(event) {
const now = Date.now();
const startDelay = event.startTime.getTime() - now;
const endDelay = event.endTime.getTime() - now;
if (startDelay > 0) {
// Schedule event start
setTimeout(() => {
this.startEventWhitelist(event.id);
}, startDelay);
} else if (endDelay > 0) {
// Event already started, activate immediately
await this.startEventWhitelist(event.id);
}
if (endDelay > 0) {
// Schedule event end
setTimeout(() => {
this.endEventWhitelist(event.id);
}, endDelay);
}
}
async startEventWhitelist(eventId) {
const event = this.activeEvents.get(eventId);
if (!event) return;
try {
// Add event participants to whitelist
if (event.participants.length > 0) {
await addToWhitelist({
channel_id: event.channelId,
channel_type: event.channelType,
uids: event.participants
});
}
event.status = 'active';
// Notify participants
for (const participantId of event.participants) {
await notifyUser(participantId, {
type: 'event_started',
event_id: eventId,
channel_id: event.channelId,
message: 'Event has started, you now have special privileges'
});
}
console.log(`Event whitelist activated for event ${eventId}`);
} catch (error) {
console.error(`Failed to start event whitelist for ${eventId}:`, error);
}
}
async endEventWhitelist(eventId) {
const event = this.activeEvents.get(eventId);
if (!event) return;
try {
// Remove event participants from whitelist
if (event.participants.length > 0) {
await removeFromWhitelist({
channel_id: event.channelId,
channel_type: event.channelType,
uids: event.participants
});
}
event.status = 'completed';
// Notify participants
for (const participantId of event.participants) {
await notifyUser(participantId, {
type: 'event_ended',
event_id: eventId,
channel_id: event.channelId,
message: 'Event has ended, special privileges revoked'
});
}
console.log(`Event whitelist deactivated for event ${eventId}`);
} catch (error) {
console.error(`Failed to end event whitelist for ${eventId}:`, error);
}
}
async addEventParticipant(eventId, userId) {
const event = this.activeEvents.get(eventId);
if (!event) return;
if (!event.participants.includes(userId)) {
event.participants.push(userId);
// If event is active, add to whitelist immediately
if (event.status === 'active') {
await addToWhitelist({
channel_id: event.channelId,
channel_type: event.channelType,
uids: [userId]
});
}
}
}
}
// Usage
const eventManager = new EventWhitelistManager();
// Create event with whitelist
await eventManager.createEventWhitelist('webinar_001', 'group123', 2, {
startTime: '2024-01-15T10:00:00Z',
endTime: '2024-01-15T12:00:00Z',
type: 'webinar'
});
// Add participants
await eventManager.addEventParticipant('webinar_001', 'speaker1');
await eventManager.addEventParticipant('webinar_001', 'speaker2');
```
## Whitelist vs Blacklist Relationship
### Priority Rules
```
Blacklist > Whitelist > Regular Permissions
```
### Conflict Resolution
| Situation | Result | Description |
| ------------------------------- | ---------------------------- | ----------------------------- |
| In both blacklist and whitelist | Blacklist takes effect | Blacklist has higher priority |
| Only in whitelist | Whitelist privileges apply | Normal whitelisted user |
| Only in blacklist | Blacklist restrictions apply | Normal blacklisted user |
| In neither list | Regular user permissions | Default permission handling |
### Implementation Example
```javascript theme={null}
// Check user permissions considering both lists
async function checkUserPermissions(channelId, channelType, userId) {
try {
const [blacklist, whitelist] = await Promise.all([
getChannelBlacklist(channelId, channelType),
getChannelWhitelist(channelId, channelType)
]);
const isBlacklisted = blacklist.includes(userId);
const isWhitelisted = whitelist.includes(userId);
if (isBlacklisted) {
return {
status: 'blacklisted',
permissions: ['none'],
canSendMessage: false,
canJoinChannel: false
};
}
if (isWhitelisted) {
return {
status: 'whitelisted',
permissions: ['bypass_mute', 'priority_access'],
canSendMessage: true,
canJoinChannel: true,
specialPrivileges: true
};
}
return {
status: 'regular',
permissions: ['basic'],
canSendMessage: true,
canJoinChannel: true
};
} catch (error) {
console.error('Failed to check user permissions:', error);
return {
status: 'error',
permissions: ['none'],
canSendMessage: false,
canJoinChannel: false
};
}
}
```
## Best Practices
1. **Clear Criteria**: Establish clear criteria for whitelist inclusion
2. **Regular Review**: Periodically review whitelist members and remove inactive users
3. **Documentation**: Document reasons for adding users to whitelist
4. **Graduated Privileges**: Implement different levels of whitelist privileges
5. **Conflict Resolution**: Clearly define how blacklist/whitelist conflicts are resolved
6. **Notification**: Inform users when they are added to or removed from whitelist
7. **Audit Trail**: Maintain logs of all whitelist operations for accountability
# Remove Channel Whitelist
Source: https://wukong.mintlify.app/en/api/channel/whitelist-remove
POST /channel/whitelist_remove
Remove specific users from channel whitelist
## Overview
Remove specific users from channel whitelist. After removal, users will lose whitelist privileges and will not be able to send messages to the channel if whitelist mode is enabled.
## Request Body
### Required Parameters
Channel ID, cannot be empty or contain special characters
Channel type
* `1` - Person channel
* `2` - Group channel
List of user IDs to remove from whitelist
User ID
```bash cURL theme={null}
curl -X POST "http://localhost:5001/channel/whitelist_remove" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["user1", "user2"]
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/channel/whitelist_remove', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: 'group123',
channel_type: 2,
uids: ['user1', 'user2']
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"channel_id": "group123",
"channel_type": 2,
"uids": ["user1", "user2"]
}
response = requests.post('http://localhost:5001/channel/whitelist_remove', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"channel_id": "group123",
"channel_type": 2,
"uids": []string{"user1", "user2"},
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/channel/whitelist_remove",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | ----------------------------------------- |
| 200 | Users removed from whitelist successfully |
| 400 | Request parameter error |
| 403 | No management permission |
| 404 | Channel does not exist |
| 500 | Internal server error |
## Functionality
### Remove Operation
The remove operation performs the following steps:
1. **Verify Users**: Check if specified users are in the whitelist
2. **Remove Entries**: Delete specified users from channel whitelist
3. **Permission Changes**: Users lose whitelist privileges
4. **Take Effect Immediately**: Permission changes take effect immediately
### Permission Impact
Removed users will lose:
| Privilege | Impact | Effective Time |
| ------------------- | ---------------------------------------------- | -------------- |
| Send Permission | Cannot send messages in whitelist mode | Immediately |
| Special Permissions | Lose ability to bypass certain restrictions | Immediately |
| Priority Processing | Messages no longer receive priority processing | Immediately |
| Privilege Identity | Lose whitelist user status | Immediately |
## Whitelist Mode Impact
### When Whitelist Mode is Enabled
* **Removed Users**: Cannot send messages to the channel
* **Other Whitelisted Users**: Unaffected, continue to enjoy privileges
* **Regular Users**: Still cannot send messages
### When Whitelist Mode is Disabled
* **Removed Users**: Can still send messages normally
* **Permission Changes**: Mainly affects special permissions and priority
## Use Cases
### Permission Downgrade
```bash theme={null}
# Remove users who no longer need privileges
curl -X POST "/channel/whitelist_remove" -d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["former_admin"]
}'
```
### Batch Cleanup
```bash theme={null}
# Batch remove multiple users
curl -X POST "/channel/whitelist_remove" -d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["user1", "user2", "user3"]
}'
```
### Temporary Restriction
```bash theme={null}
# Temporarily remove user privileges
curl -X POST "/channel/whitelist_remove" -d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["temp_restricted_user"]
}'
```
## Whitelist Management Strategy
### Operation Comparison
| Operation | Function | Impact Scope | Use Case |
| ------------------ | ------------------------ | ----------------- | ---------------------- |
| `whitelist_add` | Add to whitelist | Grant privileges | Grant user privileges |
| `whitelist_remove` | Remove from whitelist | Remove privileges | Revoke user privileges |
| `whitelist_set` | Replace entire whitelist | Complete reset | Batch management |
### Management Principles
1. **Principle of Least Privilege**: Only give necessary users whitelist permissions
2. **Regular Review**: Regularly check the necessity of whitelist users
3. **Permission Grading**: Assign different levels of permissions based on user roles
4. **Transparent Management**: Record all permission change operations
## Best Practices
1. **Permission Audit**: Regularly review whitelist user activity and necessity
2. **Progressive Removal**: For important users, consider progressive permission downgrade
3. **Notification Mechanism**: Notify relevant users and administrators before removal
4. **Backup Strategy**: Backup current whitelist state before batch operations
5. **Monitor Impact**: Monitor channel activity changes after removal
6. **Document Records**: Record removal reasons and expected impact
## Error Handling
### Common Errors
| Error Message | Cause | Solution |
| -------------------------- | ----------------------- | ----------------------------------- |
| Channel ID cannot be empty | No channel ID provided | Ensure valid channel ID is provided |
| Channel type cannot be 0 | Invalid channel type | Use valid channel type (1 or 2) |
| uids cannot be empty | No user list provided | Provide list of user IDs to remove |
| Remove whitelist failed | Remove operation failed | Check if users are in whitelist |
## Related APIs
* [Add Channel Whitelist](/en/api/channel/whitelist) - Add users to whitelist
* [Set Channel Whitelist](/en/api/channel/whitelist-set) - Set complete whitelist
* [Get Channel Whitelist](/en/api/channel/get-whitelist) - Get current whitelist
* [Remove Channel Blacklist](/en/api/channel/blacklist-remove) - Remove blacklist users
# Set Channel Whitelist
Source: https://wukong.mintlify.app/en/api/channel/whitelist-set
POST /channel/whitelist_set
Set (replace) the entire whitelist for a channel
## Overview
Set (replace) the entire whitelist for a channel. This operation will first remove all existing whitelist entries and then add new ones. If UIDs is empty, the entire whitelist will be cleared.
## Request Body
### Required Parameters
Channel ID, cannot be empty or contain special characters
Channel type
* `1` - Person channel
* `2` - Group channel
### Optional Parameters
List of user IDs to set as whitelist. If empty, clears the entire whitelist.
User ID
```bash Set Whitelist theme={null}
curl -X POST "http://localhost:5001/channel/whitelist_set" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["user1", "user2", "user3"]
}'
```
```bash Clear Whitelist theme={null}
curl -X POST "http://localhost:5001/channel/whitelist_set" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "group123",
"channel_type": 2,
"uids": []
}'
```
```javascript JavaScript theme={null}
// Set whitelist
const response = await fetch('http://localhost:5001/channel/whitelist_set', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: 'group123',
channel_type: 2,
uids: ['user1', 'user2', 'user3']
})
});
const data = await response.json();
console.log(data);
// Clear whitelist
const clearResponse = await fetch('http://localhost:5001/channel/whitelist_set', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
channel_id: 'group123',
channel_type: 2,
uids: []
})
});
```
```python Python theme={null}
import requests
# Set whitelist
data = {
"channel_id": "group123",
"channel_type": 2,
"uids": ["user1", "user2", "user3"]
}
response = requests.post('http://localhost:5001/channel/whitelist_set', json=data)
result = response.json()
print(result)
# Clear whitelist
clear_data = {
"channel_id": "group123",
"channel_type": 2,
"uids": []
}
clear_response = requests.post('http://localhost:5001/channel/whitelist_set', json=clear_data)
clear_result = clear_response.json()
print(clear_result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
// Set whitelist
data := map[string]interface{}{
"channel_id": "group123",
"channel_type": 2,
"uids": []string{"user1", "user2", "user3"},
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/channel/whitelist_set",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
// Clear whitelist
clearData := map[string]interface{}{
"channel_id": "group123",
"channel_type": 2,
"uids": []string{},
}
clearJsonData, _ := json.Marshal(clearData)
clearResp, err := http.Post(
"http://localhost:5001/channel/whitelist_set",
"application/json",
bytes.NewBuffer(clearJsonData),
)
if err != nil {
panic(err)
}
defer clearResp.Body.Close()
var clearResult map[string]interface{}
json.NewDecoder(clearResp.Body).Decode(&clearResult)
fmt.Printf("%+v\n", clearResult)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | -------------------------- |
| 200 | Whitelist set successfully |
| 400 | Request parameter error |
| 403 | No management permission |
| 404 | Channel does not exist |
| 500 | Internal server error |
## Functionality
### Set Operation
The set operation performs the following steps:
1. **Clear Existing Whitelist**: Remove all existing whitelist entries for the channel
2. **Add New Entries**: Add the provided user ID list to the whitelist
3. **Update Permissions**: Take effect immediately, only whitelisted users can send messages (if whitelist mode is enabled)
4. **Conversation Management**: Automatically create related conversations for person channels
### Clear Operation
When `uids` is an empty array:
* Clear all whitelist entries for the channel
* Restore normal permissions for all users
* Disable whitelist mode
## Whitelist Mechanism
### Permission Features
Whitelisted users enjoy the following privileges:
| Privilege | Description | Applicable Condition |
| ---------------------- | ----------------------------------------- | ------------------------------ |
| Send Permission | Can send messages to the channel | When whitelist mode is enabled |
| Bypass Restrictions | Bypass certain channel restrictions | Based on channel configuration |
| Priority Processing | Messages may receive priority processing | System configuration related |
| Conversation Guarantee | Ensure conversations are created normally | Person channels |
### Whitelist Mode
* **When Enabled**: Only whitelisted users can send messages
* **When Disabled**: Whitelist does not affect message sending permissions
* **Person Channels**: Whitelisted users automatically create conversations
## Use Cases
### Batch Management
```bash theme={null}
# Set new whitelist, replacing all existing entries
curl -X POST "/channel/whitelist_set" -d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["admin1", "admin2", "moderator1"]
}'
```
### Clear Whitelist
```bash theme={null}
# Clear all whitelist, restore all user permissions
curl -X POST "/channel/whitelist_set" -d '{
"channel_id": "group123",
"channel_type": 2,
"uids": []
}'
```
### Reset Whitelist
```bash theme={null}
# Completely reset whitelist to new user list
curl -X POST "/channel/whitelist_set" -d '{
"channel_id": "group123",
"channel_type": 2,
"uids": ["new_privileged_user"]
}'
```
## Comparison with Other Whitelist Operations
| Operation | Function | Use Case |
| ------------------ | ------------------------------------ | ---------------------------------- |
| `whitelist_add` | Add users to existing whitelist | Incrementally add privileged users |
| `whitelist_set` | Replace entire whitelist | Batch management, reset whitelist |
| `whitelist_remove` | Remove specific users from whitelist | Remove specific user privileges |
## Permission Hierarchy
Whitelist position in the permission system:
```
System Users > Administrators > Blacklist > Whitelist > Regular Users
```
### Conflict Handling
| Situation | Result | Description |
| ------------------------------- | --------------------------------- | ----------------------------- |
| Both in blacklist and whitelist | Treated as blacklist | Blacklist has higher priority |
| Only in whitelist | Enjoy whitelist privileges | Normal whitelist user |
| Only in blacklist | Subject to blacklist restrictions | Normal blacklist user |
| In neither list | Regular user permissions | Default permission handling |
## Best Practices
1. **Use Carefully**: Set operation clears all existing whitelist, ensure this is intended behavior
2. **Backup Existing Data**: Get current whitelist before setting as backup
3. **Permission Verification**: Ensure operator has sufficient management permissions
4. **Progressive Management**: For large channels, consider progressive whitelist management
5. **Monitor Effects**: Monitor channel activity and user feedback after setting
6. **Document Records**: Record whitelist change reasons and expected effects
## Error Handling
### Common Errors
| Error Message | Cause | Solution |
| -------------------------------------------- | ------------------------- | ------------------------------------------- |
| Channel ID cannot be empty | No channel ID provided | Ensure valid channel ID is provided |
| Channel type cannot be 0 | Invalid channel type | Use valid channel type (1 or 2) |
| Channel ID cannot contain special characters | Invalid channel ID format | Use alphanumeric characters and underscores |
| Remove all whitelist failed | Clear operation failed | Check channel status and permissions |
| Add whitelist failed | Add operation failed | Check user ID validity |
## Related APIs
* [Add Channel Whitelist](/en/api/channel/whitelist) - Incrementally add whitelist users
* [Remove Channel Whitelist](/en/api/channel/whitelist-remove) - Remove specific whitelist users
* [Get Channel Whitelist](/en/api/channel/get-whitelist) - Get current whitelist
* [Set Channel Blacklist](/en/api/channel/blacklist-set) - Manage blacklist users
# Kick Connection
Source: https://wukong.mintlify.app/en/api/connection/kick
POST /conn/kick
Kick/disconnect a specific connection for a specified user
## Overview
Kick/disconnect a specific connection for a specified user. Similar to removing connections, but typically used for actively disconnecting live connections.
## Request Body
### Required Parameters
User ID
Connection ID
### Optional Parameters
Node ID, specifies connection on a specific node
```bash cURL theme={null}
curl -X POST "http://localhost:5001/conn/kick" \
-H "Content-Type: application/json" \
-d '{
"uid": "user123",
"conn_id": 12345,
"node_id": 1
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/conn/kick', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
uid: 'user123',
conn_id: 12345,
node_id: 1
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"uid": "user123",
"conn_id": 12345,
"node_id": 1
}
response = requests.post('http://localhost:5001/conn/kick', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"uid": "user123",
"conn_id": 12345,
"node_id": 1,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/conn/kick",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | ------------------------------ |
| 200 | Connection kicked successfully |
| 400 | Request parameter error |
| 404 | Connection does not exist |
| 500 | Internal server error |
## Difference from Remove Connection
| Operation | Endpoint | Purpose | Characteristics |
| ----------------- | -------------- | ------------------------------------ | ---------------------------------------------- |
| Kick Connection | `/conn/kick` | Actively disconnect live connections | Sends disconnect notification to client |
| Remove Connection | `/conn/remove` | Clean up connection records | Directly cleans server-side connection records |
## Use Cases
### User Session Management
**Force User Logout**:
```javascript theme={null}
// Force logout a user from specific device
async function forceUserLogout(userId, connectionId, nodeId, reason = 'admin_action') {
try {
// Log the action
await logAdminAction({
action: 'force_logout',
target_user: userId,
connection_id: connectionId,
node_id: nodeId,
reason: reason,
timestamp: new Date().toISOString()
});
// Kick the connection
await kickConnection({
uid: userId,
conn_id: connectionId,
node_id: nodeId
});
// Notify user about forced logout
await sendNotificationToUser(userId, {
type: 'forced_logout',
reason: reason,
timestamp: new Date().toISOString()
});
console.log(`User ${userId} forcibly logged out from connection ${connectionId}`);
} catch (error) {
console.error('Failed to force user logout:', error);
}
}
```
**Session Timeout Management**:
```javascript theme={null}
// Manage session timeouts
class SessionTimeoutManager {
constructor(timeoutMinutes = 30) {
this.timeoutDuration = timeoutMinutes * 60 * 1000;
this.activeSessions = new Map();
this.checkInterval = 60000; // Check every minute
}
startMonitoring() {
this.monitoringInterval = setInterval(() => {
this.checkExpiredSessions();
}, this.checkInterval);
console.log('Session timeout monitoring started');
}
stopMonitoring() {
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval);
this.monitoringInterval = null;
}
console.log('Session timeout monitoring stopped');
}
updateSessionActivity(userId, connectionId, nodeId) {
const sessionKey = `${userId}:${connectionId}:${nodeId}`;
this.activeSessions.set(sessionKey, {
userId,
connectionId,
nodeId,
lastActivity: Date.now()
});
}
async checkExpiredSessions() {
const now = Date.now();
const expiredSessions = [];
for (const [sessionKey, session] of this.activeSessions) {
const timeSinceActivity = now - session.lastActivity;
if (timeSinceActivity > this.timeoutDuration) {
expiredSessions.push(session);
this.activeSessions.delete(sessionKey);
}
}
// Kick expired sessions
for (const session of expiredSessions) {
try {
await this.kickExpiredSession(session);
} catch (error) {
console.error(`Failed to kick expired session for user ${session.userId}:`, error);
}
}
if (expiredSessions.length > 0) {
console.log(`Kicked ${expiredSessions.length} expired sessions`);
}
}
async kickExpiredSession(session) {
await kickConnection({
uid: session.userId,
conn_id: session.connectionId,
node_id: session.nodeId
});
// Log timeout event
await logSessionEvent({
type: 'session_timeout',
user_id: session.userId,
connection_id: session.connectionId,
node_id: session.nodeId,
last_activity: new Date(session.lastActivity).toISOString(),
timeout_duration_minutes: this.timeoutDuration / 60000
});
}
getActiveSessionCount() {
return this.activeSessions.size;
}
}
// Usage
const sessionManager = new SessionTimeoutManager(30); // 30 minute timeout
sessionManager.startMonitoring();
// Update activity when user performs actions
sessionManager.updateSessionActivity('user123', 12345, 1);
```
### Security Management
**Suspicious Activity Response**:
```javascript theme={null}
// Kick connections showing suspicious activity
async function handleSuspiciousActivity(userId, connectionId, nodeId, activityType) {
try {
// Log security incident
const incidentId = await logSecurityIncident({
type: 'suspicious_activity',
user_id: userId,
connection_id: connectionId,
node_id: nodeId,
activity_type: activityType,
timestamp: new Date().toISOString(),
action_taken: 'connection_kicked'
});
// Kick the suspicious connection
await kickConnection({
uid: userId,
conn_id: connectionId,
node_id: nodeId
});
// Notify security team
await notifySecurityTeam({
incident_id: incidentId,
user_id: userId,
connection_id: connectionId,
activity_type: activityType,
action: 'Connection kicked due to suspicious activity'
});
// Temporarily block user if needed
if (activityType === 'multiple_failed_auth' || activityType === 'rate_limit_exceeded') {
await temporarilyBlockUser(userId, 15); // 15 minute block
}
console.log(`Kicked suspicious connection ${connectionId} for user ${userId}`);
} catch (error) {
console.error('Failed to handle suspicious activity:', error);
}
}
```
### Administrative Control
**Bulk Connection Management**:
```javascript theme={null}
// Kick multiple connections for administrative purposes
async function bulkKickConnections(connections, reason = 'admin_maintenance') {
const results = [];
for (const conn of connections) {
try {
await kickConnection({
uid: conn.uid,
conn_id: conn.conn_id,
node_id: conn.node_id
});
results.push({
uid: conn.uid,
conn_id: conn.conn_id,
success: true
});
// Small delay to avoid overwhelming the system
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
results.push({
uid: conn.uid,
conn_id: conn.conn_id,
success: false,
error: error.message
});
}
}
// Log bulk operation
await logAdminAction({
action: 'bulk_kick_connections',
reason: reason,
total_connections: connections.length,
successful_kicks: results.filter(r => r.success).length,
failed_kicks: results.filter(r => !r.success).length,
timestamp: new Date().toISOString()
});
return results;
}
```
### Maintenance Operations
**Graceful Node Shutdown**:
```javascript theme={null}
// Gracefully kick all connections from a node before shutdown
async function gracefulNodeShutdown(nodeId, notificationMessage = 'Server maintenance in progress') {
try {
// Get all connections on the node
const nodeConnections = await getNodeConnections(nodeId);
console.log(`Starting graceful shutdown for node ${nodeId} (${nodeConnections.length} connections)`);
// Send notification to all users first
for (const conn of nodeConnections) {
try {
await sendMaintenanceNotification(conn.uid, {
message: notificationMessage,
estimated_downtime: '10-15 minutes',
reconnect_instructions: 'Please reconnect in a few minutes'
});
} catch (error) {
console.error(`Failed to notify user ${conn.uid}:`, error);
}
}
// Wait a bit for notifications to be delivered
await new Promise(resolve => setTimeout(resolve, 5000));
// Kick connections in batches
const batchSize = 10;
const batches = chunkArray(nodeConnections, batchSize);
for (let i = 0; i < batches.length; i++) {
const batch = batches[i];
console.log(`Kicking batch ${i + 1}/${batches.length}`);
const batchResults = await bulkKickConnections(batch, 'node_maintenance');
// Wait between batches
if (i < batches.length - 1) {
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
console.log(`Graceful shutdown completed for node ${nodeId}`);
} catch (error) {
console.error(`Graceful shutdown failed for node ${nodeId}:`, error);
}
}
```
### Connection Quality Management
**Poor Connection Cleanup**:
```javascript theme={null}
// Kick connections with poor quality metrics
class ConnectionQualityManager {
constructor() {
this.qualityThresholds = {
maxLatency: 5000, // 5 seconds
minHeartbeatInterval: 60000, // 1 minute
maxFailedPings: 3
};
}
async monitorAndKickPoorConnections() {
try {
const allConnections = await getAllActiveConnections();
const poorConnections = [];
for (const conn of allConnections) {
const quality = await this.assessConnectionQuality(conn);
if (!quality.isGood) {
poorConnections.push({
connection: conn,
quality: quality
});
}
}
// Kick poor quality connections
for (const poor of poorConnections) {
await this.kickPoorConnection(poor.connection, poor.quality);
}
return {
total_checked: allConnections.length,
poor_connections: poorConnections.length,
kicked: poorConnections.length
};
} catch (error) {
console.error('Connection quality monitoring failed:', error);
return { error: error.message };
}
}
async assessConnectionQuality(connection) {
const metrics = await getConnectionMetrics(connection.conn_id);
const issues = [];
if (metrics.latency > this.qualityThresholds.maxLatency) {
issues.push(`High latency: ${metrics.latency}ms`);
}
if (metrics.lastHeartbeat &&
Date.now() - metrics.lastHeartbeat > this.qualityThresholds.minHeartbeatInterval) {
issues.push('Missed heartbeat');
}
if (metrics.failedPings > this.qualityThresholds.maxFailedPings) {
issues.push(`Too many failed pings: ${metrics.failedPings}`);
}
return {
isGood: issues.length === 0,
issues: issues,
metrics: metrics
};
}
async kickPoorConnection(connection, quality) {
try {
await kickConnection({
uid: connection.uid,
conn_id: connection.conn_id,
node_id: connection.node_id
});
// Log quality issue
await logConnectionQualityEvent({
user_id: connection.uid,
connection_id: connection.conn_id,
node_id: connection.node_id,
quality_issues: quality.issues,
metrics: quality.metrics,
action: 'connection_kicked',
timestamp: new Date().toISOString()
});
console.log(`Kicked poor quality connection ${connection.conn_id} for user ${connection.uid}`);
} catch (error) {
console.error(`Failed to kick poor connection ${connection.conn_id}:`, error);
}
}
}
// Usage
const qualityManager = new ConnectionQualityManager();
// Run quality check every 5 minutes
setInterval(async () => {
const result = await qualityManager.monitorAndKickPoorConnections();
if (result.kicked > 0) {
console.log(`Quality check: kicked ${result.kicked} poor connections`);
}
}, 5 * 60 * 1000);
```
## Best Practices
1. **Graceful Notification**: Send notifications to users before kicking connections when possible
2. **Logging**: Log all kick operations with reasons for audit purposes
3. **Rate Limiting**: Avoid kicking too many connections simultaneously
4. **Error Handling**: Handle kick failures gracefully
5. **User Experience**: Provide clear reconnection instructions to affected users
6. **Security**: Use kick operations as part of security incident response
7. **Monitoring**: Monitor kick operations to identify patterns or issues
# Remove Connection
Source: https://wukong.mintlify.app/en/api/connection/remove
POST /conn/remove
Remove a specific connection for a specified user
## Overview
Remove a specific connection for a specified user, used for cleaning up invalid connections or managing user connection states.
## Request Body
### Required Parameters
User ID
Connection ID
### Optional Parameters
Node ID, specifies connection on a specific node
```bash cURL theme={null}
curl -X POST "http://localhost:5001/conn/remove" \
-H "Content-Type: application/json" \
-d '{
"uid": "user123",
"conn_id": 12345,
"node_id": 1
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/conn/remove', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
uid: 'user123',
conn_id: 12345,
node_id: 1
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"uid": "user123",
"conn_id": 12345,
"node_id": 1
}
response = requests.post('http://localhost:5001/conn/remove', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"uid": "user123",
"conn_id": 12345,
"node_id": 1,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/conn/remove",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | ------------------------------- |
| 200 | Connection removed successfully |
| 400 | Request parameter error |
| 404 | Connection does not exist |
| 500 | Internal server error |
## Use Cases
### Connection Cleanup
**Remove Stale Connections**:
```javascript theme={null}
// Remove stale or inactive connections
async function removeStaleConnection(userId, connectionId, nodeId) {
try {
await removeConnection({
uid: userId,
conn_id: connectionId,
node_id: nodeId
});
console.log(`Stale connection ${connectionId} removed for user ${userId}`);
} catch (error) {
console.error('Failed to remove stale connection:', error);
}
}
```
**Batch Connection Cleanup**:
```javascript theme={null}
// Clean up multiple stale connections
async function batchRemoveConnections(connectionsToRemove) {
const results = [];
for (const conn of connectionsToRemove) {
try {
await removeConnection({
uid: conn.uid,
conn_id: conn.conn_id,
node_id: conn.node_id
});
results.push({
uid: conn.uid,
conn_id: conn.conn_id,
success: true
});
} catch (error) {
results.push({
uid: conn.uid,
conn_id: conn.conn_id,
success: false,
error: error.message
});
}
}
return results;
}
```
### Connection Management
**Manage User Connection Limits**:
```javascript theme={null}
// Enforce connection limits per user
class ConnectionLimitManager {
constructor(maxConnectionsPerUser = 5) {
this.maxConnections = maxConnectionsPerUser;
}
async enforceConnectionLimit(userId) {
try {
// Get user's current connections
const connections = await getUserConnections(userId);
if (connections.length > this.maxConnections) {
// Sort by last activity (oldest first)
connections.sort((a, b) => a.last_activity - b.last_activity);
// Remove oldest connections
const toRemove = connections.slice(0, connections.length - this.maxConnections);
for (const conn of toRemove) {
await removeConnection({
uid: userId,
conn_id: conn.conn_id,
node_id: conn.node_id
});
console.log(`Removed old connection ${conn.conn_id} for user ${userId}`);
}
}
} catch (error) {
console.error('Failed to enforce connection limit:', error);
}
}
}
// Usage
const limitManager = new ConnectionLimitManager(3);
await limitManager.enforceConnectionLimit('user123');
```
### Security Management
**Remove Suspicious Connections**:
```javascript theme={null}
// Remove connections flagged as suspicious
async function removeSuspiciousConnection(userId, connectionId, nodeId, reason) {
try {
// Log security event
await logSecurityEvent({
type: 'suspicious_connection_removal',
user_id: userId,
connection_id: connectionId,
node_id: nodeId,
reason: reason,
timestamp: new Date().toISOString()
});
// Remove the connection
await removeConnection({
uid: userId,
conn_id: connectionId,
node_id: nodeId
});
// Notify security team
await notifySecurityTeam({
action: 'connection_removed',
user_id: userId,
connection_id: connectionId,
reason: reason
});
console.log(`Suspicious connection ${connectionId} removed for user ${userId}`);
} catch (error) {
console.error('Failed to remove suspicious connection:', error);
}
}
```
### Node Maintenance
**Node-specific Connection Cleanup**:
```javascript theme={null}
// Remove all connections from a specific node during maintenance
async function removeNodeConnections(nodeId) {
try {
// Get all connections on the node
const nodeConnections = await getNodeConnections(nodeId);
console.log(`Removing ${nodeConnections.length} connections from node ${nodeId}`);
const results = [];
for (const conn of nodeConnections) {
try {
await removeConnection({
uid: conn.uid,
conn_id: conn.conn_id,
node_id: nodeId
});
results.push({ uid: conn.uid, conn_id: conn.conn_id, success: true });
} catch (error) {
results.push({
uid: conn.uid,
conn_id: conn.conn_id,
success: false,
error: error.message
});
}
}
const successful = results.filter(r => r.success).length;
const failed = results.filter(r => !r.success).length;
console.log(`Node ${nodeId} cleanup: ${successful} successful, ${failed} failed`);
return results;
} catch (error) {
console.error(`Failed to cleanup node ${nodeId} connections:`, error);
}
}
```
### Monitoring and Diagnostics
**Connection Health Monitoring**:
```javascript theme={null}
class ConnectionHealthMonitor {
constructor(checkInterval = 60000) {
this.checkInterval = checkInterval;
this.isMonitoring = false;
}
startMonitoring() {
if (this.isMonitoring) return;
this.isMonitoring = true;
this.monitoringInterval = setInterval(() => {
this.checkConnectionHealth();
}, this.checkInterval);
console.log('Connection health monitoring started');
}
stopMonitoring() {
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval);
this.monitoringInterval = null;
}
this.isMonitoring = false;
console.log('Connection health monitoring stopped');
}
async checkConnectionHealth() {
try {
// Get all active connections
const connections = await getAllConnections();
const unhealthyConnections = connections.filter(conn =>
this.isConnectionUnhealthy(conn)
);
if (unhealthyConnections.length > 0) {
console.log(`Found ${unhealthyConnections.length} unhealthy connections`);
// Remove unhealthy connections
for (const conn of unhealthyConnections) {
await this.removeUnhealthyConnection(conn);
}
}
} catch (error) {
console.error('Connection health check failed:', error);
}
}
isConnectionUnhealthy(connection) {
const now = Date.now();
const lastActivity = connection.last_activity || 0;
const idleTime = now - lastActivity;
// Consider connection unhealthy if idle for more than 10 minutes
return idleTime > 10 * 60 * 1000;
}
async removeUnhealthyConnection(connection) {
try {
await removeConnection({
uid: connection.uid,
conn_id: connection.conn_id,
node_id: connection.node_id
});
console.log(`Removed unhealthy connection ${connection.conn_id} for user ${connection.uid}`);
} catch (error) {
console.error(`Failed to remove unhealthy connection ${connection.conn_id}:`, error);
}
}
}
// Usage
const healthMonitor = new ConnectionHealthMonitor(30000); // Check every 30 seconds
healthMonitor.startMonitoring();
```
### Administrative Operations
**Admin Connection Management**:
```javascript theme={null}
// Administrative function to manage user connections
async function adminManageUserConnections(adminUserId, targetUserId, action) {
try {
// Verify admin permissions
const hasPermission = await verifyAdminPermission(adminUserId, 'connection_management');
if (!hasPermission) {
throw new Error('Insufficient admin permissions');
}
// Get target user's connections
const connections = await getUserConnections(targetUserId);
let results = [];
switch (action.type) {
case 'remove_all':
results = await batchRemoveConnections(connections);
break;
case 'remove_by_device':
const deviceConnections = connections.filter(conn =>
conn.device_flag === action.device_flag
);
results = await batchRemoveConnections(deviceConnections);
break;
case 'remove_specific':
const specificConn = connections.find(conn =>
conn.conn_id === action.conn_id
);
if (specificConn) {
results = await batchRemoveConnections([specificConn]);
}
break;
}
// Log admin action
await logAdminAction(adminUserId, 'connection_management', {
target_user: targetUserId,
action: action,
results: results
});
return results;
} catch (error) {
console.error('Admin connection management failed:', error);
throw error;
}
}
```
## Best Practices
1. **Permission Verification**: Ensure proper authorization before removing connections
2. **Logging**: Log all connection removals for audit and debugging purposes
3. **Graceful Handling**: Handle connection removal errors gracefully
4. **Batch Operations**: Use batch operations for better performance when removing multiple connections
5. **Monitoring**: Implement monitoring to detect and remove unhealthy connections
6. **Security**: Remove suspicious connections promptly to maintain system security
7. **Node Awareness**: Consider node distribution when managing connections in clustered environments
# Clear Unread Messages
Source: https://wukong.mintlify.app/en/api/conversation/clear-unread
POST /conversations/clearUnread
Clear the unread message count for a conversation
## Overview
Clear the unread message count for a specified conversation, resetting the unread count to 0.
## Request Body
### Required Parameters
User ID
Channel ID
Channel type
* `1` - Personal channel
* `2` - Group channel
### Optional Parameters
Message sequence number, specifies up to which message to clear
```bash cURL theme={null}
curl -X POST "http://localhost:5001/conversations/clearUnread" \
-H "Content-Type: application/json" \
-d '{
"uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"message_seq": 1001
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/conversations/clearUnread', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
uid: 'user123',
channel_id: 'group123',
channel_type: 2,
message_seq: 1001
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"message_seq": 1001
}
response = requests.post('http://localhost:5001/conversations/clearUnread', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"message_seq": 1001,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/conversations/clearUnread",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | ------------------------------------ |
| 200 | Unread messages cleared successfully |
| 400 | Request parameter error |
| 403 | No operation permission |
| 404 | Conversation does not exist |
| 500 | Internal server error |
## Use Cases
### Chat Interface Integration
**Mark Messages as Read**:
```javascript theme={null}
// Clear unread when user opens a conversation
async function openConversation(channelId, channelType, userId) {
try {
// Clear unread count
await clearUnreadMessages({
uid: userId,
channel_id: channelId,
channel_type: channelType
});
// Update UI to remove unread badge
updateUnreadBadge(channelId, 0);
console.log('Conversation opened and unread cleared');
} catch (error) {
console.error('Failed to clear unread messages:', error);
}
}
```
**Mark Read Up to Specific Message**:
```javascript theme={null}
// Clear unread up to a specific message when user scrolls
async function markReadUpToMessage(channelId, channelType, userId, messageSeq) {
try {
await clearUnreadMessages({
uid: userId,
channel_id: channelId,
channel_type: channelType,
message_seq: messageSeq
});
console.log(`Marked read up to message ${messageSeq}`);
} catch (error) {
console.error('Failed to mark messages as read:', error);
}
}
```
### Batch Operations
**Clear Multiple Conversations**:
```javascript theme={null}
// Clear unread for multiple conversations
async function clearMultipleConversations(userId, conversations) {
const results = [];
for (const conv of conversations) {
try {
await clearUnreadMessages({
uid: userId,
channel_id: conv.channel_id,
channel_type: conv.channel_type
});
results.push({
channel_id: conv.channel_id,
success: true
});
} catch (error) {
results.push({
channel_id: conv.channel_id,
success: false,
error: error.message
});
}
}
return results;
}
```
### Auto-Read Functionality
**Auto-mark as Read on Focus**:
```javascript theme={null}
// Automatically clear unread when window gains focus
class AutoReadManager {
constructor(userId) {
this.userId = userId;
this.activeConversation = null;
this.setupEventListeners();
}
setupEventListeners() {
// Clear unread when window gains focus
window.addEventListener('focus', () => {
if (this.activeConversation) {
this.clearUnreadForActive();
}
});
// Clear unread when user is actively typing
document.addEventListener('keydown', () => {
if (this.activeConversation) {
this.clearUnreadForActive();
}
});
}
setActiveConversation(channelId, channelType) {
this.activeConversation = { channelId, channelType };
this.clearUnreadForActive();
}
async clearUnreadForActive() {
if (!this.activeConversation) return;
try {
await clearUnreadMessages({
uid: this.userId,
channel_id: this.activeConversation.channelId,
channel_type: this.activeConversation.channelType
});
} catch (error) {
console.error('Auto-read failed:', error);
}
}
}
```
### Read Receipt Integration
**Combine with Read Receipts**:
```javascript theme={null}
// Clear unread and send read receipt
async function markAsReadWithReceipt(channelId, channelType, userId, messageSeq) {
try {
// Clear unread count
await clearUnreadMessages({
uid: userId,
channel_id: channelId,
channel_type: channelType,
message_seq: messageSeq
});
// Send read receipt to other participants
await sendReadReceipt({
channel_id: channelId,
channel_type: channelType,
message_seq: messageSeq,
reader_uid: userId
});
console.log('Messages marked as read with receipt sent');
} catch (error) {
console.error('Failed to mark as read with receipt:', error);
}
}
```
## Best Practices
1. **User Intent**: Only clear unread when user actually views the messages
2. **Batch Operations**: Use batch clearing for better performance when possible
3. **Error Handling**: Handle network errors gracefully without affecting UI
4. **Real-time Updates**: Combine with WebSocket events for real-time unread updates
5. **Offline Support**: Queue clear operations when offline and sync when reconnected
6. **Performance**: Avoid excessive API calls by debouncing clear operations
7. **User Experience**: Provide visual feedback when clearing unread counts
# Delete Conversation
Source: https://wukong.mintlify.app/en/api/conversation/delete
POST /conversations/delete
Delete a specified user's conversation record
## Overview
Delete a specified user's conversation record, clearing conversation history and status.
## Request Body
### Required Parameters
User ID
Channel ID
Channel type (1=personal channel, 2=group channel)
```bash cURL theme={null}
curl -X POST "http://localhost:5001/conversations/delete" \
-H "Content-Type: application/json" \
-d '{
"uid": "user123",
"channel_id": "group123",
"channel_type": 2
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/conversations/delete', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
uid: 'user123',
channel_id: 'group123',
channel_type: 2
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"uid": "user123",
"channel_id": "group123",
"channel_type": 2
}
response = requests.post('http://localhost:5001/conversations/delete', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"uid": "user123",
"channel_id": "group123",
"channel_type": 2,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/conversations/delete",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "success"
}
```
## Response Fields
Operation status, returns `"success"` on success
## Status Codes
| Status Code | Description |
| ----------- | --------------------------------- |
| 200 | Conversation deleted successfully |
| 400 | Request parameter error |
| 403 | No deletion permission |
| 500 | Internal server error |
## Deletion Impact
### What Gets Deleted
When a conversation is deleted, the following data is removed:
| Data Type | Scope | Impact |
| ------------------- | ------------------------------ | -------------------------------------- |
| Conversation Record | User's conversation list entry | Conversation disappears from chat list |
| Unread Count | User's unread message count | Unread badge removed |
| Last Message Info | Last message and timestamp | No preview in conversation list |
| User Preferences | Conversation-specific settings | Notification settings reset |
### What Remains Unchanged
* **Channel Messages**: Actual messages in the channel remain intact
* **Other Users**: Other users' conversations with the same channel are unaffected
* **Channel Membership**: User remains a member of the channel
* **Message History**: User can still access messages by rejoining the conversation
## Use Cases
### Chat List Management
**Remove Conversation from Chat List**:
```javascript theme={null}
// Remove conversation from user's chat list
async function removeFromChatList(userId, channelId, channelType) {
try {
await deleteConversation({
uid: userId,
channel_id: channelId,
channel_type: channelType
});
// Update UI to remove conversation
removeConversationFromUI(channelId);
console.log('Conversation removed from chat list');
} catch (error) {
console.error('Failed to remove conversation:', error);
}
}
```
**Clean Up Old Conversations**:
```javascript theme={null}
// Clean up conversations older than specified days
async function cleanupOldConversations(userId, daysOld = 30) {
try {
// Get user's conversations
const conversations = await getUserConversations(userId);
const cutoffTime = Date.now() - (daysOld * 24 * 60 * 60 * 1000);
const oldConversations = conversations.filter(conv =>
conv.timestamp < cutoffTime && conv.unread === 0
);
// Delete old conversations
for (const conv of oldConversations) {
await deleteConversation({
uid: userId,
channel_id: conv.channel_id,
channel_type: conv.channel_type
});
}
console.log(`Cleaned up ${oldConversations.length} old conversations`);
} catch (error) {
console.error('Failed to cleanup old conversations:', error);
}
}
```
### Privacy and Data Management
**User Privacy Control**:
```javascript theme={null}
// Allow users to remove conversations for privacy
async function removeConversationForPrivacy(userId, channelId, channelType) {
try {
// Confirm with user
const confirmed = await confirmDeletion(
'Remove this conversation from your chat list? You can still access messages by searching.'
);
if (confirmed) {
await deleteConversation({
uid: userId,
channel_id: channelId,
channel_type: channelType
});
// Log privacy action
await logPrivacyAction(userId, 'conversation_removed', {
channel_id: channelId,
channel_type: channelType
});
showNotification('Conversation removed from your chat list');
}
} catch (error) {
console.error('Failed to remove conversation for privacy:', error);
}
}
```
### Batch Operations
**Batch Delete Conversations**:
```javascript theme={null}
// Delete multiple conversations
async function batchDeleteConversations(userId, conversationsToDelete) {
const results = [];
for (const conv of conversationsToDelete) {
try {
await deleteConversation({
uid: userId,
channel_id: conv.channel_id,
channel_type: conv.channel_type
});
results.push({
channel_id: conv.channel_id,
success: true
});
} catch (error) {
results.push({
channel_id: conv.channel_id,
success: false,
error: error.message
});
}
}
// Update UI for successful deletions
const successful = results.filter(r => r.success);
successful.forEach(result => {
removeConversationFromUI(result.channel_id);
});
return results;
}
```
### Archive and Restore
**Archive Conversation (Soft Delete)**:
```javascript theme={null}
// Implement archive functionality using conversation deletion
class ConversationArchive {
constructor(userId) {
this.userId = userId;
this.archivedKey = `archived_conversations_${userId}`;
}
async archiveConversation(channelId, channelType) {
try {
// Get conversation data before deletion
const conversationData = await getConversationData(
this.userId, channelId, channelType
);
// Store in local archive
await this.storeInArchive(conversationData);
// Delete from active conversations
await deleteConversation({
uid: this.userId,
channel_id: channelId,
channel_type: channelType
});
console.log('Conversation archived successfully');
} catch (error) {
console.error('Failed to archive conversation:', error);
}
}
async storeInArchive(conversationData) {
const archived = this.getArchivedConversations();
archived.push({
...conversationData,
archived_at: Date.now()
});
localStorage.setItem(this.archivedKey, JSON.stringify(archived));
}
getArchivedConversations() {
const stored = localStorage.getItem(this.archivedKey);
return stored ? JSON.parse(stored) : [];
}
async restoreConversation(channelId, channelType) {
// Note: Restoration requires rejoining the conversation
// The conversation will reappear when new messages arrive
const archived = this.getArchivedConversations();
const filtered = archived.filter(conv =>
!(conv.channel_id === channelId && conv.channel_type === channelType)
);
localStorage.setItem(this.archivedKey, JSON.stringify(filtered));
console.log('Conversation restored from archive');
}
}
// Usage
const archive = new ConversationArchive('user123');
await archive.archiveConversation('group123', 2);
```
### Administrative Operations
**Admin Conversation Management**:
```javascript theme={null}
// Administrative function to clean up user conversations
async function adminCleanupUserConversations(adminUserId, targetUserId, criteria) {
try {
// Verify admin permissions
const hasPermission = await verifyAdminPermission(adminUserId, 'conversation_management');
if (!hasPermission) {
throw new Error('Insufficient admin permissions');
}
// Get target user's conversations
const conversations = await getUserConversations(targetUserId);
// Filter based on criteria
const toDelete = conversations.filter(conv => {
if (criteria.inactive_days && conv.last_activity) {
const daysSinceActivity = (Date.now() - conv.last_activity) / (1000 * 60 * 60 * 24);
return daysSinceActivity > criteria.inactive_days;
}
if (criteria.channel_types) {
return criteria.channel_types.includes(conv.channel_type);
}
return false;
});
// Delete conversations
const results = await batchDeleteConversations(targetUserId, toDelete);
// Log admin action
await logAdminAction(adminUserId, 'conversation_cleanup', {
target_user: targetUserId,
deleted_count: results.filter(r => r.success).length,
criteria: criteria
});
return results;
} catch (error) {
console.error('Admin conversation cleanup failed:', error);
throw error;
}
}
```
## Important Notes
**Data Recovery**: Once a conversation is deleted, it cannot be automatically restored. The conversation will only reappear if:
* New messages are sent to the channel
* The user manually rejoins the conversation
* The conversation is recreated through other means
**Message Preservation**: Deleting a conversation does not delete the actual messages in the channel. Other users can still see all messages, and the user can still access message history through other means.
## Best Practices
1. **User Confirmation**: Always confirm with users before deleting conversations
2. **Soft Delete Option**: Consider implementing archive functionality instead of permanent deletion
3. **Batch Operations**: Use batch operations for better performance when deleting multiple conversations
4. **Error Handling**: Handle deletion errors gracefully without breaking the UI
5. **Logging**: Log conversation deletions for audit and support purposes
6. **UI Updates**: Immediately update the UI to reflect conversation removal
7. **Data Backup**: Consider backing up conversation metadata before deletion
# Set Conversation Unread Count
Source: https://wukong.mintlify.app/en/api/conversation/set-unread
POST /conversations/setUnread
Set the unread message count for a specified conversation
## Overview
Set the unread message count for a specified conversation, used for manually adjusting the unread status of conversations.
## Request Body
### Required Parameters
User ID
Channel ID
Channel type (1=personal channel, 2=group channel)
Unread count to set
```bash cURL theme={null}
curl -X POST "http://localhost:5001/conversations/setUnread" \
-H "Content-Type: application/json" \
-d '{
"uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"unread": 5
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/conversations/setUnread', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
uid: 'user123',
channel_id: 'group123',
channel_type: 2,
unread: 5
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"unread": 5
}
response = requests.post('http://localhost:5001/conversations/setUnread', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"unread": 5,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/conversations/setUnread",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "success"
}
```
## Response Fields
Operation status, returns `"success"` on success
## Status Codes
| Status Code | Description |
| ----------- | ----------------------------- |
| 200 | Unread count set successfully |
| 400 | Request parameter error |
| 403 | No operation permission |
| 500 | Internal server error |
## Use Cases
### Manual Unread Management
**Mark Conversation as Important**:
```javascript theme={null}
// Mark conversation as having unread messages to draw attention
async function markAsImportant(userId, channelId, channelType) {
try {
await setUnreadCount({
uid: userId,
channel_id: channelId,
channel_type: channelType,
unread: 1
});
console.log('Conversation marked as important');
updateUIBadge(channelId, 1);
} catch (error) {
console.error('Failed to mark conversation as important:', error);
}
}
```
**Reset Unread Count**:
```javascript theme={null}
// Reset unread count to zero
async function resetUnreadCount(userId, channelId, channelType) {
try {
await setUnreadCount({
uid: userId,
channel_id: channelId,
channel_type: channelType,
unread: 0
});
console.log('Unread count reset to zero');
updateUIBadge(channelId, 0);
} catch (error) {
console.error('Failed to reset unread count:', error);
}
}
```
### Notification Management
**Custom Notification Badges**:
```javascript theme={null}
// Set custom unread count for special notifications
async function setCustomNotification(userId, channelId, channelType, count) {
try {
await setUnreadCount({
uid: userId,
channel_id: channelId,
channel_type: channelType,
unread: count
});
// Update UI with custom badge
updateNotificationBadge(channelId, count);
// Show system notification if count > 0
if (count > 0) {
showSystemNotification(`${count} new items in ${channelId}`);
}
} catch (error) {
console.error('Failed to set custom notification:', error);
}
}
```
### Batch Unread Operations
**Batch Set Unread Counts**:
```javascript theme={null}
// Set unread counts for multiple conversations
async function batchSetUnreadCounts(userId, conversationUpdates) {
const results = [];
for (const update of conversationUpdates) {
try {
await setUnreadCount({
uid: userId,
channel_id: update.channelId,
channel_type: update.channelType,
unread: update.unreadCount
});
results.push({
channelId: update.channelId,
success: true,
unreadCount: update.unreadCount
});
} catch (error) {
results.push({
channelId: update.channelId,
success: false,
error: error.message
});
}
}
return results;
}
// Usage
const updates = [
{ channelId: 'group123', channelType: 2, unreadCount: 5 },
{ channelId: 'user456', channelType: 1, unreadCount: 2 },
{ channelId: 'group789', channelType: 2, unreadCount: 0 }
];
const results = await batchSetUnreadCounts('user123', updates);
console.log('Batch update results:', results);
```
### Priority Management
**Set Priority-based Unread Counts**:
```javascript theme={null}
class ConversationPriorityManager {
constructor(userId) {
this.userId = userId;
this.priorityLevels = {
urgent: 99,
high: 10,
normal: 1,
low: 0
};
}
async setPriority(channelId, channelType, priority) {
const unreadCount = this.priorityLevels[priority] || 1;
try {
await setUnreadCount({
uid: this.userId,
channel_id: channelId,
channel_type: channelType,
unread: unreadCount
});
// Update UI with priority styling
this.updatePriorityUI(channelId, priority, unreadCount);
console.log(`Set ${channelId} priority to ${priority} (unread: ${unreadCount})`);
} catch (error) {
console.error(`Failed to set priority for ${channelId}:`, error);
}
}
updatePriorityUI(channelId, priority, unreadCount) {
const element = document.querySelector(`[data-channel="${channelId}"]`);
if (element) {
element.className = `conversation-item priority-${priority}`;
const badge = element.querySelector('.unread-badge');
if (badge) {
badge.textContent = unreadCount > 0 ? unreadCount : '';
badge.style.display = unreadCount > 0 ? 'block' : 'none';
}
}
}
async markAsUrgent(channelId, channelType) {
await this.setPriority(channelId, channelType, 'urgent');
}
async markAsNormal(channelId, channelType) {
await this.setPriority(channelId, channelType, 'normal');
}
async clearPriority(channelId, channelType) {
await this.setPriority(channelId, channelType, 'low');
}
}
// Usage
const priorityManager = new ConversationPriorityManager('user123');
// Mark conversation as urgent
await priorityManager.markAsUrgent('group123', 2);
// Set normal priority
await priorityManager.markAsNormal('user456', 1);
// Clear priority
await priorityManager.clearPriority('group789', 2);
```
### Sync and Recovery
**Sync Unread Counts from External Source**:
```javascript theme={null}
// Sync unread counts from external system
async function syncUnreadFromExternal(userId, externalUnreadData) {
const syncResults = [];
for (const item of externalUnreadData) {
try {
await setUnreadCount({
uid: userId,
channel_id: item.channelId,
channel_type: item.channelType,
unread: item.unreadCount
});
syncResults.push({
channelId: item.channelId,
synced: true,
unreadCount: item.unreadCount
});
} catch (error) {
syncResults.push({
channelId: item.channelId,
synced: false,
error: error.message
});
}
}
// Log sync results
const successful = syncResults.filter(r => r.synced).length;
const failed = syncResults.filter(r => !r.synced).length;
console.log(`Sync completed: ${successful} successful, ${failed} failed`);
return syncResults;
}
```
### Testing and Development
**Test Unread Scenarios**:
```javascript theme={null}
// Helper function for testing different unread scenarios
async function testUnreadScenarios(userId, channelId, channelType) {
const scenarios = [
{ name: 'No unread', count: 0 },
{ name: 'Single unread', count: 1 },
{ name: 'Multiple unread', count: 5 },
{ name: 'High unread', count: 99 },
{ name: 'Max unread', count: 999 }
];
for (const scenario of scenarios) {
console.log(`Testing scenario: ${scenario.name}`);
try {
await setUnreadCount({
uid: userId,
channel_id: channelId,
channel_type: channelType,
unread: scenario.count
});
// Wait for UI update
await new Promise(resolve => setTimeout(resolve, 500));
// Verify UI state
const badge = document.querySelector(`[data-channel="${channelId}"] .unread-badge`);
const displayedCount = badge ? badge.textContent : '0';
console.log(`✓ ${scenario.name}: Set ${scenario.count}, displayed ${displayedCount}`);
} catch (error) {
console.error(`✗ ${scenario.name}: Failed -`, error.message);
}
}
}
```
## Best Practices
1. **Validation**: Validate unread count values (non-negative integers)
2. **UI Consistency**: Ensure UI updates match the set unread count
3. **Error Handling**: Handle API errors gracefully without breaking UI
4. **Performance**: Batch operations when setting multiple unread counts
5. **User Experience**: Use meaningful unread counts that help users prioritize
6. **Sync Strategy**: Implement proper sync mechanisms for unread counts
7. **Testing**: Test various unread count scenarios during development
# Sync User Conversations
Source: https://wukong.mintlify.app/en/api/conversation/sync
POST /conversation/sync
Sync user's conversation list and status
## Overview
Sync user's conversation list and status, supporting both incremental and full synchronization.
## Request Body
### Required Parameters
User ID
### Optional Parameters
Version timestamp for incremental sync
Client's last message sequence numbers, format: channelID:channelType:last\_msg\_seq|channelID:channelType:last\_msg\_seq
Number of recent messages to return for each conversation
Whether to return only unread conversations (1=only unread, 0=return all)
Array of channel types to exclude
Channel type
```bash cURL theme={null}
curl -X POST "http://localhost:5001/conversation/sync" \
-H "Content-Type: application/json" \
-d '{
"uid": "user123",
"version": 1640995200000000000,
"last_msg_seqs": "user1:1:100|group1:2:200",
"msg_count": 10,
"only_unread": 0,
"exclude_channel_types": [3, 4]
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/conversation/sync', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
uid: 'user123',
version: 1640995200000000000,
last_msg_seqs: 'user1:1:100|group1:2:200',
msg_count: 10,
only_unread: 0,
exclude_channel_types: [3, 4]
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"uid": "user123",
"version": 1640995200000000000,
"last_msg_seqs": "user1:1:100|group1:2:200",
"msg_count": 10,
"only_unread": 0,
"exclude_channel_types": [3, 4]
}
response = requests.post('http://localhost:5001/conversation/sync', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"uid": "user123",
"version": 1640995200000000000,
"last_msg_seqs": "user1:1:100|group1:2:200",
"msg_count": 10,
"only_unread": 0,
"exclude_channel_types": []int{3, 4},
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/conversation/sync",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result []map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
[
{
"channel_id": "group123",
"channel_type": 2,
"unread": 5,
"timestamp": 1640995200,
"last_msg_seq": 1005,
"version": 1640995200000000000,
"recents": [
{
"message_id": 123456789,
"message_seq": 1005,
"client_msg_no": "msg_123",
"from_uid": "user456",
"timestamp": 1640995200,
"payload": "SGVsbG8gV29ybGQ="
}
]
},
{
"channel_id": "private_user123_user789",
"channel_type": 1,
"unread": 2,
"timestamp": 1640995100,
"last_msg_seq": 502,
"version": 1640995100000000000,
"recents": [
{
"message_id": 123456788,
"message_seq": 502,
"client_msg_no": "msg_122",
"from_uid": "user789",
"timestamp": 1640995100,
"payload": "SGkgdGhlcmU="
}
]
}
]
```
## Response Fields
The response is an array of conversations, each containing the following fields:
### Conversation Information
Channel ID
Channel type
* `1` - Personal channel
* `2` - Group channel
Number of unread messages
Last message timestamp
Last message sequence number
Conversation version number (nanosecond timestamp)
### Message List
List of latest messages in the conversation
Message ID
Message sequence number
Client message number
Sender user ID
Message timestamp
Base64 encoded message content
## Status Codes
| Status Code | Description |
| ----------- | ---------------------------- |
| 200 | Conversation sync successful |
| 400 | Request parameter error |
| 403 | No access permission |
| 500 | Internal server error |
## Parameter Details
### Message Count (msg\_count)
Controls the number of messages returned for each conversation:
| Value | Description | Use Case |
| ----- | ----------------------- | --------------------------- |
| 0 | No messages returned | Only need conversation list |
| 1-50 | Return specified number | Normal usage |
| > 50 | System limited to 50 | Avoid excessive data |
### Version-based Incremental Sync
Use version parameter for efficient incremental synchronization:
```javascript theme={null}
// First sync - get all conversations
const initialSync = await syncConversations({
uid: "user123",
msg_count: 1
});
// Save the latest version
const latestVersion = Math.max(...initialSync.map(conv => conv.version));
// Later incremental sync - only get updated conversations
const incrementalSync = await syncConversations({
uid: "user123",
version: latestVersion,
msg_count: 1
});
```
### Last Message Sequence Tracking
Track message sequences to detect missed messages:
```javascript theme={null}
// Build last_msg_seqs string from current conversations
function buildLastMsgSeqs(conversations) {
return conversations
.map(conv => `${conv.channel_id}:${conv.channel_type}:${conv.last_msg_seq}`)
.join('|');
}
// Use in sync request
const lastMsgSeqs = buildLastMsgSeqs(currentConversations);
const syncResult = await syncConversations({
uid: "user123",
last_msg_seqs: lastMsgSeqs,
msg_count: 5
});
```
## Use Cases
### Chat List Display
**Initial Load**:
```javascript theme={null}
// Load conversation list for chat interface
const conversations = await syncConversations({
uid: "user123",
msg_count: 1,
exclude_channel_types: [3, 4] // Exclude system channels
});
// Display in UI
conversations.forEach(conv => {
displayConversation(conv);
});
```
**Unread Badge Update**:
```javascript theme={null}
// Get only unread conversations for badge updates
const unreadConversations = await syncConversations({
uid: "user123",
only_unread: 1,
msg_count: 0
});
const totalUnread = unreadConversations.reduce((sum, conv) => sum + conv.unread, 0);
updateUnreadBadge(totalUnread);
```
### Real-time Sync
**Periodic Sync**:
```javascript theme={null}
let lastSyncVersion = 0;
async function periodicSync() {
const conversations = await syncConversations({
uid: "user123",
version: lastSyncVersion,
msg_count: 1
});
if (conversations.length > 0) {
updateConversationList(conversations);
lastSyncVersion = Math.max(...conversations.map(c => c.version));
}
}
// Sync every 30 seconds
setInterval(periodicSync, 30000);
```
### Offline Recovery
**Sync After Reconnection**:
```javascript theme={null}
async function syncAfterReconnection(uid, lastKnownVersion) {
try {
const missedConversations = await syncConversations({
uid: uid,
version: lastKnownVersion,
msg_count: 10
});
// Process missed conversations
missedConversations.forEach(conv => {
updateConversation(conv);
// Show notification for new messages
if (conv.unread > 0) {
showNewMessageNotification(conv);
}
});
} catch (error) {
console.error('Failed to sync conversations:', error);
}
}
```
## Best Practices
1. **Incremental Sync**: Use version-based incremental sync to reduce data transfer
2. **Appropriate Message Count**: Set reasonable msg\_count based on UI needs
3. **Error Handling**: Handle network errors and implement retry logic
4. **Caching**: Cache conversation data locally to improve performance
5. **Real-time Updates**: Combine with WebSocket events for real-time updates
6. **Filtering**: Use exclude\_channel\_types to filter out unwanted channel types
7. **Batch Processing**: Process conversation updates in batches for better performance
# Send Event
Source: https://wukong.mintlify.app/en/api/event/send
POST /event
Send various types of events to channels, including streaming text messages and custom events
## Overview
Send various types of events to channels, including streaming text messages and custom events. Supports AG-UI protocol events for real-time streaming communication.
## Query Parameters
Force end existing streams in the channel before starting a new one
* `0` - Do not force end
* `1` - Force end existing streams
## Request Body
### Required Parameters
Client message number - must be unique and not repeated. Used to identify and track the message/stream. For streaming messages, all events in the same stream should use the same client\_msg\_no. UUID format is recommended.
Target channel ID where the event will be sent. For person channels, this should be the target user ID. For group channels, this should be the group ID.
Channel type
* `1` - Person channel
* `2` - Group channel
Event object
Event type - supports AG-UI protocol events and custom events
**AG-UI Protocol Events:**
* `___TextMessageStart` - Initiates a streaming text message session
* `___TextMessageContent` - Sends content chunks during streaming
* `___TextMessageEnd` - Terminates a streaming text message session
* `___ToolCallStart` - Begins a tool/function call event
* `___ToolCallArgs` - Sends arguments for tool calls
* `___ToolCallEnd` - Ends a tool call event
* `___ToolCallResult` - Returns results from tool execution
**Custom Events:** Any string not starting with `___` is treated as a custom event type
Event ID (optional, auto-generated for some event types)
Event timestamp (optional, Unix timestamp in milliseconds)
Event data content. The format depends on the event type:
**For Text Message Events:**
* `___TextMessageStart` - Initial message content or metadata
* `___TextMessageContent` - Text chunk for streaming
* `___TextMessageEnd` - Final content or completion marker
**For Tool Call Events:**
* `___ToolCallStart` - Tool name or metadata
* `___ToolCallArgs` - JSON string with function arguments
* `___ToolCallEnd` - Completion status
* `___ToolCallResult` - JSON string with execution results
**For Custom Events:** Any string data relevant to your application
### Optional Parameters
Sender user ID. If not provided or empty, defaults to the system UID. This identifies who is sending the event.
```bash Start Streaming Text Message theme={null}
curl -X POST "http://localhost:5001/event" \
-H "Content-Type: application/json" \
-d '{
"client_msg_no": "msg_001_stream_start",
"channel_id": "group_ai_chat",
"channel_type": 2,
"from_uid": "ai_assistant",
"event": {
"type": "___TextMessageStart",
"data": "{\"type\":1,\"content\":\"Starting AI response...\"}"
}
}'
```
```bash Send Text Content Chunk theme={null}
curl -X POST "http://localhost:5001/event" \
-H "Content-Type: application/json" \
-d '{
"client_msg_no": "msg_001_stream_start",
"channel_id": "group_ai_chat",
"channel_type": 2,
"from_uid": "ai_assistant",
"event": {
"type": "___TextMessageContent",
"data": "Hello! How can I help you today?"
}
}'
```
```bash End Streaming Text Message theme={null}
curl -X POST "http://localhost:5001/event" \
-H "Content-Type: application/json" \
-d '{
"client_msg_no": "msg_001_stream_start",
"channel_id": "group_ai_chat",
"channel_type": 2,
"from_uid": "ai_assistant",
"event": {
"type": "___TextMessageEnd",
"data": ""
}
}'
```
```bash Send Custom Event theme={null}
curl -X POST "http://localhost:5001/event" \
-H "Content-Type: application/json" \
-d '{
"client_msg_no": "custom_event_001",
"channel_id": "user_123",
"channel_type": 1,
"from_uid": "system",
"event": {
"type": "user_status_update",
"timestamp": 1640995200000,
"data": "{\"status\": \"online\", \"last_seen\": 1640995200000}"
}
}'
```
```javascript JavaScript theme={null}
// Streaming text message example
const streamId = `stream_${Date.now()}`;
// 1. Start stream
await fetch('http://localhost:5001/event', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_msg_no: streamId,
channel_id: 'group_ai_chat',
channel_type: 2,
from_uid: 'ai_assistant',
event: {
type: '___TextMessageStart',
data: 'Starting AI response...'
}
})
});
// 2. Send content
await fetch('http://localhost:5001/event', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_msg_no: streamId,
channel_id: 'group_ai_chat',
channel_type: 2,
from_uid: 'ai_assistant',
event: {
type: '___TextMessageContent',
data: 'Hello! How can I help you today?'
}
})
});
// 3. End stream
await fetch('http://localhost:5001/event', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_msg_no: streamId,
channel_id: 'group_ai_chat',
channel_type: 2,
from_uid: 'ai_assistant',
event: {
type: '___TextMessageEnd',
data: ''
}
})
});
```
```python Python theme={null}
import requests
import time
# Streaming text message example
stream_id = f"stream_{int(time.time() * 1000)}"
base_url = "http://localhost:5001/event"
# 1. Start stream
requests.post(base_url, json={
"client_msg_no": stream_id,
"channel_id": "group_ai_chat",
"channel_type": 2,
"from_uid": "ai_assistant",
"event": {
"type": "___TextMessageStart",
"data": "Starting AI response..."
}
})
# 2. Send content
requests.post(base_url, json={
"client_msg_no": stream_id,
"channel_id": "group_ai_chat",
"channel_type": 2,
"from_uid": "ai_assistant",
"event": {
"type": "___TextMessageContent",
"data": "Hello! How can I help you today?"
}
})
# 3. End stream
requests.post(base_url, json={
"client_msg_no": stream_id,
"channel_id": "group_ai_chat",
"channel_type": 2,
"from_uid": "ai_assistant",
"event": {
"type": "___TextMessageEnd",
"data": ""
}
})
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
)
type Event struct {
Type string `json:"type"`
Data string `json:"data"`
Timestamp int64 `json:"timestamp,omitempty"`
}
type EventRequest struct {
ClientMsgNo string `json:"client_msg_no"`
ChannelID string `json:"channel_id"`
ChannelType int `json:"channel_type"`
FromUID string `json:"from_uid"`
Event Event `json:"event"`
}
func sendEvent(req EventRequest) error {
jsonData, _ := json.Marshal(req)
resp, err := http.Post(
"http://localhost:5001/event",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}
func main() {
streamID := fmt.Sprintf("stream_%d", time.Now().UnixMilli())
// 1. Start stream
sendEvent(EventRequest{
ClientMsgNo: streamID,
ChannelID: "group_ai_chat",
ChannelType: 2,
FromUID: "ai_assistant",
Event: Event{
Type: "___TextMessageStart",
Data: "Starting AI response...",
},
})
// 2. Send content
sendEvent(EventRequest{
ClientMsgNo: streamID,
ChannelID: "group_ai_chat",
ChannelType: 2,
FromUID: "ai_assistant",
Event: Event{
Type: "___TextMessageContent",
Data: "Hello! How can I help you today?",
},
})
// 3. End stream
sendEvent(EventRequest{
ClientMsgNo: streamID,
ChannelID: "group_ai_chat",
ChannelType: 2,
FromUID: "ai_assistant",
Event: Event{
Type: "___TextMessageEnd",
Data: "",
},
})
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | ---------------------------------------------- |
| 200 | Event sent successfully |
| 400 | Bad request - invalid parameters or event data |
| 500 | Internal server error |
## Streaming Message Flow
### Streaming Message Process
1. **Start Stream**: Send `___TextMessageStart` event to initiate a stream
2. **Send Content**: Send multiple `___TextMessageContent` events with message chunks
3. **End Stream**: Send `___TextMessageEnd` event to close the stream
### Important Notes
* The same `client_msg_no` must be used for all events in a streaming session
* Only one stream can be active per channel unless `force=1` is used
* For person channels, the system automatically handles fake channel ID generation
* Events are automatically routed to the appropriate cluster node
## Event Types
### AG-UI Protocol Events
AG-UI protocol events enable real-time streaming communication for AI applications:
| Event Type | Purpose | Data Format |
| ----------------------- | ------------------------------------------- | --------------------------- |
| `___TextMessageStart` | Initiates a streaming text message session | Initial content or metadata |
| `___TextMessageContent` | Sends content chunks during streaming | Text chunk content |
| `___TextMessageEnd` | Terminates a streaming text message session | Completion marker |
| `___ToolCallStart` | Begins a tool/function call event | Tool name or metadata |
| `___ToolCallArgs` | Sends arguments for tool calls | JSON formatted arguments |
| `___ToolCallEnd` | Ends a tool call event | Completion status |
| `___ToolCallResult` | Returns results from tool execution | JSON formatted results |
### Custom Events
Any event type not starting with `___` is treated as a custom event, useful for:
* User status updates
* System notifications
* Business logic events
* Application-specific interactions
## Use Cases
### AI Chatbot
```bash theme={null}
# Simulate AI typing effect
curl -X POST "/event" -d '{
"client_msg_no": "ai_response_001",
"channel_id": "user_123",
"channel_type": 1,
"from_uid": "ai_bot",
"event": {
"type": "___TextMessageStart",
"data": "Thinking..."
}
}'
# Gradually send response content
curl -X POST "/event" -d '{
"client_msg_no": "ai_response_001",
"channel_id": "user_123",
"channel_type": 1,
"from_uid": "ai_bot",
"event": {
"type": "___TextMessageContent",
"data": "Based on your question, I recommend..."
}
}'
```
### Real-time Collaboration
```bash theme={null}
# Document editing status
curl -X POST "/event" -d '{
"client_msg_no": "doc_edit_001",
"channel_id": "doc_room_456",
"channel_type": 2,
"from_uid": "user_789",
"event": {
"type": "document_editing",
"data": "{\"action\": \"start_edit\", \"section\": \"paragraph_1\"}"
}
}'
```
### System Notifications
```bash theme={null}
# User online notification
curl -X POST "/event" -d '{
"client_msg_no": "status_update_001",
"channel_id": "group_general",
"channel_type": 2,
"from_uid": "system",
"event": {
"type": "user_online",
"timestamp": 1640995200000,
"data": "{\"user_id\": \"user_123\", \"status\": \"online\"}"
}
}'
```
## Best Practices
1. **Unique Identification**: Use UUID format for `client_msg_no` to ensure uniqueness
2. **Stream Management**: Close unused streams promptly to avoid resource waste
3. **Error Handling**: Handle stream conflicts and send failures
4. **Permission Verification**: Ensure sender has channel send permissions
5. **Data Format**: Use JSON format strings for complex data
6. **Performance Optimization**: Control streaming message send frequency reasonably
## Error Handling
### Common Errors
| Error | Cause | Solution |
| --------------------------------- | --------------------------------------------- | ------------------------------------------------ |
| Event type cannot be empty | No event.type provided | Ensure valid event type is provided |
| Stream already running in channel | Trying to start new stream when one is active | Use `force=1` or wait for existing stream to end |
| Stream does not exist | Trying to send content to non-existent stream | Check if stream was created correctly |
| Stream is already closed | Sending content to closed stream | Start a new stream |
### Retry Mechanism
For temporary errors, implement exponential backoff retry mechanism:
```javascript theme={null}
async function sendEventWithRetry(eventData, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch('/event', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(eventData)
});
if (response.ok) return await response.json();
if (response.status >= 400 && response.status < 500) {
// Client error, don't retry
throw new Error(`Client error: ${response.status}`);
}
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
}
}
}
```
# API Introduction
Source: https://wukong.mintlify.app/en/api/introduction
Complete guide to WuKongIM REST API, including security specifications, request formats, and response standards
## Overview
WuKongIM provides a complete REST API interface for managing all aspects of the instant messaging system, including message sending, channel management, user management, conversation management, and other functions.
**Important Security Notice**: WuKongIM API is designed for internal business systems only. It must not be exposed to external networks to prevent security issues.
## Security Considerations
### 🔒 Internal Use Restrictions
* **Internal use only**: All API interfaces are for internal business system calls only
* **No external exposure**: Strictly prohibit exposing API services to public networks or external networks
* **Port protection**: Must block external network access to port 5001
* **Network isolation**: Recommended to deploy and use in internal network environments
### 🛡️ Security Best Practices
* Block external access to port 5001 in firewall
* Use VPN or internal network environment for API calls
* Regularly review network access permissions and logs
* Avoid exposing API endpoint information in public documentation
## Basic Information
### Server Address
```bash theme={null}
# Internal network environment (recommended)
http://localhost:5001
# Or internal IP
http://192.168.1.100:5001
```
### Authentication Mechanism
WuKongIM API **does not require Token authentication** because it is designed for internal systems only. All API calls are direct access without additional authentication headers.
## API Standards and Rules
### 📊 HTTP Status Code Standards
WuKongIM API follows standard HTTP status code specifications:
#### ✅ Success Response (200)
* **HTTP 200**: Indicates successful request execution
* Some interfaces only need to check HTTP 200 status code without parsing JSON response
* Success responses may contain data or just return status confirmation
#### ❌ Failure Response (non-200)
* **HTTP 400**: Request parameter error
* **HTTP 500**: Internal server error
### 🔧 Request Format Specifications
#### Content-Type
All POST requests must use JSON format:
```bash theme={null}
Content-Type: application/json
```
#### Request Body Example
```json theme={null}
{
"channel_id": "group123",
"channel_type": 2,
"from_uid": "user123",
"payload": "SGVsbG8gV29ybGQ="
}
```
### 📋 Response Format Specifications
#### Success Response Example
```json theme={null}
{
"message_id": 123456789,
"message_seq": 1001,
"client_msg_no": "client_msg_123"
}
```
#### Error Response Format (HTTP 400)
```json theme={null}
{
"msg": "channel_id parameter cannot be empty",
"status": 400
}
```
**Documentation Note**: This documentation only shows parameter descriptions for success responses. Error response format is unified as shown above.
## Core Data Types
### 📁 Channel Type (channel\_type)
| Value | Type | Description |
| ----- | ---------------- | ------------------------------- |
| 1 | Personal Channel | One-on-one private chat channel |
| 2 | Group Channel | Multi-user group chat channel |
### 📱 Device Flag (device\_flag)
| Flag Value | Device Type | Description |
| ---------- | ----------- | ----------------------------- |
| 0 | App | Android, iPhone, iPad devices |
| 1 | Web | Browser, Web applications |
| 2 | Desktop | Desktop applications |
### 💬 Message Format
Message content is transmitted using **Base64 encoding**:
```json theme={null}
{
"payload": "SGVsbG8gV29ybGQ=", // Base64 encoded message content
"from_uid": "user123",
"channel_id": "group123",
"channel_type": 2
}
```
### 🔑 Important Parameter Specifications
#### channel\_id Usage Specifications
* **Direct use**: `channel_id` parameter should be used directly without adding `@` prefix
* **Correct example**: `"channel_id": "group123"`
* **Incorrect example**: `"channel_id": "@group123"`
Do not add `@` symbol before `channel_id` parameter, use the original ID value directly.
# Manager Login
Source: https://wukong.mintlify.app/en/api/manager/login
POST /manager/login
Manager user login to obtain access token
## Overview
Manager user login interface for obtaining access tokens for the management backend.
## Request Body
### Required Parameters
Manager username
Manager password
```bash cURL theme={null}
curl -X POST "http://localhost:5001/manager/login" \
-H "Content-Type: application/json" \
-d '{
"username": "admin",
"password": "your_password"
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/manager/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
username: 'admin',
password: 'your_password'
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"username": "admin",
"password": "your_password"
}
response = requests.post('http://localhost:5001/manager/login', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]string{
"username": "admin",
"password": "your_password",
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/manager/login",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expire": 3600,
"user": {
"username": "admin",
"role": "administrator",
"permissions": ["read", "write", "admin"]
}
}
```
```json Error Response theme={null}
{
"error": "Invalid username or password"
}
```
## Response Fields
Access token for authentication in subsequent API calls
Token expiration time (seconds)
User information
Username
User role
User permissions list
## Status Codes
| Status Code | Description |
| ----------- | ---------------------------- |
| 200 | Login successful |
| 401 | Invalid username or password |
| 429 | Too many login attempts |
| 500 | Internal server error |
## Best Practices
1. **Password Security**: Use strong password policies, change passwords regularly
2. **Token Management**: Implement automatic token refresh mechanism
3. **Access Control**: Role-based and permission-based access control
4. **Login Restrictions**: Implement login attempt limits
5. **Session Management**: Set reasonable token expiration times
6. **Secure Storage**: Don't store sensitive information in insecure places
# Batch Message Search
Source: https://wukong.mintlify.app/en/api/message/batch-search
POST /messages
Batch search multiple messages by message ID list
## Overview
Batch search multiple messages by message ID list, suitable for scenarios where you need to retrieve details of multiple messages simultaneously.
## Request Body
### Required Parameters
List of message IDs
Message ID
```bash cURL theme={null}
curl -X POST "http://localhost:5001/messages" \
-H "Content-Type: application/json" \
-d '{
"message_ids": [123456789, 123456790, 123456791]
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
message_ids: [123456789, 123456790, 123456791]
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"message_ids": [123456789, 123456790, 123456791]
}
response = requests.post('http://localhost:5001/messages', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"message_ids": []int64{123456789, 123456790, 123456791},
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/messages",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result []map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
[
{
"message_id": 123456789,
"message_seq": 1001,
"client_msg_no": "msg_123",
"from_uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"timestamp": 1640995200,
"payload": "SGVsbG8gV29ybGQ="
},
{
"message_id": 123456790,
"message_seq": 1002,
"client_msg_no": "msg_124",
"from_uid": "user456",
"channel_id": "group123",
"channel_type": 2,
"timestamp": 1640995260,
"payload": "SGkgdGhlcmU="
}
]
```
## Response Fields
The response is an array of message objects, each containing the following fields:
Server-generated message ID
Message sequence number
Client message number
Sender user ID
Channel ID
Channel type
* `1` - Personal channel
* `2` - Group channel
Message timestamp (Unix timestamp)
Base64 encoded message content
## Status Codes
| Status Code | Description |
| ----------- | ------------------------------ |
| 200 | Message search successful |
| 400 | Request parameter error |
| 404 | Some or all messages not found |
| 500 | Internal server error |
## Use Cases
### Message Details Retrieval
**Get Multiple Message Details**:
```javascript theme={null}
// Retrieve details for multiple messages
async function getMessageDetails(messageIds) {
try {
const response = await fetch('/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message_ids: messageIds })
});
const messages = await response.json();
// Process and decode messages
const processedMessages = messages.map(msg => ({
...msg,
content: atob(msg.payload), // Decode base64 content
formatted_time: new Date(msg.timestamp * 1000).toLocaleString()
}));
return processedMessages;
} catch (error) {
console.error('Failed to get message details:', error);
return [];
}
}
// Usage
const messageIds = [123456789, 123456790, 123456791];
const messageDetails = await getMessageDetails(messageIds);
console.log('Message details:', messageDetails);
```
### Message Thread Reconstruction
**Reconstruct Message Threads**:
```javascript theme={null}
// Reconstruct conversation threads from message IDs
async function reconstructMessageThread(messageIds) {
try {
const messages = await getMessageDetails(messageIds);
// Sort messages by timestamp
messages.sort((a, b) => a.timestamp - b.timestamp);
// Group by channel
const threadsByChannel = messages.reduce((acc, msg) => {
const channelKey = `${msg.channel_id}:${msg.channel_type}`;
if (!acc[channelKey]) {
acc[channelKey] = [];
}
acc[channelKey].push(msg);
return acc;
}, {});
return threadsByChannel;
} catch (error) {
console.error('Failed to reconstruct message thread:', error);
return {};
}
}
// Usage
const threadMessageIds = [123456789, 123456790, 123456791, 123456792];
const threads = await reconstructMessageThread(threadMessageIds);
for (const [channelKey, messages] of Object.entries(threads)) {
console.log(`Thread for ${channelKey}:`, messages);
}
```
### Message Validation and Verification
**Validate Message Existence**:
```javascript theme={null}
// Validate that messages exist and are accessible
async function validateMessages(messageIds) {
try {
const response = await fetch('/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message_ids: messageIds })
});
const foundMessages = await response.json();
const foundIds = foundMessages.map(msg => msg.message_id);
const missingIds = messageIds.filter(id => !foundIds.includes(id));
return {
found: foundMessages,
missing: missingIds,
foundCount: foundMessages.length,
missingCount: missingIds.length,
totalRequested: messageIds.length
};
} catch (error) {
console.error('Message validation failed:', error);
return {
found: [],
missing: messageIds,
foundCount: 0,
missingCount: messageIds.length,
totalRequested: messageIds.length
};
}
}
// Usage
const idsToValidate = [123456789, 123456790, 999999999]; // Last ID doesn't exist
const validation = await validateMessages(idsToValidate);
console.log(`Found ${validation.foundCount}/${validation.totalRequested} messages`);
if (validation.missingCount > 0) {
console.log('Missing message IDs:', validation.missing);
}
```
### Message Export and Backup
**Export Messages for Backup**:
```javascript theme={null}
// Export messages with full details for backup
class MessageExporter {
constructor() {
this.batchSize = 100; // Process in batches
}
async exportMessages(messageIds) {
const batches = this.chunkArray(messageIds, this.batchSize);
const allMessages = [];
for (let i = 0; i < batches.length; i++) {
const batch = batches[i];
console.log(`Processing batch ${i + 1}/${batches.length} (${batch.length} messages)`);
try {
const batchMessages = await this.getBatchMessages(batch);
allMessages.push(...batchMessages);
// Small delay between batches to avoid overwhelming the server
await this.delay(100);
} catch (error) {
console.error(`Failed to process batch ${i + 1}:`, error);
}
}
return this.formatExportData(allMessages);
}
async getBatchMessages(messageIds) {
const response = await fetch('/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message_ids: messageIds })
});
return await response.json();
}
formatExportData(messages) {
return {
export_timestamp: new Date().toISOString(),
message_count: messages.length,
messages: messages.map(msg => ({
...msg,
content: atob(msg.payload),
formatted_timestamp: new Date(msg.timestamp * 1000).toISOString()
}))
};
}
chunkArray(array, size) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage
const exporter = new MessageExporter();
const messageIds = Array.from({length: 500}, (_, i) => 123456789 + i);
const exportData = await exporter.exportMessages(messageIds);
console.log(`Exported ${exportData.message_count} messages`);
// Save to file or send to backup service
const exportJson = JSON.stringify(exportData, null, 2);
// saveToFile('message_export.json', exportJson);
```
### Message Analytics
**Analyze Message Patterns**:
```javascript theme={null}
// Analyze patterns in batch of messages
async function analyzeMessageBatch(messageIds) {
try {
const messages = await getMessageDetails(messageIds);
const analysis = {
total_messages: messages.length,
unique_senders: new Set(messages.map(m => m.from_uid)).size,
unique_channels: new Set(messages.map(m => `${m.channel_id}:${m.channel_type}`)).size,
time_range: {
earliest: Math.min(...messages.map(m => m.timestamp)),
latest: Math.max(...messages.map(m => m.timestamp))
},
channel_distribution: {},
sender_distribution: {},
message_types: {}
};
// Analyze channel distribution
messages.forEach(msg => {
const channelKey = `${msg.channel_id}:${msg.channel_type}`;
analysis.channel_distribution[channelKey] =
(analysis.channel_distribution[channelKey] || 0) + 1;
});
// Analyze sender distribution
messages.forEach(msg => {
analysis.sender_distribution[msg.from_uid] =
(analysis.sender_distribution[msg.from_uid] || 0) + 1;
});
// Calculate time span
analysis.time_span_hours =
(analysis.time_range.latest - analysis.time_range.earliest) / 3600;
return analysis;
} catch (error) {
console.error('Message analysis failed:', error);
return null;
}
}
// Usage
const analysisMessageIds = [123456789, 123456790, 123456791, 123456792, 123456793];
const analysis = await analyzeMessageBatch(analysisMessageIds);
if (analysis) {
console.log('Message Analysis:');
console.log(`- Total messages: ${analysis.total_messages}`);
console.log(`- Unique senders: ${analysis.unique_senders}`);
console.log(`- Unique channels: ${analysis.unique_channels}`);
console.log(`- Time span: ${analysis.time_span_hours.toFixed(2)} hours`);
console.log('- Channel distribution:', analysis.channel_distribution);
}
```
### Message Cache Management
**Efficient Message Caching**:
```javascript theme={null}
// Cache management for frequently accessed messages
class MessageCache {
constructor(maxSize = 1000) {
this.cache = new Map();
this.maxSize = maxSize;
this.accessOrder = [];
}
async getMessages(messageIds) {
const cached = [];
const uncached = [];
// Check cache first
messageIds.forEach(id => {
if (this.cache.has(id)) {
cached.push(this.cache.get(id));
this.updateAccessOrder(id);
} else {
uncached.push(id);
}
});
// Fetch uncached messages
let fetchedMessages = [];
if (uncached.length > 0) {
fetchedMessages = await this.fetchMessages(uncached);
// Add to cache
fetchedMessages.forEach(msg => {
this.addToCache(msg.message_id, msg);
});
}
// Combine and sort by original order
const allMessages = [...cached, ...fetchedMessages];
return messageIds.map(id =>
allMessages.find(msg => msg.message_id === id)
).filter(Boolean);
}
async fetchMessages(messageIds) {
const response = await fetch('/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message_ids: messageIds })
});
return await response.json();
}
addToCache(messageId, message) {
// Remove oldest if cache is full
if (this.cache.size >= this.maxSize) {
const oldestId = this.accessOrder.shift();
this.cache.delete(oldestId);
}
this.cache.set(messageId, message);
this.accessOrder.push(messageId);
}
updateAccessOrder(messageId) {
const index = this.accessOrder.indexOf(messageId);
if (index > -1) {
this.accessOrder.splice(index, 1);
this.accessOrder.push(messageId);
}
}
getCacheStats() {
return {
size: this.cache.size,
maxSize: this.maxSize,
utilization: (this.cache.size / this.maxSize * 100).toFixed(2) + '%'
};
}
}
// Usage
const messageCache = new MessageCache(500);
// First request - will fetch from server
const messages1 = await messageCache.getMessages([123456789, 123456790]);
console.log('First request:', messages1.length, 'messages');
// Second request - will use cache
const messages2 = await messageCache.getMessages([123456789, 123456791]);
console.log('Second request:', messages2.length, 'messages');
console.log('Cache stats:', messageCache.getCacheStats());
```
## Best Practices
1. **Batch Size**: Use reasonable batch sizes (50-100 messages) to balance performance and memory usage
2. **Error Handling**: Handle partial failures gracefully when some messages are not found
3. **Caching**: Implement caching for frequently accessed messages
4. **Rate Limiting**: Respect rate limits when making multiple batch requests
5. **Memory Management**: Process large batches in chunks to avoid memory issues
6. **Validation**: Validate message IDs before making requests
7. **Performance**: Use batch search instead of individual requests for better performance
# Batch Send Messages
Source: https://wukong.mintlify.app/en/api/message/batch-send
POST /message/sendbatch
Send multiple messages in batch
## Overview
Send multiple messages in batch to improve message sending efficiency, suitable for group notifications, bulk push, and other scenarios.
## Request Body
The request body is an array of message objects, each message object contains the following fields:
### Required Parameters
Base64 encoded message content
Sender user ID
Target channel ID
Channel type (1=personal channel, 2=group channel)
### Optional Parameters
Message header information
Whether to not persist message (0=persist, 1=do not persist)
Whether to show red dot notification (0=do not show, 1=show)
Whether it's write diffusion, generally 0, only cmd messages are 1
Client message number
Stream message number
Message expiration time (seconds), 0 means no expiration
Specified list of subscribers to receive the message
Subscriber user ID
```bash cURL theme={null}
curl -X POST "http://localhost:5001/message/sendbatch" \
-H "Content-Type: application/json" \
-d '[
{
"header": {
"no_persist": 0,
"red_dot": 1,
"sync_once": 0
},
"client_msg_no": "batch_msg_1",
"from_uid": "system",
"channel_id": "group123",
"channel_type": 2,
"expire": 0,
"payload": "SGVsbG8gR3JvdXAgMQ==",
"tag_key": "notification"
},
{
"header": {
"no_persist": 0,
"red_dot": 1,
"sync_once": 0
},
"client_msg_no": "batch_msg_2",
"from_uid": "system",
"channel_id": "group456",
"channel_type": 2,
"expire": 0,
"payload": "SGVsbG8gR3JvdXAgMg==",
"tag_key": "notification"
}
]'
```
```javascript JavaScript theme={null}
const messages = [
{
header: {
no_persist: 0,
red_dot: 1,
sync_once: 0
},
client_msg_no: `batch_msg_${Date.now()}_1`,
from_uid: "system",
channel_id: "group123",
channel_type: 2,
expire: 0,
payload: btoa(JSON.stringify({
type: "text",
content: "Hello Group 1"
})),
tag_key: "notification"
},
{
header: {
no_persist: 0,
red_dot: 1,
sync_once: 0
},
client_msg_no: `batch_msg_${Date.now()}_2`,
from_uid: "system",
channel_id: "group456",
channel_type: 2,
expire: 0,
payload: btoa(JSON.stringify({
type: "text",
content: "Hello Group 2"
})),
tag_key: "notification"
}
];
const response = await fetch('http://localhost:5001/message/sendbatch', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(messages)
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
import base64
import json
messages = [
{
"header": {
"no_persist": 0,
"red_dot": 1,
"sync_once": 0
},
"client_msg_no": "batch_msg_1",
"from_uid": "system",
"channel_id": "group123",
"channel_type": 2,
"expire": 0,
"payload": base64.b64encode(
json.dumps({"type": "text", "content": "Hello Group 1"}).encode()
).decode(),
"tag_key": "notification"
},
{
"header": {
"no_persist": 0,
"red_dot": 1,
"sync_once": 0
},
"client_msg_no": "batch_msg_2",
"from_uid": "system",
"channel_id": "group456",
"channel_type": 2,
"expire": 0,
"payload": base64.b64encode(
json.dumps({"type": "text", "content": "Hello Group 2"}).encode()
).decode(),
"tag_key": "notification"
}
]
response = requests.post('http://localhost:5001/message/sendbatch', json=messages)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
)
func main() {
message1Content := map[string]interface{}{
"type": "text",
"content": "Hello Group 1",
}
content1, _ := json.Marshal(message1Content)
payload1 := base64.StdEncoding.EncodeToString(content1)
message2Content := map[string]interface{}{
"type": "text",
"content": "Hello Group 2",
}
content2, _ := json.Marshal(message2Content)
payload2 := base64.StdEncoding.EncodeToString(content2)
messages := []map[string]interface{}{
{
"header": map[string]interface{}{
"no_persist": 0,
"red_dot": 1,
"sync_once": 0,
},
"client_msg_no": "batch_msg_1",
"from_uid": "system",
"channel_id": "group123",
"channel_type": 2,
"expire": 0,
"payload": payload1,
"tag_key": "notification",
},
{
"header": map[string]interface{}{
"no_persist": 0,
"red_dot": 1,
"sync_once": 0,
},
"client_msg_no": "batch_msg_2",
"from_uid": "system",
"channel_id": "group456",
"channel_type": 2,
"expire": 0,
"payload": payload2,
"tag_key": "notification",
},
}
jsonData, _ := json.Marshal(messages)
resp, err := http.Post(
"http://localhost:5001/message/sendbatch",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result []map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
[
{
"message_id": 123456789,
"message_seq": 1001,
"client_msg_no": "batch_msg_1"
},
{
"message_id": 123456790,
"message_seq": 1002,
"client_msg_no": "batch_msg_2"
}
]
```
## Response Fields
The response is an array, each element corresponds to a sent message:
Server-generated message ID
Message sequence number
Client message number (echo)
## Status Codes
| Status Code | Description |
| ----------- | -------------------------------- |
| 200 | Batch messages sent successfully |
| 400 | Request parameter error |
| 403 | No sending permission |
| 500 | Internal server error |
## Use Cases
### System Notifications
* **Announcement Push**: Send system announcements to multiple groups
* **Activity Notifications**: Batch send activity reminder messages
* **Maintenance Notifications**: Batch notifications before system maintenance
### Marketing Promotion
* **Promotional Messages**: Send promotional information to target user groups
* **New Feature Introduction**: Batch push new feature usage guides
* **User Surveys**: Send questionnaire survey messages
### Operations Management
* **Data Statistics**: Batch send data reports
* **Task Assignment**: Batch assign tasks to team members
* **Meeting Notifications**: Batch send meeting invitations
## Performance Optimization
### Batch Size
* **Recommended Batch**: Single batch sending should not exceed 100 messages
* **Batch Processing**: Large volumes of messages can be sent in batches to avoid timeouts
* **Concurrency Control**: Control the number of concurrent batch requests
### Message Optimization
* **Content Compression**: For identical content, use templates to reduce data transmission
* **Asynchronous Processing**: Use asynchronous methods to handle batch sending
* **Error Retry**: Implement retry mechanism for failed messages
## Best Practices
1. **Message Deduplication**: Ensure each message's client\_msg\_no is unique
2. **Error Handling**: Handle cases where some messages fail to send
3. **Permission Verification**: Verify sender's sending permission for all target channels
4. **Content Review**: Review batch message content
5. **Rate Limiting**: Implement reasonable batch sending rate limits
6. **Monitoring & Alerts**: Monitor batch sending success rate and performance
# Get Channel Max Message Sequence
Source: https://wukong.mintlify.app/en/api/message/max-message-seq
GET /channel/max_message_seq
Get the maximum message sequence number for a specified channel
## Overview
Get the maximum message sequence number for a specified channel, used for message synchronization and status checking.
## Query Parameters
Channel ID
Channel type (1=personal channel, 2=group channel)
Current logged-in user ID
```bash cURL theme={null}
curl -X GET "http://localhost:5001/channel/max_message_seq?channel_id=group123&channel_type=2&login_uid=user123"
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/channel/max_message_seq?channel_id=group123&channel_type=2&login_uid=user123');
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
params = {
'channel_id': 'group123',
'channel_type': 2
}
response = requests.get('http://localhost:5001/channel/max_message_seq&login_uid=user123', params=params)
data = response.json()
print(data)
```
```go Go theme={null}
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func main() {
resp, err := http.Get("http://localhost:5001/channel/max_message_seq?channel_id=group123&channel_type=2&login_uid=user123")
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"message_seq": 1500
}
```
```json Channel Not Exists theme={null}
{
"message_seq": 0
}
```
## Response Fields
Maximum message sequence number for the channel. Returns 0 if channel doesn't exist or has no messages.
## Status Codes
| Status Code | Description |
| ----------- | ----------------------------------------------- |
| 200 | Successfully retrieved maximum message sequence |
| 400 | Request parameter error |
| 500 | Internal server error |
## Use Cases
### Message Synchronization
**Check for New Messages**:
```javascript theme={null}
// Check if there are new messages since last sync
async function checkForNewMessages(channelId, channelType, lastKnownSeq) {
try {
const response = await fetch(
`/channel/max_message_seq?channel_id=${channelId}&channel_type=${channelType}&login_uid=user123`
);
const data = await response.json();
const hasNewMessages = data.message_seq > lastKnownSeq;
const newMessageCount = hasNewMessages ? data.message_seq - lastKnownSeq : 0;
return {
hasNewMessages,
newMessageCount,
maxSeq: data.message_seq
};
} catch (error) {
console.error('Failed to check for new messages:', error);
return { hasNewMessages: false, newMessageCount: 0, maxSeq: lastKnownSeq };
}
}
// Usage
const result = await checkForNewMessages('group123', 2, 1450);
if (result.hasNewMessages) {
console.log(`${result.newMessageCount} new messages available`);
// Sync new messages
await syncNewMessages('group123', 2, 1450, result.maxSeq);
}
```
### Offline Message Detection
**Detect Missed Messages After Reconnection**:
```javascript theme={null}
class OfflineMessageDetector {
constructor() {
this.lastSeqMap = new Map(); // Store last known seq for each channel
}
// Store last known sequence before going offline
storeLastSequence(channelId, channelType, seq) {
const key = `${channelId}:${channelType}`;
this.lastSeqMap.set(key, seq);
}
// Check for missed messages after coming back online
async checkMissedMessages(channels) {
const missedMessages = [];
for (const channel of channels) {
try {
const response = await fetch(
`/channel/max_message_seq?channel_id=${channel.id}&channel_type=${channel.type}&login_uid=user123`
);
const data = await response.json();
const key = `${channel.id}:${channel.type}`;
const lastKnownSeq = this.lastSeqMap.get(key) || 0;
if (data.message_seq > lastKnownSeq) {
missedMessages.push({
channelId: channel.id,
channelType: channel.type,
missedCount: data.message_seq - lastKnownSeq,
fromSeq: lastKnownSeq + 1,
toSeq: data.message_seq
});
}
} catch (error) {
console.error(`Failed to check missed messages for ${channel.id}:`, error);
}
}
return missedMessages;
}
}
// Usage
const detector = new OfflineMessageDetector();
// Before going offline
detector.storeLastSequence('group123', 2, 1450);
// After coming back online
const missedMessages = await detector.checkMissedMessages([
{ id: 'group123', type: 2 },
{ id: 'user456', type: 1 }
]);
for (const missed of missedMessages) {
console.log(`Channel ${missed.channelId} has ${missed.missedCount} missed messages`);
// Sync missed messages
await syncMissedMessages(missed);
}
```
### Channel Activity Monitoring
**Monitor Channel Activity**:
```javascript theme={null}
class ChannelActivityMonitor {
constructor(channels, checkInterval = 30000) {
this.channels = channels;
this.checkInterval = checkInterval;
this.lastSeqMap = new Map();
this.isMonitoring = false;
}
async startMonitoring() {
if (this.isMonitoring) return;
this.isMonitoring = true;
// Initialize last sequences
await this.initializeSequences();
// Start periodic checking
this.monitoringInterval = setInterval(() => {
this.checkActivity();
}, this.checkInterval);
console.log('Channel activity monitoring started');
}
stopMonitoring() {
if (this.monitoringInterval) {
clearInterval(this.monitoringInterval);
this.monitoringInterval = null;
}
this.isMonitoring = false;
console.log('Channel activity monitoring stopped');
}
async initializeSequences() {
for (const channel of this.channels) {
try {
const response = await fetch(
`/channel/max_message_seq?channel_id=${channel.id}&channel_type=${channel.type}&login_uid=user123`
);
const data = await response.json();
const key = `${channel.id}:${channel.type}`;
this.lastSeqMap.set(key, data.message_seq);
} catch (error) {
console.error(`Failed to initialize sequence for ${channel.id}:`, error);
}
}
}
async checkActivity() {
for (const channel of this.channels) {
try {
const response = await fetch(
`/channel/max_message_seq?channel_id=${channel.id}&channel_type=${channel.type}&login_uid=user123`
);
const data = await response.json();
const key = `${channel.id}:${channel.type}`;
const lastSeq = this.lastSeqMap.get(key) || 0;
if (data.message_seq > lastSeq) {
const newMessages = data.message_seq - lastSeq;
this.onChannelActivity(channel, newMessages, data.message_seq);
this.lastSeqMap.set(key, data.message_seq);
}
} catch (error) {
console.error(`Failed to check activity for ${channel.id}:`, error);
}
}
}
onChannelActivity(channel, newMessageCount, currentSeq) {
console.log(`Channel ${channel.id} has ${newMessageCount} new messages (seq: ${currentSeq})`);
// Trigger notifications or UI updates
this.notifyChannelActivity(channel, newMessageCount);
}
notifyChannelActivity(channel, count) {
// Implement notification logic
if (count > 0) {
// Show notification badge
updateChannelBadge(channel.id, count);
// Play notification sound for important channels
if (channel.priority === 'high') {
playNotificationSound();
}
}
}
}
// Usage
const monitor = new ChannelActivityMonitor([
{ id: 'group123', type: 2, priority: 'high' },
{ id: 'user456', type: 1, priority: 'normal' }
], 15000); // Check every 15 seconds
await monitor.startMonitoring();
```
### Batch Sequence Checking
**Check Multiple Channels Efficiently**:
```javascript theme={null}
// Check max sequences for multiple channels
async function batchCheckMaxSequences(channels) {
const promises = channels.map(async (channel) => {
try {
const response = await fetch(
`/channel/max_message_seq?channel_id=${channel.id}&channel_type=${channel.type}&login_uid=user123`
);
const data = await response.json();
return {
channelId: channel.id,
channelType: channel.type,
maxSeq: data.message_seq,
success: true
};
} catch (error) {
return {
channelId: channel.id,
channelType: channel.type,
maxSeq: 0,
success: false,
error: error.message
};
}
});
const results = await Promise.all(promises);
// Separate successful and failed results
const successful = results.filter(r => r.success);
const failed = results.filter(r => !r.success);
if (failed.length > 0) {
console.warn('Failed to get max sequence for some channels:', failed);
}
return { successful, failed };
}
// Usage
const channels = [
{ id: 'group123', type: 2 },
{ id: 'group456', type: 2 },
{ id: 'user789', type: 1 }
];
const { successful, failed } = await batchCheckMaxSequences(channels);
console.log('Max sequences:', successful);
```
## Best Practices
1. **Caching**: Cache max sequence values to reduce API calls
2. **Batch Operations**: Check multiple channels efficiently when possible
3. **Error Handling**: Handle network errors gracefully
4. **Rate Limiting**: Avoid excessive polling by using reasonable intervals
5. **Offline Support**: Store last known sequences for offline message detection
6. **Performance**: Use this API for sync decisions rather than full message retrieval
7. **Monitoring**: Implement activity monitoring for real-time updates
# Sync Channel Messages
Source: https://wukong.mintlify.app/en/api/message/message-sync
POST /channel/messagesync
Sync historical messages from a specified channel
## Overview
Sync messages from a specified channel, supporting message retrieval by sequence number range.
## Request Body
### Required Parameters
Current logged-in user ID
Channel ID
Channel type (1=personal channel, 2=group channel)
### Optional Parameters
Starting message sequence number (inclusive)
Ending message sequence number (exclusive)
Maximum number of messages to return, maximum 10000
Pull mode (0=pull down, 1=pull up)
```bash cURL theme={null}
curl -X POST "http://localhost:5001/channel/messagesync" \
-H "Content-Type: application/json" \
-d '{
"login_uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"start_message_seq": 1000,
"end_message_seq": 1100,
"limit": 50,
"pull_mode": 0
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/channel/messagesync', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
login_uid: 'user123',
channel_id: 'group123',
channel_type: 2,
start_message_seq: 1000,
end_message_seq: 1100,
limit: 50,
pull_mode: 0
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"login_uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"start_message_seq": 1000,
"end_message_seq": 1100,
"limit": 50,
"pull_mode": 0
}
response = requests.post('http://localhost:5001/channel/messagesync', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"login_uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"start_message_seq": 1000,
"end_message_seq": 1100,
"limit": 50,
"pull_mode": 0,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/channel/messagesync",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result []map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
[
{
"message_id": 123456789,
"message_seq": 1001,
"client_msg_no": "client_msg_123",
"from_uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"timestamp": 1640995200,
"payload": "SGVsbG8gV29ybGQ="
},
{
"message_id": 123456790,
"message_seq": 1002,
"client_msg_no": "client_msg_124",
"from_uid": "user456",
"channel_id": "group123",
"channel_type": 2,
"timestamp": 1640995260,
"payload": "SGkgdGhlcmU="
}
]
```
## Response Fields
The response is an array of message objects, each containing:
Message ID
Message sequence number
Client message number
Sender user ID
Channel ID
Channel type
Message timestamp
Base64 encoded message content
## Status Codes
| Status Code | Description |
| ----------- | ----------------------- |
| 200 | Message sync successful |
| 400 | Request parameter error |
| 403 | No access permission |
| 500 | Internal server error |
## Use Cases
### Chat History Loading
**Load Recent Messages**:
```javascript theme={null}
// Load last 50 messages
const messages = await syncChannelMessages({
login_uid: "user123",
channel_id: "group123",
channel_type: 2,
limit: 50,
pull_mode: 0
});
```
**Load Older Messages**:
```javascript theme={null}
// Load messages before a specific sequence
const olderMessages = await syncChannelMessages({
login_uid: "user123",
channel_id: "group123",
channel_type: 2,
end_message_seq: 1000,
limit: 50,
pull_mode: 1
});
```
### Message Search and Export
**Export Chat History**:
```javascript theme={null}
async function exportChatHistory(channelId, channelType, loginUid) {
let allMessages = [];
let startSeq = 0;
const batchSize = 1000;
while (true) {
const messages = await syncChannelMessages({
login_uid: loginUid,
channel_id: channelId,
channel_type: channelType,
start_message_seq: startSeq,
limit: batchSize,
pull_mode: 0
});
if (messages.length === 0) break;
allMessages = allMessages.concat(messages);
startSeq = messages[messages.length - 1].message_seq + 1;
}
return allMessages;
}
```
### Offline Message Sync
**Sync Missed Messages**:
```javascript theme={null}
async function syncMissedMessages(channelId, channelType, loginUid, lastSeq) {
const missedMessages = await syncChannelMessages({
login_uid: loginUid,
channel_id: channelId,
channel_type: channelType,
start_message_seq: lastSeq + 1,
limit: 1000,
pull_mode: 0
});
return missedMessages;
}
```
## Best Practices
1. **Reasonable Range**: Avoid syncing too many messages at once
2. **Pagination**: Use limit parameter to control return quantity
3. **Error Handling**: Handle network errors and permission errors
4. **Caching Strategy**: Reasonably cache synced messages
5. **Performance Optimization**: Adjust sync frequency based on actual needs
6. **Permission Check**: Verify user has access to the channel before syncing
7. **Rate Limiting**: Implement rate limiting to prevent excessive API calls
# Send Message
Source: https://wukong.mintlify.app/en/api/message/send
POST /message/send
Send messages to specified channels
## Overview
Send messages to specified channels, supporting various message types including text, images, files, and more.
## Request Body
### Required Parameters
Base64 encoded message content
Sender user ID
Target channel ID
Channel type (1=personal channel, 2=group channel)
### Optional Parameters
Message header information
Whether to not persist message (0=persist, 1=do not persist)
Whether to show red dot notification (0=do not show, 1=show)
Whether it's write diffusion, generally 0, only cmd messages are 1
Client message number for deduplication and status tracking
Stream message number
Message expiration time (seconds), 0 means no expiration
Specified list of subscribers to receive the message (only valid for CMD messages)
Subscriber user ID
```bash cURL theme={null}
curl -X POST "http://localhost:5001/message/send" \
-H "Content-Type: application/json" \
-d '{
"header": {
"no_persist": 0,
"red_dot": 1,
"sync_once": 0
},
"client_msg_no": "client_msg_123",
"from_uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"expire": 0,
"payload": "SGVsbG8gV29ybGQ=",
"tag_key": "important"
}'
```
```javascript JavaScript theme={null}
// Send text message
const textMessage = {
header: {
no_persist: 0,
red_dot: 1,
sync_once: 0
},
client_msg_no: `msg_${Date.now()}`,
from_uid: "user123",
channel_id: "group123",
channel_type: 2,
expire: 0,
payload: btoa(JSON.stringify({
type: "text",
content: "Hello, World!"
})),
tag_key: "normal"
};
const response = await fetch('http://localhost:5001/message/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(textMessage)
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
import base64
import json
# Send text message
message_content = {
"type": "text",
"content": "Hello, World!"
}
payload = base64.b64encode(
json.dumps(message_content).encode('utf-8')
).decode('utf-8')
data = {
"header": {
"no_persist": 0,
"red_dot": 1,
"sync_once": 0
},
"client_msg_no": f"msg_{int(time.time() * 1000)}",
"from_uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"expire": 0,
"payload": payload,
"tag_key": "normal"
}
response = requests.post('http://localhost:5001/message/send', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"time"
)
func main() {
// Message content
messageContent := map[string]interface{}{
"type": "text",
"content": "Hello, World!",
}
contentBytes, _ := json.Marshal(messageContent)
payload := base64.StdEncoding.EncodeToString(contentBytes)
data := map[string]interface{}{
"header": map[string]interface{}{
"no_persist": 0,
"red_dot": 1,
"sync_once": 0,
},
"client_msg_no": fmt.Sprintf("msg_%d", time.Now().UnixMilli()),
"from_uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"expire": 0,
"payload": payload,
"tag_key": "normal",
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/message/send",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"message_id": 123456789,
"message_seq": 1001,
"client_msg_no": "client_msg_123"
}
```
## Response Fields
Server-generated message ID
Message sequence number
Client message number (echo)
## Status Codes
| Status Code | Description |
| ----------- | ------------------------- |
| 200 | Message sent successfully |
| 400 | Request parameter error |
| 403 | No sending permission |
| 500 | Internal server error |
## Message Type Examples
According to WuKongIM protocol specifications, here are recommended Payload structure examples:
### Regular Messages
#### Text Message
```json theme={null}
{
"type": 1,
"content": "This is a text message"
}
```
#### Text Message (with @ functionality)
```json theme={null}
{
"type": 1,
"content": "This is a text message",
"mention": {
"all": 0,
"uids": ["1223", "2323"]
}
}
```
* `mention.all`: Whether to @everyone (0=@users, 1=@everyone)
* `mention.uids`: If all=1, this field is empty
#### Text Message (with reply)
```json theme={null}
{
"type": 1,
"content": "Replied to someone",
"reply": {
"root_mid": "xxx",
"message_id": "xxxx",
"message_seq": 123,
"from_uid": "xxxx",
"from_name": "xxx",
"payload": {}
}
}
```
#### Image Message
```json theme={null}
{
"type": 2,
"url": "http://xxxxx.com/xxx",
"width": 200,
"height": 320
}
```
#### GIF Message
```json theme={null}
{
"type": 3,
"url": "http://xxxxx.com/xxx",
"width": 72,
"height": 72
}
```
#### Voice Message
```json theme={null}
{
"type": 4,
"url": "http://xxxxx.com/xxx",
"timeTrad": 10
}
```
`timeTrad`: Voice duration (seconds)
#### File Message
```json theme={null}
{
"type": 8,
"url": "http://xxxxx.com/xxx",
"name": "xxxx.docx",
"size": 238734
}
```
`size`: File size in bytes
#### Command Message
```json theme={null}
{
"type": 99,
"cmd": "groupUpdate",
"param": {}
}
```
### System Messages
System message type must be greater than 1000
#### Create Group Chat
Message settings: `NoPersist:0, RedDot:0, SyncOnce:1`
```json theme={null}
{
"type": 1001,
"creator": "xxx",
"creator_name": "John",
"content": "{0} invited {1}, {2} to join the group chat",
"extra": [
{"uid": "xxx", "name": "John"},
{"uid": "xx01", "name": "Alice"},
{"uid": "xx02", "name": "Bob"}
]
}
```
#### Add Group Members
Message settings: `NoPersist:0, RedDot:0, SyncOnce:1`
```json theme={null}
{
"type": 1002,
"content": "{0} invited {1}, {2} to join the group chat",
"extra": [
{"uid": "xxx", "name": "John"},
{"uid": "xx01", "name": "Alice"},
{"uid": "xx02", "name": "Bob"}
]
}
```
#### Remove Group Members
Message settings: `NoPersist:0, RedDot:0, SyncOnce:1`
```json theme={null}
{
"type": 1003,
"content": "{0} removed {1} from the group chat",
"extra": [
{"uid": "xxx", "name": "John"},
{"uid": "xx01", "name": "Alice"}
]
}
```
#### Group Member Kicked
Message settings: `NoPersist:0, RedDot:1, SyncOnce:0`
```json theme={null}
{
"type": 1010,
"content": "You were removed from the group chat by {0}",
"extra": [
{"uid": "xxx", "name": "John"}
]
}
```
#### Update Group Name
Message settings: `NoPersist:0, RedDot:0, SyncOnce:1`
```json theme={null}
{
"type": 1005,
"content": "{0} changed the group name to \"Test Group\"",
"extra": [
{"uid": "xxx", "name": "John"}
]
}
```
#### Update Group Announcement
Message settings: `NoPersist:0, RedDot:0, SyncOnce:1`
```json theme={null}
{
"type": 1005,
"content": "{0} changed the group announcement to \"This is a group announcement\"",
"extra": [
{"uid": "xxx", "name": "John"}
]
}
```
#### Recall Message
Message settings: `NoPersist:0, RedDot:0, SyncOnce:1`
```json theme={null}
{
"type": 1006,
"message_id": "234343435",
"content": "{0} recalled a message",
"extra": [
{"uid": "xxx", "name": "John"}
]
}
```
### Command Messages
#### Basic Command Message
Message settings: `SyncOnce:1`
```json theme={null}
{
"type": 99,
"cmd": "cmd",
"param": {}
}
```
#### Group Member Info Update
Upon receiving this message, the client should incrementally sync group member information
```json theme={null}
{
"type": 99,
"cmd": "memberUpdate",
"param": {
"group_no": "xxxx"
}
}
```
#### Red Dot Clear
Upon receiving this command, the client should clear the red dot for the corresponding conversation
```json theme={null}
{
"type": 99,
"cmd": "unreadClear",
"param": {
"channel_id": "xxxx",
"channel_type": 2
}
}
```
### Usage Examples
```javascript JavaScript theme={null}
// Send text message
const textMessage = {
type: 1,
content: "This is a text message"
};
const payload = btoa(JSON.stringify(textMessage));
const response = await fetch('/message/send', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
from_uid: "user123",
channel_id: "group123",
channel_type: 2,
payload: payload
})
});
```
```python Python theme={null}
import base64
import json
# Send image message
image_message = {
"type": 2,
"url": "http://example.com/image.jpg",
"width": 200,
"height": 320
}
payload = base64.b64encode(
json.dumps(image_message).encode('utf-8')
).decode('utf-8')
data = {
"from_uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"payload": payload
}
```
```go Go theme={null}
package main
import (
"encoding/base64"
"encoding/json"
)
// Send voice message
voiceMessage := map[string]interface{}{
"type": 4,
"url": "http://example.com/voice.mp3",
"timeTrad": 10,
}
contentBytes, _ := json.Marshal(voiceMessage)
payload := base64.StdEncoding.EncodeToString(contentBytes)
```
## Best Practices
1. **Message Deduplication**: Use unique client\_msg\_no to avoid duplicate sending
2. **Message Queue**: Add failed messages to retry queue
3. **Content Encoding**: Ensure payload is correctly Base64 encoded
4. **Permission Check**: Check if user has sending permission before sending
5. **Message Types**: Strictly follow protocol specifications for correct message type numbers
6. **System Messages**: System message types must be greater than 1000 with correct message flags
7. **Command Messages**: Command messages should set SyncOnce:1 flag
# Single Message Search
Source: https://wukong.mintlify.app/en/api/message/single-search
POST /message
Search for a single message by message ID
## Overview
Search for a single message by message ID, suitable for scenarios where you need to retrieve specific message details.
## Request Body
### Required Parameters
Message ID
```bash cURL theme={null}
curl -X POST "http://localhost:5001/message" \
-H "Content-Type: application/json" \
-d '{
"message_id": 123456789
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/message', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
message_id: 123456789
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"message_id": 123456789
}
response = requests.post('http://localhost:5001/message', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"message_id": 123456789,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/message",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"message_id": 123456789,
"message_seq": 1001,
"client_msg_no": "msg_123",
"from_uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"timestamp": 1640995200,
"payload": "SGVsbG8gV29ybGQ="
}
```
```json Message Not Found theme={null}
{
"error": "Message not found"
}
```
## Response Fields
Server-generated message ID
Message sequence number
Client message number
Sender user ID
Channel ID
Channel type
* `1` - Personal channel
* `2` - Group channel
Message timestamp (Unix timestamp)
Base64 encoded message content
## Status Codes
| Status Code | Description |
| ----------- | ------------------------- |
| 200 | Message search successful |
| 400 | Request parameter error |
| 404 | Message not found |
| 500 | Internal server error |
## Use Cases
### Message Detail Retrieval
**Get Specific Message Details**:
```javascript theme={null}
// Retrieve details for a specific message
async function getMessageDetail(messageId) {
try {
const response = await fetch('/message', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message_id: messageId })
});
if (response.status === 404) {
return { found: false, message: null };
}
const message = await response.json();
// Decode and format message
const formattedMessage = {
...message,
content: atob(message.payload), // Decode base64 content
formatted_time: new Date(message.timestamp * 1000).toLocaleString(),
found: true
};
return formattedMessage;
} catch (error) {
console.error('Failed to get message detail:', error);
return { found: false, message: null, error: error.message };
}
}
// Usage
const messageDetail = await getMessageDetail(123456789);
if (messageDetail.found) {
console.log('Message content:', messageDetail.content);
console.log('Sent by:', messageDetail.from_uid);
console.log('Sent at:', messageDetail.formatted_time);
} else {
console.log('Message not found');
}
```
### Message Verification
**Verify Message Existence and Integrity**:
```javascript theme={null}
// Verify that a message exists and check its properties
async function verifyMessage(messageId, expectedProperties = {}) {
try {
const result = await getMessageDetail(messageId);
if (!result.found) {
return {
exists: false,
verified: false,
message: 'Message not found'
};
}
const message = result;
const verification = {
exists: true,
verified: true,
checks: {},
message: message
};
// Verify expected properties
for (const [key, expectedValue] of Object.entries(expectedProperties)) {
const actualValue = message[key];
const matches = actualValue === expectedValue;
verification.checks[key] = {
expected: expectedValue,
actual: actualValue,
matches: matches
};
if (!matches) {
verification.verified = false;
}
}
return verification;
} catch (error) {
return {
exists: false,
verified: false,
message: error.message
};
}
}
// Usage
const verification = await verifyMessage(123456789, {
from_uid: 'user123',
channel_id: 'group123',
channel_type: 2
});
if (verification.verified) {
console.log('Message verified successfully');
} else {
console.log('Verification failed:', verification.checks);
}
```
### Message Reference Resolution
**Resolve Message References**:
```javascript theme={null}
// Resolve message references in replies or quotes
async function resolveMessageReference(referenceId) {
try {
const message = await getMessageDetail(referenceId);
if (!message.found) {
return {
resolved: false,
reference: null,
display: '[Message not found]'
};
}
// Create a display-friendly reference
const reference = {
id: message.message_id,
sender: message.from_uid,
content: message.content.substring(0, 100), // Truncate for preview
timestamp: message.timestamp,
channel: message.channel_id,
display: `${message.from_uid}: ${message.content.substring(0, 50)}...`
};
return {
resolved: true,
reference: reference,
display: reference.display
};
} catch (error) {
return {
resolved: false,
reference: null,
display: '[Error loading message]'
};
}
}
// Usage in message rendering
async function renderMessageWithReferences(messageText) {
// Find message references in format @msg:123456789
const referencePattern = /@msg:(\d+)/g;
let match;
const references = [];
while ((match = referencePattern.exec(messageText)) !== null) {
const messageId = parseInt(match[1]);
const reference = await resolveMessageReference(messageId);
references.push({
original: match[0],
messageId: messageId,
reference: reference
});
}
// Replace references with display text
let renderedText = messageText;
for (const ref of references) {
renderedText = renderedText.replace(ref.original, ref.reference.display);
}
return {
originalText: messageText,
renderedText: renderedText,
references: references
};
}
```
### Message Audit and Logging
**Audit Message Access**:
```javascript theme={null}
// Audit message access for security and compliance
class MessageAuditor {
constructor() {
this.accessLog = [];
}
async getMessageWithAudit(messageId, accessorUserId, reason = 'general_access') {
const startTime = Date.now();
try {
const message = await getMessageDetail(messageId);
const endTime = Date.now();
// Log successful access
this.logAccess({
message_id: messageId,
accessor_user_id: accessorUserId,
reason: reason,
success: message.found,
timestamp: new Date().toISOString(),
response_time_ms: endTime - startTime,
message_found: message.found,
channel_id: message.found ? message.channel_id : null,
sender_uid: message.found ? message.from_uid : null
});
return message;
} catch (error) {
const endTime = Date.now();
// Log failed access
this.logAccess({
message_id: messageId,
accessor_user_id: accessorUserId,
reason: reason,
success: false,
timestamp: new Date().toISOString(),
response_time_ms: endTime - startTime,
error: error.message
});
throw error;
}
}
logAccess(logEntry) {
this.accessLog.push(logEntry);
// Send to audit system
this.sendToAuditSystem(logEntry);
// Clean up old logs (keep last 1000)
if (this.accessLog.length > 1000) {
this.accessLog = this.accessLog.slice(-1000);
}
}
async sendToAuditSystem(logEntry) {
try {
// Send to external audit system
await fetch('/audit/message-access', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(logEntry)
});
} catch (error) {
console.error('Failed to send audit log:', error);
}
}
getAccessStats() {
const stats = {
total_accesses: this.accessLog.length,
successful_accesses: this.accessLog.filter(log => log.success).length,
failed_accesses: this.accessLog.filter(log => !log.success).length,
unique_messages: new Set(this.accessLog.map(log => log.message_id)).size,
unique_accessors: new Set(this.accessLog.map(log => log.accessor_user_id)).size,
avg_response_time: this.accessLog.reduce((sum, log) => sum + log.response_time_ms, 0) / this.accessLog.length
};
return stats;
}
}
// Usage
const auditor = new MessageAuditor();
// Access message with audit trail
const message = await auditor.getMessageWithAudit(
123456789,
'admin_user',
'compliance_review'
);
// Get audit statistics
const stats = auditor.getAccessStats();
console.log('Audit stats:', stats);
```
### Message Cache with Single Lookup
**Efficient Single Message Caching**:
```javascript theme={null}
// Cache for single message lookups
class SingleMessageCache {
constructor(maxSize = 500, ttlMinutes = 30) {
this.cache = new Map();
this.maxSize = maxSize;
this.ttl = ttlMinutes * 60 * 1000; // Convert to milliseconds
}
async getMessage(messageId) {
// Check cache first
const cached = this.cache.get(messageId);
if (cached && this.isValid(cached)) {
cached.lastAccessed = Date.now();
return cached.message;
}
// Fetch from server
try {
const message = await getMessageDetail(messageId);
// Cache the result (even if not found)
this.addToCache(messageId, message);
return message;
} catch (error) {
// Cache error result temporarily
this.addToCache(messageId, {
found: false,
error: error.message
}, 5 * 60 * 1000); // 5 minute TTL for errors
throw error;
}
}
addToCache(messageId, message, customTtl = null) {
// Remove oldest if cache is full
if (this.cache.size >= this.maxSize) {
this.evictOldest();
}
const cacheEntry = {
message: message,
timestamp: Date.now(),
lastAccessed: Date.now(),
ttl: customTtl || this.ttl
};
this.cache.set(messageId, cacheEntry);
}
isValid(cacheEntry) {
const age = Date.now() - cacheEntry.timestamp;
return age < cacheEntry.ttl;
}
evictOldest() {
let oldestKey = null;
let oldestTime = Date.now();
for (const [key, entry] of this.cache) {
if (entry.lastAccessed < oldestTime) {
oldestTime = entry.lastAccessed;
oldestKey = key;
}
}
if (oldestKey) {
this.cache.delete(oldestKey);
}
}
getCacheStats() {
const now = Date.now();
const validEntries = Array.from(this.cache.values()).filter(entry =>
this.isValid(entry)
);
return {
total_entries: this.cache.size,
valid_entries: validEntries.length,
expired_entries: this.cache.size - validEntries.length,
hit_rate: this.hitRate || 0,
memory_usage: this.cache.size / this.maxSize * 100
};
}
}
// Usage
const messageCache = new SingleMessageCache(1000, 60); // 1000 messages, 60 minute TTL
// Get message (will cache result)
const message1 = await messageCache.getMessage(123456789);
console.log('First access:', message1.found);
// Get same message (will use cache)
const message2 = await messageCache.getMessage(123456789);
console.log('Second access (cached):', message2.found);
console.log('Cache stats:', messageCache.getCacheStats());
```
## Best Practices
1. **Error Handling**: Always handle 404 responses when message might not exist
2. **Caching**: Cache frequently accessed messages to reduce API calls
3. **Validation**: Validate message ID format before making requests
4. **Security**: Implement proper access controls for message retrieval
5. **Audit Trail**: Log message access for security and compliance
6. **Performance**: Use batch search for multiple messages instead of individual calls
7. **Content Decoding**: Remember to decode base64 payload content for display
# User Message Search
Source: https://wukong.mintlify.app/en/api/message/user-search
POST /plugins/wk.plugin.search/usersearch
Search all messages belonging to the current user, supporting multi-dimensional search and Chinese word segmentation
## Overview
Search all messages belonging to the current user, supporting multi-dimensional search and Chinese word segmentation functionality.
* Requires WuKongIM v2.1.3-20250210 or above
* Requires installation of `wk.plugin.search` plugin
* Plugin usage documentation: [Plugin Development Guide](/en/getting-started/learning/plugin-development)
## Request Body
### Required Parameters
Current user UID (restricts search to specified user's messages)
### Optional Parameters
Message payload, supports searching custom fields
Message content search keywords
Message type search
Message type value
Sender UID
Channel ID, when specified, only search messages within this channel
Channel type
* `1` - Personal channel
* `2` - Group channel
Search by topic
Query limit, default 10
Page number for pagination, default 1
Message time (start), Unix timestamp
Message time (end, result includes end\_time), Unix timestamp
Keyword fields that need highlighting
Field name, e.g., "payload.content"
```bash cURL theme={null}
curl -X POST "http://localhost:5001/plugins/wk.plugin.search/usersearch" \
-H "Content-Type: application/json" \
-d '{
"uid": "user123",
"payload": {
"content": "Beijing"
},
"payload_types": [1, 2],
"channel_type": 2,
"limit": 10,
"page": 1,
"highlights": ["payload.content"]
}'
```
```javascript JavaScript theme={null}
const searchParams = {
uid: "user123",
payload: {
content: "Beijing"
},
payload_types: [1, 2],
channel_type: 2,
limit: 10,
page: 1,
highlights: ["payload.content"]
};
const response = await fetch('http://localhost:5001/plugins/wk.plugin.search/usersearch', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(searchParams)
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
search_params = {
"uid": "user123",
"payload": {
"content": "Beijing"
},
"payload_types": [1, 2],
"channel_type": 2,
"limit": 10,
"page": 1,
"highlights": ["payload.content"]
}
response = requests.post(
'http://localhost:5001/plugins/wk.plugin.search/usersearch',
json=search_params
)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
searchParams := map[string]interface{}{
"uid": "user123",
"payload": map[string]interface{}{
"content": "Beijing",
},
"payload_types": []int{1, 2},
"channel_type": 2,
"limit": 10,
"page": 1,
"highlights": []string{"payload.content"},
}
jsonData, _ := json.Marshal(searchParams)
resp, err := http.Post(
"http://localhost:5001/plugins/wk.plugin.search/usersearch",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"total": 25,
"limit": 10,
"page": 1,
"messages": [
{
"message_id": 1234,
"message_idstr": "1234",
"message_seq": 1,
"client_msg_no": "djzdfdfdf",
"from_uid": "u1",
"channel_id": "g1",
"channel_type": 2,
"payload": {
"type": 1,
"content": "Are you from Beijing University?"
},
"topic": "",
"timestamp": 762834
},
{
"message_id": 1235,
"message_idstr": "1235",
"message_seq": 2,
"client_msg_no": "djzdfdfde",
"from_uid": "u2",
"channel_id": "g1",
"channel_type": 2,
"payload": {
"type": 1,
"content": "I work in Beijing"
},
"topic": "",
"timestamp": 762835
}
]
}
```
## Response Fields
Total number of messages
Query limit
Current page number
Message list
Message unique ID
Message unique ID (string format)
Message sequence number
Client message unique number
Sender UID
Channel ID
Channel type
Message content object
Message type
Message content (may contain highlight tags)
Message topic
Message timestamp (10-digit seconds)
## Status Codes
| Status Code | Description |
| ----------- | ----------------------- |
| 200 | Search successful |
| 400 | Request parameter error |
| 403 | No search permission |
| 500 | Internal server error |
## Search Features
### Chinese Word Segmentation
Supports Chinese word segmentation, intelligently recognizing Chinese vocabulary for search.
**Examples**:
* Searching "Beijing University" can match messages containing "Beijing" or "University"
* Supports both fuzzy matching and exact matching
### Multi-dimensional Search
Supports combined search across multiple dimensions:
1. **Content Search**: Search message content through `payload.content`
2. **Type Search**: Limit message types through `payload_types`
3. **User Search**: Search specific user's messages through `from_uid`
4. **Channel Search**: Search specific channel's messages through `channel_id`
5. **Time Search**: Limit time range through `start_time` and `end_time`
6. **Topic Search**: Search specific topic messages through `topic`
### Highlighting
Through the `highlights` parameter, you can specify fields that need highlighting. Matching keywords in search results will be surrounded by `` tags.
**Example**:
```json theme={null}
{
"payload": {
"content": "Beijing"
},
"highlights": ["payload.content"]
}
```
Return result:
```json theme={null}
{
"payload": {
"content": "Are you from Beijing University?"
}
}
```
## Use Cases
### Chat History Search
* **Keyword Search**: Users search for keywords in chat history
* **User Messages**: Search messages sent by specific users
* **Group Messages**: Search messages within specific groups
### Content Management
* **Message Moderation**: Search messages containing specific content
* **Data Analysis**: Analyze user message content and behavior
* **Compliance Check**: Check for sensitive content
### Advanced Search Examples
**Search by Time Range**:
```javascript theme={null}
const timeRangeSearch = {
uid: "user123",
payload: { content: "project" },
start_time: 1640995200, // 2022-01-01
end_time: 1672531200, // 2023-01-01
limit: 20
};
```
**Search by Message Type**:
```javascript theme={null}
const typeSearch = {
uid: "user123",
payload_types: [1, 2], // Text and image messages only
channel_id: "group123",
limit: 50
};
```
**Search with Multiple Criteria**:
```javascript theme={null}
const complexSearch = {
uid: "user123",
payload: { content: "meeting" },
from_uid: "manager123",
channel_type: 2,
topic: "work",
highlights: ["payload.content"],
limit: 10
};
```
## Best Practices
1. **Pagination**: Use appropriate page size to avoid performance issues
2. **Time Limits**: Set reasonable time ranges for better performance
3. **Keyword Optimization**: Use specific keywords for more accurate results
4. **Result Caching**: Cache search results for frequently used queries
5. **Permission Check**: Ensure users can only search their own messages
6. **Rate Limiting**: Implement rate limiting to prevent search abuse
# Get Connection Information
Source: https://wukong.mintlify.app/en/api/monitoring/connections
GET /connz
Get current server connection statistics including connection count and user distribution
## Overview
Get current server connection statistics, including connection count, user distribution and other monitoring data.
## Query Parameters
Offset for pagination
Limit for pagination
Whether to include subscription information
* `0` - Do not include subscription information
* `1` - Include subscription information
```bash cURL theme={null}
curl -X GET "http://localhost:5001/connz?offset=0&limit=50&subs=1"
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/connz?offset=0&limit=50&subs=1');
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
params = {
'offset': 0,
'limit': 50,
'subs': 1
}
response = requests.get('http://localhost:5001/connz', params=params)
data = response.json()
print(data)
```
```go Go theme={null}
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func main() {
resp, err := http.Get("http://localhost:5001/connz?offset=0&limit=50&subs=1")
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"now": "2024-01-15T10:30:00Z",
"num_connections": 1250,
"total": 1250,
"offset": 0,
"limit": 50,
"connections": [
{
"cid": 12345,
"uid": "user123",
"ip": "192.168.1.100",
"port": 54321,
"start": "2024-01-15T09:15:30Z",
"last_activity": "2024-01-15T10:29:45Z",
"uptime": "1h14m15s",
"idle": "15s",
"pending_bytes": 0,
"in_msgs": 156,
"out_msgs": 203,
"in_bytes": 15680,
"out_bytes": 25440,
"subscriptions": 8,
"device_flag": 1,
"device_level": 1,
"version": "1.0.0"
}
]
}
```
## Response Fields
Current server time (ISO 8601 format)
Current number of connections
Total number of connections
Current offset
Current limit
Connection details list
Connection ID
User ID
Client IP address
Client port
Connection start time
Last activity time
Connection duration
Idle time
Pending bytes to send
Number of received messages
Number of sent messages
Number of received bytes
Number of sent bytes
Number of subscriptions
Device flag
Device level
Client version
## Status Codes
| Status Code | Description |
| ----------- | --------------------------------------------- |
| 200 | Successfully retrieved connection information |
| 500 | Internal server error |
## Best Practices
1. **Paginated Queries**: Use pagination for large numbers of connections to avoid performance issues
2. **Regular Monitoring**: Set reasonable monitoring intervals, avoid excessive frequency
3. **Alert Mechanisms**: Set alerts for key metrics like connection count and activity
4. **Data Export**: Support export and analysis of connection data
5. **Performance Optimization**: Monitor connection performance metrics to identify issues promptly
6. **Security Monitoring**: Pay attention to connections from abnormal IPs
# Get System Variables
Source: https://wukong.mintlify.app/en/api/monitoring/variables
GET /varz
Get system variables and performance metrics
## Overview
Get WuKongIM system runtime variables and performance metrics for system monitoring and performance analysis.
## Query Parameters
Sort field
* `in_msgs` - Sort by received message count
* `out_msgs` - Sort by sent message count
* `in_bytes` - Sort by received bytes
* `out_bytes` - Sort by sent bytes
Connection information limit count
Specify node ID (cluster environment)
```bash cURL theme={null}
curl -X GET "http://localhost:5001/varz?sort=in_msgs&conn_limit=50"
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/varz?sort=in_msgs&conn_limit=50');
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
params = {
'sort': 'in_msgs',
'conn_limit': 50
}
response = requests.get('http://localhost:5001/varz', params=params)
data = response.json()
print(data)
```
```go Go theme={null}
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func main() {
resp, err := http.Get("http://localhost:5001/varz?sort=in_msgs&conn_limit=50")
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"server_id": "wukongim-node-1",
"version": "2.0.0",
"git_commit": "abc123def",
"go_version": "go1.21.0",
"start": "2024-01-15T08:00:00Z",
"now": "2024-01-15T10:30:00Z",
"uptime": "2h30m0s",
"connections": 1250,
"total_connections": 15000,
"in_msgs": 125000,
"out_msgs": 130000,
"in_bytes": 12500000,
"out_bytes": 13000000,
"slow_consumers": 5,
"subscriptions": 8500,
"http_req_stats": {
"uri_stats": [
{
"uri": "/message/send",
"count": 5000,
"avg_time": "15ms"
},
{
"uri": "/channel/create",
"count": 200,
"avg_time": "25ms"
}
]
},
"cpu": 15.5,
"mem": 536870912,
"config": {
"max_connections": 10000,
"max_subscriptions_per_conn": 100,
"max_payload": 1048576
}
}
```
## Response Fields
### Server Information
Server identifier
WuKongIM version number
Git commit hash
Go language version
### Runtime Information
Server start time (ISO 8601 format)
Current time (ISO 8601 format)
Runtime duration
### Connection Statistics
Current connection count
Total connections (historical cumulative)
Number of slow consumers
Total subscriptions
### Message Statistics
Total received messages
Total sent messages
Total received bytes
Total sent bytes
### HTTP Request Statistics
HTTP request statistics
URI statistics list
Request URI
Request count
Average response time
### System Resources
CPU usage (percentage)
Memory usage (bytes)
### Configuration Information
System configuration information
Maximum connection limit
Maximum subscriptions per connection
Maximum message payload size (bytes)
## Status Codes
| Status Code | Description |
| ----------- | --------------------------------------- |
| 200 | Successfully retrieved system variables |
| 500 | Internal server error |
## Monitoring Metrics Description
### Performance Metrics
| Metric | Description | Normal Range | Alert Threshold |
| -------------- | ------------------------------------- | --------------- | --------------- |
| CPU Usage | Server CPU utilization percentage | \< 70% | > 80% |
| Memory Usage | Server memory consumption | \< 80% | > 90% |
| Connections | Current active connections | Based on config | Near maximum |
| Slow Consumers | Number of slow processing connections | \< 5% | > 10% |
### Throughput Metrics
| Metric | Description | Monitoring Focus |
| -------------------- | ------------------------------------ | ------------------------------------ |
| Message Receive Rate | Messages received per second | Sudden increases or decreases |
| Message Send Rate | Messages sent per second | Ratio to receive rate |
| Byte Transfer Rate | Network transfer speed | Bandwidth usage |
| HTTP Request Stats | API call frequency and response time | Hot APIs and performance bottlenecks |
## Best Practices
1. **Regular Monitoring**: Recommended to get system variables every 30-60 seconds
2. **Alert Setup**: Set reasonable alert thresholds for key metrics
3. **Trend Analysis**: Record historical data to analyze system performance trends
4. **Capacity Planning**: Perform capacity planning based on monitoring data
5. **Performance Optimization**: Identify performance bottlenecks and optimize
6. **Cluster Monitoring**: Monitor all node status in cluster environments
# Batch Get User IM Addresses
Source: https://wukong.mintlify.app/en/api/route/batch-address
POST /route/batch
Batch get IM connection addresses for multiple users
## Overview
Batch get IM connection addresses for multiple users, used to assign different connection nodes for different users.
## Query Parameters
Whether to return intranet addresses
* `0` - Return external network addresses
* `1` - Return internal network addresses
## Request Body
Array of user IDs
User ID
```bash cURL theme={null}
curl -X POST "http://localhost:5001/route/batch?intranet=0" \
-H "Content-Type: application/json" \
-d '["user1", "user2", "user3"]'
```
```javascript JavaScript theme={null}
const userIds = ["user1", "user2", "user3"];
const response = await fetch('http://localhost:5001/route/batch?intranet=0', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userIds)
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
user_ids = ["user1", "user2", "user3"]
response = requests.post(
'http://localhost:5001/route/batch',
params={'intranet': 0},
json=user_ids
)
data = response.json()
print(data)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
userIds := []string{"user1", "user2", "user3"}
jsonData, _ := json.Marshal(userIds)
resp, err := http.Post(
"http://localhost:5001/route/batch?intranet=0",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result []map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
[
{
"uids": ["user1", "user2"],
"tcp_addr": "127.0.0.1:5100",
"ws_addr": "ws://127.0.0.1:5200",
"wss_addr": "wss://127.0.0.1:5300"
},
{
"uids": ["user3"],
"tcp_addr": "127.0.0.1:5101",
"ws_addr": "ws://127.0.0.1:5201",
"wss_addr": "wss://127.0.0.1:5301"
}
]
```
## Response Fields
The response is an array, each element contains the following fields:
List of user IDs assigned to this address
TCP connection address, format: `host:port`
WebSocket connection address, format: `ws://host:port`
WebSocket Secure connection address, format: `wss://host:port`
## Status Codes
| Status Code | Description |
| ----------- | ---------------------------------------------------- |
| 200 | Successfully retrieved batch IM connection addresses |
| 400 | Request parameter error |
| 500 | Internal server error |
## Use Cases
### Multi-User Connection Setup
**Batch Connect Multiple Users**:
```javascript theme={null}
// Connect multiple users to their assigned nodes
async function batchConnectUsers(userIds) {
try {
// Get connection addresses for all users
const response = await fetch('/route/batch?intranet=0', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userIds)
});
const addressGroups = await response.json();
const connections = [];
// Connect users to their assigned nodes
for (const group of addressGroups) {
const connectionUrl = window.location.protocol === 'https:'
? group.wss_addr
: group.ws_addr;
for (const uid of group.uids) {
try {
const connection = await connectUser(uid, connectionUrl);
connections.push({
uid: uid,
connection: connection,
node_address: connectionUrl,
success: true
});
} catch (error) {
connections.push({
uid: uid,
connection: null,
node_address: connectionUrl,
success: false,
error: error.message
});
}
}
}
return connections;
} catch (error) {
console.error('Batch user connection failed:', error);
throw error;
}
}
// Usage
const userIds = ['user1', 'user2', 'user3', 'user4', 'user5'];
const connections = await batchConnectUsers(userIds);
const successful = connections.filter(c => c.success);
const failed = connections.filter(c => !c.success);
console.log(`Connected ${successful.length} users, ${failed.length} failed`);
```
### Load Distribution
**Distribute Users Across Nodes**:
```javascript theme={null}
// Distribute users across available nodes for load balancing
class UserLoadDistributor {
constructor() {
this.nodeUserMap = new Map();
}
async distributeUsers(userIds, batchSize = 50) {
const batches = this.chunkArray(userIds, batchSize);
const allDistributions = [];
for (const batch of batches) {
try {
const distributions = await this.getBatchDistribution(batch);
allDistributions.push(...distributions);
// Track node assignments
this.trackNodeAssignments(distributions);
} catch (error) {
console.error('Failed to distribute batch:', error);
}
}
return allDistributions;
}
async getBatchDistribution(userIds) {
const response = await fetch('/route/batch?intranet=0', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userIds)
});
return await response.json();
}
trackNodeAssignments(distributions) {
for (const dist of distributions) {
const nodeKey = dist.ws_addr;
if (!this.nodeUserMap.has(nodeKey)) {
this.nodeUserMap.set(nodeKey, []);
}
this.nodeUserMap.get(nodeKey).push(...dist.uids);
}
}
getNodeStatistics() {
const stats = [];
for (const [nodeAddr, users] of this.nodeUserMap) {
stats.push({
node_address: nodeAddr,
user_count: users.length,
users: users
});
}
return stats;
}
chunkArray(array, size) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
}
// Usage
const distributor = new UserLoadDistributor();
const userIds = Array.from({length: 1000}, (_, i) => `user${i + 1}`);
const distributions = await distributor.distributeUsers(userIds);
const stats = distributor.getNodeStatistics();
console.log('Node distribution statistics:', stats);
```
### Connection Pool Management
**Manage Connection Pools by Node**:
```javascript theme={null}
// Manage connection pools for different nodes
class NodeConnectionPoolManager {
constructor() {
this.pools = new Map();
this.userNodeMap = new Map();
}
async initializePools(userIds) {
// Get node assignments for users
const distributions = await this.getBatchAddresses(userIds);
// Create connection pools for each node
for (const dist of distributions) {
const nodeKey = dist.ws_addr;
if (!this.pools.has(nodeKey)) {
this.pools.set(nodeKey, {
address: dist.ws_addr,
tcp_addr: dist.tcp_addr,
wss_addr: dist.wss_addr,
connections: new Map(),
userCount: 0
});
}
const pool = this.pools.get(nodeKey);
// Track user assignments
for (const uid of dist.uids) {
this.userNodeMap.set(uid, nodeKey);
pool.userCount++;
}
}
console.log(`Initialized ${this.pools.size} connection pools`);
}
async getBatchAddresses(userIds) {
const response = await fetch('/route/batch?intranet=0', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userIds)
});
return await response.json();
}
async connectUser(userId) {
const nodeKey = this.userNodeMap.get(userId);
if (!nodeKey) {
throw new Error(`No node assignment found for user ${userId}`);
}
const pool = this.pools.get(nodeKey);
if (!pool) {
throw new Error(`No connection pool found for node ${nodeKey}`);
}
// Check if user already has a connection
if (pool.connections.has(userId)) {
return pool.connections.get(userId);
}
// Create new connection
try {
const connection = new WebSocket(pool.address);
await new Promise((resolve, reject) => {
connection.onopen = resolve;
connection.onerror = reject;
setTimeout(reject, 5000); // 5 second timeout
});
pool.connections.set(userId, connection);
console.log(`User ${userId} connected to node ${nodeKey}`);
return connection;
} catch (error) {
console.error(`Failed to connect user ${userId} to node ${nodeKey}:`, error);
throw error;
}
}
disconnectUser(userId) {
const nodeKey = this.userNodeMap.get(userId);
if (!nodeKey) return;
const pool = this.pools.get(nodeKey);
if (!pool) return;
const connection = pool.connections.get(userId);
if (connection) {
connection.close();
pool.connections.delete(userId);
console.log(`User ${userId} disconnected from node ${nodeKey}`);
}
}
getPoolStatistics() {
const stats = [];
for (const [nodeKey, pool] of this.pools) {
stats.push({
node_address: nodeKey,
assigned_users: pool.userCount,
active_connections: pool.connections.size,
connection_rate: (pool.connections.size / pool.userCount * 100).toFixed(2) + '%'
});
}
return stats;
}
}
// Usage
const poolManager = new NodeConnectionPoolManager();
// Initialize with user list
const userIds = ['user1', 'user2', 'user3', 'user4', 'user5'];
await poolManager.initializePools(userIds);
// Connect users
for (const userId of userIds) {
try {
await poolManager.connectUser(userId);
} catch (error) {
console.error(`Failed to connect ${userId}:`, error);
}
}
// Get statistics
const stats = poolManager.getPoolStatistics();
console.log('Pool statistics:', stats);
```
### Geographic Distribution
**Route Users by Geographic Location**:
```javascript theme={null}
// Route users to geographically optimal nodes
async function routeUsersByLocation(userLocations) {
const userIds = userLocations.map(ul => ul.userId);
try {
// Get node assignments
const distributions = await fetch('/route/batch?intranet=0', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userIds)
}).then(r => r.json());
// Analyze geographic distribution
const geoAnalysis = [];
for (const dist of distributions) {
const nodeUsers = dist.uids.map(uid =>
userLocations.find(ul => ul.userId === uid)
);
const avgLatitude = nodeUsers.reduce((sum, u) => sum + u.latitude, 0) / nodeUsers.length;
const avgLongitude = nodeUsers.reduce((sum, u) => sum + u.longitude, 0) / nodeUsers.length;
geoAnalysis.push({
node_address: dist.ws_addr,
users: dist.uids,
user_count: dist.uids.length,
avg_location: {
latitude: avgLatitude,
longitude: avgLongitude
},
user_locations: nodeUsers.map(u => ({
userId: u.userId,
latitude: u.latitude,
longitude: u.longitude,
country: u.country
}))
});
}
return geoAnalysis;
} catch (error) {
console.error('Geographic routing failed:', error);
throw error;
}
}
// Usage
const userLocations = [
{ userId: 'user1', latitude: 40.7128, longitude: -74.0060, country: 'US' },
{ userId: 'user2', latitude: 51.5074, longitude: -0.1278, country: 'UK' },
{ userId: 'user3', latitude: 35.6762, longitude: 139.6503, country: 'JP' },
{ userId: 'user4', latitude: 39.9042, longitude: 116.4074, country: 'CN' },
{ userId: 'user5', latitude: 52.5200, longitude: 13.4050, country: 'DE' }
];
const geoDistribution = await routeUsersByLocation(userLocations);
console.log('Geographic distribution:', geoDistribution);
```
## Best Practices
1. **Batch Size**: Use appropriate batch sizes to balance performance and resource usage
2. **Error Handling**: Handle partial failures gracefully when some users can't be routed
3. **Caching**: Cache node assignments to reduce API calls for frequently accessed users
4. **Load Monitoring**: Monitor node load distribution and adjust routing as needed
5. **Failover**: Implement failover mechanisms when assigned nodes become unavailable
6. **Geographic Optimization**: Consider user geographic location for optimal routing
7. **Connection Pooling**: Use connection pools to efficiently manage multiple user connections
# Get User IM Address
Source: https://wukong.mintlify.app/en/api/route/get-address
GET /route
Get the IM connection address for users
## Overview
Get the IM connection address for users, including TCP, WebSocket, and WebSocket Secure addresses.
## Query Parameters
Whether to return intranet address
* `0` - Return external network address
* `1` - Return internal network address
```bash cURL theme={null}
curl -X GET "http://localhost:5001/route?intranet=0"
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/route?intranet=0');
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
response = requests.get('http://localhost:5001/route', params={'intranet': 0})
data = response.json()
print(data)
```
```go Go theme={null}
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func main() {
resp, err := http.Get("http://localhost:5001/route?intranet=0")
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"tcp_addr": "127.0.0.1:5100",
"ws_addr": "ws://127.0.0.1:5200",
"wss_addr": "wss://127.0.0.1:5300"
}
```
## Response Fields
TCP connection address, format: `host:port`
WebSocket connection address, format: `ws://host:port`
WebSocket Secure connection address, format: `wss://host:port`
## Status Codes
| Status Code | Description |
| ----------- | -------------------------------------------- |
| 200 | Successfully retrieved IM connection address |
| 500 | Internal server error |
## Use Cases
### Client Connection Setup
**Dynamic Connection Discovery**:
```javascript theme={null}
// Get connection addresses and establish connection
async function connectToWuKongIM() {
try {
// Get connection addresses
const addresses = await fetch('/route?intranet=0').then(r => r.json());
// Choose appropriate connection type based on environment
let connectionUrl;
if (window.location.protocol === 'https:') {
connectionUrl = addresses.wss_addr;
} else {
connectionUrl = addresses.ws_addr;
}
// Establish WebSocket connection
const ws = new WebSocket(connectionUrl);
ws.onopen = () => {
console.log('Connected to WuKongIM:', connectionUrl);
};
return ws;
} catch (error) {
console.error('Failed to connect to WuKongIM:', error);
}
}
```
### Load Balancing
**Multiple Server Discovery**:
```javascript theme={null}
// Get addresses from multiple servers for load balancing
async function discoverWuKongIMServers(serverList) {
const availableServers = [];
for (const server of serverList) {
try {
const response = await fetch(`${server}/route?intranet=0`);
const addresses = await response.json();
availableServers.push({
server: server,
addresses: addresses,
priority: calculateServerPriority(server)
});
} catch (error) {
console.warn(`Server ${server} is not available:`, error);
}
}
// Sort by priority and return best server
availableServers.sort((a, b) => b.priority - a.priority);
return availableServers[0]?.addresses;
}
```
### Environment-based Connection
**Internal vs External Network**:
```javascript theme={null}
// Choose connection type based on network environment
async function getOptimalConnection() {
try {
// Try internal network first (faster)
const internalAddresses = await fetch('/route?intranet=1').then(r => r.json());
// Test internal connectivity
const isInternalReachable = await testConnectivity(internalAddresses.ws_addr);
if (isInternalReachable) {
return internalAddresses;
} else {
// Fallback to external network
const externalAddresses = await fetch('/route?intranet=0').then(r => r.json());
return externalAddresses;
}
} catch (error) {
console.error('Failed to get optimal connection:', error);
throw error;
}
}
async function testConnectivity(wsUrl) {
return new Promise((resolve) => {
const testWs = new WebSocket(wsUrl);
const timeout = setTimeout(() => {
testWs.close();
resolve(false);
}, 3000);
testWs.onopen = () => {
clearTimeout(timeout);
testWs.close();
resolve(true);
};
testWs.onerror = () => {
clearTimeout(timeout);
resolve(false);
};
});
}
```
### Connection Failover
**Automatic Failover**:
```javascript theme={null}
class WuKongIMConnector {
constructor(serverUrls) {
this.serverUrls = serverUrls;
this.currentServerIndex = 0;
this.connection = null;
}
async connect() {
for (let i = 0; i < this.serverUrls.length; i++) {
try {
const serverUrl = this.serverUrls[this.currentServerIndex];
const addresses = await this.getAddresses(serverUrl);
this.connection = await this.establishConnection(addresses);
console.log(`Connected to server: ${serverUrl}`);
return this.connection;
} catch (error) {
console.warn(`Failed to connect to server ${this.currentServerIndex}:`, error);
this.currentServerIndex = (this.currentServerIndex + 1) % this.serverUrls.length;
}
}
throw new Error('Failed to connect to any WuKongIM server');
}
async getAddresses(serverUrl) {
const response = await fetch(`${serverUrl}/route?intranet=0`);
return await response.json();
}
async establishConnection(addresses) {
return new Promise((resolve, reject) => {
const ws = new WebSocket(addresses.ws_addr);
ws.onopen = () => resolve(ws);
ws.onerror = (error) => reject(error);
setTimeout(() => reject(new Error('Connection timeout')), 5000);
});
}
}
// Usage
const connector = new WuKongIMConnector([
'http://server1.example.com:5001',
'http://server2.example.com:5001',
'http://server3.example.com:5001'
]);
const connection = await connector.connect();
```
### Mobile App Integration
**Platform-specific Connection**:
```javascript theme={null}
// React Native example
import { Platform } from 'react-native';
async function getMobileConnection() {
try {
const addresses = await fetch('/route?intranet=0').then(r => r.json());
// Use appropriate connection for mobile platforms
if (Platform.OS === 'ios' || Platform.OS === 'android') {
// Mobile apps typically use WebSocket
return addresses.ws_addr;
} else {
// Web apps use secure WebSocket if available
return window.location.protocol === 'https:'
? addresses.wss_addr
: addresses.ws_addr;
}
} catch (error) {
console.error('Failed to get mobile connection:', error);
throw error;
}
}
```
## Best Practices
1. **Connection Type Selection**: Choose appropriate connection type based on environment (HTTP/HTTPS)
2. **Failover Strategy**: Implement failover mechanism for high availability
3. **Network Detection**: Detect internal vs external network for optimal performance
4. **Connection Testing**: Test connectivity before establishing full connection
5. **Caching**: Cache connection addresses to reduce API calls
6. **Error Handling**: Handle network errors gracefully with retry logic
7. **Security**: Use secure connections (WSS) in production environments
# Health Check
Source: https://wukong.mintlify.app/en/api/system/health
GET /health
Check the health status of WuKongIM server and cluster
## Overview
The health check endpoint is used to monitor the operational status of WuKongIM server and cluster, ensuring the system is running normally.
```bash cURL theme={null}
curl -X GET "http://localhost:5001/health"
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/health');
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
response = requests.get('http://localhost:5001/health')
data = response.json()
print(data)
```
```go Go theme={null}
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp, err := http.Get("http://localhost:5001/health")
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Println(string(body))
}
```
```json Success Response (200) theme={null}
{
"status": "ok"
}
```
```json Error Response (500) theme={null}
{
"status": "error",
"message": "Cluster status check failed"
}
```
## Response Fields
Health status: `ok` indicates normal, `error` indicates abnormal
Error message (only appears when status is error)
## Status Codes
| Status Code | Description |
| ----------- | ------------------------------------ |
| 200 | Server health status is normal |
| 500 | Server or cluster status is abnormal |
## Use Cases
### Load Balancer Health Checks
**Nginx Configuration**:
```nginx theme={null}
upstream wukongim_backend {
server 192.168.1.10:5001;
server 192.168.1.11:5001;
server 192.168.1.12:5001;
}
server {
location /health {
proxy_pass http://wukongim_backend/health;
proxy_connect_timeout 5s;
proxy_read_timeout 5s;
}
location / {
proxy_pass http://wukongim_backend;
# Health check configuration
health_check uri=/health interval=30s fails=3 passes=2;
}
}
```
**HAProxy Configuration**:
```haproxy theme={null}
backend wukongim_servers
balance roundrobin
option httpchk GET /health
http-check expect status 200
server wk1 192.168.1.10:5001 check inter 30s
server wk2 192.168.1.11:5001 check inter 30s
server wk3 192.168.1.12:5001 check inter 30s
```
### Container Orchestration
**Docker Compose**:
```yaml theme={null}
version: '3.7'
services:
wukongim:
image: registry.cn-shanghai.aliyuncs.com/wukongim/wukongim:v2
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:5001/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
ports:
- "5001:5001"
```
**Kubernetes Deployment**:
```yaml theme={null}
apiVersion: apps/v1
kind: Deployment
metadata:
name: wukongim
spec:
replicas: 3
selector:
matchLabels:
app: wukongim
template:
metadata:
labels:
app: wukongim
spec:
containers:
- name: wukongim
image: registry.cn-shanghai.aliyuncs.com/wukongim/wukongim:v2
ports:
- containerPort: 5001
livenessProbe:
httpGet:
path: /health
port: 5001
initialDelaySeconds: 30
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 5001
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
```
### Monitoring and Alerting
**Prometheus Monitoring**:
```yaml theme={null}
# prometheus.yml
scrape_configs:
- job_name: 'wukongim-health'
metrics_path: '/health'
static_configs:
- targets: ['192.168.1.10:5001', '192.168.1.11:5001', '192.168.1.12:5001']
scrape_interval: 30s
scrape_timeout: 10s
```
**Custom Health Check Script**:
```bash theme={null}
#!/bin/bash
SERVERS=("192.168.1.10:5001" "192.168.1.11:5001" "192.168.1.12:5001")
WEBHOOK_URL="https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
for server in "${SERVERS[@]}"; do
response=$(curl -s -o /dev/null -w "%{http_code}" "http://$server/health" --max-time 10)
if [ "$response" != "200" ]; then
# Send alert
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"🚨 WuKongIM Health Check Failed: $server returned $response\"}" \
"$WEBHOOK_URL"
fi
done
```
### Application Integration
**Service Discovery**:
```javascript theme={null}
class WuKongIMServiceDiscovery {
constructor(servers) {
this.servers = servers;
this.healthyServers = [];
this.checkInterval = 30000; // 30 seconds
this.startHealthChecks();
}
async checkServerHealth(server) {
try {
const response = await fetch(`http://${server}/health`, {
timeout: 5000
});
return response.status === 200;
} catch (error) {
console.error(`Health check failed for ${server}:`, error);
return false;
}
}
async updateHealthyServers() {
const healthChecks = this.servers.map(async (server) => {
const isHealthy = await this.checkServerHealth(server);
return { server, isHealthy };
});
const results = await Promise.all(healthChecks);
this.healthyServers = results
.filter(result => result.isHealthy)
.map(result => result.server);
console.log('Healthy servers:', this.healthyServers);
}
startHealthChecks() {
this.updateHealthyServers();
setInterval(() => {
this.updateHealthyServers();
}, this.checkInterval);
}
getHealthyServer() {
if (this.healthyServers.length === 0) {
throw new Error('No healthy WuKongIM servers available');
}
// Round-robin selection
const server = this.healthyServers[Math.floor(Math.random() * this.healthyServers.length)];
return server;
}
}
// Usage
const discovery = new WuKongIMServiceDiscovery([
'192.168.1.10:5001',
'192.168.1.11:5001',
'192.168.1.12:5001'
]);
```
## Best Practices
1. **Monitoring Frequency**: Recommended to check health status every 30-60 seconds
2. **Timeout Settings**: Set reasonable timeout values to avoid false alarms
3. **Load Balancing**: Can be used for load balancer health checks
4. **Container Orchestration**: Suitable for Docker and Kubernetes health check configurations
5. **Alerting Mechanism**: Integrate with monitoring systems for automated alerting
6. **Graceful Degradation**: Implement fallback mechanisms when health checks fail
7. **Circuit Breaker**: Use circuit breaker pattern to handle unhealthy services
8. **Logging**: Log health check results for troubleshooting and analysis
# Get Migration Result
Source: https://wukong.mintlify.app/en/api/system/migrate
GET /migrate/result
Get the result status of data migration process
## Overview
Get the result status of data migration process, used to monitor the progress and status of data migration.
```bash cURL theme={null}
curl -X GET "http://localhost:5001/migrate/result"
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/migrate/result');
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
response = requests.get('http://localhost:5001/migrate/result')
data = response.json()
print(data)
```
```go Go theme={null}
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func main() {
resp, err := http.Get("http://localhost:5001/migrate/result")
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Migration Completed theme={null}
{
"status": "completed",
"step": "message",
"last_err": null,
"try_count": 1
}
```
```json Migration In Progress theme={null}
{
"status": "running",
"step": "user",
"last_err": null,
"try_count": 2
}
```
```json Migration Already Completed (Historical Status) theme={null}
{
"status": "migrated",
"step": "channel",
"last_err": null,
"try_count": 1
}
```
```json Migration Error theme={null}
{
"status": "running",
"step": "message",
"last_err": "Database connection timeout",
"try_count": 3
}
```
## Response Fields
Migration status:
* `running` - Migration is in progress
* `completed` - Migration has completed
* `migrated` - Migration has completed (historical status)
* `failed` - Migration has failed
Current migration step, e.g., `message`, `user`, `channel`, etc.
Last error message, `null` if no error occurred
Number of attempts made
## Status Codes
| Status Code | Description |
| ----------- | --------------------------------------- |
| 200 | Successfully retrieved migration result |
| 500 | Internal server error |
## Use Cases
### Migration Monitoring
**Real-time Migration Monitoring**:
```javascript theme={null}
// Monitor migration progress in real-time
class MigrationMonitor {
constructor(checkInterval = 5000) {
this.checkInterval = checkInterval;
this.isMonitoring = false;
this.lastStatus = null;
this.callbacks = {
onProgress: null,
onComplete: null,
onError: null
};
}
async startMonitoring() {
if (this.isMonitoring) return;
this.isMonitoring = true;
console.log('Starting migration monitoring...');
while (this.isMonitoring) {
try {
const status = await this.checkMigrationStatus();
await this.handleStatusUpdate(status);
// Stop monitoring if migration is complete or failed
if (status.status === 'completed' || status.status === 'migrated') {
this.isMonitoring = false;
if (this.callbacks.onComplete) {
this.callbacks.onComplete(status);
}
break;
}
// Handle errors
if (status.last_err) {
if (this.callbacks.onError) {
this.callbacks.onError(status);
}
}
await this.delay(this.checkInterval);
} catch (error) {
console.error('Migration monitoring error:', error);
if (this.callbacks.onError) {
this.callbacks.onError({ error: error.message });
}
await this.delay(this.checkInterval);
}
}
}
async checkMigrationStatus() {
const response = await fetch('/migrate/result');
return await response.json();
}
async handleStatusUpdate(status) {
// Check if status has changed
if (!this.lastStatus || this.hasStatusChanged(this.lastStatus, status)) {
console.log(`Migration status: ${status.status}, step: ${status.step}, attempts: ${status.try_count}`);
if (status.last_err) {
console.warn(`Migration error: ${status.last_err}`);
}
if (this.callbacks.onProgress) {
this.callbacks.onProgress(status);
}
this.lastStatus = status;
}
}
hasStatusChanged(oldStatus, newStatus) {
return oldStatus.status !== newStatus.status ||
oldStatus.step !== newStatus.step ||
oldStatus.try_count !== newStatus.try_count ||
oldStatus.last_err !== newStatus.last_err;
}
stopMonitoring() {
this.isMonitoring = false;
console.log('Migration monitoring stopped');
}
onProgress(callback) {
this.callbacks.onProgress = callback;
}
onComplete(callback) {
this.callbacks.onComplete = callback;
}
onError(callback) {
this.callbacks.onError = callback;
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage
const monitor = new MigrationMonitor(3000); // Check every 3 seconds
monitor.onProgress((status) => {
updateProgressUI(status);
});
monitor.onComplete((status) => {
showCompletionNotification(status);
});
monitor.onError((status) => {
showErrorAlert(status);
});
await monitor.startMonitoring();
```
### Migration Dashboard
**Migration Dashboard Implementation**:
```javascript theme={null}
// Create a comprehensive migration dashboard
class MigrationDashboard {
constructor() {
this.migrationHistory = [];
this.currentMigration = null;
this.monitor = new MigrationMonitor(2000);
this.setupEventHandlers();
}
setupEventHandlers() {
this.monitor.onProgress((status) => {
this.updateCurrentMigration(status);
this.renderDashboard();
});
this.monitor.onComplete((status) => {
this.completeMigration(status);
this.renderDashboard();
this.showNotification('Migration completed successfully!', 'success');
});
this.monitor.onError((status) => {
this.handleMigrationError(status);
this.renderDashboard();
this.showNotification(`Migration error: ${status.last_err || status.error}`, 'error');
});
}
async startDashboard() {
// Initialize dashboard
this.renderDashboard();
// Check initial status
try {
const initialStatus = await this.monitor.checkMigrationStatus();
this.updateCurrentMigration(initialStatus);
// Start monitoring if migration is in progress
if (initialStatus.status === 'running') {
await this.monitor.startMonitoring();
}
} catch (error) {
console.error('Failed to get initial migration status:', error);
}
}
updateCurrentMigration(status) {
this.currentMigration = {
...status,
timestamp: new Date().toISOString(),
progress: this.calculateProgress(status.step)
};
}
calculateProgress(step) {
const steps = ['user', 'channel', 'message', 'conversation', 'cleanup'];
const stepIndex = steps.indexOf(step);
return stepIndex >= 0 ? ((stepIndex + 1) / steps.length) * 100 : 0;
}
completeMigration(status) {
const completedMigration = {
...this.currentMigration,
completedAt: new Date().toISOString(),
duration: this.calculateDuration()
};
this.migrationHistory.push(completedMigration);
this.currentMigration = null;
}
handleMigrationError(status) {
if (this.currentMigration) {
this.currentMigration.hasError = true;
this.currentMigration.errorDetails = status.last_err || status.error;
}
}
calculateDuration() {
if (!this.currentMigration || !this.currentMigration.timestamp) {
return null;
}
const start = new Date(this.currentMigration.timestamp);
const end = new Date();
return Math.round((end - start) / 1000); // Duration in seconds
}
renderDashboard() {
const dashboardHTML = `
Migration Dashboard
${this.renderCurrentMigration()}
${this.renderMigrationHistory()}
${this.renderControls()}
`;
document.getElementById('migration-dashboard').innerHTML = dashboardHTML;
}
renderCurrentMigration() {
if (!this.currentMigration) {
return 'No active migration
';
}
const status = this.currentMigration;
const progressBar = `
`;
return `
Current Migration
Status: ${status.status}
Step: ${status.step}
Attempts: ${status.try_count}
${status.hasError ? `
Error: ${status.errorDetails}
` : ''}
Progress: ${status.progress.toFixed(1)}%
${progressBar}
`;
}
renderMigrationHistory() {
if (this.migrationHistory.length === 0) {
return 'No migration history
';
}
const historyItems = this.migrationHistory.map(migration => `
${new Date(migration.completedAt).toLocaleString()}
Duration: ${migration.duration}s
Final Step: ${migration.step}
`).join('');
return `
Migration History
${historyItems}
`;
}
renderControls() {
return `
${this.currentMigration ?
'' :
''
}
`;
}
async refreshStatus() {
try {
const status = await this.monitor.checkMigrationStatus();
this.updateCurrentMigration(status);
this.renderDashboard();
this.showNotification('Status refreshed', 'info');
} catch (error) {
this.showNotification('Failed to refresh status', 'error');
}
}
async startMonitoring() {
await this.monitor.startMonitoring();
this.renderDashboard();
}
stopMonitoring() {
this.monitor.stopMonitoring();
this.renderDashboard();
}
exportLogs() {
const logs = {
currentMigration: this.currentMigration,
history: this.migrationHistory,
exportedAt: new Date().toISOString()
};
const blob = new Blob([JSON.stringify(logs, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `migration-logs-${Date.now()}.json`;
a.click();
URL.revokeObjectURL(url);
}
showNotification(message, type) {
// Implement notification display
console.log(`[${type.toUpperCase()}] ${message}`);
}
}
// Initialize dashboard
const dashboard = new MigrationDashboard();
dashboard.startDashboard();
```
### Automated Migration Management
**Automated Migration Workflow**:
```javascript theme={null}
// Automated migration management with retry logic
class MigrationManager {
constructor() {
this.maxRetries = 3;
this.retryDelay = 30000; // 30 seconds
this.healthCheckInterval = 10000; // 10 seconds
}
async manageMigration() {
let retryCount = 0;
while (retryCount < this.maxRetries) {
try {
const result = await this.runMigrationWithMonitoring();
if (result.success) {
await this.postMigrationTasks(result);
return result;
} else {
retryCount++;
if (retryCount < this.maxRetries) {
console.log(`Migration failed, retrying in ${this.retryDelay/1000} seconds... (${retryCount}/${this.maxRetries})`);
await this.delay(this.retryDelay);
}
}
} catch (error) {
retryCount++;
console.error(`Migration attempt ${retryCount} failed:`, error);
if (retryCount < this.maxRetries) {
await this.delay(this.retryDelay);
}
}
}
throw new Error(`Migration failed after ${this.maxRetries} attempts`);
}
async runMigrationWithMonitoring() {
const monitor = new MigrationMonitor(this.healthCheckInterval);
return new Promise((resolve, reject) => {
let migrationResult = null;
monitor.onComplete((status) => {
migrationResult = { success: true, status };
resolve(migrationResult);
});
monitor.onError((status) => {
if (status.try_count >= 3) { // Max retries reached
migrationResult = { success: false, status };
resolve(migrationResult);
}
});
// Start monitoring
monitor.startMonitoring().catch(reject);
// Set timeout for migration
setTimeout(() => {
monitor.stopMonitoring();
if (!migrationResult) {
reject(new Error('Migration timeout'));
}
}, 30 * 60 * 1000); // 30 minute timeout
});
}
async postMigrationTasks(result) {
console.log('Running post-migration tasks...');
// Verify migration integrity
await this.verifyMigrationIntegrity();
// Update system configuration
await this.updateSystemConfiguration();
// Notify stakeholders
await this.notifyMigrationComplete(result);
console.log('Post-migration tasks completed');
}
async verifyMigrationIntegrity() {
// Implement migration verification logic
console.log('Verifying migration integrity...');
}
async updateSystemConfiguration() {
// Update system configuration after migration
console.log('Updating system configuration...');
}
async notifyMigrationComplete(result) {
// Notify relevant parties about migration completion
console.log('Notifying migration completion...');
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// Usage
const migrationManager = new MigrationManager();
try {
const result = await migrationManager.manageMigration();
console.log('Migration completed successfully:', result);
} catch (error) {
console.error('Migration management failed:', error);
}
```
## Best Practices
1. **Regular Monitoring**: Check migration status every 5-10 seconds during active migration
2. **Error Handling**: Handle network errors and server errors gracefully
3. **Status Caching**: Avoid redundant processing when status hasn't changed
4. **Notification System**: Notify relevant personnel when migration completes or fails
5. **Logging**: Record key status changes during the migration process
6. **Timeout Management**: Set appropriate timeouts for migration operations
7. **Retry Logic**: Implement retry mechanisms for failed migration attempts
8. **Progress Tracking**: Provide clear progress indicators for long-running migrations
# Add System User IDs
Source: https://wukong.mintlify.app/en/api/user/add-system-uids
POST /user/systemuids_add
Add users to the system user ID list
## Overview
Add specified users to the system user ID list, granting them special system user privileges and identification.
## Request Body
### Required Parameters
Array of user IDs to add to the system user list
User ID
```bash cURL theme={null}
curl -X POST "http://localhost:5001/user/systemuids_add" \
-H "Content-Type: application/json" \
-d '{
"uids": ["bot_assistant", "notification_service", "admin_helper"]
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/user/systemuids_add', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
uids: ['bot_assistant', 'notification_service', 'admin_helper']
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"uids": ["bot_assistant", "notification_service", "admin_helper"]
}
response = requests.post('http://localhost:5001/user/systemuids_add', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"uids": []string{"bot_assistant", "notification_service", "admin_helper"},
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/user/systemuids_add",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | ---------------------------------- |
| 200 | System user IDs added successfully |
| 400 | Request parameter error |
| 403 | No administrative permission |
| 500 | Internal server error |
## System User Privileges
### Special Permissions
After becoming a system user, users will gain the following privileges:
| Permission | Description | Scope |
| -------------------- | ---------------------------------------------- | ---------------- |
| Bypass Validation | Skip certain validations when sending messages | Message sending |
| System Channels | Access to system-only channels | Channel access |
| Rate Limit Exemption | Exempt from standard rate limiting | API calls |
| Priority Processing | Higher priority in message processing | Message delivery |
### Permission Hierarchy
```
System Users > Administrators > Whitelist > Regular Users > Blacklist
```
## Use Cases
### Bot Service Registration
**Register AI Assistant**:
```javascript theme={null}
// Register a new AI assistant as system user
async function registerAIAssistant(botId) {
try {
await addSystemUIDs([botId]);
console.log(`AI Assistant ${botId} registered as system user`);
// Grant additional bot permissions
await configureBotPermissions(botId);
} catch (error) {
console.error('Failed to register AI assistant:', error);
}
}
// Usage
await registerAIAssistant('ai_assistant_v2');
```
### Notification Service Setup
**Setup Notification Services**:
```javascript theme={null}
// Setup multiple notification services
async function setupNotificationServices() {
const notificationServices = [
'email_notifications',
'push_notifications',
'sms_notifications',
'webhook_notifications'
];
try {
await addSystemUIDs(notificationServices);
console.log('Notification services registered as system users');
// Configure each service
for (const service of notificationServices) {
await configureNotificationService(service);
}
} catch (error) {
console.error('Failed to setup notification services:', error);
}
}
```
### Administrative Tools
**Register Admin Tools**:
```javascript theme={null}
// Register administrative and monitoring tools
async function registerAdminTools() {
const adminTools = [
'system_monitor',
'log_analyzer',
'performance_tracker',
'security_scanner'
];
await addSystemUIDs(adminTools);
// Grant system-level access
for (const tool of adminTools) {
await grantSystemAccess(tool);
}
}
```
### Integration Services
**Third-party Integration**:
```javascript theme={null}
// Register external integration services
async function registerIntegrations() {
const integrations = [
'slack_integration',
'teams_integration',
'discord_integration',
'telegram_bridge'
];
try {
await addSystemUIDs(integrations);
// Configure integration permissions
for (const integration of integrations) {
await configureIntegrationPermissions(integration, {
canBridgeMessages: true,
canAccessAllChannels: true,
canBypassRateLimit: true
});
}
} catch (error) {
console.error('Failed to register integrations:', error);
}
}
```
### Batch Operations
**Bulk System User Registration**:
```javascript theme={null}
// Register multiple system users in batches
async function bulkRegisterSystemUsers(userGroups) {
const batchSize = 10; // Process in batches to avoid overwhelming the system
for (const group of userGroups) {
const batches = chunkArray(group.users, batchSize);
for (const batch of batches) {
try {
await addSystemUIDs(batch);
console.log(`Registered batch of ${batch.length} users for ${group.category}`);
// Small delay between batches
await delay(100);
} catch (error) {
console.error(`Failed to register batch for ${group.category}:`, error);
}
}
}
}
// Usage
await bulkRegisterSystemUsers([
{
category: 'bots',
users: ['bot1', 'bot2', 'bot3', 'bot4', 'bot5']
},
{
category: 'services',
users: ['service1', 'service2', 'service3']
}
]);
```
## Security Considerations
### Access Control
* **Admin Only**: Only system administrators should have access to this API
* **Audit Logging**: Log all system user additions for security auditing
* **Validation**: Validate user IDs before adding them to prevent injection attacks
### Best Practices
1. **Principle of Least Privilege**: Only grant system user status when necessary
2. **Regular Review**: Periodically review system user list and remove unused accounts
3. **Documentation**: Document the purpose of each system user
4. **Monitoring**: Monitor system user activities for suspicious behavior
5. **Backup**: Maintain backups of system user configurations
### Risk Management
```javascript theme={null}
// Implement safety checks before adding system users
async function safeAddSystemUIDs(uids) {
// Validate user IDs
const validUIDs = uids.filter(uid => isValidUID(uid));
if (validUIDs.length !== uids.length) {
throw new Error('Invalid user IDs detected');
}
// Check if users exist
const existingUsers = await checkUsersExist(validUIDs);
const nonExistentUsers = validUIDs.filter(uid => !existingUsers.includes(uid));
if (nonExistentUsers.length > 0) {
console.warn('Adding non-existent users as system users:', nonExistentUsers);
}
// Add with audit logging
await addSystemUIDs(validUIDs);
await logSystemUserAddition(validUIDs, getCurrentAdmin());
}
```
## Error Handling
```javascript theme={null}
async function handleSystemUserAddition(uids) {
try {
await addSystemUIDs(uids);
return { success: true, message: 'System users added successfully' };
} catch (error) {
if (error.status === 403) {
return { success: false, message: 'Insufficient permissions' };
} else if (error.status === 400) {
return { success: false, message: 'Invalid user IDs provided' };
} else {
return { success: false, message: 'Internal server error' };
}
}
}
```
# Force Device Quit
Source: https://wukong.mintlify.app/en/api/user/device-quit
POST /user/device_quit
Force user device to quit/disconnect
## Overview
Force a specified user's device to quit/disconnect, used for administrators to kick out users or handle abnormal connections.
## Request Body
### Required Parameters
User ID
### Optional Parameters
Device identifier, used to specify a particular device type
* `0` - App (Android, iPhone, iPad devices)
* `1` - Web (Browser, Web applications)
* `2` - Desktop (Desktop applications)
If not specified, all devices for the user will be disconnected.
```bash cURL theme={null}
curl -X POST "http://localhost:5001/user/device_quit" \
-H "Content-Type: application/json" \
-d '{
"uid": "user123",
"device_flag": 1
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/user/device_quit', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
uid: 'user123',
device_flag: 1
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"uid": "user123",
"device_flag": 1
}
response = requests.post('http://localhost:5001/user/device_quit', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"uid": "user123",
"device_flag": 1,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/user/device_quit",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | ------------------------------------------- |
| 200 | Device quit successfully |
| 400 | Request parameter error |
| 404 | User does not exist or device not connected |
| 500 | Internal server error |
## Use Cases
### Administrative Control
**Kick Violating Users**:
```javascript theme={null}
// Kick user from all devices
await forceDeviceQuit("violating_user");
// Kick user from specific device type
await forceDeviceQuit("violating_user", 1); // Web only
```
**Session Management**:
```javascript theme={null}
// Force logout from web sessions for security
await forceDeviceQuit("user123", 1);
```
### Security Management
**Suspicious Activity**:
```javascript theme={null}
// Disconnect suspicious connections
const suspiciousUsers = ["user1", "user2"];
for (const uid of suspiciousUsers) {
await forceDeviceQuit(uid);
}
```
**Account Compromise**:
```javascript theme={null}
// Emergency disconnect all devices
await forceDeviceQuit("compromised_user");
```
### Multi-Device Management
**Device Limit Enforcement**:
```javascript theme={null}
// Enforce single device policy
async function enforceSingleDevice(uid, allowedDeviceFlag) {
// Get current online devices
const status = await getUserOnlineStatus([uid]);
if (status[0]?.online && status[0].device_flag !== allowedDeviceFlag) {
// Disconnect other devices
await forceDeviceQuit(uid, status[0].device_flag);
}
}
```
**Platform Migration**:
```javascript theme={null}
// Force users to migrate to new platform
async function migrateFromOldPlatform(userIds) {
for (const uid of userIds) {
// Disconnect old platform (device_flag: 0)
await forceDeviceQuit(uid, 0);
}
}
```
## Best Practices
1. **Logging**: Always log device quit operations for audit purposes
2. **Notification**: Notify users when their devices are forcibly disconnected
3. **Reason Codes**: Include reason codes for different disconnect scenarios
4. **Rate Limiting**: Implement rate limiting to prevent abuse
5. **Permission Check**: Verify administrator permissions before allowing device quit
6. **Graceful Handling**: Allow users to reconnect after resolving issues
## Security Considerations
* **Admin Only**: This API should only be accessible to system administrators
* **Audit Trail**: Maintain detailed logs of all forced disconnections
* **User Notification**: Consider notifying users about forced disconnections
* **Reason Documentation**: Document reasons for forced disconnections
* **Appeal Process**: Provide a process for users to appeal disconnections
# Get User Online Status
Source: https://wukong.mintlify.app/en/api/user/online-status
POST /user/onlinestatus
Get online status information for multiple users
## Overview
Get online status information for multiple users, including whether they are online and device type.
## Request Body
The request body is an array of user ID strings:
```json theme={null}
["user1", "user2", "user3"]
```
```bash cURL theme={null}
curl -X POST "http://localhost:5001/user/onlinestatus" \
-H "Content-Type: application/json" \
-d '["user1", "user2", "user3"]'
```
```javascript JavaScript theme={null}
const userIds = ["user1", "user2", "user3"];
const response = await fetch('http://localhost:5001/user/onlinestatus', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(userIds)
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
user_ids = ["user1", "user2", "user3"]
response = requests.post('http://localhost:5001/user/onlinestatus', json=user_ids)
data = response.json()
print(data)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
userIds := []string{"user1", "user2", "user3"}
jsonData, _ := json.Marshal(userIds)
resp, err := http.Post(
"http://localhost:5001/user/onlinestatus",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result []map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
[
{
"uid": "user1",
"online": 1,
"device_flag": 1
},
{
"uid": "user2",
"online": 0,
"device_flag": 0
},
{
"uid": "user3",
"online": 1,
"device_flag": 2
}
]
```
## Response Fields
The response is an array, each element contains the following fields:
User ID
Online status
* `0` - Offline
* `1` - Online
Device identifier
* `0` - App (Android, iPhone, iPad devices)
* `1` - Web (Browser, Web applications)
* `2` - Desktop (Desktop applications)
## Status Codes
| Status Code | Description |
| ----------- | ----------------------------------------- |
| 200 | Successfully retrieved user online status |
| 400 | Request parameter error |
| 500 | Internal server error |
## Use Cases
### Contact List Status
Display online status indicators in contact lists:
```javascript theme={null}
// Get online status for contact list
const contactIds = ["friend1", "friend2", "colleague1"];
const statuses = await getUserOnlineStatus(contactIds);
// Update UI with status indicators
statuses.forEach(status => {
updateContactStatus(status.uid, status.online, status.device_flag);
});
```
### Group Member Status
Check online status of group members:
```javascript theme={null}
// Get group member online status
const groupMembers = ["member1", "member2", "member3"];
const memberStatuses = await getUserOnlineStatus(groupMembers);
// Show online member count
const onlineCount = memberStatuses.filter(s => s.online === 1).length;
console.log(`${onlineCount} members online`);
```
### Presence Indicators
Implement presence indicators in chat interfaces:
```javascript theme={null}
// Real-time presence updates
function updatePresenceIndicators(userStatuses) {
userStatuses.forEach(status => {
const indicator = document.querySelector(`[data-user="${status.uid}"] .presence`);
if (indicator) {
indicator.className = status.online ? 'presence online' : 'presence offline';
indicator.title = getDeviceTypeName(status.device_flag);
}
});
}
function getDeviceTypeName(deviceFlag) {
const deviceTypes = {
0: 'Mobile App',
1: 'Web Browser',
2: 'Desktop App'
};
return deviceTypes[deviceFlag] || 'Unknown';
}
```
## Best Practices
1. **Batch Requests**: Query multiple users in a single request to reduce API calls
2. **Caching**: Cache online status for a short period to avoid excessive requests
3. **Real-time Updates**: Combine with WebSocket events for real-time status updates
4. **UI Optimization**: Update UI efficiently when status changes
5. **Privacy**: Respect user privacy settings for status visibility
# Remove System User IDs
Source: https://wukong.mintlify.app/en/api/user/remove-system-uids
POST /user/systemuids_remove
Remove users from the system user ID list
## Overview
Remove specified users from the system user ID list, revoking their special system user privileges and identification.
## Request Body
### Required Parameters
Array of user IDs to remove from the system user list
User ID
```bash cURL theme={null}
curl -X POST "http://localhost:5001/user/systemuids_remove" \
-H "Content-Type: application/json" \
-d '{
"uids": ["old_bot", "deprecated_service"]
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/user/systemuids_remove', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
uids: ['old_bot', 'deprecated_service']
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"uids": ["old_bot", "deprecated_service"]
}
response = requests.post('http://localhost:5001/user/systemuids_remove', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"uids": []string{"old_bot", "deprecated_service"},
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/user/systemuids_remove",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | -------------------------------------- |
| 200 | System user IDs removed successfully |
| 400 | Request parameter error |
| 403 | No administrative permission |
| 404 | Specified user not in system user list |
| 500 | Internal server error |
## Impact of Removal
### Revoked Privileges
When users are removed from the system user list, they lose:
| Privilege | Impact | Immediate Effect |
| -------------------- | ----------------------------------- | ---------------------------------- |
| Bypass Validation | Must pass standard validation | Message sending restrictions apply |
| System Channels | Lose access to system-only channels | Cannot access restricted channels |
| Rate Limit Exemption | Subject to standard rate limits | API calls limited |
| Priority Processing | Normal priority in message queue | Standard message delivery |
### Transition Period
* **Immediate**: System user status revoked
* **Active Connections**: Existing connections remain but lose privileges
* **Cached Permissions**: May take up to 5 minutes to fully propagate
* **Message Queue**: Messages in queue processed with new permissions
## Use Cases
### Service Decommissioning
**Decommission Old Bot Services**:
```javascript theme={null}
// Remove deprecated bot services
async function decommissionBotServices() {
const deprecatedBots = [
'old_chatbot_v1',
'legacy_assistant',
'deprecated_analyzer'
];
try {
// Remove system user privileges
await removeSystemUIDs(deprecatedBots);
// Disable bot services
for (const bot of deprecatedBots) {
await disableBotService(bot);
await revokeAPIKeys(bot);
}
console.log('Bot services decommissioned successfully');
} catch (error) {
console.error('Failed to decommission bot services:', error);
}
}
```
### Security Incident Response
**Emergency Privilege Revocation**:
```javascript theme={null}
// Emergency removal of compromised system users
async function emergencyPrivilegeRevocation(compromisedUIDs) {
try {
// Immediately revoke system privileges
await removeSystemUIDs(compromisedUIDs);
// Force disconnect all sessions
for (const uid of compromisedUIDs) {
await forceDeviceQuit(uid);
}
// Log security incident
await logSecurityIncident({
type: 'privilege_revocation',
affected_users: compromisedUIDs,
timestamp: new Date().toISOString(),
reason: 'security_incident'
});
// Notify security team
await notifySecurityTeam(compromisedUIDs);
} catch (error) {
console.error('Emergency revocation failed:', error);
// Escalate to manual intervention
await escalateToManualIntervention(compromisedUIDs);
}
}
```
### Cleanup Operations
**Regular Cleanup of Unused System Users**:
```javascript theme={null}
// Regular cleanup of inactive system users
async function cleanupInactiveSystemUsers() {
try {
// Get current system users
const systemUIDs = await getSystemUIDs();
// Check activity for each system user
const inactiveUsers = [];
for (const uid of systemUIDs) {
const lastActivity = await getLastActivity(uid);
const daysSinceActivity = (Date.now() - lastActivity) / (1000 * 60 * 60 * 24);
if (daysSinceActivity > 90) { // 90 days inactive
inactiveUsers.push(uid);
}
}
if (inactiveUsers.length > 0) {
// Request approval for removal
const approved = await requestCleanupApproval(inactiveUsers);
if (approved) {
await removeSystemUIDs(inactiveUsers);
console.log(`Cleaned up ${inactiveUsers.length} inactive system users`);
}
}
} catch (error) {
console.error('Cleanup operation failed:', error);
}
}
// Schedule regular cleanup
setInterval(cleanupInactiveSystemUsers, 7 * 24 * 60 * 60 * 1000); // Weekly
```
### Migration and Upgrades
**Service Migration**:
```javascript theme={null}
// Migrate from old service to new service
async function migrateSystemService(oldServiceUID, newServiceUID) {
try {
// Add new service as system user
await addSystemUIDs([newServiceUID]);
// Configure new service with same permissions
await migrateServiceConfiguration(oldServiceUID, newServiceUID);
// Test new service functionality
const testResult = await testServiceFunctionality(newServiceUID);
if (testResult.success) {
// Remove old service from system users
await removeSystemUIDs([oldServiceUID]);
// Gracefully shutdown old service
await gracefulServiceShutdown(oldServiceUID);
console.log(`Successfully migrated from ${oldServiceUID} to ${newServiceUID}`);
} else {
// Rollback if test fails
await removeSystemUIDs([newServiceUID]);
throw new Error('Service migration test failed');
}
} catch (error) {
console.error('Service migration failed:', error);
// Ensure old service remains functional
await ensureServiceHealth(oldServiceUID);
}
}
```
### Batch Operations
**Bulk Removal with Validation**:
```javascript theme={null}
// Safely remove multiple system users with validation
async function bulkRemoveSystemUsers(uidsToRemove, options = {}) {
const {
validateBeforeRemoval = true,
requireApproval = true,
batchSize = 5
} = options;
try {
if (validateBeforeRemoval) {
// Validate each user before removal
const validationResults = await validateUsersForRemoval(uidsToRemove);
const invalidUsers = validationResults.filter(r => !r.canRemove);
if (invalidUsers.length > 0) {
console.warn('Cannot remove users:', invalidUsers.map(u => u.uid));
uidsToRemove = validationResults
.filter(r => r.canRemove)
.map(r => r.uid);
}
}
if (requireApproval) {
const approved = await requestBulkRemovalApproval(uidsToRemove);
if (!approved) {
throw new Error('Bulk removal not approved');
}
}
// Process in batches
const batches = chunkArray(uidsToRemove, batchSize);
const results = [];
for (const batch of batches) {
try {
await removeSystemUIDs(batch);
results.push({ batch, success: true });
// Small delay between batches
await delay(500);
} catch (error) {
results.push({ batch, success: false, error: error.message });
}
}
return results;
} catch (error) {
console.error('Bulk removal operation failed:', error);
throw error;
}
}
```
## Security Considerations
### Pre-removal Validation
```javascript theme={null}
// Validate users before removal
async function validateUserForRemoval(uid) {
const checks = {
isSystemUser: await isInSystemUserList(uid),
hasActiveConnections: await hasActiveConnections(uid),
hasCriticalServices: await hasCriticalServices(uid),
hasRecentActivity: await hasRecentActivity(uid, 24), // 24 hours
isProtectedUser: await isProtectedUser(uid)
};
const canRemove = checks.isSystemUser &&
!checks.isProtectedUser &&
!checks.hasCriticalServices;
return {
uid,
canRemove,
checks,
warnings: generateRemovalWarnings(checks)
};
}
```
### Audit and Compliance
```javascript theme={null}
// Comprehensive audit logging
async function auditSystemUserRemoval(uids, adminUser, reason) {
const auditEntry = {
action: 'system_user_removal',
timestamp: new Date().toISOString(),
admin_user: adminUser,
affected_users: uids,
reason: reason,
system_state_before: await captureSystemState(),
ip_address: await getClientIP(),
user_agent: await getClientUserAgent()
};
await writeAuditLog(auditEntry);
await notifyComplianceTeam(auditEntry);
}
```
## Best Practices
1. **Validation First**: Always validate users before removal
2. **Approval Process**: Implement approval workflows for system user changes
3. **Gradual Rollout**: Remove privileges gradually for critical services
4. **Monitoring**: Monitor system behavior after privilege removal
5. **Rollback Plan**: Have a rollback plan in case of issues
6. **Documentation**: Document reasons for removal
7. **Notification**: Notify relevant teams about privilege changes
# Get System User IDs
Source: https://wukong.mintlify.app/en/api/user/system-uids
GET /user/systemuids
Get the list of system user IDs
## Overview
Get the list of system user IDs, used to identify special built-in user accounts in the system.
```bash cURL theme={null}
curl -X GET "http://localhost:5001/user/systemuids"
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/user/systemuids');
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
response = requests.get('http://localhost:5001/user/systemuids')
data = response.json()
print(data)
```
```go Go theme={null}
package main
import (
"encoding/json"
"fmt"
"net/http"
)
func main() {
resp, err := http.Get("http://localhost:5001/user/systemuids")
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result []string
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
[
"system",
"admin",
"bot",
"notification",
"webhook"
]
```
## Response Fields
List of system user IDs
System user ID
## Status Codes
| Status Code | Description |
| ----------- | ------------------------------------------ |
| 200 | Successfully retrieved system user ID list |
| 500 | Internal server error |
## System User Types
### Common System Users
| User ID | Purpose | Description |
| -------------- | ------------------- | ---------------------------------------------------- |
| `system` | System Messages | Used for system-generated messages and notifications |
| `admin` | Administrator | System administrator account |
| `bot` | Bot Services | Automated bot services and AI assistants |
| `notification` | Notifications | Push notifications and alerts |
| `webhook` | Webhook Integration | External system integrations |
## Use Cases
### Message Filtering
**Filter System Messages**:
```javascript theme={null}
// Get system user IDs
const systemUIDs = await getSystemUIDs();
// Filter out system messages in chat display
function filterUserMessages(messages) {
return messages.filter(message =>
!systemUIDs.includes(message.from_uid)
);
}
// Display only user messages
const userMessages = filterUserMessages(allMessages);
displayMessages(userMessages);
```
### Permission Management
**Check System User Permissions**:
```javascript theme={null}
async function isSystemUser(uid) {
const systemUIDs = await getSystemUIDs();
return systemUIDs.includes(uid);
}
// Grant special permissions to system users
async function checkUserPermissions(uid) {
if (await isSystemUser(uid)) {
return {
canSendToAll: true,
canBypassLimits: true,
canAccessSystemChannels: true
};
}
return {
canSendToAll: false,
canBypassLimits: false,
canAccessSystemChannels: false
};
}
```
### UI Customization
**Special Display for System Messages**:
```javascript theme={null}
async function renderMessage(message) {
const systemUIDs = await getSystemUIDs();
if (systemUIDs.includes(message.from_uid)) {
// Render system message with special styling
return renderSystemMessage(message);
} else {
// Render regular user message
return renderUserMessage(message);
}
}
function renderSystemMessage(message) {
return `
${message.from_uid}
${message.content}
`;
}
```
### Analytics and Reporting
**Separate System vs User Activity**:
```javascript theme={null}
async function analyzeMessageActivity(messages) {
const systemUIDs = await getSystemUIDs();
const userMessages = messages.filter(m => !systemUIDs.includes(m.from_uid));
const systemMessages = messages.filter(m => systemUIDs.includes(m.from_uid));
return {
userActivity: {
count: userMessages.length,
messages: userMessages
},
systemActivity: {
count: systemMessages.length,
messages: systemMessages
}
};
}
```
### Bot Integration
**Identify Bot Messages**:
```javascript theme={null}
async function handleIncomingMessage(message) {
const systemUIDs = await getSystemUIDs();
if (systemUIDs.includes(message.from_uid)) {
// Handle system/bot message
if (message.from_uid === 'bot') {
await processBotCommand(message);
} else if (message.from_uid === 'notification') {
await showNotification(message);
}
} else {
// Handle regular user message
await processUserMessage(message);
}
}
```
## Best Practices
1. **Caching**: Cache system user IDs to avoid repeated API calls
2. **Regular Updates**: Periodically refresh the system user list
3. **Error Handling**: Handle cases where system users might change
4. **UI Distinction**: Clearly distinguish system messages from user messages
5. **Permission Checks**: Always verify system user permissions before granting special access
6. **Logging**: Log interactions with system users for audit purposes
## Security Considerations
* **Access Control**: Ensure only authorized applications can access system user information
* **Rate Limiting**: Implement rate limiting to prevent abuse
* **Audit Logging**: Log all requests to track system user ID access
* **Validation**: Validate system user IDs before using them in operations
# Update User Token
Source: https://wukong.mintlify.app/en/api/user/token
POST /user/token
Update user authentication token
## Overview
Update user authentication token, used for user re-login or token refresh scenarios.
## Request Body
### Required Parameters
Unique user ID for communication, can be random uuid (recommended to use your server's unique user uid) (required by WuKongIMSDK)
Verification token, random uuid (recommended to use your server's user token) (required by WuKongIMSDK)
Device identifier: 0=app, 1=web, 2=desktop (main devices with same user and same device flag will kick each other, secondary devices will coexist)
### Optional Parameters
Device level: 0=secondary device, 1=main device
```bash cURL theme={null}
curl -X POST "http://localhost:5001/user/token" \
-H "Content-Type: application/json" \
-d '{
"uid": "user123",
"token": "new_auth_token_here",
"device_flag": 1,
"device_level": 1
}'
```
```javascript JavaScript theme={null}
const response = await fetch('http://localhost:5001/user/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
uid: 'user123',
token: 'new_auth_token_here',
device_flag: 1,
device_level: 1
})
});
const data = await response.json();
console.log(data);
```
```python Python theme={null}
import requests
data = {
"uid": "user123",
"token": "new_auth_token_here",
"device_flag": 1,
"device_level": 1
}
response = requests.post('http://localhost:5001/user/token', json=data)
result = response.json()
print(result)
```
```go Go theme={null}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
func main() {
data := map[string]interface{}{
"uid": "user123",
"token": "new_auth_token_here",
"device_flag": 1,
"device_level": 1,
}
jsonData, _ := json.Marshal(data)
resp, err := http.Post(
"http://localhost:5001/user/token",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
panic(err)
}
defer resp.Body.Close()
var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Printf("%+v\n", result)
}
```
```json Success Response theme={null}
{
"status": "ok"
}
```
## Response Fields
Operation status, returns `"ok"` on success
## Status Codes
| Status Code | Description |
| ----------- | -------------------------- |
| 200 | Token updated successfully |
| 400 | Request parameter error |
| 500 | Internal server error |
## Best Practices
1. **Token Security**: Ensure tokens have sufficient complexity and uniqueness
2. **Device Identification**: Properly set device\_flag to distinguish different device types
3. **Permission Control**: Use device\_level to implement permission control for different devices
4. **Regular Refresh**: Implement regular token refresh mechanism
5. **Multi-device Management**: Generate different tokens for different devices for easier management and revocation
# Webhook Callbacks
Source: https://wukong.mintlify.app/en/api/webhook
WuKongIM pushes user online status, offline messages and all messages to third-party applications through webhooks
# Webhook Callbacks
## Overview
Some data from WuKongIM will be sent to third-party application services through webhooks, such as user online status, messages that need to be pushed, all messages, etc. All webhooks are **POST requests**, and the event name is passed through query parameters.
For example, if the third-party server provides a webhook address of `http://example.com/webhook`, then the online status webhook would be:
```
http://example.com/webhook?event=user.onlinestatus
```
The request body data would be similar to:
```json theme={null}
["uid1-0-1", "uid2-1-0"]
```
## Webhook Workflow
## Event Types
### 1. User Online Status Notification
Each user's online and offline status will be notified to the third-party server through this webhook.
**Event Name**: `user.onlinestatus`
**Request Method**: `POST`
**Request URL**: `{webhook_url}?event=user.onlinestatus`
#### Request Body
The request body is a string array, each element formatted as:
```
UserUID-DeviceFlag-OnlineStatus-ConnectionID-DeviceOnlineCount-UserTotalOnlineCount
```
**Example Data**:
```json theme={null}
["uid1-1-0-1001-2-4", "uid2-0-0-1001-1-2"]
```
#### Data Field Description
| Position | Field Name | Type | Description |
| -------- | ----------------------- | ------- | ----------------------------------------------------- |
| 1 | User UID | string | User unique identifier |
| 2 | Device Flag | integer | 0=APP, 1=Web |
| 3 | Online Status | integer | 0=Offline, 1=Online |
| 4 | Connection ID | integer | Connection ID established by current device on server |
| 5 | Device Online Count | integer | Online count for same user and same device type |
| 6 | User Total Online Count | integer | Total online count for all user devices |
```bash cURL theme={null}
# Simulate webhook request sent by WuKongIM
curl -X POST "http://your-server.com/webhook?event=user.onlinestatus" \
-H "Content-Type: application/json" \
-d '["user123-1-1-1001-1-1", "user456-0-0-1002-0-0"]'
```
```javascript JavaScript theme={null}
// Server-side code example for receiving webhook
app.post('/webhook', (req, res) => {
const event = req.query.event;
if (event === 'user.onlinestatus') {
const statusData = req.body; // Array format
statusData.forEach(status => {
const [uid, deviceFlag, online, connId, deviceCount, totalCount] = status.split('-');
console.log({
uid,
deviceFlag: parseInt(deviceFlag),
online: parseInt(online),
connId: parseInt(connId),
deviceCount: parseInt(deviceCount),
totalCount: parseInt(totalCount)
});
// Handle user online status change
handleUserOnlineStatus(uid, online === '1');
});
}
res.status(200).send('OK');
});
```
```python Python theme={null}
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
event = request.args.get('event')
if event == 'user.onlinestatus':
status_data = request.json # Array format
for status in status_data:
parts = status.split('-')
uid = parts[0]
device_flag = int(parts[1])
online = int(parts[2])
conn_id = int(parts[3])
device_count = int(parts[4])
total_count = int(parts[5])
# Handle user online status change
handle_user_online_status(uid, online == 1)
return 'OK', 200
```
```go Go theme={null}
package main
import (
"encoding/json"
"net/http"
"strconv"
"strings"
)
func webhookHandler(w http.ResponseWriter, r *http.Request) {
event := r.URL.Query().Get("event")
if event == "user.onlinestatus" {
var statusData []string
json.NewDecoder(r.Body).Decode(&statusData)
for _, status := range statusData {
parts := strings.Split(status, "-")
if len(parts) >= 6 {
uid := parts[0]
deviceFlag, _ := strconv.Atoi(parts[1])
online, _ := strconv.Atoi(parts[2])
connId, _ := strconv.Atoi(parts[3])
deviceCount, _ := strconv.Atoi(parts[4])
totalCount, _ := strconv.Atoi(parts[5])
// Handle user online status change
handleUserOnlineStatus(uid, online == 1)
}
}
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}
```
### 2. Offline Message Notification
Offline message notification mainly notifies the third-party server of messages that need to be pushed offline. After receiving this webhook, the third-party server needs to call mobile vendor push interfaces to push the message content to users in the ToUIDs list.
**Event Name**: `msg.offline`
**Request Method**: `POST`
**Request URL**: `{webhook_url}?event=msg.offline`
#### Request Body
The request body is a MessageResp message object:
Message header information
Message setting identifier
Server message ID (globally unique)
String type server message ID (globally unique)
Client message unique number
Message sequence number (channel unique, ordered increment)
Sender UID
Channel ID
Channel type
Server message timestamp (10 digits, to seconds)
Base64 encoded message content
Recipient user list
Recipient user UID
```javascript JavaScript theme={null}
// Receive offline message webhook
app.post('/webhook', (req, res) => {
const event = req.query.event;
if (event === 'msg.offline') {
const message = req.body;
// Decode message content
const payload = JSON.parse(atob(message.payload));
// Send push notification to users in to_uids
message.to_uids.forEach(uid => {
sendPushNotification(uid, {
title: `Message from ${message.from_uid}`,
body: payload.content,
messageId: message.message_idstr
});
});
}
res.status(200).send('OK');
});
```
```python Python theme={null}
import base64
import json
@app.route('/webhook', methods=['POST'])
def webhook():
event = request.args.get('event')
if event == 'msg.offline':
message = request.json
# Decode message content
payload = json.loads(base64.b64decode(message['payload']).decode('utf-8'))
# Send push notification to users in to_uids
for uid in message['to_uids']:
send_push_notification(uid, {
'title': f"Message from {message['from_uid']}",
'body': payload['content'],
'message_id': message['message_idstr']
})
return 'OK', 200
```
### 3. All Messages Notification
WuKongIM server will push all messages to the third-party server. To reduce pressure on the third-party server, messages are not pushed one by one but with delay processing. By default, batch push occurs every 500 milliseconds (`webhook.msgNotifyEventPushInterval`), which can be configured as needed.
**Event Name**: `msg.notify`
**Request Method**: `POST`
**Request URL**: `{webhook_url}?event=msg.notify`
#### Request Body
The request body is an array of MessageResp message objects, each containing the following fields:
Message header information
Message setting identifier
Server message ID (globally unique)
String type server message ID (globally unique)
Client message unique number
Message sequence number (channel unique, ordered increment)
Sender UID
Channel ID
Channel type
Server message timestamp (10 digits, to seconds)
Base64 encoded message content
```javascript JavaScript theme={null}
// Receive all messages webhook
app.post('/webhook', (req, res) => {
const event = req.query.event;
if (event === 'msg.notify') {
const messages = req.body; // Message array
messages.forEach(message => {
// Decode message content
const payload = JSON.parse(atob(message.payload));
// Save message to database or search engine
saveMessageToDatabase({
messageId: message.message_idstr,
fromUid: message.from_uid,
channelId: message.channel_id,
channelType: message.channel_type,
content: payload,
timestamp: message.timestamp
});
});
}
res.status(200).send('OK');
});
```
```python Python theme={null}
@app.route('/webhook', methods=['POST'])
def webhook():
event = request.args.get('event')
if event == 'msg.notify':
messages = request.json # Message array
for message in messages:
# Decode message content
payload = json.loads(base64.b64decode(message['payload']).decode('utf-8'))
# Save message to database or search engine
save_message_to_database({
'message_id': message['message_idstr'],
'from_uid': message['from_uid'],
'channel_id': message['channel_id'],
'channel_type': message['channel_type'],
'content': payload,
'timestamp': message['timestamp']
})
return 'OK', 200
```
## Configure Webhook
Set the webhook URL in the WuKongIM configuration file:
```yaml theme={null}
webhook:
url: "http://your-server.com/webhook"
timeout: 5s
msgNotifyEventPushInterval: 500ms
```
## Best Practices
1. **Response Speed**: Webhook processing should be as fast as possible to avoid blocking WuKongIM service
2. **Idempotency**: Ensure webhook processing is idempotent and can be safely retried
3. **Error Handling**: Return appropriate HTTP status codes, 2xx indicates success
4. **Async Processing**: For complex business logic, recommend asynchronous processing
5. **Monitoring & Alerting**: Monitor webhook success rate and response time
6. **Security Verification**: Verify request source to prevent malicious requests
## Troubleshooting
### Common Issues
1. **Webhook Not Received**: Check URL configuration and network connectivity
2. **Processing Timeout**: Optimize processing logic to reduce response time
3. **Duplicate Processing**: Implement idempotent processing mechanism
4. **Message Loss**: Ensure correct HTTP status codes are returned
# Channel
Source: https://wukong.mintlify.app/en/getting-started/concepts/channel
Core structure and management methods of WuKongIM channels
## Concept Explanation
### What is a Channel?
A Channel is the carrier for message transmission in WuKongIM, defining the target and scope of message delivery. Each channel has a unique identifier and type, used to organize and manage different communication scenarios.
### Why are Channels Important?
* **Message Routing**: Channels determine where messages are sent, forming the foundation of message transmission
* **Permission Control**: Different channel types have different permissions and management rules
* **Scenario Differentiation**: Channel types distinguish between different scenarios like one-on-one chat and group chat
### Relationship with Other Concepts
* **Message**: All messages must specify a target channel to be sent
* **User**: Users communicate through channels, can be channel creators or participants
* **Conversation**: Each conversation corresponds to a channel; the conversation list is actually the list of channels a user participates in
## Core Structure
Channels are the carriers of message transmission, containing the following core attributes:
| Attribute | Type | Description |
| -------------- | ------- | -------------------------------------- |
| `channel_id` | string | Channel unique identifier |
| `channel_type` | integer | Channel type (1=personal, 2=group) |
| `large` | integer | Large channel identifier (0=no, 1=yes) |
| `ban` | integer | Disabled status (0=no, 1=yes) |
| `disband` | integer | Disbanded status (0=no, 1=yes) |
### Channel Types
* **Personal Channel (1)**: One-on-one private chat
* **Group Channel (2)**: Multi-user group chat
* **Customer Service Channel (3)**: Customer service channel, no permission checks
* **Community Channel (4)**: Community channel, similar to Discord channels
* **Community Topic Channel (5)**: Community topic channel, similar to Discord sub-channels
* **News Channel (6)**: News channel (with temporary subscriber concept, join temporary subscription when viewing news, exit when leaving)
* **Live Channel (9)**: Live channel (live channels don't save recent conversation data)
* **Visitor Channel (10)**: Visitor channel (channel ID is visitor ID, supports only one visitor subscriber and multiple customer service subscribers, can replace customer service channels for customer service scenarios)
* **Personal Agent Channel (11)**: Personal Agent channel (AI Agent channel, channel ID structure is UID\@AgentID, similar to personal chat channel, optimized for AI Agent scenarios)
* **Group Agent Channel (12)**: Group Agent channel (AI Agent group chat channel, similar to group chat channel, optimized for multi-Agent collaboration scenarios)
### Channel Example
```json theme={null}
{
"channel_id": "group123",
"channel_type": 2,
"large": 1,
"ban": 0,
"disband": 0
}
```
## Related API Endpoints
| Endpoint | Method | Description |
| ---------------------------- | ------ | ----------------------- |
| `/channel` | POST | Create channel |
| `/channel/info` | POST | Get channel information |
| `/channel/delete` | DELETE | Delete channel |
| `/channel/subscriber_add` | POST | Add subscribers |
| `/channel/subscriber_remove` | POST | Remove subscribers |
| `/channel/messagesync` | POST | Sync channel messages |
## EasySDK Code Examples
### Send Messages to Different Channel Types
```javascript Web theme={null}
import { WKIM, WKIMChannelType } from 'easyjssdk';
const im = WKIM.init("ws://your-server.com:5200", {
uid: "your_user_id",
token: "your_token"
});
// Send to personal channel (one-on-one chat)
const personalPayload = {
type: 1,
content: "Hello!"
};
await im.send("user123", WKIMChannelType.Person, personalPayload);
// Send to group channel (group chat)
const groupPayload = {
type: 1,
content: "Hello everyone!"
};
await im.send("group123", WKIMChannelType.Group, groupPayload);
```
```swift iOS theme={null}
import WuKongEasySDK
let config = WuKongConfig(
serverUrl: "ws://your-server.com:5200",
uid: "your_user_id",
token: "your_token"
)
let easySDK = WuKongEasySDK(config: config)
// Send to personal channel (one-on-one chat)
let personalPayload = MessagePayload(
type: 1,
content: "Hello!"
)
try await easySDK.send(
channelId: "user123",
channelType: .person,
payload: personalPayload
)
// Send to group channel (group chat)
let groupPayload = MessagePayload(
type: 1,
content: "Hello everyone!"
)
try await easySDK.send(
channelId: "group123",
channelType: .group,
payload: groupPayload
)
```
```kotlin Android theme={null}
import com.githubim.easysdk.WuKongEasySDK
import com.githubim.easysdk.WuKongConfig
import com.githubim.easysdk.WuKongChannelType
val config = WuKongConfig.Builder()
.serverUrl("ws://your-server.com:5200")
.uid("your_user_id")
.token("your_token")
.build()
val easySDK = WuKongEasySDK.getInstance()
easySDK.init(this, config)
// Send to personal channel (one-on-one chat)
val personalPayload = MessagePayload(
type = 1,
content = "Hello!"
)
easySDK.send(
channelId = "user123",
channelType = WuKongChannelType.PERSON,
payload = personalPayload
)
// Send to group channel (group chat)
val groupPayload = MessagePayload(
type = 1,
content = "Hello everyone!"
)
easySDK.send(
channelId = "group123",
channelType = WuKongChannelType.GROUP,
payload = groupPayload
)
```
```dart Flutter theme={null}
import 'package:wukong_easy_sdk/wukong_easy_sdk.dart';
final config = WuKongConfig(
serverUrl: "ws://your-server.com:5200",
uid: "your_user_id",
token: "your_token",
);
final easySDK = WuKongEasySDK.getInstance();
await easySDK.init(config);
// Send to personal channel (one-on-one chat)
final personalPayload = MessagePayload(
type: 1,
content: "Hello!",
);
await easySDK.send(
channelId: "user123",
channelType: WuKongChannelType.person,
payload: personalPayload,
);
// Send to group channel (group chat)
final groupPayload = MessagePayload(
type: 1,
content: "Hello everyone!",
);
await easySDK.send(
channelId: "group123",
channelType: WuKongChannelType.group,
payload: groupPayload,
);
```
### Channel Type Description
**Personal Channel (person)**:
* `channelId` is the other user's ID
* Used for one-on-one private chat
* Example: To send to user "user123", channelId is "user123"
**Group Channel (group)**:
* `channelId` is the group's ID
* Used for multi-user group chat
* Example: To send to group "group123", channelId is "group123"
Channels are uniquely identified by the combination of `channel_id` and `channel_type`, serving as the fundamental carrier for message transmission.
# Conversation
Source: https://wukong.mintlify.app/en/getting-started/concepts/conversation
Core structure and management methods of WuKongIM conversations
## Concept Explanation
### What is a Conversation?
A Conversation is the interaction record between a user and a specific channel, containing the user's message history in that channel, unread status, last active time, and other information. Conversations are the fundamental data structure for chat lists in user interfaces.
### Why are Conversations Important?
* **Chat List**: The conversation list is the chat list that users see, displaying all ongoing conversations
* **Unread Management**: Conversations record the number of unread messages for each chat, helping users quickly understand which chats have new messages
* **Quick Access**: Through the conversation list, users can quickly access recent chat records
### Relationship with Other Concepts
* **Channel**: Each conversation corresponds to a channel; the conversation's `channel_id` is the channel's ID
* **Message**: Conversations display the last message of that channel and the number of unread messages
* **User**: Conversations belong to specific users; different users see different conversation lists
## Core Structure
Conversations contain the following core attributes:
| Attribute | Type | Description |
| -------------- | ------- | ---------------------------------- |
| `channel_id` | string | Channel identifier |
| `channel_type` | integer | Channel type (1=personal, 2=group) |
| `unread` | integer | Number of unread messages |
| `last_msg_seq` | integer | Last message sequence number |
| `timestamp` | integer | Last update timestamp |
| `version` | integer | Conversation version number |
### Conversation Example
```json theme={null}
{
"channel_id": "group123",
"channel_type": 2,
"unread": 5,
"last_msg_seq": 1001,
"timestamp": 1640995200,
"version": 100
}
```
## Related API Endpoints
| Endpoint | Method | Description |
| --------------------------- | ------ | ---------------------- |
| `/conversation/sync` | POST | Sync conversation list |
| `/conversation/setUnread` | POST | Set unread count |
| `/conversation/clearUnread` | POST | Clear unread count |
| `/conversation/delete` | POST | Delete conversation |
## EasySDK Code Examples
### Conversation Management
```javascript Web theme={null}
import { WKIM, WKIMEvent } from 'easyjssdk';
// Listen for conversation updates
im.on(WKIMEvent.ConversationUpdate, (conversations) => {
console.log('Conversations updated:', conversations);
// Update UI conversation list
conversations.forEach(conversation => {
console.log(`Channel: ${conversation.channel_id}, Unread: ${conversation.unread}`);
});
});
// Get conversation list
const conversations = await im.getConversations();
console.log('Current conversations:', conversations);
```
```swift iOS theme={null}
import WuKongEasySDK
// Listen for conversation updates
easySDK.onConversationUpdate { conversations in
print("Conversations updated:", conversations)
// Update UI conversation list
conversations.forEach { conversation in
print("Channel: \(conversation.channelId), Unread: \(conversation.unread)")
}
}
// Get conversation list
Task {
let conversations = await easySDK.getConversations()
print("Current conversations:", conversations)
}
```
```kotlin Android theme={null}
import com.githubim.easysdk.WuKongEvent
import com.githubim.easysdk.listener.WuKongEventListener
// Listen for conversation updates
easySDK.addEventListener(WuKongEvent.CONVERSATION_UPDATE, object : WuKongEventListener> {
override fun onEvent(conversations: List) {
Log.d("WuKong", "Conversations updated: $conversations")
// Update UI conversation list
conversations.forEach { conversation ->
Log.d("WuKong", "Channel: ${conversation.channelId}, Unread: ${conversation.unread}")
}
}
})
// Get conversation list
lifecycleScope.launch {
val conversations = easySDK.getConversations()
Log.d("WuKong", "Current conversations: $conversations")
}
```
```dart Flutter theme={null}
import 'package:wukong_easy_sdk/wukong_easy_sdk.dart';
// Listen for conversation updates
easySDK.addEventListener(WuKongEvent.conversationUpdate, (List conversations) {
print('Conversations updated: $conversations');
// Update UI conversation list
conversations.forEach((conversation) {
print('Channel: ${conversation.channelId}, Unread: ${conversation.unread}');
});
});
// Get conversation list
final conversations = await easySDK.getConversations();
print('Current conversations: $conversations');
```
### Clear Unread Messages
```javascript Web theme={null}
// Clear unread count for specific conversation
await im.clearUnread("group123", 2); // channel_id, channel_type
console.log("Unread count cleared");
```
```swift iOS theme={null}
// Clear unread count for specific conversation
Task {
try await easySDK.clearUnread(channelId: "group123", channelType: 2)
print("Unread count cleared")
}
```
```kotlin Android theme={null}
// Clear unread count for specific conversation
lifecycleScope.launch {
try {
easySDK.clearUnread("group123", 2) // channel_id, channel_type
Log.d("WuKong", "Unread count cleared")
} catch (e: Exception) {
Log.e("WuKong", "Failed to clear unread: $e")
}
}
```
```dart Flutter theme={null}
// Clear unread count for specific conversation
try {
await easySDK.clearUnread("group123", 2); // channel_id, channel_type
print("Unread count cleared");
} catch (e) {
print("Failed to clear unread: $e");
}
```
Conversations are sorted by last message time and serve as the fundamental data structure for chat lists in user interfaces.
# Device
Source: https://wukong.mintlify.app/en/getting-started/concepts/device
Core structure and management methods of WuKongIM devices
## Concept Explanation
### What is a Device?
A Device is the terminal carrier for users to access the WuKongIM system, representing the specific device used by users to send and receive messages. Each device has a unique identifier and type, supporting users to simultaneously use instant messaging services on multiple devices.
### Why are Devices Important?
* **Multi-device Sync**: Users can log in simultaneously on multiple devices like phones, computers, and tablets with real-time message synchronization
* **Device Management**: Can view and manage all user login devices, supporting remote device logout
* **Push Strategy**: Adopt different message push strategies based on device type
### Relationship with Other Concepts
* **User**: A user can own multiple devices, each device is associated with a user ID
* **Message**: Messages are synchronized to all online devices of the user
* **Connection**: Each device has an independent network connection
## Core Structure
Devices contain the following core attributes:
| Attribute | Type | Description |
| ------------- | ------- | ---------------------------- |
| `cid` | integer | Connection unique identifier |
| `uid` | string | User ID that owns the device |
| `device_id` | string | Device unique identifier |
| `device_flag` | integer | Device type identifier |
| `uptime` | string | Online duration |
| `idle` | string | Idle time |
### Device Example
```json theme={null}
{
"cid": 12345,
"uid": "user123",
"device_id": "device_456",
"device_flag": 1,
"uptime": "1h30m",
"idle": "5m"
}
```
## Related API Endpoints
| Endpoint | Method | Description |
| ------------------- | ------ | --------------------------- |
| `/user/device_quit` | POST | Force device offline |
| `/connz` | GET | View connection information |
## Device Type Identifiers
| Identifier Value | Device Type | Description |
| ---------------- | ----------- | ----------------------------- |
| 0 | App | Android, iPhone, iPad devices |
| 1 | Web | Browser, Web applications |
| 2 | Desktop | Desktop applications |
## EasySDK Code Examples
### Device Initialization and Configuration
```javascript Web theme={null}
import { WKIM } from 'easyjssdk';
// Device type is automatically set to Web (1) during initialization
const im = WKIM.init("ws://your-server.com:5200", {
uid: "user123",
token: "your_token",
deviceId: "web_device_001" // Optional: custom device ID
});
// EasySDK automatically handles device type, Web platform defaults to device type 1
console.log('Current platform: Web');
console.log('Device type: 1 (Web)');
```
```swift iOS theme={null}
import WuKongEasySDK
// Device type is automatically set to APP (0) during initialization
let config = WuKongConfig(
serverUrl: "ws://your-server.com:5200",
uid: "user123",
token: "your_token",
deviceId: "ios_device_001" // Optional: custom device ID
)
let easySDK = WuKongEasySDK(config: config)
// EasySDK automatically handles device type, iOS platform defaults to device type 0 (APP)
print("Current platform: APP")
print("Device type: 0 (APP)")
```
```kotlin Android theme={null}
import com.githubim.easysdk.WuKongEasySDK
import com.githubim.easysdk.WuKongConfig
// Device type is automatically set to APP (0) during initialization
val config = WuKongConfig.Builder()
.serverUrl("ws://your-server.com:5200")
.uid("user123")
.token("your_token")
.deviceId("android_device_001") // Optional: custom device ID
.build()
val easySDK = WuKongEasySDK.getInstance()
easySDK.init(this, config)
// EasySDK automatically handles device type, Android platform defaults to device type 0
Log.d("WuKong", "Current platform: APP")
Log.d("WuKong", "Device type: 0 (APP)")
```
```dart Flutter theme={null}
import 'package:wukong_easy_sdk/wukong_easy_sdk.dart';
// Device type is automatically set during initialization
final config = WuKongConfig(
serverUrl: "ws://your-server.com:5200",
uid: "user123",
token: "your_token",
deviceId: "flutter_device_001", // Optional: custom device ID
);
final easySDK = WuKongEasySDK.getInstance();
await easySDK.init(config);
// EasySDK automatically handles device type
print('Current platform: Flutter');
print('Device type: Auto-detected');
```
### Multi-device Synchronization
**Important Note**: EasySDK automatically handles message synchronization between multiple devices. When a user logs in on multiple devices, messages are automatically synchronized to all online devices.
```javascript Web theme={null}
// EasySDK automatically handles multi-device sync
// When receiving messages, all online devices will receive the same message
im.on(WKIMEvent.Message, (message) => {
console.log('Received message (auto-synced to all devices):', message);
});
// Check connection status
console.log('Current device connection status:', im.isConnected());
```
```swift iOS theme={null}
// EasySDK automatically handles multi-device sync
// When receiving messages, all online devices will receive the same message
easySDK.onMessage { message in
print("Received message (auto-synced to all devices):", message)
}
// Check connection status
print("Current device connection status:", easySDK.isConnected())
```
```kotlin Android theme={null}
// EasySDK automatically handles multi-device sync
// When receiving messages, all online devices will receive the same message
easySDK.addEventListener(WuKongEvent.MESSAGE, object : WuKongEventListener {
override fun onEvent(message: Message) {
Log.d("WuKong", "Received message (auto-synced to all devices): $message")
}
})
// Check connection status
Log.d("WuKong", "Current device connection status: ${easySDK.isConnected()}")
```
```dart Flutter theme={null}
// EasySDK automatically handles multi-device sync
// When receiving messages, all online devices will receive the same message
easySDK.addEventListener(WuKongEvent.message, (Message message) {
print('Received message (auto-synced to all devices): $message');
});
// Check connection status
print('Current device connection status: ${easySDK.isConnected()}');
```
### Device Management
```javascript Web theme={null}
// EasySDK automatically manages device connections
// Each device maintains its own connection state
// Listen for connection events
im.on(WKIMEvent.Connect, (result) => {
console.log('Device connected:', result);
});
im.on(WKIMEvent.Disconnect, (info) => {
console.log('Device disconnected:', info);
});
// Graceful disconnect
await im.disconnect();
console.log('Device disconnected gracefully');
```
```swift iOS theme={null}
// EasySDK automatically manages device connections
// Each device maintains its own connection state
// Listen for connection events
easySDK.onConnect { result in
print("Device connected:", result)
}
easySDK.onDisconnect { info in
print("Device disconnected:", info)
}
// Graceful disconnect
Task {
await easySDK.disconnect()
print("Device disconnected gracefully")
}
```
```kotlin Android theme={null}
// EasySDK automatically manages device connections
// Each device maintains its own connection state
// Listen for connection events
easySDK.addEventListener(WuKongEvent.CONNECT, object : WuKongEventListener {
override fun onEvent(result: ConnectResult) {
Log.d("WuKong", "Device connected: $result")
}
})
easySDK.addEventListener(WuKongEvent.DISCONNECT, object : WuKongEventListener {
override fun onEvent(info: DisconnectInfo) {
Log.d("WuKong", "Device disconnected: $info")
}
})
// Graceful disconnect
easySDK.disconnect()
Log.d("WuKong", "Device disconnected gracefully")
```
```dart Flutter theme={null}
// EasySDK automatically manages device connections
// Each device maintains its own connection state
// Listen for connection events
easySDK.addEventListener(WuKongEvent.connect, (ConnectResult result) {
print('Device connected: $result');
});
easySDK.addEventListener(WuKongEvent.disconnect, (DisconnectInfo info) {
print('Device disconnected: $info');
});
// Graceful disconnect
await easySDK.disconnect();
print('Device disconnected gracefully');
```
A user can be online on multiple devices simultaneously. EasySDK automatically handles message synchronization between multiple devices. Each device has an independent connection and device identifier.
# Message
Source: https://wukong.mintlify.app/en/getting-started/concepts/message
Core structure and usage methods of WuKongIM messages
## Concept Explanation
### What is a Message?
A Message is the basic unit of information transmission in WuKongIM, serving as the carrier for real-time communication between users. Each message contains information about the sender, receiver, content, and metadata.
### Why are Messages Important?
* **Communication Foundation**: Messages are the core of instant messaging systems; all chat functionality is based on message transmission
* **Data Carrier**: Messages can transmit not only text but also various types of data like images, files, and locations
* **Status Tracking**: Each message has a unique identifier and status, making it easy to track message sending, receiving, and read status
### Relationship with Other Concepts
* **Channel**: Messages are transmitted through channels, which define the message destination
* **User**: Messages have clear senders and receivers, all of whom are users in the system
* **Conversation**: Messages update corresponding conversation records, affecting conversation list display
## Core Structure
Messages contain the following core attributes:
| Attribute | Type | Description |
| --------------- | ------- | -------------------------------------- |
| `message_id` | integer | Message unique identifier |
| `message_seq` | integer | Message sequence number within channel |
| `client_msg_no` | string | Client message identifier |
| `from_uid` | string | Sender user ID |
| `channel_id` | string | Target channel ID |
| `channel_type` | integer | Channel type (1=personal, 2=group) |
| `timestamp` | integer | Message timestamp |
| `payload` | string | Base64 encoded message content |
### Message Example
```json theme={null}
{
"message_id": 123456789,
"message_seq": 1001,
"client_msg_no": "client_msg_123",
"from_uid": "user123",
"channel_id": "group123",
"channel_type": 2,
"timestamp": 1640995200,
"payload": "SGVsbG8gV29ybGQ="
}
```
## Related API Endpoints
| Endpoint | Method | Description |
| ---------------------- | ------ | -------------------------- |
| `/message/send` | POST | Send single message |
| `/message/sendbatch` | POST | Send batch messages |
| `/message/search` | POST | Search historical messages |
| `/channel/messagesync` | POST | Sync channel messages |
## EasySDK Code Examples
### Send Messages
```javascript Web theme={null}
import { WKIM, WKIMChannelType } from 'easyjssdk';
// Initialize SDK
const im = WKIM.init("ws://your-server.com:5200", {
uid: "your_user_id",
token: "your_token"
});
// Send text message
const textPayload = {
type: 1,
content: "Hello, World!"
};
const result = await im.send("friend_user_id", WKIMChannelType.Person, textPayload);
// Send image message
const imagePayload = {
type: 2,
url: "https://example.com/image.jpg",
width: 800,
height: 600
};
const imageResult = await im.send("group_id", WKIMChannelType.Group, imagePayload);
```
```swift iOS theme={null}
import WuKongEasySDK
// Initialize SDK
let config = WuKongConfig(
serverUrl: "ws://your-server.com:5200",
uid: "your_user_id",
token: "your_token"
)
let easySDK = WuKongEasySDK(config: config)
// Send text message
let textPayload = MessagePayload(
type: 1,
content: "Hello, World!"
)
try await easySDK.send(
channelId: "friend_user_id",
channelType: .person,
payload: textPayload
)
// Send image message
let imagePayload = MessagePayload(
type: 2,
url: "https://example.com/image.jpg",
width: 800,
height: 600
)
try await easySDK.send(
channelId: "group_id",
channelType: .group,
payload: imagePayload
)
```
```kotlin Android theme={null}
import com.githubim.easysdk.WuKongEasySDK
import com.githubim.easysdk.WuKongConfig
import com.githubim.easysdk.WuKongChannelType
// Initialize SDK
val config = WuKongConfig.Builder()
.serverUrl("ws://your-server.com:5200")
.uid("your_user_id")
.token("your_token")
.build()
val easySDK = WuKongEasySDK.getInstance()
easySDK.init(this, config)
// Send text message
val textPayload = MessagePayload(
type = 1,
content = "Hello, World!"
)
easySDK.send(
channelId = "friend_user_id",
channelType = WuKongChannelType.PERSON,
payload = textPayload
)
// Send image message
val imagePayload = MessagePayload(
type = 2,
url = "https://example.com/image.jpg",
width = 800,
height = 600
)
easySDK.send(
channelId = "group_id",
channelType = WuKongChannelType.GROUP,
payload = imagePayload
)
```
```dart Flutter theme={null}
import 'package:wukong_easy_sdk/wukong_easy_sdk.dart';
// Initialize SDK
final config = WuKongConfig(
serverUrl: "ws://your-server.com:5200",
uid: "your_user_id",
token: "your_token",
);
final easySDK = WuKongEasySDK.getInstance();
await easySDK.init(config);
// Send text message
final textPayload = MessagePayload(
type: 1,
content: "Hello, World!",
);
await easySDK.send(
channelId: "friend_user_id",
channelType: WuKongChannelType.person,
payload: textPayload,
);
// Send image message
final imagePayload = MessagePayload(
type: 2,
url: "https://example.com/image.jpg",
width: 800,
height: 600,
);
await easySDK.send(
channelId: "group_id",
channelType: WuKongChannelType.group,
payload: imagePayload,
);
```
### Listen for Messages
```javascript Web theme={null}
import { WKIMEvent } from 'easyjssdk';
// Listen for new messages
im.on(WKIMEvent.Message, (message) => {
console.log("Received new message:", message);
console.log("Message content:", message.payload);
console.log("Sender:", message.fromUid);
});
```
```swift iOS theme={null}
// Listen for new messages
easySDK.onMessage { message in
print("Received new message:", message)
print("Message content:", message.payload)
print("Sender:", message.fromUid)
}
```
```kotlin Android theme={null}
import com.githubim.easysdk.WuKongEvent
import com.githubim.easysdk.listener.WuKongEventListener
// Listen for new messages
easySDK.addEventListener(WuKongEvent.MESSAGE, object : WuKongEventListener {
override fun onEvent(message: Message) {
Log.d("WuKong", "Received new message: $message")
Log.d("WuKong", "Message content: ${message.payload}")
Log.d("WuKong", "Sender: ${message.fromUid}")
}
})
```
```dart Flutter theme={null}
// Listen for new messages
easySDK.addEventListener(WuKongEvent.message, (Message message) {
print("Received new message: $message");
print("Message content: ${message.payload}");
print("Sender: ${message.fromUid}");
});
```
### Message Type Handling
```javascript Web theme={null}
// Handle different content based on message type
im.on(WKIMEvent.Message, (message) => {
const payload = JSON.parse(message.payload);
switch (payload.type) {
case 1: // Text message
console.log("Text message:", payload.content);
break;
case 2: // Image message
console.log("Image message:", payload.url);
break;
case 100: // Custom message
console.log("Custom message:", payload);
break;
}
});
```
```swift iOS theme={null}
// Handle different content based on message type
easySDK.onMessage { message in
if let payloadData = Data(base64Encoded: message.payload),
let payload = try? JSONSerialization.jsonObject(with: payloadData) as? [String: Any],
let type = payload["type"] as? Int {
switch type {
case 1: // Text message
if let content = payload["content"] as? String {
print("Text message:", content)
}
case 2: // Image message
if let url = payload["url"] as? String {
print("Image message:", url)
}
case 100: // Custom message
print("Custom message:", payload)
default:
print("Unknown message type:", type)
}
}
}
```
```kotlin Android theme={null}
// Handle different content based on message type
easySDK.addEventListener(WuKongEvent.MESSAGE, object : WuKongEventListener {
override fun onEvent(message: Message) {
try {
val payloadJson = JSONObject(String(Base64.decode(message.payload, Base64.DEFAULT)))
val type = payloadJson.getInt("type")
when (type) {
1 -> { // Text message
val content = payloadJson.getString("content")
Log.d("WuKong", "Text message: $content")
}
2 -> { // Image message
val url = payloadJson.getString("url")
Log.d("WuKong", "Image message: $url")
}
100 -> { // Custom message
Log.d("WuKong", "Custom message: $payloadJson")
}
}
} catch (e: Exception) {
Log.e("WuKong", "Failed to parse message", e)
}
}
})
```
```dart Flutter theme={null}
// Handle different content based on message type
easySDK.addEventListener(WuKongEvent.message, (Message message) {
try {
final payloadString = utf8.decode(base64.decode(message.payload));
final payload = json.decode(payloadString);
final type = payload['type'];
switch (type) {
case 1: // Text message
print("Text message: ${payload['content']}");
break;
case 2: // Image message
print("Image message: ${payload['url']}");
break;
case 100: // Custom message
print("Custom message: $payload");
break;
}
} catch (e) {
print("Failed to parse message: $e");
}
});
```
The message content `payload` field uses Base64 encoding, with the specific format defined by the application layer.
# User
Source: https://wukong.mintlify.app/en/getting-started/concepts/user
Core structure and management methods of WuKongIM users
## Concept Explanation
### What is a User?
A User is the basic entity in the WuKongIM system, representing an individual or application using the instant messaging service. Each user has a unique identifier and related attributes, serving as the subject for sending and receiving messages.
### Why are Users Important?
* **Identity Identification**: User ID is the key to uniquely identify a user in the system
* **Permission Foundation**: All message sending and receiving permissions are based on user identity
* **Status Management**: User online status determines the method and timing of message delivery
### Relationship with Other Concepts
* **Message**: Users are the senders and receivers of messages
* **Channel**: Users communicate through channels; personal channel ID is the user ID
* **Conversation**: User conversation list shows all chats the user participates in
* **Device**: A user can log in and use the service on multiple devices
## Core Structure
Users contain the following core attributes:
| Attribute | Type | Description |
| ------------- | ------- | ----------------------------------- |
| `uid` | string | User unique identifier |
| `online` | integer | Online status (0=offline, 1=online) |
| `device_flag` | integer | Device type identifier |
### Device Type Identifiers
| Value | Device Type | Description |
| ----- | ----------- | ------------------------- |
| 1 | iOS | iPhone, iPad devices |
| 2 | Android | Android devices |
| 3 | Web | Browser, Web applications |
| 4 | Desktop | Desktop applications |
### User Example
```json theme={null}
{
"uid": "user123",
"online": 1,
"device_flag": 1
}
```
## Related API Endpoints
| Endpoint | Method | Description |
| -------------------- | ------ | ------------------------ |
| `/user/onlinestatus` | POST | Query user online status |
| `/user/token` | POST | Update user token |
| `/user/device_quit` | POST | Force device offline |
| `/user/systemuids` | GET | Get system user list |
## EasySDK Code Examples
### User Initialization and Connection
```javascript Web theme={null}
import { WKIM, WKIMEvent } from 'easyjssdk';
// Initialize SDK
const im = WKIM.init("ws://your-server.com:5200", {
uid: "user123", // User unique identifier
token: "your_auth_token" // User authentication token
});
// Listen for connection status
im.on(WKIMEvent.Connect, (result) => {
console.log('User connected:', result);
});
im.on(WKIMEvent.Disconnect, (disconnectInfo) => {
console.log('User disconnected:', disconnectInfo);
});
// Connect to server
try {
await im.connect();
console.log("Connection successful!");
} catch (error) {
console.error("Connection failed:", error);
}
```
```swift iOS theme={null}
import WuKongEasySDK
// Configure user information
let config = WuKongConfig(
serverUrl: "ws://your-server.com:5200",
uid: "user123", // User unique identifier
token: "your_auth_token" // User authentication token
)
// Initialize SDK
let easySDK = WuKongEasySDK(config: config)
// Listen for connection status
easySDK.onConnect { result in
print("User connected:", result)
}
easySDK.onDisconnect { disconnectInfo in
print("User disconnected:", disconnectInfo)
}
// Connect to server
Task {
do {
try await easySDK.connect()
print("Connection successful!")
} catch {
print("Connection failed:", error)
}
}
```
```kotlin Android theme={null}
import com.githubim.easysdk.WuKongEasySDK
import com.githubim.easysdk.WuKongConfig
import com.githubim.easysdk.WuKongEvent
import com.githubim.easysdk.listener.WuKongEventListener
// Configure user information
val config = WuKongConfig.Builder()
.serverUrl("ws://your-server.com:5200")
.uid("user123") // User unique identifier
.token("your_auth_token") // User authentication token
.build()
// Initialize SDK
val easySDK = WuKongEasySDK.getInstance()
easySDK.init(this, config)
// Listen for connection status
easySDK.addEventListener(WuKongEvent.CONNECT, object : WuKongEventListener {
override fun onEvent(result: ConnectResult) {
Log.d("WuKong", "User connected: $result")
}
})
easySDK.addEventListener(WuKongEvent.DISCONNECT, object : WuKongEventListener {
override fun onEvent(disconnectInfo: DisconnectInfo) {
Log.d("WuKong", "User disconnected: $disconnectInfo")
}
})
// Connect to server
lifecycleScope.launch {
try {
easySDK.connect()
Log.d("WuKong", "Connection successful!")
} catch (e: Exception) {
Log.e("WuKong", "Connection failed: $e")
}
}
```
```dart Flutter theme={null}
import 'package:wukong_easy_sdk/wukong_easy_sdk.dart';
// Configure user information
final config = WuKongConfig(
serverUrl: "ws://your-server.com:5200",
uid: "user123", // User unique identifier
token: "your_auth_token", // User authentication token
);
// Initialize SDK
final easySDK = WuKongEasySDK.getInstance();
await easySDK.init(config);
// Listen for connection status
easySDK.addEventListener(WuKongEvent.connect, (ConnectResult result) {
print('User connected: $result');
});
easySDK.addEventListener(WuKongEvent.disconnect, (DisconnectInfo disconnectInfo) {
print('User disconnected: $disconnectInfo');
});
// Connect to server
try {
await easySDK.connect();
print("Connection successful!");
} catch (e) {
print("Connection failed: $e");
}
```
### User Status Management
```javascript Web theme={null}
// Check connection status
const isConnected = im.isConnected();
console.log('Connection status:', isConnected);
// Disconnect
await im.disconnect();
// Listen for error events
im.on(WKIMEvent.Error, (error) => {
console.log('Error occurred:', error);
});
```
```swift iOS theme={null}
// Check connection status
let isConnected = easySDK.isConnected()
print("Connection status:", isConnected)
// Disconnect
Task {
await easySDK.disconnect()
}
// Listen for error events
easySDK.onError { error in
print("Error occurred:", error)
}
```
```kotlin Android theme={null}
// Check connection status
val isConnected = easySDK.isConnected()
Log.d("WuKong", "Connection status: $isConnected")
// Disconnect
easySDK.disconnect()
// Listen for error events
easySDK.addEventListener(WuKongEvent.ERROR, object : WuKongEventListener {
override fun onEvent(error: WuKongError) {
Log.e("WuKong", "Error occurred: $error")
}
})
```
```dart Flutter theme={null}
// Check connection status
final isConnected = easySDK.isConnected();
print('Connection status: $isConnected');
// Disconnect
await easySDK.disconnect();
// Listen for error events
easySDK.addEventListener(WuKongEvent.error, (WuKongError error) {
print('Error occurred: $error');
});
```
WuKongIM focuses on message transmission; detailed user profiles (such as nicknames, avatars, etc.) are typically managed by the application layer.
# JSON-RPC Protocol
Source: https://wukong.mintlify.app/en/getting-started/learning/jsonrpc
Learn about the JSON-RPC 2.0 communication protocol format and message types used by WuKongIM
# JSON-RPC Protocol
## Overview
WuKongIM uses the JSON-RPC 2.0 specification for communication. All requests, responses and notifications follow the JSON-RPC 2.0 standard structure.
### Protocol Field Description
* `jsonrpc`: **Optional** string, fixed as "2.0". If omitted, server should assume it's "2.0"
* `method`: Method name for request or notification
* `params`: Parameters for request or notification, usually an object
* `id`: Unique identifier for request (string type). Response must contain the same id as request. Notifications don't have id
* `result`: Result data for successful response
* `error`: Error object for error response
Complete protocol schema can be found at: [wukongim\_rpc\_schema.json](https://github.com/WuKongIM/WuKongIM/blob/main/pkg/jsonrpc/wukongim_rpc_schema.json)
## Important Notes
1. After establishing WebSocket connection, authentication (`connect`) must be performed **within 2 seconds**. Connections exceeding 2 seconds or failing authentication will be disconnected
2. Send ping packets regularly to let server know you're alive (recommended interval around 1-2 minutes)
## Common Components
### ErrorObject
When request processing fails, this object will be included in the response:
| Field | Type | Required | Description |
| :-------- | :------ | :------- | :-------------------- |
| `code` | integer | Yes | Error code |
| `message` | string | Yes | Error description |
| `data` | any | No | Additional error data |
### Header
Optional message header information:
| Field | Type | Required | Description |
| :---------- | :------ | :------- | :---------------------------- |
| `noPersist` | boolean | No | Whether message is not stored |
| `redDot` | boolean | No | Whether to show red dot |
| `syncOnce` | boolean | No | Whether only synced once |
| `dup` | boolean | No | Whether it's a resent message |
### SettingFlags
Message setting flags:
| Field | Type | Required | Description |
| :-------- | :------ | :------- | :-------------------------- |
| `receipt` | boolean | No | Message read receipt |
| `stream` | boolean | No | Whether it's stream message |
| `topic` | boolean | No | Whether contains Topic |
## Core Message Types
### 1. Connection Authentication (Connect)
#### Connect Request
The first request initiated by client, used to establish connection and authentication.
**Parameters (`params`)**
| Field | Type | Required | Description |
| :---------------- | :------ | :------- | :------------------------------------ |
| `uid` | string | Yes | User ID |
| `token` | string | Yes | Authentication Token |
| `header` | Header | No | Message header |
| `version` | integer | No | Client protocol version |
| `clientKey` | string | No | Client public key |
| `deviceId` | string | No | Device ID |
| `deviceFlag` | integer | No | Device flag (0:APP, 1:WEB...) |
| `clientTimestamp` | integer | No | Client 13-digit millisecond timestamp |
**Example Request**
```json theme={null}
{
"method": "connect",
"params": {
"uid": "testUser",
"token": "testToken"
},
"id": "req-conn-1"
}
```
#### Connect Response
**Success Response (`result`)**
| Field | Type | Required | Description |
| :----------- | :------ | :------- | :-------------------------- |
| `timeDiff` | integer | No | Time difference with server |
| `reasonCode` | integer | No | Connection reason code |
| `serverKey` | string | No | Server public key |
| `salt` | string | No | Encryption salt |
| `nodeId` | integer | No | Server node ID |
**Example Success Response**
```json theme={null}
{
"result": {
"timeDiff": 0,
"reasonCode": 1
},
"id": "req-conn-1"
}
```
**Error Response**
```json theme={null}
{
"error": {
"code": -32001,
"message": "Authentication failed"
},
"id": "req-conn-1"
}
```
### 2. Send Message
#### Send Request
Send message to specified channel.
**Parameters (`params`)**
| Field | Type | Required | Description |
| :------------ | :----------- | :------- | :----------------------------- |
| `header` | Header | No | Message header |
| `setting` | SettingFlags | No | Message settings |
| `clientMsgNo` | string | Yes | Client message number |
| `channelId` | string | Yes | Channel ID |
| `channelType` | integer | Yes | Channel type |
| `payload` | string | Yes | Base64 encoded message content |
| `expire` | integer | No | Message expiration time |
**Example Request**
```json theme={null}
{
"method": "send",
"params": {
"clientMsgNo": "msg-001",
"channelId": "user123",
"channelType": 1,
"payload": "eyJ0eXBlIjoidGV4dCIsImNvbnRlbnQiOiJIZWxsbyJ9"
},
"id": "req-send-1"
}
```
#### Send Response
**Success Response (`result`)**
| Field | Type | Required | Description |
| :------------ | :------ | :------- | :-------------------- |
| `clientMsgNo` | string | Yes | Client message number |
| `messageId` | integer | Yes | Server message ID |
| `messageSeq` | integer | Yes | Message sequence |
| `timestamp` | integer | Yes | Server timestamp |
**Example Success Response**
```json theme={null}
{
"result": {
"clientMsgNo": "msg-001",
"messageId": 12345,
"messageSeq": 1,
"timestamp": 1640995200
},
"id": "req-send-1"
}
```
### 3. Receive Message (Notification)
Server pushes messages to client.
**Parameters (`params`)**
| Field | Type | Required | Description |
| :------------ | :----------- | :------- | :----------------------------- |
| `header` | Header | No | Message header |
| `setting` | SettingFlags | No | Message settings |
| `messageId` | integer | Yes | Server message ID |
| `messageSeq` | integer | Yes | Message sequence |
| `clientMsgNo` | string | No | Client message number |
| `timestamp` | integer | Yes | Server timestamp |
| `fromUid` | string | Yes | Sender user ID |
| `channelId` | string | Yes | Channel ID |
| `channelType` | integer | Yes | Channel type |
| `payload` | string | Yes | Base64 encoded message content |
| `expire` | integer | No | Message expiration time |
**Example Notification**
```json theme={null}
{
"method": "message",
"params": {
"messageId": 12345,
"messageSeq": 1,
"timestamp": 1640995200,
"fromUid": "user456",
"channelId": "user123",
"channelType": 1,
"payload": "eyJ0eXBlIjoidGV4dCIsImNvbnRlbnQiOiJIZWxsbyJ9"
}
}
```
### 4. Ping/Pong
#### Ping Request
Client sends ping to keep connection alive.
**Example Request**
```json theme={null}
{
"method": "ping",
"id": "req-ping-1"
}
```
#### Pong Response
**Example Response**
```json theme={null}
{
"result": {},
"id": "req-ping-1"
}
```
### 5. Disconnect
#### Disconnect Request
Client initiates disconnection.
**Parameters (`params`)**
| Field | Type | Required | Description |
| :----------- | :------ | :------- | :---------------- |
| `reasonCode` | integer | No | Disconnect reason |
**Example Request**
```json theme={null}
{
"method": "disconnect",
"params": {
"reasonCode": 1
},
"id": "req-disc-1"
}
```
## Error Codes
Common error codes and their meanings:
| Code | Description |
| :----- | :--------------------- |
| -32001 | Authentication failed |
| -32002 | Invalid parameters |
| -32003 | Channel not found |
| -32004 | Permission denied |
| -32005 | Rate limit exceeded |
| -32006 | Message too large |
| -32007 | Invalid message format |
## Best Practices
1. **Connection Management**
* Always authenticate within 2 seconds after connection
* Send ping regularly to maintain connection
* Handle reconnection gracefully
2. **Message Handling**
* Use unique `clientMsgNo` for each message
* Handle message deduplication on client side
* Implement proper error handling
3. **Performance Optimization**
* Batch multiple operations when possible
* Use appropriate channel types
* Implement message queuing for offline scenarios
## Related Resources
* [API Documentation](/en/api/introduction)
* [SDK Documentation](/en/sdk/overview)
* [System Integration](/en/getting-started/learning/system-integration)
# Offline Messages
Source: https://wukong.mintlify.app/en/getting-started/learning/offline-messages
Understanding WuKongIM's offline message processing mechanism and recent conversation synchronization
# Offline Messages
*WuKongIM* adopts a read-diffusion model. What are read-diffusion and write-diffusion? Reference article: [Link](https://blog.csdn.net/m0_53246313/article/details/122674197)
After enabling the recent conversation configuration, *WuKongIM* maintains a recent conversation list for each user on the server side.
1. When an application goes from offline to online, it needs to synchronize the recent conversation list, as shown in the diagram below:
2. After the application comes online, the application should maintain the recent conversation list online.
3. When clicking on a conversation (channel) to enter the chat interface, you need to synchronize the messages of that conversation (channel), as shown in the diagram below (at the same time, you need to clear the unread count of the recent conversation using `/conversations/setUnread`):
## Offline Message Processing Flow
### 1. Application Startup Process
When your application starts up and the user comes online, follow this process:
```mermaid theme={null}
sequenceDiagram
participant App as Application
participant WK as WuKongIM Server
participant DB as Database
App->>WK: Connect to server
WK-->>App: Connection established
App->>WK: Request recent conversations
WK->>DB: Query user's conversation list
DB-->>WK: Return conversation data
WK-->>App: Send conversation list
App->>App: Update local conversation list
App->>App: Display conversations with unread counts
```
### 2. Message Synchronization Process
When entering a specific conversation:
```mermaid theme={null}
sequenceDiagram
participant App as Application
participant WK as WuKongIM Server
participant DB as Database
App->>WK: Request messages for channel
WK->>DB: Query channel messages
DB-->>WK: Return message history
WK-->>App: Send message list
App->>App: Display messages
App->>WK: Clear unread count
WK->>DB: Update conversation status
DB-->>WK: Confirm update
WK-->>App: Acknowledge unread cleared
```
## Implementation Best Practices
### 1. Conversation List Management
**Sync Recent Conversations on Startup:**
```javascript theme={null}
// Example: Sync conversations when app starts
async function syncConversationsOnStartup() {
try {
// Get recent conversations from server
const conversations = await getRecentConversations();
// Update local conversation list
updateLocalConversations(conversations);
// Update UI with unread counts
displayConversationsWithUnreadCounts(conversations);
console.log('Conversation sync completed');
} catch (error) {
console.error('Failed to sync conversations:', error);
// Handle sync failure - maybe retry or show error
}
}
// Call on app startup
window.addEventListener('load', syncConversationsOnStartup);
```
### 2. Message History Synchronization
**Load Messages When Entering Chat:**
```javascript theme={null}
// Example: Load messages when entering a conversation
async function enterConversation(channelId, channelType) {
try {
// 1. Load message history
const messages = await getChannelMessages(channelId, channelType);
// 2. Display messages in chat interface
displayMessages(messages);
// 3. Clear unread count
await clearUnreadCount(channelId, channelType);
// 4. Update conversation list UI
updateConversationUnreadCount(channelId, 0);
console.log(`Entered conversation ${channelId}`);
} catch (error) {
console.error('Failed to enter conversation:', error);
}
}
```
### 3. Real-time Updates
**Handle Real-time Message Updates:**
```javascript theme={null}
// Example: Handle incoming messages while online
function handleIncomingMessage(message) {
const { channelId, channelType } = message;
// Check if user is currently viewing this conversation
if (isCurrentlyViewingConversation(channelId, channelType)) {
// Display message immediately
displayNewMessage(message);
// Mark as read automatically
markMessageAsRead(message);
} else {
// Update conversation list with new message
updateConversationLastMessage(channelId, message);
// Increment unread count
incrementUnreadCount(channelId);
// Show notification if needed
showNotification(message);
}
}
```
## API Integration Examples
### Using WuKongIM APIs
**1. Get Recent Conversations:**
```javascript theme={null}
async function getRecentConversations() {
const response = await fetch('/conversations/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
uid: currentUserId,
version: lastSyncVersion || 0,
limit: 50
})
});
return await response.json();
}
```
**2. Get Channel Messages:**
```javascript theme={null}
async function getChannelMessages(channelId, channelType, limit = 20) {
const response = await fetch('/channel/messagesync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
channel_id: channelId,
channel_type: channelType,
start_message_seq: 0,
end_message_seq: 0,
limit: limit,
pull_mode: 1 // Pull down mode
})
});
return await response.json();
}
```
**3. Clear Unread Count:**
```javascript theme={null}
async function clearUnreadCount(channelId, channelType) {
const response = await fetch('/conversations/clearUnread', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
uid: currentUserId,
channel_id: channelId,
channel_type: channelType
})
});
return await response.json();
}
```
## Performance Optimization
### 1. Incremental Synchronization
Use version-based incremental sync to reduce data transfer:
```javascript theme={null}
// Store last sync version locally
let lastConversationSyncVersion = localStorage.getItem('lastSyncVersion') || 0;
async function incrementalConversationSync() {
const response = await fetch('/conversations/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
uid: currentUserId,
version: lastConversationSyncVersion,
limit: 50
})
});
const data = await response.json();
// Update local version
if (data.version) {
lastConversationSyncVersion = data.version;
localStorage.setItem('lastSyncVersion', data.version);
}
return data;
}
```
### 2. Pagination for Large Conversations
Implement pagination for conversations with many messages:
```javascript theme={null}
async function loadMoreMessages(channelId, channelType, oldestMessageSeq) {
const response = await fetch('/channel/messagesync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
channel_id: channelId,
channel_type: channelType,
start_message_seq: 0,
end_message_seq: oldestMessageSeq - 1,
limit: 20,
pull_mode: 1
})
});
const data = await response.json();
// Prepend older messages to chat
prependMessagesToChat(data.messages);
return data;
}
```
## Error Handling
### Robust Sync Error Handling
```javascript theme={null}
async function robustConversationSync(retryCount = 3) {
for (let i = 0; i < retryCount; i++) {
try {
const conversations = await getRecentConversations();
updateLocalConversations(conversations);
return conversations;
} catch (error) {
console.error(`Sync attempt ${i + 1} failed:`, error);
if (i === retryCount - 1) {
// Last attempt failed, show error to user
showSyncError('Failed to sync conversations. Please check your connection.');
throw error;
}
// Wait before retry (exponential backoff)
await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
}
}
}
```
## Best Practices Summary
1. **Always sync conversations on app startup** to get the latest state
2. **Use incremental sync** with version numbers to optimize performance
3. **Clear unread counts** when users view conversations
4. **Handle real-time updates** properly based on current user context
5. **Implement proper error handling** with retry mechanisms
6. **Use pagination** for large message histories
7. **Cache data locally** to improve user experience during network issues
# Plugin Development
Source: https://wukong.mintlify.app/en/getting-started/learning/plugin-development
Learn how to develop WuKongIM plugins to extend and enhance message processing functionality
## Overview
WuKongIM defines a set of plugin rules that third-party developers can implement to extend or enhance existing WuKongIM message processing logic. Through plugins, you can implement features like sensitive word filtering, message search, AI chat, and more.
Plugin functionality is only supported in WuKongIM version 2.1.3 and above
## Plugin Types
### User Plugins
User plugins can receive all messages from users bound to the plugin. After installation, they need to be bound to users to take effect.
**Features**:
* Only process messages from bound users
* Suitable for AI chat, personal assistant scenarios
* Implement `Receive` function
**Use Cases**:
* Chat with large language models: Bind specific user UID, sending messages to this user means sending to the LLM
* Personal assistant: Provide customized services for specific users
### Global Plugins
Global plugins can receive all messages in the system. After installation, they take effect globally.
**Features**:
* Monitor all system messages
* No need to bind users
* Suitable for system-level functionality
**Use Cases**:
* Sensitive word filtering: Monitor every sent message for filtering
* Message search: Build search index for all messages
* Data analysis: Statistics and analysis of message data
## Development Environment Setup
### Prerequisites
* Go language environment (currently plugin development only supports Go)
* WuKongIM source code or running instance
* [Go PDK](https://github.com/WuKongIM/go-pdk) plugin development library
### Environment Preparation
**1. Download WuKongIM Source Code**
```bash theme={null}
git clone https://github.com/WuKongIM/WuKongIM.git
cd WuKongIM
```
**2. Start Single-Node WuKongIM**
```bash theme={null}
go run main.go --config exampleconfig/single.yaml
```
**3. Create Plugin Project**
```bash theme={null}
mkdir my-plugin
cd my-plugin
go mod init my-plugin
go get github.com/WuKongIM/go-pdk
```
## Plugin Development
### Basic Structure
Here's a complete plugin development example:
```go Plugin Structure Definition theme={null}
package main
import (
"encoding/json"
"fmt"
"github.com/WuKongIM/go-pdk"
)
// Define plugin configuration struct
type Config struct {
Name string `json:"name" label:"AI Name"` // json is config item name, label is display name in WuKongIM backend
}
// Plugin struct
type AIExample struct {
Config Config // Plugin configuration, name must be Config
}
// Plugin initialization
func (a *AIExample) Init() error {
pdk.Log.Info("AI plugin initialized", pdk.Any("config", a.Config))
return nil
}
// Plugin information
func (a *AIExample) Info() pdk.PluginInfo {
return pdk.PluginInfo{
Uid: "ai_example", // Plugin unique ID
Name: "AI Example Plugin", // Plugin name
Description: "AI chat example plugin", // Plugin description
Version: "1.0.0", // Plugin version
Type: pdk.PluginTypeUser, // Plugin type: user plugin
}
}
// Receive message processing
func (a *AIExample) Receive(message pdk.Message) error {
// Parse message content
var content map[string]interface{}
if err := json.Unmarshal([]byte(message.Payload), &content); err != nil {
return err
}
// Get message text
text, ok := content["content"].(string)
if !ok {
return fmt.Errorf("invalid message content")
}
// AI processing logic (simplified example)
response := fmt.Sprintf("AI %s replies: I received your message '%s'", a.Config.Name, text)
// Send reply message
replyContent := map[string]interface{}{
"type": "text",
"content": response,
}
replyPayload, _ := json.Marshal(replyContent)
return pdk.SendMessage(pdk.SendMessageReq{
ChannelId: message.ChannelId,
ChannelType: message.ChannelType,
Payload: string(replyPayload),
})
}
// Plugin entry point
func main() {
plugin := &AIExample{}
pdk.Run(plugin)
}
```
```go Global Plugin Example theme={null}
package main
import (
"encoding/json"
"strings"
"github.com/WuKongIM/go-pdk"
)
type SensitiveWordFilter struct {
Config struct {
Words []string `json:"words" label:"Sensitive Words"`
}
}
func (s *SensitiveWordFilter) Init() error {
pdk.Log.Info("Sensitive word filter initialized")
return nil
}
func (s *SensitiveWordFilter) Info() pdk.PluginInfo {
return pdk.PluginInfo{
Uid: "sensitive_filter",
Name: "Sensitive Word Filter",
Description: "Filter sensitive words in messages",
Version: "1.0.0",
Type: pdk.PluginTypeGlobal, // Global plugin
}
}
func (s *SensitiveWordFilter) Receive(message pdk.Message) error {
var content map[string]interface{}
if err := json.Unmarshal([]byte(message.Payload), &content); err != nil {
return err
}
text, ok := content["content"].(string)
if !ok {
return nil
}
// Check for sensitive words
for _, word := range s.Config.Words {
if strings.Contains(text, word) {
pdk.Log.Warn("Sensitive word detected",
pdk.String("word", word),
pdk.String("message", text))
// Block message or replace with ***
content["content"] = strings.ReplaceAll(text, word, "***")
newPayload, _ := json.Marshal(content)
// Update message content
return pdk.UpdateMessage(pdk.UpdateMessageReq{
MessageId: message.MessageId,
Payload: string(newPayload),
})
}
}
return nil
}
func main() {
plugin := &SensitiveWordFilter{}
pdk.Run(plugin)
}
```
### Core Interfaces
#### PluginInfo
Plugin information structure:
```go theme={null}
type PluginInfo struct {
Uid string // Plugin unique ID
Name string // Plugin name
Description string // Plugin description
Version string // Plugin version
Type PluginType // Plugin type
}
```
#### Message
Message structure received by plugin:
```go theme={null}
type Message struct {
MessageId int64 // Message ID
MessageSeq int64 // Message sequence
ChannelId string // Channel ID
ChannelType int // Channel type
FromUid string // Sender UID
Payload string // Message content (JSON string)
Timestamp int64 // Timestamp
}
```
#### Plugin Types
```go theme={null}
const (
PluginTypeUser PluginType = "user" // User plugin
PluginTypeGlobal PluginType = "global" // Global plugin
)
```
### Plugin Configuration
Plugins can define configuration items that users can set in the WuKongIM management interface:
```go theme={null}
type Config struct {
APIKey string `json:"api_key" label:"API Key" placeholder:"Enter your API key"`
Model string `json:"model" label:"AI Model" default:"gpt-3.5-turbo"`
MaxTokens int `json:"max_tokens" label:"Max Tokens" default:"1000"`
Keywords []string `json:"keywords" label:"Keywords"`
Enabled bool `json:"enabled" label:"Enable Plugin" default:"true"`
}
```
**Configuration Tags**:
* `json`: Configuration item name
* `label`: Display name in management interface
* `placeholder`: Input placeholder text
* `default`: Default value
* `required`: Whether required
## Plugin Compilation and Installation
### Compilation
**1. Build Plugin**
```bash theme={null}
go build -buildmode=plugin -o ai_example.so main.go
```
**2. Upload Plugin**
Upload the compiled `.so` file to WuKongIM management interface.
### Installation and Configuration
**1. Install Plugin**
In WuKongIM management interface:
* Go to Plugin Management
* Upload plugin file
* Configure plugin parameters
* Enable plugin
**2. Bind Users (User Plugins Only)**
For user plugins, you need to bind specific users:
* Go to User Management
* Select target user
* Bind plugin to user
## API Reference
### Send Message
```go theme={null}
func SendMessage(req SendMessageReq) error
type SendMessageReq struct {
ChannelId string // Target channel ID
ChannelType int // Channel type
Payload string // Message content (JSON string)
FromUid string // Sender UID (optional)
}
```
### Update Message
```go theme={null}
func UpdateMessage(req UpdateMessageReq) error
type UpdateMessageReq struct {
MessageId int64 // Message ID to update
Payload string // New message content
}
```
### Logging
```go theme={null}
pdk.Log.Info("Info message", pdk.String("key", "value"))
pdk.Log.Warn("Warning message", pdk.Int("count", 10))
pdk.Log.Error("Error message", pdk.Any("data", obj))
```
## Best Practices
1. **Error Handling**
* Always handle errors gracefully
* Use appropriate logging levels
* Don't panic in plugin code
2. **Performance**
* Avoid blocking operations
* Use goroutines for heavy processing
* Implement proper timeouts
3. **Security**
* Validate all input data
* Sanitize user content
* Use secure API calls
4. **Configuration**
* Provide sensible defaults
* Validate configuration values
* Document all configuration options
## Related Resources
* [Go PDK Documentation](https://github.com/WuKongIM/go-pdk)
* [Plugin Examples](https://github.com/WuKongIM/plugin-examples)
* [API Documentation](/en/api/introduction)
# Binary Protocol
Source: https://wukong.mintlify.app/en/getting-started/learning/protocol
Detailed specification of WuKongIM communication protocol and packet structure
# WuKongIM Protocol
WuKongIM protocol is an efficient binary communication protocol designed specifically for instant messaging scenarios. This document provides detailed descriptions of packet structure, control types, and encoding specifications.
***
## 📋 Control Packet Structure
Each WuKongIM control packet consists of the following three parts:
| Parameter Name | Type | Description |
| :-------------- | :----- | :-------------- |
| Fixed header | 1 byte | Fixed header |
| Variable header | bytes | Variable header |
| Payload | bytes | Message body |
***
## 🔧 Fixed Header
Each WuKongIM control packet contains a fixed header used to identify packet type and length information.
| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| --------- | ---------------------------- | ---------------------------- | ---------------------------- | ---------------------------- | --------------------------------- | --------------------------------- | --------------------------------- | --------------------------------- |
| byte 1 | WuKongIM control packet type | WuKongIM control packet type | WuKongIM control packet type | WuKongIM control packet type | Flag bits for control packet type | Flag bits for control packet type | Flag bits for control packet type | Flag bits for control packet type |
| byte 2... | Remaining length | Remaining length | Remaining length | Remaining length | Remaining length | Remaining length | Remaining length | Remaining length |
### 📝 WuKongIM Control Packet Types
WuKongIM protocol defines 10 different control packet types, each with specific purposes and data structures.
| Name | Value | Description |
| ---------- | ----- | --------------------------------------------------------------------- |
| Reserved | 0 | Reserved bit |
| CONNECT | 1 | Client request to connect to server (c2s) |
| CONNACK | 2 | Server acknowledgment packet after receiving connection request (s2c) |
| SEND | 3 | Send message (c2s) |
| SENDACK | 4 | Message acknowledgment packet (s2c) |
| RECV | 5 | Receive message (s2c) |
| RECVACK | 6 | Receive message acknowledgment (c2s) |
| PING | 7 | Ping request |
| PONG | 8 | Response to ping request |
| DISCONNECT | 9 | Request to disconnect |
### 🏷️ Control Packet Flag Bits
Different protocol packets use different flag bits to control message behavior:
Flag bits in Send and Recv protocols
| bit | 3 | 2 | 1 | 0 |
| ---- | --- | -------- | ------ | --------- |
| byte | DUP | SyncOnce | RedDot | NoPersist |
Flag bits in Connack protocol
| bit | 3 | 2 | 1 | 0 |
| ---- | -------- | -------- | -------- | ---------------- |
| byte | Reserved | Reserved | Reserved | HasServerVersion |
Flag bits in Chunk protocol
| bit | 3 | 2 | 1 | 0 |
| ---- | -------- | -------- | -------- | --- |
| byte | Reserved | Reserved | Reserved | End |
**Flag bit descriptions**:
* **DUP**: Whether it's a duplicate message (clients need to mark DUP as 1 when resending messages)
* **SyncOnce**: Sync only once. In multi-device scenarios, if one device has pulled this message, other devices will not pull this message again (e.g., friend request messages)
* **RedDot**: Whether the client should show a red dot when receiving the message
* **NoPersist**: Whether not to store this message
* **Reserved**: Reserved bit
* **HasServerVersion**: Whether there is a server version number
* **End**: Whether it's the ending message chunk
### 📏 Remaining Length Encoding
The remaining length field indicates the number of bytes remaining in the current message, including variable header and payload (content). This is a variable-length encoding field.
**Encoding rules**:
* Single byte maximum value: `01111111` (0x7F, 127)
* The eighth bit (most significant bit) being 1 indicates there are subsequent bytes
* Maximum of 4 bytes allowed to represent remaining length
* Maximum length: `0xFF,0xFF,0xFF,0x7F` = 268,435,455 bytes = 256MB
**Byte ranges**:
| Digits | From | To |
| ------ | ---------------------------------- | ------------------------------------ |
| 1 | 0 (0x00) | 127 (0x7F) |
| 2 | 128 (0x80, 0x01) | 16 383 (0xFF, 0x7F) |
| 3 | 16 384 (0x80, 0x80, 0x01) | 2 097 151 (0xFF, 0xFF, 0x7F) |
| 4 | 2 097 152 (0x80, 0x80, 0x80, 0x01) | 268 435 455 (0xFF, 0xFF, 0xFF, 0x7F) |
**Encoding understanding**:
* 1st byte base: 1
* 2nd byte base: 128 (2^7)
* 3rd byte base: 128×128 = 2^14
* 4th byte base: 128×128×128 = 2^21
**Encoding example**:
Expressing 321 = 65 + 2×128 (2 bytes): `11000001 00000010`
```
First byte 193 (11000001):
- Most significant bit is 1, indicating subsequent bytes
- Lower 7 bits are 1000001 (65)
Second byte 2 (00000010):
- Most significant bit is 0, indicating end
- Lower 7 bits are 0000010 (2)
Calculation: 321 = 65 + 2 × 128 = 65 + 256
```
**Byte order**: The first byte is low-order, subsequent bytes are high-order, but within bytes, low-order is on the right, high-order is on the left.
### 🔤 String UTF-8 Encoding
WuKongIM uses a modified UTF-8 encoding format:
| bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| ---------- | ---------------------- | ---------------------- | ---------------------- | ---------------------- | ---------------------- | ---------------------- | ---------------------- | ---------------------- |
| byte 1 | String Length MSB | String Length MSB | String Length MSB | String Length MSB | String Length MSB | String Length MSB | String Length MSB | String Length MSB |
| byte 2 | String Length MSB | String Length MSB | String Length MSB | String Length MSB | String Length MSB | String Length MSB | String Length MSB | String Length MSB |
| bytes 3... | Encoded Character Data | Encoded Character Data | Encoded Character Data | Encoded Character Data | Encoded Character Data | Encoded Character Data | Encoded Character Data | Encoded Character Data |
***
## 🔄 Variable Header
Some control packets contain a variable header section located between the fixed header and payload. The content of the variable header varies depending on the packet type.
***
# 📡 Protocol Packet Specifications
## 🔌 CONNECT Connection Packet
Packet format used when a client initiates a connection request to the server.
| Parameter Name | Type | Description |
| ---------------- | -------- | -------------------------------------------------------------- |
| Packet Type | 0.5 byte | Packet type (1) |
| Flag | 0.5 byte | Flag bits |
| Remaining Length | ... byte | Remaining packet length |
| Protocol Version | int8 | Protocol version number |
| Device Flag | int8 | Device flag (same flag for same account mutual kick) |
| Device ID | string | Unique device ID |
| UID | string | User ID |
| Token | string | User token |
| Client Timestamp | int64 | Client current timestamp (13-digit timestamp, to milliseconds) |
| Client Key | string | Client KEY (base64 encoded DH public key) |
## ✅ CONNACK Connection Acknowledgment
CONNACK packet is sent by the server as a response to the client's CONNECT packet. If the client doesn't receive a CONNACK packet within a reasonable time, it should close the network connection.
| Parameter Name | Type | Description |
| ---------------- | -------- | ------------------------------------------------------------------------------ |
| Packet Type | 0.5 byte | Packet type (2) |
| Flag | 0.5 byte | Flag bits |
| Remaining Length | ... byte | Remaining packet length |
| ServerVersion | uint8 | Maximum version supported by server, valid when flag contains HasServerVersion |
| Time Diff | int64 | Difference between client time and server time, in milliseconds |
| Reason Code | uint8 | Connection reason code (see appendix) |
| Server Key | string | Server base64 DH public key |
| Salt | string | Security code |
## 📤 SEND Send Message
Packet format used when a client sends a message to the server.
| Parameter Name | Type | Description |
| ---------------- | -------- | ----------------------------------------------------------------------- |
| Packet Type | 0.5 byte | Packet type (3) |
| Flag | 0.5 byte | Flag bits |
| Remaining Length | ... byte | Remaining packet length |
| Setting | 1 byte | Message settings |
| Client Seq | uint32 | Client message sequence number (generated by client, unique per client) |
| Client Msg No | string | Client unique identifier for message deduplication |
| StreamNo | string | Stream message number (Stream must be enabled in settings) |
| Channel Id | string | Channel ID (for personal channels, ChannelId is the person's UID) |
| Channel Type | int8 | Channel type (1. Personal 2. Group) |
| Expire | uint32 | Message expiration time (in seconds) version>=3 |
| Msg Key | string | Used to verify message legitimacy (prevent man-in-the-middle tampering) |
| Topic | string | Topic ID (only present when topic is enabled in settings) |
| Payload | ... byte | Message content |
## ✅ SENDACK Send Message Acknowledgment
Server acknowledgment response to client message sending.
| Parameter Name | Type | Description |
| ---------------- | -------- | ----------------------------------------------------------- |
| Packet Type | 0.5 byte | Packet type (4) |
| Flag | 0.5 byte | Flag bits |
| Remaining Length | ... byte | Remaining packet length |
| Message ID | uint64 | Server message ID (globally unique) |
| Client Seq | uint32 | Client message sequence number |
| Message Seq | uint32 | Message sequence number (ordered increment, channel unique) |
| Reason Code | uint8 | Send reason code, 1 indicates success |
## 📥 RECV Receive Message
Packet format used when the server pushes messages to the client.
| Parameter Name | Type | Description |
| ----------------- | -------- | ------------------------------------------------------------------------------ |
| Packet Type | 0.5 byte | Packet type (5) |
| Flag | 0.5 byte | Flag bits |
| Remaining Length | ... byte | Remaining packet length |
| Setting | 1 byte | Message settings (see below, valid for version 4) |
| Msg Key | string | Used to verify message legitimacy (prevent man-in-the-middle tampering) |
| From UID | string | Sender UID |
| Channel ID | string | Channel ID |
| Channel Type | int8 | Channel type |
| Expire | uint32 | Message expiration time (in seconds) version>=3 |
| Client Msg No | string | Client unique identifier for message deduplication |
| StreamNo | string | Stream message number, present based on whether stream is enabled in settings |
| StreamId | uint32 | Stream sequence number, present based on whether stream is enabled in settings |
| Message ID | uint64 | Server message ID (globally unique) |
| Message Seq | uint32 | Server message sequence number (ordered increment, channel unique) |
| Message Timestamp | int32 | Server message timestamp (10 digits, to seconds) |
| Topic | string | Topic ID (only present when topic is enabled in settings) |
| Payload | ... byte | Message content |
## ✅ RECVACK Receive Message Acknowledgment
Client acknowledgment response to server message push.
| Parameter Name | Type | Description |
| ---------------- | -------- | ----------------------------------- |
| Packet Type | 0.5 byte | Packet type (6) |
| Flag | 0.5 byte | Flag bits |
| Remaining Length | ... byte | Remaining packet length |
| Message ID | uint64 | Server message ID (globally unique) |
| Message Seq | uint32 | Sequence number |
## 🏓 PING
Heartbeat request packet sent by the client to keep the connection active.
| Parameter Name | Type | Description |
| -------------- | -------- | --------------- |
| Packet Type | 0.5 byte | Packet type (7) |
| Flag | 0.5 byte | Flag bits |
## 🏓 PONG
Server response packet to client PING request.
| Parameter Name | Type | Description |
| -------------- | -------- | --------------- |
| Packet Type | 0.5 byte | Packet type (8) |
| Flag | 0.5 byte | Flag bits |
## 🔌 DISCONNECT
Packet format used when client or server requests to disconnect.
| Parameter Name | Type | Description |
| ---------------- | -------- | ----------------------- |
| Packet Type | 0.5 byte | Packet type (9) |
| Flag | 0.5 byte | Flag bits |
| Remaining Length | ... byte | Remaining packet length |
| ReasonCode | uint8 | Reason code |
| Reason | string | Reason |
***
# ⚙️ Message Settings
## 📊 Message Setting Bit Fields
Message settings are 1 byte (8 bits) used to control various message behavior characteristics.
| bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| ---- | ------- | -------- | ------ | --------- | ----- | -------- | ------ | -------- |
| byte | Receipt | Reserved | Signal | NoEncrypt | Topic | Reserved | Stream | Reserved |
## 🔧 Setting Bit Descriptions
**Bit function descriptions**:
* **Receipt**: Message read receipt, this flag indicates this message requires read receipt
* **Reserved**: Reserved bit, not yet used
* **Signal**: Encryption flag
* **NoEncrypt**: Whether message encryption is disabled
* **Topic**: Whether message contains topic (if 1, both send and receive packets will include topic field)
* **Reserved**: Reserved bit, not yet used
* **Stream**: Stream message flag
* **Reserved**: Reserved bit, not yet used
***
**Complete Protocol Documentation**: This document contains the core parts of the WuKongIM protocol. The complete protocol specification also includes recommended Payload structure, regular message format, system message format, and other detailed content. Please refer to the source documentation for complete information.
# Stress Testing
Source: https://wukong.mintlify.app/en/getting-started/learning/stress-testing
Learn how to perform stress testing on WuKongIM, including configuration, stress tester installation, running tests and analyzing reports
# Stress Testing
WuKongIM stress testing functionality is only available in versions v2.1.2-20250120 and above
## Server Stress Configuration
WuKongIM server new configuration:
```yaml wk.yaml theme={null}
...
stress: true
intranet:
tcpAddr: "ip:port"
...
```
```yaml environment theme={null}
WK_STRESS=true
WK_INTRANET_TCPADDR=ip:port
```
### Configuration Description
**`stress`**
Enable stress testing configuration. Only when enabled can the server be stress tested.
**`intranet.tcpAddr`**
* **No load balancer configured (single node mode)**: `tcpAddr` is WuKongIM server's internal IP + port 5100, e.g.: 192.168.1.12:5100
* **Load balancer configured (distributed mode)**: `tcpAddr` is load balancer's internal IP + port 15100, e.g.: 192.168.1.11:15100
## Install Stress Tester
### Configuration Requirements
Stress tester requires a server with large memory, recommended 4 cores 16GB or above.
### Installation Steps
**1. Download Executable File**
```bash amd64 theme={null}
sudo curl -L -o wkstress https://github.com/WuKongIM/StressTester/releases/download/v1.1.0/wkstress-linux-amd64
```
```bash arm64 theme={null}
sudo curl -L -o wkstress https://github.com/WuKongIM/StressTester/releases/download/v1.1.0/wkstress-linux-arm64
```
**2. Modify Executable File Permissions**
```bash theme={null}
sudo chmod +x wkstress
```
### Run Stress Tester
**Start**
```bash theme={null}
nohup ./wkstress &
```
Port: 9466
**Stop**
```bash theme={null}
kill -9 $(lsof -t -i :9466)
```
## Start Stress Testing
### 1. Add Stress Testing Machine
Address: `http://ip:9466`, where ip is the **internal IP** of the stress testing machine, as shown below:
### 2. Set Stress Testing Metrics and Run
Set stress testing metrics and run, as shown below:
## View Stress Testing Report
### Report Field Explanation
**Test Metrics**
Represents the stress testing data currently being tested by the stress tester.
**Test Report**
* **Runtime**: Duration since stress testing started
* **Online Users**: Number of simulated concurrent online users
* **Offline Users**: Number of simulated online users that have disconnected
* **Sent Messages**: Total number of messages sent to server
* **Send Rate**: Current rate of messages sent to server
* **Send Size**: Total size of sent messages
* **Send Traffic**: Current bandwidth real-time traffic of sent messages
* **Send Success**: Number of messages that received successful response from server after sending
* **Send Errors**: Number of messages that received failed response from server after sending
* **Send Min Latency**: Minimum round-trip time from sending message to server until receiving server response
* **Send Max Latency**: Maximum round-trip time from sending message to server until receiving server response
* **Send Avg Latency**: Average round-trip time from sending message to server until receiving server response
* **Received Messages**: Total number of messages received
* **Receive Rate**: Current rate of messages received
* **Receive Size**: Total size of received messages
* **Receive Traffic**: Current bandwidth traffic of received messages
* **Receive Min Latency**: Minimum time from sending message until subscriber receives message
* **Receive Max Latency**: Maximum time from sending message until subscriber receives message
* **Receive Avg Latency**: Average time from sending message until subscriber receives message
## Report Analysis
### Core Metrics Analysis
#### Send Rate
Also known as send message concurrency.
**Description**
This metric shows the server's ability to handle concurrent messages. The higher the value, the stronger the server's concurrency capability, and the more users can send messages simultaneously per second.
Based on the following algorithm, you can roughly estimate the relationship between `send rate` and `Daily Active Users (DAU)`:
```
Peak Concurrency = DAU × Peak Active User Ratio × Send Rate Per User
```
**Assumptions**:
* Daily Active Users (DAU): 100,000 people
* Peak online user ratio: approximately 10%~20%, take 15%
* Send rate per user per minute ~= 6 messages/minute = 0.1 messages/second
**Then**:
`Peak message sending concurrency = 100,000 × 0.15 × 0.1 = 1,500 messages/second`
**Conclusion**:
`A send rate around 1,500 messages/second can support approximately 100,000 daily active users.`
By adjusting peak online user ratio and send rate per user, you can roughly estimate your peak send rate, and indirectly verify through stress testing whether the server can meet your daily active user requirements.
#### Receive Rate
Also known as receive message concurrency.
**Description**
This metric shows the server's ability to deliver messages concurrently. The higher the value, the stronger the server's concurrency capability, and the more private chats and group chats it can support simultaneously.
For group chat or broadcast scenarios, one message may be received by multiple users. Therefore, receive concurrency also needs to be calculated:
```
Peak Receive Message Concurrency = Peak Active Group Count × Average Online Members Per Group × Message Send Frequency Per Group
```
**Assumptions**:
* Daily active group chat count: 10,000 groups
* Average online members per group: 20 people
* Peak active group ratio: approximately 10%~20%, take 15%
* Message send frequency per group: 0.5 messages/second
**Then**:
`Peak receive message concurrency = 10,000 × 20 × 0.15 × 0.5 = 15,000 messages/second`
**Conclusion**:
`Message receive rate around 15,000 messages/second can support approximately 10,000 daily active groups with 20 online members each`
Through this formula + stress tester + daily active group chats, you can stress test the required server resource configuration.
#### Send Success and Errors
`Send success and failure` refers to the result from when client starts sending message until server receives and stores it, then returns success or failure to client.
`This metric reflects server health. Higher send success rate indicates healthier system.`
#### Send and Receive Average Latency
`This metric observes server stability`
Lower average latency indicates more stable system.
#### Send Success and Expected Received Message Count
`This metric observes whether there are missing messages`
Expected received message count can be viewed by clicking the question mark next to the receive message metric.
After stopping stress testing, click view report again. If the send success message count in the report equals the expected received message count, it means all messages were received by subscribing clients.
This metric is only meaningful when Send Messages = Send Success
#### Send Traffic and Receive Traffic
`This metric observes the bandwidth size the server needs to support`
## Common Issues
**Answer**: Modify file descriptor limits for WuKongIM and nginx servers
**Check current file descriptor limit**:
```bash theme={null}
ulimit -n
```
**Temporarily modify file descriptor limit**:
```bash theme={null}
ulimit -n 100000
```
**Permanently modify file descriptor limit**:
Edit `/etc/security/limits.conf` file, add the following lines:
```
* soft nofile 100000
* hard nofile 100000
```
Modify `worker_connections` in nginx configuration, can be changed to around 40960
## Related Resources
* [Stress Testing Report](/en/getting-started/stress-report)
* [System Integration Guide](/en/getting-started/learning/system-integration)
* [API Documentation](/en/api/introduction)
# System Integration
Source: https://wukong.mintlify.app/en/getting-started/learning/system-integration
Complete guide for integrating WuKongIM into existing business systems
# System Integration
`All WuKongIM APIs should not be called directly by the APP client. The frontend should first call your own business API, and the business API should then call WuKongIM's API. WuKongIM's API should not be exposed to the external network to ensure data security.`
## Step 1: Integrate with Your Business System Users
1. Your application calls your own login or registration business API.
2. After your login or registration business API processes its own business logic, it generates a user ID (uid) and token, then calls WuKongIM's [Login or Register User](/en/api/user/token) API to update WuKongIM
3. Return the uid and token to your own application client.
4. The application client calls WuKongIM SDK's connect method, passing in the uid and token. WuKongIM SDK will pass the uid and token to WuKongIM server for verification. If the client-passed values match the server-passed values, verification will pass, and after successful verification, a persistent connection will be maintained.
## Step 2: Provide Webhook Interface
The third-party service provides an HTTP API interface. WuKongIM will pass corresponding data to this interface according to the Webhook agreement using an event mechanism.
## Step 3: Configure Webhook Interface to WuKongIM
Configure your HTTP API interface into ***WuKongIM***.
```yaml theme={null}
---
webhook: #
httpAddr: 'http://xxxxx' # webhook HTTP address, used to notify data to third parties
```
Events from applications integrated with WuKongIM SDK will be pushed to the third-party system. For example, after a user comes online, ***WuKong SDK*** will notify ***WuKongIM*** server, and ***WuKongIM*** will push the online event to the third-party server through the configured webhook address.
# Stress Test Report (Single Node)
Source: https://wukong.mintlify.app/en/getting-started/stress-report
Detailed performance stress test report for WuKongIM single node mode
## Preface
The excellence of a communication system is primarily measured by four indicators: `high performance`, `stability`, `reliability`, and `ordering`.
**High performance**: Extreme stress testing of message sending concurrency and message receiving concurrency.
**Stability**: Testing whether CPU and memory remain stable during long-term operation under high concurrency.
**Reliability**: Testing whether messages are lost.
**Ordering**: Testing whether messages are out of order.
## Test Environment
All stress test results can be reproduced on your own server according to the test content and hardware specifications.
**WuKongIM Test Version**: `v2.1.2-20250120`
### Hardware Information
| Resource Description | Hardware Configuration | Quantity |
| :------------------- | :------------------------------------------------------------------------------------------------------------------------- | :------- |
| WuKongIM Server | Ubuntu 22.04 LTS SA5.4XLARGE32 (Standard SA5, 16 cores 32GB), 50G Enhanced SSD Cloud Disk, Baseline Performance: 4300 IOPS | 1 unit |
| Stress Test Machine | Ubuntu 22.04 LTS SA5.4XLARGE32 (Standard SA5, 16 cores 32GB), 50G Enhanced SSD Cloud Disk, Baseline Performance: 4300 IOPS | 1 unit |
## Performance Testing
`Core indicator of whether a communication system is high-performance: message sending concurrency and message receiving concurrency (in simple terms: the maximum number of messages the system can process for sending and delivery per second)`
### Send Rate Testing
**Test Content**
| Content | Online | Send Rate |
| :---------------------------- | :------------------------ | :----------------------------------- |
| 100 groups of 100 people each | 1 person online per group | 24,000 messages per minute per group |
**Total Send Rate**: `100 * (24000/60) = 40,000 messages/second`
**Test Screenshots**
| Test Content | System Top Information |
| :-------------------------------- | :-------------------------------- |
|
|
|
**Test Results**
| Metric | Value |
| :-------------------------- | :-------------- |
| Total Messages Sent | `20 million` |
| Message Sending Concurrency | `40,000/second` |
| Average Send Latency | `400ms~600ms` |
| Memory Usage | `1~2G` |
| CPU Usage | `60~80%` |
**Summary**: With sending concurrency reaching `40,000/second`, the average message sending latency is still within milliseconds, memory is stable, CPU usage is within expectations. Excellent performance! 👍
(If you have no concept of `40,000/second`, compared to MySQL, a 16-core server has write performance of about 3,000~8,000/second. `40,000/second` is close to Redis write performance with AOF mode enabled)
### Receive Rate Testing
**Test Content**
| Content | Online | Send Rate |
| :----------------------- | :------------------------------- | :------------------------ |
| 1 group of 10,000 people | 5,000 group members online (50%) | 2,400 messages per minute |
**Total Receive Rate**: `5000 * (2400/60) = 200,000 messages/second`
**Test Screenshots**
| Test Content | System Top Information |
| :------------------ | :--------------------- |
|
|
|
**Test Results**
| Metric | Value |
| :---------------------------- | :--------------- |
| Total Messages Received | `100 million` |
| Message Receiving Concurrency | `200,000/second` |
| Average Receive Latency | `Under 1s` |
| Memory Usage | `1~2G` |
| CPU Usage | `40~60%` |
**Summary**: Under maximum server performance utilization, receiving concurrency reached `200,000/s`, demonstrating very strong performance.
### Mixed Testing
**Test Content**
| Content | Online | Send Rate |
| :---------------------------- | :------------------------- | :---------------------------------- |
| 1,000 one-on-one chats | 1,000 people online | 600 messages per minute per pair |
| 100 groups of 100 people each | 1 person online per group | 1,200 messages per minute per group |
| 1 group of 10,000 people | 5,000 group members online | 300 messages per minute |
**Total Send Rate**: `1000 * (600/60) + 100 * (1200/60) + 1 * (600/60) = 12,010 messages/second`
**Total Receive Rate**: `1000 * (600/60) + 100 * (600/60) + 1 * 5000 * (300/60) = 36,000 messages/second`
**Test Screenshots**
| Test Content | System Top Information |
| :------------------ | :--------------------- |
|
|
|
**Test Results**
| Metric | Value |
| :---------------------- | :----------------------- |
| Messages Sent | `~4 million` |
| Send Concurrency | `12,000 messages/second` |
| Average Send Latency | `10~500ms` |
| Messages Received | `~10 million` |
| Receive Concurrency | `36,000 messages/second` |
| Average Receive Latency | `Under 1s` |
| Memory Usage | `1~2G` |
| CPU Usage | `70~80%` |
**Summary**: Under maximum server performance utilization, send and receive concurrency reached `12,000/s` and `36,000/s` respectively, showing excellent performance.
## Stability Testing
Testing whether the system can run stably under low message latency
### Test Objective
Test whether the system can run smoothly under high-frequency sending and receiving concurrency, with average message latency in ideal state (under 500ms).
### Test Content
**20,000 people online simultaneously**
| Content | Online | Send Rate |
| :---------------------------- | :-------------------------------- | :------------------------------- |
| 1,000 one-on-one chats | 1,000 people online | 30 messages per minute per pair |
| 200 groups of 200 people each | 50 (25%) people online per group | 30 messages per minute per group |
| 100 groups of 500 people each | 125 (25%) people online per group | 30 messages per minute per group |
| 1 group of 10,000 people | 2,500 group members online (25%) | 30 messages per minute per group |
**Duration**: 24 hours
### Test Results
**Result Screenshots**
| Test Content | System Top Information |
| :------------------ | :--------------------- |
|
|
|
**Core Metrics**
| Content | Result |
| :----------------------- | :----------------------- |
| Continuous Test Duration | 48 hours |
| Simultaneous Online | 20,000 |
| Messages Sent | 100 million |
| Messages Received | 2 billion |
| Success Rate | 100% |
| Send Rate | \~600 messages/second |
| Receive Rate | \~15,000 messages/second |
| Average Send Latency | 1~30ms |
| Average Receive Latency | 1~200ms |
| Memory | \~5G |
| CPU | Maintained at \~20% |
**Summary**: With a 16-core 32GB server under high-frequency messaging in numerous group chats and one-on-one chats, CPU remained stable at around 20%, memory stable at 4-5G, and average message latency remained at millisecond level. Excellent performance.
## Reliability Testing
Testing whether messages are lost
### Test Objective
Under massive message sending and receiving, all recipients can receive all messages completely.
### Test Content
| Content | Online | Send Rate |
| :---------------------------- | :-------------------------------- | :------------------------------- |
| 1,000 one-on-one chats | 1,000 people online | 30 messages per minute per pair |
| 200 groups of 200 people each | 50 (25%) people online per group | 30 messages per minute per group |
| 100 groups of 500 people each | 125 (25%) people online per group | 30 messages per minute per group |
| 1 group of 10,000 people | 2,500 group members online (25%) | 30 messages per minute per group |
**Duration**: 24 hours
### Test Results
**Result Screenshots**
| Test Content | Expected Messages Received |
| :------------------ | :------------------------- |
|
|
|
**Core Metrics**
| Content | Result |
| :--------------------------- | :------------ |
| Simultaneous Online | 20,000 |
| Messages Sent | 102,335,788 |
| Messages Received | 2,021,201,206 |
| Success Rate | 100% |
| Expected Messages to Receive | 2,021,201,206 |
| Actual Messages Received | 2,021,201,206 |
**Summary**: After massive message sending and receiving, the actual number of messages received is completely consistent with the expected number calculated by the program based on test data. That is, after sending over 100 million messages, all online recipients received them completely. This fully demonstrates the reliability of `WuKongIM`.
## Ordering Testing
Testing whether messages are out of order
### Test Objective
Rapidly send messages within a channel to see if the recipient's order is consistent with the sender's.
### Test Content
| Content | Online | Send Rate |
| :---------------- | :-------------- | :-------------------- |
| 1 one-on-one chat | 2 people online | Rapid message sending |
### Test Results
**Test Video**
**Summary**: When one user rapidly sends messages to another user, the recipient's message order is completely consistent with the sender's order.
# Introduction to WuKongIM
Source: https://wukong.mintlify.app/en/index
Learn about WuKongIM's core features and capabilities
## What is WuKongIM?
WuKongIM is a high-performance distributed instant messaging service that supports various communication scenarios including chat applications, message push, IoT communication, audio/video signaling, live streaming, customer service systems, AI communication, and instant communities.
## Core Philosophy
**As simple as Redis, as high-performance as Kafka, as reliable as MySQL**
WuKongIM is designed with three core principles:
* **Simplicity**: Easy to deploy and manage with zero dependencies
* **Performance**: High throughput and low latency for real-time communication
* **Reliability**: Distributed architecture with automatic failover and data consistency
## Key Features
### 🎯 Unique Capabilities
* **Unlimited Group Members**: Support for 100,000+ member group chats
* **Permanent Message Storage**: Messages are stored permanently with efficient retrieval
* **Custom Binary Protocol**: Optimized for minimal bandwidth usage
### 📱 Low Resource Consumption
* **Efficient Protocol**: 1-byte heartbeat packets save bandwidth and battery
* **Optimized Storage**: Custom storage engine designed specifically for IM workloads
* **Smart Caching**: Intelligent message caching for optimal performance
### 🔐 Security First
* **End-to-End Encryption**: Message channels and content are fully encrypted
* **Attack Prevention**: Protection against man-in-the-middle attacks and message tampering
* **Data Backup**: Real-time server data backup ensures no data loss
### 🚀 High Performance
* **Custom Storage**: Built on PebbleDB with IM-specific optimizations
* **Distributed Database**: Purpose-built distributed database eliminates generic database overhead
* **Fast Storage = Fast Messages**: Optimized storage layer enables high-speed message delivery
### 🔥 High Availability
* **Modified Raft Protocol**: Custom distributed consensus for automatic disaster recovery
* **Zero Downtime**: Automatic failover when nodes go down, transparent to users
* **Decentralized**: No single point of failure, all nodes are independent and equal
* **Easy Scaling**: Add machines without downtime or data migration
### 0️⃣ Easy to Use
* **Zero Dependencies**: No third-party middleware required
* **Simple Deployment**: Start with a single command
* **Channel-Based Design**: Easy to understand publish-subscribe model
* **Developer Friendly**: Comprehensive documentation and SDK support
### 🌲 Technical Support
* **Official Support**: Technical support from the core team
* **Documentation**: Comprehensive technical documentation
* **Community**: Active community and discussion groups
* **Issue Tracking**: GitHub Issues for feedback and bug reports
## Architecture Overview
WuKongIM uses a channel-based architecture where:
* **Channels** are the core communication units
* **Users** subscribe to channels to receive messages
* **Messages** are published to channels and delivered to subscribers
* **Nodes** work together in a distributed cluster for high availability
## Use Cases
WuKongIM is perfect for:
* **Chat Applications**: Group chats, private messaging, and social platforms
* **Message Push**: Notification systems and real-time updates
* **IoT Communication**: Device-to-device and device-to-server messaging
* **Audio/Video Signaling**: WebRTC signaling and media coordination
* **Live Streaming**: Chat and interaction features for live broadcasts
* **Customer Service**: Support chat systems and helpdesk solutions
* **AI Communication**: Chatbots and AI-powered messaging
* **Instant Communities**: Real-time community platforms and forums
## Getting Help
If you encounter any issues or have suggestions for improvement, please provide feedback through GitHub Issues: [https://github.com/WuKongIM/WuKongIM/issues](https://github.com/WuKongIM/WuKongIM/issues)
# Multi-Node Deployment
Source: https://wukong.mintlify.app/en/installation/docker/multi-node
Deploy WuKongIM multi-node cluster using Docker Compose
WuKongIM multi-node cluster provides high availability, disaster recovery capability and load balancing, suitable for large applications with high data security requirements.
## Cluster Features
**Advantages**:
* High availability and strong disaster recovery capability
* Supports online scaling
* Real-time automatic backup between multiple replicas
* Load balancing
**Disadvantages**:
* Slightly complex deployment
* Requires multiple machines
**Cluster Principle**: WuKongIM follows the `2n+1` principle, where n represents the number of nodes allowed to fail.
* Allow 1 machine to fail: requires 3 machines (2×1+1=3)
* Allow 2 machines to fail: requires 5 machines (2×2+1=5)
## Environment Requirements
* **Number of machines**: 4 or more
* **Operating System**: Linux (Ubuntu recommended)
* **Configuration**: 2 cores 4GB or 4 cores 8GB
* **Docker**: Version 24.0.4 or above
**Example server configuration**:
| Role | Description | Internal IP | External IP |
| ---------------------------- | ------------- | ----------- | -------------- |
| Load balancer and monitoring | gateway | 10.206.0.2 | 119.45.33.109 |
| WuKongIM node | node1 (ID: 1) | 10.206.0.10 | 146.56.249.208 |
| WuKongIM node | node2 (ID: 2) | 10.206.0.12 | 129.211.171.99 |
| WuKongIM node | node3 (ID: 3) | 10.206.0.5 | 119.45.175.82 |
## Deployment Steps
### 1. Install Load Balancer and Monitoring
Create installation directory on the `gateway` node:
```bash theme={null}
mkdir ~/gateway
cd ~/gateway
```
Create `docker-compose.yml` file:
```yaml theme={null}
version: '3.7'
services:
prometheus: # Monitoring service
image: registry.cn-shanghai.aliyuncs.com/wukongim/prometheus:v2.53.1
volumes:
- "./prometheus.yml:/etc/prometheus/prometheus.yml"
ports:
- "9090:9090"
nginx: # Load balancer
image: registry.cn-shanghai.aliyuncs.com/wukongim/nginx:1.27.0
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
ports:
- "15001:15001"
- "15100:15100"
- "15200:15200"
- "15300:15300"
- "15172:15172"
```
Create `nginx.conf` file (replace IP addresses with actual addresses):
```nginx theme={null}
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
use epoll;
worker_connections 4096;
multi_accept on;
accept_mutex off;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
# API load balancing
upstream wukongimapi {
server 10.206.0.10:5001;
server 10.206.0.12:5001;
server 10.206.0.5:5001;
}
# Demo load balancing
upstream wukongimdemo {
server 10.206.0.10:5172;
server 10.206.0.12:5172;
server 10.206.0.5:5172;
}
# Manager load balancing
upstream wukongimanager {
server 10.206.0.10:5300;
server 10.206.0.12:5300;
server 10.206.0.5:5300;
}
# WebSocket load balancing
upstream wukongimws {
server 10.206.0.10:5200;
server 10.206.0.12:5200;
server 10.206.0.5:5200;
}
# HTTP API forwarding
server {
listen 15001;
location / {
proxy_pass http://wukongimapi;
proxy_connect_timeout 20s;
proxy_read_timeout 60s;
}
}
# Demo interface
server {
listen 15172;
location / {
proxy_pass http://wukongimdemo;
proxy_connect_timeout 20s;
proxy_read_timeout 60s;
}
location /login {
rewrite ^ /chatdemo?apiurl=http://119.45.33.109:15001;
proxy_pass http://wukongimdemo;
proxy_connect_timeout 20s;
proxy_read_timeout 60s;
}
}
# Manager interface
server {
listen 15300;
location / {
proxy_pass http://wukongimanager;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
}
}
# WebSocket forwarding
server {
listen 15200;
location / {
proxy_pass http://wukongimws;
proxy_redirect off;
proxy_http_version 1.1;
proxy_read_timeout 180s;
proxy_send_timeout 120s;
proxy_connect_timeout 4s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}
# TCP load balancing
stream {
upstream wukongimtcp {
server 10.206.0.10:5100;
server 10.206.0.12:5100;
server 10.206.0.5:5100;
}
server {
listen 15100;
proxy_connect_timeout 4s;
proxy_timeout 120s;
proxy_pass wukongimtcp;
}
}
```
Create `prometheus.yml` file (replace IP addresses with actual addresses):
```yaml theme={null}
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: wukongim1-trace-metrics
static_configs:
- targets: ['10.206.0.10:5300']
labels:
id: "1"
- job_name: wukongim2-trace-metrics
static_configs:
- targets: ['10.206.0.12:5300']
labels:
id: "2"
- job_name: wukongim3-trace-metrics
static_configs:
- targets: ['10.206.0.5:5300']
labels:
id: "3"
```
### 2. Install WuKongIM Nodes
Create installation directory on all WuKongIM nodes:
```bash theme={null}
mkdir ~/wukongim
cd ~/wukongim
```
**Node 1 Configuration** (replace IP addresses with actual addresses):
```yaml theme={null}
version: '3.7'
services:
wukongim:
image: registry.cn-shanghai.aliyuncs.com/wukongim/wukongim:v2
environment:
- "WK_MODE=release"
- "WK_CLUSTER_NODEID=1"
- "WK_INTRANET_TCPADDR=10.206.0.10:5100"
- "WK_CLUSTER_APIURL=http://10.206.0.10:5001"
- "WK_CLUSTER_SERVERADDR=10.206.0.10:11110"
- "WK_EXTERNAL_WSADDR=ws://119.45.33.109:15200"
- "WK_EXTERNAL_TCPADDR=119.45.33.109:15100"
- "WK_TRACE_PROMETHEUSAPIURL=http://10.206.0.2:9090"
- "WK_CLUSTER_INITNODES=1@10.206.0.10 2@10.206.0.12 3@10.206.0.5"
healthcheck:
test: "wget -q -Y off -O /dev/null http://localhost:5001/health > /dev/null 2>&1"
interval: 10s
timeout: 10s
retries: 3
restart: always
volumes:
- ./wukongim_data:/root/wukongim
ports:
- 11110:11110 # Distributed node communication port
- 5001:5001 # Internal API communication port
- 5100:5100 # TCP port
- 5200:5200 # WebSocket port
- 5300:5300 # Management port
- 5172:5172 # Demo port
```
**Node 2 Configuration** (replace IP addresses with actual addresses):
```yaml theme={null}
version: '3.7'
services:
wukongim:
image: registry.cn-shanghai.aliyuncs.com/wukongim/wukongim:v2
environment:
- "WK_MODE=release"
- "WK_CLUSTER_NODEID=2"
- "WK_CLUSTER_APIURL=http://10.206.0.12:5001"
- "WK_CLUSTER_SERVERADDR=10.206.0.12:11110"
- "WK_EXTERNAL_WSADDR=ws://119.45.33.109:15200"
- "WK_EXTERNAL_TCPADDR=119.45.33.109:15100"
- "WK_INTRANET_TCPADDR=10.206.0.12:5100"
- "WK_TRACE_PROMETHEUSAPIURL=http://10.206.0.2:9090"
- "WK_CLUSTER_INITNODES=1@10.206.0.10 2@10.206.0.12 3@10.206.0.5"
healthcheck:
test: "wget -q -Y off -O /dev/null http://localhost:5001/health > /dev/null 2>&1"
interval: 10s
timeout: 10s
retries: 3
restart: always
volumes:
- ./wukongim_data:/root/wukongim
ports:
- 11110:11110
- 5001:5001
- 5100:5100
- 5200:5200
- 5300:5300
- 5172:5172
```
**Node 3 Configuration** (replace IP addresses with actual addresses):
```yaml theme={null}
version: '3.7'
services:
wukongim:
image: registry.cn-shanghai.aliyuncs.com/wukongim/wukongim:v2
environment:
- "WK_MODE=release"
- "WK_CLUSTER_NODEID=3"
- "WK_CLUSTER_APIURL=http://10.206.0.5:5001"
- "WK_CLUSTER_SERVERADDR=10.206.0.5:11110"
- "WK_EXTERNAL_WSADDR=ws://119.45.33.109:15200"
- "WK_EXTERNAL_TCPADDR=119.45.33.109:15100"
- "WK_INTRANET_TCPADDR=10.206.0.5:5100"
- "WK_TRACE_PROMETHEUSAPIURL=http://10.206.0.2:9090"
- "WK_CLUSTER_INITNODES=1@10.206.0.10 2@10.206.0.12 3@10.206.0.5"
healthcheck:
test: "wget -q -Y off -O /dev/null http://localhost:5001/health > /dev/null 2>&1"
interval: 10s
timeout: 10s
retries: 3
restart: always
volumes:
- ./wukongim_data:/root/wukongim
ports:
- 11110:11110
- 5001:5001
- 5100:5100
- 5200:5200
- 5300:5300
- 5172:5172
```
### 3. Start Services
**Startup Order**:
1. First start load balancer and monitoring:
```bash theme={null}
# On gateway node
cd ~/gateway
docker-compose up -d
```
2. Then start all WuKongIM nodes:
```bash theme={null}
# On each WuKongIM node
cd ~/wukongim
docker-compose up -d
```
### 4. Verify Deployment
**Check service status**:
```bash theme={null}
# Check container status
docker-compose ps
# View logs
docker-compose logs -f
```
**Verify cluster status**:
```bash theme={null}
# Check cluster nodes
curl http://119.45.33.109:15001/cluster/nodes
# Check health status
curl http://119.45.33.109:15001/health
```
**Access services**:
* **Demo Interface**: [http://119.45.33.109:15172](http://119.45.33.109:15172)
* **Management Interface**: [http://119.45.33.109:15300](http://119.45.33.109:15300)
* **Monitoring Interface**: [http://119.45.33.109:9090](http://119.45.33.109:9090)
* **API Address**: [http://119.45.33.109:15001](http://119.45.33.109:15001)
## Configuration Description
### Key Environment Variables
| Variable Name | Description | Example Value |
| ----------------------- | -------------------------- | -------------------------------------------------- |
| `WK_CLUSTER_NODEID` | Node ID | 1, 2, 3 |
| `WK_CLUSTER_APIURL` | Node API address | [http://10.206.0.10:5001](http://10.206.0.10:5001) |
| `WK_CLUSTER_SERVERADDR` | Node communication address | 10.206.0.10:11110 |
| `WK_CLUSTER_INITNODES` | Initial node list | 1\@10.206.0.10 2\@10.206.0.12 3\@10.206.0.5 |
| `WK_EXTERNAL_WSADDR` | External WebSocket address | ws\://119.45.33.109:15200 |
| `WK_EXTERNAL_TCPADDR` | External TCP address | 119.45.33.109:15100 |
### Port Description
| Port | Description | Access Method |
| ----- | ------------------------ | ------------------------ |
| 5001 | HTTP API | Internal access |
| 5100 | TCP connection | Client connection |
| 5200 | WebSocket | Client connection |
| 5300 | Management interface | Web access |
| 5172 | Demo interface | Web access |
| 11110 | Cluster communication | Inter-node communication |
| 15001 | Load balanced API | External access |
| 15100 | Load balanced TCP | External access |
| 15200 | Load balanced WebSocket | External access |
| 15300 | Load balanced management | External access |
| 15172 | Load balanced Demo | External access |
## Troubleshooting
### Common Issues
**Node cannot join cluster**:
```bash theme={null}
# Check network connectivity
ping 10.206.0.10
# Check if port is open
telnet 10.206.0.10 11110
# View node logs
docker-compose logs wukongim
```
**Load balancer cannot be accessed**:
```bash theme={null}
# Check nginx configuration
docker-compose exec nginx nginx -t
# Restart nginx
docker-compose restart nginx
```
**Monitoring data anomaly**:
```bash theme={null}
# Check Prometheus configuration
curl http://119.45.33.109:9090/api/v1/targets
# Restart monitoring service
docker-compose restart prometheus
```
### Log Viewing
```bash theme={null}
# View all service logs
docker-compose logs
# View specific service logs
docker-compose logs wukongim
docker-compose logs nginx
docker-compose logs prometheus
# View logs in real-time
docker-compose logs -f wukongim
```
## Scaling Operations
Adding new nodes to existing cluster:
1. Create configuration file on new node
2. Set new node ID
3. Update `WK_CLUSTER_INITNODES` to include new node
4. Start new node service
5. Update load balancer configuration
## Next Steps
Configure cluster authentication and performance optimization
Detailed cluster configuration guide
Learn about single node deployment
Start using WuKongIM API
# Cluster Scaling
Source: https://wukong.mintlify.app/en/installation/docker/scaling
WuKongIM Docker cluster scaling operations guide
WuKongIM supports dynamic scaling in Docker environments, allowing flexible adjustment of cluster size based on business requirements.
## Single Node Mode Scaling
### Description
The previously deployed [single node mode](./single-node) now needs to be scaled to multiple servers. Here we use two servers as an example to explain how to scale.
Assume there are two servers with the following information:
| Name | Internal IP | External IP | Description |
| ----------- | ------------ | ------------- | --------------------------------------------- |
| node1(1001) | 192.168.1.10 | 221.123.68.10 | Master node (originally deployed single node) |
| node2(1002) | 192.168.1.20 | 221.123.68.20 | New node to be added |
node1 is the originally deployed single node, now we want to scale to two servers, node2 is the newly added node.
The following file contents are set with assumed server IPs, just replace the corresponding IPs with your own.
### Deploy WuKongIM on node2
#### 1. Create Installation Directory
Create directory:
```bash theme={null}
mkdir ~/wukongim
```
Enter directory:
```bash theme={null}
cd ~/wukongim
```
#### 2. Create docker-compose.yml File in Installation Directory
Content as follows (note to replace corresponding IPs with your own):
```yaml theme={null}
version: '3.7'
services:
wukongim: # WuKongIM service
image: registry.cn-shanghai.aliyuncs.com/wukongim/wukongim:v2
environment:
- "WK_MODE=release" # release mode
- "WK_CLUSTER_NODEID=1002"
# - "WK_TOKENAUTHON=true" # Enable token authentication, strongly recommended for production
- "WK_EXTERNAL_IP=221.123.68.20" # Server external IP
- "WK_EXTERNAL_WSADDR=ws://221.123.68.10:15200" # WebSocket address for web clients, note this is node1's external IP
- "WK_EXTERNAL_TCPADDR=221.123.68.10:15100" # TCP address for app clients, note this is node1's external IP
- "WK_CLUSTER_APIURL=http://192.168.1.20:5001" # Node internal communication API URL, replace IP with actual node2 internal IP
- "WK_CLUSTER_SERVERADDR=192.168.1.20:11110" # Node internal communication request address
- "WK_CLUSTER_SEED=1001@192.168.1.10:11110" # Seed node, any node in original cluster can be seed node, here use node1 as seed
- "WK_TRACE_PROMETHEUSAPIURL=http://192.168.1.10:9090" # Prometheus monitoring address, node1's internal address
healthcheck:
test: "wget -q -Y off -O /dev/null http://localhost:5001/health > /dev/null 2>&1"
interval: 10s
timeout: 10s
retries: 3
restart: always
volumes:
- ./wukongim_data:/root/wukongim # Mount data to physical machine directory
ports:
- 11110:11110 # Distributed node communication port
- 5001:5001 # Internal API communication port
- 5100:5100 # TCP port
- 5200:5200 # WebSocket port
- 5300:5300 # Management port
```
### Adjust node1's Original docker-compose.yml Configuration
Add the following content under the `wukongim1` service:
```yaml theme={null}
wukongim1:
...
environment:
- "WK_EXTERNAL_WSADDR=ws://221.123.68.10:15200" # WebSocket address for web clients, using load balancer address
- "WK_EXTERNAL_TCPADDR=221.123.68.10:15100" # TCP address for app clients, using load balancer address
- "WK_CLUSTER_APIURL=http://192.168.1.10:5001" # Node internal API URL, replace IP with actual node1 internal IP
- "WK_CLUSTER_SERVERADDR=192.168.1.10:11110" # Node internal communication request address
...
```
### Deploy Load Balancer nginx
Add the following content to the `docker-compose.yml` file on `node1`:
```yaml theme={null}
nginx:
image: registry.cn-shanghai.aliyuncs.com/wukongim/nginx:1.27.0
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
ports:
- "15001:5001"
- "15100:5100"
- "15200:5200"
- "15300:5300"
- "15172:5172"
```
Create `nginx.conf` file in `node1`'s installation directory with the following content:
```nginx theme={null}
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
use epoll; # Use epoll (Linux) or kqueue (BSD), suitable for high concurrency
worker_connections 40960; # Maximum connections per worker process
multi_accept on; # Accept multiple connections at once
accept_mutex off; # Disable accept_mutex to improve performance
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
# API load balancing
upstream wukongimapi {
server 192.168.1.10:5001;
server 192.168.1.20:5001;
}
# Demo load balancing
upstream wukongimdemo {
server 192.168.1.10:5172;
server 192.168.1.20:5172;
}
# Manager load balancing
upstream wukongimanager {
server 192.168.1.10:5300;
server 192.168.1.20:5300;
}
# WebSocket load balancing
upstream wukongimws {
server 192.168.1.10:5200;
server 192.168.1.20:5200;
}
# HTTP API forwarding
server {
listen 5001;
location / {
proxy_pass http://wukongimapi;
proxy_connect_timeout 20s;
proxy_read_timeout 60s;
}
}
# Demo
server {
listen 5172;
location / {
proxy_pass http://wukongimdemo;
proxy_connect_timeout 20s;
proxy_read_timeout 60s;
}
location /login {
rewrite ^ /chatdemo?apiurl=http://221.123.68.10:15001;
proxy_pass http://wukongimdemo;
proxy_connect_timeout 20s;
proxy_read_timeout 60s;
}
}
# Manager
server {
listen 5300;
location / {
proxy_pass http://wukongimanager;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
}
}
# WebSocket
server {
listen 5200;
location / {
proxy_pass http://wukongimws;
proxy_redirect off;
proxy_http_version 1.1;
proxy_read_timeout 120s;
proxy_send_timeout 120s;
proxy_connect_timeout 4s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}
# TCP
stream {
# TCP load balancing
upstream wukongimtcp {
server 192.168.1.10:5100;
server 192.168.1.20:5100;
}
server {
listen 5100;
proxy_connect_timeout 4s;
proxy_timeout 120s;
proxy_pass wukongimtcp;
}
}
```
### Include New Node in Monitoring prometheus.yml
Modify the `prometheus.yml` file on node1, complete content as follows:
```yaml theme={null}
global:
scrape_interval: 10s
evaluation_interval: 10s
scrape_configs:
- job_name: wukongim1-trace-metrics
static_configs:
- targets: ['wukongim:5300']
labels:
id: "1001"
- job_name: wukongim2-trace-metrics
static_configs:
- targets: ['192.168.1.20:5300']
labels:
id: "1002"
```
### Start and Stop
Execute the following commands in each node's installation directory:
#### Start
```bash theme={null}
sudo docker-compose up -d
```
#### Stop
```bash theme={null}
sudo docker-compose stop
```
### Port Configuration
#### External Network Ports
| Port | Description |
| ----- | ---------------------------------------------------------------- |
| 15001 | HTTP API port (only open to internal LAN) |
| 15100 | TCP port, app clients need access |
| 15200 | WebSocket port, web IM clients need access |
| 15300 | Management system port |
| 15172 | Demo port, for demonstrating WuKongIM communication capabilities |
#### Internal Network Ports (nodes need to access each other)
| Port | Description |
| ---- | ----------------------------------------------------------------------- |
| 5001 | HTTP API port (only open to internal LAN) |
| 5100 | TCP port, only needs internal network access in distributed setup |
| 5200 | WebSocket port, only needs internal network access in distributed setup |
| 5300 | Management system port |
### Verification
Log into the management system, in node management you can see if the newly added node's status is "Joined". If so, scaling is successful.
## Multi-Node Scaling Mode
### Description
Nodes originally deployed using [multi-node deployment](./multi-node) can expand cluster size by adding nodes. This document describes how to expand cluster size by adding nodes.
Assume the newly added node information is as follows:
| Name | Internal IP | External IP |
| ----------- | ----------- | ------------- |
| node4(1004) | 10.206.0.6 | 146.56.232.98 |
### Deploy WuKongIM on node4
#### 1. Create Installation Directory
Create directory:
```bash theme={null}
mkdir ~/wukongim
```
Enter directory:
```bash theme={null}
cd ~/wukongim
```
#### 2. Create docker-compose.yml File in Installation Directory
```yaml theme={null}
version: '3.7'
services:
wukongim: # WuKongIM service
image: registry.cn-shanghai.aliyuncs.com/wukongim/wukongim:v2
environment:
- "WK_CLUSTER_NODEID=1004"
# - "WK_TOKENAUTHON=true" # Enable token authentication, strongly recommended for production
- "WK_CLUSTER_APIURL=http://10.206.0.6:5001" # Node internal communication API URL, replace IP with actual node4 internal IP
- "WK_CLUSTER_SERVERADDR=10.206.0.6:11110" # Node internal communication request address
- "WK_EXTERNAL_WSADDR=ws://119.45.229.172:15200" # WebSocket address for web clients
- "WK_EXTERNAL_TCPADDR=119.45.229.172:15100" # TCP address for app clients
- "WK_TRACE_PROMETHEUSAPIURL=http://10.206.0.13:9090" # Monitoring address
- "WK_CLUSTER_SEED=1001@10.206.0.13:11110" # Seed node, any node in original cluster can be seed node, here use node1 as seed
healthcheck:
test: "wget -q -Y off -O /dev/null http://localhost:5001/health > /dev/null 2>&1"
interval: 10s
timeout: 10s
retries: 3
restart: always
volumes:
- ./wukongim_data:/root/wukongim # Mount data to physical machine directory
ports:
- 11110:11110 # Distributed node communication port
- 5001:5001 # Internal API communication port
- 5100:5100 # TCP port
- 5200:5200 # WebSocket port
- 5300:5300 # Management port
- 5172:5172 # Demo port
```
#### 3. Configure Monitoring
In the original `node1`'s installation directory (`~/wukongim`), add the following content under `scrape_configs` in the `prometheus.yml` file:
```yaml theme={null}
scrape_configs:
...
- job_name: 'wukongim4-trace-metrics'
static_configs:
- targets: ['10.206.0.6:5300']
labels:
id: "1004"
```
#### 4. Configure Load Balancer
In the `gateway` node's installation directory (`~/gateway`), add the following content under all upstream sections in the `nginx.conf` file:
```nginx theme={null}
upstream wukongimapi {
...
server 10.206.0.6:5001;
}
upstream wukongimdemo {
...
server 10.206.0.6:5172;
}
upstream wukongimanager {
...
server 10.206.0.6:5300;
}
upstream wukongimws {
...
server 10.206.0.6:5200;
}
stream {
...
upstream wukongimtcp {
...
server 10.206.0.6:5100;
}
...
}
```
#### 5. Restart Gateway
In the gateway node, enter the installation directory (`~/gateway`) and execute the following command:
```bash theme={null}
sudo docker-compose restart
```
#### 6. Start node4
In `node4`, enter the installation directory (`~/wukongim`) and execute the following command:
```bash theme={null}
sudo docker-compose up -d
```
### Verification
Log into the management system, in node management you can see if the newly added node's status is "Joined". If so, scaling is successful.
## Best Practices
### Pre-scaling Checklist
1. **Resource Planning**: Ensure new nodes have adequate CPU, memory, and storage
2. **Network Connectivity**: Verify all nodes can communicate with each other
3. **Backup**: Create backup of existing cluster before scaling
4. **Monitoring**: Ensure monitoring is configured for new nodes
### Post-scaling Verification
1. **Cluster Status**: Check all nodes are in "Joined" state
2. **Load Distribution**: Verify traffic is distributed across all nodes
3. **Performance**: Monitor system performance after scaling
4. **Data Consistency**: Verify data replication is working correctly
## Troubleshooting
### Common Issues
**New node cannot join cluster**:
```bash theme={null}
# Check network connectivity
ping
# Check if cluster ports are accessible
telnet 11110
# View node logs
docker-compose logs wukongim
```
**Load balancer not distributing traffic**:
```bash theme={null}
# Check nginx configuration
docker-compose exec nginx nginx -t
# Restart nginx
docker-compose restart nginx
# Check upstream status
curl http://:15001/health
```
## Next Steps
Set up comprehensive monitoring for scaled cluster
Advanced cluster configuration options
Backup strategies and cluster best practices
Test cluster performance under load
# Docker Single Node
Source: https://wukong.mintlify.app/en/installation/docker/single-node
Deploy WuKongIM in single node mode using Docker Compose
# Docker Single Node Deployment
Deploy WuKongIM as a single node using Docker Compose for development and small production environments.
## Overview
Single node deployment is ideal for:
* **Small Applications**: Up to 400k daily active users
* **Development Environment**: Testing and development
* **Simple Setup**: Quick deployment with minimal configuration
* **Cost-Effective**: Single server deployment
**Scaling**: Single node can be easily upgraded to cluster mode later without data migration.
### Advantages
* Simple deployment and management
* Good performance for moderate loads
* Supports online scaling to cluster
* Lower resource requirements
### Disadvantages
* No automatic failover
* Manual backup required
* Single point of failure
## Prerequisites
Before starting, ensure you have:
Ubuntu 18.04+ or CentOS 7+ (Ubuntu 20.04+ recommended)
* **Minimum**: 2 cores, 4GB RAM
* **Recommended**: 4 cores, 8GB RAM
Docker 20.10+ and Docker Compose 1.29+
Ports 5001, 5100, 5200, 5300 available
## Installation
### 1. Create Installation Directory
```bash theme={null}
# Create directory
mkdir ~/wukongim
cd ~/wukongim
```
### 2. Create Docker Compose Configuration
Create a `docker-compose.yml` file with the following content:
```yaml theme={null}
version: '3.7'
services:
wukongim:
image: registry.cn-shanghai.aliyuncs.com/wukongim/wukongim:v2
environment:
- "WK_CLUSTER_NODEID=1001"
# - "WK_TOKENAUTHON=true" # Enable token auth (recommended for production)
- "WK_CLUSTER_SERVERADDR=YOUR_INTERNAL_IP:11110" # Internal communication address
- "WK_TRACE_PROMETHEUSAPIURL=http://prometheus:9090" # Prometheus monitoring
- "WK_MODE=release" # Release mode
- "WK_EXTERNAL_IP=YOUR_EXTERNAL_IP" # Server external IP
- "WK_CLUSTER_APIURL=http://YOUR_INTERNAL_IP:5001" # Internal API address
- "WK_INTRANET_TCPADDR=YOUR_INTERNAL_IP:5100" # Internal TCP address
healthcheck:
test: "wget -q -Y off -O /dev/null http://localhost:5001/health > /dev/null 2>&1"
interval: 10s
timeout: 10s
retries: 3
restart: always
volumes:
- ./wukongim_data:/root/wukongim # Data persistence
ports:
- "5001:5001" # HTTP API
- "5100:5100" # TCP connections
- "5200:5200" # WebSocket connections
- "5300:5300" # Management interface
- "5172:5172" # Demo interface
- "11110:11110" # Cluster communication
prometheus:
image: registry.cn-shanghai.aliyuncs.com/wukongim/prometheus:v2.53.1
volumes:
- "./prometheus.yml:/etc/prometheus/prometheus.yml"
ports:
- "9090:9090"
```
**Important**: Replace the following placeholders with your actual values:
* `YOUR_EXTERNAL_IP`: Your server's external IP address
* `YOUR_INTERNAL_IP`: Your server's internal IP address
### 3. Configure Prometheus Monitoring
Create a `prometheus.yml` file for monitoring:
```yaml theme={null}
global:
scrape_interval: 10s
evaluation_interval: 10s
scrape_configs:
- job_name: wukongim-metrics
static_configs:
- targets: ['wukongim:5300']
```
### 4. Start the Services
```bash theme={null}
# Start WuKongIM and monitoring
docker-compose up -d
# Check service status
docker-compose ps
# View logs
docker-compose logs -f wukongim
```
## Configuration
### Environment Variables
Key environment variables for single node deployment:
| Variable | Description | Example |
| ----------------------- | ---------------------------------- | ------------------ |
| `WK_CLUSTER_NODEID` | Unique node identifier | `1001` |
| `WK_EXTERNAL_IP` | External IP for client connections | `203.0.113.1` |
| `WK_CLUSTER_SERVERADDR` | Internal communication address | `10.0.1.100:11110` |
| `WK_TOKENAUTHON` | Enable token authentication | `true` |
| `WK_MODE` | Running mode | `release` |
### Security Configuration
For production environments, enable authentication:
```yaml theme={null}
environment:
- "WK_TOKENAUTHON=true"
- "WK_MANAGERTOKEN=your-secure-manager-token"
```
## Verification
### 1. Health Check
```bash theme={null}
# Check if WuKongIM is running
curl http://localhost:5001/health
# Expected response: {"status":"ok"}
```
### 2. Get Connection Information
```bash theme={null}
# Get connection details for a user
curl "http://localhost:5001/route?uid=testuser"
# Expected response:
# {
# "tcp_addr": "YOUR_EXTERNAL_IP:5100",
# "ws_addr": "ws://YOUR_EXTERNAL_IP:5200"
# }
```
### 3. Access Management Interface
Open your browser and navigate to:
* **Management Interface**: `http://YOUR_EXTERNAL_IP:5300`
* **Prometheus Monitoring**: `http://YOUR_EXTERNAL_IP:9090`
* **Demo Interface**: `http://YOUR_EXTERNAL_IP:5172`
## Data Management
### Backup
```bash theme={null}
# Stop services
docker-compose down
# Backup data directory
tar -czf wukongim-backup-$(date +%Y%m%d).tar.gz wukongim_data/
# Restart services
docker-compose up -d
```
### Restore
```bash theme={null}
# Stop services
docker-compose down
# Restore data
tar -xzf wukongim-backup-YYYYMMDD.tar.gz
# Restart services
docker-compose up -d
```
## Troubleshooting
### Common Issues
Check if ports are already in use:
```bash theme={null}
sudo netstat -tulpn | grep :5001
sudo netstat -tulpn | grep :5100
sudo netstat -tulpn | grep :5200
```
Verify the service is running and healthy:
```bash theme={null}
docker-compose ps
docker-compose logs wukongim
```
Ensure firewall allows connections:
```bash theme={null}
sudo ufw allow 5001/tcp
sudo ufw allow 5100/tcp
sudo ufw allow 5200/tcp
```
### Log Analysis
```bash theme={null}
# View real-time logs
docker-compose logs -f wukongim
# View specific number of log lines
docker-compose logs --tail=100 wukongim
# Export logs to file
docker-compose logs wukongim > wukongim.log
```
## Next Steps
Upgrade to multi-node cluster deployment
Set up user authentication
Start using the WuKongIM API
Set up comprehensive monitoring
# Version Upgrade
Source: https://wukong.mintlify.app/en/installation/docker/upgrade
WuKongIM Docker deployment version upgrade guide
WuKongIM supports smooth upgrades, ensuring service continuity and data security.
## Description
WuKongIM's version numbering follows the `major.minor.patch` format, for example `1.0.0`. When the patch number increases, it indicates bug fixes or minor feature updates; when the minor version increases, it indicates new features; when the major version increases, it indicates incompatible API changes.
Therefore, as long as you don't upgrade major versions, the upgrade process is smooth.
**Version Compatibility**:
* **Patch updates** (e.g., 2.0.1 → 2.0.2): Always safe, includes bug fixes and minor improvements
* **Minor updates** (e.g., 2.0.x → 2.1.x): Generally safe, includes new features with backward compatibility
* **Major updates** (e.g., 2.x.x → 3.x.x): May include breaking changes, requires careful planning
## Upgrade Steps
### 1. Check Current Version
First, check your current WuKongIM version:
```bash theme={null}
# Check running version
docker-compose exec wukongim /wukongim version
# Or check via API
curl http://localhost:5001/version
```
### 2. Backup Data (Recommended)
Before upgrading, it's recommended to backup your data:
```bash theme={null}
# Stop services
docker-compose stop
# Create backup
sudo cp -r ./wukongim_data ./wukongim_data_backup_$(date +%Y%m%d)
# Or create compressed backup
sudo tar -czf wukongim_backup_$(date +%Y%m%d).tar.gz ./wukongim_data
```
### 3. Update Docker Compose Configuration
Modify the `image` field of the `wukongim` service in `docker-compose.yml` to the new version.
For example:
```yaml theme={null}
version: '3.7'
services:
wukongim: # WuKongIM service
image: registry.cn-shanghai.aliyuncs.com/wukongim/wukongim:v2.1.0 # New version number
# ... other configurations remain unchanged
```
### 4. Pull New Image
Use the following command to get the latest image:
```bash theme={null}
sudo docker-compose pull wukongim
```
### 5. Restart Services
```bash theme={null}
sudo docker-compose up -d
```
### 6. Verify Upgrade
After restart, verify the upgrade was successful:
```bash theme={null}
# Check container status
docker-compose ps
# Check logs
docker-compose logs wukongim
# Verify version
curl http://localhost:5001/version
# Check health status
curl http://localhost:5001/health
```
## Rolling Upgrade for Clusters
For cluster deployments, perform rolling upgrades to maintain service availability:
### 1. Upgrade One Node at a Time
```bash theme={null}
# On node1
cd ~/wukongim
# Update docker-compose.yml with new version
docker-compose pull wukongim
docker-compose up -d
# Wait for node1 to be healthy, then proceed to node2
# Repeat for each node
```
### 2. Verify Cluster Health
```bash theme={null}
# Check cluster status
curl http://load-balancer-ip:15001/cluster/nodes
# Verify all nodes are running the new version
curl http://node1-ip:5001/version
curl http://node2-ip:5001/version
curl http://node3-ip:5001/version
```
## Upgrade Strategies
### Blue-Green Deployment
For critical production environments, consider blue-green deployment:
1. **Prepare Green Environment**: Set up a new environment with the new version
2. **Data Sync**: Ensure data is synchronized between blue and green environments
3. **Switch Traffic**: Update load balancer to point to green environment
4. **Verify**: Confirm everything works correctly
5. **Cleanup**: Remove blue environment after successful verification
### Canary Deployment
For gradual rollout:
1. **Deploy to Subset**: Upgrade only a portion of nodes
2. **Monitor**: Watch metrics and logs for issues
3. **Gradual Rollout**: Progressively upgrade more nodes
4. **Full Deployment**: Complete upgrade once confident
## Rollback Procedures
If issues occur during upgrade, you can rollback:
### 1. Quick Rollback
```bash theme={null}
# Revert docker-compose.yml to previous version
# Pull previous image
docker-compose pull wukongim
# Restart with previous version
docker-compose up -d
```
### 2. Data Rollback (if needed)
```bash theme={null}
# Stop services
docker-compose stop
# Restore backup
sudo rm -rf ./wukongim_data
sudo cp -r ./wukongim_data_backup_YYYYMMDD ./wukongim_data
# Or restore from compressed backup
sudo tar -xzf wukongim_backup_YYYYMMDD.tar.gz
# Restart services
docker-compose up -d
```
## Upgrade Checklist
### Pre-Upgrade
* [ ] Check current version and target version compatibility
* [ ] Review release notes for breaking changes
* [ ] Create data backup
* [ ] Plan maintenance window
* [ ] Notify users of potential downtime
* [ ] Prepare rollback plan
### During Upgrade
* [ ] Monitor system resources
* [ ] Watch application logs
* [ ] Verify service health endpoints
* [ ] Test critical functionality
* [ ] Monitor cluster status (for multi-node)
### Post-Upgrade
* [ ] Verify version upgrade successful
* [ ] Test all major features
* [ ] Monitor performance metrics
* [ ] Check data integrity
* [ ] Update documentation
* [ ] Clean up old backups (after verification period)
## Troubleshooting
### Common Issues
**Container fails to start after upgrade**:
```bash theme={null}
# Check logs for errors
docker-compose logs wukongim
# Check if image was pulled correctly
docker images | grep wukongim
# Verify configuration compatibility
docker-compose config
```
**Data migration issues**:
```bash theme={null}
# Check data directory permissions
ls -la ./wukongim_data
# Verify data integrity
docker-compose exec wukongim ls -la /root/wukongim
```
**Cluster nodes out of sync**:
```bash theme={null}
# Check cluster status
curl http://localhost:5001/cluster/nodes
# Restart problematic nodes
docker-compose restart wukongim
```
### Recovery Steps
1. **Stop services**: `docker-compose stop`
2. **Restore backup**: Restore from backup created before upgrade
3. **Revert configuration**: Use previous docker-compose.yml
4. **Restart**: `docker-compose up -d`
5. **Verify**: Confirm system is working correctly
## Best Practices
### Planning
* **Test in staging**: Always test upgrades in a staging environment first
* **Read release notes**: Understand what changes are included
* **Schedule maintenance**: Plan upgrades during low-traffic periods
* **Communicate**: Inform stakeholders about planned maintenance
### Execution
* **Monitor closely**: Watch logs and metrics during upgrade
* **Upgrade gradually**: For clusters, upgrade one node at a time
* **Verify thoroughly**: Test all critical functionality after upgrade
* **Keep backups**: Maintain backups until upgrade is fully verified
### Automation
Consider automating upgrades for consistency:
```bash theme={null}
#!/bin/bash
# upgrade.sh - Automated upgrade script
# Configuration
NEW_VERSION="v2.1.0"
BACKUP_DIR="./backups"
COMPOSE_FILE="docker-compose.yml"
# Create backup
echo "Creating backup..."
mkdir -p $BACKUP_DIR
sudo cp -r ./wukongim_data $BACKUP_DIR/wukongim_data_$(date +%Y%m%d_%H%M%S)
# Update compose file
echo "Updating docker-compose.yml..."
sed -i "s|wukongim:v.*|wukongim:$NEW_VERSION|g" $COMPOSE_FILE
# Pull new image
echo "Pulling new image..."
docker-compose pull wukongim
# Restart services
echo "Restarting services..."
docker-compose up -d
# Verify upgrade
echo "Verifying upgrade..."
sleep 10
curl -f http://localhost:5001/health || echo "Health check failed!"
echo "Upgrade completed!"
```
## Next Steps
Set up monitoring for upgraded system
Optimize performance after upgrade
Backup strategies and best practices
Learn about cluster scaling and management
# Multi-Node Deployment
Source: https://wukong.mintlify.app/en/installation/k8s/multi-node
Deploy WuKongIM multi-node cluster in Kubernetes using Helm
Deploy WuKongIM multi-node cluster in Kubernetes cluster, providing high availability, disaster recovery capability and load balancing.
## Deployment Features
**Advantages**:
* High availability and strong disaster recovery capability
* Supports online scaling
* Real-time automatic backup between multiple replicas
* Load balancing without manual configuration
* Fast scaling
**Applicable scenarios**:
* Applications with high data security requirements
* Large applications
* Enterprise production environments
**Note**: WuKongIM currently supports hot scaling but does not support hot shrinking!
## Prerequisites
* Kubernetes cluster 1.19+
* kubectl configured and able to access cluster
* Helm 3.0+
* At least 3 worker nodes, each with 4GB+ memory and 2 CPU cores
## Deployment Steps
### 1. Add Helm Repository
```bash theme={null}
helm repo add wukongim https://wukongim.github.io/helm/
```
### 2. Update Helm Repository
```bash theme={null}
helm repo update
```
### 3. Search Available Charts
```bash theme={null}
helm search repo wukongim
```
### 4. Deploy WuKongIM Multi-Node Cluster
```bash theme={null}
# Multi-node deployment (replica count is 3)
helm install wkim wukongim/wukongim \
-n wukongim \
--create-namespace \
--version 0.1.0 \
--set replicaCount=3
```
**Optional parameters**:
* `replicaCount=3`: Number of replicas (default is 2)
* `externalIP=`: External IP address
### 5. Check Installation Status
```bash theme={null}
helm status wkim -n wukongim
```
### 6. Verify Deployment
```bash theme={null}
# Check Pod status
kubectl get pods -n wukongim
# Check service status
kubectl get svc -n wukongim
# Check cluster status
kubectl port-forward svc/wkim-wukongim 5001:5001 -n wukongim &
curl http://localhost:5001/cluster/nodes
```
## Access Services
### Port Forward Access
```bash theme={null}
# API service
kubectl port-forward svc/wkim-wukongim 5001:5001 -n wukongim
# WebSocket service
kubectl port-forward svc/wkim-wukongim 5200:5200 -n wukongim
# Demo interface
kubectl port-forward svc/wkim-wukongim 5172:5172 -n wukongim
# Management interface
kubectl port-forward svc/wkim-wukongim 5300:5300 -n wukongim
```
### Access URLs
* **API Endpoint**: [http://localhost:5001](http://localhost:5001)
* **WebSocket**: ws\://localhost:5200
* **Demo Interface**: [http://localhost:5172](http://localhost:5172)
* **Management Interface**: [http://localhost:5300](http://localhost:5300)
## Configuration Options
### Common Helm Parameters
```bash theme={null}
# Complete configuration example
helm install wkim wukongim/wukongim \
-n wukongim \
--create-namespace \
--version 0.1.0 \
--set replicaCount=3 \
--set image.tag=v2.0.0 \
--set service.type=LoadBalancer \
--set persistence.enabled=true \
--set persistence.size=50Gi \
--set resources.requests.memory=4Gi \
--set resources.requests.cpu=2000m
```
### Main Configuration Parameters
| Parameter | Description | Default Value |
| --------------------- | ------------------------- | ------------- |
| `replicaCount` | Number of replicas | `2` |
| `image.tag` | Image version | `latest` |
| `service.type` | Service type | `ClusterIP` |
| `persistence.enabled` | Enable persistent storage | `true` |
| `persistence.size` | Storage size | `10Gi` |
| `externalIP` | External IP address | `""` |
## Scaling Operations
### Scale Cluster
```bash theme={null}
# Scale to 5 replicas
helm upgrade wkim wukongim/wukongim \
-n wukongim \
--version 0.1.0 \
--set replicaCount=5
```
### Verify Scaling
```bash theme={null}
# Check Pod count
kubectl get pods -n wukongim
# Check cluster status
kubectl port-forward svc/wkim-wukongim 5001:5001 -n wukongim &
curl http://localhost:5001/cluster/nodes
# View cluster node information
curl http://localhost:5001/cluster/nodes | jq '.'
```
## Troubleshooting
### Common Issues
**Pod startup failure**:
```bash theme={null}
# Check Pod status
kubectl get pods -n wukongim
# View detailed information
kubectl describe pod -n wukongim
# View logs
kubectl logs -n wukongim
```
**Cluster nodes cannot communicate**:
```bash theme={null}
# Check network connectivity
kubectl exec -it -n wukongim -- ping
# Check cluster ports
kubectl exec -it -n wukongim -- netstat -tulpn | grep 11110
# View cluster status
kubectl logs -n wukongim | grep cluster
```
**Service inaccessible**:
```bash theme={null}
# Check service status
kubectl get svc -n wukongim
# Check endpoints
kubectl get endpoints -n wukongim
# Test service connectivity
kubectl exec -it -n wukongim -- wget -qO- http://localhost:5001/health
```
### Log Viewing
```bash theme={null}
# View real-time logs
kubectl logs -f deployment/wkim-wukongim -n wukongim
# View all replica logs
kubectl logs -l app.kubernetes.io/name=wukongim -n wukongim
# View historical logs
kubectl logs deployment/wkim-wukongim -n wukongim --previous
```
## Upgrade and Maintenance
### Version Upgrade
```bash theme={null}
# Update Helm repository
helm repo update
# Search for new versions
helm search repo wukongim
# Upgrade to new version
helm upgrade wkim wukongim/wukongim \
-n wukongim \
--version
```
### Data Backup
```bash theme={null}
# Create data snapshot
kubectl exec -n wukongim deployment/wkim-wukongim -- \
tar czf /tmp/backup-$(date +%Y%m%d).tar.gz /data
# Copy backup file to local
kubectl cp wukongim/:/tmp/backup-$(date +%Y%m%d).tar.gz \
./wukongim-backup-$(date +%Y%m%d).tar.gz
```
## Uninstall
```bash theme={null}
# Uninstall WuKongIM
helm uninstall wkim -n wukongim
# Delete namespace (optional)
kubectl delete namespace wukongim
```
## Next Steps
Learn about single node deployment
Detailed configuration options
Docker multi-node deployment
Start using the API
# Cluster Scaling
Source: https://wukong.mintlify.app/en/installation/k8s/scaling
WuKongIM Kubernetes cluster scaling operations guide
WuKongIM supports dynamic scaling in Kubernetes environments, allowing flexible adjustment of cluster size based on business requirements.
## Description
**Applicable scenarios**: Applications with high data security requirements, large applications.
**Advantages**: High availability, strong disaster recovery, supports online scaling, real-time automatic backup between multiple replicas, load balancing, no manual configuration required, fast scaling.
**Note**: WuKongIM currently supports hot scaling but does not support hot shrinking! Does not support hot shrinking! Does not support hot shrinking!
## Scaling Operations
### Scale Up Cluster
```bash theme={null}
# Scale to 3 replicas
helm upgrade wkim wukongim/wukongim \
-n wukongim \
--create-namespace \
--version 0.1.0 \
--set replicaCount=3
```
**Optional parameters**:
* `replicaCount=3`: Number of replicas (default is 2)
* `externalIP=`: External IP address
### Scale to Different Sizes
```bash theme={null}
# Scale to 5 replicas for high load
helm upgrade wkim wukongim/wukongim \
-n wukongim \
--version 0.1.0 \
--set replicaCount=5
# Scale to 7 replicas for very high load
helm upgrade wkim wukongim/wukongim \
-n wukongim \
--version 0.1.0 \
--set replicaCount=7
```
### Verify Scaling
```bash theme={null}
# Check Pod status
kubectl get pods -n wukongim
# Check cluster nodes
kubectl port-forward svc/wkim-wukongim 5001:5001 -n wukongim &
curl http://localhost:5001/cluster/nodes
# View detailed cluster information
curl http://localhost:5001/cluster/nodes | jq '.'
```
## Scaling Best Practices
### Pre-Scaling Checklist
1. **Resource Planning**: Ensure Kubernetes cluster has sufficient resources
2. **Monitoring**: Check current resource usage and performance metrics
3. **Backup**: Create backup before scaling operations
4. **Load Testing**: Verify current performance baseline
### Recommended Scaling Patterns
**For Growing Load**:
```bash theme={null}
# Start with 3 replicas (minimum for HA)
helm upgrade wkim wukongim/wukongim -n wukongim --set replicaCount=3
# Scale to 5 for moderate load increase
helm upgrade wkim wukongim/wukongim -n wukongim --set replicaCount=5
# Scale to 7 for high load
helm upgrade wkim wukongim/wukongim -n wukongim --set replicaCount=7
```
**Resource Allocation per Replica**:
```bash theme={null}
# Scale with resource adjustments
helm upgrade wkim wukongim/wukongim \
-n wukongim \
--set replicaCount=5 \
--set resources.requests.memory=4Gi \
--set resources.requests.cpu=2000m \
--set resources.limits.memory=8Gi \
--set resources.limits.cpu=4000m
```
### Post-Scaling Verification
```bash theme={null}
# Check all pods are running
kubectl get pods -n wukongim -o wide
# Verify cluster health
kubectl exec -n wukongim deployment/wkim-wukongim -- \
curl -f http://localhost:5001/health
# Check cluster status
kubectl port-forward svc/wkim-wukongim 5001:5001 -n wukongim &
curl http://localhost:5001/cluster/nodes
# Monitor resource usage
kubectl top pods -n wukongim
kubectl top nodes
```
## Monitoring During Scaling
### Watch Scaling Progress
```bash theme={null}
# Watch pods being created
kubectl get pods -n wukongim -w
# Monitor events
kubectl get events -n wukongim --sort-by='.lastTimestamp'
# Check deployment status
kubectl rollout status deployment/wkim-wukongim -n wukongim
```
### Performance Monitoring
```bash theme={null}
# Check resource usage
kubectl top pods -n wukongim
# View logs during scaling
kubectl logs -f deployment/wkim-wukongim -n wukongim
# Monitor cluster metrics
kubectl port-forward svc/wkim-wukongim 5300:5300 -n wukongim &
curl http://localhost:5300/metrics
```
## Troubleshooting Scaling Issues
### Common Scaling Problems
**Insufficient Resources**:
```bash theme={null}
# Check node resources
kubectl describe nodes
# Check resource requests vs limits
kubectl describe deployment wkim-wukongim -n wukongim
# View pod events
kubectl describe pods -n wukongim
```
**Pod Startup Issues**:
```bash theme={null}
# Check pod status
kubectl get pods -n wukongim
# View pod logs
kubectl logs -n wukongim
# Check pod events
kubectl describe pod -n wukongim
```
**Cluster Communication Issues**:
```bash theme={null}
# Test inter-pod communication
kubectl exec -it -n wukongim -- ping
# Check cluster ports
kubectl exec -it -n wukongim -- netstat -tulpn | grep 11110
# View cluster logs
kubectl logs -n wukongim | grep cluster
```
### Recovery Procedures
```bash theme={null}
# Rollback to previous replica count if issues occur
helm rollback wkim -n wukongim
# Force restart problematic pods
kubectl delete pod -n wukongim
# Check and fix resource constraints
kubectl patch deployment wkim-wukongim -n wukongim -p '{"spec":{"template":{"spec":{"containers":[{"name":"wukongim","resources":{"requests":{"memory":"4Gi","cpu":"2000m"}}}]}}}}'
```
## Automated Scaling
### Horizontal Pod Autoscaler (HPA)
```yaml theme={null}
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: wkim-hpa
namespace: wukongim
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: wkim-wukongim
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
```
Apply HPA:
```bash theme={null}
kubectl apply -f hpa.yaml
kubectl get hpa -n wukongim
```
### Vertical Pod Autoscaler (VPA)
```yaml theme={null}
# vpa.yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: wkim-vpa
namespace: wukongim
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: wkim-wukongim
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: wukongim
maxAllowed:
cpu: 4
memory: 8Gi
minAllowed:
cpu: 500m
memory: 1Gi
```
## Performance Considerations
### Optimal Replica Counts
| Load Level | Recommended Replicas | Use Case |
| ---------- | -------------------- | ------------------------ |
| Light | 3 | Development, small teams |
| Medium | 5 | Growing applications |
| High | 7 | Large applications |
| Very High | 9+ | Enterprise scale |
### Resource Planning
```bash theme={null}
# Calculate total resources needed
# For 5 replicas with 4Gi memory each = 20Gi total memory required
# For 5 replicas with 2 CPU each = 10 CPU cores total required
helm upgrade wkim wukongim/wukongim \
-n wukongim \
--set replicaCount=5 \
--set resources.requests.memory=4Gi \
--set resources.requests.cpu=2000m
```
## Next Steps
Learn about multi-node cluster deployment
Upgrade WuKongIM in Kubernetes
Set up monitoring for scaled cluster
Optimize performance for scaled deployment
# Single Node Deployment
Source: https://wukong.mintlify.app/en/installation/k8s/single-node
Deploy WuKongIM single node instance in Kubernetes cluster using Helm
Deploy WuKongIM single node instance in Kubernetes cluster, suitable for development, testing, and small-scale production environments.
## Deployment Features
**Advantages**:
* High availability and strong disaster recovery capability
* Supports online scaling
* Real-time automatic backup between multiple replicas
* Load balancing without manual configuration
* Fast scaling
**Applicable scenarios**:
* Applications with high data security requirements
* Large applications
* Environments requiring rapid deployment
**Note**: WuKongIM currently supports hot scaling but does not support hot shrinking!
## Prerequisites
* Kubernetes cluster 1.19+
* kubectl configured and able to access cluster
* Helm 3.0+
* At least 2GB available memory and 2 CPU cores
## Deployment Steps
### 1. Add Helm Repository
```bash theme={null}
helm repo add wukongim https://wukongim.github.io/helm/
```
### 2. Update Helm Repository
```bash theme={null}
helm repo update
```
### 3. Search Available Charts
```bash theme={null}
helm search repo wukongim
```
### 4. Deploy WuKongIM
```bash theme={null}
# Single node deployment (replica count is 1)
helm install wkim wukongim/wukongim \
-n wukongim \
--create-namespace \
--version 0.1.0 \
--set replicaCount=1
```
**Optional parameters**:
* `replicaCount=1`: Number of replicas (default is 2)
* `externalIP=`: External IP address
### 5. Check Installation Status
```bash theme={null}
helm status wkim -n wukongim
```
### 6. Verify Deployment
```bash theme={null}
# Check Pod status
kubectl get pods -n wukongim
# Check service status
kubectl get svc -n wukongim
# View logs
kubectl logs -l app.kubernetes.io/name=wukongim -n wukongim
```
## Access Services
### Port Forward Access
```bash theme={null}
# API service
kubectl port-forward svc/wkim-wukongim 5001:5001 -n wukongim
# WebSocket service
kubectl port-forward svc/wkim-wukongim 5200:5200 -n wukongim
# Demo interface
kubectl port-forward svc/wkim-wukongim 5172:5172 -n wukongim
# Management interface
kubectl port-forward svc/wkim-wukongim 5300:5300 -n wukongim
```
### Access URLs
* **API Endpoint**: [http://localhost:5001](http://localhost:5001)
* **WebSocket**: ws\://localhost:5200
* **Demo Interface**: [http://localhost:5172](http://localhost:5172)
* **Management Interface**: [http://localhost:5300](http://localhost:5300)
## Configuration Options
### Common Helm Parameters
```bash theme={null}
# Complete configuration example
helm install wkim wukongim/wukongim \
-n wukongim \
--create-namespace \
--version 0.1.0 \
--set replicaCount=1 \
--set image.tag=v2.0.0 \
--set service.type=LoadBalancer \
--set persistence.enabled=true \
--set persistence.size=20Gi \
--set resources.requests.memory=2Gi \
--set resources.requests.cpu=1000m
```
### Main Configuration Parameters
| Parameter | Description | Default Value |
| --------------------- | ------------------------- | ------------- |
| `replicaCount` | Number of replicas | `2` |
| `image.tag` | Image version | `latest` |
| `service.type` | Service type | `ClusterIP` |
| `persistence.enabled` | Enable persistent storage | `true` |
| `persistence.size` | Storage size | `10Gi` |
| `externalIP` | External IP address | `""` |
## Scaling Operations
### Scale to Multi-Node
```bash theme={null}
# Scale to 3 replicas
helm upgrade wkim wukongim/wukongim \
-n wukongim \
--version 0.1.0 \
--set replicaCount=3
```
### Verify Scaling
```bash theme={null}
# Check Pod count
kubectl get pods -n wukongim
# Check cluster status
kubectl port-forward svc/wkim-wukongim 5001:5001 -n wukongim &
curl http://localhost:5001/cluster/nodes
```
## Troubleshooting
### Common Issues
**Pod startup failure**:
```bash theme={null}
# Check Pod status
kubectl get pods -n wukongim
# View detailed information
kubectl describe pod -n wukongim
# View logs
kubectl logs -n wukongim
```
**Service inaccessible**:
```bash theme={null}
# Check service status
kubectl get svc -n wukongim
# Check endpoints
kubectl get endpoints -n wukongim
# Test service connectivity
kubectl exec -it -n wukongim -- wget -qO- http://localhost:5001/health
```
**Storage issues**:
```bash theme={null}
# Check PVC status
kubectl get pvc -n wukongim
# View storage details
kubectl describe pvc -n wukongim
```
### Log Viewing
```bash theme={null}
# View real-time logs
kubectl logs -f deployment/wkim-wukongim -n wukongim
# View all replica logs
kubectl logs -l app.kubernetes.io/name=wukongim -n wukongim
# View historical logs
kubectl logs deployment/wkim-wukongim -n wukongim --previous
```
## Upgrade and Maintenance
### Version Upgrade
```bash theme={null}
# Update Helm repository
helm repo update
# Search for new versions
helm search repo wukongim
# Upgrade to new version
helm upgrade wkim wukongim/wukongim \
-n wukongim \
--version
```
### Data Backup
```bash theme={null}
# Create data snapshot
kubectl exec -n wukongim deployment/wkim-wukongim -- \
tar czf /tmp/backup-$(date +%Y%m%d).tar.gz /data
# Copy backup file to local
kubectl cp wukongim/:/tmp/backup-$(date +%Y%m%d).tar.gz \
./wukongim-backup-$(date +%Y%m%d).tar.gz
```
## Uninstall
```bash theme={null}
# Uninstall WuKongIM
helm uninstall wkim -n wukongim
# Delete namespace (optional)
kubectl delete namespace wukongim
```
## Advanced Configuration
### Custom Values File
Create a `values.yaml` file for custom configuration:
```yaml theme={null}
# values.yaml
replicaCount: 1
image:
repository: registry.cn-shanghai.aliyuncs.com/wukongim/wukongim
tag: "v2"
pullPolicy: IfNotPresent
service:
type: LoadBalancer
ports:
api: 5001
tcp: 5100
websocket: 5200
management: 5300
demo: 5172
persistence:
enabled: true
size: 20Gi
storageClass: ""
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"
nodeSelector: {}
tolerations: []
affinity: {}
```
Deploy with custom values:
```bash theme={null}
helm install wkim wukongim/wukongim \
-n wukongim \
--create-namespace \
-f values.yaml
```
### Environment Variables
Configure WuKongIM through environment variables:
```yaml theme={null}
# In values.yaml
env:
- name: WK_MODE
value: "release"
- name: WK_TOKENAUTHON
value: "true"
- name: WK_EXTERNAL_IP
value: "your-external-ip"
```
## Monitoring and Observability
### Enable Monitoring
```bash theme={null}
# Deploy with monitoring enabled
helm install wkim wukongim/wukongim \
-n wukongim \
--create-namespace \
--set monitoring.enabled=true \
--set monitoring.prometheus.enabled=true
```
### Health Checks
```bash theme={null}
# Check health endpoint
kubectl exec -n wukongim deployment/wkim-wukongim -- \
curl -f http://localhost:5001/health
# Check cluster status
kubectl exec -n wukongim deployment/wkim-wukongim -- \
curl -f http://localhost:5001/cluster/nodes
```
## Next Steps
Deploy high availability cluster
Detailed configuration options
Docker deployment method
Start using the API
# Version Upgrade
Source: https://wukong.mintlify.app/en/installation/k8s/upgrade
WuKongIM Kubernetes deployment version upgrade guide
WuKongIM supports version upgrades in Kubernetes environments with rolling updates to ensure zero-downtime deployments.
## Upgrade Overview
Kubernetes deployments provide built-in rolling update capabilities, making WuKongIM upgrades safe and seamless. The upgrade process maintains service availability by updating pods gradually.
**Version Compatibility**:
* **Patch updates** (e.g., 2.0.1 → 2.0.2): Always safe, includes bug fixes and minor improvements
* **Minor updates** (e.g., 2.0.x → 2.1.x): Generally safe, includes new features with backward compatibility
* **Major updates** (e.g., 2.x.x → 3.x.x): May include breaking changes, requires careful planning
## Upgrade Steps
### 1. Check Current Version
First, check your current WuKongIM version:
```bash theme={null}
# Check current Helm release
helm list -n wukongim
# Check running version via API
kubectl port-forward svc/wkim-wukongim 5001:5001 -n wukongim &
curl http://localhost:5001/version
```
### 2. Update Helm Repository
```bash theme={null}
helm repo update
```
### 3. Search for New Version Charts
```bash theme={null}
helm search repo wukongim
```
### 4. Backup Current Configuration
```bash theme={null}
# Export current values
helm get values wkim -n wukongim > current-values.yaml
# Create data backup
kubectl exec -n wukongim deployment/wkim-wukongim -- \
tar czf /tmp/backup-$(date +%Y%m%d).tar.gz /data
# Copy backup to local
kubectl cp wukongim/:/tmp/backup-$(date +%Y%m%d).tar.gz \
./wukongim-backup-$(date +%Y%m%d).tar.gz
```
### 5. Perform Upgrade
```bash theme={null}
# Upgrade to specific version
helm upgrade wkim wukongim/wukongim \
-n wukongim \
--version
# Or upgrade with custom values
helm upgrade wkim wukongim/wukongim \
-n wukongim \
--version \
-f current-values.yaml
```
### 6. Monitor Upgrade Progress
```bash theme={null}
# Watch the rolling update
kubectl rollout status deployment/wkim-wukongim -n wukongim
# Monitor pods during upgrade
kubectl get pods -n wukongim -w
# Check events
kubectl get events -n wukongim --sort-by='.lastTimestamp'
```
### 7. Verify Upgrade
```bash theme={null}
# Check all pods are running
kubectl get pods -n wukongim
# Verify new version
kubectl port-forward svc/wkim-wukongim 5001:5001 -n wukongim &
curl http://localhost:5001/version
# Check cluster health
curl http://localhost:5001/health
curl http://localhost:5001/cluster/nodes
```
## Advanced Upgrade Strategies
### Blue-Green Deployment
For critical production environments:
```bash theme={null}
# Deploy new version to different namespace
helm install wkim-green wukongim/wukongim \
-n wukongim-green \
--create-namespace \
--version
# Test new deployment
kubectl port-forward svc/wkim-green-wukongim 5001:5001 -n wukongim-green &
curl http://localhost:5001/health
# Switch traffic (update ingress/load balancer)
# Remove old deployment after verification
helm uninstall wkim -n wukongim
```
### Canary Deployment
For gradual rollout:
```bash theme={null}
# Scale down current deployment
kubectl scale deployment wkim-wukongim --replicas=2 -n wukongim
# Deploy canary version
helm install wkim-canary wukongim/wukongim \
-n wukongim-canary \
--create-namespace \
--version \
--set replicaCount=1
# Monitor canary performance
# Gradually increase canary replicas and decrease old replicas
```
## Rollback Procedures
### Quick Rollback
```bash theme={null}
# Rollback to previous version
helm rollback wkim -n wukongim
# Or rollback to specific revision
helm rollback wkim -n wukongim
# Check rollback status
kubectl rollout status deployment/wkim-wukongim -n wukongim
```
### Manual Rollback
```bash theme={null}
# List revision history
helm history wkim -n wukongim
# Rollback to specific version
helm rollback wkim -n wukongim
# Verify rollback
kubectl get pods -n wukongim
curl http://localhost:5001/version
```
### Data Rollback (if needed)
```bash theme={null}
# Stop current deployment
kubectl scale deployment wkim-wukongim --replicas=0 -n wukongim
# Restore data from backup
kubectl exec -n wukongim deployment/wkim-wukongim -- \
tar -xzf /tmp/backup-YYYYMMDD.tar.gz -C /
# Restart deployment
kubectl scale deployment wkim-wukongim --replicas=3 -n wukongim
```
## Upgrade Configuration
### Custom Values for Upgrade
```yaml theme={null}
# upgrade-values.yaml
replicaCount: 3
image:
tag: "v2.1.0"
pullPolicy: IfNotPresent
resources:
requests:
memory: "4Gi"
cpu: "2000m"
limits:
memory: "8Gi"
cpu: "4000m"
persistence:
enabled: true
size: 50Gi
# Rolling update strategy
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
# Health checks
livenessProbe:
httpGet:
path: /health
port: 5001
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 5001
initialDelaySeconds: 5
periodSeconds: 5
```
Use custom values during upgrade:
```bash theme={null}
helm upgrade wkim wukongim/wukongim \
-n wukongim \
--version \
-f upgrade-values.yaml
```
## Monitoring During Upgrade
### Health Checks
```bash theme={null}
# Monitor pod health
kubectl get pods -n wukongim -o wide
# Check service endpoints
kubectl get endpoints -n wukongim
# Test API availability
kubectl port-forward svc/wkim-wukongim 5001:5001 -n wukongim &
while true; do
curl -f http://localhost:5001/health && echo " - OK" || echo " - FAIL"
sleep 5
done
```
### Performance Monitoring
```bash theme={null}
# Monitor resource usage
kubectl top pods -n wukongim
# Check cluster metrics
kubectl port-forward svc/wkim-wukongim 5300:5300 -n wukongim &
curl http://localhost:5300/metrics
# View upgrade logs
kubectl logs -f deployment/wkim-wukongim -n wukongim
```
## Troubleshooting Upgrades
### Common Issues
**Upgrade stuck or failing**:
```bash theme={null}
# Check deployment status
kubectl describe deployment wkim-wukongim -n wukongim
# Check pod events
kubectl describe pods -n wukongim
# View detailed logs
kubectl logs deployment/wkim-wukongim -n wukongim
```
**Resource constraints**:
```bash theme={null}
# Check node resources
kubectl describe nodes
# Check resource requests
kubectl describe deployment wkim-wukongim -n wukongim
# Adjust resources if needed
helm upgrade wkim wukongim/wukongim \
-n wukongim \
--set resources.requests.memory=2Gi \
--set resources.requests.cpu=1000m
```
**Image pull issues**:
```bash theme={null}
# Check image pull status
kubectl describe pods -n wukongim | grep -A 5 "Events:"
# Verify image exists
kubectl run test-image --image=registry.cn-shanghai.aliyuncs.com/wukongim/wukongim:v2.1.0 --rm -it --restart=Never
```
## Automation Scripts
### Automated Upgrade Script
```bash theme={null}
#!/bin/bash
# upgrade-wukongim.sh
set -e
NAMESPACE="wukongim"
RELEASE_NAME="wkim"
NEW_VERSION="$1"
if [ -z "$NEW_VERSION" ]; then
echo "Usage: $0 "
exit 1
fi
echo "Starting WuKongIM upgrade to version $NEW_VERSION..."
# Backup current configuration
echo "Backing up current configuration..."
helm get values $RELEASE_NAME -n $NAMESPACE > backup-values-$(date +%Y%m%d).yaml
# Update Helm repository
echo "Updating Helm repository..."
helm repo update
# Perform upgrade
echo "Performing upgrade..."
helm upgrade $RELEASE_NAME wukongim/wukongim \
-n $NAMESPACE \
--version $NEW_VERSION \
--wait \
--timeout=10m
# Verify upgrade
echo "Verifying upgrade..."
kubectl rollout status deployment/wkim-wukongim -n $NAMESPACE
# Check health
echo "Checking health..."
kubectl port-forward svc/wkim-wukongim 5001:5001 -n $NAMESPACE &
PF_PID=$!
sleep 5
if curl -f http://localhost:5001/health > /dev/null 2>&1; then
echo "Upgrade successful! WuKongIM is healthy."
else
echo "Health check failed! Consider rollback."
kill $PF_PID
exit 1
fi
kill $PF_PID
echo "Upgrade completed successfully!"
```
Make script executable and use:
```bash theme={null}
chmod +x upgrade-wukongim.sh
./upgrade-wukongim.sh v2.1.0
```
## Best Practices
### Pre-Upgrade Checklist
* [ ] Review release notes for breaking changes
* [ ] Backup current configuration and data
* [ ] Test upgrade in staging environment
* [ ] Plan maintenance window
* [ ] Notify users of potential brief interruption
* [ ] Prepare rollback plan
### During Upgrade
* [ ] Monitor pod status and logs
* [ ] Watch resource usage
* [ ] Test API endpoints
* [ ] Verify cluster communication
* [ ] Check performance metrics
### Post-Upgrade
* [ ] Verify version upgrade successful
* [ ] Test all major features
* [ ] Monitor performance for 24 hours
* [ ] Update documentation
* [ ] Clean up old backups after verification period
## Next Steps
Scale your upgraded cluster
Learn about cluster deployment
Set up monitoring for upgraded system
Configure advanced settings
# Monitoring Setup
Source: https://wukong.mintlify.app/en/installation/linux/monitoring
Set up monitoring system for WuKongIM Linux deployment
# Monitoring
Setting up comprehensive monitoring for WuKongIM ensures optimal performance and early detection of issues.
## Prerequisites
Install [Prometheus](https://github.com/prometheus/prometheus) for metrics collection and monitoring.
### Install Prometheus
```bash Ubuntu/Debian theme={null}
# Download Prometheus
wget https://github.com/prometheus/prometheus/releases/download/v2.45.0/prometheus-2.45.0.linux-amd64.tar.gz
# Extract
tar xvfz prometheus-2.45.0.linux-amd64.tar.gz
cd prometheus-2.45.0.linux-amd64
# Create user and directories
sudo useradd --no-create-home --shell /bin/false prometheus
sudo mkdir /etc/prometheus
sudo mkdir /var/lib/prometheus
sudo chown prometheus:prometheus /etc/prometheus
sudo chown prometheus:prometheus /var/lib/prometheus
# Copy binaries
sudo cp prometheus /usr/local/bin/
sudo cp promtool /usr/local/bin/
sudo chown prometheus:prometheus /usr/local/bin/prometheus
sudo chown prometheus:prometheus /usr/local/bin/promtool
```
```bash CentOS/RHEL theme={null}
# Download Prometheus
wget https://github.com/prometheus/prometheus/releases/download/v2.45.0/prometheus-2.45.0.linux-amd64.tar.gz
# Extract
tar xvfz prometheus-2.45.0.linux-amd64.tar.gz
cd prometheus-2.45.0.linux-amd64
# Create user and directories
sudo useradd --no-create-home --shell /bin/false prometheus
sudo mkdir /etc/prometheus
sudo mkdir /var/lib/prometheus
sudo chown prometheus:prometheus /etc/prometheus
sudo chown prometheus:prometheus /var/lib/prometheus
# Copy binaries
sudo cp prometheus /usr/local/bin/
sudo cp promtool /usr/local/bin/
sudo chown prometheus:prometheus /usr/local/bin/prometheus
sudo chown prometheus:prometheus /usr/local/bin/promtool
```
## Configure Prometheus
Add WuKongIM monitoring targets under the `scrape_configs` section in your Prometheus configuration.
### Single Node Configuration
For single node deployment, create `/etc/prometheus/prometheus.yml`:
```yaml theme={null}
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'wukongim-trace-metrics'
static_configs:
- targets: ['xx.xx.xx.xx:5300']
labels:
id: "1001"
instance: "wukongim-node1"
```
### Multi-Node Configuration
For multi-node cluster deployment:
```yaml theme={null}
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'wukongim1-trace-metrics'
static_configs:
- targets: ['10.206.0.13:5300']
labels:
id: "1001"
instance: "wukongim-node1"
- job_name: 'wukongim2-trace-metrics'
static_configs:
- targets: ['10.206.0.14:5300']
labels:
id: "1002"
instance: "wukongim-node2"
- job_name: 'wukongim3-trace-metrics'
static_configs:
- targets: ['10.206.0.8:5300']
labels:
id: "1003"
instance: "wukongim-node3"
```
**Configuration Parameters**:
* `job_name`: Unique job name for each WuKongIM node
* `targets`: WuKongIM internal IP + port 5300
* `labels.id`: WuKongIM node ID
* `labels.instance`: Human-readable instance name
Replace `xx.xx.xx.xx` with the actual internal IP address of your WuKongIM nodes.
## Configure WuKongIM
Add Prometheus configuration to each node's `wk.yaml` file:
```yaml theme={null}
mode: "release"
# ... other configurations ...
trace:
prometheusApiUrl: "http://xx.xx.xx.xx:9090"
```
Replace `xx.xx.xx.xx` with the internal IP address of your Prometheus server.
### Complete WuKongIM Configuration Example
```yaml theme={null}
mode: "release"
rootDir: "./wukongim_data"
# Cluster configuration (for multi-node)
cluster:
nodeId: 1001
serverAddr: "10.206.0.13:11110"
apiUrl: "http://10.206.0.13:5001"
initNodes:
- "1001@10.206.0.13:11110"
- "1002@10.206.0.14:11110"
- "1003@10.206.0.8:11110"
# External configuration
external:
ip: "119.45.229.172"
tcpAddr: "119.45.229.172:15100"
wsAddr: "ws://119.45.229.172:15200"
# Monitoring configuration
trace:
prometheusApiUrl: "http://10.206.0.13:9090"
# Logging configuration
logger:
level: "info"
dir: "./logs"
```
## Start Services
### Start Prometheus
Create a systemd service file for Prometheus:
```bash theme={null}
sudo nano /etc/systemd/system/prometheus.service
```
Add the following content:
```ini theme={null}
[Unit]
Description=Prometheus
Wants=network-online.target
After=network-online.target
[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
--config.file /etc/prometheus/prometheus.yml \
--storage.tsdb.path /var/lib/prometheus/ \
--web.console.templates=/etc/prometheus/consoles \
--web.console.libraries=/etc/prometheus/console_libraries \
--web.listen-address=0.0.0.0:9090 \
--web.external-url=
[Install]
WantedBy=multi-user.target
```
Enable and start Prometheus:
```bash theme={null}
sudo systemctl daemon-reload
sudo systemctl enable prometheus
sudo systemctl start prometheus
sudo systemctl status prometheus
```
### Restart WuKongIM
After updating the configuration, restart WuKongIM on all nodes:
```bash theme={null}
./wukongim stop
./wukongim --config wk.yaml -d
```
## Verification
### Check Prometheus Targets
1. Access Prometheus web interface: `http://prometheus-server-ip:9090`
2. Go to **Status** → **Targets**
3. Verify all WuKongIM targets are **UP**
### Check Metrics
Query WuKongIM metrics in Prometheus:
```promql theme={null}
# Check if WuKongIM metrics are being collected
wukongim_connections_total
# Check message throughput
rate(wukongim_messages_total[5m])
# Check memory usage
wukongim_memory_usage_bytes
# Check CPU usage
wukongim_cpu_usage_percent
```
## Key Metrics to Monitor
### System Metrics
| Metric | Description |
| ----------------------------- | ---------------------------------- |
| `wukongim_connections_total` | Total number of active connections |
| `wukongim_messages_total` | Total number of messages processed |
| `wukongim_memory_usage_bytes` | Memory usage in bytes |
| `wukongim_cpu_usage_percent` | CPU usage percentage |
| `wukongim_disk_usage_bytes` | Disk usage in bytes |
### Cluster Metrics (Multi-node)
| Metric | Description |
| -------------------------------------------- | ----------------------------- |
| `wukongim_cluster_nodes_total` | Total number of cluster nodes |
| `wukongim_cluster_leader_changes_total` | Number of leader changes |
| `wukongim_cluster_proposals_failed_total` | Failed proposals count |
| `wukongim_cluster_proposals_committed_total` | Committed proposals count |
### Performance Metrics
| Metric | Description |
| --------------------------------------- | --------------------------- |
| `wukongim_message_latency_seconds` | Message processing latency |
| `wukongim_api_request_duration_seconds` | API request duration |
| `wukongim_websocket_connections` | WebSocket connections count |
| `wukongim_tcp_connections` | TCP connections count |
## Alerting Rules
Create alerting rules in `/etc/prometheus/alert_rules.yml`:
```yaml theme={null}
groups:
- name: wukongim
rules:
- alert: WuKongIMDown
expr: up{job=~"wukongim.*"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "WuKongIM instance is down"
description: "WuKongIM instance {{ $labels.instance }} has been down for more than 1 minute."
- alert: HighMemoryUsage
expr: wukongim_memory_usage_bytes / (1024*1024*1024) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "High memory usage on WuKongIM"
description: "WuKongIM instance {{ $labels.instance }} is using more than 2GB of memory."
- alert: HighCPUUsage
expr: wukongim_cpu_usage_percent > 80
for: 5m
labels:
severity: warning
annotations:
summary: "High CPU usage on WuKongIM"
description: "WuKongIM instance {{ $labels.instance }} CPU usage is above 80%."
- alert: TooManyConnections
expr: wukongim_connections_total > 10000
for: 2m
labels:
severity: warning
annotations:
summary: "Too many connections on WuKongIM"
description: "WuKongIM instance {{ $labels.instance }} has more than 10,000 active connections."
```
Update Prometheus configuration to include alert rules:
```yaml theme={null}
# Add to prometheus.yml
rule_files:
- "alert_rules.yml"
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
```
## Grafana Dashboard
### Install Grafana
```bash theme={null}
# Add Grafana repository
sudo apt-get install -y software-properties-common
sudo add-apt-repository "deb https://packages.grafana.com/oss/deb stable main"
wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
# Install Grafana
sudo apt-get update
sudo apt-get install grafana
# Start Grafana
sudo systemctl enable grafana-server
sudo systemctl start grafana-server
```
### Configure Data Source
1. Access Grafana: `http://grafana-server-ip:3000` (admin/admin)
2. Add Prometheus data source: `http://prometheus-server-ip:9090`
3. Import WuKongIM dashboard or create custom dashboards
### Sample Dashboard Queries
**Connection Count**:
```promql theme={null}
sum(wukongim_connections_total)
```
**Message Rate**:
```promql theme={null}
sum(rate(wukongim_messages_total[5m]))
```
**Memory Usage**:
```promql theme={null}
wukongim_memory_usage_bytes / (1024*1024*1024)
```
**CPU Usage**:
```promql theme={null}
wukongim_cpu_usage_percent
```
## Troubleshooting
### Prometheus Not Collecting Metrics
```bash theme={null}
# Check if WuKongIM metrics endpoint is accessible
curl http://wukongim-node-ip:5300/metrics
# Check Prometheus logs
sudo journalctl -u prometheus -f
# Verify Prometheus configuration
promtool check config /etc/prometheus/prometheus.yml
```
### WuKongIM Not Sending Metrics
```bash theme={null}
# Check WuKongIM logs
tail -f ./wukongim_data/logs/wukongim.log
# Verify trace configuration in wk.yaml
grep -A 5 "trace:" wk.yaml
# Test connectivity to Prometheus
curl http://prometheus-server-ip:9090/api/v1/targets
```
## Next Steps
Optimize WuKongIM performance based on monitoring data
Set up automated backup and recovery
Test system performance under load
Learn about cluster scaling and management
# Multi-Node Deployment
Source: https://wukong.mintlify.app/en/installation/linux/multi-node
Deploy WuKongIM multi-node cluster on Linux systems
# Multi-Node Mode
## Description
**Applicable scenarios**: Applications with high data security requirements, large applications.
**Advantages**: High availability, strong disaster recovery, supports online scaling, real-time automatic backup between multiple replicas, load balancing, etc.
**Disadvantages**: Slightly complex deployment, requires multiple machines.
WuKongIM cluster follows the `2n+1` principle, where n represents the number of allowed failures. For example, to allow 1 machine to fail without affecting normal service operation requires 2×1+1=3 machines in the cluster; to allow 2 machines to fail without affecting normal service operation requires 2×2+1=5 machines in the cluster, and so on.
## Environment Requirements
* **Number of machines**: 3 or more
* **Operating System**: Linux (Ubuntu recommended) (Recommended configuration: 2 cores 4GB or 4 cores 8GB)
* **Load Balancer**: nginx (recommended version 1.27.0 or above)
Assume three servers with the following information:
| Name | Internal IP | External IP |
| ----------- | ----------- | -------------- |
| node1(1001) | 10.206.0.13 | 119.45.229.172 |
| node2(1002) | 10.206.0.14 | 129.211.213.76 |
| node3(1003) | 10.206.0.8 | 1.13.191.138 |
## Preparation
You need to deploy `nginx` (recommended version 1.27.0) on the `node1` node for load balancing.
## Installation
### 1. Download Executable File
**Scope**: All nodes
```bash AMD64 theme={null}
curl -L -o wukongim https://github.com/WuKongIM/WuKongIM/releases/download/latest/wukongim-linux-amd64
```
```bash ARM64 theme={null}
curl -L -o wukongim https://github.com/WuKongIM/WuKongIM/releases/download/latest/wukongim-linux-arm64
```
### 2. Modify Executable File Permissions
**Scope**: All nodes
```bash theme={null}
chmod +x wukongim
```
## Configuration
### Configure WuKongIM
**On node1**, create configuration file `wk.yaml` with the following content:
```yaml theme={null}
mode: "release"
external: # Public network configuration
ip: "119.45.229.172" # Node external IP, IP address that clients can access
tcpAddr: "119.45.229.172:15100" # Long connection address for app access, note this is the load balancer server's IP and port, not local
wsAddr: "ws://119.45.229.172:15200" # Long connection address for web access, note this is the load balancer server's IP and port, not local
cluster:
nodeId: 1001 # Node ID
apiUrl: "http://10.206.0.13:5001" # Current node's internal API address
serverAddr: "10.206.0.13:11110" # Current node's internal distributed communication address
initNodes:
- "1001@10.206.0.13:11110"
- "1002@10.206.0.14:11110"
- "1003@10.206.0.8:11110"
```
**On node2**, create configuration file `wk.yaml` with the following content:
```yaml theme={null}
mode: "release"
external: # Public network configuration
ip: "129.211.213.76" # Node external IP, IP address that clients can access
tcpAddr: "119.45.229.172:15100" # Long connection address for app access, note this is the load balancer server's IP and port, not local
wsAddr: "ws://119.45.229.172:15200" # Long connection address for web access, note this is the load balancer server's IP and port, not local
cluster:
nodeId: 1002 # Node ID
apiUrl: "http://10.206.0.14:5001" # Current node's internal API address
serverAddr: "10.206.0.14:11110" # Current node's internal distributed communication address
initNodes:
- "1001@10.206.0.13:11110"
- "1002@10.206.0.14:11110"
- "1003@10.206.0.8:11110"
```
**On node3**, create configuration file `wk.yaml` with the following content:
```yaml theme={null}
mode: "release"
external: # Public network configuration
ip: "1.13.191.138" # Node external IP, IP address that clients can access
tcpAddr: "119.45.229.172:15100" # Long connection address for app access, note this is the load balancer server's IP and port, not local
wsAddr: "ws://119.45.229.172:15200" # Long connection address for web access, note this is the load balancer server's IP and port, not local
cluster:
nodeId: 1003 # Node ID
apiUrl: "http://10.206.0.8:5001" # Current node's internal API address
serverAddr: "10.206.0.8:11110" # Current node's internal distributed communication address
initNodes:
- "1001@10.206.0.13:11110"
- "1002@10.206.0.14:11110"
- "1003@10.206.0.8:11110"
```
### Configure nginx
Create nginx configuration file with the following content:
```nginx theme={null}
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
# API load balancing
upstream wukongimapi {
server 10.206.0.13:5001;
server 10.206.0.14:5001;
server 10.206.0.8:5001;
}
# Demo load balancing
upstream wukongimdemo {
server 10.206.0.13:5172;
server 10.206.0.14:5172;
server 10.206.0.8:5172;
}
# Manager load balancing
upstream wukongimanager {
server 10.206.0.13:5300;
server 10.206.0.14:5300;
server 10.206.0.8:5300;
}
# WebSocket load balancing
upstream wukongimws {
server 10.206.0.13:5200;
server 10.206.0.14:5200;
server 10.206.0.8:5200;
}
# HTTP API forwarding
server {
listen 15001;
location / {
proxy_pass http://wukongimapi;
proxy_connect_timeout 20s;
proxy_read_timeout 60s;
}
}
# Demo
server {
listen 15172;
location / {
proxy_pass http://wukongimdemo;
proxy_connect_timeout 20s;
proxy_read_timeout 60s;
}
location /login {
rewrite ^ /chatdemo?apiurl=http://119.45.229.172:15001;
proxy_pass http://wukongimdemo;
proxy_connect_timeout 20s;
proxy_read_timeout 60s;
}
}
# Manager
server {
listen 15300;
location / {
proxy_pass http://wukongimanager;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
}
}
# WebSocket
server {
listen 15200;
location / {
proxy_pass http://wukongimws;
proxy_redirect off;
proxy_http_version 1.1;
# nginx receives data from upstream server timeout, default 120s, connection closes if no byte received in consecutive 120s
proxy_read_timeout 120s;
# nginx sends data to upstream server timeout, default 120s, connection closes if no byte sent in consecutive 120s
proxy_send_timeout 120s;
# nginx connection timeout with upstream server
proxy_connect_timeout 4s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}
# TCP
stream {
# TCP load balancing
upstream wukongimtcp {
server 10.206.0.13:5100;
server 10.206.0.14:5100;
server 10.206.0.8:5100;
}
server {
listen 15100;
proxy_connect_timeout 4s;
proxy_timeout 120s;
proxy_pass wukongimtcp;
}
}
```
Remember to restart nginx for the configuration to take effect:
```bash theme={null}
sudo systemctl restart nginx
# or
sudo nginx -s reload
```
## Start or Stop
Start WuKongIM on all nodes:
```bash theme={null}
./wukongim --config wk.yaml -d
# Stop
# ./wukongim stop
```
## Port Configuration
### External Network Ports
| Port | Description |
| ----- | ---------------------------------------------------------------- |
| 15001 | HTTP API port (only open to internal LAN) |
| 15100 | TCP port, app clients need access |
| 15200 | WebSocket port, web IM clients need access |
| 15300 | Management system port |
| 15172 | Demo port, for demonstrating WuKongIM communication capabilities |
### Internal Network Ports (nodes need to access each other)
| Port | Description |
| ---- | ----------------------------------------------------------------------- |
| 5001 | HTTP API port (only open to internal LAN) |
| 5100 | TCP port, only needs internal network access in distributed setup |
| 5200 | WebSocket port, only needs internal network access in distributed setup |
| 5300 | Management system port |
Make sure to open the required ports in your firewall:
```bash theme={null}
# External ports (on load balancer node)
sudo ufw allow 15001
sudo ufw allow 15100
sudo ufw allow 15200
sudo ufw allow 15300
sudo ufw allow 15172
# Internal ports (on all nodes)
sudo ufw allow 5001
sudo ufw allow 5100
sudo ufw allow 5200
sudo ufw allow 5300
sudo ufw allow 11110 # Cluster communication
```
## Verification
1. Access `http://119.45.229.172:15172/login`, enter any username and password, after login you can chat, indicating successful deployment.
2. Access `http://119.45.229.172:15300/web` to enter the management system. The default built-in guest has read-only permissions. If you need operation permissions, please see [Authorization Configuration](/en/server/configuration#administrator-authentication-configuration).
## Next Steps
Configure authentication and performance optimization
Set up monitoring and alerting
Learn about single node deployment
Start using WuKongIM API
# Cluster Scaling
Source: https://wukong.mintlify.app/en/installation/linux/scaling
WuKongIM Linux cluster scaling operations guide
WuKongIM supports dynamic scaling in Linux environments, allowing flexible adjustment of cluster size based on business requirements.
# Single Node Mode Scaling
## Description
The previously deployed [single node mode](single-node.md) now needs to be scaled to multiple servers. Here we use two servers as an example to explain how to scale.
Assume there are two servers with the following information:
| Name | Internal IP | External IP | Description |
| ----------- | ------------ | ------------- | --------------------------------------------- |
| node1(1001) | 192.168.1.10 | 221.123.68.10 | Master node (originally deployed single node) |
| node2(1002) | 192.168.1.20 | 221.123.68.20 | New node to be added |
node1 is the originally deployed single node, now we want to scale to two servers, node2 is the newly added node.
The following file contents are set with assumed server IPs, just replace the corresponding IPs with your own.
## Preparation
You need to deploy `nginx` (recommended version 1.27.0) on the node1 node for load balancing.
## Deploy WuKongIM
Deploy `WuKongIM` on the node2 node. The process is the same as single node mode, so it won't be repeated here. For details:
Refer to the WuKongIM deployment tutorial [Single Node Mode](./single-node).
## Configure WuKongIM
Modify the configuration file `wk.yaml` on node2, complete content as follows:
```yaml theme={null}
mode: "release"
external: # Public network configuration
ip: "221.123.68.20" # Node external IP, IP address that clients can access
tcpAddr: "221.123.68.10:15100" # Long connection address for app access, note this is the load balancer server's IP and port, not local
wsAddr: "ws://221.123.68.10:15200" # Long connection address for web access, note this is the load balancer server's IP and port, not local
cluster:
nodeId: 1002 # Node ID
apiUrl: "http://192.168.1.20:5001" # Current node's internal API address
serverAddr: "192.168.1.20:11110" # Current node's internal distributed communication address
seed: "1001@192.168.1.10:11110" # Seed node, original node's address
```
Modify the configuration file `wk.yaml` on node1, complete content as follows:
```yaml theme={null}
mode: "release"
external: # Public network configuration
ip: "221.123.68.10" # Node external IP, IP address that clients can access
tcpAddr: "221.123.68.10:15100" # Long connection address for app access, note this is the load balancer server's IP and port
wsAddr: "ws://221.123.68.10:15200" # Long connection address for web access, note this is the load balancer server's IP and port
cluster:
nodeId: 1001 # Node ID
apiUrl: "http://192.168.1.10:5001" # Current node's internal API address
serverAddr: "192.168.1.10:11110" # Current node's internal distributed communication address
```
## Configure nginx
```nginx theme={null}
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
# API load balancing
upstream wukongimapi {
server 192.168.1.10:5001;
server 192.168.1.20:5001;
}
# Demo load balancing
upstream wukongimdemo {
server 192.168.1.10:5172;
server 192.168.1.20:5172;
}
# Manager load balancing
upstream wukongimanager {
server 192.168.1.10:5300;
server 192.168.1.20:5300;
}
# WebSocket load balancing
upstream wukongimws {
server 192.168.1.10:5200;
server 192.168.1.20:5200;
}
# HTTP API forwarding
server {
listen 5001;
location / {
proxy_pass http://wukongimapi;
proxy_connect_timeout 20s;
proxy_read_timeout 60s;
}
}
# Demo
server {
listen 5172;
location / {
proxy_pass http://wukongimdemo;
proxy_connect_timeout 20s;
proxy_read_timeout 60s;
}
location /login {
rewrite ^ /chatdemo?apiurl=http://221.123.68.10:15001;
proxy_pass http://wukongimdemo;
proxy_connect_timeout 20s;
proxy_read_timeout 60s;
}
}
# Manager
server {
listen 5300;
location / {
proxy_pass http://wukongimanager;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
}
}
# WebSocket
server {
listen 5200;
location / {
proxy_pass http://wukongimws;
proxy_redirect off;
proxy_http_version 1.1;
# nginx receives data from upstream server timeout, default 120s, connection closes if no byte received in consecutive 120s
proxy_read_timeout 120s;
# nginx sends data to upstream server timeout, default 120s, connection closes if no byte sent in consecutive 120s
proxy_send_timeout 120s;
# nginx connection timeout with upstream server
proxy_connect_timeout 4s;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}
# TCP
stream {
# TCP load balancing
upstream wukongimtcp {
server 192.168.1.10:5100;
server 192.168.1.20:5100;
}
server {
listen 5100;
proxy_connect_timeout 4s;
proxy_timeout 120s;
proxy_pass wukongimtcp;
}
}
```
## Restart
Finally, restart nginx on node1 and WuKongIM on both node1 and node2.
```bash theme={null}
# Restart nginx on node1
sudo systemctl restart nginx
# Restart WuKongIM on node1
./wukongim stop
./wukongim --config wk.yaml -d
# Start WuKongIM on node2
./wukongim --config wk.yaml -d
```
## Verification
Log into the management system, in node management you can see if the newly added node's status is "Joined". If so, scaling is successful.
# Multi-Node Scaling Mode
## Description
Nodes originally deployed using [multi-node mode](./multi-node) can expand cluster size by adding nodes. This document describes how to expand cluster size by adding nodes.
Assume the newly added node information is as follows:
| Name | Internal IP | External IP |
| ----------- | ------------ | ------------- |
| node4(1004) | 192.168.12.4 | 222.222.222.4 |
## Install WuKongIM
On node4:
### 1. Download Executable File
```bash AMD64 theme={null}
curl -L -o wukongim https://github.com/WuKongIM/WuKongIM/releases/download/latest/wukongim-linux-amd64
```
```bash ARM64 theme={null}
curl -L -o wukongim https://github.com/WuKongIM/WuKongIM/releases/download/latest/wukongim-linux-arm64
```
### 2. Modify Executable File Permissions
```bash theme={null}
chmod +x wukongim
```
## Configuration
### Configure WuKongIM
Create configuration file `wk.yaml` on node4 with the following content:
```yaml theme={null}
mode: "release"
external: # Public network configuration
ip: "222.222.222.4" # Node external IP, IP address that clients can access
tcpAddr: "222.222.222.1:15100" # Long connection address for app access, note this is the load balancer server's IP and port, not local
wsAddr: "ws://222.222.222.1:15200" # Long connection address for web access, note this is the load balancer server's IP and port, not local
cluster:
nodeId: 1004 # Node ID
apiUrl: "http://192.168.12.4:5001" # Current node's internal API address
serverAddr: "192.168.12.4:11110" # Current node's internal distributed communication address
seed: "1001@192.168.12.1:11100" # Seed node, any node in original cluster can be seed node, here use node1 as seed
```
### Configure nginx
Configure nginx on the original node1 node, add load balancing configuration for node4.
```nginx theme={null}
upstream wukongimapi {
# ... existing servers ...
server 192.168.12.4:5001;
}
upstream wukongimdemo {
# ... existing servers ...
server 192.168.12.4:5172;
}
upstream wukongimanager {
# ... existing servers ...
server 192.168.12.4:5300;
}
upstream wukongimws {
# ... existing servers ...
server 192.168.12.4:5200;
}
stream {
# ... existing configuration ...
upstream wukongimtcp {
# ... existing servers ...
server 192.168.12.4:5100;
}
# ... rest of configuration ...
}
```
Remember to restart nginx for the configuration to take effect:
```bash theme={null}
sudo systemctl restart nginx
```
## Start WuKongIM
```bash theme={null}
./wukongim --config wk.yaml -d
```
## Verification
Log into the management system, in node management you can see if the newly added node's status is "Joined". If so, scaling is successful.
## Next Steps
Learn about version upgrades
Set up monitoring for scaled cluster
Learn about multi-node deployment
Configure advanced settings
# Single Node Deployment
Source: https://wukong.mintlify.app/en/installation/linux/single-node
Deploy WuKongIM single node instance on Linux system
# Single Node Mode
## Description
**Applicable scenarios**: Small applications, applications with low data security requirements, can scale to cluster later when volume increases.
**Advantages**: Simple deployment, good performance, supports online scaling.
**Disadvantages**: Cannot provide disaster recovery, requires manual backup.
## Environment Requirements
* Linux system (Ubuntu recommended) (Recommended configuration: 2 cores 4GB or 4 cores 8GB)
## Installation
### 1. Download Executable File
```bash AMD64 theme={null}
curl -L -o wukongim https://github.com/WuKongIM/WuKongIM/releases/download/latest/wukongim-linux-amd64
```
```bash ARM64 theme={null}
curl -L -o wukongim https://github.com/WuKongIM/WuKongIM/releases/download/latest/wukongim-linux-arm64
```
### 2. Modify Executable File Permissions
```bash theme={null}
chmod +x wukongim
```
### 3. Create Configuration File
Create configuration file `wk.yaml` with the following content:
```yaml theme={null}
mode: "release"
rootDir: "./wukongim_data"
cluster:
nodeId: 1001 # Node ID
serverAddr: "xx.xx.xx.xx:11110" # Node internal communication request address
external: # Public network configuration
ip: "xx.xx.xx.xx" # Node external IP, IP address that clients can access
```
* Replace `ip` with your server's external IP address
* Replace `xx.xx.xx.xx` in `serverAddr` with your server's internal IP address
### 4. Start or Stop
```bash theme={null}
# Start (-d means run in background, otherwise run in foreground)
./wukongim --config wk.yaml -d
# Stop
./wukongim stop
```
## Port Configuration
| Port | Description |
| ---- | --------------------------------------------------------------------- |
| 5001 | HTTP API port (only open to internal LAN) |
| 5100 | TCP port, app clients need to access |
| 5200 | WebSocket port, web IM clients need to access |
| 5300 | Management system port |
| 5172 | Demo port, used for demonstrating WuKongIM communication capabilities |
Make sure to open the required ports in your firewall:
```bash theme={null}
# Ubuntu/Debian
sudo ufw allow 5100
sudo ufw allow 5200
sudo ufw allow 5300
sudo ufw allow 5172
# CentOS/RHEL
sudo firewall-cmd --permanent --add-port=5100/tcp
sudo firewall-cmd --permanent --add-port=5200/tcp
sudo firewall-cmd --permanent --add-port=5300/tcp
sudo firewall-cmd --permanent --add-port=5172/tcp
sudo firewall-cmd --reload
```
## Verification
Access `http://server_ip:5300` to enter the management system. If you can access it normally, the deployment is successful.
### Additional Verification Steps
1. **Check service status**:
```bash theme={null}
# Check if WuKongIM is running
ps aux | grep wukongim
# Check port listening
netstat -tlnp | grep -E ':(5001|5100|5200|5300|5172)'
```
2. **Test API endpoint**:
```bash theme={null}
# Check health status
curl http://localhost:5001/health
# Check version information
curl http://localhost:5001/version
```
3. **Access demo**:
Visit `http://server_ip:5172` to access the demo interface and test messaging functionality.
## Service Management
### Using systemd (Recommended)
Create a systemd service file for easier management:
```bash theme={null}
sudo nano /etc/systemd/system/wukongim.service
```
Add the following content:
```ini theme={null}
[Unit]
Description=WuKongIM Server
After=network.target
[Service]
Type=forking
User=root
WorkingDirectory=/path/to/wukongim
ExecStart=/path/to/wukongim --config wk.yaml -d
ExecStop=/path/to/wukongim stop
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
Enable and start the service:
```bash theme={null}
# Reload systemd
sudo systemctl daemon-reload
# Enable auto-start
sudo systemctl enable wukongim
# Start service
sudo systemctl start wukongim
# Check status
sudo systemctl status wukongim
# View logs
sudo journalctl -u wukongim -f
```
## Configuration Optimization
### Performance Tuning
For production environments, consider the following optimizations:
```yaml theme={null}
mode: "release"
rootDir: "./wukongim_data"
cluster:
nodeId: 1001
serverAddr: "xx.xx.xx.xx:11110"
external:
ip: "xx.xx.xx.xx"
# Performance optimization
logger:
level: "info" # Reduce log level in production
dir: "./logs"
datasource:
addr: "./wukongim_data"
shardNum: 32 # Increase shard number for better performance
```
### Security Configuration
```yaml theme={null}
# Add security settings
security:
tokenExpire: 3600 # Token expiration time
maxConnections: 10000 # Maximum connections
rateLimit: 1000 # Rate limiting
```
## Troubleshooting
### Common Issues
1. **Port already in use**:
```bash theme={null}
# Check which process is using the port
sudo lsof -i :5100
# Kill the process if needed
sudo kill -9
```
2. **Permission denied**:
```bash theme={null}
# Make sure the executable has proper permissions
chmod +x wukongim
# Check if running as appropriate user
whoami
```
3. **Configuration file not found**:
```bash theme={null}
# Make sure config file exists and is readable
ls -la wk.yaml
cat wk.yaml
```
### Log Analysis
```bash theme={null}
# View WuKongIM logs
tail -f ./wukongim_data/logs/wukongim.log
# Check system logs
sudo journalctl -u wukongim -n 50
```
## Next Steps
Scale to cluster deployment for high availability
Configure authentication and performance optimization
Set up monitoring and alerting
Start using WuKongIM API
# Version Upgrade
Source: https://wukong.mintlify.app/en/installation/linux/upgrade
WuKongIM Linux version upgrade guide
# Upgrade
WuKongIM supports smooth upgrades on Linux systems, ensuring service continuity and data security.
## Description
WuKongIM's version numbering follows the `major.minor.patch` format, for example `1.0.0`. When the patch number increases, it indicates bug fixes or minor feature updates; when the minor version increases, it indicates new features; when the major version increases, it indicates incompatible API changes.
Therefore, as long as you don't upgrade major versions, the upgrade process is smooth.
**Version Compatibility**:
* **Patch updates** (e.g., 2.0.1 → 2.0.2): Always safe, includes bug fixes and minor improvements
* **Minor updates** (e.g., 2.0.x → 2.1.x): Generally safe, includes new features with backward compatibility
* **Major updates** (e.g., 2.x.x → 3.x.x): May include breaking changes, requires careful planning
## Upgrade Steps
### Single Node Upgrade
#### 1. Check Current Version
First, check your current WuKongIM version:
```bash theme={null}
# Check running version
./wukongim version
# Or check via API
curl http://localhost:5001/version
```
#### 2. Backup Data (Recommended)
Before upgrading, it's recommended to backup your data:
```bash theme={null}
# Stop WuKongIM service
./wukongim stop
# Create backup
sudo cp -r ./wukongim_data ./wukongim_data_backup_$(date +%Y%m%d)
# Or create compressed backup
sudo tar -czf wukongim_backup_$(date +%Y%m%d).tar.gz ./wukongim_data
```
#### 3. Download New Version
```bash AMD64 theme={null}
curl -L -o wukongim_new https://github.com/WuKongIM/WuKongIM/releases/download/latest/wukongim-linux-amd64
```
```bash ARM64 theme={null}
curl -L -o wukongim_new https://github.com/WuKongIM/WuKongIM/releases/download/latest/wukongim-linux-arm64
```
#### 4. Replace Executable File
```bash theme={null}
# Make new executable file executable
chmod +x wukongim_new
# Backup old executable
mv wukongim wukongim_old
# Replace with new version
mv wukongim_new wukongim
```
#### 5. Start Service
```bash theme={null}
# Start with existing configuration
./wukongim --config wk.yaml -d
```
#### 6. Verify Upgrade
```bash theme={null}
# Check service status
ps aux | grep wukongim
# Verify version
./wukongim version
# Check health status
curl http://localhost:5001/health
# Test API functionality
curl http://localhost:5001/version
```
### Multi-Node Cluster Upgrade
For cluster deployments, perform rolling upgrades to maintain service availability:
#### 1. Upgrade One Node at a Time
```bash theme={null}
# On node1
./wukongim stop
# Replace executable file (same steps as single node)
./wukongim --config wk.yaml -d
# Wait for node1 to be healthy, then proceed to node2
# Repeat for each node
```
#### 2. Verify Cluster Health
```bash theme={null}
# Check cluster status
curl http://load-balancer-ip:15001/cluster/nodes
# Verify all nodes are running the new version
curl http://node1-ip:5001/version
curl http://node2-ip:5001/version
curl http://node3-ip:5001/version
```
## Advanced Upgrade Strategies
### Blue-Green Deployment
For critical production environments:
1. **Prepare Green Environment**: Set up new servers with the new version
2. **Data Sync**: Ensure data is synchronized between blue and green environments
3. **Switch Traffic**: Update load balancer to point to green environment
4. **Verify**: Confirm everything works correctly
5. **Cleanup**: Remove blue environment after successful verification
### Rolling Upgrade with Load Balancer
```bash theme={null}
# Remove node from load balancer
# Update nginx configuration to exclude the node
sudo nano /etc/nginx/nginx.conf
sudo systemctl reload nginx
# Upgrade the node
./wukongim stop
# Replace executable
./wukongim --config wk.yaml -d
# Add node back to load balancer
# Update nginx configuration to include the node
sudo systemctl reload nginx
# Repeat for other nodes
```
## Rollback Procedures
If issues occur during upgrade, you can rollback:
### 1. Quick Rollback
```bash theme={null}
# Stop current version
./wukongim stop
# Restore old executable
mv wukongim wukongim_failed
mv wukongim_old wukongim
# Restart with old version
./wukongim --config wk.yaml -d
```
### 2. Data Rollback (if needed)
```bash theme={null}
# Stop service
./wukongim stop
# Restore backup
sudo rm -rf ./wukongim_data
sudo cp -r ./wukongim_data_backup_YYYYMMDD ./wukongim_data
# Or restore from compressed backup
sudo tar -xzf wukongim_backup_YYYYMMDD.tar.gz
# Restart service
./wukongim --config wk.yaml -d
```
## Systemd Service Upgrade
If using systemd service:
```bash theme={null}
# Stop service
sudo systemctl stop wukongim
# Replace executable file
sudo cp wukongim_new /path/to/wukongim
# Start service
sudo systemctl start wukongim
# Check status
sudo systemctl status wukongim
# View logs
sudo journalctl -u wukongim -f
```
## Upgrade Checklist
### Pre-Upgrade
* [ ] Check current version and target version compatibility
* [ ] Review release notes for breaking changes
* [ ] Create data backup
* [ ] Plan maintenance window
* [ ] Notify users of potential downtime
* [ ] Prepare rollback plan
### During Upgrade
* [ ] Monitor system resources
* [ ] Watch application logs
* [ ] Verify service health endpoints
* [ ] Test critical functionality
* [ ] Monitor cluster status (for multi-node)
### Post-Upgrade
* [ ] Verify version upgrade successful
* [ ] Test all major features
* [ ] Monitor performance metrics
* [ ] Check data integrity
* [ ] Update documentation
* [ ] Clean up old backups (after verification period)
## Troubleshooting
### Common Issues
**Service fails to start after upgrade**:
```bash theme={null}
# Check logs
tail -f ./wukongim_data/logs/wukongim.log
# Check if executable has proper permissions
ls -la wukongim
# Verify configuration file
cat wk.yaml
```
**Configuration compatibility issues**:
```bash theme={null}
# Check configuration syntax
./wukongim --config wk.yaml --check-config
# Compare with default configuration
./wukongim --help
```
**Data migration issues**:
```bash theme={null}
# Check data directory permissions
ls -la ./wukongim_data
# Verify data integrity
./wukongim --config wk.yaml --verify-data
```
### Recovery Steps
1. **Stop services**: `./wukongim stop`
2. **Restore backup**: Restore from backup created before upgrade
3. **Revert executable**: Use previous version executable
4. **Restart**: `./wukongim --config wk.yaml -d`
5. **Verify**: Confirm system is working correctly
## Automation Scripts
### Automated Upgrade Script
```bash theme={null}
#!/bin/bash
# upgrade-wukongim.sh - Automated upgrade script
set -e
NEW_VERSION="$1"
BACKUP_DIR="./backups"
CONFIG_FILE="wk.yaml"
if [ -z "$NEW_VERSION" ]; then
echo "Usage: $0 "
exit 1
fi
echo "Starting WuKongIM upgrade to version $NEW_VERSION..."
# Create backup
echo "Creating backup..."
mkdir -p $BACKUP_DIR
sudo cp -r ./wukongim_data $BACKUP_DIR/wukongim_data_$(date +%Y%m%d_%H%M%S)
cp wukongim $BACKUP_DIR/wukongim_$(date +%Y%m%d_%H%M%S)
# Stop service
echo "Stopping WuKongIM..."
./wukongim stop
# Download new version
echo "Downloading new version..."
curl -L -o wukongim_new https://github.com/WuKongIM/WuKongIM/releases/download/$NEW_VERSION/wukongim-linux-amd64
# Replace executable
echo "Replacing executable..."
chmod +x wukongim_new
mv wukongim wukongim_old
mv wukongim_new wukongim
# Start service
echo "Starting WuKongIM..."
./wukongim --config $CONFIG_FILE -d
# Wait for startup
sleep 10
# Verify upgrade
echo "Verifying upgrade..."
if curl -f http://localhost:5001/health > /dev/null 2>&1; then
echo "Upgrade successful! WuKongIM is healthy."
echo "New version: $(./wukongim version)"
else
echo "Health check failed! Consider rollback."
exit 1
fi
echo "Upgrade completed successfully!"
```
Make script executable and use:
```bash theme={null}
chmod +x upgrade-wukongim.sh
./upgrade-wukongim.sh v2.1.0
```
## Best Practices
### Planning
* **Test in staging**: Always test upgrades in a staging environment first
* **Read release notes**: Understand what changes are included
* **Schedule maintenance**: Plan upgrades during low-traffic periods
* **Communicate**: Inform stakeholders about planned maintenance
### Execution
* **Monitor closely**: Watch logs and metrics during upgrade
* **Upgrade gradually**: For clusters, upgrade one node at a time
* **Verify thoroughly**: Test all critical functionality after upgrade
* **Keep backups**: Maintain backups until upgrade is fully verified
### Monitoring
```bash theme={null}
# Monitor system resources during upgrade
top
htop
iostat 1
# Watch WuKongIM logs
tail -f ./wukongim_data/logs/wukongim.log
# Monitor API health
while true; do
curl -f http://localhost:5001/health && echo " - OK" || echo " - FAIL"
sleep 5
done
```
## Next Steps
Set up monitoring for upgraded system
Optimize performance after upgrade
Learn about cluster scaling
Backup strategies and best practices
# Installation Overview
Source: https://wukong.mintlify.app/en/installation/overview
Choose the right deployment method for your WuKongIM setup
WuKongIM offers flexible deployment options to suit different environments and requirements. Choose the method that best fits your infrastructure and scaling needs.
## Deployment Options
Quick setup for development and small production deployments
Cloud-native deployment with automatic scaling and management
Direct installation on Linux servers for maximum control
## Single Node vs Cluster Deployment
### Single Node Deployment
**Advantages:**
* Simple deployment and management
* Good performance for moderate loads
* Easy to upgrade to cluster later
* Lower resource requirements
**Disadvantages:**
* No automatic failover
* Manual backup required
* Single point of failure
**Best for:**
* Development environments
* Small to medium applications (up to 400k daily active users)
* Applications with relaxed availability requirements
### Cluster Deployment
**Advantages:**
* High availability with automatic failover
* Automatic data replication and backup
* Load balancing across nodes
* Horizontal scaling capabilities
**Disadvantages:**
* More complex setup and management
* Requires multiple servers
* Higher resource overhead
**Best for:**
* Production environments
* Large applications (1M+ daily active users)
* Mission-critical applications requiring high availability
## Hardware Requirements
### Single Node Deployment
For up to 400k daily active users:
* **CPU**: 2 cores
* **Memory**: 8GB RAM
* **Storage**: SSD recommended
* **Network**: 1Gbps
* **CPU**: 4 cores
* **Memory**: 16GB RAM
* **Storage**: NVMe SSD
* **Network**: 10Gbps
### Cluster Deployment
For 1M+ daily active users (3-node cluster):
* **CPU**: 4+ cores
* **Memory**: 16GB+ RAM
* **Storage**: NVMe SSD
* **Network**: 10Gbps
* **Nodes**: 3+ (odd number recommended)
* **Total CPU**: 12+ cores
* **Total Memory**: 48GB+ RAM
* **Replication**: 3x data redundancy
## Network Requirements
### Ports
WuKongIM uses the following ports by default:
| Port | Protocol | Purpose | Required |
| ----- | --------- | ---------------------- | ------------ |
| 5001 | HTTP | REST API | Yes |
| 5100 | TCP | Client connections | Yes |
| 5200 | WebSocket | Web client connections | Yes |
| 5210 | WSS | Secure WebSocket | Optional |
| 5300 | HTTP | Management interface | Optional |
| 11110 | TCP | Cluster communication | Cluster only |
### Firewall Configuration
```bash theme={null}
# Allow client connections
ufw allow 5001/tcp # API
ufw allow 5100/tcp # TCP clients
ufw allow 5200/tcp # WebSocket clients
ufw allow 5210/tcp # WSS (if using SSL)
```
```bash theme={null}
# Client connections (same as single node)
ufw allow 5001/tcp
ufw allow 5100/tcp
ufw allow 5200/tcp
ufw allow 5210/tcp
# Cluster communication
ufw allow 11110/tcp # Inter-node communication
```
## Geographic Distribution
### Supported Configurations
**Current Limitation**: Cross-region data node deployment is not supported. However, proxy nodes can be deployed globally.
* **Data Nodes**: Must be in the same region/datacenter
* **Proxy Nodes**: Can be deployed globally for reduced latency
* **Client Routing**: Automatic routing to nearest proxy node
### Multi-Region Architecture
```mermaid theme={null}
graph TB
subgraph "Region 1 (Primary)"
DN1[Data Node 1]
DN2[Data Node 2]
DN3[Data Node 3]
PN1[Proxy Node 1]
end
subgraph "Region 2"
PN2[Proxy Node 2]
end
subgraph "Region 3"
PN3[Proxy Node 3]
end
C1[Clients Region 1] --> PN1
C2[Clients Region 2] --> PN2
C3[Clients Region 3] --> PN3
PN2 --> DN1
PN3 --> DN1
DN1 <--> DN2
DN2 <--> DN3
DN3 <--> DN1
```
## Choosing Your Deployment Method
### Decision Matrix
| Factor | Docker Compose | Kubernetes | Linux Binary |
| -------------------- | -------------- | ---------- | ------------ |
| **Ease of Setup** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| **Production Ready** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| **Scaling** | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| **Monitoring** | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| **Resource Usage** | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
### Recommendations
**Docker Compose** - Quick setup for local development and testing
```bash theme={null}
# Get started in minutes
curl -O https://raw.githubusercontent.com/WuKongIM/WuKongIM/main/docker-compose.yml
docker-compose up -d
```
**Linux Binary** - Direct control and optimal performance
* Single node deployment
* Manual backup strategy
* Direct server management
**Kubernetes** - Full cloud-native deployment
* Automatic scaling and management
* Built-in monitoring and logging
* High availability by default
## Next Steps
Choose your deployment method and follow the detailed installation guide:
Start with Docker for quick deployment
Deploy on Kubernetes cluster
Install directly on Linux servers
## Common Questions
Yes! WuKongIM supports online migration from single node to cluster deployment without downtime. The process involves adding new nodes and redistributing data automatically.
Data nodes store and process messages, while proxy nodes handle client connections and route requests to data nodes. This separation allows for better scaling and geographic distribution.
For single node deployments, no load balancer is needed. For cluster deployments, WuKongIM includes built-in load balancing, but you may want an external load balancer for additional features like SSL termination.
# 5-Minute Android Integration
Source: https://wukong.mintlify.app/en/sdk/easy/android/getting-started
Quickly integrate WuKongIM Android EasySDK and implement chat functionality in 5 minutes
## Overview
WuKongIM Android EasySDK is a lightweight Android SDK that enables you to add real-time chat functionality to your Android application in just 5 minutes. This guide will take you through the complete process from installation to sending your first message.
**System Requirements**: Android 5.0 (API level 21) or higher, Kotlin 1.5.0 or higher
## Step 1: Install SDK
Choose any of the following methods to install Android EasySDK:
Add to your `app/build.gradle`:
```kotlin theme={null}
dependencies {
implementation 'com.githubim:easysdk-android:1.0.0'
}
```
Add to your `app/build.gradle.kts`:
```kotlin theme={null}
dependencies {
implementation("com.githubim:easysdk-android:1.0.0")
}
```
Add to your `pom.xml`:
```xml theme={null}
com.githubim
easysdk-android
1.0.0
```
## Step 2: Basic Integration
### 2.1 Import SDK
```kotlin theme={null}
import com.githubim.easysdk.WuKongEasySDK
import com.githubim.easysdk.WuKongConfig
import com.githubim.easysdk.WuKongChannelType
import com.githubim.easysdk.WuKongEvent
import com.githubim.easysdk.listener.WuKongEventListener
```
### 2.2 Initialize SDK
```kotlin theme={null}
// 1. Initialize SDK
val config = WuKongConfig.Builder()
.serverUrl("ws://your-wukongim-server.com:5200")
.uid("your_user_id") // Your user ID
.token("your_auth_token") // Your authentication token
// .deviceId("optional_device_id") // Optional: Device ID
// .deviceFlag(WuKongDeviceFlag.APP) // Optional: Device flag, default is APP
.build()
val easySDK = WuKongEasySDK.getInstance()
easySDK.init(this, config) // this is Application or Activity context
```
### 2.3 Listen for Events
```kotlin theme={null}
// 2. Listen for various events
easySDK.addEventListener(WuKongEvent.CONNECT, object : WuKongEventListener {
override fun onEvent(result: ConnectResult) {
Log.d("WuKong", "Event: Connected! $result")
// Connection successful, can start sending messages
runOnUiThread {
updateUI(true)
}
}
})
easySDK.addEventListener(WuKongEvent.DISCONNECT, object : WuKongEventListener {
override fun onEvent(disconnectInfo: DisconnectInfo) {
Log.d("WuKong", "Event: Disconnected. $disconnectInfo")
Log.d("WuKong", "Disconnect code: ${disconnectInfo.code}, reason: ${disconnectInfo.reason}")
// Connection lost, update UI status
runOnUiThread {
updateUI(false)
}
}
})
easySDK.addEventListener(WuKongEvent.MESSAGE, object : WuKongEventListener {
override fun onEvent(message: Message) {
Log.d("WuKong", "Event: Message Received $message")
// Handle received messages
runOnUiThread {
displayMessage(message)
}
}
})
easySDK.addEventListener(WuKongEvent.ERROR, object : WuKongEventListener {
override fun onEvent(error: WuKongError) {
Log.e("WuKong", "Event: Error Occurred ${error.message}")
// Handle errors, may need to update UI or reconnect
runOnUiThread {
handleError(error)
}
}
})
// You can add multiple listeners for the same event
easySDK.addEventListener(WuKongEvent.MESSAGE, object : WuKongEventListener {
override fun onEvent(message: Message) {
Log.d("WuKong", "Second listener also received message: ${message.messageId}")
// Add different processing logic here
}
})
```
### 2.4 Remove Event Listeners
In some cases, you may need to remove event listeners to avoid memory leaks or duplicate processing. Android EasySDK provides methods to remove listeners.
**Important Reminder**: In Android, removing event listeners requires maintaining references to the listeners. It's recommended to use class properties to store listener references for later removal.
#### Correct Usage
```kotlin theme={null}
class ChatActivity : AppCompatActivity() {
private lateinit var easySDK: WuKongEasySDK
// Save listener references
private var messageListener: WuKongEventListener? = null
private var connectListener: WuKongEventListener? = null
private var disconnectListener: WuKongEventListener? = null
private var errorListener: WuKongEventListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat)
easySDK = WuKongEasySDK.getInstance()
setupEventListeners()
connectToServer()
}
private fun setupEventListeners() {
// ✅ Correct: Save listener references
messageListener = object : WuKongEventListener {
override fun onEvent(message: Message) {
Log.d("WuKong", "Handle message: $message")
runOnUiThread {
handleMessage(message)
}
}
}
connectListener = object : WuKongEventListener {
override fun onEvent(result: ConnectResult) {
Log.d("WuKong", "Connection successful: $result")
runOnUiThread {
handleConnect(result)
}
}
}
disconnectListener = object : WuKongEventListener {
override fun onEvent(disconnectInfo: DisconnectInfo) {
Log.d("WuKong", "Connection lost: $disconnectInfo")
Log.d("WuKong", "Disconnect code: ${disconnectInfo.code}, reason: ${disconnectInfo.reason}")
runOnUiThread {
handleDisconnect(disconnectInfo)
}
}
}
errorListener = object : WuKongEventListener {
override fun onEvent(error: WuKongError) {
Log.e("WuKong", "Error occurred: $error")
runOnUiThread {
handleError(error)
}
}
}
// Add event listeners
messageListener?.let { easySDK.addEventListener(WuKongEvent.MESSAGE, it) }
connectListener?.let { easySDK.addEventListener(WuKongEvent.CONNECT, it) }
disconnectListener?.let { easySDK.addEventListener(WuKongEvent.DISCONNECT, it) }
errorListener?.let { easySDK.addEventListener(WuKongEvent.ERROR, it) }
}
private fun removeEventListeners() {
// Remove specific event listeners - use saved references
messageListener?.let {
easySDK.removeEventListener(WuKongEvent.MESSAGE, it)
messageListener = null
}
connectListener?.let {
easySDK.removeEventListener(WuKongEvent.CONNECT, it)
connectListener = null
}
disconnectListener?.let {
easySDK.removeEventListener(WuKongEvent.DISCONNECT, it)
disconnectListener = null
}
errorListener?.let {
easySDK.removeEventListener(WuKongEvent.ERROR, it)
errorListener = null
}
}
override fun onDestroy() {
super.onDestroy()
// Clean up listeners when Activity is destroyed
removeEventListeners()
}
private fun handleMessage(message: Message) {
// Message handling logic
}
private fun handleConnect(result: ConnectResult) {
// Connection success handling logic
}
private fun handleDisconnect(disconnectInfo: DisconnectInfo) {
// Connection lost handling logic
Log.d("WuKong", "Handle disconnect event - code: ${disconnectInfo.code}, reason: ${disconnectInfo.reason}")
}
private fun handleError(error: WuKongError) {
// Error handling logic
}
private fun connectToServer() {
lifecycleScope.launch {
try {
easySDK.connect()
Log.d("WuKong", "Connection successful!")
} catch (e: Exception) {
Log.e("WuKong", "Connection failed: $e")
}
}
}
}
```
### 2.5 Connect to Server
```kotlin theme={null}
// 4. Connect to server
lifecycleScope.launch {
try {
easySDK.connect()
Log.d("WuKong", "Connection successful!")
} catch (e: Exception) {
Log.e("WuKong", "Connection failed: $e")
}
}
```
### 2.6 Send Messages
```kotlin theme={null}
// 5. Send message example
private fun sendMessage() {
val targetChannelID = "friend_user_id" // Target user ID
val messagePayload = MessagePayload(
type = 1,
content = "Hello from Android EasySDK!"
) // Your custom message payload
lifecycleScope.launch {
try {
val result = easySDK.send(
channelId = targetChannelID,
channelType = WuKongChannelType.PERSON,
payload = messagePayload
)
Log.d("WuKong", "Message sent successfully: $result")
} catch (e: Exception) {
Log.e("WuKong", "Message sending failed: $e")
}
}
}
```
## Step 3: Error Handling and Best Practices
### 3.1 Error Handling
**Built-in Auto Reconnection**: Android EasySDK has built-in intelligent reconnection mechanism, no need to manually implement reconnection logic. The SDK will automatically attempt to reconnect when the connection is lost.
```kotlin theme={null}
// Proper connection status listening
easySDK.addEventListener(WuKongEvent.CONNECT, object : WuKongEventListener {
override fun onEvent(result: ConnectResult) {
Log.d("WuKong", "Connection successful: $result")
// Update UI status, enable sending functionality
runOnUiThread {
updateConnectionUI(true)
}
}
})
easySDK.addEventListener(WuKongEvent.DISCONNECT, object : WuKongEventListener {
override fun onEvent(disconnectInfo: DisconnectInfo) {
Log.d("WuKong", "Connection lost: $disconnectInfo")
Log.d("WuKong", "Disconnect code: ${disconnectInfo.code}, reason: ${disconnectInfo.reason}")
// Update UI status, disable sending functionality
runOnUiThread {
updateConnectionUI(false)
}
// SDK will automatically attempt to reconnect, no manual handling needed
}
})
easySDK.addEventListener(WuKongEvent.ERROR, object : WuKongEventListener {
override fun onEvent(error: WuKongError) {
Log.e("WuKong", "Error occurred: $error")
// Handle based on error type
runOnUiThread {
when (error.code) {
WuKongErrorCode.AUTH_FAILED -> {
// Authentication failed, need to get new token
handleAuthError()
}
WuKongErrorCode.NETWORK_ERROR -> {
// Network error, show network prompt
showNetworkError()
}
else -> {
// Other errors
showGeneralError(error.message)
}
}
}
}
})
```
## Related Resources
View complete example code and more feature demonstrations
View WuKongIM's complete protocol documentation
Visit EasyJSSDK's GitHub repository
Report issues or provide suggestions
## Next Steps
Congratulations! You have successfully integrated WuKongIM Android EasySDK. Now you can:
1. **Extend Functionality**: Add group chat, file transfer and other features
2. **Customize UI**: Customize chat interface according to your app design
3. **Integrate into Project**: Integrate chat functionality into your existing project
4. **Performance Optimization**: Optimize performance based on actual usage
If you need more complex functionality or higher performance requirements, consider using the full version of WuKongIMSDK.
# 5-Minute Flutter Integration
Source: https://wukong.mintlify.app/en/sdk/easy/flutter/getting-started
Quickly integrate WuKongIM Flutter EasySDK and implement chat functionality in 5 minutes
## Overview
WuKongIM Flutter EasySDK is a lightweight Flutter SDK that enables you to add real-time chat functionality to your Flutter application in just 5 minutes. This guide will take you through the complete process from installation to sending your first message.
**System Requirements**: Flutter 3.0.0 or higher, Dart 2.17.0 or higher
## Step 1: Install SDK
Add dependency to your `pubspec.yaml`:
```yaml theme={null}
dependencies:
flutter:
sdk: flutter
wukong_easy_sdk: ^1.0.0
```
Then run:
```bash theme={null}
flutter pub get
```
## Step 2: Basic Integration
### 2.1 Import SDK
```dart theme={null}
import 'package:wukong_easy_sdk/wukong_easy_sdk.dart';
```
### 2.2 Initialize SDK
```dart theme={null}
// 1. Initialize SDK
final config = WuKongConfig(
serverUrl: "ws://your-wukongim-server.com:5200",
uid: "your_user_id", // Your user ID
token: "your_auth_token", // Your authentication token
// deviceId: "optional_device_id", // Optional: Device ID
// deviceFlag: WuKongDeviceFlag.app, // Optional: Device flag, default is app
);
final easySDK = WuKongEasySDK.getInstance();
await easySDK.init(config);
```
### 2.3 Listen for Events
```dart theme={null}
// 2. Listen for various events
easySDK.addEventListener(WuKongEvent.connect, (ConnectResult result) {
print("Event: Connected! $result");
// Connection successful, can start sending messages
updateUI(true);
});
easySDK.addEventListener(WuKongEvent.disconnect, (DisconnectInfo disconnectInfo) {
print("Event: Disconnected. $disconnectInfo");
print("Disconnect code: ${disconnectInfo.code}, reason: ${disconnectInfo.reason}");
// Connection lost, update UI status
updateUI(false);
});
easySDK.addEventListener(WuKongEvent.message, (Message message) {
print("Event: Message Received $message");
// Handle received messages
displayMessage(message);
});
easySDK.addEventListener(WuKongEvent.error, (WuKongError error) {
print("Event: Error Occurred ${error.message}");
// Handle errors, may need to update UI or reconnect
handleError(error);
});
// You can add multiple listeners for the same event
easySDK.addEventListener(WuKongEvent.message, (Message message) {
print("Second listener also received message: ${message.messageId}");
// Add different processing logic here
});
```
### 2.4 Remove Event Listeners
In some cases, you may need to remove event listeners to avoid memory leaks or duplicate processing. Flutter EasySDK provides methods to remove listeners.
**Important Reminder**: In Flutter, removing event listeners requires maintaining references to the listeners. It's recommended to use class properties to store listener references for later removal.
#### Correct Usage
```dart theme={null}
class ChatPage extends StatefulWidget {
@override
_ChatPageState createState() => _ChatPageState();
}
class _ChatPageState extends State {
late WuKongEasySDK easySDK;
// Save listener references
WuKongEventListener? messageListener;
WuKongEventListener? connectListener;
WuKongEventListener? disconnectListener;
WuKongEventListener? errorListener;
@override
void initState() {
super.initState();
easySDK = WuKongEasySDK.getInstance();
setupEventListeners();
connectToServer();
}
void setupEventListeners() {
// ✅ Correct: Save listener references
messageListener = (Message message) {
print("Handle message: $message");
if (mounted) {
setState(() {
handleMessage(message);
});
}
};
connectListener = (ConnectResult result) {
print("Connection successful: $result");
if (mounted) {
setState(() {
handleConnect(result);
});
}
};
disconnectListener = (DisconnectInfo disconnectInfo) {
print("Connection lost: $disconnectInfo");
print("Disconnect code: ${disconnectInfo.code}, reason: ${disconnectInfo.reason}");
if (mounted) {
setState(() {
handleDisconnect(disconnectInfo);
});
}
};
errorListener = (WuKongError error) {
print("Error occurred: $error");
if (mounted) {
setState(() {
handleError(error);
});
}
};
// Add event listeners
easySDK.addEventListener(WuKongEvent.message, messageListener!);
easySDK.addEventListener(WuKongEvent.connect, connectListener!);
easySDK.addEventListener(WuKongEvent.disconnect, disconnectListener!);
easySDK.addEventListener(WuKongEvent.error, errorListener!);
}
void removeEventListeners() {
// Remove specific event listeners - use saved references
if (messageListener != null) {
easySDK.removeEventListener(WuKongEvent.message, messageListener!);
messageListener = null;
}
if (connectListener != null) {
easySDK.removeEventListener(WuKongEvent.connect, connectListener!);
connectListener = null;
}
if (disconnectListener != null) {
easySDK.removeEventListener(WuKongEvent.disconnect, disconnectListener!);
disconnectListener = null;
}
if (errorListener != null) {
easySDK.removeEventListener(WuKongEvent.error, errorListener!);
errorListener = null;
}
}
@override
void dispose() {
// Clean up listeners when Widget is destroyed
removeEventListeners();
super.dispose();
}
void handleMessage(Message message) {
// Message handling logic
}
void handleConnect(ConnectResult result) {
// Connection success handling logic
}
void handleDisconnect(DisconnectInfo disconnectInfo) {
// Connection lost handling logic
print("Handle disconnect event - code: ${disconnectInfo.code}, reason: ${disconnectInfo.reason}");
}
void handleError(WuKongError error) {
// Error handling logic
}
Future connectToServer() async {
try {
await easySDK.connect();
print("Connection successful!");
} catch (e) {
print("Connection failed: $e");
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Chat")),
body: Container(
// Chat interface
),
);
}
}
```
### 2.5 Connect to Server
```dart theme={null}
// 4. Connect to server
try {
await easySDK.connect();
print("Connection successful!");
} catch (e) {
print("Connection failed: $e");
}
```
### 2.6 Send Messages
```dart theme={null}
// 5. Send message example
Future sendMessage() async {
final targetChannelID = "friend_user_id"; // Target user ID
final messagePayload = MessagePayload(
type: 1,
content: "Hello from Flutter EasySDK!",
); // Your custom message payload
try {
final result = await easySDK.send(
channelId: targetChannelID,
channelType: WuKongChannelType.person,
payload: messagePayload,
);
print("Message sent successfully: $result");
} catch (e) {
print("Message sending failed: $e");
}
}
```
## Step 3: Error Handling and Best Practices
### 3.1 Error Handling
**Built-in Auto Reconnection**: Flutter EasySDK has built-in intelligent reconnection mechanism, no need to manually implement reconnection logic. The SDK will automatically attempt to reconnect when the connection is lost.
```dart theme={null}
// Proper connection status listening
easySDK.addEventListener(WuKongEvent.connect, (ConnectResult result) {
print("Connection successful: $result");
// Update UI status, enable sending functionality
if (mounted) {
setState(() {
updateConnectionUI(true);
});
}
});
easySDK.addEventListener(WuKongEvent.disconnect, (DisconnectInfo disconnectInfo) {
print("Connection lost: $disconnectInfo");
print("Disconnect code: ${disconnectInfo.code}, reason: ${disconnectInfo.reason}");
// Update UI status, disable sending functionality
if (mounted) {
setState(() {
updateConnectionUI(false);
});
}
// SDK will automatically attempt to reconnect, no manual handling needed
});
easySDK.addEventListener(WuKongEvent.error, (WuKongError error) {
print("Error occurred: $error");
// Handle based on error type
if (mounted) {
setState(() {
switch (error.code) {
case WuKongErrorCode.authFailed:
// Authentication failed, need to get new token
handleAuthError();
break;
case WuKongErrorCode.networkError:
// Network error, show network prompt
showNetworkError();
break;
default:
// Other errors
showGeneralError(error.message);
break;
}
});
}
});
```
## Related Resources
View complete example code and more feature demonstrations
View WuKongIM's complete protocol documentation
Visit EasyJSSDK's GitHub repository
Report issues or provide suggestions
## Next Steps
Congratulations! You have successfully integrated WuKongIM Flutter EasySDK. Now you can:
1. **Extend Functionality**: Add group chat, file transfer and other features
2. **Customize UI**: Customize chat interface according to your app design
3. **Integrate into Project**: Integrate chat functionality into your existing project
4. **Performance Optimization**: Optimize performance based on actual usage
If you need more complex functionality or higher performance requirements, consider using the full version of WuKongIMSDK.
# 5-Minute iOS Integration
Source: https://wukong.mintlify.app/en/sdk/easy/ios/getting-started
Quickly integrate WuKongIM iOS EasySDK and implement chat functionality in 5 minutes
## Overview
WuKongIM iOS EasySDK is a lightweight iOS SDK that enables you to add real-time chat functionality to your iOS application in just 5 minutes. This guide will take you through the complete process from installation to sending your first message.
**System Requirements**: iOS 12.0 or higher, Xcode 12.0 or higher, Swift 5.0 or higher
## Step 1: Install SDK
Choose any of the following methods to install iOS EasySDK:
Add to your `Podfile`:
```ruby theme={null}
pod 'WuKongEasySDK', '~> 1.0.0'
```
Then run:
```bash theme={null}
pod install
```
In Xcode:
1. File → Add Package Dependencies
2. Enter URL: `https://github.com/WuKongIM/WuKongEasySDK-iOS`
3. Select version and add to project
1. Download the latest [Release](https://github.com/WuKongIM/WuKongEasySDK-iOS/releases)
2. Drag `WuKongEasySDK.framework` into your project
3. Add dependency in Target → General → Frameworks
## Step 2: Basic Integration
### 2.1 Import SDK
```swift theme={null}
import WuKongEasySDK
```
### 2.2 Initialize SDK
```swift theme={null}
// 1. Initialize SDK
let config = WuKongConfig(
serverUrl: "ws://your-wukongim-server.com:5200",
uid: "your_user_id", // Your user ID
token: "your_auth_token" // Your authentication token
// deviceId: "optional_device_id", // Optional: Device ID
// deviceFlag: .APP // Optional: Device flag, default is .APP
)
let easySDK = WuKongEasySDK(config: config)
```
### 2.3 Listen for Events
```swift theme={null}
// 2. Listen for various events
easySDK.onConnect { result in
print("Event: Connected!", result)
// Connection successful, can start sending messages
DispatchQueue.main.async {
self.updateUI(connected: true)
}
}
easySDK.onDisconnect { disconnectInfo in
print("Event: Disconnected.", disconnectInfo)
print("Disconnect code: \(disconnectInfo.code), reason: \(disconnectInfo.reason)")
// Connection lost, update UI status
DispatchQueue.main.async {
self.updateUI(connected: false)
}
}
easySDK.onMessage { message in
print("Event: Message Received", message)
// Handle received messages
DispatchQueue.main.async {
self.displayMessage(message)
}
}
easySDK.onError { error in
print("Event: Error Occurred", error.localizedDescription)
// Handle errors, may need to update UI or reconnect
DispatchQueue.main.async {
self.handleError(error)
}
}
// You can add multiple listeners for the same event
easySDK.onMessage { message in
print("Second listener also received message:", message.messageId)
// Add different processing logic here
}
```
### 2.4 Remove Event Listeners
In some cases, you may need to remove event listeners to avoid memory leaks or duplicate processing. iOS EasySDK provides methods to remove listeners.
**Important Reminder**: In iOS, removing event listeners requires maintaining references to the listeners. It's recommended to use class properties to store listener references for later removal.
#### Correct Usage
```swift theme={null}
class ChatManager {
private let easySDK: WuKongEasySDK
// Save listener references
private var messageListener: EventListener?
private var connectListener: EventListener?
private var disconnectListener: EventListener?
private var errorListener: EventListener?
init(config: WuKongConfig) {
self.easySDK = WuKongEasySDK(config: config)
}
func setupEventListeners() {
// ✅ Correct: Save listener references
messageListener = easySDK.onMessage { [weak self] message in
print("Handle message:", message)
DispatchQueue.main.async {
self?.handleMessage(message)
}
}
connectListener = easySDK.onConnect { [weak self] result in
print("Connection successful:", result)
DispatchQueue.main.async {
self?.handleConnect(result)
}
}
disconnectListener = easySDK.onDisconnect { [weak self] disconnectInfo in
print("Connection lost:", disconnectInfo)
print("Disconnect code: \(disconnectInfo.code), reason: \(disconnectInfo.reason)")
DispatchQueue.main.async {
self?.handleDisconnect(disconnectInfo)
}
}
errorListener = easySDK.onError { [weak self] error in
print("Error occurred:", error)
DispatchQueue.main.async {
self?.handleError(error)
}
}
}
func removeEventListeners() {
// Remove specific event listeners - use saved references
if let listener = messageListener {
easySDK.removeListener(listener)
messageListener = nil
}
if let listener = connectListener {
easySDK.removeListener(listener)
connectListener = nil
}
if let listener = disconnectListener {
easySDK.removeListener(listener)
disconnectListener = nil
}
if let listener = errorListener {
easySDK.removeListener(listener)
errorListener = nil
}
}
private func handleMessage(_ message: Message) {
// Message handling logic
}
private func handleConnect(_ result: ConnectResult) {
// Connection success handling logic
}
private func handleDisconnect(_ disconnectInfo: DisconnectInfo) {
// Connection lost handling logic
print("Handle disconnect event - code: \(disconnectInfo.code), reason: \(disconnectInfo.reason)")
}
private func handleError(_ error: Error) {
// Error handling logic
}
}
```
### 2.5 Connect to Server
```swift theme={null}
// 4. Connect to server
Task {
do {
try await easySDK.connect()
print("Connection successful!")
} catch {
print("Connection failed:", error)
}
}
```
### 2.6 Send Messages
```swift theme={null}
// 5. Send message example
func sendMessage() async {
let targetChannelID = "friend_user_id" // Target user ID
let messagePayload = MessagePayload(
type: 1,
content: "Hello from iOS EasySDK!"
) // Your custom message payload
do {
let result = try await easySDK.send(
channelId: targetChannelID,
channelType: .person,
payload: messagePayload
)
print("Message sent successfully:", result)
} catch {
print("Message sending failed:", error)
}
}
```
## Step 3: Error Handling and Best Practices
### 3.1 Error Handling
**Built-in Auto Reconnection**: iOS EasySDK has built-in intelligent reconnection mechanism, no need to manually implement reconnection logic. The SDK will automatically attempt to reconnect when the connection is lost.
```swift theme={null}
// Proper connection status listening
easySDK.onConnect { result in
print("Connection successful:", result)
// Update UI status, enable sending functionality
DispatchQueue.main.async {
self.updateConnectionUI(connected: true)
}
}
easySDK.onDisconnect { disconnectInfo in
print("Connection lost:", disconnectInfo)
print("Disconnect code: \(disconnectInfo.code), reason: \(disconnectInfo.reason)")
// Update UI status, disable sending functionality
DispatchQueue.main.async {
self.updateConnectionUI(connected: false)
}
// SDK will automatically attempt to reconnect, no manual handling needed
}
easySDK.onError { error in
print("Error occurred:", error)
// Handle based on error type
DispatchQueue.main.async {
switch error {
case WuKongError.authFailed:
// Authentication failed, need to get new token
self.handleAuthError()
case WuKongError.networkError:
// Network error, show network prompt
self.showNetworkError()
default:
// Other errors
self.showGeneralError(error.localizedDescription)
}
}
}
```
## Related Resources
View complete example code and more feature demonstrations
View WuKongIM's complete protocol documentation
Visit EasyJSSDK's GitHub repository
Report issues or provide suggestions
## Next Steps
Congratulations! You have successfully integrated WuKongIM iOS EasySDK. Now you can:
1. **Extend Functionality**: Add group chat, file transfer and other features
2. **Customize UI**: Customize chat interface according to your app design
3. **Integrate into Project**: Integrate chat functionality into your existing project
4. **Performance Optimization**: Optimize performance based on actual usage
If you need more complex functionality or higher performance requirements, consider using the full version of WuKongIMSDK.
# 5-Minute Web Integration
Source: https://wukong.mintlify.app/en/sdk/easy/javascript/getting-started
Quickly integrate WuKongIM Web EasySDK and implement chat functionality in 5 minutes
## Overview
WuKongIM Web EasySDK is a lightweight JavaScript SDK that enables you to add real-time chat functionality to your web application in just 5 minutes. This guide will take you through the complete process from installation to sending your first message.
**System Requirements**: Supports modern browsers (Chrome 60+, Firefox 55+, Safari 11+, Edge 79+)
## Step 1: Install SDK
Choose any of the following methods to install EasyJSSDK:
```bash theme={null}
npm install easyjssdk
```
```bash theme={null}
yarn add easyjssdk
```
```html theme={null}
```
## Step 2: Basic Integration
### 2.1 Import SDK
```javascript theme={null}
import { WKIM, WKIMChannelType, WKIMEvent } from 'easyjssdk';
```
```javascript theme={null}
const { WKIM, WKIMChannelType, WKIMEvent } = require('easyjssdk');
```
```javascript theme={null}
// After loading SDK via CDN, use global variables directly
const { WKIM, WKIMChannelType, WKIMEvent } = window.EasyJSSDK;
```
### 2.2 Initialize SDK
```javascript theme={null}
// 1. Initialize SDK
const im = WKIM.init("ws://your-wukongim-server.com:5200", {
uid: "your_user_id", // Your user ID
token: "your_auth_token" // Your authentication token
// deviceId: "optional_device_id", // Optional: Device ID
// deviceFlag: 2 // Optional: Device flag (1:APP, 2:WEB, default is 2)
});
```
### 2.3 Listen for Messages
```javascript theme={null}
// 2. Listen for various events
im.on(WKIMEvent.Connect, (result) => {
console.log("Event: Connected!", result);
// Connection successful, can start sending messages
updateUI(true);
});
im.on(WKIMEvent.Disconnect, (disconnectInfo) => {
console.log("Event: Disconnected.", disconnectInfo);
console.log("Disconnect code:", disconnectInfo.code, "reason:", disconnectInfo.reason);
// Connection lost, update UI status
updateUI(false);
});
im.on(WKIMEvent.Message, (message) => {
console.log("Event: Message Received", message);
// Handle received messages
displayMessage(message);
});
im.on(WKIMEvent.Error, (error) => {
console.log("Event: Error Occurred", error.message || error);
// Handle errors, may need to update UI or reconnect
});
// You can add multiple listeners for the same event
im.on(WKIMEvent.Message, (message) => {
console.log("Second listener also received message:", message.messageId);
// Add different processing logic here
});
```
### 2.4 Remove Event Listeners
In some cases, you may need to remove event listeners to avoid memory leaks or duplicate processing. EasyJSSDK provides the `off` method to remove event listeners.
**Important Reminder**: Both `eventName` and `callback` parameters are required for the `off` method. The `callback` reference must be exactly the same as the one used when adding the listener with `on()`.
#### Syntax
```javascript theme={null}
im.off(eventName, callback)
```
**Parameter Description:**
* `eventName` (Event): The event name to remove the listener for (required)
* `callback` (EventHandler): The specific callback function to remove (required)
#### Correct Usage
```javascript theme={null}
// ✅ Correct: Use named functions
function handleMessage(message) {
console.log('Handle message:', message);
}
function handleConnect(result) {
console.log('Connection successful:', result);
}
function handleError(error) {
console.log('Error occurred:', error);
}
// Add event listeners
im.on(WKIMEvent.Message, handleMessage);
im.on(WKIMEvent.Connect, handleConnect);
im.on(WKIMEvent.Error, handleError);
// Remove specific event listeners - use the same function reference
im.off(WKIMEvent.Message, handleMessage);
im.off(WKIMEvent.Connect, handleConnect);
im.off(WKIMEvent.Error, handleError);
```
#### Incorrect Usage
```javascript theme={null}
// ❌ Incorrect: Using anonymous functions cannot be properly removed
im.on(WKIMEvent.Message, (message) => {
console.log('Handle message:', message);
});
// This cannot remove the above listener because the function reference is different
im.off(WKIMEvent.Message, (message) => {
console.log('Handle message:', message);
});
```
#### Practical Application Example
```javascript theme={null}
class ChatManager {
constructor() {
// Bind methods to instance to ensure correct 'this' context
this.handleMessage = this.handleMessage.bind(this);
this.handleConnect = this.handleConnect.bind(this);
this.handleDisconnect = this.handleDisconnect.bind(this);
this.handleError = this.handleError.bind(this);
}
init() {
// Initialize SDK
this.im = WKIM.init("ws://your-server.com:5200", {
uid: "user123",
token: "user-token"
});
// Add event listeners
this.im.on(WKIMEvent.Message, this.handleMessage);
this.im.on(WKIMEvent.Connect, this.handleConnect);
this.im.on(WKIMEvent.Disconnect, this.handleDisconnect);
this.im.on(WKIMEvent.Error, this.handleError);
}
destroy() {
if (this.im) {
// Remove event listeners - use the same method references
this.im.off(WKIMEvent.Message, this.handleMessage);
this.im.off(WKIMEvent.Connect, this.handleConnect);
this.im.off(WKIMEvent.Disconnect, this.handleDisconnect);
this.im.off(WKIMEvent.Error, this.handleError);
this.im = null;
}
}
handleMessage(message) {
console.log('Message received:', message);
}
handleConnect(result) {
console.log('Connection successful:', result);
}
handleDisconnect(disconnectInfo) {
console.log('Connection lost:', disconnectInfo);
console.log('Disconnect code:', disconnectInfo.code, 'reason:', disconnectInfo.reason);
}
handleError(error) {
console.log('Error occurred:', error);
}
}
// Usage example
const chatManager = new ChatManager();
chatManager.init();
// Cleanup when page unloads
window.addEventListener('beforeunload', () => {
chatManager.destroy();
});
```
#### Framework Integration Best Practices
**React Example:**
```javascript theme={null}
import React, { useEffect, useRef } from 'react';
import { WKIM, WKIMEvent } from 'easyjssdk';
function ChatComponent() {
const imRef = useRef(null);
useEffect(() => {
// Define event handler functions
const handleMessage = (message) => {
console.log('Message received:', message);
};
const handleConnect = (result) => {
console.log('Connection successful:', result);
};
// Initialize SDK
const im = WKIM.init("ws://your-server.com:5200", {
uid: "user123",
token: "user-token"
});
imRef.current = im;
// Add event listeners
im.on(WKIMEvent.Message, handleMessage);
im.on(WKIMEvent.Connect, handleConnect);
// Cleanup function: remove listeners when component unmounts
return () => {
if (imRef.current) {
imRef.current.off(WKIMEvent.Message, handleMessage);
imRef.current.off(WKIMEvent.Connect, handleConnect);
}
};
}, []);
return Chat Component
;
}
```
**Vue Example:**
```javascript theme={null}
export default {
data() {
return {
im: null
};
},
mounted() {
// Define event handler functions
this.handleMessage = (message) => {
console.log('Message received:', message);
};
this.handleConnect = (result) => {
console.log('Connection successful:', result);
};
// Initialize SDK
this.im = WKIM.init("ws://your-server.com:5200", {
uid: "user123",
token: "user-token"
});
// Add event listeners
this.im.on(WKIMEvent.Message, this.handleMessage);
this.im.on(WKIMEvent.Connect, this.handleConnect);
},
beforeUnmount() {
// Remove listeners before component destruction
if (this.im) {
this.im.off(WKIMEvent.Message, this.handleMessage);
this.im.off(WKIMEvent.Connect, this.handleConnect);
}
}
};
```
**Source Code Reference**: You can check the [EasyJSSDK source code](https://github.com/WuKongIM/EasyJSSDK/blob/main/src/index.ts) to understand the specific implementation details of the `off` method.
### 2.5 Connect to Server
```javascript theme={null}
// 4. Connect to server
try {
await im.connect();
console.log("Connection successful!");
} catch (error) {
console.error("Connection failed:", error);
}
```
### 2.6 Send Messages
```javascript theme={null}
// 5. Send message example
async function sendMessage() {
const targetChannelID = "friend_user_id"; // Target user ID
const messagePayload = {
type: 1,
content: "Hello from EasyJSSDK!"
}; // Your custom message payload
try {
const result = await im.send(targetChannelID, WKIMChannelType.Person, messagePayload);
console.log("Message sent successfully:", result);
} catch (error) {
console.error("Message sending failed:", error);
}
}
```
## Related Resources
View complete example code and more feature demonstrations
View WuKongIM's complete protocol documentation
Visit EasyJSSDK's GitHub repository
Report issues or provide suggestions
# Overview
Source: https://wukong.mintlify.app/en/sdk/easy/overview
WuKongIM EasySDK cross-platform quick integration guide
## What is WuKongEasySDK
WuKongEasySDK is a lightweight instant messaging SDK series designed for rapid integration. It provides unified API design across platforms, enabling developers to add real-time chat functionality to any application in just 5 minutes.
**Design Philosophy**: Simplify integration complexity, focus on core functionality, and provide out-of-the-box chat experience.
## Core Advantages
### 🚀 Lightning Fast Integration
* **5-Minute Setup**: From installation to sending the first message takes only 5 minutes
* **Zero Configuration Start**: Ready to use out of the box, no complex initialization configuration needed
* **Automated Processing**: Built-in connection management, auto-reconnection, message synchronization mechanisms
### 📱 Full Platform Coverage
* **Web/JavaScript**: Supports modern browsers (Chrome 60+, Firefox 55+, Safari 11+, Edge 79+)
* **iOS**: Supports iOS 12.0 or higher, Xcode 12.0+, Swift 5.0+
* **Android**: Supports Android 5.0 (API level 21) or higher, Kotlin 1.5.0+
* **Flutter**: Supports Flutter 3.0.0 or higher, Dart 2.17.0+
### 💡 Unified Design
* **Consistent API**: All platforms use the same method names and parameter structures
* **Modern Async Patterns**: JavaScript Promise, Swift async/await, Kotlin coroutines, Dart async/await
* **Event-Driven Architecture**: Unified event listening and handling mechanisms
### Integration Process Overview
```mermaid theme={null}
graph TD
A[Choose Platform SDK] --> B[Install Dependencies]
B --> C[Initialize SDK]
C --> D[Setup Event Listeners]
D --> E[Connect to Server]
E --> F[Send First Message]
F --> G[🎉 Integration Complete]
style A fill:#e1f5fe
style G fill:#e8f5e8
```
## Cross-Platform Code Examples
Here are basic integration code examples for each platform, demonstrating WuKongEasySDK's unified API design:
```javascript Web/JavaScript theme={null}
import { WKIM, WKIMChannelType, WKIMEvent } from 'easyjssdk';
// 1. Initialize SDK
const im = WKIM.init("ws://your-server.com:5200", {
uid: "your_user_id",
token: "your_auth_token"
});
// 2. Listen for messages
im.on(WKIMEvent.Message, (message) => {
console.log("New message received:", message);
});
// 3. Connect to server
await im.connect();
// 4. Send message
const result = await im.send("friend_user_id", WKIMChannelType.Person, {
type: 1,
content: "Hello from Web!"
});
```
```swift iOS theme={null}
import WuKongEasySDK
// 1. Initialize SDK
let config = WuKongConfig(
serverUrl: "ws://your-server.com:5200",
uid: "your_user_id",
token: "your_auth_token"
)
let easySDK = WuKongEasySDK(config: config)
// 2. Listen for messages
easySDK.onMessage { message in
print("New message received:", message)
}
// 3. Connect to server
try await easySDK.connect()
// 4. Send message
let result = try await easySDK.send(
to: "friend_user_id",
channelType: .person,
payload: MessagePayload(type: 1, content: "Hello from iOS!")
)
```
```kotlin Android theme={null}
import com.githubim.easysdk.*
// 1. Initialize SDK
val config = WuKongConfig.Builder()
.serverUrl("ws://your-server.com:5200")
.uid("your_user_id")
.token("your_auth_token")
.build()
val easySDK = WuKongEasySDK.getInstance()
easySDK.init(this, config)
// 2. Listen for messages
easySDK.addEventListener(WuKongEvent.MESSAGE, object : WuKongEventListener {
override fun onEvent(message: Message) {
Log.d("WuKong", "New message received: $message")
}
})
// 3. Connect to server
lifecycleScope.launch {
easySDK.connect()
}
// 4. Send message
val result = easySDK.send(
channelId = "friend_user_id",
channelType = WuKongChannelType.PERSON,
payload = MessagePayload(type = 1, content = "Hello from Android!")
)
```
```dart Flutter theme={null}
import 'package:wukong_easy_sdk/wukong_easy_sdk.dart';
// 1. Initialize SDK
final config = WuKongConfig(
serverUrl: "ws://your-server.com:5200",
uid: "your_user_id",
token: "your_auth_token",
);
final easySDK = WuKongEasySDK.getInstance();
await easySDK.init(config);
// 2. Listen for messages
easySDK.addEventListener(WuKongEvent.message, (Message message) {
print("New message received: $message");
});
// 3. Connect to server
await easySDK.connect();
// 4. Send message
final result = await easySDK.send(
channelId: "friend_user_id",
channelType: WuKongChannelType.person,
payload: MessagePayload(type: 1, content: "Hello from Flutter!"),
);
```
**Unified Design**: Notice that all platforms use the same method names (init, connect, send) and similar parameter structures, making cross-platform development easier.
## Use Case Guidelines
### ✅ Recommended Scenarios for WuKongEasySDK
* **Rapid Prototyping**: Prototype projects that need to quickly validate chat functionality
* **MVP Projects**: Minimum viable products for quick market validation
* **Simple Chat Applications**: Chat applications with relatively simple feature requirements
* **Learning and Demos**: Learning instant messaging development or product feature demonstrations
* **Internal Tools**: Enterprise internal communication tools or customer service systems
* **Tight Development Timeline**: Projects that need rapid delivery
* **Simple Tech Stack**: Teams with limited experience in complex SDK integration
* **Standard Feature Needs**: Standard chat functionality meets requirements
* **Cross-Platform Consistency**: Need consistent user experience across multiple platforms
* **Maintenance Cost Sensitive**: Want to reduce long-term maintenance and upgrade costs
## Get Started Now
Choose your development platform and begin your 5-minute quick integration journey:
Quick integration for web applications and H5 pages
Quick integration for iOS native applications
Quick integration for Android native applications
Quick integration for Flutter cross-platform applications
**Selection Advice**: If you're unsure which platform to choose, we recommend starting with Web EasySDK as it can quickly validate functionality and is easy to debug.
# SDK Overview
Source: https://wukong.mintlify.app/en/sdk/overview
Choose the WuKongIM SDK that fits your project
WuKongIM provides two SDK solutions to meet different project needs:
## SDK Type Selection
**5-Minute Quick Integration**
* Zero configuration, ready to use out of the box
* Suitable for rapid prototyping and simple applications
* Low learning curve, clean code
* Supports Web, iOS, Android, Flutter
**Full-Featured Enterprise SDK**
* Complete instant messaging functionality
* Highly customizable and extensible
* Suitable for complex enterprise applications
* Supports all mainstream platforms
## Quick Comparison
| Feature | WuKongEasySDK | WuKongIMSDK |
| ------------------------ | ------------------------------ | ------------------------------ |
| **Integration Time** | 5 minutes | 30+ minutes |
| **Learning Curve** | Very low | Medium |
| **Feature Completeness** | Core features | Complete features |
| **Customization** | Limited | Highly customizable |
| **Use Cases** | Rapid prototyping, simple apps | Enterprise-level, complex apps |
## Platform Support
### WuKongEasySDK Platforms
Browser and Web applications
iOS native applications
Android native applications
Flutter cross-platform applications
### WuKongIMSDK Platforms
Swift/Objective-C
Kotlin/Java
JavaScript/TypeScript
Dart
ArkTS
Vue.js
## Selection Recommendations
**Recommended Scenarios:**
* Rapid prototype development
* MVP project validation
* Simple chat functionality
* Learning and demonstrations
* Tight development timeline
**Get Started:**
Choose your platform and complete integration in 5 minutes
**Recommended Scenarios:**
* Enterprise-level applications
* Complex business requirements
* Need complete functionality
* High performance requirements
* Deep customization needs
**Get Started:**
Check detailed documentation and integration guides
## Next Steps
1. **Evaluate Project Requirements**: Choose SDK type based on feature complexity and development time
2. **Select Development Platform**: Choose the corresponding platform SDK based on your tech stack
3. **Start Integration**: Follow the corresponding quick start guide for integration
**Not sure which to choose?** We recommend starting with WuKongEasySDK to quickly validate functionality.
# SDK Source Code List
Source: https://wukong.mintlify.app/en/sdk/source-code
WuKongIM official SDK source code repository list, including open source implementations for all platforms
WuKongIM provides complete open source SDK implementations, supporting multiple platforms and development languages. All SDK source code is open sourced on GitHub and Gitee, making it convenient for developers to learn, use and contribute.
## WuKongEasySDK Series
WuKongEasySDK is a lightweight quick integration solution that can be integrated in 5 minutes.
| SDK Name | Repository | Example Code | Description |
| --------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------- |
| WuKongEasySDK-JS | [GitHub](https://github.com/WuKongIM/WuKongEasySDK-JS) | [GitHub](https://github.com/WuKongIM/WuKongEasySDK-JS/tree/main/example) | Web SDK, supports JavaScript/TypeScript |
| WuKongEasySDK-iOS | [GitHub](https://github.com/WuKongIM/WuKongEasySDK-iOS) | [GitHub](https://github.com/WuKongIM/WuKongEasySDK-iOS/tree/main/Examples) | iOS SDK, supports Swift/Objective-C |
| WuKongEasySDK-Android | [GitHub](https://github.com/WuKongIM/WuKongEasySDK-Android) | [GitHub](https://github.com/WuKongIM/WuKongEasySDK-Android/tree/main/example) | Android SDK, supports Kotlin/Java |
| WuKongEasySDK-Flutter | [GitHub](https://github.com/WuKongIM/WuKongEasySDK-Flutter) | [GitHub](https://github.com/WuKongIM/WuKongEasySDK-Flutter/tree/main/example) | Flutter cross-platform SDK, supports iOS, Android, Web, Desktop |
## WuKongIMSDK Series
WuKongIMSDK is a feature-complete enterprise-grade SDK providing comprehensive instant messaging functionality.
| SDK Name | Repository | Example Code | Description |
| ------------------ | ---------------------------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------- |
| WuKongIMiOSSDK | [GitHub](https://github.com/WuKongIM/WuKongIMiOSSDK) | [GitHub](https://github.com/WuKongIM/WuKongIMiOSSDK/tree/main/Example) | iOS SDK |
| WuKongIMAndroidSDK | [GitHub](https://github.com/WuKongIM/WuKongIMAndroidSDK) | [GitHub](https://github.com/WuKongIM/WuKongIMAndroidSDK/tree/master/app) | Android SDK |
| WuKongIMFlutterSDK | [GitHub](https://github.com/WuKongIM/WuKongIMFlutterSDK) | [GitHub](https://github.com/WuKongIM/WuKongIMFlutterSDK/tree/master/example) | Flutter SDK |
| WuKongIMHarmonySDK | [GitHub](https://github.com/WuKongIM/WuKongIMHarmonyOSSDK) | [GitHub](https://github.com/WuKongIM/WuKongIMHarmonyOSSDK/tree/main/entry) | HarmonyOS SDK |
| WuKongIMJSSDK | [GitHub](https://github.com/WuKongIM/WuKongIMJSSDK) | [GitHub](https://github.com/WuKongIM/WuKongIMJSSDK/tree/main/examples) | JavaScript SDK, supports Web, WeChat Mini Program, Uniapp |
## Open Source License
All WuKongIM SDKs use the **Apache 2.0** open source license, which allows you to:
* ✅ **Commercial Use**: Can be used in commercial projects
* ✅ **Modify Code**: Can modify source code according to needs
* ✅ **Distribute Code**: Can redistribute modified code
* ✅ **Patent Grant**: Obtain usage rights for related patents
* ⚠️ **Retain Notice**: Must retain original copyright and license notices
## Contribution Guide
We welcome community contributions! Ways to participate:
1. **Report Issues**: Submit Issues in the corresponding repository
2. **Submit Code**: Fork repository and submit Pull Requests
3. **Improve Documentation**: Help improve documentation and examples
4. **Share Experience**: Share usage experience in the community
**Selection Recommendations**:
* Non-IM applications: Choose EasySDK series
* IM applications: Choose WuKongIMSDK series
## Technical Support
* **Documentation Center**: [https://docs.githubim.com](https://docs.githubim.com)
* **GitHub Issues**: Issues page of each repository
* **Community Discussion**: GitHub Discussions
* **Official Website**: [https://githubim.com](https://githubim.com)
## SDK Feature Comparison
### EasySDK vs WuKongIMSDK
| Feature | EasySDK | WuKongIMSDK |
| ---------------------- | ------------------------------ | ----------------------------------- |
| **Integration Time** | 5 minutes | 30+ minutes |
| **Code Complexity** | Simple | Comprehensive |
| **Message Types** | Text, Image, File | All message types |
| **Custom Messages** | Limited | Full support |
| **Offline Messages** | Basic | Advanced |
| **Message Reactions** | ❌ | ✅ |
| **Message Replies** | ❌ | ✅ |
| **Message Receipts** | ❌ | ✅ |
| **Channel Management** | Basic | Advanced |
| **User Management** | Basic | Advanced |
| **Real-time Typing** | ❌ | ✅ |
| **Message Search** | ❌ | ✅ |
| **File Size** | Small | Large |
| **Use Case** | Quick prototyping, simple chat | Enterprise IM, complex applications |
### Platform Support Matrix
| Platform | EasySDK | WuKongIMSDK | Notes |
| ----------------------- | ------- | ----------- | --------------------- |
| **iOS** | ✅ | ✅ | Swift/Objective-C |
| **Android** | ✅ | ✅ | Kotlin/Java |
| **Web** | ✅ | ✅ | JavaScript/TypeScript |
| **Flutter** | ✅ | ✅ | Cross-platform |
| **HarmonyOS** | ❌ | ✅ | ArkTS |
| **WeChat Mini Program** | ❌ | ✅ | JavaScript |
| **Uniapp** | ❌ | ✅ | Vue.js |
| **React Native** | 🚧 | 🚧 | Coming soon |
| **Unity** | 🚧 | 🚧 | Coming soon |
## Getting Started
### Quick Start with EasySDK
```bash theme={null}
# JavaScript/Web
npm install @wukongim/easysdk-js
# iOS (CocoaPods)
pod 'WuKongEasySDK'
# Android (Gradle)
implementation 'com.wukongim:easysdk-android:latest'
# Flutter
flutter pub add wukongim_easysdk
```
### Enterprise SDK Installation
```bash theme={null}
# JavaScript/Web
npm install @wukongim/sdk-js
# iOS (CocoaPods)
pod 'WuKongIMSDK'
# Android (Gradle)
implementation 'com.wukongim:sdk-android:latest'
# Flutter
flutter pub add wukongim_flutter_sdk
```
## Community and Ecosystem
### Official Repositories
* **Main Server**: [WuKongIM](https://github.com/WuKongIM/WuKongIM)
* **Documentation**: [WuKongIM Docs](https://github.com/WuKongIM/WuKongIM-Docs)
* **Demo Applications**: [WuKongIM Demo](https://github.com/WuKongIM/WuKongIMDemo)
### Community Projects
* **UI Components**: Community-contributed UI libraries
* **Plugins**: Third-party plugins and extensions
* **Templates**: Project templates and boilerplates
* **Tools**: Development tools and utilities
### Contributing
1. **Fork** the repository you want to contribute to
2. **Create** a feature branch: `git checkout -b feature/amazing-feature`
3. **Commit** your changes: `git commit -m 'Add amazing feature'`
4. **Push** to the branch: `git push origin feature/amazing-feature`
5. **Open** a Pull Request
### Code of Conduct
We are committed to providing a welcoming and inclusive environment for all contributors. Please read our [Code of Conduct](https://github.com/WuKongIM/WuKongIM/blob/main/CODE_OF_CONDUCT.md) before contributing.
## Next Steps
Get started with WuKongIM in 5 minutes
Comprehensive SDK documentation
Complete API documentation
Best practices and examples
# Advanced Features
Source: https://wukong.mintlify.app/en/sdk/wukongim/android/advance
WuKongIM Android SDK advanced features, including custom messages, message extensions, message receipts and message replies
Advanced features provide developers with the ability to extend WuKongIM Android SDK, including custom message types, message extensions, message receipts, message editing and message replies and other enterprise-level features.
In WuKongIM, all message types are custom messages
## Custom Messages
### Custom Regular Messages
Below we use a business card message as an example to show how to create custom message types.
#### Step 1: Define Message
Define a message object that inherits from `WKMessageContent` and specify the message type in the constructor.
Built-in message types in SDK can be viewed through `WKMsgContentType`
```java Java theme={null}
public class WKCardContent extends WKMessageContent {
public WKCardContent() {
type = 3; // Specify message type
}
// Define fields to send to the recipient
public String uid; // User ID
public String name; // Name
public String avatar; // Avatar
}
```
```kotlin Kotlin theme={null}
class WKCardContent : WKMessageContent() {
var uid: String = ""
var name: String = ""
var avatar: String = ""
init {
type = 3 // Specify message type
}
}
```
Note: Custom message objects must provide a parameterless constructor
#### Step 2: Encoding and Decoding
We need to send the three fields `uid`, `name`, `avatar` to the recipient. The final message content passed is:
```json theme={null}
{
"type": 3,
"uid": "xxxx",
"name": "xxx",
"avatar": "xxx"
}
```
Override the `encodeMsg` method of `WKMessageContent` to start encoding:
```java Java theme={null}
@Override
public JSONObject encodeMsg() {
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("uid", uid);
jsonObject.put("name", name);
jsonObject.put("avatar", avatar);
} catch (JSONException e) {
e.printStackTrace();
}
return jsonObject;
}
```
```kotlin Kotlin theme={null}
override fun encodeMsg(): JSONObject {
val jsonObject = JSONObject()
jsonObject.put("uid", uid)
jsonObject.put("name", name)
jsonObject.put("avatar", avatar)
return jsonObject
}
```
Override the `decodeMsg` method of `WKMessageContent` to start decoding:
```java Java theme={null}
@Override
public WKMessageContent decodeMsg(JSONObject jsonObject) {
uid = jsonObject.optString("uid");
name = jsonObject.optString("name");
avatar = jsonObject.optString("avatar");
return this;
}
```
```kotlin Kotlin theme={null}
override fun decodeMsg(jsonObject: JSONObject): WKMessageContent {
this.uid = jsonObject.optString("uid")
this.name = jsonObject.optString("name")
this.avatar = jsonObject.optString("avatar")
return this
}
```
When encoding and decoding messages, there's no need to consider the `type` field, as the SDK handles it internally
If you want to control the content displayed when this custom message is retrieved, you can override the `getDisplayContent` method:
```java Java theme={null}
@Override
public String getDisplayContent() {
return "[Business Card]";
}
```
```kotlin Kotlin theme={null}
override fun getDisplayContent(): String {
return "[Business Card]"
}
```
If you want this type of message to be searchable in global search, you can override the `getSearchableWord` method:
```java Java theme={null}
@Override
public String getSearchableWord() {
return "[Card]";
}
```
```kotlin Kotlin theme={null}
override fun getSearchableWord(): String {
return "[Card]"
}
```
#### Step 3: Register Message
```java Java theme={null}
WKIM.getInstance().getMsgManager().registerContentMsg(WKCardContent.class);
```
```kotlin Kotlin theme={null}
WKIM.getInstance().msgManager.registerContentMsg(WKCardContent::class.java)
```
Through these three steps, the custom regular message is complete. When receiving a message, if the type in `WKMsg` is 3, it indicates that the message is a business card message, where `baseContentMsgModel` is the custom `WKCardContent`. At this time, you can cast `baseContentMsgModel` to `WKCardContent` and render it on the UI.
Complete code reference: [Business Card Message](https://github.com/TangSengDaoDao/TangSengDaoDaoAndroid/blob/master/wkuikit/src/main/java/com/chat/uikit/chat/msgmodel/WKCardContent.java)
### Complete Business Card Message Implementation Example
```java theme={null}
public class WKCardContent extends WKMessageContent {
public String uid;
public String name;
public String avatar;
public String phone;
public String email;
public WKCardContent() {
type = 3;
}
public WKCardContent(String uid, String name, String avatar) {
this();
this.uid = uid;
this.name = name;
this.avatar = avatar;
}
@Override
public JSONObject encodeMsg() {
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("uid", uid);
jsonObject.put("name", name);
jsonObject.put("avatar", avatar);
if (!TextUtils.isEmpty(phone)) {
jsonObject.put("phone", phone);
}
if (!TextUtils.isEmpty(email)) {
jsonObject.put("email", email);
}
} catch (JSONException e) {
e.printStackTrace();
}
return jsonObject;
}
@Override
public WKMessageContent decodeMsg(JSONObject jsonObject) {
uid = jsonObject.optString("uid");
name = jsonObject.optString("name");
avatar = jsonObject.optString("avatar");
phone = jsonObject.optString("phone");
email = jsonObject.optString("email");
return this;
}
@Override
public String getDisplayContent() {
return String.format("[Business Card] %s", name);
}
@Override
public String getSearchableWord() {
return String.format("[Business Card] %s %s", name, phone != null ? phone : "");
}
// Validate if business card information is complete
public boolean isValid() {
return !TextUtils.isEmpty(uid) && !TextUtils.isEmpty(name);
}
}
// Register business card message
WKIM.getInstance().getMsgManager().registerContentMsg(WKCardContent.class);
// Send business card message
public void sendCardMessage(WKChannel channel, String uid, String name, String avatar) {
WKCardContent cardContent = new WKCardContent(uid, name, avatar);
if (cardContent.isValid()) {
WKIM.getInstance().getMsgManager().sendMessage(cardContent, channel);
}
}
```
### Custom Attachment Messages
Sometimes we need to send messages with attachments when sending messages. WuKongIM also provides custom attachment messages, which are not much different from regular messages. Below we use location messages as an example.
#### Step 1: Define Message
Note that custom attachment messages need to inherit from `WKMediaMessageContent` instead of `WKMessageContent`.
```java Java theme={null}
public class WKLocationContent extends WKMediaMessageContent {
// Define fields to send to the recipient
public double longitude; // Longitude
public double latitude; // Latitude
public String address; // Detailed address information
public WKLocationContent(double longitude, double latitude, String address) {
type = 6;
this.longitude = longitude;
this.latitude = latitude;
this.address = address;
}
// Must provide parameterless constructor here
public WKLocationContent() {
type = 6;
}
}
```
```kotlin Kotlin theme={null}
class WKLocationContent(var longitude: Double, var latitude: Double, var address: String) :
WKMediaMessageContent() {
init {
type = 6 // Specify message type
}
}
```
`WKMediaMessageContent` provides `url` and `localPath` fields, so custom messages don't need to define network address and local address fields again
#### Step 2: Encoding and Decoding
We need to send `longitude`, `latitude`, `address`, `url` information to the recipient. The final message content passed is:
```json theme={null}
{
"type": 6,
"longitude": 115.25,
"latitude": 39.26,
"url": "xxx",
"address": "xxx"
}
```
Override the `encodeMsg` method of `WKMessageContent` to start encoding:
```java Java theme={null}
@Override
public JSONObject encodeMsg() {
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("address", address);
jsonObject.put("latitude", latitude);
jsonObject.put("longitude", longitude);
jsonObject.put("url", url); // Location screenshot
jsonObject.put("localPath", localPath);
} catch (JSONException e) {
e.printStackTrace();
}
return jsonObject;
}
```
```kotlin Kotlin theme={null}
override fun encodeMsg(): JSONObject {
val jsonObject = JSONObject()
jsonObject.put("longitude", longitude)
jsonObject.put("latitude", latitude)
jsonObject.put("address", address)
jsonObject.put("url", url)
jsonObject.put("localPath", localPath)
return jsonObject
}
```
When encoding messages, you can write `localPath` local fields. After the SDK saves the message, the message sent to the recipient does not include this field
Override the `decodeMsg` method of `WKMessageContent` to start decoding:
```java Java theme={null}
@Override
public WKMessageContent decodeMsg(JSONObject jsonObject) {
latitude = jsonObject.optDouble("latitude");
longitude = jsonObject.optDouble("longitude");
address = jsonObject.optString("address");
url = jsonObject.optString("url");
if (jsonObject.has("localPath"))
localPath = jsonObject.optString("localPath");
return this;
}
```
```kotlin Kotlin theme={null}
override fun decodeMsg(jsonObject: JSONObject): WKMessageContent {
this.latitude = jsonObject.optDouble("latitude")
this.longitude = jsonObject.optDouble("longitude")
this.address = jsonObject.optString("address")
this.url = jsonObject.optString("url")
if (jsonObject.has("localPath"))
this.localPath = jsonObject.optString("localPath")
return this
}
```
When decoding messages, if decoding local fields, you need to check if the field exists, because received messages don't have local fields. For example, `localPath` is not available when receiving messages
#### Step 3: Register Message
```java Java theme={null}
WKIM.getInstance().getMsgManager().registerContentMsg(WKLocationContent.class);
```
```kotlin Kotlin theme={null}
WKIM.getInstance().msgManager.registerContentMsg(WKLocationContent::class.java)
```
## Message Extensions
As business develops, applications have increasingly more features in chat. To meet most requirements, WuKongIM has added message extension functionality. Message extensions are divided into `local extensions` and `remote extensions`. Local extensions are only for local app use and will be lost after uninstalling the app. Remote extensions are saved on the server and data will be restored after uninstalling and reinstalling.
### Local Extensions
Local extensions are the `localExtraMap` field in the message object `WKMsg`.
```java Java theme={null}
/**
* Modify message local extensions
*
* @param clientMsgNo Client ID
* @param hashExtra Extension fields
*/
WKIM.getInstance().getMsgManager().updateLocalExtraWithClientMsgNo(String clientMsgNo, HashMap hashExtra);
```
```kotlin Kotlin theme={null}
WKIM.getInstance().msgManager.updateLocalExtraWithClientMsgNo(clientMsgNo, hashExtra)
```
After successful update, the SDK will trigger a refresh message callback
### Remote Extensions
Remote extensions are the `remoteExtra` field in the message object `WKMsg`.
```java Java theme={null}
/**
* Save remote extensions
* @param channel Channel information
* @param list Remote extension data
*/
WKIM.getInstance().getMsgManager().saveRemoteExtraMsg(WKChannel channel, List list);
```
```kotlin Kotlin theme={null}
WKIM.getInstance().msgManager.saveRemoteExtraMsg(channel, list)
```
After successful update, the SDK will trigger a refresh message callback
## Message Read/Unread
Message read/unread is also called message receipts. Message receipt functionality can be set through settings.
```java Java theme={null}
WKMsgSetting setting = new WKMsgSetting();
setting.receipt = 1; // Enable receipts
WKSendOptions options = new WKSendOptions();
options.setting = setting;
// Send message
WKIM.getInstance().getMsgManager().sendWithOptions(contentModel, channel, options);
```
```kotlin Kotlin theme={null}
val setting = WKMsgSetting()
setting.receipt = 1 // Enable receipts
val options = WKSendOptions()
options.setting = setting
// Send message
WKIM.getInstance().msgManager.sendWithOptions(
wkBaseContentMsgModel, channel, options
)
```
When a logged-in user views messages sent by others, if the sender has enabled message receipts, the viewed messages need to be uploaded to the server to mark them as read. When the sender or yourself uploads read messages, the server will send a sync message extension cmd (command) message `syncMessageExtra`. At this time, you need to sync the latest message extensions and save them to the SDK.
## Message Editing
When we send a message to someone and find that the content is wrong, we don't need to recall and resend it. We just need to edit the message.
### Set Edit Content
```java Java theme={null}
/**
* Modify edit content
* @param msgID Message server ID
* @param channelID Channel ID
* @param channelType Channel type
* @param content Edited content
*/
WKIM.getInstance().getMsgManager().updateMsgEdit(String msgID, String channelID, byte channelType, String content);
```
```kotlin Kotlin theme={null}
WKIM.getInstance().msgManager.updateMsgEdit(msgID, channelID, channelType, content)
```
After changing the SDK message edit content, you need to upload the edited content to the server, which requires listening for upload message extensions.
### Listen for Upload Message Extensions
```java Java theme={null}
// Listen for upload message extensions
WKIM.getInstance().getMsgManager().addOnUploadMsgExtraListener(new IUploadMsgExtraListener() {
@Override
public void onUpload(WKMsgExtra msgExtra) {
// Upload to your own server
}
});
```
```kotlin Kotlin theme={null}
WKIM.getInstance().msgManager.addOnUploadMsgExtraListener(object : IUploadMsgExtraListener {
override fun onUpload(msgExtra: WKMsgExtra) {
// Upload to server
}
})
```
## Message Reply
In chat, if there are too many messages, sending message replies will make the messages very messy and hard to follow. At this time, you need to make specific replies to certain messages, which is message reply.
When sending a message, you just need to assign the `WKReply` object in the message content `WKMessageContent` to achieve the message reply effect.
### WKReply Object Core Fields
```java theme={null}
public class WKReply {
// Root message ID of the replied message, the first reply message ID in multi-level replies
public String root_mid;
// Replied message ID
public String message_id;
// Replied MessageSeq
public long message_seq;
// Replied user uid
public String from_uid;
// Replied user name
public String from_name;
// Replied message body
public WKMessageContent payload;
// Edited content of replied message
public String contentEdit;
// Edited message entity of replied message
public WKMessageContent contentEditMsgModel;
// Edit time
public long editAt;
}
```
## Message Reactions (Likes)
### Save Message Reactions
```java Java theme={null}
// Save message reactions
WKIM.getInstance().getMsgManager().saveMessageReactions(List list)
```
```kotlin Kotlin theme={null}
// Save message reactions
WKIM.getInstance().msgManager.saveMessageReactions(list)
```
The same user can only make one reaction to the same message. Repeated reactions with different emojis to the same message will be treated as modifying the reaction, while repeated reactions with the same emoji will be treated as deleting the reaction. After the SDK updates message reactions, it will trigger a message refresh event. The app needs to listen for this event and refresh the UI.
### Get Message Reactions
```java Java theme={null}
// Get reactions for a message
WKIM.getInstance().getMsgManager().getMsgReactions(String messageID);
```
```kotlin Kotlin theme={null}
// Get reactions for a message
WKIM.getInstance().msgManager.getMsgReactions(messageID)
```
## Next Steps
Return to data source configuration
Return to message management functionality
Return to basic features
Return to channel management
# Basic Features
Source: https://wukong.mintlify.app/en/sdk/wukongim/android/base
WuKongIM Android SDK basic functionality, including initialization, connection management and status monitoring
## Initialization
Initialize the SDK in the Application's onCreate method:
```java Java theme={null}
/**
* Initialize IM
* @param context Application Context
* @param uid Login user ID (uid registered with IM communication end by business server)
* @param token Login user token (token registered with IM communication end by business server)
*/
WKIM.getInstance().init(context, uid, token);
```
```kotlin Kotlin theme={null}
WKIM.getInstance().init(context, uid, token)
```
### Advanced Initialization
You can also initialize with custom options:
```java Java theme={null}
WKIMOptions options = new WKIMOptions();
options.setLogLevel(WKLogLevel.DEBUG);
options.setDbPassword("your_db_password");
options.setFileUploadUrl("https://your-upload-server.com/upload");
WKIM.getInstance().init(context, uid, token, options);
```
```kotlin Kotlin theme={null}
val options = WKIMOptions().apply {
logLevel = WKLogLevel.DEBUG
dbPassword = "your_db_password"
fileUploadUrl = "https://your-upload-server.com/upload"
}
WKIM.getInstance().init(context, uid, token, options)
```
## Server Configuration
Listen for events to get connection server IP and Port:
```java Java theme={null}
WKIM.getInstance().getConnectionManager().addOnGetIpAndPortListener(new IGetIpAndPort() {
@Override
public void getIP(IGetSocketIpAndPortListener iGetSocketIpAndPortListener) {
// Return connection IP and port
iGetSocketIpAndPortListener.onGetSocketIpAndPort("xxx.xxx.xxx.xxx", 5100);
}
});
```
```kotlin Kotlin theme={null}
WKIM.getInstance().connectionManager.addOnGetIpAndPortListener { listener ->
listener?.onGetSocketIpAndPort(
"172.0.0.1",
5100
)
}
```
Return the IP of the IM communication end and the TCP port of the IM communication end. **For distributed systems, call the interface to get IP and Port before returning**.
## Connection Management
### Connect
```java Java theme={null}
// Connect to IM
WKIM.getInstance().getConnectionManager().connection();
```
```kotlin Kotlin theme={null}
// Connect to IM
WKIM.getInstance().connectionManager.connection()
```
### Disconnect
```java Java theme={null}
// Disconnect IM
WKIM.getInstance().getConnectionManager().disconnect(isLogout);
```
```kotlin Kotlin theme={null}
// Disconnect IM
WKIM.getInstance().connectionManager.disconnect(isLogout)
```
**Parameters:**
* `isLogout`:
* `true`: SDK will no longer reconnect
* `false`: SDK maintains reconnection mechanism
### Connection Status Monitoring
```java Java theme={null}
WKIM.getInstance().getConnectionManager().addOnConnectionStatusListener("key", new IConnectionStatus() {
@Override
public void onStatus(int status, String reason) {
switch (status) {
case WKConnectStatus.success:
// Connection successful
break;
case WKConnectStatus.failed:
// Connection failed
break;
case WKConnectStatus.connecting:
// Connecting
break;
case WKConnectStatus.syncMsg:
// Syncing messages
break;
case WKConnectStatus.noNetwork:
// No network
break;
case WKConnectStatus.kicked:
// Kicked offline - need to exit app and return to login page
break;
}
}
});
```
```kotlin Kotlin theme={null}
WKIM.getInstance().connectionManager.addOnConnectionStatusListener("key") { status, reason ->
when (status) {
WKConnectStatus.success -> {
// Connection successful
}
WKConnectStatus.failed -> {
// Connection failed
}
WKConnectStatus.connecting -> {
// Connecting
}
WKConnectStatus.syncMsg -> {
// Syncing messages
}
WKConnectStatus.noNetwork -> {
// No network
}
WKConnectStatus.kicked -> {
// Kicked offline
}
}
}
```
### Remove Connection Status Listener
```java Java theme={null}
// Remove specific listener
WKIM.getInstance().getConnectionManager().removeOnConnectionStatusListener("key");
// Remove all listeners
WKIM.getInstance().getConnectionManager().removeAllOnConnectionStatusListener();
```
```kotlin Kotlin theme={null}
// Remove specific listener
WKIM.getInstance().connectionManager.removeOnConnectionStatusListener("key")
// Remove all listeners
WKIM.getInstance().connectionManager.removeAllOnConnectionStatusListener()
```
## Connection Status Types
| Status | Description |
| ---------------------------- | -------------------------------- |
| `WKConnectStatus.success` | Connection successful |
| `WKConnectStatus.failed` | Connection failed |
| `WKConnectStatus.connecting` | Connecting to server |
| `WKConnectStatus.syncMsg` | Syncing messages |
| `WKConnectStatus.noNetwork` | No network available |
| `WKConnectStatus.kicked` | Kicked offline by another device |
## Best Practices
### 1. Application Lifecycle Management
```java Java theme={null}
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Initialize SDK
WKIM.getInstance().init(this, "user123", "user-token");
// Setup connection listener
setupConnectionListener();
// Setup server configuration
setupServerConfig();
}
private void setupConnectionListener() {
WKIM.getInstance().getConnectionManager().addOnConnectionStatusListener("app",
(status, reason) -> {
Log.d("WuKongIM", "Connection status: " + status + ", reason: " + reason);
if (status == WKConnectStatus.kicked) {
// Handle being kicked offline
handleKickedOffline();
}
});
}
private void setupServerConfig() {
WKIM.getInstance().getConnectionManager().addOnGetIpAndPortListener(listener -> {
// In production, get from your server
listener.onGetSocketIpAndPort("your-server.com", 5100);
});
}
private void handleKickedOffline() {
// Redirect to login screen
Intent intent = new Intent(this, LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
}
}
```
```kotlin Kotlin theme={null}
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize SDK
WKIM.getInstance().init(this, "user123", "user-token")
// Setup connection listener
setupConnectionListener()
// Setup server configuration
setupServerConfig()
}
private fun setupConnectionListener() {
WKIM.getInstance().connectionManager.addOnConnectionStatusListener("app") { status, reason ->
Log.d("WuKongIM", "Connection status: $status, reason: $reason")
if (status == WKConnectStatus.kicked) {
// Handle being kicked offline
handleKickedOffline()
}
}
}
private fun setupServerConfig() {
WKIM.getInstance().connectionManager.addOnGetIpAndPortListener { listener ->
// In production, get from your server
listener?.onGetSocketIpAndPort("your-server.com", 5100)
}
}
private fun handleKickedOffline() {
// Redirect to login screen
val intent = Intent(this, LoginActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
}
startActivity(intent)
}
}
```
### 2. Activity Lifecycle Integration
```java Java theme={null}
public class MainActivity extends AppCompatActivity {
@Override
protected void onResume() {
super.onResume();
// Connect when app comes to foreground
if (!WKIM.getInstance().getConnectionManager().isConnected()) {
WKIM.getInstance().getConnectionManager().connection();
}
}
@Override
protected void onPause() {
super.onPause();
// Optionally disconnect when app goes to background
// WKIM.getInstance().getConnectionManager().disconnect(false);
}
}
```
```kotlin Kotlin theme={null}
class MainActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
// Connect when app comes to foreground
if (!WKIM.getInstance().connectionManager.isConnected()) {
WKIM.getInstance().connectionManager.connection()
}
}
override fun onPause() {
super.onPause()
// Optionally disconnect when app goes to background
// WKIM.getInstance().connectionManager.disconnect(false)
}
}
```
### 3. Network State Monitoring
```java Java theme={null}
public class NetworkMonitor extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting();
if (isConnected) {
// Network available, try to connect
WKIM.getInstance().getConnectionManager().connection();
} else {
// Network unavailable
Log.d("WuKongIM", "Network unavailable");
}
}
}
```
```kotlin Kotlin theme={null}
class NetworkMonitor : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetwork = cm.activeNetworkInfo
val isConnected = activeNetwork?.isConnectedOrConnecting == true
if (isConnected) {
// Network available, try to connect
WKIM.getInstance().connectionManager.connection()
} else {
// Network unavailable
Log.d("WuKongIM", "Network unavailable")
}
}
}
```
## Troubleshooting
### Common Issues
1. **Connection Failed**
* Check if server IP and port are correct
* Verify network connectivity
* Ensure user credentials are valid
2. **Frequent Disconnections**
* Check network stability
* Verify server availability
* Review connection timeout settings
3. **Kicked Offline**
* Handle gracefully by redirecting to login
* Clear user session data
* Notify user about the logout
## Next Steps
Learn how to handle message sending and receiving
Manage channels and groups
Handle conversation lists
Explore advanced features and configuration
# Channel Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/android/channel
WuKongIM Android SDK channel management functionality, including channel information retrieval, updates and monitoring
Channel is an abstract concept in WuKongIM. Messages are first sent to channels, and channels deliver messages according to their configuration rules. Channels are divided into channel and channel details.
Need to implement channel information data source: [Channel Information Data Source](/en/sdk/wukongim/android/datasource#channel-information-data-source)
## Channel Information Management
### Get Channel Information
Get channel information, first from memory, then from database if not available:
```java Java theme={null}
// Get channel information - first from memory, then from database if not available
WKIM.getInstance().getChannelManager().getChannel(String channelID, byte channelType);
```
```kotlin Kotlin theme={null}
// Get channel information - first from memory, then from database if not available
WKIM.getInstance().channelManager.getChannel(channelID, channelType)
```
### Force Refresh Channel Information
Get channel information from remote server:
```java Java theme={null}
// Get channel information from remote server
WKIM.getInstance().getChannelManager().fetchChannelInfo(String channelID, byte channelType);
```
```kotlin Kotlin theme={null}
// Get channel information from remote server
WKIM.getInstance().channelManager.fetchChannelInfo(channelID, channelType)
```
### Complete Usage Example
```java theme={null}
public class ChatActivity extends AppCompatActivity {
private String channelID;
private byte channelType;
private WKChannel currentChannel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
// Add channel refresh listener
WKIM.getInstance().getChannelManager().addOnRefreshChannelInfo("ChatActivity", new IRefreshChannel() {
@Override
public void onRefreshChannel(WKChannel channel, boolean isEnd) {
if (channel.channelID.equals(channelID) && channel.channelType == channelType) {
runOnUiThread(() -> {
currentChannel = channel;
updateChannelUI();
});
}
}
});
// Load channel information
loadChannelInfo();
}
private void loadChannelInfo() {
// First get from local
currentChannel = WKIM.getInstance().getChannelManager().getChannel(channelID, channelType);
if (currentChannel != null) {
// Local data available, use directly
updateChannelUI();
} else {
// No local data, fetch from server
WKIM.getInstance().getChannelManager().fetchChannelInfo(channelID, channelType);
showLoadingState();
}
}
private void updateChannelUI() {
// Update title
setTitle(currentChannel.channelRemark != null ? currentChannel.channelRemark : currentChannel.channelName);
// Update avatar
loadAvatar(currentChannel.avatar);
// Update top status
updateTopStatus(currentChannel.top == 1);
// Update mute status
updateMuteStatus(currentChannel.mute == 1);
hideLoadingState();
}
@Override
protected void onDestroy() {
super.onDestroy();
// Remove listeners
WKIM.getInstance().getChannelManager().removeRefreshChannelInfo("ChatActivity");
}
}
```
## Event Listening
### Channel Information Refresh Listener
```java Java theme={null}
// Listen for channel refresh events
WKIM.getInstance().getChannelManager().addOnRefreshChannelInfo("key", new IRefreshChannel() {
@Override
public void onRefreshChannel(WKChannel channel, boolean isEnd) {
// Handle channel information update
runOnUiThread(() -> {
updateChannelInfo(channel);
});
}
});
// Remove listener
WKIM.getInstance().getChannelManager().removeRefreshChannelInfo("key");
```
```kotlin Kotlin theme={null}
// Listen for channel refresh events
WKIM.getInstance().channelManager.addOnRefreshChannelInfo("key", object : IRefreshChannel {
override fun onRefreshChannel(channel: WKChannel, isEnd: Boolean) {
// Handle channel information update
runOnUiThread {
updateChannelInfo(channel)
}
}
})
// Remove listener
WKIM.getInstance().channelManager.removeRefreshChannelInfo("key")
```
key is the unique identifier for the listener, can be any string. The same key must be passed when adding and removing listeners
### Channel Avatar Update Listener
```java Java theme={null}
// Listen for channel avatar update events
WKIM.getInstance().getChannelManager().addOnRefreshChannelAvatar(new IRefreshChannelAvatar() {
@Override
public void onRefreshChannelAvatar(String channelID, byte channelType) {
// Avatar needs local modification
String key = UUID.randomUUID().toString().replace("-", "");
WKIM.getInstance().getChannelManager().updateAvatarCacheKey(channelID, channelType, key);
// Refresh avatar in UI
runOnUiThread(() -> {
refreshChannelAvatar(channelID, channelType);
});
}
});
```
```kotlin Kotlin theme={null}
// Listen for channel avatar update events
WKIM.getInstance().channelManager.addOnUpdateChannelAvatar("key", object : IRefreshChannelAvatar {
override fun onRefreshChannelAvatar(channelID: String, channelType: Int) {
// Avatar needs local modification
val key = UUID.randomUUID().toString().replace("-", "")
WKIM.getInstance().channelManager.updateAvatarCacheKey(channelID, channelType, key)
// Refresh avatar in UI
runOnUiThread {
refreshChannelAvatar(channelID, channelType)
}
}
})
// Remove listener
WKIM.getInstance().channelManager.removeUpdateChannelAvatar("key")
```
## Common Operations
### Update Remark
```java Java theme={null}
// Update channel remark
WKIM.getInstance().getChannelManager().updateRemark(String channelID, byte channelType, String remark);
```
```kotlin Kotlin theme={null}
// Update channel remark
WKIM.getInstance().channelManager.updateRemark(channelID, channelType, remark)
```
### Pin/Unpin Channel
```java Java theme={null}
// Pin channel: 1=pin, 0=unpin
WKIM.getInstance().getChannelManager().updateTop(String channelID, byte channelType, int isTop);
```
```kotlin Kotlin theme={null}
// Pin channel
WKIM.getInstance().channelManager.setTopChannel(channelID, channelType, isTop)
```
### Save Channel Information
```java Java theme={null}
// Save channel information
WKIM.getInstance().getChannelManager().saveOrUpdateChannel(WKChannel channel);
// Batch save channel information
WKIM.getInstance().getChannelManager().saveOrUpdateChannels(List list);
```
```kotlin Kotlin theme={null}
// Save channel information
WKIM.getInstance().channelManager.saveOrUpdateChannel(channel)
// Batch save channel information
WKIM.getInstance().channelManager.saveOrUpdateChannels(list)
```
## WKChannel Data Structure
### Channel Properties
```java theme={null}
public class WKChannel {
// Channel ID
public String channelID;
// Channel type: 1=personal chat, 2=group chat
public byte channelType;
// Channel name
public String channelName;
// Channel remark (personal remark or group alias)
public String channelRemark;
// Channel avatar
public String avatar;
// Is pinned
public int top;
// Do not disturb
public int mute;
// Is forbidden
public int forbidden;
// Remote extensions
public HashMap remoteExtraMap;
// Local extension fields
public HashMap extraMap;
}
```
### Property Description
| Property | Type | Description |
| ---------------- | ------- | ----------------------------------------------- |
| `channelID` | String | Channel unique identifier |
| `channelType` | byte | Channel type (1=personal, 2=group) |
| `channelName` | String | Channel name |
| `channelRemark` | String | Channel remark (personal remark or group alias) |
| `avatar` | String | Channel avatar URL |
| `top` | int | Is pinned (1=pinned, 0=not pinned) |
| `mute` | int | Do not disturb (1=muted, 0=not muted) |
| `forbidden` | int | Is forbidden (1=forbidden, 0=not forbidden) |
| `remoteExtraMap` | HashMap | Remote extension fields |
| `extraMap` | HashMap | Local extension fields |
## Best Practices
### 1. Channel Information Caching Strategy
```java theme={null}
public class ChannelInfoManager {
private Map channelCache = new ConcurrentHashMap<>();
public WKChannel getChannelWithCache(String channelID, byte channelType) {
String key = channelID + "_" + channelType;
// First get from memory cache
WKChannel channel = channelCache.get(key);
if (channel != null) {
return channel;
}
// Get from SDK
channel = WKIM.getInstance().getChannelManager().getChannel(channelID, channelType);
if (channel != null) {
channelCache.put(key, channel);
return channel;
}
// Trigger network request
WKIM.getInstance().getChannelManager().fetchChannelInfo(channelID, channelType);
return null;
}
public void updateChannelCache(WKChannel channel) {
String key = channel.channelID + "_" + channel.channelType;
channelCache.put(key, channel);
}
public void clearCache() {
channelCache.clear();
}
}
```
### 2. Channel Status Management
```java theme={null}
public class ChannelStatusHelper {
public static String getDisplayName(WKChannel channel) {
// Prioritize remark, show name if no remark
return !TextUtils.isEmpty(channel.channelRemark) ?
channel.channelRemark : channel.channelName;
}
public static boolean isTopChannel(WKChannel channel) {
return channel.top == 1;
}
public static boolean isMuteChannel(WKChannel channel) {
return channel.mute == 1;
}
public static boolean isForbiddenChannel(WKChannel channel) {
return channel.forbidden == 1;
}
public static String getChannelTypeText(byte channelType) {
switch (channelType) {
case 1:
return "Personal";
case 2:
return "Group";
default:
return "Unknown";
}
}
}
```
## Next Steps
Learn how to manage channel members
Handle conversation lists and unread messages
Configure channel data sources
Return to message handling functionality
# Channel Member Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/android/channel-member
WuKongIM Android SDK channel member management functionality, including member retrieval, search and management
The channel member manager is responsible for managing member information within channels, including getting member lists, searching members, modifying member remarks, and other functions.
Need to implement channel member data source: [Channel Member Data Source](/en/sdk/wukongim/android/datasource#channel-member-data-source)
## Get Channel Members
### Get All Members
```java Java theme={null}
// Get all members in a channel
WKIM.getInstance().getChannelMembersManager().getMembers(channelId, channelType);
```
```kotlin Kotlin theme={null}
// Get all members in a channel
WKIM.getInstance().channelMembersManager.getMembers(channelId, channelType)
```
### Get Single Member
```java Java theme={null}
// Get single channel member
WKIM.getInstance().getChannelMembersManager().getMember(channelId, channelType, uid);
```
```kotlin Kotlin theme={null}
// Get single channel member
WKIM.getInstance().channelMembersManager.getMember(channelId, channelType, uid)
```
### Complete Usage Example
```java theme={null}
public class GroupMembersActivity extends AppCompatActivity {
private String channelId;
private byte channelType;
private List memberList = new ArrayList<>();
private MemberAdapter memberAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_group_members);
setupRecyclerView();
loadMembers();
}
private void loadMembers() {
// Get all channel members
List members = WKIM.getInstance().getChannelMembersManager()
.getMembers(channelId, channelType);
if (members != null && !members.isEmpty()) {
memberList.clear();
memberList.addAll(members);
memberAdapter.notifyDataSetChanged();
updateMemberCount(members.size());
} else {
// No local data, may need to sync from server
showEmptyState();
}
}
private void getMemberInfo(String uid) {
WKChannelMember member = WKIM.getInstance().getChannelMembersManager()
.getMember(channelId, channelType, uid);
if (member != null) {
showMemberProfile(member);
} else {
showToast("Member information not found");
}
}
private void updateMemberCount(int count) {
setTitle("Group Members (" + count + ")");
}
}
```
## Search Members
### Paginated Member Search
```java Java theme={null}
// Search channel member list
WKIM.getInstance().getChannelMembersManager().getWithPageOrSearch(
channelId,
channelType,
"keyword",
1, // Page number
20, // Items per page
(list, isRemote) -> {
// list: member list
// isRemote: whether data is from remote
runOnUiThread(() -> {
handleSearchResult(list, isRemote);
});
}
);
```
```kotlin Kotlin theme={null}
// Search channel member list
WKIM.getInstance().channelMembersManager.getWithPageOrSearch(
channelId,
channelType,
"keyword",
1, // Page number
20 // Items per page
) { list, isRemote ->
// list: member list
// isRemote: whether data is from remote
runOnUiThread {
handleSearchResult(list, isRemote)
}
}
```
### Search Functionality Example
```java theme={null}
public class MemberSearchActivity extends AppCompatActivity {
private EditText searchEditText;
private RecyclerView searchResultRecyclerView;
private List searchResults = new ArrayList<>();
private MemberAdapter searchAdapter;
private String channelId;
private byte channelType;
private int currentPage = 1;
private boolean isLoading = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_member_search);
setupViews();
setupSearch();
}
private void setupSearch() {
searchEditText.addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
String keyword = s.toString().trim();
if (!TextUtils.isEmpty(keyword)) {
searchMembers(keyword);
} else {
clearSearchResults();
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
});
}
private void searchMembers(String keyword) {
if (isLoading) return;
isLoading = true;
showLoadingIndicator();
WKIM.getInstance().getChannelMembersManager().getWithPageOrSearch(
channelId,
channelType,
keyword,
1, // Start from first page for new search
20,
(list, isRemote) -> {
runOnUiThread(() -> {
isLoading = false;
hideLoadingIndicator();
if (list != null && !list.isEmpty()) {
searchResults.clear();
searchResults.addAll(list);
searchAdapter.notifyDataSetChanged();
showSearchResults();
} else {
showNoResultsState();
}
});
}
);
}
private void loadMoreMembers(String keyword) {
if (isLoading) return;
isLoading = true;
currentPage++;
WKIM.getInstance().getChannelMembersManager().getWithPageOrSearch(
channelId,
channelType,
keyword,
currentPage,
20,
(list, isRemote) -> {
runOnUiThread(() -> {
isLoading = false;
if (list != null && !list.isEmpty()) {
int oldSize = searchResults.size();
searchResults.addAll(list);
searchAdapter.notifyItemRangeInserted(oldSize, list.size());
}
});
}
);
}
}
```
## Common Operations
### Save Channel Members
```java Java theme={null}
// Batch save members
WKIM.getInstance().getChannelMembersManager().save(List list);
```
```kotlin Kotlin theme={null}
// Batch save members
WKIM.getInstance().channelMembersManager.save(list)
```
### Update Member Remark
```java Java theme={null}
// Update remark
WKIM.getInstance().getChannelMembersManager().updateRemarkName(channelId, channelType, uid, remark);
```
```kotlin Kotlin theme={null}
// Update remark
WKIM.getInstance().channelMembersManager.updateRemarkName(channelId, channelType, uid, remark)
```
### Operations Example
```java theme={null}
public class MemberManagementHelper {
// Batch add members
public void addMembers(String channelId, byte channelType, List newMembers) {
// Save to local database
WKIM.getInstance().getChannelMembersManager().save(newMembers);
// Also call server API
ApiManager.addChannelMembers(channelId, channelType, newMembers,
new ApiCallback() {
@Override
public void onSuccess(Void result) {
// Add successful, local data already saved
notifyMembersChanged();
}
@Override
public void onError(int code, String message) {
// Add failed, may need to rollback local data
handleAddMembersError(code, message);
}
});
}
// Update member remark
public void updateMemberRemark(String channelId, byte channelType, String uid, String newRemark) {
// Update local remark
WKIM.getInstance().getChannelMembersManager().updateRemarkName(channelId, channelType, uid, newRemark);
// Sync to server
ApiManager.updateMemberRemark(channelId, channelType, uid, newRemark,
new ApiCallback() {
@Override
public void onSuccess(Void result) {
// Remark update successful
notifyMemberRemarkChanged(uid, newRemark);
}
@Override
public void onError(int code, String message) {
// Update failed, restore original remark
WKChannelMember member = WKIM.getInstance().getChannelMembersManager()
.getMember(channelId, channelType, uid);
if (member != null) {
WKIM.getInstance().getChannelMembersManager()
.updateRemarkName(channelId, channelType, uid, member.memberRemark);
}
}
});
}
// Get online members
public List getOnlineMembers(String channelId, byte channelType) {
List allMembers = WKIM.getInstance().getChannelMembersManager()
.getMembers(channelId, channelType);
List onlineMembers = new ArrayList<>();
if (allMembers != null) {
for (WKChannelMember member : allMembers) {
if (member.status == 1) { // 1 means normal status
onlineMembers.add(member);
}
}
}
return onlineMembers;
}
// Get admin members
public List getAdminMembers(String channelId, byte channelType) {
List allMembers = WKIM.getInstance().getChannelMembersManager()
.getMembers(channelId, channelType);
List adminMembers = new ArrayList<>();
if (allMembers != null) {
for (WKChannelMember member : allMembers) {
if (member.role > 0) { // role > 0 means has admin privileges
adminMembers.add(member);
}
}
}
return adminMembers;
}
}
```
## WKChannelMember Data Structure
### Member Properties
```java theme={null}
public class WKChannelMember {
// Auto-increment ID
public long id;
// Channel ID
public String channelID;
// Channel type
public byte channelType;
// Member ID
public String memberUID;
// Member name
public String memberName;
// Member remark
public String memberRemark;
// Member avatar
public String memberAvatar;
// Member role
public int role;
// Member status (blacklist etc.) 1: normal 2: blacklist
public int status;
// Is deleted
public int isDeleted;
// Creation time
public String createdAt;
// Update time
public String updatedAt;
// Version
public long version;
// Robot 0: no 1: yes
public int robot;
// Extension fields
public HashMap extraMap;
// User remark
public String remark;
// Inviter UID
public String memberInviteUID;
// Forbidden expiration time
public long forbiddenExpirationTime;
public String memberAvatarCacheKey;
}
```
### Property Description
| Property | Type | Description |
| ------------------------- | ------ | ---------------------------------------- |
| `id` | long | Auto-increment ID |
| `channelID` | String | Channel ID |
| `channelType` | byte | Channel type |
| `memberUID` | String | Member user ID |
| `memberName` | String | Member name |
| `memberRemark` | String | Member remark |
| `memberAvatar` | String | Member avatar URL |
| `role` | int | Member role (0=regular member, >0=admin) |
| `status` | int | Member status (1=normal, 2=blacklist) |
| `isDeleted` | int | Is deleted (0=normal, 1=deleted) |
| `robot` | int | Is robot (0=no, 1=yes) |
| `remark` | String | User remark |
| `memberInviteUID` | String | Inviter UID |
| `forbiddenExpirationTime` | long | Forbidden expiration timestamp |
## Next Steps
Learn how to manage conversation lists
Configure channel member data sources
Return to channel management functionality
Explore advanced features and configuration
# Command Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/android/cmd
WuKongIM Android SDK command management functionality, handling command messages sent from the server
CMD (Command) messages are special message types that can only be sent from the server to the client for parsing, used to implement server-to-client control instructions and system notifications.
## Listen for CMD Messages
### Basic Listening
```java Java theme={null}
WKIM.getInstance().getCMDManager().addCmdListener("key", cmd -> {
// Handle cmd message
handleCommand(cmd);
});
// Remove listener
WKIM.getInstance().getCMDManager().removeCmdListener("key");
```
```kotlin Kotlin theme={null}
WKIM.getInstance().cmdManager.addCmdListener("key") { cmd ->
// Handle cmd message
handleCommand(cmd)
}
// Remove listener
WKIM.getInstance().cmdManager.removeCmdListener("key")
```
### Complete Usage Example
```java theme={null}
public class CommandManager {
private static final String LISTENER_KEY = "CommandManager";
public void initialize() {
// Add command listener
WKIM.getInstance().getCMDManager().addCmdListener(LISTENER_KEY, this::handleCommand);
}
private void handleCommand(WKCMD cmd) {
if (cmd == null || cmd.cmdKey == null) {
return;
}
Log.d("CommandManager", "Received command: " + cmd.cmdKey);
switch (cmd.cmdKey) {
case "user_status_change":
handleUserStatusChange(cmd.paramJsonObject);
break;
case "group_member_update":
handleGroupMemberUpdate(cmd.paramJsonObject);
break;
case "system_notification":
handleSystemNotification(cmd.paramJsonObject);
break;
case "force_logout":
handleForceLogout(cmd.paramJsonObject);
break;
case "message_recall":
handleMessageRecall(cmd.paramJsonObject);
break;
case "typing_status":
handleTypingStatus(cmd.paramJsonObject);
break;
case "online_status":
handleOnlineStatus(cmd.paramJsonObject);
break;
default:
Log.w("CommandManager", "Unknown command type: " + cmd.cmdKey);
handleUnknownCommand(cmd);
break;
}
}
// Handle user status change
private void handleUserStatusChange(JSONObject params) {
try {
String userId = params.getString("user_id");
int status = params.getInt("status");
// Update user status
UserStatusManager.updateUserStatus(userId, status);
// Notify UI update
EventBus.getDefault().post(new UserStatusChangedEvent(userId, status));
} catch (JSONException e) {
Log.e("CommandManager", "Failed to parse user status change command", e);
}
}
// Handle group member update
private void handleGroupMemberUpdate(JSONObject params) {
try {
String groupId = params.getString("group_id");
String action = params.getString("action"); // add, remove, update
JSONArray members = params.getJSONArray("members");
switch (action) {
case "add":
// Add group members
handleGroupMemberAdd(groupId, members);
break;
case "remove":
// Remove group members
handleGroupMemberRemove(groupId, members);
break;
case "update":
// Update group member info
handleGroupMemberInfoUpdate(groupId, members);
break;
}
} catch (JSONException e) {
Log.e("CommandManager", "Failed to parse group member update command", e);
}
}
// Handle system notification
private void handleSystemNotification(JSONObject params) {
try {
String title = params.getString("title");
String content = params.getString("content");
String type = params.optString("type", "info");
// Show system notification
NotificationHelper.showSystemNotification(title, content, type);
} catch (JSONException e) {
Log.e("CommandManager", "Failed to parse system notification command", e);
}
}
// Handle force logout
private void handleForceLogout(JSONObject params) {
try {
String reason = params.optString("reason", "Account logged in from another device");
// Clear local data
clearLocalData();
// Disconnect
WKIM.getInstance().getConnectionManager().disconnect();
// Navigate to login page
Intent intent = new Intent(context, LoginActivity.class);
intent.putExtra("logout_reason", reason);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
context.startActivity(intent);
} catch (Exception e) {
Log.e("CommandManager", "Failed to handle force logout command", e);
}
}
// Handle message recall
private void handleMessageRecall(JSONObject params) {
try {
String messageId = params.getString("message_id");
String channelId = params.getString("channel_id");
byte channelType = (byte) params.getInt("channel_type");
// Update local message status
MessageManager.recallMessage(messageId, channelId, channelType);
// Notify UI update
EventBus.getDefault().post(new MessageRecalledEvent(messageId, channelId, channelType));
} catch (JSONException e) {
Log.e("CommandManager", "Failed to parse message recall command", e);
}
}
// Handle typing status
private void handleTypingStatus(JSONObject params) {
try {
String channelId = params.getString("channel_id");
byte channelType = (byte) params.getInt("channel_type");
String userId = params.getString("user_id");
boolean isTyping = params.getBoolean("is_typing");
// Update typing status
TypingStatusManager.updateTypingStatus(channelId, channelType, userId, isTyping);
// Notify UI update
EventBus.getDefault().post(new TypingStatusEvent(channelId, channelType, userId, isTyping));
} catch (JSONException e) {
Log.e("CommandManager", "Failed to parse typing status command", e);
}
}
// Handle online status
private void handleOnlineStatus(JSONObject params) {
try {
JSONArray users = params.getJSONArray("users");
for (int i = 0; i < users.length(); i++) {
JSONObject user = users.getJSONObject(i);
String userId = user.getString("user_id");
boolean isOnline = user.getBoolean("is_online");
long lastSeen = user.optLong("last_seen", 0);
// Update online status
OnlineStatusManager.updateOnlineStatus(userId, isOnline, lastSeen);
}
// Notify UI update
EventBus.getDefault().post(new OnlineStatusUpdatedEvent());
} catch (JSONException e) {
Log.e("CommandManager", "Failed to parse online status command", e);
}
}
// Handle unknown command
private void handleUnknownCommand(WKCMD cmd) {
// Log unknown command for debugging and extension
Log.w("CommandManager", "Received unknown command: " + cmd.cmdKey + ", params: " + cmd.paramJsonObject);
// Can send to server for statistics
AnalyticsManager.trackUnknownCommand(cmd.cmdKey);
}
// Clean up resources
public void destroy() {
WKIM.getInstance().getCMDManager().removeCmdListener(LISTENER_KEY);
}
// Clear local data
private void clearLocalData() {
// Clear user info
UserManager.clearUserData();
// Clear conversation list
ConversationManager.clearConversations();
// Clear message cache
MessageManager.clearMessageCache();
// Clear other local data
PreferenceManager.clearAllData();
}
}
```
## WKCMD Data Structure
### Command Object Properties
```java theme={null}
public class WKCMD {
// Command ID
public String cmdKey;
// Command parameters
public JSONObject paramJsonObject;
}
```
### Property Description
| Property | Type | Description |
| ----------------- | ---------- | -------------------------------------------------------------------------- |
| `cmdKey` | String | Command unique identifier, used to distinguish different types of commands |
| `paramJsonObject` | JSONObject | Command parameters, containing data required for command execution |
## Common Command Types
### System Commands
| Command Type | Description | Parameter Example |
| ---------------------- | --------------------------- | --------------------------------------------------------------------------------- |
| `force_logout` | Force logout | `{"reason": "Account logged in from another device"}` |
| `system_notification` | System notification | `{"title": "System Maintenance", "content": "System will be maintained tonight"}` |
| `server_config_update` | Server configuration update | `{"config_key": "max_file_size", "value": "100MB"}` |
### User-Related Commands
| Command Type | Description | Parameter Example |
| -------------------- | -------------------- | ---------------------------------------------------- |
| `user_status_change` | User status change | `{"user_id": "123", "status": 1}` |
| `online_status` | Online status update | `{"users": [{"user_id": "123", "is_online": true}]}` |
| `user_info_update` | User info update | `{"user_id": "123", "nickname": "New Nickname"}` |
### Message-Related Commands
| Command Type | Description | Parameter Example |
| ---------------- | -------------- | -------------------------------------------------------------- |
| `message_recall` | Message recall | `{"message_id": "msg123", "channel_id": "ch123"}` |
| `typing_status` | Typing status | `{"channel_id": "ch123", "user_id": "123", "is_typing": true}` |
| `message_read` | Message read | `{"channel_id": "ch123", "message_id": "msg123"}` |
### Group-Related Commands
| Command Type | Description | Parameter Example |
| ------------------------- | ----------------------- | --------------------------------------------------------- |
| `group_member_update` | Group member update | `{"group_id": "g123", "action": "add", "members": [...]}` |
| `group_info_update` | Group info update | `{"group_id": "g123", "name": "New Group Name"}` |
| `group_permission_change` | Group permission change | `{"group_id": "g123", "user_id": "123", "role": "admin"}` |
## Best Practices
### 1. Command Handler Pattern
```java theme={null}
public abstract class CommandHandler {
protected String commandType;
public CommandHandler(String commandType) {
this.commandType = commandType;
}
public abstract void handle(JSONObject params);
public boolean canHandle(String cmdKey) {
return commandType.equals(cmdKey);
}
}
public class CommandDispatcher {
private Map handlers = new HashMap<>();
public void registerHandler(CommandHandler handler) {
handlers.put(handler.commandType, handler);
}
public void dispatch(WKCMD cmd) {
CommandHandler handler = handlers.get(cmd.cmdKey);
if (handler != null) {
handler.handle(cmd.paramJsonObject);
} else {
Log.w("CommandDispatcher", "No handler found for command: " + cmd.cmdKey);
}
}
}
// Usage example
CommandDispatcher dispatcher = new CommandDispatcher();
dispatcher.registerHandler(new UserStatusCommandHandler());
dispatcher.registerHandler(new MessageRecallCommandHandler());
dispatcher.registerHandler(new SystemNotificationCommandHandler());
WKIM.getInstance().getCMDManager().addCmdListener("Dispatcher", dispatcher::dispatch);
```
### 2. Asynchronous Processing
```java theme={null}
public class AsyncCommandManager {
private ExecutorService executorService = Executors.newCachedThreadPool();
public void initialize() {
WKIM.getInstance().getCMDManager().addCmdListener("AsyncManager", this::handleCommandAsync);
}
private void handleCommandAsync(WKCMD cmd) {
executorService.execute(() -> {
try {
processCommand(cmd);
} catch (Exception e) {
Log.e("AsyncCommandManager", "Command processing exception", e);
}
});
}
private void processCommand(WKCMD cmd) {
// Time-consuming command processing logic
switch (cmd.cmdKey) {
case "sync_data":
syncDataFromServer(cmd.paramJsonObject);
break;
case "update_cache":
updateLocalCache(cmd.paramJsonObject);
break;
}
}
public void destroy() {
WKIM.getInstance().getCMDManager().removeCmdListener("AsyncManager");
executorService.shutdown();
}
}
```
### 3. Memory Management
```java theme={null}
@Override
protected void onDestroy() {
super.onDestroy();
// Remove all command listeners
WKIM.getInstance().getCMDManager().removeCmdListener("ActivityKey");
// Clean up command dispatcher
if (commandDispatcher != null) {
commandDispatcher.clear();
}
// Clean up async tasks
if (executorService != null && !executorService.isShutdown()) {
executorService.shutdown();
}
}
```
## Next Steps
Learn how to configure data sources
Handle message reminder functionality
Explore advanced features and optimizations
Return to conversation management functionality
# Conversation Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/android/conversation
WuKongIM Android SDK conversation management functionality, including conversation lists, unread messages and conversation monitoring
The recent conversation manager is responsible for managing the user's recent conversation list, including getting conversations, listening for conversation changes, deleting conversations, and other functions.
Need to implement recent conversation data source: [Recent Conversation Data Source](/en/sdk/wukongim/android/datasource#recent-conversation-data-source)
## Get Recent Conversation List
### Get All Recent Conversations
```java Java theme={null}
// Query all recent conversations
WKIM.getInstance().getConversationManager().getAll();
```
```kotlin Kotlin theme={null}
// Query all recent conversations
WKIM.getInstance().conversationManager.getAll()
```
### Complete Usage Example
```java theme={null}
public class ConversationListActivity extends AppCompatActivity {
private List conversationList = new ArrayList<>();
private ConversationAdapter conversationAdapter;
private RecyclerView recyclerView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_conversation_list);
setupRecyclerView();
setupConversationListeners();
loadConversations();
}
private void setupConversationListeners() {
// Listen for conversation refresh
WKIM.getInstance().getConversationManager().addOnRefreshMsgListener("ConversationList",
new IRefreshConversationMsg() {
@Override
public void onRefreshConversationMsg(WKUIConversationMsg wkUIConversationMsg, boolean isEnd) {
// wkUIConversationMsg: recent conversation message content
// If UI already has this conversation, update it; otherwise add to UI
// isEnd: to prevent frequent UI refreshes, refresh UI only when isEnd is true
if (isEnd) {
runOnUiThread(() -> {
updateConversationInList(wkUIConversationMsg);
});
}
}
});
// Listen for conversation deletion
WKIM.getInstance().getConversationManager().addOnDeleteMsgListener("ConversationList",
new IDeleteConversationMsg() {
@Override
public void onDelete(String channelID, byte channelType) {
runOnUiThread(() -> {
removeConversationFromList(channelID, channelType);
});
}
});
}
private void loadConversations() {
// Get all recent conversations
List conversations = WKIM.getInstance().getConversationManager().getAll();
if (conversations != null) {
conversationList.clear();
conversationList.addAll(conversations);
// Sort by time
Collections.sort(conversationList, (o1, o2) ->
Long.compare(o2.lastMsgTimestamp, o1.lastMsgTimestamp));
conversationAdapter.notifyDataSetChanged();
updateTotalUnreadCount();
}
}
private void updateConversationInList(WKUIConversationMsg newConversation) {
// Check if conversation already exists
int existingIndex = -1;
for (int i = 0; i < conversationList.size(); i++) {
WKUIConversationMsg existing = conversationList.get(i);
if (existing.getWkChannel().channelID.equals(newConversation.getWkChannel().channelID) &&
existing.getWkChannel().channelType == newConversation.getWkChannel().channelType) {
existingIndex = i;
break;
}
}
if (existingIndex >= 0) {
// Update existing conversation
conversationList.set(existingIndex, newConversation);
conversationAdapter.notifyItemChanged(existingIndex);
} else {
// Add new conversation
conversationList.add(0, newConversation);
conversationAdapter.notifyItemInserted(0);
}
// Re-sort
Collections.sort(conversationList, (o1, o2) ->
Long.compare(o2.lastMsgTimestamp, o1.lastMsgTimestamp));
conversationAdapter.notifyDataSetChanged();
updateTotalUnreadCount();
}
private void removeConversationFromList(String channelID, byte channelType) {
for (int i = 0; i < conversationList.size(); i++) {
WKUIConversationMsg conversation = conversationList.get(i);
if (conversation.getWkChannel().channelID.equals(channelID) &&
conversation.getWkChannel().channelType == channelType) {
conversationList.remove(i);
conversationAdapter.notifyItemRemoved(i);
break;
}
}
updateTotalUnreadCount();
}
private void updateTotalUnreadCount() {
int totalUnread = 0;
for (WKUIConversationMsg conversation : conversationList) {
totalUnread += conversation.unreadCount;
}
// Update app badge
updateAppBadge(totalUnread);
// Update TabBar badge
updateTabBadge(totalUnread);
}
@Override
protected void onDestroy() {
super.onDestroy();
// Remove listeners
WKIM.getInstance().getConversationManager().removeOnRefreshMsgListener("ConversationList");
WKIM.getInstance().getConversationManager().removeOnDeleteMsgListener("ConversationList");
}
}
```
## New Message Listening
Only when opening the app for the first time, you need to sync the recent conversation list. Subsequent changes to the recent conversation list are obtained through listening.
```java Java theme={null}
// Listen for recent conversation message refresh
WKIM.getInstance().getConversationManager().addOnRefreshMsgListener("key", new IRefreshConversationMsg() {
@Override
public void onRefreshConversationMsg(WKUIConversationMsg wkUIConversationMsg, boolean isEnd) {
// wkUIConversationMsg: recent conversation message content
// If UI already has this conversation, update it; otherwise add to UI
// isEnd: to prevent frequent UI refreshes, refresh UI only when isEnd is true
if (isEnd) {
runOnUiThread(() -> {
handleConversationUpdate(wkUIConversationMsg);
});
}
}
});
// Remove listener when exiting page
WKIM.getInstance().getConversationManager().removeOnRefreshMsgListener("key");
```
```kotlin Kotlin theme={null}
// Listen for recent conversation message refresh
WKIM.getInstance().conversationManager.addOnRefreshMsgListener("key") { wkUIConversationMsg, isEnd ->
// wkUIConversationMsg: recent conversation message content
// If UI already has this conversation, update it; otherwise add to UI
// isEnd: to prevent frequent UI refreshes, refresh UI only when isEnd is true
if (isEnd) {
runOnUiThread {
handleConversationUpdate(wkUIConversationMsg)
}
}
}
// Remove listener when exiting page
WKIM.getInstance().conversationManager.removeOnRefreshMsgListener("key")
```
## Remove Recent Conversations
### Delete Conversation
```java Java theme={null}
// Delete a recent conversation
WKIM.getInstance().getConversationManager().deleteWitchChannel(String channelId, byte channelType);
```
```kotlin Kotlin theme={null}
// Delete a recent conversation
WKIM.getInstance().conversationManager.deleteWitchChannel(channelId, channelType)
```
### Listen for Deletion
This method is called when deleting a recent conversation:
```java Java theme={null}
// Listen for recent conversation message deletion
WKIM.getInstance().getConversationManager().addOnDeleteMsgListener("key", new IDeleteConversationMsg() {
@Override
public void onDelete(String channelID, byte channelType) {
// channelID: chat channel ID
// channelType: chat channel type
runOnUiThread(() -> {
handleConversationDeleted(channelID, channelType);
});
}
});
// Remove listener when exiting page
WKIM.getInstance().getConversationManager().removeOnDeleteMsgListener("key");
```
```kotlin Kotlin theme={null}
// Listen for recent conversation message deletion
WKIM.getInstance().conversationManager.addOnDeleteMsgListener("key") { channelID, channelType ->
// channelID: chat channel ID
// channelType: chat channel type
runOnUiThread {
handleConversationDeleted(channelID, channelType)
}
}
// Remove listener when exiting page
WKIM.getInstance().conversationManager.removeOnDeleteMsgListener("key")
```
## Common Methods
```java Java theme={null}
// Query all recent conversations
WKIM.getInstance().getConversationManager().getAll();
// Update message red dot
WKIM.getInstance().getConversationManager().updateRedDot(String channelID, byte channelType, int redDot);
// Delete a conversation
WKIM.getInstance().getConversationManager().deleteMsg(String channelId, byte channelType);
```
```kotlin Kotlin theme={null}
// Query all recent conversations
WKIM.getInstance().conversationManager.getAll()
// Update message red dot
WKIM.getInstance().conversationManager.updateRedDot(channelID, channelType, redDot)
// Delete a conversation
WKIM.getInstance().conversationManager.deleteMsg(channelId, channelType)
```
### Common Operations Example
```java theme={null}
public class ConversationManager {
// Update red dot status
public void updateRedDotStatus(String channelID, byte channelType, boolean hasRedDot) {
int redDot = hasRedDot ? 1 : 0;
WKIM.getInstance().getConversationManager().updateRedDot(channelID, channelType, redDot);
}
// Clear unread count
public void clearUnreadCount(String channelID, byte channelType) {
// Clear unread count by updating red dot status
updateRedDotStatus(channelID, channelType, false);
// Also call server API
ApiManager.clearUnreadCount(channelID, channelType, new ApiCallback() {
@Override
public void onSuccess(Void result) {
// Clear successful
}
@Override
public void onError(int code, String message) {
// Clear failed
}
});
}
// Get total unread count
public int getTotalUnreadCount() {
List conversations = WKIM.getInstance().getConversationManager().getAll();
int totalUnread = 0;
if (conversations != null) {
for (WKUIConversationMsg conversation : conversations) {
totalUnread += conversation.unreadCount;
}
}
return totalUnread;
}
// Get conversations by type
public List getConversationsByType(byte channelType) {
List allConversations = WKIM.getInstance().getConversationManager().getAll();
List filteredConversations = new ArrayList<>();
if (allConversations != null) {
for (WKUIConversationMsg conversation : allConversations) {
if (conversation.getWkChannel().channelType == channelType) {
filteredConversations.add(conversation);
}
}
}
return filteredConversations;
}
}
```
## WKUIConversationMsg Data Structure
### Conversation Message Properties
```java theme={null}
public class WKUIConversationMsg {
// Last message timestamp
public long lastMsgTimestamp;
// Message channel - channel info, may be null
// If null, call WKChannelManager's fetchChannelInfo(channelID, channelType) to trigger channel info change
private WKChannel wkChannel;
// Message content
private WKMsg wkMsg;
// Unread message count
public int unreadCount;
// Remote extensions
private WKConversationMsgExtra remoteMsgExtra;
// Local extension fields
public HashMap localExtraMap;
// Recent conversation reminder items like [someone @you] [group audit] etc.
public List getReminderList() {
// ...
}
// Get remote extensions
public WKConversationMsgExtra getRemoteMsgExtra() {
// ...
}
// Conversation channel info
public WKChannel getWkChannel() {
// ...
}
}
```
### Property Description
| Property | Type | Description |
| ------------------ | ---------------------- | ---------------------------- |
| `lastMsgTimestamp` | long | Last message timestamp |
| `wkChannel` | WKChannel | Message channel information |
| `wkMsg` | WKMsg | Last message content |
| `unreadCount` | int | Unread message count |
| `remoteMsgExtra` | WKConversationMsgExtra | Remote extension information |
| `localExtraMap` | HashMap | Local extension fields |
## Next Steps
Learn how to configure conversation data sources
Manage channel member information
Manage message reminder functionality
Return to message handling functionality
# Data Source Configuration
Source: https://wukong.mintlify.app/en/sdk/wukongim/android/datasource
WuKongIM Android SDK data source configuration, including file upload/download, conversation sync, channel information and message sync
Data source management is one of the core functions of WuKongIM SDK, responsible for handling key business logic such as file upload/download, conversation sync, channel information retrieval, and message sync.
## File Management
### Listen for Attachment Upload
When sending custom attachment messages, the message sent to the recipient is a network address, not the actual file. In this case, we need to listen for attachment uploads.
```java Java theme={null}
WKIM.getInstance().getMsgManager().addOnUploadAttachListener(new IUploadAttachmentListener() {
@Override
public void onUploadAttachmentListener(WKMsg wkMsg, IUploadAttacResultListener listener) {
// Upload unuploaded files to server and return to SDK
if(wkMsg.type == WKMsgContentType.WK_IMAGE){
WKMediaMessageContent mediaMessageContent = (WKMediaMessageContent) wkMsg.baseContentMsgModel;
if (TextUtils.isEmpty(mediaMessageContent.url)) {
// TODO: Upload file
// ...
mediaMessageContent.url = "xxxxxx"; // Set network address and return to SDK
listener.onUploadResult(true, mediaMessageContent);
}
}
}
});
```
```kotlin Kotlin theme={null}
WKIM.getInstance().msgManager.addOnUploadAttachListener { wkMsg, listener ->
// Upload unuploaded files to server and return to SDK
if (wkMsg.type == WKMsgContentType.WK_IMAGE) {
val mediaMessageContent = wkMsg.baseContentMsgModel as WKMediaMessageContent
if (TextUtils.isEmpty(mediaMessageContent.url)) {
// TODO: Upload file
// ...
mediaMessageContent.url = "xxxxxx" // Set network address and return to SDK
listener.onUploadResult(true, mediaMessageContent)
}
}
}
```
### Listen for Attachment Download
The SDK will not actively download message attachments. When receiving messages with attachments, the app needs to download them as needed. After the app completes the download, it can change the local file address to avoid repeated downloads.
```java Java theme={null}
/**
* Update message content
*
* @param clientMsgNo Client message ID
* @param messageContent Message module - save local address in messageContent
* @param isRefreshUI Whether to notify UI to refresh corresponding message
*/
WKIM.getInstance().getMsgManager().updateContent(String clientMsgNo, WKMessageContent messageContent, boolean isRefreshUI);
```
```kotlin Kotlin theme={null}
WKIM.getInstance().msgManager.updateContent(clientMsgNo, messageContent)
```
### Complete File Management Example
```java theme={null}
public class FileManager {
private static final String TAG = "FileManager";
public void initialize() {
// Set file upload listener
WKIM.getInstance().getMsgManager().addOnUploadAttachListener(this::handleFileUpload);
}
private void handleFileUpload(WKMsg wkMsg, IUploadAttacResultListener listener) {
switch (wkMsg.type) {
case WKMsgContentType.WK_IMAGE:
uploadImage(wkMsg, listener);
break;
case WKMsgContentType.WK_VIDEO:
uploadVideo(wkMsg, listener);
break;
case WKMsgContentType.WK_VOICE:
uploadVoice(wkMsg, listener);
break;
case WKMsgContentType.WK_FILE:
uploadFile(wkMsg, listener);
break;
default:
listener.onUploadResult(false, null);
break;
}
}
private void uploadImage(WKMsg wkMsg, IUploadAttacResultListener listener) {
WKImageContent imageContent = (WKImageContent) wkMsg.baseContentMsgModel;
if (!TextUtils.isEmpty(imageContent.url)) {
// Already has network address, return directly
listener.onUploadResult(true, imageContent);
return;
}
// Upload image to server
String localPath = imageContent.localPath;
if (TextUtils.isEmpty(localPath)) {
listener.onUploadResult(false, null);
return;
}
// Async upload
uploadFileToServer(localPath, "image", new UploadCallback() {
@Override
public void onSuccess(String url) {
imageContent.url = url;
listener.onUploadResult(true, imageContent);
}
@Override
public void onError(String error) {
Log.e(TAG, "Image upload failed: " + error);
listener.onUploadResult(false, null);
}
});
}
private void uploadVideo(WKMsg wkMsg, IUploadAttacResultListener listener) {
WKVideoContent videoContent = (WKVideoContent) wkMsg.baseContentMsgModel;
if (!TextUtils.isEmpty(videoContent.url)) {
listener.onUploadResult(true, videoContent);
return;
}
String localPath = videoContent.localPath;
if (TextUtils.isEmpty(localPath)) {
listener.onUploadResult(false, null);
return;
}
// Upload video and thumbnail
uploadFileToServer(localPath, "video", new UploadCallback() {
@Override
public void onSuccess(String url) {
videoContent.url = url;
// If there's a thumbnail, upload it too
if (!TextUtils.isEmpty(videoContent.coverLocalPath)) {
uploadFileToServer(videoContent.coverLocalPath, "image", new UploadCallback() {
@Override
public void onSuccess(String coverUrl) {
videoContent.cover = coverUrl;
listener.onUploadResult(true, videoContent);
}
@Override
public void onError(String error) {
// Thumbnail upload failed, but video upload succeeded
listener.onUploadResult(true, videoContent);
}
});
} else {
listener.onUploadResult(true, videoContent);
}
}
@Override
public void onError(String error) {
Log.e(TAG, "Video upload failed: " + error);
listener.onUploadResult(false, null);
}
});
}
// File upload to server implementation
private void uploadFileToServer(String localPath, String fileType, UploadCallback callback) {
// Implement specific file upload logic here
// Can use OkHttp, Retrofit or other network libraries
File file = new File(localPath);
if (!file.exists()) {
callback.onError("File does not exist");
return;
}
// Example: Using OkHttp to upload file
RequestBody fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file);
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), fileBody);
ApiService.uploadFile(filePart)
.enqueue(new Callback() {
@Override
public void onResponse(Call call, Response response) {
if (response.isSuccessful() && response.body() != null) {
callback.onSuccess(response.body().getUrl());
} else {
callback.onError("Upload failed: " + response.message());
}
}
@Override
public void onFailure(Call call, Throwable t) {
callback.onError("Network error: " + t.getMessage());
}
});
}
// Download file and update message content
public void downloadAndUpdateMessage(WKMsg message) {
if (message.baseContentMsgModel instanceof WKMediaMessageContent) {
WKMediaMessageContent mediaContent = (WKMediaMessageContent) message.baseContentMsgModel;
if (!TextUtils.isEmpty(mediaContent.url) && TextUtils.isEmpty(mediaContent.localPath)) {
downloadFile(mediaContent.url, new DownloadCallback() {
@Override
public void onSuccess(String localPath) {
mediaContent.localPath = localPath;
// Update message content
WKIM.getInstance().getMsgManager().updateContent(
message.clientMsgNO,
mediaContent,
true // Refresh UI
);
}
@Override
public void onError(String error) {
Log.e(TAG, "File download failed: " + error);
}
});
}
}
}
// Callback interfaces
interface UploadCallback {
void onSuccess(String url);
void onError(String error);
}
interface DownloadCallback {
void onSuccess(String localPath);
void onError(String error);
}
}
```
## Recent Conversation Data Source
```java Java theme={null}
WKIM.getInstance().getConversationManager().addOnSyncConversationListener(new ISyncConversationChat() {
@Override
public void syncConversationChat(String last_msg_seqs, int msg_count, long version, ISyncConversationChatBack iSyncConversationChatBack) {
/**
* Sync conversations
*
* @param last_msg_seqs Recent conversation list msg_seq collection
* @param msg_count Message sync count in conversations
* @param version Maximum version number
* @param iSyncConvChatBack Callback
*/
// Need to request business interface and return data to SDK
syncConversationsFromServer(last_msg_seqs, msg_count, version, iSyncConversationChatBack);
}
});
```
```kotlin Kotlin theme={null}
WKIM.getInstance().conversationManager.addOnSyncConversationListener { last_msg_seqs, msg_count, version, iSyncConversationChatBack ->
// TODO: Sync recent conversation data
syncConversationsFromServer(last_msg_seqs, msg_count, version, iSyncConversationChatBack)
}
```
## Channel Information Data Source
```java Java theme={null}
// Listen for getting channel information
WKIM.getInstance().getChannelManager().addOnGetChannelInfoListener(new IGetChannelInfo() {
@Override
public WKChannel onGetChannelInfo(String channelID, byte channelType, IChannelInfoListener iChannelInfoListener) {
// Whether to get personal or group info can be distinguished by channelType
// If app has local channel info, return data directly; otherwise get network data and return via iChannelInfoListener
return getChannelFromCache(channelID, channelType, iChannelInfoListener);
}
});
```
```kotlin Kotlin theme={null}
// Listen for getting channel information
WKIM.getInstance().channelManager.addOnGetChannelInfoListener { channelID, channelType, iChannelInfoListener ->
// Whether to get personal or group info can be distinguished by channelType
// If app has local channel info, return data directly; otherwise get network data and return via iChannelInfoListener
getChannelFromCache(channelID, channelType, iChannelInfoListener)
}
```
Built-in channel types in SDK can be viewed through `WKChannelType`
## Channel Member Data Source
```java Java theme={null}
// Listen for getting channel member information
WKIM.getInstance().getChannelMembersManager().addOnGetChannelMembersListener((channelID, channelType, keyword, page, limit, iChannelMemberListResult) -> {
// Get channel members and return to SDK via iChannelMembersListener
fetchChannelMembers(channelID, channelType, keyword, page, limit, iChannelMemberListResult);
});
```
```kotlin Kotlin theme={null}
// Listen for getting channel member information
WKIM.getInstance().channelMembersManager.addOnGetChannelMembersListener { channelID, channelType, keyword, page, limit, back ->
// Get channel members and return to SDK via iChannelMembersListener
fetchChannelMembers(channelID, channelType, keyword, page, limit, back)
}
```
## Channel Message Data Source
```java Java theme={null}
WKIM.getInstance().getMsgManager().addOnSyncChannelMsgListener(new ISyncChannelMsgListener() {
@Override
public void syncChannelMsgs(String channelID, byte channelType, long startMessageSeq, long endMessageSeq, int limit, int pullMode, ISyncChannelMsgBack iSyncChannelMsgBack) {
/**
* Sync messages for a channel
*
* @param channelID Channel ID
* @param channelType Channel type
* @param startMessageSeq Start message sequence (result includes start_message_seq message)
* @param endMessageSeq End message sequence (result excludes end_message_seq message)
* @param limit Message count limit
* @param pullMode Pull mode 0: pull down 1: pull up
* @param iSyncChannelMsgBack Request return
*/
syncMessagesFromServer(channelID, channelType, startMessageSeq, endMessageSeq, limit, pullMode, iSyncChannelMsgBack);
}
});
```
```kotlin Kotlin theme={null}
WKIM.getInstance().msgManager.addOnSyncChannelMsgListener { channelID, channelType, startMessageSeq, endMessageSeq, limit, pullMode, iSyncChannelMsgBack ->
// Call interface to get channel history messages
syncMessagesFromServer(channelID, channelType, startMessageSeq, endMessageSeq, limit, pullMode, iSyncChannelMsgBack)
}
```
## Next Steps
Explore advanced features and optimization
Return to channel member management
Return to integration guide
Return to message management functionality
# Integration Guide
Source: https://wukong.mintlify.app/en/sdk/wukongim/android/integration
WuKongIM Android SDK integration and initialization configuration guide
## Quick Start
**Gradle**
[](https://jitpack.io/#WuKongIM/WuKongIMAndroidSDK)
```gradle theme={null}
implementation 'com.github.WuKongIM:WuKongIMAndroidSDK:version' // Please check version number above
```
For JitPack, you also need to add this to your main project's `build.gradle` file:
```gradle theme={null}
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
```
Since the SDK uses SQLCipher encrypted database and Curve25519 encryption algorithm, you need to add these libraries to your project:
```gradle theme={null}
implementation "net.zetetic:android-database-sqlcipher:4.5.3"
implementation "androidx.sqlite:sqlite-ktx:2.3.1"
implementation 'org.whispersystems:curve25519-android:0.5.0'
implementation 'org.whispersystems:signal-protocol-android:2.8.1'
```
## Alternative Installation Methods
### Maven
```xml theme={null}
com.github.WuKongIM
WuKongIMAndroidSDK
version
```
### Manual Integration
1. Download the latest AAR file from [GitHub Releases](https://github.com/WuKongIM/WuKongIMAndroidSDK/releases)
2. Place the AAR file in your `app/libs` directory
3. Add to your `app/build.gradle`:
```gradle theme={null}
dependencies {
implementation files('libs/wukongim-android-sdk-version.aar')
// Add required dependencies
implementation "net.zetetic:android-database-sqlcipher:4.5.3"
implementation "androidx.sqlite:sqlite-ktx:2.3.1"
implementation 'org.whispersystems:curve25519-android:0.5.0'
implementation 'org.whispersystems:signal-protocol-android:2.8.1'
}
```
## ProGuard Configuration
Add the following ProGuard rules to your `proguard-rules.pro` file:
```proguard theme={null}
# WuKongIM SDK
-dontwarn com.xinbida.wukongim.**
-keep class com.xinbida.wukongim.**{*;}
# Database encryption
-keep,includedescriptorclasses class net.sqlcipher.** { *; }
-keep,includedescriptorclasses interface net.sqlcipher.** { *; }
# Curve25519 encryption
-keep class org.whispersystems.curve25519.**{*;}
-keep class org.whispersystems.** { *; }
-keep class org.thoughtcrime.securesms.** { *; }
# Additional rules for encryption
-keep class org.signal.** { *; }
-dontwarn org.signal.**
```
## Permissions
Add necessary permissions to your `AndroidManifest.xml`:
```xml theme={null}
```
## Basic Setup
### Initialize in Application
Create or modify your Application class:
```kotlin theme={null}
// Kotlin
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize WuKongIM SDK
WKIM.getInstance().init(this)
}
}
```
```java theme={null}
// Java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Initialize WuKongIM SDK
WKIM.getInstance().init(this);
}
}
```
Don't forget to register your Application class in `AndroidManifest.xml`:
```xml theme={null}
```
### Configuration Options
You can configure various options during initialization:
```kotlin theme={null}
// Kotlin
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Initialize with custom configuration
val options = WKIMOptions().apply {
logLevel = WKLogLevel.DEBUG
dbPassword = "your_db_password"
fileUploadUrl = "https://your-upload-server.com/upload"
apiUrl = "https://your-api-server.com"
}
WKIM.getInstance().init(this, options)
}
}
```
```java theme={null}
// Java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Initialize with custom configuration
WKIMOptions options = new WKIMOptions();
options.setLogLevel(WKLogLevel.DEBUG);
options.setDbPassword("your_db_password");
options.setFileUploadUrl("https://your-upload-server.com/upload");
options.setApiUrl("https://your-api-server.com");
WKIM.getInstance().init(this, options);
}
}
```
## Verification
To verify that the SDK is properly integrated, add this test code:
```kotlin theme={null}
// Kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Test SDK initialization
val isInitialized = WKIM.getInstance().isInitialized()
Log.d("WuKongIM", "SDK Initialized: $isInitialized")
// Test connection manager
val connectionManager = WKIM.getInstance().connectionManager
Log.d("WuKongIM", "Connection Manager: ${connectionManager != null}")
}
}
```
```java theme={null}
// Java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Test SDK initialization
boolean isInitialized = WKIM.getInstance().isInitialized();
Log.d("WuKongIM", "SDK Initialized: " + isInitialized);
// Test connection manager
ConnectionManager connectionManager = WKIM.getInstance().getConnectionManager();
Log.d("WuKongIM", "Connection Manager: " + (connectionManager != null));
}
}
```
## Troubleshooting
### Common Issues
1. **Build Error: "Could not resolve dependency"**
* Make sure you've added the JitPack repository
* Check if the version number is correct
2. **Runtime Error: "ClassNotFoundException"**
* Verify ProGuard rules are correctly configured
* Check if all required dependencies are included
3. **Database Error: "SQLCipher not found"**
* Ensure SQLCipher dependency is added
* Check if ProGuard rules for SQLCipher are included
4. **Encryption Error: "Curve25519 not found"**
* Verify Curve25519 dependencies are included
* Check ProGuard rules for encryption libraries
### Debug Mode
Enable debug mode to get more detailed logs:
```kotlin theme={null}
// Kotlin
val options = WKIMOptions().apply {
logLevel = WKLogLevel.DEBUG
enableDebugMode = true
}
WKIM.getInstance().init(this, options)
```
## Next Steps
Learn basic SDK functionality usage
Implement message sending and receiving
Manage channels and groups
Handle conversation lists
# SDK Introduction
Source: https://wukong.mintlify.app/en/sdk/wukongim/android/intro
WuKongIM Android SDK design philosophy, architecture overview and core functionality introduction
## Design Philosophy
To enable developers to use the SDK faster and more conveniently, WuKong SDK provides a single entry point to access all functionality in the SDK. Like a book's table of contents, you can find corresponding content through the directory. For example, connecting to IM: `WKIM.getInstance().getConnectionManager().connection()`
## Architecture Overview
Common SDK functionality introduction:
```java theme={null}
// Message Manager
// Responsible for message CRUD operations, new message listening, refresh message listening,
// message storage, send message receipt listening, monitoring sync of specific chat data, etc.
WKIM.getInstance().getMsgManager()
// Connection Manager
// Responsible for IM connection, disconnection, logout, connection status monitoring,
// connection IP monitoring, etc.
WKIM.getInstance().getConnectionManager()
// Channel Manager
// Can get Channel information, refresh Channel cache, monitor Channel changes
// [pinning, do not disturb, muting], search Channels, etc.
WKIM.getInstance().getChannelManager()
// Conversation Manager
// Get recent chat records, refresh recent conversations [add chat, red dot changes],
// monitor removal of conversations, monitor sync recent conversations, etc.
WKIM.getInstance().getConversationManager()
// Channel Members Manager
// Get Channel member list, set member notes, save and modify member data,
// monitor refresh members and remove members, etc.
WKIM.getInstance().getChannelMembersManager()
// Reminder Manager
// Get conversation reminders like: [someone @me] [group join request], etc.
// Also supports custom reminder items like voice unread, etc.
WKIM.getInstance().getReminderManager()
// CMD Manager
// Responsible for monitoring command messages sent from the server
WKIM.getInstance().getCMDManager()
// Robot Manager
// Can get robot menus, sync robot menus, query menus, etc.
WKIM.getInstance().getRobotManager()
```
## SDK Integration with Apps
The SDK-APP interaction flow is: APP calls SDK provided methods, SDK processes data and callbacks data to APP through events. For example, message sending flow: APP calls send message method, SDK pushes the stored message to APP.
## Core Functionality Modules
### Message Management (MsgManager)
* Message CRUD operations
* New message listening and refresh message listening
* Message storage and status management
* Send message receipt listening
* Sync chat data monitoring
### Connection Management (ConnectionManager)
* IM connection establishment and disconnection
* Logout handling
* Connection status monitoring
* Connection IP monitoring
* Network status handling
### Channel Management (ChannelManager)
* Get channel information
* Refresh channel cache
* Monitor channel changes (pinning, do not disturb, muting)
* Search channel functionality
* Channel settings management
### Conversation Management (ConversationManager)
* Get recent chat records
* Refresh recent conversations (add chat, red dot changes)
* Monitor removal of conversations
* Monitor sync recent conversations
* Unread message counting
### Channel Members Management (ChannelMembersManager)
* Get channel member list
* Set member notes
* Save and modify member data
* Monitor refresh members and remove members
* Member permission management
### Reminder Management (ReminderManager)
* Get conversation reminders (someone @me, group join request, etc.)
* Custom reminder items (voice unread, etc.)
* Reminder status management
* Reminder message handling
### CMD Management (CMDManager)
* Monitor command messages sent from server
* Command message handling
* System notification management
### Robot Management (RobotManager)
* Get robot menus
* Sync robot menus
* Query menu functionality
* Robot interaction handling
## Development Advantages
* **Unified Entry Point**: Access all functionality through `WKIM.getInstance()`
* **Modular Design**: Clear separation of functional modules for easy maintenance and extension
* **Event-Driven**: Event callback mechanism based on listener pattern
* **High Performance**: Local database caching, optimized network requests
* **Easy Integration**: Clean API design for quick integration into existing Android projects
* **Complete Functionality**: Covers all core instant messaging features
## Next Steps
After understanding the overall architecture of Android SDK, you can:
1. [SDK Integration](/en/sdk/wukongim/android/integration) - Start integrating WuKongIM Android SDK
2. [Basic Features](/en/sdk/wukongim/android/base) - Learn basic SDK configuration and usage
3. [Message Management](/en/sdk/wukongim/android/message) - Implement message sending and receiving functionality
4. [Channel Management](/en/sdk/wukongim/android/channel) - Manage channels and members
5. [Conversation Management](/en/sdk/wukongim/android/conversation) - Handle conversation lists and unread messages
# Message Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/android/message
WuKongIM Android SDK message management functionality, including message sending/receiving, history messages and message monitoring
The message manager is responsible for CRUD operations on messages, new message listening, refresh message listening, message storage, send message receipt listening, monitoring sync of specific chat data, etc.
## Sending Messages
### Basic Send Method
```java Java theme={null}
/**
* Send message
* @param textContent Message content
* @param channelID Target channel ID
* @param channelType Target channel type (personal channel, group channel, customer service channel, etc.)
*/
WKIM.getInstance().getMsgManager().sendMessage(textContent, channelID, channelType);
```
```kotlin Kotlin theme={null}
WKIM.getInstance().msgManager.send(textContent, channel)
```
Built-in channel types in SDK can be viewed through `WKChannelType`
### Text Messages
```java Java theme={null}
// Define text message
WKTextContent textContent = new WKTextContent("Hello, WuKong");
// Send message
WKIM.getInstance().getMsgManager().send(textContent, channel);
```
```kotlin Kotlin theme={null}
// Define text message
val textContent = WKTextContent("Hello, WuKong")
// Send message
WKIM.getInstance().msgManager.send(textContent, channel)
```
### Image Messages
```java Java theme={null}
// Define image message
WKImageContent imageContent = new WKImageContent(localPath);
// Send message
WKIM.getInstance().getMsgManager().send(imageContent, channel);
```
```kotlin Kotlin theme={null}
// Define image message
val imageContent = WKImageContent(localPath)
// Send message
WKIM.getInstance().msgManager.send(imageContent, channel)
```
When building image message content, there's no need to pass image width and height. The SDK will automatically get the image dimensions.
### Complete Send Example
```java theme={null}
public class ChatActivity extends AppCompatActivity {
private String channelID;
private byte channelType;
// Send text message
private void sendTextMessage(String content) {
WKTextContent textContent = new WKTextContent(content);
WKIM.getInstance().getMsgManager().send(textContent, channelID, channelType);
}
// Send image message
private void sendImageMessage(String imagePath) {
WKImageContent imageContent = new WKImageContent(imagePath);
WKIM.getInstance().getMsgManager().send(imageContent, channelID, channelType);
}
// Send voice message
private void sendVoiceMessage(String voicePath, int duration) {
WKVoiceContent voiceContent = new WKVoiceContent(voicePath, duration);
WKIM.getInstance().getMsgManager().send(voiceContent, channelID, channelType);
}
// Send location message
private void sendLocationMessage(double latitude, double longitude, String address) {
WKLocationContent locationContent = new WKLocationContent(latitude, longitude, address);
WKIM.getInstance().getMsgManager().send(locationContent, channelID, channelType);
}
}
```
### Custom Messages
See custom messages: [Custom Messages](/en/sdk/wukongim/android/advance#custom-messages)
## Message Storage Listening
When sending messages, the SDK will trigger a storage callback after saving the message to the local database. At this point, the message has not been sent yet, and you can display the message in the UI in this listener.
```java Java theme={null}
WKIM.getInstance().getMsgManager().addOnSendMsgCallback("key", new ISendMsgCallBackListener() {
@Override
public void onInsertMsg(WKMsg wkMsg) {
// You can display the message `wkMsg` saved in the database on the UI here
runOnUiThread(() -> {
addMessageToUI(wkMsg);
});
}
});
```
```kotlin Kotlin theme={null}
WKIM.getInstance().msgManager.addOnSendMsgCallback("key") { wkMsg ->
// Display message wkMsg on UI
runOnUiThread {
addMessageToUI(wkMsg)
}
}
```
For explanation about whether to pass a unique key for events, see [Event Listening](/en/sdk/wukongim/android#explanation)
## New Message Listening
```java Java theme={null}
// Add listener
WKIM.getInstance().getMsgManager().addOnNewMsgListener("key", new INewMsgListener() {
@Override
public void newMsg(List list) {
// list: received messages
runOnUiThread(() -> {
handleNewMessages(list);
});
}
});
// Remove listener when exiting page
WKIM.getInstance().getMsgManager().removeNewMsgListener("key");
```
```kotlin Kotlin theme={null}
// Add listener
WKIM.getInstance().msgManager.addOnNewMsgListener("key") { list ->
// list: received messages
runOnUiThread {
handleNewMessages(list)
}
}
// Remove listener when exiting page
WKIM.getInstance().msgManager.removeNewMsgListener("key")
```
If you receive new messages in a chat page, you need to determine whether the message belongs to the current conversation by checking the `channelID` and `channelType` of the message object `WKMsg`
### New Message Handling Example
```java theme={null}
public class ChatActivity extends AppCompatActivity {
private List messageList = new ArrayList<>();
private MessageAdapter messageAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Add new message listener
WKIM.getInstance().getMsgManager().addOnNewMsgListener("ChatActivity", new INewMsgListener() {
@Override
public void newMsg(List list) {
handleNewMessages(list);
}
});
}
private void handleNewMessages(List newMessages) {
for (WKMsg msg : newMessages) {
// Check if message belongs to current conversation
if (msg.channelID.equals(this.channelID) && msg.channelType == this.channelType) {
runOnUiThread(() -> {
messageList.add(msg);
messageAdapter.notifyItemInserted(messageList.size() - 1);
// Scroll to latest message
recyclerView.scrollToPosition(messageList.size() - 1);
// Mark message as read
markMessageAsRead(msg);
});
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
// Remove listener
WKIM.getInstance().getMsgManager().removeNewMsgListener("ChatActivity");
}
}
```
## Message Refresh Listening
When the SDK updates messages, such as: message send status, someone likes a message, message read receipt, message recall, message editing, etc., the SDK will callback the following event. The UI can determine which specific message has changed through the `clientMsgNO` of the message object `WKMsg`.
```java Java theme={null}
// Add refresh listener
WKIM.getInstance().getMsgManager().addOnRefreshMsgListener("key", new IRefreshMsg() {
@Override
public void onRefresh(WKMsg wkMsg, boolean isEnd) {
// wkMsg: refreshed message object
// isEnd: to avoid frequent UI refreshes causing lag, refresh UI only when isEnd is true
if (isEnd) {
runOnUiThread(() -> {
refreshMessageInUI(wkMsg);
});
}
}
});
// Remove refresh listener when exiting page
WKIM.getInstance().getMsgManager().removeRefreshMsgListener("key");
```
```kotlin Kotlin theme={null}
// Add refresh listener
WKIM.getInstance().msgManager.addOnRefreshMsgListener("key") { wkMsg, isEnd ->
// wkMsg: refreshed message object
// isEnd: to avoid frequent UI refreshes causing lag, refresh UI only when isEnd is true
if (isEnd) {
runOnUiThread {
refreshMessageInUI(wkMsg)
}
}
}
// Remove refresh listener when exiting page
WKIM.getInstance().msgManager.removeRefreshMsgListener("key")
```
### Message Refresh Handling Example
```java theme={null}
private void refreshMessageInUI(WKMsg updatedMsg) {
// Find corresponding message by clientMsgNO and update
for (int i = 0; i < messageList.size(); i++) {
WKMsg msg = messageList.get(i);
if (msg.clientMsgNO.equals(updatedMsg.clientMsgNO)) {
messageList.set(i, updatedMsg);
messageAdapter.notifyItemChanged(i);
break;
}
}
}
```
## Message Send Status Code (ReasonCode)
When a message is sent, you can obtain the `WKMsg` object by listening for message refresh events. The `status` (send status) and `reasonCode` in `WKMsg` indicate the result of the message delivery.
| Value | Name | Description |
| ----- | ---------------------------- | ------------------------------------------ |
| 0 | ReasonUnknown | Unknown error |
| 1 | ReasonSuccess | Success |
| 2 | ReasonAuthFail | Authentication failed |
| 3 | ReasonSubscriberNotExist | Subscriber does not exist in the channel |
| 4 | ReasonInBlacklist | In blacklist |
| 5 | ReasonChannelNotExist | Channel does not exist |
| 6 | ReasonUserNotOnNode | User is not on node |
| 7 | ReasonSenderOffline | Sender is offline, message delivery failed |
| 8 | ReasonMsgKeyError | Message key error, invalid message |
| 9 | ReasonPayloadDecodeError | Payload decoding failed |
| 10 | ReasonForwardSendPacketError | Forwarding send packet failed |
| 11 | ReasonNotAllowSend | Not allowed to send message |
| 12 | ReasonConnectKick | Connection kicked |
| 13 | ReasonNotInWhitelist | Not in whitelist |
| 14 | ReasonQueryTokenError | Query user token error |
| 15 | ReasonSystemError | System error |
| 16 | ReasonChannelIDError | Wrong channel ID |
| 17 | ReasonNodeMatchError | Node matching error |
| 18 | ReasonNodeNotMatch | Node not matched |
| 19 | ReasonBan | Channel is banned |
| 20 | ReasonNotSupportHeader | Unsupported header |
| 21 | ReasonClientKeyIsEmpty | clientKey is empty |
| 22 | ReasonRateLimit | Rate limit exceeded |
| 23 | ReasonNotSupportChannelType | Unsupported channel type |
| 24 | ReasonDisband | Channel disbanded |
| 25 | ReasonSendBan | Sending is banned |
## View History Messages
```java Java theme={null}
/**
* Query or sync messages for a channel
*
* @param channelId Channel ID
* @param channelType Channel type
* @param oldestOrderSeq Last message's large orderSeq, pass 0 for first entry into chat
* @param contain Whether to include the oldestOrderSeq message
* @param dropDown Whether it's a dropdown
* @param aroundMsgOrderSeq Query messages around this message, e.g. aroundMsgOrderSeq=20 returns [16,17,19,20,21,22,23,24,25]
* @param limit Number to get each time
* @param iGetOrSyncHistoryMsgBack Request callback
*/
WKIM.getInstance().getMsgManager().getOrSyncHistoryMessages(
channelId,
channelType,
oldestOrderSeq,
contain,
dropDown,
limit,
aroundMsgOrderSeq,
new IGetOrSyncHistoryMsgBack() {
@Override
public void onSyncing() {
// Syncing - show loading as needed
}
@Override
public void onResult(List list) {
// Display messages
}
}
);
```
```kotlin Kotlin theme={null}
WKIM.getInstance().msgManager.getOrSyncHistoryMessages(
channelId,
channelType,
oldestOrderSeq,
contain,
dropDown,
limit,
aroundMsgOrderSeq,
object : IGetOrSyncHistoryMsgBack {
override fun onSyncing() {
// Syncing
}
override fun onResult(list: MutableList?) {
// list: retrieved messages, display in UI
}
}
)
```
Getting history messages is not a synchronous method, as there may be non-continuous data that needs to be synced from the server
## Next Steps
Learn how to manage channels and groups
Handle conversation lists and unread messages
Manage channel member information
Configure data sources and sync logic
# Reminder Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/android/reminder
WuKongIM Android SDK reminder management functionality, handling conversation reminders and notifications
Reminder management is responsible for handling various reminder information in conversations, such as @mentions and group audits. Conversation reminders currently only support server-issued commands, and clients only need to listen for sync conversation reminders and refresh conversation messages.
## Get Reminders
### Get Reminders for Specific Conversation
```java Java theme={null}
// Get reminders for specific conversation
WKIM.getInstance().getReminderManager().getReminders(channelId, channelType);
```
```kotlin Kotlin theme={null}
// Get reminders for specific conversation
WKIM.getInstance().reminderManager.getReminders(channelId, channelType)
```
### Get Reminders by Type
```java Java theme={null}
// Get reminders by type
WKIM.getInstance().getReminderManager().getRemindersWithType(String channelID, byte channelType, int type);
```
```kotlin Kotlin theme={null}
// Get reminders by type
WKIM.getInstance().reminderManager.getRemindersWithType(channelID, channelType, type);
```
### Complete Retrieval Example
```java theme={null}
public class ReminderHelper {
// Get all @mention reminders
public List getMentionReminders(String channelId, byte channelType) {
return WKIM.getInstance().getReminderManager().getRemindersWithType(channelId, channelType, 1);
}
// Get group audit reminders
public List getAuditReminders(String channelId, byte channelType) {
return WKIM.getInstance().getReminderManager().getRemindersWithType(channelId, channelType, 2);
}
// Get all unfinished reminders
public List getUnfinishedReminders(String channelId, byte channelType) {
List allReminders = WKIM.getInstance().getReminderManager().getReminders(channelId, channelType);
List unfinishedReminders = new ArrayList<>();
if (allReminders != null) {
for (WKReminder reminder : allReminders) {
if (reminder.done == 0) { // 0 means unfinished
unfinishedReminders.add(reminder);
}
}
}
return unfinishedReminders;
}
// Count reminders by type
public Map getReminderCountByType(String channelId, byte channelType) {
List allReminders = WKIM.getInstance().getReminderManager().getReminders(channelId, channelType);
Map countMap = new HashMap<>();
if (allReminders != null) {
for (WKReminder reminder : allReminders) {
if (reminder.done == 0) { // Only count unfinished ones
countMap.put(reminder.type, countMap.getOrDefault(reminder.type, 0) + 1);
}
}
}
return countMap;
}
// Get latest reminder
public WKReminder getLatestReminder(String channelId, byte channelType) {
List reminders = WKIM.getInstance().getReminderManager().getReminders(channelId, channelType);
if (reminders == null || reminders.isEmpty()) {
return null;
}
// Sort by version number, get the latest
WKReminder latestReminder = reminders.get(0);
for (WKReminder reminder : reminders) {
if (reminder.version > latestReminder.version) {
latestReminder = reminder;
}
}
return latestReminder;
}
}
```
## Save Reminders
```java Java theme={null}
// Save reminders
WKIM.getInstance().getReminderManager().saveOrUpdateReminders(List reminderList);
```
```kotlin Kotlin theme={null}
// Save reminders
WKIM.getInstance().reminderManager.saveOrUpdateReminders(list)
```
### Save Operation Example
```java theme={null}
public class ReminderManager {
// Batch save reminders
public void saveReminders(List reminders) {
if (reminders != null && !reminders.isEmpty()) {
WKIM.getInstance().getReminderManager().saveOrUpdateReminders(reminders);
Log.d("ReminderManager", "Saved " + reminders.size() + " reminders");
}
}
// Create and save @mention reminder
public void createMentionReminder(String channelId, byte channelType, String messageId,
long messageSeq, String mentionedBy, String text) {
WKReminder reminder = new WKReminder();
reminder.reminderID = System.currentTimeMillis(); // Use timestamp as ID
reminder.messageID = messageId;
reminder.channelID = channelId;
reminder.channelType = channelType;
reminder.messageSeq = messageSeq;
reminder.type = 1; // @mention type
reminder.uid = mentionedBy;
reminder.text = text;
reminder.version = System.currentTimeMillis();
reminder.done = 0; // Unfinished
reminder.needUpload = 0; // No need to upload
reminder.publisher = mentionedBy;
// Save single reminder
List reminderList = new ArrayList<>();
reminderList.add(reminder);
saveReminders(reminderList);
}
// Mark reminder as done
public void markReminderAsDone(long reminderId) {
// Here you need to get the reminder first, then update status
// Actual implementation might need more direct update methods
Log.d("ReminderManager", "Marked reminder " + reminderId + " as done");
}
// Clean up expired reminders
public void cleanupExpiredReminders(String channelId, byte channelType, long expireTime) {
List allReminders = WKIM.getInstance().getReminderManager().getReminders(channelId, channelType);
List validReminders = new ArrayList<>();
if (allReminders != null) {
for (WKReminder reminder : allReminders) {
if (reminder.version > expireTime) {
validReminders.add(reminder);
}
}
// Re-save valid reminders
if (validReminders.size() != allReminders.size()) {
saveReminders(validReminders);
Log.d("ReminderManager", "Cleaned up " + (allReminders.size() - validReminders.size()) + " expired reminders");
}
}
}
}
```
## Event Listening
### Listen for New Reminders
```java Java theme={null}
// Listen for new reminders
WKIM.getInstance().getReminderManager().addOnNewReminderListener("key", new INewReminderListener() {
@Override
public void newReminder(List list) {
// Handle new reminders
handleNewReminders(list);
}
});
// Remove listener
WKIM.getInstance().getReminderManager().removeNewReminderListener("key");
```
```kotlin Kotlin theme={null}
// Listen for new reminders
WKIM.getInstance().reminderManager.addOnNewReminderListener("key", object : INewReminderListener {
override fun newReminder(list: List) {
// Handle new reminders
handleNewReminders(list)
}
})
// Remove listener
WKIM.getInstance().reminderManager.removeNewReminderListener("key");
```
The key is a unique identifier for the listener, can be any string. The same key must be used when adding and removing listeners.
### Complete Listening Example
```java theme={null}
public class ReminderListener {
private static final String LISTENER_KEY = "ReminderListener";
public void initialize() {
// Add new reminder listener
WKIM.getInstance().getReminderManager().addOnNewReminderListener(LISTENER_KEY, this::handleNewReminders);
}
private void handleNewReminders(List reminders) {
if (reminders == null || reminders.isEmpty()) {
return;
}
Log.d("ReminderListener", "Received " + reminders.size() + " new reminders");
for (WKReminder reminder : reminders) {
switch (reminder.type) {
case 1: // @mention
handleMentionReminder(reminder);
break;
case 2: // Group audit
handleAuditReminder(reminder);
break;
default:
handleUnknownReminder(reminder);
break;
}
}
// Update UI
updateReminderUI(reminders);
// Send notifications
sendReminderNotifications(reminders);
}
private void handleMentionReminder(WKReminder reminder) {
Log.d("ReminderListener", "Handle @mention: " + reminder.text);
// Update @mention status in conversation list
updateConversationMentionStatus(reminder.channelID, reminder.channelType, true);
// Send local notification
sendMentionNotification(reminder);
}
private void handleAuditReminder(WKReminder reminder) {
Log.d("ReminderListener", "Handle audit reminder: " + reminder.text);
// Update group management interface
updateGroupAuditStatus(reminder.channelID);
// Send audit notification
sendAuditNotification(reminder);
}
private void handleUnknownReminder(WKReminder reminder) {
Log.w("ReminderListener", "Unknown reminder type: " + reminder.type);
}
private void updateReminderUI(List reminders) {
// Notify UI to update reminder status
Intent intent = new Intent("com.app.REMINDER_UPDATED");
intent.putExtra("reminder_count", reminders.size());
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
private void sendReminderNotifications(List reminders) {
for (WKReminder reminder : reminders) {
if (shouldShowNotification(reminder)) {
showReminderNotification(reminder);
}
}
}
private boolean shouldShowNotification(WKReminder reminder) {
// Check if notification should be shown
// Can decide based on user settings, app state, etc.
return !isAppInForeground() && reminder.done == 0;
}
private void showReminderNotification(WKReminder reminder) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(getReminderTitle(reminder))
.setContentText(reminder.text)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true);
// Set click intent
Intent intent = new Intent(context, ChatActivity.class);
intent.putExtra("channel_id", reminder.channelID);
intent.putExtra("channel_type", reminder.channelType);
intent.putExtra("message_id", reminder.messageID);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(pendingIntent);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify((int) reminder.reminderID, builder.build());
}
private String getReminderTitle(WKReminder reminder) {
switch (reminder.type) {
case 1:
return "Someone mentioned you";
case 2:
return "Group audit";
default:
return "New reminder";
}
}
public void destroy() {
WKIM.getInstance().getReminderManager().removeNewReminderListener(LISTENER_KEY);
}
}
```
## WKReminder Data Structure
### Reminder Properties
```java theme={null}
public class WKReminder {
public long reminderID; // Reminder ID
public String messageID; // Message ID
public String channelID; // Channel ID
public byte channelType; // Channel type
public long messageSeq; // Message sequence number
public int type; // Reminder type [1=@mention][2=group audit] etc.
public String uid; // User ID
public String text; // Reminder content
public Map data; // Custom data included in reminder
public long version; // Version number for incremental sync
public int done; // 0=unfinished 1=finished
public int needUpload; // 0=no need to upload 1=need to upload
public String publisher; // Publisher
}
```
### Property Description
| Property | Type | Description |
| ------------- | ------ | ----------------------------------------------- |
| `reminderID` | long | Unique reminder identifier |
| `messageID` | String | Associated message ID |
| `channelID` | String | Channel ID |
| `channelType` | byte | Channel type |
| `messageSeq` | long | Message sequence number |
| `type` | int | Reminder type (1=@mention, 2=group audit, etc.) |
| `uid` | String | Related user ID |
| `text` | String | Reminder display text |
| `data` | Map | Custom data |
| `version` | long | Version number for incremental sync |
| `done` | int | Completion status (0=unfinished, 1=finished) |
| `needUpload` | int | Whether needs upload (0=no, 1=yes) |
| `publisher` | String | Publisher ID |
### Reminder Type Description
| Type Value | Description | Use Case |
| ---------- | ---------------- | -------------------------------------------- |
| `1` | @mention | Someone mentioned current user in group chat |
| `2` | Group audit | Items that group admin needs to audit |
| Others | Custom reminders | Extended based on business needs |
## Best Practices
### 1. Reminder Status Management
```java theme={null}
public class ReminderStatusManager {
// Get unread reminder count
public int getUnreadReminderCount(String channelId, byte channelType) {
List reminders = WKIM.getInstance().getReminderManager().getReminders(channelId, channelType);
int count = 0;
if (reminders != null) {
for (WKReminder reminder : reminders) {
if (reminder.done == 0) {
count++;
}
}
}
return count;
}
// Check if has @mention reminder
public boolean hasMentionReminder(String channelId, byte channelType) {
List mentionReminders = WKIM.getInstance().getReminderManager()
.getRemindersWithType(channelId, channelType, 1);
if (mentionReminders != null) {
for (WKReminder reminder : mentionReminders) {
if (reminder.done == 0) {
return true;
}
}
}
return false;
}
// Get reminder summary text
public String getReminderSummary(String channelId, byte channelType) {
Map countMap = getReminderCountByType(channelId, channelType);
if (countMap.isEmpty()) {
return "";
}
StringBuilder summary = new StringBuilder();
if (countMap.containsKey(1)) {
summary.append("[Someone mentioned you]");
}
if (countMap.containsKey(2)) {
if (summary.length() > 0) {
summary.append(" ");
}
summary.append("[Group audit]");
}
return summary.toString();
}
}
```
### 2. Memory Management
```java theme={null}
@Override
protected void onDestroy() {
super.onDestroy();
// Remove reminder listeners
WKIM.getInstance().getReminderManager().removeNewReminderListener("ActivityKey");
// Clean up local broadcast receivers
if (reminderReceiver != null) {
LocalBroadcastManager.getInstance(this).unregisterReceiver(reminderReceiver);
}
}
```
## Next Steps
Explore advanced features and optimizations
Return to data source configuration
Return to conversation management functionality
Return to command management functionality
# Basic Features
Source: https://wukong.mintlify.app/en/sdk/wukongim/flutter/base
WuKongIM Flutter SDK basic functionality, including initialization, connection management and status monitoring
## Initialization
### Connection IP
```dart theme={null}
WKIM.shared.options.getAddr = (Function(String address) complete) async {
// Can get through interface and return
complete('xxxxx:5100');
};
```
Return the IP of the IM communication end and the TCP port of the IM communication end. **For distributed systems, call the interface to get IP and Port before returning**
### Initialize SDK
```dart theme={null}
// uid Login user ID (uid registered with IM communication end by business server)
// token Login user token (token registered with IM communication end by business server)
WKIM.shared.setup(Options.newDefault('uid', 'token'));
```
## Connect/Disconnect
### Connect
```dart theme={null}
WKIM.shared.connectionManager.connect();
```
### Disconnect
```dart theme={null}
// isLogout true: logout and no longer reconnect false: logout but maintain reconnection
WKIM.shared.connectionManager.disconnect(isLogout)
```
## Connection Status Monitoring
```dart theme={null}
WKIM.shared.connectionManager.addOnConnectionStatus('home',
(status, reason, connInfo) {
if (status == WKConnectStatus.connecting) {
// Connecting
} else if (status == WKConnectStatus.success) {
// Connection successful
// connInfo.nodeId Node ID
} else if (status == WKConnectStatus.noNetwork) {
// No network connection
} else if (status == WKConnectStatus.syncMsg) {
// Syncing messages
} else if (status == WKConnectStatus.kicked) {
// Kicked offline - need to exit app and return to login page
} else if (status == WKConnectStatus.fail) {
// Connection failed
} else if (status == WKConnectStatus.syncCompleted) {
// Sync completed
}
});
```
### Remove Connection Status Listener
```dart theme={null}
// Remove specific listener
WKIM.shared.connectionManager.removeOnConnectionStatus('home');
```
## Complete Connection Management Example
```dart theme={null}
class ConnectionManager {
static final ConnectionManager _instance = ConnectionManager._internal();
factory ConnectionManager() => _instance;
ConnectionManager._internal();
bool _isConnected = false;
String? _currentNodeId;
// Initialize connection
Future initialize(String uid, String token) async {
try {
// Setup SDK
await WKIM.shared.setup(Options.newDefault(uid, token));
// Configure server address
WKIM.shared.options.getAddr = (Function(String address) complete) async {
final serverAddress = await _getServerAddress();
complete(serverAddress);
};
// Setup connection listener
_setupConnectionListener();
print('Connection manager initialized');
} catch (e) {
print('Failed to initialize connection manager: $e');
}
}
void _setupConnectionListener() {
WKIM.shared.connectionManager.addOnConnectionStatus('connection_manager',
(status, reason, connInfo) {
_handleConnectionStatus(status, reason, connInfo);
});
}
void _handleConnectionStatus(int status, String reason, WKConnectInfo? connInfo) {
switch (status) {
case WKConnectStatus.connecting:
print('🔄 Connecting to server...');
_isConnected = false;
_notifyConnectionStatus(false, 'Connecting...');
break;
case WKConnectStatus.success:
print('✅ Connected successfully');
_isConnected = true;
_currentNodeId = connInfo?.nodeId;
_notifyConnectionStatus(true, 'Connected');
_onConnected();
break;
case WKConnectStatus.noNetwork:
print('❌ No network connection');
_isConnected = false;
_notifyConnectionStatus(false, 'No network');
break;
case WKConnectStatus.syncMsg:
print('🔄 Syncing messages...');
_notifyConnectionStatus(false, 'Syncing messages...');
break;
case WKConnectStatus.kicked:
print('❌ Kicked offline');
_isConnected = false;
_handleKickedOffline();
break;
case WKConnectStatus.fail:
print('❌ Connection failed: $reason');
_isConnected = false;
_notifyConnectionStatus(false, 'Connection failed');
_handleConnectionFailure(reason);
break;
case WKConnectStatus.syncCompleted:
print('✅ Sync completed');
_notifyConnectionStatus(true, 'Sync completed');
break;
default:
print('Unknown connection status: $status');
break;
}
}
void _onConnected() {
// Perform actions after successful connection
_syncOfflineData();
_updateOnlineStatus();
}
void _handleKickedOffline() {
// Handle being kicked offline
_notifyKickedOffline();
_clearUserSession();
_redirectToLogin();
}
void _handleConnectionFailure(String reason) {
// Handle connection failure
if (reason.contains('auth')) {
// Authentication failure
_handleAuthFailure();
} else {
// Network or server error
_scheduleReconnect();
}
}
void _syncOfflineData() {
// Sync offline conversations and messages
print('Syncing offline data...');
}
void _updateOnlineStatus() {
// Update user online status
print('Updating online status...');
}
void _handleAuthFailure() {
// Handle authentication failure
print('Authentication failed - redirecting to login');
_clearUserSession();
_redirectToLogin();
}
void _scheduleReconnect() {
// Schedule automatic reconnection
print('Scheduling reconnection...');
}
void _clearUserSession() {
// Clear user session data
print('Clearing user session...');
}
void _redirectToLogin() {
// Redirect to login page
print('Redirecting to login page...');
}
// Public methods
void connect() {
WKIM.shared.connectionManager.connect();
}
void disconnect({bool logout = false}) {
WKIM.shared.connectionManager.disconnect(logout);
_isConnected = false;
}
bool get isConnected => _isConnected;
String? get currentNodeId => _currentNodeId;
// Helper methods
Future _getServerAddress() async {
// Get server address from configuration or API
// For distributed systems, call API to get current server
return 'your-server.com:5100';
}
void _notifyConnectionStatus(bool connected, String message) {
// Notify UI about connection status changes
// You can use streams, callbacks, or state management solutions
}
void _notifyKickedOffline() {
// Notify UI that user was kicked offline
}
void dispose() {
WKIM.shared.connectionManager.removeOnConnectionStatus('connection_manager');
}
}
```
## Connection Status Types
| Status | Description |
| ------------------------------- | -------------------------------- |
| `WKConnectStatus.connecting` | Connecting to server |
| `WKConnectStatus.success` | Connection successful |
| `WKConnectStatus.noNetwork` | No network available |
| `WKConnectStatus.syncMsg` | Syncing messages |
| `WKConnectStatus.kicked` | Kicked offline by another device |
| `WKConnectStatus.fail` | Connection failed |
| `WKConnectStatus.syncCompleted` | Message sync completed |
## Best Practices
### 1. Application Lifecycle Management
```dart theme={null}
class AppLifecycleManager extends WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
switch (state) {
case AppLifecycleState.resumed:
// App came to foreground
_onAppResumed();
break;
case AppLifecycleState.paused:
// App went to background
_onAppPaused();
break;
case AppLifecycleState.detached:
// App is being terminated
_onAppDetached();
break;
default:
break;
}
}
void _onAppResumed() {
// Reconnect if needed
if (!ConnectionManager().isConnected) {
ConnectionManager().connect();
}
}
void _onAppPaused() {
// Optionally disconnect to save battery
// ConnectionManager().disconnect(logout: false);
}
void _onAppDetached() {
// Clean up resources
ConnectionManager().dispose();
}
}
```
### 2. Network State Monitoring
```dart theme={null}
import 'package:connectivity_plus/connectivity_plus.dart';
class NetworkMonitor {
late StreamSubscription _connectivitySubscription;
void startMonitoring() {
_connectivitySubscription = Connectivity().onConnectivityChanged.listen((result) {
_handleConnectivityChange(result);
});
}
void _handleConnectivityChange(ConnectivityResult result) {
switch (result) {
case ConnectivityResult.wifi:
case ConnectivityResult.mobile:
print('Network available - attempting to connect');
if (!ConnectionManager().isConnected) {
ConnectionManager().connect();
}
break;
case ConnectivityResult.none:
print('Network unavailable');
break;
}
}
void dispose() {
_connectivitySubscription.cancel();
}
}
```
### 3. Error Handling and Retry Logic
```dart theme={null}
class ConnectionRetryManager {
int _retryCount = 0;
int _maxRetries = 5;
Timer? _retryTimer;
void handleConnectionFailure(String reason) {
if (_retryCount < _maxRetries) {
_retryCount++;
final delay = Duration(seconds: _getRetryDelay());
print('Connection failed, retrying in ${delay.inSeconds} seconds (attempt $_retryCount/$_maxRetries)');
_retryTimer = Timer(delay, () {
ConnectionManager().connect();
});
} else {
print('Max retry attempts reached, giving up');
_handleMaxRetriesReached();
}
}
void onConnectionSuccess() {
_retryCount = 0;
_retryTimer?.cancel();
}
int _getRetryDelay() {
// Exponential backoff: 2, 4, 8, 16, 32 seconds
return math.min(math.pow(2, _retryCount).toInt(), 32);
}
void _handleMaxRetriesReached() {
// Show error message to user
// Maybe redirect to offline mode or login page
}
void dispose() {
_retryTimer?.cancel();
}
}
```
## Next Steps
Learn how to handle message sending and receiving
Manage channels and groups
Handle conversation lists
Explore advanced features and configuration
# Integration Guide
Source: https://wukong.mintlify.app/en/sdk/wukongim/flutter/integration
WuKongIM Flutter SDK integration guide, including installation, configuration and initialization
## Quick Start
### Installation
[](https://pub.dartlang.org/packages/wukongimfluttersdk)
```yaml theme={null}
dependencies:
wukongimfluttersdk: ^version # Check version number above
```
### Import
```dart theme={null}
import 'package:wukongimfluttersdk/wkim.dart';
```
## Basic Setup
### 1. Initialize SDK
Initialize the SDK in your app's main function or during app startup:
```dart theme={null}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize WuKongIM SDK
await WKIM.shared.setup(
uid: 'your_user_id',
token: 'your_auth_token',
);
runApp(MyApp());
}
```
### 2. Configure Connection
Set up connection parameters and server information:
```dart theme={null}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State {
@override
void initState() {
super.initState();
_initializeWuKongIM();
}
void _initializeWuKongIM() {
// Configure server address
WKIM.shared.connectionManager.setServerAddress('your-server.com', 5100);
// Set connection options
WKIM.shared.connectionManager.setOptions(
heartbeatInterval: 30, // Heartbeat interval in seconds
reconnectInterval: 5, // Reconnect interval in seconds
maxReconnectAttempts: 10, // Maximum reconnect attempts
);
// Listen for connection status
WKIM.shared.connectionManager.addOnConnectionStatus('main', (status, reason, connInfo) {
print('Connection status: $status, reason: $reason');
_handleConnectionStatus(status, reason);
});
// Connect to server
WKIM.shared.connectionManager.connect();
}
void _handleConnectionStatus(int status, String reason) {
switch (status) {
case WKConnectStatus.success:
print('Connected successfully');
break;
case WKConnectStatus.connecting:
print('Connecting...');
break;
case WKConnectStatus.disconnect:
print('Disconnected');
break;
case WKConnectStatus.kicked:
print('Kicked offline');
_handleKickedOffline();
break;
}
}
void _handleKickedOffline() {
// Handle being kicked offline
// Redirect to login page or show notification
}
@override
void dispose() {
// Clean up listeners
WKIM.shared.connectionManager.removeOnConnectionStatus('main');
super.dispose();
}
}
```
## Advanced Configuration
### 1. Custom Configuration Options
```dart theme={null}
class WuKongIMConfig {
static void configure() {
// Set debug mode
WKIM.shared.setDebugMode(true);
// Configure database options
WKIM.shared.setDatabaseOptions(
dbName: 'wukongim.db',
dbPassword: 'your_db_password', // Optional encryption
);
// Configure file upload settings
WKIM.shared.setFileUploadOptions(
maxFileSize: 100 * 1024 * 1024, // 100MB
allowedFileTypes: ['jpg', 'png', 'gif', 'mp4', 'mp3'],
uploadTimeout: 60, // seconds
);
// Configure message options
WKIM.shared.setMessageOptions(
maxMessageLength: 5000,
enableMessageReceipt: true,
enableTypingIndicator: true,
);
}
}
```
### 2. Environment-Specific Setup
```dart theme={null}
class EnvironmentConfig {
static void setupForEnvironment() {
if (kDebugMode) {
// Development environment
_setupDevelopment();
} else {
// Production environment
_setupProduction();
}
}
static void _setupDevelopment() {
WKIM.shared.setDebugMode(true);
WKIM.shared.connectionManager.setServerAddress('dev-server.com', 5100);
WKIM.shared.setLogLevel(WKLogLevel.debug);
}
static void _setupProduction() {
WKIM.shared.setDebugMode(false);
WKIM.shared.connectionManager.setServerAddress('prod-server.com', 5100);
WKIM.shared.setLogLevel(WKLogLevel.error);
}
}
```
### 3. Permission Setup
Add necessary permissions to your platform-specific configuration files:
#### Android (android/app/src/main/AndroidManifest.xml)
```xml theme={null}
```
#### iOS (ios/Runner/Info.plist)
```xml theme={null}
NSCameraUsageDescription
This app needs access to camera to take photos
NSMicrophoneUsageDescription
This app needs access to microphone to record audio
NSPhotoLibraryUsageDescription
This app needs access to photo library to select images
```
## Complete Integration Example
```dart theme={null}
import 'package:flutter/material.dart';
import 'package:wukongimfluttersdk/wkim.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize SDK
await WuKongIMManager.initialize();
runApp(MyApp());
}
class WuKongIMManager {
static Future initialize() async {
try {
// Setup SDK with user credentials
await WKIM.shared.setup(
uid: await _getUserId(),
token: await _getAuthToken(),
);
// Configure SDK
_configureSDK();
// Setup listeners
_setupGlobalListeners();
print('WuKongIM SDK initialized successfully');
} catch (e) {
print('Failed to initialize WuKongIM SDK: $e');
}
}
static void _configureSDK() {
// Set debug mode based on build mode
WKIM.shared.setDebugMode(kDebugMode);
// Configure connection
WKIM.shared.connectionManager.setServerAddress(
_getServerAddress(),
_getServerPort(),
);
// Set connection options
WKIM.shared.connectionManager.setOptions(
heartbeatInterval: 30,
reconnectInterval: 5,
maxReconnectAttempts: 10,
);
}
static void _setupGlobalListeners() {
// Connection status listener
WKIM.shared.connectionManager.addOnConnectionStatus('global', (status, reason, connInfo) {
_handleGlobalConnectionStatus(status, reason);
});
// Global message listener
WKIM.shared.messageManager.addOnNewMsgListener('global', (messages) {
_handleGlobalNewMessages(messages);
});
// Global conversation listener
WKIM.shared.conversationManager.addOnRefreshMsgListener('global', (conversation, isEnd) {
if (isEnd) {
_handleConversationUpdate(conversation);
}
});
}
static void _handleGlobalConnectionStatus(int status, String reason) {
switch (status) {
case WKConnectStatus.success:
print('Global: Connected successfully');
break;
case WKConnectStatus.kicked:
print('Global: Kicked offline');
_handleGlobalKickOff();
break;
case WKConnectStatus.disconnect:
print('Global: Disconnected - $reason');
break;
}
}
static void _handleGlobalNewMessages(List messages) {
// Handle new messages globally (notifications, badges, etc.)
for (var message in messages) {
_showNotificationForMessage(message);
}
}
static void _handleConversationUpdate(WKUIConversationMsg conversation) {
// Update app badge count
_updateAppBadge();
}
static void _handleGlobalKickOff() {
// Handle being kicked offline
// Clear user session and redirect to login
}
static void _showNotificationForMessage(WKMsg message) {
// Show local notification for new message
}
static void _updateAppBadge() {
// Update app icon badge with unread count
final unreadCount = WKIM.shared.conversationManager.getAllUnreadCount();
// Update badge using platform-specific code
}
// Helper methods
static Future _getUserId() async {
// Get user ID from secure storage or preferences
return 'user123';
}
static Future _getAuthToken() async {
// Get auth token from secure storage
return 'auth_token_here';
}
static String _getServerAddress() {
return kDebugMode ? 'dev-server.com' : 'prod-server.com';
}
static int _getServerPort() {
return 5100;
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'WuKongIM Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State {
int _connectionStatus = WKConnectStatus.disconnect;
@override
void initState() {
super.initState();
_setupListeners();
_connectToServer();
}
void _setupListeners() {
WKIM.shared.connectionManager.addOnConnectionStatus('home', (status, reason, connInfo) {
setState(() {
_connectionStatus = status;
});
});
}
void _connectToServer() {
WKIM.shared.connectionManager.connect();
}
@override
void dispose() {
WKIM.shared.connectionManager.removeOnConnectionStatus('home');
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('WuKongIM Demo'),
actions: [
Icon(
_connectionStatus == WKConnectStatus.success
? Icons.wifi
: Icons.wifi_off,
color: _connectionStatus == WKConnectStatus.success
? Colors.green
: Colors.red,
),
],
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Connection Status: ${_getStatusText(_connectionStatus)}'),
SizedBox(height: 20),
ElevatedButton(
onPressed: _connectionStatus == WKConnectStatus.success
? null
: _connectToServer,
child: Text('Connect'),
),
],
),
),
);
}
String _getStatusText(int status) {
switch (status) {
case WKConnectStatus.success:
return 'Connected';
case WKConnectStatus.connecting:
return 'Connecting';
case WKConnectStatus.disconnect:
return 'Disconnected';
case WKConnectStatus.kicked:
return 'Kicked Offline';
default:
return 'Unknown';
}
}
}
```
## Next Steps
Learn connection management and basic API usage
Explore message sending and receiving functionality
Handle conversation lists and unread messages
Discover custom messages and extension features
# SDK Introduction
Source: https://wukong.mintlify.app/en/sdk/wukongim/flutter/intro
WuKongIM Flutter SDK design philosophy, architecture overview and core functionality introduction
WuKongIM Flutter SDK provides a complete instant messaging solution for Flutter applications, using modular design to enable developers to quickly integrate and use various IM features.
## Design Philosophy
To make it faster and more convenient for developers to use the SDK, WuKong SDK provides a single entry point to access all functions in the SDK. Like a table of contents in a book, you can find corresponding content through the directory. For example, connecting to IM:
```dart theme={null}
WKIM.shared.connectionManager.connect();
```
This design allows developers to:
* **Unified Entry**: Access all functions through `WKIM.shared`
* **Modular Management**: Each functional module has clear responsibilities
* **Easy Maintenance**: Clear code structure, easy to debug and maintain
## Architecture Overview
WuKongIM Flutter SDK adopts a modular architecture design, where various modules work together to provide developers with complete instant messaging functionality:
```mermaid theme={null}
graph TB
A[WKIM.shared] --> B[MessageManager Message Manager]
A --> C[ConversationManager Conversation Manager]
A --> D[ConnectionManager Connection Manager]
A --> E[ChannelManager Channel Manager]
A --> F[ChannelMemberManager Channel Member Manager]
A --> G[ReminderManager Reminder Manager]
A --> H[CMDManager Command Manager]
B --> B1[Message Send/Receive]
B --> B2[Message History]
B --> B3[Message Listening]
C --> C1[Conversation List]
C --> C2[Unread Messages]
C --> C3[Conversation Operations]
D --> D1[Connection Status]
D --> D2[Network Management]
D --> D3[Reconnection Mechanism]
E --> E1[Channel Information]
E --> E2[Channel Operations]
E --> E3[Channel Listening]
F --> F1[Member List]
F --> F2[Member Operations]
F --> F3[Permission Management]
G --> G1[@Reminders]
G --> G2[Custom Reminders]
G --> G3[Reminder Management]
H --> H1[Command Listening]
H --> H2[Command Processing]
H --> H3[System Messages]
```
## Core Functional Modules
### Message Manager (MessageManager)
Responsible for CRUD operations on messages, new message listening, refresh message listening, message storage, monitoring sync of specific chat data, etc.
```dart theme={null}
// Message Manager
WKIM.shared.messageManager
// Main Functions
- Send various types of messages (text, image, voice, video, etc.)
- Receive and process new messages
- Query message history
- Message status management
- Custom message types
```
### Conversation Manager (ConversationManager)
Get recent chat records, refresh recent conversations \[new chats, red dot changes], listen for removing a conversation, listen for syncing recent conversations, etc.
```dart theme={null}
// Conversation Management
WKIM.shared.conversationManager
// Main Functions
- Get conversation list
- Conversation sorting and filtering
- Unread message statistics
- Conversation operations (delete, pin, etc.)
- Conversation status listening
```
### Connection Manager (ConnectionManager)
Responsible for IM connection, disconnection, logout, listening for connection status, listening for getting connection IP, etc.
```dart theme={null}
// Connection Management
WKIM.shared.connectionManager
// Main Functions
- Establish and maintain connections
- Connection status listening
- Automatic reconnection mechanism
- Network status handling
- Login/logout management
```
### Channel Manager (ChannelManager)
Can get Channel information, refresh Channel cache, listen for Channel changes \[pin, do not disturb, mute], etc.
```dart theme={null}
// Channel Management
WKIM.shared.channelManager
// Main Functions
- Get channel information
- Channel settings management
- Channel status listening
- Channel cache management
- Channel search
```
### Channel Member Manager (ChannelMemberManager)
Get Channel member list, set member remarks, save and modify member data, listen for refreshing members and removing members, etc.
```dart theme={null}
// Channel Member Management
WKIM.shared.channelMemberManager
// Main Functions
- Get member list
- Member information management
- Member permission control
- Member operation listening
- Member search and filtering
```
### Reminder Manager (ReminderManager)
Get reminders for a conversation such as: \[someone @me] \[group join request], etc. You can also customize reminder items, such as unread voice messages, etc.
```dart theme={null}
// Reminder Management
WKIM.shared.reminderManager
// Main Functions
- @Reminder management
- System notification reminders
- Custom reminder types
- Reminder status management
- Reminder history
```
### Command Manager (CMDManager)
Responsible for listening to command messages sent by the server.
```dart theme={null}
// Command Management
WKIM.shared.cmdManager
// Main Functions
- System command listening
- Command message processing
- Business instruction dispatch
- Status sync commands
- Custom command extensions
```
## SDK and APP Interaction Principles
WuKongIM Flutter SDK adopts an event-driven interaction mode to ensure clear and controllable data flow:
```mermaid theme={null}
sequenceDiagram
participant App as Flutter App
participant SDK as WuKongIM SDK
participant Server as IM Server
App->>SDK: Call SDK methods
SDK->>SDK: Process business logic
SDK->>Server: Send network requests
Server-->>SDK: Return response data
SDK->>SDK: Update local data
SDK-->>App: Callback data through events
App->>App: Update UI interface
```
### Interaction Flow Description
1. **APP calls SDK methods**: Application initiates operations through SDK-provided APIs
2. **SDK processes data**: SDK internally handles business logic, including data validation, format conversion, etc.
3. **Network communication**: SDK exchanges data with the server
4. **Event callbacks**: SDK callbacks processing results to the application through event mechanisms
5. **UI updates**: Application updates user interface based on callback data
### Event Listening Example
```dart theme={null}
class ChatPage extends StatefulWidget {
@override
_ChatPageState createState() => _ChatPageState();
}
class _ChatPageState extends State {
@override
void initState() {
super.initState();
// Listen for new messages
WKIM.shared.messageManager.addOnNewMsgListener('chat', (msgs) {
setState(() {
// Update message list
_updateMessageList(msgs);
});
});
// Listen for connection status
WKIM.shared.connectionManager.addOnConnectionStatus('chat', (status, reason, connInfo) {
setState(() {
// Update connection status
_updateConnectionStatus(status);
});
});
}
@override
void dispose() {
// Remove listeners
WKIM.shared.messageManager.removeNewMsgListener('chat');
WKIM.shared.connectionManager.removeOnConnectionStatus('chat');
super.dispose();
}
// Send message example
void _sendMessage(String text) {
final textContent = WKTextContent(text);
final channel = WKChannel('channelId', WKChannelType.personal);
// Call SDK method to send message
WKIM.shared.messageManager.sendMessage(textContent, channel);
// SDK will callback message sending result through events
}
}
```
## Development Advantages
### 1. Simple and Easy to Use
* **Unified Entry**: All functions accessed through `WKIM.shared`
* **Clear Structure**: Modular design with clear responsibilities
* **Rich Examples**: Complete usage examples provided
### 2. Complete Functionality
* **Full Platform Support**: Supports both iOS and Android platforms
* **Rich Message Types**: Supports text, image, voice, video and other message types
* **Custom Extensions**: Supports custom message types and business logic
### 3. Performance Optimization
* **Local Caching**: Smart caching mechanism reduces network requests
* **Incremental Sync**: Only syncs changed data for improved efficiency
* **Memory Management**: Optimized memory usage, avoiding memory leaks
### 4. Stable and Reliable
* **Auto Reconnection**: Automatic reconnection during network exceptions
* **Data Consistency**: Ensures data integrity and consistency
* **Error Handling**: Comprehensive error handling mechanisms
## Quick Start
Ready to start using WuKongIM Flutter SDK?
Learn how to integrate WuKongIM SDK in Flutter projects
Master connection management and basic API usage
Learn message sending/receiving and history query
Explore custom messages and extension features
# Message Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/flutter/message
WuKongIM Flutter SDK message management functionality, including message sending/receiving, listening and history messages
## Sending Messages
```dart theme={null}
WKIM.shared.messageManager.sendMessage(WKTextContent('I am a text message'), WKChannel('uid_1', WKChannelType.personal));
```
### Text Messages
```dart theme={null}
// Define text message
WKTextContent text = WKTextContent("Hello, WuKong");
// Send text message
WKIM.shared.messageManager.sendMessage(text, channel);
```
### Image Messages
```dart theme={null}
// Define image message
WKImageContent image = WKImageContent(100, 100);
image.localPath = "xxx"; // Image local path
image.url = "http://xxx.com/xxx.jpg"
// Send image message
WKIM.shared.messageManager.sendMessage(image, channel);
```
### Custom Messages
Reference custom messages: [Custom Messages](/en/sdk/wukongim/flutter/advance#custom-messages)
## Message Storage Callback (Not Message Send Status)
When sending messages, the SDK will trigger a storage callback after saving the message to the local database. At this point, the message has not been sent yet, and you can display the message in the UI in this listener.
Listen for message storage events:
```dart theme={null}
WKIM.shared.messageManager.addOnMsgInsertedListener((wkMsg) {
// Display in UI
});
```
## New Messages
Listen for new message events:
```dart theme={null}
// Listen for new message events
WKIM.shared.messageManager.addOnNewMsgListener('chat', (msgs) {
// Display in UI
});
// Remove new message listener
WKIM.shared.messageManager.removeNewMsgListener('chat');
```
If you receive new messages in a chat page, you need to determine whether the message belongs to the current conversation by checking the `channelID` and `channelType` of the message object `WKMsg`
## Message Refresh Listening
When the SDK updates messages, such as: message send status, someone likes a message, message read receipt, message recall, message editing, etc., the SDK will callback the following event. The UI can determine which specific message has changed through the `clientMsgNO` of the message object `WKMsg`.
Listen for refresh message events:
```dart theme={null}
// Listen for refresh message events
WKIM.shared.messageManager.addOnRefreshMsgListener('chat', (wkMsg) {
// TODO refresh message
});
// Remove refresh message listener
WKIM.shared.messageManager.removeOnRefreshMsgListener('chat');
```
## Message Send Status Code (ReasonCode)
When a message is sent, you can obtain the `WKMsg` object by listening for message refresh events. The `status` (send status) and `reasonCode` in `WKMsg` indicate the result of the message delivery.
| Value | Name | Description |
| ----- | ---------------------------- | ------------------------------------------ |
| 0 | ReasonUnknown | Unknown error |
| 1 | ReasonSuccess | Success |
| 2 | ReasonAuthFail | Authentication failed |
| 3 | ReasonSubscriberNotExist | Subscriber does not exist in the channel |
| 4 | ReasonInBlacklist | In blacklist |
| 5 | ReasonChannelNotExist | Channel does not exist |
| 6 | ReasonUserNotOnNode | User is not on node |
| 7 | ReasonSenderOffline | Sender is offline, message delivery failed |
| 8 | ReasonMsgKeyError | Message key error, invalid message |
| 9 | ReasonPayloadDecodeError | Payload decoding failed |
| 10 | ReasonForwardSendPacketError | Forwarding send packet failed |
| 11 | ReasonNotAllowSend | Not allowed to send message |
| 12 | ReasonConnectKick | Connection kicked |
| 13 | ReasonNotInWhitelist | Not in whitelist |
| 14 | ReasonQueryTokenError | Query user token error |
| 15 | ReasonSystemError | System error |
| 16 | ReasonChannelIDError | Wrong channel ID |
| 17 | ReasonNodeMatchError | Node matching error |
| 18 | ReasonNodeNotMatch | Node not matched |
| 19 | ReasonBan | Channel is banned |
| 20 | ReasonNotSupportHeader | Unsupported header |
| 21 | ReasonClientKeyIsEmpty | clientKey is empty |
| 22 | ReasonRateLimit | Rate limit exceeded |
| 23 | ReasonNotSupportChannelType | Unsupported channel type |
| 24 | ReasonDisband | Channel disbanded |
| 25 | ReasonSendBan | Sending is banned |
## View Chat Information for a Channel
```dart theme={null}
/*
* Query or sync messages for a channel
*
* @param channelId Channel ID
* @param channelType Channel type
* @param oldestOrderSeq Last message's large orderSeq, pass 0 for first entry into chat
* @param contain Whether to include the oldestOrderSeq message
* @param pullMode Pull mode 0: pull down 1: pull up
* @param aroundMsgOrderSeq Query messages around this message, e.g. aroundMsgOrderSeq=20 returns [16,17,19,20,21,22,23,24,25]
* @param limit Number to get each time
* @param iGetOrSyncHistoryMsgBack Request callback
* @param syncBack Sync message callback, can show loading through this callback
*/
WKIM.shared.messageManager.getOrSyncHistoryMessages(
channelID, channelType, oldestOrderSeq, contain, pullMode, limit, aroundMsgOrderSeq, Function(List)){
}, Function() syncBack);
```
Getting history messages is not a synchronous method, as there may be non-continuous data that needs to be synced from the server
## Complete Message Management Example
```dart theme={null}
class MessageManager {
static final MessageManager _instance = MessageManager._internal();
factory MessageManager() => _instance;
MessageManager._internal();
final Map> _channelMessages = {};
final StreamController> _newMessagesController = StreamController.broadcast();
final StreamController _messageUpdateController = StreamController.broadcast();
// Streams for UI to listen
Stream> get newMessagesStream => _newMessagesController.stream;
Stream get messageUpdateStream => _messageUpdateController.stream;
void initialize() {
_setupMessageListeners();
}
void _setupMessageListeners() {
// Listen for message storage
WKIM.shared.messageManager.addOnMsgInsertedListener((wkMsg) {
_handleMessageInserted(wkMsg);
});
// Listen for new messages
WKIM.shared.messageManager.addOnNewMsgListener('global', (msgs) {
_handleNewMessages(msgs);
});
// Listen for message updates
WKIM.shared.messageManager.addOnRefreshMsgListener('global', (wkMsg) {
_handleMessageUpdate(wkMsg);
});
}
void _handleMessageInserted(WKMsg message) {
// Message saved to database, update UI immediately
final channelKey = '${message.channelID}_${message.channelType}';
_channelMessages[channelKey] ??= [];
_channelMessages[channelKey]!.add(message);
// Notify UI
_newMessagesController.add([message]);
}
void _handleNewMessages(List messages) {
for (var message in messages) {
final channelKey = '${message.channelID}_${message.channelType}';
_channelMessages[channelKey] ??= [];
// Check if message already exists
final existingIndex = _channelMessages[channelKey]!
.indexWhere((m) => m.clientMsgNO == message.clientMsgNO);
if (existingIndex >= 0) {
// Update existing message
_channelMessages[channelKey]![existingIndex] = message;
} else {
// Add new message
_channelMessages[channelKey]!.add(message);
}
}
// Notify UI
_newMessagesController.add(messages);
}
void _handleMessageUpdate(WKMsg message) {
final channelKey = '${message.channelID}_${message.channelType}';
if (_channelMessages.containsKey(channelKey)) {
final messages = _channelMessages[channelKey]!;
final index = messages.indexWhere((m) => m.clientMsgNO == message.clientMsgNO);
if (index >= 0) {
messages[index] = message;
_messageUpdateController.add(message);
}
}
}
// Send text message
Future sendTextMessage(String text, WKChannel channel) async {
try {
final textContent = WKTextContent(text);
await WKIM.shared.messageManager.sendMessage(textContent, channel);
} catch (e) {
print('Failed to send text message: $e');
rethrow;
}
}
// Send image message
Future sendImageMessage(String imagePath, WKChannel channel, {int? width, int? height}) async {
try {
final imageContent = WKImageContent(width ?? 0, height ?? 0);
imageContent.localPath = imagePath;
await WKIM.shared.messageManager.sendMessage(imageContent, channel);
} catch (e) {
print('Failed to send image message: $e');
rethrow;
}
}
// Load history messages
Future> loadHistoryMessages(
String channelID,
int channelType, {
int oldestOrderSeq = 0,
bool contain = false,
int pullMode = 0,
int limit = 20,
int aroundMsgOrderSeq = 0,
}) async {
final completer = Completer>();
WKIM.shared.messageManager.getOrSyncHistoryMessages(
channelID,
channelType,
oldestOrderSeq,
contain,
pullMode,
limit,
aroundMsgOrderSeq,
(messages) {
// Update local cache
final channelKey = '${channelID}_$channelType';
_channelMessages[channelKey] = messages;
completer.complete(messages);
},
() {
// Sync callback - show loading
print('Syncing messages for channel $channelID...');
},
);
return completer.future;
}
// Get messages for a channel
List getMessagesForChannel(String channelID, int channelType) {
final channelKey = '${channelID}_$channelType';
return _channelMessages[channelKey] ?? [];
}
// Clear messages for a channel
void clearMessagesForChannel(String channelID, int channelType) {
final channelKey = '${channelID}_$channelType';
_channelMessages.remove(channelKey);
}
void dispose() {
_newMessagesController.close();
_messageUpdateController.close();
// Remove listeners
WKIM.shared.messageManager.removeNewMsgListener('global');
WKIM.shared.messageManager.removeOnRefreshMsgListener('global');
}
}
```
## Offline Messages
`Need to implement sync channel message data source` [Channel Message Data Source](/en/sdk/wukongim/flutter/datasource#channel-message-data-source)
Because WuKongIM supports permanent message storage, it will generate massive offline messages. For this, we adopt an on-demand pull mechanism. For example, with 10 conversations each having 100,000 messages, WuKongIM will not pull all 10\*100,000=1 million messages to local storage. Instead, it pulls information for these 10 conversations and the corresponding latest 20 messages, which means actually only 200 messages are pulled. Compared to 1 million messages, this greatly improves offline pull speed. Users will only pull messages for a specific conversation when they enter that conversation. These mechanisms are already encapsulated within the SDK, so users don't need to worry about them. Users only need to focus on recent conversation changes and listen for data retrieval callbacks.
## Data Structure Description
### Message Class Core Properties
```dart theme={null}
class WKMsg {
// Message header redDot: whether to show red dot noPersist: whether not to store syncOnce: whether to sync only once
MessageHeader header = MessageHeader();
// Message settings receipt: whether receipt, topic: whether topic chat, stream: whether stream message;
Setting setting = Setting();
// Server message ID (globally unique, unordered)
String messageID = "";
// Server message ID (ordered)
int messageSeq = 0;
// Local message ordered ID
int clientSeq = 0;
// 10-digit timestamp
int timestamp = 0;
// Local unique ID
String clientMsgNO = "";
// Sender
String fromUID = "";
// Channel ID
String channelID = "";
// Channel type
int channelType = WKChannelType.personal;
// Message content type e.g. 1:[Text] 2:[Image]...
int contentType = 0;
// Message payload
String content = "";
// Message status 0.sending 1.success
int status = 0;
// Whether deleted 1.yes
int isDeleted = 0;
// Sender's profile
WKChannel? _from;
// Channel profile
WKChannel? _channelInfo;
// Sender's type profile in channel (only for group messages)
WKChannelMember? _memberOfFrom;
// Sort number
int orderSeq = 0;
// Local extension fields
dynamic localExtraMap;
// Remote extension fields, maintained by server
WKMsgExtra? wkMsgExtra;
// Message reaction data
List? reactionList;
// Message content body contentType==1.WKTextContent contentType==2.WKImageContent
WKMessageContent? messageContent;
}
```
### Message Content Body
```dart theme={null}
class WKMessageContent {
// Message type 1.text 2.image
var contentType = 0;
// Message content
String content = "";
// Reply message
WKReply? reply;
// Message content rendering data
List? entities;
// Mention information
WKMentionInfo? mentionInfo;
}
```
## Next Steps
Learn how to manage channels and groups
Handle conversation lists and unread messages
Configure message data sources
Explore advanced features and custom messages
# Advanced Features
Source: https://wukong.mintlify.app/en/sdk/wukongim/ios/advanced
WuKongIM iOS SDK advanced features including custom message types and extension functionality
Advanced features provide developers with the ability to extend WuKongIM iOS SDK, including custom message types, attachment message handling, and other enterprise-level functionality.
## Custom Messages
### Custom Regular Messages
We'll use creating a custom GIF message as an example to demonstrate how to create custom message types.
#### Step 1: Inherit WKMessageContent and Define Message Structure
```objc Objective-C theme={null}
@interface WKGIFContent : WKMessageContent
// GIF URL
@property(nonatomic, copy) NSString *url;
// Width
@property(nonatomic, assign) NSInteger width;
// Height
@property(nonatomic, assign) NSInteger height;
@end
```
```swift Swift theme={null}
class WKGIFContent: WKMessageContent {
// GIF URL
var url: String?
// Width
var width: Int = 0
// Height
var height: Int = 0
}
```
#### Step 2: Encoding and Decoding
The final message content will be `{"type":3,"url":"xxxx","width":xxx,"height":xxx}`
```objc Objective-C theme={null}
@implementation WKGIFContent
// Encode message content to dictionary
- (NSDictionary *)encodeMsg {
NSMutableDictionary *dataDict = [NSMutableDictionary dictionary];
if (self.url) {
dataDict[@"url"] = self.url;
}
dataDict[@"width"] = @(self.width);
dataDict[@"height"] = @(self.height);
return dataDict;
}
// Decode dictionary to message content
- (void)decodeMsg:(NSDictionary *)contentDic {
self.url = contentDic[@"url"];
self.width = [contentDic[@"width"] integerValue];
self.height = [contentDic[@"height"] integerValue];
}
// Message type
- (WKContentType)contentType {
return 3; // Custom type, avoid conflicts with built-in types
}
@end
```
```swift Swift theme={null}
extension WKGIFContent {
// Encode message content to dictionary
override func encodeMsg() -> [String : Any] {
var dataDict: [String: Any] = [:]
if let url = self.url {
dataDict["url"] = url
}
dataDict["width"] = self.width
dataDict["height"] = self.height
return dataDict
}
// Decode dictionary to message content
override func decodeMsg(_ contentDic: [String : Any]) {
self.url = contentDic["url"] as? String
self.width = contentDic["width"] as? Int ?? 0
self.height = contentDic["height"] as? Int ?? 0
}
// Message type
override func contentType() -> WKContentType {
return 3 // Custom type, avoid conflicts with built-in types
}
}
```
#### Step 3: Register Custom Message
```objc Objective-C theme={null}
// Register in application initialization
[[WKSDK shared] registerMessageContent:[WKGIFContent class]];
```
```swift Swift theme={null}
// Register in application initialization
WKSDK.shared().registerMessageContent(WKGIFContent.self)
```
#### Step 4: Send Custom Message
```objc Objective-C theme={null}
WKGIFContent *gifContent = [[WKGIFContent alloc] init];
gifContent.url = @"https://example.com/sample.gif";
gifContent.width = 200;
gifContent.height = 150;
WKChannel *channel = [[WKChannel alloc] initWithChannelID:@"user123" channelType:WKChannelTypePerson];
[[WKSDK shared].chatManager sendMessage:gifContent channel:channel];
```
```swift Swift theme={null}
let gifContent = WKGIFContent()
gifContent.url = "https://example.com/sample.gif"
gifContent.width = 200
gifContent.height = 150
let channel = WKChannel(channelID: "user123", channelType: .person)
WKSDK.shared().chatManager.sendMessage(gifContent, channel: channel)
```
### Custom Attachment Messages
For messages that need to upload files (like images, videos, audio), you need to inherit from `WKMediaMessageContent`.
#### Example: Custom Video Message
```objc Objective-C theme={null}
@interface WKCustomVideoContent : WKMediaMessageContent
@property(nonatomic, copy) NSString *videoUrl;
@property(nonatomic, assign) NSTimeInterval duration;
@property(nonatomic, copy) NSString *thumbnailUrl;
@end
@implementation WKCustomVideoContent
- (NSDictionary *)encodeMsg {
NSMutableDictionary *dataDict = [NSMutableDictionary dictionary];
if (self.videoUrl) {
dataDict[@"video_url"] = self.videoUrl;
}
if (self.thumbnailUrl) {
dataDict[@"thumbnail_url"] = self.thumbnailUrl;
}
dataDict[@"duration"] = @(self.duration);
return dataDict;
}
- (void)decodeMsg:(NSDictionary *)contentDic {
self.videoUrl = contentDic[@"video_url"];
self.thumbnailUrl = contentDic[@"thumbnail_url"];
self.duration = [contentDic[@"duration"] doubleValue];
}
- (WKContentType)contentType {
return 4; // Custom video type
}
@end
```
```swift Swift theme={null}
class WKCustomVideoContent: WKMediaMessageContent {
var videoUrl: String?
var duration: TimeInterval = 0
var thumbnailUrl: String?
override func encodeMsg() -> [String : Any] {
var dataDict: [String: Any] = [:]
if let videoUrl = self.videoUrl {
dataDict["video_url"] = videoUrl
}
if let thumbnailUrl = self.thumbnailUrl {
dataDict["thumbnail_url"] = thumbnailUrl
}
dataDict["duration"] = self.duration
return dataDict
}
override func decodeMsg(_ contentDic: [String : Any]) {
self.videoUrl = contentDic["video_url"] as? String
self.thumbnailUrl = contentDic["thumbnail_url"] as? String
self.duration = contentDic["duration"] as? TimeInterval ?? 0
}
override func contentType() -> WKContentType {
return 4 // Custom video type
}
}
```
## Message Extensions
### Message Reactions
Add reaction functionality to messages:
```objc Objective-C theme={null}
// Add reaction to message
WKMessage *message = // Get message object
[[WKSDK shared].chatManager addReaction:@"👍" toMessage:message];
// Remove reaction
[[WKSDK shared].chatManager removeReaction:@"👍" fromMessage:message];
// Listen for reaction updates
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onMessageReactionUpdate:)
name:@"WKMessageReactionUpdateNotification"
object:nil];
- (void)onMessageReactionUpdate:(NSNotification *)notification {
WKMessage *message = notification.userInfo[@"message"];
// Handle reaction update
}
```
```swift Swift theme={null}
// Add reaction to message
let message: WKMessage = // Get message object
WKSDK.shared().chatManager.addReaction("👍", to: message)
// Remove reaction
WKSDK.shared().chatManager.removeReaction("👍", from: message)
// Listen for reaction updates
NotificationCenter.default.addObserver(
self,
selector: #selector(onMessageReactionUpdate(_:)),
name: NSNotification.Name("WKMessageReactionUpdateNotification"),
object: nil
)
@objc func onMessageReactionUpdate(_ notification: Notification) {
if let message = notification.userInfo?["message"] as? WKMessage {
// Handle reaction update
}
}
```
### Message Replies
Implement message reply functionality:
```objc Objective-C theme={null}
// Reply to a message
WKMessage *originalMessage = // Original message
WKTextContent *replyContent = [[WKTextContent alloc] initWithContent:@"This is a reply"];
// Set reply information
replyContent.reply = [[WKReply alloc] init];
replyContent.reply.messageID = originalMessage.messageID;
replyContent.reply.messageSeq = originalMessage.messageSeq;
replyContent.reply.fromUID = originalMessage.fromUID;
replyContent.reply.payload = originalMessage.content;
WKChannel *channel = [[WKChannel alloc] initWithChannelID:@"group123" channelType:WKChannelTypeGroup];
[[WKSDK shared].chatManager sendMessage:replyContent channel:channel];
```
```swift Swift theme={null}
// Reply to a message
let originalMessage: WKMessage = // Original message
let replyContent = WKTextContent(content: "This is a reply")
// Set reply information
replyContent.reply = WKReply()
replyContent.reply?.messageID = originalMessage.messageID
replyContent.reply?.messageSeq = originalMessage.messageSeq
replyContent.reply?.fromUID = originalMessage.fromUID
replyContent.reply?.payload = originalMessage.content
let channel = WKChannel(channelID: "group123", channelType: .group)
WKSDK.shared().chatManager.sendMessage(replyContent, channel: channel)
```
## Advanced Configuration
### Message Encryption
Enable end-to-end encryption for messages:
```objc Objective-C theme={null}
// Enable encryption
WKOptions *options = [WKSDK shared].options;
options.encryptionEnabled = YES;
options.encryptionKey = @"your-encryption-key";
// Send encrypted message
WKTextContent *content = [[WKTextContent alloc] initWithContent:@"Encrypted message"];
content.encryptionEnabled = YES;
WKChannel *channel = [[WKChannel alloc] initWithChannelID:@"user123" channelType:WKChannelTypePerson];
[[WKSDK shared].chatManager sendMessage:content channel:channel];
```
```swift Swift theme={null}
// Enable encryption
let options = WKSDK.shared().options
options.encryptionEnabled = true
options.encryptionKey = "your-encryption-key"
// Send encrypted message
let content = WKTextContent(content: "Encrypted message")
content.encryptionEnabled = true
let channel = WKChannel(channelID: "user123", channelType: .person)
WKSDK.shared().chatManager.sendMessage(content, channel: channel)
```
### Message Persistence Control
Control message storage behavior:
```objc Objective-C theme={null}
// Send temporary message (not stored)
WKTextContent *content = [[WKTextContent alloc] initWithContent:@"Temporary message"];
content.header.noPersist = YES;
WKChannel *channel = [[WKChannel alloc] initWithChannelID:@"user123" channelType:WKChannelTypePerson];
[[WKSDK shared].chatManager sendMessage:content channel:channel];
```
```swift Swift theme={null}
// Send temporary message (not stored)
let content = WKTextContent(content: "Temporary message")
content.header.noPersist = true
let channel = WKChannel(channelID: "user123", channelType: .person)
WKSDK.shared().chatManager.sendMessage(content, channel: channel)
```
## Performance Optimization
### Message Caching
Optimize message loading performance:
```objc Objective-C theme={null}
// Configure message cache
WKOptions *options = [WKSDK shared].options;
options.messageCacheCount = 1000; // Cache 1000 messages
options.messageCacheExpiry = 3600; // Cache for 1 hour
// Preload messages
WKChannel *channel = [[WKChannel alloc] initWithChannelID:@"group123" channelType:WKChannelTypeGroup];
[[WKSDK shared].chatManager preloadMessages:channel count:50];
```
```swift Swift theme={null}
// Configure message cache
let options = WKSDK.shared().options
options.messageCacheCount = 1000 // Cache 1000 messages
options.messageCacheExpiry = 3600 // Cache for 1 hour
// Preload messages
let channel = WKChannel(channelID: "group123", channelType: .group)
WKSDK.shared().chatManager.preloadMessages(channel, count: 50)
```
### Batch Operations
Perform batch operations for better performance:
```objc Objective-C theme={null}
// Batch send messages
NSArray *messages = @[
[[WKTextContent alloc] initWithContent:@"Message 1"],
[[WKTextContent alloc] initWithContent:@"Message 2"],
[[WKTextContent alloc] initWithContent:@"Message 3"]
];
WKChannel *channel = [[WKChannel alloc] initWithChannelID:@"group123" channelType:WKChannelTypeGroup];
[[WKSDK shared].chatManager batchSendMessages:messages channel:channel];
```
```swift Swift theme={null}
// Batch send messages
let messages: [WKMessageContent] = [
WKTextContent(content: "Message 1"),
WKTextContent(content: "Message 2"),
WKTextContent(content: "Message 3")
]
let channel = WKChannel(channelID: "group123", channelType: .group)
WKSDK.shared().chatManager.batchSendMessages(messages, channel: channel)
```
## Error Handling
### Advanced Error Handling
Implement comprehensive error handling:
```objc Objective-C theme={null}
// Set error handler
[[WKSDK shared].chatManager setErrorHandler:^(WKError *error, WKMessage *message) {
switch (error.code) {
case WKErrorCodeNetworkUnavailable:
// Handle network error
[self handleNetworkError:error message:message];
break;
case WKErrorCodeMessageTooLarge:
// Handle message size error
[self handleMessageSizeError:error message:message];
break;
case WKErrorCodePermissionDenied:
// Handle permission error
[self handlePermissionError:error message:message];
break;
default:
// Handle other errors
[self handleGenericError:error message:message];
break;
}
}];
```
```swift Swift theme={null}
// Set error handler
WKSDK.shared().chatManager.setErrorHandler { error, message in
switch error.code {
case .networkUnavailable:
// Handle network error
self.handleNetworkError(error, message: message)
case .messageTooLarge:
// Handle message size error
self.handleMessageSizeError(error, message: message)
case .permissionDenied:
// Handle permission error
self.handlePermissionError(error, message: message)
default:
// Handle other errors
self.handleGenericError(error, message: message)
}
}
```
## Best Practices
1. **Custom Message Types**: Use unique type IDs to avoid conflicts
2. **Memory Management**: Properly manage memory for large attachments
3. **Error Handling**: Implement comprehensive error handling for all operations
4. **Performance**: Use batch operations for multiple messages
5. **Security**: Enable encryption for sensitive communications
6. **Caching**: Configure appropriate cache settings for your use case
# Channel Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/ios/channel
WuKongIM iOS SDK channel management functionality, including channel information retrieval, updates and monitoring
The channel manager is responsible for CRUD operations on channel information data. Through channel management, you can implement user/group nicknames, user/group avatars, user/group pinning, user/group do not disturb, and other features.
Personal channels and group channels are collectively called channels. Personal information and group information are collectively called channel information.
This documentation only covers core methods. For more details, check the `[WKSDK shared].channelManager` interface in the code.
## Getting Channel Information
### Basic Retrieval Method
Get channel information from the client's local storage. If not available locally, call `fetchChannelInfo` to trigger the data source to request from the server.
When `fetchChannelInfo` is called and channel information data is obtained, it will trigger data monitoring. In the listener, refresh the UI again, and then `[[WKSDK shared].channelManager getChannelInfo:channel]` will be able to get the channel information data.
```objc theme={null}
WKChannelInfo *channelInfo = [[WKSDK shared].channelManager getChannelInfo:channel];
if(!channelInfo) {
[[WKSDK shared].channelManager fetchChannelInfo:channel];
}
```
### Data Monitoring
`Trigger timing: When channelInfo data changes`
Add `WKChannelManagerDelegate` delegate:
```objc theme={null}
[[WKSDK shared].channelManager addDelegate:self]
```
`WKChannelManagerDelegate` description:
```objc theme={null}
// Channel update
// @param channelInfo New channel information
// @param oldChannelInfo Old channel information
-(void) channelInfoUpdate:(WKChannelInfo*)channelInfo oldChannelInfo:(WKChannelInfo* __nullable)oldChannelInfo {
// Handle channel information update
// Update UI with new channel information
}
```
### Data Source
`Trigger timing: Triggered when calling [[WKSDK shared].channelManager fetchChannelInfo]`
Channel information data source, needs to implement logic to request channel information from server:
```objc theme={null}
// channel Channel
// callback Should call this callback when getting data from server (Note: callback must be called regardless of success or failure)
[[WKSDK shared] setChannelInfoUpdate:^WKTaskOperator * (WKChannel * _Nonnull channel, WKChannelInfoCallback _Nonnull callback) {
// Implement your server API call here
// Example:
[YourAPIManager getChannelInfo:channel.channelId
channelType:channel.channelType
success:^(WKChannelInfo *channelInfo) {
callback(channelInfo, nil);
} failure:^(NSError *error) {
callback(nil, error);
}];
return nil; // Return task operator if needed for cancellation
}];
```
## Modifying Channels
### Data Operations
Modify channel information (triggers data monitoring simultaneously):
```objc theme={null}
// Update channel information
[[WKSDK shared].channelManager updateChannelInfo:(WKChannelInfo*) channelInfo]
// Add or update channel information
[[WKSDK shared].channelManager addOrUpdateChannelInfo:(WKChannelInfo*) channelInfo]
```
### Example Usage
```objc theme={null}
// Get current channel info
WKChannel *channel = [[WKChannel alloc] initWith:@"user123" channelType:WK_PERSON];
WKChannelInfo *channelInfo = [[WKSDK shared].channelManager getChannelInfo:channel];
if (channelInfo) {
// Update channel properties
channelInfo.stick = YES; // Pin the channel
channelInfo.mute = NO; // Turn off do not disturb
channelInfo.name = @"New Name"; // Update name
// Save changes
[[WKSDK shared].channelManager updateChannelInfo:channelInfo];
} else {
// Create new channel info
WKChannelInfo *newChannelInfo = [[WKChannelInfo alloc] init];
newChannelInfo.channel = channel;
newChannelInfo.name = @"User Name";
newChannelInfo.logo = @"avatar_url";
newChannelInfo.stick = NO;
newChannelInfo.mute = NO;
// Add to manager
[[WKSDK shared].channelManager addOrUpdateChannelInfo:newChannelInfo];
}
```
## Channel Settings Management
### Pin/Unpin Channel
```objc theme={null}
// Pin a channel
WKChannelInfo *channelInfo = [[WKSDK shared].channelManager getChannelInfo:channel];
channelInfo.stick = YES;
[[WKSDK shared].channelManager updateChannelInfo:channelInfo];
// Unpin a channel
channelInfo.stick = NO;
[[WKSDK shared].channelManager updateChannelInfo:channelInfo];
```
### Mute/Unmute Channel
```objc theme={null}
// Mute a channel (do not disturb)
WKChannelInfo *channelInfo = [[WKSDK shared].channelManager getChannelInfo:channel];
channelInfo.mute = YES;
[[WKSDK shared].channelManager updateChannelInfo:channelInfo];
// Unmute a channel
channelInfo.mute = NO;
[[WKSDK shared].channelManager updateChannelInfo:channelInfo];
```
### Update Channel Avatar and Name
```objc theme={null}
WKChannelInfo *channelInfo = [[WKSDK shared].channelManager getChannelInfo:channel];
if (channelInfo) {
channelInfo.name = @"New Channel Name";
channelInfo.logo = @"https://example.com/new-avatar.jpg";
[[WKSDK shared].channelManager updateChannelInfo:channelInfo];
}
```
## Batch Operations
### Get Multiple Channel Information
```objc theme={null}
NSArray *channels = @[channel1, channel2, channel3];
for (WKChannel *channel in channels) {
WKChannelInfo *channelInfo = [[WKSDK shared].channelManager getChannelInfo:channel];
if (!channelInfo) {
[[WKSDK shared].channelManager fetchChannelInfo:channel];
}
}
```
### Batch Update Channel Settings
```objc theme={null}
// Batch mute multiple channels
NSArray *channelsToMute = @[channel1, channel2, channel3];
for (WKChannel *channel in channelsToMute) {
WKChannelInfo *channelInfo = [[WKSDK shared].channelManager getChannelInfo:channel];
if (channelInfo) {
channelInfo.mute = YES;
[[WKSDK shared].channelManager updateChannelInfo:channelInfo];
}
}
```
## Core Class Properties
```objc theme={null}
@interface WKChannelInfo : NSObject
// Channel
@property(nonatomic,strong) WKChannel *channel;
/**
Channel name
*/
@property(nonatomic,copy) NSString *name;
/**
Channel logo/avatar
*/
@property(nonatomic,copy) NSString *logo;
/**
Whether pinned
*/
@property(nonatomic,assign) BOOL stick;
/**
Whether muted (do not disturb)
*/
@property(nonatomic,assign) BOOL mute;
/// Whether all members are muted
@property(nonatomic,assign) BOOL forbidden;
/**
Whether followed 0.Not followed (stranger) 1.Followed (friend)
*/
@property(nonatomic,assign) WKChannelInfoFollow follow;
/**
Extension field, custom channel business properties can be added to extension fields
*/
@property(nonatomic,strong) NSMutableDictionary *extra;
/**
Channel status (online/offline for personal channels)
*/
@property(nonatomic,assign) WKChannelStatus status;
/**
Member count (for group channels)
*/
@property(nonatomic,assign) NSInteger memberCount;
/**
Channel description
*/
@property(nonatomic,copy) NSString *channelDesc;
@end
```
### Channel Follow Status
```objc theme={null}
typedef NS_ENUM(NSInteger, WKChannelInfoFollow) {
WKChannelInfoFollowUnknown = 0, // Unknown
WKChannelInfoFollowNo = 1, // Not followed (stranger)
WKChannelInfoFollowYes = 2, // Followed (friend)
};
```
### Channel Status
```objc theme={null}
typedef NS_ENUM(NSInteger, WKChannelStatus) {
WKChannelStatusUnknown = 0, // Unknown
WKChannelStatusOffline = 1, // Offline
WKChannelStatusOnline = 2, // Online
};
```
## Best Practices
### 1. Efficient Channel Info Loading
```objc theme={null}
// Check local cache first, then fetch if needed
- (void)loadChannelInfo:(WKChannel *)channel completion:(void(^)(WKChannelInfo *channelInfo))completion {
WKChannelInfo *cachedInfo = [[WKSDK shared].channelManager getChannelInfo:channel];
if (cachedInfo) {
completion(cachedInfo);
} else {
// Set up one-time listener for this specific channel
__weak typeof(self) weakSelf = self;
[[WKSDK shared].channelManager addDelegate:weakSelf];
// Fetch from server
[[WKSDK shared].channelManager fetchChannelInfo:channel];
}
}
// In delegate method
-(void) channelInfoUpdate:(WKChannelInfo*)channelInfo oldChannelInfo:(WKChannelInfo* __nullable)oldChannelInfo {
// Handle the update and remove delegate if needed
// completion(channelInfo);
}
```
### 2. Handle Channel Settings Changes
```objc theme={null}
-(void) channelInfoUpdate:(WKChannelInfo*)channelInfo oldChannelInfo:(WKChannelInfo* __nullable)oldChannelInfo {
// Check what changed
if (oldChannelInfo) {
if (channelInfo.stick != oldChannelInfo.stick) {
// Handle pin status change
[self handlePinStatusChange:channelInfo];
}
if (channelInfo.mute != oldChannelInfo.mute) {
// Handle mute status change
[self handleMuteStatusChange:channelInfo];
}
if (![channelInfo.name isEqualToString:oldChannelInfo.name]) {
// Handle name change
[self handleNameChange:channelInfo];
}
}
// Update UI
[self updateChannelUI:channelInfo];
}
```
## Next Steps
Learn how to manage conversation lists
Handle images, voice, and video messages
Return to chat message management
Manage connection status
# Chat Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/ios/chat
WuKongIM iOS SDK chat management functionality, including message sending/receiving, message history and message extensions
The chat manager is responsible for message data management, such as: message sending, message receiving, message updates, etc.
This documentation only covers core methods. For more details, check the `[WKSDK shared].chatManager` interface in the code.
## Online Message Sending and Receiving
### Sending Messages
#### Basic Send Method
```objc theme={null}
/**
Send message (send and save message)
@param content Message content
@param channel Target channel (personal channel, group channel, customer service channel, etc.)
*/
[[WKSDK shared].chatManager sendMessage:(WKMessageContent*)content channel:(WKChannel*)channel];
```
#### Send Example
```objc theme={null}
// Send message "hello" to user A
WKChannel *channel = [[WKChannel alloc] initWith:@"A" channelType:WK_PERSON];
// Send to group g1
// WKChannel *channel = [[WKChannel alloc] initWith:@"g1" channelType:WK_GROUP];
// Build a text message object
WKTextContent *content = [[WKTextContent alloc] initWithContent:@"hello"];
// Send this text message to the specified channel
[[WKSDK shared].chatManager sendMessage:content channel:channel];
```
### Message Listening
#### Add Listener
```objc theme={null}
[WKSDK.shared.chatManager addDelegate:self]; // WKChatManagerDelegate
```
#### WKChatManagerDelegate Description
```objc theme={null}
// ------ WKChatManagerDelegate ------
/**
Received message notification
@param message Received message
@param left Remaining message count, can refresh UI when left is 0 to avoid frequent UI refreshes causing lag
*/
- (void)onRecvMessages:(WKMessage*)message left:(NSInteger)left;
/**
Message update notification
@param message Changed message
*/
-(void) onMessageUpdate:(WKMessage*) message;
/**
Message delete notification
@param message Deleted message
*/
-(void) onMessageDelete:(WKMessage*) message;
/**
Message status update
@param message Message with updated status
*/
-(void) onMessageStatusUpdate:(WKMessage*) message;
```
## Message Send Status Code (ReasonCode)
When a message is sent, the `reasonCode` in the `WKMessage` object returned by the `onMessageUpdate:` or `onMessageStatusUpdate:` delegate methods indicates the result of the message delivery. Below are the descriptions for each status code:
| Value | Name | Description |
| ----- | ------------------------ | ------------------------------------------ |
| 0 | ReasonUnknown | Unknown error |
| 1 | ReasonSuccess | Success |
| 2 | ReasonAuthFail | Authentication failed |
| 3 | ReasonSubscriberNotExist | Subscriber does not exist in the channel |
| 4 | ReasonInBlacklist | In blacklist |
| 5 | ReasonChannelNotExist | Channel does not exist |
| 6 | ReasonUserNotOnNode | User is not on node |
| 7 | ReasonSenderOffline | Sender is offline, message delivery failed |
| 8 | ReasonMsgKeyError | Message key error, invalid message |
| 9 | PayloadDecodeError | Payload decoding failed |
| 10 | ForwardSendPacketError | Forwarding send packet failed |
| 11 | NotAllowSend | Not allowed to send message |
| 12 | ConnectKick | Connection kicked |
| 13 | NotInWhitelist | Not in whitelist |
| 14 | QueryTokenError | Query user token error |
| 15 | SystemError | System error |
| 16 | ChannelIDError | Wrong channel ID |
| 17 | NodeMatchError | Node matching error |
| 18 | NodeNotMatch | Node not matched |
| 19 | Ban | Channel is banned |
| 20 | NotSupportHeader | Unsupported header |
| 21 | ClientKeyIsEmpty | clientKey is empty |
| 22 | RateLimit | Rate limit exceeded |
| 23 | NotSupportChannelType | Unsupported channel type |
| 24 | Disband | Channel disbanded |
| 25 | SendBan | Sending is banned |
## Common Message Types
The SDK includes some common message types, such as text messages, image messages, and voice messages.
### Text Messages
```objc theme={null}
@interface WKTextContent : WKMessageContent
- (instancetype)initWithContent:(NSString*)content;
@property(nonatomic,copy) NSString *content; // Message content
@property(nonatomic,copy,nullable) NSString *format; // Content format, default is plain text: html, markdown
@end
```
#### Usage Example
```objc theme={null}
// Create text message
WKTextContent *textContent = [[WKTextContent alloc] initWithContent:@"Hello World"];
// Set format (optional)
textContent.format = @"markdown"; // Supports html, markdown
// Send message
WKChannel *channel = [[WKChannel alloc] initWith:@"user123" channelType:WK_PERSON];
[[WKSDK shared].chatManager sendMessage:textContent channel:channel];
```
### Image Messages
```objc theme={null}
@interface WKImageContent : WKMediaMessageContent
@property(nonatomic,assign) CGFloat width; // Image width
@property(nonatomic,assign) CGFloat height; // Image height
/*!
Initialize image message
@param image Original image
@return Image message object
*/
- (instancetype)initWithImage:(UIImage *)image;
/// Initialize with data
/// @param data Image data
/// @param width Image width
/// @param height Image height
- (instancetype)initWithData:(NSData *)data width:(CGFloat)width height:(CGFloat)height;
/// Initialize
/// @param data Original image data
/// @param width Original image width
/// @param height Original image height
/// @param thumbData Thumbnail data (if thumbnail data is provided, SDK will not generate thumbnail data)
- (instancetype)initWithData:(NSData *)data width:(CGFloat)width height:(CGFloat)height thumbData:(nullable NSData*)thumbData;
/*!
Whether to send original image
@discussion When sending images, whether to send original image, default is NO.
*/
@property (nonatomic, getter=isFull) BOOL full;
@end
```
#### Usage Example
```objc theme={null}
// Create image message from UIImage
UIImage *image = [UIImage imageNamed:@"example.jpg"];
WKImageContent *imageContent = [[WKImageContent alloc] initWithImage:image];
// Set to send original image
imageContent.full = YES;
// Send image message
WKChannel *channel = [[WKChannel alloc] initWith:@"user123" channelType:WK_PERSON];
[[WKSDK shared].chatManager sendMessage:imageContent channel:channel];
```
### Voice Messages
```objc theme={null}
@interface WKVoiceContent : WKMediaMessageContent
/**
Initialize
@param voiceData Audio data
@param second Audio duration in seconds
@param waveform Audio waveform data (optional parameter)
@return Voice message object
*/
- (instancetype)initWithData:(NSData *)voiceData second:(int)second waveform:(NSData*)waveform;
// Audio data
@property(nonatomic,strong) NSData *voiceData;
// Audio duration (in seconds)
@property(nonatomic,assign) NSInteger second;
// Audio waveform data (optional parameter)
@property(nonatomic,strong) NSData *waveform;
@end
```
#### Usage Example
```objc theme={null}
// Create voice message
NSData *voiceData = [NSData dataWithContentsOfFile:voicePath];
WKVoiceContent *voiceContent = [[WKVoiceContent alloc] initWithData:voiceData second:30 waveform:nil];
// Send voice message
WKChannel *channel = [[WKChannel alloc] initWith:@"user123" channelType:WK_PERSON];
[[WKSDK shared].chatManager sendMessage:voiceContent channel:channel];
```
### Custom Messages
See [Custom Messages](./advanced) for details.
## Message Extensions
Some messages may need to carry additional information, such as message read status, whether the message has been edited, etc. This information can be implemented through message extension properties.
### Update Extensions
The WKMessage class has a `remoteExtra` property. After modifying this property, you need to call this method to update the remote extension:
```objc theme={null}
[[WKSDK shared].chatManager updateMessageRemoteExtra:(WKMessage*)message];
```
### Sync Extensions
Incrementally sync all message extension data for a specified channel (this method is generally called once when opening a chat page):
```objc theme={null}
// channel Channel to sync
[[WKSDK shared].chatManager syncMessageExtra:(WKChannel*)channel
complete:(void(^_Nullable)(NSError * _Nullable error))complete];
```
### Extension Data Source
#### Update Extension Data Source
Trigger timing: Triggered when calling `[[WKSDK shared].chatManager updateMessageRemoteExtra]`
```objc theme={null}
// newExtra New extension data
// oldExtra Old extension data
[[[WKSDK shared] chatManager] setUpdateMessageExtraProvider:^(WKMessageExtra *newExtra,WKMessageExtra *oldExtra,WKUpdateMessageExtraCallback callback) {
// Implement your server API call here
// Call callback with result
}];
```
#### Sync Extension Data Source
Trigger timing: Triggered when calling `[[WKSDK shared].chatManager syncMessageExtra]`
```objc theme={null}
// channel Channel to sync extension messages
// extraVersion Current client data version
// limit Data amount per sync
// callback Should call this callback when getting messages from server (Note: callback must be called regardless of success or failure)
[[[WKSDK shared] chatManager] setSyncMessageExtraProvider:^(WKChannel * _Nonnull channel, long long extraVersion,NSInteger limit, WKSyncMessageExtraCallback _Nonnull callback) {
// Implement your server API call here
// Call callback with result
}];
```
## Message History
### Query Latest Messages
Query the latest messages for a channel (generally called when first entering a conversation page to query first screen messages):
```objc theme={null}
/**
Query latest messages for a channel
@param channel Channel
@param limit Message count limit
@param complete Query callback
*/
[[WKSDK shared].chatManager pullLastMessages:(WKChannel*)channel
limit:(int)limit
complete:(void(^)(NSArray *messages, NSError *error))complete];
```
### Pull Down to Load Messages
```objc theme={null}
/**
Pull down to load messages
@param channel Channel
@param startOrderSeq Starting orderSeq, e.g., to query 10 messages above 100, startOrderSeq would be 100, resulting data: 90 91 92 93 94 95 96 97 98 99
@param limit Message count limit
@param complete Query callback
*/
[[WKSDK shared].chatManager pullDown:(WKChannel*)channel
startOrderSeq:(uint32_t)startOrderSeq
limit:(int)limit
complete:(void(^)(NSArray *messages, NSError *error))complete];
```
### Pull Up to Load Messages
```objc theme={null}
/**
Pull up to load messages
@param startOrderSeq Starting orderSeq, e.g., to query 10 messages below 100, startOrderSeq would be 100, resulting data: 101 102 103 104 105 106 107 108 109 110
@param limit Message count limit
@param complete Query callback
*/
[[WKSDK shared].chatManager pullUp:(WKChannel*)channel
startOrderSeq:(uint32_t)startOrderSeq
limit:(int)limit
complete:(void(^)(NSArray *messages, NSError *error))complete];
```
### Query Messages Around
```objc theme={null}
/**
Query messages around specified orderSeq, 5 above and 5 below, e.g., if orderSeq is 20, query 16 17 18 19 20 21 22 23 24 25, mainly used for message positioning
@param channel Channel
@param orderSeq Query messages around this OrderSeq
*/
[[WKSDK shared].chatManager pullAround:(WKChannel*)channel
orderSeq:(uint32_t)orderSeq
limit:(int)limit
complete:(void(^)(NSArray *messages, NSError *error))complete];
```
### Message History Data Source
`Trigger timing: When getting message history and the message doesn't exist locally, SDK will call this method to complete local messages`
Sync channel messages. When requesting message history, SDK will check if messages exist locally. If not or missing, SDK will call this method to request messages from server.
```objc theme={null}
// channel Channel to sync messages
// startMessageSeq Start message sequence number
// endMessageSeq End message sequence number
// limit Message sync count per request
// pullMode Pull message mode 0: pull down 1: pull up
// callback Should call this callback when getting messages from server (Note: callback must be called regardless of success or failure)
[WKSDK.shared.chatManager setSyncChannelMessageProvider:^(WKChannel * _Nonnull channel, uint32_t startMessageSeq, uint32_t endMessageSeq, NSInteger limit, WKPullMode pullMode, WKSyncChannelMessageCallback _Nonnull callback) {
// Implement your server API call here
// Call callback with result
}];
```
## Core Class Properties
Message class core properties:
```objc theme={null}
@interface WKMessage : NSObject
@property(nonatomic,strong) WKMessageHeader *header; // Message header
@property(nonatomic,strong) WKSetting *setting; // Message settings
@property(nonatomic,strong) WKChannel *channel; // Chat channel
@property(nonatomic,copy) NSString *fromUid; // Sender uid
@property(nonatomic,strong) WKMessageContent *content; // Message content
@property(nonatomic,assign) NSInteger timestamp; // Message time (server time, in seconds)
@property(nonatomic,strong) NSMutableDictionary *extra; // Message local extension data
@property(nonatomic,strong) WKMessageExtra *remoteExtra; // Message remote extension
@property(nonatomic,assign) WKMessageStatus status; // Message status
@end
```
Message content core properties:
```objc theme={null}
@interface WKMessageContent : NSObject
/**
Your custom message type, should be consistent across platforms
@return Content type
*/
- (NSNumber*) contentType;
// Upper layer doesn't need to implement encode, implement this method instead
- (NSDictionary*) encodeWithJSON;
// Upper layer doesn't need to implement decode, implement this method instead
- (void) decodeWithJSON:(NSDictionary*)contentDic;
// @mention information in message
@property (nonatomic, strong) WKMentionedInfo *mentionedInfo;
/// Reply content
@property(nonatomic,strong) WKReply *reply;
@end
```
## Next Steps
Learn how to manage channels and groups
Handle conversation lists and unread messages
Handle images, voice, video and other multimedia messages
Custom messages and advanced configuration
# Connection Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/ios/connection
WuKongIM iOS SDK connection management and status monitoring guide
Responsible for establishing, maintaining, and disconnecting IM connections.
This documentation only covers core methods. For more details, check the `[WKSDK shared].connectionManager` interface in the code.
## Configuration
```objc theme={null}
[WKSDK shared].options.host = @"xxx.xxx.xxx.xxx"; // IM communication IP
[WKSDK shared].options.port = 5100; // IM communication TCP port
// Set IM connection authentication information
[WKSDK shared].options.connectInfoCallback = ^WKConnectInfo * _Nonnull{
WKConnectInfo *connectInfo = [WKConnectInfo new];
connectInfo.uid = "xxxx"; // User uid (registered with IM communication end by business server)
connectInfo.token = "xxxx"; // User token (registered with IM communication end by business server)
return connectInfo;
};
```
For more configuration options, check `[WKSDK shared].options`
## Swift Configuration
```swift theme={null}
WKSDK.shared.options.host = "xxx.xxx.xxx.xxx" // IM communication IP
WKSDK.shared.options.port = 5100 // IM communication TCP port
// Set IM connection authentication information
WKSDK.shared.options.connectInfoCallback = {
let connectInfo = WKConnectInfo()
connectInfo.uid = "xxxx" // User uid
connectInfo.token = "xxxx" // User token
return connectInfo
}
```
## Advanced Configuration
```objc theme={null}
// Set connection timeout
[WKSDK shared].options.connectTimeout = 10; // 10 seconds
// Set heartbeat interval
[WKSDK shared].options.heartbeatInterval = 30; // 30 seconds
// Set auto-reconnect
[WKSDK shared].options.autoReconnect = YES;
// Set max reconnect attempts
[WKSDK shared].options.maxReconnectAttempts = 10;
// Set reconnect interval
[WKSDK shared].options.reconnectInterval = 5; // 5 seconds
```
## Connect
```objc theme={null}
// Connect
[[WKSDK shared].connectionManager connect];
```
```swift theme={null}
// Swift
WKSDK.shared.connectionManager.connect()
```
## Disconnect
```objc theme={null}
// Disconnect - NO: SDK maintains reconnection mechanism, YES: SDK will no longer reconnect
[[WKSDK shared].connectionManager disconnect:NO];
```
```swift theme={null}
// Swift
WKSDK.shared.connectionManager.disconnect(false) // false: maintain reconnection
```
## Connection Status Monitoring
```objc theme={null}
[WKSDK.shared.connectionManager addDelegate:self]; // WKConnectionManagerDelegate
```
```swift theme={null}
// Swift
WKSDK.shared.connectionManager.addDelegate(self) // WKConnectionManagerDelegate
```
### Delegate Implementation
```objc theme={null}
// ---------- WKConnectionManagerDelegate ----------
/**
Connection status monitoring
*/
-(void) onConnectStatus:(WKConnectStatus)status reasonCode:(WKReason)reasonCode {
switch (status) {
case WKConnecting:
NSLog(@"Connecting...");
break;
case WKConnected:
NSLog(@"Connected successfully!");
break;
case WKDisconnected:
NSLog(@"Disconnected, reason: %d", reasonCode);
break;
case WKConnectFail:
NSLog(@"Connection failed, reason: %d", reasonCode);
break;
}
}
/**
Connection kicked off (logged in from another device)
*/
-(void) onKick:(WKReason)reasonCode {
NSLog(@"Kicked off, reason: %d", reasonCode);
// Handle being kicked off, usually show login page
}
```
```swift theme={null}
// Swift implementation
extension YourViewController: WKConnectionManagerDelegate {
func onConnectStatus(_ status: WKConnectStatus, reasonCode: WKReason) {
switch status {
case .connecting:
print("Connecting...")
case .connected:
print("Connected successfully!")
case .disconnected:
print("Disconnected, reason: \(reasonCode)")
case .connectFail:
print("Connection failed, reason: \(reasonCode)")
@unknown default:
break
}
}
func onKick(_ reasonCode: WKReason) {
print("Kicked off, reason: \(reasonCode)")
// Handle being kicked off
}
}
```
## Connection Status Types
| Status | Description |
| ---------------- | ------------------------ |
| `WKConnecting` | Connecting to server |
| `WKConnected` | Successfully connected |
| `WKDisconnected` | Disconnected from server |
| `WKConnectFail` | Connection failed |
## Reason Codes
Common reason codes for connection status changes:
| Code | Description |
| ------------------------ | ---------------------------- |
| `WKReasonConnectSuccess` | Connection successful |
| `WKReasonConnectTimeout` | Connection timeout |
| `WKReasonAuthFail` | Authentication failed |
| `WKReasonNetworkError` | Network error |
| `WKReasonKickOff` | Kicked off by another device |
## Best Practices
### 1. Connection Lifecycle Management
```objc theme={null}
// In AppDelegate
- (void)applicationDidBecomeActive:(UIApplication *)application {
// App becomes active, connect if needed
if (![WKSDK.shared.connectionManager isConnected]) {
[WKSDK.shared.connectionManager connect];
}
}
- (void)applicationDidEnterBackground:(UIApplication *)application {
// App enters background, you may choose to disconnect
// [WKSDK.shared.connectionManager disconnect:NO];
}
```
### 2. Network Status Monitoring
```objc theme={null}
#import
// Monitor network reachability
- (void)startNetworkMonitoring {
// Implementation depends on your network monitoring solution
// When network becomes available, reconnect
[WKSDK.shared.connectionManager connect];
}
```
### 3. Error Handling
```objc theme={null}
-(void) onConnectStatus:(WKConnectStatus)status reasonCode:(WKReason)reasonCode {
if (status == WKConnectFail) {
switch (reasonCode) {
case WKReasonAuthFail:
// Handle authentication failure - refresh token
[self refreshTokenAndReconnect];
break;
case WKReasonNetworkError:
// Handle network error - retry later
[self scheduleReconnect];
break;
default:
break;
}
}
}
```
## Next Steps
Learn how to send and receive messages
Manage channels and groups
Handle conversation lists
Handle image, voice, and video messages
# Conversation Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/ios/conversation
WuKongIM iOS SDK conversation management functionality, including conversation lists, unread messages and conversation extensions
Responsible for managing recent conversation data, such as: adding recent conversations, deleting recent conversations, unread message counts, etc.
This documentation only covers core methods. For more details, check the `[WKSDK shared].conversationManager` interface in the code.
## Recent Conversation List
### Data Operations
Get local recent conversation list:
```objc theme={null}
NSArray *conversations = [[WKSDK shared].conversationManager getConversationList];
```
### Data Monitoring
Add `WKConversationManagerDelegate`:
```objc theme={null}
[[WKSDK shared].conversationManager addDelegate:self];
```
WKConversationManagerDelegate description:
```objc theme={null}
/**
Called when recent conversation objects are updated
@param conversations Recent conversation collection
*/
- (void)onConversationUpdate:(NSArray*)conversations;
/**
Recent conversation unread count changes
@param channel Channel
@param unreadCount Unread count
*/
- (void)onConversationUnreadCountUpdate:(WKChannel*)channel unreadCount:(NSInteger)unreadCount;
/**
Conversation deleted
@param channel Deleted conversation channel
*/
- (void)onConversationDelete:(WKChannel*)channel;
/**
New conversation added
@param conversation New conversation
*/
- (void)onConversationAdd:(WKConversation*)conversation;
```
### Data Source
`Trigger timing: After establishing connection, SDK actively triggers pulling recent conversations after going offline`
Incrementally sync recent conversation data after going offline:
```objc theme={null}
// version Data version number
// lastMsgSeqs Concatenated recent conversation seq relationship data
// callback SDK should call this callback when getting messages from server (Note: callback must be called regardless of success or failure)
[[WKSDK shared].conversationManager setSyncConversationProviderAndAck:^(long long version, NSString * _Nonnull lastMsgSeqs, WKSyncConversationCallback _Nonnull callback) {
// Implement your server API call here
// Example:
[YourAPIManager syncConversations:version
lastMsgSeqs:lastMsgSeqs
success:^(NSArray *conversations, long long newVersion) {
callback(conversations, newVersion, nil);
} failure:^(NSError *error) {
callback(nil, version, error);
}];
} ack:^(uint64_t cmdVersion, void (^ _Nullable complete)(NSError * _Nullable)) {
// If ack receipt is not needed, directly call complete(nil);
complete(nil);
}];
```
## Recent Conversation Extensions
Through recent conversation extension properties, you can customize your unique business attributes, such as implementing draft functionality similar to WeChat, with draft data synchronized across multiple devices.
### Data Operations
#### Update Extensions
```objc theme={null}
[[WKSDK shared].conversationManager updateOrAddExtra:(WKConversationExtra*)extra]
```
#### Sync Extensions
```objc theme={null}
[[WKSDK shared].conversationManager syncExtra]
```
### Data Monitoring
Any data changes to recent conversation objects will trigger the `WKConversationManagerDelegate` delegate. Similarly, calling `updateOrAddExtra` will also trigger this delegate.
### Data Source
#### Update Extensions
`Trigger timing: Triggered when calling [[WKSDK shared].conversationManager updateOrAddExtra]`
```objc theme={null}
// extra Updated extension data
// callback Callback to SDK after update completion
[[WKSDK shared].conversationManager setUpdateConversationExtraProvider:^(WKConversationExtra * _Nonnull extra, WKUpdateConversationExtraCallback _Nonnull callback) {
// Implement your server API call here
// Example:
[YourAPIManager updateConversationExtra:extra
success:^{
callback(nil);
} failure:^(NSError *error) {
callback(error);
}];
}];
```
#### Sync Extensions
`Trigger timing: Triggered when calling [[WKSDK shared].conversationManager syncExtra]`
```objc theme={null}
// version Latest extension data version existing on client
// callback Callback data to SDK after syncing extensions
[[WKSDK shared].conversationManager setSyncConversationExtraProvider:^(long long version, WKSyncConversationExtraCallback _Nonnull callback) {
// Implement your server API call here
// Example:
[YourAPIManager syncConversationExtras:version
success:^(NSArray *extras, long long newVersion) {
callback(extras, newVersion, nil);
} failure:^(NSError *error) {
callback(nil, version, error);
}];
}];
```
## Conversation Operations
### Delete Conversation
```objc theme={null}
// Delete a conversation
WKChannel *channel = [[WKChannel alloc] initWith:@"user123" channelType:WK_PERSON];
[[WKSDK shared].conversationManager deleteConversation:channel];
```
### Clear Unread Count
```objc theme={null}
// Clear unread count for a conversation
WKChannel *channel = [[WKChannel alloc] initWith:@"user123" channelType:WK_PERSON];
[[WKSDK shared].conversationManager clearUnreadCount:channel];
```
### Set Conversation Draft
```objc theme={null}
// Set draft for a conversation using extensions
WKChannel *channel = [[WKChannel alloc] initWith:@"user123" channelType:WK_PERSON];
WKConversationExtra *extra = [[WKConversationExtra alloc] init];
extra.channel = channel;
extra.draft = @"This is a draft message...";
extra.draftUpdatedAt = [[NSDate date] timeIntervalSince1970];
[[WKSDK shared].conversationManager updateOrAddExtra:extra];
```
### Get Conversation by Channel
```objc theme={null}
// Get specific conversation
WKChannel *channel = [[WKChannel alloc] initWith:@"user123" channelType:WK_PERSON];
WKConversation *conversation = [[WKSDK shared].conversationManager getConversation:channel];
if (conversation) {
NSLog(@"Unread count: %ld", conversation.unreadCount);
NSLog(@"Last message: %@", conversation.lastMessage.content);
}
```
## Core Class Properties
### WKConversation
```objc theme={null}
@interface WKConversation : NSObject
// Channel information
@property(nonatomic,strong) WKChannel *channel;
// Last message
@property(nonatomic,strong) WKMessage *lastMessage;
// Unread message count
@property(nonatomic,assign) NSInteger unreadCount;
// Conversation timestamp
@property(nonatomic,assign) NSTimeInterval timestamp;
// Whether conversation is pinned
@property(nonatomic,assign) BOOL stick;
// Conversation extensions
@property(nonatomic,strong) WKConversationExtra *extra;
@end
```
### WKConversationExtra
```objc theme={null}
@interface WKConversationExtra : NSObject
// Channel
@property(nonatomic,strong) WKChannel *channel;
// Draft content
@property(nonatomic,copy) NSString *draft;
// Draft update time
@property(nonatomic,assign) NSTimeInterval draftUpdatedAt;
// Custom extension data
@property(nonatomic,strong) NSMutableDictionary *extraData;
// Extension version
@property(nonatomic,assign) long long version;
@end
```
## Best Practices
### 1. Efficient Conversation Loading
```objc theme={null}
- (void)loadConversations {
// Get cached conversations first
NSArray *conversations = [[WKSDK shared].conversationManager getConversationList];
if (conversations.count > 0) {
[self updateConversationUI:conversations];
}
// Sync latest data from server
[[WKSDK shared].conversationManager syncConversations];
}
```
### 2. Handle Conversation Updates
```objc theme={null}
- (void)onConversationUpdate:(NSArray*)conversations {
dispatch_async(dispatch_get_main_queue(), ^{
// Sort conversations by timestamp
NSArray *sortedConversations = [conversations sortedArrayUsingComparator:^NSComparisonResult(WKConversation *obj1, WKConversation *obj2) {
if (obj1.stick && !obj2.stick) return NSOrderedAscending;
if (!obj1.stick && obj2.stick) return NSOrderedDescending;
if (obj1.timestamp > obj2.timestamp) return NSOrderedAscending;
if (obj1.timestamp < obj2.timestamp) return NSOrderedDescending;
return NSOrderedSame;
}];
[self updateConversationUI:sortedConversations];
});
}
```
### 3. Draft Management
```objc theme={null}
- (void)saveDraft:(NSString *)draftText forChannel:(WKChannel *)channel {
WKConversationExtra *extra = [[WKSDK shared].conversationManager getConversationExtra:channel];
if (!extra) {
extra = [[WKConversationExtra alloc] init];
extra.channel = channel;
}
extra.draft = draftText;
extra.draftUpdatedAt = [[NSDate date] timeIntervalSince1970];
[[WKSDK shared].conversationManager updateOrAddExtra:extra];
}
- (NSString *)getDraftForChannel:(WKChannel *)channel {
WKConversationExtra *extra = [[WKSDK shared].conversationManager getConversationExtra:channel];
return extra.draft;
}
```
## Next Steps
Learn how to handle multimedia messages
Return to chat message management
Manage channel information
Manage connection status
# Integration Guide
Source: https://wukong.mintlify.app/en/sdk/wukongim/ios/integration
WuKongIM iOS SDK integration and initialization configuration guide
## Integration via CocoaPods
Add the dependency to your project's `Podfile`:
```objc theme={null}
pod 'WuKongIMSDK'
```
Then run the installation command:
```bash theme={null}
pod install
```
## Integration via Source Code
If you need to use the latest development version, you can integrate directly from the GitHub repository:
```objc theme={null}
pod 'WuKongIMSDK', :git => 'https://github.com/WuKongIM/WuKongIMiOSSDK.git'
```
## Swift Package Manager
You can also integrate using Swift Package Manager:
1. In Xcode, go to File → Add Package Dependencies
2. Enter the repository URL: `https://github.com/WuKongIM/WuKongIMiOSSDK.git`
3. Select the version and add to your project
## Manual Integration
For manual integration:
1. Download the latest release from [GitHub Releases](https://github.com/WuKongIM/WuKongIMiOSSDK/releases)
2. Drag `WuKongIMSDK.framework` into your project
3. Add the framework to your target's "Frameworks, Libraries, and Embedded Content"
4. Set "Embed & Sign" for the framework
## Basic Setup
After integration, import the SDK in your code:
```swift theme={null}
import WuKongIMSDK
```
Or in Objective-C:
```objc theme={null}
#import
```
## Initialization
Initialize the SDK in your AppDelegate:
```swift theme={null}
// Swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Initialize WuKongIM SDK
WKSDK.shared.setup()
return true
}
```
```objc theme={null}
// Objective-C
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Initialize WuKongIM SDK
[WKSDK.shared setup];
return YES;
}
```
## Configuration Options
You can configure various options during initialization:
```swift theme={null}
// Swift
let options = WKOptions()
options.connectAddr = "ws://your-server.com:5200"
options.apiURL = "http://your-api-server.com"
options.uploadURL = "http://your-upload-server.com"
WKSDK.shared.setup(options: options)
```
```objc theme={null}
// Objective-C
WKOptions *options = [[WKOptions alloc] init];
options.connectAddr = @"ws://your-server.com:5200";
options.apiURL = @"http://your-api-server.com";
options.uploadURL = @"http://your-upload-server.com";
[WKSDK.shared setupWithOptions:options];
```
## Permissions
Add necessary permissions to your `Info.plist`:
```xml theme={null}
NSAppTransportSecurity
NSAllowsArbitraryLoads
NSMicrophoneUsageDescription
This app needs microphone access to record voice messages
NSCameraUsageDescription
This app needs camera access to take photos and videos
NSPhotoLibraryUsageDescription
This app needs photo library access to send images
```
## Next Steps
Learn how to manage connection status
Implement message sending and receiving
Manage channels and groups
Handle conversation lists
# SDK Introduction
Source: https://wukong.mintlify.app/en/sdk/wukongim/ios/intro
WuKongIM iOS SDK design philosophy, architecture overview and core functionality introduction
## Design Philosophy
Like designing a book's table of contents, we design APIs through `WKSDK.shared.xxxManager` to access all needed functionality, for example sending messages `[WKSDK.shared.chatManager sendMessage:xxx]`
## Architecture Overview
```objc theme={null}
// Chat Manager
// Responsible for message-related CRUD operations like sending messages, deleting messages,
// recalling messages, listening to chat messages, etc.
WKSDK.shared.chatManager
// Connection Manager
// Responsible for establishing or disconnecting connections with IM,
// monitoring IM connection status, etc.
WKSDK.shared.connectionManager
// Channel Manager
// Responsible for channel data retrieval, caching and channel settings
// like pinning, do not disturb, muting, etc.
WKSDK.shared.channelManager
// Conversation Manager
// Responsible for maintaining recent conversation data like unread counts,
// drafts, @mentions, last messages, etc.
WKSDK.shared.conversationManager
// Reaction Manager
// Responsible for maintaining like/reaction data
WKSDK.shared.reactionManager
// CMD Manager
// Responsible for listening to command-type messages sent from the server
WKSDK.shared.cmdManager
// Receipt Manager
// Responsible for maintaining read/unread status of messages
WKSDK.shared.receiptManager
// Reminder Manager
// Responsible for reminder items in recent conversations like @mentions,
// group join requests, etc. Also supports custom reminders like WeChat's
// [Red Packet] [Transfer] list reminders
WKSDK.shared.reminderManager
// Media Manager
// Responsible for uploading and downloading multimedia files in messages
// like images, videos, and other messages with attachments
WKSDK.shared.mediaManager
```
## SDK Integration with Existing Apps
The overall flow of SDK integration with existing apps is: Existing APP calls SDK methods → Data changes occur → Notify existing APP through delegate callbacks
For example, common message sending → Message status changes → Notify existing APP to update UI send status indicators
```objc theme={null}
// Send message through chatManager
[WKSDK.shared.chatManager sendMessage:xxx]
// Listen to message status changes through chatManager's delegate
-(void) onMessageUpdate:(WKMessage*) message {
if(message.status == SUCCESS) {
[self updateItemUIWithSuccess:message];
}else {
[self updateItemUIWithFail:message];
}
}
```
## Core Functionality Modules
### Chat Management (ChatManager)
* Message sending, receiving, deletion
* Message recall and editing
* Message status monitoring
* Historical message queries
### Connection Management (ConnectionManager)
* IM connection establishment and disconnection
* Connection status monitoring
* Network status handling
* Automatic reconnection mechanism
### Channel Management (ChannelManager)
* Channel information retrieval and caching
* Channel settings (pinning, do not disturb, muting)
* Channel member management
* Channel status synchronization
### Conversation Management (ConversationManager)
* Recent conversation list maintenance
* Unread message counting
* Conversation draft management
* @mention reminders
### Media Management (MediaManager)
* Image, video, audio upload and download
* File transfer progress monitoring
* Media file cache management
* Thumbnail generation
## Development Advantages
* **Unified Entry Point**: Access all functionality through `WKSDK.shared`
* **Modular Design**: Clear separation of functional modules for easy maintenance
* **Event-Driven**: Event callbacks based on delegate pattern
* **High Performance**: Local database caching reduces network requests
* **Easy Integration**: Clean API design for quick integration into existing projects
## Next Steps
After understanding the overall architecture of iOS SDK, you can:
1. [SDK Integration](/en/sdk/wukongim/ios/integration) - Start integrating WuKongIM iOS SDK
2. [Connection Management](/en/sdk/wukongim/ios/connection) - Learn how to establish and manage connections
3. [Chat Management](/en/sdk/wukongim/ios/chat) - Implement message sending and receiving functionality
4. [Channel Management](/en/sdk/wukongim/ios/channel) - Manage channels and members
5. [Conversation Management](/en/sdk/wukongim/ios/conversation) - Handle conversation lists and unread messages
# Media Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/ios/media
WuKongIM iOS SDK media management functionality including file upload/download and progress management
The media manager is responsible for managing multimedia resources such as files, images, videos, and audio in messages, including upload and download operations.
Files, images, videos, audio and other multimedia resources in messages are all managed through the media manager
This documentation only covers core methods. For more details, check the `[WKSDK shared].mediaManager` interface in the code.
## Custom Upload
### Create Upload Task
Inherit from `WKMessageFileUploadTask` and implement necessary methods:
```objc Objective-C theme={null}
// Inherit from WKMessageFileUploadTask
@interface WKFileUploadTask : WKMessageFileUploadTask
@end
```
```swift Swift theme={null}
// Inherit from WKMessageFileUploadTask
class WKFileUploadTask: WKMessageFileUploadTask {
}
```
### Implement Upload Task
```objc Objective-C theme={null}
// Implement four methods: initWithMessage, resume, cancel, suspend
@implementation WKFileUploadTask
- (instancetype)initWithMessage:(WKMessage *)message {
self = [super initWithMessage:message];
if(self) {
[self initTask];
}
return self;
}
- (void)initTask {
// Initialize upload task
// Set up network request, configure parameters, etc.
}
- (void)resume {
// Start or resume upload
[self startUpload];
}
- (void)cancel {
// Cancel upload
[self cancelUpload];
}
- (void)suspend {
// Pause upload
[self pauseUpload];
}
- (void)startUpload {
// Implement actual upload logic
// Example using NSURLSession
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"your-upload-url"]];
request.HTTPMethod = @"POST";
// Set up multipart form data
NSString *boundary = @"----WebKitFormBoundary7MA4YWxkTrZu0gW";
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
NSMutableData *body = [NSMutableData data];
// Add file data to body
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:body];
[uploadTask resume];
}
// NSURLSessionDelegate methods
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
// Update upload progress
float progress = (float)totalBytesSent / (float)totalBytesExpectedToSend;
dispatch_async(dispatch_get_main_queue(), ^{
[self updateProgress:progress];
});
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (error) {
// Upload failed
[self uploadFailedWithError:error];
} else {
// Upload successful
[self uploadCompleted];
}
}
@end
```
```swift Swift theme={null}
// Implement four methods: initWithMessage, resume, cancel, suspend
extension WKFileUploadTask {
override init(message: WKMessage) {
super.init(message: message)
initTask()
}
func initTask() {
// Initialize upload task
// Set up network request, configure parameters, etc.
}
override func resume() {
// Start or resume upload
startUpload()
}
override func cancel() {
// Cancel upload
cancelUpload()
}
override func suspend() {
// Pause upload
pauseUpload()
}
func startUpload() {
// Implement actual upload logic
// Example using URLSession
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
var request = URLRequest(url: URL(string: "your-upload-url")!)
request.httpMethod = "POST"
// Set up multipart form data
let boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"
let contentType = "multipart/form-data; boundary=\(boundary)"
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
let body = NSMutableData()
// Add file data to body
let uploadTask = session.uploadTask(with: request, from: body as Data)
uploadTask.resume()
}
}
// URLSessionDelegate methods
extension WKFileUploadTask: URLSessionDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
// Update upload progress
let progress = Float(totalBytesSent) / Float(totalBytesExpectedToSend)
DispatchQueue.main.async {
self.updateProgress(progress)
}
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
// Upload failed
uploadFailed(with: error)
} else {
// Upload successful
uploadCompleted()
}
}
}
```
### Register Upload Task
```objc Objective-C theme={null}
// Register custom upload task
[[WKSDK shared].mediaManager addFileUploadTask:[WKFileUploadTask class] contentType:WKContentTypeImage];
```
```swift Swift theme={null}
// Register custom upload task
WKSDK.shared().mediaManager.addFileUploadTask(WKFileUploadTask.self, contentType: .image)
```
## Custom Download
### Create Download Task
```objc Objective-C theme={null}
@interface WKFileDownloadTask : WKMessageFileDownloadTask
@end
@implementation WKFileDownloadTask
- (instancetype)initWithMessage:(WKMessage *)message {
self = [super initWithMessage:message];
if(self) {
[self initTask];
}
return self;
}
- (void)initTask {
// Initialize download task
}
- (void)resume {
// Start or resume download
[self startDownload];
}
- (void)cancel {
// Cancel download
[self cancelDownload];
}
- (void)suspend {
// Pause download
[self pauseDownload];
}
- (void)startDownload {
// Implement download logic
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
NSURL *downloadURL = [NSURL URLWithString:@"file-download-url"];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:downloadURL];
[downloadTask resume];
}
// NSURLSessionDownloadDelegate methods
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
// Update download progress
float progress = (float)totalBytesWritten / (float)totalBytesExpectedToWrite;
dispatch_async(dispatch_get_main_queue(), ^{
[self updateProgress:progress];
});
}
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
// Download completed, move file to final location
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *filePath = [documentsPath stringByAppendingPathComponent:@"downloaded_file"];
NSError *error;
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:filePath] error:&error];
if (!error) {
[self downloadCompletedWithPath:filePath];
} else {
[self downloadFailedWithError:error];
}
}
@end
```
```swift Swift theme={null}
class WKFileDownloadTask: WKMessageFileDownloadTask {
override init(message: WKMessage) {
super.init(message: message)
initTask()
}
func initTask() {
// Initialize download task
}
override func resume() {
// Start or resume download
startDownload()
}
override func cancel() {
// Cancel download
cancelDownload()
}
override func suspend() {
// Pause download
pauseDownload()
}
func startDownload() {
// Implement download logic
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config, delegate: self, delegateQueue: nil)
let downloadURL = URL(string: "file-download-url")!
let downloadTask = session.downloadTask(with: downloadURL)
downloadTask.resume()
}
}
// URLSessionDownloadDelegate methods
extension WKFileDownloadTask: URLSessionDownloadDelegate {
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
// Update download progress
let progress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
DispatchQueue.main.async {
self.updateProgress(progress)
}
}
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// Download completed, move file to final location
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let filePath = documentsPath + "/downloaded_file"
do {
try FileManager.default.moveItem(at: location, to: URL(fileURLWithPath: filePath))
downloadCompleted(withPath: filePath)
} catch {
downloadFailed(with: error)
}
}
}
```
### Register Download Task
```objc Objective-C theme={null}
// Register custom download task
[[WKSDK shared].mediaManager addFileDownloadTask:[WKFileDownloadTask class] contentType:WKContentTypeImage];
```
```swift Swift theme={null}
// Register custom download task
WKSDK.shared().mediaManager.addFileDownloadTask(WKFileDownloadTask.self, contentType: .image)
```
## Progress Management
### Monitor Upload Progress
```objc Objective-C theme={null}
// Listen for upload progress
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onUploadProgress:)
name:@"WKMessageUploadProgressNotification"
object:nil];
- (void)onUploadProgress:(NSNotification *)notification {
WKMessage *message = notification.userInfo[@"message"];
NSNumber *progress = notification.userInfo[@"progress"];
NSLog(@"Upload progress: %.2f%% for message: %@", progress.floatValue * 100, message.messageID);
}
```
```swift Swift theme={null}
// Listen for upload progress
NotificationCenter.default.addObserver(
self,
selector: #selector(onUploadProgress(_:)),
name: NSNotification.Name("WKMessageUploadProgressNotification"),
object: nil
)
@objc func onUploadProgress(_ notification: Notification) {
if let message = notification.userInfo?["message"] as? WKMessage,
let progress = notification.userInfo?["progress"] as? NSNumber {
print("Upload progress: \(progress.floatValue * 100)% for message: \(message.messageID)")
}
}
```
### Monitor Download Progress
```objc Objective-C theme={null}
// Listen for download progress
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onDownloadProgress:)
name:@"WKMessageDownloadProgressNotification"
object:nil];
- (void)onDownloadProgress:(NSNotification *)notification {
WKMessage *message = notification.userInfo[@"message"];
NSNumber *progress = notification.userInfo[@"progress"];
NSLog(@"Download progress: %.2f%% for message: %@", progress.floatValue * 100, message.messageID);
}
```
```swift Swift theme={null}
// Listen for download progress
NotificationCenter.default.addObserver(
self,
selector: #selector(onDownloadProgress(_:)),
name: NSNotification.Name("WKMessageDownloadProgressNotification"),
object: nil
)
@objc func onDownloadProgress(_ notification: Notification) {
if let message = notification.userInfo?["message"] as? WKMessage,
let progress = notification.userInfo?["progress"] as? NSNumber {
print("Download progress: \(progress.floatValue * 100)% for message: \(message.messageID)")
}
}
```
## File Management
### Get File Path
```objc Objective-C theme={null}
// Get local file path for message
WKMessage *message = // Your message object
NSString *filePath = [[WKSDK shared].mediaManager getFilePathForMessage:message];
if (filePath && [[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
// File exists locally
NSLog(@"File path: %@", filePath);
} else {
// File needs to be downloaded
[[WKSDK shared].mediaManager downloadFileForMessage:message];
}
```
```swift Swift theme={null}
// Get local file path for message
let message: WKMessage = // Your message object
let filePath = WKSDK.shared().mediaManager.getFilePath(for: message)
if let filePath = filePath, FileManager.default.fileExists(atPath: filePath) {
// File exists locally
print("File path: \(filePath)")
} else {
// File needs to be downloaded
WKSDK.shared().mediaManager.downloadFile(for: message)
}
```
### Clear Cache
```objc Objective-C theme={null}
// Clear all media cache
[[WKSDK shared].mediaManager clearAllCache];
// Clear cache for specific message type
[[WKSDK shared].mediaManager clearCacheForContentType:WKContentTypeImage];
// Clear cache older than specified days
[[WKSDK shared].mediaManager clearCacheOlderThanDays:7];
```
```swift Swift theme={null}
// Clear all media cache
WKSDK.shared().mediaManager.clearAllCache()
// Clear cache for specific message type
WKSDK.shared().mediaManager.clearCache(for: .image)
// Clear cache older than specified days
WKSDK.shared().mediaManager.clearCacheOlderThan(days: 7)
```
## Best Practices
1. **Progress Feedback**: Always provide progress feedback for long-running operations
2. **Error Handling**: Implement proper error handling for network failures
3. **Cache Management**: Regularly clean up old cached files to save storage space
4. **Background Tasks**: Use background tasks for large file uploads/downloads
5. **Network Optimization**: Implement retry logic and adaptive quality based on network conditions
6. **Security**: Validate file types and sizes before processing
# Server Configuration
Source: https://wukong.mintlify.app/en/server/configuration
Complete WuKongIM server configuration guide
WuKongIM supports both YAML configuration files and environment variables. Environment variables take precedence over configuration files.
## Basic Server Configuration
```yaml YAML Configuration theme={null}
# Basic server configuration
mode: "release" # Run mode: debug, test, release, bench
addr: "tcp://0.0.0.0:5100" # TCP listen address
httpAddr: "0.0.0.0:5001" # HTTP API listen address
wsAddr: "ws://0.0.0.0:5200" # WebSocket listen address
wssAddr: "wss://0.0.0.0:5210" # Secure WebSocket address
rootDir: "./wukongimdata" # Data storage directory
ginMode: "release" # Gin framework mode
deadlockCheck: false # Deadlock detection
pprofOn: false # pprof performance analysis
```
```bash Environment Variables theme={null}
WK_MODE=release
WK_ADDR=tcp://0.0.0.0:5100
WK_HTTPADDR=0.0.0.0:5001
WK_WSADDR=ws://0.0.0.0:5200
WK_WSSADDR=wss://0.0.0.0:5210
WK_ROOTDIR=./wukongimdata
WK_GINMODE=release
WK_DEADLOCKCHECK=false
WK_PPROFON=false
```
## Admin Authentication Configuration
```yaml YAML Configuration theme={null}
# Admin authentication configuration
tokenAuthOn: false # Enable admin token authentication
managerUID: "____manager" # Admin user ID
managerToken: "" # Admin API token
whitelistOffOfPerson: true # Disable personal whitelist verification
```
```bash Environment Variables theme={null}
WK_TOKENAUTHON=false
WK_MANAGERUID=____manager
WK_MANAGERTOKEN=
WK_WHITELISTOFFOFPERSON=true
```
## External Access Configuration
```yaml YAML Configuration theme={null}
# External access configuration
external:
ip: "" # External IP address
tcpAddr: "" # External TCP address
wsAddr: "" # External WebSocket address
wssAddr: "" # External secure WebSocket address
monitorAddr: "" # External monitor address
apiUrl: "" # External API URL
```
```bash Environment Variables theme={null}
WK_EXTERNAL_IP=
WK_EXTERNAL_TCPADDR=
WK_EXTERNAL_WSADDR=
WK_EXTERNAL_WSSADDR=
WK_EXTERNAL_MONITORADDR=
WK_EXTERNAL_APIURL=
```
## SSL/TLS Configuration
```yaml YAML Configuration theme={null}
# SSL/TLS configuration
wssConfig:
certFile: "" # SSL certificate file path
keyFile: "" # SSL private key file path
```
```bash Environment Variables theme={null}
WK_WSSCONFIG_CERTFILE=
WK_WSSCONFIG_KEYFILE=
```
## Logging Configuration
```yaml YAML Configuration theme={null}
# Logging configuration
logger:
level: 0 # Log level: 0=auto, 1=debug, 2=info, 3=warn, 4=error
dir: "./logs" # Log directory
lineNum: false # Show line numbers
```
```bash Environment Variables theme={null}
WK_LOGGER_LEVEL=0
WK_LOGGER_DIR=./logs
WK_LOGGER_LINENUM=false
```
## Monitoring Configuration
```yaml YAML Configuration theme={null}
# Monitoring configuration
monitor:
on: true # Enable monitoring
addr: "0.0.0.0:5300" # Monitor listen address
# Demo interface configuration
demo:
on: true # Enable demo interface
addr: "0.0.0.0:5172" # Demo interface address
```
```bash Environment Variables theme={null}
WK_MONITOR_ON=true
WK_MONITOR_ADDR=0.0.0.0:5300
WK_DEMO_ON=true
WK_DEMO_ADDR=0.0.0.0:5172
```
## Channel Configuration
```yaml YAML Configuration theme={null}
# Channel configuration
channel:
cacheCount: 1000 # Channel cache count
createIfNoExist: true # Auto-create non-existent channels
subscriberCompressOfCount: 0 # Subscriber compression threshold
# Temporary channel configuration
tmpChannel:
suffix: "@tmp" # Temporary channel suffix
cacheCount: 500 # Temporary channel cache count
```
```bash Environment Variables theme={null}
WK_CHANNEL_CACHECOUNT=1000
WK_CHANNEL_CREATEIFNOEXIST=true
WK_CHANNEL_SUBSCRIBERCOMPRESSOFCOUNT=0
WK_TMPCHANNEL_SUFFIX=@tmp
WK_TMPCHANNEL_CACHECOUNT=500
```
## User Authentication Configuration
```yaml YAML Configuration theme={null}
# User authentication configuration
auth:
on: true # Enable user authentication
kind: "jwt" # Authentication method: jwt, none
users: # User list (for username/password auth)
- "admin:pwd:*" # Admin user
- "guest:guest:[*:r]" # Read-only user
# JWT configuration
jwt:
secret: "" # JWT secret (32-character random string)
expire: "30d" # Token expiration time
```
```bash Environment Variables theme={null}
WK_AUTH_ON=true
WK_AUTH_KIND=jwt
WK_AUTH_USERS="admin:pwd:* guest:guest:[*:r]"
WK_JWT_SECRET=
WK_JWT_EXPIRE=30d
```
## Webhook Configuration
```yaml YAML Configuration theme={null}
# Webhook configuration
webhook:
httpAddr: "" # Webhook HTTP address
grpcAddr: "" # Webhook gRPC address
msgNotifyEventPushInterval: "500ms" # Message notification push interval
msgNotifyEventRetryMaxCount: 5 # Max retry count for push failures
msgNotifyEventCountPerPush: 100 # Message count limit per push
focusEvents: # Focused event types
- "msg.offline"
- "msg.notify"
- "user.onlinestatus"
```
```bash Environment Variables theme={null}
WK_WEBHOOK_HTTPADDR=
WK_WEBHOOK_GRPCADDR=
WK_WEBHOOK_MSGNOTIFYEVENTPUSHINTERVAL=500ms
WK_WEBHOOK_MSGNOTIFYEVENTRETRYMAXCOUNT=5
WK_WEBHOOK_MSGNOTIFYEVENTCOUNTperpush=100
WK_WEBHOOK_FOCUSEVENTS="msg.offline msg.notify user.onlinestatus"
```
## Datasource Configuration
```yaml YAML Configuration theme={null}
# Datasource configuration
datasource:
addr: "" # Datasource address
channelInfoOn: false # Enable channel info datasource
```
```bash Environment Variables theme={null}
WK_DATASOURCE_ADDR=
WK_DATASOURCE_CHANNELINFOON=false
```
## Conversation Configuration
```yaml YAML Configuration theme={null}
# Recent conversation configuration
conversation:
on: true # Enable recent conversations
cacheExpire: "1d" # Cache expiration time
syncInterval: "5m" # Save interval
syncOnce: 100 # Sync save count
userMaxCount: 1000 # Max conversation count per user
```
```bash Environment Variables theme={null}
WK_CONVERSATION_ON=true
WK_CONVERSATION_CACHEEXPIRE=1d
WK_CONVERSATION_SYNCINTERVAL=5m
WK_CONVERSATION_SYNCONCE=100
WK_CONVERSATION_USERMAXCOUNT=1000
```
## Message Retry Configuration
```yaml YAML Configuration theme={null}
# Message retry configuration
messageRetry:
interval: "60s" # Retry interval
scanInterval: "5s" # Scan interval
maxCount: 5 # Max retry count
# User message queue configuration
userMsgQueueMaxSize: 0 # User message queue max size, 0 for unlimited
```
```bash Environment Variables theme={null}
WK_MESSAGERETRY_INTERVAL=60s
WK_MESSAGERETRY_SCANINTERVAL=5s
WK_MESSAGERETRY_MAXCOUNT=5
WK_USERMSGQUEUEMAXSIZE=0
```
## Tracing Configuration
```yaml YAML Configuration theme={null}
# Data tracing configuration
trace:
prometheusApiUrl: "" # Prometheus API URL
```
```bash Environment Variables theme={null}
WK_TRACE_PROMETHEUSAPIURL=
```
## Cluster Configuration
```yaml YAML Configuration theme={null}
# Cluster configuration
cluster:
nodeId: 1001 # Node ID
addr: "tcp://0.0.0.0:11110" # Distributed listen address
serverAddr: "" # Inter-node communication address
apiUrl: "" # Node HTTP address
slotCount: 64 # Slot count
slotReplicaCount: 3 # Slot replica count
channelReplicaCount: 3 # Channel replica count
initNodes: # Initial node list
- "1001@192.168.1.12:11110"
- "1002@192.168.1.13:11110"
- "1003@192.168.1.14:11110"
seed: # Cluster seed nodes
- "1001@192.168.1.12:11110"
```
```bash Environment Variables theme={null}
WK_CLUSTER_NODEID=1001
WK_CLUSTER_ADDR=tcp://0.0.0.0:11110
WK_CLUSTER_SERVERADDR=
WK_CLUSTER_APIURL=
WK_CLUSTER_SLOTCOUNT=64
WK_CLUSTER_SLOTREPLICACOUNT=3
WK_CLUSTER_CHANNELREPLICACOUNT=3
WK_CLUSTER_INITNODES="1001@192.168.1.12:11110 1002@192.168.1.13:11110 1003@192.168.1.14:11110"
WK_CLUSTER_SEED="1001@192.168.1.12:11110"
```
## Plugin Configuration
```yaml YAML Configuration theme={null}
# Plugin configuration
plugin:
socketPath: "./wukongimdata/1/wukongim.sock" # Plugin Unix Socket address
install: # Default installed plugin list
- "https://gitee.com/WuKongDev/plugins/releases/download/latest/wk.plugin.ai-example-${os}-${arch}.wkp"
- "https://gitee.com/WuKongDev/plugins/releases/download/latest/wk.plugin.ai-volcengine-${os}-${arch}.wkp"
```
```bash Environment Variables theme={null}
WK_PLUGIN_SOCKETPATH=./wukongimdata/1/wukongim.sock
WK_PLUGIN_INSTALL="https://gitee.com/WuKongDev/plugins/releases/download/latest/wk.plugin.ai-example-${os}-${arch}.wkp https://gitee.com/WuKongDev/plugins/releases/download/latest/wk.plugin.ai-volcengine-${os}-${arch}.wkp"
```
## Configuration Examples
### Production Environment
```yaml theme={null}
# Production configuration
mode: "release"
tokenAuthOn: true
managerUID: "admin"
managerToken: "prod-secure-token-2024"
external:
ip: "203.0.113.1"
wssAddr: "wss://yourdomain.com:5210"
wssConfig:
certFile: "/etc/letsencrypt/live/yourdomain.com/fullchain.pem"
keyFile: "/etc/letsencrypt/live/yourdomain.com/privkey.pem"
logger:
level: 3
dir: "/var/log/wukongim"
auth:
on: true
kind: "jwt"
jwt:
secret: "your-production-jwt-secret-32-chars"
expire: "7d"
monitor:
on: true
demo:
on: false
```
### Development Environment
```yaml theme={null}
# Development configuration
mode: "debug"
tokenAuthOn: false
logger:
level: 1
lineNum: true
auth:
on: true
kind: "jwt"
users:
- "dev:dev123:*"
- "test:test123:[*:r]"
jwt:
secret: "dev-jwt-secret-for-testing-only"
expire: "24h"
monitor:
on: true
demo:
on: true
```
## Docker Compose Configuration
```yaml theme={null}
version: '3.7'
services:
wukongim:
image: wukongim/wukongim:latest
environment:
# Basic configuration
- "WK_MODE=release"
- "WK_TOKENAUTHON=true"
- "WK_MANAGERTOKEN=secure-token-123"
# External access
- "WK_EXTERNAL_IP=203.0.113.1"
- "WK_EXTERNAL_WSSADDR=wss://yourdomain.com:5210"
# SSL configuration
- "WK_WSSCONFIG_CERTFILE=/etc/ssl/certs/wukongim.crt"
- "WK_WSSCONFIG_KEYFILE=/etc/ssl/private/wukongim.key"
# User authentication
- "WK_AUTH_ON=true"
- "WK_AUTH_KIND=jwt"
- "WK_JWT_SECRET=your-secure-jwt-secret-32-characters"
ports:
- "5001:5001" # HTTP API
- "5100:5100" # TCP
- "5200:5200" # WebSocket
- "5210:5210" # WSS
- "5300:5300" # Monitor
volumes:
- "./data:/root/wukongim"
- "./certs:/etc/ssl/certs:ro"
- "./private:/etc/ssl/private:ro"
```
## Environment Variable Rules
* **Prefix**: All environment variables start with `WK_`
* **Hierarchy**: Use underscore `_` to separate configuration levels
* **Case**: Use all uppercase letters
* **Array Format**: Use space-separated values, e.g., `WK_AUTH_USERS="user1:pass1 user2:pass2"`
* **Boolean Values**: Use `true` or `false`
* **Priority**: Environment variables > Configuration file > Default values
## Configuration Validation
Validate configuration before startup:
```bash theme={null}
# Check configuration syntax
./wukongim --config wukongim.yaml --validate
# Check configuration on startup
./wukongim --config wukongim.yaml --check-config
```
## Security Recommendations
* Production environments must enable `tokenAuthOn`
* Use 32-character random string for JWT secret
* Regularly rotate admin tokens and JWT secrets
* Set SSL certificate file permissions to 600
* Disable unnecessary demo interfaces and debug features
# AI Agent Support
Source: https://wukong.mintlify.app/en/ai/overview
WuKongIM natively supports AI Agent functionality, implementing streaming AI conversation experiences through the ag-ui protocol
# AI Agent Support
## Overview
WuKongIM natively supports AI Agent functionality, implementing streaming AI conversation experiences through integration with the [ag-ui protocol](https://docs.ag-ui.com/introduction). Developers can easily build intelligent chatbots, AI assistants, and other applications, providing users with natural and smooth AI interaction experiences.
## Workflow
```mermaid theme={null}
sequenceDiagram
participant Client as Client
participant WuKong as WuKongIM
participant Server as Developer Server
participant Agent as Agent
Client->>WuKong: 1. Send message to Agent channel
WuKong->>Server: 2. Call developer interface
Server->>Agent: 3. Call Agent
Agent-->>Server: 4. Return AI response
Server->>WuKong: 5. Stream via ag-ui protocol
WuKong-->>Client: 6. Real-time push AI response
```
### Process Details
1. **User sends message**: Client user sends a message to WuKongIM's Agent channel
2. **Trigger callback**: WuKongIM receives the message and calls the developer's configured server interface
3. **Call Agent**: Developer server sends the user message to the Agent large model for processing
4. **Get response**: Agent large model generates AI response content
5. **Stream delivery**: Developer server streams the response to the client through WuKongIM's ag-ui protocol interface
6. **Real-time display**: Client receives and displays AI response content in real-time
## ag-ui Protocol Support
WuKongIM natively supports the [ag-ui protocol](https://docs.ag-ui.com/introduction), which is a user interface protocol specifically designed for AI applications.
# Advanced Features
Source: https://wukong.mintlify.app/en/sdk/wukongim/flutter/advance
WuKongIM Flutter SDK advanced features, including custom message types and extension functionality
## Custom Message Types
In WuKongIM, all message types are custom messages. Below we use a `gif` message as an example.
### Step 1: Define Message
Define a message object that inherits from `WKMessageContent` and specify the message type in the constructor.
Built-in message types in SDK can be viewed through `WkMessageContentType`
**Inherit `WKMessageContent` and define gif message structure**
```dart theme={null}
class GifContent extends WKMessageContent {
int width = 0; // Width
int height = 0; // Height
String url; // Remote URL
GifContent(this.url) {
// Specify message type
contentType = WkMessageContentType.gif;
}
}
```
### Step 2: Encoding and Decoding
```dart theme={null}
@override
WKMessageContent decodeJson(Map json) {
url = readString(json, 'url');
width = readInt(json, 'width');
height = readInt(json, 'height');
return this;
}
@override
Map encodeJson() {
return {'url': url, 'width': width, 'height': height};
}
```
When encoding and decoding messages, there's no need to consider the `type` field, as the SDK handles it internally
### Step 3: Register Message
```dart theme={null}
WKIM.shared.messageManager.registerMsgContent(WkMessageContentType.gif,
(dynamic data) {
return GifContent('').decodeJson(data);
});
```
Through these three steps, the custom regular message is complete. When receiving a message, if the type in `WKMsg` is 3, it indicates that the message is a business card message, where `messageContent` is the custom `GifContent`. At this time, you can cast `messageContent` to `GifContent` and render it on the UI.
Complete code as follows:
```dart theme={null}
class GifContent extends WKMessageContent {
int width = 0; // Width
int height = 0; // Height
String url; // Remote URL
GifContent(this.url) {
// Specify message type
contentType = WkMessageContentType.gif;
}
@override
WKMessageContent decodeJson(Map json) {
url = readString(json, 'url');
width = readInt(json, 'width');
height = readInt(json, 'height');
return this;
}
@override
Map encodeJson() {
return {'url': url, 'width': width, 'height': height};
}
// Override if you need to get displayable content
@override
String displayText() {
return "[Animated Image]";
}
}
```
## Custom Attachment Messages
Sometimes we need to send messages with attachments when sending messages. WuKongIM also provides custom attachment messages, which are not much different from regular messages. Below we use location messages as an example.
### Step 1: Define Message
Note that custom attachment messages need to inherit from `WKMediaMessageContent` instead of `WKMessageContent`.
```dart theme={null}
class WKLocationContent extends WKMediaMessageContent {
var longitude = 0.0;
var latitude = 0.0;
var address = "";
WKLocationContent() {
contentType = 10;
}
}
```
### Step 2: Encoding and Decoding
```dart theme={null}
@override
Map encodeJson() {
return {
'longitude': longitude,
'latitude': latitude,
'url': url,
'address': address,
'localPath': localPath
};
}
@override
WKMessageContent decodeJson(Map json) {
address = readString(json, 'address');
longitude = readDouble(json, 'longitude');
url = readString(json, 'url');
latitude = readDouble(json, 'latitude');
localPath = readString(json, 'localPath');
return this;
}
```
### Step 3: Register Message
```dart theme={null}
WKIM.shared.messageManager.registerMsgContent(10,
(dynamic data) {
return WKLocationContent().decodeJson(data);
});
```
## Complete Custom Message Example
```dart theme={null}
// Business Card Message Example
class BusinessCardContent extends WKMessageContent {
String uid = '';
String name = '';
String avatar = '';
String phone = '';
String email = '';
BusinessCardContent({
this.uid = '',
this.name = '',
this.avatar = '',
this.phone = '',
this.email = '',
}) {
contentType = WkMessageContentType.businessCard;
}
@override
WKMessageContent decodeJson(Map json) {
uid = readString(json, 'uid');
name = readString(json, 'name');
avatar = readString(json, 'avatar');
phone = readString(json, 'phone');
email = readString(json, 'email');
return this;
}
@override
Map encodeJson() {
return {
'uid': uid,
'name': name,
'avatar': avatar,
'phone': phone,
'email': email,
};
}
@override
String displayText() {
return "[Business Card] $name";
}
@override
String searchableText() {
return "[Business Card] $name $phone $email";
}
bool isValid() {
return uid.isNotEmpty && name.isNotEmpty;
}
}
// Register business card message
WKIM.shared.messageManager.registerMsgContent(
WkMessageContentType.businessCard,
(dynamic data) => BusinessCardContent().decodeJson(data),
);
// Send business card message
void sendBusinessCard(String channelID, int channelType, BusinessCardContent card) {
if (card.isValid()) {
WKIM.shared.messageManager.sendMessage(
card,
WKChannel(channelID, channelType),
);
}
}
```
## Message Extensions
As business develops, applications have increasingly more features in chat. To meet most requirements, WuKongIM has added message extension functionality. Message extensions are divided into `local extensions` and `remote extensions`. Local extensions are only for local app use and will be lost after uninstalling the app. Remote extensions are saved on the server and data will be restored after uninstalling and reinstalling.
### Local Extensions
Local extensions are the `localExtraMap` field in the message object `WKMsg`.
```dart theme={null}
// Modify message local extensions
WKIM.shared.messageManager.updateLocalExtraWithClientMsgNo(String clientMsgNo, dynamic data);
```
After successful update, the SDK will trigger a refresh message callback
### Remote Extensions
Remote extensions are the `wkMsgExtra` field in the message object `WKMsg`.
```dart theme={null}
// Modify message remote extensions
WKIM.shared.messageManager.saveRemoteExtraMsg(List list);
```
## Message Read/Unread
Message read/unread is also called message receipts. Message receipt functionality can be set through settings.
### Send Receipt Message
```dart theme={null}
Setting setting = Setting();
setting.receipt = 1; // Enable receipts
var option = WKSendOptions();
option.setting = setting;
// Send message
WKIM.shared.messageManager.sendWithOption(
text, WKChannel(channelID, channelType), option);
```
When a logged-in user views messages sent by others, if the sender has enabled message receipts, the viewed messages need to be uploaded to the server to mark them as read. When the sender or yourself uploads read messages, the server will send a sync message extension cmd (command) message. At this time, you need to sync the latest message extensions through `WKIM.shared.messageManager.saveRemoteExtraMsg(List list)` method and save them to the SDK.
## Message Reply
In chat, if there are too many messages, sending message replies will make the messages very messy and hard to follow. At this time, you need to make specific replies to certain messages, which is message reply.
When sending a message, you just need to assign the `WKReply` object in the message content `WKMessageContent` to achieve the message reply effect.
### Send Reply Message
```dart theme={null}
// Reply
WKTextContent text = WKTextContent(content);
WKReply reply = WKReply();
reply.messageId = "11";
reply.rootMid = "111";
reply.fromUID = "11";
reply.fromName = "12";
WKTextContent payloadText = WKTextContent("dds");
reply.payload = payloadText;
text.reply = reply;
// Send message
WKIM.shared.messageManager.sendMessage(text, WKChannel(channelID, channelType));
```
## Message Reactions (Likes)
When you or others react to messages (like), it will trigger cmd (command) message notifications to the application. When the app receives a sync message reaction cmd, it can call the server sync interface to update the obtained reaction data to the SDK.
```dart theme={null}
// Save message reactions
WKIM.shared.messageManager.saveMessageReactions(List list);
```
The same user can only make one reaction to the same message. Repeated reactions with different emojis to the same message will be treated as modifying the reaction, while repeated reactions with the same emoji will be treated as deleting the reaction. After the SDK updates message reactions, it will trigger a message refresh event. The app needs to listen for this event and refresh the UI.
## Message Editing
When we send a message to someone and find that the content is wrong, we don't need to recall and resend it. We just need to edit the message.
### Set Edit Content
```dart theme={null}
WKIM.shared.messageManager.updateMsgEdit(String messageID, String channelID, int channelType, String content);
```
After changing the SDK message edit content, you need to upload the edited content to the server, which requires listening for upload message extensions.
### Listen for Upload Message Extensions
```dart theme={null}
WKIM.shared.messageManager.addOnUploadMsgExtra((wkMsgExtra) => {
// Upload to your own server
});
```
If you or others edit messages, it will trigger cmd (command) messages. The app can determine based on the cmd type and then sync message extensions. The app needs to listen for message update events to complete UI refresh.
## Advanced Message Management Example
```dart theme={null}
class AdvancedMessageManager {
static final AdvancedMessageManager _instance = AdvancedMessageManager._internal();
factory AdvancedMessageManager() => _instance;
AdvancedMessageManager._internal();
void initialize() {
_registerCustomMessages();
_setupAdvancedListeners();
}
void _registerCustomMessages() {
// Register GIF message
WKIM.shared.messageManager.registerMsgContent(
WkMessageContentType.gif,
(dynamic data) => GifContent('').decodeJson(data),
);
// Register business card message
WKIM.shared.messageManager.registerMsgContent(
WkMessageContentType.businessCard,
(dynamic data) => BusinessCardContent().decodeJson(data),
);
// Register location message
WKIM.shared.messageManager.registerMsgContent(
10,
(dynamic data) => WKLocationContent().decodeJson(data),
);
}
void _setupAdvancedListeners() {
// Listen for message extension uploads
WKIM.shared.messageManager.addOnUploadMsgExtra((wkMsgExtra) {
_uploadMessageExtension(wkMsgExtra);
});
}
Future _uploadMessageExtension(WKMsgExtra msgExtra) async {
try {
// Upload to your server
await _callServerAPI('/api/message/extension', msgExtra.toJson());
} catch (e) {
print('Failed to upload message extension: $e');
}
}
// Send message with receipt
Future sendMessageWithReceipt(WKMessageContent content, WKChannel channel) async {
final setting = Setting();
setting.receipt = 1; // Enable receipts
final option = WKSendOptions();
option.setting = setting;
await WKIM.shared.messageManager.sendWithOption(content, channel, option);
}
// Send reply message
Future sendReplyMessage(
String replyText,
WKChannel channel,
WKMsg originalMessage,
) async {
final textContent = WKTextContent(replyText);
final reply = WKReply();
reply.messageId = originalMessage.messageID;
reply.rootMid = originalMessage.messageID; // For single-level reply
reply.fromUID = originalMessage.fromUID;
reply.fromName = originalMessage.fromUID; // You might want to get actual name
reply.payload = originalMessage.messageContent;
textContent.reply = reply;
await WKIM.shared.messageManager.sendMessage(textContent, channel);
}
// Update message local extension
Future updateMessageLocalExtension(String clientMsgNo, Map data) async {
await WKIM.shared.messageManager.updateLocalExtraWithClientMsgNo(clientMsgNo, data);
}
// Save message reactions
Future saveMessageReactions(List reactions) async {
await WKIM.shared.messageManager.saveMessageReactions(reactions);
}
// Edit message
Future editMessage(String messageID, String channelID, int channelType, String newContent) async {
await WKIM.shared.messageManager.updateMsgEdit(messageID, channelID, channelType, newContent);
}
Future _callServerAPI(String endpoint, Map data) async {
// Implement your server API call
}
}
```
## Next Steps
Learn how to configure data sources for custom messages
Return to message management functionality
Return to basic features
Return to integration guide
# Channel Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/flutter/channel
WuKongIM Flutter SDK channel management functionality, including channel information retrieval and monitoring
Channel is an abstract concept in WuKongIM. Messages are first sent to channels, and channels deliver messages according to their configuration rules. Channels are divided into channel and channel details. For more information, please refer to [What is a Channel](/en/guide/initialize#channels).
## Data Source
`Need to implement channel information data source` [Channel Information Data Source](/en/sdk/wukongim/flutter/datasource#channel-information-data-source)
## Channel Information
### Get Channel Information
```dart theme={null}
// Get channel information
WKIM.shared.channelManager.getChannel(String channelID, int channelType);
```
### Force Refresh Channel Information
```dart theme={null}
// Force refresh channel information
WKIM.shared.channelManager.fetchChannelInfo(String channelID, int channelType)
```
## Events
### Listen for Channel Update Events
```dart theme={null}
// Listen for channel refresh events
WKIM.shared.channelManager.addOnRefreshListener('key', (wkChannel) {
// Refresh corresponding channel information
});
// Remove listener
WKIM.shared.channelManager.removeOnRefreshListener('key');
```
The key is a unique identifier for the listener, can be any string. The same key must be used when adding and removing listeners.
## Common Methods
```dart theme={null}
// Batch save channel information
WKIM.shared.channelManager.addOrUpdateChannels(List channelList);
// Search
WKIM.shared.channelManager.search(String keyword);
```
## Complete Channel Management Example
```dart theme={null}
class ChannelManager {
static final ChannelManager _instance = ChannelManager._internal();
factory ChannelManager() => _instance;
ChannelManager._internal();
final Map _channelCache = {};
final StreamController _channelUpdateController = StreamController.broadcast();
// Stream for UI to listen
Stream get channelUpdateStream => _channelUpdateController.stream;
void initialize() {
_setupChannelListener();
}
void _setupChannelListener() {
// Listen for channel refresh
WKIM.shared.channelManager.addOnRefreshListener('global', (wkChannel) {
_handleChannelUpdate(wkChannel);
});
}
void _handleChannelUpdate(WKChannel channel) {
final channelKey = '${channel.channelID}_${channel.channelType}';
_channelCache[channelKey] = channel;
// Notify listeners
_channelUpdateController.add(channel);
}
// Get channel with caching
WKChannel? getChannelWithCache(String channelID, int channelType) {
final channelKey = '${channelID}_$channelType';
// First check cache
if (_channelCache.containsKey(channelKey)) {
return _channelCache[channelKey];
}
// Get from SDK
final channel = WKIM.shared.channelManager.getChannel(channelID, channelType);
if (channel != null) {
_channelCache[channelKey] = channel;
return channel;
}
// Trigger network request
fetchChannelInfo(channelID, channelType);
return null;
}
// Force refresh channel information
void fetchChannelInfo(String channelID, int channelType) {
WKIM.shared.channelManager.fetchChannelInfo(channelID, channelType);
}
// Batch save channels
void saveChannels(List channels) {
WKIM.shared.channelManager.addOrUpdateChannels(channels);
// Update cache
for (var channel in channels) {
final channelKey = '${channel.channelID}_${channel.channelType}';
_channelCache[channelKey] = channel;
}
}
// Search channels
List searchChannels(String keyword) {
return WKIM.shared.channelManager.search(keyword);
}
// Get channel display name
String getChannelDisplayName(WKChannel channel) {
return channel.channelRemark.isNotEmpty
? channel.channelRemark
: (channel.channelName.isNotEmpty ? channel.channelName : channel.channelID);
}
// Check if channel is online
bool isChannelOnline(WKChannel channel) {
return channel.online == 1;
}
// Check if channel is muted
bool isChannelMuted(WKChannel channel) {
return channel.mute == 1;
}
// Check if channel is pinned
bool isChannelPinned(WKChannel channel) {
return channel.top == 1;
}
// Get channel type text
String getChannelTypeText(int channelType) {
switch (channelType) {
case WKChannelType.personal:
return 'Personal';
case WKChannelType.group:
return 'Group';
default:
return 'Unknown';
}
}
// Get channel avatar URL
String getChannelAvatarUrl(WKChannel channel) {
if (channel.avatar.isNotEmpty) {
final cacheKey = channel.avatarCacheKey.isNotEmpty ? '?v=${channel.avatarCacheKey}' : '';
return '${channel.avatar}$cacheKey';
}
return _getDefaultAvatar(channel.channelID);
}
String _getDefaultAvatar(String channelID) {
return 'https://ui-avatars.com/api/?name=$channelID&background=random';
}
// Format last offline time
String formatLastOfflineTime(int lastOffline) {
if (lastOffline == 0) return 'Never';
final date = DateTime.fromMillisecondsSinceEpoch(lastOffline * 1000);
final now = DateTime.now();
final difference = now.difference(date);
if (difference.inMinutes < 1) return 'Just now';
if (difference.inMinutes < 60) return '${difference.inMinutes} minutes ago';
if (difference.inHours < 24) return '${difference.inHours} hours ago';
if (difference.inDays < 7) return '${difference.inDays} days ago';
return '${date.month}/${date.day}/${date.year}';
}
// Get device flag text
String getDeviceFlagText(int deviceFlag) {
switch (deviceFlag) {
case 0: return 'APP';
case 1: return 'WEB';
case 2: return 'PC';
default: return 'Unknown';
}
}
// Check if channel allows invites
bool canInviteMembers(WKChannel channel) {
return channel.invite == 1;
}
// Check if channel has receipt enabled
bool hasReceiptEnabled(WKChannel channel) {
return channel.receipt == 1;
}
// Check if channel is a robot
bool isRobotChannel(WKChannel channel) {
return channel.robot == 1;
}
// Clear channel cache
void clearChannelCache() {
_channelCache.clear();
}
void dispose() {
_channelUpdateController.close();
WKIM.shared.channelManager.removeOnRefreshListener('global');
}
}
```
## Data Structure Description
```dart theme={null}
class WKChannel {
String channelID = "";
int channelType = WKChannelType.personal;
String channelName = "";
// Channel remark (channel's remark name, personal remark for individuals, group alias for groups)
String channelRemark = "";
int showNick = 0;
// Whether pinned
int top = 0;
// Whether saved in contacts
int save = 0;
// Do not disturb
int mute = 0;
// Forbidden
int forbidden = 0;
// Invite confirmation
int invite = 0;
// Channel status [1: normal 2: blacklist]
int status = 1;
// Whether followed 0: not followed (stranger) 1: followed (friend)
int follow = 0;
// Whether deleted
int isDeleted = 0;
// Creation time
String createdAt = "";
// Update time
String updatedAt = "";
// Channel avatar
String avatar = "";
// Version
int version = 0;
// Extension fields
dynamic localExtra;
// Whether online
int online = 0;
// Last offline time
int lastOffline = 0;
// Last offline device flag
int deviceFlag = 0;
// Whether receipt message
int receipt = 0;
// Robot
int robot = 0;
// Category [service: customer service]
String category = "";
String username = "";
String avatarCacheKey = "";
dynamic remoteExtraMap;
String parentChannelID = "";
int parentChannelType = 0;
}
```
## UI Integration Example
```dart theme={null}
class ChannelInfoWidget extends StatefulWidget {
final String channelID;
final int channelType;
const ChannelInfoWidget({
Key? key,
required this.channelID,
required this.channelType,
}) : super(key: key);
@override
_ChannelInfoWidgetState createState() => _ChannelInfoWidgetState();
}
class _ChannelInfoWidgetState extends State {
WKChannel? _channel;
late StreamSubscription _channelSubscription;
@override
void initState() {
super.initState();
_loadChannelInfo();
_setupChannelListener();
}
void _loadChannelInfo() {
_channel = ChannelManager().getChannelWithCache(widget.channelID, widget.channelType);
if (mounted) setState(() {});
}
void _setupChannelListener() {
_channelSubscription = ChannelManager().channelUpdateStream.listen((channel) {
if (channel.channelID == widget.channelID && channel.channelType == widget.channelType) {
setState(() {
_channel = channel;
});
}
});
}
@override
void dispose() {
_channelSubscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_channel == null) {
return const Center(child: CircularProgressIndicator());
}
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
CircleAvatar(
radius: 30,
backgroundImage: NetworkImage(
ChannelManager().getChannelAvatarUrl(_channel!),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
ChannelManager().getChannelDisplayName(_channel!),
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
ChannelManager().getChannelTypeText(_channel!.channelType),
style: TextStyle(
color: Colors.grey[600],
),
),
],
),
),
if (ChannelManager().isChannelOnline(_channel!))
Container(
width: 12,
height: 12,
decoration: const BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
),
),
],
),
const SizedBox(height: 16),
_buildInfoRow('Status', ChannelManager().isChannelOnline(_channel!) ? 'Online' : 'Offline'),
if (!ChannelManager().isChannelOnline(_channel!))
_buildInfoRow('Last Seen', ChannelManager().formatLastOfflineTime(_channel!.lastOffline)),
_buildInfoRow('Muted', ChannelManager().isChannelMuted(_channel!) ? 'Yes' : 'No'),
_buildInfoRow('Pinned', ChannelManager().isChannelPinned(_channel!) ? 'Yes' : 'No'),
if (ChannelManager().isRobotChannel(_channel!))
_buildInfoRow('Type', 'Robot'),
],
),
),
);
}
Widget _buildInfoRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: TextStyle(
color: Colors.grey[600],
),
),
Text(
value,
style: const TextStyle(
fontWeight: FontWeight.w500,
),
),
],
),
);
}
}
```
## Next Steps
Learn how to manage channel members
Configure channel data sources
Return to message handling functionality
Explore advanced features and custom messages
# Channel Member Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/flutter/channel_member
WuKongIM Flutter SDK channel member management functionality, including member retrieval, listening and operations
Channel member management provides complete group member management functionality, including member information retrieval, member status listening, permission management and other core capabilities.
## Get Channel Members
### Get All Members in Channel
```dart theme={null}
// Get all members in channel
List members = WKIM.shared.channelMemberManager.getMembers(channelId: channelId);
```
### Get Specific User's Member Information in Channel
```dart theme={null}
// Get specific user's member information in channel
WKChannelMember? member = WKIM.shared.channelMemberManager.getMember(channelId: channelId, uid: uid);
```
### Complete Member Retrieval Example
```dart theme={null}
class ChannelMemberManager {
// Get all members
static List getAllMembers(String channelId) {
try {
final members = WKIM.shared.channelMemberManager.getMembers(channelId: channelId);
print('Retrieved ${members.length} members');
return members;
} catch (error) {
print('Failed to get member list: $error');
return [];
}
}
// Get specific member information
static WKChannelMember? getMemberInfo(String channelId, String uid) {
try {
final member = WKIM.shared.channelMemberManager.getMember(channelId: channelId, uid: uid);
if (member != null) {
print('Successfully retrieved member info: ${member.memberName}');
}
return member;
} catch (error) {
print('Failed to get member info: $error');
return null;
}
}
// Filter members by role
static List getMembersByRole(String channelId, int role) {
final allMembers = getAllMembers(channelId);
return allMembers.where((member) => member.role == role).toList();
}
// Get admin list
static List getAdminMembers(String channelId) {
return getMembersByRole(channelId, 1); // Assume 1 is admin role
}
// Get normal member list
static List getNormalMembers(String channelId) {
return getMembersByRole(channelId, 0); // Assume 0 is normal member role
}
// Search members
static List searchMembers(String channelId, String keyword) {
if (keyword.trim().isEmpty) {
return [];
}
final allMembers = getAllMembers(channelId);
return allMembers.where((member) {
final name = member.memberName.toLowerCase();
final remark = member.memberRemark.toLowerCase();
final uid = member.memberUID.toLowerCase();
final searchKey = keyword.toLowerCase();
return name.contains(searchKey) ||
remark.contains(searchKey) ||
uid.contains(searchKey);
}).toList();
}
// Get online members (needs to combine with channel info)
static List getOnlineMembers(String channelId) {
final allMembers = getAllMembers(channelId);
// Here needs to combine with channel manager to get online status
return allMembers.where((member) {
final channel = WKIM.shared.channelManager.getChannel(member.memberUID, WKChannelType.personal);
return channel?.online == 1;
}).toList();
}
// Get member display name
static String getMemberDisplayName(WKChannelMember member) {
if (member.memberRemark.isNotEmpty) {
return member.memberRemark;
}
if (member.memberName.isNotEmpty) {
return member.memberName;
}
return member.memberUID;
}
// Check if member is admin
static bool isAdmin(WKChannelMember member) {
return member.role == 1; // Assume 1 is admin role
}
// Check if member is muted
static bool isMuted(WKChannelMember member) {
if (member.forbiddenExpirationTime == 0) {
return false;
}
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
return member.forbiddenExpirationTime > now;
}
// Check if member is blacklisted
static bool isBlacklisted(WKChannelMember member) {
return member.status == 2; // 2 means blacklist status
}
}
```
## Event Listening
### Refresh Channel Member Listening
```dart theme={null}
// Refresh channel member
WKIM.shared.channelMemberManager.addOnRefreshMemberListener('key', (WKChannelMember member, bool isEnd) {
// TODO refresh conversation list
});
// Remove refresh channel member listener
WKIM.shared.channelMemberManager.removeRefreshMemberListener('key');
```
The key is a unique identifier for the listener, can be any string. The same key must be used when adding and removing listeners.
### Complete Event Listening Management
```dart theme={null}
class ChannelMemberListener {
static final Map _refreshListeners = {};
static StreamController? _updateController;
// Get member update stream
static Stream get memberUpdateStream {
_updateController ??= StreamController.broadcast();
return _updateController!.stream;
}
// Add member refresh listener
static void addRefreshMemberListener(String key, Function(WKChannelMember, bool) callback) {
_refreshListeners[key] = callback;
WKIM.shared.channelMemberManager.addOnRefreshMemberListener(key, (WKChannelMember member, bool isEnd) {
// Call callback
callback(member, isEnd);
// Send to stream
_updateController?.add(ChannelMemberUpdateEvent(
member: member,
isEnd: isEnd,
timestamp: DateTime.now(),
));
print('Channel member updated: ${member.memberUID}, isEnd: $isEnd');
});
}
// Remove member refresh listener
static void removeRefreshMemberListener(String key) {
_refreshListeners.remove(key);
WKIM.shared.channelMemberManager.removeRefreshMemberListener(key);
}
// Remove all listeners
static void removeAllListeners() {
for (final key in _refreshListeners.keys) {
WKIM.shared.channelMemberManager.removeRefreshMemberListener(key);
}
_refreshListeners.clear();
}
// Add listener for specific channel
static void addChannelMemberListener(String channelId, Function(WKChannelMember, bool) callback) {
final key = 'channel_$channelId';
addRefreshMemberListener(key, (WKChannelMember member, bool isEnd) {
if (member.channelID == channelId) {
callback(member, isEnd);
}
});
}
// Remove listener for specific channel
static void removeChannelMemberListener(String channelId) {
final key = 'channel_$channelId';
removeRefreshMemberListener(key);
}
// Dispose
static void dispose() {
removeAllListeners();
_updateController?.close();
_updateController = null;
}
}
// Channel member update event
class ChannelMemberUpdateEvent {
final WKChannelMember member;
final bool isEnd;
final DateTime timestamp;
ChannelMemberUpdateEvent({
required this.member,
required this.isEnd,
required this.timestamp,
});
@override
String toString() {
return 'ChannelMemberUpdateEvent{memberUID: ${member.memberUID}, isEnd: $isEnd, timestamp: $timestamp}';
}
}
```
## Data Structure Description
### WKChannelMember Channel Member Object
```dart theme={null}
class WKChannelMember {
String channelID = ""; // Channel ID
int channelType = 0; // Channel type
String memberUID = ""; // Member ID
String memberName = ""; // Member name
String memberRemark = ""; // Member remark
String memberAvatar = ""; // Member avatar
int role = 0; // Member role
int status = 0; // Member status (1: normal, 2: blacklist)
int isDeleted = 0; // Whether deleted
String createdAt = ""; // Creation time
String updatedAt = ""; // Update time
int version = 0; // Version
int robot = 0; // Robot (0: no, 1: yes)
dynamic extraMap; // Extension fields
String remark = ""; // User remark
String memberInviteUID = ""; // Inviter UID
int forbiddenExpirationTime = 0; // Mute expiration time
String memberAvatarCacheKey = "";// Member avatar cache key
}
```
### Field Description
| Field | Type | Description |
| ------------------------- | ------ | ----------------------------------------------- |
| `channelID` | String | Channel ID |
| `channelType` | int | Channel type |
| `memberUID` | String | Member ID |
| `memberName` | String | Member name |
| `memberRemark` | String | Member remark |
| `memberAvatar` | String | Member avatar URL |
| `role` | int | Member role (0=normal member, 1=admin, 2=owner) |
| `status` | int | Member status (1=normal, 2=blacklist) |
| `isDeleted` | int | Whether deleted (0=no, 1=yes) |
| `version` | int | Version number |
| `robot` | int | Whether robot (0=no, 1=yes) |
| `forbiddenExpirationTime` | int | Mute expiration timestamp |
### Member Role Description
| Role Value | Description |
| ---------- | ------------- |
| `0` | Normal member |
| `1` | Admin |
| `2` | Owner |
### Member Status Description
| Status Value | Description |
| ------------ | ----------- |
| `1` | Normal |
| `2` | Blacklist |
## Flutter Widget Integration Example
```dart theme={null}
class ChannelMemberListWidget extends StatefulWidget {
final String channelId;
const ChannelMemberListWidget({Key? key, required this.channelId}) : super(key: key);
@override
_ChannelMemberListWidgetState createState() => _ChannelMemberListWidgetState();
}
class _ChannelMemberListWidgetState extends State {
List _members = [];
List _filteredMembers = [];
bool _loading = true;
String _searchKeyword = '';
StreamSubscription? _subscription;
@override
void initState() {
super.initState();
_loadMembers();
_setupListener();
}
@override
void dispose() {
_subscription?.cancel();
ChannelMemberListener.removeChannelMemberListener(widget.channelId);
super.dispose();
}
void _loadMembers() {
setState(() {
_loading = true;
});
final members = ChannelMemberManager.getAllMembers(widget.channelId);
setState(() {
_members = members;
_filteredMembers = members;
_loading = false;
});
}
void _setupListener() {
// Add member update listener
ChannelMemberListener.addChannelMemberListener(widget.channelId, (member, isEnd) {
if (isEnd) {
_loadMembers();
}
});
// Listen to member update stream
_subscription = ChannelMemberListener.memberUpdateStream.listen((event) {
if (event.member.channelID == widget.channelId && event.isEnd) {
_loadMembers();
}
});
}
void _searchMembers(String keyword) {
setState(() {
_searchKeyword = keyword;
if (keyword.isEmpty) {
_filteredMembers = _members;
} else {
_filteredMembers = ChannelMemberManager.searchMembers(widget.channelId, keyword);
}
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
// Search box
Padding(
padding: EdgeInsets.all(16),
child: TextField(
decoration: InputDecoration(
hintText: 'Search members',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
onChanged: _searchMembers,
),
),
// Member list
Expanded(
child: _loading
? Center(child: CircularProgressIndicator())
: _filteredMembers.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.people_outline, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text('No members', style: TextStyle(color: Colors.grey)),
],
),
)
: ListView.builder(
itemCount: _filteredMembers.length,
itemBuilder: (context, index) {
final member = _filteredMembers[index];
return _buildMemberItem(member);
},
),
),
],
);
}
Widget _buildMemberItem(WKChannelMember member) {
return ListTile(
leading: CircleAvatar(
backgroundImage: member.memberAvatar.isNotEmpty
? NetworkImage(member.memberAvatar)
: null,
child: member.memberAvatar.isEmpty
? Text(member.memberUID.substring(0, 1).toUpperCase())
: null,
),
title: Text(
ChannelMemberManager.getMemberDisplayName(member),
style: TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('ID: ${member.memberUID}'),
if (ChannelMemberManager.isMuted(member))
Text('Muted', style: TextStyle(color: Colors.red, fontSize: 12)),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (ChannelMemberManager.isAdmin(member))
Container(
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.orange,
borderRadius: BorderRadius.circular(4),
),
child: Text(
'Admin',
style: TextStyle(color: Colors.white, fontSize: 10),
),
),
if (member.robot == 1)
Container(
margin: EdgeInsets.only(left: 4),
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(4),
),
child: Text(
'Bot',
style: TextStyle(color: Colors.white, fontSize: 10),
),
),
],
),
onTap: () {
_showMemberDetails(member);
},
);
}
void _showMemberDetails(WKChannelMember member) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Member Details'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Name: ${member.memberName}'),
Text('ID: ${member.memberUID}'),
Text('Role: ${_getRoleText(member.role)}'),
Text('Status: ${_getStatusText(member.status)}'),
if (member.memberRemark.isNotEmpty)
Text('Remark: ${member.memberRemark}'),
if (ChannelMemberManager.isMuted(member))
Text('Mute expires: ${_formatTime(member.forbiddenExpirationTime)}'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Close'),
),
],
),
);
}
String _getRoleText(int role) {
switch (role) {
case 0: return 'Member';
case 1: return 'Admin';
case 2: return 'Owner';
default: return 'Unknown';
}
}
String _getStatusText(int status) {
switch (status) {
case 1: return 'Normal';
case 2: return 'Blacklist';
default: return 'Unknown';
}
}
String _formatTime(int timestamp) {
if (timestamp == 0) return 'Permanent';
final date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')} ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}';
}
}
```
## Next Steps
Learn command message processing and listening
Understand @mentions and custom reminder functionality
Return to channel management functionality
Explore custom messages and extension features
# Command Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/flutter/cmd
WuKongIM Flutter SDK command management functionality, including CMD message listening and processing
Command management is responsible for handling CMD (command) messages sent from the server. CMD messages can only be sent from the server to the client for parsing, used to handle various system-level instructions and status synchronization.
CMD messages can only be sent from the server, clients cannot actively send CMD messages
## Listen for Command Messages
### Basic Listening
```dart theme={null}
// Listen for command messages
WKIM.shared.cmdManager.addOnCmdListener('chat', (cmdMsg) {
// TODO handle cmd messages as needed
});
// Remove listener
WKIM.shared.cmdManager.removeCmdListener('chat');
```
### Complete Command Listening Management
```dart theme={null}
class CMDMessageManager {
static final Map _cmdListeners = {};
static StreamController? _cmdController;
// Get command message stream
static Stream get cmdMessageStream {
_cmdController ??= StreamController.broadcast();
return _cmdController!.stream;
}
// Add command listener
static void addCmdListener(String key, Function(WKCMD) callback) {
_cmdListeners[key] = callback;
WKIM.shared.cmdManager.addOnCmdListener(key, (WKCMD cmdMsg) {
// Call callback
callback(cmdMsg);
// Send to stream
_cmdController?.add(CMDMessageEvent(
cmd: cmdMsg,
timestamp: DateTime.now(),
));
// Handle common commands
_handleCommonCommands(cmdMsg);
print('Received command message: ${cmdMsg.cmd}');
});
}
// Remove command listener
static void removeCmdListener(String key) {
_cmdListeners.remove(key);
WKIM.shared.cmdManager.removeCmdListener(key);
}
// Remove all listeners
static void removeAllListeners() {
for (final key in _cmdListeners.keys) {
WKIM.shared.cmdManager.removeCmdListener(key);
}
_cmdListeners.clear();
}
// Handle common commands
static void _handleCommonCommands(WKCMD cmdMsg) {
switch (cmdMsg.cmd) {
case 'syncConversation':
_handleSyncConversation(cmdMsg);
break;
case 'syncChannelInfo':
_handleSyncChannelInfo(cmdMsg);
break;
case 'syncChannelMember':
_handleSyncChannelMember(cmdMsg);
break;
case 'syncMessageExtra':
_handleSyncMessageExtra(cmdMsg);
break;
case 'syncMessageReaction':
_handleSyncMessageReaction(cmdMsg);
break;
case 'userStatusUpdate':
_handleUserStatusUpdate(cmdMsg);
break;
case 'channelUpdate':
_handleChannelUpdate(cmdMsg);
break;
case 'memberUpdate':
_handleMemberUpdate(cmdMsg);
break;
default:
print('Unhandled command: ${cmdMsg.cmd}');
break;
}
}
// Handle sync conversation command
static void _handleSyncConversation(WKCMD cmdMsg) {
print('Handle sync conversation command');
// Trigger conversation sync logic
// Can call conversation manager's sync method here
}
// Handle sync channel info command
static void _handleSyncChannelInfo(WKCMD cmdMsg) {
print('Handle sync channel info command');
if (cmdMsg.param is Map) {
final params = cmdMsg.param as Map;
final channelId = params['channel_id'] as String?;
final channelType = params['channel_type'] as int?;
if (channelId != null && channelType != null) {
// Refresh channel info
WKIM.shared.channelManager.fetchChannelInfo(channelId, channelType);
}
}
}
// Handle sync channel member command
static void _handleSyncChannelMember(WKCMD cmdMsg) {
print('Handle sync channel member command');
// Trigger channel member sync logic
}
// Handle sync message extra command
static void _handleSyncMessageExtra(WKCMD cmdMsg) {
print('Handle sync message extra command');
// Trigger message extra sync logic
}
// Handle sync message reaction command
static void _handleSyncMessageReaction(WKCMD cmdMsg) {
print('Handle sync message reaction command');
// Trigger message reaction sync logic
}
// Handle user status update command
static void _handleUserStatusUpdate(WKCMD cmdMsg) {
print('Handle user status update command');
if (cmdMsg.param is Map) {
final params = cmdMsg.param as Map;
final uid = params['uid'] as String?;
final online = params['online'] as int?;
if (uid != null && online != null) {
// Update user online status
print('User $uid status update: ${online == 1 ? 'online' : 'offline'}');
}
}
}
// Handle channel update command
static void _handleChannelUpdate(WKCMD cmdMsg) {
print('Handle channel update command');
// Handle channel info changes
}
// Handle member update command
static void _handleMemberUpdate(WKCMD cmdMsg) {
print('Handle member update command');
// Handle member info changes
}
// Dispose
static void dispose() {
removeAllListeners();
_cmdController?.close();
_cmdController = null;
}
}
// Command message event
class CMDMessageEvent {
final WKCMD cmd;
final DateTime timestamp;
CMDMessageEvent({
required this.cmd,
required this.timestamp,
});
@override
String toString() {
return 'CMDMessageEvent{cmd: ${cmd.cmd}, timestamp: $timestamp}';
}
}
```
## Common Command Types
### System Sync Commands
These commands are used to sync various data states:
```dart theme={null}
class SystemSyncCommands {
// Sync conversation list
static const String SYNC_CONVERSATION = 'syncConversation';
// Sync channel info
static const String SYNC_CHANNEL_INFO = 'syncChannelInfo';
// Sync channel member
static const String SYNC_CHANNEL_MEMBER = 'syncChannelMember';
// Sync message extra
static const String SYNC_MESSAGE_EXTRA = 'syncMessageExtra';
// Sync message reaction
static const String SYNC_MESSAGE_REACTION = 'syncMessageReaction';
// Handle system sync commands
static void handleSyncCommand(WKCMD cmdMsg) {
switch (cmdMsg.cmd) {
case SYNC_CONVERSATION:
_syncConversations(cmdMsg.param);
break;
case SYNC_CHANNEL_INFO:
_syncChannelInfo(cmdMsg.param);
break;
case SYNC_CHANNEL_MEMBER:
_syncChannelMember(cmdMsg.param);
break;
case SYNC_MESSAGE_EXTRA:
_syncMessageExtra(cmdMsg.param);
break;
case SYNC_MESSAGE_REACTION:
_syncMessageReaction(cmdMsg.param);
break;
}
}
static void _syncConversations(dynamic param) {
// Implement conversation sync logic
print('Sync conversation data');
}
static void _syncChannelInfo(dynamic param) {
// Implement channel info sync logic
print('Sync channel info');
}
static void _syncChannelMember(dynamic param) {
// Implement channel member sync logic
print('Sync channel member');
}
static void _syncMessageExtra(dynamic param) {
// Implement message extra sync logic
print('Sync message extra');
}
static void _syncMessageReaction(dynamic param) {
// Implement message reaction sync logic
print('Sync message reaction');
}
}
```
### Status Update Commands
These commands are used for real-time status updates:
```dart theme={null}
class StatusUpdateCommands {
// User online status update
static const String USER_STATUS_UPDATE = 'userStatusUpdate';
// Channel status update
static const String CHANNEL_UPDATE = 'channelUpdate';
// Member status update
static const String MEMBER_UPDATE = 'memberUpdate';
// Message status update
static const String MESSAGE_UPDATE = 'messageUpdate';
// Handle status update commands
static void handleStatusCommand(WKCMD cmdMsg) {
switch (cmdMsg.cmd) {
case USER_STATUS_UPDATE:
_handleUserStatusUpdate(cmdMsg.param);
break;
case CHANNEL_UPDATE:
_handleChannelUpdate(cmdMsg.param);
break;
case MEMBER_UPDATE:
_handleMemberUpdate(cmdMsg.param);
break;
case MESSAGE_UPDATE:
_handleMessageUpdate(cmdMsg.param);
break;
}
}
static void _handleUserStatusUpdate(dynamic param) {
if (param is Map) {
final uid = param['uid'] as String?;
final online = param['online'] as int?;
final lastSeen = param['last_seen'] as int?;
print('User status update: $uid, online: $online, last seen: $lastSeen');
// Update local user status
// Can notify UI update through events
}
}
static void _handleChannelUpdate(dynamic param) {
if (param is Map) {
final channelId = param['channel_id'] as String?;
final channelType = param['channel_type'] as int?;
print('Channel update: $channelId, type: $channelType');
// Refresh channel info
if (channelId != null && channelType != null) {
WKIM.shared.channelManager.fetchChannelInfo(channelId, channelType);
}
}
}
static void _handleMemberUpdate(dynamic param) {
if (param is Map) {
final channelId = param['channel_id'] as String?;
final memberUid = param['member_uid'] as String?;
print('Member update: channel $channelId, member $memberUid');
// Refresh member info
// Trigger member list update
}
}
static void _handleMessageUpdate(dynamic param) {
if (param is Map) {
final messageId = param['message_id'] as String?;
final action = param['action'] as String?;
print('Message update: $messageId, action: $action');
// Handle message updates (like recall, edit, etc.)
}
}
}
```
## Custom Command Processing
### Business Command Handler
```dart theme={null}
class BusinessCommandHandler {
// Register business command handlers
static void registerBusinessCommands() {
CMDMessageManager.addCmdListener('business', (WKCMD cmdMsg) {
_handleBusinessCommand(cmdMsg);
});
}
// Handle business commands
static void _handleBusinessCommand(WKCMD cmdMsg) {
switch (cmdMsg.cmd) {
case 'groupInvite':
_handleGroupInvite(cmdMsg.param);
break;
case 'friendRequest':
_handleFriendRequest(cmdMsg.param);
break;
case 'systemNotice':
_handleSystemNotice(cmdMsg.param);
break;
case 'customAction':
_handleCustomAction(cmdMsg.param);
break;
default:
print('Unknown business command: ${cmdMsg.cmd}');
break;
}
}
// Handle group invite
static void _handleGroupInvite(dynamic param) {
if (param is Map) {
final groupId = param['group_id'] as String?;
final inviterUid = param['inviter_uid'] as String?;
final inviterName = param['inviter_name'] as String?;
print('Received group invite: group $groupId, inviter $inviterName');
// Show invite notification
_showInviteNotification(groupId, inviterName);
}
}
// Handle friend request
static void _handleFriendRequest(dynamic param) {
if (param is Map) {
final fromUid = param['from_uid'] as String?;
final fromName = param['from_name'] as String?;
final message = param['message'] as String?;
print('Received friend request: from $fromName, message: $message');
// Show friend request notification
_showFriendRequestNotification(fromUid, fromName, message);
}
}
// Handle system notice
static void _handleSystemNotice(dynamic param) {
if (param is Map) {
final title = param['title'] as String?;
final content = param['content'] as String?;
final type = param['type'] as String?;
print('Received system notice: $title - $content');
// Show system notification
_showSystemNotification(title, content, type);
}
}
// Handle custom action
static void _handleCustomAction(dynamic param) {
if (param is Map) {
final action = param['action'] as String?;
final data = param['data'];
print('Received custom action: $action');
// Process custom business logic
_processCustomAction(action, data);
}
}
// Show invite notification
static void _showInviteNotification(String? groupId, String? inviterName) {
// Implement invite notification UI
}
// Show friend request notification
static void _showFriendRequestNotification(String? fromUid, String? fromName, String? message) {
// Implement friend request notification UI
}
// Show system notification
static void _showSystemNotification(String? title, String? content, String? type) {
// Implement system notification UI
}
// Process custom action
static void _processCustomAction(String? action, dynamic data) {
// Implement custom business logic
}
}
```
## Data Structure Description
### WKCMD Command Object
```dart theme={null}
class WKCMD {
String cmd = ''; // Command ID
dynamic param; // Corresponding command parameters
}
```
### Field Description
| Field | Type | Description |
| ------- | ------- | ------------------------------------------------------------------- |
| `cmd` | String | Command identifier, used to distinguish different types of commands |
| `param` | dynamic | Command parameters, can be any type of data |
### Common Command Parameter Formats
#### Sync Command Parameters
```dart theme={null}
// Sync conversation command parameters
{
"version": 123456,
"last_msg_seqs": "1,2,3,4,5",
"msg_count": 20
}
// Sync channel info command parameters
{
"channel_id": "channel123",
"channel_type": 2,
"version": 123456
}
```
#### Status Update Command Parameters
```dart theme={null}
// User status update parameters
{
"uid": "user123",
"online": 1,
"last_seen": 1640995200,
"device_flag": 1
}
// Channel update parameters
{
"channel_id": "channel123",
"channel_type": 2,
"action": "update",
"fields": ["name", "avatar"]
}
```
## Flutter Widget Integration Example
```dart theme={null}
class CMDMessageHandler extends StatefulWidget {
final Widget child;
const CMDMessageHandler({Key? key, required this.child}) : super(key: key);
@override
_CMDMessageHandlerState createState() => _CMDMessageHandlerState();
}
class _CMDMessageHandlerState extends State {
StreamSubscription? _subscription;
@override
void initState() {
super.initState();
_setupCMDListener();
}
@override
void dispose() {
_subscription?.cancel();
CMDMessageManager.removeCmdListener('app');
super.dispose();
}
void _setupCMDListener() {
// Add global command listener
CMDMessageManager.addCmdListener('app', (WKCMD cmdMsg) {
_handleAppLevelCommand(cmdMsg);
});
// Listen to command message stream
_subscription = CMDMessageManager.cmdMessageStream.listen((event) {
_processCommandEvent(event);
});
}
void _handleAppLevelCommand(WKCMD cmdMsg) {
// Handle app-level commands
switch (cmdMsg.cmd) {
case 'appUpdate':
_handleAppUpdate(cmdMsg.param);
break;
case 'maintenance':
_handleMaintenance(cmdMsg.param);
break;
case 'forceLogout':
_handleForceLogout(cmdMsg.param);
break;
}
}
void _processCommandEvent(CMDMessageEvent event) {
// Process command events
print('Processing command event: ${event.cmd.cmd}');
}
void _handleAppUpdate(dynamic param) {
// Handle app update command
if (param is Map) {
final version = param['version'] as String?;
final url = param['url'] as String?;
final force = param['force'] as bool? ?? false;
_showUpdateDialog(version, url, force);
}
}
void _handleMaintenance(dynamic param) {
// Handle maintenance notice command
if (param is Map) {
final message = param['message'] as String?;
final startTime = param['start_time'] as int?;
final endTime = param['end_time'] as int?;
_showMaintenanceNotice(message, startTime, endTime);
}
}
void _handleForceLogout(dynamic param) {
// Handle force logout command
if (param is Map) {
final reason = param['reason'] as String?;
_performForceLogout(reason);
}
}
void _showUpdateDialog(String? version, String? url, bool force) {
// Show update dialog
}
void _showMaintenanceNotice(String? message, int? startTime, int? endTime) {
// Show maintenance notice
}
void _performForceLogout(String? reason) {
// Perform force logout
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
```
## Next Steps
Learn about @mentions and custom reminder functionality
Explore custom messages and extension features
Return to channel member management functionality
Configure data sources and sync logic
# Conversation Management
Source: https://wukong.mintlify.app/en/sdk/wukongim/flutter/conversation
WuKongIM Flutter SDK conversation management functionality, including recent conversation retrieval, listening and operations
## Get Recent Conversations
### All Recent Conversations
```dart theme={null}
// Query all recent conversations
WKIM.shared.conversationManager.getAll();
```
## New Message Listening
Only when opening the app for the first time, you need to sync the recent conversation list. Subsequent changes to the recent conversation list are obtained through listening.
```dart theme={null}
// Listen for refresh messages
WKIM.shared.conversationManager.addOnRefreshMsgListener('key', (wkUIConversationMsg, isEnd) {
// wkUIConversationMsg: recent conversation message content
// If UI already has this conversation, update it; otherwise add to UI
// isEnd: to prevent frequent UI refreshes, refresh UI only when isEnd is true
if (isEnd) {
// Update UI
_updateConversationUI(wkUIConversationMsg);
}
});
// Remove listener
WKIM.shared.conversationManager.removeOnRefreshMsg('key');
```
## Delete Recent Conversations
```dart theme={null}
// Delete recent conversation
WKIM.shared.conversationManager.deleteMsg(channelId, channelType);
```
## Common Methods
```dart theme={null}
// Set red dot
WKIM.shared.conversationManager.updateRedDot(channelId, channelType, count);
// Delete all recent conversations
WKIM.shared.conversationManager.clearAll();
```
## Complete Conversation Management Example
```dart theme={null}
class ConversationManager {
static final ConversationManager _instance = ConversationManager._internal();
factory ConversationManager() => _instance;
ConversationManager._internal();
final List _conversations = [];
final StreamController> _conversationsController = StreamController.broadcast();
final StreamController _conversationUpdateController = StreamController.broadcast();
// Streams for UI to listen
Stream> get conversationsStream => _conversationsController.stream;
Stream get conversationUpdateStream => _conversationUpdateController.stream;
void initialize() {
_setupConversationListeners();
_loadInitialConversations();
}
void _setupConversationListeners() {
// Listen for conversation refresh
WKIM.shared.conversationManager.addOnRefreshMsgListener('global', (conversation, isEnd) {
if (isEnd) {
_handleConversationUpdate(conversation);
}
});
// Listen for conversation deletion
WKIM.shared.conversationManager.addOnDeleteMsgListener('global', (channelID, channelType) {
_handleConversationDeleted(channelID, channelType);
});
}
void _loadInitialConversations() {
try {
final conversations = WKIM.shared.conversationManager.getAll();
_conversations.clear();
_conversations.addAll(conversations);
// Sort by timestamp
_conversations.sort((a, b) => b.lastMsgTimestamp.compareTo(a.lastMsgTimestamp));
_conversationsController.add(List.from(_conversations));
} catch (e) {
print('Failed to load conversations: $e');
}
}
void _handleConversationUpdate(WKConversationMsg conversation) {
final existingIndex = _conversations.indexWhere(
(c) => c.channelID == conversation.channelID && c.channelType == conversation.channelType,
);
if (existingIndex >= 0) {
// Update existing conversation
_conversations[existingIndex] = conversation;
} else {
// Add new conversation
_conversations.add(conversation);
}
// Sort by timestamp
_conversations.sort((a, b) => b.lastMsgTimestamp.compareTo(a.lastMsgTimestamp));
// Notify listeners
_conversationsController.add(List.from(_conversations));
_conversationUpdateController.add(conversation);
}
void _handleConversationDeleted(String channelID, int channelType) {
_conversations.removeWhere(
(c) => c.channelID == channelID && c.channelType == channelType,
);
_conversationsController.add(List.from(_conversations));
}
// Public methods
List getAllConversations() {
return List.from(_conversations);
}
WKConversationMsg? getConversation(String channelID, int channelType) {
try {
return _conversations.firstWhere(
(c) => c.channelID == channelID && c.channelType == channelType,
);
} catch (e) {
return null;
}
}
Future deleteConversation(String channelID, int channelType) async {
try {
await WKIM.shared.conversationManager.deleteMsg(channelID, channelType);
} catch (e) {
print('Failed to delete conversation: $e');
rethrow;
}
}
Future updateRedDot(String channelID, int channelType, int count) async {
try {
await WKIM.shared.conversationManager.updateRedDot(channelID, channelType, count);
} catch (e) {
print('Failed to update red dot: $e');
rethrow;
}
}
Future clearAllConversations() async {
try {
await WKIM.shared.conversationManager.clearAll();
_conversations.clear();
_conversationsController.add([]);
} catch (e) {
print('Failed to clear conversations: $e');
rethrow;
}
}
// Get total unread count
int getTotalUnreadCount() {
return _conversations.fold(0, (sum, conversation) => sum + conversation.unreadCount);
}
// Get conversations by type
List getConversationsByType(int channelType) {
return _conversations.where((c) => c.channelType == channelType).toList();
}
// Search conversations
List searchConversations(String keyword) {
if (keyword.isEmpty) return getAllConversations();
return _conversations.where((conversation) {
// You can implement search logic based on channel name, last message content, etc.
// This would require getting channel info for each conversation
return conversation.channelID.toLowerCase().contains(keyword.toLowerCase());
}).toList();
}
void dispose() {
_conversationsController.close();
_conversationUpdateController.close();
// Remove listeners
WKIM.shared.conversationManager.removeOnRefreshMsg('global');
WKIM.shared.conversationManager.removeOnDeleteMsgListener('global');
}
}
```
## UI Integration Example
```dart theme={null}
class ConversationListPage extends StatefulWidget {
@override
_ConversationListPageState createState() => _ConversationListPageState();
}
class _ConversationListPageState extends State {
late StreamSubscription> _conversationsSubscription;
List _conversations = [];
@override
void initState() {
super.initState();
_setupListeners();
_loadConversations();
}
void _setupListeners() {
_conversationsSubscription = ConversationManager().conversationsStream.listen((conversations) {
setState(() {
_conversations = conversations;
});
});
}
void _loadConversations() {
_conversations = ConversationManager().getAllConversations();
}
@override
void dispose() {
_conversationsSubscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Conversations'),
actions: [
_buildUnreadBadge(),
],
),
body: _buildConversationList(),
);
}
Widget _buildUnreadBadge() {
final totalUnread = ConversationManager().getTotalUnreadCount();
if (totalUnread == 0) return SizedBox.shrink();
return Container(
margin: EdgeInsets.only(right: 16),
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(12),
),
child: Text(
totalUnread > 99 ? '99+' : totalUnread.toString(),
style: TextStyle(color: Colors.white, fontSize: 12),
),
);
}
Widget _buildConversationList() {
if (_conversations.isEmpty) {
return Center(
child: Text('No conversations'),
);
}
return ListView.builder(
itemCount: _conversations.length,
itemBuilder: (context, index) {
final conversation = _conversations[index];
return _buildConversationItem(conversation);
},
);
}
Widget _buildConversationItem(WKConversationMsg conversation) {
return ListTile(
leading: CircleAvatar(
child: Text(conversation.channelID.substring(0, 1).toUpperCase()),
),
title: Text(conversation.channelID),
subtitle: Text(_getLastMessageText(conversation)),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_formatTimestamp(conversation.lastMsgTimestamp)),
if (conversation.unreadCount > 0)
Container(
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(10),
),
child: Text(
conversation.unreadCount > 99 ? '99+' : conversation.unreadCount.toString(),
style: TextStyle(color: Colors.white, fontSize: 10),
),
),
],
),
onTap: () => _openChat(conversation),
onLongPress: () => _showConversationOptions(conversation),
);
}
String _getLastMessageText(WKConversationMsg conversation) {
// You would need to get the actual last message content
// This is a simplified version
return 'Last message...';
}
String _formatTimestamp(int timestamp) {
final date = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
final now = DateTime.now();
if (date.day == now.day && date.month == now.month && date.year == now.year) {
return DateFormat('HH:mm').format(date);
} else {
return DateFormat('MM/dd').format(date);
}
}
void _openChat(WKConversationMsg conversation) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatPage(
channelID: conversation.channelID,
channelType: conversation.channelType,
),
),
);
}
void _showConversationOptions(WKConversationMsg conversation) {
showModalBottomSheet(
context: context,
builder: (context) => Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(Icons.delete),
title: Text('Delete Conversation'),
onTap: () {
Navigator.pop(context);
_deleteConversation(conversation);
},
),
ListTile(
leading: Icon(Icons.notifications_off),
title: Text('Mute'),
onTap: () {
Navigator.pop(context);
// Implement mute functionality
},
),
],
),
);
}
void _deleteConversation(WKConversationMsg conversation) async {
try {
await ConversationManager().deleteConversation(
conversation.channelID,
conversation.channelType,
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Conversation deleted')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to delete conversation')),
);
}
}
}
```
## Data Structure Description
```dart theme={null}
class WKConversationMsg {
// Channel ID
String channelID = '';
// Channel type
int channelType = WKChannelType.personal;
// Last message local ID
String lastClientMsgNO = '';
// Whether deleted
int isDeleted = 0;
// Server sync version number
int version = 0;
// Last message timestamp
int lastMsgTimestamp = 0;
// Unread message count
int unreadCount = 0;
// Last message sequence number
int lastMsgSeq = 0;
// Extension fields
dynamic localExtraMap;
WKConversationMsgExtra? msgExtra;
String parentChannelID = '';
int parentChannelType = 0;
}
```
## Next Steps
Learn how to manage channels and groups
Configure conversation data sources
Return to message handling functionality
Explore advanced features and custom messages
# Data Source Configuration
Source: https://wukong.mintlify.app/en/sdk/wukongim/flutter/datasource
WuKongIM Flutter SDK data source configuration, including file upload/download, conversation sync, channel information and message sync
Data source configuration is one of the core functions of WuKongIM Flutter SDK, responsible for handling key business logic such as file upload/download, conversation sync, channel information retrieval, and message sync.
## File Management
When sending custom attachment messages, the message sent to the recipient is a network address, not the actual file. In this case, we need to listen for attachment uploads.
### Listen for Attachment Upload
```dart theme={null}
// Listen for message attachment upload
WKIM.shared.messageManager.addOnUploadAttachmentListener((wkMsg, back) {
if (wkMsg.contentType == WkMessageContentType.image) {
// TODO upload attachment
WKImageContent imageContent = wkMsg.messageContent! as WKImageContent;
imageContent.url = 'xxxxxx';
wkMsg.messageContent = imageContent;
back(wkMsg);
}
if (wkMsg.contentType == WkMessageContentType.voice) {
// TODO upload voice
WKVoiceContent voiceContent = wkMsg.messageContent! as WKVoiceContent;
voiceContent.url = 'xxxxxx';
wkMsg.messageContent = voiceContent;
back(wkMsg);
} else if (wkMsg.contentType == WkMessageContentType.video) {
WKVideoContent videoContent = wkMsg.messageContent! as WKVideoContent;
// TODO upload cover and video
videoContent.cover = 'xxxxxx';
videoContent.url = 'ssssss';
wkMsg.messageContent = videoContent;
back(wkMsg);
}
});
```
### Complete File Upload Management Example
```dart theme={null}
class FileUploadManager {
static void setupUploadListener() {
WKIM.shared.messageManager.addOnUploadAttachmentListener((wkMsg, back) {
_handleFileUpload(wkMsg, back);
});
}
static void _handleFileUpload(WKMsg wkMsg, Function(WKMsg) back) async {
try {
switch (wkMsg.contentType) {
case WkMessageContentType.image:
await _uploadImage(wkMsg, back);
break;
case WkMessageContentType.voice:
await _uploadVoice(wkMsg, back);
break;
case WkMessageContentType.video:
await _uploadVideo(wkMsg, back);
break;
case WkMessageContentType.file:
await _uploadFile(wkMsg, back);
break;
default:
// For custom attachment messages
await _uploadCustomAttachment(wkMsg, back);
break;
}
} catch (error) {
print('File upload failed: $error');
// Can call back with original message to indicate failure
back(wkMsg);
}
}
static Future _uploadImage(WKMsg wkMsg, Function(WKMsg) back) async {
final imageContent = wkMsg.messageContent! as WKImageContent;
if (imageContent.localPath.isNotEmpty) {
// Compress image if needed
final compressedPath = await _compressImage(imageContent.localPath);
// Upload to server
final uploadResult = await _uploadToServer(compressedPath, 'image');
// Update message content
imageContent.url = uploadResult['url'];
imageContent.size = uploadResult['size'];
wkMsg.messageContent = imageContent;
print('Image upload successful: ${imageContent.url}');
}
back(wkMsg);
}
static Future _uploadVoice(WKMsg wkMsg, Function(WKMsg) back) async {
final voiceContent = wkMsg.messageContent! as WKVoiceContent;
if (voiceContent.localPath.isNotEmpty) {
// Upload voice file
final uploadResult = await _uploadToServer(voiceContent.localPath, 'voice');
// Update message content
voiceContent.url = uploadResult['url'];
voiceContent.size = uploadResult['size'];
wkMsg.messageContent = voiceContent;
print('Voice upload successful: ${voiceContent.url}');
}
back(wkMsg);
}
static Future _uploadVideo(WKMsg wkMsg, Function(WKMsg) back) async {
final videoContent = wkMsg.messageContent! as WKVideoContent;
if (videoContent.localPath.isNotEmpty) {
// Generate video thumbnail
final thumbnailPath = await _generateVideoThumbnail(videoContent.localPath);
// Upload thumbnail
final thumbnailResult = await _uploadToServer(thumbnailPath, 'image');
videoContent.cover = thumbnailResult['url'];
// Upload video
final videoResult = await _uploadToServer(videoContent.localPath, 'video');
videoContent.url = videoResult['url'];
videoContent.size = videoResult['size'];
wkMsg.messageContent = videoContent;
print('Video upload successful: ${videoContent.url}');
}
back(wkMsg);
}
static Future _uploadFile(WKMsg wkMsg, Function(WKMsg) back) async {
final fileContent = wkMsg.messageContent! as WKFileContent;
if (fileContent.localPath.isNotEmpty) {
// Upload file
final uploadResult = await _uploadToServer(fileContent.localPath, 'file');
// Update message content
fileContent.url = uploadResult['url'];
fileContent.size = uploadResult['size'];
wkMsg.messageContent = fileContent;
print('File upload successful: ${fileContent.url}');
}
back(wkMsg);
}
static Future _uploadCustomAttachment(WKMsg wkMsg, Function(WKMsg) back) async {
// Handle custom attachment messages
print('Uploading custom attachment for message type: ${wkMsg.contentType}');
// Example: Location message with image
if (wkMsg.contentType == 17) { // Assuming 17 is location message type
final locationContent = wkMsg.messageContent as LocationMessageContent;
if (locationContent.localPath.isNotEmpty) {
final uploadResult = await _uploadToServer(locationContent.localPath, 'image');
locationContent.url = uploadResult['url'];
wkMsg.messageContent = locationContent;
}
}
back(wkMsg);
}
// Helper methods
static Future _compressImage(String imagePath) async {
// Implement image compression logic
// Can use image compression packages
return imagePath; // Return compressed path
}
static Future _generateVideoThumbnail(String videoPath) async {
// Implement video thumbnail generation
// Can use video thumbnail packages
return 'thumbnail_path';
}
static Future