WebAPI/Inter App Communication Alt proposal: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
(→‎Receiver: I'd prefer s/connect/connections just like "messages" and "permissions".)
Line 57: Line 57:


=== Connection acknowledgement ===
=== Connection acknowledgement ===
Applications that have the required minimum level of access specified in the ''.connect()'' call and have explicitly been allowed by the user to connect with the caller application will receive a system message named 'connect' containing a ''ConnectionRequest'' object of this form as a ''message''.
Applications that have the required minimum level of access specified in the ''.connect()'' call and have explicitly been allowed by the user to connect with the caller application will receive a system message named 'connection' containing a ''ConnectionRequest'' object of this form as a ''message''.


   Dictionary ConnectionRequest {
   Dictionary ConnectionRequest {

Revision as of 07:39, 19 July 2013

Description

This proposal is just a tweaked version of the Inter App Communication API. Its description, intend and use cases do not defer from the previous proposal, only the API does.

API

Caller

 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 MessagePort, 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 true if at least a peer is allowed
   // to communicate with the application, false otherwise.
   Promise connectionRegistered(DOMString keyword);
   
   Promise getConnectionMessages(DOMString keyword);
 };

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 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 receiver app requires in order to be able to receive the connection request. The default value will be 'web'.
    • origin (array) can be used to set specific receivers by a list of origins.
    • developer (array) list of objects identifying app authors whose apps are allowed to receive a connection request.

Receiver

Applications can advertise themselves as able to connect given an specific keyword by adding an entry named connection in the manifest 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',
       'deferQueue': true
     },
     'keyword2': {
       'handler_path': '/handler2.html',
       'description': 'Do something for keyword2 connection',
       'defer': false
     }
   }
 }

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.
  • defer is a flag to allow apps to be woken up because of a connection request. If 'defer' is not present, it will default to true. The 'defer' value will be ignored if the connection has not being previously accepted by the user and the receiver which will be woken up so it can handle and properly accept the connection request.
  • deferQueue is a flag that indicates if an app expects to keep a queue of messages received while it is not being executed or has no handler for the messages. An app may choose to ignore messages sent to it while it is not being executed. If deferQueue is not present, false will be taken as the default value.

Connection acknowledgement

Applications that have the required minimum level of access specified in the .connect() call and have explicitly been allowed by the user to connect with the caller application will receive a system message named 'connection' containing a ConnectionRequest object of this form as a message.

 Dictionary ConnectionRequest {
   MessagePort port;
   DOMString   keyword;
   jsval       callerInfo;
   void        reject();
 };

where

  • port is an instance of MessagePort that will be the message channel for the connection.
  • keyword is the key string given with the .connect() call.
  • callerInfo is an object containing basic information about the app requesting the connection channel. This information will allow the receiver to decide if the connection is secure enough or not. It may contain the following data (TODO: we need to decide which data do we want to provider. We might want to provide the whole caller app manifest):
    • origin which is the origin of the app requesting the connection.
    • accessLevel which is the access level of the app requesting the connection.
    • developer which contains the information about the developer of the app requesting the connection. It may contain the name and url of the developer.

MessagePort

A MessagePort is the channel that connects two apps and allow them to send and receive messages.

  interface MessagePort {
   void postMessage(...);
   attribute jsval onmessage;
 };

Defer mechanism

TODO

Offline message queues

TODO

Usage examples

Lockscreen and Music

(Disclaimer: this is just a rough example of a possible solution for this use case. This can probably be done with an unique keyword, different access rules and an agreed bidirectional API, but I wanted a wider example)

The lockscreen 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 privileged app.

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

{
   'name': 'System',
   /* ... */
   'connect': {
     'musictrack': {
       'description': 'Show the currently played music track information in the lockscreen'
     }
   }
}

where it advertises itself as able to receive connection requests through the 'musictrack' keyword.

On the other side, music apps (lets say the Gaia Music app and a fictitious Songbirdy privileged app installed from the Firefox Marketplace and an also fictitious iTunos unprivileged app installed from an unknown source) that wants to be controlled from the lockscreen should add the following 'connect' field to its manifest:

 {
   'name': 'Music', (or Songbirdy or iTunos)    
   /* ... */
   'connect': {
     'musicremotecontrol': {
       'description': 'Play, pause and stop music tracks'
     }
   }
 }

Current track showed in the lockscreen

The Gaia Music app 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 receiver, 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 MessagePorts as parameter (ports in this case).
  // At this point the Gaia Music app can start sending information about
  // the currently played track.
  ports.forEach(function(port) {
    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 MessagePort.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.
  ...
});

Assuming that the lockscreen 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 to connect with System 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. The lockscreen only allows connections with privileged apps, so it checks that in the connection request handler, and set a handler for the messages sent through the MessagePort via MessagePort.onmessage

navigator.setMessageHandler('connect', function(connectionRequest) {
  if (connectionRequest.keyword !== 'musictrack') {
    return;
  }
 
  if (connectionRequest.callerInfo.accessLevel === 'certified' ||
      connectionRequest.callerInfo.accessLevel === 'privileged') {
    connectionRequest.port.onmessage = onMusicTrackHandler;
  } else {
    connectionRequest.reject();
  }
});

A similar connection request coming from Sonbirdy would also be accepted, cause we said that this app is privileged. However a connection request coming from iTunos would be rejected, since this app is unprivileged web content.

Music controls in the lockscreen

The Lockscreen, in this example, wants to control only the music from the Gaia Music app (certified and with a 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];
  this.madeUpPlayButton.onclick = function() {
    musicPort.postMessage({
      action: 'play'
    });
  };
  this.madeUpPauseButton.onclick = function() {
    musicPort.postMessage({
      action: 'pause'
    });
  };
  this.madeUpStopButton.onclick = function () {
    musicPort.postMessage({
      action: 'stop'
    });
  };
}, function onConnectionRejected(reason) {
  ...
}, {
  origin: ['music.gaiamobile.org']
}).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 to communicate with Music 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 receiver to have the 'music.gaiamobile.org' origin, these apps won't be listed in the above UI and won't receive any connection request.

The user allows that connection and a system message is sent to the Music app. This app only wants to be controlled only by certified apps, so it checks for this kind of applications.

navigator.setMessageHandler('connect', function(connectionRequest) {
  if (connectionRequest.keyword !== 'musicremotecontrol') {
    return;
  }
 
  if (connectionRequest.callerInfo.accessLevel === 'certified') {
    connectionRequest.port.onmessage = onMusicRemoteControlHandler;
  } else {
    connectionRequest.reject();
  }
});

Other use case

TODO

Open Questions

  • Should we change 'connect' verbs for 'pub/sub' verbs? Like
 partial interface Application { 
   Promise publish(DOMString keyword, object rules);
 
   Promise subcriptionsRegistered(DOMString keyword);
   
   Promise getSubcriptionMessages(DOMString keyword);
 };
  • Do we want to expose app access level as a enum, so the developer can do something like
if (connectionRequest.callerInfo.accessLevel <= PRIVILEGED) { //whatever }
  • It would be great if apps could also filter by install origin, but certified apps has not a clear install origin.
  • We might want to have a bigger set of types to allow apps to connect (web, installed, signed, privileged, certified).