Strophe JS 說明

    • Strophe對像中
      • 命名空間
      • 連接狀態
      • 日誌
      • addConnectionPlugin 添加插件
      • forEachChild
        • 示例,解析Vcard節
      • isTagEqual
      • xmlescape,xmlunescape,serialize
      • escapeNode,unescapeNode
      • 與JID相關的幾個方法getNodeFromJid,getDomainFromJid,getResourceFromJid,getBareJidFromJid
    • Strophe.Builder
      • 示例
    • Strophe.Handler和trophe.TimeHandler
    • SASL
      • Strophe.SASLMechanism 驗證機制
    • Strophe.Connection
      • connect, disconnect斷開連接。
      • send(elem) 發送節, sendIQ(elem, callback, errback, ti​​meout) 發送IQ節
      • addHandler,deleteHandler,addTimedHandler,deleteTimedHandler
      • xmlInput,xmlOutput,rawInput,rawOutput
    • Strohpe.Websocket,Strophe.Bosh 和Strophe.Request

Strohpe1.2.7.API的介紹。Strophe是使用JS實現的與XMPP服務器進行連接、發送、接收消息的WEB端底層插件。

  • Builder
  • Handler
  • TimeHandler
  • Connection
  • SASLMechanism
  • SASLPlain
  • SASLSHA1
  • SASLMD5
  • SASLOAuthBearer
  • Request
  • Bosh
  • WebSocket

或者可以通過代碼來查看,最後暴露了些什麼到全局中。

var o = factory(root.SHA1, root.Base64, root.MD5, root.stropheUtils);
window.Strophe =        o.Strophe;
window.$build =         o.$build;
window.$iq =            o.$iq;
window.$msg =           o.$msg;
window.$pres =          o.$pres;
window.SHA1 =           o.SHA1;
window.Base64 =         o.Base64;
window.MD5 =            o.MD5;
window.b64_hmac_sha1 =  o.SHA1.b64_hmac_sha1;
window.b64_sha1 =       o.SHA1.b64_sha1;
window.str_hmac_sha1 =  o.SHA1.str_hmac_sha1;
window.str_sha1 =       o.SHA1.str_sha1;

分類一下:

  • Strophe
  • Builder 用來構建節,已經提供了三種基本的構建方式
    • iq 請求響應節
    • msg 消息節
    • pre 出席節
  • Handler和TimeHandler
  • Connection
    • 通訊通道一:Bosh
      • Request Bosh通道不是真正意義上的長連接,通過不斷發起請求來模擬長連接
    • 通訊通道二:WebSocket,真正意義上的長連接。
    • 登錄加密:作為Connection登錄時加密所用。
      • SASLMechanism
      • SASLPlain
      • SASLSHA1
      • SASLMD5
      • SASLOAuthBearer

Strophe對像中

命名空間

對應協議中定義的命名空間。NS對象指定了一些Strophe中使用到的命名空間

 

NS: {
        HTTPBIND: "http://jabber.org/protocol/httpbind",
        BOSH: "urn:xmpp:xbosh",
        CLIENT: "jabber:client",
        AUTH: "jabber:iq:auth",
        ROSTER: "jabber:iq:roster",
        PROFILE: "jabber:iq:profile",
        DISCO_INFO: "http://jabber.org/protocol/disco#info",
        DISCO_ITEMS: "http://jabber.org/protocol/disco#items",
        MUC: "http://jabber.org/protocol/muc",
        SASL: "urn:ietf:params:xml:ns:xmpp-sasl",
        STREAM: "http://etherx.jabber.org/streams",
        FRAMING: "urn:ietf:params:xml:ns:xmpp-framing",
        BIND: "urn:ietf:params:xml:ns:xmpp-bind",
        SESSION: "urn:ietf:params:xml:ns:xmpp-session",
        VERSION: "jabber:iq:version",
        STANZAS: "urn:ietf:params:xml:ns:xmpp-stanzas",
        XHTML_IM: "http://jabber.org/protocol/xhtml-im",
        XHTML: "http://www.w3.org/1999/xhtml"
    },

同時,如果我們需要擴展命名空間,可以使用addNamespace方法。

addNamespace: function (name, value) {
      Strophe.NS[name] = value;
    },

 

連接狀態

登錄結果各種狀態。

Status: {
      ERROR: 0, // 错误
      CONNECTING: 1, // 连接中
      CONNFAIL: 2, // 连接失败
      AUTHENTICATING: 3, // 认证中
      AUTHFAIL: 4, // 认证失败
      CONNECTED: 5, // 已连接
      DISCONNECTED: 6, // 已断开连接
      DISCONNECTING: 7, // 断开连接中
      ATTACHED: 8, // 附加
      REDIRECT: 9, // 重定向
      CONNTIMEOUT: 10 // 连接超时
  },

 

日誌

Strophe還提供了一個Log實現,當然,只是一個小實現。

我們要通過重寫Strophe.log方法來

LogLevel: {
    DEBUG: 0,
    INFO: 1,
    WARN: 2,
    ERROR: 3,
    FATAL: 4
},

/**
 * 
 * 函数:log
    用户重写打日志函数

    默认的实现没有做任何事情
    如果客户端代码想要去处理日志消息,应该重写这个函数:
    Strophe.log = function(level, msg) {
        // user code here
    };
    以下是不同的等级和它们所代表的意义
参数:
    数值型 level - 日志等级,
    字符串 msg - 日志内容
 */
log: function (level, msg) {
    return;
},

debug: function(msg){
    this.log(this.LogLevel.DEBUG, msg);
},

info: function (msg) {
    this.log(this.LogLevel.INFO, msg);
},

warn: function (msg) {
    this.log(this.LogLevel.WARN, msg);
},

error: function (msg) {
    this.log(this.LogLevel.ERROR, msg);
},

fatal: function (msg) {
    this.log(this.LogLevel.FATAL, msg);
},

 

addConnectionPlugin 添加插件

Strophe的一些其它插件通過該方法加入Strophe中。

/**
 * 扩展Strophe.Connection使之能够接受给定的插件
 * 参数
 *      name - 插件的名称
 *      ptype  - 插件的标准|原型
 */
addConnectionPlugin: function (name, ptype) {
    Strophe._connectionPlugins[name] = ptype;
}

forEachChild

/**
 *
 * elem 传入的元素
 * eleName 子元素标签名过滤器,注意这个是区分大小写的。如果elemName是空的,所有子元素都会被通过这个函数的执
 * 否则只有那些标签名称能够匹配eleName的才会被执行
 * func 找到符合的每个子元素都会调用一次该函数,该函数参数为一个符合要求的DOM类型的元素
 */
forEachChild: function (elem, elemName, func) {
    var i, childNode;

    for (i = 0; i < elem.childNodes.length; i++) {
        childNode = elem.childNodes[i];
        if (childNode.nodeType == Strophe.ElementType.NORMAL &&
            (!elemName || this.isTagEqual(childNode, elemName))) {
            func(childNode);
        }
    }
},

 

示例,解析Vcard節

  • 可以這麼做,但是如果用JQuery來做更方便。
/**
 * 将Vcard节转为Vcard对象。
 */
parseVcardStanzaToVcard : function(stanza) {
    // 前面这几行可以忽略,主要是展示后面使用forEachChild方法。
    var $stanza = $(stanza);
    var vcardTemp = new XoW.Vcard();
    var jid = $stanza.attr('from');
    vcardTemp.jid = jid; 
    var $vcard = $stanza.find('vCard');

    // stanza就是服务器返回的未经处理的Vcard节,
    Strophe.forEachChild(stanza, "vCard", function(vcard) {
        // 解析出该DOM元素中标签为vCard的DOM元素,即<vCard>...</vCard>     
        Strophe.forEachChild(vcard, "N", function(N) {
            // 从Vcard元素中标签为N的元素,即<N>...</N>
            Strophe.forEachChild(N, "FAMILY", function(FAMILY) {
                // 解析出N标签中的<FAMILY></FAMILY>元素。
                vcardTemp.N.FAMILY = FAMILY.textContent;
            });
            Strophe.forEachChild(N, "GIVEN", function(GIVEN) {
                // 解析出N标签中的<GIVEN></GIVEN>元素。
                vcardTemp.N.GIVEN = GIVEN.textContent;
            });
            Strophe.forEachChild(N, "MIDDLE", function(MIDDLE) {
            // 解析出N标签中的<MIDDLE></MIDDLE>元素。
                vcardTemp.N.MIDDLE = MIDDLE.textContent;
            });
        });
        Strophe.forEachChild(vcard, "ORG", function(ORG) {
            Strophe.forEachChild(ORG, "ORGNAME", function(ORGNAME) {
                vcardTemp.ORG.ORGNAME = ORGNAME.textContent;
            });
            Strophe.forEachChild(ORG, "ORGUNIT", function(ORGUNIT) {
                vcardTemp.ORG.ORGUNIT = ORGUNIT.textContent;
            });
        });

        // ... 省略其他代码
    });

    return vcardTemp;
},

