kin: BATON-BIZ-002 Убрать hardcoded VAPID key из meta-тега, читать с /api/push/public-key
This commit is contained in:
parent
ea06309a6e
commit
6444b30d17
7 changed files with 488 additions and 159 deletions
103
frontend/app.js
103
frontend/app.js
|
|
@ -92,6 +92,21 @@ function _setStatus(msg, cls) {
|
|||
el.hidden = !msg;
|
||||
}
|
||||
|
||||
function _setRegStatus(msg, cls) {
|
||||
const el = document.getElementById('reg-status');
|
||||
if (!el) return;
|
||||
el.textContent = msg;
|
||||
el.className = 'reg-status' + (cls ? ' reg-status--' + cls : '');
|
||||
el.hidden = !msg;
|
||||
}
|
||||
|
||||
function _showView(id) {
|
||||
['view-login', 'view-register'].forEach((vid) => {
|
||||
const el = document.getElementById(vid);
|
||||
if (el) el.hidden = vid !== id;
|
||||
});
|
||||
}
|
||||
|
||||
function _updateNetworkIndicator() {
|
||||
const el = document.getElementById('indicator-network');
|
||||
if (!el) return;
|
||||
|
|
@ -210,6 +225,7 @@ async function _handleSignal() {
|
|||
|
||||
function _showOnboarding() {
|
||||
_showScreen('screen-onboarding');
|
||||
_showView('view-login');
|
||||
|
||||
const input = document.getElementById('name-input');
|
||||
const btn = document.getElementById('btn-confirm');
|
||||
|
|
@ -221,6 +237,24 @@ function _showOnboarding() {
|
|||
if (e.key === 'Enter' && !btn.disabled) _handleRegister();
|
||||
});
|
||||
btn.addEventListener('click', _handleRegister);
|
||||
|
||||
const btnToRegister = document.getElementById('btn-switch-to-register');
|
||||
if (btnToRegister) {
|
||||
btnToRegister.addEventListener('click', () => {
|
||||
_setRegStatus('', '');
|
||||
_showView('view-register');
|
||||
});
|
||||
}
|
||||
|
||||
const btnToLogin = document.getElementById('btn-switch-to-login');
|
||||
if (btnToLogin) {
|
||||
btnToLogin.addEventListener('click', () => _showView('view-login'));
|
||||
}
|
||||
|
||||
const btnRegister = document.getElementById('btn-register');
|
||||
if (btnRegister) {
|
||||
btnRegister.addEventListener('click', _handleSignUp);
|
||||
}
|
||||
}
|
||||
|
||||
function _showMain() {
|
||||
|
|
@ -295,6 +329,75 @@ async function _initPushSubscription(vapidPublicKey) {
|
|||
}
|
||||
}
|
||||
|
||||
// ========== Registration (account sign-up) ==========
|
||||
|
||||
async function _getPushSubscriptionForReg() {
|
||||
if (!('serviceWorker' in navigator) || !('PushManager' in window)) return null;
|
||||
try {
|
||||
const vapidKey = await _fetchVapidPublicKey();
|
||||
if (!vapidKey) return null;
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
const existing = await registration.pushManager.getSubscription();
|
||||
if (existing) return existing.toJSON();
|
||||
const applicationServerKey = _urlBase64ToUint8Array(vapidKey);
|
||||
const subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey,
|
||||
});
|
||||
return subscription.toJSON();
|
||||
} catch (err) {
|
||||
console.warn('[baton] Push subscription for registration failed:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function _handleSignUp() {
|
||||
const emailInput = document.getElementById('reg-email');
|
||||
const loginInput = document.getElementById('reg-login');
|
||||
const passwordInput = document.getElementById('reg-password');
|
||||
const btn = document.getElementById('btn-register');
|
||||
if (!emailInput || !loginInput || !passwordInput || !btn) return;
|
||||
|
||||
const email = emailInput.value.trim();
|
||||
const login = loginInput.value.trim();
|
||||
const password = passwordInput.value;
|
||||
|
||||
if (!email || !login || !password) {
|
||||
_setRegStatus('Заполните все поля.', 'error');
|
||||
return;
|
||||
}
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
_setRegStatus('Введите корректный email.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
const originalText = btn.textContent.trim();
|
||||
btn.textContent = '...';
|
||||
_setRegStatus('', '');
|
||||
|
||||
try {
|
||||
const push_subscription = await _getPushSubscriptionForReg().catch(() => null);
|
||||
await _apiPost('/api/auth/register', { email, login, password, push_subscription });
|
||||
passwordInput.value = '';
|
||||
_setRegStatus('Заявка отправлена. Ожидайте подтверждения администратора.', 'success');
|
||||
} catch (err) {
|
||||
let msg = 'Ошибка. Попробуйте ещё раз.';
|
||||
if (err && err.message) {
|
||||
const colonIdx = err.message.indexOf(': ');
|
||||
if (colonIdx !== -1) {
|
||||
try {
|
||||
const parsed = JSON.parse(err.message.slice(colonIdx + 2));
|
||||
if (parsed.detail) msg = parsed.detail;
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
_setRegStatus(msg, 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = originalText;
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Init ==========
|
||||
|
||||
function _init() {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@
|
|||
|
||||
<!-- Onboarding screen: shown on first visit (no UUID registered yet) -->
|
||||
<div id="screen-onboarding" class="screen" role="main" hidden>
|
||||
<div class="screen-content">
|
||||
|
||||
<!-- View: name entry (existing onboarding) -->
|
||||
<div class="screen-content" id="view-login">
|
||||
<input
|
||||
type="text"
|
||||
id="name-input"
|
||||
|
|
@ -52,7 +54,53 @@
|
|||
<button type="button" id="btn-confirm" class="btn-confirm" disabled>
|
||||
Confirm
|
||||
</button>
|
||||
<button type="button" id="btn-switch-to-register" class="btn-link">
|
||||
Зарегистрироваться
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- View: account registration -->
|
||||
<div class="screen-content" id="view-register" hidden>
|
||||
<input
|
||||
type="email"
|
||||
id="reg-email"
|
||||
class="name-input"
|
||||
placeholder="Email"
|
||||
autocomplete="email"
|
||||
autocorrect="off"
|
||||
autocapitalize="none"
|
||||
spellcheck="false"
|
||||
aria-label="Email"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="reg-login"
|
||||
class="name-input"
|
||||
placeholder="Логин"
|
||||
maxlength="64"
|
||||
autocomplete="username"
|
||||
autocorrect="off"
|
||||
autocapitalize="none"
|
||||
spellcheck="false"
|
||||
aria-label="Логин"
|
||||
>
|
||||
<input
|
||||
type="password"
|
||||
id="reg-password"
|
||||
class="name-input"
|
||||
placeholder="Пароль"
|
||||
autocomplete="new-password"
|
||||
aria-label="Пароль"
|
||||
>
|
||||
<button type="button" id="btn-register" class="btn-confirm">
|
||||
Зарегистрироваться
|
||||
</button>
|
||||
<button type="button" id="btn-switch-to-login" class="btn-link">
|
||||
← Назад
|
||||
</button>
|
||||
<div id="reg-status" class="reg-status" hidden></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Main screen: SOS button -->
|
||||
|
|
|
|||
|
|
@ -198,3 +198,35 @@ body {
|
|||
.status[hidden] { display: none; }
|
||||
.status--error { color: #f87171; }
|
||||
.status--success { color: #4ade80; }
|
||||
|
||||
/* ===== Registration form ===== */
|
||||
|
||||
/* Override display:flex so [hidden] works on screen-content divs */
|
||||
.screen-content[hidden] { display: none; }
|
||||
|
||||
.btn-link {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
padding: 4px 0;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.btn-link:active { color: var(--text); }
|
||||
|
||||
.reg-status {
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.reg-status[hidden] { display: none; }
|
||||
.reg-status--error { color: #f87171; }
|
||||
.reg-status--success { color: #4ade80; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue