openfire web client 開發系列(2) -- XMPP通訊主控台

openfire web client 開發系列(2) -- XMPP通訊主控台

一、目的:openfire以XMPP通訊協定為基礎,故要後續進行通訊開發,有必要了解其傳輸的XML格式,瀏覽器或開發環境無法顯示其間傳遞的XML訊息,非連線上錯誤,瀏覽器開發人員主控台無法揪出問題在哪,因此我們自建一個簡單的封包擷取器,並將聆聽到的訊息顯示在主控台上,作為進一步剖析了解,XMPP伺服終端之間傳了哪些東西。

 

二、準備

1. 開發語言:HTML5 + javascripts

2. 開發環境:(可以使用其他,僅供參考)

Webstorm

3. 程式庫&工具:

jQuery 2.1.0

jQuery Mobile 1.4.2(非必要)

Strophe

4. 通訊伺服器:安裝在Ubuntu14.04上的openfire 3.9.3

 

三、介面說明

image

 

四、程式碼

程式碼以第一個連線的範例為基礎,關鍵在得知連線成功狀態後,加上聆聽事件。

image

 

index.html


<html>
<head lang="en">
    <meta charset="UTF-8">
    <link rel="stylesheet" href="css/jquery.mobile-1.4.2.css">
    <link rel="stylesheet" href="css/jquery.mobile.structure-1.4.2.css">
    <link rel="stylesheet" href="css/jquery.mobile.theme-1.4.2.css">
    <link rel="stylesheet" href="css/jquery.mobile.inline-svg-1.4.2.css">
    <link rel="stylesheet" href="css/jquery.mobile.inline-png-1.4.2.css">
    <link rel="stylesheet" href="css/jquery.mobile.icons-1.4.2.css">
    <link rel="stylesheet" href="css/jquery.mobile.external-png-1.4.2.css">
    <link rel="stylesheet" href="css/index.css">
    <script src="js/jquery-2.1.0.js"></script>
    <script src="js/jquery.mobile-1.4.2.js"></script>
    <script src="js/strophe.js"></script>
    <script src="js/index.js"></script>
    <title>XMPP Connection</title>
</head>
<body>
<div data-role="page" id="page_main" style="margin-top: 20px">
    <div data-role="header">
        <h1>XMPP Client</h1>
    </div>
    <div data-role="content">
        <form id="button-area">
            <input type="button" data-role="none" value="Connect" onclick="connect_server();"/>
            <input type="button" data-role="none" value="Disconnect" onclick="disconnect_server();"/>
            <input type="button" data-role="none" value="Clear Message" onclick="btn_clear_msg();"/>
            <input type="button" data-role="none" value="Send Stanza" onclick="btn_send_stanza();"/>
        </form>
        <form>
            <div id="input_area">
                <textarea id="input"></textarea>
            </div>
            <div id="console">
            </div>
            <div id="message">
            </div>
        </form>
    </div>
</div>

</body>
</html>

 

index.js


var SHORT_HOST_NAME = "of3";
var LOGON_USER = "t002";
var LOGON_PWD = "t002";

var my = {
    connection: null,
    connected:false
};

$(document).ready(function () {

});

function connect_server() {
    var conn = new Strophe.Connection(BOSH_HOST);//使用Strophe的連線方法
    //指派connection的Incoming、Outgoing負責函式 -- PO出通信訊息
    conn.xmlInput = function(body){
        showTraffic(body, "incoming");//Incoming message
    };
    conn.xmlOutput = function(body){
        showTraffic(body, "outgoing");//Outgoing message
    };
    // connect: function (jid, password, callback, wait, hold, route)
    // jid: 登入帳號需含域名以@隔開,
    // password: 登入帳號密碼
    // callback: 回呼函數這裡我們用來處理連線狀態以便確認連線成功與否
    // wait、hold、route 均為非必要參數,詳細作用請翻閱官方說明及參閱XEP-124規範
    conn.connect(LOGON_USER+"@"+SHORT_HOST_NAME, LOGON_PWD, function (status) {
        // 判斷連線狀態,開發者依據目前連線狀態,附加動作或聆聽事件
        if(status === Strophe.Status.CONNECTED) {
            //連線成功
            $("#message").append("<p>Connected!!!</p>");
            my.connected = true;
            //必須確認已連線狀態,才掛上聆聽事件
            conn.addHandler(handle_message,null,"message",'chat');
            //送出 new Strophe Builder 物件,即 <presence /> 為XML通訊節的根
            conn.send($pres());
        }else if(status === Strophe.Status.CONNECTING){
            //連線中,尚未確認成功
            $("#message").append("<p>Connecting!!!</p>");
        }else if(status === Strophe.Status.DISCONNECTED) {
            //斷線
            $("#message").append("<p>Disconnected!!!</p>");
            my.connected = false;
        }else if(status === Strophe.Status.DISCONNECTING) {
            //斷線中
            $("#message").append("<p>Disconnecting!!!</p>");
        }else if(status === Strophe.Status.ERROR){
            //連線錯誤
            $("#message").append("<p>An error has occurred</p>");
        }else if(status === Strophe.Status.CONNFAIL){
            //連線失敗
            $("#message").append("<p>Connection fail!!!</p>");
        }else{
            //其他不在篩選範圍的狀態顯示
            $("#message").append("<p>Status:"+status+"</p>");
        }
    });
    my.connection = conn;
}


