一个基于 react-native + mobx + socke
Im.JS
im-js-logo一个基于 react-native + mobx + socket.io + node 的仿微信 JS-Wechat。
项目主页 Github-Im.JS
写在前面
Why Not JS ?
这是自己作为一个 JS
开发者面对来自他人的不确信,常常问起自己的问题。
我想不管答案是什么,更重要的是探索深挖的过程;不肤浅,总能有意想不到的收获。
效果图
iOS 预览 | Android 请直接扫码下载体验 |
---|---|
ios-demo.gif | 安卓下载二维码 |
运行项目
react-native 在 debug 和 release 模式之间的性能差距是惊人的。
安装依赖
npm install
进入开发模式
react-native run-ios
// or
react-native run-android
Release 包生成
Running On Device
Generating Signed APK
im.js-server
基于 socket.io + koa2 + cloverx(自用 RestuFul 框架)
服务端用到了 cloverx-doc 从接口注释生成 Swagger
在线调试文档,还自带了一个输出格式化器,用来保证 Api
接口输出的一致性,纯手撸的,有兴趣可以看下。附一张效果图:
组件库
开发本项目的时候,要求自己尽量手写基础组件,基础组件与业务无关,可通用
组件库地址:UiLibrary
通过更改 app.json 的 appMode 字段,进行组件调试模式(UiLibrary)和 Im 模式(ImClient)的切换
开发笔记
TODO
接下来的开发重点在
- socket 集群间通讯和管理(大量精力会在这里,还是服务器擅长一点)
- 聊天室体验优化
计划中
- [x] 应用内离线消息,基于
Reids
实现。 - [ ] Important [聊天室] 聊天室重构,使用
FlatList
和SectionList
替换ListView
- [ ] Important [聊天室] 优化加载历史消息下拉加载更多体验,实现连续滑屏。 Issue#1
- [ ]
Ack
消息触达、已读等状态回调。 - [ ] 添加群聊支持
- [ ] 公众号菜单以及对应后台
Dashboard
开发 - [ ] 服务器升级为
https
和wss
或许会做
- [ ] 集成微信登录
- [ ] 集成一个第三方推送服务
已知 Bug 列表
- [ ]
rn
的Text
控件,即使指定numberOfLines={1}
,消息如果以\n
结尾,也会造成显示成 2 行。
技术文档
应用层消息事件和数据格式约定
Paylaod
基础定义
{
from: String('用户ID'),
to: String('用户ID'),
uuid: String('消息唯一UUID'),
// 用于存储消息内容
msg: {
......
},
ext: {
avatar: String('用户头像地址'),
name: String('用户姓名'),
// 可使用 moment().startOf('minute').fromNow() 格式化
timestamp: timestamp(毫秒),
},
// 不参与网络传输,本地传递拓展字段位置
localeExt: {
......
}
}
msg 字段定义
对象必须包含如下字段
字段 | 定义 | 可选值 |
---|---|---|
type | 消息类型 | txt |
消息类型
// txt - 文本类型消息
{
type: 'txt',
content: '文本内容',
}
ext 字段定义
对象必须包含如下字段
字段 | 定义 | 可选值 |
---|---|---|
timestamp | 消息创建时间 | UnixTimestamp(毫秒) |
群聊与私聊
在消息传递层面,群聊和私聊共用一套事件机制,对于客户端来说,都是收到了某个用户的消息。
服务器层面,一个群的定义是 “业务逻辑圈定的一群用户”,当一个 payload
的投递对象是一个群时,
服务器需要向这个群内的所有有效用户,发送 message
事件。
群聊的优化点在于,如何保证一个群内的用户尽可能多的在一个 socket
节点上,以此来降低 socket
节点间通讯的开销。
消息事件
Event: message
-
payloads: Array<Paylaod>
- Payload 数组,新消息在数组尾部
客户端之间无法直接发送消息,需要由 socket
服务器转发。
客户端和服务端建立 socket
连接后,每个 emit
, on
的对象都是远端,而非本地端。
客户端的所有 im
消息,都将通过监听 message
事件来接收。
交互时序如下:
Event: disconnect
发生在服务器和客户端之间的 socket
连接断开时。
这个事件将更改用户状态为 offline
。
Event: connection
-
socket
- socket 连接对象
发生在服务器和客户端之间的 socket
连接建立、重连成功时。
Event: user:online
-
data: Object
- 业务数据
当客户端存在登录用户信息且 socket
处于连接状态时,客户端向服务器发送用户上线事件。
在 im.js
设计中,data
中存放了如下信息,这个事件将更改用户的状态为 online
{
userId: String('用户ID')
}
用户状态裁决
AppState 状态与 socket、用户在线状态关系。
State | background | inactive | active |
---|---|---|---|
socket-ios | close | close | connect |
socket-android | close | \ | connect |
user-status | offline | offline | online |
离线消息机制
当用户状态为 offline
时候,触发离线消息机制。
im.js.server 的实现基于 Redis
,单用户离线队列命名规则为 offline:queue:userId:${userId}
,存储结构为 Lists
。
当用户上线时候,客户端向服务器发送 user:online
事件,服务器以数组的形式返回对应用户的离线消息,并清空离线缓存。
ACK 处理
socket.emit(eventName[, ...args][, ack]) 提供了 Ack
回调。
此处 Ack
函数返回的状态,指的是单个 socket
连接两端点间消息是否送达成功,不代表对方用户是否收到(因为消息是由服务转发的)。
如果要做消息已读、是否送达等状态,需要在应用层继续做开发。