Nuxt PWA 注册服务工作者未在本地主机外部注册

Nuxt PWA register service worker not registering outside localhost

我们正在尝试托管 PWA nuxt,服务人员将仅使用

在 localhost:3000 上注册
npm start

尝试从 192.168.0.2:3000 访问它会导致以下情况:

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 - 在 chrome 调试器上可见

{
  "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": "/"
}

对于可能遇到此问题的任何人, was right; I needed a valid certificate. How to generate a local cert using MKCert

  1. 安装 MKcert 并将其添加到本地根 CA

    mkcert -install
    
  2. 为本地应用程序创建证书(在应用程序的文件夹中运行)

    mkcert 192.168.0.2
    
  3. 将证书添加到 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. 清理浏览器 cookie 和缓存

  5. 访问 https://192.168.0.2,等等:可以安装 PWA 添加

手机使用说明

奖励: 对于试图让它在移动设备上运行的人:

  1. 将您的 rootCA.pem 转换为 rootCA.crtHow to convert pem to crt:

    openssl x509 -outform der -in rootCA.pem -out rootCA.crt
    
  2. crt 证书导入您的移动设备。 How to import CRT on android mobile。 (可能因设备而异。)