ternandsparrow / wild-orchid-watch-pwa

Progressive Web App for the Wild Orchid Watch citizen science project
https://app.wildorchidwatch.org
4 stars 4 forks source link

Handle local photo storage separately #69

Closed tomsaleeba closed 3 years ago

tomsaleeba commented 3 years ago

Currently we save a single record in IndexedDB that contains everything for an observation including photos. The IndexedDB best practices advise against doing that:

While IndexedDB makes is possible to store large, nested objects as a single record (and doing so is admittedly quite convenient from a developer perspective), this practice should be avoided. The reason is because when IndexedDB stores an object, it must first create a structured clone of that object, and the structured cloning process happens on the main thread. The larger the object, the longer the blocking time will be.

This means we need make the following changes:

tomsaleeba commented 3 years ago

When it comes to dealing with photos that don't have a thumbnail as part of the EXIF, we have some options:

The third option is the best for UX. We need to benchmark some options and see how they perform. The options are:

Any solution that relies on using a canvas to do the resize might not be suitable. I don't think workers can use a canvas. They might be able to use an OffscreenCanvas but it's still experimental and Safari doesn't support it.

On the observation details page, I'm not sure if we can get away with showing the thumbnail or if we want the full size image to render. Perhaps we can have a lazy-load approach where we show the thumbnail and a button that will load the full-size photo. That way, we're less likely to be loading photos and if doing so causes issues, the user can just not do that.

tomsaleeba commented 3 years ago

Some thoughts on migrating. We need to move all the existing photos out into their own records, but if the user's phone is already full, we have to be able to read/write records without killing the browser tab.

If we no longer load photos, we'd be fine. But with the expectation that all photos are thumbnails, we plan on blindly loading them safely. Perhaps we should add a sanity check during obs record load to protect against:

  1. pre-migrated environments so we don't load all photos into memory
  2. larger than expected thumbnails

According to page 27 (of the PDF, page 21 in the document) of the spec, the thumbnail will be less than 64kb: image

...but maybe it's best to verify.

Back onto the topic, if we run the migration before we let anything else load, we might be safe with memory usage.

As for how to tell if we need to do a migration, perhaps we should adopt the approach like other DB migration software. Have a list of migrations and set flags when they've been applied.

tomsaleeba commented 3 years ago

Doing the migration on 20 test observations (generated from the admin screen) takes:

tomsaleeba commented 3 years ago

We're experiencing https://github.com/localForage/localForage/issues/910 in Safari.

Test process:

  1. go to <wow-instance>/settings, e.g. https://dev.app.wildorchidwatch.org/settings (you don't need to login)
  2. set "when to sync" to "never"
  3. open the admin page by using 7 taps on the version in the menu easter-egg or go to /zzadmin
  4. if you can't scroll, tap the title of the "login" toast message 7 times to hide it for a minute
  5. scroll down to the GH-69 test card
  6. open the browser devtools console, make sure you can see debug level messages
  7. click the button to create 20 observations (this has worked all but one time for me)
  8. when the observations are created, refresh the page
  9. you'll see the migration start to run
  10. if the error is going to happen, it's as the first record is written back to the DB

Error screenshot: ios-migration-fail

Test results:

tomsaleeba commented 3 years ago

Also seen this error on LambdaTest iOS 13.3 (Safari 13.1) simulator: image

This is just after the page refresh when the migration starts to run.

tomsaleeba commented 3 years ago

When I change the code for https://github.com/localForage/localForage/blob/c1cc34f/dist/localforage.js#L1060 to:

                    transaction.onabort = function () {
                      console.warn('abort')
                        reject(req.transaction.error);
                    };
                    transaction.onerror = function () {
                      console.warn('error')
                      try {
                        var err = req.error ? req.error : req.transaction.error;
                        reject(err);
                      } catch (err2) {
                        reject(err2);
                      }
                    };

...so we stop masking the true error, we get: image

Apparently this error can be triggered by:

new WeakSet().add(1);

When looking in the built code for our obs worker, the only mentions of a WeakSet I can find come from:

./node_modules/@sentry/utils/esm/memo.js
./node_modules/lodash/lodash.js

Lodash doesn't seem to use WeakSets internally.

I can't find any use of WeakSet in our main code:

yarn serve:debug
curl http://localhost:8081/js/app.js | grep -i weakset
curl http://localhost:8081/js/vendors.js | grep -i weakset