I am trying to create a LWC version of Doug Ayers library for doing promise-based service component for calling REST API from Lightning Aura Components.
Basically, the LWC communicates using Penpal via an iframe
with a Visualforce
page which uses jsforce
to interact with the Salesforce REST API's.
There are 3 elements to this:
- LWC: Caller (caller.js)
- LWC: Parent (api.js)
- VF page: Child (LC_APIPage.page)
The caller.js
code:
restRequest() {
const lwcApi = this.template.querySelector('c-api');
lwcApi.restRequest({
'url': '/services/data/v45.0/tooling/query/?q=SELECT+Id,+Name+FROM+ApexClass+ORDER+BY+Name',
'method': 'get',
'body': null
}).then(function (results) {
console.log(JSON.stringify(results));
}).catch(function (err) {
console.error('LWC_API: error when calling restRequest', err);
});
}
The caller.html
code:
<template>
<c-api></c-api>
<lightning-button label='rest request' onclick={restRequest}></lightning-button>
</template>
Reviewing the api.js
component, the connection.promise
does not resolve.
The LWC api.js
code is below:
/* eslint-disable @lwc/lwc/no-async-operation */
import { LightningElement, wire, api } from 'lwc';
import { loadScript } from 'lightning/platformResourceLoader';
import PP from '@salesforce/resourceUrl/penpal'
import getVisualforceDomainURL from '@salesforce/apex/LC_VisualforceDomainController.getVisualforceDomainURL';
export default class Api extends LightningElement {
_penpal = {};
penpalInitialized = false;
iframeSrc = '';
@wire(getVisualforceDomainURL) wiredDomainUrl({ error, data }) {
if (data) {
this.iframeSrc = data + '/apex/LC_APIPage';
console.log(this.iframeSrc);
this.initializePenpal();
} else if (error) {
console.error('LWC_API: error getting Visualforce Domain URL', error);
this.iframeSrc = undefined;
}
}
connectedCallback() {
Promise.all([
loadScript(this, PP)
]).then(() => { });
}
initializePenpal() {
let initialized = this.penpalInitialized;
var self = this;
// Since the iframe source is calculated asynchronously,
// we listen to the component's render events and each time
// check if the iframe is ready, and if so, then we initialize
// penpal to connect this component to the iframe.
// Since we only want to do this once, we also set the initialized flag.
if (!initialized) {
const container = this.template.querySelector('div');
const iframeElmt = document.createElement('iframe');
iframeElmt.src = this.iframeSrc;
container.appendChild(iframeElmt);
if (iframeElmt != null) {
this.penpalInitialized = true;
// eslint-disable-next-line no-undef
const connection = Penpal.connectToChild({
// The iframe to which a connection should be made
iframe: iframeElmt
});
this._penpal.connection = connection;
connection.promise.then(function (child) {
// Cache a reference to the child so that we can
// use it in the restRequest/fetchRequest methods,
// as well as be able to destroy it when this component unrenders.
console.log(child);
self._penpal.child = child;
}).catch(function (err) {
console.error('LWC_API: Error establishing connection to iframe', err);
this.penpalInitialized = false;
});
} // else, iframe source is empty, keep waiting
}
}
// Makes a Salesforce REST API request and returns a promise that resolves to the response.
@api restRequest(request) {
var self = this;
var defaultRequest = {
'method': 'get'
};
var defaultHeaders = {
'Content-Type': 'application/json'
};
request = Object.assign({}, defaultRequest, request);
request.headers = Object.assign({}, defaultHeaders, request.headers);
return this.getPenpalChild().then(function (child) {
return self.makePenpalRequest('rest', child, request);
});
}
@api fetchRequest(request) {
return this.getPenpalChild().then(function (child) {
return this.makePenpalRequest('fetch', child, request);
});
}
// ------------------------------------------------------------
/**
* For internal use.
* Returns a promise waiting for the parent-child postmate handshake to complete
* then resolves with reference to the postmate child for making requests.
*/
getPenpalChild() {
var child;
var self = this;
var timerId;
var timeout = 10000; // ten seconds
var pollFrequency = 500; // half a second
var startTime = new Date().getTime();
var endTime = startTime + timeout;
return new Promise(function (resolve, reject) {
child = self._penpal.child;
if (child) {
resolve(child);
} else {
// all time values in milliseconds
timerId = setInterval(function () {
child = this._penpal.child;
if (child) {
// parent-child postmate handshake now complete
clearInterval(timerId);
resolve(child);
} else {
// check if we have exceeded our timeout
let currentTime = new Date().getTime();
if (currentTime > endTime) {
clearInterval(timerId);
reject('LWC_API: Timeout trying to establish connection to iframe');
}
// else, keep polling
}
}, pollFrequency);
}
});
}
/**
* For internal use.
* Returns a promise waiting for the parent-child postmate request to complete
* then resolves with response from the child iframe.
*/
makePenpalRequest(requestType, child, request) {
var p;
if (requestType === 'rest') {
console.log('makePenpalRequest');
console.log(child);
console.log(child.restRequest);
p = child.restRequest(request);
} else if (requestType === 'fetch') {
p = child.fetchRequest(request);
} else {
p = Promise.resolve({
success: false,
data: 'LWC_API: Invalid request type: ' + requestType
});
}
console.log('makePenpalRequest');
return p.then(function (response) {
console.log(response);
if (response.success) {
return response.data;
}
throw new Error(response.data);
});
}
disconnectedCallback() { // unrender
// this.superUnrender();
// When component unrenders then cleanup penpal
// resources by destroying the connection and nulling out
// the helper's cached reference to the connection and child.
// This ensures that the helper.handleXyzRequest(..) methods
// wait appropriately for the new parent-child handshake to complete
// when this component is re-initialized and scripts are loaded.
if (this._penpal && this._penpal.connection) {
this._penpal.connection.destroy();
this._penpal = {};
}
}
}
The api.html
code:
<template>
<div lwc:dom="manual" class="slds-hide"></div>
</template>
Question
- What is causing the error?
- How do I fix it?