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();
+})();