Files
lifeman/pwa-notes.md
2025-12-05 11:19:17 +00:00

426 lines
9.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
## Part 1 Turn your current app into a PWA
You already have:
* `index.html`
* `style.css`
* `script.js`
Well add:
* `manifest.webmanifest`
* `sw.js`
* A couple of small changes to `index.html`
Ill assume your app is served from the site root (e.g. `https://mydomain.com/`) and that your icon is a PNG. You can keep everything inside `web/` in your repo; when you deploy, make sure these files end up in the same directory as `index.html`.
---
### 1. Create icons from your existing icon file
Chrome expects at least:
* **192×192** PNG
* **512×512** PNG
If your icon file is large enough (e.g. 1024×1024), generate:
* `icons/icon-192.png`
* `icons/icon-512.png`
(Use something like GIMP, Figma, or an online “PWA icon generator”.)
Folder structure (in `web/`):
```text
web/
index.html
style.css
script.js
sw.js
manifest.webmanifest
icons/
icon-192.png
icon-512.png
```
---
### 2. Add `manifest.webmanifest`
Create `web/manifest.webmanifest`:
```json
{
"name": "Life Manager",
"short_name": "LifeMgr",
"description": "Offline-first life organizer with calendar, tags, and backups.",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#0f1014",
"theme_color": "#0f1014",
"orientation": "portrait",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
```
> `start_url` and `scope` assume the app is at the root. If you serve from `/life-manager/`, change both to `"/life-manager/"`.
---
### 3. Wire manifest + theme color into `index.html`
Modify your `<head>` in `web/index.html` to include the manifest and theme color.
Right now you have:
```html
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>life-manager</title>
<link rel="stylesheet" href="style.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
</head>
```
Change to:
```html
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>life-manager</title>
<!-- PWA: theme color (matches manifest) -->
<meta name="theme-color" content="#0f1014">
<!-- PWA: manifest -->
<link rel="manifest" href="manifest.webmanifest">
<link rel="stylesheet" href="style.css">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
</head>
```
---
### 4. Register a service worker from `index.html`
Just before `</body>` you already have:
```html
<script src="script.js"></script>
</body>
</html>
```
Change to:
```html
<script src="script.js"></script>
<!-- PWA: Service worker registration -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.catch(err => {
console.error('SW registration failed', err);
});
});
}
</script>
</body>
</html>
```
> If you host the app under a path (e.g. `/life-manager/`), change the registration path to `'/life-manager/sw.js'`.
---
### 5. Add `sw.js` (service worker)
Create `web/sw.js`:
```js
// sw.js - simple cache-first service worker
const CACHE_NAME = 'lifemgr-cache-v1';
// List everything needed for offline
const ASSETS = [
'/',
'/index.html',
'/style.css',
'/script.js',
'/manifest.webmanifest',
'/icons/icon-192.png',
'/icons/icon-512.png'
];
// Install: cache core assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(ASSETS);
})
);
self.skipWaiting();
});
// Activate: cleanup old caches if you bump the version
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((keys) =>
Promise.all(
keys.map((key) => {
if (key !== CACHE_NAME) {
return caches.delete(key);
}
})
)
)
);
self.clients.claim();
});
// Fetch: cache-first strategy
self.addEventListener('fetch', (event) => {
const request = event.request;
// Only handle GET
if (request.method !== 'GET') return;
event.respondWith(
caches.match(request).then((cached) => {
// Serve from cache if available, else network
return (
cached ||
fetch(request).catch(() => {
// Optional: custom offline response for HTML requests
if (request.headers.get('accept')?.includes('text/html')) {
return caches.match('/index.html');
}
})
);
})
);
});
```
> Again, if hosted under a sub-path, prepend `/life-manager` in `ASSETS`.
Now when you deploy over **HTTPS**, Chrome will see:
* a valid manifest,
* a service worker controlling the scope,
* and will treat it as a PWA with install capability.
---
### 6. Deploy and sanity-check
1. Deploy `web/` to somewhere HTTPS (Netlify, Vercel, etc.).
2. Open it in Chrome (Android or desktop).
3. In DevTools → Application → Manifest/Service Workers:
* Confirm manifest is found,
* service worker is active,
* and pages are controlled.
4. On Android Chrome, you should see **“Install app” / “Add to Home screen”** in the menu.
Once this works, youre ready for the **Trusted Web Activity** step.
---
## Part 2 Wrap your PWA in a Trusted Web Activity (TWA) with Bubblewrap
Now you have a real PWA live at (for example):
```text
https://your-domain.com/
```
Well use **Bubblewrap**, which takes your hosted `manifest.webmanifest` and generates an Android project that starts your PWA as a TWA.
### 1. Prereqs checklist
You need:
* Node.js (v14+)
* Java JDK
* Android SDK
* Your PWA served via **HTTPS** with:
* manifest,
* service worker,
* and working offline (which we just did).
And of course, a **Google Play developer account** for publishing later.
---
### 2. Install Bubblewrap CLI
```bash
npm install -g @bubblewrap/cli
bubblewrap --version
```
(Just to confirm its installed.)
---
### 3. Initialize a Bubblewrap project from your manifest
Pick a new folder for the Android project:
```bash
mkdir life-manager-twa
cd life-manager-twa
```
Run the init command, pointing to your **hosted** manifest:
```bash
bubblewrap init --manifest https://your-domain.com/manifest.webmanifest
```
Bubblewrap will:
* Download the web manifest.
* Ask you about:
* **Package ID** (e.g. `com.yourdomain.lifemanager`)
* **App name** (can reuse `"Life Manager"`)
* **Colors, icons, orientation** (it can reuse values from the manifest)
* **Signing key** (create a new keystore or use an existing one)
It will generate:
* An Android project (Gradle, Java/Kotlin code that opens your URL as a TWA).
* A `twa-manifest.json` with config about your app.
* An `assetlinks.json` file template for your website.
---
### 4. Build the APK/App Bundle
Inside that new project folder:
```bash
bubblewrap build
```
This compiles the Android project and outputs a release build (APK/App Bundle).
You can also install directly on a connected device:
```bash
bubblewrap install
```
At this stage, when you open the app on the device:
* It will usually open as a **Custom Tab** (with a little browser UI) until we set up the trust link (Digital Asset Links).
---
### 5. Set up Digital Asset Links (`assetlinks.json`)
To get true **Trusted Web Activity** (full screen, no browser bar), Android needs to verify that:
* The app signing key
* And your websites domain
belong to the same owner, via **Digital Asset Links**.
Bubblewrap will give you an `assetlinks.json` snippet. If you need a template, it looks like this:
```json
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourdomain.lifemanager",
"sha256_cert_fingerprints": [
"AA:BB:CC:DD:EE:FF:..."
]
}
}
]
```
* Replace `com.yourdomain.lifemanager` with your actual package ID.
* Replace the fingerprint with the SHA-256 of the keystore you **actually sign the Play build with** (Bubblewrap/Android Studio docs show how to get this).
Host this file at:
```text
https://your-domain.com/.well-known/assetlinks.json
```
Once this is accessible and valid:
* Opening your app on device will show your PWA **full-screen** as a Trusted Web Activity, not a normal Chrome tab.
---
### 6. Open in Android Studio (optional but recommended)
You can open the generated Android project in Android Studio for:
* Changing app name, icon, version code, etc.
* Building signed App Bundles for the Play Store.
* Running on emulators.
Bubblewraps `build` and `install` commands already use Gradle under the hood, so this is optional, but very handy for Play Store upload.
---
### 7. Upload to Google Play
Once:
* PWA runs well over HTTPS.
* Asset Links are configured and verified.
* The app works as full-screen TWA on a device.
Then:
1. In the **Google Play Console**, create a new app.
2. Upload the signed **App Bundle/APK** from your Bubblewrap/Android Studio build.
3. Fill in listing details (screenshots, description, etc.).
4. Roll out a test track or production release.
---