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 anOPTIONS
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.