Would default() allow mixed content while fetch() forbids it? Or should default() disallow it too?
@mikewest
@slightlyoff @jakearchibald and I had a conversation about this a week or two ago. I think Jake was going to write something up?
Fetch should return an opaque response for mix content, then elements can accept/reject based on their rules. Eg, would work but cause a mix padlock.
This allows something like a podcast app to be built without proxying heavy audio files through your own https server. Unfortunately we're a long way from podcasts being served over https :(
Wait, we would allow fetch() to get to non-TLS URLs too? o_O
Why are podcasts suddenly a problem? YouTube has TLS. Cloudflare makes it cheap.
Yeah, but the processor of the data gets to decide whether it reads the data or not.
A podcasting app would need to download and play content from around the web. If we can't do that we're crippling ourselves compared to native.
Proxying the RSS feeds seems like a small deal, but proxying 100mb files... not so much.
Are @briansmith and others game with this plan? It seems bad to poke holes.
Hrm. I recall a different conclusion. @slightlyoff noted that it's odd for a <video>, <audio>, or <IMG> tag to request an insecure resource, but the Service Worker can't do the same.
I suggested that when fetching in response to a page's request, we should pass through the request context and frame type in order to do the mixed content check.
Yeah, that would make it work for default(), but not fetch(). However, that those elements can fetch non-authenticated content is a bug and Firefox Nightly already shows a broken lock for it.
Thinking about this following a chat on IRC.
fetch('http://...', {mode: 'no-cors'}) should fail from clients without a direct link to a single window (so, fails in SharedWorker and ServiceWorker).
An empty serviceworker shouldn't change the behaviour of mixed content.
The question is do we want to support the "podcast" app case?
fetch('http://...', {mode: 'no-cors'}) from a window could succeed & show a mixed content warning unless "block-all-mixed-content" is set in CSP. The resolved value would be opaque.
To support this in a service worker we'd need a way to link the request to the client, which could be event.default() or similar, or something like windowClient.fetch(). This means we know where to show the warning, or fail if it cannot be shown. (added bonus: the request can be aborted if the client goes away)
If we can't build a podcast app today on the web, we're definitely losing something to native, but maybe the mixed content warning means we're losing there anyway. Perhaps it's not worth it.
CSP defines how SW handles mixed content and I LIKE IT.
Sorry to bust in so late in this discussion but the fact is that I'm building a podcast pwa (available for disfunctional testing at https://tolu.github.io/podspace) and I've just spent a day trying to figure out how to solve this problem.
The app is very much WIP and I don't even have a well defined caching strategy so the service worker simply caches everything that comes through.
The only separation in logic is serving application files "network first" and everything else "cache first"(where requests for iTunes images fail, not to mention mp3's).
@jakearchibald if you have the time, could you please elaborate on how CSP can be used for avoiding this issue, or point me in the right direction. I would be eternally greatful!
I've looked around and tried some approaches but I fear I don't really understand how CSP should be used for a case like this.
@tolu there's no solution. Nobody implemented service worker mixed content exceptions and that feature has since been dropped. Everything needs to be HTTPS.
I know that's not great, but mixed content would not have been great either as it would decrease trust in your application. And as long as you can't actually get to the bits of the pod you can't display chapter information and such, which would make the experience a bit subpar.
Thanks for the quick response, really appreciate it!
Then I guess that the only solution is having a proxy since I cant control the availability of https on resources from the iTunes API or the podcast file providers 馃槩
@tolu Did you ever find a solution to your problem other than using a proxy? I'm handling video so if you think proxying podcast audio is bad then you can imagine my concerns :)
@grkblood13 actually I found out (this summer) that you are allowed (at least in Chrome) to add anonymous HTTP-requests to the cache 馃槃 (鈿狅笍)
The issue remains that you CANT know if the data is a-ok or not however since there is no programmatic access to the response 馃槩
But for my case, just a private podcast player - the issue of risk / stability is fine...
The reason this is a big issue for audio is that there are SO MANY podcast providers out there that do not provide CORS headers or HTTPS (and a proxy would just prove to costly).
I was wondering why you would have this issue though?
From where are you fetching video (what services)?
Are you streaming (hls, hds, dash, etc) or using progressive download (mp4)?
// example using workbox
workbox.routing.registerRoute(
({url}: {url: URL}) => {
// Return true if the route should match
const isMp3 = /\.(?:mp3)$/.test(url.pathname);
// define some own query param to figure out if this is
// a regular request or if this should be cached...
const offline = /podspace-offline/.test(url.search);
return isMp3 && offline;
},
workbox.strategies.cacheFirst({
cacheName: 'audio',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60, // 30 Days
}),
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
}),
],
})
);
// in another file, trigger cache by fetching resource like so
// request with no-cors since that is what audio-element does
const src = 'some-mp3-url';
await fetch(src + "?podspace-offline", { mode: "no-cors" });
// then you need to wait some time or do interval checking on cache like so
async function isItReadyYet(src) {
const mp3Cache = await caches.open("audio");
const keys = await mp3Cache.keys();
return keys.map((r) => r.url).find((cacheUrl) => cacheUrl.includes(src));
}
The use-case for me would be private HLS streams, none of which i've seen being hosted via https. I've created a web-player that I would like to host using https, but of course those non-ssl sources won't allow them to play, and instead spit out mixed-content error messages. Proxying is not a option for me since that's outside the scope of a player and too costly as you mentioned. I looked into serviceworkers after watching a demo video on it's client-side proxying capabilities only to learn at the end serviceworkers are only supported via https. I might have to give what you mentioned a shot though. I also found a post that suggested setting the CSP content header to "upgrade-insecure-requests". Apparently that will take a http source and attempt to convert is to https, then if it fails it'll accept the original http feed. That would by far be the simplist method as it would illiminate the need for a serviceworker entirely.
At least HLS ensures that CORS must be in place, at least if cross browser web playback with things like hls.js has been taken into account, which is a good thing!
The HTTPS part on the other part is tricky if you have no control over the servers...
Best of luck!
Most helpful comment
@tolu there's no solution. Nobody implemented service worker mixed content exceptions and that feature has since been dropped. Everything needs to be HTTPS.
I know that's not great, but mixed content would not have been great either as it would decrease trust in your application. And as long as you can't actually get to the bits of the pod you can't display chapter information and such, which would make the experience a bit subpar.