File size: 4,346 Bytes
ba1e29d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6a88eb7
 
 
 
 
 
 
 
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
// dl-sw.js β€” downloads za background (Background Fetch) kwa JAYNES MAX
// Weka mstari huu JUU kabisa kwenye sw.js yako:   importScripts('dl-sw.js');
const JM_OFFLINE_DIR = 'jm-offline';   // lazima iwe sawa na OFFLINE_DIR kwenye index.html

async function jmDir(){ const root = await navigator.storage.getDirectory(); return root.getDirectoryHandle(JM_OFFLINE_DIR, { create: true }); }
async function jmReadMeta(dir, oid){
  try { const fh = await dir.getFileHandle(oid + '.json'); return JSON.parse(await (await fh.getFile()).text()); } catch (_) { return null; }
}
async function jmWriteMeta(dir, oid, obj){
  const fh = await dir.getFileHandle(oid + '.json', { create: true });
  const w = await fh.createWritable(); await w.write(JSON.stringify(obj)); await w.close();
}
async function jmBroadcast(msg){
  const all = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
  all.forEach(c => c.postMessage(msg));
}
async function jmOpenDownloads(){
  const all = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
  for (const c of all) {
    if ('focus' in c) { try { await c.focus(); } catch (_) {} c.postMessage({ type: 'jm-open-downloads' }); return; }
  }
  await self.clients.openWindow('./?nav=downloads');
}

// Ukurasa unauliza "je dl-sw.js ipo?"
self.addEventListener('message', (e) => {
  if (e.data && e.data.type === 'jm-ping' && e.source) e.source.postMessage({ type: 'jm-pong' });
});

// Download imekamilika (hata app ikiwa imefungwa): hamisha faili kwenda OPFS
self.addEventListener('backgroundfetchsuccess', (event) => {
  const bgf = event.registration;
  event.waitUntil((async () => {
    const oid = bgf.id.replace(/^jm-/, '');
    const dir = await jmDir();
    const meta = (await jmReadMeta(dir, oid)) || { id: oid, title: 'Video' };
    let ok = false;
    try {
      const records = await bgf.matchAll();
      const res = await records[0].responseReady;
      const ct = (res.headers.get('content-type') || '').toLowerCase();
      if (!res.ok || !res.body || /mpegurl|text\/|html|json/.test(ct)) throw new Error('not_direct_video');
      const fh = await dir.getFileHandle(oid + '.bin', { create: true });
      const w = await fh.createWritable();
      await res.body.pipeTo(w);                       // stream β€” haitumii RAM
      const size = (await fh.getFile()).size;
      // Thibitisha ukubwa: usikubali "imehifadhiwa" kwa faili lililokatika nusu
      let expected = 0;
      const cr = /bytes\s+\d+-\d+\/(\d+)/i.exec(res.headers.get('content-range') || '');
      if (cr) expected = Number(cr[1]);
      else if (!res.headers.get('content-encoding')) expected = Number(res.headers.get('content-length')) || 0;
      if (!size) throw new Error('empty_file');
      if (expected && size !== expected) throw new Error('size_mismatch');
      if (bgf.downloadTotal && size < bgf.downloadTotal) throw new Error('incomplete');
      await jmWriteMeta(dir, oid, { ...meta, state: 'done', size });
      ok = true;
    } catch (e) {
      try { await dir.removeEntry(oid + '.bin'); } catch (_) {}
      await jmWriteMeta(dir, oid, { ...meta, state: 'failed', reason: String((e && e.message) || e) });
    }
    try { event.updateUI({ title: (meta.title || 'Video') + (ok ? ' β€” imehifadhiwa βœ”' : ' β€” imeshindwa') }); } catch (_) {}
    await jmBroadcast({ type: 'jm-bg-update', bgId: bgf.id });
  })());
});

async function jmMarkFailed(bgf, reason){
  const oid = bgf.id.replace(/^jm-/, '');
  const dir = await jmDir();
  const meta = (await jmReadMeta(dir, oid)) || { id: oid };
  await jmWriteMeta(dir, oid, { ...meta, state: 'failed', reason });
  await jmBroadcast({ type: 'jm-bg-update', bgId: bgf.id });
}
self.addEventListener('backgroundfetchfail', (e) => { e.waitUntil(jmMarkFailed(e.registration, e.registration.failureReason || 'failed')); });
self.addEventListener('backgroundfetchabort', (e) => { e.waitUntil(jmMarkFailed(e.registration, 'aborted')); });
self.addEventListener('backgroundfetchclick', (e) => { e.waitUntil(jmOpenDownloads()); });

// Bonyeza notification ya "xx%" / "Imekamilika" (njia ya ndani ya ukurasa) β†’ fungua Downloads
self.addEventListener('notificationclick', (e) => {
  if (!/^jm-dl-/.test((e.notification && e.notification.tag) || '')) return;
  e.notification.close();
  e.waitUntil(jmOpenDownloads());
});