I am trying to create a LWC version of [Doug Ayers](https://github.com/douglascayers) library for doing [promise-based service component for calling REST API from Lightning Aura Components](https://github.com/douglascayers/sfdx-lightning-api-component).

Basically, the LWC communicates using [Penpal](https://github.com/Aaronius/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:

1. LWC: Caller (caller.js)
2. LWC: Parent (api.js)
3. 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>

The `catch` is getting an error:

> TypeError

With no details.

Reviewing the `api.js` component, I think the issue is in method `makePenpalRequest` around line:

    p = child.restRequest(request);

Because when I do `console.log(child.restRequest)` I get `undefined`, but `child` is a `Promise` as expected.

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;

                    Promise.all([connection]).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.
                        self._penpal.child = child[0];
                    }).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**

1. What is causing the error?
2. How do I fix it?