Skip to content
This repository has been archived by the owner on Jun 12, 2024. It is now read-only.

Website API

koto edited this page Nov 18, 2014 · 18 revisions

End-To-End extension provides message encryption, decryption and signing services for third-party web applications through the Website API. By using the API web applications (e.g. webmail systems, instant messaging applications) can interact with End-To-End, giving them a secure, asynchronous way to encrypt/sign and verify arbitrary content. This page serves as a documentation for the API in its current version.

High-level description

End-to-End extension allows the user to encrypt/decrypt/compose and verify messages by clicking on the extension page action button. In order to communicate with the web application in the current browser tab, the extension tries to establish a permanent HTML5 Channel Messaging connection with the Javascript code of the web application. Then API requests and responses are sent between the extension and the web application through the established channel. Responses to the requests are sent asynchronously.

If the channel cannot be established, the extension fallbacks to using standard DOM methods to extract currently selected/active objects and inject information back into the document.

API connection timeline

  1. User clicks on the extension page action button
  2. If content script has already been injected, go to 3. Otherwise, content script is injected into the current browser tab document.
  3. If API connection is already established, go to 8. Otherwise, if DOM fallback is enabled, call the appropriate DOM method and exit. Content script detects API support in the web application. If support is not detected, enable DOM fallback and go to 8.
  4. For chosen origins (e.g. Gmail) appropriate stub script is injected, enabling API support.
  5. Content script sends a bootstrap message to the web application, passing a message port.
  6. Web application responds to a bootstrap message. If the response timeouts or indicates no API availability, enable DOM fallback and go to 8.
  7. Content script and web application attach event listeners to a message port message event.
  8. If DOM fallback is enabled, call an appropriate DOM method in a content script. Otherwise, send the API request through the message port to the web application and forward the response back.

Detecting API support

To minimise message noise, End-To-End extension only starts the API bootstrap procedure if the current browser tab document is enabled for the API support. As of now, the whitelist of origins with the API support is hardcoded in the extension. Please contact us to discuss adding Website API support for your web application.

Starting the connection

In order to extract or inject messages into the current browser tab, the extension injects a content script into the current tab document, when the user first clicks on the page action button (for a given document). The content script is responsible for detecting if the Website API endpoint is provided by the web application in the current document and tries to establish a permanent connection with the API endpoint by sending a bootstrap message.

Bootstrap message

Bootstrap message is sent using HTML5 Cross-document messaging from the content script in the current window. The event that is triggered by sending the message will have the following properties:

event.data = {
  api: 'e2e-init'
};
event.origin = window.location.origin; // The message is sent from a content script from the same origin
event.ports = [messagePort]; // The port through which the messages should be sent.

In order to establish the connection, the web application SHOULD send a following message through the provided messagePort:

{
  api: 'e2e-init',
  version: 1
  available: true|false
}

Value true of the available field signifies that the web application is ready to receive further API requests over the messagePort. If the value of available field is false, the extension will end the connection and fallback to the DOM API.

Example

Example code that correctly responds to a bootstrap message can be found below:

/**
 * Handles the bootstrap message coming from the extension.
 * @param {MessageEvent} e bootstrap message sent from the extension.
 */
var bootstrapMessageHandler = function(e) {
  if (e.origin == window.location.origin &&
      e.data.api == 'e2e-init' &&
      e.ports &&
      e.ports.length == 1) {
    e.ports[0].addEventListener('message', messageHandler, false);
    e.ports[0].start();
    e.ports[0].postMessage({
      api: 'e2e-init',
      version: 1,
      available: isApiAvailable()
    });
    window.removeEventListener('message', bootstrapMessageHandler);
  }
};


/**
 * Initializes channel messaging with E2E.
 */
var initChannel = function() {
  window.addEventListener('message', bootstrapMessageHandler, false);
};

initChannel();

/**
 * Indicates if we should report API availability to E2E.
 * @return {boolean} True if the API is available.
 */
var isApiAvailable = function() {
  return true;
};

Request and response format

Further messages (requests and responses) are exchanged by peers over the port. Requests have the following format:

{
  id: string, // Unique ID
  call: string, // API function name to call
  args: Object|null // Call parameters
}

If the request is successful, a peer SHOULD respond by sending the following result object:

{
  requestId: string // ID of the request
  result: *  // Result
}

To notify the peer of an error condition, the following result object SHOULD be sent instead:

{
  requestId: string // ID of the request
  error: string  // Error message
  result: null
}

Example

Exemplary function to handle requests coming from the extension is provided below:

/**
 * Handles incoming E2E requests.
 * @param  {MessageEvent} event Event to handle.
 */
var messageHandler = function(event) {
  var request = event.data;
  var port = event.target;
  var call = event.data.call;
  var args = event.data.args;
  var requestId = event.data.id;
  // ...
  if (error) {
    port.postMessage({
      requestId: requestId,
      result: null,
      error: 'An error occurred.'
    }); 
  } else {
    port.postMessage({
      requestId: requestId,
      result: theResult
    }); 
  }
};

This function should be bound to the message event on the MessagePort object during bootstrapping.

API requests

API requests can originate from either of the peer types (the extension or the web application). Different API calls are available for each of the peer types.

Requests sent from the extension

getCurrentMessage

Called to retrieve the current message element to create a reading glass IFRAME for and/or fetch the message contents to decrypt.

  • Request parameters: none

  • Response format:

      {
        id: string // ID of the element with the active message.
        body: string // Text contents of the active message, with HTML formatting removed.
      }
    

hasActiveDraft

Called to check if the web application has a currently open draft message to fetch it contents to encrypt or decrypt.

  • Request parameters: none

  • Response format:

      boolean // does the web application have the active draft message
    

###getActiveDraft Called to fetch the currently opened message draft, to start an encrypt/decrypt/sign UI flow.

  • Request parameters: none

  • Response format:

      {
        to: Array<{address: string, name: string}> // Intended recipients of the currently active message draft. E-mails in the address fields.
        cc: Array<{address: string, name: string}> // Recipients to CC
        body: string // Text contents of the active draft, with HTML formatting removed.
      }
    

setActiveDraft

Called to set the new (usually encrypted or signed) message body and recipients in the active message draft.

  • Request parameters:

      {
        to: Array<{address: string, name: string}> // Intended recipients of the currently active message draft. E-mails in the address fields.
        cc: Array<{address: string, name: string}> // Recipients to CC
        body: string // Text contents of the draft, with HTML formatting removed.
      }
    
  • Response format:

      boolean // Was the draft updated successfully
    

Requests sent from the web application

TBD.

Timeouts

In order to provide better user experience, a timeout for responding to events is enforced. Currently the web application needs to respond to the bootstrap request in 1000 ms, and 100ms for subsequent API requests.

Clone this wiki locally