|
@@ -0,0 +1,574 @@
|
|
|
|
|
+// tiktok-push.js
|
|
|
|
|
+// 抖音小程序 WebSocket 推送模块(完整修复版)
|
|
|
|
|
+// 支持频道订阅、心跳保活、自动重连、私有频道认证
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 辅助函数 ====================
|
|
|
|
|
+function getGlobalObject() {
|
|
|
|
|
+ if (typeof global !== 'undefined') return global;
|
|
|
|
|
+ if (typeof window !== 'undefined') return window;
|
|
|
|
|
+ return this;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ==================== CallbackRegistry ====================
|
|
|
|
|
+function CallbackRegistry() {
|
|
|
|
|
+ this._callbacks = {};
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function prefix(name) {
|
|
|
|
|
+ return '_' + name;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+CallbackRegistry.prototype.get = function (name) {
|
|
|
|
|
+ var key = prefix(name);
|
|
|
|
|
+ return this._callbacks[key] || [];
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+CallbackRegistry.prototype.add = function (name, callback, context) {
|
|
|
|
|
+ var key = prefix(name);
|
|
|
|
|
+ if (!this._callbacks[key]) {
|
|
|
|
|
+ this._callbacks[key] = [];
|
|
|
|
|
+ }
|
|
|
|
|
+ this._callbacks[key].push({
|
|
|
|
|
+ fn: callback,
|
|
|
|
|
+ context: context
|
|
|
|
|
+ });
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+CallbackRegistry.prototype.remove = function (name, callback, context) {
|
|
|
|
|
+ if (!name && !callback && !context) {
|
|
|
|
|
+ this._callbacks = {};
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ var names = name ? [prefix(name)] : Object.keys(this._callbacks);
|
|
|
|
|
+ for (var i = 0; i < names.length; i++) {
|
|
|
|
|
+ var key = names[i];
|
|
|
|
|
+ if (!this._callbacks[key]) continue;
|
|
|
|
|
+ if (callback || context) {
|
|
|
|
|
+ var filtered = [];
|
|
|
|
|
+ for (var j = 0; j < this._callbacks[key].length; j++) {
|
|
|
|
|
+ var cb = this._callbacks[key][j];
|
|
|
|
|
+ if ((callback && callback !== cb.fn) || (context && context !== cb.context)) {
|
|
|
|
|
+ filtered.push(cb);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (filtered.length === 0) {
|
|
|
|
|
+ delete this._callbacks[key];
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this._callbacks[key] = filtered;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ delete this._callbacks[key];
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// ==================== Dispatcher ====================
|
|
|
|
|
+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) {
|
|
|
|
|
+ if (typeof callback === 'function') {
|
|
|
|
|
+ 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) {
|
|
|
|
|
+ // 全局回调
|
|
|
|
|
+ if (Array.isArray(this.global_callbacks)) {
|
|
|
|
|
+ for (var i = 0; i < this.global_callbacks.length; i++) {
|
|
|
|
|
+ var gc = this.global_callbacks[i];
|
|
|
|
|
+ if (typeof gc === 'function') {
|
|
|
|
|
+ try {
|
|
|
|
|
+ gc(eventName, data);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('global callback error:', e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取事件回调列表
|
|
|
|
|
+ var callbacks = null;
|
|
|
|
|
+ try {
|
|
|
|
|
+ callbacks = this.callbacks && this.callbacks.get ? this.callbacks.get(eventName) : null;
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.warn('get callbacks error:', e);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (callbacks && Array.isArray(callbacks) && callbacks.length > 0) {
|
|
|
|
|
+ for (var j = 0; j < callbacks.length; j++) {
|
|
|
|
|
+ var item = callbacks[j];
|
|
|
|
|
+ if (item && typeof item.fn === 'function') {
|
|
|
|
|
+ var ctx = item.context || getGlobalObject();
|
|
|
|
|
+ try {
|
|
|
|
|
+ item.fn.call(ctx, data);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('callback error for event "' + eventName + '":', e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (this.failThrough && typeof this.failThrough === 'function') {
|
|
|
|
|
+ this.failThrough(eventName, data);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return this;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// ==================== Channel ====================
|
|
|
|
|
+function Channel(connection, channel_name) {
|
|
|
|
|
+ this.subscribed = false;
|
|
|
|
|
+ this.dispatcher = new Dispatcher();
|
|
|
|
|
+ this.connection = connection;
|
|
|
|
|
+ this.channelName = channel_name;
|
|
|
|
|
+ this.subscribeCb = null;
|
|
|
|
|
+ this.queue = [];
|
|
|
|
|
+
|
|
|
|
|
+ // 绑定 dispatcher 方法,确保 this 指向 dispatcher
|
|
|
|
|
+ this.on = this.dispatcher.on.bind(this.dispatcher);
|
|
|
|
|
+ this.off = this.dispatcher.off.bind(this.dispatcher);
|
|
|
|
|
+ this.emit = this.dispatcher.emit.bind(this.dispatcher);
|
|
|
|
|
+ this.on_global = this.dispatcher.on_global.bind(this.dispatcher);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+Channel.prototype.processSubscribe = function () {
|
|
|
|
|
+ if (this.connection.state !== 'connected') {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (this.subscribeCb) {
|
|
|
|
|
+ this.subscribeCb();
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+Channel.prototype.processQueue = function () {
|
|
|
|
|
+ if (this.connection.state !== 'connected' || !this.subscribed) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ for (var i = 0; i < this.queue.length; i++) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ this.queue[i]();
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('processQueue error:', e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ this.queue = [];
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+Channel.prototype.trigger = function (event, data) {
|
|
|
|
|
+ if (event.indexOf('client-') !== 0) {
|
|
|
|
|
+ throw new Error("Event '" + event + "' should start with 'client-'");
|
|
|
|
|
+ }
|
|
|
|
|
+ var self = this;
|
|
|
|
|
+ this.queue.push(function () {
|
|
|
|
|
+ self.connection.send(JSON.stringify({ event: event, data: data, channel: self.channelName }));
|
|
|
|
|
+ });
|
|
|
|
|
+ this.processQueue();
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// ==================== Connection ====================
|
|
|
|
|
+function Connection(options) {
|
|
|
|
|
+ this.dispatcher = new Dispatcher();
|
|
|
|
|
+
|
|
|
|
|
+ // 绑定 dispatcher 方法
|
|
|
|
|
+ this.on = this.dispatcher.on.bind(this.dispatcher);
|
|
|
|
|
+ this.off = this.dispatcher.off.bind(this.dispatcher);
|
|
|
|
|
+ this.emit = this.dispatcher.emit.bind(this.dispatcher);
|
|
|
|
|
+ this.on_global = this.dispatcher.on_global.bind(this.dispatcher);
|
|
|
|
|
+
|
|
|
|
|
+ this.options = options;
|
|
|
|
|
+ this.state = 'initialized';
|
|
|
|
|
+ this.doNotConnect = 0;
|
|
|
|
|
+ this.reconnectInterval = 1000;
|
|
|
|
|
+ this.socketTask = null;
|
|
|
|
|
+ this.reconnectTimer = 0;
|
|
|
|
|
+ this.socket_id = null;
|
|
|
|
|
+
|
|
|
|
|
+ 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 () {
|
|
|
|
|
+ var self = this;
|
|
|
|
|
+ this.doNotConnect = 0;
|
|
|
|
|
+
|
|
|
|
|
+ if (this.state === 'connected') {
|
|
|
|
|
+ console.log('networkState is "' + this.state + '" , no need to connect');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (this.reconnectTimer) {
|
|
|
|
|
+ clearTimeout(this.reconnectTimer);
|
|
|
|
|
+ this.reconnectTimer = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.closeAndClean();
|
|
|
|
|
+
|
|
|
|
|
+ var url = this.options.url;
|
|
|
|
|
+ var app_key = this.options.app_key;
|
|
|
|
|
+ var fullUrl = url + '/app/' + app_key;
|
|
|
|
|
+
|
|
|
|
|
+ this.socketTask = tt.connectSocket({
|
|
|
|
|
+ url: fullUrl,
|
|
|
|
|
+ success: function (res) {
|
|
|
|
|
+ console.log('WebSocket connect success, socketTaskId:', res.socketTaskId);
|
|
|
|
|
+ },
|
|
|
|
|
+ fail: function (err) {
|
|
|
|
|
+ console.error('WebSocket connect fail:', err);
|
|
|
|
|
+ self.updateNetworkState('disconnected');
|
|
|
|
|
+ if (!self.doNotConnect) {
|
|
|
|
|
+ self.waitReconnect();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (self.options.onError) {
|
|
|
|
|
+ self.options.onError(err);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ this.socketTask.onOpen(function (res) {
|
|
|
|
|
+ self.reconnectInterval = 1000;
|
|
|
|
|
+ if (self.doNotConnect) {
|
|
|
|
|
+ self.updateNetworkState('disconnected');
|
|
|
|
|
+ self.socketTask.close();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ self.updateNetworkState('connected');
|
|
|
|
|
+ if (self.options.onOpen) {
|
|
|
|
|
+ self.options.onOpen(res);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ this.socketTask.onMessage(function (res) {
|
|
|
|
|
+ if (self.options.onMessage) {
|
|
|
|
|
+ self.options.onMessage(res);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ this.socketTask.onClose(function (res) {
|
|
|
|
|
+ console.log('WebSocket closed, code:', res);
|
|
|
|
|
+ self.updateNetworkState('disconnected');
|
|
|
|
|
+ if (!self.doNotConnect) {
|
|
|
|
|
+ self.waitReconnect();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (self.options.onClose) {
|
|
|
|
|
+ self.options.onClose(res);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ this.socketTask.onError(function (res) {
|
|
|
|
|
+ console.log('WebSocket error, code:', res);
|
|
|
|
|
+ self.updateNetworkState('disconnected');
|
|
|
|
|
+ if (!self.doNotConnect) {
|
|
|
|
|
+ self.waitReconnect();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (self.options.onError) {
|
|
|
|
|
+ self.options.onError(res);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ this.updateNetworkState('connecting');
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+Connection.prototype.closeAndClean = function () {
|
|
|
|
|
+ if (this.socketTask) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ this.socketTask.close();
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.warn('closeAndClean error:', e);
|
|
|
|
|
+ }
|
|
|
|
|
+ this.socketTask = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.updateNetworkState('disconnected');
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+Connection.prototype.waitReconnect = function () {
|
|
|
|
|
+ if (this.state === 'connected' || this.state === 'connecting') {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!this.doNotConnect) {
|
|
|
|
|
+ this.updateNetworkState('connecting');
|
|
|
|
|
+ var self = this;
|
|
|
|
|
+ if (this.reconnectTimer) {
|
|
|
|
|
+ clearTimeout(this.reconnectTimer);
|
|
|
|
|
+ }
|
|
|
|
|
+ this.reconnectTimer = setTimeout(function () {
|
|
|
|
|
+ self.connect();
|
|
|
|
|
+ }, this.reconnectInterval);
|
|
|
|
|
+ if (this.reconnectInterval < 1000) {
|
|
|
|
|
+ this.reconnectInterval = 1000;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ this.reconnectInterval = Math.min(this.reconnectInterval * 2, 2000);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+Connection.prototype.send = function (data) {
|
|
|
|
|
+ if (this.state !== 'connected') {
|
|
|
|
|
+ console.warn('state is "' + this.state + '", cannot send:', data);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.socketTask.send({
|
|
|
|
|
+ data: data,
|
|
|
|
|
+ fail: function (err) {
|
|
|
|
|
+ console.error('send fail:', err);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+Connection.prototype.close = function () {
|
|
|
|
|
+ this.doNotConnect = 1;
|
|
|
|
|
+ this.updateNetworkState('disconnected');
|
|
|
|
|
+ if (this.socketTask) {
|
|
|
|
|
+ this.socketTask.close();
|
|
|
|
|
+ }
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// ==================== Push 客户端 ====================
|
|
|
|
|
+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.instances = [];
|
|
|
|
|
+
|
|
|
|
|
+Push.prototype.checkoutPing = function () {
|
|
|
|
|
+ var self = this;
|
|
|
|
|
+ setTimeout(function () {
|
|
|
|
|
+ if (self.connection.state === 'connected') {
|
|
|
|
|
+ self.connection.send('hi');
|
|
|
|
|
+ if (self.pingTimeoutTimer) {
|
|
|
|
|
+ clearTimeout(self.pingTimeoutTimer);
|
|
|
|
|
+ self.pingTimeoutTimer = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ self.pingTimeoutTimer = setTimeout(function () {
|
|
|
|
|
+ self.connection.closeAndClean();
|
|
|
|
|
+ if (!self.connection.doNotConnect) {
|
|
|
|
|
+ self.connection.waitReconnect();
|
|
|
|
|
+ }
|
|
|
|
|
+ }, self.config.pingTimeout);
|
|
|
|
|
+ }
|
|
|
|
|
+ }, this.config.heartbeat);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+Push.prototype.channel = function (name) {
|
|
|
|
|
+ return this.channels[name];
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+Push.prototype.allChannels = function () {
|
|
|
|
|
+ return this.channels;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+Push.prototype.createConnection = function () {
|
|
|
|
|
+ if (this.connection) {
|
|
|
|
|
+ throw new Error('Connection already exists');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var self = this;
|
|
|
|
|
+ var url = this.config.url;
|
|
|
|
|
+ var app_key = this.config.app_key;
|
|
|
|
|
+
|
|
|
|
|
+ function updateSubscribed() {
|
|
|
|
|
+ for (var i in self.channels) {
|
|
|
|
|
+ if (self.channels[i]) {
|
|
|
|
|
+ self.channels[i].subscribed = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ this.connection = new Connection({
|
|
|
|
|
+ url: url,
|
|
|
|
|
+ app_key: app_key,
|
|
|
|
|
+ onOpen: function () {
|
|
|
|
|
+ self.connection.state = 'connected';
|
|
|
|
|
+ self.checkoutPing();
|
|
|
|
|
+ },
|
|
|
|
|
+ onMessage: function (res) {
|
|
|
|
|
+ if (self.pingTimeoutTimer) {
|
|
|
|
|
+ clearTimeout(self.pingTimeoutTimer);
|
|
|
|
|
+ self.pingTimeoutTimer = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var params;
|
|
|
|
|
+ try {
|
|
|
|
|
+ params = JSON.parse(res.data);
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.warn('parse message error:', e, res.data);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var event = params.event;
|
|
|
|
|
+ var channel_name = params.channel;
|
|
|
|
|
+
|
|
|
|
|
+ if (event === 'pusher:pong') {
|
|
|
|
|
+ self.checkoutPing();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (event === 'pusher:error') {
|
|
|
|
|
+ throw new Error(params.data && params.data.message);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var data = params.data;
|
|
|
|
|
+ var channel;
|
|
|
|
|
+
|
|
|
|
|
+ if (event === 'pusher_internal:subscription_succeeded') {
|
|
|
|
|
+ channel = self.channels[channel_name];
|
|
|
|
|
+ if (channel) {
|
|
|
|
|
+ channel.subscribed = true;
|
|
|
|
|
+ channel.processQueue();
|
|
|
|
|
+ channel.emit('pusher:subscription_succeeded');
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (event === 'pusher:connection_established') {
|
|
|
|
|
+ self.connection.socket_id = data.socket_id;
|
|
|
|
|
+ self.connection.state = 'connected';
|
|
|
|
|
+ self.subscribeAll();
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (event && event.indexOf('pusher_internal') !== -1) {
|
|
|
|
|
+ console.log('Event "' + event + '" not implemented');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ channel = self.channels[channel_name];
|
|
|
|
|
+ if (channel) {
|
|
|
|
|
+ if (typeof data === 'string') {
|
|
|
|
|
+ try {
|
|
|
|
|
+ data = JSON.parse(data);
|
|
|
|
|
+ } catch (e) {}
|
|
|
|
|
+ }
|
|
|
|
|
+ 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) {
|
|
|
|
|
+ if (this.channels[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 () {
|
|
|
|
|
+ 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);
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 频道创建辅助函数 ====================
|
|
|
|
|
+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 () {
|
|
|
|
|
+ tt.request({
|
|
|
|
|
+ url: push.config.auth,
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ data: { channel_name: channel_name, socket_id: push.connection.socket_id },
|
|
|
|
|
+ success: function (res) {
|
|
|
|
|
+ var data;
|
|
|
|
|
+ try {
|
|
|
|
|
+ data = res.data;
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ console.error('parse auth response error:', e);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ data.channel = channel_name;
|
|
|
|
|
+ push.connection.send(JSON.stringify({ event: 'pusher:subscribe', data: data }));
|
|
|
|
|
+ },
|
|
|
|
|
+ fail: function (err) {
|
|
|
|
|
+ console.error('auth request fail:', err);
|
|
|
|
|
+ throw new Error(err.errMsg);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+ channel.processSubscribe();
|
|
|
|
|
+ return channel;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function createPresenceChannel(channel_name, push) {
|
|
|
|
|
+ return createPrivateChannel(channel_name, push);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ==================== 导出 ====================
|
|
|
|
|
+module.exports = {
|
|
|
|
|
+ Push: Push
|
|
|
|
|
+};
|