xgqfrms / FEIQA

FEIQA: Front End Interviews Question & Answers
https://feiqa.xgqfrms.xyz
MIT License
7 stars 0 forks source link

websocket & bug #87

Open xgqfrms opened 5 years ago

xgqfrms commented 5 years ago

websocket & bug

image

https://www.cnblogs.com/xgqfrms/p/11067070.html

https://www.cnblogs.com/xgqfrms/p/11242198.html

xgqfrms commented 5 years ago

"use strict";

/**
 *
 * @author xgqfrms
 * @license MIT
 * @copyright xgqfrms
 *
 * @description WS client
 * @augments
 * @example
 * @link
 *
 */

const url = `ws://www.xgqfrms.xyz/chat/?token=666666`;

let ws = new WebSocket(url);

let log = console.log;

ws.onopen = function(e) {
    log(`已经建立连接 open`, ws.readyState);
    log(`B uid = 6845422`);
    // log(`e = `, e);
    if (ws.readyState === 1) {
        allReady();
    }
};

ws.onerror = function(e) {
    log(`连接异常 error`, ws.readyState);
    // log(`e = `, e);
    // if (ws.readyState === 4) {
    //     unSubscribe();
    // }
    unSubscribe();
};

ws.onmessage = function(res) {
    log(`B 收到消息 message`, ws.readyState);
    let data = res.data;
    data = JSON.parse(data);
    // let origin = res.origin;
    // log(`res & e = `, res);
    // log(`typeof(data) = `, typeof(data));
    // log(`res.origin = `, origin);
    log(`res.data = `, JSON.stringify(data, null, 4));
    let {
        Action,
    } = data;
    switch (Action) {
        case 20:
            let {
                // SendUserID,
                Info,
            } = data;
            handleList(Info);
            break;
        case 21:
            (() => {
                let {
                    SendUserID,
                    Info,
                } = data;
                handleNewMessages(Info, SendUserID);
            })();
            break;
        case 1:
            if (Action) {
                handleNew();
            } else {
                handleMessage();
            }
            break;
        case 4:
            // subscribe();
            break;
        case 5:
            // unSubscribe();
            break;

        default:
            // sendMsg();
            break;
    }
};

ws.onclose = function(e) {
    log(`已经关闭连接 close`, ws.readyState);
    if (ws.readyState === 3) {
        // 关闭连接
        unSubscribe();
    }
    // log(`e = `, e);
};

const allReady = () => {
    let msg = {
        Action: 20,
        LastMsgIds: [],
    };
    log(`B 准备就绪`);
    let str_msg = JSON.stringify(msg);
    ws.send(str_msg);
};

const sendMsg = (text = `` ) => {
    let msg = {
        Action: 1,
        ReciveUserID: "6845488",
        Info: text ? text : `B 文本消息 text`,
        MsgType: 1,// 消息类型,number, 可取值: 1-文本、2-图片、3-表情、4-视频、5-音频
    };
    // let msg = {
    //     Action: 1,
    //     SendUserID: "6845422",
    //     ReciveUserID: "6845488",
    //     SerialNumber: "消息序列",
    //     Info: text,
    //     MsgType: 1,// 消息类型,number, 可取值: 1-文本、2-图片、3-表情、4-视频、5-音频
    // };
    log(`B 发送聊天消息`);
    let str_msg = JSON.stringify(msg);
    ws.send(str_msg);
};

