How To Implement Bidirectional Communication LWC With VF

on

|

views

and

comments

Hello #Trailblazers,

In this blog post we are going to talk about how to communicate between LWC and Visualforce page.

Background

While running the VisualForce page in Salesforce, you need to know about two things.

Different DOMs. A Visualforce page hosted in Lightning Experience is loaded in an iframe. In other words, it’s loaded in its own window object which is different from the main window object where the Lightning Components are loaded.

Different Origins. Visualforce pages and Lightning Components are served from different domains. For example, if you are using a developer edition:

  • Lightning Web Components are loaded from a domain that looks like this: yourdomain-dev-ed.lightning.force.com and your-domain.my.lightning.force.com in case of Production
  • VisualForce pages are loaded from a domain that looks like this: Expected Format for DE, Sandbox & Production ORgs = domain–c.visualforce.com and domain.my.salesforce.com for Prod Org.

In our case, that means that a VisualForce page can’t use the parent window reference to access content or execute code in the Lightning Component wrapper. Similarly, the Lightning component can’t use the iframe’s contentWindow reference to access content or execute code in the VisualForce page it wraps.

Window.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.

Normally, scripts on different pages are allowed to access each other if and only if the pages they originate from share the same protocol, port number, and host (also known as the “same-origin policy“). window.postMessage() provides a controlled mechanism to securely circumvent this restriction (if used properly).

Complete Code

You can get the complete code From GitHub Repo.

Lightning Component to VisualForce Page

<!--
    @description       : 
    @author            : Amit Singh
    @group             : 
    @last modified on  : 03-28-2021
    @last modified by  : Amit Singh
    Modifications Log 
    Ver   Date         Author       Modification
    1.0   03-28-2021   Amit Singh   Initial Version
-->
<template>
    <lightning-card  variant="Narrow"  title="lWC-VF Biredirectonal Communication" icon-name="custom:custom43">
        <div class="slds-p-horizontal_small">
            <lightning-textarea name="message" label="Message From VF" value={receivedMessage}></lightning-textarea>
        </div>
        <div class="slds-p-horizontal_small">
            <lightning-input type="text" variant="standard" onchange={handleChange} name="messgaeToSend" 
                label="messgaeToSend">
            </lightning-input>
        </div>
        <div class="slds-p-around_small">
            <lightning-button variant="brand" label="Send to VisualForce" onclick={sendMessgaeToVisualForce}></lightning-button>
        </div>
        <div class="slds-p-around_small">
            Visual Force Content
        </div>
        <div class="slds-p-horizontal_small">
            <iframe height="500" width="100%" frameborder="0" src="/apex/SimpleVF"></iframe>
        </div>
        <p slot="footer">
            <lightning-badge label="LWC"></lightning-badge>
            <lightning-badge label="VF"></lightning-badge>
            <lightning-badge label="Bidirectonal Communication"></lightning-badge>
        </p>
    </lightning-card>
</template>
/**
 * @description       : 
 * @author            : Amit Singh
 * @group             : 
 * @last modified on  : 03-28-2021
 * @last modified by  : Amit Singh
 * Modifications Log 
 * Ver   Date         Author       Modification
 * 1.0   03-28-2021   Amit Singh   Initial Version
**/
import { LightningElement,wire} from 'lwc';

import getVisualforceOrigin from '@salesforce/apex/VFC_DomainsController.getVisualforceOrigin';

export default class PocLwcWithIframe extends LightningElement {

    @wire(getVisualforceOrigin) visualForceOrigin;
    receivedMessage;
    messageToSend;

    connectedCallback() {
        // Binding EventListener here when Data received from VF
        // param1 - the event which will listen the message
        // param2 - the method which will be called when the message will be recieved
        // param3 - event capture
        window.addEventListener( "message", this.handleResponse.bind(this), false );
    }

    handleResponse(message){
        // check the origin match for both source and target
        if (message.origin === this.visualForceOrigin.data) {
            this.receivedMessage = JSON.stringify(message.data);
        }
    }

    handleChange(event) {
        this.messageToSend = event.detail.value;
    }

    sendMessgaeToVisualForce() {
        let message = {
            message : this.messageToSend,
            source  : 'LWC'
        };
        
        let visualForce = this.template.querySelector("iframe");
        if( visualForce ){
            visualForce.contentWindow.postMessage(message, this.visualForceOrigin.data);
        }
        
    }
}

Code Highlights: sendMessgaeToVisualForce Method

  1. To get the iframe content we need to use querySelector and then store into a variable – let visualForce = this.template.querySelector(“iframe”);
  2. Check if there is value for iframe that means VF has been loaded inside the iframe
  3. use contentWindow.postMessage to send the message to VisualForce Page.

Code Highlights : connectedCallback Method

  1. Connected callback is subscribing the message event so that it can listen to any message which is sent by VisualForce Page.
  2. handleResponse method is used to handle the response received from VF page

Code Highlights : @wire(getVisualforceOrigin) Method

  • Calling the Apex Class Method which returns the Origin for the VisualForce Image.

Receiving the Message in the VisualForce Page

var lexOrigin = '{!lexOrigin}';
            window.addEventListener("message", function (event) {
                
                if (event.origin === lexOrigin) {
                    var receivedfromLWC = event.data;
                    
                    let outputLWC = document.getElementById('text-area-sfdc-lwc');
                    outputLWC.innerHTML = JSON.stringify(receivedfromLWC);
                }
            }, false );

Code Highlights:

  • lexOrigin is the origin (protocol + port + host) Lightning components are loaded from. This is where we expect the messages to come from.
  • event.origin is the actual origin of the window that sent the message at the time postMessage() was called. You should always verify that the actual origin and the expected origin match, and reject the message if they don’t.
  • event.data is the message sent from the other window

VFC_DomainsController Apex Class

/**
 * @description       : 
 * @author            : Amit Singh
 * @group             : 
 * @last modified on  : 03-28-2021
 * @last modified by  : Amit Singh
 * Modifications Log 
 * Ver   Date         Author       Modification
 * 1.0   03-28-2021   Amit Singh   Initial Version
**/
public with sharing class VFC_DomainsController {

    public string lexOrigin { 
        get { 
            return URL.getOrgDomainUrl().toExternalForm().split('.my.')[0]+'.lightning.force.com';
            // Exprcted outcome for Developer Orgs & Sandbox https://your-domain-dev-ed.lightning.force.com/
            // Expected outcome for Prod Org - https://your-domain.my.lightning.force.com/
        } 
        set;
    }

    @AuraEnabled(cacheable = true)
    public static string getVisualforceOrigin() {
        string visualOrigin = '';
        string baseUrl = URL.getOrgDomainUrl().toExternalForm(); 
        // Expected Format = https://domain.my.salesforce.com
        // Expected Format for DE, Sandbox & Production ORgs = domain--c.visualforce.com
        visualOrigin = baseUrl.split('.my.')[0] + '--c.' + 'visualforce.com';
        return visualOrigin;
    }
}

VisualForce Page to Lightning Component

In this second scenario, we still have the same arrangement: a VisualForce page wrapped in a Lightning component. This time we need communication to happen in the opposite direction: The VisualForce page sends messages to the Lightning component.

Sending the Message in a Visualforce Page

function sendToLigntningWC(){
                
                let lightningOrigin = '{!lexOrigin}';
                let textAread = document.getElementById('text-area-sfdc');
                let messgaeToLWC = {
                    message : textAread.value,
                    source  : 'VisualForce Page'
                }
                window.parent.postMessage( messgaeToLWC, lightningOrigin );
            }

Code highlights:

  • lexOrigin is the origin (protocol + port + host) Lightning Components are loaded from
  • The message field is used to capture the simple string message we will send to the Lightning Component
  • The second argument of postMessage() is the origin of the parent window. Again, the event will not be sent if the content of the parent window at the time postMessage() is called wasn’t loaded from lexOrigin.

Receiving the Message in a Lightning Component

