-
- 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, timeout) 發送IQ節
- addHandler,deleteHandler,addTimedHandler,deleteTimedHandler
- xmlInput,xmlOutput,rawInput,rawOutput
- Strohpe.Websocket,Strophe.Bosh 和Strophe.Request
- Strophe對像中
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
- 通訊通道一:Bosh
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, "&"); text = text.replace(/</g, "<"); text = text.replace(/>/g, ">"); text = text.replace(/'/g, "'"); text = text.replace(/"/g, """); return text; }, xmlunescape: function(text) { text = text.replace(/\&/g, "&"); text = text.replace(/</g, "<"); text = text.replace(/>/g, ">"); text = text.replace(/'/g, "'"); text = text.replace(/"/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, timeout) 發送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類。
使用插件時並不會調用到這邊的方法。