Path: blob/master/webroot/rsrc/js/application/aphlict/Aphlict.js
12242 views
/**1* @provides javelin-aphlict2* @requires javelin-install3* javelin-util4* javelin-websocket5* javelin-leader6* javelin-json7*/89/**10* Client for the notification server. Example usage:11*12* var aphlict = new JX.Aphlict('ws://localhost:22280', subscriptions)13* .setHandler(function(message) {14* // ...15* })16* .start();17*18*/19JX.install('Aphlict', {2021construct: function(uri, subscriptions) {22if (__DEV__) {23if (JX.Aphlict._instance) {24JX.$E('Aphlict object is a singleton.');25}26}2728this._uri = uri;29this._subscriptions = subscriptions;30this._setStatus('setup');31this._startTime = new Date().getTime();3233JX.Aphlict._instance = this;34},3536events: ['didChangeStatus'],3738members: {39_uri: null,40_socket: null,41_subscriptions: null,42_status: null,43_isReconnect: false,44_keepaliveInterval: false,45_startTime: null,4647start: function() {48JX.Leader.listen('onBecomeLeader', JX.bind(this, this._lead));49JX.Leader.listen('onReceiveBroadcast', JX.bind(this, this._receive));50JX.Leader.start();5152JX.Leader.call(JX.bind(this, this._begin));53},5455getSubscriptions: function() {56return this._subscriptions;57},5859setSubscriptions: function(subscriptions) {60this._subscriptions = subscriptions;61JX.Leader.broadcast(62null,63{type: 'aphlict.subscribe', data: this._subscriptions});64},6566clearSubscriptions: function(subscriptions) {67this._subscriptions = null;68JX.Leader.broadcast(69null,70{type: 'aphlict.unsubscribe', data: subscriptions});71},7273getStatus: function() {74return this._status;75},7677getWebsocket: function() {78return this._socket;79},8081_begin: function() {82JX.Leader.broadcast(83null,84{type: 'aphlict.getstatus'});85JX.Leader.broadcast(86null,87{type: 'aphlict.subscribe', data: this._subscriptions});88},8990_lead: function() {91this._socket = new JX.WebSocket(this._uri);92this._socket.setOpenHandler(JX.bind(this, this._open));93this._socket.setMessageHandler(JX.bind(this, this._message));94this._socket.setCloseHandler(JX.bind(this, this._close));9596this._socket.open();97},9899_open: function() {100// If this is a reconnect, ask the server to replay recent messages101// after other tabs have had a chance to subscribe. Do this before we102// broadcast that the connection status is now open.103if (this._isReconnect) {104setTimeout(JX.bind(this, this._didReconnect), 100);105}106107this._broadcastStatus('open');108JX.Leader.broadcast(null, {type: 'aphlict.getsubscribers'});109110// By default, ELBs terminate connections after 60 seconds with no111// traffic. Other load balancers may have similar configuration. Send112// a keepalive message every 15 seconds to prevent load balancers from113// deciding they can reap this connection.114115var keepalive = JX.bind(this, this._keepalive);116this._keepaliveInterval = setInterval(keepalive, 15000);117},118119_didReconnect: function() {120this.replay();121this.reconnect();122},123124replay: function() {125var age = 60000;126127// If the page was loaded a few moments ago, only query for recent128// history. This keeps us from replaying events over and over again as129// a user browses normally.130131// Allow a small margin of error for the actual page load time. It's132// also fine to replay a notification which the user saw for a brief133// moment on the previous page.134var extra_time = 500;135var now = new Date().getTime();136137age = Math.min(extra_time + (now - this._startTime), age);138139var replay = {140age: age141};142143JX.Leader.broadcast(null, {type: 'aphlict.replay', data: replay});144},145146reconnect: function() {147JX.Leader.broadcast(null, {type: 'aphlict.reconnect', data: null});148},149150_close: function() {151if (this._keepaliveInterval) {152clearInterval(this._keepaliveInterval);153this._keepaliveInterval = null;154}155156this._broadcastStatus('closed');157},158159_broadcastStatus: function(status) {160JX.Leader.broadcast(null, {type: 'aphlict.status', data: status});161},162163_message: function(raw) {164var message = JX.JSON.parse(raw);165var id = message.uniqueID || null;166167// If this is just a keepalive response, don't bother broadcasting it.168if (message.type == 'pong') {169return;170}171172JX.Leader.broadcast(id, {type: 'aphlict.server', data: message});173},174175_receive: function(message, is_leader) {176switch (message.type) {177case 'aphlict.status':178this._setStatus(message.data);179break;180181case 'aphlict.getstatus':182if (is_leader) {183this._broadcastStatus(this.getStatus());184}185break;186187case 'aphlict.getsubscribers':188JX.Leader.broadcast(189null,190{type: 'aphlict.subscribe', data: this._subscriptions});191break;192193case 'aphlict.subscribe':194if (is_leader) {195this._writeCommand('subscribe', message.data);196}197break;198199case 'aphlict.replay':200if (is_leader) {201this._writeCommand('replay', message.data);202}203break;204205default:206var handler = this.getHandler();207handler && handler(message);208break;209}210},211212_setStatus: function(status) {213this._status = status;214215// If we've ever seen an open connection, any new connection we make216// is a reconnect and should replay history.217if (status == 'open') {218this._isReconnect = true;219}220221this.invoke('didChangeStatus');222},223224_write: function(message) {225this._socket.send(JX.JSON.stringify(message));226},227228_writeCommand: function(command, message) {229var frame = {230command: command,231data: message232};233234return this._write(frame);235},236237_keepalive: function() {238this._writeCommand('ping', null);239}240241},242243properties: {244handler: null245},246247statics: {248_instance: null,249250getInstance: function() {251var self = JX.Aphlict;252if (!self._instance) {253return null;254}255return self._instance;256}257258}259260});261262263