const handleList = (datas = [], debug = false) => {
    // log(`list =`, JSON.stringify(datas, null, 4));
    let result = [];
    let chat_list_uids = [];
    if (datas.length) {
        datas.forEach(
            (obj, i) => {
                let {
                    ChatUserName: userName,
                    ChatUserID: userId,
                    CompanyName: corpName,
                    Top: isTop,
                    Hide: isHidden,
                } = obj;
                let temp = {
                    userName,
                    userId,
                    corpName,
                    isTop,
                    isHidden,
                    text: "default news placeholder",
                    time: "",
                    count: 0,
                };
                result.push(temp);
                chat_list_uids.push(userId);
                // chat_list_uids.push({
                //     userId,
                // });
            }
        );
    }
    listAutoGenerator(result);
    // TODO old + new
    window.localStorage.setItem(`chat_list`, JSON.stringify(result));
    window.localStorage.setItem(`chat_list_uids`, JSON.stringify(chat_list_uids));
    // return result;
};

const handleNewMessages = (datas = [], selfId = ``, debug = false) => {
    // log(`New Messages =`, JSON.stringify(datas, null, 4));
    let result = [];
    let chat_list = JSON.parse(window.localStorage.getItem(`chat_list`));
    let chat_list_uids = JSON.parse(window.localStorage.getItem(`chat_list_uids`));
    log(`chat_list =`, chat_list);
    log(`chat_list_uids =`, chat_list_uids);
    if (chat_list_uids.length) {
        window.DB = window.DB || {};
        chat_list_uids.forEach(
            (item, i) => {
                window.DB[item] = window.DB[item] || [];
                // DB Store
            }
        );
    }
    // chat_list_uids.includes(senderUid);
    if (datas.length) {
        datas.forEach(
            (obj, i) => {
                let {
                    SendUserID: senderUid,
                    ReciveUserID: receiverUid,
                    SerialNumber: serialNum,
                    MsgID: msgId,
                    Info: text,
                    // MsgType: msgType,
                    SendTime: time,
                    UnReadMsgCount: count,
                } = obj;
                if (senderUid !== selfId) {
                    let temp = {
                        senderUid,
                        receiverUid,
                        serialNum,
                        msgId,
                        // msgType,
                        text,
                        time,
                        count,
                    };
                    window.DB[senderUid].push(temp);
                    // result.push(temp);
                    // window.DB[6845333];
                }
            }
        );
    }
    // listAutoUpdater(result);
    // window.localStorage.setItem(`chat_list_messages`, JSON.stringify(result));
    // return result;
};

const handleNew = (datas = [], debug = false) => {
    //
};

const handleMessage = (datas = [], debug = false) => {
    //
};

const handleHistoryMessage = (datas = [], debug = false) => {
    // self + others
};

let LastMsgId = ``;

const subscribe = () => {
    let msg = {
        Action: 4,
        SendUserID: "6845422",
        ReciveUserID: "6845488",
        SerialNumber: "消息序列",
        LastMsgId: 605337337539633200,
        // LastMsgId: LastMsgId,
        // LastMsgId,
    };
    log(`B 订阅消息`);
    let str_msg = JSON.stringify(msg);
    ws.send(str_msg);
};

const unSubscribe = () => {
    let msg = {
        Action: 5,
    };
    log(`B 取消订阅`);
    let str_msg = JSON.stringify(msg);
    ws.send(str_msg);
    log(`B 取消订阅 OK`);
};

const listAutoGenerator = (list = []) => {
    // log(`%cchat_list =`, `color: #f0f;`, `%c${JSON.stringify(list, null, 4)}`, `color: red;`);
    // log(`%cchat_list =\n%c${JSON.stringify(list, null, 4)}`, `color: #f0f;`, `color: #0f0; background: #000;`);
    log(`%cchat_list =\n%c${JSON.stringify(list, null, 4)}`, `color: #f0f;`, `color: #3f3;`);
    let msgBox = document.querySelector(`[data-dom="msg-box"]`);
    let html = ``;
    if (list.length) {
        list.forEach(
            (obj, i) => {
                let {
                    userName,
                    // userId,
                    // corpName,
                    text,
                    count,
                    // time,
                } = obj;
                if (count > 0) {
                    html += `
                        <p class="msg" data-uid="">
                            <span class="msg-name">${userName}</span>
                            ${text}
                            <span class="msg-count">${count}</span>
                        </p>
                    `;
                } else {
                    html += `
                        <p class="msg" data-uid="">
                            <span class="msg-name">${userName}</span>
                            ${text}
                        </p>
                    `;
                }
            }
        );
        msgBox.insertAdjacentHTML(`beforeend`, html);
    }
};

