Lightning Aura ComponentとCTIアダプタ(Visualforce)間での相互通信
Lightning Aura ComponentとCTIアダプタ(Visualforce)でデータのやり取りを行う必要がありました。しかし、直接通信する手段がありません。そのため、JavaScriptのWeb Messaging API「postMessage」を使うことにより間接的にメッセージ通信することにより実現しました。
条件
- CTIアダプタにはVisualforceが設定されている
- (諸事情により)Salesforceの機能(Apexなど)が使えない
実現方法
Lightning Aura Componentは自身のiframe内のVisualforceとしか通信ができません。今回の場合、通信先VisualforceはLightning Aura Component外に存在します。よって、仲介用のVisualforceを用意し、そのVisualforceを介してCTIアダプタ内のVisualforceと通信を行います。
実装
以下に実装内容を記載します。
Lightning Aura Componentと自信のiframe内のVisualforceで通信
@hrk623さんがすでに詳細な説明をQiitaの記事に投稿しています。Lightning Aura Componentの実装はそちらを参考にしてください。Lightning Aura Component内に仲介Visualforceを埋め込みます。本記事内では埋め込むVisualforceを「proxy.vfp」とします。
CTIアダプタ(Visualforce)と仲介用Visualforce間の通信
Salesforce Lightningのため、CTIアダプタとメッセージ交換するためのSalesforce API 1が使用できません。JavaScriptのWeb Messaging APIを使用します。
Salesforceは複数のiframeで構成されているようです。単純にpostMessageするだけではメッセージが届きません。まず親Windowオブジェクトを取得し、そこから子iframe全てに対してpostMessageを実行します。SalesforceのUIの実装が今後どうなるかわかりませんので、iframeがネストされていることも念の為考慮します。
以下はCTIアダプタ内のVisualforceとなります。
<apex:page>
<button type="button" onclick="sendMsgToProxyVF();">Send Message To Proxy VF</button>
<script>
console.log = console.log.bind(console, '[CTIAdaptor]'); // ログの見易さのため部分適用
let proxyVFPort;
function sendMsgToProxyVF() {
proxyVFPort.postMessage("hello, I'm CTI Adaptor.");
}
function connectListener(e) {
if (e.data === 'CONNECT') {
console.log('Receive connect request message.');
// 送信元でMessageChannelを生成しても可能、そちらの方が実装は楽です。
// 今回はブロードキャストのように子iframe全てにメッセージが送信されますので、接続先で生成するようにしました。
const msgChannel = new MessageChannel();
proxyVFPort = msgChannel.port1;
msgChannel.port1.onmessage = function(e) {
console.log('Receive message');
};
e.source.postMessage('CONNECTED', e.origin, [ msgChannel.port2 ]);
window.removeEventListener('message', connectListener);
}
}
window.addEventListener('message', connectListener);
</script>
</apex:page>
以下は仲介用Visualforceとなります。Lightning Aura Componentのiframe内に埋め込まれます。
<apex:page>
<button type="button" onclick="connectToCTIAdaptor();">Connect to CTI Adaptor</button>
<button type="button" onclick="sendToCTIAdaptor();">Send Message to CTI Adaptor</button>
<script>
console.log = console.log.bind(console, '[Proxy]'); // ログの見易さのため部分適用
let ctiAdaptorPort;
function sendToCTIAdaptor() {
ctiAdaptorPort.postMessage("hello, I'm Proxy.");
}
function connectToCTIAdaptor() {
const frameArr = window.top.frames;
for (let i = 0; i < frameArr.length; i++) {
console.log('connect to CTI Adaptor.');
// サンプルのため、第3引数に送信先オリジンを指定しません。実際の実装ではきちんと設定してください。
postMessageRecursive(frameArr[i], 'CONNECT', '*');
}
}
// iframe内にネストされたiframeがある場合を考慮して、再帰的にメッセージ送信を行います
function postMessageRecursive(iframe, msg, targetOrigin) {
iframe.postMessage(msg, targetOrigin);
if (iframe.frames.length > 0) {
// window.framesはiterableオブジェクトではないためfor...of構文は使用できません
for (let i = 0; i < iframe.frames.length; i++) {
postMessageRecursive(iframe.frames[i], msg, targetOrigin);
}
}
}
function connectedListener(e) {
if (e.data === 'CONNECTED') {
console.log('Success to connect.');
ctiAdaptorPort = e.ports[0];
ctiAdaptorPort.onmessage = function(e) {
console.log('Receive message.');
}
window.removeEventListener('message', connectedListener);
}
}
// 今回はMessageChannelは接続先で生成します。
// 接続先でMessageChannelを生成してそれを利用するため、メッセージを待ち受けます。
window.addEventListener('message', connectedListener);
</script>
</apex:page>
Lightning Componentとの通信も実装した仲介用Visualforceでは、以下のようにしてメッセージをバケツリレーするイメージです。
class ProxySession {
constructor(lcPort, ctiAdaptorPort) {
lcPort.onmessage = function(e) {
ctiAdaptorPort.postMessage(e.data);
};
ctiAdaptorPort.onmessage = function(e) {
lcPort.postMessage(e.data);
};
}
}
参考
-
Salesforce Classicの場合、CTIアダプタとメッセージ交換するAPIが用意されています。 ↩