hx-websdk
Version:
Easemob IM websdk
1,618 lines (1,456 loc) • 152 kB
JavaScript
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');