let msgBox = document.querySelector(`[data-dom="msg-box"]`);

const autoListBind = () => {
    let msgs = [...document.querySelectorAll(`[data-dom="msg"]`)];
    if (msgs.length) {
        msgs.forEach(
            (msg, i) => {
                let bindOnceFlag = msg.dataset.bindOnceFlag || false;
                if (!bindOnceFlag) {
                    msg.addEventListener(`click`, () => {
                        msg.dataset.bindOnceFlag = true;
                        // i ??? data-uid
                    });
                }
            }
        )
    }
};
xgqfrms commented 5 years ago

image

xgqfrms commented 5 years ago

image

image

xgqfrms commented 5 years ago

image

xgqfrms commented 5 years ago

https://github.com/xgqfrms/IndexedDB

xgqfrms commented 5 years ago

WS 连接总是被关闭 ???

1006

image

https://wdd.js.org/websocket-close-reasons.html https://segmentfault.com/a/1190000014582485

https://zhuanlan.zhihu.com/p/25592934

https://blog.csdn.net/jintingbo/article/details/80864030

https://github.com/Pines-Cheng/blog/issues/37

https://www.cnblogs.com/1wen/p/9789260.html

xgqfrms commented 5 years ago

var timerID = 0; 
function keepAlive() { 
    var timeout = 20000;  
    if (webSocket.readyState == webSocket.OPEN) {  
        webSocket.send('');  
    }  
    timerId = setTimeout(keepAlive, timeout);  
}  
function cancelKeepAlive() {  
    if (timerId) {  
        clearTimeout(timerId);  
    }  
}

https://www.jstips.co/en/javascript/working-with-websocket-timeout/ https://docs.k6.io/docs/websockets

WebSocket 断开,自动重连 & 解决方案

心跳检测

  1. 服务端支持Ping/Pong

  2. 客户端不支持Ping/Pong

solutions

  1. 服务端向客户端发送 Ping 检测; 客户端向服务端相应 Pong 应答

  2. 客户端向服务端发送自定义的 heartbeat 检测空消息; 服务端添加判断逻辑,忽略收到的消息,以保持会话的持续连接,防止2分钟内没有消息通信,断开 websocket 连接!

https://github.com/xgqfrms/FEIQA/issues/87#issuecomment-516341470

https://stackoverflow.com/questions/10585355/sending-websocket-ping-pong-frame-from-browser/64658696#64658696

xgqfrms commented 5 years ago

websocket close 1005

https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent

image

https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close

xgqfrms commented 5 years ago

WebSockets 心跳 & ping & pong

https://stackoverflow.com/questions/10585355/sending-websocket-ping-pong-frame-from-browser

ws.onopen = function(e) {
    log(`已经建立连接 open`, ws.readyState);
    log(`B uid = 6845422`);
    // log(`e = `, e);
    if (ws.readyState === 1) {
        allReady();
        autoKeepAlive();
        // ping
        // ws.send(0x9);
        // pong
        // ws.send(0xA);
    }
};

image

Heart beat & WebSockets

https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#Pings_and_Pongs_The_Heartbeat_of_WebSockets

https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers

xgqfrms commented 5 years ago

js & websocket & ping / pong

seems not work ?


 // ping
ws.send(0x9);
// pong
ws.send(0xA);

https://github.com/websockets/ws/issues/977

image

still not work

ws.onping = function(e) {
    log(`ping`, ws.readyState);
    // ws.ping();
};

ws.onpong = function(e) {
    log(`pong`, ws.readyState);
    // ws.ping();
};
ws.on(`ping`, function heartbeat() {
    log(`ping`);
    // this.isAlive = true;
});

