Cross-Origin Communication Between Windows
As anyone developing for the web, whether backend or frontend, will know, Cross-Origin Resource Sharing (CORS) is the source of many headaches; but, to its credit, it’s responsible for letting us all browse the web a little safer.
The problem we came across was how to enable communication between a popup and its parent window when the two windows were on different domains. The aim was to close the popup after it has landed on a certain URL after a chain of redirects.
By default, CORS will prevent the parent window from reading any information about the popup’s location. Hence the following would not work:
// on example.org
var popup = window.open("http://example2.org");
console.log(popup.location.href);
// Uncaught DOMException: Blocked a frame with origin "http://example.org" from accessing a cross-origin frame.
(Side note: TIL someone bought example2.org and helpfully set up a page that says in H1 “It works!”; thank you stranger!)
By messing around in Chrome DevTools and digging around the documentation at Mozilla Developer Network, which I think is still the best source for web/Javascript documentation, we managed to find a method that might fit our use case: Window.postMessage(). According to the documentation, “Window.postMessage()
provides a controlled mechanism to circumvent [CORS restrictions] in a way which is secure when properly used”.
The postMessage()
mechanism consists of two parts: a postMessage()
on the sender’s end, and an event listener for "message"
on the receiver’s end. This would mean both sender and receiver would have to be aware and prepared for a message. In addition, there are a few other checks to enhance security.
Fortunately, we could control the response from the final destination. Using that, we came up with the solution to return a HTML page with a script tag to execute postMessage()
as the response:
// on example.org
var popup = window.open("http://example2.org");
// Listen for messages
window.addEventListener("message", function(event) {
// Ignore messages from unexpected origins
if(event.origin !== "http://example2.org") {
return;
}
if(event.data === "success") {
popup.close();
} else {
// Oh no!
}
});
// on example2.org
var parent = window.opener;
// Queues a message to be sent to the parent window if the parent's location is "http://example.org"
parent.postMessage("success", "http://example.org");