CORS Iframe messaging with DOM access and callback
Sometimes you can't go around using iframes, but you still want your own design and have the iframe do your commands but be invisible. I made a small little utility that uses the postmessage to send and receive json parsed DOM data.
Now i haven't set this all up to be 100% safe yet, it's origin calls are wildcarded which on production would be ill advised. Anyway it works and you could send flow through the iframe and pick off errors by checking at which page it is or what the dom is doing. It's not pretty but iframes aren't pretty..
Place this on the iframe side to enable some dom manipulation via postmessage packages
# DOM Manipulation via PostMessage
#
do ( dom = window ) ->
#
# Abstract API for doing small DOM manipulations
#
class DomManipulator
parseHTML: ( strHtml ) ->
elConverted = document.implementation.createHTMLDocument()
elConverted.body.innerHTML = strHTML
elConverted.body.children
stringifyHTML: ( elTarget ) ->
if typeof elTarget is 'object' then elTarget.outerHTML else false
getElement: ( strElement ) ->
elTarget = document.body.querySelector( strElement )
elTarget
getText: ( strElement ) ->
elTarget = @getElement strElement
elTarget.textContent
getProperty: ( strElement, strProperty ) ->
elTarget = @getElement strElement
if elTarget then elTarget.getAttribute( strProperty ) else false
setProperty: ( strElement, strProperty, strValue ) ->
elTarget = @getElement strElement
if elTarget then elTarget.setAttribute strProperty, strValue #elTarget[strProperty] = strValue
@getProperty strElement, strProperty
doMouseEvent: ( strElement, strEvent ) ->
elTarget = @getElement strElement
if elTarget
evtMouseAny = document.createEvent('MouseEvents')
evtMouseAny.initMouseEvent strEvent, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null
elTarget.dispatchEvent evtMouseAny
true
false
#
# Extends DOM Manipulator and transforms the manipulations to be compatible with PostMessage
#
class PostMessageDomManipulator extends DomManipulator
constructor: () ->
#
# Public PostMessage DomManipulator API
#
@objApi =
getElement: (( objOptions, objJsonData ) ->
strElement = objOptions['strElement']
if strElement then objJsonData.strReturn = @stringifyHTML @getElement(strElement)
).bind @
getText: (( objOptions, objJsonData ) ->
strElement = objOptions['strElement']
if strElement then objJsonData.strReturn = @getText strElement
).bind @
getProperty: (( objOptions, objJsonData ) ->
strElement = objOptions['strElement']
strProperty = objOptions['strProperty']
if strElement and strProperty then objJsonData.strReturn = @getProperty strElement, strProperty
).bind @
setProperty: (( objOptions, objJsonData ) ->
strElement = objOptions['strElement']
strProperty = objOptions['strProperty']
strValue = objOptions['strValue']
if strElement and strProperty and strValue then objJsonData.strReturn = @setProperty strElement, strProperty, strValue
).bind @
doMouseEvent: (( objOptions, objJsonData ) ->
strElement = objOptions['strElement']
strEvent = objOptions['strEvent']
if strElement and strEvent then objJsonData.strReturn = @doMouseEvent strElement, strEvent
).bind @
# Give a debug message that can be read
console.debug 'Initializing postMessage protocol'
# Bind the postMessage to onReceiveData. That will transform the call for the Public API
dom.addEventListener 'message', @onReceiveData.bind( @ )
@doSendData.bind(@) boolInitialized: true, strId: 'intitialize'
#
# For receiving messages via PostMessage to DOM Manipulation
#
onReceiveData: ( e ) ->
strOrigin = e.origin
objJsonData = JSON.parse e.data
strLocation = objJsonData['strLocation']
strAction = objJsonData['strAction']
objParameters = objJsonData['objParameters']
# Parent always needs to say what he expects, else we might end up going out of sync
boolExpected = strLocation is window.location.href or strLocation is '*'
boolOrigin = true
boolAction = strAction
boolParameters = objParameters
boolPassing = boolExpected and boolOrigin and boolAction and boolParameters
if not boolPassing then return @doSendData strErrorMessage: 'Origin not allowed or you were expecting someone else.'
# Try to execute the request by testing if the action exists and can be called
try
objJsonData['strReturn'] = false
if @objApi[strAction] then @objApi[strAction]( objParameters, objJsonData )
@doSendData objJsonData
catch
@doSendData { strErrorMessage: 'Could not pass action onto manipulator. Something malformed..' }
#
# For sending messages via PostMessage back to the parent
#
doSendData: ( objData = {} ) ->
objData.objLocation = window.location
objData.strId = objData.strId or 'keep-alive'
strOrgin = '*' # Needs to be set for safety, only send messages to allowed domain
strJsonData = JSON.stringify objData
if parent
parent.postMessage strJsonData, strOrgin
#
# on DOMContentLoaded initialize the PostMessageDomManipulator
#
onLoad = () ->
dom.pmdm = new PostMessageDomManipulator()
dom.addEventListener 'DOMContentLoaded', onLoad
On the parent place something like
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script>
window.onload = function() {
var onClickDemo = function() {
console.info('Trying to post a message to the target');
var objData = {
strId: '',
strLocation: '*',
strAction: 'setProperty',
objParameters: {
strElement: '#amount',
strProperty: 'value',
strValue: '100'
}
}
document.getElementsByTagName('iframe')[0].contentWindow.postMessage(JSON.stringify(objData), '*');
},
onClickTriggerDemo = function() {
console.info('Trying to trigger the mouse event');
var objData = {
strId: 'ontoNextPage',
strLocation: '*',
strAction: 'doMouseEvent',
objParameters: {
strElement: '#continueButton',
strEvent: 'click'
}
}
document.getElementsByTagName('iframe')[0].contentWindow.postMessage(JSON.stringify(objData), '*');
},
onGetErrorMessage = function() {
console.info('Trying to get the error');
var objData = {
strLocation: '*',
strAction: 'getElement',
objParameters: {
strElement: '#textWithdrawalDeclined'
}
}
document.getElementsByTagName('iframe')[0].contentWindow.postMessage(JSON.stringify(objData), '*');
}
onMessageReceived = function( e ) {
console.debug( 'data;', JSON.parse(e.data));
}
elIframe = document.createElement('iframe');
elIframe.width = '100%';
elIframe.height = '800';
document.body.querySelectorAll( '.try' )[0].addEventListener('click', onClickDemo);
document.body.querySelectorAll( '.do' )[0].addEventListener('click', onClickTriggerDemo);
window.addEventListener( 'message', onMessageReceived )
document.body.appendChild(elIframe);
}
</script>
<input type="submit" value="Try a postMessage" class="try">
<input type="submit" value="Try a trigger" class="do">
</body>
</html>
Basically you throw packages over the line JSON stringified:
var objData = {
strId: '',
strLocation: '*',
strAction: 'setProperty',
objParameters: {
strElement: '#amount',
strProperty: 'value',
strValue: '100'
}
}
document.getElementsByTagName('iframe')[0].contentWindow.postMessage(JSON.stringify(objData), '*');
PostMessage DOM i have made very small, you have the following:
- getElement
- getText
- getProperty
- setProperty
- doMouseEvent
You can check them and add to them in the DomManipulator
Cheers!