1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-18 12:52:42 +01:00

Rewrite Aphlict to use Websockets

Summary:
Fixes T6559. No more flash, use Websockets. This is less aggressive than the earlier version, and retains more server logic.

  - Support "wss".
  - Make the client work.
  - Remove "notification.user" entirely.
  - Seems ok?

Test Plan:
In Safari, Firefox and Chrome, saw the browsers connect. Made a bunch of comments/updates and saw notifications.

Notable holes in the test plan:

  - Haven't tested "wss" yet. I'll do this on secure.
  - Notifications are //too fast// now, locally. I get them after I hit submit but before the page reloads.
  - There are probably some other rough edges, this is a fairly big patch.

Reviewers: joshuaspence, btrahan

Reviewed By: joshuaspence, btrahan

Subscribers: fabe, btrahan, epriestley

Maniphest Tasks: T6713, T6559

Differential Revision: https://secure.phabricator.com/D11143
This commit is contained in:
epriestley 2015-01-08 10:03:00 -08:00
parent 9a8eb4026e
commit 9e0f70e17d
31 changed files with 394 additions and 2776 deletions

3
.gitignore vendored
View file

@ -30,3 +30,6 @@
# User extensions
/src/extensions/*
# NPM local packages
/support/aphlict/server/node_modules/

View file

@ -1,581 +0,0 @@
Mozilla Public License 1.1 (MPL 1.1)
1. Definitions.
1.0.1. "Commercial Use" means distribution or
otherwise making the Covered Code available to a third
party.
1.1. ''Contributor'' means each entity that creates or
contributes to the creation of Modifications.
1.2. ''Contributor Version'' means the combination of
the Original Code, prior Modifications used by
a Contributor, and the Modifications made by that
particular Contributor.
1.3. ''Covered Code'' means the Original Code or
Modifications or the combination of the Original Code
and Modifications, in each case including portions
thereof.
1.4. ''Electronic Distribution Mechanism'' means a
mechanism generally accepted in the software
development community for the electronic transfer of
data.
1.5. ''Executable'' means Covered Code in any form
other than Source Code.
1.6. ''Initial Developer'' means the individual or
entity identified as the Initial Developer in the
Source Code notice required by Exhibit A.
1.7. ''Larger Work'' means a work which combines
Covered Code or portions thereof with code not
governed by the terms of this License.
1.8. ''License'' means this document.
1.8.1. "Licensable" means having the right to grant,
to the maximum extent possible, whether at the time of
the initial grant or subsequently acquired, any and
all of the rights conveyed herein.
1.9. ''Modifications'' means any addition to or
deletion from the substance or structure of either the
Original Code or any previous Modifications. When
Covered Code is released as a series of files, a
Modification is:
A. Any addition to or deletion from the contents
of a file containing Original Code or previous
Modifications.
B. Any new file that contains any part of the
Original Code or previous Modifications.
1.10. ''Original Code'' means Source Code of computer
software code which is described in the Source Code
notice required by Exhibit A as Original Code, and
which, at the time of its release under this License
is not already Covered Code governed by this License.
1.10.1. "Patent Claims" means any patent claim(s), now
owned or hereafter acquired, including without
limitation, method, process, and apparatus claims, in
any patent Licensable by grantor.
1.11. ''Source Code'' means the preferred form of the
Covered Code for making modifications to it, including
all modules it contains, plus any associated interface
definition files, scripts used to control compilation
and installation of an Executable, or source code
differential comparisons against either the Original
Code or another well known, available Covered Code of
the Contributor's choice. The Source Code can be in a
compressed or archival form, provided the appropriate
decompression or de-archiving software is widely
available for no charge.
1.12. "You'' (or "Your") means an individual or a
legal entity exercising rights under, and complying
with all of the terms of, this License or a future
version of this License issued under Section 6.1. For
legal entities, "You'' includes any entity which
controls, is controlled by, or is under common control
with You. For purposes of this definition, "control''
means (a) the power, direct or indirect, to cause the
direction or management of such entity, whether by
contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or
beneficial ownership of such entity.
2. Source Code License.
2.1. The Initial Developer Grant.
The Initial Developer hereby grants You a world-wide,
royalty-free, non-exclusive license, subject to third
party intellectual property claims:
(a) under intellectual property rights (other
than patent or trademark) Licensable by Initial
Developer to use, reproduce, modify, display,
perform, sublicense and distribute the Original
Code (or portions thereof) with or without
Modifications, and/or as part of a Larger Work;
and
(b) under Patents Claims infringed by the making,
using or selling of Original Code, to make, have
made, use, practice, sell, and offer for sale,
and/or otherwise dispose of the Original Code (or
portions thereof).
(c) the licenses granted in this Section 2.1(a)
and (b) are effective on the date Initial
Developer first distributes Original Code under
the terms of this License.
(d) Notwithstanding Section 2.1(b) above, no
patent license is granted: 1) for code that You
delete from the Original Code; 2) separate from
the Original Code; or 3) for infringements
caused by: i) the modification of the Original
Code or ii) the combination of the Original Code
with other software or devices.
2.2. Contributor Grant.
Subject to third party intellectual property claims,
each Contributor hereby grants You a world-wide,
royalty-free, non-exclusive license
(a) under intellectual property rights (other
than patent or trademark) Licensable by
Contributor, to use, reproduce, modify, display,
perform, sublicense and distribute the
Modifications created by such Contributor (or
portions thereof) either on an unmodified basis,
with other Modifications, as Covered Code and/or
as part of a Larger Work;
and
(b) under Patent Claims infringed by the making,
using, or selling of Modifications made by that
Contributor either alone and/or in combination
with its Contributor Version (or portions of such
combination), to make, use, sell, offer for sale,
have made, and/or otherwise dispose of: 1)
Modifications made by that Contributor (or
portions thereof); and 2) the combination of
Modifications made by that Contributor with its
Contributor Version (or portions of such
combination).
(c) the licenses granted in Sections 2.2(a) and
2.2(b) are effective on the date Contributor
first makes Commercial Use of the Covered Code.
(d) Notwithstanding Section 2.2(b) above, no
patent license is granted: 1) for any code that
Contributor has deleted from the Contributor
Version; 2) separate from the Contributor
Version; 3) for infringements caused by: i)
third party modifications of Contributor Version
or ii) the combination of Modifications made by
that Contributor with other software (except as
part of the Contributor Version) or other
devices; or 4) under Patent Claims infringed by
Covered Code in the absence of Modifications made
by that Contributor.
3. Distribution Obligations.
3.1. Application of License.
The Modifications which You create or to which You
contribute are governed by the terms of this License,
including without limitation Section 2.2. The Source
Code version of Covered Code may be distributed only
under the terms of this License or a future version of
this License released under Section 6.1, and You must
include a copy of this License with every copy of the
Source Code You distribute. You may not offer or
impose any terms on any Source Code version that
alters or restricts the applicable version of this
License or the recipients' rights hereunder. However,
You may include an additional document offering the
additional rights described in Section 3.5.
3.2. Availability of Source Code.
Any Modification which You create or to which You
contribute must be made available in Source Code form
under the terms of this License either on the same
media as an Executable version or via an accepted
Electronic Distribution Mechanism to anyone to whom
you made an Executable version available; and if made
available via Electronic Distribution Mechanism, must
remain available for at least twelve (12) months after
the date it initially became available, or at least
six (6) months after a subsequent version of that
particular Modification has been made available to
such recipients. You are responsible for ensuring that
the Source Code version remains available even if the
Electronic Distribution Mechanism is maintained by a
third party.
3.3. Description of Modifications.
You must cause all Covered Code to which You
contribute to contain a file documenting the changes
You made to create that Covered Code and the date of
any change. You must include a prominent statement
that the Modification is derived, directly or
indirectly, from Original Code provided by the Initial
Developer and including the name of the Initial
Developer in (a) the Source Code, and (b) in any
notice in an Executable version or related
documentation in which You describe the origin or
ownership of the Covered Code.
3.4. Intellectual Property Matters
(a) Third Party Claims.
If Contributor has knowledge that a license under
a third party's intellectual property rights is
required to exercise the rights granted by such
Contributor under Sections 2.1 or 2.2,
Contributor must include a text file with the
Source Code distribution titled "LEGAL'' which
describes the claim and the party making the
claim in sufficient detail that a recipient will
know whom to contact. If Contributor obtains such
knowledge after the Modification is made
available as described in Section 3.2,
Contributor shall promptly modify the LEGAL file
in all copies Contributor makes available
thereafter and shall take other steps (such as
notifying appropriate mailing lists or
newsgroups) reasonably calculated to inform those
who received the Covered Code that new knowledge
has been obtained.
(b) Contributor APIs.
If Contributor's Modifications include an
application programming interface and Contributor
has knowledge of patent licenses which are
reasonably necessary to implement that API,
Contributor must also include this information in
the LEGAL file.
(c) Representations.
Contributor represents that, except as disclosed
pursuant to Section 3.4(a) above, Contributor
believes that Contributor's Modifications are
Contributor's original creation(s) and/or
Contributor has sufficient rights to grant the
rights conveyed by this License.
3.5. Required Notices.
You must duplicate the notice in Exhibit A in each
file of the Source Code. If it is not possible to put
such notice in a particular Source Code file due to
its structure, then You must include such notice in a
location (such as a relevant directory) where a user
would be likely to look for such a notice. If You
created one or more Modification(s) You may add your
name as a Contributor to the notice described in
Exhibit A. You must also duplicate this License in
any documentation for the Source Code where You
describe recipients' rights or ownership rights
relating to Covered Code. You may choose to offer,
and to charge a fee for, warranty, support, indemnity
or liability obligations to one or more recipients of
Covered Code. However, You may do so only on Your own
behalf, and not on behalf of the Initial Developer or
any Contributor. You must make it absolutely clear
than any such warranty, support, indemnity or
liability obligation is offered by You alone, and You
hereby agree to indemnify the Initial Developer and
every Contributor for any liability incurred by the
Initial Developer or such Contributor as a result of
warranty, support, indemnity or liability terms You
offer.
3.6. Distribution of Executable Versions.
You may distribute Covered Code in Executable form
only if the requirements of Section 3.1-3.5 have been
met for that Covered Code, and if You include a notice
stating that the Source Code version of the Covered
Code is available under the terms of this License,
including a description of how and where You have
fulfilled the obligations of Section 3.2. The notice
must be conspicuously included in any notice in an
Executable version, related documentation or
collateral in which You describe recipients' rights
relating to the Covered Code. You may distribute the
Executable version of Covered Code or ownership rights
under a license of Your choice, which may contain
terms different from this License, provided that You
are in compliance with the terms of this License and
that the license for the Executable version does not
attempt to limit or alter the recipient's rights in
the Source Code version from the rights set forth in
this License. If You distribute the Executable version
under a different license You must make it absolutely
clear that any terms which differ from this License
are offered by You alone, not by the Initial Developer
or any Contributor. You hereby agree to indemnify the
Initial Developer and every Contributor for any
liability incurred by the Initial Developer or such
Contributor as a result of any such terms You offer.
3.7. Larger Works.
You may create a Larger Work by combining Covered Code
with other code not governed by the terms of this
License and distribute the Larger Work as a single
product. In such a case, You must make sure the
requirements of this License are fulfilled for the
Covered Code.
4. Inability to Comply Due to Statute or Regulation.
If it is impossible for You to comply with any of the
terms of this License with respect to some or all of
the Covered Code due to statute, judicial order, or
regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b)
describe the limitations and the code they affect.
Such description must be included in the LEGAL file
described in Section 3.4 and must be included with all
distributions of the Source Code. Except to the extent
prohibited by statute or regulation, such description
must be sufficiently detailed for a recipient of
ordinary skill to be able to understand it.
5. Application of this License.
This License applies to code to which the Initial
Developer has attached the notice in Exhibit A and to
related Covered Code.
6. Versions of the License.
6.1. New Versions.
Netscape Communications Corporation (''Netscape'') may
publish revised and/or new versions of the License
from time to time. Each version will be given a
distinguishing version number.
6.2. Effect of New Versions.
Once Covered Code has been published under a
particular version of the License, You may always
continue to use it under the terms of that version.
You may also choose to use such Covered Code under the
terms of any subsequent version of the License
published by Netscape. No one other than Netscape has
the right to modify the terms applicable to Covered
Code created under this License.
6.3. Derivative Works.
If You create or use a modified version of this
License (which you may only do in order to apply it to
code which is not already Covered Code governed by
this License), You must (a) rename Your license so
that the phrases ''Mozilla'', ''MOZILLAPL'',
''MOZPL'', ''Netscape'', "MPL", ''NPL'' or any
confusingly similar phrase do not appear in your
license (except to note that your license differs from
this License) and (b) otherwise make it clear that
Your version of the license contains terms which
differ from the Mozilla Public License and Netscape
Public License. (Filling in the name of the Initial
Developer, Original Code or Contributor in the notice
described in Exhibit A shall not of themselves be
deemed to be modifications of this License.)
7. DISCLAIMER OF WARRANTY.
COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS
IS'' BASIS, WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION,
WARRANTIES THAT THE COVERED CODE IS FREE OF DEFECTS,
MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-
INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE COVERED CODE IS WITH YOU. SHOULD
ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU
(NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR)
ASSUME THE COST OF ANY NECESSARY SERVICING, REPAIR OR
CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN
ESSENTIAL PART OF THIS LICENSE. NO USE OF ANY COVERED
CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS
DISCLAIMER.
8. TERMINATION.
8.1. This License and the rights granted hereunder
will terminate automatically if You fail to comply
with terms herein and fail to cure such breach within
30 days of becoming aware of the breach. All
sublicenses to the Covered Code which are properly
granted shall survive any termination of this License.
Provisions which, by their nature, must remain in
effect beyond the termination of this License shall
survive.
8.2. If You initiate litigation by asserting a patent
infringement claim (excluding declatory judgment
actions) against Initial Developer or a Contributor
(the Initial Developer or Contributor against whom You
file such action is referred to as "Participant")
alleging that:
(a) such Participant's Contributor Version directly
or indirectly infringes any patent, then any and all
rights granted by such Participant to You under
Sections 2.1 and/or 2.2 of this License shall, upon 60
days notice from Participant terminate prospectively,
unless if within 60 days after receipt of notice You
either: (i) agree in writing to pay Participant a
mutually agreeable reasonable royalty for Your past
and future use of Modifications made by such
Participant, or (ii) withdraw Your litigation claim
with respect to the Contributor Version against such
Participant. If within 60 days of notice, a
reasonable royalty and payment arrangement are not
mutually agreed upon in writing by the parties or the
litigation claim is not withdrawn, the rights granted
by Participant to You under Sections 2.1 and/or 2.2
automatically terminate at the expiration of the 60
day notice period specified above.
(b) any software, hardware, or device, other than
such Participant's Contributor Version, directly or
indirectly infringes any patent, then any rights
granted to You by such Participant under Sections 2.1
(b) and 2.2(b) are revoked effective as of the date
You first made, used, sold, distributed, or had made,
Modifications made by that Participant.
8.3. If You assert a patent infringement claim
against Participant alleging that such Participant's
Contributor Version directly or indirectly infringes
any patent where such claim is resolved (such as by
license or settlement) prior to the initiation of
patent infringement litigation, then the reasonable
value of the licenses granted by such Participant
under Sections 2.1 or 2.2 shall be taken into account
in determining the amount or value of any payment or
license.
8.4. In the event of termination under Sections 8.1
or 8.2 above, all end user license agreements
(excluding distributors and resellers) which have been
validly granted by You or any distributor hereunder
prior to termination shall survive termination.
9. LIMITATION OF LIABILITY.
UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY,
WHETHER TORT (INCLUDING NEGLIGENCE), CONTRACT, OR
OTHERWISE, SHALL YOU, THE INITIAL DEVELOPER, ANY OTHER
CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, OR
ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY
PERSON FOR ANY INDIRECT, SPECIAL, INCIDENTAL, OR
CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING,
WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK
STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND
ALL OTHER COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH
PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF
SUCH DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT
APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT
APPLICABLE LAW PROHIBITS SUCH LIMITATION. SOME
JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR LIMITATION
OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS
EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
10. U.S. GOVERNMENT END USERS.
The Covered Code is a ''commercial item,'' as that
term is defined in 48 C.F.R. 2.101 (Oct. 1995),
consisting of ''commercial computer software'' and
''commercial computer software documentation,'' as
such terms are used in 48 C.F.R. 12.212 (Sept. 1995).
Consistent with 48 C.F.R. 12.212 and 48 C.F.R.
227.7202-1 through 227.7202-4 (June 1995), all U.S.
Government End Users acquire Covered Code with only
those rights set forth herein.
11. MISCELLANEOUS.
This License represents the complete agreement
concerning subject matter hereof. If any provision of
this License is held to be unenforceable, such
provision shall be reformed only to the extent
necessary to make it enforceable. This License shall
be governed by California law provisions (except to
the extent applicable law, if any, provides
otherwise), excluding its conflict-of-law provisions.
With respect to disputes in which at least one party
is a citizen of, or an entity chartered or registered
to do business in the United States of America, any
litigation relating to this License shall be subject
to the jurisdiction of the Federal Courts of the
Northern District of California, with venue lying in
Santa Clara County, California, with the losing party
responsible for costs, including without limitation,
court costs and reasonable attorneys' fees and
expenses. The application of the United Nations
Convention on Contracts for the International Sale of
Goods is expressly excluded. Any law or regulation
which provides that the language of a contract shall
be construed against the drafter shall not apply to
this License.
12. RESPONSIBILITY FOR CLAIMS.
As between Initial Developer and the Contributors,
each party is responsible for claims and damages
arising, directly or indirectly, out of its
utilization of rights under this License and You agree
to work with Initial Developer and Contributors to
distribute such responsibility on an equitable basis.
Nothing herein is intended or shall be deemed to
constitute any admission of liability.
13. MULTIPLE-LICENSED CODE.
Initial Developer may designate portions of the
Covered Code as Multiple-Licensed. Multiple-Licensed
means that the Initial Developer permits you to
utilize portions of the Covered Code under Your choice
of the NPL or the alternative licenses, if any,
specified by the Initial Developer in the file
described in Exhibit A.
EXHIBIT A - Mozilla Public License.
The contents of this file are subject to the Mozilla Public License Version
1.1 (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.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is ______________________________________.
The Initial Developer of the Original Code is
________________________.
Portions created by ______________________ are Copyright (C) ______
_______________________. All Rights Reserved.
Contributor(s): ______________________________________.
Alternatively, the contents of this file may be used under the terms of
the _____ license (the [___] License), in which case the provisions of
[______] License are applicable instead of those above. If you wish to
allow use of your version of this file only under the terms of the [____]
License and not to allow others to use your version of this file under
the MPL, indicate your decision by deleting the provisions above and
replace them with the notice and other provisions required by the [___]
License. If you do not delete the provisions above, a recipient may use
your version of this file under either the MPL or the [___] License.
[NOTE: The text of this Exhibit A may differ slightly from the text of
the notices in the Source Code files of the Original Code. You should use
the text of this Exhibit A rather than the text found in the Original Code
Source Code for Your Modifications.]

View file

@ -1,29 +0,0 @@
VEGAS AS3 - version 1.8.5.2228
The "vegas.swc" library contains all the AS3 source code of the VEGAS project with all this extensions.
LICENCE
Version: MPL 1.1/GPL 2.0/LGPL 2.1
PROJECT PAGES
* http://code.google.com/p/vegas/
DOCUMENTATION & CO
* http://code.google.com/p/vegas/ (tutorials and install)
* http://code.google.com/p/vegas/issues/list (issues)
* http://www.ekameleon.net/vegas/docs
* http://www.ekameleon.net/blog/ (french blog)
ABOUT AUTHOR
* Author : ALCARAZ Marc (eKameleon)
* Link : http://www.ekameleon.net/blog
* Mail : ekameleon@gmail.com
NOTES
The vegas.swc file target now the FlashPlayer 10.2 and build with the Flex SDK 4.5.0.19786.

View file

@ -1,86 +0,0 @@
/*
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (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.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the
License.
The Original Code is [maashaack framework].
The Initial Developers of the Original Code are
Zwetan Kjukov <zwetan@gmail.com> and Marc Alcaraz <ekameleon@gmail.com>.
Portions created by the Initial Developers are Copyright (C) 2006-2011
the Initial Developers. All Rights Reserved.
Contributor(s):
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the LGPL or the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
*/
package system
{
/**
* Defines what a Serializer have to implements to be integrated in the framework.
* <p><b>Note :</b> Every serializers (eden, json, wddx, etc.) should implement it.</p>
*/
public interface Serializer
{
/**
* The prettyIndent value of the serializer.
*/
function get prettyIndent():int;
/**
* @private
*/
function set prettyIndent( value:int ):void;
/**
* The prettyPrinting value of the serializer.
*/
function get prettyPrinting():Boolean;
/**
* @private
*/
function set prettyPrinting( value:Boolean ):void;
/**
* The identor String value of the serializer.
*/
function get indentor():String;
/**
* @private
*/
function set indentor( value:String ):void;
/**
* Deserialize the specified String source representation.
*/
function deserialize( source:String ):*;
/**
* Serialize the specified object.
*/
function serialize( value:* ):String;
}
}

