From 77ac4a80d73edc73b464bcd99c8785c9e3e88c6c Mon Sep 17 00:00:00 2001 From: SalesforceBobLightning Date: Wed, 3 Jul 2019 19:05:07 +0100 Subject: [PATCH] Added Lightning Web Component --- force-app/main/jsconfig.json | 11 + force-app/main/lwcApi/lwcApi.html | 3 + force-app/main/lwcApi/lwcApi.js | 247 +++++++++++++++++++++++ force-app/main/lwcApi/lwcApi.js-meta.xml | 5 + 4 files changed, 266 insertions(+) create mode 100644 force-app/main/jsconfig.json create mode 100644 force-app/main/lwcApi/lwcApi.html create mode 100644 force-app/main/lwcApi/lwcApi.js create mode 100644 force-app/main/lwcApi/lwcApi.js-meta.xml diff --git a/force-app/main/jsconfig.json b/force-app/main/jsconfig.json new file mode 100644 index 0000000..520be6a --- /dev/null +++ b/force-app/main/jsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "experimentalDecorators": true, + "baseUrl": ".", + "paths": { + "c/api": [ + "lwcApi/lwcApi.js" + ], + } + } +} \ No newline at end of file diff --git a/force-app/main/lwcApi/lwcApi.html b/force-app/main/lwcApi/lwcApi.html new file mode 100644 index 0000000..66bce8e --- /dev/null +++ b/force-app/main/lwcApi/lwcApi.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/force-app/main/lwcApi/lwcApi.js b/force-app/main/lwcApi/lwcApi.js new file mode 100644 index 0000000..08ef299 --- /dev/null +++ b/force-app/main/lwcApi/lwcApi.js @@ -0,0 +1,247 @@ +/* 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 LwcApi extends LightningElement { + + _penpal = {}; + penpalInitialized = false; + iframeSrc = ''; + + @wire(getVisualforceDomainURL) wiredDomainUrl({ error, data }) { + if (data) { + this.iframeSrc = data + '/apex/LC_APIPage'; + 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. + self._penpal.child = child; + }).catch(function (err) { + console.error('LWC_API: Error establishing connection to iframe', err); + self.penpalInitialized = false; + }); + + } // else, iframe source is empty, keep waiting + } + } + + // Makes a Salesforce REST API request and returns a promise that resolves to the response. + + // @param request + // JSON object with properties: + // 'url'(String, required) The Salesforce REST endpoint to call. + // 'method'(String, optional) The http method like 'get' or 'post'.Default is 'get'. + // 'body'(String, optional) The request body, varies by the endpoint you're calling. + // 'headers'(Map, optional) String key - value pairs of http headers to send. + // Default is { 'Content-Type' : 'application/json' }. + // Your headers are merged with the default headers, + // overwriting any existing keys. + + // Example usage: + // component.find('lcAPI').restRequest({ + // 'url': '/services/data/v45.0/sobjects/Account', + // 'method': 'post', + // 'body': JSON.stringify({ + // 'Name': 'Salesforce', + // 'BillingStreet': '1 Market Street', + // 'BillingCity': 'San Francisco', + // 'BillingState': 'CA' + // }), + // 'headers': { + // 'Sforce-Query-Options': 'batchSize=200' + // } + // }).then($A.getCallback(function (response) { + // // handle response + // })).catch($A.getCallback(function (err) { + // // handle error + // })); + + @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); + }); + } + + // Makes a JavaScript Fetch request and returns a promise that resolves to the response. + // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch + + // @param request + // JSON object with properties: + // 'url'(String, required) The url to fetch. + // 'options'(Map, optional) The init options for the request. + + // Example usage: + // component.find('lcAPI').fetchRequest({ + // 'url': 'https://example.com', + // 'options': { + // 'method': 'GET', + // 'headers': { + // 'Accepts': 'application/json' + // } + // } + // }).then($A.getCallback(function (response) { + // // handle response + // })).catch($A.getCallback(function (err) { + // // handle error + // })); + + @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 self = this; + + return new Promise(function (resolve, reject) { + + let child = self._penpal.child; + + if (child) { + + resolve(child); + + } else { + + // all time values in milliseconds + let timeout = 10000; // ten seconds + let pollFrequency = 500; // half a second + let startTime = new Date().getTime(); + let endTime = startTime + timeout; + + // all time values in milliseconds + let timerId = setInterval(function () { + + child = self._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') { + 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 + }); + } + + return p.then(function (response) { + + if (response.success) { + return response.data; + } + throw new Error(response.data); + + }); + + } + + disconnectedCallback() { + // 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 = {}; + } + } + +} diff --git a/force-app/main/lwcApi/lwcApi.js-meta.xml b/force-app/main/lwcApi/lwcApi.js-meta.xml new file mode 100644 index 0000000..a483fbc --- /dev/null +++ b/force-app/main/lwcApi/lwcApi.js-meta.xml @@ -0,0 +1,5 @@ + + + 46.0 + false + \ No newline at end of file