From b22130645af9965f5c5a58fbe6c6f6eabdb077a3 Mon Sep 17 00:00:00 2001 From: AvaLilac <257690424+AvaLilac@users.noreply.github.com> Date: Wed, 1 Apr 2026 18:08:46 -0400 Subject: [PATCH] adds the ability to login to stoat desktop with your session token Signed-off-by: AvaLilac <257690424+AvaLilac@users.noreply.github.com> --- avia_core/LoginWithToken.js | 139 ++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 avia_core/LoginWithToken.js diff --git a/avia_core/LoginWithToken.js b/avia_core/LoginWithToken.js new file mode 100644 index 0000000..2c778c9 --- /dev/null +++ b/avia_core/LoginWithToken.js @@ -0,0 +1,139 @@ +(function () { + if (window.__LOGIN_WITH_TOKEN__) return; + window.__LOGIN_WITH_TOKEN__ = true; + + async function loginWithToken(token) { + const res = await fetch('https://stoat.chat/api/users/@me', { + headers: { 'x-session-token': token } + }); + if (!res.ok) throw new Error('Invalid token'); + const user = await res.json(); + + const db = await new Promise((resolve, reject) => { + const r = indexedDB.open('localforage'); + r.onsuccess = () => resolve(r.result); + r.onerror = () => reject(r.error); + }); + + const tx = db.transaction('keyvaluepairs', 'readwrite'); + await new Promise((resolve, reject) => { + const r = tx.objectStore('keyvaluepairs').put({ + session: { + _id: user._id, + token: token, + userId: user._id, + valid: true + } + }, 'auth'); + r.onsuccess = () => resolve(); + r.onerror = () => reject(r.error); + }); + + location.reload(); + } + + function openTokenDialog() { + const backdrop = document.createElement('div'); + backdrop.className = 'top_0 left_0 right_0 bottom_0 pos_fixed z_100 max-h_100% d_grid us_none place-items_center pointer-events_all anim-n_scrimFadeIn anim-dur_0.1s anim-fm_forwards trs_var(--transitions-medium)_all p_80px ov-y_auto'; + backdrop.style.cssText = '--background: rgba(0, 0, 0, 0.6);'; + + backdrop.innerHTML = ` +
+
+ Login With Token +
+
+ +
+
+
+ + +
+
+
+ `; + + document.body.appendChild(backdrop); + + const closeBtn = backdrop.querySelector('#lwt-close-btn'); + const loginBtn = backdrop.querySelector('#lwt-login-btn'); + const tokenInput = backdrop.querySelector('#lwt-token-input'); + + function close() { backdrop.remove(); } + + function setLoading(loading) { + loginBtn.disabled = loading; + loginBtn.style.cursor = loading ? 'not-allowed' : 'pointer'; + const ripple = loginBtn.querySelector('md-ripple'); + loginBtn.textContent = loading ? 'Logging in…' : 'Login'; + if (ripple) loginBtn.prepend(ripple); + } + + function setError(msg) { + loginBtn.disabled = false; + loginBtn.style.cursor = 'pointer'; + const ripple = loginBtn.querySelector('md-ripple'); + loginBtn.textContent = msg; + if (ripple) loginBtn.prepend(ripple); + setTimeout(() => { + loginBtn.textContent = 'Login'; + if (ripple) loginBtn.prepend(ripple); + }, 2000); + } + + backdrop.addEventListener('click', (e) => { if (e.target === backdrop) close(); }); + closeBtn.addEventListener('click', close); + + loginBtn.addEventListener('click', async () => { + const token = tokenInput.value?.trim(); + if (!token) { + setError('Enter a token!'); + return; + } + setLoading(true); + try { + await loginWithToken(token); + } catch (err) { + setError('Invalid token!'); + } + }); + } + + function injectLoginButton() { + const signUpBtn = [...document.querySelectorAll('button')] + .find(b => b.textContent.trim() === 'Sign Up'); + if (!signUpBtn) return; + + const parent = signUpBtn.parentElement; + if (parent.querySelector('[data-lwt-btn]')) return; + + const clone = signUpBtn.cloneNode(false); + clone.dataset.lwtBtn = 'true'; + clone.textContent = 'Login With Token'; + + const ripple = document.createElement('md-ripple'); + ripple.setAttribute('aria-hidden', 'true'); + clone.prepend(ripple); + + clone.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + openTokenDialog(); + }); + + signUpBtn.insertAdjacentElement('afterend', clone); + } + + let debounceTimer = null; + new MutationObserver(() => { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(injectLoginButton, 150); + }).observe(document.body, { childList: true, subtree: true }); + + injectLoginButton(); +})();