Skip to content

Instantly share code, notes, and snippets.

@justin-lyon
Last active March 1, 2021 19:57
Show Gist options
  • Save justin-lyon/2e06a9f8e8c9bc5a8f3d5ba47db7bec5 to your computer and use it in GitHub Desktop.
Save justin-lyon/2e06a9f8e8c9bc5a8f3d5ba47db7bec5 to your computer and use it in GitHub Desktop.
Some things I learned about window.postMessage on Salesforce

Salesforce and window.postMessage

I have a visualforce page in lightning console, and I need it to send messages other components in the page. This is troublesome because Salesforce always embeds VF Pages in an iframe, even in classic.

In my research, and from folks in a few different communities I heard about window.postMessage as a possible solution.

I found that the Locker API Viewer did not block it, so I sent about figuring it out.

The following regails my misconceptions and pitfalls along the way.

  1. .postMessage does not bubble like other events. It is a method invoked on your target window for receiving the message.
  2. My first instinct was to console.log(window.parent) from within the iframe before calling .postMessage from it.
    • This was a mistake as directly referencing parent in an iframe causes a cross site scripting error.
    • We can invoke parent.postMessage(...), but we cannot directly reference the parent.
  3. The listening window must have an event listener on it (not document)
    • window.addEventListener('message', handlePostMessage)
  4. Post message takes two arguments
    • data, targetOrigin
    • data was simple enough, it was my object of information I wanted to send.
    • targetOrigin was a little more complex. I had to derive the target window's URL from my current location in the iframe window.
      • First I used $CurrentPage.URL to get the Visualforce page's URL
      • Then I stripped that down to the subdomain which is the org's My Domain plus '--c' for a visual force page.
      • Then I concatenated that with the lightning domain to end up with https://<mydomain>.lightning.force.com. This is the origin of my iframes parent window.
  5. Now my postMessage from the iframe is securely targeting exactly the parent I want with a strict match on target origin.
  6. With that, the listener evaluates the message's source origin to make sure it came from my vf page, and I added an additional property in event.data to further verify.

Some Code Examples:

<!-- VF Page inside a Lightning Console Tab's iframe -->
<apex:page>
  
  <script>
    // derive window.parent.location.href
    const visualforceUrl = "{!$CurrentPage.URL}"
    const parser = document.createElement('a')
    const subdomain = parser.host.substring(0, parser.host.indexOf('.'))
    const mydomain = subdomain.replace('--c', '')
    const parentOrigin = 'https://' + mydomain + '.lightning.force.com')
    
    const postMessage = () => {
      parent.postMessage({
        channel: 'someChannelId',
        message: 'hi'
      },
      parentOrigin)
    }
    
    postMessage()
  </script>
</apex:page>
// EventBusController.js in the Lightning Console Utility Bar
({
  init: function(cmp, event, helper) {
    // function to handle the postMessage
    const handlePostMessage = event => {
      // verify the message came from the expected window
      if (event.origin.endsWith('.visual.force.com') && event.data.channel === 'someChannelId') {
        // handle post message
        console.log('post message data', event.data)
      }
    }
    
    // Add the event listener
    window.addEventListener('message', handlePostMessage)
    
    // Before the page unloads, clean up and remove the event listener.
    document.addEventListener('beforeunload', () => {
      window.removeEventListener('message', handlePostMessage)
    })
  }
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment