incognitolm commited on
Commit ·
9989593
1
Parent(s): 87c5cf1
TOS & Subscription Info
Browse files- public/js/auth.js +53 -9
- public/js/settings.js +4 -0
- server/auth.js +13 -2
- server/wsHandler.js +15 -3
public/js/auth.js
CHANGED
|
@@ -108,31 +108,43 @@ export async function logout() {
|
|
| 108 |
// ── Session handling ──────────────────────────────────────────────────────
|
| 109 |
|
| 110 |
async function handleSupabaseSession(data) {
|
|
|
|
| 111 |
if (!data.access_token) throw new Error('No access token');
|
| 112 |
saveAuth({ access_token: data.access_token, refresh_token: data.refresh_token, user: data.user });
|
| 113 |
|
| 114 |
return new Promise((resolve, reject) => {
|
| 115 |
const tempId = getTempId();
|
| 116 |
const clientId = getClientId();
|
|
|
|
| 117 |
send({ type: 'auth:login', accessToken: data.access_token, refreshToken: data.refresh_token,
|
| 118 |
tempId, clientId });
|
| 119 |
|
| 120 |
-
const unsubOk = on('auth:ok', (msg) => {
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
|
| 123 |
setTimeout(() => { unsubOk(); unsubErr(); reject(new Error('Auth timeout')); }, 12000);
|
| 124 |
});
|
| 125 |
}
|
| 126 |
|
| 127 |
function applyAuthOk(msg) {
|
|
|
|
| 128 |
currentUser = { id: msg.userId, email: msg.email };
|
| 129 |
userProfile = msg.profile;
|
| 130 |
userSettings = msg.settings;
|
| 131 |
subscriptionInfo = msg.subscription || null;
|
| 132 |
-
console.log('Auth
|
|
|
|
|
|
|
| 133 |
const auth = loadAuth() || {};
|
| 134 |
saveAuth({ ...auth, userId: msg.userId, deviceToken: msg.deviceToken });
|
| 135 |
notifyListeners();
|
|
|
|
| 136 |
updateSidebarProfile();
|
| 137 |
// Apply saved theme
|
| 138 |
if (msg.settings?.theme) {
|
|
@@ -174,11 +186,21 @@ on('settings:updated', (msg) => {
|
|
| 174 |
// ── Reconnect on WS connect ────────────────────────────────────────────────
|
| 175 |
|
| 176 |
on('ws:connected', async () => {
|
|
|
|
| 177 |
const auth = loadAuth();
|
|
|
|
| 178 |
if (auth?.access_token) {
|
| 179 |
-
try {
|
| 180 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
} else {
|
|
|
|
| 182 |
initAsGuest();
|
| 183 |
}
|
| 184 |
});
|
|
@@ -191,16 +213,25 @@ on('ws:connected', async () => {
|
|
| 191 |
|
| 192 |
window.addEventListener('storage', async (e) => {
|
| 193 |
if (e.key !== OAUTH_PENDING_KEY || !e.newValue) return;
|
|
|
|
| 194 |
// Consume immediately so other tabs don't also try to log in
|
| 195 |
localStorage.removeItem(OAUTH_PENDING_KEY);
|
| 196 |
let tokens;
|
| 197 |
-
try { tokens = JSON.parse(e.newValue); } catch {
|
| 198 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
try {
|
| 200 |
await handleSupabaseSession(tokens);
|
| 201 |
import('./ui.js').then(({ showNotification }) =>
|
| 202 |
showNotification({ type: 'success', message: 'Signed in!', duration: 2500 }));
|
| 203 |
} catch (err) {
|
|
|
|
| 204 |
import('./ui.js').then(({ showNotification }) =>
|
| 205 |
showNotification({ type: 'error', message: `Sign-in failed: ${err.message}`, duration: 4000 }));
|
| 206 |
}
|
|
@@ -210,9 +241,13 @@ window.addEventListener('storage', async (e) => {
|
|
| 210 |
(function checkOAuthRedirect() {
|
| 211 |
const params = new URLSearchParams(location.search);
|
| 212 |
const t = params.get('t'), r = params.get('r');
|
|
|
|
| 213 |
if (params.get('oauth') === '1' && t) {
|
|
|
|
| 214 |
history.replaceState({}, '', '/');
|
| 215 |
-
handleSupabaseSession({ access_token: t, refresh_token: r || '' }).catch(() => {
|
|
|
|
|
|
|
| 216 |
}
|
| 217 |
})();
|
| 218 |
|
|
@@ -220,10 +255,16 @@ window.addEventListener('storage', async (e) => {
|
|
| 220 |
window.addEventListener('message', async (e) => {
|
| 221 |
if (e.origin !== location.origin) return;
|
| 222 |
if (e.data?.type !== 'oauth:callback') return;
|
|
|
|
| 223 |
const { access_token, refresh_token } = e.data;
|
| 224 |
-
if (!access_token)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
try { await handleSupabaseSession({ access_token, refresh_token }); }
|
| 226 |
catch (err) {
|
|
|
|
| 227 |
import('./ui.js').then(({ showNotification }) =>
|
| 228 |
showNotification({ type: 'error', message: `Sign-in failed: ${err.message}`, duration: 4000 }));
|
| 229 |
}
|
|
@@ -232,6 +273,7 @@ window.addEventListener('message', async (e) => {
|
|
| 232 |
// ── Sidebar profile ───────────────────────────────────────────────────────
|
| 233 |
|
| 234 |
export function updateSidebarProfile() {
|
|
|
|
| 235 |
const guestEl = document.getElementById('guest-section');
|
| 236 |
const userEl = document.getElementById('user-section');
|
| 237 |
const nameEl = document.getElementById('user-name-display');
|
|
@@ -239,6 +281,7 @@ export function updateSidebarProfile() {
|
|
| 239 |
const avatarEl= document.getElementById('user-avatar');
|
| 240 |
|
| 241 |
if (!currentUser) {
|
|
|
|
| 242 |
guestEl?.classList.remove('hidden');
|
| 243 |
userEl?.classList.add('hidden');
|
| 244 |
return;
|
|
@@ -252,6 +295,7 @@ export function updateSidebarProfile() {
|
|
| 252 |
|
| 253 |
const plan = subscriptionInfo?.planKey || 'free';
|
| 254 |
const planName = subscriptionInfo?.planName || 'Free';
|
|
|
|
| 255 |
if (planEl) { planEl.textContent = planName; planEl.setAttribute('data-plan', plan); }
|
| 256 |
}
|
| 257 |
|
|
|
|
| 108 |
// ── Session handling ──────────────────────────────────────────────────────
|
| 109 |
|
| 110 |
async function handleSupabaseSession(data) {
|
| 111 |
+
console.log('[Frontend Auth] handleSupabaseSession called with token:', data.access_token?.slice(0, 20) + '...');
|
| 112 |
if (!data.access_token) throw new Error('No access token');
|
| 113 |
saveAuth({ access_token: data.access_token, refresh_token: data.refresh_token, user: data.user });
|
| 114 |
|
| 115 |
return new Promise((resolve, reject) => {
|
| 116 |
const tempId = getTempId();
|
| 117 |
const clientId = getClientId();
|
| 118 |
+
console.log('[Frontend Auth] Sending auth:login to backend with token:', data.access_token?.slice(0, 20) + '...');
|
| 119 |
send({ type: 'auth:login', accessToken: data.access_token, refreshToken: data.refresh_token,
|
| 120 |
tempId, clientId });
|
| 121 |
|
| 122 |
+
const unsubOk = on('auth:ok', (msg) => {
|
| 123 |
+
console.log('[Frontend Auth] Received auth:ok response');
|
| 124 |
+
unsubOk(); unsubErr(); applyAuthOk(msg); resolve(msg);
|
| 125 |
+
});
|
| 126 |
+
const unsubErr = on('auth:error', (msg) => {
|
| 127 |
+
console.error('[Frontend Auth] Received auth:error:', msg.message);
|
| 128 |
+
unsubOk(); unsubErr(); reject(new Error(msg.message));
|
| 129 |
+
});
|
| 130 |
|
| 131 |
setTimeout(() => { unsubOk(); unsubErr(); reject(new Error('Auth timeout')); }, 12000);
|
| 132 |
});
|
| 133 |
}
|
| 134 |
|
| 135 |
function applyAuthOk(msg) {
|
| 136 |
+
console.log('[Frontend Auth] applyAuthOk called with msg:', msg);
|
| 137 |
currentUser = { id: msg.userId, email: msg.email };
|
| 138 |
userProfile = msg.profile;
|
| 139 |
userSettings = msg.settings;
|
| 140 |
subscriptionInfo = msg.subscription || null;
|
| 141 |
+
console.log('[Frontend Auth] Subscription info set to:', subscriptionInfo);
|
| 142 |
+
console.log('[Frontend Auth] subscriptionInfo?.planKey:', subscriptionInfo?.planKey);
|
| 143 |
+
console.log('[Frontend Auth] subscriptionInfo?.planName:', subscriptionInfo?.planName);
|
| 144 |
const auth = loadAuth() || {};
|
| 145 |
saveAuth({ ...auth, userId: msg.userId, deviceToken: msg.deviceToken });
|
| 146 |
notifyListeners();
|
| 147 |
+
console.log('[Frontend Auth] Calling updateSidebarProfile...');
|
| 148 |
updateSidebarProfile();
|
| 149 |
// Apply saved theme
|
| 150 |
if (msg.settings?.theme) {
|
|
|
|
| 186 |
// ── Reconnect on WS connect ────────────────────────────────────────────────
|
| 187 |
|
| 188 |
on('ws:connected', async () => {
|
| 189 |
+
console.log('[Frontend Auth] WS connected event');
|
| 190 |
const auth = loadAuth();
|
| 191 |
+
console.log('[Frontend Auth] Loaded auth:', auth ? 'exists' : 'null');
|
| 192 |
if (auth?.access_token) {
|
| 193 |
+
try {
|
| 194 |
+
console.log('[Frontend Auth] Attempting to resume session with existing token');
|
| 195 |
+
await handleSupabaseSession(auth);
|
| 196 |
+
}
|
| 197 |
+
catch (err) {
|
| 198 |
+
console.error('[Frontend Auth] Failed to resume session:', err);
|
| 199 |
+
clearAuth();
|
| 200 |
+
initAsGuest();
|
| 201 |
+
}
|
| 202 |
} else {
|
| 203 |
+
console.log('[Frontend Auth] No stored auth, initializing as guest');
|
| 204 |
initAsGuest();
|
| 205 |
}
|
| 206 |
});
|
|
|
|
| 213 |
|
| 214 |
window.addEventListener('storage', async (e) => {
|
| 215 |
if (e.key !== OAUTH_PENDING_KEY || !e.newValue) return;
|
| 216 |
+
console.log('[Frontend Auth] OAuth pending key detected in localStorage');
|
| 217 |
// Consume immediately so other tabs don't also try to log in
|
| 218 |
localStorage.removeItem(OAUTH_PENDING_KEY);
|
| 219 |
let tokens;
|
| 220 |
+
try { tokens = JSON.parse(e.newValue); } catch {
|
| 221 |
+
console.error('[Frontend Auth] Failed to parse OAuth tokens');
|
| 222 |
+
return;
|
| 223 |
+
}
|
| 224 |
+
if (!tokens?.access_token) {
|
| 225 |
+
console.warn('[Frontend Auth] No access token in OAuth response');
|
| 226 |
+
return;
|
| 227 |
+
}
|
| 228 |
+
console.log('[Frontend Auth] Processing OAuth tokens:', tokens.access_token?.slice(0, 20) + '...');
|
| 229 |
try {
|
| 230 |
await handleSupabaseSession(tokens);
|
| 231 |
import('./ui.js').then(({ showNotification }) =>
|
| 232 |
showNotification({ type: 'success', message: 'Signed in!', duration: 2500 }));
|
| 233 |
} catch (err) {
|
| 234 |
+
console.error('[Frontend Auth] OAuth sign-in error:', err);
|
| 235 |
import('./ui.js').then(({ showNotification }) =>
|
| 236 |
showNotification({ type: 'error', message: `Sign-in failed: ${err.message}`, duration: 4000 }));
|
| 237 |
}
|
|
|
|
| 241 |
(function checkOAuthRedirect() {
|
| 242 |
const params = new URLSearchParams(location.search);
|
| 243 |
const t = params.get('t'), r = params.get('r');
|
| 244 |
+
console.log('[Frontend Auth] Checking for OAuth redirect params:', t ? 'found token' : 'no token');
|
| 245 |
if (params.get('oauth') === '1' && t) {
|
| 246 |
+
console.log('[Frontend Auth] Processing OAuth redirect with token:', t.slice(0, 20) + '...');
|
| 247 |
history.replaceState({}, '', '/');
|
| 248 |
+
handleSupabaseSession({ access_token: t, refresh_token: r || '' }).catch((err) => {
|
| 249 |
+
console.error('[Frontend Auth] OAuth redirect failed:', err);
|
| 250 |
+
});
|
| 251 |
}
|
| 252 |
})();
|
| 253 |
|
|
|
|
| 255 |
window.addEventListener('message', async (e) => {
|
| 256 |
if (e.origin !== location.origin) return;
|
| 257 |
if (e.data?.type !== 'oauth:callback') return;
|
| 258 |
+
console.log('[Frontend Auth] postMessage oauth:callback received');
|
| 259 |
const { access_token, refresh_token } = e.data;
|
| 260 |
+
if (!access_token) {
|
| 261 |
+
console.warn('[Frontend Auth] No access_token in postMessage oauth:callback');
|
| 262 |
+
return;
|
| 263 |
+
}
|
| 264 |
+
console.log('[Frontend Auth] Processing postMessage tokens:', access_token?.slice(0, 20) + '...');
|
| 265 |
try { await handleSupabaseSession({ access_token, refresh_token }); }
|
| 266 |
catch (err) {
|
| 267 |
+
console.error('[Frontend Auth] postMessage sign-in error:', err);
|
| 268 |
import('./ui.js').then(({ showNotification }) =>
|
| 269 |
showNotification({ type: 'error', message: `Sign-in failed: ${err.message}`, duration: 4000 }));
|
| 270 |
}
|
|
|
|
| 273 |
// ── Sidebar profile ───────────────────────────────────────────────────────
|
| 274 |
|
| 275 |
export function updateSidebarProfile() {
|
| 276 |
+
console.log('[Frontend Auth] updateSidebarProfile called - currentUser:', currentUser);
|
| 277 |
const guestEl = document.getElementById('guest-section');
|
| 278 |
const userEl = document.getElementById('user-section');
|
| 279 |
const nameEl = document.getElementById('user-name-display');
|
|
|
|
| 281 |
const avatarEl= document.getElementById('user-avatar');
|
| 282 |
|
| 283 |
if (!currentUser) {
|
| 284 |
+
console.log('[Frontend Auth] Not logged in, showing guest section');
|
| 285 |
guestEl?.classList.remove('hidden');
|
| 286 |
userEl?.classList.add('hidden');
|
| 287 |
return;
|
|
|
|
| 295 |
|
| 296 |
const plan = subscriptionInfo?.planKey || 'free';
|
| 297 |
const planName = subscriptionInfo?.planName || 'Free';
|
| 298 |
+
console.log('[Frontend Auth] Setting plan display - plan:', plan, 'planName:', planName, 'subscriptionInfo:', subscriptionInfo);
|
| 299 |
if (planEl) { planEl.textContent = planName; planEl.setAttribute('data-plan', plan); }
|
| 300 |
}
|
| 301 |
|
public/js/settings.js
CHANGED
|
@@ -250,11 +250,15 @@ function setupAccountSettings(b) {
|
|
| 250 |
|
| 251 |
const subHandler = (msg) => {
|
| 252 |
off('account:subscription', subHandler);
|
|
|
|
| 253 |
const planEl = b.querySelector('#plan-name-display');
|
| 254 |
if (planEl && msg.info) {
|
| 255 |
const pKey = msg.info.planKey || 'free';
|
| 256 |
const pName = msg.info.planName || 'Free Tier';
|
|
|
|
| 257 |
planEl.innerHTML = `<span style="color:var(--plan-${pKey})">${escHtml(pName)}</span>`;
|
|
|
|
|
|
|
| 258 |
}
|
| 259 |
};
|
| 260 |
on('account:subscription', subHandler);
|
|
|
|
| 250 |
|
| 251 |
const subHandler = (msg) => {
|
| 252 |
off('account:subscription', subHandler);
|
| 253 |
+
console.log('[Settings] Received account:subscription:', msg);
|
| 254 |
const planEl = b.querySelector('#plan-name-display');
|
| 255 |
if (planEl && msg.info) {
|
| 256 |
const pKey = msg.info.planKey || 'free';
|
| 257 |
const pName = msg.info.planName || 'Free Tier';
|
| 258 |
+
console.log('[Settings] Updating plan display - key:', pKey, 'name:', pName);
|
| 259 |
planEl.innerHTML = `<span style="color:var(--plan-${pKey})">${escHtml(pName)}</span>`;
|
| 260 |
+
} else {
|
| 261 |
+
console.warn('[Settings] No plan info available:', msg.info);
|
| 262 |
}
|
| 263 |
};
|
| 264 |
on('account:subscription', subHandler);
|
server/auth.js
CHANGED
|
@@ -62,11 +62,22 @@ export async function setUsername(userId, accessToken, username) {
|
|
| 62 |
|
| 63 |
export async function getSubscriptionInfo(accessToken) {
|
| 64 |
try {
|
|
|
|
| 65 |
const r = await fetch('https://sharktide-lightning.hf.space/subscription', {
|
| 66 |
headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' },
|
| 67 |
});
|
| 68 |
-
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
}
|
| 71 |
|
| 72 |
export async function getTierConfig() {
|
|
|
|
| 62 |
|
| 63 |
export async function getSubscriptionInfo(accessToken) {
|
| 64 |
try {
|
| 65 |
+
console.log('[Subscription] Fetching subscription info for token:', accessToken?.slice(0, 20) + '...');
|
| 66 |
const r = await fetch('https://sharktide-lightning.hf.space/subscription', {
|
| 67 |
headers: { Authorization: `Bearer ${accessToken}`, Accept: 'application/json' },
|
| 68 |
});
|
| 69 |
+
console.log('[Subscription] Response status:', r.status);
|
| 70 |
+
if (!r.ok) {
|
| 71 |
+
console.warn(`[Subscription] Failed: HTTP ${r.status}`);
|
| 72 |
+
return null;
|
| 73 |
+
}
|
| 74 |
+
const data = await r.json();
|
| 75 |
+
console.log('[Subscription] Response data:', data);
|
| 76 |
+
return data;
|
| 77 |
+
} catch (err) {
|
| 78 |
+
console.error('[Subscription] Error fetching subscription info:', err.message);
|
| 79 |
+
return null;
|
| 80 |
+
}
|
| 81 |
}
|
| 82 |
|
| 83 |
export async function getTierConfig() {
|
server/wsHandler.js
CHANGED
|
@@ -78,9 +78,11 @@ const handlers = {
|
|
| 78 |
|
| 79 |
'auth:login': async (ws, msg, client, wsClients) => {
|
| 80 |
const { accessToken, tempId: clientTempId } = msg;
|
|
|
|
| 81 |
if (!accessToken) return safeSend(ws, { type: 'auth:error', message: 'Missing token' });
|
| 82 |
const user = await verifySupabaseToken(accessToken);
|
| 83 |
if (!user) return safeSend(ws, { type: 'auth:error', message: 'Invalid token' });
|
|
|
|
| 84 |
|
| 85 |
client.userId = user.id; client.accessToken = accessToken; client.authenticated = true;
|
| 86 |
client.deviceToken = deviceSessionStore.create(user.id, client.ip, client.userAgent);
|
|
@@ -90,15 +92,19 @@ const handlers = {
|
|
| 90 |
const tId = client.tempId;
|
| 91 |
await sessionStore.transferTempToUser(tId, user.id, accessToken);
|
| 92 |
|
|
|
|
| 93 |
const [sessions, settings, profile, subscription] = await Promise.all([
|
| 94 |
sessionStore.loadUserSessions(user.id, accessToken),
|
| 95 |
getUserSettings(user.id, accessToken),
|
| 96 |
getUserProfile(user.id, accessToken),
|
| 97 |
getSubscriptionInfo(accessToken),
|
| 98 |
]);
|
|
|
|
| 99 |
|
| 100 |
-
|
| 101 |
-
deviceToken: client.deviceToken, sessions: sessions.map(ser), settings, profile, subscription }
|
|
|
|
|
|
|
| 102 |
|
| 103 |
bcast(wsClients, user.id, { type: 'auth:newLogin', message: 'New login on your account.',
|
| 104 |
ip: client.ip, userAgent: client.userAgent, timestamp: new Date().toISOString() }, ws);
|
|
@@ -381,7 +387,13 @@ const handlers = {
|
|
| 381 |
|
| 382 |
'account:getProfile': async (ws, msg, c) => { if (!c.userId) return; safeSend(ws, { type: 'account:profile', profile: await getUserProfile(c.userId, c.accessToken) }); },
|
| 383 |
'account:setUsername': async (ws, msg, c) => { if (!c.userId) return; safeSend(ws, { type: 'account:usernameResult', ...await setUsername(c.userId, c.accessToken, msg.username) }); },
|
| 384 |
-
'account:getSubscription': async (ws, msg, c) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
'account:getUsage': async (ws, msg, c) => { safeSend(ws, { type: 'account:usage', usage: await getUsageInfo(c.accessToken) }); },
|
| 386 |
'account:getTierConfig': async (ws) => { safeSend(ws, { type: 'account:tierConfig', config: await getTierConfig() }); },
|
| 387 |
'account:getSessions': (ws, msg, c) => { if (!c.userId) return; safeSend(ws, { type: 'account:deviceSessions', sessions: deviceSessionStore.getForUser(c.userId), currentToken: c.deviceToken }); },
|
|
|
|
| 78 |
|
| 79 |
'auth:login': async (ws, msg, client, wsClients) => {
|
| 80 |
const { accessToken, tempId: clientTempId } = msg;
|
| 81 |
+
console.log('[Auth] Login request received, token:', accessToken?.slice(0, 20) + '...');
|
| 82 |
if (!accessToken) return safeSend(ws, { type: 'auth:error', message: 'Missing token' });
|
| 83 |
const user = await verifySupabaseToken(accessToken);
|
| 84 |
if (!user) return safeSend(ws, { type: 'auth:error', message: 'Invalid token' });
|
| 85 |
+
console.log('[Auth] User verified:', user.id);
|
| 86 |
|
| 87 |
client.userId = user.id; client.accessToken = accessToken; client.authenticated = true;
|
| 88 |
client.deviceToken = deviceSessionStore.create(user.id, client.ip, client.userAgent);
|
|
|
|
| 92 |
const tId = client.tempId;
|
| 93 |
await sessionStore.transferTempToUser(tId, user.id, accessToken);
|
| 94 |
|
| 95 |
+
console.log('[Auth] Fetching user data...');
|
| 96 |
const [sessions, settings, profile, subscription] = await Promise.all([
|
| 97 |
sessionStore.loadUserSessions(user.id, accessToken),
|
| 98 |
getUserSettings(user.id, accessToken),
|
| 99 |
getUserProfile(user.id, accessToken),
|
| 100 |
getSubscriptionInfo(accessToken),
|
| 101 |
]);
|
| 102 |
+
console.log('[Auth] User data fetched - subscription:', subscription);
|
| 103 |
|
| 104 |
+
const authOkMsg = { type: 'auth:ok', userId: user.id, email: user.email,
|
| 105 |
+
deviceToken: client.deviceToken, sessions: sessions.map(ser), settings, profile, subscription };
|
| 106 |
+
console.log('[Auth] Sending auth:ok with subscription:', subscription);
|
| 107 |
+
safeSend(ws, authOkMsg);
|
| 108 |
|
| 109 |
bcast(wsClients, user.id, { type: 'auth:newLogin', message: 'New login on your account.',
|
| 110 |
ip: client.ip, userAgent: client.userAgent, timestamp: new Date().toISOString() }, ws);
|
|
|
|
| 387 |
|
| 388 |
'account:getProfile': async (ws, msg, c) => { if (!c.userId) return; safeSend(ws, { type: 'account:profile', profile: await getUserProfile(c.userId, c.accessToken) }); },
|
| 389 |
'account:setUsername': async (ws, msg, c) => { if (!c.userId) return; safeSend(ws, { type: 'account:usernameResult', ...await setUsername(c.userId, c.accessToken, msg.username) }); },
|
| 390 |
+
'account:getSubscription': async (ws, msg, c) => {
|
| 391 |
+
if (!c.userId) return console.warn('[Account] getSubscription called without userId');
|
| 392 |
+
console.log('[Account] getSubscription request for user:', c.userId);
|
| 393 |
+
const subInfo = await getSubscriptionInfo(c.accessToken);
|
| 394 |
+
console.log('[Account] getSubscription response:', subInfo);
|
| 395 |
+
safeSend(ws, { type: 'account:subscription', info: subInfo });
|
| 396 |
+
},
|
| 397 |
'account:getUsage': async (ws, msg, c) => { safeSend(ws, { type: 'account:usage', usage: await getUsageInfo(c.accessToken) }); },
|
| 398 |
'account:getTierConfig': async (ws) => { safeSend(ws, { type: 'account:tierConfig', config: await getTierConfig() }); },
|
| 399 |
'account:getSessions': (ws, msg, c) => { if (!c.userId) return; safeSend(ws, { type: 'account:deviceSessions', sessions: deviceSessionStore.getForUser(c.userId), currentToken: c.deviceToken }); },
|