Strophe简介与Openfire配置
Strophe.js是为XMPP写的一个js类库。因为http协议本身不能实现持久连接,所以strophe利用BOSH模拟实现持久连接。
官方文档:
http://strophe.im/strophejs/doc/1.2.15/files/strophe-js.html
https://stackoverflow.com/questions/17311901/strophe-js-giving-authfail-status-always
Strophe操作相关插件:
https://github.com/ggozad/strophe.plugins/
https://github.com/gm19900510/strophejs-plugins
原文链接: https://www.cnblogs.com/gmhappy/p/9472406.html
连接状态常量发起一个链接后,会返回一个连接状态
Status.ERROR 错误 Status.CONNECTING 正在创建连接 Status.CONNFAIL 连接创建失败 Status.AUTHENTICATING 正在验证 Status.AUTHFAIL 验证失败 Status.CONNECTED 连接创建成功 Status.DISCONNECTED 连接已关闭 Status.DISCONNECTING 连接正在关闭XMPP服务器通常会实现BOSH扩展,下面是Openfire和Tigase的BOSH默认URL:
Openfire:http://host:7070/http-bind
Tigase:http://host:5280
在使用Strophe.js的时候,需要使用对应的HTTP地址才能连接上XMPP服务器。
如果使用Opnefire,还需要在管理后台配置一下:
注意图中的pc-20170308pkrs是下图的服务器名称
XMPP协议简介:
XMPP服务器和客户端之间,是通过XML节(XML Stanza)来进行通讯。其中有三种非常重要的XML Stanza类型:<message>、<presence>、<iq>。<message>:
聊天消息的发送和接收就是通过message节来实现。例如xxg1@host发送一条信息"你好"给xxg2@host,xxg1@host客户端会将下面的这段XML发送到XMPP服务器,服务器再推送给xxg2@host客户端。其中<message>的from属性是发送者,to属性是接收者,<body>子元素的内容就是聊天信息。
<message from="a@pc-20170308pkrs" to="b@pc-20170308pkrs" type="chat"> <body>你好</body> </message>
<presence>:
可用于表明用户的状态,例如用户状态改变成“Do not disturb”(“请勿打扰”),会向服务器发送:<presence from="xxg@host"> <status>Do not disturb</status> <priority>0</priority> <show>dnd</show> </presence>
<iq>:
iq即Info/Query,采用“请求-响应”机制,类似于HTTP的机制。下面的例子是客户端通过<iq>请求获取联系人,XMPP服务器将结果返回:
客户端请求获取联系人:<iq from='a@pc-20170308pkrs' id='bv1bs71f' type='get'> <query xmlns='jabber:iq:roster'/> </iq>服务器结果返回:
<iq to='a@pc-20170308pkrs' id='bv1bs71f' type='result'> <query xmlns='jabber:iq:roster'> <item jid='b@pc-20170308pkrs'/> <item jid='c@pc-20170308pkrs'/> </query> </iq>
构建WebIM
新建echobot.html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head> <title>Strophe.js Echobot Example</title> <script type='text/javascript' src='jquery-1.9.1.min.js'></script> <script type='text/javascript' src='strophe.min.js'></script> <script type='text/javascript' src='echobot.js'></script></head><body> JID:<input type="text" id="input-jid" value="gm@pc-20170308pkrs"> <br> 密码:<input type="password" id="input-pwd"> <br> <button id="btn-login">登录</button> <div id="msg" style="height: 400px; width: 400px; overflow: scroll;"></div> 联系人JID: <input type="text" id="input-contacts"> <br> 消息: <br> <textarea id="input-msg" cols="30" rows="4"></textarea> <br> <button id="btn-send">发送</button></body></html>
新建echobot.js
var BOSH_SERVICE = 'http://pc-20170308pkrs:7070/http-bind/'; // XMPP连接 var connection = null; // 当前状态是否连接 var connected = false; // 当前登录的JID var jid = ""; // 连接状态改变的事件 function onConnect(status) { console.log('status: ' + status) if (status == Strophe.Status.CONNFAIL) { alert("连接失败!"); } else if (status == Strophe.Status.AUTHFAIL) { alert("登录失败!"); } else if (status == Strophe.Status.DISCONNECTED) { alert("连接断开!"); connected = false; } else if (status == Strophe.Status.CONNECTED) { alert("连接成功,可以开始聊天了!"); console.log('pubsub',connection) connected = true; // 当接收到<message>节,调用onMessage回调函数 connection.addHandler(onMessage, null, null, null, null, null); // 首先要发送一个<presence>给服务器(initial presence) connection.send($pres().tree()); //获取订阅的主题信息 connection.pubsub.getSubscriptions(onMessage,5000); } } // 接收到<message>function onMessage(msg) { console.log('--- msg ---', msg); // 解析出<message>的from、type属性,以及body子元素 var from = msg.getAttribute('from'); var type = msg.getAttribute('type'); var elems = msg.getElementsByTagName('body'); if (type == "chat" && elems.length > 0) { var body = elems[0]; $("#msg").append(from + ":<br>" + Strophe.getText(body) + "<br>") } return true; } $(document).ready(function() { // 通过BOSH连接XMPP服务器 $('#btn-login').click(function() { if(!connected) { console.log('jid: ' + $("#input-jid").val()); console.log('pwd: ' + $("#input-pwd").val()); connection = new Strophe.Connection(BOSH_SERVICE); connection.connect($("#input-jid").val(), $("#input-pwd").val(), onConnect); jid = $("#input-jid").val(); } }); // 发送消息 $("#btn-send").click(function() { if(connected) { if($("#input-contacts").val() == '') { alert("请输入联系人!"); return; } // 创建一个<message>元素并发送 var msg = $msg({ to: $("#input-contacts").val(), from: jid, type: 'chat' }).c("body", null, $("#input-msg").val()); connection.send(msg.tree()); $("#msg").append(jid + ":<br>" + $("#input-msg").val() + "<br>"); $("#input-msg").val(''); } else { alert("请先登录!"); } }); });
strophe.plugins插件使用(connection+“插件名称”+ “对应方法”)
connection.pubsub.getSubscriptions(onMessage,5000);
新建strophe.pubsub.js
/* This program is distributed under the terms of the MIT license. Please see the LICENSE file for details. Copyright 2008, Stanziq Inc. Overhauled in October 2009 by Liam Breck [How does this affect copyright?] */ /** File: strophe.pubsub.js * A Strophe plugin for XMPP Publish-Subscribe. * * Provides Strophe.Connection.pubsub object, * parially implementing XEP 0060. * * Strophe.Builder.prototype methods should probably move to strophe.js */ /** Function: Strophe.Builder.form * Add an options form child element. * * Does not change the current element. * * Parameters: * (String) ns - form namespace. * (Object) options - form properties. * * Returns: * The Strophe.Builder object. */ Strophe.Builder.prototype.form = function (ns, options) { var aX = this.node.appendChild(Strophe.xmlElement('x', {"xmlns": "jabber:x:data", "type": "submit"})); aX.appendChild(Strophe.xmlElement('field', {"var":"FORM_TYPE", "type": "hidden"})) .appendChild(Strophe.xmlElement('value')) .appendChild(Strophe.xmlTextNode(ns)); for (var i in options) { aX.appendChild(Strophe.xmlElement('field', {"var": i})) .appendChild(Strophe.xmlElement('value')) .appendChild(Strophe.xmlTextNode(options[i])); } return this; }; /** Function: Strophe.Builder.list * Add many child elements. * * Does not change the current element. * * Parameters: * (String) tag - tag name for children. * (Array) array - list of objects with format: * { attrs: { [string]:[string], ... }, // attributes of each tag element * data: [string | XML_element] } // contents of each tag element * * Returns: * The Strophe.Builder object. */ Strophe.Builder.prototype.list = function (tag, array) { for (var i=0; i < array.length; ++i) { this.c(tag, array[i].attrs) this.node.appendChild(array[i].data.cloneNode ? array[i].data.cloneNode(true) : Strophe.xmlTextNode(array[i].data)); this.up(); } return this; }; Strophe.Builder.prototype.children = function (object) { var key, value; for (key in object) { if (!object.hasOwnProperty(key)) continue; value = object[key]; if (Array.isArray(value)) { this.list(key, value); } else if (typeof value === 'string') { this.c(key, {}, value); } else if (typeof value === 'number') { this.c(key, {}, ""+value); } else if (typeof value === 'object') { this.c(key).children(value).up(); } else { this.c(key).up(); } } return this; }; // TODO Ideas Adding possible conf values? /* Extend Strophe.Connection to have member 'pubsub'. */Strophe.addConnectionPlugin('pubsub', { /*Extend connection object to have plugin name 'pubsub'.*/ _connection: null, _autoService: true, service: null, jid: null, //The plugin must have the init function. init: function(conn) { this._connection = conn; /* Function used to setup plugin. */ /* extend name space * NS.PUBSUB - XMPP Publish Subscribe namespace * from XEP 60. * * NS.PUBSUB_SUBSCRIBE_OPTIONS - XMPP pubsub * options namespace from XEP 60. */ Strophe.addNamespace('PUBSUB',"http://jabber.org/protocol/pubsub"); Strophe.addNamespace('PUBSUB_SUBSCRIBE_OPTIONS', Strophe.NS.PUBSUB+"#subscribe_options"); Strophe.addNamespace('PUBSUB_ERRORS',Strophe.NS.PUBSUB+"#errors"); Strophe.addNamespace('PUBSUB_EVENT',Strophe.NS.PUBSUB+"#event"); Strophe.addNamespace('PUBSUB_OWNER',Strophe.NS.PUBSUB+"#owner"); Strophe.addNamespace('PUBSUB_AUTO_CREATE', Strophe.NS.PUBSUB+"#auto-create"); Strophe.addNamespace('PUBSUB_PUBLISH_OPTIONS', Strophe.NS.PUBSUB+"#publish-options"); Strophe.addNamespace('PUBSUB_NODE_CONFIG', Strophe.NS.PUBSUB+"#node_config"); Strophe.addNamespace('PUBSUB_CREATE_AND_CONFIGURE', Strophe.NS.PUBSUB+"#create-and-configure"); Strophe.addNamespace('PUBSUB_SUBSCRIBE_AUTHORIZATION', Strophe.NS.PUBSUB+"#subscribe_authorization"); Strophe.addNamespace('PUBSUB_GET_PENDING', Strophe.NS.PUBSUB+"#get-pending"); Strophe.addNamespace('PUBSUB_MANAGE_SUBSCRIPTIONS', Strophe.NS.PUBSUB+"#manage-subscriptions"); Strophe.addNamespace('PUBSUB_META_DATA', Strophe.NS.PUBSUB+"#meta-data"); Strophe.addNamespace('ATOM', "http://www.w3.org/2005/Atom"); if (conn.disco) conn.disco.addFeature(Strophe.NS.PUBSUB); }, // Called by Strophe on connection event statusChanged: function (status, condition) { var that = this._connection; if (this._autoService && status === Strophe.Status.CONNECTED) { this.service = 'pubsub.'+Strophe.getDomainFromJid(that.jid); this.jid = that.jid; } }, /***Function Parameters: (String) jid - The node owner's jid. (String) service - The name of the pubsub service. */ connect: function (jid, service) { var that = this._connection; if (service === undefined) { service = jid; jid = undefined; } this.jid = jid || that.jid; this.service = service || null; this._autoService = false; }, /***Function Create a pubsub node on the given service with the given node name. Parameters: (String) node - The name of the pubsub node. (Dictionary) options - The configuration options for the node. (Function) call_back - Used to determine if node creation was sucessful. Returns: Iq id used to send subscription. */ createNode: function(node,options, call_back) { var that = this._connection; var iqid = that.getUniqueId("pubsubcreatenode"); var iq = $iq({from:this.jid, to:this.service, type:'set', id:iqid}) .c('pubsub', {xmlns:Strophe.NS.PUBSUB}) .c('create',{node:node}); if(options) { iq.up().c('configure').form(Strophe.NS.PUBSUB_NODE_CONFIG, options); } that.addHandler(call_back, null, 'iq', null, iqid, null); that.send(iq.tree()); return iqid; }, /** Function: deleteNode * Delete a pubsub node. * * Parameters: * (String) node - The name of the pubsub node. * (Function) call_back - Called on server response. * * Returns: * Iq id */ deleteNode: function(node, call_back) { var that = this._connection; var iqid = that.getUniqueId("pubsubdeletenode"); var iq = $iq({from:this.jid, to:this.service, type:'set', id:iqid}) .c('pubsub', {xmlns:Strophe.NS.PUBSUB_OWNER}) .c('delete', {node:node}); that.addHandler(call_back, null, 'iq', null, iqid, null); that.send(iq.tree()); return iqid; }, /** Function * * Get all nodes that currently exist. * * Parameters: * (Function) success - Used to determine if node creation was sucessful. * (Function) error - Used to determine if node * creation had errors. */ discoverNodes: function(success, error, timeout) { //ask for all nodes var iq = $iq({from:this.jid, to:this.service, type:'get'}) .c('query', { xmlns:Strophe.NS.DISCO_ITEMS }); return this._connection.sendIQ(iq.tree(),success, error, timeout); }, /** Function: getConfig * Get node configuration form. * * Parameters: * (String) node - The name of the pubsub node. * (Function) call_back - Receives config form. * * Returns: * Iq id */ getConfig: function (node, call_back) { var that = this._connection; var iqid = that.getUniqueId("pubsubconfigurenode"); var iq = $iq({from:this.jid, to:this.service, type:'get', id:iqid}) .c('pubsub', {xmlns:Strophe.NS.PUBSUB_OWNER}) .c('configure', {node:node}); that.addHandler(call_back, null, 'iq', null, iqid, null); that.send(iq.tree()); return iqid; }, /** * Parameters: * (Function) call_back - Receives subscriptions. * * http://xmpp.org/extensions/tmp/xep-0060-1.13.html * 8.3 Request Default Node Configuration Options * * Returns: * Iq id */ getDefaultNodeConfig: function(call_back) { var that = this._connection; var iqid = that.getUniqueId("pubsubdefaultnodeconfig"); var iq = $iq({from:this.jid, to:this.service, type:'get', id:iqid}) .c('pubsub', {'xmlns':Strophe.NS.PUBSUB_OWNER}) .c('default'); that.addHandler(call_back, null, 'iq', null, iqid, null); that.send(iq.tree()); return iqid; }, /***Function Subscribe to a node in order to receive event items. Parameters: (String) node - The name of the pubsub node. (Array) options - The configuration options for the node. (Function) event_cb - Used to recieve subscription events. (Function) success - callback function for successful node creation. (Function) error - error callback function. (Boolean) barejid - use barejid creation was sucessful. Returns: Iq id used to send subscription. */ subscribe: function(node, options, event_cb, success, error, barejid) { var that = this._connection; var iqid = that.getUniqueId("subscribenode"); var jid = this.jid; if(barejid) jid = Strophe.getBareJidFromJid(jid); var iq = $iq({from:this.jid, to:this.service, type:'set', id:iqid}) .c('pubsub', { xmlns:Strophe.NS.PUBSUB }) .c('subscribe', {'node':node, 'jid':jid}); if(options) { iq.up().c('options').form(Strophe.NS.PUBSUB_SUBSCRIBE_OPTIONS, options); } //add the event handler to receive items that.addHandler(event_cb, null, 'message', null, null, null); that.sendIQ(iq.tree(), success, error); return iqid; }, /***Function Unsubscribe from a node. Parameters: (String) node - The name of the pubsub node. (Function) success - callback function for successful node creation. (Function) error - error callback function. */ unsubscribe: function(node, jid, subid, success, error) { var that = this._connection; var iqid = that.getUniqueId("pubsubunsubscribenode"); var iq = $iq({from:this.jid, to:this.service, type:'set', id:iqid}) .c('pubsub', { xmlns:Strophe.NS.PUBSUB }) .c('unsubscribe', {'node':node, 'jid':jid}); if (subid) iq.attrs({subid:subid}); that.sendIQ(iq.tree(), success, error); return iqid; }, /***Function Publish and item to the given pubsub node. Parameters: (String) node - The name of the pubsub node. (Array) items - The list of items to be published. (Function) call_back - Used to determine if node creation was sucessful. */ publish: function(node, items, call_back) { var that = this._connection; var iqid = that.getUniqueId("pubsubpublishnode"); var iq = $iq({from:this.jid, to:this.service, type:'set', id:iqid}) .c('pubsub', { xmlns:Strophe.NS.PUBSUB }) .c('publish', { node:node, jid:this.jid }) .list('item', items); that.addHandler(call_back, null, 'iq', null, iqid, null); that.send(iq.tree()); return iqid; }, /*Function: items Used to retrieve the persistent items from the pubsub node. */ items: function(node, success, error, timeout) { //ask for all items var iq = $iq({from:this.jid, to:this.service, type:'get'}) .c('pubsub', { xmlns:Strophe.NS.PUBSUB }) .c('items', {node:node}); return this._connection.sendIQ(iq.tree(), success, error, timeout); }, /** Function: getSubscriptions * Get subscriptions of a JID. * * Parameters: * (Function) call_back - Receives subscriptions. * * http://xmpp.org/extensions/tmp/xep-0060-1.13.html * 5.6 Retrieve Subscriptions * * Returns: * Iq id */ getSubscriptions: function(call_back, timeout) { var that = this._connection; var iqid = that.getUniqueId("pubsubsubscriptions"); var iq = $iq({from:this.jid, to:this.service, type:'get', id:iqid}) .c('pubsub', {'xmlns':Strophe.NS.PUBSUB}) .c('subscriptions'); that.addHandler(call_back, null, 'iq', null, iqid, null); that.send(iq.tree()); console.log('-- iq pubsub --',iq.tree()) return iqid; }, /** Function: getNodeSubscriptions * Get node subscriptions of a JID. * * Parameters: * (Function) call_back - Receives subscriptions. * * http://xmpp.org/extensions/tmp/xep-0060-1.13.html * 5.6 Retrieve Subscriptions * * Returns: * Iq id */ getNodeSubscriptions: function(node, call_back) { var that = this._connection; var iqid = that.getUniqueId("pubsubsubscriptions"); var iq = $iq({from:this.jid, to:this.service, type:'get', id:iqid}) .c('pubsub', {'xmlns':Strophe.NS.PUBSUB_OWNER}) .c('subscriptions', {'node':node}); that.addHandler(call_back, null, 'iq', null, iqid, null); that.send(iq.tree()); return iqid; }, /** Function: getSubOptions * Get subscription options form. * * Parameters: * (String) node - The name of the pubsub node. * (String) subid - The subscription id (optional). * (Function) call_back - Receives options form. * * Returns: * Iq id */ getSubOptions: function(node, subid, call_back) { var that = this._connection; var iqid = that.getUniqueId("pubsubsuboptions"); var iq = $iq({from:this.jid, to:this.service, type:'get', id:iqid}) .c('pubsub', {xmlns:Strophe.NS.PUBSUB}) .c('options', {node:node, jid:this.jid}); if (subid) iq.attrs({subid:subid}); that.addHandler(call_back, null, 'iq', null, iqid, null); that.send(iq.tree()); return iqid; }, /** * Parameters: * (String) node - The name of the pubsub node. * (Function) call_back - Receives subscriptions. * * http://xmpp.org/extensions/tmp/xep-0060-1.13.html * 8.9 Manage Affiliations - 8.9.1.1 Request * * Returns: * Iq id */ getAffiliations: function(node, call_back) { var that = this._connection; var iqid = that.getUniqueId("pubsubaffiliations"); if (typeof node === 'function') { call_back = node; node = undefined; } var attrs = {}, xmlns = {'xmlns':Strophe.NS.PUBSUB}; if (node) { attrs.node = node; xmlns = {'xmlns':Strophe.NS.PUBSUB_OWNER}; } var iq = $iq({from:this.jid, to:this.service, type:'get', id:iqid}) .c('pubsub', xmlns).c('affiliations', attrs); that.addHandler(call_back, null, 'iq', null, iqid, null); that.send(iq.tree()); return iqid; }, /** * Parameters: * (String) node - The name of the pubsub node. * (Function) call_back - Receives subscriptions. * * http://xmpp.org/extensions/tmp/xep-0060-1.13.html * 8.9.2 Modify Affiliation - 8.9.2.1 Request * * Returns: * Iq id */ setAffiliation: function(node, jid, affiliation, call_back) { var that = this._connection; var iqid = thiat.getUniqueId("pubsubaffiliations"); var iq = $iq({from:this.jid, to:this.service, type:'set', id:iqid}) .c('pubsub', {'xmlns':Strophe.NS.PUBSUB_OWNER}) .c('affiliations', {'node':node}) .c('affiliation', {'jid':jid, 'affiliation':affiliation}); that.addHandler(call_back, null, 'iq', null, iqid, null); that.send(iq.tree()); return iqid; }, /** Function: publishAtom */ publishAtom: function(node, atoms, call_back) { if (!Array.isArray(atoms)) atoms = [atoms]; var i, atom, entries = []; for (i = 0; i < atoms.length; i++) { atom = atoms[i]; atom.updated = atom.updated || (new Date()).toISOString(); if (atom.published && atom.published.toISOString) atom.published = atom.published.toISOString(); entries.push({ data: $build("entry", { xmlns:Strophe.NS.ATOM }) .children(atom).tree(), attrs:(atom.id ? { id:atom.id } : {}), }); } return this.publish(node, entries, call_back); }, });
效果: