mirror of
https://github.com/WuKongIM/WuKongIMDocs
synced 2025-06-06 09:38:20 +00:00
docs: 重构jssdk的文档
This commit is contained in:
parent
6900dee40e
commit
9ea2b37784
@ -10,7 +10,7 @@ export default defineConfig({
|
|||||||
title: "悟空IM",
|
title: "悟空IM",
|
||||||
description: "IM",
|
description: "IM",
|
||||||
lang: "zh-CN",
|
lang: "zh-CN",
|
||||||
ignoreDeadLinks: true,
|
ignoreDeadLinks: false,
|
||||||
head: [
|
head: [
|
||||||
["meta", { name: "keywords", content: "IM" }],
|
["meta", { name: "keywords", content: "IM" }],
|
||||||
["link", { rel: "icon", type: "image/x-icon", href: "/favicon.ico" }],
|
["link", { rel: "icon", type: "image/x-icon", href: "/favicon.ico" }],
|
||||||
|
@ -17,7 +17,8 @@ export const navbar: DefaultTheme.NavItem[] = [
|
|||||||
items: [
|
items: [
|
||||||
{ text: "IOS", link: "/sdk/ios" },
|
{ text: "IOS", link: "/sdk/ios" },
|
||||||
{ text: "Android", link: "/sdk/android" },
|
{ text: "Android", link: "/sdk/android" },
|
||||||
{ text: "Javascript", link: "/sdk/javascript" },
|
{ text: "Javascript", link: "/sdk/jssdk/intro" },
|
||||||
|
{ text: "Flutter", link: "/sdk/flutter" },
|
||||||
{ text: "Uniapp", link: "/sdk/uniapp" },
|
{ text: "Uniapp", link: "/sdk/uniapp" },
|
||||||
{ text: "微信小程序", link: "/sdk/smallprogram" },
|
{ text: "微信小程序", link: "/sdk/smallprogram" },
|
||||||
],
|
],
|
||||||
|
@ -53,12 +53,26 @@ export const sidebar: DefaultTheme.Sidebar = {
|
|||||||
{ text: "最近会话", link: "/api/conversation" },
|
{ text: "最近会话", link: "/api/conversation" },
|
||||||
{ text: "Webhook", link: "/api/webhook" },
|
{ text: "Webhook", link: "/api/webhook" },
|
||||||
{ text: "Datasource", link: "/api/datasource" },
|
{ text: "Datasource", link: "/api/datasource" },
|
||||||
|
{ text: "API调用时机说明", link: "/api/instructions" },
|
||||||
],
|
],
|
||||||
"/sdk": [
|
"/sdk": [
|
||||||
{ text: "概述", link: "/sdk/" },
|
{ text: "概述", link: "/sdk/" },
|
||||||
{ text: "IOS", link: "/sdk/ios" },
|
{ text: "IOS", link: "/sdk/ios" },
|
||||||
{ text: "Android", link: "/sdk/android" },
|
{ text: "Android", link: "/sdk/android" },
|
||||||
{ text: "Javascript", link: "/sdk/javascript" },
|
{
|
||||||
|
text: "Javascript",
|
||||||
|
collapsed: true,
|
||||||
|
items: [
|
||||||
|
{text:"说明",link:"/sdk/jssdk/intro"},
|
||||||
|
{text:"集成",link:"/sdk/jssdk/integration"},
|
||||||
|
{text:"基础",link:"/sdk/jssdk/base"},
|
||||||
|
{text:"聊天管理",link:"/sdk/jssdk/chat"},
|
||||||
|
{text:"最近会话管理",link:"/sdk/jssdk/conversation"},
|
||||||
|
{text:"频道管理",link:"/sdk/jssdk/channel"},
|
||||||
|
{text:"数据源管理",link:"/sdk/jssdk/datasource"},
|
||||||
|
{text:"高级功能",link:"/sdk/jssdk/advance"},
|
||||||
|
]
|
||||||
|
},
|
||||||
{ text: "Uniapp", link: "/sdk/uniapp" },
|
{ text: "Uniapp", link: "/sdk/uniapp" },
|
||||||
{ text: "Flutter", link: "/sdk/flutter" },
|
{ text: "Flutter", link: "/sdk/flutter" },
|
||||||
{ text: "c", link: "/sdk/c" },
|
{ text: "c", link: "/sdk/c" },
|
||||||
|
@ -33,7 +33,7 @@ http status为200
|
|||||||
|
|
||||||
## 删除频道
|
## 删除频道
|
||||||
|
|
||||||
删除一个频道(注意:如果配置了[datasource](/guide/datasource)记得不要返回删除了频道的数据,要不然重启又会恢复回来)
|
删除一个频道(注意:如果配置了[datasource](/api/datasource)记得不要返回删除了频道的数据,要不然重启又会恢复回来)
|
||||||
|
|
||||||
> /channel/delete
|
> /channel/delete
|
||||||
|
|
||||||
|
43
src/api/instructions.md
Normal file
43
src/api/instructions.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
# API调用时机说明
|
||||||
|
|
||||||
|
所有悟空IM的API应该都由业务后端调用,业务后端再将结果返回给app,不要将悟空IM的API暴露给app。
|
||||||
|
|
||||||
|
## app用户注册/登录时
|
||||||
|
|
||||||
|
业务后端需要调用悟空IM的接口,将用户注册到悟空IM。
|
||||||
|
|
||||||
|
|
||||||
|
[注册或登录](/api/user.html#注册或登录)
|
||||||
|
|
||||||
|
## app启动时
|
||||||
|
|
||||||
|
1. [获取长连接地址](/api/user.html)
|
||||||
|
|
||||||
|
2. [同步离线的最近会话](/api/conversation.html#同步最近会话)
|
||||||
|
|
||||||
|
3. [同步离线的命令消息](/api/message.html#同步离线命令消息)
|
||||||
|
|
||||||
|
4. [回执离线命令消息](/api/message.html#回执离线命令消息) (表示命令app已经成功收到,服务端可以移除了)
|
||||||
|
|
||||||
|
## app使用时
|
||||||
|
|
||||||
|
1. [查看某个群/个人的历史消息](/api/message.html#获取某频道消息)
|
||||||
|
|
||||||
|
2. [清空某个群/个人的红点数量](/api/conversation.html#设置最近会话红点数量)
|
||||||
|
|
||||||
|
3. [创建群或更新群](/api/channel.html#创建或更新频道)
|
||||||
|
|
||||||
|
4. [添加群成员](/api/channel.html#添加订阅者)
|
||||||
|
|
||||||
|
5. [禁止某个群成员发消息/拉黑好友](/api/channel.html#添加黑名单)
|
||||||
|
|
||||||
|
6. [全员禁言管理员除外](/api/channel.html#设置白名单)(将管理员拉入白名单,未在白名单的都不能发言)
|
||||||
|
|
||||||
|
7. [删除群/个人的最近会话](/api/conversation.html#删除最近会话)
|
||||||
|
|
||||||
|
8. [删除群](/api/channel.html#删除频道)(删除群后,消息并不会删除,只是群内不能发消息了,客户端可以将此群的最近会话删除了,本地数据也清空了,理论上此群就被删除了)
|
||||||
|
|
||||||
|
9. 等等
|
||||||
|
|
||||||
|
`(以上是以IM的场景为例并只列举了主要的一些API)`
|
@ -27,7 +27,7 @@ title: 介绍
|
|||||||
| WuKongIMiOSSDK | [Github](https://github.com/WuKongIM/WuKongIMiOSSDK) | [Gitee](https://gitee.com/WuKongDev/WuKongIMiOSSDK) | [文档](/sdk/ios) | 悟空IM的iOS SDK |
|
| WuKongIMiOSSDK | [Github](https://github.com/WuKongIM/WuKongIMiOSSDK) | [Gitee](https://gitee.com/WuKongDev/WuKongIMiOSSDK) | [文档](/sdk/ios) | 悟空IM的iOS SDK |
|
||||||
| WuKongIMUniappSDK | [Github](https://github.com/WuKongIM/WuKongIMUniappSDK) | [Gitee](https://gitee.com/WuKongDev/WuKongIMUniappSDK) | [文档](/sdk/uniapp) | 悟空IM的 Uniapp SDK |
|
| WuKongIMUniappSDK | [Github](https://github.com/WuKongIM/WuKongIMUniappSDK) | [Gitee](https://gitee.com/WuKongDev/WuKongIMUniappSDK) | [文档](/sdk/uniapp) | 悟空IM的 Uniapp SDK |
|
||||||
| WuKongIMJSSDK | [Github](https://github.com/WuKongIM/WuKongIMJSSDK) | [Gitee](https://gitee.com/WuKongDev/WuKongIMJSSDK) | [文档](/sdk/javascript) | 悟空IM的 JS SDK |
|
| WuKongIMJSSDK | [Github](https://github.com/WuKongIM/WuKongIMJSSDK) | [Gitee](https://gitee.com/WuKongDev/WuKongIMJSSDK) | [文档](/sdk/javascript) | 悟空IM的 JS SDK |
|
||||||
| WuKongIMFlutterSDK | [Github](https://github.com/WuKongIM/WuKongIMFlutterSDK) | [Gitee](https://gitee.com/WuKongDev/WuKongIMFlutterSDK) | 编写中 | 悟空IM的 Flutter SDK |
|
| WuKongIMFlutterSDK | [Github](https://github.com/WuKongIM/WuKongIMFlutterSDK) | [Gitee](https://gitee.com/WuKongDev/WuKongIMFlutterSDK) |[文档](/sdk/flutter) | 悟空IM的 Flutter SDK |
|
||||||
| WuKongIMReactNativeDemo | [Github](https://github.com/wengqianshan/WuKongIMReactNative) | 无 | Apache2.0 | 悟空IM的 React Native Demo(由贡献者 [wengqianshan](https://github.com/wengqianshan) 提供) |
|
| WuKongIMReactNativeDemo | [Github](https://github.com/wengqianshan/WuKongIMReactNative) | 无 | Apache2.0 | 悟空IM的 React Native Demo(由贡献者 [wengqianshan](https://github.com/wengqianshan) 提供) |
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ const WKSDK = require("wukongimjssdk").default
|
|||||||
|
|
||||||
``` js [浏览器]
|
``` js [浏览器]
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/wukongimjssdk@1.1.0/lib/wukongimjssdk.umd.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/wukongimjssdk@1.1.3/lib/wukongimjssdk.umd.js"></script>
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -435,6 +435,7 @@ WKSDK.shared().config.provider.messageUploadTaskCallback = (
|
|||||||
return new MediaMessageUploadTask(message);
|
return new MediaMessageUploadTask(message);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### 自定义普通消息
|
### 自定义普通消息
|
||||||
|
|
||||||
我们以自定义一个 gif 消息为例。
|
我们以自定义一个 gif 消息为例。
|
||||||
|
105
src/sdk/jssdk/advance.md
Normal file
105
src/sdk/jssdk/advance.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# 高级功能
|
||||||
|
|
||||||
|
|
||||||
|
## 自定义消息
|
||||||
|
|
||||||
|
### 自定义普通消息
|
||||||
|
|
||||||
|
我们以自定义一个 gif 消息为例。
|
||||||
|
#### 第一步继承 MessageContent 和定义 gif 消息的正文结构
|
||||||
|
|
||||||
|
```ts
|
||||||
|
class GifContent extends MessageContent {
|
||||||
|
width!: number; // gif宽度
|
||||||
|
height!: number; // gif高度
|
||||||
|
url!: string; // gif远程下载地址
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 第二步 编码解码
|
||||||
|
|
||||||
|
```ts
|
||||||
|
|
||||||
|
// 最终传递的消息内容为 {"type":101,"url":"xxxx","width":xxx,"height":xxx}
|
||||||
|
|
||||||
|
class GifContent extends MessageContent {
|
||||||
|
width!: number // gif宽度
|
||||||
|
height!: number // gif高度
|
||||||
|
url!: string // gif远程下载地址
|
||||||
|
|
||||||
|
// 解码
|
||||||
|
decodeJSON(content: any) {
|
||||||
|
this.width = content["width"] || 0
|
||||||
|
this.height = content["height"] || 0
|
||||||
|
this.url = content["url"]
|
||||||
|
}
|
||||||
|
// 编码
|
||||||
|
encodeJSON() {
|
||||||
|
return { "width": this.width, "height": this.height, "url": this.url }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
#### 第三步 注册
|
||||||
|
|
||||||
|
```ts
|
||||||
|
|
||||||
|
const contentTypeGif = 101 // 自定义消息类型
|
||||||
|
WKSDK.shared().register(contentTypeGif, () => new GifContent()); // gif动图
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自定义附件消息
|
||||||
|
|
||||||
|
自定义附件消息的流程与普通消息差异不大,我们以图片消息为例
|
||||||
|
|
||||||
|
#### 第一步继承 MediaMessageContent
|
||||||
|
|
||||||
|
注意这里是继承 MediaMessageContent 不是 MessageContent,当发送附件消息的时候,sdk 会调用[上传任务](/sdk/jssdk/datasource.html#文件上传数据源),将本地的文件上传到服务器,然后再进行消息的编码和发送
|
||||||
|
|
||||||
|
最终传递的消息内容为 ```{"type":3,"url":"xxxx","width":xxx,"height":xxx}```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
|
||||||
|
class ImageContent : MediaMessageContent {
|
||||||
|
width!: number // 图片宽度
|
||||||
|
height!: number // 图片高度
|
||||||
|
url!: string // 图片远程下载地址
|
||||||
|
}
|
||||||
|
```
|
||||||
|
#### 第二步编码解码
|
||||||
|
|
||||||
|
```ts
|
||||||
|
|
||||||
|
class ImageContent : MediaMessageContent {
|
||||||
|
width!: number // 图片宽度
|
||||||
|
height!: number // 图片高度
|
||||||
|
url!: string // 图片远程下载地址
|
||||||
|
|
||||||
|
constructor(file?: File,width?:number,height?:number) {
|
||||||
|
super()
|
||||||
|
this.file = file // File为要上传的图片文件对象
|
||||||
|
this.width = width || 0
|
||||||
|
this.height = height || 0
|
||||||
|
}
|
||||||
|
// 附件file上传成功后会得到 this.remoteUrl这个远程下载地址,这时可以将此地址编码到消息内
|
||||||
|
encodeJSON() {
|
||||||
|
return { "width": this.width || 0, "height": this.height || 0, "url": this.remoteUrl || "" }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解码消息
|
||||||
|
decodeJSON(content: any) {
|
||||||
|
this.width = content["width"] || 0
|
||||||
|
this.height = content["height"] || 0
|
||||||
|
this.url = content["url"] || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 第三步 注册
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const contentTypeImage = 3 // 自定义消息类型
|
||||||
|
WKSDK.shared().register(contentTypeImage,() => new ImageContent());
|
||||||
|
```
|
60
src/sdk/jssdk/base.md
Normal file
60
src/sdk/jssdk/base.md
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
# 基础
|
||||||
|
|
||||||
|
## 配置
|
||||||
|
|
||||||
|
|
||||||
|
``` js
|
||||||
|
// 集群模式通过此方法获取连接地址
|
||||||
|
// WKSDK.shared().config.provider.connectAddrCallback = async (callback: ConnectAddrCallback) => {
|
||||||
|
// const addr = await xxxx // addr 格式为 ip:port
|
||||||
|
// callback(addr)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 单机模式可以直接设置地址
|
||||||
|
WKSDK.shared().config.addr = 'ws://IP:PORT'; // 默认端口为5200
|
||||||
|
// 认证信息
|
||||||
|
WKSDK.shared().config.uid = 'xxxx'; // 用户uid(需要在悟空通讯端注册过)
|
||||||
|
WKSDK.shared().config.token = 'xxxx'; // 用户token (需要在悟空通讯端注册过)
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
更多配置,查看:
|
||||||
|
|
||||||
|
`WKSDK.shared().config`;
|
||||||
|
|
||||||
|
## 连接
|
||||||
|
|
||||||
|
|
||||||
|
``` js
|
||||||
|
// 连接
|
||||||
|
WKSDK.shared().connectManager.connect();
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 断开
|
||||||
|
|
||||||
|
``` js
|
||||||
|
|
||||||
|
// 断开
|
||||||
|
WKSDK.shared().connectManager.disconnect();
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 监听连接状态
|
||||||
|
|
||||||
|
``` js
|
||||||
|
|
||||||
|
// 连接状态监听
|
||||||
|
WKSDK.shared().connectManager.addConnectStatusListener(
|
||||||
|
(status: ConnectStatus, reasonCode?: number) => {
|
||||||
|
if (status === ConnectStatus.Connected) {
|
||||||
|
console.log('连接成功');
|
||||||
|
} else {
|
||||||
|
console.log('连接失败', reasonCode); // reasonCode: 2表示认证失败(uid或token错误)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
```
|
153
src/sdk/jssdk/channel.md
Normal file
153
src/sdk/jssdk/channel.md
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
# 频道管理
|
||||||
|
|
||||||
|
## 数据结构说明
|
||||||
|
|
||||||
|
频道资料的数据结构
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
class ChannelInfo {
|
||||||
|
/* tslint:disable-line */
|
||||||
|
channel!: Channel; // 频道
|
||||||
|
title!: string; // 频道标题
|
||||||
|
logo!: string; // 频道logo
|
||||||
|
mute!: boolean; // 是否免打扰
|
||||||
|
top!: boolean; // 是否置顶
|
||||||
|
orgData: any; // 第三方数据
|
||||||
|
online: boolean = false // 是否在线
|
||||||
|
lastOffline: number = 0 // 最后一次离线时间
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
订阅者的数据结构
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
class Subscriber {
|
||||||
|
/* tslint:disable-line */
|
||||||
|
uid!: string; // 订阅者uid
|
||||||
|
name!: string; // 订阅者名称
|
||||||
|
remark!: string; // 订阅者备注
|
||||||
|
avatar!: string; // 订阅者头像
|
||||||
|
role!: number; // 订阅者角色
|
||||||
|
channel!: Channel; // 频道
|
||||||
|
version!: number; // 数据版本
|
||||||
|
isDeleted!: boolean; // 是否已删除
|
||||||
|
status!: number; // 订阅者状态
|
||||||
|
orgData: any; // 第三方数据
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 频道资料
|
||||||
|
|
||||||
|
### 请求频道资料
|
||||||
|
|
||||||
|
`需要实现获取频道资料的数据源` [获取频道资料数据源](sdk/jssdk/datasource.html#获取频道资料数据源)
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 强制从服务器获取频道资料并放入缓存
|
||||||
|
WKSDK.shared().channelManager.fetchChannelInfo(channel)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取频道资料
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 从缓存获取频道资料
|
||||||
|
const channelInfo = WKSDK.shared().channelManager.getChannelInfo(channel)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 监听频道资料
|
||||||
|
|
||||||
|
` WKSDK.shared().channelManager.fetchChannelInfo(channel)方法会触发此监听`
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
const listen = (channelInfo: ChannelInfo) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
添加监听
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
WKSDK.shared().channelManager.addListener(listen)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
移出监听
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
WKSDK.shared().channelManager.removeListener(listen)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 订阅者(成员)
|
||||||
|
|
||||||
|
### 同步频道的订阅者列表
|
||||||
|
|
||||||
|
`需要实现同步频道订阅者数据源` [同步频道订阅者数据源](sdk/jssdk/datasource.html#同步频道订阅者数据源)
|
||||||
|
|
||||||
|
```js
|
||||||
|
WKSDK.shared().channelManager.syncSubscribes(channel)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 获取频道订阅者列表
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
const subscribers = WKSDK.shared().channelManager.getSubscribes(channel)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取我在频道内的订阅者身份
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
const subscriber = WKSDK.shared().channelManager.getSubscribeOfMe(channel)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 监听某个频道订阅者变化
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
const listen = (channel: Channel) => {
|
||||||
|
const subscribers = WKSDK.shared().channelManager.getSubscribes(channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
添加监听
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
WKSDK.shared().channelManager.addSubscriberChangeListener(listen)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
移出监听
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
WKSDK.shared().channelManager.removeSubscriberChangeListener(listen)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 流消息(适合ChatGPT)
|
||||||
|
|
||||||
|
### 订阅频道流消息
|
||||||
|
|
||||||
|
待补充
|
||||||
|
|
||||||
|
### 取消订阅频道流消息
|
||||||
|
|
||||||
|
待补充
|
248
src/sdk/jssdk/chat.md
Normal file
248
src/sdk/jssdk/chat.md
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
# 聊天管理
|
||||||
|
|
||||||
|
## 消息发送
|
||||||
|
|
||||||
|
|
||||||
|
### 说明
|
||||||
|
|
||||||
|
发送的方法说明
|
||||||
|
```js
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送消息
|
||||||
|
* @param content 消息内容
|
||||||
|
* @param channel 频道对象 个人频道,群频道
|
||||||
|
* @returns 完整消息对象
|
||||||
|
*/
|
||||||
|
WKSDK.shared().chatManager.send(content: MessageContent, channel: Channel)
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
举例说明
|
||||||
|
|
||||||
|
``` js
|
||||||
|
|
||||||
|
// 例如发送文本消息hello给用户u10001
|
||||||
|
const text = new MessageText("hello") // 文本消息
|
||||||
|
WKSDK.shared().chatManager.send(text,new Channel("u10001",ChannelTypePerson))
|
||||||
|
|
||||||
|
// 例如发送文本消息hello给群频道g10001
|
||||||
|
WKSDK.shared().chatManager.send(text,new Channel("g10001",ChannelTypeGroup))
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 发送文本消息
|
||||||
|
|
||||||
|
``` js
|
||||||
|
// 文本消息
|
||||||
|
const msgContent = new MessageText("hello")
|
||||||
|
|
||||||
|
// 发送
|
||||||
|
WKSDK.shared().chatManager.send(msgContent,channel)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 发送图片消息
|
||||||
|
|
||||||
|
自己上传图片
|
||||||
|
|
||||||
|
``` js
|
||||||
|
|
||||||
|
// 图片消息
|
||||||
|
const msgContent = new MessageImage()
|
||||||
|
msgContent.url = url // 图片的下载地址
|
||||||
|
msgContent.width = width // 图片宽度
|
||||||
|
msgContent.height = height // 图片高度
|
||||||
|
|
||||||
|
// 发送
|
||||||
|
WKSDK.shared().chatManager.send(msgContent,channel)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
sdk去上传文件
|
||||||
|
|
||||||
|
`需要实现上传文件的数据源` 参考:[上传文件数据源](/sdk/jssdk/datasource.html#文件上传数据源)
|
||||||
|
|
||||||
|
``` js
|
||||||
|
|
||||||
|
const msgContent = new MessageImage() // 文本消息
|
||||||
|
msgContent.file = file // 上传的图片文件对象, 例如 <input type="file" /> 获取到的文件对象
|
||||||
|
msgContent.width = width // 图片宽度
|
||||||
|
msgContent.height = height // 图片高度
|
||||||
|
|
||||||
|
// 发送,sdk会判断图片是否已经上传过,如果没有上传过,会调用上传任务,上传图片
|
||||||
|
WKSDK.shared().chatManager.send(msgContent,channel)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 发送自定义消息
|
||||||
|
|
||||||
|
参考自定义消息: [自定义消息](/sdk/jssdk/advance.html#自定义消息)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
|
||||||
|
const msgContent = new XXXX() // XXXX为自定义消息的正文
|
||||||
|
|
||||||
|
// 发送
|
||||||
|
WKSDK.shared().chatManager.send(msgContent,channel)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 消息监听
|
||||||
|
|
||||||
|
### 监听发送消息状态
|
||||||
|
|
||||||
|
``` js
|
||||||
|
|
||||||
|
const listen = (packet: SendackPacket) => {
|
||||||
|
console.log('消息clientSeq->', packet.clientSeq); // 消息客户端序号用来匹配对应的发送的消息
|
||||||
|
if (packet.reasonCode === 1) {
|
||||||
|
// 发送成功
|
||||||
|
} else {
|
||||||
|
// 发送失败
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
添加监听
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
// 消息发送状态监听
|
||||||
|
WKSDK.shared().chatManager.addMessageStatusListener(listen);
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
移出监听
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 消息发送状态监听
|
||||||
|
WKSDK.shared().chatManager.removeMessageStatusListener(listen)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 监听常规消息
|
||||||
|
|
||||||
|
|
||||||
|
``` js
|
||||||
|
|
||||||
|
const listen = (message: Message) => {
|
||||||
|
message.content // 消息内容
|
||||||
|
message.channel // 消息频道
|
||||||
|
message.fromUID // 消息发送者
|
||||||
|
....
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
添加监听
|
||||||
|
|
||||||
|
```js
|
||||||
|
WKSDK.shared().chatManager.addMessageListener(listen);
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
移出监听
|
||||||
|
|
||||||
|
```js
|
||||||
|
WKSDK.shared().chatManager.removeMessageListener(listen)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 监听cmd消息
|
||||||
|
|
||||||
|
``` js
|
||||||
|
|
||||||
|
const listen = (message: Message) => {
|
||||||
|
const cmdContent = message.content as CMDContent
|
||||||
|
const cmd = cmdContent.cmd // 指令名称
|
||||||
|
const param = cmdContent.param // 指令参数
|
||||||
|
....
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
添加监听
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
WKSDK.shared().chatManager.addCMDListener(listen)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
移出监听
|
||||||
|
|
||||||
|
```js
|
||||||
|
WKSDK.shared().chatManager.removeCMDListener(listen)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 历史消息
|
||||||
|
|
||||||
|
`需要实现同步频道消息数据源` 参考:[同步频道消息数据源](/sdk/jssdk/datasource.html#同步频道消息数据源)
|
||||||
|
|
||||||
|
获取某个频道的历史消息
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
const messages = await WKSDK.shared().chatManager.syncMessages(channel, opts)
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
opts 参数解释
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
{
|
||||||
|
startMessageSeq: number = 0 // 开始消息列号(结果包含startMessageSeq的消息)
|
||||||
|
endMessageSeq: number = 0 // 结束消息列号(结果不包含endMessageSeq的消息)0表示不限制
|
||||||
|
limit: number = 30 // 每次限制数量
|
||||||
|
pullMode: PullMode = PullMode.Down // 拉取模式 0:向下拉取 1:向上拉取
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
详细解释:
|
||||||
|
|
||||||
|
```
|
||||||
|
以startMessageSeq为基准 pullMode控制拉取方向,endMessageSeq和limit控制结束位置
|
||||||
|
|
||||||
|
------------------ 上拉 ------------------
|
||||||
|
|
||||||
|
pullMode为1 表示向上拉,逻辑如下:
|
||||||
|
|
||||||
|
消息以startMessageSeq为起点,加载大于或等于startMessageSeq的消息,加载到超过endMessageSeq(结果不包含endMessageSeq)或超过limit为止,如果endMessageSeq为0则以limit为准
|
||||||
|
|
||||||
|
例如:
|
||||||
|
startMessageSeq=100 endMessageSeq=200 limit=10 以limit为准,则返回的messageSeq为100-110的消息.
|
||||||
|
startMessageSeq=100 endMessageSeq=105 limit=10 以endMessageSeq为准,则返回的messageSeq为100-104的消息
|
||||||
|
startMessageSeq=100 endMessageSeq=0 limit=10 以limit为准,则返回的messageSeq为100-110的消息
|
||||||
|
|
||||||
|
------------------ 下拉 ------------------
|
||||||
|
|
||||||
|
pullMode为0 表示向下拉,逻辑如下:
|
||||||
|
|
||||||
|
消息以startMessageSeq为起点,加载小于或等于startMessageSeq的消息,加载到超过endMessageSeq(结果不包含endMessageSeq)或超过limit为止,如果endMessageSeq为0则以limit为准
|
||||||
|
|
||||||
|
例如:
|
||||||
|
startMessageSeq=100 endMessageSeq=50 limit=10 以limit为准,则返回的messageSeq为100-91的消息.
|
||||||
|
startMessageSeq=100 endMessageSeq=95 limit=10 以endMessageSeq为准,则返回的messageSeq为100-96的消息
|
||||||
|
startMessageSeq=100 endMessageSeq=0 limit=10 以limit为准,则返回的messageSeq为100-91的消息
|
||||||
|
|
||||||
|
|
||||||
|
如果startMessageSeq和endMessageSeq都为0,则不管pullMode为那种都加载最新的limit条消息。
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 离线消息
|
||||||
|
|
||||||
|
在悟空 **IM**中为了应付海量离线消息,采用了按需拉取的机制,比如 10 个会话一个会话 10 万条消息,**悟空 IM** 不会把100 万条消息都拉取到本地。 而是采用拉取这 10 个会话的信息和对应的最新 20 条消息,也就是实际只拉取了 200 条消息 相对 100 万条消息来说大大提高了离线拉取速度。用户点进对应的会话才会去按需拉取这个会话的消息。 这些机制 SDK 内部都已做好了封装,使用者其实不需要关心。使用者只需要关心最近会话的变化
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
85
src/sdk/jssdk/conversation.md
Normal file
85
src/sdk/jssdk/conversation.md
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# 最近会话管理
|
||||||
|
|
||||||
|
|
||||||
|
## 同步最近会话列表
|
||||||
|
|
||||||
|
`需要实现最近会话数据源` [最近会话数据源](/sdk/jssdk/datasource.html#最近会话数据源)
|
||||||
|
|
||||||
|
只有第一次打开应用时,需要同步最近会话列表, 后续最近会话列表的变化,通过监听来获取
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
const conversations = await WKSDK.shared().conversationManager.sync({})
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 监听最近会话列表
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
const listen = (conversation: Conversation, action: ConversationAction) => {
|
||||||
|
|
||||||
|
if (action === ConversationAction.add) { // 新增最近会话
|
||||||
|
|
||||||
|
} else if (action === ConversationAction.update) { // 更新最近会话
|
||||||
|
|
||||||
|
} else if (action === ConversationAction.remove) { // 删除最近会话
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
添加监听
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
WKSDK.shared().conversationManager.addConversationListener(listen);
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
移出监听
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
WKSDK.shared().conversationManager.removeConversationListener(listen)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 其他常用方法
|
||||||
|
|
||||||
|
### 获取某个频道的最近会话
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
const conversation = WKSDK.shared().conversationManager.findConversation(channel)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 移出一个频道的最近会话
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
WKSDK.shared().conversationManager.removeConversation(channel)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 获取所红点数量
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
const unreadCount = WKSDK.shared().conversationManager.getAllUnreadCount()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建一个空会话
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
WKSDK.shared().conversationManager.createEmptyConversation(channel)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
168
src/sdk/jssdk/datasource.md
Normal file
168
src/sdk/jssdk/datasource.md
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
# 数据源管理
|
||||||
|
|
||||||
|
## 文件
|
||||||
|
|
||||||
|
### 文件上传数据源
|
||||||
|
|
||||||
|
代码如下:
|
||||||
|
|
||||||
|
``` ts
|
||||||
|
参考代码如下
|
||||||
|
export class MediaMessageUploadTask extends MessageTask {
|
||||||
|
private _progress?:number
|
||||||
|
private canceler: Canceler | undefined
|
||||||
|
getUUID(){
|
||||||
|
var len=32;//32长度
|
||||||
|
var radix=16;//16进制
|
||||||
|
var chars='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');var uuid=[],i;radix=radix||chars.length;if(len){for(i=0;i<len;i++)uuid[i]=chars[0|Math.random()*radix];}else{var r;uuid[8]=uuid[13]=uuid[18]=uuid[23]='-';uuid[14]='4';for(i=0;i<36;i++){if(!uuid[i]){r=0|Math.random()*16;uuid[i]=chars[(i===19)?(r&0x3)|0x8:r];}}}
|
||||||
|
return uuid.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(): Promise<void> {
|
||||||
|
const mediaContent = this.message.content as MediaMessageContent
|
||||||
|
if(mediaContent.file) {
|
||||||
|
const param = new FormData();
|
||||||
|
param.append("file", mediaContent.file);
|
||||||
|
const fileName = this.getUUID();
|
||||||
|
const path = `/${this.message.channel.channelType}/${this.message.channel.channelID}/${fileName}${mediaContent.extension??""}`
|
||||||
|
const uploadURL = this.getUploadURL(path)
|
||||||
|
this.uploadFile(mediaContent.file,uploadURL)
|
||||||
|
}else {
|
||||||
|
console.log('多媒体消息不存在附件!');
|
||||||
|
if (mediaContent.remoteUrl && mediaContent.remoteUrl !== "") {
|
||||||
|
this.status = TaskStatus.success
|
||||||
|
this.update()
|
||||||
|
} else {
|
||||||
|
this.status = TaskStatus.fail
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadFile(file:File,uploadURL:string) {
|
||||||
|
const param = new FormData();
|
||||||
|
param.append("file", file);
|
||||||
|
const resp = await axios.post(uploadURL,param,{
|
||||||
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
|
cancelToken: new axios.CancelToken((c: Canceler) => {
|
||||||
|
this.canceler = c
|
||||||
|
}),
|
||||||
|
onUploadProgress: e => {
|
||||||
|
var completeProgress = ((e.loaded / e.total) | 0);
|
||||||
|
this._progress = completeProgress
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.log('文件上传失败!->', error);
|
||||||
|
this.status = TaskStatus.fail
|
||||||
|
this.update()
|
||||||
|
})
|
||||||
|
if(resp) {
|
||||||
|
if(resp.data.path) {
|
||||||
|
const mediaContent = this.message.content as MediaMessageContent
|
||||||
|
mediaContent.remoteUrl = resp.data.path
|
||||||
|
this.status = TaskStatus.success
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取上传路径
|
||||||
|
getUploadURL(path:string) :string {
|
||||||
|
return 'http://xxxx'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 请求暂停
|
||||||
|
suspend(): void {
|
||||||
|
}
|
||||||
|
// 请求恢复
|
||||||
|
resume(): void {
|
||||||
|
}
|
||||||
|
// 请求取消
|
||||||
|
cancel(): void {
|
||||||
|
this.status = TaskStatus.cancel
|
||||||
|
if(this.canceler) {
|
||||||
|
this.canceler()
|
||||||
|
}
|
||||||
|
this.update()
|
||||||
|
}
|
||||||
|
progress(): number {
|
||||||
|
return this._progress??0
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
注册上传任务
|
||||||
|
|
||||||
|
```ts
|
||||||
|
WKSDK.shared().config.provider.messageUploadTaskCallback = (message: Message): MessageTask => {
|
||||||
|
return new MediaMessageUploadTask(message)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
完整代码参考:[TangSengDaoDaoWeb](https://github.com/TangSengDaoDao/TangSengDaoDaoWeb/blob/main/packages/tsdaodaodatasource/src/task.ts)
|
||||||
|
|
||||||
|
|
||||||
|
## 最近会话
|
||||||
|
|
||||||
|
### 最近会话数据源
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// 提供最近会话同步的数据源
|
||||||
|
WKSDK.shared().config.provider.syncConversationsCallback = async (): Promise<Array<Conversation>> => {
|
||||||
|
// 后端提供的获取最近会话列表的接口数据 然后构建成 Conversation对象数组返回
|
||||||
|
let conversations = new Array<Conversation>();
|
||||||
|
conversations = await request(...)
|
||||||
|
return conversations
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## 频道
|
||||||
|
|
||||||
|
### 获取频道资料数据源
|
||||||
|
|
||||||
|
```ts
|
||||||
|
WKSDK.shared().config.provider.channelInfoCallback = async function (channel: Channel): Promise<ChannelInfo> {
|
||||||
|
// 后端提供的获取频道资料的接口数据 然后构建成 ChannelInfo对象返回
|
||||||
|
let channelInfo = new ChannelInfo();
|
||||||
|
channelInfo = await request(...)
|
||||||
|
// channelInfo.orgData = ... //一些第三方数据可以放在channelInfo.orgData中
|
||||||
|
return channelInfo
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### 同步频道订阅者数据源
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
WKSDK.shared().config.provider.syncSubscribersCallback = async function (channel: Channel, version: number): Promise<Array<Subscriber>> {
|
||||||
|
// 后端提供的获取频道订阅者列表的接口数据 然后构建成 Subscriber对象数组返回
|
||||||
|
let subscribers = new Array<Subscriber>();
|
||||||
|
subscribers = await request(...)
|
||||||
|
// subscriber.orgData = ... //一些第三方数据可以放在subscriber.orgData中
|
||||||
|
return subscribers
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 消息
|
||||||
|
|
||||||
|
### 同步频道消息数据源
|
||||||
|
|
||||||
|
```js
|
||||||
|
|
||||||
|
WKSDK.shared().config.provider.syncMessagesCallback = async function(channel:Channel,opts:SyncOptions): Promise<Message[]> {
|
||||||
|
// 后端提供的获取频道消息列表的接口数据 然后构建成 Message对象数组返回
|
||||||
|
let messages = new Array<Message>();
|
||||||
|
messages = await request(...)
|
||||||
|
// message.remoteExtra.extra = ... //一些第三方数据可以放在这里
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- ## 同步离线cmd数据源 -->
|
||||||
|
|
45
src/sdk/jssdk/integration.md
Normal file
45
src/sdk/jssdk/integration.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
|
||||||
|
# 集成
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
::: code-group
|
||||||
|
|
||||||
|
``` sh [npm]
|
||||||
|
npm i wukongimjssdk
|
||||||
|
```
|
||||||
|
|
||||||
|
``` sh [yarn]
|
||||||
|
yarn add wukongimjssdk
|
||||||
|
```
|
||||||
|
|
||||||
|
``` sh [pnpm]
|
||||||
|
pnpm add wukongimjssdk
|
||||||
|
```
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 引入
|
||||||
|
|
||||||
|
::: code-group
|
||||||
|
|
||||||
|
``` js [ES6]
|
||||||
|
import WKSDK from "wukongimjssdk"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
``` js [AMD]
|
||||||
|
const WKSDK = require("wukongimjssdk").default
|
||||||
|
```
|
||||||
|
|
||||||
|
``` js [浏览器]
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/wukongimjssdk@1.1.3/lib/wukongimjssdk.umd.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
`通过<script>标签引入使用必须都加前缀 wk, 例如 wk.WKSDK.shared()`
|
31
src/sdk/jssdk/intro.md
Normal file
31
src/sdk/jssdk/intro.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# 说明
|
||||||
|
|
||||||
|
|
||||||
|
## 设计理念
|
||||||
|
像设计书的目录一样设计 api, 通过 WKSDK.shared().xxxManager 我们可以访问到所有需要的功能,例如发送消息 WKSDK.shared().chatManager.send(xxx)
|
||||||
|
|
||||||
|
## 结构说明
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
``` js
|
||||||
|
// 聊天管理者
|
||||||
|
// 负责消息相关的增删改查操作 比如发送消息,删除消息,撤回消息,聊天消息的监听等等
|
||||||
|
WKSDK.shared().chatManager;
|
||||||
|
|
||||||
|
// 连接管理者
|
||||||
|
// 负责与IM建立连接或断开连接 监听IM连接状态等等
|
||||||
|
WKSDK.shared().connectionManager;
|
||||||
|
|
||||||
|
// 频道管理者
|
||||||
|
// 负责频道数据的获取和缓存和一些频道的设置,比如置顶,免打扰,禁言等等
|
||||||
|
WKSDK.shared().channelManager;
|
||||||
|
|
||||||
|
// 最近会话管理者
|
||||||
|
// 负责维护最近会话的相关数据,比如未读数量,草稿,@我,最后一条消息等等
|
||||||
|
WKSDK.shared().conversationManager;
|
||||||
|
|
||||||
|
// 提醒管理者
|
||||||
|
// 负责最近会话的提醒事项维护
|
||||||
|
WKSDK.shared().reminderManager;
|
||||||
|
```
|
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 225 KiB |
Loading…
x
Reference in New Issue
Block a user