[SalesForce] How to dynamically get VF page and Lightning page host name to use with Window.postMessage between VF and Lightning Component communication

I've lightning component where I'm going to embed a visualforce page, and then there will be communication between these two windows via postMessage API. the problem is how will I get the VF page along with abslute url (including hostname) like https://mycustomdomain--c.visualforce.com/apex/managedPackageNS__VfPage

I tried several ways but there is no full proof way which provide a robust solution which should work in different cases. first it will be part of managed package so we cannot hardcode VF page URL and second we need the URL in any case because first of all we need it to pass into iframe src and second PostMessage targetorigin to ensure secure and valid communication. On visulforce side, the host name change based on namespace, without namespace, domain, without domain and sometime I see some cases where visualforce.com url is became, visual.force.com.

I'm trying to do what is mentioned in below post, the only change is URL will be dynamically constructed at both sides (Lightning and VF).
https://developer.salesforce.com/blogs/developer-relations/2017/01/lightning-visualforce-communication.html

I'm open for other alternative suggestions as well through which I can validate the origin of the message to prevent any miscommunication.

Best Answer

I've solved this in the following way, which seems to cover most bases. I've assumed that my domain is enabled for customers, because otherwise lightning components are not usable.

The main issue I think you are having is this critical update, which removes the pod from visualforce (and other) hostnames, and is responsible for the change from visual.force.com to visualforce.com.

To handle this, you have two options:

  1. Instruct all of your customers to enable the critical update (which will be done 19/10/2019 anyway.)

  2. Generate both the old and new format hostnames, send messages to both, and check messages coming in are from one or the other. To be very clear - I've not reviewed this approach with Salesforce's security review team - you might want to validate it with them, but in my mind it's sane because both domains will contain the same pages under package control.

Posting a message to a hostname which is not present in the iframe presents a message in the console in chrome, but no errors.

    public ProxyController() {
        String hostname = Url.getSalesforceBaseUrl().getHost();
        String myDomain = hostname.split('\\.')[0];
        if(myDomain.contains('--')) {
            //uses lastIndex to exclude namespace but include sandbox names
            // e.g. https://mydomain--sandbox--namespace.visualforce.com
            // and https://mydomain--namespace.visualforce.com
            myDomain = myDomain.substring(0, hostname.lastIndexOf('--'));
        }
        lcBaseURL = 'https://' + myDomain + '.lightning.force.com';
        Map<String, String> headers = ApexPages.currentPage().getHeaders();
        headers.put('X-Frame-Options', 'ALLOW-FROM ' + lcBaseURL);
        headers.put('Content-Security-Policy', 'frame-ancestors ' + lcBaseURL);
    }

    @AuraEnabled
    public static List<String> getVFBaseURL() {
        String hostname = Url.getSalesforceBaseUrl().getHost();
        // will support prod and sandboxes
        // e.g. https://mydomain--sandbox.lightning.force.com
            // and https://mydomain.lightning.force.com
        String myDomain = hostname.split('\\.')[0];
        String namespace = ProxyController.class.getName().split('\\.')[0];
        String pod = [SELECT InstanceName FROM Organization].InstanceName.toLowerCase();
        return new List<String>{
                'https://' + myDomain + '--' + namespace + '.visualforce.com',
                'https://' + myDomain + '--' + namespace + '.' + pod + '.visual.force.com'
        };
    }

One warning - we could not get iframing to work reliably in Safari or Salesforce Mobile on ios, so did not use this mechanism in our managed package in the end. I've posted a question about it, but not made much progress in understanding the problem nor resolving it. This approach worked successfully for us in Chrome / Edge / Firefox and Salesforce Mobile on Android.

Related Topic