Skip to content

benzap/penpal.js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Penpal

./media/logo.png

Penpal is a Request-Response Messaging System that works between frames, iframes and external windows.

It leverages the power of flyer.js

Features and Operation

  • Define a server prototype, and create server instances from it.
  • Responses from server instances are returned in the form of a Promise Object
  • Create clients which communicate to the server by a unique keyword defining the server instance
  • Server instances can be easily extended into any application through the use of any object reference

A Basic Example

//Define our echo server
var EchoServer = Penpal.newServer("EchoServer", {
  echo: function(value) {
    return value;
  }
});

//Create a server instance with a unique keyword
EchoServer.start("main");

//Create a client to communicate to our server instance
var client = EchoServer.newClient("main");

//Use our client to echo to the server
var response = client.echo("Hello World!");

//retrieve the response from our returned promise
response.done(function(responseString) {
  console.log("Client Received the response: ", responseString); //Hello World!
});

Configuration

penpal.js uses the parent project flyer.js to magically send messages between frames, iframes, and external windows. You can grab the version I used from here

I also used jquery $.Deferred (jquery 1.11.0) for the promise functionality, which was obtained from the CDN. As long as your version of Jquery has $.Deferred, it should work.

Here is what would be required at the end of your page body:

<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
<script src="flyer.min.js"></script>
<script src="penpal.js"></script>

Download

Version 0.0.1

Usage Notes

Extending our objects to serve data

Say we have listing of Food Items being displayed on the page. This listing of food items is controlled by the Object FoodManager

var FoodManager = function() {
  this.listing = [];
}

FoodManager.prototype.addFoodItem = function(name) {
  this.listing.push(name);
  return this;
}

FoodManager.prototype.removeFoodByName = function(name) {
  this.listing = this.listing.filter(function(n) { n != name });
}

FoodManager.prototype.getFoodListing = function(name) {
  return this.listing;
}

/* other functions left out */

We instantiate this FoodManager and assign it to some sort of view, which shows whatever is currently listed there

var foodManager = new FoodManager();
var foodView = new FoodView(foodManager);

Updating our food view might be as simple as adding new food items to the manager (and refreshing the view)

foodManager
  .addItem("banana")
  .addItem("apple")
  .addItem("orange")

But what if we want to access the data for the foodManager in a different frame, iframe, or external window? Normally we could try combing through the window.frames, or try going down through the window.opener, but we can do better.

Let’s extend the foodManager to be accessed from any frame, iframe, or external window using Penpal.

First off, we need to define our server prototype using Penpal.newServer.

//This would be placed in a separate file, and the javascript src
//would be shared among webpages.
var FoodServer = Penpal.newServer("FoodServer", {
  getFoodListing: function() {
    return this.getFoodListing();
  }
});

Next, the server prototype needs to be instantiated, and the foodManager instance that we wish to link to will be included as the serverObject as an extension.

//This would be called on the page including the foodManager instance
FoodServer.start("main-foodmanager", foodManager);

Now, if we want to service this information to anywhere else in our application, we would call it by instantiating a client to grab that data.

//some other frame, iframe, or external window
var foodClient = FoodServer.newClient("main-foodmanager");
foodClient.getFoodListing().then(function(listing) {
  console.log("The food listing", listing);
})

Kind of magical, don’t you think?

Examples of extending the server

If you plan on using the server standalone, you can pass in an object when you first start a new server instance to configure the server accordingly.

For example, we have a prefixed echo server that by default prefixes the responses with “Hello”

var PrefixedEchoServer = function("EchoServer", {
  echo: function(value) {
    var prefix = this.prefix || "Hello";
    return prefix + " " + value + "!"
  }
});

We can extend this server by providing a new prefix value

PrefixedEchoServer.start("pirate", {prefix: "Yarr"});
PrefixedEchoServer.start("german", {prefix: "Guten Tag"});

Calling these with clients will result in different outputs

var pirateClient = PrefixedEchoServer.newClient("pirate")
  .echo("Ben")
  .then(function(response) {
    console.log(response); //Yarr Ben!
  });
  
var pirateClient = PrefixedEchoServer.newClient("german")
  .echo("Ben")
  .then(function(response) {
    console.log(response); //Guten Tag Ben!
  });

Handling errors

One thing that becomes more difficult when working with clients and servers is handling cases where the server stops working, doesn’t exist, or receives invalid input arguments.

Fortunately, all of this is handled through the promise object, and makes it easy to generate errors, and provide easy failover when things don’t go your way

PickyFoodServer = Penpal.newServer("PickyServer", {
  giveFood: function(name) {
    if (name == "apple") {
      console.error("Eww, I don't like apples");
      return Penpal.ResponseCode.INVALID;
    }
    else {
      return "success";
    }
  }
});

PickyFoodServer.start("main");

var client = PickyFoodServer.newClient("main");
client.giveFood("apple")
  .then(function() {
    console.log("this isn't called")
  })
  .fail(function() {
    console.error("this will be called");
  });

Penpal also includes timeout functionality, which by default is set to 5 seconds (5000 milliseconds). If you wish to increase or decrease this timeout value, it can be supplied through the client’s optional arguments.

//1ms is too short, so a timeout occurs
var client = PickyFoodServer.newClient("main", {timeout: 1}) 
client.giveFood("banana")
  .then(function() {
    console.log("this isn't called")
  })
  .fail(function(errCode) {
    if (errCode == Penpal.ResponseCode.INVALID) {
      console.error("Invalid input... did you give the server apples?");
    }
    else if (errCode == Penpal.ResponseCode.TIMEOUT) {
      console.error("Service Request Timeout");
    }
    else {
      console.error("Unknown error!");
    }
  });

Javascript API

work in progress

About

Request-Response Messaging System between frames, iframes, and external windows (flyer.js add-on)

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published