View file

@ -1,107 +0,0 @@
/*
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (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.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is VEGAS Framework.
The Initial Developer of the Original Code is
ALCARAZ Marc (aka eKameleon) <ekameleon@gmail.com>.
Portions created by the Initial Developer are Copyright (C) 2004-2011
the Initial Developer. All Rights Reserved.
Contributor(s) :
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the LGPL or the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
*/
package vegas.strings
{
import vegas.strings.json.JSONSerializer;
/**
* <code class="prettyprint">JSON</code> (JavaScript object Notation) is a lightweight data-interchange format.
* <p>More information in the official site : <a href="http://www.JSON.org/">http://www.JSON.org</a></p>
* <p>Add Hexa Digits tool in deserialize method - <a href="http://code.google.com/p/edenrr/">eden inspiration</a></p>
*
* @example Example
* <listing version="3.0">
* <code class="prettyprint">
* import core.getClassName ;
*
* import vegas.strings.JSON;
* import vegas.strings.errors.JSONError;
*
* // --- Init
*
* var a:Array = [2, true, "hello"] ;
* var o:Object = { prop1 : 1 , prop2 : 2 } ;
* var s:String = "hello world" ;
* var n:Number = 4 ;
* var b:Boolean = true ;
*
* trace("# Serialize \r") ;
*
* trace("- a : " + JSON.serialize( a ) ) ;
* trace("- o : " + JSON.serialize( o ) ) ;
* trace("- s : " + JSON.serialize( s ) ) ;
* trace("- n : " + JSON.serialize( n ) ) ;
* trace("- b : " + JSON.serialize( b ) ) ;
*
* trace ("\r# Deserialize \r") ;
*
* var source:String = '[ { "prop1" : 0xFF0000 , prop2:2, prop3:"hello", prop4:true} , 2, true, 3, [3, 2] ]' ;
*
* o = JSON.deserialize(source) ;
*
* var l:uint = o.length ;
* for (var i:uint = 0 ; i &lt; l ; i++)
* {
* trace("> " + i + " : " + o[i] + " -> typeof :: " + typeof(o[i])) ;
* if (typeof(o[i]) == "object")
* {
* for (var each:String in o[i])
* {
* trace(" > " + each + " : " + o[i][each] + " :: " + getClassName(o[i][each]) ) ;
* }
* }
* }
*
* trace ("\r# JSONError \r") ;
*
* source = "[3, 2," ; // test1
*
* // var source:String = '{"prop1":coucou"}' ; // test2
*
* try
* {
* var errorObj:Object = JSON.deserialize(source) ;
* }
* catch( e:JSONError )
* {
* trace( e.toString() ) ;
* }
* </code>
* </listing>
*/
public var JSON:JSONSerializer = new JSONSerializer() ;
}

