vite-pwa / vite-plugin-pwa

Zero-config PWA for Vite
https://vite-pwa-org.netlify.app/
MIT License
3.19k stars 207 forks source link

Background sync does not work #739

Open VityaSchel opened 2 months ago

VityaSchel commented 2 months ago

My goal is to have PWA that can work offline and online with Vite. I've added vite-plugin-pwa and configured background sync as follows:

workbox: {
  maximumFileSizeToCacheInBytes: 5 * 1024 * 1024,
  runtimeCaching: [
    {
      urlPattern: /\/api\/.*$/,
      handler: 'NetworkFirst',
      method: 'POST',
      options: {
        backgroundSync: {
          name: 'api-queue',
          options: {
            maxRetentionTime: 72 * 60
          }
        }
      }
    },
    {
      urlPattern: /\/api\/.*$/,
      handler: 'NetworkFirst',
      method: 'PATCH',
      options: {
        backgroundSync: {
          name: 'api-queue',
          options: {
            maxRetentionTime: 72 * 60
          }
        }
      }
    },
    {
      urlPattern: /\/api\/.*$/,
      handler: 'NetworkFirst',
      method: 'DELETE',
      options: {
        backgroundSync: {
          name: 'api-queue',
          options: {
            maxRetentionTime: 72 * 60
          }
        }
      }
    },
  ]
}

Now when I make fetch request to my server when offline I expect it to immediately reject with some kind of error and add to that indexeddb queue with name 'api-queue' and automatically retry when browser is online within period of 72 hours

This is my wrapper function for making api request:

export async function changeAvatar({ userId, avatar }) {
  try {
    await fetch(apiUrl + '/user', {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        type: 'avatar',
        userId,
        avatar
      })
    })
  } catch {
    if(navigator.onLine) {
      throw new Error('Failed to change avatar')
    } else {
      toast.info('Changes will be synced when you come back online')
    }
  }
  // some calls that change avatar in local database so that user sees changes instantly
}

Which should either throw error (if user is online, for example if api is down) or show 'Changes will be synced when you come back online' toast when user is offline indicating that service worker added request to queue

However most of the times the request is simply stuck in network tab until it timeouts in like 1 minute, and promise never neither rejects nor fulfills. Other tiems it just instantly rejects with some error like NS_ERROR_UNKNOWN_HOST and correctly shows toast about pending changes. However after I connect to internet again, no requests to network are made in network tab.

More importantly, in neither of these cases, nothing is added to indexeddb workbox-background-sync which should hold requests until they fulfilled

os: macos 14.5 browser: firefox 129.0

userquin commented 2 months ago

Try replacing /\/api\/.*$/ with /^\/api\// adding it to workbox.navigateFallbackDenylist regex array: you need to exclude sw precaching handler to intercept any /api/ request.

You can also add internal plugins to the handlers, check sveltekit linked comment issue.

VityaSchel commented 2 months ago

I just realized Firefox does not support this API at all I tried it in chrome however and it looks like it still does not add anything to background sync tab of application tab fetch rejects immediately and toast is displayed correctly, but I don't see anything added to background sync tab or indexed db or cache storage

userquin commented 2 months ago

can you share the repo if public?

graphographer commented 1 month ago

I haven't yet gotten background sync to work either, but I do know that you should be getting errors because you have multiple configurations that use the same name. If you need to apply the background sync to multiple methods, I think you have make urlPattern a callback function,. Then you can check the method property on the event that's passed into it, in addition to any url regex.

I'm about to try userquin's advice and I'll report back here.

graphographer commented 1 month ago

I'm unable to get anything at all w/r/t background sync. I'm stopping my dev server(s) and have even tried turning off my WiFi. Typically I create an entity using a POST command. Then I turn off my dev server/wifi and issue the DELETE command from the app. Nothing turns up in DevTools under IndexedDB or Background sync.

Here's my Vite PWA configuration:

VitePWA({
    registerType: 'autoUpdate',
    injectRegister: false,
    injectManifest: {
        injectionPoint: undefined
    },
    devOptions: {
        enabled: true
    },
    manifest: {
        name: '...',
        theme_color: '#ffffff',
        background_color: '#ffffff',
        start_url: '/',
        display: 'standalone',
        prefer_related_applications: false,
        icons: [
            {
                src: 'pwa-64x64.png',
                sizes: '64x64',
                type: 'image/png'
            },
            {
                src: 'pwa-192x192.png',
                sizes: '192x192',
                type: 'image/png'
            },
            {
                src: 'pwa-512x512.png',
                sizes: '512x512',
                type: 'image/png',
                purpose: 'any'
            },
            {
                src: 'maskable-icon-512x512.png',
                sizes: '512x512',
                type: 'image/png',
                purpose: 'maskable'
            }
        ]
    },
    workbox: {
        globPatterns: ['**/*.{js,css,html,ico,png,svg}'],
        sourcemap: true,
        navigateFallback: null,
        navigateFallbackDenylist: [/^\/api\//],
        runtimeCaching: [
            {
                handler: 'CacheFirst',
                urlPattern: e => {
                    return e.url.toString().match(/\.(jpg|jpeg|gif|png)/);
                },
                options: {
                    cacheName: 'images-cache',
                    cacheableResponse: {
                        statuses: [0, 200]
                    }
                }
            },
            {
                handler: 'NetworkOnly',
                urlPattern: e => {
                    const { method, url } = e.request;

                    if (method !== 'POST' && method !== 'DELETE') {
                        return false;
                    }

                    return url.toString().match(/^\/api\//);
                },
                options: {
                    backgroundSync: {
                        name: 'backgroundSyncQueue',
                        options: {
                            maxRetentionTime: 24 * 60
                        }
                    }
                }
            }
        ]
        // disableDevLogs: true
    },
    pwaAssets: {
        config: true,
        overrideManifestIcons: true
    }
})
graphographer commented 1 month ago

After searching and tinkering, I came across this github issue: https://github.com/GoogleChrome/workbox/issues/2393

My tests indicate that this is pivotal bit to get background sync working, at least when running things from the Vite dev server.

If they are trying to test this feature using the dev server, my recommendation for @VityaSchel then, is to simply add forceSyncFallback: true to the options, and to make each name for each http method config have unique names.

For example

{
  handler: 'NetworkOnly',
  method: 'PUT',
  urlPattern: /\/api\//,
  options: {
      backgroundSync: {
          name: 'bg-sync-queue-PUT',
          options: {
              maxRetentionTime: 24 * 60,
              forceSyncFallback: true
          }
      }
  }
}