connectedCallback() {
        // Binding EventListener here when Data received from VF
        // param1 - the event which will listen the message
        // param2 - the method which will be called when the message will be recieved
        // param3 - event capture
        window.addEventListener( "message", this.handleResponse.bind(this), false );
    }

    handleResponse(message){
        // check the origin match for both source and target
        if (message.origin === this.visualForceOrigin.data) {
            this.receivedMessage = JSON.stringify(message.data);
        }
    }

Code Highlights:

  • event.origin is the actual origin of the window that sent the message at the time postMessage() was called. You should always verify that the actual origin and the expected origin match and reject the message if they don’t.
  • event.data is the message sent from the other window

Complete Code for VF

<!--
    @description       : 
    @author            : Amit Singh
    @group             : 
    @last modified on  : 03-28-2021
    @last modified by  : Amit Singh
    Modifications Log 
    Ver   Date         Author       Modification
    1.0   03-28-2021   Amit Singh   Initial Version
-->
<apex:page lightningStylesheets="true" controller="VFC_DomainsController">
    <apex:slds></apex:slds>
    <apex:form>
        <script>
            var lexOrigin = '{!lexOrigin}';
            window.addEventListener("message", function (event) {
                
                if (event.origin === lexOrigin) {
                    var receivedfromLWC = event.data;
                    
                    let outputLWC = document.getElementById('text-area-sfdc-lwc');
                    outputLWC.innerHTML = JSON.stringify(receivedfromLWC);
                }
            }, false );

            function sendToLigntningWC(){
                
                let lightningOrigin = '{!lexOrigin}';
                let textAread = document.getElementById('text-area-sfdc');
                let messgaeToLWC = {
                    message : textAread.value,
                    source  : 'VisualForce Page'
                }
                window.parent.postMessage( messgaeToLWC, lightningOrigin );
            }
        </script>
        <div class="slds-grid slds-gutters slds-p-around_medium">
            <div class="slds-col">
                <p>Message Send To LWC</p>
                <input type="text" id="text-area-sfdc" />
            </div>
            <div class="slds-col">
                <p>Message Recieved FROM LWC</p>
                <div id="text-area-sfdc-lwc" class="slds-box" />
            </div>
        </div>
        <button class="slds-button slds-button_outline-brand" onclick="sendToLigntningWC();return false;">Send to LWC</button>
    </apex:form>
</apex:page>

Bidirectional Communication

Important Security Consideration

window.postMessage() is a standard web API that is not aware of the Lightning and Locker service namespace isolation level. As a result, there is no way to send a message to a specific namespace or to check which namespace a message is coming from. Therefore, messages sent using postMessage() should be limited to non sensitive data and should not include sensitive data such as user data or cryptographic secrets.

Additional Resources

Amit Singh
Amit Singhhttps://www.pantherschools.com/
Amit Singh aka @sfdcpanther/pantherschools, a Salesforce Technical Architect, Consultant with over 8+ years of experience in Salesforce technology. 21x Certified. Blogger, Speaker, and Instructor. DevSecOps Champion
Share this

Leave a review

Excellent

SUBSCRIBE-US

Book a 1:1 Call

Must-read

How to start your AI Journey?

Table of Contents Introduction Are you tired of the same old world? Do you dream of painting landscapes with your code, composing symphonies with data, or...

The Secret Weapon: Prompt Engineering: 🪄

Table of Contents Introduction Crafting the perfect prompt is like whispering secret instructions to your AI muse. But there's no one-size-fits-all approach! Exploring different types of...

How to Singup for your own Production org?

Let's see how we can have our salesforce enterprise Salesforce Org for a 30-day Trial and use it as a Production environment. With the...

Recent articles

More like this

4 COMMENTS

  1. Hi Amit,

    I am not able to communicate between LWC and VFpage inside the Iframe. I used postMessage API with ‘*’ as a second parameter (Domain).

    But nothing is happening on vfpage EventListener.
    Can you please help me?

  2. Hi Amit
    If this isnt the secure way for communication between lwc and VF then whats the alternative for secure connection? Is it LMS (lightning messaging service)?

    Thanks

    • Lightning Message Service is the preferable way. However LMS won not work if the VF page is inside Iframe of any LWC or AURA in that case you have use this approach.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

5/5

Stuck in coding limbo?

Our courses unlock your tech potential