> If a user is currently offline, we’ll hold off submitting the form for now
Be careful not to completely rely on navigator.onLine for this as your next request could be the first to fail, so you need to deal with submission errors and timeouts too (or safer: always put the data in local storage and remove once you have confirmation of a successful save).
The article seems good overall though, I can forgive leaving out some of the nitty-gritty like this to keep the main points clear.
On top of that, I'd consider adding a bit of idempotency to the form submission -- to make sure that if the request did in fact succeed even though you did not get the confirmation of a success, subsequent retry/resubmit attempts won't result in duplicate entries.
A rough way to do this would be to add a hidden UUID value in the form data that is generated once up front and used by both the client and the server to ensure that the result of the first successful request can be cached and re-returned.
Agreed, although if you look at the code, they do in fact do that - it's only removed from localStorage after receiving a 200 status response. Any other status code will leave the data in localStorage and display an error message.
The problem then becomes what happens when the server receives the request, processes and stores the data, but loses connection before the client can receive the 200. In this case, the client will assume the submission failed and will retry the submission - creating two records.
You really have to pick whether you want possible additional copies or possibly zero copies. This isn't really a problem with this particular implementation. I think it's interesting to have the possibility of additional copies versus the standard web form behavior of at-most-one copy.
> You really have to pick whether you want possible additional copies or possibly zero copies.
You really don't. You can, as mentioned above, have a hidden UUID field in the form that's used serverside to deduplicate submissions within a given timeframe.
That wouldn't be a successful request... if the user gets an error from your application, they know to try again.
(Or more likely and more on-topic, for the offline-friendly form if the application gets the error first, the application could be smart enough to try again...)
The UUID doesn't guarantee anything other than capability to prevent duplicate records from duplicate submissions. Something else has to be responsible to make sure the submission is not abandoned without user input, unless a 200 or other successful status is received.
Standard web forms don't guarantee at-most-once. If the connection fails when the response is sent, the user thinks it failed and retries, submitting another request and causing another record creation.
If you need to support more browsers it isn't difficult. Just make a request to a small static asset or a health route and keep track of the online status yourself. You could even combine this with `navigator.onLine` for the best of both worlds.
I would just submit the form and check if it had a successful response. It can still be unreliable on a flaky network (the exactly once issue), but anyway it's cheaper on resources.
Is listening for the event really expensive? Honest question. We update the UI when it happens, so I don't think relying on the success/failure of network requests is reasonable for us anyways.
It's a shame the API's aren't better for this situation, pinging a static asset to determine network status is not good for battery life on smartphones if it's going to occur for any length of time.
The real problem is that there is no way to reliably know just how far your connection goes, without pinging. You might be able to connect to URLs on a university WAN, but not to the rest of the internet, for example. In this situation, as far as uni websites are concerned, you are online, but for Youtube, you are effectively offline. The concept of "online" does not have a trivial boolean definition.
It also badly impacts UX, because on slow networks the request-response roundtrip even for small static asset may be very long, so that the time needed for the feedback in the form will be in fact doubled.
I don't have any citations, it's just my understanding that switching on the mobile radio is a major battery drain, and should be avoided as much as possible.
As a user, instead of the "we will send the data once you are online" message, I would prefer "you seem offline, please try again once you are online" kind of approach. I would not like to rely on a background worker on a mobile device and I would not know whether my form has been sent, a timeout occured, what happens if I refresh the tab, what happens in a poor connection (seems online but can't connect to internet). Therefore, I would like to be just notified about the connection issue and try again once I got out of the metro.
If this tied into the platforms' notification systems, it could get around that; a notification appears for an unsubmitted form, and hangs around until it has successfully gone through, possibly with another notification of success, or failure after a certain timeout.
If you delete it as soon as the user comes online, wouldn't their information be safe? No one else would've been able to get the information because they were offline, right? And so it's not still saved if the user exits the webpage before they return online, you can use SessionStorage.
Any situation where a password is persisted, especially in plaintext, can present a security risk. It is one of those things where you don't just guess that it is "safe enough" unless you have thoroughly proven it to be so.
It's more that you put the password on the disk in unencrypted format without telling the user. For instance, chrome stores them in sqlite databases which you can just open and select from:
Your disk could be encrypted, but not everyone's will be. It's better to just localStorage as it was intended.
Plus if you introduce a bug later down the line, you might not prune older localStorage entries, meaning they will stay there for much longer than you want. AND the user may not revisit your site ever again after going offline, which doesn't give you an opportunity to prune it.
No one's going to have a lot of confidence if you ask them to store your sensitive info in plaintext. Just don't do it. The convenience or UX isn't worth it.
You can have a malware that copies anything stored in the local storage to its own database and transmits to a server as soon as the user goes back online. local storage is just as vulnerable to being read by JavaScript as cookies are.
local storage can be read using JavaScript from the same domain if you control all the JS on the domain, then this shouldn't be a problem. But if any other code is executed (i.e. via injection), they will be able to access the local storage
A persistent threat can stay on a device even when it's offline.
Nobody here is saying that an attacker can easily access your domain's localstorage, but just expressing the sentiment that "storing plaintext passwords is bad in almost any case".
Just like you can store plaintext passwords in your application database, and theoretically they are safe, but if a bad guy gets in your users are screwed, not just on your site but on others.
Exactly. In the very worst case, if local storage is to be used for storing password, it should be stored with asymmetric cryptography so that encryption is done with public key, but decryption can only be done with private key which is stored in the server (And not on the client). With a proper key rotation scheme, this could be an OK solution.
> If you dont want to use ajax to send your form submission, another solution would be to just repopulate the form fields with the stored data, then calling form.submit()
What are the pros and cons between these 2 approaches?
It mostly depends on the server receiving the form submission. If your server is setup to receive form posts, constructing a form post in javascript would be really cumbersome, so just repopulating the form and resubmitting would be the easiest. But if your server handles JSON, you can just take your stored data and post it right to the server.
Instead of serializing the form, jquery can also just serialize a flat js object. I wouldn't call that complicated enough to make a distinction. (it's probably really easy in modern plain js too)
In your example, you'll also need to change the headers because $.ajax will set content type to application/json, which wouldn't work if your server is setup to receive x-www-form-urlencoded or multipart/form-data.
Starting to get complicated now, might be easier to just do $("#form").submit().
Sure, it's more complicated than $("#form").submit(). But it's close enough that other considerations become more important (e.g. do I want a page reload, do I have to populate the form first etc)
Yeah, but the returned data is completely different. On one case, you'd expect a new page which the browser would render. In the other, a response which is handled via JS.
You could also use caching headers to know whether the client is about to update an object older than the current on on the server in the case where the form is being used to "edit" an object. Hopefully the server would respond to the If-None-Match header with the object version and give you a 304: Not Modified response... yay, you're free to upload the data! Otherwise you might want to notify the user that their changes are out-dated when the server returns the newer version of the object.
How do you signal to the user when the form has been commited to a server? Would you have to create a queue view that shows all the pending modifications and send push notifications when an item in the queue has been committed?
Ok, let me just open up my developer console using F12 and check the Offline box on the network panel. So glad I took the extra 20 seconds to do that before reading part of the article and navigating away from this "distraction".
class Foo {
checkStorage = () => {
// Do check storage stuff here
}
}
And the language will take care of binding the function. (Really, it's just doing `this.checkStorage = this.checkStorage.bind(this)` in the constructor).
Be careful not to completely rely on navigator.onLine for this as your next request could be the first to fail, so you need to deal with submission errors and timeouts too (or safer: always put the data in local storage and remove once you have confirmation of a successful save).
The article seems good overall though, I can forgive leaving out some of the nitty-gritty like this to keep the main points clear.