View file

@ -1,84 +0,0 @@
/*
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (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.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is VEGAS Framework.
The Initial Developer of the Original Code is
ALCARAZ Marc (aka eKameleon) <ekameleon@gmail.com>.
Portions created by the Initial Developer are Copyright (C) 2004-2011
the Initial Developer. All Rights Reserved.
Contributor(s) :
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the LGPL or the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
*/
package vegas.strings.json
{
/**
* This JSONError is throw in the JSON static methods.
*/
public class JSONError extends Error
{
/**
* Creates a new JSONError instance.
*/
public function JSONError( message:String, at:uint, source:String , id:int=0 )
{
super( message , id );
name = "JSONError" ;
this.at = at ;
this.source = source ;
}
/**
* The position of char with an error parsing in the JSON String representation.
*/
public var at:uint ;
/**
* The source ot the bad parsing.
*/
public var source:String ;
/**
* Returns a String representation of the object.
* @return a String representation of the object.
*/
public function toString():String
{
var msg:String = "## " + name + " : " + message + " ##" ;
if (!isNaN(at))
{
msg += ", at:" + at ;
}
if ( source != null )
{
msg += " in \"" + source + "\"";
}
return msg ;
}
}
}

View file

@ -1,757 +0,0 @@
/*
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (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.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is VEGAS Framework.
The Initial Developer of the Original Code is
ALCARAZ Marc (aka eKameleon) <ekameleon@gmail.com>.
Portions created by the Initial Developer are Copyright (C) 2004-2011
the Initial Developer. All Rights Reserved.
Contributor(s) :
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the LGPL or the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
*/
package vegas.strings.json
{
import system.Serializer;
/**
* This class is the concrete class of the JSON singleton.
* <code class="prettyprint">JSON</code> (JavaScript object Notation) is a lightweight data-interchange format.
* <p>More information in the official site : <a href="http://www.JSON.org/">http://www.JSON.org</a></p>
* <p>Add Hexa Digits tool in deserialize method - <a href="http://code.google.com/p/edenrr/">eden inspiration</a></p>
* <p><b>Example :</b></p>
* <pre class="prettyprint">
* import core.getClassName ;
*
* import vegas.strings.JSON;
* import vegas.strings.JSONError;
*
* // --- Init
*
* var a:Array = [2, true, "hello"] ;
* var o:Object = { prop1 : 1 , prop2 : 2 } ;
* var s:String = "hello world" ;
* var n:Number = 4 ;
* var b:Boolean = true ;
*
* trace("Serialize") ;
*
* trace("- a : " + JSON.serialize( a ) ) ;
* trace("- o : " + JSON.serialize( o ) ) ;
* trace("- s : " + JSON.serialize( s ) ) ;
* trace("- n : " + JSON.serialize( n ) ) ;
* trace("- b : " + JSON.serialize( b ) ) ;
*
* trace ("Deserialize") ;
*
* var source:String = '[ { "prop1" : 0xFF0000 , prop2:2, prop3:"hello", prop4:true} , 2, true, 3, [3, 2] ]' ;
*
* o = JSON.deserialize(source) ;
*
* var l:uint = o.length ;
* for (var i:uint = 0 ; i &lt; l ; i++)
* {
* trace("- " + i + " : " + o[i] + " , typeof :: " + typeof(o[i])) ;
* if (typeof(o[i]) == "object")
* {
* for (var each:String in o[i])
* {
* trace(" + " + each + " : " + o[i][each] + " :: " + getClassName(o[i][each]) ) ;
* }
* }
* }
*
* trace ("JSONError") ;
*
* source = "[3, 2," ; // test1
*
* // var source:String = '{"prop1":coucou"}' ; // test2
*
* try
* {
* var errorObj:Object = JSON.deserialize(source) ;
* }
* catch( e:JSONError )
* {
* trace( e.toString() ) ;
* }
* </pre>
*/
public class JSONSerializer implements Serializer
{
/**
* Creates a new JSONSerializer instance.
*/
public function JSONSerializer()
{
//
}
/**
* The source to evaluate.
*/
public var source:String ;
/**
* Indicates the indentor string representation.
*/
public function get indentor():String
{
return _indentor;
}
/**
* @private
*/
public function set indentor(value:String):void
{
_indentor = value;
}
/**
* Indicates the pretty indent value.
*/
public function get prettyIndent():int
{
return _prettyIndent;
}
/**
* @private
*/
public function set prettyIndent(value:int):void
{
_prettyIndent = value ;
}
/**
* Indicates the pretty printing flag value.
*/
public function get prettyPrinting():Boolean
{
return _prettyPrinting ;
}
/**
* @private
*/
public function set prettyPrinting(value:Boolean):void
{
_prettyPrinting = value;
}
/**
* Parse a string and interpret the source code to the correct object construct.
* <p><b>Example :</b></p>
* <pre class="prettyprint">
* "hello world" --> "hello world"
* "0xFF" --> 255
* "{a:1,"b":2}" --> {a:1,b:2}
* </pre>
* @return a string representing the data.
*/
public function deserialize( source:String ):*
{
this.source = source ;
at = 0 ;
ch = ' ' ;
return value() ;
}
/**
* Serialize the specified value object passed-in argument.
*/
public function serialize( value:* ):String
{
var c:String ; // char
var i:int ;
var l:int ;
var s:String = '' ;
var v:* ;
var tof:String = typeof(value) ;
switch (tof)
{
case 'object' :
{
if (value)
{
if (value is Array)
{
l = (value as Array).length ;
for (i = 0 ; i < l ; ++i)
{
v = serialize(value[i]);
if (s) s += ',' ;
s += v ;
}
return '[' + s + ']';
}
else if ( typeof( value.toString ) != 'undefined')
{
for (var prop:String in value)
{
v = value[prop];
if ( (typeof(v) != 'undefined') && (typeof(v) != 'function') )
{
v = serialize(v);
if (s)
{
s += ',' ;
}
s += serialize(prop) + ':' + v ;
}
}
return "{" + s + "}";
}
}
return 'null';
}
case 'number':
{
return isFinite(value) ? String(value) : 'null' ;
}
case 'string' :
{
l = (value as String).length ;
s = '"' ;
for (i = 0 ; i < l ; i += 1)
{
c = (value as String).charAt(i) ;
if (c >= ' ')
{
if (c == '\\' || c == '"')
{
s += '\\';
}
s += c;
}
else
{
switch (c)
{
case '\b':
{
s += '\\b';
break ;
}
case '\f' :
{
s += '\\f' ;
break ;
}
case '\n' :
{
s += '\\n' ;
break ;
}
case '\r':
{
s += '\\r' ;
break ;
}
case '\t':
{
s += '\\t' ;
break ;
}
default:
{
var code:Number = c.charCodeAt() ;
s += '\\u00' + String(Math.floor(code / 16).toString(16)) + ((code % 16).toString(16)) ;
}
}
}
}
return s + '"' ;
}
case 'boolean' :
{
return String(value);
}
default :
{
return 'null';
}
}
}
/**
* The current position of the iterator in the source.
*/
protected var at:Number = 0 ;
/**
* The current character of the iterator in the source.
*/
protected var ch:String = ' ' ;
/**
* Check the Array objects in the source expression.
*/
protected function array():Array
{
var a:Array = [];
if ( ch == '[' )
{
next() ;
white() ;
if (ch == ']')
{
next();
return a;
}
while (ch)
{
a.push( value() ) ;
white();
if (ch == ']')
{
next();
return a;
}
else if (ch != ',')
{
break;
}
next();
white();
}
}
error( JSONStrings.badArray );
return null ;
}
/**
* Throws a JSONError with the passed-in message.
*/
protected function error( m:String ):void
{
throw new JSONError( m, at - 1 , source) ;
}
/**
* Indicates if the passed-in character is a digit.
*/
protected function isDigit( c:String ):Boolean
{
return( ("0" <= c) && (c <= "9") );
}
/**
* Indicates if the passed-in character is a hexadecimal digit.
*/
protected function isHexDigit( c:String ):Boolean
{
return( isDigit( c ) || (("A" <= c) && (c <= "F")) || (("a" <= c) && (c <= "f")) );
}
/**
* Indicates if the current character is a key.
*/
protected function key():*
{
var s:String = ch ;
var semiColon:int = source.indexOf( ':' , at ) ;
var quoteIndex:int = source.indexOf( '"' , at ) ;
var squoteIndex:int = source.indexOf( "'" , at ) ;
if( (quoteIndex <= semiColon && quoteIndex > -1) || (squoteIndex <= semiColon && squoteIndex > -1))
{
s = string() ;
white() ;
if(ch == ':')
{
return s;
}
else
{
error(JSONStrings.badKey);
}
}
while ( next() ) // Use key handling
{
if (ch == ':')
{
return s;
}
if(ch <= ' ')
{
//
}
else
{
s += ch;
}
}
error( JSONStrings.badKey ) ;
}
/**
* Returns the next character in the source String representation.
* @return the next character in the source String representation.
*/
protected function next():String
{
ch = source.charAt(at);
at += 1;
return ch;
}
/**
* Check the Number values in the source expression.
*/
protected function number():*
{
var n:* = '' ;
var v:* ;
var hex:String = '' ;
var sign:String = '' ;
if (ch == '-')
{
n = '-';
sign = n ;
next();
}
if( ch == "0" )
{
next() ;
if( ( ch == "x") || ( ch == "X") )
{
next();
while( isHexDigit( ch ) )
{
hex += ch ;
next();
}
if( hex == "" )
{
error(JSONStrings.malFormedHexadecimal) ;
}
else
{
return Number( sign + "0x" + hex ) ;
}
}
else
{
n += "0" ;
}
}
while ( isDigit(ch) )
{
n += ch ;
next() ;
}
if (ch == '.')
{
n += '.';
while (next() && ch >= '0' && ch <= '9')
{
n += ch ;
}
}
v = 1 * n ;
if (!isFinite(v))
{
error( JSONStrings.badNumber );
}
else
{
return v ;
}
return NaN ;
}
/**
* Check the Object values in the source expression.
*/
protected function object():*
{
var k:* = {} ;
var o:* = {} ;
if (ch == '{')
{
next();
white();
if (ch == '}')
{
next() ;
return o ;
}
while (ch)
{
k = key() ;
white();
if (ch != ':')
{
break;
}
next();
o[k] = value() ;
white();
if (ch == '}')
{
next();
return o;
}
else if (ch != ',')
{
break;
}
next();
white();
}
}
error( JSONStrings.badObject ) ;
}
/**
* Check the string objects in the source expression.
*/
protected function string():*
{
var i:* = '' ;
var s:* = '' ;
var t:* ;
var u:* ;
var outer:Boolean ;
if (ch == '"' || ch == "'" )
{
var outerChar:String = ch ;
while ( next() )
{
if (ch == outerChar)
{
next() ;
return s ;
}
else if (ch == '\\')
{
switch ( next() )
{
case 'b':
{
s += '\b' ;
break ;
}
case 'f' :
{
s += '\f';
break ;
}
case 'n':
{
s += '\n';
break ;
}
case 'r' :
{
s += '\r';
break ;
}
case 't' :
{
s += '\t' ;
break ;
}
case 'u' :
{
u = 0;
for (i = 0; i < 4; i += 1)
{
t = parseInt( next() , 16 ) ;
if (!isFinite(t))
{
outer = true;
break;
}
u = u * 16 + t;
}
if(outer)
{
outer = false;
break;
}
s += String.fromCharCode(u);
break;
}
default :
{
s += ch;
}
}
}
else
{
s += ch;
}
}
}
error( JSONStrings.badString );
return null ;
}
/**
* Evaluates the values in the source expression.
*/
protected function value():*
{
white() ;
if (ch == '{' )
{
return object();
}
else if ( ch == '[' )
{
return array();
}
else if ( ch == '"' || ch == "'" )
{
return string();
}
else if ( ch == '-' )
{
return number();
}
else
{
return ( ch >= '0' && ch <= '9' ) ? number() : word() ;
}
}
/**
* Check all white spaces.
*/
protected function white():void
{
while (ch)
{
if (ch <= ' ')
{
next();
}
else if (ch == '/')
{
switch ( next() )
{
case '/' :
{
while ( next() && ch != '\n' && ch != '\r')
{
}
break;
}
case '*' :
{
next();
for (;;)
{
if (ch)
{
if (ch == '*')
{
if ( next() == '/' )
{
next();
break;
}
}
else
{
next();
}
}
else
{
error( JSONStrings.unterminatedComment );
}
}
break ;
}
default :
{
error( JSONStrings.syntaxError );
}
}
}
else
{
break ;
}
}
}
/**
* Check all special words in the source to evaluate.
*/
protected function word():*
{
if (ch == 't')
{
if (next() == 'r' && next() == 'u' && next() == 'e')
{
next() ;
return true ;
}
}
else if ( ch == 'f' )
{
if (next() == 'a' && next() == 'l' && next() == 's' && next() == 'e')
{
next() ;
return false ;
}
}
else if ( ch == 'n' )
{
if (next() == 'u' && next() == 'l' && next() == 'l')
{
next() ;
return null ;
}
}
error( JSONStrings.syntaxError );
return null ;
}
/**
* @private
*/
private var _prettyIndent:int = 0 ;
/**
* @private
*/
private var _prettyPrinting:Boolean ;
/**
* @private
*/
private var _indentor:String = " " ;
}
}

