function Push(options) { this.doNotConnect = 0; options = options || {}; options.heartbeat = options.heartbeat || 25000; options.pingTimeout = options.pingTimeout || 10000; this.config = options; this.uid = 0; this.channels = {}; this.connection = null; this.pingTimeoutTimer = 0; Push.instances.push(this); this.createConnection(); } Push.prototype.checkoutPing = function() { var _this = this; _this.checkoutPingTimer && clearTimeout(_this.checkoutPingTimer); _this.checkoutPingTimer = setTimeout(function() { _this.checkoutPingTimer = 0; if (_this.connection.state === 'connected') { _this.connection.send('{"event":"pusher:ping","data":{}}'); if (_this.pingTimeoutTimer) { clearTimeout(_this.pingTimeoutTimer); _this.pingTimeoutTimer = 0; } _this.pingTimeoutTimer = setTimeout(function() { _this.connection.closeAndClean(); if (!_this.connection.doNotConnect) { _this.connection.waitReconnect(); } }, _this.config.pingTimeout); } }, this.config.heartbeat); }; Push.prototype.channel = function(name) { return this.channels.find(name); }; Push.prototype.allChannels = function() { return this.channels.all(); }; Push.prototype.createConnection = function() { if (this.connection) { throw Error('Connection already exist'); } var _this = this; var url = this.config.url; function updateSubscribed() { for (var i in _this.channels) { _this.channels[i].subscribed = false; } } this.connection = new Connection({ url: url, app_key: this.config.app_key, onOpen: function() { _this.connection.state = 'connecting'; _this.checkoutPing(); }, onMessage: function(params) { if (_this.pingTimeoutTimer) { clearTimeout(_this.pingTimeoutTimer); _this.pingTimeoutTimer = 0; } params = JSON.parse(params.data); var event = params.event; var channel_name = params.channel; if (event === 'pusher:pong') { _this.checkoutPing(); return; } if (event === 'pusher:error') { throw Error(params.data.message); } var data = JSON.parse(params.data), channel; if (event === 'pusher_internal:subscription_succeeded') { channel = _this.channels[channel_name]; channel.subscribed = true; channel.processQueue(); channel.emit('pusher:subscription_succeeded'); return; } if (event === 'pusher:connection_established') { _this.connection.socket_id = data.socket_id; _this.connection.updateNetworkState('connected'); _this.subscribeAll(); } if (event.indexOf('pusher_internal') !== -1) { console.log("Event '" + event + "' not implement"); return; } channel = _this.channels[channel_name]; if (channel) { channel.emit(event, data); } }, onClose: function() { updateSubscribed(); }, onError: function() { updateSubscribed(); } }); }; Push.prototype.disconnect = function() { this.connection.doNotConnect = 1; this.connection.close(); }; Push.prototype.subscribeAll = function() { if (this.connection.state !== 'connected') { return; } for (var channel_name in this.channels) { //this.connection.send(JSON.stringify({event:"pusher:subscribe", data:{channel:channel_name}})); this.channels[channel_name].processSubscribe(); } }; Push.prototype.unsubscribe = function(channel_name) { if (this.channels[channel_name]) { delete this.channels[channel_name]; if (this.connection.state === 'connected') { this.connection.send(JSON.stringify({ event: "pusher:unsubscribe", data: { channel: channel_name } })); } } }; Push.prototype.unsubscribeAll = function() { var channels = Object.keys(this.channels); if (channels.length) { if (this.connection.state === 'connected') { for (var channel_name in this.channels) { this.unsubscribe(channel_name); } } } this.channels = {}; }; Push.prototype.subscribe = function(channel_name) { if (this.channels[channel_name]) { return this.channels[channel_name]; } if (channel_name.indexOf('private-') === 0) { return createPrivateChannel(channel_name, this); } if (channel_name.indexOf('presence-') === 0) { return createPresenceChannel(channel_name, this); } return createChannel(channel_name, this); }; Push.instances = []; function createChannel(channel_name, push) { var channel = new Channel(push.connection, channel_name); push.channels[channel_name] = channel; channel.subscribeCb = function() { push.connection.send(JSON.stringify({ event: "pusher:subscribe", data: { channel: channel_name } })); } channel.processSubscribe(); return channel; } function createPrivateChannel(channel_name, push) { var channel = new Channel(push.connection, channel_name); push.channels[channel_name] = channel; channel.subscribeCb = function() { __ajax({ url: push.config.auth, type: 'POST', data: { channel_name: channel_name, socket_id: push.connection.socket_id }, success: function(data) { data = JSON.parse(data); data.channel = channel_name; push.connection.send(JSON.stringify({ event: "pusher:subscribe", data: data })); }, error: function(e) { throw Error(e); } }); }; channel.processSubscribe(); return channel; } function createPresenceChannel(channel_name, push) { return createPrivateChannel(channel_name, push); } uni.onNetworkStatusChange(function(res) { if (res.isConnected) { for (var i in Push.instances) { con = Push.instances[i].connection; con.reconnectInterval = 1; if (con.state === 'connecting') { con.connect(); } } } }); function Connection(options) { this.dispatcher = new Dispatcher(); __extends(this, this.dispatcher); var properies = ['on', 'off', 'emit']; for (var i in properies) { this[properies[i]] = this.dispatcher[properies[i]]; } this.options = options; this.state = 'initialized'; //initialized connecting connected disconnected this.doNotConnect = 0; this.reconnectInterval = 1; this.connection = null; this.reconnectTimer = 0; this.connect(); } Connection.prototype.updateNetworkState = function(state) { var old_state = this.state; this.state = state; if (old_state !== state) { this.emit('state_change', { previous: old_state, current: state }); } }; Connection.prototype.connect = function() { this.doNotConnect = 0; if (this.networkState == 'connecting' || this.networkState == 'established') { console.log('networkState is ' + this.networkState + ' and do not need connect'); return; } if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = 0; } this.closeAndClean(); var options = this.options; var _this = this; _this.updateNetworkState('connecting'); var cb = function() { uni.onSocketOpen(function(res) { _this.reconnectInterval = 1; if (_this.doNotConnect) { _this.updateNetworkState('closing'); uni.closeSocket(); return; } _this.updateNetworkState('established'); if (options.onOpen) { options.onOpen(res); } }); if (options.onMessage) { uni.onSocketMessage(options.onMessage); } uni.onSocketClose(function(res) { _this.updateNetworkState('disconnected'); if (!_this.doNotConnect) { _this.waitReconnect(); } if (options.onClose) { options.onClose(res); } }); uni.onSocketError(function(res) { _this.close(); if (!_this.doNotConnect) { _this.waitReconnect(); } if (options.onError) { options.onError(res); } }); }; uni.connectSocket({ url: options.url, fail: function(res) { console.log('uni.connectSocket fail'); console.log(res); _this.updateNetworkState('disconnected'); _this.waitReconnect(); }, success: function() { } }); cb(); } Connection.prototype.connect = function() { this.doNotConnect = 0; if (this.state === 'connected') { console.log('networkState is "' + this.state + '" and do not need connect'); return; } if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = 0; } this.closeAndClean(); var options = this.options; this.updateNetworkState('connecting'); var _this = this; var cb = function() { uni.onSocketOpen(function(res) { _this.reconnectInterval = 1; if (_this.doNotConnect) { _this.updateNetworkState('disconnected'); uni.closeSocket(); return; } if (options.onOpen) { options.onOpen(res); } }); if (options.onMessage) { uni.onSocketMessage(options.onMessage); } uni.onSocketClose(function(res) { _this.updateNetworkState('disconnected'); if (!_this.doNotConnect) { _this.waitReconnect(); } if (options.onClose) { options.onClose(res); } }); uni.onSocketError(function(res) { _this.close(); if (!_this.doNotConnect) { _this.waitReconnect(); } if (options.onError) { options.onError(res); } }); }; uni.connectSocket({ url: options.url + '/app/' + options.app_key, fail: function(res) { console.log('uni.connectSocket fail'); console.log(res); _this.updateNetworkState('disconnected'); _this.waitReconnect(); }, success: function() { } }); cb(); } Connection.prototype.closeAndClean = function() { if (this.state === 'connected') { uni.closeSocket(); } this.updateNetworkState('disconnected'); }; Connection.prototype.waitReconnect = function() { if (this.state === 'connected' || this.state === 'connecting') { return; } if (!this.doNotConnect) { this.updateNetworkState('connecting'); var _this = this; if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); } this.reconnectTimer = setTimeout(function() { _this.connect(); }, this.reconnectInterval); if (this.reconnectInterval < 1000) { this.reconnectInterval = 1000; } else { // 每次重连间隔增大一倍 this.reconnectInterval = this.reconnectInterval * 2; } // 有网络的状态下,重连间隔最大2秒 if (this.reconnectInterval > 2000 && navigator.onLine) { _this.reconnectInterval = 2000; } } } Connection.prototype.send = function(data) { if (this.state !== 'connected') { console.trace('networkState is "' + this.state + '", can not send ' + data); return; } uni.sendSocketMessage({ data: data }); } Connection.prototype.close = function() { this.updateNetworkState('disconnected'); uni.closeSocket(); } var __extends = (this && this.__extends) || function(d, b) { for (var p in b) if (b.hasOwnProperty(p)) { d[p] = b[p]; } function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; function Channel(connection, channel_name) { this.subscribed = false; this.dispatcher = new Dispatcher(); this.connection = connection; this.channelName = channel_name; this.subscribeCb = null; this.queue = []; __extends(this, this.dispatcher); var properies = ['on', 'off', 'emit']; for (var i in properies) { this[properies[i]] = this.dispatcher[properies[i]]; } } Channel.prototype.processSubscribe = function() { if (this.connection.state !== 'connected') { return; } this.subscribeCb(); }; Channel.prototype.processQueue = function() { if (this.connection.state !== 'connected' || !this.subscribed) { return; } for (var i in this.queue) { this.queue[i](); } this.queue = []; }; Channel.prototype.trigger = function(event, data) { if (event.indexOf('client-') !== 0) { throw new Error("Event '" + event + "' should start with 'client-'"); } var _this = this; this.queue.push(function() { _this.connection.send(JSON.stringify({ event: event, data: data, channel: _this.channelName })); }); this.processQueue(); }; //////////////// var Collections = (function() { var exports = {}; function extend(target) { var sources = []; for (var _i = 1; _i < arguments.length; _i++) { sources[_i - 1] = arguments[_i]; } for (var i = 0; i < sources.length; i++) { var extensions = sources[i]; for (var property in extensions) { if (extensions[property] && extensions[property].constructor && extensions[property].constructor === Object) { target[property] = extend(target[property] || {}, extensions[property]); } else { target[property] = extensions[property]; } } } return target; } exports.extend = extend; function stringify() { var m = ["Push"]; for (var i = 0; i < arguments.length; i++) { if (typeof arguments[i] === "string") { m.push(arguments[i]); } else { m.push(safeJSONStringify(arguments[i])); } } return m.join(" : "); } exports.stringify = stringify; function arrayIndexOf(array, item) { var nativeIndexOf = Array.prototype.indexOf; if (array === null) { return -1; } if (nativeIndexOf && array.indexOf === nativeIndexOf) { return array.indexOf(item); } for (var i = 0, l = array.length; i < l; i++) { if (array[i] === item) { return i; } } return -1; } exports.arrayIndexOf = arrayIndexOf; function objectApply(object, f) { for (var key in object) { if (Object.prototype.hasOwnProperty.call(object, key)) { f(object[key], key, object); } } } exports.objectApply = objectApply; function keys(object) { var keys = []; objectApply(object, function(_, key) { keys.push(key); }); return keys; } exports.keys = keys; function values(object) { var values = []; objectApply(object, function(value) { values.push(value); }); return values; } exports.values = values; function apply(array, f, context) { for (var i = 0; i < array.length; i++) { f.call(context || (window), array[i], i, array); } } exports.apply = apply; function map(array, f) { var result = []; for (var i = 0; i < array.length; i++) { result.push(f(array[i], i, array, result)); } return result; } exports.map = map; function mapObject(object, f) { var result = {}; objectApply(object, function(value, key) { result[key] = f(value); }); return result; } exports.mapObject = mapObject; function filter(array, test) { test = test || function(value) { return !!value; }; var result = []; for (var i = 0; i < array.length; i++) { if (test(array[i], i, array, result)) { result.push(array[i]); } } return result; } exports.filter = filter; function filterObject(object, test) { var result = {}; objectApply(object, function(value, key) { if ((test && test(value, key, object, result)) || Boolean(value)) { result[key] = value; } }); return result; } exports.filterObject = filterObject; function flatten(object) { var result = []; objectApply(object, function(value, key) { result.push([key, value]); }); return result; } exports.flatten = flatten; function any(array, test) { for (var i = 0; i < array.length; i++) { if (test(array[i], i, array)) { return true; } } return false; } exports.any = any; function all(array, test) { for (var i = 0; i < array.length; i++) { if (!test(array[i], i, array)) { return false; } } return true; } exports.all = all; function encodeParamsObject(data) { return mapObject(data, function(value) { if (typeof value === "object") { value = safeJSONStringify(value); } return encodeURIComponent(base64_1["default"](value.toString())); }); } exports.encodeParamsObject = encodeParamsObject; function buildQueryString(data) { var params = filterObject(data, function(value) { return value !== undefined; }); return map(flatten(encodeParamsObject(params)), util_1["default"].method("join", "=")).join("&"); } exports.buildQueryString = buildQueryString; function decycleObject(object) { var objects = [], paths = []; return (function derez(value, path) { var i, name, nu; switch (typeof value) { case 'object': if (!value) { return null; } for (i = 0; i < objects.length; i += 1) { if (objects[i] === value) { return { $ref: paths[i] }; } } objects.push(value); paths.push(path); if (Object.prototype.toString.apply(value) === '[object Array]') { nu = []; for (i = 0; i < value.length; i += 1) { nu[i] = derez(value[i], path + '[' + i + ']'); } } else { nu = {}; for (name in value) { if (Object.prototype.hasOwnProperty.call(value, name)) { nu[name] = derez(value[name], path + '[' + JSON.stringify(name) + ']'); } } } return nu; case 'number': case 'string': case 'boolean': return value; } }(object, '$')); } exports.decycleObject = decycleObject; function safeJSONStringify(source) { try { return JSON.stringify(source); } catch (e) { return JSON.stringify(decycleObject(source)); } } exports.safeJSONStringify = safeJSONStringify; return exports; })(); var Dispatcher = (function() { function Dispatcher(failThrough) { this.callbacks = new CallbackRegistry(); this.global_callbacks = []; this.failThrough = failThrough; } Dispatcher.prototype.on = function(eventName, callback, context) { this.callbacks.add(eventName, callback, context); return this; }; Dispatcher.prototype.on_global = function(callback) { this.global_callbacks.push(callback); return this; }; Dispatcher.prototype.off = function(eventName, callback, context) { this.callbacks.remove(eventName, callback, context); return this; }; Dispatcher.prototype.emit = function(eventName, data) { var i; for (i = 0; i < this.global_callbacks.length; i++) { this.global_callbacks[i](eventName, data); } var callbacks = this.callbacks.get(eventName); if (callbacks && callbacks.length > 0) { for (i = 0; i < callbacks.length; i++) { callbacks[i].fn.call(callbacks[i].context || (window), data); } } else if (this.failThrough) { this.failThrough(eventName, data); } return this; }; return Dispatcher; }()); var CallbackRegistry = (function() { function CallbackRegistry() { this._callbacks = {}; } CallbackRegistry.prototype.get = function(name) { return this._callbacks[prefix(name)]; }; CallbackRegistry.prototype.add = function(name, callback, context) { var prefixedEventName = prefix(name); this._callbacks[prefixedEventName] = this._callbacks[prefixedEventName] || []; this._callbacks[prefixedEventName].push({ fn: callback, context: context }); }; CallbackRegistry.prototype.remove = function(name, callback, context) { if (!name && !callback && !context) { this._callbacks = {}; return; } var names = name ? [prefix(name)] : Collections.keys(this._callbacks); if (callback || context) { this.removeCallback(names, callback, context); } else { this.removeAllCallbacks(names); } }; CallbackRegistry.prototype.removeCallback = function(names, callback, context) { Collections.apply(names, function(name) { this._callbacks[name] = Collections.filter(this._callbacks[name] || [], function( oning) { return (callback && callback !== oning.fn) || (context && context !== oning.context); }); if (this._callbacks[name].length === 0) { delete this._callbacks[name]; } }, this); }; CallbackRegistry.prototype.removeAllCallbacks = function(names) { Collections.apply(names, function(name) { delete this._callbacks[name]; }, this); }; return CallbackRegistry; }()); function prefix(name) { return "_" + name; } function __ajax(options) { options = options || {}; options.type = (options.type || 'GET').toUpperCase(); options.dataType = options.dataType || 'json'; var params = formatParams(options.data); var xhr; if (window.XMLHttpRequest) { xhr = new XMLHttpRequest(); } else { xhr = ActiveXObject('Microsoft.XMLHTTP'); } xhr.onreadystatechange = function() { if (xhr.readyState === 4) { var status = xhr.status; if (status >= 200 && status < 300) { options.success && options.success(xhr.responseText, xhr.responseXML); } else { options.error && options.error(status); } } } if (options.type === 'GET') { xhr.open('GET', options.url + '?' + params, true); xhr.send(null); } else if (options.type === 'POST') { xhr.open('POST', options.url, true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(params); } } function formatParams(data) { var arr = []; for (var name in data) { arr.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name])); } return arr.join('&'); } export default Push