[SalesForce] How to fire an event from a canvas app, to a lightning component

I have a canvas app thats running inside a lightning component. There are tons of articles about how to fire events in every other permutation of lightning, visualforce and canvas, but I can't find a good example of canvas->lightning.

I've tried creating an <aura:event>, creating an <aura:handler> for that event in my lightning component, and doing a Sfdc.canvas.client.publish from the canvas app, which works for visualforce events. But this isn't working for lightning.

Any suggestions? And better yet, a working example?

Best Answer

The wiring to do this is mostly there, but lightning apps don't have access to Sfdc.canvas at present, it seems. If they did have access to Sfdc.canvas then this would work.

You can send a message from the canvas app, and it will reach the containing window, but it seems there's no way to listen for it at present. You can also subscribe to events in the canvas app, and if you construct and post a correctly formatted message from the top window, the canvas app will receive it. Funnily enough, onCanvasSubscribed will fire when the canvas app subscribes, but I cannot for the life of my figure out why it would, when it seems impossible to publish a message from the lightning component.

So, in short, the answer to this is "it's not possible". The long answer is "it's a very bad idea, because you need to partially reimplement the canvas event messaging protocol, which exposes you to the risk that the unpublished, proprietary protocol will change, thus breaking your app without warning."

A note on access to Sfdc.canvas

The Salesforce Spring ’16 Release Notes state that Sfdc JavaScript Global Is Removed for lightning components, however:

The sole exception is the official Force.com Canvas API, which is available using the Sfdc.canvas object. Canvas APIs as documented in the Canvas Developer Guide remain available and are fully supported.

This seems to suggest that Sfdc.canvas ought to be available to lightning components, and the fact that it appears to be unavailable could be considered a defect.

Warning: rest of this post covers non-standard, probably unsupported techniques

So, for posterity and those unmotivated by pragmatic programming practices, this is how you can send and receive messages from a canvas app within lightning.

Sending messages from canvas to lightning

Send a message as normal from the canvas app:

var srClient = signedRequest.client;
Sfdc.canvas.client.publish(
    srClient,
    {name : "mynamespace.statusChanged", payload : {status : 'Completed'}});

You ought to be able to listen for this message using:

Sfdc.canvas.parent.subscribe('mynamespace.statusChanged', callbackFn);

And indeed, if you run this in the javascript console, you can listen for these messages. If only lightning components had access to Sfdc.canvas! Since we don't, the only way to receive this message in the lightning component is by listening for it directly on the window:

window.addEventListener('message', function (event) {
    var data = JSON.parse(event.data);
    if (data.targetModule === 'Canvas' && 
        data.body && 
        data.body.event && 
        data.body.event.name === 'mynamespace.statusChanged')
    console.log('payload from canvas app', data.body.event.payload);
}, false);

Sending messages from the lightning component to canvas app

We can subscribe to messages as normal in the canvas app:

var srClient = signedRequest.client;
Sfdc.canvas.client.subscribe(
    srClient,
    {
        name : 'johnny.begood',
        onData : function (event) {
            console.log("Subscribed to custom event ", event);
        }
    });

// at this point, onCanvasSubscribed in the lightning component seems to fire

It's very odd that onCanvasSubscribed fires at this point. That seems pointless if there's no way official way to publish. Perhaps there is a way, but if there is, I haven't been able to find it.

Ideally, you'd be able to publish an event from the lightning app this way:

Sfdc.canvas.parent.publish('mynamespace.statusChanged', payload);

As with subscribing, this is not possible, but there is a workaround. From the lightning component, post a correctly formatted message to the outer wrapper iframe:

// canvas apps contain both an inner and outer iframe. The canvas app is
// in the inner iframe, but we post our message to the outer iframe
var outerIframe = document.querySelector('.forceCanvasApp iframe');

// we need the correct target URL for the outer iframe. hard-coding this
// is a Bad Idea. How to do it otherwise left as an exercise for the reader
// Hint: listen for the subscribe message via 
// `window.addEventListener('message', listener)`
var wrapperUrl = 'https://my-domain.my.salesforce.com';

// to correctly format this message, you'll need to listen on the window 
// for the canvas app subscribe message
// and extract the instance id
var instanceIdFromClientSubscribeMessage = '_:test01:6:0:canvasapp';

var payload = { whatever: 'you want' };
var wrappedPayload = {
    parentVersion: '39.0',
    clientVersion: '34.0',
    body: {
        type: 'event',
        config: {
            client: {
                instanceId: instanceIdFromClientSubscribeMessage
            }
        },
        event: {
            name: 'johnny.begood',
            method: 'onData',
            payload: payload
        }
    }
}
outerIframe.contentWindow
           .postMessage(JSON.stringify(wrappedPayload), wrapperUrl);
Related Topic