View file

@ -1,85 +0,0 @@
/*
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (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.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is VEGAS Framework.
The Initial Developer of the Original Code is
ALCARAZ Marc (aka eKameleon) <ekameleon@gmail.com>.
Portions created by the Initial Developer are Copyright (C) 2004-2011
the Initial Developer. All Rights Reserved.
Contributor(s) :
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the LGPL or the GPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
*/
package vegas.strings.json
{
/**
* The string messages used in the JSON class.
*/
public class JSONStrings
{
/**
* The bad Array error message.
*/
public static var badArray:String = "Bad Array" ;
/**
* The bad key error message.
*/
public static var badKey:String = "Bad key" ;
/**
* The bad Number error message.
*/
public static var badNumber:String = "Bad Number" ;
/**
* The bad Object error message.
*/
public static var badObject:String = "Bad Object" ;
/**
* The bad String error message.
*/
public static var badString:String = "Bad String" ;
/**
* The mal formed Hexadecimal error message.
*/
public static var malFormedHexadecimal:String = "Mal formed Hexadecimal" ;
/**
* The syntax error message.
*/
public static var syntaxError:String = "Syntax Error" ;
/**
* The unterminated comment error message.
*/
public static var unterminatedComment:String = "Unterminated Comment" ;
}
}

View file

@ -7,8 +7,8 @@
*/
return array(
'names' => array(
'core.pkg.css' => '63e782fb',
'core.pkg.js' => '44aac665',
'core.pkg.css' => '8fc8031a',
'core.pkg.js' => '21041609',
'darkconsole.pkg.js' => '8ab24e01',
'differential.pkg.css' => '8af45893',
'differential.pkg.js' => 'dad3622f',
@ -342,9 +342,9 @@ return array(
'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' => '4a07e8e3',
'rsrc/js/application/aphlict/Aphlict.js' => '464d333a',
'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'f6bc26f0',
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'a826c925',
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '1162a152',
'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '58f7803f',
'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18',
'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de',
@ -489,7 +489,6 @@ return array(
'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8',
'rsrc/js/phuix/PHUIXActionView.js' => '6e8cefa4',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bd4c8dca',
'rsrc/swf/aphlict.swf' => 'f19daffb',
),
'symbols' => array(
'almanac-css' => 'dbb9b3af',
@ -536,10 +535,10 @@ return array(
'herald-rule-editor' => '335fd41f',
'herald-test-css' => '778b008e',
'inline-comment-summary-css' => '8cfd34e8',
'javelin-aphlict' => '4a07e8e3',
'javelin-aphlict' => '464d333a',
'javelin-behavior' => '61cbc29a',
'javelin-behavior-aphlict-dropdown' => 'f6bc26f0',
'javelin-behavior-aphlict-listen' => 'a826c925',
'javelin-behavior-aphlict-listen' => '1162a152',
'javelin-behavior-aphlict-status' => '58f7803f',
'javelin-behavior-aphront-basic-tokenizer' => 'b3a4b884',
'javelin-behavior-aphront-crop' => 'fa0f4fc2',
@ -909,6 +908,18 @@ return array(
'javelin-uri',
'javelin-install',
),
'1162a152' => array(
'javelin-behavior',
'javelin-aphlict',
'javelin-stratcom',
'javelin-request',
'javelin-uri',
'javelin-dom',
'javelin-json',
'javelin-router',
'javelin-util',
'phabricator-notification',
),
'13c739ea' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1079,6 +1090,13 @@ return array(
'javelin-behavior',
'javelin-dom',
),
'464d333a' => array(
'javelin-install',
'javelin-util',
'javelin-websocket',
'javelin-leader',
'javelin-json',
),
'469c0d9e' => array(
'javelin-behavior',
'javelin-dom',
@ -1100,10 +1118,6 @@ return array(
'javelin-request',
'javelin-util',
),
'4a07e8e3' => array(
'javelin-install',
'javelin-util',
),
'4d94d9c3' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1489,18 +1503,6 @@ return array(
'javelin-stratcom',
'javelin-dom',
),
'a826c925' => array(
'javelin-behavior',
'javelin-aphlict',
'javelin-stratcom',
'javelin-request',
'javelin-uri',
'javelin-dom',
'javelin-json',
'javelin-router',
'javelin-util',
'phabricator-notification',
),
'a8d8459d' => array(
'javelin-behavior',
'javelin-dom',
@ -2024,6 +2026,11 @@ return array(
'sprite-tokens-css',
'tokens-css',
'phui-status-list-view-css',
'phui-feed-story-css',
'phabricator-feed-css',
'phabricator-dashboard-css',
'aphront-multi-column-view-css',
'phui-action-header-view-css',
),
'core.pkg.js' => array(
'javelin-util',
@ -2093,6 +2100,11 @@ return array(
'javelin-behavior-phabricator-show-older-transactions',
'javelin-behavior-phui-timeline-dropdown-menu',
'javelin-behavior-doorkeeper-tag',
'phabricator-title',
'javelin-leader',
'javelin-websocket',
'javelin-behavior-dashboard-async-panel',
'javelin-behavior-dashboard-tab-panel',
),
'darkconsole.pkg.js' => array(
'javelin-behavior-dark-console',

View file

@ -69,6 +69,11 @@ return array(
'javelin-behavior-phabricator-show-older-transactions',
'javelin-behavior-phui-timeline-dropdown-menu',
'javelin-behavior-doorkeeper-tag',
'phabricator-title',
'javelin-leader',
'javelin-websocket',
'javelin-behavior-dashboard-async-panel',
'javelin-behavior-dashboard-tab-panel',
),
'core.pkg.css' => array(
'phabricator-core-css',
@ -126,6 +131,12 @@ return array(
'sprite-tokens-css',
'tokens-css',
'phui-status-list-view-css',
'phui-feed-story-css',
'phabricator-feed-css',
'phabricator-dashboard-css',
'aphront-multi-column-view-css',
'phui-action-header-view-css',
),
'differential.pkg.css' => array(
'differential-core-view-css',

View file

@ -1239,7 +1239,6 @@ phutil_register_library_map(array(
'PhabricatorAlmanacApplication' => 'applications/almanac/application/PhabricatorAlmanacApplication.php',
'PhabricatorAmazonAuthProvider' => 'applications/auth/provider/PhabricatorAmazonAuthProvider.php',
'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php',
'PhabricatorAphlictManagementBuildWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementBuildWorkflow.php',
'PhabricatorAphlictManagementDebugWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementDebugWorkflow.php',
'PhabricatorAphlictManagementRestartWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementRestartWorkflow.php',
'PhabricatorAphlictManagementStartWorkflow' => 'applications/aphlict/management/PhabricatorAphlictManagementStartWorkflow.php',
@ -4393,7 +4392,6 @@ phutil_register_library_map(array(
'PhabricatorAlmanacApplication' => 'PhabricatorApplication',
'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorAnchorView' => 'AphrontView',
'PhabricatorAphlictManagementBuildWorkflow' => 'PhabricatorAphlictManagementWorkflow',
'PhabricatorAphlictManagementDebugWorkflow' => 'PhabricatorAphlictManagementWorkflow',
'PhabricatorAphlictManagementRestartWorkflow' => 'PhabricatorAphlictManagementWorkflow',
'PhabricatorAphlictManagementStartWorkflow' => 'PhabricatorAphlictManagementWorkflow',

View file

@ -1,61 +0,0 @@
<?php
final class PhabricatorAphlictManagementBuildWorkflow
extends PhabricatorAphlictManagementWorkflow {
public function didConstruct() {
$this
->setName('build')
->setSynopsis(pht('Build the Aphlict client.'))
->setArguments(
array(
array(
'name' => 'debug',
'help' => 'Enable a debug build.',
),
));
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$root = dirname(__FILE__).'/../../../..';
if (!Filesystem::binaryExists('mxmlc')) {
throw new PhutilArgumentUsageException(
pht(
"The `mxmlc` binary was not found in PATH. This compiler binary ".
"is required to rebuild the Aphlict client.\n\n".
"Adjust your PATH, or install the Flex SDK from:\n\n".
" http://flex.apache.org\n\n".
"You may also be able to install it with `npm`:\n\n".
" $ npm install flex-sdk\n\n".
"(Note: you should only need to rebuild Aphlict if you are ".
"developing Phabricator.)"));
}
$argv = array(
"-source-path=$root/externals/vegas/src",
'-static-link-runtime-shared-libraries=true',
'-warnings=true',
'-strict=true',
);
if ($args->getArg('debug')) {
$argv[] = '-debug=true';
}
list ($err, $stdout, $stderr) = exec_manual('mxmlc %Ls -output=%s %s',
$argv,
$root.'/webroot/rsrc/swf/aphlict.swf',
$root.'/support/aphlict/client/src/AphlictClient.as');
if ($err) {
$console->writeErr($stderr);
return 1;
}
$console->writeOut("Done.\n");
return 0;
}
}

View file

@ -14,7 +14,7 @@ final class PhabricatorAphlictManagementDebugWorkflow
}
public function execute(PhutilArgumentParser $args) {
$this->willLaunch();
$this->willLaunch(true);
return $this->launch(true);
}

View file

@ -50,7 +50,7 @@ abstract class PhabricatorAphlictManagementWorkflow
}
}
final protected function willLaunch() {
final protected function willLaunch($debug = false) {
$console = PhutilConsole::getConsole();
$pid = $this->getPID();
@ -61,15 +61,66 @@ abstract class PhabricatorAphlictManagementWorkflow
'running. Use `aphlict restart` to restart it.'));
}
if (posix_getuid() != 0) {
if (posix_getuid() == 0) {
throw new PhutilArgumentUsageException(
pht(
'You must run this script as root; the Aphlict server needs to bind '.
'to privileged ports.'));
// TODO: Update this message after a while.
'The notification server should not be run as root. It no '.
'longer requires access to privileged ports.'));
}
// This will throw if we can't find an appropriate `node`.
$this->getNodeBinary();
// Make sure we can write to the PID file.
if (!$debug) {
Filesystem::writeFile($this->getPIDPath(), '');
}
// First, start the server in configuration test mode with --test. This
// will let us error explicitly if there are missing modules, before we
// fork and lose access to the console.
$test_argv = $this->getServerArgv($debug);
$test_argv[] = '--test=true';
execx(
'%s %s %Ls',
$this->getNodeBinary(),
$this->getAphlictScriptPath(),
$test_argv);
}
private function getServerArgv($debug) {
$ssl_key = PhabricatorEnv::getEnvConfig('notification.ssl-key');
$ssl_cert = PhabricatorEnv::getEnvConfig('notification.ssl-cert');
$server_uri = PhabricatorEnv::getEnvConfig('notification.server-uri');
$server_uri = new PhutilURI($server_uri);
$client_uri = PhabricatorEnv::getEnvConfig('notification.client-uri');
$client_uri = new PhutilURI($client_uri);
$log = PhabricatorEnv::getEnvConfig('notification.log');
$server_argv = array();
$server_argv[] = '--port='.$client_uri->getPort();
$server_argv[] = '--admin='.$server_uri->getPort();
if ($ssl_key) {
$server_argv[] = '--ssl-key='.$ssl_key;
}
if ($ssl_cert) {
$server_argv[] = '--ssl-cert='.$ssl_cert;
}
if (!$debug) {
$server_argv[] = '--log='.$log;
}
return $server_argv;
}
private function getAphlictScriptPath() {
$root = dirname(phutil_get_library_root('phabricator'));
return $root.'/support/aphlict/server/aphlict_server.js';
}
final protected function launch($debug = false) {
@ -81,33 +132,11 @@ abstract class PhabricatorAphlictManagementWorkflow
Filesystem::writeFile($this->getPIDPath(), getmypid());
}
$server_uri = PhabricatorEnv::getEnvConfig('notification.server-uri');
$server_uri = new PhutilURI($server_uri);
$client_uri = PhabricatorEnv::getEnvConfig('notification.client-uri');
$client_uri = new PhutilURI($client_uri);
$user = PhabricatorEnv::getEnvConfig('notification.user');
$log = PhabricatorEnv::getEnvConfig('notification.log');
$server_argv = array();
$server_argv[] = csprintf('--port=%s', $client_uri->getPort());
$server_argv[] = csprintf('--admin=%s', $server_uri->getPort());
$server_argv[] = csprintf('--host=%s', $server_uri->getDomain());
if ($user) {
$server_argv[] = csprintf('--user=%s', $user);
}
if (!$debug) {
$server_argv[] = csprintf('--log=%s', $log);
}
$command = csprintf(
'%s %s %C',
'%s %s %Ls',
$this->getNodeBinary(),
dirname(__FILE__).'/../../../../support/aphlict/server/aphlict_server.js',
implode(' ', $server_argv));
$this->getAphlictScriptPath(),
$this->getServerArgv($debug));
if (!$debug) {
declare(ticks = 1);
@ -159,6 +188,7 @@ abstract class PhabricatorAphlictManagementWorkflow
fclose(STDOUT);
fclose(STDERR);
$this->launch();
return 0;
}

View file

@ -194,6 +194,11 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
'This option has been renamed to `phabricator.show-prototypes` '.
'to emphasize the unfinished nature of many prototype applications. '.
'Your existing setting has been migrated.'),
'notification.user' => pht(
'The notification server no longer requires root permissions. Start '.
'the server as the user you want it to run under.'),
'notification.debug' => pht(
'Notifications no longer have a dedicated debugging mode.'),
);
return $ancient_config;

View file

@ -36,23 +36,21 @@ final class PhabricatorNotificationConfigOptions
'string',
'http://localhost:22281/')
->setDescription(pht('Location of the notification receiver server.')),
$this->newOption('notification.user', 'string', null)
->setSummary(pht('Drop permissions to a less-privileged user.'))
->setDescription(
pht(
'The notifcation server must be started as root so it can bind '.
'to privileged ports, but if you specify a system user here it '.
'will drop permissions to that user after binding to the ports '.
'it needs.')),
$this->newOption('notification.log', 'string', '/var/log/aphlict.log')
->setDescription(pht('Location of the server log file.')),
$this->newOption('notification.ssl-key', 'string', null)
->setLocked(true)
->setDescription(
pht('Path to SSL key to use for secure WebSockets.')),
$this->newOption('notification.ssl-cert', 'string', null)
->setLocked(true)
->setDescription(
pht('Path to SSL certificate to use for secure WebSockets.')),
$this->newOption(
'notification.pidfile',
'string',
'/var/run/aphlict.pid')
'/var/tmp/aphlict/pid/aphlict.pid')
->setDescription(pht('Location of the server PID file.')),
$this->newOption('notification.debug', 'bool', false)
->setDescription(pht('Enable debug output in the browser.')),
);
}

View file

@ -16,6 +16,11 @@ final class PhabricatorNotificationTestController
$viewer_phid = $viewer->getPHID();
// NOTE: Because we don't currently show you your own notifications, make
// sure this comes from a different PHID.
$application_phid = id(new PhabricatorNotificationsApplication())
->getPHID();
// TODO: When it's easier to get these buttons to render as forms, this
// would be slightly nicer as a more standard isFormPost() check.
@ -24,7 +29,7 @@ final class PhabricatorNotificationTestController
->setStoryType($story_type)
->setStoryData($story_data)
->setStoryTime(time())
->setStoryAuthorPHID($viewer_phid)
->setStoryAuthorPHID($application_phid)
->setRelatedPHIDs(array($viewer_phid))
->setPrimaryObjectPHID($viewer_phid)
->setSubscribedPHIDs(array($viewer_phid))

View file

@ -38,7 +38,7 @@ final class PhabricatorAphlictSetupCheck extends PhabricatorSetupCheck {
->addCommand(
pht(
"(To start the server, run this command.)\n".
"phabricator/ $ sudo ./bin/aphlict start"));
"phabricator/ $ ./bin/aphlict start"));
return;
}
@ -57,7 +57,7 @@ final class PhabricatorAphlictSetupCheck extends PhabricatorSetupCheck {
->setShortName(pht('Notification Server Version'))
->setName(pht('Notification Server Out of Date'))
->setMessage($message)
->addCommand('phabricator/ $ sudo ./bin/aphlict restart');
->addCommand('phabricator/ $ ./bin/aphlict restart');
}
}

View file

@ -13,18 +13,8 @@ final class PhabricatorNotificationStatusView extends AphrontTagView {
'nodeID' => $this->getID(),
'pht' => array(
'setup' => pht('Setting Up Client'),
'start' => pht('Starting Client'),
'ready' => pht('Ready to Connect'),
'connecting' => pht('Connecting...'),
'connected' => pht('Connected'),
'error' => pht('Connection Error'),
'client' => pht('Connected Locally'),
'error.flash.xdomain' => pht(
'Unable to connect to Flash Policy Server. Check that the '.
'notification server is running and port 843 is not firewalled.'),
'error.flash.disconnected' => pht(
'Disconnected from notification server.'),
'open' => pht('Connected'),
'closed' => pht('Disconnected'),
),
));

View file

