Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle long sessions #87

Closed
Rich-Harris opened this issue Oct 29, 2020 · 14 comments
Closed

Handle long sessions #87

Rich-Harris opened this issue Oct 29, 2020 · 14 comments
Labels
feature request New feature or request
Milestone

Comments

@Rich-Harris
Copy link
Member

In Second-guessing the modern web, Tom MacWright makes several good points (I responded here).

The biggest, to me, is this one:

User sessions are surprisingly long: someone might have your website open in a tab for weeks at a time. I’ve seen it happen. So if they open the ‘about page’, keep the tab open for a week, and then request the ‘home page’, then the home page that they request is dictated by the index bundle that they downloaded last week. This is a deeply weird and under-discussed situation.

I wrote:

It's an excellent point that isn't really being addressed, though (as Tom acknowledges) it's really just exacerbating a problem that was always there. I think there are solutions to it — we can iterate on the 'index bundle' approach, we could include the site version in a cookie and use that to show actionable feedback if there's a mismatch — but we do need to spend time on it.

It would be great if there was some mechanism for solving (or at least alleviating) this problem. I think the 'include the site version in a cookie' approach is the most promising, but I haven't thought through any of the details.

@Rich-Harris Rich-Harris added this to the 1.0 milestone Oct 29, 2020
@antony
Copy link
Member

antony commented Oct 29, 2020

This is very important to me because before my horrendous workaround we regularly broke checkout in the middle of people using it when we deployed. It was not pleasant and got loads of complaints.

The way we do it now which is really, not fun at all, is:

  • On build, make a zip of the bundle and upload it to the site
  • On build, download the previous build's zip and unzip it into the sapper/build directory alongside the current build.

It's crap and doesn't work if we deploy twice in quick succession as you have people still using the assets from the 1st deploy, when only the 2nd and 3rd deployment's files are present, but it's better than breaking people in the middle of purchasing every single deploy.

