Thursday, 26 December 2024

LWC Custom File-Upload File Size Limit 4MB ??

 

Business Requirement

The system must support uploading files of any size from Salesforce to the external system, as the external system does not impose any file size limitations. This ensures seamless handling of large files without restrictions, aligning with the external system's capabilities.

Current Design: The file upload process is implemented using a custom LWC component instead of Salesforce's standard file upload feature because the files are uploaded directly to an external system, not stored in Salesforce. This design ensures seamless integration with the external system's APIs and allows for additional custom logic during the upload process.


File Upload Limitations : 

  1. Heap Size Limit: Files passed from LWC to Apex are restricted by the 6 MB heap size limit, regardless of whether the Apex method is synchronous or asynchronous.

  2. Misconception About Limits: Unlike the standard file upload feature, which supports files up to 2 GB, this design cannot handle files larger than 6 MB without chunking.

  3. When transferring a blob file from Lightning Web Components (LWC) to Apex, or vice versa, the file size limitation increases due to the need to encode the blob in Base64. This is because:

  • Serialization Limitation: LWC cannot directly pass blob data to Apex, as blobs are binary, and binary data is not supported in JSON serialization (the format used for LWC-Apex communication). Encoding the blob in Base64 allows it to be safely serialized and transmitted as a string.

  • Size Increase: Base64 encoding increases the file size by approximately 33%. This means that the size of the original blob is effectively enlarged, potentially making it exceed the limits for data transfer between LWC and Apex, which are restricted by Salesforce's platform limits. For example, the JSON response size limit for Apex to LWC communication is 6 MB, so after Base64 encoding, the original blob size should be less (<) than 4.5 MB to stay within the limit.

Why is the upload of a 3.2 MB or 3.5 MB file itself failing when it should allow up to 4.5 MB, as per the above?

  1. If LWC component is sending additional parameters or properties along with the file (e.g., File Metadata (FileChecksum,fileId,message,success) Details,user details, configuration settings), these are included in the response as well.When these are included with the Base64-encoded file, the overall response size can exceed the 6 MB limit if the file size is large.

  2. In Apex, the file is converted into Hexadecimal encoding (Increases size by 100%) before sending it to the external system because the system expects all file contents to be combined into a single file. Unlike Base64, Hexadecimal allows for more efficient concatenation of multiple file parts into one, ensuring compatibility with the external system's requirements.

  3. When calling a server-side action from a client-side controller in Lightning components, the request payload limit is 4 MB. If any greater than this size will receive an error <Error: Received exception event aura:systemError from server>

https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/controllers_server_actions_call.htm?utm_source=chatgpt.com

Comparison with Base64

  • Base64 Encoding: Increases size by ~33%.

  • Hexadecimal Encoding: Increases size by 100%.


  1. The maximum HTTP request size for Apex callouts is 12 MB, so files larger than this must be split into chunks.

Debugging Steps: 

Environment

Details

LWC 

Validate the file size in LWC before sending it to Apex to avoid unnecessary processing.

console.log('Base64 size in bytes:', <base64Size>); //File + Additional Parameters

Track Network Requests

Use browser developer tools (e.g., Chrome DevTools) to inspect the size of the payload being sent to Apex. This helps confirm if the issue arises during data transmission.

Apex

Monitor the heap size at critical points to identify where the data might exceed the limit.

System.debug('Heap size after receiving file data: ' + Limits.getHeapSize());//After Receiving the File Data +  Additional Parameters

data

Tuesday, 24 September 2024

Fixing the 'Failed to Save Prompt' Error in Google Cloud Vertex AI Lab - Invalid structured dataset metadata value

 

For two days, I encountered the "Invalid structured dataset metadata value" error while attempting to save a prompt in the Google Cloud Vertex AI lab (see the screenshot below). Each time, I provided feedback with comments, screenshots, and a rating to express my frustration.



Though I’m unsure if they acted on my feedback, after two days, the issue was resolved. The key takeaway: Ensure the region specified for saving matches what is mentioned in the course. This small detail can save you a lot of trouble!

Wednesday, 24 July 2024

Dynamic Data Collection and Nested Structure Creation in LWC with Apex

To handle various types of input elements dynamically, you can use a more generic approach to select all form elements and collect their values. You'll need to modify the JavaScript code to handle different types of inputs (e.g., radio buttons, checkboxes, dropdowns) accordingly.

