iFrame: sending data back and forth - dan7davis/Lambda GitHub Wiki
Imagine an intervention (e.g. a study planner or a search engine) that is not added directly as custom HTML element to one or more edX pages in a course, but instead added as an <iframe>
element. edX supports iframes
and in general this is a good idea to make writing and testing code much easier (no constant copy and pasting of code into a custom edX HTML element - tedious and not easily testable).
iframes
look great on first sight, however they have the issue of lack of access to edx data.
What if we build an application that requires data to be exchanged between edx and the iframe
(e.g. click data, user profile data, discussion forum data)?
The first thing to try is a standard ajax request from within the iframe
. If for instance our iframe
wants access to some edx content (e.g. the search engine should also display search results from the edx forum), the browser that makes the HTTP request needs to send the edx cookie (which contains the authentication token) along with the request from the iframe
; if the cookie is not send along, the edx content cannot be accessed.
As the iframe
is not hosted on the edx.org server, this requires Cross-Origin Resource Sharing or CORS. Withouth CORS, the browser still executes the request from the iframe
, but for security reasons the browser does not send the edx cookie along (without the cookie no edx content can be accessed). Visually:
The server (i.e. the edx.org server) needs to be configured to allow CORS, by default it is disabled. Whether or not a server supports CORS is visible in the HTTP header sent in response to an HTTP request to the server: if the header Access-Control-Allow-Credentials
is set to true
, CORS is allowed; if the header is not sent, it defaults to false
and CORS is disabled. Right now, edx.org does not set this header and thus easy use of an iframe
is not possible (once more: if the iframe
needs to access data from edx.org).
window.postMessage
is part of the HTML5 API that can be used to send messages between windows/frames (in our case the browser window showing edx.org
content and the embedded iframe
with our intervention) across domains. Most importantly, this happens on the client-side, it does not matter what kind of security restrictions the server has. The idea is as follows: we embed a piece of JavaScript through a custom HTML element on our edx course. This piece of JavaScript can then make the call to edx.org
, receive the data and send it via window.postMessage
to the iframe
. This code is only triggered when the iframe
sends a message through window.postMessage
that requests it. The advantage is lack of server involvement, the disadvantage is that two pieces of code have to be maintained: the script embedded in our custom HTML element and the piece of code within the iframe
.
Concretely, lets assume a search engine wants to show edx forum search results in the iframe
. Embedded is the following custom HTML snippet (via <script>..</script>
) on the edx page:
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event)
{
console.log("[edx page] Message received, query is: "+event.data);
console.log("[edx page] Querying the edx forum ... ");
//the query was sent by the iframe, lets construct a call to edx forum search
//note that this will only work with this url from within an edge.edx.org course,
//adapt the url if a live edx course is used
var query = event.data;
var url = "https://edge.edx.org/courses/course-v1:DelftX+DD.003x+2017/discussion/forum/search?ajax=1&format=json&text="+query;
//make an ajax request, if that is successful use postMessage to send the result (json) to the iframe
$.ajax({url: url,
success: function(result){
console.log("[edx page] Sending data to pienapple: "+result);
var objString = JSON.stringify(result);
console.log("[edx page] object JSONIFIED: "+objString);
event.source.postMessage(objString,event.origin);
}});
}
Inside the iframe
we have a similar setup, with a listener on the window, waiting for incoming messages:
window.addEventListener('message', function (event) {
console.log("[pienapple-front]" + event.data);
console.log("[pienapple-front]" + event.origin);
//are we receiving events from the right source?
if (event.origin != "https://edge.edx.org" && event.orgin != "https://edx.org")
return;
var json = JSON.parse(event.data);
var entries = json.discussion_data.length;
console.log("[pienapple-front] Number of discussion entries: " + entries);
for (let i = 0; i < entries; i++) {
var t = json.discussion_data[i].title;
var b = json.discussion_data[i].body;
//the absolute URL of the discussion entry needs to be reconstructed (TODO: test this bit!)
var u = event.origin + "/courses/" + json.discussion_data[i].course_id + "/discussion/forum/" + json.discussion_data[i].commentable_id + "/threads/" + json.discussion_data[i].id;
console.log("[pienapple-front] " + t);
console.log("[pienapple-front] " + b);
console.log("[pienapple-front] " + u);
}
}, false);
This works in general fairly well, though some browsers (e.g. Firefox) may not work as expected immediately.
- Is your
iframe
included in edx with ahttps
prefix (http
does not work for security reasons)? - If messages don't make it to the parent/child window, check whether
postMessage
has the correct domain (including sub-domain) set; there is a difference betweenedx.org
andedge.edx.org