isTagEqual

/**
 * 判断某个元素的标签名
 * e1 一个dom元素
 * name 元素的名字
 */
isTagEqual: function (el, name) {
    return el.tagName == name;
},

xmlescape,xmlunescape,serialize

  • xmlescape,xmlunescape 用於轉義標籤
  • serialize用於轉義整個DOM,使之可以打印在HTML中,一般我們要打印DOM的時候可以用這個。
xmlescape: function(text)
{
    text = text.replace(/\&/g, "&amp;");
    text = text.replace(/</g,  "&lt;");
    text = text.replace(/>/g,  "&gt;");
    text = text.replace(/'/g,  "&apos;");
    text = text.replace(/"/g,  "&quot;");
    return text;
},
    xmlunescape: function(text)
{
    text = text.replace(/\&amp;/g, "&");
    text = text.replace(/&lt;/g,  "<");
    text = text.replace(/&gt;/g,  ">");
    text = text.replace(/&apos;/g,  "'");
    text = text.replace(/&quot;/g,  "\"");
    return text;
},

serialize: function (elem) {
    var result;

    // 省略...

    return result;
},

escapeNode,unescapeNode

JID因為它的格式,其中包含/和@符號,在有些地方可能不能直接使用。使用這個方法可以轉義它。

/**
     * 对jid的node部分进行换码,node@domain/resource
     * 
     * 参数
     *      node - 一个node
     * 返回值
     *      换码后的node
     */
    escapeNode: function (node) {
        if (typeof node !== "string") { return node; }
        return node.replace(/^\s+|\s+$/g, '')
            .replace(/\\/g,  "\\5c")
            .replace(/ /g,   "\\20")
            .replace(/\"/g,  "\\22")
            .replace(/\&/g,  "\\26")
            .replace(/\'/g,  "\\27")
            .replace(/\//g,  "\\2f")
            .replace(/:/g,   "\\3a")
            .replace(/</g,   "\\3c")
            .replace(/>/g,   "\\3e")
            .replace(/@/g,   "\\40");
    },

    unescapeNode: function (node) {
        if (typeof node !== "string") { return node; }
        return node.replace(/\\20/g, " ")
            .replace(/\\22/g, '"')
            .replace(/\\26/g, "&")
            .replace(/\\27/g, "'")
            .replace(/\\2f/g, "/")
            .replace(/\\3a/g, ":")
            .replace(/\\3c/g, "<")
            .replace(/\\3e/g, ">")
            .replace(/\\40/g, "@")
            .replace(/\\5c/g, "\\");
    },

與JID相關的幾個方法getNodeFromJid,getDomainFromJid,getResourceFromJid,getBareJidFromJid

/**
 * 从JID中获得node部分。node@domain/resource
 * 参数
 *      jid
 * 返回值
 *      node
 */
getNodeFromJid: function (jid) {
    if (jid.indexOf("@") < 0) { return null; }
    return jid.split("@")[0];
},

/**
 * 从JID中得到domain
 * 参数
 *      jid
 * 返回值
 *      node
 */
getDomainFromJid: function (jid) {
    var bare = Strophe.getBareJidFromJid(jid);
    if (bare.indexOf("@") < 0) {
        return bare;
    } else {
        var parts = bare.split("@");
        parts.splice(0, 1);
        return parts.join('@');
    }
},
/**
 * 从JID中得到resource部分
 * 
 */
getResourceFromJid: function (jid) {
    var s = jid.split("/");
    if (s.length < 2) { return null; }
    s.splice(0, 1);
    return s.join('/');
},
/**
 * 得到纯JID
 */
getBareJidFromJid: function (jid) {
    return jid ? jid.split("/")[0] : null;
},

Strophe.Builder

這個對象提供了一個和JQuery很像的接口,但是構建DOM元素更容易和快速,
所有的函數返回值都是對像類型的,除了toString()和tree(),
所以可以鍊式調用,這裡是一個使用$iq()構建者的例子

方法

  • Builder(name, attrs) 構造函數,兩個參數:該節名稱,節的屬性
  • tree() 返回當前節的DOM對象。返回的對象可以使用Strophe.Connection.send()方法直接發送給服務端
  • up()返回上一級
  • toString() 返回當前節的字符串形式
  • attrs() 添加或者修改當前元素的屬性
  • c(name, attrs, text) 添加節點。參數分別是:節點名稱,節點屬性,節點文本內容。
  • cnode(elem) 這個函數和c()函數以一樣,除了它不是使用name 和attrs 去創建
  • t(text) 添加一個文本子元素
  • h(html) 用HTML替換當前的元素內容

已經默認提供了三種基本的節:

  • 消息節function $msg(attrs) { return new Strophe.Builder(“message”, attrs); }
  • iq節function $iq(attrs) { return new Strophe.Builder(“iq”, attrs); }
  • 出席節function $pres(attrs) { return new Strophe.Builder(“presence”, attrs); }

示例

$iq({to: 'you', from: 'me', type: 'get', id: '1'})
    .c('query', {xmlns: 'strophe:example'})
    .c('example')
    .toString();

以上的代碼會生成如下的XML片段

<iq to='you' from='me' type='get' id='1'>
    <query xmlns='strophe:example'>
        <example/>
    </query>
</iq>

Strophe.Handler和trophe.TimeHandler

這兩個是Strophe內部使用的。

如下是Handler構造函數,我們不直接使用這兩個對象。

参数:
    函数 handler - 触发式执行的函数
    字符串 ns - 需要匹配的命名空间
    字符串 name - 需要匹配的name
    字符串 type - 同上
    字符串 id - 同上
    字符串 from - 同上
    字符串 options - 处理器选项
Handler (handler, ns, name, type, id, from, options)

但是當我們監聽Strophe中是否接收到節的時候,使用Strophe.Connection.addHandler()和Strophe.Connection.deleteHandler()間接使用。TimeHandler也是一樣的。

addHandler: function (handler, ns, name, type, id, from, options) {
    var hand = new Strophe.Handler(handler, ns, name, type, id, from, options);
    this.addHandlers.push(hand);
    return hand;
},

SASL

Strophe.SASLMechanism 驗證機制

優先級,所以如果沒有設置,默認驗證就是用SCRAM-SHA1。

*  SCRAM-SHA1 - 40
*  DIGEST-MD5 - 30
*  Plain - 20

Strophe.Connection

我們的程序主要使用Connection類,用它來建立連接,發送節等操作。

通道有兩種,WebSocket和Bosh。他們對Connection提供了相同的接口。所以在使用Connection時,它會根據你在創建Connection對象時傳進來的參數,判斷需要使用的通道類型。

構造方法中,有這麼一段。其中service就是服務器的地址,如果傳進來的服務器地址以ws:或者wss:開頭,那麼就會使用Websocket作為通道,否則使用Bosh。

Connection (service, options) {
    // ....省略其他代码
    if (service.indexOf("ws:") === 0 || service.indexOf("wss:") === 0 ||
            proto.indexOf("ws") === 0) {
        this._proto = new Strophe.Websocket(this); // proto - WS对象
    } else {
        this._proto = new Strophe.Bosh(this);
    }
    // ....省略其他代码
}

connect, disconnect斷開連接。

// jid,密码,登录结果回调。
connect(jid, pass, callback, wait, hold, route, authcid) {

下面我寫的一段代碼,我的connect方法中,在新建了new Strophe.Connection(serviceURL);後,使用connect與服務器建立連接

connect : function(serviceURL, jid, pass) {
    XoW.logger.ms(this.classInfo + "connect()");

    // 新建一个Strophe.Connection对象,此时并未开始连接服务器
    this._stropheConn = new Strophe.Connection(serviceURL);
    // 重写Stroope的rowInput/Ouput方法用于打印报文。
    this._rawInputOutput();
    // 连接服务器
    this._stropheConn.connect(jid, pass, this._connectCb.bind(this));

    XoW.logger.me(this.classInfo + "connect()");
},

send(elem) 發送節, sendIQ(elem, callback, errback, ti​​meout) 發送IQ節

  • send,發送消息節和出席節的時候使用這個。
  • sendIQ,建議在發送IQ節的時候使用這個。因為iq節一定有一個返回的節。用一個回調去接受響應的節。

示例:發送在線出席節

    var p1 = $pres({
        id : XoW.utils.getUniqueId("presOnline")// 获得唯一的id
    }).c("status")
    .t("在线")
    .up()
    .c("priority")
    .t('1');

    this._gblMgr.getConnMgr().send(p1);
1

示例:發送請求vcard節。

getVcard : function(jid, successCb, errorCb, timeout) {

    if(!jid) {
        jid = null;
    }
    // 没有带from属性,服务器也能知道是“我”发送的,服务器中有做处理。
    vcard = $iq({
        id : XoW.utils.getUniqueId("getVcard"), 
        type : "get", 
        to : jid
    }).c("vCard", {
        xmlns : XoW.NS.VCARD
    });

    this._gblMgr.getConnMgr().sendIQ(vcard, function(stanza) {
        // 成功获取vcard处理
    }.bind(this), function(errorStanza) {
        // 错误处理
    }, timeout);
},

addHandler,deleteHandler,addTimedHandler,deleteTimedHandler

addHandler監聽節。我們通過這個方法,監聽從服務器發送來的節。

/**
 * 添加监听器
 *      ns - 要匹配的命名空间
 *      name - 同上
 *      type - 匹配节的类型
 *      id  - 匹配节的id
 *      from - 匹配节的from
 *      options 处理器的可选项
 */
addHandler: function (handler, ns, name, type, id, from, options) {
    var hand = new Strophe.Handler(handler, ns, name, type, id, from, options);
    this.addHandlers.push(hand);
    return hand;
},

deleteHandler: function (handRef) {
    // this must be done in the Idle loop so that we don't change
    // the handlers during iteration
    this.removeHandlers.push(handRef);
    // If a handler is being deleted while it is being added,
    // prevent it from getting added
    var i = this.addHandlers.indexOf(handRef);
    if (i >= 0) {
        this.addHandlers.splice(i, 1);
    }
},

addTimedHandler: function (period, handler) {
    var thand = new Strophe.TimedHandler(period, handler);
    this.addTimeds.push(thand);
    return thand;
},

deleteTimedHandler: function (handRef) {
    // this must be done in the Idle loop so that we don't change
    // the handlers during iteration
    this.removeTimeds.push(handRef);
},

比如

 // 若不指定具体参数,只给定回调函数,那么监听所有从服务器发送来的节
this._gblMgr.getConnMgr().addHandler(this._needDealCb.bind(this));

// 监听presence节,第一个参数是我的回调函数。
this._gblMgr.getConnMgr().addHandler(this._presenceCb.bind(this), null, "presence");

xmlInput,xmlOutput,rawInput,rawOutput

這四個方法,是發送接收節過程中必定會調用到了,它們都是空實現,這個的作用是讓我們實現他們,來做一些自己想做的事,比如打印每次發送接收的節到控制台,方便調試。

這下面這段是我自定義的一個方法,裡面重寫了rawInput和rawOutput這兩個方法,然後調用自己的日誌類,打印他們。

_rawInputOutput : function() {
    XoW.logger.ms(this.classInfo + "_rawInputOutput()");

    this._stropheConn.rawInput = function(data) {
        XoW.logger.receivePackage(data);
    }.bind(this);
    this._stropheConn.rawOutput = function(data) {
        XoW.logger.sendPackage(data);
    }.bind(this);
    XoW.logger.me(this.classInfo + "_rawInputOutput()");
},

Strohpe.Websocket,Strophe.Bosh 和Strophe.Request

Bosh和Websocket類作為底層連接類,提供一些連接方法的處理。我們的關注點主要是在Connection類。

使用插件時並不會調用到這邊的方法。