Home > Software engineering >  Apps Script webhooks and Access-Control-Allow-Origin header missing
Apps Script webhooks and Access-Control-Allow-Origin header missing

Time:05-04

I have a Google Apps Script project acting as a webhook. When calling the endpoint using a library like htmx, the preflight check fails and the request subsequently fails. When calling directly with fetch or XMLHttpRequest, it works fine.

I have a sample endpoint with a simple doPost for testing:

const doPost = (request = {}) => {
  const { postData: { contents, type } = {} } = request;
  return ContentService.createTextOutput(contents);
};

This Codepen sample shows how requests with HTMX fail while fetch and XHRHttpRequest are successful.

Some things I've learned:

  • The OPTIONS header sent in a preflight results in a 405 error, aborting the request entirely. You can mimic this by sending an OPTIONS request via Postman (or similar) to the web app URL.
  • The error doesn't include Access-Control-Allow-Origin header, which is what shows as the failure reason in the console.
  • HTMX sends non-standard headers, which trigger preflight requests in modern browsers. However, you can strip all headers out, which should bypass the preflight, but doesn't. See this related discussion in the repo.

In this kind of situation, what is the best method for debugging? I'm not really sure what else to try to get this working.

CodePudding user response:

This is an issue with HTMX, that requires modification of its source code. The source of the problem is that HTMX adds some event listener to xhr.upload that makes the browsers mark the request as "not simple", triggering the CORS preflight request:

If the request is made using an XMLHttpRequest object, no event listeners are registered on the object returned by the XMLHttpRequest.upload property used in the request; that is, given an XMLHttpRequest instance xhr, no code has called xhr.upload.addEventListener() to add an event listener to monitor the upload

The specific part of HTMX source code:

forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
    forEach([xhr, xhr.upload], function (target) {
        target.addEventListener(eventName, function(event){
            triggerEvent(elt, "htmx:xhr:"   eventName, {
                lengthComputable:event.lengthComputable,
                loaded:event.loaded,
                total:event.total
            });
        })
    });
});

Sadly, the addEventListener uses anonymous functions, so there's no way to remove them with removeEventListener AFAIK.

But if you are willing to use a custom HTMX script in your app until the authors fixes this issue (e.g. add ability to prevent event listener creation on xhr.upload), just remove the xhr.upload from the list in the second row:

forEach(['loadstart', 'loadend', 'progress', 'abort'], function(eventName) {
    forEach([xhr], function (target) {
        target.addEventListener(eventName, function(event){
            triggerEvent(elt, "htmx:xhr:"   eventName, {
                lengthComputable:event.lengthComputable,
                loaded:event.loaded,
                total:event.total
            });
        })
    });
}); 

With this modification your original suggestion of removing non-standard HTMX-specific headers via evt.detail.headers = [] will work, since now this request becomes "simple", so no more CORS preflight is made by the browsers.

Note: the modification may break the HTMX file upload, I did not test it.

  • Related