diff --git a/resources/celerity/map.php b/resources/celerity/map.php --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => '8d1c0f87', - 'core.pkg.js' => 'b6a9c22a', + 'core.pkg.js' => 'f68622ac', 'darkconsole.pkg.js' => '8ab24e01', 'differential.pkg.css' => '8af45893', 'differential.pkg.js' => 'dad3622f', @@ -342,9 +342,9 @@ 'rsrc/image/texture/table_header.png' => '5c433037', 'rsrc/image/texture/table_header_hover.png' => '038ec3b9', 'rsrc/image/texture/table_header_tall.png' => 'd56b434f', - 'rsrc/js/application/aphlict/Aphlict.js' => 'b300dac3', + 'rsrc/js/application/aphlict/Aphlict.js' => 'bd5ef34c', 'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => '335470d7', - 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '62998733', + 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'cb695c97', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', @@ -535,10 +535,10 @@ 'herald-rule-editor' => '335fd41f', 'herald-test-css' => '778b008e', 'inline-comment-summary-css' => '8cfd34e8', - 'javelin-aphlict' => 'b300dac3', + 'javelin-aphlict' => 'bd5ef34c', 'javelin-behavior' => '61cbc29a', 'javelin-behavior-aphlict-dropdown' => '335470d7', - 'javelin-behavior-aphlict-listen' => '62998733', + 'javelin-behavior-aphlict-listen' => 'cb695c97', 'javelin-behavior-aphlict-status' => 'ea681761', 'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884', 'javelin-behavior-aphront-crop' => 'fa0f4fc2', @@ -1212,18 +1212,6 @@ 'javelin-magical-init', 'javelin-util', ), - 62998733 => array( - 'javelin-behavior', - 'javelin-aphlict', - 'javelin-stratcom', - 'javelin-request', - 'javelin-uri', - 'javelin-dom', - 'javelin-json', - 'javelin-router', - 'javelin-util', - 'phabricator-notification', - ), '6453c869' => array( 'javelin-install', 'javelin-dom', @@ -1581,13 +1569,6 @@ 'javelin-dom', 'phortune-credit-card-form', ), - 'b300dac3' => array( - 'javelin-install', - 'javelin-util', - 'javelin-websocket', - 'javelin-leader', - 'javelin-json', - ), 'b3a4b884' => array( 'javelin-behavior', 'phabricator-prefab', @@ -1623,6 +1604,13 @@ 'javelin-vector', 'javelin-stratcom', ), + 'bd5ef34c' => array( + 'javelin-install', + 'javelin-util', + 'javelin-websocket', + 'javelin-leader', + 'javelin-json', + ), 'bdaf4d04' => array( 'javelin-behavior', 'javelin-dom', @@ -1682,6 +1670,19 @@ 'javelin-stratcom', 'phabricator-phtize', ), + 'cb695c97' => array( + 'javelin-behavior', + 'javelin-aphlict', + 'javelin-stratcom', + 'javelin-request', + 'javelin-uri', + 'javelin-dom', + 'javelin-json', + 'javelin-router', + 'javelin-util', + 'javelin-leader', + 'phabricator-notification', + ), 'cc1bd0b0' => array( 'javelin-install', 'javelin-event', diff --git a/webroot/rsrc/js/application/aphlict/Aphlict.js b/webroot/rsrc/js/application/aphlict/Aphlict.js --- a/webroot/rsrc/js/application/aphlict/Aphlict.js +++ b/webroot/rsrc/js/application/aphlict/Aphlict.js @@ -41,6 +41,12 @@ _subscriptions: null, _status: null, + /** + * Starts the Aphlict client. + * + * This tab will attempt to become the leader tab. The leader tab will be + * responsible for sending and receiving messages from the Aphlict server. + */ start: function() { JX.Leader.listen('onBecomeLeader', JX.bind(this, this._lead)); JX.Leader.listen('onReceiveBroadcast', JX.bind(this, this._receive)); @@ -62,6 +68,12 @@ {type: 'aphlict.subscribe', data: this._subscriptions}); }, + /** + * Communicate with the Aphlict server. + * + * This function will be executed by the leader tab and will setup the + * necessary callbacks for communicating with the Aphlict server. + */ _lead: function() { this._socket = new JX.WebSocket(this._uri); this._socket.setOpenHandler(JX.bind(this, this._open)); @@ -84,29 +96,39 @@ JX.Leader.broadcast(null, {type: 'aphlict.status', data: status}); }, + /** + * Receive a message from the Aphlict server. + * + * This callback is used by the leader tab to handle messages from the + * Aphlict server. When a message is received from the server it is then + * broadcast to all other tabs. + */ _message: function(raw) { var message = JX.JSON.parse(raw); JX.Leader.broadcast(null, {type: 'aphlict.server', data: message}); }, + /** + * Receive a message from the leader. + * + * When the leader receives a message from the Aphlict server, it will be + * broadcast to all tabs. + */ _receive: function(message, is_leader) { switch (message.type) { case 'aphlict.status': this._setStatus(message.data); break; - case 'aphlict.getstatus': if (is_leader) { this._broadcastStatus(this.getStatus()); } break; - case 'aphlict.getsubscribers': JX.Leader.broadcast( null, {type: 'aphlict.subscribe', data: this._subscriptions}); break; - case 'aphlict.subscribe': if (is_leader) { this._write({ @@ -115,19 +137,28 @@ }); } break; - case 'aphlict.server': var handler = this.getHandler(); handler && handler(message.data); break; + default: + var handler = this.getHandler(); + handler && handler(message, is_leader); + break; } }, + /** + * Set a human-readable status string. + */ _setStatus: function(status) { this._status = status; this.invoke('didChangeStatus'); }, + /** + * Send a message to the Aphlict server by writing to the socket. + */ _write: function(message) { this._socket.send(JX.JSON.stringify(message)); } diff --git a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js --- a/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js +++ b/webroot/rsrc/js/application/aphlict/behavior-aphlict-listen.js @@ -9,22 +9,46 @@ * javelin-json * javelin-router * javelin-util + * javelin-leader * phabricator-notification */ JX.behavior('aphlict-listen', function(config) { + var showing_reload = false; - JX.Stratcom.listen('aphlict-receive-message', null, function(e) { + JX.Stratcom.listen('aphlict-server-message', null, function(e) { var message = e.getData(); if (message.type != 'notification') { return; } + JX.Leader.callIfLeader(function() { + requestNotification(message); + }); + }); + + // Respond to a notification from the Aphlict notification server. We send + // a request to Phabricator to get notification details. + function onAphlictMessage(message, is_leader) { + switch (message.type) { + case 'aphlict.server': + JX.Stratcom.invoke('aphlict-server-message', null, message); + break; + + case 'notification.individual': + JX.Stratcom.invoke('aphlict-notification-message'); + break; + } + + // Request the individual notification. This function is only called by the + // leader tab, which then broadcasts the notification content back to all + // other tabs. + function requestNotification(message) { var request = new JX.Request( - '/notification/individual/', - onNotification); + '/notification/individual/', + receiveNotification); var routable = request .addData({key: message.key}) @@ -35,30 +59,32 @@ .setPriority(250); JX.Router.getInstance().queue(routable); - }); - - // Respond to a notification from the Aphlict notification server. We send - // a request to Phabricator to get notification details. - function onAphlictMessage(message) { - JX.Stratcom.invoke('aphlict-receive-message', null, message); } - // Respond to a response from Phabricator about a specific notification. - function onNotification(response) { + // Receive the individual notification and broadcast it to all other tabs. + function receiveNotification(response) { if (!response.pertinent) { return; } + JX.Leader.broadcast(null, { + type: 'notification.individual', + data: response + }); + } + + // Respond to a response from Phabricator about a specific notification. + JX.Stratcom.listen('aphlict-notification-message', null, function(e) { JX.Stratcom.invoke('notification-panel-update', null, {}); // Show the notification itself. new JX.Notification() - .setContent(JX.$H(response.content)) + .setContent(JX.$H(e.content)) .show(); - // If the notification affected an object on this page, show a - // permanent reload notification if we aren't already. - if ((response.primaryObjectPHID in config.pageObjects) && !showing_reload) { + // If the notification affected an object on this page, show a permanent + // reload notification if we aren't already. + if ((e.primaryObjectPHID in config.pageObjects) && !showing_reload) { var reload = new JX.Notification() .setContent('Page updated, click to reload.') .alterClassName('jx-notification-alert', true)