Issues when downloading a file with a service worker and ReadableStream - download

I did some testing and I have some problems with streaming a file meant to be downloaded (with Content-Disposition header set to attachment in the response). The following behaviour is common to both Firefox et Chrome and I'll call 'browser' the entity that executes code that is not code written by the user.
The stream starts being consumed straight away when the response is sent back with event.respondWith(), meaning that while the popup appears to ask the user if he wants and where to download the file or if he simply wants to refuse the download, the stream is being consumed and pulling data non-stop. This seems like a crazy behavior, is it intended, or a bug in both browsers?
If the user refuses the download or accept and cancel it later, the browser just stops consuming the stream and that's it. It never calls cancel() on its ReadableStreamDefaultReader instance, nor does it call releaseLock(). So the stream just pull data from the underlying source until the queue is full and wait there without knowing anything. How are we supposed to deal with it?
From the Streams specification (https://streams.spec.whatwg.org/#rs-cancel):
The cancel method cancels the stream, signaling a loss of interest in
the stream by a consumer. The supplied reason argument will be given
to the underlying source’s cancel() method, which might or might not
use it.
So I think the browser should call cancel on its ReadableStreamDefaultReader instance but neither Firefox nor Chrome does.
Moreover, if I call the error method on the stream's underlying ReadableStreamDefaultController instance, the lock is still never released by the browser.

Related

Firefox Extension API - permissions.request may only be called from a user input handler?

I'm using the Firefox permissions API documented HERE
I'm having a problem with the request method, wherein all of my permissions requests result in:
Error: permissions.request may only be called from a user input handler
You can produce this in firefox by debugging any addon or extension and entering browser.permissions.request({origins: ["https://google.com/*"]}) into the console.
I find it hard to swallow that a permissions request must always have a user input event callback in the parent stack trace. I'm using Vue.js, and my Permissions are due to user interaction, but my user interactions are decoupled from the events they trigger.
What counts as a user input handler?
Why does it work like this?
Is there a good work-around?
Is there a good work-around"
I'd like to add onto Andrew's answer with some code examples.
As it turns out, promise chains destroy the browser's notion of what is and isn't triggered by a user input handler. Take the code below, for example:
document.getElementById('foo').addEventListener('click', event => {
browser.permissions.request({origins: ["https://google.com/*"]})
})
This code works as expected. I originally assumed that it was Vue.js's unique event handling framework that was eating my "browser events", such as when you do <div #click="somefunc"></div>. This actually works just fine, as long as you put your permissions request in somefunc.
Now it gets fun. If you replace your permissions request with a promise that resolves and then does a permissions request, VIOLA!
Promise.resolve('foobar').then(foobar => {
browser.permissions.request({origins: ["https://google.com/*"]})
})
Results in:
Error: permissions.request may only be called from a user input handler
Why does this happen?
I'm going to guess it has to do with stack traces. Firefox can't detect that a permission came from a stack with a user input event at the root if the permissions request happens in a promise chain.
I consider this to be a pretty egregious design choice. My app is large (>4K LoC) and to keep it simple I rely on promise chains to keep the spaghetti away. This has crippled my ability to write clean code, and as a result, I've moved from asking for optional_permissions and then prompting the user for permissions only when needed to just being overly permissive at the time of installation.
GG, Firefox.
What counts as a user input handler?
A DOM event handler that corresponds to user input (e.g., target.addEventHandler("click", ...) or a WebExtension event listener that corresponds to user input (e.g., browser.browserAction.onClicked.addListener(...)
Why does it work like this?
Partly for basic UX (if a user is not directly interacting with an extension and a prompt for the extension suddenly prompts up, it can easily confuse them), but also to avoid clickjacking attacks where the prompt is put up at a carefully chosen moment when the user is likely to be expecting some unrelated prompt.
Is there a good work-around?
I think just organizing your code so that you request permissions from a user input handler is probably your best bet.

Play data while downloading

I am trying do play songs from soundcloud, which is working fine for one exception: when the response handler is called, the download of the file is allready complete. I'd like to start playing the file directly after the download started, but i have no clue how to access the data before the response handler gets called. Accessing the data ln the progress handler would be nice, but i need a hint on how to do it.
If you write your own download code using NSURLConnection and
initWithRequest:delegate:startImmediately:, the delegate methods (e.g.
connection:didReceiveData:) will be called as the data becomes available.
You risk running out of data if you try to play the sound as it is downloaded. You should probably implement a buffering system where you download enough extra data for several seconds of playback before you start playing. That way you can smooth over short "stutters" in the download.
I suggest you at least two options to perform what you want:
1. AVPlayer supports playing a file from http. If you download a static file - you can just ask a player to stream over http.
2. You can download a small chunks of file (5 MB at once, for example) and append them to the result file or write directly into memory buffer. You can download a file chunk of specified size by just adding a Content-Range header with an offset you need. (see RFC, 14.16 Content-Range for more specific info). This method requires server to support partial downloads, but in nowadays it is harder to find a sever that does not support this =) Alamofire easily allows you to do that.

BackgroundTransferService: what are some of the details of its operation?

I kick off 5 uploads (of varying size) via the BackgroundTransferService. I have the following questions about the way it works:
It seems that on the emulator it does 2 uploads at a time. Is this how it works on the actual device? Can I programmatically change this behavior?
Can I count on the uploads going out in the order that I submitted them? I seem to be getting conflicting results in my testing.
When I inspect the BackgroundTransferService in my application, does it contain requests from other apps as well or just mine?
Do I need to reconnect events for all the BackgroundTransferRequest objects when coming back from being tombstoned? What about coming back from being reactivated?
Do I need to disconnect events from the BackgroundTransferRequest when I remove it from the BackgroundTransferService.Requests collection?
When I try to upload a non-existing URL:Port (on the localhost), the TransferStatus is reported as WaitingForNonVoiceBlockingNetwork. The upload never actually completes/fails. Is this how it is on the device? Should I remove the request when it encounters this TransferStatus?
You cannot influence the behaviour of the BTS. If you don't like the way it works you can write the transfer functionality as part of your own application but then you have to handle running in the background yourself.
There is no guarantee on sequence.
The BTS may be handling requests from other apps but you won't be able to see the details. Requests() will only return details for your app.
Surely a quick test will tell you this.
It's good practice to.
Have you checked the TransferError property whe you reach this situation? This is a perfectly valild status in other situations and so you shouldn't treat this as a automatic fail.

[OSX Core Foundation]How can I asynchronously upload a file though HTTP and get a callback called while sending bytes of the stream?

on MacOSX, with Core Foundation, I want to upload a large file (several hundreds of megabytes) to a remote server through a REST API.
Since the file is big and I also need to give the user some feedback, I want to implement a resume upload feature and gives the user feedback on the number of bytes written.
I first followed the Apple Guide for CFNetwork programming: http://developer.apple.com/library/ios/#documentation/Networking/Conceptual/CFNetwork/CFFTPTasks/CFFTPTasks.html#//apple_ref/doc/uid/TP30001132-CH9-SW1
But the asynchronous upload of file is for FTP only.
I tried to use CFReadStreamCreateForHTTPRequest butI only got callbacks on response.
I tried with CFReadStreamCreateForHTTPStreamedRequest and I set a delegate on the ReadStreamRef body parameter but it is never called even though I open the stream before actually scheduling it on the runloop.
If somebody has some tips about how to do it, it would be great.
Thanks a lot!
--
Rémy
I got an answer here: http://lists.apple.com/archives/macnetworkprog/2010/Dec/msg00000.html.
CFReadStreamCreateForHTTPStreamedRequest is the good function to use.
For upload feedback, I use a timer scheduled on the runloop when creating the request:
CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 10.0, 0, 0, ...);
For resume, there are two steps.
Seek the local content stream at the good offset
Once the local content stream created (but not yet opened), I can seek in it using
CFReadStreamSetProperty(content_stream, kCFStreamPropertyFileCurrentOffset, uploaded_length);
Configure http headers
I don't have webdav style remote server, so I use HTTP Range headers to inform the server about which part of the file I want to upload. This step depends on what the remote server expects.
CFHTTPMessageSetHeaderFieldValue(request_headers, CFSTR("Range"), content_range_value);
Hope this will help.

How to "stream" json from server to client using javascript

I'm familiar enough with Ajax and JSON that I can send a request and get a parse a JSON request. Ideally I'd like to receive multiple response to periodically update a progress bar. This way clients can have a positive feedback.
I've heard of JSON streams but have not found a good resource on how to implement this. Does anyone know of a good resource or how to do this?
JSON is just yet another format of data going over the HTTP protocol (like text, html, pdf, etc). You are probably referring to cometd.
This allows you to open a persistent connection and push data from the server to the client (ie stream it). Any format is valid to push, the client just needs to understand it.
Found a technique called page streaming.
Basically you write <script>some js</script> entries into the persistent connection and flush them into the network interface. As browser receives that, it will parse and execute the script.
Try looking into the library "comet." It's implements what's known as "reverse AJAX." It'll allow you to send events from the server to the client easily.
The polling suggestion made just before mine, is also perfectly valid.
<script language="JavaScript">
function doSomething() {
// do something here...
}
setInterval('doSomething()',10000);
<script>
That will call a function every 10 seconds. So you can poll the server every 10 seconds (or 1 second) to get a response on the status of whatever event you're trying to track. Simply put your AJAX call inside that function and it'll send.

Resources