LWC JavaScript Level

HTML Template:
html
Copy code
<template>

    <lightning-input label="Name" name="EditableFields.Name"></lightning-input>

    <lightning-input label="Age" name="EditableFields.Age" type="number"></lightning-input>

    <lightning-input label="Mom" name="EditableFields.Parents.Mom"></lightning-input>

    <lightning-input label="Dad" name="EditableFields.Parents.Dad"></lightning-input>

    <lightning-radio-group 

        label="Gender" 

        name="EditableFields.Gender"

        options={genderOptions}

        onchange={handleChange}>

    </lightning-radio-group>

    <lightning-combobox

        name="EditableFields.Country"

        label="Country"

        placeholder="Select Country"

        options={countryOptions}

        onchange={handleChange}>

    </lightning-combobox>

    <lightning-button label="Submit" onclick={handleSubmit}></lightning-button>

</template>


JavaScript Controller:
javascript
Copy code
import { LightningElement } from 'lwc';

import submitData from '@salesforce/apex/YourApexClass.submitData';


export default class EditableForm extends LightningElement {

    genderOptions = [

        { label: 'Male', value: 'Male' },

        { label: 'Female', value: 'Female' },

        { label: 'Other', value: 'Other' }

    ];


    countryOptions = [

        { label: 'USA', value: 'USA' },

        { label: 'Canada', value: 'Canada' },

        { label: 'India', value: 'India' }

    ];


    handleSubmit() {

        const fields = {};

        const inputs = this.template.querySelectorAll('lightning-input, lightning-radio-group, lightning-combobox');


        inputs.forEach(input => {

            if (input.type === 'checkbox') {

                fields[input.name] = input.checked;

            } else if (input.type === 'radio') {

                fields[input.name] = input.value;

            } else {

                fields[input.name] = input.value;

            }

        });


        submitData({ fields: JSON.stringify(fields) })

            .then(result => {

                console.log('Submitted Data: ', result);

            })

            .catch(error => {

                console.error('Error: ', error);

            });

    }

}


Apex Controller

Apex Controller:
apex
Copy code
public with sharing class YourApexClass {

    @AuraEnabled

    public static String submitData(String fields) {

        try {

            Map<String, Object> fieldsMap = (Map<String, Object>) JSON.deserializeUntyped(fields);


            Map<String, Object> customerData = new Map<String, Object>();

            customerData.put('CustomerId', 'D1');

            customerData.put('IdempotencyKey', '012ADQ');

            Map<String, Object> editableFields = new Map<String, Object>();

            customerData.put('EditableFields', editableFields);


            for (String path : fieldsMap.keySet()) {

                dynamicInsert(editableFields, path.split('\\.'), fieldsMap.get(path));

            }


            return JSON.serialize(customerData);

        } catch (Exception e) {

            throw new AuraHandledException('Error: ' + e.getMessage());

        }

    }


    private static void dynamicInsert(Map<String, Object> map, List<String> pathParts, Object value) {

        if (pathParts.size() == 1) {

            map.put(pathParts[0], value);

            return;

        }


        String key = pathParts[0];

        if (!map.containsKey(key)) {

            map.put(key, new Map<String, Object>());

        }


        dynamicInsert((Map<String, Object>) map.get(key), pathParts.subList(1, pathParts.size()), value);

    }

}


Explanation

  1. LWC JavaScript Level:

    • The handleSubmit method is triggered when the submit button is clicked.

    • It selects all lightning-input, lightning-radio-group, and lightning-combobox elements and iterates over them to build the fields object, where the key is the name attribute (which contains the bind path) and the value is the input's value.

    • For checkbox inputs, it stores the checked state; for radio buttons and other inputs, it stores the value.

    • The fields object is then serialized to JSON and sent to the Apex controller using the submitData method.

  2. Apex Controller:

    • The submitData method receives the JSON string and deserializes it into a Map<String, Object>.

    • customerData is initialized with fixed values for CustomerId and IdempotencyKey.

    • For each entry in the fieldsMap, the dynamicInsert method is called to construct the nested structure.

    • dynamicInsert is a recursive method that navigates through the path parts and dynamically creates nested maps as needed, inserting the value at the correct position.

This approach ensures that you can collect all input data with a single handler and dynamically create the required structure in Apex based on the received bind fields, regardless of the type of input element.

Sunday, 21 July 2024

Embedding External Content in Salesforce using Apex Callouts and Lightning Web Components

1. Can achieve this using Apex by creating a callout to the external service from Salesforce and then displaying the response within an iframe. Here's a detailed approach to accomplish this without using a proxy server:

Step 1: Create an Apex Controller for the Callout

Create an Apex class that performs the callout to the external service, includes the API key in the headers, and returns the response.

apex

public class ExternalServiceController { @AuraEnabled(cacheable=true) public static String getExternalContent() { String apiKey = 'YOUR_API_KEY'; Http http = new Http(); HttpRequest request = new HttpRequest(); request.setEndpoint('https://external.url'); request.setMethod('GET'); request.setHeader('Authorization', 'Bearer ' + apiKey); HttpResponse response = http.send(request); if (response.getStatusCode() == 200) { return response.getBody(); } else { throw new AuraHandledException('Error: ' + response.getStatus()); } } }

Step 2: Create a Lightning Web Component (LWC)

Create a Lightning Web Component that calls the Apex method and displays the response within an iframe or div.

LWC JavaScript Controller

javascript

import { LightningElement, track, wire } from 'lwc'; import getExternalContent from '@salesforce/apex/ExternalServiceController.getExternalContent'; export default class ExternalContentComponent extends LightningElement { @track externalContent; @wire(getExternalContent) wiredContent({ error, data }) { if (data) { this.externalContent = data; } else if (error) { this.externalContent = 'Error fetching content: ' + error.body.message; } } }

LWC HTML Template

html

<template> <template if:true={externalContent}> <div class="external-content" lwc:dom="manual"></div> </template> <template if:true={error}> <div class="error-message">{error}</div> </template> </template>

LWC CSS (Optional)

css

.external-content { width: 100%; height: 600px; overflow: auto; }

Step 3: Render External Content

Since you cannot directly render HTML within an iframe from the response body, you'll need to set the content dynamically using JavaScript.

LWC JavaScript Controller (Updated)

javascript

import { LightningElement, track, wire } from 'lwc'; import getExternalContent from '@salesforce/apex/ExternalServiceController.getExternalContent'; export default class ExternalContentComponent extends LightningElement { @track externalContent; @wire(getExternalContent) wiredContent({ error, data }) { if (data) { this.externalContent = data; this.renderExternalContent(); } else if (error) { this.externalContent = 'Error fetching content: ' + error.body.message; } } renderExternalContent() { if (this.externalContent) { const contentDiv = this.template.querySelector('.external-content'); if (contentDiv) { contentDiv.innerHTML = this.externalContent; } } } }

Security Considerations

  • CSP (Content Security Policy): Ensure that your Salesforce instance allows loading content from the external service.
  • API Key Security: Ensure the API key is handled securely and not exposed in client-side code.
  • Sanitization: Make sure to sanitize the response if it contains HTML to avoid XSS (Cross-Site Scripting) attacks.

This approach leverages Apex to handle the API call and set the necessary headers, then dynamically injects the content into your Lightning component. This avoids the need for a proxy server and leverages Salesforce's capabilities directly.


2.

Let's consider an alternative approach using Visualforce and a custom Visualforce controller to handle the callout and display the response within an iframe.

Using Visualforce with Apex

Step 1: Create an Apex Controller

Create an Apex class to handle the callout.

apex
public class ExternalServiceController { public String getExternalContent() { String apiKey = 'YOUR_API_KEY'; Http http = new Http(); HttpRequest request = new HttpRequest(); request.setEndpoint('https://external.url'); request.setMethod('GET'); request.setHeader('Authorization', 'Bearer ' + apiKey); HttpResponse response = http.send(request); if (response.getStatusCode() == 200) { return response.getBody(); } else { throw new VisualforceException('Error: ' + response.getStatus()); } } }

Step 2: Create a Visualforce Page

Create a Visualforce page to embed the external content.

html
<apex:page controller="ExternalServiceController" contentType="text/html"> <apex:form> <apex:outputPanel layout="block"> <apex:iframe src="data:text/html;base64,{!BASE64ENCODE(getExternalContent())}" width="100%" height="600px"/> </apex:outputPanel> </apex:form> </apex:page>

