aboutsummaryrefslogtreecommitdiff
path: root/src/frontend/app/utils/serviceWorkerManager.ts
blob: a1ddbabda75e2138981d279a71473291cc9ad3ae (plain)
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
export class ServiceWorkerManager {
  private registration: ServiceWorkerRegistration | null = null;
  private updateAvailable = false;
  private onUpdateCallback?: () => void;

  async initialize() {
    if (!("serviceWorker" in navigator)) {
      console.log("Service Workers not supported");
      return;
    }

    try {
      // First, unregister any old service workers to start fresh
      const registrations = await navigator.serviceWorker.getRegistrations();
      for (const registration of registrations) {
        if (registration.scope.includes(window.location.origin)) {
          console.log("Unregistering old service worker:", registration.scope);
          await registration.unregister();
        }
      }

      // Register the new worker with a fresh name
      this.registration = await navigator.serviceWorker.register("/pwa-worker.js", {
        updateViaCache: 'none' // Disable caching for the SW file itself
      });
      console.log("PWA Worker registered with scope:", this.registration.scope);

      // Implement proper updatefound detection (web.dev pattern)
      await this.detectSWUpdate();

      // Check for updates periodically
      setInterval(() => {
        this.checkForUpdates();
      }, 30 * 1000);

      // Check when page becomes visible
      document.addEventListener("visibilitychange", () => {
        if (!document.hidden) {
          this.checkForUpdates();
        }
      });

    } catch (error) {
      console.error("Service Worker registration failed:", error);
    }
  }

  private async detectSWUpdate() {
    if (!this.registration) return;

    // Listen for new service worker discovery
    this.registration.addEventListener("updatefound", () => {
      const newSW = this.registration!.installing;
      if (!newSW) return;

      console.log("New service worker found, monitoring installation...");

      newSW.addEventListener("statechange", () => {
        console.log("New SW state:", newSW.state);
        
        if (newSW.state === "installed") {
          if (navigator.serviceWorker.controller) {
            // New service worker is installed, but old one is still controlling
            // This means an update is available
            console.log("New service worker installed - update available!");
            this.updateAvailable = true;
            this.onUpdateCallback?.();
          } else {
            // First install, no controller yet
            console.log("Service worker installed for the first time");
          }
        }

        if (newSW.state === "activated") {
          console.log("New service worker activated");
          // Optionally notify about successful update
        }
      });
    });

    // Also listen for controller changes
    navigator.serviceWorker.addEventListener("controllerchange", () => {
      console.log("Service worker controller changed - reloading page");
      window.location.reload();
    });
  }

  async checkForUpdates() {
    if (this.registration) {
      try {
        await this.registration.update();
      } catch (error) {
        console.error("Failed to check for updates:", error);
      }
    }
  }

  activateUpdate() {
    if (this.registration && this.registration.waiting) {
      this.registration.waiting.postMessage({ type: "SKIP_WAITING" });
      this.updateAvailable = false;
    }
  }

  onUpdate(callback: () => void) {
    this.onUpdateCallback = callback;
  }

  isUpdateAvailable() {
    return this.updateAvailable;
  }

  async clearCache(): Promise<void> {
    try {
      // Delete all caches
      const cacheNames = await caches.keys();
      await Promise.all(cacheNames.map(name => caches.delete(name)));
      console.log("All caches cleared");
    } catch (error) {
      console.error("Failed to clear cache:", error);
      throw error;
    }
  }

  // Nuclear option: completely reset the PWA
  async resetPWA(): Promise<void> {
    try {
      console.log("Resetting PWA completely...");
      
      // 1. Unregister ALL service workers
      const registrations = await navigator.serviceWorker.getRegistrations();
      await Promise.all(registrations.map(reg => reg.unregister()));
      
      // 2. Clear all caches
      await this.clearCache();
      
      // 3. Clear local storage (optional)
      localStorage.clear();
      sessionStorage.clear();
      
      console.log("PWA reset complete - reloading...");
      
      // 4. Force reload after a short delay
      setTimeout(() => {
        window.location.reload();
      }, 500);
      
    } catch (error) {
      console.error("Failed to reset PWA:", error);
      throw error;
    }
  }
}

export const swManager = new ServiceWorkerManager();