Pmrpc API documentation, feature list and usage examples

Introduction

Pmrpc is an inter-window and web workers communication JavaScript library for use in HTML5 browsers. The pmrpc library supports three kinds of communication: message-passing, remote procedure call (RPC) and publish-subscribe (pubsub).

The library provides a simple API for exposing and calling procedures (RPC) between browser windows, iFrames and web workers (both dedicated and shared web workers). In case of inter-window communication, the windows/iframes may also be on different domains and can communicate without being subject to the same-origin policy. Below are two simple examples of using pmrpc.

Inter-window communication example (parent window invokes procedure in nested iframe):

First, a procedure is registered for remote calls in the iframe that contains the procedure:

<html>
  <head>
    <script type="text/javascript" src="http://izuzak.github.com/pmrpc/pmrpc.js"></script>
  </head>
  <body>
    <script type="text/javascript">
      // expose a procedure
      pmrpc.register( {
        publicProcedureName : "HelloPMRPC",
        procedure : function(printParam) { alert(printParam); } }
      );
    </script>
  </body>
</html>

Second, the procedure is called from the parent window by specifying the iframe object which contains the remote procedure, name of the procedure and parameters:

<html>
  <head>
    <script type="text/javascript" src="http://izuzak.github.com/pmrpc/pmrpc.js"></script>
  </head>
  <body>
    <iframe id="ifr" name="ifr" src="iframe.html" width="0" height="0" frameborder=0></iframe>
    <script type="text/javascript">
      // call the exposed procedure
      pmrpc.call( {
        destination : window.frames["ifr"],
        publicProcedureName : "HelloPMRPC",
        params : ["Hello World!"] } );
    </script>
  </body>
</html>

Simple Web worker communication example (window -> worker communication):

importScripts('http://izuzak.github.com/pmrpc/pmrpc.js');

// expose a procedure
pmrpc.register( {
  publicProcedureName : "HelloPMRPC",
  procedure : function(printParam) { return printParam; } } );
<html>
  <head>
    <script src='http://izuzak.github.com/pmrpc/pmrpc.js' type='text/javascript'></script>
  </head>
  <body>
    <script type="text/javascript">
      var worker = new Worker("worker.js");

      setTimeout(function() {
        pmrpc.call( {
          destination : worker,
          onSuccess : function(retVal) { alert(retVal.returnValue); },
          publicProcedureName : "HelloPMRPC",
          params : ["Hello World!"] } ); }, 1000);
    </script>
  </body>
</html>

Pmrpc also provides several advanced features, like AJAX-style callbacks and access control lists.

Usage overview

This section provides a high-level overview of pmrpc features, the full API is described below.

As in most communication systems, a pmrpc system consists of a client and a server. The purpose of the server is to implement and publically expose one or more procedures. These procedures can then be remotely called by clients from different domains.

In pmrpc, every browser window, any iframe within a window or any web worker (dedicated or shared) that loads the pmrpc library may be both a client and a server. A client can call procedures from more than one server, more than one client can call procedures from a single server, and an entity can be a client and a server at the same time.

The slides below give an overview of the basic usage and advanced features of pmrpc, explained in more detail in the following sections.

Basic usage

The basic scenario of using pmrpc is as follows:

Advanced features

Pmrpc also supports four advanced and optional features:

Callbacks

Pmrpc calls are asynchronous, like XMLHttpRequest calls. This means that the pmrpc call method doesn't block the execution of the client, rather it just sends the call and returns. If the client wishes to be notified when the execution of the called procedure finishes or receive return values, a callback method should be supplied as a parameter to the pmrpc call. This callback method is called after the server finishes execution of that call.

Pmrpc supports two callback methods:

Retries and timeouts

Like with AJAX calls, sometimes the server will not be running at the exact time of the client request, or the server will have different configuration than the one expected by the client. For example, the client (e.g. window) will load faster than the server (e.g. worker) and will send the request before the server even registers any procedures.

For this reason, pmrpc let's clients specify that a call should be retried a certain number of times before giving up, if the previous request didn't complete successfuly for any reason. Also, pmrpc enables clients to specify the timeout period between two retries of the same call. For example, the client could specify that a call should be retried up to 5 times, waiting 2000 milliseconds between calls.

Asynchronous procedures

When a call is routed to the server, pmrpc calls the target procedure, waits for it to execute and returns the result to the client. This execution flow works for synchronous procedures registered on the server and doesn't work for asynchronous procedures. If the registered procedure is asynchronous - it returns immediately, continues processing in the background and invokes a callback function when execution finishes. Pmrpc supports asynchronous procedures by automatically passing onsuccess and onerror callback functions which asynchronous procedures should call with the return value.

Access control

The Access control advanced feature is available only for inter-window communication (i.e. communication not involving web workers) since communication involving web workers is always between contexts on the same domain (the worker script must be on the same domain as the context in which the worker was created).

Sometimes servers need to restrict the access to their methods only to clients from certain domains. Similarly, clients sometimes need to restrict the set of servers which may process a specific call to certain domains. In order to ensure that messages are dilivered to and from trusted domains, pmrpc supports a simple access control mechanism on both the client and the server side.

On the client side, the client can specify from which domains must the server be loaded or specify that the server may be loaded from any domain. On the server side, the server can specify which procedures can be called from which domains. The server access control mechanism is based on access control lists (ACLs). An access control list contains a whitelist and a blacklist. The whitelist defines which subjects (windows or iframes) are permitted to perform an action (call a procedure or process a call), while the blacklist defines which subjects are not permitted to perform an action. Both the whitelist and the blacklist are lists of URLs defined with regular expressions. In order to satisfy the ACL, the domain of a subject must be in the whitelist and must not be in the blacklist.

Multicast and publish-subscribe

Pmrpc also supports calling the same remote procedure on multiple servers as a single call. In order to make such a multicast call, the client lists all servers it wishes to call the procedure on. The execution of such call is the same as would be multiple calls to each server exposing the procedure.

Sometimes the client will not know the number or identity of servers implementing the procedure that needs to be called. In those cases, a publish-subscribe model is more appropriate. In order to make a publish call, the client doesn't specify any servers but only the procedure, and pmrpc will try to invoke the procedure on all servers exposing that procedure (broadcast).

Discovery

In cases when clients don't know which servers expose a procedure or which procedures are exposed at which server, a discovery mechanism is needed. Pmrpc implements a simple discovery mechanism that enables clients to fetch information on all registered procedures from all servers and filter them by procedure name, server or server origin.

API

Pmrpc is available as a single-file JavaScript library. The library exposes the pmrpc object with 4 public methods: register, call, unregister and discover. Furthermore, in order to enable web worker communication, the library wraps the default Worker and SharedWorker constructors. Instructions for loading the library and using the API are given below.

Loading the library

The pmrpc library must be loaded in every window, iframe and web worker that intends to use pmrpc. Furthermore, since pmrpc uses JSON as a data-interchange format, the JSON library must be loaded also. Most modern web browsers (Chrome, Firefox, ...) implement the JSON library natively, so loading it will usually not be necessary.

Note: the JSON library available on http://www.json.org/json2.js contains an alert statement which forces you to download the library, remove the alert and serve the it from your server.

Windows and iFrames

<script src='http://www.json.org/json2.js' type='text/javascript'></script>
<script src='http://izuzak.github.com/pmrpc/pmrpc.js' type='text/javascript'></script>

Web workers

importScripts('http://www.json.org/json2.js');
importScripts('http://izuzak.github.com/pmrpc/pmrpc.js');

Register method

The register method is used by servers to publically expose a procedure under a known name and sets the access control rules for that procedure:

pmrpc.register( {
  "publicProcedureName" : publicProcedureName,
  "procedure" : procedure,
  "acl" : accessControlList,
  "isAsynchronous" : isAsynchronous } );

Example 1: synchronous procedure named "HelloPMRPC" accessible from windows and iframes loaded from any page on "http://www.myFriendlyDomain1.com" or exactly from the "http://www.myFriendlyDomain2.com" page.

var publicProcedureName = "HelloPMRPC";
var procedure = function (alertText) { alert(alertText); return "Hello!"; };
var accessControlList = {
  whitelist : ["http://www\\.myFriendlyDomain1\\.com.*", "http://www\\.myFriendlyDomain2\\.com"],
  blacklist : []
};
var isAsynchronous = false;

pmrpc.register( {
  "publicProcedureName" : publicProcedureName,
  "procedure" : procedure,
  "acl" : accessControlList,
  "isAsynchronous" : isAsynchronous } );

Example 2: asynchronous method named "HelloPMRPC" accessible from windows and iframes loaded from any domain, except from "http://evil.com".

var publicProcedureName = "HelloPMRPC";
var procedure = function (alertText, successCallback, errorCallback) {
  alert(alertText);
  successCallback("Hello!"); };

// the errorCallback should be called with an object that has a message property, which will be extracted and returned as the error message (e.g. { message : "boink! error!"})

var accessControlList = {
  whitelist : [".*"],
  blacklist : ["http://evil\\.com.*"]
};
var isAsynchronous = true;

pmrpc.register( {
  "publicProcedureName" : publicProcedureName,
  "procedure" : procedure,
  "acl" : accessControlList,
  "isAsynchronous" : isAsynchronous } );

Unregister method

The unregister method cancles the registration of an exposed procedure:

pmrpc.unregister(publicProcedureName);

Example 3: unregister a procedure named "HelloPMRPC".

var publicProcedureName = "HelloPMRPC";
pmrpc.unregister(publicProcedureName);

Call method

The call method calls a procedure exposed on the server:

pmrpc.call(parameters);

Example 4: invoke procedure "HelloPMRPC" with single positional parameter "Hello world from pmrpc client!". The onSuccess and onError methods alert the response, and the call will be retried up to 15 times, waiting 1500 milliseconds between retries. Servers loaded from any domain may process the call.

var parameters = {
  destination : window.frames["targetIframeName"],
  publicProcedureName : "HelloPMRPC",
  params : ["Hello world from pmrpc client!"],
  onSuccess : function(returnObj) {alert(returnObj.returnValue);},
  onError : function(statusObj) {alert(statusObj.description);},
  retries : 15,
  timeout : 1500,
  destinationDomain :  "*"
};

pmrpc.call(parameters);

Example 5: invoke procedure "HelloPMRPC" with single named parameter "alertText" with value "Hello world from pmrpc client!". The response is disregarded (no onSuccess and onError methods), and the call will be retried up to 5 times, waiting 1000 milliseconds between retries (default values). Servers loaded only from http://good.com domain may process the call.

var parameters = {
  destination : window.frames["targetIframeName"],
  publicProcedureName : "HelloPMRPC",
  params : {alertText : "Hello world from pmrpc client!"},
  destinationDomain :  "http://good.com"
};

pmrpc.call(parameters);

Discover method

pmrpc.discover(parameters);

Example 5: discover procedures registered at any server loaded from any origin and filter out procedures that do not contain "goodOrigin" in their public name. After that, choose the first element in the returned result array and call the procedure.

pmrpc.discover({
  nameRegex : ".*goodName.*",
  callback : function(discoveredMethods) {
    pmrpc.call({
      destination : discoveredMethods[0].destination,
      publicProcedureName : discoveredMethods[0].publicProcedureName,
      params : ["Hello World!"],
      destinationDomain : "*",
    });
  }
});

Examples

See here.

Implementation

The implementation of the library is based on the cross-document messaging and web workers APIs and the JSON-RPC protocol.

The cross-document messaging API is a simple method for passing messages between browser window objects (windows and iframes) which may be loaded from different domains. When using the postMessage APIs, restrictions concerning the same-origin policy do not apply, and programmers can call postMessage on any window. Nevertheles, the API is secure in a sense that it provides mechanisms both for the sender and the receiver to ensure that messages are delivered from trusted domains. Web workers are background scripts running in parallel to their main page. The scripts communicate with the main page with an identical API as in cross-document messaging.

Pmrpc uses the postMessage API as an underlying communication mechanism, and extends it to a more RPC-like model using the JSON-RPC protocol. JSON-RPC is a stateless, light-weight remote procedure call (RPC) protocol. It uses JSON as data format, and is transport-independent. By combining the postMessage API as the transport mechanism with JSON-RPC as the communication protocol pmrpc provides a powerful and open communication mechanism for inter-window communication.

Pmrpc implements the following requirements as defined in the JSON-RPC specification: * normal remote procedure call * notification (procedure call without Response) * request, response and error JSON message format * call parameters (positional and named)

Pmrpc doesn't implement the Batch call feature of the JSON-RPC specification since there is a lot of discussion on how this feature will work and how it should be implemented.

Here is an example of request and response messages in pmrpc (and JSON-RPC):

(request)  --> {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}
(response) <-- {"jsonrpc": "2.0", "result": 19, "id": 1}

Important notes