ws.on(`pong`, function heartbeat() {
    log(`pong`);
    // this.isAlive = true;
});
xgqfrms commented 5 years ago

https://tools.ietf.org/html/rfc6455#section-5.5.2

xgqfrms commented 5 years ago

ws

https://github.com/websockets/ws#how-to-detect-and-close-broken-connections

image

xgqfrms commented 5 years ago

error

image


    // ws.ping();
    // ws.ping(0x9);
    // ws.pong();
    // ws.pong(0xA);
xgqfrms commented 5 years ago

let ws = new WebSocket('wss://echo.websocket.org/');

ws.onmessage = function (res) {
   console.log(`res.data =`, res.data);
};
xgqfrms commented 5 years ago

https://stackoverflow.com/questions/50876766/how-to-implement-ping-pong-request-for-websocket-connection-alive-in-javascript/50883592

https://www.w3.org/TR/websockets/#ping-and-pong-frames

https://stackoverflow.com/questions/10585355/sending-websocket-ping-pong-frame-from-browser

xgqfrms commented 5 years ago

https://django-websocket-redis.readthedocs.io/en/latest/heartbeats.html

https://codeday.me/bug/20170714/39844.html

https://group.swoole.com/question/107170

我搜了一圈 貌似在现有的websocket的api中,没有提供ping的接口。 所以暂时我还是使用普通的发消息搞的心跳

xgqfrms commented 5 years ago

https://www.w3.org/Bugs/Public/show_bug.cgi?id=13104

xgqfrms commented 5 years ago

https://websocket.org/ wss://echo.websocket.org

xgqfrms commented 5 years ago

https://websocket.org/book.html

https://github.com/vjwang/WebSocketBook


// Send a Blob
var blob = new Blob("blob contents");
ws.send(blob);
// Send an ArrayBuffer
var a = new Uint8Array([8,6,7,5,3,0,9]);
ws.send(a.buffer);

http://file.allitebooks.com/20150518/The%20Definitive%20Guide%20to%20HTML5%20WebSocket.pdf

xgqfrms commented 5 years ago

"use strict";

/**
 *
 * @author xgqfrms
 * @license MIT
 * @copyright xgqfrms
 *
 * @description WS client
 * @augments
 * @example
 * @link
 *
 */

const url = `ws://xgqfrms.xyz/api/chat/?token=11`;
// const url = `wss://echo.websocket.org`;

let ws = new WebSocket(url);

let log = console.log;

let MSG_COUNTER = 1;

let startTime = new Date().getTime();
let endTime = ``;

var timerID = 0;
function autoKeepAlive() {
    let timeout = 30 * 1000;
    if (ws.readyState === 1) {
        // ws.readyState === ws.OPEN
        // ws.OPEN == 1
        log(`B keep alive && heart beat`);
        ws.send(``);
        // ws.send(`keep alive!`);
    }
    timerID = setTimeout(autoKeepAlive, timeout);
}

function cancelKeepAlive() {
    if (timerID) {
        clearTimeout(timerID);
    }
}

ws.onopen = function(e) {
    log(`已经建立连接 open`, ws.readyState);
    log(`B uid = 6845422`);
    // ws.ping();
    // ws.ping(0x9);
    // ws.pong();
    // ws.pong(0xA);
    // log(`e = `, e);
    if (ws.readyState === 1) {
        allReady();
        autoKeepAlive();
        // ping
        // ws.send(0x9);
        // pong
        // ws.send(0xA);
    }
};

// ws.onping = function(e) {
//     log(`连接 ping`, ws.readyState);
//     // ws.ping();
// };

// ws.onpong = function(e) {
//     log(`连接 pong`, ws.readyState);
//     // ws.ping();
// };

// ws.on(`ping`, function heartbeat() {
//     log(`连接 ping`);
//     // this.isAlive = true;
// });

// ws.on(`pong`, function heartbeat() {
//     log(`连接 pong`);
//     // this.isAlive = true;
// });

ws.onerror = function(e) {
    log(`连接异常 error`, ws.readyState);
    log(`连接异常 e = `, e);
    // if (ws.readyState === 4) {
    //     unSubscribe();
    // }
    unSubscribe();
    cancelKeepAlive();
};

ws.onmessage = function(res) {
    log(`B 收到消息 message`, ws.readyState);
    let data = res.data;
    data = JSON.parse(data);
    // let origin = res.origin;
    // log(`res & e = `, res);
    // log(`typeof(data) = `, typeof(data));
    // log(`res.origin = `, origin);
    log(`res.data = `, JSON.stringify(data, null, 4));
    let {
        Action,
    } = data;
    switch (Action) {
        case 20:
            let {
                // SendUserID,
                Info,
            } = data;
            handleList(Info);
            break;
        case 21:
            (() => {
                let {
                    SendUserID,
                    Info,
                } = data;
                handleBlockMessages(Info, SendUserID);
            })();
            break;
        case 1:
            (() => {
                handleNewMessage(data);
            })();
            // if (Action) {
            //     handleNew();
            // } else {
            //     handleMessage();
            // }
            break;
        case 4:
            // subscribe();
            break;
        case 5:
            // unSubscribe();
            break;

        default:
            // sendMsg();
            break;
    }
};

ws.onclose = function(e) {
    log(`已经关闭连接 close`, ws.readyState);
    log(`连接关闭 e = `, e);
    endTime = new Date().getTime();
    let timer = (endTime - startTime) / 1000;
    log(`auto 关闭 timer`,  timer);
    if (ws.readyState === 3) {
        // 关闭连接
        unSubscribe();
        cancelKeepAlive();
    }
};

const allReady = () => {
    let msg = {
        Action: 20,
        // LastMsgIds: [],
        LastMsgIds: [
            {
                K: "6845488",
                V: 605775149602742300,
                // UserId: "6845488",
                // LastMsgId: 605775149602742300,
            },
        ],
    };
    log(`B 准备就绪`);
    let str_msg = JSON.stringify(msg);
    ws.send(str_msg);
};

const sendMsg = (text = `` ) => {
    let timestamp = new Date().getTime();
    let msg = {
        Action: 1,
        ReciveUserID: "6845488",
        Info: text ? text : `B 文本消息 text ${MSG_COUNTER}`,
        MsgType: 1,// 消息类型,number, 可取值: 1-文本、2-图片、3-表情、4-视频、5-音频
        SerialNumber: `B ${timestamp}`,
    };
    MSG_COUNTER ++;
    // let msg = {
    //     Action: 1,
    //     SendUserID: "6845422",
    //     ReciveUserID: "6845488",
    //     SerialNumber: "消息序列",
    //     Info: text,
    //     MsgType: 1,// 消息类型,number, 可取值: 1-文本、2-图片、3-表情、4-视频、5-音频
    // };
    log(`B 发送聊天消息`);
    let str_msg = JSON.stringify(msg);
    ws.send(str_msg);
};

const handleList = (datas = [], debug = false) => {
    // log(`list =`, JSON.stringify(datas, null, 4));
    let result = [];
    let chat_list_uids = [];
    if (datas.length) {
        datas.forEach(
            (obj, i) => {
                let {
                    ChatUserName: userName,
                    ChatUserID: userId,
                    ChatUserLogo: userIcon,
                    CompanyName: corpName,
                    Top: isTop,
                    Hide: isHidden,
                } = obj;
                let temp = {
                    userName,
                    userId,
                    userIcon,
                    corpName,
                    isTop,
                    isHidden,
                    text: "",
                    // text: "default news placeholder",
                    time: "",
                    count: 0,
                };
                result.push(temp);
                chat_list_uids.push(userId);
                // chat_list_uids.push({
                //     userId,
                // });
            }
        );
    }
    listAutoGenerator(result);
    // TODO old + new
    window.localStorage.setItem(`chat_list`, JSON.stringify(result));
    window.localStorage.setItem(`chat_list_uids`, JSON.stringify(chat_list_uids));
    // return result;
};

