Add web push notifications
This commit is contained in:
parent
91cb65e896
commit
4e4def843d
5 changed files with 92 additions and 3 deletions
32
index.php
32
index.php
|
|
@ -97,9 +97,37 @@ usort($videos, function($a, $b) use ($videoCache) {
|
||||||
$page=max(1,(int)($_GET['page']??1)); $per=$config['items_per_page']; $total=count($videos); $pages=max(1,(int)ceil($total/$per)); $page=min($page,$pages); $slice=array_slice($videos,($page-1)*$per,$per);
|
$page=max(1,(int)($_GET['page']??1)); $per=$config['items_per_page']; $total=count($videos); $pages=max(1,(int)ceil($total/$per)); $page=min($page,$pages); $slice=array_slice($videos,($page-1)*$per,$per);
|
||||||
?>
|
?>
|
||||||
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>MVLog</title><link rel="alternate" type="application/rss+xml" title="MVLog RSS" href="feed.php"><link rel="icon" type="image/png" href="assets/img/moto_travel.png"><link rel="stylesheet" href="style.css"></head><body>
|
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>MVLog</title><link rel="alternate" type="application/rss+xml" title="MVLog RSS" href="feed.php"><link rel="icon" type="image/png" href="assets/img/moto_travel.png"><link rel="stylesheet" href="style.css"></head><body>
|
||||||
<header class="site-header"><div class="brand-wrap"><a class="header-logo" href="index.php" aria-label="MVLog home"><img src="assets/img/moto_travel.png" alt=""></a><a class="brand" href="index.php"><h1>MVLog</h1><p>Bubulescu.Org</p></a></div><a class="rss-link" href="feed.php" aria-label="RSS feed" title="RSS feed"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.2 17.8a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM2.2 8.7v3.1a10 10 0 0 1 10 10h3.1A13.1 13.1 0 0 0 2.2 8.7Zm0-5.5v3.1a15.5 15.5 0 0 1 15.5 15.5h3.1A18.6 18.6 0 0 0 2.2 3.2Z"/></svg></a></header><main>
|
<header class="site-header"><div class="brand-wrap"><a class="header-logo" href="index.php" aria-label="MVLog home"><img src="assets/img/moto_travel.png" alt=""></a><a class="brand" href="index.php"><h1>MVLog</h1><p>Bubulescu.Org</p></a></div><button id="push-toggle" class="push-toggle" type="button" hidden aria-label="Notifications" title="Notifications"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 22a2.7 2.7 0 0 0 2.6-2h-5.2A2.7 2.7 0 0 0 12 22Zm7-6V11a7 7 0 0 0-5-6.7V3a2 2 0 0 0-4 0v1.3A7 7 0 0 0 5 11v5l-2 2v1h18v-1l-2-2Z"/></svg></button><a class="rss-link" href="feed.php" aria-label="RSS feed" title="RSS feed"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.2 17.8a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM2.2 8.7v3.1a10 10 0 0 1 10 10h3.1A13.1 13.1 0 0 0 2.2 8.7Zm0-5.5v3.1a15.5 15.5 0 0 1 15.5 15.5h3.1A18.6 18.6 0 0 0 2.2 3.2Z"/></svg></a></header><main>
|
||||||
<section id="videos"><?php if(!$slice): ?><p>No videos yet.</p><?php endif; ?><div class="video-list">
|
<section id="videos"><?php if(!$slice): ?><p>No videos yet.</p><?php endif; ?><div class="video-list">
|
||||||
<?php foreach($slice as $v): $name=basename($v); $url=$config['public_videos'].'/'.rawurlencode($name); $m=$videoCache[$name]['metadata'] ?? video_metadata($v); ?>
|
<?php foreach($slice as $v): $name=basename($v); $url=$config['public_videos'].'/'.rawurlencode($name); $m=$videoCache[$name]['metadata'] ?? video_metadata($v); ?>
|
||||||
<article class="video-row"><time class="created-at" datetime="<?=h(date('c', filemtime($v)))?>"><?=h(date('d.m.Y H:i', filemtime($v)))?></time><video controls controlsList="nodownload" oncontextmenu="return false" preload="metadata" src="<?=h($url)?>"></video><div class="video-info"><h2><?=h($m['title'])?></h2><p class="meta"><span><?=h($m['date'])?></span><?php if($m['location']): ?><span><?=h($m['location'])?></span><?php endif; ?></p><?php if($m['description']): ?><p class="description"><?=nl2br(h($m['description']), false)?></p><?php endif; ?></div></article>
|
<article class="video-row"><time class="created-at" datetime="<?=h(date('c', filemtime($v)))?>"><?=h(date('d.m.Y H:i', filemtime($v)))?></time><video controls controlsList="nodownload" oncontextmenu="return false" preload="metadata" src="<?=h($url)?>"></video><div class="video-info"><h2><?=h($m['title'])?></h2><p class="meta"><span><?=h($m['date'])?></span><?php if($m['location']): ?><span><?=h($m['location'])?></span><?php endif; ?></p><?php if($m['description']): ?><p class="description"><?=nl2br(h($m['description']), false)?></p><?php endif; ?></div></article>
|
||||||
<?php endforeach; ?></div><div class="pages"><?php for($i=1;$i<=$pages;$i++): ?><a class="<?=$i===$page?'active':''?>" href="?page=<?=$i?>"><?=$i?></a><?php endfor; ?></div></section>
|
<?php endforeach; ?></div><div class="pages"><?php for($i=1;$i<=$pages;$i++): ?><a class="<?=$i===$page?'active':''?>" href="?page=<?=$i?>"><?=$i?></a><?php endfor; ?></div></section>
|
||||||
</main><footer>Bubulescu.Org</footer></body></html>
|
</main><footer>Bubulescu.Org</footer><script>
|
||||||
|
(function(){
|
||||||
|
const btn=document.getElementById('push-toggle');
|
||||||
|
if(!btn || !('serviceWorker' in navigator) || !('PushManager' in window) || !('Notification' in window)) return;
|
||||||
|
function b64uToUint8Array(s){ const pad='='.repeat((4-s.length%4)%4); const b64=(s+pad).replace(/-/g,'+').replace(/_/g,'/'); const raw=atob(b64); return Uint8Array.from([...raw].map(c=>c.charCodeAt(0))); }
|
||||||
|
async function state(reg){ const sub=await reg.pushManager.getSubscription(); btn.classList.toggle('active', !!sub); btn.title=sub?'Notifications on':'Notifications'; btn.hidden=false; return sub; }
|
||||||
|
navigator.serviceWorker.register('sw.js').then(async reg=>{
|
||||||
|
const cfg=await fetch('push_config.php',{cache:'no-store'}).then(r=>r.json()).catch(()=>({enabled:false}));
|
||||||
|
if(!cfg.enabled || !cfg.publicKey) return;
|
||||||
|
await state(reg);
|
||||||
|
btn.addEventListener('click', async ()=>{
|
||||||
|
btn.disabled=true;
|
||||||
|
try{
|
||||||
|
let sub=await reg.pushManager.getSubscription();
|
||||||
|
if(sub){
|
||||||
|
return;
|
||||||
|
}else{
|
||||||
|
const perm=await Notification.requestPermission();
|
||||||
|
if(perm!=='granted') return;
|
||||||
|
sub=await reg.pushManager.subscribe({userVisibleOnly:true,applicationServerKey:b64uToUint8Array(cfg.publicKey)});
|
||||||
|
await fetch('push_subscribe.php',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(sub)});
|
||||||
|
}
|
||||||
|
await state(reg);
|
||||||
|
}catch(e){ alert(e.message || 'Could not update notifications'); }
|
||||||
|
finally{ btn.disabled=false; }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script></body></html>
|
||||||
|
|
|
||||||
11
push_config.php
Normal file
11
push_config.php
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
header('Cache-Control: no-store');
|
||||||
|
$cfgFile = '/etc/mvlog/push.php';
|
||||||
|
if (!is_file($cfgFile)) {
|
||||||
|
echo json_encode(['enabled' => false]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$cfg = require $cfgFile;
|
||||||
|
$public = (string)($cfg['public_key'] ?? '');
|
||||||
|
echo json_encode(['enabled' => $public !== '', 'publicKey' => $public], JSON_UNESCAPED_SLASHES);
|
||||||
25
push_subscribe.php
Normal file
25
push_subscribe.php
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
header('Content-Type: application/json; charset=UTF-8');
|
||||||
|
header('Cache-Control: no-store');
|
||||||
|
$raw = file_get_contents('php://input');
|
||||||
|
$data = json_decode((string)$raw, true);
|
||||||
|
$endpoint = is_array($data) ? (string)($data['endpoint'] ?? '') : '';
|
||||||
|
if ($endpoint === '') { http_response_code(400); echo json_encode(['ok'=>false,'error'=>'missing endpoint']); exit; }
|
||||||
|
$cacheDir = __DIR__ . '/cache';
|
||||||
|
if (!is_dir($cacheDir)) mkdir($cacheDir, 0775, true);
|
||||||
|
$file = $cacheDir . '/push_subscriptions.json';
|
||||||
|
$subs = is_file($file) ? json_decode((string)file_get_contents($file), true) : [];
|
||||||
|
if (!is_array($subs)) $subs = [];
|
||||||
|
if (!empty($data['_delete'])) {
|
||||||
|
$subs = array_values(array_filter($subs, fn($s)=>!is_array($s) || ($s['endpoint'] ?? '') !== $endpoint));
|
||||||
|
} else {
|
||||||
|
$found = false;
|
||||||
|
foreach ($subs as &$sub) {
|
||||||
|
if (is_array($sub) && ($sub['endpoint'] ?? '') === $endpoint) { $sub = $data; $found = true; break; }
|
||||||
|
}
|
||||||
|
unset($sub);
|
||||||
|
if (!$found) $subs[] = $data;
|
||||||
|
}
|
||||||
|
file_put_contents($file, json_encode($subs, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX);
|
||||||
|
chmod($file, 0664);
|
||||||
|
echo json_encode(['ok'=>true,'count'=>count($subs)]);
|
||||||
File diff suppressed because one or more lines are too long
25
sw.js
Normal file
25
sw.js
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
self.addEventListener('push', event => {
|
||||||
|
let data = {};
|
||||||
|
try { data = event.data ? event.data.json() : {}; } catch (e) { data = { title: 'MVLog', body: event.data ? event.data.text() : 'New video published' }; }
|
||||||
|
const title = data.title || 'MVLog';
|
||||||
|
const options = {
|
||||||
|
body: data.body || 'New video published',
|
||||||
|
icon: 'assets/img/moto_travel.png',
|
||||||
|
badge: 'assets/img/moto_travel.png',
|
||||||
|
data: { url: data.url || './' },
|
||||||
|
tag: data.tag || 'mvlog-new-video',
|
||||||
|
renotify: true
|
||||||
|
};
|
||||||
|
event.waitUntil(self.registration.showNotification(title, options));
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener('notificationclick', event => {
|
||||||
|
event.notification.close();
|
||||||
|
const url = event.notification.data && event.notification.data.url ? event.notification.data.url : './';
|
||||||
|
event.waitUntil(clients.matchAll({ type: 'window', includeUncontrolled: true }).then(clientList => {
|
||||||
|
for (const client of clientList) {
|
||||||
|
if (client.url === url && 'focus' in client) return client.focus();
|
||||||
|
}
|
||||||
|
return clients.openWindow(url);
|
||||||
|
}));
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue