Last active
December 15, 2020 14:12
-
-
Save VictorZhang2014/3811b38aea41390039cbbe79d9dad0a3 to your computer and use it in GitHub Desktop.
Javascript WebSocke chat example, it supports mobile and PC pages communication.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Doc: https://developer.mozilla.org/en-US/docs/Web/API/WebSocket | |
* */ | |
'use strict'; | |
// 通信协议定义 | |
class YLGroupChatProtocol { | |
constructor () { | |
this.create_room = 1001; | |
this.send_content = 1002; | |
this.send_qiniu_image = 1003; | |
this.send_file = 1004; | |
this.newUserBroadcast = 1005; // 新用户连接时,广播消息给所有的用户 | |
this.heartbeat = 1006; // 心跳 | |
} | |
} | |
// WebSocket状态 | |
// WebSocket文档:https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState | |
class YLWebSocketReadyState { | |
constructor () { | |
this.CONNECTING = 0; // Socket has been created. The connection is not yet open. | |
this.OPEN = 1; // The connection is open and ready to communicate. | |
this.CLOSING = 2; // The connection is in the process of closing. | |
this.CLOSED = 3; // The connection is closed or couldn't be opened. | |
} | |
} | |
// 聊天消息实体模型 | |
class YLChatMessageModel { | |
constructor () { | |
this.protocol = ""; | |
this.roomUUID = ""; | |
this.sender = ""; | |
this.content = ""; | |
this.imagekey = ""; // 图片的七牛云key | |
this.uuid = ""; // 消息UUID | |
this.users = null; // 用户列表 | |
this.client_ip = ""; // 用户真实IP | |
this.rawProtocol = new YLGroupChatProtocol(); | |
} | |
setData (data) { | |
let jsonDict = JSON.parse(data); | |
this.protocol = jsonDict["protocol"]; | |
if (this.protocol == this.rawProtocol.heartbeat) { | |
} else if (this.protocol == this.rawProtocol.send_content) { | |
this.roomUUID = jsonDict["roomUUID"]; | |
this.client_ip = jsonDict["client_ip"]; | |
this.sender = jsonDict["sender"]; | |
this.content = jsonDict["content"]; | |
this.uuid = jsonDict["uuid"]; | |
} else if (this.protocol == this.rawProtocol.send_qiniu_image) { | |
this.roomUUID = jsonDict["roomUUID"]; | |
this.client_ip = jsonDict["client_ip"]; | |
this.sender = jsonDict["sender"]; | |
this.content = jsonDict["content"]; | |
this.imagekey = jsonDict["imagekey"]; | |
this.uuid = jsonDict["uuid"]; | |
} else if (this.protocol == this.rawProtocol.newUserBroadcast) { | |
this.roomUUID = jsonDict["roomUUID"]; | |
this.client_ip = jsonDict["client_ip"]; | |
this.users = jsonDict["users"]; | |
} | |
} | |
} | |
// 群组聊天 | |
class YLGroupChatWebSocket { | |
constructor () { | |
this.current_user = null; // 当前登录用户实例对象 | |
this._group_ws = null; | |
this.heartbeat_enabled = false; // 心跳包是否启用 | |
var ws_protocol = 'https:' == document.location.protocol ? "wss:": "ws:"; | |
this._roomUUID = "yooulchat2019"; | |
// 测试域名 | |
let test_domains = [ | |
"localhost:8080", | |
"localhost:9000", | |
]; | |
let real_domain = ""; | |
if (test_domains.indexOf(window.location.host) != -1) { | |
// 测试环境 | |
real_domain = "ws://test.yourdomain.com"; | |
} else { | |
// 线上环境 | |
real_domain = "wss://yourdomain.com"; | |
} | |
this._group_ws_url = real_domain + "/stream/ws/gchat/" + this._roomUUID; | |
// 临时的消息暂存 | |
this.tmp_message_protocol = null; | |
this.tmp_message_content = null; | |
} | |
// 连接服务器 | |
connect_to_server () { | |
if (!this.hasLogin()) { | |
console.log("请选登录!"); | |
return; | |
} | |
this._group_ws = new WebSocket(this._group_ws_url); | |
this._group_ws.binaryType = "arraybuffer"; | |
var that = this; | |
this._group_ws.onopen = function (event) { | |
console.log("成功连接到服务器!"); | |
window.YLNotificationMessages.publish(window.YLNotificationMessageType.connected, event); | |
// 重新连接上服务器后,把消息发出去 | |
if (that.tmp_message_protocol != null && that.tmp_message_content != null) { | |
that.send_content(that.tmp_message_protocol, that.tmp_message_content); | |
that.tmp_message_protocol = null; | |
that.tmp_message_content = null; | |
} | |
// 新加入连接的用户,发给服务器记录起来 | |
let rawProtocol = new YLGroupChatProtocol(); | |
that.send_content(rawProtocol.newUserBroadcast, ""); | |
// 定期发送心跳包 | |
that.heartbeat_enabled = true; | |
that.send_heartbeat_periodically(); | |
} | |
this._group_ws.onmessage = function (event) { | |
// console.log("收到消息了", event) | |
let msgModel = new YLChatMessageModel(); | |
msgModel.setData(event.data); | |
if (msgModel.protocol == new YLGroupChatProtocol().heartbeat) { | |
return; | |
} | |
// 发送消息到聊天的页面上显示 | |
window.YLNotificationMessages.publish(window.YLNotificationMessageType.receive_messages, msgModel); | |
} | |
this._group_ws.onclose = function (event) { | |
console.log("与服务器已断开连接。", event); | |
this.heartbeat_enabled = false; | |
window.YLNotificationMessages.publish(window.YLNotificationMessageType.disconnected, event); | |
if (that.hasLogin() && window.location.pathname.indexOf("/chatbox") > -1) { | |
console.log("正在重新连接..."); | |
that.connect_to_server(); | |
} | |
} | |
this._group_ws.onerror = function (err) { | |
console.log(err); | |
this.heartbeat_enabled = false; | |
window.YLNotificationMessages.publish(window.YLNotificationMessageType.socket_error, err); | |
} | |
} | |
// 发送数据到服务器 | |
send_content (protocolEnumVal, content, imagekey="") { | |
if (window.location.pathname.indexOf("/chatbox") == -1) { | |
return; | |
} | |
if (!this.hasLogin()) { | |
console.log("请选登录!"); | |
return; | |
} | |
if (this._group_ws == null) { | |
this.connect_to_server(); | |
// 暂存发送的消息,待重新连接上服务器后,再把消息发出去 | |
this.tmp_message_protocol = protocolEnumVal; | |
this.tmp_message_content = content; | |
return; | |
} | |
if ((this._group_ws.readyState == window.YLWebSocketReadyState.CLOSING || | |
this._group_ws.readyState == window.YLWebSocketReadyState.CLOSED)) { | |
if (confirm("您与全球用户聊天已失去连接,是否自动连接?")) { | |
this.connect_to_server(); | |
// 暂存发送的消息,待重新连接上服务器后,再把消息发出去 | |
this.tmp_message_protocol = protocolEnumVal; | |
this.tmp_message_content = content; | |
} | |
return; | |
} | |
// 当前登录用户的User对象(游客) | |
var token = window.localStorage.getItem("token"); | |
if (token == undefined || token == null) { | |
token = window.sessionStorage.getItem("token_guest"); | |
} | |
// 当前登录用户的User对象 | |
var yooulUserStr = window.localStorage.getItem("$Yoouluser"); | |
if (yooulUserStr == undefined || yooulUserStr == null) { | |
yooulUserStr = window.sessionStorage.getItem("$Yooulguest"); | |
} | |
let data = { | |
"protocol" : protocolEnumVal, | |
"roomUUID" : this._roomUUID, | |
"token": token, | |
"content" : content, | |
"sender": JSON.parse(yooulUserStr) | |
} | |
let rawProtocol = new YLGroupChatProtocol(); | |
if (protocolEnumVal == rawProtocol.send_qiniu_image && imagekey.length > 0) { | |
data["imagekey"] = imagekey; | |
} | |
let jsonData = JSON.stringify(data) | |
this._group_ws.send(jsonData); | |
} | |
// 关闭WebSocket链接 | |
close_connection () { | |
if (this._group_ws != null) { | |
this._group_ws.close(); | |
this._group_ws = null; | |
} | |
} | |
// 间隔30秒发送心跳包 | |
send_heartbeat_periodically() { | |
// 当前登录用户的User对象 | |
let self = this; | |
var myHeartBeatInterval = window.setInterval(() => { | |
if (!this.heartbeat_enabled) { | |
// 关闭计时器 | |
window.clearInterval(myHeartBeatInterval); | |
return; | |
} | |
let data = {"protocol" : new YLGroupChatProtocol().heartbeat} | |
let jsonData = JSON.stringify(data) | |
self._group_ws.send(jsonData); | |
}, 30 * 1000); | |
} | |
getUUID () { | |
var d = new Date().getTime(); | |
if (window.performance && typeof window.performance.now === "function") { | |
d += performance.now(); //use high-precision timer if available | |
} | |
var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { | |
var r = (d + Math.random() * 16) % 16 | 0; | |
d = Math.floor(d / 16); | |
return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16); | |
}); | |
return uuid; | |
} | |
hasLogin () { | |
var token = "Your token"; | |
if (token != null) { | |
return true; | |
} | |
return false; | |
} | |
} | |
// 定义通知类型 | |
class YLNotificationMessageType { | |
constructor () { | |
this.socket_error = -1; // socket发生错误 | |
this.connected = 1; // 已连接到服务器 | |
this.reconnect = 2; // 重新连接服务器 | |
this.receive_messages = 3; // 接收消息 | |
this.disconnected = 4; // 客户端已掉线 | |
} | |
} | |
// 消息通知 | |
// 发布/订阅模式 | |
class YLNotificationMessages { | |
constructor () { | |
// 事件对象:存放事件的订阅名字和回调 | |
this.events = {}; | |
} | |
// 订阅事件 | |
subscribe (eventName, callback) { | |
if (!this.events[eventName]) { | |
// 一个名称可以有多个订阅回调事件,所以使用数组存储回调 | |
this.events[eventName] = [callback]; | |
} else { | |
// 如果该事件名称存在,则继续往该名称添加回调事件 | |
this.events[eventName].push(callback); | |
} | |
} | |
// 发布事件 | |
publish (eventName, ...args) { | |
this.events[eventName] && this.events[eventName].forEach(cb => cb(...args)); | |
} | |
// 取消订阅事件 | |
unsubscribe (eventName, callback) { | |
if (this.events[eventName]) { | |
// 找到该回调,并移除它 | |
// this.events[eventName].filter(cb => cb != callback); // 不管用 | |
var _events = []; | |
for (var i = 0; i < this.events[eventName].length; i++) { | |
if (this.events[eventName][i].toString()!=callback.toString()) { | |
_events.push(this.events[eventName][i]); | |
} | |
} | |
this.events[eventName] = _events; | |
} | |
} | |
// 取消订阅所有事件 | |
unsubscribeAll(eventName) { | |
if (this.events[eventName]) { | |
this.events[eventName] = []; | |
} | |
} | |
} | |
// 注册到Window对象中 | |
window["YLNotificationMessageType"] = new YLNotificationMessageType(); | |
window["YLNotificationMessages"] = new YLNotificationMessages(); | |
window["YLGroupChatProtocol"] = new YLGroupChatProtocol(); | |
window["YLWebSocketReadyState"] = new YLWebSocketReadyState(); | |
window["YLGroupChatWebSocket"] = new YLGroupChatWebSocket(); | |
// 当页面关闭或强制刷新时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常 | |
window.onbeforeunload = function() { | |
console.log("客户端连接已关闭"); | |
if (isUserLoginWhenConnectingToWSServer()) { | |
window.YLGroupChatWebSocket.close_connection(); | |
// 取消订阅事件,取消接收聊天消息 | |
window.YLNotificationMessages.unsubscribe(window.YLNotificationMessageType.receive_messages, chatReceiveMessagesCallback); | |
} | |
} | |
window.onload = function () { | |
grantNotification(); | |
// 仅打开聊天界面时才开启WebSocket聊天 | |
// 等待页面加载完后再去连接WebSocket服务 | |
if (isUserLoginWhenConnectingToWSServer()) { | |
window.YLGroupChatWebSocket.connect_to_server() | |
// 订阅事件,接收聊天消息 | |
window.YLNotificationMessages.subscribe(window.YLNotificationMessageType.receive_messages, chatReceiveMessagesCallback); | |
} | |
} | |
// 连接WebSocket服务器时判断一下登录状态 | |
function isUserLoginWhenConnectingToWSServer() { | |
var token = "You token"; | |
if (token != null) { | |
return true; | |
} | |
return false; | |
} | |
// 订阅事件,接收聊天消息回调 | |
function chatReceiveMessagesCallback(data) { | |
let rawProtocol = new YLGroupChatProtocol(); | |
var yooulUserStr = window.localStorage.getItem("$Yoouluser"); | |
let yooulUser = JSON.parse(yooulUserStr); | |
if (yooulUser.user_name == data.sender.user_name && yooulUser.user_id == data.sender.user_id) { | |
// 如果是自己的消息,则不通知 | |
} else if (data.protocol == rawProtocol.send_content || data.protocol == rawProtocol.send_qiniu_image) { | |
if (window.location.pathname == "/chatbox") { | |
// 当用户在聊天页面时,不显示通知 | |
return; | |
} | |
// 只通知收到别人发的消息 | |
showBrowserNotification(data.sender.user_avatar, data.content); | |
} | |
} | |
// 显示浏览器通知 | |
function showBrowserNotification(msgAvatar, msgContent) { | |
console.log("用户设定的通知状态是:", Notification.permission); | |
if (Notification.permission !== 'granted') { | |
Notification.requestPermission(); | |
} else { | |
var notification = new Notification('有人给你发送了消息', { | |
icon: msgAvatar, | |
body: msgContent, | |
}); | |
notification.onclick = function() { | |
window.open('/chatbox'); | |
}; | |
} | |
} | |
// 授权显示通知 | |
function grantNotification() { | |
if (Notification.permission !== 'granted') { | |
Notification.requestPermission(); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment