Difference between revisions of "Bitgroup"
(skins and extensions undeer interface) |
(→Nodes - structured group data: semantic) |
||
Line 31: | Line 31: | ||
When a user creates a new group, the request is processed by the ''newGroup'' method in the main Python ''App'' class (defined in [http://svn.organicdesign.co.nz/filedetails.php?repname=tools&path=%2FBitgroup%2Fapp.py app.py]) which simply instantiates a new ''Group'' instance (the ''Group'' class is defined in the [http://svn.organicdesign.co.nz/filedetails.php?repname=tools&path=%2FBitgroup%2Fgroup.py group.py] module) with the desired name for the new group as the only parameter. When a ''Group'' instance is created with a name instead of an existing Bitmessage address as its construction parameter, it goes through the process of creating a new private key and address pair (public and private) for the group. The ''Group'' constructor then adds an entry for the new group to the "groups" section of the Bitgroup configuration file using the group's private key as the entry key and the group's private Bitmessage address as the entry value (see the [http://svn.organicdesign.co.nz/filedetails.php?repname=tools&path=%2FBitgroup%2Fconfig.sample config.sample] file for the exact format). And then finally it creates a new data structure for the group based on a template structure in the ''group.py'' module and adds the new group's name and public Bitmessage address to the structure. The data structure is automatically saved onto disk by the ''Node'' class which is a base-classes of ''Group''. The group's stored data is encrypted using the group's private key. | When a user creates a new group, the request is processed by the ''newGroup'' method in the main Python ''App'' class (defined in [http://svn.organicdesign.co.nz/filedetails.php?repname=tools&path=%2FBitgroup%2Fapp.py app.py]) which simply instantiates a new ''Group'' instance (the ''Group'' class is defined in the [http://svn.organicdesign.co.nz/filedetails.php?repname=tools&path=%2FBitgroup%2Fgroup.py group.py] module) with the desired name for the new group as the only parameter. When a ''Group'' instance is created with a name instead of an existing Bitmessage address as its construction parameter, it goes through the process of creating a new private key and address pair (public and private) for the group. The ''Group'' constructor then adds an entry for the new group to the "groups" section of the Bitgroup configuration file using the group's private key as the entry key and the group's private Bitmessage address as the entry value (see the [http://svn.organicdesign.co.nz/filedetails.php?repname=tools&path=%2FBitgroup%2Fconfig.sample config.sample] file for the exact format). And then finally it creates a new data structure for the group based on a template structure in the ''group.py'' module and adds the new group's name and public Bitmessage address to the structure. The data structure is automatically saved onto disk by the ''Node'' class which is a base-classes of ''Group''. The group's stored data is encrypted using the group's private key. | ||
− | === Nodes - structured group data === | + | === Nodes - structured group data and semantic relationships === |
The group functionality created by the ''Group'' class is a messaging-only system with no persistent content, so the next level of functionality is done by a class called ''Node'' (defined in the [http://svn.organicdesign.co.nz/filedetails.php?repname=tools&path=%2FBitgroup%2Fnode.py node.py] module) which defines this content aspect in the form of a structure of nodes and file attachments. Nodes are a hierarchical structure of properties (key:value pairs) which are are stored in JSON form, and at run-time are in the form of a JavaScript object (called ''app.data'') on the client side, and a Python dictionary on the server-side (called ''[GROUP].data''). | The group functionality created by the ''Group'' class is a messaging-only system with no persistent content, so the next level of functionality is done by a class called ''Node'' (defined in the [http://svn.organicdesign.co.nz/filedetails.php?repname=tools&path=%2FBitgroup%2Fnode.py node.py] module) which defines this content aspect in the form of a structure of nodes and file attachments. Nodes are a hierarchical structure of properties (key:value pairs) which are are stored in JSON form, and at run-time are in the form of a JavaScript object (called ''app.data'') on the client side, and a Python dictionary on the server-side (called ''[GROUP].data''). | ||
A group can have any number of nodes which can be viewed and modified in different ways depending on the extensions the group has installed, for example as a folder of files, a list of historical posts, as a collaborative document etc. Later as more extensions are created, this channel functionality can form the basis for many other applications such as group schedules and decision-making tools. | A group can have any number of nodes which can be viewed and modified in different ways depending on the extensions the group has installed, for example as a folder of files, a list of historical posts, as a collaborative document etc. Later as more extensions are created, this channel functionality can form the basis for many other applications such as group schedules and decision-making tools. | ||
− | The job of the ''Node'' class is to use both the Bitmessage network and real-time | + | The job of the ''Node'' class is to use both the Bitmessage network and real-time connections between members to keep the group's data structure synchronised across all members of the group. It also keeps the data synchronised across all interface pages open in the local member's browser and keeps the member's local storage up to date. Extensions also allow the group to define additional external backup storage repositories and sites on which to publish selected aspects of the group's data. |
+ | |||
+ | The hierarchical aspect of the data is generally only one level deep allowing a group to contain settings, members and nodes. Further structure is created via semantic relationships between the nodes. These relationships are still just in the form of key:value pairs like all the other data, but the difference is that both the ''key'' and ''value'' parts are references to other nodes. This creates a relationship between one node and another whereby the type of relationship and its meaning or effect on the node having the connection is determined by the node referred to by the ''key'' part of the property. | ||
+ | |||
+ | The function of a relationships may be as simple as a tag whereby it does nothing at all except that it has particular meaning to the users, and allows them to organise with it for example by searching for all nodes that exhibit that tag. But on the other hand the type of relationship could be very active having an effect of the nodes content or appearance, or restricting the nodes that can exhibit the relationship to only those that satisfy particular criteria. Extensions can even allow some kinds of relationships to automatically be applied to nodes that satisfy particular criteria. | ||
+ | |||
+ | One very useful relationship that will be added very early on will be a kind that requires a given number of members agreement before it gets applied to a node. This will allow for other useful sub-relations to be developed such as an "Administrator" relation that gives a member higher privileges in the group (for example being able to invite other members or terminate memberships) but for any user to gain this privilege would require the agreement of a number (or all) of the other members first. | ||
=== Bitgroup messages === | === Bitgroup messages === |
Revision as of 17:02, 15 October 2013
These days the issue of privacy online is becoming more and more important as we hear about the shocking extent to which organisations like the NSA are violating people's privacy and the giant corporations like Facebook and Google are misusing our personal information. Social networks have become an important tool for people from diverse locations and cultures to get together and organise, but when this organisation is used for purposes that oppose them they're shut down like this, this and this. It's become very apparent to many people that a new form of social networking tool is required that meets a number of important criteria.
Free and open source: For it to gain widespread use and be as accessible as possible to all people like Facebook and Google applications are it must be completely free to use. But it's very important that the source code be available for peer review so that the users can be satisfied that it's secure and there's no nasty tricks involved such as misuse of personal information or the inclusion of back doors through the security systems.
Peer-to-peer: One of the big problems we're starting to see nowadays is that the centralised model for setting up sites on a web-server for the users to access is not very resilient. We're seeing many sites that start off with great ideals when it comes to upholding peoples rights to privacy, but when they become too popular they come under attack by "the global agenda" by being accused of aiding child pornography or "supporting terrorism". They're then forced by law to turn over their user's data or face being prosecuted and shut down, some such as Silent Circle and Lavabit just shut down voluntarily rather than violate their user's privacy or face prosecution. However there are some online applications and services which have come under attack but have not been successfully shut down such as The Pirate Bay and other file sharing applications and the Bitcoin crypto-currency. Although these applications are considered a major problem from the perspective of the global agenda, it's so far proven impossible to do anything about them. The reason is that they have no central point of attack, shutting down accounts or servers or prosecuting people has no effect on these systems. It's for this reason that it's absolutely necessary for us, the people, to have access to a new social networking tool that is completely peer-to-peer.
Supports privacy and anonymity: Another thing that has been growing in importance a lot over the last few years is the ability for users to maintain their anonymity when working online. It's rapidly becoming a violation of law simply to voice opinion that opposes the status quo, which is in itself ample reason, or even a moral obligation, to oppose it! It's important for people who oppose this draconian system to be able to do so anonymously, especially in exceedingly oppressive countries that don't allow basic freedom of speech rights such as China and the USA.
Contents
Bitmessage
Anyone whose thought seriously about developing a any kind of peer-to-peer application that has more than basic file sharing functionality will know that there are some extremely difficult challenges to overcome, particularly when it comes to the "sign up/sign in" and the ability for many users to "subscribe" to information. There are many complex issues involved in security and content distribution which are orders of magnitude more difficult to accomplish in a system where there are no fixed servers, and every peer must be considered as a potentially hostile to the network.
The first application which has really made headway in moving the world of peer-to-peer beyond the simple file-sharing application is the Bitcoin crypto-currency. The fact that it's financially oriented creates much more motivation for attackers to try and exploit any weakness in it, but it has stood up to everything the hackers and the governments with their billions of dollars have thrown at it. Now a few years down the track, the Bitcoin code is starting to be used in other more diverse applications such as in the Namecoin alternative domain name system and the Bitmessage secure anonymous messaging system.
Bitgroup
Bitmessage is of particular interest because messaging is a very general application that can be used as the foundation for many other applications - social networking being one of them. And that's what the Bitgroup project is about, building the foundations of a secure peer-to-peer anonymous social network using Bitmessage as it's fundamental communications over which users and groups are formed and connected, hence the name "Bitgroup".
Bitmessage is programmed in Python, and since I've now had some experience working with the Bitessage API through Python when I made the Bitmessage gateway so the main networking aspect of Bitgroup will also be written in Python. The interface will be in JavaScript running within an embedded browser component within the Python application. This is so that it will be very easy to allow it to be run as a web-application server and will also make the interface programming and theming much simpler.
The code is being developed in our tools repo which you can view on our Github mirror here. It'll get moved into it's own repo after it's our of the pre-alpha stage.
How groups work in Bitgroup
The basic functionality required for building a social network application is the channel which is a collection of changing content maintained by one or more users that can be "subscribed" to, or "followed" by many others. This functionality allows for all the different behaviours that make up a social network system such as creating pages, posting items, following and "liking" things, as well as other potential functionality such as blogs, wikis or scheduling applications.
The first step in this "channels" idea is to extend the basic messaging and subscription functionality of Bitmessage to allow for the formation of groups that have not only subscribers that can view the public content generated by the group, but also "members" who produce and publish the group's content together and also have access to the private members-only content.
There are three key features of Bitmessage that allow for the formation of private groups.
- Any number of running instances can have the same From address, so sharing the private keys associated with an address (or in the case of a deterministically generated address, sharing the key used to create it from) with other people allows them to become a member of the group by being able to publish information from that group to its subscribers or other members.
- Users are able to subscribe to their own addresses which means that you will receive broadcast messages from others sending from that same address. This means that a many-to-many messaging system can be set up by a group all using the same address to broadcast from and subscribing to it. They can then use the private key used to create the address as a shared encryption key so that messages broadcast to the group's private address are only readable by members of the group.
- Broadcast messages do not reveal the identity of the sender, so the group can be private and the members anonymous even though its possible for a non-member to subscribe to the group's private address if they were to find it somehow (for example by observing the block chain).
When a user creates a new group, the request is processed by the newGroup method in the main Python App class (defined in app.py) which simply instantiates a new Group instance (the Group class is defined in the group.py module) with the desired name for the new group as the only parameter. When a Group instance is created with a name instead of an existing Bitmessage address as its construction parameter, it goes through the process of creating a new private key and address pair (public and private) for the group. The Group constructor then adds an entry for the new group to the "groups" section of the Bitgroup configuration file using the group's private key as the entry key and the group's private Bitmessage address as the entry value (see the config.sample file for the exact format). And then finally it creates a new data structure for the group based on a template structure in the group.py module and adds the new group's name and public Bitmessage address to the structure. The data structure is automatically saved onto disk by the Node class which is a base-classes of Group. The group's stored data is encrypted using the group's private key.
Nodes - structured group data and semantic relationships
The group functionality created by the Group class is a messaging-only system with no persistent content, so the next level of functionality is done by a class called Node (defined in the node.py module) which defines this content aspect in the form of a structure of nodes and file attachments. Nodes are a hierarchical structure of properties (key:value pairs) which are are stored in JSON form, and at run-time are in the form of a JavaScript object (called app.data) on the client side, and a Python dictionary on the server-side (called [GROUP].data).
A group can have any number of nodes which can be viewed and modified in different ways depending on the extensions the group has installed, for example as a folder of files, a list of historical posts, as a collaborative document etc. Later as more extensions are created, this channel functionality can form the basis for many other applications such as group schedules and decision-making tools.
The job of the Node class is to use both the Bitmessage network and real-time connections between members to keep the group's data structure synchronised across all members of the group. It also keeps the data synchronised across all interface pages open in the local member's browser and keeps the member's local storage up to date. Extensions also allow the group to define additional external backup storage repositories and sites on which to publish selected aspects of the group's data.
The hierarchical aspect of the data is generally only one level deep allowing a group to contain settings, members and nodes. Further structure is created via semantic relationships between the nodes. These relationships are still just in the form of key:value pairs like all the other data, but the difference is that both the key and value parts are references to other nodes. This creates a relationship between one node and another whereby the type of relationship and its meaning or effect on the node having the connection is determined by the node referred to by the key part of the property.
The function of a relationships may be as simple as a tag whereby it does nothing at all except that it has particular meaning to the users, and allows them to organise with it for example by searching for all nodes that exhibit that tag. But on the other hand the type of relationship could be very active having an effect of the nodes content or appearance, or restricting the nodes that can exhibit the relationship to only those that satisfy particular criteria. Extensions can even allow some kinds of relationships to automatically be applied to nodes that satisfy particular criteria.
One very useful relationship that will be added very early on will be a kind that requires a given number of members agreement before it gets applied to a node. This will allow for other useful sub-relations to be developed such as an "Administrator" relation that gives a member higher privileges in the group (for example being able to invite other members or terminate memberships) but for any user to gain this privilege would require the agreement of a number (or all) of the other members first.
Bitgroup messages
Bitgroup messages are Bitmessage messages that are designed to be interpreted by the Bitgroup application rather than by the recipient user directly. Bitgroup messages are distinguished from normal messages by a simple means of having a subject line that consists of the Bitgroup version that generated the message, the class that it should be instantiated as, and a human readable comment indicating that the message is to be interpreted by Bitgroup. The message body is in the form of a Base64 encoded JSON properties structure. It's up to the specific message class to ensure that the data of the message is valid and from a legitimate source.
Bitgroup messages exist in the Bitmessage network and in user's Bitmessage in-boxes and out-boxes, but at runtime they exist as instances of the Message class, or more specifically one of the sub-classes of Message which are all defined in the message.py module. The Message class has methods for sending or broadcasting and other basic Bitmessage operations and also has methods for creating and updating the message's properties, and the sub-classes the refine this common functionality for the classes specific purpose such as data synchronisation or inviting new members.
The Message class has a static method called getClass which takes a Bitmessage message data structure as its single parameter and returns the Bitgroup class that the message should be instantiated as. It does this by checking if the subject line indicates that the message is for Bitgroup to interpret and extracts the class that's specified. It also checks that the class not only exists, but is indeed a sub-class of the Message class before returning it. If it's not a Bitgroup message, or the class was not a Message sub-class, then the Message class is returned which is the Bitgroup wrapper for a plain Bitmessage message.
The Message constructor (__new__()) calls getClass to determine the sub-class (if any) that the Message should be instantiated as. It instantiates the returned class and before returning form the constructor it first checks the new instances invalid property. If the returned instance is a sub-class of Message, it's still possible that it could be a forgery with a subject line intended to make the method instantiate a sub-class, for example to try and forge a group invitation acceptance. After the sub-class is instantiated, it will be able to determine the authenticity of the message from the data contained in it, and if there's a problem it can set the invalid property so that the Message constructor can return the original Message instance.
All of the Message sub-classes are based on an intermediate "abstract" class called BitgroupMessage which takes care of all the functionality in common with all the Bitgroup Message sub-classes which are as follows:
- Determines if the message is incoming or outgoing based on whether the first parameter is a Group class or a Bitmessage message data structure.
- Adds the Group instance that the message was or will be broadcast to.
- Base64-encodes and encrypts the data for outgoing messages using the group's password.
- Base64-ddecodes and decrypts the data for incoming messages
Bitgroup messages are also present in the interface and so the interface also has a Message class which is defined in the message.js script. The message data is stored in the user's node data so that it's automatically synchronised with the Python side and the back-end storage but is not part of any distributed group data.
Note that only low-level functionality used by the Bitgroup system itself uses these message types. High level workflow for group organisation applications will be implemented later and will be done within the context of group node data which is on a higher level of abstraction than dealing with individual Bitmessages.
Invitations
The first type of Bitmessage implemented is the Invitation class which is used to invite other Bitgroup users to become a member of a group. If the recipient doesn't yet run Bitgroup (or even Bitmessage), then they'll need to be contacted directly first with a message requesting that they install Bitgroup so that they can then accept an invitation message. Later this process of first inviting users to install Bitgroup will be incorporated into the interface, but initially only the Bitgroup invitation messages are being implemented.
An Invitation has a property called status which is set to created by default when the instance is first created. Once a recipient has been set and the message sent to their address, the message which resides in the local Bitmessage out-box will have a status set to "sent". When the recipient's Bitgroup application receives the message an Invitation instance will be instantiated for it and its status will be set to received and it will at this point have a potential "accept" action associated with it which the Bitgroup interface will present. If the user chooses to accept the invitation, then the accept method of the Invitation will be executed and the message's status will be set to a final accepted value and the process of creating the group's public and private Bitmessage addresses locally will begin.
Presence & real-time connections
The Bitgroup system connects groups members directly if they're online so that changes to group data can synchronise in near real-time. This also allows for functionality such as group chat to be added to the system at some point. For offline users the data updates are compiled into Changes messages (messages of the Changes class) which are broadcast to the group private address periodically.
For connections to be established between peers (group members who are currently online), the members's connectivity status needs to be broadcast when they come online. This includes their IP address, Bitmessage address and approximate bandwidth information. This is done via a Bitgroup message of the Presence class which is broadcast to the group's private Bitmessage address. It's done via Bitmessage rather than direct connection so that no connectivity information is required about any of the other members in order for a user to announce their presence.
The real-time connections are organised in a client-server topology by designating one of the members as the "server" for the group in a deterministic way that doesn't require any communication or organisation. When member comes online all other peers will receive their Presence message and can store their online information in a list, but when a peer disconnects, only the server knows, and so all other peers must then be notified so they can remove the peer from their list.
To determine which peer is currently the server for the group, each peer's Bitgroup instance then takes their list and extracts the members with the fastest bandwidth and then selects the first one in order of Bitmessage address as the server for the group which is essentially random, but will be the same result for all members.
When a Presence message is received by the group server, it responds by establishing a real-time connection to the IP address and port that the peer specified in its Presence message, and then sends a Welcome message over the new connection which contains any group data that has changed since the peer was last online, and the list of connectivity information for all the other peers.
Data synchronisation
Each group consists of a number of users each running one or more instances of the software (only one instance per host though), and each of these instances may have many interface pages open in their browser. The data for the group needs to be synchronised across all of these instances and interface pages.
The general idea is that each context propagates it's changes over real-time bidirectional channel where they can, and where real-time connections are not possible or practical, the changes to data are queued up synchronised on a regular interval. The regular synchronisations are done using Bitmessage and are broadcast to the whole group, the real-time connections are done via a designated server. In the case of communicating changes between members with real-time connections, the server one of the members and is chosen in a deterministic way so that no communications is required to decide who will be the server.
As changes to data occur they're queued up to be sent when the next time a synchronisation request occurs. All the data is in the form of a hierarchical key:value structure with every value being a two-element array contain the value and an accompanying time-stamp denoting when the current value was set. The time-stamp originates at the source of the change and accompanies the value in the queue and then in the sent synchronisation data.
When any instance receives incoming data to be synchronised it checks each items time-stamp and compares it with the time-stamo if the local version of the same value (if one exists) and only stores it if it's more recent. This way all the instances' data will always eventually end up in a consistent state with each other as long as all the messages eventually arrive, see eventual consistency.
Interface synchronisation
Each instance has a unique ID so that the service can keep track of the last time each one connected last and merge it's queued client changes with it's local changes that have occurred during that time. An HTTP header called X-Bitgroup-ID is included in data sync requests for assigning an ID to each client since each Ajax request opens a different socket even if it's originating from the same page. The Python service's HTTP server is defined in the http.py module.
This bidirectional channels connecting the Bitgroup service with the interface browser pages in its basic form is a standard Ajax-based mechanism. An Ajax request to the server is made by each page on a regular one-second interval at which time all the queued changes to the group data that have occurred within the context of that page are sent and the server responds with the changes it has accumulated during that time that have come from other group member's hosts or from other local interface pages.
Each interface instance also includes a 1x1 pixel SWF at the bottom of the page which attempts to connect with the JavaScript application running in that same page, and also connects to the local Python service with an XMLSocket which remains open and automatically reconnects if it's disconnected. If the XMLSocket successfully connects with both the Python service and the local JavaScript, then it forwards all data sent by the Python service to the JavaScript application. In this way changes can be sent directly by the Python service as they happen rather than periodically sending queued data. The changes going from the page to the Python service are done via Ajax requests regardless of whether the SWF is running or not, but if it is, then they'll be sent immediately, otherwise they'll be queued for periodic sending.
The SWF binary is compiled by the open source MTASC compiler to the version 8 Flash standard and consists of just the socket.as script. The make-swf.sh script is included with the source to automatically download the MTASC compiler if it's not present and then build the interface/socket.swf binary from the socket.as source.
Other back-end aspects
The description of the groups and message structure covers most of the necessary information about the Bitgroup application, but there are a few other aspects worthy of mention which are covered here.
Encryption & Security
Most of the privacy aspects of Bitgroup rely on Bitmessage's security because the underlying messaging infrastructure of the groups is built on Bitmessage messages. But there are also real-time connections between on-line members in groups and group data is stored on disk and can be optionally distributed to third party storage services that the group has access to, so encryption is needed for these two mechanisms. I'm no encryption expert at all, so I've essentially delegated this aspect of the work to Bitmessage by utilising its encryption code for Bitgroup's encryption requirements.
This is done by importing the required modules directly from the Bitmessage source directory and calling the same high-level encrypt method as the Bitmessage code does to encrypt its message's. These modules are OpenSSL, pyelliptic and highlevelcrypto which offers a simple high-level interface to encrypting content using ECDHE-AES256-CBC (Diffie Hellman Elliptic Curve, 256 bit AES in Cipher-Block-Chaining mode).
Another aspect requiring attention is the connection between the local Python background service and the interface running in the browser. This uses the standard HTTP digest access authentication method which is necessary even though the connection is purely local, because it's possible for an attacker to set up a malicious Ajax request on another site that the same browser may visit which can then make a local request to the background service. Also it's useful to have authentication in place so that the application can be served over the web, although this is not a very good idea currently as there's no SSL capability on its HTTP server, but at least the user's login and password are not exposed by using the digest method for authentication, only hashes are sent to the server.
External IP address
The external IP address of the running instance needs to be known so that it can include it in its broadcast Presence information. Later when the network is more established this information will be able to be gained from the network without requiring a connection to a "legacy" server. Initially however connections to other peers cannot be relied upon, so the getExternalIP method obtains its information from checkip.dyndns.org.
Constants
I'm used to programming with constants and find them pretty useful, but there's no constants in Python or JavaScript (at least not in a browser-independent way), so I've made the constants.py module which declares a function called const that allows for a fairly standard way of defining constants and makes them "super-global" (available in every module and every scope). They're not really constants because they can be reassigned, but by sticking to all upper-case names makes it quite clear what their role is so they can be constants by convention. The const function also stores them all in an dictionary so that the http module can send them to the client side so that all the same constants are also available there.
Interface
Browser-based interfaces are extremely convenient because they allow very easy linking to other sites, and are very easy to develop since there's so many useful tools and technologies available such as jQuery and CSS, and allows groups to open up some of their content to the web for easy access. Some groups could even run a complete web gateway if privacy and security were less of a concern to them so that their members and subscribers could log in over the web like a normal social networking site. Since the browser will be connecting only to our local socket, and our local socket only accepts requests from our own application the browser is safer than the usual situation. However later we'd like to offer the option of an in-application version of the interface using PyWebkit or PyGtkMozembed.
The interface uses the Single Page Application pattern for the structure of the interface code. This keeps the roles of the Python and the JavaScript very clear with no need for a messy mish-mash of interface functionality spanning both the sides. The entire interface application will be written in JavaScript and only reloads the page/application when the user changes the context to a different group (since each group decides for themselves which extensions will be active and therefore which JavaScript will be loaded in the page).
The local part of the URL consists only of the group name or Bitmessage address and everything after that is the hash-fragment, so that only a change in group will result in a page-reload. The URL format after the hash is a slash-separated path, the first element being the currently selected view and subsequent elements being decoded by that view's URL-routing method and sent to the relevant method for processing.
To allow for more rapid interface development and make it easier for other developers to be involved and extend the system we're including jQuery and using jQueryUI for all the interface elements such as tooltips, progress bars, tab-sets etc.
Internationalisation (i18n)
All the interface messages are stored in the i18n.json file (this will probably be changed to separate files for each language later such as i18n.es.json and i18n.de.json etc). This file is shared by both the Python and JavaScript sides.
Application start-up sequence
First the Python-based Bitgroup service needs to be run before the interface can be loaded since the interface code and resources are served to the browser from this service. The starting point is main.py which reads in the .config file, ensures that the application isn't already running, and then instantiates an instance of the main App class which is defined in the app.py module. When the App class initiates it establishes itself as a "superglobal" called app which is available from all scopes and modules of the application. This may not be a very Pythonic way of doing things, but I'm too noob and found the easiest way to get things rolling so that's how it is for now. I used the following line in app's __init__ method to create the app super-global:
After the interface is running in the browser it can be used even if the Python service is stopped as long as the group is not changed. Any data changes made while the background service isn't running will be queued up and sent after it can be connected to again. A change in group requires a page reload (and therefore needs the service to be present) since only one group's data and extensions are loaded at once in case one group you're a member of chooses to run extensions that another group you're a member of doesn't trust.
When the browser first requests a page from Bitmessage's HTTP server (defined in http.py), it returns a page containing just an empty body element and a head element containing the main application script, the default views (currently "overview" and "newgroup") and some common resources such jQuery and jQueryUI. The page also contains a small number of variables specifying the current group, user information and the group's extension names. Everything then starts with the execution of the main application script, main.js.
The main.js script defines a class called App which encapsulates all the main functionality of the entire application, and then instantiates a singleton instance of this class called window.app. Execution is then handed over to the constructor of the App class.
The constructor first copies all the data that was sent in the request HTML head element into the application instance, and then does an Ajax request to retrieve i18n.json which contains all the internationalisation messages used by the application. After the i18n messages have finished loading, the group's extensions are loaded and a handler is registered to respond to changes in the "hash fragment" portion of the URL (see Single Page Application for more details about what this means). Execution is then passed to the application's main start-up method, run().
The run() method first calls the locationChange() method to simulate the changing of the hash-fragment in the URL so that the current view can be rendered into the content area of the page. If there is no group selected, then the pageRender method is called since it will not be automatically rendered in response to data events in this case. The final step in the start-up process is to set up an event that runs every second which keeps the data synchronised between the backend and client side.
From then on, everything that happens in the ongoing running of the application is in response to either user interface events or data-change events coming from the Python service.
Page rendering sequence
In a pure Single Page Application page rendering is a rare occurrence because there are no page reloads. In Bitgroup a page render occurs when the user changes the group as described above since it causes a page reload. A full page render is also done if data synchronisation hasn't occurred for some time or the number of changes received is over a certain amount.
The first response from the server side for a dataSync request returns the entire data structure (this happens if the last sync was more than a certain time ago or the number of changes is more than a certain amount). The dataSync method detects if the response is a list of changes or the whole structure, and if it's the whole structure it calls renderPage which builds the entire page from scratch.
The renderPage method first loads the CSS for the group's selected skin, or the default if no skin is selected. It then renders the personal links in the top bar and the views menu, and then renders empty div elements for the main page items such as notification. page title and content areas which will be filled later. The skin's JavaScript (if it has any) is then executed which may completely alter the layout of the page as long as it keeps the elements ID attributes intact.
After the skin's script as finished executing the renderPage method will then finish off the page by populating the title area, connecting the live menu elements to their data sources and executing the selected page view method, or overview if none is selected. The view method is responsible for populating the main content area of the page and is called not only in a full page render, but also whenever a new view is selected from the view menu or when the currently selected node changes.
Forms & data
In a Single Page Application, the input elements that allow the user to update data and content in the application do not use the classic form/post methodology because everything is handled by the currently loaded page. In fact the entire form element is quite redundant and so is the name attribute of input elements. Instead the form components simply connect into the application functionality directly via event handlers.
In the Bitgroup application we have some methods of the main App class that make the handling of inputs much simpler. First we have componentType which returns a general type string when passed an input element. This type string is used to determine how the value of the input is set or retrieved and how changes in the value are to be connected in to the application. The componentRender method is a high-level helper function for constructing an input element given some data such as a list, and componentSetValue / componentGetValue are used for setting or retrieving the value of a form or other interface component.
Components can have their own getValue and/or setValue functions by adding them directly to the element and then these will be used by componentSetValue and componentGetValue if they're present. This way it's easy to make complicated custom components that are connected to live data changes.
App.componentConnect: The main function is componentConnect which creates a "live" connection between the specified path into the group's data and the interface component. The connection is bidirectional (if it's an input component) and asynchronous so that if the user changes the value it will automatically propagate back to the server-side to be stored and will automatically be reflected in any other open instances of the interface. Conversely if the data undergoes change from another remote instance via another user in the group or by some other back-end service, all interface components connected to the changed data will automatically have their values updated.
Here's a simple example of how it can be used:
In this example, a checklist (like a select list but with checkboxes instead) is rendered with items "foo", "bar" and "baz". The second line then connects this new input element to the data source settings.extensions in the group's shared data structure. As soon as this is done, any of the checklist items that are in the settings.extensions value will become checked, and if any of the items checked state are changed by the user, the group's data and any other pages containing the input will update accordingly.
Here's a slightly more complicated example which creates a live table connected to a data source that's an array. Whenever the data in the array changes, the element's custom setter method is called which renders the data as a table into the element.
One complication (bug#2) is that the event handler prevents the DOM element that it's connected to from dying when it's no longer present in the DOM, so the event handler first checks this by using a jQuery statement to check if the component is a descendant of the body element, and removes itself from the event if not.
The handler function is assigned to a variable in the local scope so that it can be referred to when it's called to unhook itself from the event using the jQuery .off() method.
Skins
The main content is generated unconditionally by the main application and is a plain set of div and ul elements with CSS class and ID attributes allowing CSS to define the style and page layout. Skins are a directory of the skins name that go in the skins directory and contain at least a style.css stylesheet that will be loaded if the group has selected that skin. There can then be any number of support files such as images etc in the skin's directory that the stylesheet can refer to.
Extensions
Extensions play an important role in the Bitgroup system. The idea is that each group's members can choose what extensions are active for them based on their needs and their security concerns. The extensions are only implemented in the JavaScript side currently as that's where the majority of the application's functionality resides - the Python side is only the foundation network functionality which is unlikely to require extending and raises security issues at least until the application has reached a mature state of development. Extensions are in the form of directories that reside in the extensions directory. The directory should be the name of the extension, and it should contain a JavaScript file of the same name within it that has the .js file extension. This file will be loaded prior to application startup if the current group has selected to use it. If the user changes to a different group, the page reloads so that a new selection of extension can load depending on the group's preferences.
Adding new views
To add a new view, an extension defines a class, adds a render method which populates the content div, and adds the class to the available views in the app.views list. It may also add the view name to the various node types that should have the view.
Application events
We need to add custom events throughout the code that extensions can subscribe to so they can adjust or extend the behaviour of the application. Extensions will initially only be available on the JavaScript side and will use the jQuery trigger method. Events are triggered with parameters passed to the handler that are specific to the event are packaged inside an args object so that they can be adjusted since JavaScript doesn't support passing-by-reference on non-object variables.
This is an example of an event the could be triggered by an extension:
Here the argument foo is being output after the new bgNewEvent event is triggered to see if any handlers have changed it's value. We precede the event name with the "bg" prefix to ensure there's no conflict with existing events in the environment.
A handler could subscribe to this event and modify the foo value as follows:
Current events
bgHashChange
bgPoller
bgDataChange-KEY
Development and testing
It can be very time consuming and difficult to set up an environment to test a group running in the Bitmessage network because it's difficult to set up and maintain multiple instances of Bitmessage. Running them on multiple machines presents its own problems and running them all on a single host is very processor and bandwidth intensive. Even after getting multiple instances set up and running it's still a maintenance headache and is a very laborious process having to wait minutes for messages to route across the network especially when many debugging processes often require many iterations which all involve long delays waiting for message propagations. These problems are multiplied when working in a low bandwidth environment like our rural net connection.
So for these reasons I've created the dev.py module which does two main jobs to help ease the development and testing process which are activated by adding the dev parameter to the command line arguments to launch Bitgroup in development mode.
The first things the dev.py module provides is a fake Bitmessage API which provides replicas of all the functions that Bitgroup requires from Bitmessage. Instead of sending messages peer-to-peer across the network, this fake version simply stores the data locally on disk allowing any number of local Bitgroup programs to communicate together as if they were communicating via Bitmessage. This is done in app.py by checking if the program is running in dev mode and replacing the app.api value with the fakeBitmessage class instead of the proper xmlrpclib connection.
The second thing that development does is to make it easy to start and stop a number of Bitgroup instances with different user names and Bitmessage addresses. By adding a second command line argument to specify the number of instances to run, the first instance will then spawn a number of additional instances each with their own determinate fake Bitmessage addresses based on the hash of their nickname.
The nickname names are based on the nickname of the initial user which stays the same, the subsequent ones are appended with 2,3,4 etc, for example, if the main user's nickname is set to "Foo" and the script is run with the dev 3 parameters, then three instances will start up with the nicknames Foo, Foo2 and Foo3.
The port numbers that each instance's interface runs on start at the initial configured port plus one and then each successive instance is on the next port and so on. So for example if the port is set to 8080 in the configuration and three instances are running, their interfaces will be on port 8081, 8082 and 8083. This was done rather than starting at 8080 so that their right-most digit matches their nickname's digit.
All the settings (except the user) such as port number (initial port number in the case of multiple instances), groups and other settings are copied from the main configuration file. All the data for the development instances is stored in their own directories using their fake Bitmessage addresses for a name and which all reside in a directory called dev which resides in the same location as the Bitgroup Python scripts.
Each instance's subscriptions are in a file called subscriptions.json in their respective data directories. Unlike the subscriptions, the mailboxes are all stored in a single file which has a name based on the initial user's nickname and is directly under .dev, for example .dev/messages.Foo.json. This is done because each instance needs to access other instance's mailboxes to store broadcast messages and the file needs to be locked when it's being updated to avoid conflicts, so having only one file to deal with makes this a lot simpler. This is not necessary for subscriptions because instances only ever modify their own subscriptions.
See also
- Bitgroup source code: WebSVN | GitHub mirror
- Bitgroup/Bugs - a record of the bugs and fixes
- Bitgroup/Tasks - a record of the tasks to do and done
- Bitmessage
- Bitcoin
- Python
- Single Page Application
- Closure