I've read a bunch of stuff around solving this before my solution and there is some prior art from other frameworks/libraries:

  • If the file doesn't exist any more, reload the page on fetch (not great as they lose their currently inputted data when they click Pay) and have to start over
  • Show a site updated banner they have to click (a wonderful experience in a tiny payment details iframe at checkout I'm sure you'll agree)
  • Add a reload button on the 500 page which occurs (lol)
  • rely on the cdn to cache things for a very very long time (which Vercel doesn't, it wipes the whole thing each deploy - but they have said they are changing this a bit soon)
  • Use a service worker to keep the site the user first visited in their browser "available" until they close it (but if the shape of your data changes when talking to your API it'll probably break anyway - not sure how this could be avoided)

Yeah, it's hard to think of a really good solution.

@benmccann
Copy link
Member

benmccann commented Dec 14, 2020

I wonder if we could make any navigation be a server-side navigation instead of client-side navigation after you've had your page open for some configurable amount of time (e.g. > 24 hrs, if a new version of the app has been deployed, etc.). We're already talking about building a toggle between client-side and server-side navigation in #231

I think the best solution will probably depend on use case. We should make it possible that users can implement it how they like, but probably provide a good default option(s)

@Rich-Harris
Copy link
Member Author

Yeah, I like that idea, and I think it'd be fairly easy to establish a mechanism for knowing if a new version has been deployed (something as simple as a /__svelte__.json file with a timestamp that we check every few minutes, or something). It doesn't completely solve the problem though, as something like a fetch (inside or outside a load) to a differently-versioned endpoint could still break things, without navigation occurring.

Perhaps if we exposed that mechanism via a store, we could make it easy for developers to show a toast:

<script>
  import Toast from '$components/Toast.svelte';
  import { updated } = '$app/stores';
</script>

{#if $updated}
  <Toast>
    This version of the app is out of date — please
    <button on:click={() => location.reload()}>reload</button>
    to continue using it
  </Toast>
{/if}

@benmccann
Copy link
Member

For posterity, here was the corresponding Sapper issue: sveltejs/sapper#389

@oodavid
Copy link

oodavid commented Mar 30, 2021

I could see users falling into various camps as to how to approach this issue.

For us, we have an "your app is out of date" message. Which is great for "dashboard" type sites, but not so good for blogs etc.

Screenshot 2021-03-30 at 09 48 26

For Svelte, I've seen this handled with a navigator.serviceWorker statechange event. Example code:

https://github.com/tobinbradley/Mecklenburg-County-GeoPortal/blob/c22980c0915e48be18e292cb5f4d9cc221e41a09/src/js/registerServiceWorker.js

I'd love to see this approach well documented in the $service-worker module.

My biggest concern with this issue isn't the the inner-workings, but rather the learning-curve. Most users will have to experience the issue before they start to look for a solution. For many it will be a heisenbug. I'm tempted to request that the default behaviour should be a little intrusive.

Flutter has a debugShowCheckedModeBanner flag, which enables a bunch of debug features. One of the side-effects is that a little "debug" ribbon is visible on all views. This is part of the onboarding experience. It helps early users dig a little deeper into the intention of their work. This could be useful for this particular issue.

@CaptainCodeman
Copy link
Contributor

I've used a web-socket subscription (using Firebase RTDB) to a release version or timestamp to trigger the service-worker update check.

That can then force a refresh or prompt the user, depending on the app itself and what they may be doing with it.

Updates that don't require all the clients to update can use the regular service-worker check (when the app is restarted) and the release version or timestamp only updated when you need to force everyone to update to the latest ASAP.

@sserdyuk
Copy link

sserdyuk commented Apr 20, 2021

This problem exists for any web app that uses chunking with timestamps. For example, we have the same issue with Angular & Webpack. I haven't really solved it yet but I thought an elegant way of doing it would be a service worker that receive a list of chunks from the bundler and precaches them in background. This way the app is cached as one consistent build and continues working even if a new build has been uploaded. Just an idea.

@Rich-Harris Rich-Harris modified the milestones: 1.0, post-1.0 May 1, 2021
@Nick-Mazuk
Copy link
Contributor

Would it help if the page is refreshed in the background? You could probably use the Page Visibility API to detect if a user is viewing the site. If not (e.g., they're on another tab) and there's a new version, force a reload. Then, the refresh is hidden to the user.

https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API

Don't think this will completely solve the issue (e.g., antony's checkout example above), but it should help limit the need for a "refresh the page" toast.

@WaltzingPenguin
Copy link

WaltzingPenguin commented May 5, 2021

I'm worried that a lot of suggestions in this thread presuppose an active server. I hope the use case of writing docs in SvelteKit, exporting the project to HTML, and throwing the result up on Surge/Netlify/etc won't get second class treatment.

Simply reloading the page on navigation if the required files cannot be imported covers a lot of ground. The default code splitting is done on page boundaries, so this is when the error occurs most frequently. The different URL means we should be able to reload without impact; the only times this isn't true would also break if someone opened that link in a new tab. No polling or version numbers are required.

The big cases where this approach fails:

  • Dynamic imports within the same page - This can be mitigated by making it easier to prefetch these chunks. If it's already loaded in to the browser cache, it doesn't matter if it disappears from the server.
  • API incompatibility - I don't think a generic solution will ever work for this. The needs for applications vary a lot here.

@tgecho
Copy link

tgecho commented May 6, 2021

The way I’ve usually handled this is to upload the pre-generated chunks to a separate static file service (usually S3). With filenames containing the MD5 of their contents, the chunks from multiple versions can coexist as long as needed, and older unchanged chunks can remain cached downstream.

It’s slightly messy operationally, but for my purposes the simplicity and robustness is well worth it. Unfortunately, I’m not sure if this approach can be abstracted in a portable way.

@sserdyuk
Copy link

sserdyuk commented May 6, 2021

I think keeping old versions of chunks around is a working but highly inconvenient solution because this sort of compound versioning isn't natively available from (any that I know of) cloud providers.

I think precaching chunks is the most elegant way. As long as they have the same or longer (than the html page itself) cache retention set, it will guarantee that full code tree is cached in the browser. If we know the chunk list from the bundler, we can inject <link rel="prefetch" href="foo.js"> into the html header to achieve that effect.

@phelbas
Copy link

phelbas commented Jul 15, 2021

Not seen mentioned here is making sure whatever solution for this works also for mobile progressive web apps. I've had trouble even intercepting errors and putting out a message that invokes window.location.reload(true). If browser reload is not working or the message says "reload the page" and they are in a PWA, it is confusing. With service workers and so on things get really difficult really quickly. See: https://deanhume.com/displaying-a-new-version-available-progressive-web-app/

@mquandalle
Copy link

For reference I was able to port my Sapper workaround into SvelteKit without much change (cf. sveltejs/sapper#389 (comment)). The idea remain to fetch every 10 seconds a static file mysite.com/_version containing a timestamp of the last deployment, and if the timestamp has changed reload the app on the next page navigation — details are in the sapper thread.

The only change was to put the _version file in the static/ directory using

"scripts": {
  "postinstall": "date '+%s%N' > static/_version"
}

@Rich-Harris
Copy link
Member Author

Closed via #3412 (thanks @pz-mxu!) I've opened #3666 and #3667 as potential follow-ups.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request New feature or request
Projects
None yet
Development

No branches or pull requests