-
-
Save yangfch3/c60babb36179b556bed011b8db7e72ce to your computer and use it in GitHub Desktop.
pomelo 小程序客户端
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function() { | |
var JS_WS_CLIENT_TYPE = 'js-websocket'; | |
var JS_WS_CLIENT_VERSION = '0.0.1'; | |
//微信web开发工具v0.10.102800之前不支持arraybuffer,websocket发送data数据之前转换为utf8字符串 | |
function toUTF8Array(str) { | |
var utf8 = []; | |
for (var i=0; i < str.length; i++) { | |
var charcode = str.charCodeAt(i); | |
if (charcode < 0x80) utf8.push(charcode); | |
else if (charcode < 0x800) { | |
utf8.push(0xc0 | (charcode >> 6), | |
0x80 | (charcode & 0x3f)); | |
} | |
else if (charcode < 0xd800 || charcode >= 0xe000) { | |
utf8.push(0xe0 | (charcode >> 12), | |
0x80 | ((charcode>>6) & 0x3f), | |
0x80 | (charcode & 0x3f)); | |
} | |
// surrogate pair | |
else { | |
i++; | |
// UTF-16 encodes 0x10000-0x10FFFF by | |
// subtracting 0x10000 and splitting the | |
// 20 bits of 0x0-0xFFFFF into two halves | |
charcode = 0x10000 + (((charcode & 0x3ff)<<10) | |
| (str.charCodeAt(i) & 0x3ff)); | |
utf8.push(0xf0 | (charcode >>18), | |
0x80 | ((charcode>>12) & 0x3f), | |
0x80 | ((charcode>>6) & 0x3f), | |
0x80 | (charcode & 0x3f)); | |
} | |
} | |
return utf8; | |
} | |
var isArray = Array.isArray; | |
var root = this; | |
function EventEmitter() { | |
} | |
// if (typeof module !== 'undefined' && module.exports) { | |
// module.exports.EventEmitter = EventEmitter; | |
// } | |
// else { | |
// root = window; | |
// root.EventEmitter = EventEmitter; | |
// } | |
// By default EventEmitters will print a warning if more than | |
// 10 listeners are added to it. This is a useful default which | |
// helps finding memory leaks. | |
// | |
// Obviously not all Emitters should be limited to 10. This function allows | |
// that to be increased. Set to zero for unlimited. | |
var defaultMaxListeners = 10; | |
EventEmitter.prototype.setMaxListeners = function(n) { | |
if (!this._events) this._events = {}; | |
this._maxListeners = n; | |
}; | |
EventEmitter.prototype.emit = function() { | |
var type = arguments[0]; | |
// If there is no 'error' event listener then throw. | |
if (type === 'error') { | |
if (!this._events || !this._events.error || | |
(isArray(this._events.error) && !this._events.error.length)) | |
{ | |
if (this.domain) { | |
var er = arguments[1]; | |
er.domain_emitter = this; | |
er.domain = this.domain; | |
er.domain_thrown = false; | |
this.domain.emit('error', er); | |
return false; | |
} | |
if (arguments[1] instanceof Error) { | |
throw arguments[1]; // Unhandled 'error' event | |
} else { | |
throw new Error("Uncaught, unspecified 'error' event."); | |
} | |
return false; | |
} | |
} | |
if (!this._events) return false; | |
var handler = this._events[type]; | |
if (!handler) return false; | |
if (typeof handler == 'function') { | |
if (this.domain) { | |
this.domain.enter(); | |
} | |
switch (arguments.length) { | |
// fast cases | |
case 1: | |
handler.call(this); | |
break; | |
case 2: | |
handler.call(this, arguments[1]); | |
break; | |
case 3: | |
handler.call(this, arguments[1], arguments[2]); | |
break; | |
// slower | |
default: | |
var l = arguments.length; | |
var args = new Array(l - 1); | |
for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; | |
handler.apply(this, args); | |
} | |
if (this.domain) { | |
this.domain.exit(); | |
} | |
return true; | |
} else if (isArray(handler)) { | |
if (this.domain) { | |
this.domain.enter(); | |
} | |
var l = arguments.length; | |
var args = new Array(l - 1); | |
for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; | |
var listeners = handler.slice(); | |
for (var i = 0, l = listeners.length; i < l; i++) { | |
listeners[i].apply(this, args); | |
} | |
if (this.domain) { | |
this.domain.exit(); | |
} | |
return true; | |
} else { | |
return false; | |
} | |
}; | |
EventEmitter.prototype.addListener = function(type, listener) { | |
if ('function' !== typeof listener) { | |
throw new Error('addListener only takes instances of Function'); | |
} | |
if (!this._events) this._events = {}; | |
// To avoid recursion in the case that type == "newListeners"! Before | |
// adding it to the listeners, first emit "newListeners". | |
this.emit('newListener', type, typeof listener.listener === 'function' ? | |
listener.listener : listener); | |
if (!this._events[type]) { | |
// Optimize the case of one listener. Don't need the extra array object. | |
this._events[type] = listener; | |
} else if (isArray(this._events[type])) { | |
// If we've already got an array, just append. | |
this._events[type].push(listener); | |
} else { | |
// Adding the second element, need to change to array. | |
this._events[type] = [this._events[type], listener]; | |
} | |
// Check for listener leak | |
if (isArray(this._events[type]) && !this._events[type].warned) { | |
var m; | |
if (this._maxListeners !== undefined) { | |
m = this._maxListeners; | |
} else { | |
m = defaultMaxListeners; | |
} | |
if (m && m > 0 && this._events[type].length > m) { | |
this._events[type].warned = true; | |
console.error('(node) warning: possible EventEmitter memory ' + | |
'leak detected. %d listeners added. ' + | |
'Use emitter.setMaxListeners() to increase limit.', | |
this._events[type].length); | |
console.trace(); | |
} | |
} | |
return this; | |
}; | |
EventEmitter.prototype.on = EventEmitter.prototype.addListener; | |
EventEmitter.prototype.once = function(type, listener) { | |
if ('function' !== typeof listener) { | |
throw new Error('.once only takes instances of Function'); | |
} | |
var self = this; | |
function g() { | |
self.removeListener(type, g); | |
listener.apply(this, arguments); | |
}; | |
g.listener = listener; | |
self.on(type, g); | |
return this; | |
}; | |
EventEmitter.prototype.removeListener = function(type, listener) { | |
if ('function' !== typeof listener) { | |
throw new Error('removeListener only takes instances of Function'); | |
} | |
// does not use listeners(), so no side effect of creating _events[type] | |
if (!this._events || !this._events[type]) return this; | |
var list = this._events[type]; | |
if (isArray(list)) { | |
var position = -1; | |
for (var i = 0, length = list.length; i < length; i++) { | |
if (list[i] === listener || | |
(list[i].listener && list[i].listener === listener)) | |
{ | |
position = i; | |
break; | |
} | |
} | |
if (position < 0) return this; | |
list.splice(position, 1); | |
} else if (list === listener || | |
(list.listener && list.listener === listener)) | |
{ | |
delete this._events[type]; | |
} | |
return this; | |
}; | |
EventEmitter.prototype.removeAllListeners = function(type) { | |
if (arguments.length === 0) { | |
this._events = {}; | |
return this; | |
} | |
var events = this._events && this._events[type]; | |
if (!events) return this; | |
if (isArray(events)) { | |
events.splice(0); | |
} else { | |
this._events[type] = null; | |
} | |
return this; | |
}; | |
EventEmitter.prototype.listeners = function(type) { | |
if (!this._events) this._events = {}; | |
if (!this._events[type]) this._events[type] = []; | |
if (!isArray(this._events[type])) { | |
this._events[type] = [this._events[type]]; | |
} | |
return this._events[type]; | |
} | |
var Protocol = exports; | |
var PKG_HEAD_BYTES = 4; | |
var MSG_FLAG_BYTES = 1; | |
var MSG_ROUTE_CODE_BYTES = 2; | |
var MSG_ID_MAX_BYTES = 5; | |
var MSG_ROUTE_LEN_BYTES = 1; | |
var MSG_ROUTE_CODE_MAX = 0xffff; | |
var MSG_COMPRESS_ROUTE_MASK = 0x1; | |
var MSG_TYPE_MASK = 0x7; | |
var Package = Protocol.Package = {}; | |
var Message = Protocol.Message = {}; | |
Package.TYPE_HANDSHAKE = 1; | |
Package.TYPE_HANDSHAKE_ACK = 2; | |
Package.TYPE_HEARTBEAT = 3; | |
Package.TYPE_DATA = 4; | |
Package.TYPE_KICK = 5; | |
Message.TYPE_REQUEST = 0; | |
Message.TYPE_NOTIFY = 1; | |
Message.TYPE_RESPONSE = 2; | |
Message.TYPE_PUSH = 3; | |
/** | |
* pomele client encode | |
* id message id; | |
* route message route | |
* msg message body | |
* socketio current support string | |
*/ | |
Protocol.strencode = function(str) { | |
var buffer = new Uint8Array(str.length * 3); | |
var offset = 0; | |
for(var i = 0; i < str.length; i++){ | |
var charCode = str.charCodeAt(i); | |
var codes = null; | |
if(charCode <= 0x7f){ | |
codes = [charCode]; | |
}else if(charCode <= 0x7ff){ | |
codes = [0xc0|(charCode>>6), 0x80|(charCode & 0x3f)]; | |
}else{ | |
codes = [0xe0|(charCode>>12), 0x80|((charCode & 0xfc0)>>6), 0x80|(charCode & 0x3f)]; | |
} | |
for(var j = 0; j < codes.length; j++){ | |
buffer[offset] = codes[j]; | |
++offset; | |
} | |
} | |
var _buffer = new Uint8Array(offset); | |
copyArray(_buffer, 0, buffer, 0, offset); | |
return _buffer; | |
}; | |
/** | |
* client decode | |
* msg String data | |
* return Message Object | |
*/ | |
Protocol.strdecode = function(buffer) { | |
var bytes = new Uint8Array(buffer); | |
var array = []; | |
var offset = 0; | |
var charCode = 0; | |
var end = bytes.length; | |
while(offset < end){ | |
if(bytes[offset] < 128){ | |
charCode = bytes[offset]; | |
offset += 1; | |
}else if(bytes[offset] < 224){ | |
charCode = ((bytes[offset] & 0x3f)<<6) + (bytes[offset+1] & 0x3f); | |
offset += 2; | |
}else{ | |
charCode = ((bytes[offset] & 0x0f)<<12) + ((bytes[offset+1] & 0x3f)<<6) + (bytes[offset+2] & 0x3f); | |
offset += 3; | |
} | |
array.push(charCode); | |
} | |
return String.fromCharCode.apply(null, array); | |
}; | |
/** | |
* Package protocol encode. | |
* | |
* Pomelo package format: | |
* +------+-------------+------------------+ | |
* | type | body length | body | | |
* +------+-------------+------------------+ | |
* | |
* Head: 4bytes | |
* 0: package type, | |
* 1 - handshake, | |
* 2 - handshake ack, | |
* 3 - heartbeat, | |
* 4 - data | |
* 5 - kick | |
* 1 - 3: big-endian body length | |
* Body: body length bytes | |
* | |
* @param {Number} type package type | |
* @param {Uint8Array} body body content in bytes | |
* @return {Uint8Array} new byte array that contains encode result | |
*/ | |
Package.encode = function(type, body){ | |
var length = body ? body.length : 0; | |
var buffer = new Uint8Array(PKG_HEAD_BYTES + length); | |
var index = 0; | |
buffer[index++] = type & 0xff; | |
buffer[index++] = (length >> 16) & 0xff; | |
buffer[index++] = (length >> 8) & 0xff; | |
buffer[index++] = length & 0xff; | |
if(body) { | |
copyArray(buffer, index, body, 0, length); | |
} | |
// return String.fromCharCode.apply(null,buffer); | |
return buffer; | |
}; | |
/** | |
* Package protocol decode. | |
* See encode for package format. | |
* | |
* @param {Uint8Array} buffer byte array containing package content | |
* @return {Object} {type: package type, buffer: body byte array} | |
*/ | |
Package.decode = function(buffer){ | |
// buffer = toUTF8Array(str) | |
var offset = 0; | |
var bytes = new Uint8Array(buffer); | |
var length = 0; | |
var rs = []; | |
while(offset < bytes.length) { | |
var type = bytes[offset++]; | |
length = ((bytes[offset++]) << 16 | (bytes[offset++]) << 8 | bytes[offset++]) >>> 0; | |
var body = length ? new Uint8Array(length) : null; | |
copyArray(body, 0, bytes, offset, length); | |
offset += length; | |
rs.push({'type': type, 'body': body}); | |
} | |
return rs.length === 1 ? rs[0]: rs; | |
}; | |
/** | |
* Message protocol encode. | |
* | |
* @param {Number} id message id | |
* @param {Number} type message type | |
* @param {Number} compressRoute whether compress route | |
* @param {Number|String} route route code or route string | |
* @param {Buffer} msg message body bytes | |
* @return {Buffer} encode result | |
*/ | |
Message.encode = function(id, type, compressRoute, route, msg){ | |
// caculate message max length | |
var idBytes = msgHasId(type) ? caculateMsgIdBytes(id) : 0; | |
var msgLen = MSG_FLAG_BYTES + idBytes; | |
if(msgHasRoute(type)) { | |
if(compressRoute) { | |
if(typeof route !== 'number'){ | |
throw new Error('error flag for number route!'); | |
} | |
msgLen += MSG_ROUTE_CODE_BYTES; | |
} else { | |
msgLen += MSG_ROUTE_LEN_BYTES; | |
if(route) { | |
route = Protocol.strencode(route); | |
if(route.length>255) { | |
throw new Error('route maxlength is overflow'); | |
} | |
msgLen += route.length; | |
} | |
} | |
} | |
console.log('msg.len:'+msg.length) | |
if(msg) { | |
msgLen += msg.length; | |
} | |
var buffer = new Uint8Array(msgLen); | |
var offset = 0; | |
// add flag | |
offset = encodeMsgFlag(type, compressRoute, buffer, offset); | |
// add message id | |
if(msgHasId(type)) { | |
offset = encodeMsgId(id, buffer, offset); | |
} | |
// add route | |
if(msgHasRoute(type)) { | |
offset = encodeMsgRoute(compressRoute, route, buffer, offset); | |
} | |
// add body | |
if(msg) { | |
offset = encodeMsgBody(msg, buffer, offset); | |
} | |
return buffer; | |
}; | |
/** | |
* Message protocol decode. | |
* | |
* @param {Buffer|Uint8Array} buffer message bytes | |
* @return {Object} message object | |
*/ | |
Message.decode = function(buffer) { | |
var bytes = new Uint8Array(buffer); | |
var bytesLen = bytes.length || bytes.byteLength; | |
var offset = 0; | |
var id = 0; | |
var route = null; | |
// parse flag | |
var flag = bytes[offset++]; | |
var compressRoute = flag & MSG_COMPRESS_ROUTE_MASK; | |
var type = (flag >> 1) & MSG_TYPE_MASK; | |
// parse id | |
if(msgHasId(type)) { | |
var m = parseInt(bytes[offset]); | |
var i = 0; | |
do{ | |
var m = parseInt(bytes[offset]); | |
id = id + ((m & 0x7f) * Math.pow(2,(7*i))); | |
offset++; | |
i++; | |
}while(m >= 128); | |
} | |
// parse route | |
if(msgHasRoute(type)) { | |
if(compressRoute) { | |
route = (bytes[offset++]) << 8 | bytes[offset++]; | |
} else { | |
var routeLen = bytes[offset++]; | |
if(routeLen) { | |
route = new Uint8Array(routeLen); | |
copyArray(route, 0, bytes, offset, routeLen); | |
route = Protocol.strdecode(route); | |
} else { | |
route = ''; | |
} | |
offset += routeLen; | |
} | |
} | |
// parse body | |
var bodyLen = bytesLen - offset; | |
var body = new Uint8Array(bodyLen); | |
copyArray(body, 0, bytes, offset, bodyLen); | |
return {'id': id, 'type': type, 'compressRoute': compressRoute, | |
'route': route, 'body': body}; | |
}; | |
var copyArray = function(dest, doffset, src, soffset, length) { | |
if('function' === typeof src.copy) { | |
// Buffer | |
src.copy(dest, doffset, soffset, soffset + length); | |
} else { | |
// Uint8Array | |
for(var index=0; index<length; index++){ | |
dest[doffset++] = src[soffset++]; | |
} | |
} | |
}; | |
var msgHasId = function(type) { | |
return type === Message.TYPE_REQUEST || type === Message.TYPE_RESPONSE; | |
}; | |
var msgHasRoute = function(type) { | |
return type === Message.TYPE_REQUEST || type === Message.TYPE_NOTIFY || | |
type === Message.TYPE_PUSH; | |
}; | |
var caculateMsgIdBytes = function(id) { | |
var len = 0; | |
do { | |
len += 1; | |
id >>= 7; | |
} while(id > 0); | |
return len; | |
}; | |
var encodeMsgFlag = function(type, compressRoute, buffer, offset) { | |
if(type !== Message.TYPE_REQUEST && type !== Message.TYPE_NOTIFY && | |
type !== Message.TYPE_RESPONSE && type !== Message.TYPE_PUSH) { | |
throw new Error('unkonw message type: ' + type); | |
} | |
buffer[offset] = (type << 1) | (compressRoute ? 1 : 0); | |
return offset + MSG_FLAG_BYTES; | |
}; | |
var encodeMsgId = function(id, buffer, offset) { | |
do{ | |
var tmp = id % 128; | |
var next = Math.floor(id/128); | |
if(next !== 0){ | |
tmp = tmp + 128; | |
} | |
buffer[offset++] = tmp; | |
id = next; | |
} while(id !== 0); | |
return offset; | |
}; | |
var encodeMsgRoute = function(compressRoute, route, buffer, offset) { | |
if (compressRoute) { | |
if(route > MSG_ROUTE_CODE_MAX){ | |
throw new Error('route number is overflow'); | |
} | |
buffer[offset++] = (route >> 8) & 0xff; | |
buffer[offset++] = route & 0xff; | |
} else { | |
if(route) { | |
buffer[offset++] = route.length & 0xff; | |
copyArray(buffer, offset, route, 0, route.length); | |
offset += route.length; | |
} else { | |
buffer[offset++] = 0; | |
} | |
} | |
return offset; | |
}; | |
var encodeMsgBody = function(msg, buffer, offset) { | |
copyArray(buffer, offset, msg, 0, msg.length); | |
return offset + msg.length; | |
}; | |
// var Protocol = getApp().Protocol; | |
// var protobuf = window.protobuf; | |
// var decodeIO_protobuf = window.decodeIO_protobuf; | |
// var decodeIO_encoder = null; | |
// var decodeIO_decoder = null; | |
// var Package = Protocol.Package; | |
// var Message = Protocol.Message; | |
// var EventEmitter = getApp().EventEmitter; | |
// var rsa = window.rsa; | |
// if(typeof(window) != "undefined" && typeof(sys) != 'undefined' && sys.localStorage) { | |
// window.localStorage = sys.localStorage; | |
// } | |
var RES_OK = 200; | |
var RES_FAIL = 500; | |
var RES_OLD_CLIENT = 501; | |
if (typeof Object.create !== 'function') { | |
Object.create = function (o) { | |
function F() {} | |
F.prototype = o; | |
return new F(); | |
}; | |
} | |
var root = getApp(); | |
var pomelo = Object.create(EventEmitter.prototype); // object extend from object | |
root.pomelo = pomelo; | |
var socket = false; | |
var reqId = 0; | |
var callbacks = {}; | |
var handlers = {}; | |
//Map from request id to route | |
// var routeMap = {}; | |
// var dict = {}; // route string to code | |
// var abbrs = {}; // code to route string | |
// var serverProtos = {}; | |
// var clientProtos = {}; | |
// var protoVersion = 0; | |
var heartbeatInterval = 0; | |
var heartbeatTimeout = 0; | |
var nextHeartbeatTimeout = 0; | |
var gapThreshold = 100; // heartbeat gap threashold | |
var heartbeatId = null; | |
var heartbeatTimeoutId = null; | |
var handshakeCallback = null; | |
var decode = null; | |
var encode = null; | |
var reconnect = false; | |
var reconncetTimer = null; | |
var reconnectUrl = null; | |
var reconnectAttempts = 0; | |
var reconnectionDelay = 5000; | |
var DEFAULT_MAX_RECONNECT_ATTEMPTS = 10; | |
var useCrypto; | |
var handshakeBuffer = { | |
'sys': { | |
type: JS_WS_CLIENT_TYPE, | |
version: JS_WS_CLIENT_VERSION, | |
rsa: {} | |
}, | |
'user': { | |
} | |
}; | |
var initCallback = null; | |
pomelo.init = function(params, cb) { | |
initCallback = cb; | |
var host = params.host; | |
var port = params.port; | |
encode = params.encode || defaultEncode; | |
decode = params.decode || defaultDecode; | |
var url = 'ws://' + host; | |
if(port) { | |
url += ':' + port; | |
} | |
handshakeBuffer.user = params.user; | |
// if(params.encrypt) { | |
// useCrypto = true; | |
// rsa.generate(1024, "10001"); | |
// var data = { | |
// rsa_n: rsa.n.toString(16), | |
// rsa_e: rsa.e | |
// } | |
// handshakeBuffer.sys.rsa = data; | |
// } | |
handshakeCallback = params.handshakeCallback; | |
connect(params, url, cb); | |
}; | |
var defaultDecode = pomelo.decode = function(data) { | |
//probuff decode | |
var msg = Message.decode(data); | |
// if(msg.id > 0){ | |
// msg.route = routeMap[msg.id]; | |
// delete routeMap[msg.id]; | |
// if(!msg.route){ | |
// return; | |
// } | |
// } | |
msg.body = deCompose(msg); | |
return msg; | |
}; | |
var defaultEncode = pomelo.encode = function(reqId, route, msg) { | |
var type = reqId ? Message.TYPE_REQUEST : Message.TYPE_NOTIFY; | |
//compress message by protobuf | |
// if(protobuf && clientProtos[route]) { | |
// msg = protobuf.encode(route, msg); | |
// } else if(decodeIO_encoder && decodeIO_encoder.lookup(route)) { | |
// var Builder = decodeIO_encoder.build(route); | |
// msg = new Builder(msg).encodeNB(); | |
// } else { | |
msg = Protocol.strencode(JSON.stringify(msg)); | |
// } | |
var compressRoute = 0; | |
// if(dict && dict[route]) { | |
// route = dict[route]; | |
// compressRoute = 1; | |
// } | |
return Message.encode(reqId, type, compressRoute, route, msg); | |
}; | |
var connect = function(params, url, cb) { | |
console.log('connect to ' + url); | |
var params = params || {}; | |
var maxReconnectAttempts = params.maxReconnectAttempts || DEFAULT_MAX_RECONNECT_ATTEMPTS; | |
reconnectUrl = url; | |
//Add protobuf version | |
// if(window.localStorage && window.localStorage.getItem('protos') && protoVersion === 0) { | |
// var protos = JSON.parse(window.localStorage.getItem('protos')); | |
// | |
// protoVersion = protos.version || 0; | |
// serverProtos = protos.server || {}; | |
// clientProtos = protos.client || {}; | |
// | |
// if(!!protobuf) { | |
// protobuf.init({encoderProtos: clientProtos, decoderProtos: serverProtos}); | |
// } | |
// if(!!decodeIO_protobuf) { | |
// decodeIO_encoder = decodeIO_protobuf.loadJson(clientProtos); | |
// decodeIO_decoder = decodeIO_protobuf.loadJson(serverProtos); | |
// } | |
// } | |
//Set protoversion | |
// handshakeBuffer.sys.protoVersion = protoVersion; | |
var onopen = function(event) { | |
if(!!reconnect) { | |
pomelo.emit('reconnect'); | |
} | |
reset(); | |
socket =true; | |
var obj = Package.encode(Package.TYPE_HANDSHAKE, Protocol.strencode(JSON.stringify(handshakeBuffer))); | |
send(obj); | |
}; | |
var onmessage = function(event) { | |
processPackage(Package.decode(event.data), cb); | |
// new package arrived, update the heartbeat timeout | |
if(heartbeatTimeout) { | |
nextHeartbeatTimeout = Date.now() + heartbeatTimeout; | |
} | |
}; | |
var onerror = function(event) { | |
pomelo.emit('io-error', event); | |
console.error('socket error: ', event); | |
}; | |
var onclose = function(event) { | |
pomelo.emit('close',event); | |
pomelo.emit('disconnect', event); | |
if(!!params.reconnect && reconnectAttempts < maxReconnectAttempts) { | |
reconnect = true; | |
reconnectAttempts++; | |
reconncetTimer = setTimeout(function() { | |
connect(params, reconnectUrl, cb); | |
}, reconnectionDelay); | |
reconnectionDelay *= 2; | |
} | |
}; | |
// socket = new WebSocket(url); | |
// socket.binaryType = 'arraybuffer'; | |
// socket.onopen = onopen; | |
// socket.onmessage = onmessage; | |
// socket.onerror = onerror; | |
// socket.onclose = onclose; | |
wx.connectSocket({ | |
// url: `${url}?EIO=3&transport=websocket` | |
url:url | |
}); | |
wx.onSocketError(onerror); | |
wx.onSocketOpen(onopen); | |
wx.onSocketMessage(onmessage); | |
wx.onSocketClose(onclose); | |
}; | |
pomelo.disconnect = function() { | |
if(socket) { | |
wx.closeSocket() | |
console.log('disconnect'); | |
socket = false; | |
} | |
if(heartbeatId) { | |
clearTimeout(heartbeatId); | |
heartbeatId = null; | |
} | |
if(heartbeatTimeoutId) { | |
clearTimeout(heartbeatTimeoutId); | |
heartbeatTimeoutId = null; | |
} | |
}; | |
var reset = function() { | |
reconnect = false; | |
reconnectionDelay = 1000 * 5; | |
reconnectAttempts = 0; | |
clearTimeout(reconncetTimer); | |
}; | |
pomelo.request = function(route, msg, cb) { | |
if(arguments.length === 2 && typeof msg === 'function') { | |
cb = msg; | |
msg = {}; | |
} else { | |
msg = msg || {}; | |
} | |
route = route || msg.route; | |
if(!route) { | |
return; | |
} | |
reqId++; | |
sendMessage(reqId, route, msg); | |
callbacks[reqId] = cb; | |
// routeMap[reqId] = route; | |
}; | |
pomelo.notify = function(route, msg) { | |
msg = msg || {}; | |
sendMessage(0, route, msg); | |
}; | |
var sendMessage = function(reqId, route, msg) { | |
// if(useCrypto) { | |
// msg = JSON.stringify(msg); | |
// var sig = rsa.signString(msg, "sha256"); | |
// msg = JSON.parse(msg); | |
// msg['__crypto__'] = sig; | |
// } | |
if(encode) { | |
msg = encode(reqId, route, msg); | |
} | |
var packet = Package.encode(Package.TYPE_DATA, msg); | |
send(packet); | |
}; | |
var send = function(packet) { | |
if(socket) | |
wx.sendSocketMessage({data:packet.buffer}); | |
}; | |
var handler = {}; | |
var heartbeat = function(data) { | |
if(!heartbeatInterval) { | |
// no heartbeat | |
return; | |
} | |
var obj = Package.encode(Package.TYPE_HEARTBEAT); | |
if(heartbeatTimeoutId) { | |
clearTimeout(heartbeatTimeoutId); | |
heartbeatTimeoutId = null; | |
} | |
if(heartbeatId) { | |
// already in a heartbeat interval | |
return; | |
} | |
heartbeatId = setTimeout(function() { | |
heartbeatId = null; | |
send(obj); | |
nextHeartbeatTimeout = Date.now() + heartbeatTimeout; | |
heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, heartbeatTimeout); | |
}, heartbeatInterval); | |
}; | |
var heartbeatTimeoutCb = function() { | |
var gap = nextHeartbeatTimeout - Date.now(); | |
if(gap > gapThreshold) { | |
heartbeatTimeoutId = setTimeout(heartbeatTimeoutCb, gap); | |
} else { | |
console.error('server heartbeat timeout'); | |
pomelo.emit('heartbeat timeout'); | |
pomelo.disconnect(); | |
} | |
}; | |
var handshake = function(data) { | |
data = JSON.parse(Protocol.strdecode(data)); | |
if(data.code === RES_OLD_CLIENT) { | |
pomelo.emit('error', 'client version not fullfill'); | |
return; | |
} | |
if(data.code !== RES_OK) { | |
pomelo.emit('error', 'handshake fail'); | |
return; | |
} | |
handshakeInit(data); | |
var obj = Package.encode(Package.TYPE_HANDSHAKE_ACK); | |
send(obj); | |
if(initCallback) { | |
initCallback(socket); | |
} | |
}; | |
var onData = function(data) { | |
var msg = data; | |
if(decode) { | |
msg = decode(msg); | |
} | |
processMessage(pomelo, msg); | |
}; | |
var onKick = function(data) { | |
data = JSON.parse(Protocol.strdecode(data)); | |
pomelo.emit('onKick', data); | |
}; | |
handlers[Package.TYPE_HANDSHAKE] = handshake; | |
handlers[Package.TYPE_HEARTBEAT] = heartbeat; | |
handlers[Package.TYPE_DATA] = onData; | |
handlers[Package.TYPE_KICK] = onKick; | |
var processPackage = function(msgs) { | |
if(Array.isArray(msgs)) { | |
for(var i=0; i<msgs.length; i++) { | |
var msg = msgs[i]; | |
handlers[msg.type](msg.body); | |
} | |
} else { | |
handlers[msgs.type](msgs.body); | |
} | |
}; | |
var processMessage = function(pomelo, msg) { | |
if(!msg.id) { | |
// server push message | |
console.log(msg.route +':'); | |
console.log(msg.body); | |
pomelo.emit(msg.route, msg.body); | |
return; | |
} | |
//if have a id then find the callback function with the request | |
var cb = callbacks[msg.id]; | |
delete callbacks[msg.id]; | |
if(typeof cb !== 'function') { | |
return; | |
} | |
cb(msg.body); | |
return; | |
}; | |
var processMessageBatch = function(pomelo, msgs) { | |
for(var i=0, l=msgs.length; i<l; i++) { | |
processMessage(pomelo, msgs[i]); | |
} | |
}; | |
var deCompose = function(msg) { | |
var route = msg.route; | |
// | |
// //Decompose route from dict | |
// if(msg.compressRoute) { | |
// if(!abbrs[route]){ | |
// return {}; | |
// } | |
// | |
// route = msg.route = abbrs[route]; | |
// } | |
// if(protobuf && serverProtos[route]) { | |
// return protobuf.decodeStr(route, msg.body); | |
// } else if(decodeIO_decoder && decodeIO_decoder.lookup(route)) { | |
// return decodeIO_decoder.build(route).decode(msg.body); | |
// } else { | |
return JSON.parse(Protocol.strdecode(msg.body)); | |
// } | |
// return msg; | |
}; | |
var handshakeInit = function(data) { | |
if(data.sys && data.sys.heartbeat) { | |
heartbeatInterval = data.sys.heartbeat * 1000; // heartbeat interval | |
heartbeatTimeout = heartbeatInterval * 2; // max heartbeat timeout | |
} else { | |
heartbeatInterval = 0; | |
heartbeatTimeout = 0; | |
} | |
initData(data); | |
if(typeof handshakeCallback === 'function') { | |
handshakeCallback(data.user); | |
} | |
}; | |
//Initilize data used in pomelo client | |
var initData = function(data) { | |
if(!data || !data.sys) { | |
return; | |
} | |
// dict = data.sys.dict; | |
// var protos = data.sys.protos; | |
// | |
// //Init compress dict | |
// if(dict) { | |
// dict = dict; | |
// abbrs = {}; | |
// | |
// for(var route in dict) { | |
// abbrs[dict[route]] = route; | |
// } | |
// } | |
// | |
// //Init protobuf protos | |
// if(protos) { | |
// protoVersion = protos.version || 0; | |
// serverProtos = protos.server || {}; | |
// clientProtos = protos.client || {}; | |
// | |
// //Save protobuf protos to localStorage | |
// window.localStorage.setItem('protos', JSON.stringify(protos)); | |
// | |
// if(!!protobuf) { | |
// protobuf.init({encoderProtos: protos.client, decoderProtos: protos.server}); | |
// } | |
// if(!!decodeIO_protobuf) { | |
// decodeIO_encoder = decodeIO_protobuf.loadJson(clientProtos); | |
// decodeIO_decoder = decodeIO_protobuf.loadJson(serverProtos); | |
// } | |
// } | |
}; | |
module.exports = pomelo; | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment