UNPKG

hx-websdk

Version:
1,618 lines (1,456 loc) 152 kB
var _version = '1.4.13'; var _code = require('./status').code; var _utils = require('./utils').utils; var _msg = require('./message'); var _message = _msg._msg; var _msgHash = {}; var Queue = require('./queue').Queue; var CryptoJS = require('crypto-js'); var _ = require('underscore'); var Strophe = window.Strophe var isStropheLog; var stropheConn = {} var hxStrophe = require('hx-strophe') // import { Strophe, $build, $iq, $msg, $pres } from 'hx-strophe' // var Strophe = require('hx-strophe').Strophe // // var Strophe = require('./strophe.js').Strophe; // var meStrophe = require('hx-strophe').Strophe var { Strophe, $build, $iq, $msg, $pres } = hxStrophe.default.Strophe // $iq = meStrophe.$iq; // $build = meStrophe.$build; // $msg = meStrophe.$msg; // $pres = meStrophe.$pres; window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL; var logMessage = function (message) { } if (window.XDomainRequest) { // not support ie8 send is not a function , canot // case send is object, doesn't has a attr of call // XDomainRequest.prototype.oldsend = XDomainRequest.prototype.send; // XDomainRequest.prototype.send = function () { // XDomainRequest.prototype.oldsend.call(this, arguments); // this.readyState = 2; // }; } Strophe.Request.prototype._newXHR = function () { var xhr = _utils.xmlrequest(true); if (xhr.overrideMimeType) { xhr.overrideMimeType('text/xml'); } // use Function.bind() to prepend ourselves as an argument xhr.onreadystatechange = this.func.bind(null, this); return xhr; }; Strophe.Websocket.prototype._closeSocket = function () { if (this.socket) { var me = this; setTimeout(function () { try { me.socket.close(); } catch (e) { } }, 0); } else { this.socket = null; } }; /** Function: log * User overrideable logging function. * * This function is called whenever the Strophe library calls any * of the logging functions. The default implementation of this * function does nothing. If client code wishes to handle the logging * messages, it should override this with * > Strophe.log = function (level, msg) { * > (user code here) * > }; * * Please note that data sent and received over the wire is logged * via Strophe.Connection.rawInput() and Strophe.Connection.rawOutput(). * * The different levels and their meanings are * * DEBUG - Messages useful for debugging purposes. * INFO - Informational messages. This is mostly information like * 'disconnect was called' or 'SASL auth succeeded'. * WARN - Warnings about potential problems. This is mostly used * to report transient connection errors like request timeouts. * ERROR - Some error occurred. * FATAL - A non-recoverable fatal error occurred. * * Parameters: * (Integer) level - The log level of the log message. This will * be one of the values in Strophe.LogLevel. * (String) msg - The log message. */ /* jshint ignore:start */ Strophe.log = function (level, msg) { if(!isStropheLog){ return } switch(level){ case this.LogLevel.DEBUG: console.debug(msg) break; case this.LogLevel.INFO: console.info(msg); break; case this.LogLevel.WARN: console.warn(msg); break; case this.LogLevel.ERROR: case this.LogLevel.FATAL: console.error(msg); break; default: console.log(msg); } return; } /** * * Strophe.Websocket has a bug while logout: * 1.send: <presence xmlns='jabber:client' type='unavailable'/> is ok; * 2.send: <close xmlns='urn:ietf:params:xml:ns:xmpp-framing'/> will cause a problem,log as follows: * WebSocket connection to 'ws://im-api.easemob.com/ws/' failed: Data frame received after close_connect @ strophe.js:5292connect @ strophe.js:2491_login @ websdk-1.1.2.js:278suc @ websdk-1.1.2.js:636xhr.onreadystatechange @ websdk-1.1.2.js:2582 * 3 "Websocket error [object Event]" * _changeConnectStatus * onError Object {type: 7, msg: "The WebSocket connection could not be established or was disconnected.", reconnect: true} * * this will trigger socket.onError, therefore _doDisconnect again. * Fix it by overide _onMessage */ Strophe.Websocket.prototype._onMessage = function (message) { logMessage(message) // 获取Resource var data = message.data; if (data.indexOf('<jid>') > 0) { var start = data.indexOf('<jid>'), end = data.indexOf('</jid>'), data = data.substring(start + 5, end); stropheConn.setJid = function (jid) { this.jid = jid; this.authzid = Strophe.getBareJidFromJid(this.jid); this.authcid = Strophe.getNodeFromJid(this.jid); // console.log(this) } stropheConn.getJid = function () { return this.jid; } // console.log(stropheConn) stropheConn.setJid(data); } var elem, data; // check for closing stream // var close = '<close xmlns="urn:ietf:params:xml:ns:xmpp-framing" />'; // if (message.data === close) { // this._conn.rawInput(close); // this._conn.xmlInput(message); // if (!this._conn.disconnecting) { // this._conn._doDisconnect(); // } // return; // // send and receive close xml: <close xmlns='urn:ietf:params:xml:ns:xmpp-framing'/> // so we can't judge whether message.data equals close by === simply. if (message.data.indexOf("<close ") === 0) { elem = new DOMParser().parseFromString(message.data, "text/xml").documentElement; var see_uri = elem.getAttribute("see-other-uri"); if (see_uri) { this._conn._changeConnectStatus(Strophe.Status.REDIRECT, "Received see-other-uri, resetting connection"); this._conn.reset(); this._conn.service = see_uri; this._connect(); } else { // if (!this._conn.disconnecting) { this._conn._doDisconnect("receive <close> from server"); // } } return; } else if (message.data.search("<open ") === 0) { // This handles stream restarts elem = new DOMParser().parseFromString(message.data, "text/xml").documentElement; if (!this._handleStreamStart(elem)) { return; } } else { data = this._streamWrap(message.data); elem = new DOMParser().parseFromString(data, "text/xml").documentElement; } if (this._check_streamerror(elem, Strophe.Status.ERROR)) { return; } //handle unavailable presence stanza before disconnecting if (this._conn.disconnecting && elem.firstChild.nodeName === "presence" && elem.firstChild.getAttribute("type") === "unavailable") { this._conn.xmlInput(elem); this._conn.rawInput(Strophe.serialize(elem)); // if we are already disconnecting we will ignore the unavailable stanza and // wait for the </stream:stream> tag before we close the connection return; } this._conn._dataRecv(elem, message.data); }; var _listenNetwork = function (onlineCallback, offlineCallback) { if (window.addEventListener) { window.addEventListener('online', onlineCallback); window.addEventListener('offline', offlineCallback); } else if (window.attachEvent) { if (document.body) { document.body.attachEvent('ononline', onlineCallback); document.body.attachEvent('onoffline', offlineCallback); } else { window.attachEvent('load', function () { document.body.attachEvent('ononline', onlineCallback); document.body.attachEvent('onoffline', offlineCallback); }); } } else { /*var onlineTmp = window.ononline; var offlineTmp = window.onoffline; window.attachEvent('ononline', function () { try { typeof onlineTmp === 'function' && onlineTmp(); } catch ( e ) {} onlineCallback(); }); window.attachEvent('onoffline', function () { try { typeof offlineTmp === 'function' && offlineTmp(); } catch ( e ) {} offlineCallback(); });*/ } }; var _parseRoom = function (result) { var rooms = []; var items = result.getElementsByTagName('item'); if (items) { for (var i = 0; i < items.length; i++) { var item = items[i]; var roomJid = item.getAttribute('jid'); var tmp = roomJid.split('@')[0]; var room = { jid: roomJid, name: item.getAttribute('name'), roomId: tmp.split('_')[1] }; rooms.push(room); } } return rooms; }; var _parseRoomOccupants = function (result) { var occupants = []; var items = result.getElementsByTagName('item'); if (items) { for (var i = 0; i < items.length; i++) { var item = items[i]; var room = { jid: item.getAttribute('jid'), name: item.getAttribute('name') }; occupants.push(room); } } return occupants; }; var _parseResponseMessage = function (msginfo) { var parseMsgData = {errorMsg: true, data: []}; var msgBodies = msginfo.getElementsByTagName('body'); if (msgBodies) { for (var i = 0; i < msgBodies.length; i++) { var msgBody = msgBodies[i]; var childNodes = msgBody.childNodes; if (childNodes && childNodes.length > 0) { var childNode = msgBody.childNodes[0]; if (childNode.nodeType == Strophe.ElementType.TEXT) { var jsondata = childNode.wholeText || childNode.nodeValue; jsondata = jsondata.replace('\n', '<br>'); try { var data = eval('(' + jsondata + ')'); parseMsgData.errorMsg = false; parseMsgData.data = [data]; } catch (e) { } } } } var delayTags = msginfo.getElementsByTagName('delay'); if (delayTags && delayTags.length > 0) { var delayTag = delayTags[0]; var delayMsgTime = delayTag.getAttribute('stamp'); if (delayMsgTime) { parseMsgData.delayTimeStamp = delayMsgTime; } } } else { var childrens = msginfo.childNodes; if (childrens && childrens.length > 0) { var child = msginfo.childNodes[0]; if (child.nodeType == Strophe.ElementType.TEXT) { try { var data = eval('(' + child.nodeValue + ')'); parseMsgData.errorMsg = false; parseMsgData.data = [data]; } catch (e) { } } } } return parseMsgData; }; var _parseNameFromJidFn = function (jid, domain) { domain = domain || ''; var tempstr = jid; var findex = tempstr.indexOf('_'); if (findex !== -1) { tempstr = tempstr.substring(findex + 1); } var atindex = tempstr.indexOf('@' + domain); if (atindex !== -1) { tempstr = tempstr.substring(0, atindex); } return tempstr; }; var _parseFriend = function (queryTag, conn, from) { var rouster = []; var items = queryTag.getElementsByTagName('item'); if (items) { for (var i = 0; i < items.length; i++) { var item = items[i]; var jid = item.getAttribute('jid'); if (!jid) { continue; } var subscription = item.getAttribute('subscription'); var friend = { subscription: subscription, jid: jid }; var ask = item.getAttribute('ask'); if (ask) { friend.ask = ask; } var name = item.getAttribute('name'); if (name) { friend.name = name; } else { var n = _parseNameFromJidFn(jid); friend.name = n; } var groups = []; Strophe.forEachChild(item, 'group', function (group) { groups.push(Strophe.getText(group)); }); friend.groups = groups; rouster.push(friend); // B同意之后 -> B订阅A // fix: 含有ask标示的好友代表已经发送过反向订阅消息,不需要再次发送。 if (conn && (subscription == 'from') && !ask) { conn.subscribe({ toJid: jid, message: "[resp:true]" }); } if (conn && (subscription == 'to')) { conn.subscribed({ toJid: jid }); } } } return rouster; }; var _login = function (options, conn) { if(!options){ return; } var accessToken = options.access_token || ''; if (accessToken == '') { var loginfo = _utils.stringify(options); conn.onError({ type: _code.WEBIM_CONNCTION_OPEN_USERGRID_ERROR, data: options }); return; } conn.context.accessToken = options.access_token; conn.context.accessTokenExpires = options.expires_in; if (conn.isOpening() && conn.context.stropheConn) { stropheConn = conn.getStrophe(); } else if (conn.isOpened() && conn.context.stropheConn) { // return; stropheConn = conn.getStrophe(); } else { stropheConn = conn.getStrophe(); } var callback = function (status, msg) { _loginCallback(status, msg, conn); }; conn.context.stropheConn = stropheConn; if (conn.route) { stropheConn.connect(conn.context.jid, '$t$' + accessToken, callback, conn.wait, conn.hold, conn.route); } else { stropheConn.connect(conn.context.jid, '$t$' + accessToken, callback, conn.wait, conn.hold); } }; var _parseMessageType = function (msginfo) { var receiveinfo = msginfo.getElementsByTagName('received'), inviteinfo = msginfo.getElementsByTagName('invite'), deliveryinfo = msginfo.getElementsByTagName('delivery'), acked = msginfo.getElementsByTagName('acked'), error = msginfo.getElementsByTagName('error'), msgtype = 'normal'; if (receiveinfo && receiveinfo.length > 0 && receiveinfo[0].namespaceURI === 'urn:xmpp:receipts') { msgtype = 'received'; } else if (inviteinfo && inviteinfo.length > 0) { msgtype = 'invite'; } else if (deliveryinfo && deliveryinfo.length > 0) { msgtype = 'delivery'; // 消息送达 } else if (acked && acked.length) { msgtype = 'acked'; // 消息已读 } else if (error && error.length) { var errorItem = error[0], userMuted = errorItem.getElementsByTagName('user-muted'); if (userMuted && userMuted.length) { msgtype = 'userMuted'; } } return msgtype; }; var _handleMessageQueue = function (conn) { for (var i in _msgHash) { if (_msgHash.hasOwnProperty(i)) { _msgHash[i].send(conn); } } }; var _loginCallback = function (status, msg, conn) { var conflict, error; if (msg === 'conflict') { conflict = true; conn.close(); } if (status == Strophe.Status.CONNFAIL) { //client offline, ping/pong timeout, server quit, server offline error = { type: _code.WEBIM_CONNCTION_SERVER_CLOSE_ERROR, msg: msg, reconnect: true }; conflict && (error.conflict = true); conn.onError(error); } else if (status == Strophe.Status.ATTACHED || status == Strophe.Status.CONNECTED) { // client should limit the speed of sending ack messages up to 5/s (后修改为10/s途虎重复收到离线消息) conn.autoReconnectNumTotal = 0; conn.intervalId = setInterval(function () { conn.handelSendQueue(); }, 100); var handleMessage = function (msginfo) { var delivery = msginfo.getElementsByTagName('delivery'); var acked = msginfo.getElementsByTagName('acked'); if (delivery.length) { conn.handleDeliveredMessage(msginfo); return true; } if (acked.length) { conn.handleAckedMessage(msginfo); return true; } var type = _parseMessageType(msginfo); switch (type) { case "received": conn.handleReceivedMessage(msginfo); return true; case "invite": conn.handleInviteMessage(msginfo); return true; case "delivery": conn.handleDeliveredMessage(msginfo); return true; case "acked": conn.handleAckedMessage(msginfo); return true; case "userMuted": conn.handleMutedMessage(msginfo); return true; default: conn.handleMessage(msginfo); return true; } }; var handlePresence = function (msginfo) { conn.handlePresence(msginfo); return true; }; var handlePing = function (msginfo) { conn.handlePing(msginfo); return true; }; var handleIqRoster = function (msginfo) { conn.handleIqRoster(msginfo); return true; }; var handleIqPrivacy = function (msginfo) { conn.handleIqPrivacy(msginfo); return true; }; var handleIq = function (msginfo) { conn.handleIq(msginfo); return true; }; conn.addHandler(handleMessage, null, 'message', null, null, null); conn.addHandler(handlePresence, null, 'presence', null, null, null); conn.addHandler(handlePing, 'urn:xmpp:ping', 'iq', 'get', null, null); conn.addHandler(handleIqRoster, 'jabber:iq:roster', 'iq', 'set', null, null); conn.addHandler(handleIqPrivacy, 'jabber:iq:privacy', 'iq', 'set', null, null); conn.addHandler(handleIq, null, 'iq', null, null, null); conn.registerConfrIQHandler && (conn.registerConfrIQHandler()); conn.context.status = _code.STATUS_OPENED; var supportRecMessage = [ _code.WEBIM_MESSAGE_REC_TEXT, _code.WEBIM_MESSAGE_REC_EMOJI]; if (_utils.isCanDownLoadFile) { supportRecMessage.push(_code.WEBIM_MESSAGE_REC_PHOTO); supportRecMessage.push(_code.WEBIM_MESSAGE_REC_AUDIO_FILE); } var supportSedMessage = [_code.WEBIM_MESSAGE_SED_TEXT]; if (_utils.isCanUploadFile) { supportSedMessage.push(_code.WEBIM_MESSAGE_REC_PHOTO); supportSedMessage.push(_code.WEBIM_MESSAGE_REC_AUDIO_FILE); } conn.notifyVersion(); conn.retry && _handleMessageQueue(conn); conn.heartBeat(); conn.isAutoLogin && conn.setPresence(); console.log("conn",conn); try { if (conn.unSendMsgArr.length > 0) { console.log("unSendMesArr",conn.unSendMsgArr); for (var i in conn.unSendMsgArr) { var dom = conn.unSendMsgArr[i]; conn.sendCommand(dom); delete conn.unSendMsgArr[i]; } } } catch (e) { console.error(e.message); } conn.offLineSendConnecting = false; conn.logOut = false; conn.onOpened({ canReceive: supportRecMessage, canSend: supportSedMessage, accessToken: conn.context.accessToken }); } else if (status == Strophe.Status.DISCONNECTING) { if (conn.isOpened()) { conn.stopHeartBeat(); conn.context.status = _code.STATUS_CLOSING; error = { type: _code.WEBIM_CONNCTION_SERVER_CLOSE_ERROR, msg: msg, reconnect: true }; conflict && (error.conflict = true); conn.onError(error); } } else if (status == Strophe.Status.DISCONNECTED) { if (!conn.isClosing() || conn.isOpened()) { if (conn.autoReconnectNumTotal < conn.autoReconnectNumMax) { conn.reconnect(!conn.isClosing()); return; } else { error = { type: _code.WEBIM_CONNCTION_DISCONNECTED }; conn.onError(error); } } conn.context.status = _code.STATUS_CLOSED; conn.clear(); conn.onClosed(); } else if (status == Strophe.Status.AUTHFAIL) { error = { type: _code.WEBIM_CONNCTION_AUTH_ERROR }; conflict && (error.conflict = true); conn.onError(error); conn.clear(); } else if (status == Strophe.Status.ERROR) { conn.context.status = _code.STATUS_ERROR; error = { type: _code.WEBIM_CONNCTION_SERVER_ERROR }; conflict && (error.conflict = true); conn.onError(error); } conn.context.status_now = status; }; var _getJid = function (options, conn) { var jid = options.toJid || ''; if (jid === '') { var appKey = conn.context.appKey || ''; var toJid = appKey + '_' + options.to + '@' + conn.domain; if (options.resource) { toJid = toJid + '/' + options.resource; } jid = toJid; } return jid; }; var _getJidByName = function (name, conn) { var options = { to: name }; return _getJid(options, conn); }; var _validCheck = function (options, conn) { options = options || {}; if (options.user == '') { conn.onError({ type: _code.WEBIM_CONNCTION_USER_NOT_ASSIGN_ERROR }); return false; } var user = (options.user + '') || ''; var appKey = options.appKey || ''; var devInfos = appKey.split('#'); if (devInfos.length !== 2) { conn.onError({ type: _code.WEBIM_CONNCTION_APPKEY_NOT_ASSIGN_ERROR }); return false; } var orgName = devInfos[0]; var appName = devInfos[1]; if (!orgName) { conn.onError({ type: _code.WEBIM_CONNCTION_APPKEY_NOT_ASSIGN_ERROR }); return false; } if (!appName) { conn.onError({ type: _code.WEBIM_CONNCTION_APPKEY_NOT_ASSIGN_ERROR }); return false; } var jid = appKey + '_' + user.toLowerCase() + '@' + conn.domain, resource = options.resource || 'webim'; if (conn.isMultiLoginSessions) { resource += user + new Date().getTime() + Math.floor(Math.random().toFixed(6) * 1000000); } conn.context.jid = jid + '/' + resource; /*jid: {appkey}_{username}@domain/resource*/ conn.context.userId = user; conn.context.appKey = appKey; conn.context.appName = appName; conn.context.orgName = orgName; return true; }; var _getXmppUrl = function (baseUrl, https) { if (/^(ws|http)s?:\/\/?/.test(baseUrl)) { return baseUrl; } var url = { prefix: 'http', base: '://' + baseUrl, suffix: '/http-bind/' }; if (https && _utils.isSupportWss) { url.prefix = 'wss'; url.suffix = '/ws/'; } else { if (https) { url.prefix = 'https'; } else if (window.WebSocket) { url.prefix = 'ws'; url.suffix = '/ws/'; } } return url.prefix + url.base + url.suffix; }; /** * The connection class. * @constructor * @param {Object} options - 创建连接的初始化参数 * @param {String} options.url - xmpp服务器的URL * @param {String} options.apiUrl - API服务器的URL * @param {Boolean} options.isHttpDNS - 防止域名劫持 * @param {Boolean} options.isMultiLoginSessions - 为true时同一账户可以同时在多个Web页面登录(多标签登录,默认不开启,如有需要请联系商务),为false时同一账号只能在一个Web页面登录 * @param {Boolean} options.https - 是否启用wss. * @param {Number} options.heartBeatWait - 发送心跳包的时间间隔(毫秒) * @param {Boolean} options.isAutoLogin - 登录成功后是否自动出席 * @param {Number} options.autoReconnectNumMax - 掉线后重连的最大次数 * @param {Number} options.autoReconnectInterval - 掉线后重连的间隔时间(毫秒) * @param {Boolean} options.isWindowSDK - 是否运行在WindowsSDK上 * @param {Boolean} options.encrypt - 是否加密文本消息 * @param {Boolean} options.delivery - 是否发送delivered ack * @returns {Class} 连接实例 */ //class var connection = function (options) { if (!this instanceof connection) { return new connection(options); } var options = options || {}; this.isHttpDNS = options.isHttpDNS || false; this.isMultiLoginSessions = options.isMultiLoginSessions || false; this.wait = options.wait || 30; this.retry = options.retry || false; this.https = options.https && location.protocol === 'https:'; this.url = _getXmppUrl(options.url, this.https); this.hold = options.hold || 1; this.route = options.route || null; this.domain = options.domain || 'easemob.com'; this.inactivity = options.inactivity || 30; this.heartBeatWait = options.heartBeatWait || 4500; this.maxRetries = options.maxRetries || 5; this.isAutoLogin = options.isAutoLogin === false ? false : true; this.pollingTime = options.pollingTime || 800; this.stropheConn = false; this.autoReconnectNumMax = options.autoReconnectNumMax || 0; this.autoReconnectNumTotal = 0; this.autoReconnectInterval = options.autoReconnectInterval || 0; this.context = {status: _code.STATUS_INIT}; this.sendQueue = new Queue(); //instead of sending message immediately,cache them in this queue this.intervalId = null; //clearInterval return value this.apiUrl = options.apiUrl || ''; this.isWindowSDK = options.isWindowSDK || false; this.encrypt = options.encrypt || {encrypt: {type: 'none'}}; this.delivery = options.delivery || false; this.user = ''; this.orgName = ''; this.appName = ''; this.token = ''; this.unSendMsgArr = []; this.dnsArr = ['https://rs.easemob.com', 'https://rsbak.easemob.com', 'http://182.92.174.78', 'http://112.126.66.111']; //http dns server hosts this.dnsIndex = 0; //the dns ip used in dnsArr currently this.dnsTotal = this.dnsArr.length; //max number of getting dns retries this.restHosts = null; //rest server ips this.restIndex = 0; //the rest ip used in restHosts currently this.restTotal = 0; //max number of getting rest token retries this.xmppHosts = null; //xmpp server ips this.xmppIndex = 0; //the xmpp ip used in xmppHosts currently this.xmppTotal = 0; //max number of creating xmpp server connection(ws/bosh) retries this.groupOption = {}; // global params isStropheLog = options.isStropheLog || false; }; /** * 注册新用户 * @param {Object} options - * @param {String} options.username - 用户名,即用户ID * @param {String} options.password - 密码 * @param {String} options.nickname - 用户昵称 * @param {Function} options.success - 成功之后的回调,默认为空 * @param {Function} options.error - 失败之后的回调,默认为空 */ connection.prototype.registerUser = function (options) { if (location.protocol != 'https:' && this.isHttpDNS) { this.dnsIndex = 0; this.getHttpDNS(options, 'signup'); } else { this.signup(options); } } /** * 处理发送队列 * @private */ connection.prototype.handelSendQueue = function () { var options = this.sendQueue.pop(); if (options !== null) { this.sendReceiptsMessage(options); } }; /** * 注册监听函数 * @param {Object} options - 回调函数集合 * @param {connection~onOpened} options.onOpened - 处理登录的回调 * @param {connection~onTextMessage} options.onTextMessage - 处理文本消息的回调 * @param {connection~onEmojiMessage} options.onEmojiMessage - 处理表情消息的回调 * @param {connection~onPictureMessage} options.onPictureMessage - 处理图片消息的回调 * @param {connection~onAudioMessage} options.onAudioMessage - 处理音频消息的回调 * @param {connection~onVideoMessage} options.onVideoMessage - 处理视频消息的回调 * @param {connection~onFileMessage} options.onFileMessage - 处理文件消息的回调 * @param {connection~onLocationMessage} options.onLocationMessage - 处理位置消息的回调 * @param {connection~onCmdMessage} options.onCmdMessage - 处理命令消息的回调 * @param {connection~onPresence} options.onPresence - 处理Presence消息的回调 * @param {connection~onError} options.onError - 处理错误消息的回调 * @param {connection~onReceivedMessage} options.onReceivedMessage - 处理Received消息的回调 * @param {connection~onInviteMessage} options.onInviteMessage - 处理邀请消息的回调 * @param {connection~onDeliverdMessage} options.onDeliverdMessage - 处理Delivered ACK消息的回调 * @param {connection~onReadMessage} options.onReadMessage - 处理Read ACK消息的回调 * @param {connection~onMutedMessage} options.onMutedMessage - 处理禁言消息的回调 * @param {connection~onOffline} options.onOffline - 处理断网的回调 * @param {connection~onOnline} options.onOnline - 处理联网的回调 * @param {connection~onCreateGroup} options.onCreateGroup - 处理创建群组的回调 */ connection.prototype.listen = function (options) { /** * 登录成功后调用 * @callback connection~onOpened */ /** * 收到文本消息 * @callback connection~onTextMessage */ /** * 收到表情消息 * @callback connection~onEmojiMessage */ /** * 收到图片消息 * @callback connection~onPictureMessage */ /** * 收到音频消息 * @callback connection~onAudioMessage */ /** * 收到视频消息 * @callback connection~onVideoMessage */ /** * 收到文件消息 * @callback connection~onFileMessage */ /** * 收到位置消息 * @callback connection~onLocationMessage */ /** * 收到命令消息 * @callback connection~onCmdMessage */ /** * 收到错误消息 * @callback connection~onError */ /** * 收到Presence消息 * @callback connection~onPresence */ /** * 收到Received消息 * @callback connection~onReceivedMessage */ /** * 被邀请进群 * @callback connection~onInviteMessage */ /** * 收到已送达回执 * @callback connection~onDeliverdMessage */ /** * 收到已读回执 * @callback connection~onReadMessage */ /** * 被群管理员禁言 * @callback connection~onMutedMessage */ /** * 浏览器被断网时调用 * @callback connection~onOffline */ /** * 浏览器联网时调用 * @callback connection~onOnline */ /** * 建群成功后调用 * @callback connection~onCreateGroup */ this.onOpened = options.onOpened || _utils.emptyfn; this.onClosed = options.onClosed || _utils.emptyfn; this.onTextMessage = options.onTextMessage || _utils.emptyfn; this.onEmojiMessage = options.onEmojiMessage || _utils.emptyfn; this.onPictureMessage = options.onPictureMessage || _utils.emptyfn; this.onAudioMessage = options.onAudioMessage || _utils.emptyfn; this.onVideoMessage = options.onVideoMessage || _utils.emptyfn; this.onFileMessage = options.onFileMessage || _utils.emptyfn; this.onLocationMessage = options.onLocationMessage || _utils.emptyfn; this.onCmdMessage = options.onCmdMessage || _utils.emptyfn; this.onPresence = options.onPresence || _utils.emptyfn; this.onRoster = options.onRoster || _utils.emptyfn; this.onError = options.onError || _utils.emptyfn; this.onReceivedMessage = options.onReceivedMessage || _utils.emptyfn; this.onInviteMessage = options.onInviteMessage || _utils.emptyfn; this.onDeliverdMessage = options.onDeliveredMessage || _utils.emptyfn; this.onReadMessage = options.onReadMessage || _utils.emptyfn; this.onMutedMessage = options.onMutedMessage || _utils.emptyfn; this.onOffline = options.onOffline || _utils.emptyfn; this.onOnline = options.onOnline || _utils.emptyfn; this.onConfirmPop = options.onConfirmPop || _utils.emptyfn; this.onCreateGroup = options.onCreateGroup || _utils.emptyfn; //for WindowSDK start this.onUpdateMyGroupList = options.onUpdateMyGroupList || _utils.emptyfn; this.onUpdateMyRoster = options.onUpdateMyRoster || _utils.emptyfn; //for WindowSDK end this.onBlacklistUpdate = options.onBlacklistUpdate || _utils.emptyfn; _listenNetwork(this.onOnline, this.onOffline); }; /** * 发送心跳 * webrtc需要强制心跳,加个默认为false的参数 向下兼容 * @param {Boolean} forcing - 是否强制发送 * @private */ connection.prototype.heartBeat = function (forcing) { if (forcing !== true) { forcing = false; } var me = this; //IE8: strophe auto switch from ws to BOSH, need heartbeat var isNeed = !/^ws|wss/.test(me.url) || /mobile/.test(navigator.userAgent); if (this.heartBeatID || (!forcing && !isNeed)) { return; } var options = { toJid: this.domain, type: 'normal' }; this.heartBeatID = setInterval(function () { // fix: do heartbeat only when websocket _utils.isSupportWss && me.ping(options); }, this.heartBeatWait); }; /** * @private */ connection.prototype.stopHeartBeat = function () { if (typeof this.heartBeatID == "number") { this.heartBeatID = clearInterval(this.heartBeatID); } }; /** * 发送接收消息回执 * @param {Object} options - * @param {String} options.id - 消息id * @private */ connection.prototype.sendReceiptsMessage = function (options) { var dom = $msg({ from: this.context.jid || '', to: this.domain, id: options.id || '' }).c('received', { xmlns: 'urn:xmpp:receipts', id: options.id || '' }); this.sendCommand(dom.tree()); }; /** * @private */ connection.prototype.cacheReceiptsMessage = function (options) { this.sendQueue.push(options); }; /** * @private */ connection.prototype.getStrophe = function () { if (location.protocol != 'https:' && this.isHttpDNS) { //TODO: try this.xmppTotal times on fail var url = ''; var host = this.xmppHosts[this.xmppIndex]; var domain = _utils.getXmlFirstChild(host, 'domain'); var ip = _utils.getXmlFirstChild(host, 'ip'); if (ip) { url = ip.textContent; var port = _utils.getXmlFirstChild(host, 'port'); if (port.textContent != '80') { url += ':' + port.textContent; } } else { url = domain.textContent; } if (url != '') { var parter = /(.+\/\/).+(\/.+)/; this.url = this.url.replace(parter, "$1" + url + "$2"); } } var stropheConn = new Strophe.Connection(this.url, { inactivity: this.inactivity, maxRetries: this.maxRetries, pollingTime: this.pollingTime }); return stropheConn; }; /** * * @param data * @param tagName * @private */ connection.prototype.getHostsByTag = function (data, tagName) { var tag = _utils.getXmlFirstChild(data, tagName); if (!tag) { console.log(tagName + ' hosts error'); return null; } var hosts = tag.getElementsByTagName('hosts'); if (hosts.length == 0) { console.log(tagName + ' hosts error2'); return null; } return hosts[0].getElementsByTagName('host'); }; /** * @private */ connection.prototype.getRestFromHttpDNS = function (options, type) { if (this.restIndex > this.restTotal) { console.log('rest hosts all tried,quit'); return; } var url = ''; var host = this.restHosts[this.restIndex]; var domain = _utils.getXmlFirstChild(host, 'domain'); var ip = _utils.getXmlFirstChild(host, 'ip'); if (ip) { var port = _utils.getXmlFirstChild(host, 'port'); url = (location.protocol === 'https:' ? 'https:' : 'http:') + '//' + ip.textContent + ':' + port.textContent; } else { url = (location.protocol === 'https:' ? 'https:' : 'http:') + '//' + domain.textContent; } if (url != '') { this.apiUrl = url; options.apiUrl = url; } if (type == 'login') { this.login(options); } else { this.signup(options); } }; /** * @private */ connection.prototype.getHttpDNS = function (options, type) { // if (this.restHosts) { // this.getRestFromHttpDNS(options, type); // return; // } var self = this; var suc = function (data, xhr) { data = new DOMParser().parseFromString(data, "text/xml").documentElement; //get rest ips var restHosts = self.getHostsByTag(data, 'rest'); if (!restHosts) { console.log('rest hosts error3'); return; } self.restHosts = restHosts; self.restTotal = restHosts.length; //get xmpp ips var makeArray = function(obj){ //伪数组转为数组 return Array.prototype.slice.call(obj,0); } try{ Array.prototype.slice.call(document.documentElement.childNodes, 0)[0].nodeType; }catch(e){ makeArray = function(obj){ var res = []; for(var i=0,len=obj.length; i<len; i++){ res.push(obj[i]); } return res; } } var xmppHosts = makeArray(self.getHostsByTag(data, 'xmpp')); if (!xmppHosts) { console.log('xmpp hosts error3'); return; } for(var i = 0; i< xmppHosts.length; i++){ var httpType = self.https ? 'https' : 'http'; if(_utils.getXmlFirstChild(xmppHosts[i], 'protocol').textContent === httpType ){ var currentPost = xmppHosts[i]; xmppHosts.splice(i,1); xmppHosts.unshift(currentPost); } } self.xmppHosts = xmppHosts; self.xmppTotal = xmppHosts.length; self.getRestFromHttpDNS(options, type); }; var error = function (res, xhr, msg) { console.log('getHttpDNS error', res, msg); self.dnsIndex++; if (self.dnsIndex < self.dnsTotal) { self.getHttpDNS(options, type); } }; var options2 = { url: this.dnsArr[this.dnsIndex] + '/easemob/server.xml', dataType: 'text', type: 'GET', // url: 'http://www.easemob.com/easemob/server.xml', // dataType: 'xml', data: {app_key: encodeURIComponent(options.appKey)}, success: suc || _utils.emptyfn, error: error || _utils.emptyfn }; _utils.ajax(options2); }; /** * @private */ connection.prototype.signup = function (options) { var self = this; var orgName = options.orgName || ''; var appName = options.appName || ''; var appKey = options.appKey || ''; var suc = options.success || EMPTYFN; var err = options.error || EMPTYFN; if (!orgName && !appName && appKey) { var devInfos = appKey.split('#'); if (devInfos.length === 2) { orgName = devInfos[0]; appName = devInfos[1]; } } if (!orgName && !appName) { err({ type: _code.WEBIM_CONNCTION_APPKEY_NOT_ASSIGN_ERROR }); return; } var error = function (res, xhr, msg) { if (location.protocol != 'https:' && self.isHttpDNS) { if ((self.restIndex + 1) < self.restTotal) { self.restIndex++; self.getRestFromHttpDNS(options, 'signup'); return; } } self.clear(); err(res); }; var https = options.https || https; var apiUrl = options.apiUrl; var restUrl = apiUrl + '/' + orgName + '/' + appName + '/users'; var userjson = { username: options.username, password: options.password, nickname: options.nickname || '' }; var userinfo = _utils.stringify(userjson); var options2 = { url: restUrl, dataType: 'json', data: userinfo, success: suc, error: error }; _utils.ajax(options2); }; /** * 登录 * @param {Object} options - 用户信息 * @param {String} options.user - 用户名 * @param {String} options.pwd - 用户密码,跟token二选一 * @param {String} options.accessToken - token,跟密码二选一 * @param {String} options.appKey - Appkey * @param {String} options.apiUrl - Rest 服务地址,非必须。可在项目的WebIMConfig配置 * @param {String} options.xmppURL - Xmpp 服务地址,非必须。可在项目的WebIMConfig配置 * @param {Function} options.success - 成功之后的回调,默认为空,token登录没有该回调 * @param {Function} options.error - 失败之后的回调,默认为空,token登录没有该回调 */ connection.prototype.open = function (options) { console.log(8888888); var appkey = options.appKey, orgName = appkey.split('#')[0], appName = appkey.split('#')[1]; this.orgName = orgName; this.appName = appName; if (options.accessToken) { this.token = options.accessToken; } if (options.xmppURL) { this.url = _getXmppUrl(options.xmppURL, this.https); } if (location.protocol != 'https:' && this.isHttpDNS) { this.dnsIndex = 0; this.getHttpDNS(options, 'login'); } else { this.login(options); } }; /** * * @param options * @private */ connection.prototype.login = function (options) { this.user = options.user; var pass = _validCheck(options, this); if (!pass) { return; } var conn = this; if (conn.isOpened()) { return; } if (options.accessToken) { options.access_token = options.accessToken; conn.context.restTokenData = options; _login(options, conn); } else { var apiUrl = options.apiUrl; var userId = this.context.userId; var pwd = options.pwd || ''; var appName = this.context.appName; var orgName = this.context.orgName; var suc = function (data, xhr) { conn.context.status = _code.STATUS_DOLOGIN_IM; conn.context.restTokenData = data; if (options.success) options.success(data); conn.token = data.access_token; _login(data, conn); }; var error = function (res, xhr, msg) { if (options.error) options.error(); if (location.protocol != 'https:' && conn.isHttpDNS) { if ((conn.restIndex + 1) < conn.restTotal) { conn.restIndex++; conn.getRestFromHttpDNS(options, 'login'); return; } } conn.clear(); if (res.error && res.error_description) { conn.onError({ type: _code.WEBIM_CONNCTION_OPEN_USERGRID_ERROR, data: res, xhr: xhr }); } else { conn.onError({ type: _code.WEBIM_CONNCTION_OPEN_ERROR, data: res, xhr: xhr }); } }; this.context.status = _code.STATUS_DOLOGIN_USERGRID; var loginJson = { grant_type: 'password', username: userId, password: pwd, timestamp: +new Date() }; var loginfo = _utils.stringify(loginJson); var options2 = { url: apiUrl + '/' + orgName + '/' + appName + '/token', dataType: 'json', data: loginfo, success: suc || _utils.emptyfn, error: error || _utils.emptyfn }; _utils.ajax(options2); } }; /** * attach to xmpp server for BOSH * @private */ connection.prototype.attach = function (options) { var pass = _validCheck(options, this); if (!pass) { return; } options = options || {}; var accessToken = options.accessToken || ''; if (accessToken == '') { this.onError({ type: _code.WEBIM_CONNCTION_TOKEN_NOT_ASSIGN_ERROR }); return; } var sid = options.sid || ''; if (sid === '') { this.onError({ type: _code.WEBIM_CONNCTION_SESSIONID_NOT_ASSIGN_ERROR }); return; } var rid = options.rid || ''; if (rid === '') { this.onError({ type: _code.WEBIM_CONNCTION_RID_NOT_ASSIGN_ERROR }); return; } var stropheConn = this.getStrophe(); this.context.accessToken = accessToken; this.context.stropheConn = stropheConn; this.context.status = _code.STATUS_DOLOGIN_IM; var conn = this; var callback = function (status, msg) { _loginCallback(status, msg, conn); }; var jid = this.context.jid; var wait = this.wait; var hold = this.hold; var wind = this.wind || 5; stropheConn.attach(jid, sid, rid, callback, wait, hold, wind); }; /** * 断开连接,同时心跳停止 * @param {String} reason - 断开原因 */ connection.prototype.close = function (reason) { this.logOut = true; this.stopHeartBeat(); var status = this.context.status; if (status == _code.STATUS_INIT) { return; } if (this.isClosed() || this.isClosing()) { return; } this.context.status = _code.STATUS_CLOSING; this.context.stropheConn.disconnect(reason); }; /** * strophe 的方法 * @private */ connection.prototype.addHandler = function (handler, ns, name, type, id, from, options) { this.context.stropheConn.addHandler(handler, ns, name, type, id, from, options); }; /** * strophe sendIQ * @private */ connection.prototype.notifyVersion = function (suc, fail) { var jid = _getJid({}, this); var dom = $iq({ from: this.context.jid || '' , to: this.domain , type: 'result' }) .c('query', {xmlns: 'jabber:iq:version'}) .c('name') .t('easemob') .up() .c('version') .t(_version) .up() .c('os') .t('webim'); var suc = suc || _utils.emptyfn; var error = fail || this.onError; var failFn = function (ele) { error({ type: _code.WEBIM_CONNCTION_NOTIFYVERSION_ERROR , data: ele }); }; this.context.stropheConn.sendIQ(dom.tree(), suc, failFn); return; }; /** * handle all types of presence message * @private */ connection.prototype.handlePresence = function (msginfo) { if (this.isClosed()) { return; } var from = msginfo.getAttribute('from') || ''; var to = msginfo.getAttribute('to') || ''; var type = msginfo.getAttribute('type') || ''; var presence_type = msginfo.getAttribute('presence_type') || ''; var fromUser = _parseNameFromJidFn(from); var toUser = _parseNameFromJidFn(to); var isCreate = false; var isMemberJoin = false; var isDecline = false; var isApply = false; var info = { from: fromUser, to: toUser, fromJid: from, toJid: to, type: type, chatroom: msginfo.getElementsByTagName('roomtype').length ? true : false }; var showTags = msginfo.getElementsByTagName('show'); if (showTags && showTags.length > 0) { var showTag = showTags[0]; info.show = Strophe.getText(showTag); } var statusTags = msginfo.getElementsByTagName('status'); if (statusTags && statusTags.length > 0) { var statusTag = statusTags[0]; info.status = Strophe.getText(statusTag); info.code = statusTag.getAttribute('code'); } var priorityTags = msginfo.getElementsByTagName('priority'); if (priorityTags && priorityTags.length > 0) { var priorityTag = priorityTags[0]; info.priority = Strophe.getText(priorityTag); } var error = msginfo.getElementsByTagName('error'); if (error && error.length > 0) { var error = error[0]; info.error = { code: error.getAttribute('code') }; } var destroy = msginfo.getElementsByTagName('destroy'); if (destroy && destroy.length > 0) { var destroy = destroy[0]; info.destroy = true; var reason = destroy.getElementsByTagName('reason'); if (reason && reason.length > 0) { info.reason = Strophe.getText(reason[0]); } } var members = msginfo.getElementsByTagName('item'); if (members && members.length > 0) { var member = members[0]; var role = member.getAttribute('role'); var jid = member.getAttribute('jid'); var affiliation = member.getAttribute('affiliation'); // dismissed by group if (role == 'none' && jid) { var kickedMember = _parseNameFromJidFn(jid); var actor = member.getElementsByTagName('actor')[0]; var actorNick = actor.getAttribute('nick'); info.actor = actorNick; info.kicked = kickedMember; } // Service Acknowledges Room Creation `createGroupACK` if (role == 'moderator' && info.code == '201') { if (affiliation === 'owner') { info.type = 'createGroupACK'; isCreate = true; } // else // info.type = 'joinPublicGroupSuccess'; } } var x = msginfo.getElementsByTagName('x'); if (x && x.length > 0) { // 加群申请 var apply = x[0].getElementsByTagName('apply'); // 加群成功 var accept = x[0].getElementsByTagName('accept'); // 同意加群后用户进群通知 var item = x[0].getElementsByTagName('item'); // 加群被拒绝 var decline = x[0].getElementsByTagName('decline');