Web App and Logo

How do you make a web page into a web app so that it can be used on mobile devices without an internet connection?

  • The web page needs to be responsive so that it looks good on narrow (phones) or wide (tablets & desktop) screens.
  • Background synchronization and push notifications via service workers.
  • Cached assets such as html, images, css, and JavaScript
  • Seamless updates to assetts and data after going back online
  • Offline logic to handle situations where an internet connection is not available.
  • Manifest file with metadata about the app such as its name, icon, and screen orientation

What I’m focusing on today is specifically how to make a web page into a Progressive Web Application, often referred to as PWA. The first step is to create a manifest file and link to it. Doing this lets the browser know that the current web page may be installed as a progressive web application.

With webpack, I had used a plugin called favicons-webpack-plugin that would generate the manifest file for me as well as a large number of icons for different mobile devices. Now we are using Vite. It appears they have their own plugin called Vite PWA available on npm as vite-plugin-pwa.

Vite
Vite PWA

Before we do that, let’s prompt Microsoft Designers’ Image Creator to create a logo.

  • create a circular logo for a mobile app called PeriPlux. The application focuses on scavenger huts using GPS. It lets people create and hide 3D objects. People who find the objects have to solve challenges like riddles and puzzles to reveal a clue to the next item. The app focuses a lot of creativity and the ability to trade items that were found. The logo should spark a sense of fun and adventure. It should also be simple enough that it can be drawn in the sand with a finger. PeriPlux is derived from the word Periplus, and ancient greek word that refers to a navigational document or voyage around a coastline.

Wow. I’m actually a little impressed. Some of these remind me of the “Dial-A-Pirate” wheel from The Secret of Monkey Island. It was an early form of DRM in the 90’s. I could make something similar that people could find and then use to complete various challenges.

Hillsfar
Dial-A-Pirate Code Wheel
Stratego

Let’s try to add a little more onto that image.

  • create a circular logo for a mobile app called PeriPlux. The application focuses on scavenger huts using GPS. It lets people create and hide 3D objects. People who find the objects have to solve challenges like riddles and puzzles to reveal a clue to the next item. The app focuses a lot of creativity and the ability to trade items that were found. The logo should spark a sense of fun and adventure. It should also be simple enough that it can be drawn in the sand with a finger. PeriPlux is derived from the word Periplus, and ancient greek word that refers to a navigational document or voyage around a coastline. Just draw the logo itself. It should be a line drawing. It’s okay to add some color to color in the lines.

That last image is simple enough. A location marker on top of a compass. It’s so simple I’m thinking, “Why didn’t I think of that?”.

PeriPlux Logo

Let’s see if we can take it a step further.

  • Create a circular logo of a landmark pin in front of a compass. The line-art should be a simple cartoon-like drawing that a child could draw in the sand with their finger. The landmark pin sort of resembles an upside-down teardrop that is usually red with a hole in the center. The compass doesn’t need to have the markings on it such as N, W, S, E, or any degrees. People simply need to see it and recognize that it is a compass. Nothing else should appear except for the logo. The logo is from a GPS-based scavenger hunt game on a mobile phone.

Much better. Much simpler. Although the AI seems to have trouble with the location of cardinal points in a few images.

Let’s try cartoonifying what we’ve already got with Vance AI Toongineer Cartoonizer in the style of Ghibli.

Toongineer Cartoonizer

Meh. Not sure. Let’s stick with what we got and move on with the web manifest.

/* eslint-env node */
import { defineConfig } from 'vite';
import path from 'path';
import compression from 'vite-plugin-compression';
import { readFileSync } from 'fs';
import { VitePWA } from 'vite-plugin-pwa'

const key = readFileSync('./key.pem');
const cert = readFileSync('./cert.pem');

export default defineConfig({
  root: path.resolve(__dirname, 'src'),
  server: {
    base: '',
    https: {
      key,
      cert,
    },
  },
  build: {
    sourcemap: true,
    outDir: path.resolve(__dirname, 'dist'),
    rollupOptions: {
      output: {
        chunkFileNames: 'assets/[name]-[hash].js',
      },
    },
  },
  plugins: [
    compression({
      algorithm: 'brotliCompress',
      ext: '.br',
      threshold: 1040,
      deleteOriginFile: false,
    }),
    VitePWA({
      manifest: {
        name: "PeriPlux",
        short_name: "PeriPlux",
        theme_color: "#ffffff",
        icons: [{
          src: "src/images/pwa/pwa-64x64.png",
          sizes: "64x64",
          type: "image/png"
        }]
      }
    })
  ],
});

Something is off. Starting the app isn’t showing anything different. Maybe it’s a production-only plugin. Let’s do a build…

Yes. We got our web manifest along with a bunch of other files. A service worker, a file to register the service worker, and something called workbox. None of the files were compressed. I lowered the threshold to one and got the registerSW.js file to comrpess. Perhaps something is special that the other files are outside of the compression plugins scope.

Another thing that I’m noticing is that the image didn’t get built or copied.

In webpack, I had a plugin (copy-webpack-plugin) that let me copy files during the build process. Since Vite uses rollup, we can use the plugin rollup-plugin-copy.

copy({
      targets: [
        { src: 'src/images/pwa/**/*', dest: 'dist/images/pwa' }
      ]
    })

I’m building, but nothing is copying. Does Vite not know where the src and dist folders are?

copy({
      targets: [
        {
          src: path.resolve(__dirname, 'src/images/pwa/**/*'),
          dest: path.resolve(__dirname, 'dist/images/pwa')
        }
      ]
    })

Still nothing… maybe path resolve can’t deal with folders that don’t exist or glob patterns?

    copy({
      targets: [
        {
          src: path.resolve(__dirname, 'src') + '/images/pwa/**/*',
          dest: path.resolve(__dirname, 'dist') + '/images/pwa'
        }
      ]
    })

No… I’m missing something. I get the feeling that this should be an easy setup, and I’m overlooking something. First, it seems odd the PWA plugin didn’t copy the images over to begin with. Let’s go back and look at its configuration.

I got PWA working with the dev server.

devOptions: {
   enabled: true
}

Unfortunately the service worker complains about a SSL certificate error.

An SSL certificate error occurred when fetching the script.
Uncaught (in promise) DOMException: Failed to register a ServiceWorker for scope (‘https://localhost:5173/’

I suspect it’s because the certificate root is untrusted since I signed it myself. Let’s go and see if we can “trust” the certificate.

  • In Chrome – Settings -> Privacy and Security -> Security -> Manage Certificates (Opens iCloud Keychain Access).
  • File -> Import Item -> Lewis’s MacBook Pro -> Macintosh HD -> Users -> lewismoten -> dev -> PeriPlux -> cert.pem
  • Certificate is displayed with “x” icon “This root certificate is not trusted”
  • Let’s see if we can tell the system to always trust the certificate for SSL.
Always trust the certificate for SSL

I’m getting prompted again about the certificate when refreshing the site.

This server could not prove that it is localhost; its security certificate does not specify Subject Alternative Names. This may be caused by a misconfiguration or an attacker intercepting your connection.

Going past this screen, I’m getting the same errors about the PWA service worker. Let’s see if we can create a new self-signed certificate that addresses this problem.

openssl req -x509 -out localhost.crt -keyout localhost.key -newkey rsa:2048 -days 365 -nodes -sha256 -subj "/CN=localhost"

Going through the same steps, we now have a trusted certificate named after the localhost rather than ourselves.

Always trust the localhost certificate for SSL

Did it work? I’m still getting the first warning message – but it no longer complains about “Subject Alternative Names”.

Google Chrome waring about an invalid common name

Let’s try adding IP’s and DNS

openssl req -x509 -out localhost.crt -keyout localhost.key -newkey rsa:2048 -days 365 -nodes -sha256 -subj "/CN=localhost" -extensions v3_req -config scripts/openssl.cnf

and openssl.cnf

[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
[req_distinguished_name]
[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
IP.1 = 0.0.0.0
IP.2 = 127.0.0.1

Now I’m seeing something different. In Keychain Access, it says “⚠️This certificate has not been verified by a third party”. Is this progress? I marked it as trusted for SSL.

Yes! The browser no longer complains when I first visit the page, and I get a nice console message in the browser.

Workbox Precaching 2 files.

It looks like the images need to be specified in the HTML itself.

<head>
    <meta charset="UTF-8" />
    <title>PeriPlux</title>
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0"
    />
    <meta name="description" content="Create immersive scavenger hunts with GPS, 3D objects, and riddles. Integrate customizable hunts and adventure features into your app easily. Perfect for adding exploration and fun to your project!">
    <link rel="icon" href="/images/pwa/periplux-logo-48x48.ico">
    <link rel="apple-touch-icon" href="/images/pwa/periplux-logo-180x180.png" sizes="180x180">
    <link rel="mask-icon" href="/images/pwa/periplux-logo.svg" color="#000000">
    <meta name="theme-color" content="#ffffff">
  </head>

I found a few websites to convert the logo into an SVG file. Eventually I’d like a simple drawing with two or three colors to fill it in. Maybe use some gradients.

I’m now getting warnings from Vite as I startup the application.

PWA v0.20.0
WARNING: “theme_color” is missing from the web manifest, your application will not be able to be installed

I specified the theme color in the meta tag. Perhaps it needs to have an underscore in the name. Nope. I’m still getting the same error. Ah, here we go. I needed to add it back to the configuration.

VitePWA({
      registerType: 'autoUpdate',
      devOptions: {
        enabled: true
      },
      manifest: {
        theme_color: '#000000'
      }
    })

Looking at the actual manifest created, I see nothing about icons.

{
    "name": "@codejamboree/periplux",
    "short_name": "@codejamboree/periplux",
    "start_url": "/",
    "display": "standalone",
    "background_color": "#ffffff",
    "lang": "en",
    "scope": "/",
    "theme_color": "#000000"
}

It didn’t read anything from the HTML that I added. It pulled the name and short name from my package.config. The start_url needs to point to a fully qualified domain name.

Well, it turns out that none of that stuff in the HTML was needed. Everything is in the vite config. This is what I’ve got now:

VitePWA({
      registerType: 'autoUpdate',
      devOptions: {
        enabled: true
      },
      includeAssets: [
        'src/images/pwa/periplux-logo-48x48.ico',
        'src/images/pwa/periplux-logo-64x64.png',
        'src/images/pwa/periplux-logo-180x180.png',
        'src/images/pwa/periplux-logo-192x192.png',
        'src/images/pwa/periplux-logo-512x512.png',
        'src/images/pwa/periplux-logo.svg',
      ],
      manifest: {
        name: 'PeriPlux',
        short_name: 'PeriPlux',
        description: 'Create immersive scavenger hunts with GPS, 3D objects, and riddles. Integrate customizable hunts and adventure features into your app easily. Perfect for adding exploration and fun to your project!',
        theme_color: '#000000',
        icons: [
          {
            src: 'src/images/pwa/periplux-logo-64x64.png',
            sizes: '64x64',
            type: 'image/png'
          },
          {
            src: 'src/images/pwa/periplux-logo-180x180.png',
            sizes: '180x180',
            type: 'image/png'
          },
          {
            src: 'src/images/pwa/periplux-logo-192x192.png',
            sizes: '192x192',
            type: 'image/png'
          },
          {
            src: 'src/images/pwa/periplux-logo-512x512.png',
            sizes: '512x512',
            type: 'image/png'
          },
        ]
      }
    })

The good news is that I can now install it as a web app in Google Chrome.

Progressive Web App Installation Dialog

The bad news is that the build doesn’t place any of the images in the build folder that are listed in the manifest. The images in the index.html meta tags appear, but they have hash values. None of the icons in the generated manifest file have hashes.

I’m back in a loop here. This is where I started from.

I’m seeing references to a public directory where my images should be kept. If I move my index.html page there, the vite pwa plugin complains that it doesn’t know where index.html is. So I move index.html back to src/index.html. Ok – it’s no longer complaining. Still, no images. In fact, the old metadata tagged images are no longer built as well. It seems like vite is unaware of the public folder.

Well this is weird. I figured it out. I had to resolve the name.

export default defineConfig({
  publicDir: path.resolve(__dirname, 'public'),
  root: path.resolve(__dirname, 'src'),

Somethings a bit off. I feel like I shouldn’t have to resolve the names. It should just work out of the box. What else isn’t working correctly because of this? Do I need to go in and verify every folder/path is resolved compared to the default configuration?

I figured it out. The setup in Vite is different. I was fairly used to webpack projects where the index.ejs file is in the src folder. Vite expects index.html to be in the root project folder. It seems a bit odd since that area of a project is mostly reserved for configuring the build environment. Once I moved the index page, I was able to remove the publicDir, root and build.outDir properties. I was trying to work out why a dev-dist folder was appearing under src. Once I removed all of the properties in vite, dev-dist appeared off of the root of my project folder. This is like one of those moments where anyone who uses vite would say – Well… DUH! This is the problem I have when considering learning new frameworks and languages. If you aren’t in the ecosystem, it’s often a slow trek to get up and running without a team.

End of Line

So where are we?

  • We fixed additional certificate errors by assigning Subject Alternative Names (SAN)
  • We created a logo and various image sizes and formats as ICO, PNG, and SVG
  • Google Chrome recognized our website as a progressive web application
  • We installed our website on the local desktop
  • We discovered the correct way to setup a vite project

Where do we need to go?

  • Verify installation on Android (Emulator?)
    • Verify browser menu says “Add to home screen”
    • Verify install banner appears
    • Install
    • Launch from home screen
    • Test core functionality
    • Test offline capability
    • Test looks like an app (not a web page/browser)
    • Test Chrome, Firefox
  • Verify installation on iPhone (Emulator?)
    • Verify installation banner appears
    • Launch from home screen
    • Test core functionality
    • Test offline capability
    • Test looks like an app (not a web page/browser)
    • Test Safari, Chrome, Firefox
  • Test push notifications
  • Test service worker

One response to “Web App and Logo”

Discover more from Lewis Moten

Subscribe now to keep reading and get access to the full archive.

Continue reading