Files
PaperPhone/client/index.html
2026-03-30 00:34:35 +08:00

98 lines
3.3 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="PaperPhone">
<meta name="theme-color" content="#07C160">
<meta name="description" content="PaperPhone - 端对端加密即时通讯">
<title>PaperPhone</title>
<!-- PWA -->
<link rel="manifest" href="/manifest.json">
<link rel="apple-touch-icon" href="/public/icons/icon-192.png">
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
<!-- Flaticon Uicons -->
<link href="https://cdn.jsdelivr.net/npm/@flaticon/flaticon-uicons@3.3.1/css/all/all.min.css" rel="stylesheet">
<!-- libsodium (local — no CDN dependency) -->
<script>
window._sodiumPromise = new Promise((resolve, reject) => {
const TIMEOUT = 30000; // 30s for slow mobile networks
const timer = setTimeout(() =>
reject(new Error('Crypto library load timeout')), TIMEOUT);
function loadScript(src) {
return new Promise((res, rej) => {
const s = document.createElement('script');
s.src = src;
s.onload = res;
s.onerror = () => rej(new Error('Failed to load: ' + src));
document.head.appendChild(s);
});
}
function tryLoad(attempt) {
const bust = attempt > 0 ? ('?v=' + Date.now()) : '';
return loadScript('/public/js/libsodium.js' + bust)
.then(() => loadScript('/public/js/libsodium-wrappers.js' + bust))
.then(async () => {
clearTimeout(timer);
await window.sodium.ready;
window._sodium = window.sodium;
resolve(window.sodium);
});
}
// Try once, retry with cache-bust on failure
tryLoad(0).catch(() => tryLoad(1)).catch(e => {
clearTimeout(timer);
reject(e);
});
});
</script>
<link rel="stylesheet" href="/src/style.css">
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/app.js"></script>
<!-- Median.co OneSignal Bridge -->
<script>
// Median (GoNative) calls this function on every page load with OneSignal info.
// Must be a global, synchronously-available function (not in a module).
function median_onesignal_info(info) {
if (!info || !info.oneSignalUserId) return;
// Wait for token to be available, then register player
var token = localStorage.getItem('pp_token');
if (!token) return;
fetch('/api/push/onesignal', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token,
},
body: JSON.stringify({
player_id: info.oneSignalUserId,
platform: info.platform || 'android',
}),
}).catch(function() {});
}
</script>
<!-- Service Worker -->
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js').catch(() => {});
}
</script>
</body>
</html>