Explanation

  1. Apex Controller:

    • The ExternalServiceController class makes a callout to the external service, includes the API key in the headers, and returns the response body.
  2. Visualforce Page:

    • The Visualforce page uses the ExternalServiceController to fetch the external content.
    • The apex:iframe tag embeds the content within an iframe, converting the response body to a base64-encoded string to safely embed it.

Security Considerations

  • CSP (Content Security Policy): Ensure that your Salesforce instance allows the external URL.
  • API Key Security: Securely handle the API key.
  • Sanitization: Ensure the response does not contain any malicious content.

By using Visualforce, you can effectively embed external content with headers set in the Apex callout. This approach provides a viable solution within the Salesforce framework, leveraging Visualforce's capability to embed content securely.

3.

Using Apex Callout and Custom Object

Step 1: Apex Controller for Callout

apex

public class ExternalServiceController { @AuraEnabled public static String getExternalContent() { String apiKey = 'YOUR_API_KEY'; Http http = new Http(); HttpRequest request = new HttpRequest(); request.setEndpoint('https://external.url'); request.setMethod('GET'); request.setHeader('Authorization', 'Bearer ' + apiKey); HttpResponse response = http.send(request); if (response.getStatusCode() == 200) { return response.getBody(); } else { throw new AuraHandledException('Error: ' + response.getStatus()); } } }

Step 2: LWC to Display Content

  1. LWC JavaScript Controller

    javascript

    import { LightningElement, track, wire } from 'lwc'; import getExternalContent from '@salesforce/apex/ExternalServiceController.getExternalContent'; export default class ExternalContentComponent extends LightningElement { @track externalContent; @wire(getExternalContent) wiredContent({ error, data }) { if (data) { this.externalContent = data; } else if (error) { this.externalContent = 'Error fetching content: ' + error.body.message; } } }
  2. LWC HTML Template

    html

    <template> <template if:true={externalContent}> <div lwc:dom="manual" class="external-content"></div> </template> <template if:true={error}> <div class="error-message">{error}</div> </template> </template>
  3. LWC JavaScript to Inject Content

    javascript

    export default class ExternalContentComponent extends LightningElement { @track externalContent; @wire(getExternalContent) wiredContent({ error, data }) { if (data) { this.externalContent = data; this.renderExternalContent(); } else if (error) { this.externalContent = 'Error fetching content: ' + error.body.message; } } renderExternalContent() { if (this.externalContent) { const contentDiv = this.template.querySelector('.external-content'); if (contentDiv) { contentDiv.innerHTML = this.externalContent; } } } }

Considerations:

  • CSP (Content Security Policy): Ensure that your Salesforce CSP settings allow the external URL.
  • Sanitization: Make sure to sanitize any HTML content to avoid XSS attacks.
  • Security: Ensure the API key is securely stored and not exposed.
3.Calling an external system's URL from within Salesforce, and the external system needs to authenticate the request by recognizing that you're a valid client. You want to do this by passing the API key in the HTTP header rather than in the query string, and then displaying the content within Salesforce (likely in an iframe or similar).

Approach: Passing API Key in Header for External System Authentication

Step 1: Create an Apex Controller to Perform the HTTP Callout

  • Create an Apex class that sends an HTTP request to the external system's URL with the API key in the HTTP header.
  • The external system will authenticate the request based on the API key provided in the header and return the content or a signed URL.

Step 2: Return the Signed URL or Content

  • If the external system returns a signed URL (or some other link that includes authentication), return this URL from the Apex class.
  • Alternatively, if the system returns the actual HTML content, return this directly.

Step 3: Create LWC to Render the Content

  • In the LWC: If the Apex method returns a signed URL, set this as the src of an iframe. If HTML content is returned, render it directly.

Example Implementation

Apex Controller (ExternalSystemController.apex):


