WIP PWA for Grain
1// PWA install prompt service
2class PWAService {
3 #deferredPrompt = null;
4 #listeners = new Set();
5
6 constructor() {
7 window.addEventListener('beforeinstallprompt', (e) => {
8 e.preventDefault();
9 this.#deferredPrompt = e;
10 this.#notify();
11 });
12
13 window.addEventListener('appinstalled', () => {
14 this.#deferredPrompt = null;
15 this.#notify();
16 });
17 }
18
19 get isIOS() {
20 return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
21 }
22
23 get isStandalone() {
24 return window.matchMedia('(display-mode: standalone)').matches ||
25 window.navigator.standalone === true;
26 }
27
28 get canInstall() {
29 return this.#deferredPrompt !== null;
30 }
31
32 get showIOSInstructions() {
33 return this.isIOS && !this.isStandalone;
34 }
35
36 async install() {
37 if (!this.#deferredPrompt) return false;
38
39 this.#deferredPrompt.prompt();
40 const { outcome } = await this.#deferredPrompt.userChoice;
41
42 if (outcome === 'accepted') {
43 this.#deferredPrompt = null;
44 this.#notify();
45 }
46
47 return outcome === 'accepted';
48 }
49
50 subscribe(callback) {
51 this.#listeners.add(callback);
52 return () => this.#listeners.delete(callback);
53 }
54
55 #notify() {
56 this.#listeners.forEach(cb => cb(this.canInstall));
57 }
58}
59
60export const pwa = new PWAService();