|
|
|
|
|
// common/websocket-manager.js
|
|
|
|
|
|
|
|
|
|
|
|
import * as wsApi from './wssocket';
|
|
|
|
|
|
import * as msgType from './messageType';
|
|
|
|
|
|
import * as enums from './enums';
|
|
|
|
|
|
|
|
|
|
|
|
class WebSocketManager {
|
|
|
|
|
|
constructor() {
|
|
|
|
|
|
if (WebSocketManager.instance) return WebSocketManager.instance;
|
|
|
|
|
|
WebSocketManager.instance = this;
|
|
|
|
|
|
|
|
|
|
|
|
// 状态标志
|
|
|
|
|
|
this.isInit = false;
|
|
|
|
|
|
this.isExit = false;
|
|
|
|
|
|
this.reconnecting = false;
|
|
|
|
|
|
this.audioTip = null;
|
|
|
|
|
|
this.WS_URL=this.$wsUrl
|
|
|
|
|
|
|
|
|
|
|
|
// Store 引用(需外部注入)
|
|
|
|
|
|
this.chatStore = null;
|
|
|
|
|
|
this.userId=null;
|
|
|
|
|
|
// 事件监听器(供页面注册)
|
|
|
|
|
|
this.messageListeners = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 初始化 WebSocket 管理器
|
|
|
|
|
|
* @param {Object} stores - 各个 store 实例
|
|
|
|
|
|
* @param {string} accessToken - 用户 token
|
|
|
|
|
|
*/
|
|
|
|
|
|
init(stores, accessToken) {
|
|
|
|
|
|
if (this.isInit) return;
|
|
|
|
|
|
|
|
|
|
|
|
this.chatStore = stores.chatStore;
|
|
|
|
|
|
this.userId=stores.userId;
|
|
|
|
|
|
this.isExit = false;
|
|
|
|
|
|
this.reconnecting = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 加载本地数据并启动 WebSocket
|
|
|
|
|
|
this.loadStore()
|
|
|
|
|
|
.then(() => {
|
|
|
|
|
|
this.initWebSocket(accessToken);
|
|
|
|
|
|
this.isInit = true;
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((e) => {
|
|
|
|
|
|
console.error(e);
|
|
|
|
|
|
this.exit();
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
initWebSocket(accessToken) {
|
|
|
|
|
|
wsApi.connect(this.WS_URL, accessToken);
|
|
|
|
|
|
|
|
|
|
|
|
wsApi.onConnect(() => {
|
|
|
|
|
|
if (this.reconnecting) {
|
|
|
|
|
|
this.onReconnectWs();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 首次连接,拉取离线消息
|
|
|
|
|
|
this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId).catch(() => {});
|
|
|
|
|
|
this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId).catch(() => {});
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
wsApi.onMessage((cmd, msgInfo) => {
|
|
|
|
|
|
// 分发原始消息给外部监听器
|
|
|
|
|
|
this.messageListeners.forEach(listener => listener(cmd, msgInfo));
|
|
|
|
|
|
|
|
|
|
|
|
// 内部业务处理
|
|
|
|
|
|
if (cmd == 2) {
|
|
|
|
|
|
// 异地登录
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
content: '您已在其他地方登录,将被强制下线',
|
|
|
|
|
|
showCancel: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
this.exit();
|
|
|
|
|
|
} else if (cmd == 3) {
|
|
|
|
|
|
this.handlePrivateMessage(msgInfo);
|
|
|
|
|
|
} else if (cmd == 4) {
|
|
|
|
|
|
this.handleGroupMessage(msgInfo);
|
|
|
|
|
|
} else if (cmd == 5) {
|
|
|
|
|
|
this.handleSystemMessage(msgInfo);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
wsApi.onClose((res) => {
|
|
|
|
|
|
console.log("ws断开", res);
|
|
|
|
|
|
this.reconnect(accessToken);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 重新连接(token 刷新后调用)
|
|
|
|
|
|
* @param {string} accessToken
|
|
|
|
|
|
*/
|
|
|
|
|
|
reconnect(accessToken) {
|
|
|
|
|
|
if (this.isExit) return;
|
|
|
|
|
|
this.reconnecting = true;
|
|
|
|
|
|
wsApi.reconnect(this.WS_URL, accessToken);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 关闭连接并退出登录
|
|
|
|
|
|
*/
|
|
|
|
|
|
exit() {
|
|
|
|
|
|
console.log("WebSocket exit");
|
|
|
|
|
|
this.isExit = true;
|
|
|
|
|
|
wsApi.close(3099);
|
|
|
|
|
|
// uni.removeStorageSync("loginInfo");
|
|
|
|
|
|
// uni.reLaunch({ url: "/pages/login/login" });
|
|
|
|
|
|
this.unloadStore();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 注册消息监听器(页面可用来接收新消息通知)
|
|
|
|
|
|
* @param {Function} listener - (cmd, msgInfo) => {}
|
|
|
|
|
|
*/
|
|
|
|
|
|
addMessageListener(listener) {
|
|
|
|
|
|
if (typeof listener === 'function') {
|
|
|
|
|
|
this.messageListeners.push(listener);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 移除消息监听器
|
|
|
|
|
|
*/
|
|
|
|
|
|
removeMessageListener(listener) {
|
|
|
|
|
|
const index = this.messageListeners.indexOf(listener);
|
|
|
|
|
|
if (index !== -1) this.messageListeners.splice(index, 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ============= 私有方法 =============
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 视频通话相关(可根据需要保留或删除)
|
|
|
|
|
|
handleRtcPrivate(msg) {
|
|
|
|
|
|
// #ifdef MP-WEIXIN
|
|
|
|
|
|
// return; // 小程序不支持
|
|
|
|
|
|
// // #endif
|
|
|
|
|
|
// let delayTime = 100;
|
|
|
|
|
|
// if (msg.type === enums.MESSAGE_TYPE.RTC_CALL_VOICE || msg.type === enums.MESSAGE_TYPE.RTC_CALL_VIDEO) {
|
|
|
|
|
|
// const mode = msg.type === enums.MESSAGE_TYPE.RTC_CALL_VIDEO ? "video" : "voice";
|
|
|
|
|
|
// const pages = getCurrentPages();
|
|
|
|
|
|
// const curPage = pages[pages.length - 1].route;
|
|
|
|
|
|
// if (curPage !== "pages/chat/chat-private-video") {
|
|
|
|
|
|
// const friend = this.loadFriendInfo(msg.selfSend ? msg.recvId : msg.sendId);
|
|
|
|
|
|
// const friendInfo = encodeURIComponent(JSON.stringify(friend));
|
|
|
|
|
|
// uni.navigateTo({
|
|
|
|
|
|
// url: `/pages/chat/chat-private-video?mode=${mode}&friend=${friendInfo}&isHost=false`,
|
|
|
|
|
|
// });
|
|
|
|
|
|
// delayTime = 500;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
// setTimeout(() => {
|
|
|
|
|
|
// uni.$emit('WS_RTC_PRIVATE', msg);
|
|
|
|
|
|
// }, delayTime);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handleRtcGroup(msg) {
|
|
|
|
|
|
// #ifdef MP-WEIXIN
|
|
|
|
|
|
return;
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
let delayTime = 100;
|
|
|
|
|
|
if (msg.type === enums.MESSAGE_TYPE.RTC_GROUP_SETUP) {
|
|
|
|
|
|
const pages = getCurrentPages();
|
|
|
|
|
|
const curPage = pages[pages.length - 1].route;
|
|
|
|
|
|
if (curPage !== "pages/chat/chat-group-video") {
|
|
|
|
|
|
const userInfos = encodeURIComponent(msg.content);
|
|
|
|
|
|
const inviterId = msg.sendId;
|
|
|
|
|
|
const groupId = msg.groupId;
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
url: `/pages/chat/chat-group-video?groupId=${groupId}&isHost=false&inviterId=${inviterId}&userInfos=${userInfos}`,
|
|
|
|
|
|
});
|
|
|
|
|
|
delayTime = 500;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
uni.$emit('WS_RTC_GROUP', msg);
|
|
|
|
|
|
}, delayTime);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
playAudioTip() {
|
|
|
|
|
|
// 音频播放实现(如需)
|
|
|
|
|
|
// this.audioTip = uni.createInnerAudioContext();
|
|
|
|
|
|
// this.audioTip.src = "/static/audio/tip.wav";
|
|
|
|
|
|
// this.audioTip.play();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async refreshToken(loginInfo) {
|
|
|
|
|
|
// if (!loginInfo || !loginInfo.refreshToken) {
|
|
|
|
|
|
// throw new Error("No refreshToken");
|
|
|
|
|
|
// }
|
|
|
|
|
|
// const newLoginInfo = await http({
|
|
|
|
|
|
// url: '/refreshToken',
|
|
|
|
|
|
// method: 'PUT',
|
|
|
|
|
|
// header: { refreshToken: loginInfo.refreshToken },
|
|
|
|
|
|
// });
|
|
|
|
|
|
// uni.setStorageSync("loginInfo", newLoginInfo);
|
|
|
|
|
|
// return newLoginInfo;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 导出单例
|
|
|
|
|
|
const wsManager = new WebSocketManager();
|
|
|
|
|
|
export default wsManager;
|