Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CORS error with Range header on Safari 14 #207

Closed
pieterdd opened this issue Dec 14, 2024 · 12 comments · Fixed by #208
Closed

CORS error with Range header on Safari 14 #207

pieterdd opened this issue Dec 14, 2024 · 12 comments · Fixed by #208

Comments

@pieterdd
Copy link

While solving some compatibility issues with legacy Safari versions in my own codebase, I bumped into one involving the Range header in the library. I tested on Safari 14 (macOS Big Sur). Here's a minimal reproduction case:

<script src="https://eshaz.github.io/icecast-metadata-js/icecast-metadata-player-1.17.7.main.min.js"></script>
<script>
const player = new IcecastMetadataPlayer("https://ice4.somafm.com/groovesalad-64-aac", {
  onMetadata: (data) => {
  	document.querySelector('#metadata').innerHTML = data.StreamTitle;
  },
});
</script>
<div id="metadata">
metadata goes here
</div>
<button onclick="player.play()">
Play
</button>

This triggers the following errors:
image
image

I'm aware that the Range header is there for a reason. However, when I play the stream in a separate tab the stream plays fine despite the browser not sending it:

image

Do you see a solution that avoids breaking other scenarios?

@eshaz
Copy link
Owner

eshaz commented Dec 14, 2024

It should retry the request without the range header when encountering the CORS error in Safari. There's some conditional logic that checks for this case, but the error details must be different in Safari 14.

Would you be able to put a breakpoint on this condition, reproduce the error in your test environment, and let me know what the value of e.name and e.message are? I can add these in as an additional check.

const res = await request().catch((e) => {
// work around for Safari desktop to remove Range header for CORS
// Even though it's a safelisted header, and this shouldn't be needed
// See: https://fetch.spec.whatwg.org/#cors-safelisted-request-header
if (e.name === "TypeError" && e.message === "Load failed") {
delete headers["Range"];
return request();
}
throw e;
});

I should note, this is a bug in Safari. The range header is allowed per w3c specs for fetch regardless of the CORS response, but Safari choose not to follow this standard for a few older versions.

@pieterdd
Copy link
Author

pieterdd commented Dec 14, 2024

Thank you for trying to accommodate Safari 14's non-compliance with the W3C spec for fetch. Here's that breakpoint in action:

image

Curiously, it appears that e is a promise a function returning a promise in this context 🤔

@pieterdd
Copy link
Author

This looks marginally more usable:

image

image

So when the promise's catch is triggered, I get these values:

  • err.name: TypeError
  • err.message: Request header field Range is not allowed by Access-Control-Allow-Headers.

@eshaz eshaz closed this as completed in 90db5f3 Dec 15, 2024
@eshaz
Copy link
Owner

eshaz commented Dec 15, 2024

Thanks for getting that info. I've released icecast-metadata-player/1.17.8 that should fix this.

Also, thanks for all the testing and for reporting these issues as they come up. This really helps make this library better for everyone.

@pieterdd
Copy link
Author

While validating the fix I noticed that something's still off, but I can't pinpoint what. Notice how the new condition block is not entered despite its condition evaluating to true:

image

When resuming execution, I can see that the Range header is still present:

image

@pieterdd
Copy link
Author

Any chance it's related to the way string equality checks work in JavaScript?

I took a look at https://stackoverflow.com/questions/3586775/what-is-the-correct-way-to-check-for-string-equality-in-javascript and it appears that "my_string".valueOf() === "other_string".valueOf() might work. Then again, its behavior looks identical to "my_string" === "other_string" in the Safari 14 console:

image

@eshaz
Copy link
Owner

eshaz commented Dec 15, 2024

The === should be fine for string equality. There's probably something unexpected happening after that initial if statement on line 147 and before the return statement on 158. Could you step through this code one statement at a time and note where things don't match what the code is expecting?

if (typeof e === "function") {
// Safari 14 puts this error in a function.
// See https://github.com/eshaz/icecast-metadata-js/issues/207
const promiseError = await e().catch((err) => err);
if (
typeof promiseError === "object" &&
promiseError.name === "TypeError" &&
promiseError.message ===
"Request header field Range is not Allowed byt Access-Control-Allow-Headers."
) {
delete headers["Range"];
return request();

I'm happy to continue pair programming through this github issue, but it might be faster for you to clone this repo and try things out yourself. Everything you need to do to build and run locally can be found here.

@eshaz eshaz reopened this Dec 15, 2024
@eshaz
Copy link
Owner

eshaz commented Dec 15, 2024

Looks like I have a typo in the error message check, so that will need to be fixed... However, we might be overthinking this. Could you try out this branch?

https://github.com/eshaz/icecast-metadata-js/tree/fix-safari-14

If this change doesn't work, I'll put the promise code back into this branch and fix the typo.

@pieterdd
Copy link
Author

I don't have a development environment on the Safari 14 machine, but I might be able to figure out some way to host the assets somewhere the machine can access them.

@pieterdd
Copy link
Author

Hmm, looks like the debugger's watches were a red herring:

image

As you can see the condition ends up being true despite e.name and e.message supposedly not matching up with the expected values.

image

Good call on your part. It's running smoothly now, and I'm glad that the working solution is simpler than the previous iteration. Thanks Ethan!

@eshaz
Copy link
Owner

eshaz commented Dec 15, 2024

Excellent, glad it worked! I've released this fix as icecast-metadata-player/1.17.9

@pieterdd
Copy link
Author

Observed fixed in the demo player!

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants