WebAPI/Inter App Communication Alt proposal

From MozillaWiki
Jump to: navigation, search

Description

This proposal is just a tweaked version of the Inter App Communication API. Its description, intent, and use cases do not defer from the previous proposal, only the API does. See bug 876397 for implementation.

API

Publisher

 partial interface Application {
   // The returned Promise will be resolved if at least one peer is
   // allowed to connect with the application and will contain in that
   // case an array of InterAppMessagePort, each of them representing a peer
   // that allowed the connection.
   // If no peer is allowed to connect with the app, the Promise will
   // be rejected with the reason of the rejection.
   Promise connect(DOMString keyword, jsval rules);
 
   // The returned Future will contain an array of InterAppConnection objects each
   // of them representing an InterAppconnection with a peer through a specific
   // keyword.
   Promise getConnections();
 };

where

  • keyword is the key string subject of the connection. Only apps advertising themselves as able to connect through this keyword will receive a connection request (if the user allows to and the app fulfills the requirements specified in the rules argument).
  • rules (optional) is an object containing a set of constraints for the requested connection. Only apps fulfilling these constraints will receive a connection request. These rules may contain:
    • minimumAccessLevel is the minimum level of access (one of https://developer.mozilla.org/en-US/docs/Web/Apps/Manifest#type) that the subscriber app requires in order to be able to receive the connection request. The default value will be 'web'.
    • installOrigins (array) list of install origins from where subscriber apps should have been installed. Since certified apps has not a valid install origin, these constraint does not apply to them.

Subscriber

Applications can advertise themselves as able to connect given an specific keyword by adding an entry named connections in their manifests containing an object list where each object represents the description and details of each potential connection.

 {
   'name': 'Foobar application',
   /* ... */
   'connections': {
     'keyword1': {
       'handler_path': '/handler1.html',
       'description': 'Do something for keyword1 connection',
       'rules': {
         'minimumAccessLevel': 'web',
         'installOrigins': ['install_origin_1', 'install_origin_n']
       }
     },
     'keyword2': {
       'handler_path': '/handler2.html',
       'description': 'Do something for keyword2 connection',       
     }
   }
 }

where

  • handler_path is the path of the page where the handler of the connection request lives in the app's code. If 'handling_path' is absent, the 'launch_patch' will be taken as default.
  • description is the message to be shown to the user during the connection request that should describe why the connection is required and what does the app intends to do within that connection.
  • rules is an object containing a set of constraints to be fulfilled by connection requesters. These rules may contain:
    • minimumAccessLevel is the minimum level of access (one of https://developer.mozilla.org/en-US/docs/Web/Apps/Manifest#type) that the requester app requires in order to be able to send a connection request. The default value will be 'web'.
    • installOrigins (array) list of install origins from where the requester apps should have been installed. Since certified apps has not a valid install origin, these constraint does not apply to them.

Connection acknowledgement

Applications that fulfill the rules specified in the .connect() call and have explicitly been allowed by the user to connect with the publisher application will receive a system message named 'connection' containing a InterAppConnectionRequest object of this form as a message.

 interface InterAppConnectionRequest {
   DOMString keyword;
   InterAppMessagePort port;
 };

where

  • keyword is the key string given with the .connect() call.
  • port is an instance of InterAppMessagePort that will be the message channel for the connection.

InterAppMessagePort

A InterAppMessagePort is the channel that connects two applications and allow them to send and receive messages.

 interface InterAppMessagePort {
   void start();
   void close();
   void postMessage(in jsval message);
   attribute jsval onmessage;
   // To Fernando: we should try to avoid defining the following two event handlers
   // so that we can reuse the formal W3C MessagePort. Need to discuss about this.
   attribute jsval onstart;
   attribute jsval onclose;
 };

InterAppConnection

A InterAppConnection object represents a connection between two apps through an specific keyword.

interface InterAppConnection {
  readonly attribute DOMString keyword;
  readonly attribute DOMString publisher;
  readonly attribute DOMString subscriber;
  void cancel();
};

InterAppConnection objects will be obtained within a resolved Promise returned by a getConnections() call. This function is likely going to be called by a settings application that intends to show the user a list of existing connections, allowing her to cancel them if needed.

Defer mechanism

To be done in future versions.

The fact that the implementation of this API is based in System Messages leaves us in a situation where potential performance issues might appear if several applications advertise themselves as able to connect through the same keyword and so are targets of the same connection request. In that case, a defer mechanism where apps are forced to specify that they really need to be woken up to handle a connection request, along with the current rules mechanism, might help to mitigate this issue.

Usage examples

Lockscreen and Music

(Disclaimer: this is just a rough example of a possible solution for this use case. The permission model does not need to meet the real requirements for these apps. Also this use cases can probably be done with an unique keyword, different access rules and an agreed bidirectional API, but I just wanted a wider example)

The lockscreen (Gaia System app) wants to be able to:

  • control the music played by the Gaia Music app
  • display information about the currently played track from the Gaia Music app or any other 3rd party music app installed from the Firefox Marketplace.

In order to receive information about the currently played music track, the System app needs to add the following 'connections' field to its manifest

{
   'name': 'System',
   /* ... */
   'connections': {
     'musictrack': {
       'handler_path': 'musicmanager.html',
       'description': 'Show the currently played music track information in the lockscreen',
       'rules': {
         'installOrigins': ['marketplace.firefox.com']
       }
     }
   }
}

where it advertises itself as able to receive connections through the 'musictrack' keyword, requested by apps installed from the Firefox Marketplace and handled within the musicmanager.html page.

On the other side, the Gaia Music app that wants to be controlled from the lockscreen (and only from there) should add the following 'connections' field to its manifest:

 {
   'name': 'Music',
   /* ... */
   'connections': {
     'musicremotecontrol': {
       'description': 'Play, pause and stop music tracks',
       'rules': {
       }
     }
   }
 }

We also have a fictitious Songbirdy privileged app installed from the Firefox Marketplace and an also fictitious iTunos unprivileged app installed from an unknown source that want to be controlled by any privileged app.

 {
   'name': 'Songbirdy', (or iTunos)
   /* ... */
   'connections': {
     'musicremotecontrol': {
       'description': 'Play, pause and stop music tracks',
       'rules': {
         'minimumAccessLevel': 'privileged'
       }
     }
   }
 }

Current track showed in the lockscreen

Gaia Music starts playing a random track and wants to share the information about this track with other apps. Since the shared information is harmless, it doesn't care about the subscriber, so no rules are required while requesting the connection.

connect('musictrack').then(function onConnectionAccepted(ports) {
  // If the connection is allowed at least by one peer, the resolved callback
  // will be triggered with a list of InterAppMessagePort as parameter (ports in this case).
  ports.forEach(function(port) {
    // We still need to wait for the connected peer to notify as able to start
    // receiving messages through the port.
    port.onstart(function() {
      // At this point the Gaia Music app can start sending information about
      // the currently played track.
      port.postMessage({
        title: 'The Beatles',
        artist: 'Strawberry fields forever'
      });
      // In this example approach, we probably don't need a bidirectional communication
      // as we have that through a different keyword, but we can always set a message
      // handler via InterAppMessagePort.onmessage at this point to handle the messages sent from
      // the peer on the other side of the port.
      port.onmessage = myMessageHandler;     
    });
  });
}, function onConnectionRejected(reason) {
  // If there is no peer that connects with the 'musictrack' keyword, the user
  // didn't allow the connection with at least one peer or all the peers rejected
  // the connection, the reject callback will be triggered.
  ...
});

The System app only allows connections with apps installed from the Firefox Marketplace (or from certified apps). In this case, the publisher (Gaia Music app) is a certified app, so the connection request is allowed by the platform. Assuming that the lockscreen (System app) is the only application advertising itself as able to connect through the 'musictrack' keyword and there is no record of a previously allowed connection between this two apps through this keyword, a popup will be shown to the user saying something like:

Do you want to allow Music (music.gaiamobile.org) to connect with System (system.gaiamobile.org) to Show the currently played music track information in the lockscreen? [Allow|Deny]

In case that there would be more than one app subscribed to the 'musictrack' keyword, a different UI with a list of these apps would have been shown.

There is no need to show this UI again unless a new app subscribing to 'musictrack' is installed.

The user allows the connection and a system message is sent to the lockscreen (System app).

navigator.setMessageHandler('connection', function(connectionRequest) {
  if (connectionRequest.keyword !== 'musictrack') {
    return;
  }
  
  let port = connectionRequest.port;
  port.onmessage = onMusicTrackHandler;
  port.start();
});

A similar connection request coming from Sonbirdy would also be allowed by the platform, cause we said that this app was installed from the Firefox Marketplace. However a connection request coming from iTunos would be rejected by the platform, since this app is unprivileged web content installed from an unknown source non listed within the lockscreen app rules. This request will not trigger any system messages and so the subscriber won't ever be awake because of it (in this case the subscriber (System app) is already live though).

Music controls in the lockscreen

The lockscreen, in this example, wants to control only (no reasons, just an example) the music from the Gaia Music app (known origin). Once the lockscreen is ready, it sends a connection request of this kind:

connect('musicremotecontrol').then((function onConnectionAccepted(ports) {
  if (ports.length > 1) {
    // We shouldn't be here as we only want to communicate with the Gaia Music app.
    return;
  }
 
  let musicPort = ports[0];
  musicPort.onstart((function() {
    this.enableControlButtons();
    this.madeUpPlayButton.onclick = function() {
      musicPort.postMessage({
        action: 'play'
      });
    };
    this.madeUpPauseButton.onclick = function() {
      musicPort.postMessage({
        action: 'pause'
      });
    };
    this.madeUpStopButton.onclick = function () {
      musicPort.postMessage({
        action: 'stop'
      });
    };
  }).bind(this));
  musicPort.onclose((function() {
    this.disableControlButtons();
  }).bind(this));
}).bind(this), (function onConnectionRejected(reason) {
  ...
}).bind(this));

If no previous connection through the 'musicremotecontrol' keyword between the lockscreen and the Gaia Music app has been previously allowed an UI is shown to the user with a message like:

'Do you want to allow System (system.gaiamobile.org) to communicate with Music (music.gaiamobile.org) to Play, pause and stop music tracks? [Accept|Reject]'

Even if Songbirdy and iTunos advertise themselves as able to connect through the 'musicremotecontrol' keyword, as the request is done with a specific rule that requires the subscriber to have the 'music.gaiamobile.org' origin, these apps won't be listed in the above UI and won't receive any system message with a connection request.

The user allows that connection and a system message is sent to the Music app.

navigator.setMessageHandler('connection', function(connectionRequest) {
  let keyword = connectionRequest.keyword;
  if (keyword != 'musictrack' ||
      keyword != 'musicremotecontrol') {
    return;
  }
 
  let port = connectionRequest.port;   
  switch(keyword) { 
    case 'musictrack':
      port.onmessage = this.onMusicTrackHandler;
      break;
    case 'musicremotecontrol':
      port.onmessage = this.onMusicRemoteControlHandler;
      break;
  }
  port.start();
});

Open Questions

  • Should we change 'connect' verbs for 'pub/sub' verbs? Like
 partial interface Application { 
   Promise publish(DOMString keyword, object rules);
 
   Promise subcriptionsRegistered();    
 };
  • We might want to have a bigger set of types to allow apps to connect (web, installed, signed, privileged, certified).