K. Alan Bates K. Alan Bates - 4 months ago 21
AngularJS Question

window.postMessage between iframe and its parent with Angular: does anyone have a working example?

Does anyone have a working example of how to send and receive window.postMessage() calls in angular? I found the ng-post-message module on github and ngmodules, but I look at that code and it doesn't make a whole lot of sense to me and the documentation is lacking a working example.

Edit: to add my failed attempt



The view

<div simulation-host element="thing in things"></div>
</div>
<div id="debugConsole">
<h1>MESSAGE CONSOLE</h1>
<p id="debugText"></p>
</div>


The model

$scope.things =
[
{
"location" : "Foobar",
"resource" : $sce.trustAsResourceUrl("http://external.domain:14168/Foo/Bar"),
"title" : "Launch"
}
];


My attempt at a directive

var simulationFrameHost = angular.module('proxy.directives', []);
simulationFrameHost.config(function ($sceDelegateProvider){
//URL Regex provided by Microsoft Patterns & Practices.
$sceDelegateProvider.resourceUrlWhitelist(['^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)([a-zA-Z0-9\-\.\?\,\'\/\\\+&amp;%\$#_]*)?$','self']);
});

simulationFrameHost.directive('simulationHost', ['$window',function($window) {
return {
retrict: 'ACE',
transclude: 'element',
replace: true,
scope: true,
template: [
'<ul>',
'<li>',
'<span>',
'<a href="#">',
'{{thing.location}}',
'</a>',
'</span>',
'<messenger>',
'<iframe ng-src="{{thing.resource}}" />',
'</messenger>',
'</li>',
'</ul>'
].join(''),
compile: function (tElement, tAttrs, transclude) {
var interval;

var show = function(msg)
{
var debugText = document.getElementById("debugText");
if(debugText){
debugText.innerHTML += msg + "<br/>";
}
};
var rpt = document.createAttribute('ng-repeat');
rpt.value = tAttrs.element;
console.log(tAttrs.element);
tElement[0].children[0].attributes.setNamedItem(rpt);

$(tElement[0].children[0].children[0].children[0]).on("click", function(event){

console.log(event);
var iframe = tElement[0].children[0].children[1].children[0].contentWindow;
show("Initiating connection with: " + event.currentTarget.host);
var message = {
action: 'syn',
param: 'connection'
};

interval = setInterval(function(){

//*****************************************
iframe.postMessage(JSON.stringify(message), 'http://'+ event.currentTarget.host);
//*****************************************

}, 500);
return false;
});



}
}
}]);


Working legacy code that I am trying to adapt to Angular



Note that this code uses a popup rather than an iframe; ran into the complication that IE's postMessage between windows is broken so have to fall back to iframe.

Markup

<body>
<div id="debugConsole">
<h1>MESSAGE CONSOLE</h1>
<p id="debugText"></p>
</div>
<h1>This is a test</h1>

<ul>
<li>
<a href= "http://external.domain:14168/Foo/Bar" target="_blank" ><p>Foobar</p></a>
</li>
</ul>
<script src="js/jquery-1.10.2.min.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-animate/angular-animate.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="js/SCORM_API_wrapper.js"></script>
<script src="js/json2.js"></script>
<script src="js/plugins.js"></script>
<script src="js/index.js"></script>
</body>


index.js

$('a').on("click",function(event){
console.log(event);
var pop = window.open(event.currentTarget.href, 'poop');
show("Initiating connection with: " + event.currentTarget.host);
var message = {
action: 'syn',
param: 'connection',
};

interval = setInterval(function(){
pop.postMessage(JSON.stringify(message), 'http://'+ event.currentTarget.host);
}, 500);
return false;

});

$(window).on("message", function(e) {
clearInterval(interval);

var eventData = JSON.parse(e.originalEvent.data);
show("Message received from: " + e.originalEvent.origin);
if(eventData.action) {
switch (eventData.action) {
case 'syn-ack':
ack(e.originalEvent, eventData.param, eventData.value);
break;
case "set":
show("Set request received: " + e.originalEvent.data);
set(eventData.param, eventData.value);
break;
case "get":
show("Get request received: " + e.originalEvent.data);
var value = get(eventData.param);
var response = {
action: "response",
type: "get",
param: eventData.param,
value: value
};
e.originalEvent.source.postMessage(JSON.stringify(response), channel);
break;
}
}
});


In my directive's compile, I'm trying to wire up a click event to the generated anchor tag. I'm trying to get the click to post a message to the iframe, but iframe.postMessage is doing nothing. It just goes off into the nether, and I've been working on this since 10 this morning. My eyes are starting to glaze over : p

Edit: Adding an extension requirement (now that I have functioning code) for a general messaging directive between separate containers, regardless of the container type:



1)iframe to parent


2)window to window
(<=yes, I already know this doesn't work in IE)

I had legacy code working that performed window to window messaging by having the window that spawned
the second post a "syn" message to it immediately after creating it. The second window then received the message as a "syn" and stored the sender as a messageHandle so that it could maintain a channel to post return messages then returned a "syn-ack." The originator followed up with an "ack" and the secondary window received the ack and proceeded with its work. (If the ack did not return before the timeout, I logged that the connection had failed and then the secondary window polled on an interval to attempt to restore the connection)

Answer

I couldn't get this working with an Angular directive. I pulled a wasted all nighter trying to get this done "The Right Way" and wish I had ejected that idea sooner because my requirements didn't really warrant it. This thing doesn't have to scale because it is purpose build software for providing a messaging proxy between X-Domain systems.

'use strict'
var app = angular.module('domain.system.controllers', ['services.proxy.mine']);
app.controller('ProxyCtrl', ['$scope', '$routeParams', '$window', '$sce', 'MyService',
                function    ( $scope,   $routeParams,   $window,   $sce,   MyService)
               {
                    $($window).on("message", function(e){
                       var message = JSON.parse(e.originalEvent.data);
                       if(message.recipient){
                            switch(message.recipient){
                                case: "ProxyCtrl":
                                       //handle message;
                                       break;
                            }
                       }
                    }
              }
]);

I am 100% interested in a detailed explanation of how to convert this code into a functioning directive.

Comments