Skip to content

Commit 422c94b

Browse files
authored
Merge pull request #6 from f3bot/sw-trusted-feature
Add service workers, support for trusted types, minor fixes
2 parents 24fe6a8 + 9ae651e commit 422c94b

14 files changed

Lines changed: 328 additions & 31 deletions

File tree

.github/workflows/prettier.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: 'Lint and Format'
1+
name: 'Check formatting'
22

33
# This workflow runs on pushes to any branch
44
# and on any pull request
@@ -7,7 +7,7 @@ on:
77

88
jobs:
99
prettier-format-commit:
10-
name: Prettier format and commit
10+
name: Check prettier formatting
1111
runs-on: ubuntu-latest
1212

1313
steps:

README.md

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
# IWA project templates
22

3-
This project will contain templates that could be used for creation of new Isolated Web Apps (IWAs) projects.
3+
This project will contain templates that could be used for creation of new Isolated Web Apps (IWAs)
4+
projects.
5+
46
## Prerequisites
57

6-
- Google Chrome 120+
8+
- Google Chrome 140+
79

810
- openssl
911

@@ -23,7 +25,7 @@ Open Google Chrome, go to `chrome://flags` and enable
2325

2426
chrome://flags/#enable-isolated-web-app-dev-mode
2527

26-
chrome://flags/#enable-isolated-web-apps
28+
chrome://flags/#enable-isolated-web-apps (MacOS/Windows/Linux only)
2729

2830
Once enabled, and chrome is restarted, go to `chrome://web-app-internals`, the web app internals
2931
page. This page shows you the web apps you have installed, including Isolated Web Apps and
@@ -82,7 +84,8 @@ Next, create a .env file containing:
8284
PRIVATE_KEY_PASSWORD='YOUR_KEY_PASSWORD'
8385
PORT=YOUR_PORT
8486

85-
_Note: If you're using vite, also declare a variable called NODE_ENV, you can set it to either production/development_
87+
_Note: If you're using vite, also declare a variable called NODE_ENV, you can set it to either
88+
production/development_
8689

8790
_Note: If you don't specify PORT, the app will fall back to default value (4321)_
8891

@@ -105,9 +108,8 @@ slu74sbybztfypa43w7f7rd34cbhautjcrfegz5lbow7vmwjojbqaaic).
105108

106109
_Note: Your web bundle ID will look differently, this is just an example_
107110

108-
Since IWA are using a different schema `isolated-app://` instead of `https://`, you can access it by
109-
pasting `isolated-app://your-web-bundle-id` in Chrome, or by running it like any other app on your
110-
computer.
111+
To run your newly installed Isolated Web App, use chrome://apps (Windows/Mac/Linux only) or by
112+
running it like any other application on your desktop.
111113

112114
### Personalizing this template
113115

@@ -126,7 +128,6 @@ Related information:
126128
Isolated Web Apps share the same
127129
[Manifest Properties](https://web.dev/articles/add-manifest#manifest-properties) as Progressive Web
128130
Apps, with some slight variations, see:
129-
[Isolated Web Apps explainer - Web App manifest section](https://chromeos.dev/en/web/isolated-web-apps)
130131

131132
There are two fields that should be included, `version` and `update_manifest_url`
132133

@@ -139,8 +140,8 @@ There are two fields that should be included, `version` and `update_manifest_url
139140

140141
- How to add API Permissions?
141142

142-
By default, Chrome blocks all permission requests from IWAs. You can opt-in to permission policies you need by specifying
143-
a `permissions_policy` field in your manifest.
143+
By default, Chrome blocks all permission requests from IWAs. You can opt-in to permission policies
144+
you need by specifying a `permissions_policy` field in your manifest.
144145

145146
_Note: Adding a permission here does not automatically grant it, it just makes it avaliable to be
146147
granted, when a request for that capacity is made._
@@ -155,7 +156,7 @@ granted, when a request for that capacity is made._
155156
Related Information
156157

157158
- [Permissions Policy - MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Permissions_Policy#allowlists)
158-
- [Permissions Policy header - MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Permissions_Policy#allowlists)
159+
- [Permissions Policy header - MDN Docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Permissions-Policy)
159160
- [Web Application Manifest - MDN Docs](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest)
160161

161162
### Related Information

vite-iwa-template/package-lock.json

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vite-iwa-template/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"prettier": "prettier -w ."
1212
},
1313
"devDependencies": {
14+
"@types/trusted-types": "^2.0.7",
15+
"dompurify": "^3.2.6",
1416
"dotenv": "^17.2.0",
1517
"prettier": "^3.6.2",
1618
"rollup-plugin-webbundle": "^0.2.0",

vite-iwa-template/src/main.ts

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
6-
* You may not use a copy of the License at
6+
* You may obtain a copy of the License at
77
*
88
* http://www.apache.org/licenses/LICENSE-2.0
99
*
@@ -15,21 +15,33 @@
1515
*/
1616

1717
import "./style.css";
18+
import DOMPurify from "dompurify";
1819

1920
const parent_container = document.querySelector("#app") as HTMLDivElement;
2021

21-
function deleteChildNodes(container: HTMLDivElement): void {
22-
while (container.firstChild) {
23-
container.removeChild(container.firstChild);
24-
}
22+
let sanitizerPolicy: any = null!;
23+
let urlPolicy: any = null!;
24+
25+
// This block will always execute in Chrome, so the policies will be assigned.
26+
if (window.trustedTypes && window.trustedTypes.createPolicy) {
27+
// Policy for sanitizing HTML strings before assigning them to innerHTML.
28+
sanitizerPolicy = window.trustedTypes.createPolicy("iwa-sanitizer", {
29+
createHTML: (string) => DOMPurify.sanitize(string),
30+
});
31+
32+
// Policy for creating TrustedScriptURL. This is required for navigator.serviceWorker.register().
33+
// For this app, we can just pass the URL through, as we know it's safe.
34+
urlPolicy = window.trustedTypes.createPolicy("iwa-url-policy", {
35+
createScriptURL: (url) => url,
36+
});
2537
}
2638

2739
/**
2840
* Loads images and other content when the app is online.
2941
* It first clears the container to prevent duplicate content.
3042
*/
3143
function loadOnlineContent() {
32-
deleteChildNodes(parent_container);
44+
parent_container.innerHTML = sanitizerPolicy.createHTML("");
3345

3446
const icon_elements: [string, string][] = [
3547
[
@@ -74,7 +86,7 @@ function loadOnlineContent() {
7486
* Displays an offline message in the app container.
7587
*/
7688
function showOfflineMessage() {
77-
deleteChildNodes(parent_container);
89+
parent_container.innerHTML = sanitizerPolicy.createHTML("");
7890
const p_offline = document.createElement("p");
7991

8092
const title = document.createElement("h1");
@@ -87,6 +99,22 @@ function showOfflineMessage() {
8799
parent_container.appendChild(p_offline);
88100
}
89101

102+
window.addEventListener("load", () => {
103+
if ("serviceWorker" in navigator) {
104+
// Use the trusted type policy to create a TrustedScriptURL.
105+
const swUrl = urlPolicy.createScriptURL("./service-worker.js");
106+
107+
navigator.serviceWorker
108+
.register(swUrl)
109+
.then((registration) => {
110+
console.log("Service Worker registered with scope:", registration.scope);
111+
})
112+
.catch((error) => {
113+
console.error("Service Worker registration failed:", error);
114+
});
115+
}
116+
});
117+
90118
window.addEventListener("online", () => {
91119
loadOnlineContent();
92120
});
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Copyright 2025 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/// <reference lib="webworker" />
18+
19+
declare const self: ServiceWorkerGlobalScope;
20+
21+
const CACHE_NAME = "iwa-image-cache-v1";
22+
23+
const URLS_TO_CACHE = [
24+
"https://upload.wikimedia.org/wikipedia/commons/f/f1/Vitejs-logo.svg",
25+
"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Typescript_logo_2020.svg/250px-Typescript_logo_2020.svg.png",
26+
"https://upload.wikimedia.org/wikipedia/commons/e/e1/Google_Chrome_icon_%28February_2022%29.svg",
27+
];
28+
29+
/**
30+
* Installation event.
31+
*/
32+
self.addEventListener("install", (event: ExtendableEvent) => {
33+
event.waitUntil(
34+
caches.open(CACHE_NAME).then((cache) => {
35+
console.log("Opened cache");
36+
return cache.addAll(URLS_TO_CACHE);
37+
}),
38+
);
39+
});
40+
41+
/**
42+
* Fetch event.
43+
*/
44+
self.addEventListener("fetch", (event: FetchEvent) => {
45+
if (!event.request.url.startsWith("http")) {
46+
return;
47+
}
48+
49+
event.respondWith(
50+
caches.match(event.request).then((response) => {
51+
if (response) {
52+
return response;
53+
}
54+
55+
return fetch(event.request).then((networkResponse) => {
56+
if (
57+
!networkResponse ||
58+
networkResponse.status !== 200 ||
59+
(networkResponse.type !== "basic" && networkResponse.type !== "cors")
60+
) {
61+
return networkResponse;
62+
}
63+
64+
const responseToCache = networkResponse.clone();
65+
66+
caches.open(CACHE_NAME).then((cache) => {
67+
cache.put(event.request, responseToCache);
68+
});
69+
70+
return networkResponse;
71+
});
72+
}),
73+
);
74+
});
75+
76+
/**
77+
* Activate event.
78+
*/
79+
self.addEventListener("activate", (event: ExtendableEvent) => {
80+
const cacheWhitelist = [CACHE_NAME];
81+
event.waitUntil(
82+
caches.keys().then((cacheNames) => {
83+
return Promise.all(
84+
cacheNames.map((cacheName) => {
85+
if (cacheWhitelist.indexOf(cacheName) === -1) {
86+
return caches.delete(cacheName);
87+
}
88+
}),
89+
);
90+
}),
91+
);
92+
});

vite-iwa-template/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"compilerOptions": {
3+
"types": ["trusted-types"],
34
"target": "ES2022",
45
"useDefineForClassFields": true,
56
"module": "ESNext",

vite-iwa-template/vite.config.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import { defineConfig } from "vite";
1818
import injectHTML from "vite-plugin-html-inject";
1919
import fs from "fs";
20+
import { resolve } from "path";
2021

2122
import wbn from "rollup-plugin-webbundle";
2223
import * as wbnSign from "wbn-sign";
@@ -77,6 +78,17 @@ export default defineConfig({
7778
rollupOptions: {
7879
input: {
7980
main: "./index.html",
81+
"service-worker": resolve(__dirname, "src/service-worker.ts"),
82+
},
83+
84+
output: {
85+
entryFileNames: (chunkInfo) => {
86+
if (chunkInfo.name === "service-worker") {
87+
return "service-worker.js";
88+
}
89+
90+
return "assets/[name]-[hash].js";
91+
},
8092
},
8193
},
8294
},

webpack-iwa-template/package-lock.json

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webpack-iwa-template/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
},
1212
"license": "Apache-2.0",
1313
"devDependencies": {
14+
"@types/trusted-types": "^2.0.7",
1415
"copy-webpack-plugin": "^13.0.0",
1516
"css-loader": "^7.1.2",
17+
"dompurify": "^3.2.6",
1618
"dotenv": "^17.2.0",
1719
"file-loader": "^6.2.0",
1820
"html-webpack-plugin": "^5.6.3",

0 commit comments

Comments
 (0)