const handleBlockMessages = (datas = [], selfId = ``, debug = false) => {
    // log(`New Messages =`, JSON.stringify(datas, null, 4));
    // let result = [];
    let chat_list = JSON.parse(window.localStorage.getItem(`chat_list`));
    let chat_list_uids = JSON.parse(window.localStorage.getItem(`chat_list_uids`));
    log(`chat_list =`, chat_list);
    log(`chat_list_uids =`, chat_list_uids);
    if (chat_list_uids.length) {
        window.DB = window.DB || {};
        chat_list_uids.forEach(
            (item, i) => {
                window.DB[item] = window.DB[item] || [];
                // DB Store
            }
        );
    }
    if (datas.length) {
        datas.forEach(
            (obj, i) => {
                let {
                    SendUserID: senderUid,
                    ReciveUserID: receiverUid,
                    SerialNumber: serialNum,
                    MsgID: msgId,
                    Info: text,
                    // MsgType: msgType,
                    SendTime: time,
                    // ReciverReaded: false,
                    // ReceiverReaded: false,
                    UnReadMsgCount: count,
                } = obj;
                if (senderUid !== selfId) {
                    let temp = {
                        senderUid,
                        receiverUid,
                        serialNum,
                        msgId,
                        // msgType,
                        text,
                        time,
                        count,
                    };
                    window.DB[senderUid].push(temp);
                }
            }
        );
    }
    if (chat_list.length) {
        chat_list = chat_list.map(
            (obj, i) => {
                let {
                    userId,
                } = obj;
                let l = window.DB[userId].length;
                let temp = {};
                if (l > 0) {
                    let {
                        count,
                        msgId,
                        // receiverUid,
                        senderUid,
                        serialNum,
                        text,
                        time,
                    } = window.DB[userId][l - 1];
                    temp = Object.assign(obj, {
                        count,
                        lastMsgId: msgId,
                        // receiverUid,
                        senderUid,
                        serialNum,
                        text,
                        time,
                    });
                } else {
                    temp = Object.assign(obj, {});
                    // temp = Object.assign(obj, {
                    //     lastMsgId: null,
                    // });
                }
                log(`temp =`, JSON.stringify(temp, null, 4));
                return temp;
            }
        );
        // autoUpdateList();
        listAutoGenerator(chat_list);
    }
};

const handleNewMessage = (obj = {}, debug = false) => {
    let chat_list = JSON.parse(window.localStorage.getItem(`chat_list`));
    let chat_list_uids = JSON.parse(window.localStorage.getItem(`chat_list_uids`));
    log(`chat_list =`, chat_list);
    log(`chat_list_uids =`, chat_list_uids);
    if (Object.keys(obj).length) {
        let {
            SendUserID: senderUid,
            ReciveUserID: receiverUid,
            SerialNumber: serialNum,
            MsgID: msgId,
            Info: text,
            // MsgType: msgType,
            SendTime: time,
            // ReciverReaded: false,
            // ReceiverReaded: false,
            UnReadMsgCount: count,
        } = obj;
        if (!chat_list_uids.includes(senderUid)) {
            log(`not exist DB Store!`, senderUid);
            chat_list_uids.push(senderUid);
            // chat_list.push({
            //     senderUid,
            // });
            window.DB[senderUid] = window.DB[senderUid] || [];
            // window.localStorage.setItem(`chat_list`, JSON.stringify(result));
            window.localStorage.setItem(`chat_list_uids`, JSON.stringify(chat_list_uids));
        } else {
            log(`exist DB Store!`, senderUid);
        }
        if (chat_list.length) {
            chat_list = chat_list.map(
                (obj, i) => {
                    let {
                        userId,
                    } = obj;
                    let l = window.DB[userId].length;
                    let temp = {};
                    if (senderUid === userId) {
                        if (l > 0) {
                            temp = Object.assign(obj, {
                                count,
                                lastMsgId: msgId,
                                // receiverUid,
                                senderUid,
                                serialNum,
                                text,
                                time,
                            });
                        } else {
                            temp = Object.assign(obj, {});
                            // temp = Object.assign(obj, {
                            //     lastMsgId: null,
                            // });
                        }
                        log(`temp =`, JSON.stringify(temp, null, 4));
                    } else {
                        temp = obj;
                    }
                    return temp;
                }
            );
            // autoUpdateList();
            listAutoGenerator(chat_list);
        }
    }
};

