Labs/F1/Modularity
F1, aka Firefox Share, lets users share links right from their browser. Here we work to make it easy to add/remove sharing providers, even by the user.
Preamble
F1 will move towards using "Headless Open Web Apps" (HOWA). A HOWA is almost identical to an OWA - it has a manifest and a list of supported services. When used, the OWA gets loaded into its own iframe and all communication with the OWA is via the regular postMessage channels used by all apps. The key difference is that the iframe remains hidden (meaning it can not display a UI related to the service) and instead performs actions based purely on the messages it receives - in other words, it exposes a postMessage-based javascript API rather than a UI. The lack of UI means HOWAs can only be used by services which have direct support in the browser (either directly or via an add-on) - F1 will provide this support (ie, the UI) for link-sharing services.
[interestingly, there is no reason a single app could not be both headless and "heady" if this was communicated as part of the OWA handshake, but that is largely a distraction which can be discussed later.]
HOWAs provide challenges around authentication that we need to address - while they can not provide UI for the execution of the service, they will need the ability to display UI, probably in a popup, for authenticating.
This document describes the (proposed) path F1 will take to get to this model.
There will be 2 distinct stages on this path:
Stage 1: An "app-like" model
The primary goals of this stage are to:
- provide a long-term solution for the long-tail of F1 services in the shortest possible timeframe.
- provide an auth mechanism which is both flexible and in the direct control of the service itself (eg, avoids the need for Mozilla infrastructure to store oauth tokens to any 3rd party).
- help refine and prove certain aspects of the OWA design, including the HOWA concept.
In this stage, F1 will use an "app-like" model but will not depend on the OWA extension being installed or even the spec being finalized. Thus there will be no standard way of installing, managing or discovering apps - F1 will take special, possibly temporary, action for these.
Each supported service will be implemented as a "headless open web app" - this will be true for "external" apps hosted directly by the vendors (eg, if we can convince google/twitter to host their app) and for "internal" apps hosted by F1 (ie, for all the existing services F1 supports where the vendor is not yet on-board).
F1 will need to define the postMessage javascript API, implement it for the "internal" services and advocate for it to "external" services (hopefully overlapping, so the externals can offer feedback on the API). F1 must be implemented using just this API - the F1 UI must not have any special UI behaviour hard-coded for facebook etc - everything must be able to be expressed via the API.
In addition to the constraints above, all work must be done with an eye towards the OWA-enabled world in Stage 2. Or to put it another way, if we can't see a clear path to Stage 2, we don't really need bother working within the constraints of HOWAs in the first place.
Stage 2: Using Open Web Apps directly
The primary goals here are:
- to use the Open Web Apps specification for all "app" management - including discovery and installation of new services.
- To reuse as much of the OWA UI as possible (eg, the door-hanger) to both ensure a consistent app UX and to reduce duplicated code between F1 and OWA.
- Allow OWA to be the primary mechanism for new vendors supporting the service and the discovery of that new support.
This will depend on 2 things largely out of our control: a final spec and release of Open Web Apps, and significant uptake of this model by 3rd party external services.
At the end of this stage, F1 will look as if it had been designed and implemented *after* the HOWA spec was finalized. Eventually F1 will not host any "internal" services and the backend can be retired.
Implementation plan
The following high-level things need to be done for stage 1
Mental model
While this is obvious, it is important - we need to move to a mental model of F1 using "apps", of which there are many available even though the average user may only have a few installed. Any UX issues or other considerations which arise directly from the OWA model need to be addressed in the context of OWA rather than "worked around" in F1 (in theory at least ;)
Service Discovery, Installation and Storage
The F1 UI will need to make a distinction between "installed" apps and "discoverable" apps - currently this distinction doesn't really exist as the number of possible apps is assumed to be small.
The F1 server will be the primary service discovery mechanism. An end-point will be exposed to list the URL of the manifest for all apps which support our service. The service will return both the "internal" apps supported directly by F1 and any other "external" services we know about. A config file or similar will list those external services, so as (say) facebook supports this sharing directly, we make a simple change to that config file and the facebook hosted app is able to be discovered by F1.
F1 will allow the user to "install" these discovered applications. Installed apps will be kept in local-storage using the same basic model as OWA itself. The routines used internally by F1 to enumerate the installed applications should be identical to the OWA counterparts. There will need to be some UI for removing an app, which needs to be different from any UI relating to "disconnect/logout from this app service." F1 will probably relax the OWA restriction about "only one app per domain" simply so all "internal" F1 apps can be hosted together.
Other UI and Front-End Issues
The main F1 panel and related code will need to be revamped in a couple of ways: each installed app will get an invisible iframe and all UI config information will be obtained using postMessage with that iframe. ie, the F1 UI will not be able to keep a global list of "services" as it does now, but dynamically build that list by querying each of the apps. Additionally, any service specific UI (both the service specific templates currently applied to F1 and the few hard-coding of service behaviour in the javascript) will need to be removed and implemented using data obtained via the postMessage channel. This means F1 probably doubles the number of iframes in use - one invisible one for each app (pointing at the app's domain), and one visible one for each app (holding dynamically built F1 content.)
Each existing F1 service will need to be broken into its own "app" and all send and auth related functionality etc implemented in each app rather than "globally". This can probably be done simply by having each app load the same javascript module (ie, share the implementation even though it exists in different apps). The F1 server end-points for sharing probably do not need to change.
Authentication
Ideally, F1 would not get involved in authentication other than creating a window for it to happen in. The login window itself could use whatever mechanism it liked for storing the credentials (eg, in localstorage for that domain, a cookie, etc) and the service endpoint (hosted by the same domain as the login url) could read it. F1 need not make any policy decision about how and where anything related to authentication is stored.
The descriptions below attempt to define the postMessage APIs such that the above remains true while (optionally) allowing an app to support multiple concurrent accounts being used (eg, having 2 twitter accounts configured.) But this hasn't been given a huge amount of thought and needs to be carefully considered.
F1 postMessage APIs
F1 will need to define the following javascript postMessage/jschannel based APIs (this is off-the-top-of-my-head and hoping to capture the intent rather than the actual spelling)
To be implemented by the app (ie, APIs called by F1)
getShareCharacteristics()
Get enough metadata about the application so the necessary UI for the service can be dynamically built. This is similar to the current F1 "services" global, and allows F1 to offer "send to wall" for facebook versus "send to my contacts" for linkedin.
getUserInfos()
Return a *list* of info for each logged in user to the service. If the list contains more than 1 item, multiple accounts against that service are being used (eg, 2 twitter accounts are configured). The user-info will include some pre-defined fields (eg, name, username, avatar) and any other values the service desires. The full user info (including the service specific "blobs") will be passed back into the service for many future operations.
Optionally, the return value will include a URL which can be used for logging in. Services which only allow one user at a time will only return that URL when the list of logged-in users is zero. Services which allow multiple users/accounts will return this URL even when there are returned users.
When login is required/desired, F1 will open the popup and establish a postMessage channel between the popup and the invisible iframe, so the login screen and the application itself can directly communicate. When login is complete, the app itself will be able to communicate this back to F1 (which will then re-execute this request and presumably get an extra user in the returned list, and adjust the UI accordingly)
getAutocompleteEntries(user_info, send_to, to_prefix)
Get all the autocomplete entries for the "to" field based on the current setting of the "send to" field for the specified user. For example, the facebook app might return the list of all groups you are a member of if send_to="group wall", and twitter would send a list of all your followers for send_to="direct".
send(user_info, data)
Send an item as described by "data" - ie, perform the actual share.
APIs to be implemented by F1, suitable for being called by any of the services:
userInfoChanged()
Inform F1 that the list of logged-in users for the app has changed. F1 will refresh the service by calling getUserInfo() and adjust itself accordingly - eg, by creating a second visible iframe for the service if a new user appears, or displaying special UI if no logins exist at all.
others?