//Disconnect
function disconnect_server() {
    my.connection.disconnect();//Connection執行斷線
    my.connected = false;//連線旗號改為false
}

//Show Traffic
function showTraffic(body, type){
    if(body.childNodes.length>0){
        //訊息會顯示在ID為console的網頁元素中
        var console = $("#console").get(0);
        var at_bottom = console.scrollTop >= console.scrollHeight - console.clientHeight;;
        $.each(body.childNodes, function(){
            //Strophe.serialize(): Render a DOM element and all descendants to a String.
            $("#console").append("<div class='"+type+"'>"+xml2html(Strophe.serialize(this))+"</div>");
        });
        if(at_bottom){
            //讓textarea能自動捲到最下
            console.scrollTop = console.scrollHeight;
        }
    };
}

function btn_send_stanza(){
    var input = $("#input").val();
    var error = false;
    if(input.length>0){
        if(input[0]==="<"){
            var xml = textToXml(input);//把輸入的字串轉成可辨識的XML
            if(xml){
                my.connection.send(xml);//要透過connection的send方法才能動作
            }else{
                error = true;
            }
        }else if(input[0]==="$"){
            try{
                var builder = eval(input);
                my.connection.send(builder);
                $("#message").append("<p>Send OK!</p>");
            }catch (e){
                console.log(e);
                error = true;
            }
        }else{
            error = true;
        }
    }
    if(error){
        $("#input").animate({backgroundColor:"#faa"});
    }
}

function textToXml(text){
    var doc = null;
    if(window['DOMParser']){ //Firefox、Safari、Opera、Chrome都支持這個剖析器
        var parser = new DOMParser();
        doc = parser.parseFromString(text, "text/xml")
    }else if(window['ActiveXObject']){ //針對IE6之前版本,沒有DOMParser API
        var doc = new ActiveXObject("MSXML2.DOMDocument");
        doc.async = false;
        doc.loadXML(text);
    }else{
        throw {
            type:"PeekError",
            message:"No DOMParser object found."
        };
    }
    var elem = doc.documentElement;
    //使用者不合法的XML輸入,會導致DOMParser產生error文件,
    //此文件會以<parsererror>元素作為最上層標簽,透過檢查此標籤過濾掉不合法的輸入
    if($(elem).filter("parsererror").length>0){
        return null;
    }
    return elem;
}

//xml string to html
function xml2html(str){
    //去除XML字串中會導致Html顯示有問題的特定符號
    return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
}

function btn_clear_msg(){
    $("#console").empty();
    $("#message").empty();
}

//Handler for receiving message
function handle_message(message){
    var from = $(message).attr("from");
    var body = $(message).children("body").text();
    showTraffic(message, "message-chat")
    return true;
}

 

index.css

body {
    font-family: Helvetica;
}

h1 {
    text-align: center;
}


#console{
    padding: 10px;
    height: 300px;
    border: solid 1px #aaa;
    background-color: #000;
    color: #eee;
    font-family: monospace;
    overflow: auto;
}

#message{
    padding: 10px;
    height: 50px;
    border: solid 1px #aaaaaa;
    background-color: #ffff00;
    color: #000000;
    font-family: monospace;
    overflow: auto;
}

.incoming {
    background-color: #005599;
}

.outgoing {
    background-color: #8b008b;
}

.message-chat{
    background-color: #adff2f;
    color: #000000;
}

 

部分名詞如XML stream、XML Stanza、<message/>、$pres(),另專章解釋。