const handleNew = (datas = [], debug = false) => {
    //
};

const handleHistoryMessage = (datas = [], debug = false) => {
    // self + others
};

let LastMsgId = ``;

const subscribe = (LastMsgId = ``) => {
    let msg = {
        Action: 4,
        SendUserID: "6845422",
        ReciveUserID: "6845488",
        SerialNumber: "消息序列",
        // LastMsgId: 605792912446627800,
        // LastMsgId: LastMsgId,
        LastMsgId,
    };
    log(`B 订阅消息`);
    let str_msg = JSON.stringify(msg);
    ws.send(str_msg);
};

const unSubscribe = () => {
    let msg = {
        Action: 5,
    };
    log(`B 取消订阅`);
    let str_msg = JSON.stringify(msg);
    ws.send(str_msg);
    log(`B 取消订阅 OK`);
};

const listAutoGenerator = (list = []) => {
    // log(`%cchat_list =\n%c${JSON.stringify(list, null, 4)}`, `color: #f0f;`, `color: #3f3;`);
    let msgBox = document.querySelector(`[data-dom="msg-box"]`);
    msgBox.innerHTML = ``;
    let html = ``;
    if (list.length) {
        list.forEach(
            (obj, i) => {
                let {
                    userName,
                    // userId,
                    // corpName,
                    text,
                    count,
                    // time,
                    lastMsgId,
                } = obj;
                if (!lastMsgId) {
                    lastMsgId = ``;
                }
                if (count > 0) {
                    html += `
                        <p class="msg" data-uid="" data-dom="msg" data-LastMsgId="${lastMsgId}">
                            <span class="msg-name">${userName}</span>
                            ${text}
                            <span class="msg-count">${count}</span>
                        </p>
                    `;
                } else {
                    html += `
                        <p class="msg" data-uid="" data-dom="msg" data-LastMsgId="${lastMsgId}">
                            <span class="msg-name">${userName}</span>
                            ${text}
                        </p>
                    `;
                }
            }
        );
        msgBox.insertAdjacentHTML(`beforeend`, html);
    }
    setTimeout(() => {
        autoListBind();
    }, 0);
};

// let msgBox = document.querySelector(`[data-dom="msg-box"]`);

const autoListBind = () => {
    let msgs = [...document.querySelectorAll(`[data-dom="msg"]`)];
    if (msgs.length) {
        msgs.forEach(
            (msg, i) => {
                let bindOnceFlag = msg.dataset.bindOnceFlag || false;
                if (!bindOnceFlag) {
                    msg.addEventListener(`click`, () => {
                        msg.dataset.bindOnceFlag = true;
                        let LastMsgId = msg.dataset.lastmsgid;
                        // let LastMsgId = msg.dataset.LastMsgId;
                        log(`click LastMsgId`, LastMsgId);
                        if(LastMsgId) {
                            subscribe(LastMsgId);
                        }
                    });
                } else {
                    log(`only bind once!`);
                }
            }
        )
    } else {
        log(`no auto subscribe`);
    }
};