Search code examples
nuxt.jsprogressive-web-appsservice-worker

Nuxt PWA register service worker not registering outside localhost


We are trying to host a PWA nuxt and the service worker will register on localhost:3000 only using

npm start

Trying to access it from 192.168.0.2:3000 will cause the following :

Page is not served from a secure origin No matching service worker detected. You may need to reload the page, or check that the scope of the service worker for the current page encloses the scope and start URL from the manifest.

nuxt.config.js

    export default {
    // Global page headers: https://go.nuxtjs.dev/config-head
    head: {
        title: 'ONX',
        htmlAttrs: {
            lang: 'en'
        },
        meta: [
            { charset: 'utf-8' },
            { name: 'viewport', content: 'width=device-width, initial-scale=1' },
            { hid: 'description', name: 'description', content: '' },
            { name: 'format-detection', content: 'telephone=no' }
        ],
        link: [
            { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
        ]
    },


    // Global CSS: https://go.nuxtjs.dev/config-css
    css: [],

    // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
    plugins: [

        //{src: '~/plugins/VueCtkDateTimePicker.js', ssr: false }, // datepicker plugin here
        { src: '~/plugins/DateTimePicker.js' },
        { src: '~/plugins/VueTabulator.js', mode: 'client', ssr: false }

        // { src: '~/plugins/Filters.js' }
    ],

    // Auto import components: https://go.nuxtjs.dev/config-components
    components: true,

    // Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
    buildModules: [
        '@nuxtjs/device',
        '@nuxtjs/pwa',
    ],

    // Modules: https://go.nuxtjs.dev/config-modules
    modules: [
        // https://go.nuxtjs.dev/bootstrap
        'bootstrap-vue/nuxt',
        '@nuxtjs/axios',
        '@nuxtjs/proxy',
        '@nuxt/content',
    ],

    proxy: {
        '/api': {
            target: 'http://target:81',
            
            pathRewrite: {
                '^/api': '/',
            },
        },
        changeOrigin: true,
    },
    bootstrapVue: {
        // Install the `IconsPlugin` plugin (in addition to `BootstrapVue` plugin)
        icons: true
    },
    // Build Configuration: https://go.nuxtjs.dev/config-build
    build: {
        extend(config, ctx) {
            if (ctx.isDev) {
                config.devtool = ctx.isClient ? 'source-map' : 'inline-source-map'
            }
        },
        
    },

    server: {
        host: '0.0.0.0',
        port: 8000, // default: 3000
    },

    pwa: {
  
        
        manifest: {
            "short_name": "my_project",
            "name": "my_project",
            "background_color": "white",
            "display": "standalone",
            "scope": "/",
            "theme_color": "white"
        },


    }

}

sw.js

importScripts(...[options.workboxURL, ...options.importScripts])

initWorkbox(workbox, options)
workboxExtensions(workbox, options)
precacheAssets(workbox, options)
cachingExtensions(workbox, options)
runtimeCaching(workbox, options)
offlinePage(workbox, options)
routingExtensions(workbox, options)

function getProp(obj, prop) {
  return prop.split('.').reduce((p, c) => p[c], obj)
}

function initWorkbox(workbox, options) {
  if (options.config) {
    // Set workbox config
    workbox.setConfig(options.config)
  }

  if (options.cacheNames) {
    // Set workbox cache names
    workbox.core.setCacheNameDetails(options.cacheNames)
  }

  if (options.clientsClaim) {
    // Start controlling any existing clients as soon as it activates
    workbox.core.clientsClaim()
  }

  if (options.skipWaiting) {
    workbox.core.skipWaiting()
  }

  if (options.cleanupOutdatedCaches) {
    workbox.precaching.cleanupOutdatedCaches()
  }

  if (options.offlineAnalytics) {
    // Enable offline Google Analytics tracking
    workbox.googleAnalytics.initialize()
  }
}

function precacheAssets(workbox, options) {
  if (options.preCaching.length) {
    workbox.precaching.precacheAndRoute(options.preCaching, options.cacheOptions)
  }
}


function runtimeCaching(workbox, options) {
  const requestInterceptor = {
    requestWillFetch({ request }) {
      if (request.cache === 'only-if-cached' && request.mode === 'no-cors') {
        return new Request(request.url, { ...request, cache: 'default', mode: 'no-cors' })
      }
      return request
    },
    fetchDidFail(ctx) {
      ctx.error.message =
        '[workbox] Network request for ' + ctx.request.url + ' threw an error: ' + ctx.error.message
      console.error(ctx.error, 'Details:', ctx)
    },
    handlerDidError(ctx) {
      ctx.error.message =
        `[workbox] Network handler threw an error: ` + ctx.error.message
      console.error(ctx.error, 'Details:', ctx)
      return null
    }
  }

  for (const entry of options.runtimeCaching) {
    const urlPattern = new RegExp(entry.urlPattern)
    const method = entry.method || 'GET'

    const plugins = (entry.strategyPlugins || [])
      .map(p => new (getProp(workbox, p.use))(...p.config))

    plugins.unshift(requestInterceptor)

    const strategyOptions = { ...entry.strategyOptions, plugins }

    const strategy = new workbox.strategies[entry.handler](strategyOptions)

    workbox.routing.registerRoute(urlPattern, strategy, method)
  }
}

function offlinePage(workbox, options) {
  if (options.offlinePage) {
    // Register router handler for offlinePage
    workbox.routing.registerRoute(new RegExp(options.pagesURLPattern), ({ request, event }) => {
      const strategy = new workbox.strategies[options.offlineStrategy]
      return strategy
        .handle({ request, event })
        .catch(() => caches.match(options.offlinePage))
    })
  }
}

function workboxExtensions(workbox, options) {
  
}

function cachingExtensions(workbox, options) {
  
}

function routingExtensions(workbox, options) {
  
}


manifest.json - visible on chrome debugger

{
  "name": "my_project",
  "short_name": "my_project",
  "icons": [
    {
      "src": "/_nuxt/icons/icon_64x64.64bc82.png",
      "sizes": "64x64",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/_nuxt/icons/icon_120x120.64bc82.png",
      "sizes": "120x120",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/_nuxt/icons/icon_144x144.64bc82.png",
      "sizes": "144x144",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/_nuxt/icons/icon_152x152.64bc82.png",
      "sizes": "152x152",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/_nuxt/icons/icon_192x192.64bc82.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/_nuxt/icons/icon_384x384.64bc82.png",
      "sizes": "384x384",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/_nuxt/icons/icon_512x512.64bc82.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ],
  "start_url": "/?standalone=true",
  "display": "standalone",
  "background_color": "white",
  "theme_color": "white",
  "lang": "en",
  "scope": "/"
}

Solution

  • For anyone who may face this issue, @Lawrence Cherone's comment was right; I needed a valid certificate. How to generate a local cert using MKCert.

    1. Install and add MKcert to local root CA

      mkcert -install
      
    2. Create cert for local application (in the folder of the app run)

      mkcert 192.168.0.2
      
    3. Add the cert to nuxt.config.js

      server: {
         https: {
             key: fs.readFileSync(path.resolve(__dirname, '192.168.0.2-key.pem')),
             cert: fs.readFileSync(path.resolve(__dirname, '192.168.0.2.pem'))
         },
      }
      
    4. Clean your browser cookies and cache

    5. Access https://192.168.0.2, et voilà: the PWA add can be installed

    Mobile Instructions

    Bonus: For people trying to get it to work on a mobile device:

    1. Convert your rootCA.pem to rootCA.crt. How to convert pem to crt:

      openssl x509 -outform der -in rootCA.pem -out rootCA.crt
      
    2. Import the crt cert to your mobile device. How to import CRT on android mobile. (May vary depending on the device.)