@ -3,7 +3,8 @@
Guide to setting up notifications.
= Overview =
Overview
========
By default, Phabricator delivers information about events (like users creating
tasks or commenting on code reviews) through email and in-application
@ -20,61 +21,95 @@ To enable real-time notifications:
This document describes the process in detail.
= Running the Aphlict Server =
Phabricator implements realtime notifications using a Node.js server called
"Aphlict". To run it:
Supported Browsers
==================
- Install node.js.
- Run `bin/aphlict start` (this script must be run as root).
Notifications are supported for browsers which support WebSockets. This covers
most modern browsers (like Chrome, Firefox, Safari, and recent versions of
Internet Explorer) and many mobile browsers.
The server must be able to listen on port **843** and port **22280** for Aphlict
to work. You can change the latter port in the `notification.client-uri` config,
but port 843 is used by Flash and can not be changed. In particular, if you're
running in EC2, you need to unblock both of these ports in the server's security
group configuration.
IE8 and IE9 do not support WebSockets, so real-time notifications won't work in
those browsers.
You may want to adjust these settings:
Installing Node and Modules
===========================
The notification server uses Node.js, so you'll need to install it first.
To install Node.js, follow the instructions on
[[ http://nodejs.org | nodejs.org ]].
You will also need to install the `ws` module for Node. After installing
Node, run `npm install -g ws` to install it.
name="(Option 1, Recommended) Install 'ws' Module Globally"
$ npm install -g ws # Global Install
If you prefer, you can also install it locally in the `support/aphlict/server/`
directory:
name="(Option 2) Install 'ws' Module Locally"
phabricator/support/aphlict/server/ $ npm install ws
Once Node.js and the `ws` module are installed, you're ready to start the
server.
Running the Aphlict Server
==========================
After installing Node.js, you can control the notification server with the
`bin/aphlict` command. To start the server:
phabricator/ $ bin/aphlict start
The server must be able to listen on port **22280** for Aphlict to work. In
particular, if you're running in EC2, you need to unblock this port in the
server's security group configuration. You can change this port in the
`notification.client-uri` config.
You may need to adjust these settings:
- `notification.ssl-cert` Point this at an SSL certificate for secure
WebSockets.
- `notification.ssl-key` Point this at an SSL keyfile for secure WebSockets.
In particular, if your server uses HTTPS, you **must** configure these options.
Browsers will not allow you to use non-SSL websockets from an SSL web page.
You may also want to adjust these settings:
- `notification.client-uri` Externally-facing host and port that browsers will
connect to in order to listen for notifications.
- `notification.server-uri` Internally-facing host and port that Phabricator
will connect to in order to publish notifications.
- `notification.log` Log file location for the server.
- `notification.user` Non-root user to drop permissions to after binding to
privileged ports.
- `notification.pid` Pidfile location used to stop any running server when
aphlict is restarted.
In most cases, the defaults are appropriate, except that you should set
`notification.user` to some valid system user so Aphlict isn't running as root.
== Verifying Server Status ==
Verifying Server Status
=======================
Access `/notification/status/` to verify the server is operational. You should
see a table showing stats like "uptime" and connection/message counts if the
server is working. If it isn't working, you should see an error.
== Testing the Server ==
You can also send a test notification by clicking the button in the upper right
corner of this screen.
The easiest way to test the server is to have two users login and comment on
the same Maniphest Task or Differential Revision. They should receive in-browser
notifications about the other user's activity.
NOTE: This is cumbersome. There will be better testing tools at some point.
== Debugging Server Problems ==
Troubleshooting
===============
You can run `aphlict` in the foreground to get output to your console:
phabricator/ $ sudo ./bin/aphlict debug
phabricator/ $ ./bin/aphlict debug
You can run `support/aphlict/client/aphlict_test_client.php` to connect to the
Aphlict server from the command line. Messages the client receives will be
printed to stdout.
You can set `notification.debug` in your configuration to get additional
output in your browser.
Because the notification server uses WebSockets, your browser error console
may also have information that is useful in figuring out what's wrong.
The server also generates a log, by default in `/var/log/aphlict.log`. You can
change this location by changing `notification.log` in your configuration. The

View file

@ -371,9 +371,6 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
if (PhabricatorEnv::getEnvConfig('notification.enabled')) {
if ($user && $user->isLoggedIn()) {
$aphlict_object_id = celerity_generate_unique_node_id();
$aphlict_container_id = celerity_generate_unique_node_id();
$client_uri = PhabricatorEnv::getEnvConfig('notification.client-uri');
$client_uri = new PhutilURI($client_uri);
if ($client_uri->getDomain() == 'localhost') {
@ -382,37 +379,24 @@ final class PhabricatorStandardPageView extends PhabricatorBarePageView {
$client_uri->setDomain($this_host->getDomain());
}
$map = CelerityResourceMap::getNamedInstance('phabricator');
$swf_uri = $response->getURI($map, 'rsrc/swf/aphlict.swf', true);
$enable_debug = PhabricatorEnv::getEnvConfig('notification.debug');
$subscriptions = $this->pageObjects;
if ($user) {
$subscriptions[] = $user->getPHID();
}
if ($request->isHTTPS()) {
$client_uri->setProtocol('wss');
} else {
$client_uri->setProtocol('ws');
}
Javelin::initBehavior(
'aphlict-listen',
array(
'id' => $aphlict_object_id,
'containerID' => $aphlict_container_id,
'server' => $client_uri->getDomain(),
'port' => $client_uri->getPort(),
'debug' => $enable_debug,
'swfURI' => $swf_uri,
'websocketURI' => (string)$client_uri,
'pageObjects' => array_fill_keys($this->pageObjects, true),
'subscriptions' => $subscriptions,
));
$tail[] = phutil_tag(
'div',
array(
'id' => $aphlict_container_id,
'style' =>
'position: absolute; width: 0; height: 0; overflow: hidden;',
),
'');
}
}

View file

@ -1,55 +0,0 @@
#!/usr/bin/env php
<?php
$root = dirname(dirname(dirname(dirname(__FILE__))));
require_once $root.'/scripts/__init_script__.php';
$args = new PhutilArgumentParser($argv);
$args->setTagline('test client for Aphlict server');
$args->setSynopsis(<<<EOHELP
**aphlict_test_client.php** [__options__]
Connect to the Aphlict server configured in the Phabricator config.
EOHELP
);
$args->parseStandardArguments();
$args->parse(
array(
array(
'name' => 'server',
'param' => 'uri',
'default' => PhabricatorEnv::getEnvConfig('notification.client-uri'),
'help' => 'Connect to __uri__ instead of the default server.',
),
));
$console = PhutilConsole::getConsole();
$errno = null;
$errstr = null;
$uri = $args->getArg('server');
$uri = new PhutilURI($uri);
$uri->setProtocol('tcp');
$console->writeErr("Connecting...\n");
$socket = stream_socket_client(
$uri,
$errno,
$errstr);
if (!$socket) {
$console->writeErr(
"Unable to connect to Aphlict (at '$uri'). Error #{$errno}: {$errstr}");
exit(1);
} else {
$console->writeErr("Connected.\n");
}
$io_channel = new PhutilSocketChannel($socket);
$proto_channel = new PhutilJSONProtocolChannel($io_channel);
$json = new PhutilJSON();
while (true) {
$message = $proto_channel->waitForMessage();
$console->writeOut($json->encodeFormatted($message));
}

View file

@ -1,47 +0,0 @@
package {
import flash.display.Sprite;
import flash.external.ExternalInterface;
import flash.net.LocalConnection;
public class Aphlict extends Sprite {
/**
* A transport channel used to receive data.
*/
protected var recv:LocalConnection;
/**
* A transport channel used to send data.
*/
protected var send:LocalConnection;
public function Aphlict() {
super();
this.recv = new LocalConnection();
this.recv.client = this;
this.send = new LocalConnection();
}
final protected function externalInvoke(
type:String,
object:Object = null):void {
ExternalInterface.call('JX.Aphlict.didReceiveEvent', type, object);
}
final protected function error(error:Object):void {
this.externalInvoke('error', error.toString());
}
final protected function log(message:String):void {
this.externalInvoke('log', message);
}
}
}

View file

@ -1,160 +0,0 @@
package {
import flash.events.TimerEvent;
import flash.external.ExternalInterface;
import flash.utils.Dictionary;
import flash.utils.Timer;
import flash.events.UncaughtErrorEvent;
final public class AphlictClient extends Aphlict {
/**
* The connection name for this client. This will be used for the
* @{class:LocalConnection} object.
*/
private var client:String;
/**
* The expiry timestamp for the @{class:AphlictMaster}. If this time is
* elapsed then the master will be assumed to be dead and another
* @{class:AphlictClient} will create a master.
*/
private var expiry:Number = 0;
/**
* The interval at which to ping the @{class:AphlictMaster}.
*/
public static const INTERVAL:Number = 3000;
private var master:AphlictMaster;
private var timer:Timer;
private var remoteServer:String;
private var remotePort:Number;
private var subscriptions:Array;
public function AphlictClient() {
super();
loaderInfo.uncaughtErrorEvents.addEventListener(
UncaughtErrorEvent.UNCAUGHT_ERROR,
this.uncaughtErrorHandler);
ExternalInterface.marshallExceptions = true;
ExternalInterface.addCallback('connect', this.externalConnect);
this.setStatus('ready');
}
private function uncaughtErrorHandler(event:UncaughtErrorEvent):void {
this.error(event.error.toString());
}
public function externalConnect(
server:String,
port:Number,
subscriptions:Array):void {
this.remoteServer = server;
this.remotePort = port;
this.subscriptions = subscriptions;
this.client = AphlictClient.generateClientId();
this.recv.connect(this.client);
this.timer = new Timer(AphlictClient.INTERVAL);
this.timer.addEventListener(TimerEvent.TIMER, this.keepalive);
this.connectToMaster();
}
/**
* Generate a unique identifier that will be used to communicate with the
* @{class:AphlictMaster}.
*/
private static function generateClientId():String {
return 'aphlict_client_' + Math.round(Math.random() * 100000);
}
/**
* Create a new connection to the @{class:AphlictMaster}.
*
* If there is no current @{class:AphlictMaster} instance, then a new master
* will be created.
*/
private function connectToMaster():void {
this.timer.stop();
// Try to become the master.
try {
this.log('Attempting to become the master...');
this.master = new AphlictMaster(this.remoteServer, this.remotePort);
this.log('I am the master.');
} catch (err:ArgumentError) {
this.log('Cannot become the master... probably one already exists');
} catch (err:Error) {
this.error(err);
}
this.registerWithMaster();
this.timer.start();
}
/**
* Register our client ID with the @{class:AphlictMaster} and send our
* subscriptions.
*/
private function registerWithMaster():void {
this.send.send('aphlict_master', 'register', this.client);
this.expiry = new Date().getTime() + (5 * AphlictClient.INTERVAL);
this.log('Registered client ' + this.client);
// Send subscriptions to master.
this.log('Sending subscriptions to master.');
this.send.send(
'aphlict_master',
'subscribe',
this.client,
this.subscriptions);
}
/**
* Send a keepalive signal to the @{class:AphlictMaster}.
*
* If the connection to the master has expired (because the master has not
* sent a heartbeat signal), then a new connection to master will be
* created.
*/
private function keepalive(event:TimerEvent):void {
if (new Date().getTime() > this.expiry) {
this.connectToMaster();
}
this.send.send('aphlict_master', 'ping', this.client);
}
/**
* This function is used to receive the heartbeat signal from the
* @{class:AphlictMaster}.
*/
public function pong():void {
this.expiry = new Date().getTime() + (2 * AphlictClient.INTERVAL);
}
/**
* Receive a message from the Aphlict Server, via the
* @{class:AphlictMaster}.
*/
public function receiveMessage(msg:Object):void {
this.log('Received message.');
this.externalInvoke('receive', msg);
}
public function setStatus(status:String, code:String = null):void {
this.externalInvoke('status', {type: status, code: code});
}
}
}

View file

@ -1,312 +0,0 @@
package {
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.events.TimerEvent;
import flash.net.Socket;
import flash.utils.ByteArray;
import flash.utils.Dictionary;
import flash.utils.Timer;
import vegas.strings.JSON;
final public class AphlictMaster extends Aphlict {
/**
* The pool of connected clients.
*/
private var clients:Dictionary;
/**
* A timer used to trigger periodic events.
*/
private var timer:Timer;
/**
* The interval after which clients will be considered dead and removed
* from the pool.
*/
public static const PURGE_INTERVAL:Number = 3 * AphlictClient.INTERVAL;
/**
* The hostname for the Aphlict Server.
*/
private var remoteServer:String;
/**
* The port number for the Aphlict Server.
*/
private var remotePort:Number;
/**
* A dictionary mapping PHID to subscribed clients.
*/
private var subscriptions:Dictionary;
private var socket:Socket;
private var readBuffer:ByteArray;
private var status:String;
private var statusCode:String;
public function AphlictMaster(server:String, port:Number) {
super();
this.remoteServer = server;
this.remotePort = port;
this.clients = new Dictionary();
this.subscriptions = new Dictionary();
// Connect to the Aphlict Server.
this.recv.connect('aphlict_master');
this.connectToServer();
// Start a timer and regularly purge dead clients.
this.timer = new Timer(AphlictMaster.PURGE_INTERVAL);
this.timer.addEventListener(TimerEvent.TIMER, this.purgeClients);
this.timer.start();
}
/**
* Register a @{class:AphlictClient}.
*/
public function register(client:String):void {
if (!this.clients[client]) {
this.log('Registering client: ' + client);
this.clients[client] = new Date().getTime();
this.send.send(client, 'setStatus', this.status, this.statusCode);
}
}
/**
* Purge stale client connections from the client pool.
*/
private function purgeClients(event:TimerEvent):void {
for (var client:String in this.clients) {
var checkin:Number = this.clients[client];
if (new Date().getTime() - checkin > AphlictMaster.PURGE_INTERVAL) {
this.log('Purging client: ' + client);
delete this.clients[client];
this.log('Removing client subscriptions: ' + client);
this.unsubscribeAll(client);
}
}
}
/**
* Clients will regularly "ping" the master to let us know that they are
* still alive. We will "pong" them back to let the client know that the
* master is still alive.
*/
public function ping(client:String):void {
this.clients[client] = new Date().getTime();
this.send.send(client, 'pong');
}
private function connectToServer():void {
this.setStatusOnClients('connecting');
var socket:Socket = new Socket();
socket.addEventListener(Event.CONNECT, didConnectSocket);
socket.addEventListener(Event.CLOSE, didCloseSocket);
socket.addEventListener(ProgressEvent.SOCKET_DATA, didReceiveSocket);
socket.addEventListener(IOErrorEvent.IO_ERROR, didIOErrorSocket);
socket.addEventListener(
SecurityErrorEvent.SECURITY_ERROR,
didSecurityErrorSocket);
socket.connect(this.remoteServer, this.remotePort);
this.readBuffer = new ByteArray();
this.socket = socket;
}
private function didConnectSocket(event:Event):void {
this.setStatusOnClients('connected');
// Send subscriptions
var phids = new Array();
for (var phid:String in this.subscriptions) {
phids.push(phid);
}
if (phids.length) {
this.sendSubscribeCommand(phids);
}
}
private function didCloseSocket(event:Event):void {
this.setStatusOnClients('error', 'error.flash.disconnected');
}
private function didIOErrorSocket(event:IOErrorEvent):void {
this.externalInvoke('error', event.text);
}
private function didSecurityErrorSocket(event:SecurityErrorEvent):void {
var text = event.text;
// This is really gross but there doesn't seem to be anything else
// on the object which gives us an error code.
if (text.match(/^Error #2048/)) {
this.setStatusOnClients('error', 'error.flash.xdomain');
}
this.error(text);
}
public function subscribe(client:String, phids:Array):void {
var newPHIDs = new Array();
for (var i:String in phids) {
var phid = phids[i];
if (!this.subscriptions[phid]) {
this.subscriptions[phid] = new Dictionary();
newPHIDs.push(phid);
}
this.subscriptions[phid][client] = true;
}
if (newPHIDs.length) {
this.sendSubscribeCommand(newPHIDs);
}
}
private function getSubscriptions(client:String):Array {
var subscriptions = new Array();
for (var phid:String in this.subscriptions) {
var clients = this.subscriptions[phid];
if (clients[client]) {
subscriptions.push(phid);
}
}
return subscriptions;
}
public function unsubscribeAll(client:String):void {
this.unsubscribe(client, this.getSubscriptions(client));
}
public function unsubscribe(client:String, phids:Array):void {
var oldPHIDs = new Array();
for (var i:String in phids) {
var phid = phids[i];
if (!this.subscriptions[phid]) {
continue;
}
delete this.subscriptions[phid][client];
var empty = true;
for (var key:String in this.subscriptions[phid]) {
empty = false;
}
if (empty) {
delete this.subscriptions[phid];
oldPHIDs.push(phid);
}
}
if (oldPHIDs.length) {
this.sendUnsubscribeCommand(oldPHIDs);
}
}
private function sendSubscribeCommand(phids:Array):void {
var msg:Dictionary = new Dictionary();
msg['command'] = 'subscribe';
msg['data'] = phids;
this.log('Sending subscribe command to server.');
this.socket.writeUTF(vegas.strings.JSON.serialize(msg));
this.socket.flush();
}
private function sendUnsubscribeCommand(phids:Array):void {
var msg:Dictionary = new Dictionary();
msg['command'] = 'unsubscribe';
msg['data'] = phids;
this.log('Sending subscribe command to server.');
this.socket.writeUTF(vegas.strings.JSON.serialize(msg));
this.socket.flush();
}
private function didReceiveSocket(event:Event):void {
try {
var b:ByteArray = this.readBuffer;
this.socket.readBytes(b, b.length);
do {
b = this.readBuffer;
b.position = 0;
if (b.length <= 8) {
break;
}
var msg_len:Number = parseInt(b.readUTFBytes(8), 10);
if (b.length >= msg_len + 8) {
var bytes:String = b.readUTFBytes(msg_len);
var data:Object = vegas.strings.JSON.deserialize(bytes);
var t:ByteArray = new ByteArray();
t.writeBytes(b, msg_len + 8);
this.readBuffer = t;
// Send the message to all clients.
for (var client:String in this.clients) {
var subscribed = false;
for (var i:String in data.subscribers) {
var phid = data.subscribers[i];
if (this.subscriptions[phid] &&
this.subscriptions[phid][client]) {
subscribed = true;
break;
}
}
if (subscribed) {
this.log('Sending message to client: ' + client);
this.send.send(client, 'receiveMessage', data);
}
}
} else {
break;
}
} while (true);
} catch (err:Error) {
this.error(err);
}
}
private function setStatusOnClients(
status:String,
code:String = null):void {
this.status = status;
this.statusCode = code;
for (var client:String in this.clients) {
this.send.send(client, 'setStatus', status, code);
}
}
}
}

View file

@ -1,14 +1,9 @@
/**
* Notification server. Launch with:
*
* sudo node aphlict_server.js --user=aphlict
*
* You can also specify `port`, `admin`, `host` and `log`.
*/
var JX = require('./lib/javelin').JX;
var http = require('http');
var https = require('https');
var util = require('util');
var fs = require('fs');
JX.require('lib/AphlictFlashPolicyServer', __dirname);
JX.require('lib/AphlictListenerList', __dirname);
JX.require('lib/AphlictLog', __dirname);
@ -17,8 +12,10 @@ function parse_command_line_arguments(argv) {
port: 22280,
admin: 22281,
host: '127.0.0.1',
user: null,
log: '/var/log/aphlict.log'
log: '/var/log/aphlict.log',
'ssl-key': null,
'ssl-certificate': null,
test: false
};
for (var ii = 2; ii < argv.length; ii++) {
@ -42,124 +39,120 @@ function parse_command_line_arguments(argv) {
var debug = new JX.AphlictLog()
.addConsole(console);
var clients = new JX.AphlictListenerList();
var config = parse_command_line_arguments(process.argv);
process.on('uncaughtException', function(err) {
debug.log('\n<<< UNCAUGHT EXCEPTION! >>>\n' + err.stack);
process.exit(1);
});
var WebSocket;
try {
WebSocket = require('ws');
} catch (ex) {
throw new Error(
'You need to install the Node.js "ws" module for websocket support. ' +
'Usually, you can do this with `npm install -g ws`. ' + ex.toString());
}
var ssl_config = {
enabled: (config['ssl-key'] || config['ssl-cert'])
};
// Load the SSL certificates (if any were provided) now, so that runs with
// `--test` will see any errors.
if (ssl_config.enabled) {
ssl_config.key = fs.readFileSync(config['ssl-key']);
ssl_config.cert = fs.readFileSync(config['ssl-cert']);
}
// Add the logfile so we'll fail if we can't write to it.
if (config.logfile) {
debug.addLogfile(config.logfile);
}
if (process.getuid() !== 0) {
console.log(
"ERROR: " +
"This server must be run as root because it needs to bind to privileged " +
"port 843 to start a Flash policy server. It will downgrade to run as a " +
"less-privileged user after binding if you pass a user in the command " +
"line arguments with '--user=alincoln'.");
process.exit(1);
// If we're just doing a configuration test, exit here before starting any
// servers.
if (config.test) {
debug.log('Configuration test OK.');
process.exit(0);
}
var net = require('net');
var http = require('http');
var start_time = new Date().getTime();
var messages_out = 0;
var messages_in = 0;
process.on('uncaughtException', function(err) {
debug.log('\n<<< UNCAUGHT EXCEPTION! >>>\n' + err.stack);
var clients = new JX.AphlictListenerList();
process.exit(1);
});
function https_discard_handler(req, res) {
res.writeHead(501);
res.end('HTTP/501 Use Websockets\n');
}
new JX.AphlictFlashPolicyServer()
.setDebugLog(debug)
.setAccessPort(config.port)
.start();
var ws;
if (ssl_config.enabled) {
var https_server = https.createServer({
key: ssl_config.key,
cert: ssl_config.cert
}, https_discard_handler).listen(config.port);
ws = new WebSocket.Server({server: https_server});
} else {
ws = new WebSocket.Server({port: config.port});
}
net.createServer(function(socket) {
var listener = clients.addListener(socket);
ws.on('connection', function(ws) {
var listener = clients.addListener(ws);
debug.log('<%s> Connected from %s',
listener.getDescription(),
socket.remoteAddress);
function log() {
debug.log(
util.format('<%s>', listener.getDescription()) +
' ' +
util.format.apply(null, arguments));
}
var buffer = new Buffer([]);
var length = 0;
log('Connected from %s.', ws._socket.remoteAddress);
socket.on('data', function(data) {
buffer = Buffer.concat([buffer, new Buffer(data)]);
ws.on('message', function(data) {
log('Received message: %s', data);
while (buffer.length) {
if (!length) {
length = buffer.readUInt16BE(0);
buffer = buffer.slice(2);
}
var message;
try {
message = JSON.parse(data);
} catch (err) {
log('Message is invalid: %s', err.message);
return;
}
if (buffer.length < length) {
// We need to wait for the rest of the data.
return;
}
switch (message.command) {
case 'subscribe':
log(
'Subscribed to: %s',
JSON.stringify(message.data));
listener.subscribe(message.data);
break;
var message;
try {
message = JSON.parse(buffer.toString('utf8', 0, length));
} catch (err) {
debug.log('<%s> Received invalid data.', listener.getDescription());
continue;
} finally {
buffer = buffer.slice(length);
length = 0;
}
case 'unsubscribe':
log(
'Unsubscribed from: %s',
JSON.stringify(message.data));
listener.unsubscribe(message.data);
break;
debug.log('<%s> Received data: %s',
listener.getDescription(),
JSON.stringify(message));
switch (message.command) {
case 'subscribe':
debug.log(
'<%s> Subscribed to: %s',
listener.getDescription(),
JSON.stringify(message.data));
listener.subscribe(message.data);
break;
case 'unsubscribe':
debug.log(
'<%s> Unsubscribed from: %s',
listener.getDescription(),
JSON.stringify(message.data));
listener.unsubscribe(message.data);
break;
default:
debug.log('<s> Unrecognized command.', listener.getDescription());
}
default:
log('Unrecognized command "%s".', message.command || '<undefined>');
}
});
socket.on('close', function() {
ws.on('close', function() {
clients.removeListener(listener);
debug.log('<%s> Disconnected', listener.getDescription());
log('Disconnected.');
});
socket.on('timeout', function() {
debug.log('<%s> Timed Out', listener.getDescription());
ws.on('error', function(err) {
log('Error: %s', err.message);
});
socket.on('end', function() {
debug.log('<%s> Ended Connection', listener.getDescription());
});
socket.on('error', function(e) {
debug.log('<%s> Error: %s', listener.getDescription(), e);
});
}).listen(config.port);
var messages_out = 0;
var messages_in = 0;
var start_time = new Date().getTime();
});
function transmit(msg) {
var listeners = clients.getListeners().filter(function(client) {
@ -195,7 +188,7 @@ http.createServer(function(request, response) {
try {
var msg = JSON.parse(body);
debug.log('notification: ' + JSON.stringify(msg));
debug.log('Received notification: ' + JSON.stringify(msg));
++messages_in;
try {
@ -242,10 +235,4 @@ http.createServer(function(request, response) {
}
}).listen(config.admin, config.host);
// If we're configured to drop permissions, get rid of them now that we've
// bound to the ports we need and opened logfiles.
if (config.user) {
process.setuid(config.user);
}
debug.log('Started Server (PID %d)', process.pid);

View file

@ -1,68 +0,0 @@
var JX = require('javelin').JX;
var net = require('net');
/**
* Server which handles cross-domain policy requests for Flash.
*
* var server = new AphlictFlashPolicyServer()
* .setAccessPort(9999)
* .start();
*/
JX.install('AphlictFlashPolicyServer', {
members: {
_server: null,
_port: 843,
_accessPort: null,
_debug: null,
setDebugLog: function(log) {
this._debug = log;
return this;
},
setAccessPort: function(port) {
this._accessPort = port;
return this;
},
start: function() {
this._server = net.createServer(JX.bind(this, this._didConnect));
this._server.listen(this._port);
return this;
},
_didConnect: function(socket) {
this._log('<FlashPolicy> Policy Request From %s', socket.remoteAddress);
socket.on('error', JX.bind(this, this._didSocketError, socket));
socket.write(this._getFlashPolicyResponse());
socket.end();
},
_didSocketError: function(socket, error) {
this._log('<FlashPolicy> Socket Error: %s', error);
},
_log: function() {
this._debug && this._debug.log.apply(this._debug, arguments);
},
_getFlashPolicyResponse: function() {
var policy = [
'<?xml version="1.0"?>',
'<!DOCTYPE cross-domain-policy SYSTEM ' +
'"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">',
'<cross-domain-policy>',
'<allow-access-from domain="*" to-ports="' + this._accessPort + '"/>',
'</cross-domain-policy>'
];
return policy.join('\n') + '\0';
}
}
});

View file

@ -49,15 +49,7 @@ JX.install('AphlictListener', {
},
writeMessage: function(message) {
var serial = JSON.stringify(message);
var length = Buffer.byteLength(serial, 'utf8');
length = length.toString();
while (length.length < 8) {
length = '0' + length;
}
this._socket.write(length + serial);
this._socket.send(JSON.stringify(message));
}
}

View file

@ -2,39 +2,31 @@
* @provides javelin-aphlict
* @requires javelin-install
* javelin-util
* javelin-websocket
* javelin-leader
* javelin-json
*/
/**
* Simple JS API for the Flash Aphlict client. Example usage:
* Client for the notification server. Example usage:
*
* var aphlict = new JX.Aphlict('aphlict_swf', '127.0.0.1', 22280)
* .setHandler(function(type, message) {
* JX.log("Got " + type + " event!")
* var aphlict = new JX.Aphlict('ws://localhost:22280', subscriptions)
* .setHandler(function(message) {
* // ...
* })
* .start();
*
* Your handler will receive these events:
*
* - `connect` The client initiated a connection to the server.
* - `connected` The client completed a connection to the server.
* - `close` The client disconnected from the server.
* - `error` There was an error.
* - `receive` Received a message from the server.
*
* You do not have to handle any of them in any specific way.
*/
JX.install('Aphlict', {
construct: function(id, server, port, subscriptions) {
construct: function(uri, subscriptions) {
if (__DEV__) {
if (JX.Aphlict._instance) {
JX.$E('Aphlict object is a singleton.');
}
}
this._id = id;
this._server = server;
this._port = port;
this._uri = uri;
this._subscriptions = subscriptions;
this._setStatus('setup');
@ -44,7 +36,6 @@ JX.install('Aphlict', {
events: ['didChangeStatus'],
members: {
_id: null,
_server: null,
_port: null,
_subscriptions: null,
@ -52,47 +43,92 @@ JX.install('Aphlict', {
_statusCode: null,
start: function(node, uri) {
this._setStatus('start');
JX.Leader.listen('onBecomeLeader', JX.bind(this, this._lead));
JX.Leader.listen('onReceiveBroadcast', JX.bind(this, this._receive));
JX.Leader.start();
// NOTE: This is grotesque, but seems to work everywhere.
node.innerHTML =
'<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000">' +
'<param name="movie" value="' + uri + '" />' +
'<param name="allowScriptAccess" value="always" />' +
'<param name="wmode" value="opaque" />' +
'<embed src="' + uri + '" wmode="opaque"' +
'width="0" height="0" id="' + this._id + '">' +
'</embed>' +
'</object>';
},
_didStartFlash: function() {
var id = this._id;
// Flash puts its "objects" into global scope in an inconsistent way,
// because it was written in like 1816 when globals were awesome and IE4
// didn't support other scopes since global scope is the best anyway.
var container = document[id] || window[id];
this._flashContainer = container;
this._flashContainer.connect(
this._server,
this._port,
this._subscriptions);
JX.Leader.call(JX.bind(this, this._begin));
},
getStatus: function() {
return this._status;
},
getStatusCode: function() {
return this._statusCode;
_begin: function() {
JX.Leader.broadcast(
null,
{type: 'aphlict.getstatus'});
JX.Leader.broadcast(
null,
{type: 'aphlict.subscribe', data: this._subscriptions});
},
_setStatus: function(status, code) {
_lead: function() {
var socket = new JX.WebSocket(this._uri);
socket.setOpenHandler(JX.bind(this, this._open));
socket.setMessageHandler(JX.bind(this, this._message));
socket.setCloseHandler(JX.bind(this, this._close));
this._socket = socket;
socket.open();
},
_open: function() {
this._broadcastStatus('open');
JX.Leader.broadcast(null, {type: 'aphlict.getsubscribers'});
},
_close: function() {
this._broadcastStatus('closed');
},
_broadcastStatus: function(status) {
JX.Leader.broadcast(null, {type: 'aphlict.status', data: status});
},
_message: function(raw) {
var message = JX.JSON.parse(raw);
JX.Leader.broadcast(null, {type: 'aphlict.server', data: message});
},
_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({
command: 'subscribe',
data: message.data
});
}
break;
case 'aphlict.server':
var handler = this.getHandler();
handler && handler(message.data);
break;
}
},
_setStatus: function(status) {
this._status = status;
this._statusCode = code || null;
this.invoke('didChangeStatus');
},
_write: function(message) {
this._socket.send(JX.JSON.stringify(message));
}
},
@ -110,28 +146,8 @@ JX.install('Aphlict', {
return null;
}
return self._instance;
},
didReceiveEvent: function(type, message) {
var client = JX.Aphlict.getInstance();
if (!client) {
return;
}
if (type == 'status') {
client._setStatus(message.type, message.code);
switch (message.type) {
case 'ready':
client._didStartFlash();
break;
}
}
var handler = client.getHandler();
if (handler) {
handler(type, message);
}
}
}
});

View file

@ -41,22 +41,8 @@ JX.behavior('aphlict-listen', function(config) {
// Respond to a notification from the Aphlict notification server. We send
// a request to Phabricator to get notification details.
function onaphlictmessage(type, message) {
switch (type) {
case 'receive':
JX.Stratcom.invoke('aphlict-receive-message', null, message);
break;
default:
case 'error':
case 'log':
case 'status':
if (config.debug) {
var details = message ? JX.JSON.stringify(message) : '';
JX.log('(Aphlict) [' + type + '] ' + details);
}
break;
}
function onaphlictmessage(message) {
JX.Stratcom.invoke('aphlict-receive-message', null, message);
}
@ -89,13 +75,11 @@ JX.behavior('aphlict-listen', function(config) {
}
var client = new JX.Aphlict(
config.id,
config.server,
config.port,
config.websocketURI,
config.subscriptions);
client
.setHandler(onaphlictmessage)
.start(JX.$(config.containerID), config.swfURI);
.start();
});

Binary file not shown.