Permalink
Browse files

Make Notifications Realtime

Summary:
Adds the node.js Aphlict server, the flash Aphlict client, and some
supporting javascript. Built on top of - and requires - D2703 (which is still
in progress).  Will likely work with no modification on top of the final
version, though.

The node server is currently run with

   sudo node support/aphlict/server/aphlict_server.js

Test Plan: tested locally

Reviewers: epriestley

Reviewed By: epriestley

CC: allenjohnashton, keebuhm, aran, Korvin

Differential Revision: https://secure.phabricator.com/D2704
  • Loading branch information...
1 parent 2bade93 commit f8f195b329f92900b29bac8c9098c328dd3daa74 @ddfisher ddfisher committed with epriestley Jun 12, 2012
@@ -772,14 +772,15 @@
),
'javelin-behavior-aphlict-listen' =>
array(
- 'uri' => '/res/6388e057/rsrc/js/application/aphlict/behavior-aphlict-listen.js',
+ 'uri' => '/res/7f4bc63b/rsrc/js/application/aphlict/behavior-aphlict-listen.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-aphlict',
2 => 'javelin-util',
3 => 'javelin-stratcom',
+ 4 => 'javelin-behavior-aphlict-dropdown',
),
'disk' => '/rsrc/js/application/aphlict/behavior-aphlict-listen.js',
),
@@ -736,6 +736,7 @@
'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/PhabricatorMySQLFileStorageEngine.php',
'PhabricatorNotificationBuilder' => 'applications/notification/builder/PhabricatorNotificationBuilder.php',
'PhabricatorNotificationController' => 'applications/notification/controller/PhabricatorNotificationController.php',
+ 'PhabricatorNotificationIndividualController' => 'applications/notification/controller/PhabricatorNotificationIndividualController.php',
'PhabricatorNotificationPanelController' => 'applications/notification/controller/PhabricatorNotificationPanelController.php',
'PhabricatorNotificationQuery' => 'applications/notification/PhabricatorNotificationQuery.php',
'PhabricatorNotificationStoryView' => 'applications/notification/view/PhabricatorNotificationStoryView.php',
@@ -1698,6 +1699,7 @@
'PhabricatorMustVerifyEmailController' => 'PhabricatorAuthController',
'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine',
'PhabricatorNotificationController' => 'PhabricatorController',
+ 'PhabricatorNotificationIndividualController' => 'PhabricatorNotificationController',
'PhabricatorNotificationPanelController' => 'PhabricatorNotificationController',
'PhabricatorNotificationStoryView' => 'PhabricatorNotificationView',
'PhabricatorNotificationTestController' => 'PhabricatorNotificationController',
@@ -423,6 +423,8 @@ public function getURIMap() {
'/notification/test/' => 'PhabricatorNotificationTestController',
'/notification/panel/' => 'PhabricatorNotificationPanelController',
+ '/notification/individual/'
+ => 'PhabricatorNotificationIndividualController',
'/flag/' => array(
'' => 'PhabricatorFlagListController',
'view/(?P<view>[^/]+)/' => 'PhabricatorFlagListController',
@@ -98,6 +98,7 @@ public function publish() {
if (PhabricatorEnv::getEnvConfig('notification.enabled')) {
$this->insertNotifications($chrono_key);
+ $this->sendNotification($chrono_key);
}
return $story;
}
@@ -136,6 +137,17 @@ private function insertNotifications($chrono_key) {
implode(', ', $sql));
}
+ private function sendNotification($chrono_key) {
+ $aphlict_url = 'http://127.0.0.1:22281/push?'; //TODO: make configurable
+ $future = new HTTPFuture($aphlict_url, array(
+ "key" => (string)$chrono_key,
+ // TODO: fix. \r\n appears to be appended to the final value here.
+ // this is a temporary workaround
+ "nothing" => "",
+ ));
+ $future->setMethod('POST');
+ $future->resolve();
+ }
/**
* We generate a unique chronological key for each story type because we want
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * Copyright 2012 Facebook, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+final class PhabricatorNotificationIndividualController
+ extends PhabricatorNotificationController {
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $user = $request->getUser();
+
+ $chron_key = $request->getStr('key');
+ $story = id(new PhabricatorFeedStoryNotification())
+ ->loadOneWhere('userPHID = %s AND chronologicalKey = %s',
+ $user->getPHID(),
+ $chron_key);
+
+ if ($story == null) {
+ $json = array( "pertinent" => false );
+ } else {
+ $json = array(
+ "pertinent" => true,
+ "primaryObjectPHID" => $story->getPrimaryObjectPHID(),
+ );
+ }
+
+ return id(new AphrontAjaxResponse())->setContent($json);
+ }
+}
@@ -33,10 +33,18 @@ public function processRequest() {
$builder = new PhabricatorNotificationBuilder($stories);
$notifications_view = $builder->buildView();
+ $num_unconsumed = 0;
+ foreach ($stories as $story) {
+ if (!$story->getHasViewed()) {
+ $num_unconsumed++;
+ }
+ }
+
$json = array(
"content" => $stories ?
$notifications_view->render() :
"<b>You currently have no notifications<b>",
+ "number" => $num_unconsumed,
);
return id(new AphrontAjaxResponse())->setContent($json);
@@ -376,18 +376,19 @@ protected function getBody() {
if (PhabricatorEnv::getEnvConfig('notification.enabled') &&
$user->isLoggedIn()) {
+
$aphlict_object_id = 'aphlictswfobject';
- $aphlict_content = phutil_render_tag(
- 'object',
+ $server_uri = new PhutilURI(PhabricatorEnv::getURI(''));
+ $server_domain = $server_uri->getDomain();
+
+ Javelin::initBehavior(
+ 'aphlict-listen',
array(
- 'classid' => 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000',
- ),
- '<param name="movie" value="/rsrc/swf/aphlict.swf" />'.
- '<param name="allowScriptAccess" value="always" />'.
- '<param name="wmode" value="opaque" />'.
- '<embed src="/rsrc/swf/aphlict.swf" wmode="opaque" id="'.
- $aphlict_object_id.'"></embed>');
+ 'id' => $aphlict_object_id,
+ 'server' => $server_domain,
+ 'port' => 2600,
+ ));
Javelin::initBehavior('aphlict-dropdown', array());
@@ -405,8 +406,7 @@ protected function getBody() {
$notification_header =
$notification_indicator.
'<td>'.
- '<div style="height:1px; width:1px;">'.
- $aphlict_content.
+ '<div id="aphlictswf-container" style="height:1px; width:1px;">'.
'</div>'.
'</td>';
$notification_dropdown =
@@ -7,23 +7,12 @@ package {
import flash.events.*;
import flash.external.ExternalInterface;
- import com.phabricator.*;
-
import vegas.strings.JSON;
public class Aphlict extends Sprite {
private var client:String;
- private var master:LocalConnection;
- private var recv:LocalConnection;
- private var send:LocalConnection;
-
- private var receiver:AphlictReceiver;
- private var loyalUntil:Number = 0;
- private var subjects:Array;
- private var frequency:Number = 100;
-
private var socket:Socket;
private var readBuffer:ByteArray;
@@ -47,60 +36,10 @@ package {
this.remoteServer = server;
this.remotePort = port;
- this.master = null;
- this.receiver = new AphlictReceiver(this);
- this.subjects = [];
-
- this.send = new LocalConnection();
-
- this.recv = new LocalConnection();
- this.recv.client = this.receiver;
- for (var ii:Number = 0; ii < 32; ii++) {
- try {
- this.recv.connect('aphlict_subject_' + ii);
- this.client = 'aphlict_subject_' + ii;
- } catch (x:Error) {
- // Some other Aphlict client is holding that ID.
- }
- }
-
- if (!this.client) {
- // Too many clients open already, just exit.
- return;
- }
-
- this.usurp();
+ this.connectToServer();
+ return;
}
- private function usurp():void {
- if (this.master) {
- for (var ii:Number = 0; ii < this.subjects.length; ii++) {
- if (this.subjects[ii] == this.client) {
- continue;
- }
- this.send.send(this.subjects[ii], 'remainLoyal');
- }
- } else if (this.loyalUntil < new Date().getTime()) {
- var recv:LocalConnection = new LocalConnection();
- recv.client = this.receiver;
- try {
- recv.connect('aphlict_master');
- this.master = recv;
- this.subjects = [this.client];
-
- this.connectToServer();
-
- } catch (x:Error) {
- // Can't become the master.
- }
-
- if (!this.master) {
- this.send.send('aphlict_master', 'becomeLoyal', this.client);
- this.remainLoyal();
- }
- }
- setTimeout(this.usurp, this.frequency);
- }
public function connectToServer():void {
var socket:Socket = new Socket();
@@ -156,24 +95,14 @@ package {
t.writeBytes(b, msg_len + 8);
this.readBuffer = t;
- for (var ii:Number = 0; ii < this.subjects.length; ii++) {
- this.send.send(this.subjects[ii], 'receiveMessage', data);
- }
+ this.receiveMessage(data);
} else {
break;
}
} while (true);
}
- public function remainLoyal():void {
- this.loyalUntil = new Date().getTime() + (2 * this.frequency);
- }
-
- public function becomeLoyal(subject:String):void {
- this.subjects.push(subject);
- }
-
public function receiveMessage(msg:Object):void {
this.externalInvoke('receive', msg);
}
@@ -188,4 +117,4 @@ package {
}
-}
+}
@@ -1,25 +0,0 @@
-package com.phabricator {
-
- public class AphlictReceiver {
-
- private var core:Object;
-
- public function AphlictReceiver(core:Object) {
- this.core = core;
- }
-
- public function remainLoyal():void {
- this.core.remainLoyal();
- }
-
- public function becomeLoyal(subject:String):void {
- this.core.becomeLoyal(subject);
- }
-
- public function receiveMessage(msg:Object):void {
- this.core.receiveMessage(msg);
- }
-
- }
-
-}
Oops, something went wrong.

0 comments on commit f8f195b

Please sign in to comment.