本文由我们团队肖建朋编写
技术背景:使用leancloud后端存储技术和实时通信SDK、react-native开发手机APP
签名验证
开启签名验证设置: 控制台 > 消息 > 实时消息 > 设置 > 实时消息选项 勾选要开启的签名认证 ,如图:
对于不同操作需要实现不同的signature 工厂方法:signatureFactory
与 conversationSignatureFactory
。
如果使用leancloud内置的用户鉴权系统连接实时通信服务器,可以省掉登录签名操作。这里使用了内置用户鉴权系统所以只需要对话操作签名验证conversationSignatureFactory
根据sdk文档说明conversationSignatureFactory
需要参数 有:
- conversationId : 对话Id
- clientId: 当前用户Id
- targetIds: 此次操作的目标用户 IDs
- action: 此次行为的动作,可能的值为 create(创建对话)、add(加群和邀请)和 remove(踢出群)之一(这里的动作有问题,实际和文档中描述有差异)
客户端签名实现
var signatureFactory = function(clientId) {
return AV.Cloud.rpc('sign', { clientId: clientId }); // AV.Cloud.rpc returns a Promise
};
var conversationSignatureFactory = function(conversationId, clientId, targetIds, action) {
return AV.Cloud.rpc('sign-conversation', {
conversationId: conversationId,
clientId: clientId,
targetIds: targetIds,
action: action,
});
};
realtime.createIMClient('Tom', {
signatureFactory: signatureFactory,
conversationSignatureFactory: conversationSignatureFactory,
}).then(function(tom) {
console.log('Tom 登录');
}).catch(function(error) {
// 如果 signatureFactory 抛出了异常,或者签名没有验证通过,会在这里被捕获
});
实际使用中遇到的问题
-
当action为create 时,不需要把action push到msg中
-
当邀请用户加入对话时,云函数收到的action为‘add’,此时直接使用add 加入到msg中生产的签名验证不通过,后经过使用签名测试工具发现对话签名类型只有三种:
开启对话(create)、 加入对话(invite)、踢出对话(kick );
所以当action为‘add'时应替换为'invite' push到msg中来生成签名
云函数实现签名算法:
AV.Cloud.useMasterKey();
let crypto = require('crypto');
console.log('接收参数', request.params);
let clientId = request.params.clientId.id === undefined ? request.params.clientId : request.params.clientId.id; // 当前用户ID 如果获取Client使用user对象 则这里的需要取clientId.id
let conversationId = request.params.conversationId;
let targetIds = request.params.targetIds; // 此次操作的目标用户
let action = request.params.action; // 此次行为的动作,可能的值为 create(创建对话)、add(加群和邀请)和 remove(踢出群)之一
let msg = ['您应用appId', clientId];
if (conversationId) {
msg.push(conversationId);
}
// 如果该用户不允许创建对话可以在这里直接返回一个 no 即可
if (targetIds.length) {
targetIds.sort();
msg.push(targetIds.join(':'));
} else {
msg.push('');
}
let ts = parseInt(new Date().getTime() / 1000);
let d = [];
for (let i=0; i<5; i++) {
d.push(parseInt(Math.random()*10));
}
let nonce = d.join('');
msg.push(ts);
msg.push(nonce);
if(action) {
if(action !== 'create') {
if (action === 'add' || action ==='invite' ) {
msg.push('invite'); // add 动作也要使用invite来验证签名
}
if (action === 'remove' || action ==='kick' ) {
msg.push('kick');
}
}
}
msg = msg.join(':');
console.log('待检验的字符', msg);
let sig =crypto.createHmac('sha1', '你应用的masterKey').update(msg).digest('hex');
let res ={ 'nonce': nonce, 'timestamp': ts, 'signature': sig, 'msg': msg };
console.log(res);
return res;
测试签名
进入控制台在 消息 > 实时消息 > 用户,输入一个 clientId 进行查找, 找到后界面会显示 测试签名
监听事件
拥有监听事件的对象 IMClient, Conversation
on(event, listener, contextopt) → {this} 注册监听事件
给指定的 event 添加监听器,并将该监听器置于监听器列表的末位。该方法不会检查是否已经添加过该监听器。重复添加相同的 event 和 listener 会导致该事件和监听器被重复触发。
根据实际情况可以rn生命周期的不同阶段来注册监听事件
例如:
conversation.on(Event.MESSAGE, this.newMessage) // 监听新消息
newMessage = (message) => {
console.log(message)
}
off(event, listeneropt, contextopt, onceopt) → {this} 移除监听事件
移除 event 事件的监听器列表中的 listener。 一般在页面销毁前卸载所有监听事件
conversation.off(Event.MESSAGE, this.newMessage) // 卸载监听事件
注意:off 有两种情况
var callback = function() {};
conversation.on(Event.MESSAGE, callback);
conversation.off(Event.MESSAGE, callback); // off注册Event.MESSAG 事件的回调callback
conversation.off(Event.MESSAGE); // off掉所有的注册的 Event.MESSAG 事件回掉
once(event, listener, contextopt) → {this} 一次性的监听
为 event 事件添加一个一次性的监听器,该事件第一次触发之后就会被注销。
目前使用到的事件
客户端事件
DISCONNECT
:与服务端连接断开,此时聊天服务不可用。
OFFLINE
:网络不可用。
ONLINE
:网络恢复。
SCHEDULE
:计划在一段时间后尝试重连,此时聊天服务仍不可用。
RETRY
:正在重连。
RECONNECT
:与服务端连接恢复,此时聊天服务可用。
MESSAGE
: 监听消息
UNREAD_MESSAGES_COUNT_UPDATE
: 监听对话的未读消息数量
对话监听事件
MESSAGE
: 监听消息
MEMBERS_JOINED
: 有用户被添加至某个对话
MEMBERS_LEFT
: 有成员被从某个对话中移除
KICKED
: 当前用户被从对话中移除
React-Native 事件监听与回调
事件监听
用于解决子页面某些状态发生变化后,需要在返回父页面时,父页面能得到通知进行一些其他操作,如:刷新页面、更改某个状态等
参考资料:www.jianshu.com/p/847fbba8b…
首先父页面引入DeviceEventEmitter
import {
DeviceEventEmitter,//引入监听事件
} from 'react-native';
然后在父页面的componentDidMount方法注册一个监听事件
//注册通知
componentDidMount(){
DeviceEventEmitter.addListener('ChangeUI',(dic)=>{
//接收到详情页发送的通知,进行自己的操作如刷新页面,改变样式等
console.log(dic)
});
}
子页面也要引入DeviceEventEmitter,然后在componentWillUnmount发送一个通知方法给父页面
DeviceEventEmitter.emit('ChangeUI', {color:'red',text:'通知'});
移除监听
componentWillUnmount(){
// 移除监听
this.listener.remove();
}
事件回调
A界面在push到B界面的时候定义个回调函数
push = () =>{
this.props.navigator.push({
component:DetailsView,
passProps:{
callback:(msg)=>{ alert(msg) }
}
})
}
B界面在pop回A界面的时候调用该回调函数
pop = () =>{
this.props.navigator.pop({
})
if(this.props.callback){
this.props.callback('回调')
}
}