Raj Nandan Sharma

linkMessaging between Browser Windows using ruto.js

At my work I have to build solutions around communication between browser and iframe or a popup window .ie <iframe> or window.open(). I have to use postMessage js api, and feel it is not very developer friendly. Consider the below situation

  1. The parent window send a message to the child window
  2. The child window receives this message,
  3. The child calls a backend server with the message
  4. The child sends the response back to parent
  5. The parent wants to receive this backend response, does some calculation.
  6. The parent then wants to send a second message to the child.
  7. And the same thing repeats

While this may seem trivial when you are doing simple you are doing simple http calls from frontend but across windows something like this is much more complicated. We have also to keep in mind that security is our outmost concern.

Let us try to code this thing. Parent is parent.com, child is at child.com.

I am assuming parent has child as an iframe

link1. Send Message from parent to child

1link//parent.com

2linkiframe.contentWindow.postMessage(message, 'child.com');

3link

link2. Receive message from parent in child

1link//child.com

2linkwindow.addEventListener('message', (event) => {

3link const expectedOrigin = 'parent.com';

4link if (event.origin !== expectedOrigin) {

5link return

6link }

7link const message = event.data;

8link doPorcessingOfMessage(message)

9link})

link3. Child does some processing

1link//child.com

2linkasync function doPorcessingOfMessage(message){

3link const resp = await api(message);

4link return resp;

5link}

link4. Child sends response back

1link//child.com

2linkwindow.addEventListener('message', (event) => {

3link const expectedOrigin = 'parent.com';

4link if (event.origin !== expectedOrigin) {

5link return

6link }

7link const message = event.data;

8link const resp = await doPorcessingOfMessage(message);

9link window.parent.postMessage(resp, 'parent.com');

10link})

11link

link5. Parent receives the message

1link//parent.com

2linkwindow.addEventListener('message', (event) => {

3link const expectedOrigin = 'child.com';

4link if (event.origin !== expectedOrigin) {

5link return

6link }

7link const message = event.data;

8link

9link})

link6. Parent sends 2nd message

1link//parent.com

2linkwindow.addEventListener('message', (event) => {

3link const expectedOrigin = 'child.com';

4link if (event.origin !== expectedOrigin) {

5link return

6link }

7link const message = event.data;

8link const newMessage = getNewMessage(message);

9link iframe.contentWindow.postMessage(message, 'child.com');

10link})

As we can see the process is completely event based. And writing code to do something like this gets very messy.

What if there was nicer way to do these. Let us say

1link//parent.com

2linkconst resp1 = await sendMessageToChild(message1);

3linkconst message2 = await someProcessing(resp1);

4linkconst resp2 = await sendMessageToChild(message2);

1link//child.com

2linkconst message1 = await receiveMessageFromParent();

3linkconst resp1 = await someProcessing(message1);

4linkawait sendMessageToParent(resp1)

5link

In the above scenario, the parent is the orchestrator and child is just responding to actions triggered by parent.

In a different scenario a child can be the orchestrator and the parent would be just responding to actions triggered by the child.

That means both the parent and child can send and receiver. If parent is send then child has to be receive, and when parent is receiver the child has to be sender.

linkHandle Retry

We have to make sure that there are retries in communication. And also both parent and child should be ready to receive.

linkSecurity

We have to have origin checks in both child and domain. Ignore all messages if not received from where we want to receive

linkSolution

I think the best way to do this would be to build it like a client/server kind of a thing. Let us take an example of web client and node js express server.

1linkfetch('/message').then(response => response.json())

2link .then(data => {

3link document.getElementById('response').textContent = JSON.stringify(data, null, 2);

4link })

5link .catch(error => {

6link document.getElementById('response').textContent = 'Error: ' + error;

7link });

1linkapp.get('/message', (req, res) => {

2link res.send('Hello, world!');

3link});

I started to write the solution but soon realized it is not easy. So I have to take a more conservative approach

linkIntroducing Ruto

Ruto is a lightweight(4KB), fast and easy-to-use JS library that streamlines the communication between parent and child window(iframe/popup).

It uses client-server design pattern to communicate between parent and child window. Any window can become the client or the server depending on who wants to send. It abstracts out the complications of postMessage API and provides a simple API to send and receive messages.

You can visit the github page here

linkRuto Examples

linkParent to iframe

