sorenkrabbe / Chrome-Salesforce-inspector

Chrome extension to add a metadata layout on top of the standard Salesforce UI to improve the productivity and joy of Salesforce configuration, development, and integration.
328 stars 124 forks source link

Tried to migrate to Safari but xhr request blocked by CSP #253

Open BenjaminLiCN opened 1 year ago

BenjaminLiCN commented 1 year ago

Refused to connect to 'https://mycompany.sandbox.my.salesforce.com/services/data/v56.0/query?q=select%20Id%2C%20Name%20FROM%20User%20LIMIT%201&cache=0.22271687033207455' because it violates the following Content Security Policy directive: "connect-src 'self' https://static.lightning.force.com https://api.bluetail.salesforce.com https://staging.bluetail.salesforce.com https://preprod.bluetail.salesforce.com blob: *.vf.force.com https://mycompany.sandbox.file.force.com https://cs171.salesforce.com https://notification-service.sfproxy.null.ia5.aws.sfdc.cl wss://notification-service.sfproxy.null.ia5.aws.sfdc.cl".

So in safari extension I only have one file named script.js which is lilke background.js. It can send data to Swfit code and display them.

let sfConn = {
    async rest(url, {logErrors = true, method = "GET", api = "normal", body = undefined, bodyType = "json", headers = {}, progressHandler = null} = {}) {
        try {
            const sidRegex = /sid=(.*?);/;
            const sidMatch = document.cookie.match(sidRegex);
            const sessionId = sidMatch ? sidMatch[1] : null;
            const hostname = window.location.hostname;
            let xhr = new XMLHttpRequest();
            url += (url.includes("?") ? "&" : "?") + "cache=" + Math.random();
            xhr.open(method, "https://" + hostname + url, true);
            console.log("https://" + hostname + url + " sid: " + sessionId);
            xhr.setRequestHeader("Accept", "application/json; charset=UTF-8");
            if (api == "bulk") {
                xhr.setRequestHeader("X-SFDC-Session", sessionId);
            } else if (api == "normal") {
                xhr.setRequestHeader("Authorization", "Bearer " + sessionId);
            } else {
                throw new Error("Unknown api");
            }

            if (body !== undefined) {
                if (bodyType == "json") {
                    body = JSON.stringify(body);
                    xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
                } else if (bodyType == "raw") {
                    // Do nothing
                } else {
                    throw new Error("Unknown bodyType");
                }
            }
            xhr.responseType = "json";
            console.log("before promise");
            await new Promise((resolve, reject) => {
                if (progressHandler) {
                    progressHandler.abort = () => {
                        let err = new Error("The request was aborted.");
                        err.name = "AbortError";
                        reject(err);
                        xhr.abort();
                    };
                }

                xhr.onreadystatechange = () => {
                    if (xhr.readyState == 4) {
                        resolve();
                    }
                };
                console.log("sending xhr");
                xhr.send(body);
            });
            if (xhr.status >= 200 && xhr.status < 300) {
                console.log("good, returning ");
                console.log(xhr);
                console.log(xhr.response);
                return xhr.response;
            } else if (xhr.status == 0) {
                if (!logErrors) { console.error("Received no response from Salesforce REST API", xhr); }
                let err = new Error();
                err.name = "SalesforceRestError";
                err.message = "Network error, offline or timeout";
                throw err;
            } else {
                if (!logErrors) { console.error("Received error response from Salesforce REST API", xhr); }
                let err = new Error();
                err.name = "SalesforceRestError";
                err.detail = xhr.response;
                try {
                    err.message = err.detail.map(err => `${err.errorCode}: ${err.message}${err.fields && err.fields.length > 0 ? ` [${err.fields.join(", ")}]` : ""}`).join("\n");
                } catch (ex) {
                    err.message = JSON.stringify(xhr.response);
                }
                if (!err.message) {
                    err.message = "HTTP error " + xhr.status + " " + xhr.statusText;
                }
                throw err;
            }
        } catch(e) {
            console.log(e);
        }

    }
};
    const fullQuerySelect = "select Id, Name FROM User LIMIT 1";
    console.log("calling");
    sfConn.rest("/services/data/v56.0" + "/query?q=" + encodeURIComponent(fullQuerySelect), {logErrors: false})
    .then(res => {
      for (let record of res.records) {
        console.log(record.Name);
      }
    }).catch(() => {
      //Swallow this exception since it is likely due to missing standard attributes on the record - i.e. an invalid query.
      console.log("caught error");
    });

This is executed in my sandbox hostname ends with "lightning.force.com", however I observed the request will be redirected to another ends with "my.salesforce.com". I suspect this is why it blocks my request. Anyone can directly copy this code and run in chrome console.

Could anyone shed some light, please? First image is the successful request sent by inspector extension on chrome.

image

Second is my code executed on chrome console

image
BenjaminLiCN commented 1 year ago

According to this article, it is because my request is generated from lightning.force.com which is the same as LWC. The api hostname ends with salesforce.com. And since they are not the same origin hence the request fails. What I couldn't understand is why is Inspector capable of making calls from salesforce.com????? I didn't see any secret in the inspector.js code.