Nanfeng

Notes on software development, code, and curious ideas

Dynamically Loading a PWA Manifest

This continues the earlier PWA installation article. Dynamic manifests are useful when selecting a manifest by language, theme, environment, or start URL.

Replace the static manifest link:

1
<link rel="manifest" href="/manifest.json">

with:

1
<link rel="manifest" id="my-manifest">

Build a server URL and assign it at runtime:

1
2
3
4
5
6
7
8
9
const url = 'https://lengmo714.top' + window.location.search;
let params = {
start_url: url,
i1: '/icon-192x192.png',
i2: '/icon-512x512.png'
};
const encoded = encodeURIComponent(JSON.stringify(params));
const manifestURL = 'https://example.com/manifest?p=' + encoded;
document.querySelector('#my-manifest').setAttribute('href', manifestURL);

The server receives the start URL and icon paths and returns JSON. A minimal Go implementation is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package main

import (
"encoding/json"
"fmt"
"net/http"
"net/url"
)

type Manifest struct {
Name string `json:"name"`
ShortName string `json:"short_name"`
StartURL string `json:"start_url"`
Display string `json:"display"`
ThemeColor string `json:"theme_color"`
BackgroundColor string `json:"background_color"`
Icons []Icon `json:"icons"`
}

type Icon struct {
Src string `json:"src"`
Sizes string `json:"sizes"`
Type string `json:"type"`
}

func manifestHandler(w http.ResponseWriter, r *http.Request) {
p := r.URL.Query().Get("p")
if p == "" {
http.Error(w, `{"error":"Missing parameter 'p'"}`, http.StatusBadRequest)
return
}
decoded, err := url.QueryUnescape(p)
if err != nil { http.Error(w, "Invalid parameter", 400); return }

var data map[string]string
if json.Unmarshal([]byte(decoded), &data) != nil {
http.Error(w, "Invalid JSON", 400); return
}

result := Manifest{
Name: data["name"], ShortName: data["name"], StartURL: data["start_url"],
Display: "standalone", ThemeColor: "#ffffff", BackgroundColor: "#000000",
Icons: []Icon{
{Src: data["i1"], Sizes: "192x192", Type: "image/png"},
{Src: data["i2"], Sizes: "512x512", Type: "image/png"},
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
}

func main() {
http.HandleFunc("/manifest", manifestHandler)
fmt.Println("Server running at http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
+