1

Trying to solve the problem referenced in this article: https://philna.sh/blog/2018/10/23/service-workers-beware-safaris-range-request/ and here: PWA - cached video will not play in Mobile Safari (11.4)

The root problem is that we aren't able to show videos on Safari. The article says it has the fix for the issue but seems to cause another problem on Chrome. A difference in our solution is that we aren't using caching. Currently we just want to pass through the request in our service worker. Implementation looks like this:

self.addEventListener('fetch', function (event){
    if (event.request.cache === 'only-if-cached' && event.request.mode !== 'same-origin') {
        return;
    }

    if (event.request.headers.get('range')) {
        event.respondWith(returnRangeRequest(event.request));
    } else {
        event.respondWith(fetch(event.request));
    }
});

function returnRangeRequest(request) {
return fetch(request)
    .then(res => {
        return res.arrayBuffer();
    })
    .then(function(arrayBuffer) {
        var bytes = /^bytes\=(\d+)\-(\d+)?$/g.exec(
            request.headers.get('range')
        );
        if (bytes) {
            var  start = Number(bytes[1]);
            var end = Number(bytes[2]) || arrayBuffer.byteLength - 1;
            return new Response(arrayBuffer.slice(start, end + 1), {
                status: 206,
                statusText: 'Partial Content',
                headers: [
                    ['Content-Range', `bytes ${start}-${end}/${arrayBuffer.byteLength}`]
                ]
            });
        } else {
            return new Response(null, {
                status: 416,
                statusText: 'Range Not Satisfiable',
                headers: [['Content-Range', `*/${arrayBuffer.byteLength}`]]
            });
        }
    });
}

We do get an array buffer returned on the range request fetch but it has a byteLength of zero and appears to be empty. The range header actually contains "bytes=0-" and subsequent requests have a start value but no end value.

Maybe there is some feature detection we can do to determine that it's chrome and we can just call fetch regularly? I'd rather have a solution that works everywhere though. Also res is showing type:"opaque" so maybe that has something to do with it? Not quite sure what to look at next. If we can't solve the problem for Chrome I might need a different solution for Safari.

1 Answers1

2

It seems that it was the opaque response. I didn't realize that fetch was 'nocors' by default. Adding 'cors' mode and overwriting the range header seems to have allowed the rewrite to work on chrome. Sadly, it still doesn't work on Safari, but I was able to access the arrayBuffer after setting the cors values properly.

Here is the change I had to make:

var myHeaders = {};
return fetch(request, { headers: myHeaders, mode: 'cors', credentials: 'omit' })
    .then(res => {
        return res.arrayBuffer();
    })

It's important that the server respond with allowed headers. e.g.

access-control-allow-methods: GET
access-control-allow-origin: *
  • 1
    Nevermind, this does work on Safari, I just had a breakpoint set. – Uriah Blatherwick Jan 15 '19 at 22:22
  • Hi @UriahBlatherwick, where exactly did you add those 5 lines of JS specifically? – allenski Mar 03 '21 at 22:51
  • 1
    I'm afraid I don't have the code anymore. I think it would be wherever you were returning your fetch response though in your proxy. The whole thing was a bit misguided though. I was trying to recreate what is essentially a noop and the goal should have been to skip the fetch altogether because it was always impacting video delivery. I think in my code it was probably this that got replaced: return fetch(request) .then(res => { return res.arrayBuffer(); }) – Uriah Blatherwick Mar 04 '21 at 07:01