public class ExternalSystemController { @AuraEnabled(cacheable=true) public static String fetchSignedUrlOrContent() { HttpRequest req = new HttpRequest(); HttpResponse res; Http http = new Http(); req.setMethod('GET'); req.setEndpoint('https://external-system-url.com/resource'); // Replace with the actual URL req.setHeader('Authorization', 'Bearer YOUR_API_KEY'); // Pass the API key in the header try { res = http.send(req); if (res.getStatusCode() == 200) { return res.getBody(); // Assuming this is the signed URL or content } else { throw new CalloutException('Request failed with status code: ' + res.getStatusCode()); } } catch (Exception e) { throw new CalloutException('Error during HTTP callout: ' + e.getMessage()); } } }

LWC to Display the Content:

LWC Template (externalContent.html):
html
<template> <lightning-card title="External System Content"> <iframe src={iframeSrc} width="100%" height="600px" if:true={iframeSrc}></iframe> <div lwc:dom="manual" if:true={htmlContent}></div> </lightning-card> </template>
LWC JavaScript (externalContent.js):
javascript
import { LightningElement, track, wire } from 'lwc'; import fetchSignedUrlOrContent from '@salesforce/apex/ExternalSystemController.fetchSignedUrlOrContent'; export default class ExternalContent extends LightningElement { @track iframeSrc; @track htmlContent; @wire(fetchSignedUrlOrContent) wiredUrlOrContent({ error, data }) { if (data) { if (data.startsWith('http')) { this.iframeSrc = data; // If it's a URL, set it as iframe src } else { this.htmlContent = data; // Otherwise, treat it as HTML content } } else if (error) { console.error('Error fetching data from external system', error); } } }

Step 4: Deploy and Test

  • Deploy the Apex class and LWC to your Salesforce org.
  • Add the LWC to a Lightning page to verify that the content from the external system loads correctly, with the API key securely passed in the HTTP header.

Summary

  • Apex handles the HTTP callout to the external system, passing the API key in the header.
  • LWC dynamically displays the returned signed URL in an iframe or the returned HTML content directly.

This approach ensures that the external system authenticates your request without exposing the API key in the query string.


4.an alternative way to achieve the same goal, where you can authenticate with the external system without directly exposing or handling the API key in Salesforce. Here are two alternate methods:

Alternative 1: Use a Middleware or Proxy Server

Overview

  • Implement a middleware or proxy server that handles the API key and authentication. Salesforce would make requests to this middleware, which then forwards the request to the external system with the API key in the header.
  • This approach abstracts the authentication process from Salesforce, keeping the API key secure and maintaining a clean separation between systems.

Steps

  1. Set Up a Middleware Server:

    • Create a simple middleware server using a technology like Node.js, Express, or any preferred web framework.
    • This server should accept requests from Salesforce, attach the API key in the header, and forward the request to the external system.
  2. Salesforce to Middleware:

    • In Salesforce, make an HTTP request to the middleware server from an Apex class.
    • The middleware adds the necessary headers (including the API key) and forwards the request to the external system.
  3. Middleware to External System:

    • The middleware receives the response from the external system and sends it back to Salesforce.
    • The response can be a signed URL or the actual content.
  4. Render in Salesforce:

    • Use LWC to render the content or URL returned from the middleware.

Advantages:

  • Keeps API key secure and hidden from Salesforce.
  • Simplifies Salesforce implementation as it only needs to communicate with the middleware.

Alternative 2: OAuth 2.0 Authentication

Overview

  • If the external system supports OAuth 2.0, you can set up OAuth authentication between Salesforce and the external system.
  • Salesforce will obtain an access token to authenticate API calls instead of directly passing an API key.

Steps

  1. OAuth Setup:

    • Register Salesforce as a client in the external system's OAuth 2.0 setup.
    • Obtain client credentials (client ID and secret).
  2. Implement OAuth Flow in Salesforce:

    • Use an Apex class to handle the OAuth 2.0 authentication flow (e.g., client credentials flow).
    • Salesforce will send a request to the external system’s token endpoint to obtain an access token.
  3. Use Access Token:

    • Once the access token is obtained, use it in the header of subsequent API requests to the external system.
    • The external system authenticates based on the access token, ensuring secure access.
  4. Render in Salesforce:

    • After receiving the response from the external system (signed URL or content), render it in Salesforce using LWC.

Advantages:

  • OAuth 2.0 is a secure and standardized authentication method.
  • No need to handle or store API keys directly in Salesforce.

Summary

  • Middleware Approach: Salesforce communicates with a proxy server that handles the API key and forwards the request.
  • OAuth 2.0: Use OAuth to securely authenticate and obtain access tokens, avoiding direct API key usage.

Both alternatives offer more secure and scalable solutions, depending on your infrastructure and the external system's capabilities.