Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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)

like image 202
K. Alan Bates Avatar asked Jul 28 '14 02:07

K. Alan Bates


People also ask

How can an iframe communicate with its parent?

All you have to do is first dispatch an event from the iframe to the parent that notifies the parent that the iframe is loaded (essentially a "ready message"). The parent will be listening for messages and if it receives the "ready message" event, it can then reply to the iframe with whatever message you want to send.

What does window parent postMessage do?

postMessage() The window. postMessage() method safely enables cross-origin communication between Window objects; e.g., between a page and a pop-up that it spawned, or between a page and an iframe embedded within it.

How do I pass an iframe event to parent?

you can call parent. functionname() from your iframe. what if parent window is in different domain, then cannot raise events on parent window from child iframes.


2 Answers

Just came across this post, this may help you.

(function() {
    'use strict';

    angular
    .module('postmessage')
    .factory('iFrameMessagingService', iFrameMessagingService);

iFrameMessagingService.$inject = ['$window', '$location', '$rootScope', '$log'];

function iFrameMessagingService($window, $location, $rootScope, $log) {
    var service = {
        sendMessage : sendMessage
    };

    activate();
    return service;

    function activate(){
        activateIncomingMessageHandler();
    }

    function activateIncomingMessageHandler(){
        $window.addEventListener('message', function(event){
            if (typeof(event.data) !== 'undefined'){

               // handle message
            }
        });

    }

    function sendMessage(message){
        // Dispatch the event.
        $log.debug(message);
        if($window.parent !== $window){
            $window.parent.postMessage(message, '*');
        }
    }

}
})();
like image 74
Matt Avatar answered Oct 26 '22 20:10

Matt


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.

like image 33
K. Alan Bates Avatar answered Oct 26 '22 21:10

K. Alan Bates