File size: 5,099 Bytes
ba1e29d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
// JAYNES MAX β€” Service Worker
// Hifadhi "app shell" + maktaba za Shaka/hls ili app ifunguke hata bila mtandao.
// API, video na streams HAZIHIFADHIWI (zinahitaji mtandao). Movie zilizopakuliwa ziko OPFS ndani ya app.
const VERSION = 'jm-v9';
const RUNTIME = 'jm-cdn-v1';
const SHELL = ['./', './index.html', './manifest.webmanifest', './config.js', './icon-192.png', './icon-512.png', './icon-maskable-512.png', './apple-touch-icon.png'];
const CDN_HOST = 'cdnjs.cloudflare.com';
const CDN = [
  'https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.13/hls.min.js',
  'https://cdnjs.cloudflare.com/ajax/libs/shaka-player/4.10.0/shaka-player.compiled.js',
];

self.addEventListener('install', (e) => {
  e.waitUntil((async () => {
    // Kila faili peke yake: faili moja likikosekana, install HAIFEI (addAll ingefeli yote)
    const shell = await caches.open(VERSION);
    await Promise.all(SHELL.map((u) => shell.add(u).catch(() => {})));
    const rt = await caches.open(RUNTIME);
    await Promise.all(CDN.map((u) => fetch(u, { mode: 'no-cors' }).then((r) => rt.put(u, r)).catch(() => {})));
    await self.skipWaiting();
  })());
});

self.addEventListener('activate', (e) => {
  e.waitUntil(
    caches.keys().then((keys) => Promise.all(keys.filter((k) => k !== VERSION && k !== RUNTIME).map((k) => caches.delete(k))))
      .then(() => self.clients.claim())
  );
});

function shellFallback() {
  return caches.match('./index.html', { ignoreSearch: true })
    .then((r) => r || caches.match('./', { ignoreSearch: true }));
}

self.addEventListener('fetch', (e) => {
  const req = e.request;
  if (req.method !== 'GET') return;
  const url = new URL(req.url);

  // Maktaba za CDN (hls/shaka): cache-first ili zipatikane offline na zipakie haraka
  if (url.hostname === CDN_HOST) {
    e.respondWith(
      caches.match(req).then((hit) => hit || fetch(req).then((res) => {
        if (res && (res.ok || res.type === 'opaque')) { const copy = res.clone(); caches.open(RUNTIME).then((c) => c.put(req, copy)); }
        return res;
      }))
    );
    return;
  }

  // Usiguse chochote kingine nje ya asili yetu (Supabase API, video, picha za CDN n.k.)
  if (url.origin !== self.location.origin) return;
  // Range requests (video/sauti) β€” acha mtandao ushughulikie
  if (req.headers.has('range')) return;

  // Ukurasa mkuu: network-first, lakini mtandao ukichelewa (>3.5s) au ukikosekana β†’ cache.
  // Hii inazuia app "kuning'inia" ukiwa na mtandao dhaifu / bila bando.
  if (req.mode === 'navigate') {
    e.respondWith(new Promise((resolve) => {
      let settled = false;
      const settle = (r) => { if (!settled && r) { settled = true; resolve(r); } };
      const timer = setTimeout(() => { shellFallback().then(settle); }, 3500);
      fetch(req).then((res) => {
        clearTimeout(timer);
        if (res && res.ok) { const copy = res.clone(); caches.open(VERSION).then((c) => c.put('./index.html', copy)); }
        settle(res);
      }).catch(() => {
        clearTimeout(timer);
        shellFallback().then((r) => { if (!settled) { settled = true; resolve(r || Response.error()); } });
      });
    }));
    return;
  }

  // config.js: network-first (funguo/URL zikibadilishwa zifike mara moja), cache ikiwa offline
  if (url.pathname.endsWith('/config.js')) {
    e.respondWith(
      fetch(req).then((res) => { if (res.ok) { const copy = res.clone(); caches.open(VERSION).then((c) => c.put(req, copy)); } return res; })
        .catch(() => caches.match(req, { ignoreSearch: true }))
    );
    return;
  }

  // Faili tuli (icons, manifest): cache-first
  e.respondWith(
    caches.match(req).then((hit) => hit || fetch(req).then((res) => {
      if (res.ok) { const copy = res.clone(); caches.open(VERSION).then((c) => c.put(req, copy)); }
      return res;
    }))
  );
});

// ── Web Push (inatoka Supabase Edge Function 'send-push') ──
self.addEventListener('push', (e) => {
  let d = {};
  try { d = e.data ? e.data.json() : {}; } catch (_) { d = { body: e.data && e.data.text() }; }
  const opts = {
    body: d.body || '',
    icon: './icon-192.png',
    badge: './icon-192.png',
    tag: d.id ? 'jm-ann-' + d.id : undefined,
    data: { url: d.url || './' },
  };
  if (d.image) opts.image = d.image;      // picha kubwa (Android/Chrome)
  e.waitUntil(self.registration.showNotification(d.title || 'JAYNES MAX', opts));
});

// Kubonyeza notification: fungua app moja kwa moja kwenye ukurasa ulioainishwa (#/title/ID, #/live, ...)
self.addEventListener('notificationclick', (e) => {
  e.notification.close();
  const raw = (e.notification.data && e.notification.data.url) || './';
  const target = new URL(raw, self.registration.scope).href;
  e.waitUntil((async () => {
    const all = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
    for (const c of all) {
      if (c.url.startsWith(self.registration.scope)) {
        try { c.postMessage({ type: 'jm-open', url: target }); } catch (_) {}
        if ('focus' in c) await c.focus();
        return;
      }
    }
    await self.clients.openWindow(target);
  })());
});