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
- To get the iframe content we need to use querySelector and then store into a variable – let visualForce = this.template.querySelector(“iframe”);
- Check if there is value for iframe that means VF has been loaded inside the iframe
- use contentWindow.postMessage to send the message to VisualForce Page.
Code Highlights : connectedCallback Method
- Connected callback is subscribing the message event so that it can listen to any message which is sent by VisualForce Page.
- 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.
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?
What is the error in the browser console?
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.