mirror of
https://github.com/619dev/PaperPhone.git
synced 2026-06-05 17:49:18 +08:00
98 lines
3.3 KiB
HTML
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>
|