Parent will send a message iframe, Parent expects a response from the iframe with 10 secs

1link<iframe id="iframe" src="https://example.com/child.html"></iframe>

2link<script src="https://cdn.jsdelivr.net/gh/rajnandan1/ruto/dist/ruto.min.js"></script>

3link<script>

4linkconst iframe = document.getElementById('iframe');

5linkconst message = 'Hello from parent';

6linkconst options = {

7link timeout: 10000

8link}

9link

10linkruto.send("https://example.com/parent-to-iframe/topic1", iframe, message, options).then((response) => {

11link console.log(response); //Hello from child

12link}).catch((error) => {

13link console.log(error);

14link});

15link</script>

1link<script src="https://cdn.jsdelivr.net/gh/rajnandan1/ruto/dist/ruto.min.js"></script>

2link<script>

3linkruto.receive("/parent-to-iframe/topic1", window.parent, (response, message) => {

4link console.log(message); //Hello from parent

5link return response.send('Hello from child'); //ra

6link});

7link</script>

linkIframe to Parent

Iframe will send a message to parent, and expects a response from parent within 5 secs

1link<iframe id="iframe" src="https://example.com/child.html"></iframe>

2link<script src="https://cdn.jsdelivr.net/gh/rajnandan1/ruto/dist/ruto.min.js"></script>

3link<script>

4linkconst iframe = document.getElementById('iframe');

5linkruto.receive("/iframe-to-parent/topic1", iframe, (response, message) => {

6link console.log(message); //Hello from iframe

7link return response.send('Hello from parent');

8link});

9link</script>

1link<script src="https://cdn.jsdelivr.net/gh/rajnandan1/ruto/dist/ruto.min.js"></script>

2link<script>

3linkconst message = 'Hello from iframe';

4linkconst options = {

5link timeout: 5000

6link}

7linkruto.send("https://example.com/iframe-to-parent/topic1", window.parent, message, options).then((response) => {

8link console.log(response); //Hello from parent

9link}).catch((error) => {

10link console.log(error);

11link});

12link</script>

linkParent to Popup

Parent will send a message to popup, and expects a response from popup within 5 secs

1link<script src="https://cdn.jsdelivr.net/gh/rajnandan1/ruto/dist/ruto.min.js"></script>

2link<script>

3linkconst popup = window.open('https://example.com/popup.html', 'popup', 'width=600,height=400');

4linkconst message = 'Hello from parent';

5linkconst options = {

6link timeout: 5000

7link}

8linkruto.send("https://example.com/parent-to-popup/topic1", popup, message, options).then((response) => {

9link console.log(response); //Hello from popup

10link}).catch((error) => {

11link console.log(error);

12link});

13link</script>

1link<script src="https://cdn.jsdelivr.net/gh/rajnandan1/ruto/dist/ruto.min.js"></script>

2link<script>

3linkruto.receive("/parent-to-popup/topic1", window.opener, (response, message) => {

4link console.log(message); //Hello from parent

5link return response.send('Hello from popup');

6link});

7link</script>

Popup will send a message to parent, and expects a response from parent within 5 secs

1link<script src="https://cdn.jsdelivr.net/gh/rajnandan1/ruto/dist/ruto.min.js"></script>

2link<script>

3linkconst popup = window.open('https://example.com/popup.html', 'popup', 'width=600,height=400');

4linkruto.receive("/popup-to-parent/topic1", popup, (response, message) => {

5link console.log(message); //Hello from popup

6link return response.send('Hello from parent');

7link});

8link</script>

1link<script src="https://cdn.jsdelivr.net/gh/rajnandan1/ruto/dist/ruto.min.js"></script>

2link<script>

3linkconst message = 'Hello from popup';

4linkconst options = {

5link timeout: 5000

6link}

7linkruto.send("https://example.com/popup-to-parent/topic1", window.opener, message, options).then((response) => {

8link console.log(response); //Hello from parent

9link}).catch((error) => {

10link console.log(error);

11link});

12link</script>

Messaging between Browser Windows using ruto.js1. Send Message from parent to child2. Receive message from parent in child3. Child does some processing4. Child sends response back5. Parent receives the message6. Parent sends 2nd messageHandle RetrySecuritySolutionIntroducing RutoRuto ExamplesParent to iframeIframe to ParentParent to PopupPopup to Parent

Home