SHA256
Добавить смену профилей, но она пока не работает
This commit is contained in:
+254
-3
@@ -11,6 +11,8 @@ import { emptyPasswordWords } from './services/password-words.js';
|
||||
|
||||
const clone = (value) => JSON.parse(JSON.stringify(value));
|
||||
const SESSION_STORAGE_KEY = 'shine-ui-current-session-v1';
|
||||
const PROFILES_STORAGE_KEY = 'shine-ui-profiles-v1';
|
||||
const ACTIVE_PROFILE_STORAGE_KEY = 'shine-ui-active-profile-v1';
|
||||
const REACTIONS_STORAGE_KEY = 'shine-ui-message-reactions-v2';
|
||||
const WEB_PUSH_SUBSCRIPTION_KEY = 'shine-ui-webpush-subscription-v1';
|
||||
const ENTRY_SETTINGS_STORAGE_KEY = 'shine-ui-entry-settings-v1';
|
||||
@@ -122,7 +124,121 @@ function normalizeToolsSettings(rawTools) {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function normalizeProfileLogin(value) {
|
||||
return String(value || '').trim().toLowerCase();
|
||||
}
|
||||
|
||||
function loadProfileStoreRaw() {
|
||||
try {
|
||||
const raw = localStorage.getItem(PROFILES_STORAGE_KEY);
|
||||
if (!raw) return [];
|
||||
const parsed = JSON.parse(raw);
|
||||
if (!Array.isArray(parsed)) return [];
|
||||
return parsed.filter((item) => item && normalizeProfileLogin(item.login) && String(item.sessionId || '').trim());
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function persistProfileStoreRaw(items) {
|
||||
try {
|
||||
localStorage.setItem(PROFILES_STORAGE_KEY, JSON.stringify(Array.isArray(items) ? items : []));
|
||||
} catch {
|
||||
// ignore storage errors
|
||||
}
|
||||
}
|
||||
|
||||
function getActiveProfileLoginRaw() {
|
||||
try {
|
||||
return normalizeProfileLogin(localStorage.getItem(ACTIVE_PROFILE_STORAGE_KEY));
|
||||
} catch {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function setActiveProfileLoginRaw(login) {
|
||||
const normalized = normalizeProfileLogin(login);
|
||||
try {
|
||||
if (normalized) localStorage.setItem(ACTIVE_PROFILE_STORAGE_KEY, normalized);
|
||||
else localStorage.removeItem(ACTIVE_PROFILE_STORAGE_KEY);
|
||||
} catch {
|
||||
// ignore storage errors
|
||||
}
|
||||
}
|
||||
|
||||
function profileEntrySettingsSnapshot(settings = {}) {
|
||||
return {
|
||||
solanaServer: String(settings.solanaServer || ''),
|
||||
shineServer: String(settings.shineServer || ''),
|
||||
shineServerLogin: String(settings.shineServerLogin || ''),
|
||||
shineServerHttp: String(settings.shineServerHttp || ''),
|
||||
arweaveServer: String(settings.arweaveServer || ''),
|
||||
callPreflightTimeoutMs: Number(settings.callPreflightTimeoutMs || DEFAULT_CALL_PREFLIGHT_TIMEOUT_MS),
|
||||
remoteAddBlockSessionId: String(settings.remoteAddBlockSessionId || ''),
|
||||
};
|
||||
}
|
||||
|
||||
function upsertSavedProfileInternal({ login, sessionId, isLocalDemo = false, entrySettings = null } = {}) {
|
||||
const normalized = normalizeProfileLogin(login);
|
||||
const cleanSessionId = String(sessionId || '').trim();
|
||||
if (!normalized || !cleanSessionId) return;
|
||||
const items = loadProfileStoreRaw();
|
||||
const index = items.findIndex((item) => normalizeProfileLogin(item.login) === normalized);
|
||||
const previous = index >= 0 ? items[index] : {};
|
||||
const next = {
|
||||
...previous,
|
||||
login: String(login || '').trim(),
|
||||
sessionId: cleanSessionId,
|
||||
isLocalDemo: Boolean(isLocalDemo),
|
||||
entrySettings: entrySettings ? profileEntrySettingsSnapshot(entrySettings) : (previous.entrySettings || {}),
|
||||
updatedAtMs: Date.now(),
|
||||
};
|
||||
if (index >= 0) items[index] = next;
|
||||
else items.push(next);
|
||||
persistProfileStoreRaw(items);
|
||||
setActiveProfileLoginRaw(normalized);
|
||||
}
|
||||
|
||||
function migrateLegacySessionToProfileStore() {
|
||||
const existing = loadProfileStoreRaw();
|
||||
if (existing.length) return;
|
||||
try {
|
||||
const raw = localStorage.getItem(SESSION_STORAGE_KEY);
|
||||
if (!raw) return;
|
||||
const legacy = JSON.parse(raw);
|
||||
if (!legacy?.login || !legacy?.sessionId) return;
|
||||
let entrySettings = {};
|
||||
try {
|
||||
entrySettings = JSON.parse(localStorage.getItem(ENTRY_SETTINGS_STORAGE_KEY) || '{}') || {};
|
||||
} catch {}
|
||||
upsertSavedProfileInternal({
|
||||
login: legacy.login,
|
||||
sessionId: legacy.sessionId,
|
||||
isLocalDemo: legacy.isLocalDemo,
|
||||
entrySettings,
|
||||
});
|
||||
} catch {
|
||||
// ignore migration errors
|
||||
}
|
||||
}
|
||||
|
||||
function loadStoredSession() {
|
||||
migrateLegacySessionToProfileStore();
|
||||
const profiles = loadProfileStoreRaw();
|
||||
if (profiles.length) {
|
||||
const activeLogin = getActiveProfileLoginRaw();
|
||||
const active = profiles.find((item) => normalizeProfileLogin(item.login) === activeLogin) || profiles[0];
|
||||
if (active) {
|
||||
setActiveProfileLoginRaw(active.login);
|
||||
return {
|
||||
isAuthorized: false,
|
||||
isLocalDemo: Boolean(active.isLocalDemo),
|
||||
login: String(active.login || '').trim(),
|
||||
sessionId: String(active.sessionId || '').trim(),
|
||||
};
|
||||
}
|
||||
}
|
||||
try {
|
||||
const raw = localStorage.getItem(SESSION_STORAGE_KEY);
|
||||
if (!raw) return null;
|
||||
@@ -169,6 +285,15 @@ function clearStoredSession() {
|
||||
}
|
||||
|
||||
function loadStoredEntrySettings() {
|
||||
migrateLegacySessionToProfileStore();
|
||||
const profiles = loadProfileStoreRaw();
|
||||
if (profiles.length) {
|
||||
const activeLogin = getActiveProfileLoginRaw();
|
||||
const active = profiles.find((item) => normalizeProfileLogin(item.login) === activeLogin) || profiles[0];
|
||||
if (active?.entrySettings && typeof active.entrySettings === 'object') {
|
||||
return active.entrySettings;
|
||||
}
|
||||
}
|
||||
try {
|
||||
const raw = localStorage.getItem(ENTRY_SETTINGS_STORAGE_KEY);
|
||||
if (!raw) return null;
|
||||
@@ -199,6 +324,19 @@ function persistEntrySettings(settings) {
|
||||
tools: normalizeToolsSettings(settings?.tools),
|
||||
};
|
||||
localStorage.setItem(ENTRY_SETTINGS_STORAGE_KEY, JSON.stringify(payload));
|
||||
const activeLogin = getActiveProfileLoginRaw();
|
||||
if (activeLogin) {
|
||||
const profiles = loadProfileStoreRaw();
|
||||
const index = profiles.findIndex((item) => normalizeProfileLogin(item.login) === activeLogin);
|
||||
if (index >= 0) {
|
||||
profiles[index] = {
|
||||
...profiles[index],
|
||||
entrySettings: profileEntrySettingsSnapshot(payload),
|
||||
updatedAtMs: Date.now(),
|
||||
};
|
||||
persistProfileStoreRaw(profiles);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore storage errors
|
||||
}
|
||||
@@ -402,6 +540,7 @@ function persistMessageRecord(chatId, row) {
|
||||
const resolvedTs = resolveChatMessageTimeMs(row);
|
||||
void putStoredMessage({
|
||||
messageKey: row.messageKey,
|
||||
ownerLogin: String(state.session.login || '').trim().toLowerCase(),
|
||||
chatId: normalizedChatId,
|
||||
from: row.from || 'in',
|
||||
text: String(row.text || ''),
|
||||
@@ -422,12 +561,12 @@ function persistMessageRecord(chatId, row) {
|
||||
|
||||
function removeStoredMessageRecord(messageKey) {
|
||||
if (!messageKey) return;
|
||||
void deleteStoredMessage(messageKey).catch(() => {});
|
||||
void deleteStoredMessage(messageKey, state.session.login).catch(() => {});
|
||||
}
|
||||
|
||||
export async function hydrateMessagesFromStore() {
|
||||
try {
|
||||
const rows = await listStoredMessages();
|
||||
const rows = await listStoredMessages(state.session.login);
|
||||
const touchedChats = new Set();
|
||||
rows
|
||||
.sort((a, b) => Number(a?.ts || 0) - Number(b?.ts || 0))
|
||||
@@ -928,6 +1067,7 @@ export function authorizeSession({
|
||||
login,
|
||||
sessionId,
|
||||
});
|
||||
upsertSavedProfileInternal({ login, sessionId, isLocalDemo: localDemo, entrySettings: state.entrySettings });
|
||||
authService.setActiveSessionContext({ login, sessionId });
|
||||
state.startHint = '';
|
||||
if (onSessionAuthorized) {
|
||||
@@ -1020,13 +1160,14 @@ async function tryCloseCurrentSessionOnServer() {
|
||||
}
|
||||
|
||||
export async function terminateCurrentSession({ infoMessage = '', closeServerSession = false, notifySessionReset = true } = {}) {
|
||||
const signedOutLogin = String(state.session.login || '').trim();
|
||||
if (closeServerSession) {
|
||||
await tryCloseCurrentSessionOnServer();
|
||||
}
|
||||
|
||||
clearStoredSession();
|
||||
resetStateForSignedOut();
|
||||
await clearStoredMessages().catch(() => {});
|
||||
await clearStoredMessages(signedOutLogin).catch(() => {});
|
||||
authService.close();
|
||||
authService.clearActiveSessionContext();
|
||||
if (infoMessage) {
|
||||
@@ -1046,6 +1187,116 @@ export async function closeCurrentSessionAndSignOut({ infoMessage = '' } = {}) {
|
||||
await terminateCurrentSession({ infoMessage, closeServerSession: true });
|
||||
}
|
||||
|
||||
|
||||
export function getSavedProfiles() {
|
||||
migrateLegacySessionToProfileStore();
|
||||
const activeLogin = getActiveProfileLoginRaw();
|
||||
return loadProfileStoreRaw().map((item) => ({
|
||||
login: String(item.login || '').trim(),
|
||||
sessionId: String(item.sessionId || '').trim(),
|
||||
isLocalDemo: Boolean(item.isLocalDemo),
|
||||
isActive: normalizeProfileLogin(item.login) === activeLogin,
|
||||
entrySettings: item.entrySettings || {},
|
||||
}));
|
||||
}
|
||||
|
||||
export async function switchToSavedProfile(login) {
|
||||
const targetLogin = normalizeProfileLogin(login);
|
||||
const target = loadProfileStoreRaw().find((item) => normalizeProfileLogin(item.login) === targetLogin);
|
||||
if (!target) throw new Error('Профиль не найден на этом устройстве');
|
||||
if (targetLogin === normalizeProfileLogin(state.session.login)) return target;
|
||||
|
||||
const targetServer = String(target?.entrySettings?.shineServer || state.entrySettings.shineServer || DEFAULT_SHINE_SERVER).trim();
|
||||
const probe = new AuthService(targetServer);
|
||||
try {
|
||||
await probe.reconnect(targetServer);
|
||||
const resumed = await probe.resumeSession(target.login, target.sessionId);
|
||||
target.login = resumed.login || target.login;
|
||||
target.sessionId = resumed.sessionId || target.sessionId;
|
||||
target.updatedAtMs = Date.now();
|
||||
persistProfileStoreRaw(loadProfileStoreRaw().map((item) => (
|
||||
normalizeProfileLogin(item.login) === targetLogin ? target : item
|
||||
)));
|
||||
setActiveProfileLoginRaw(target.login);
|
||||
persistSession({ isAuthorized: true, isLocalDemo: Boolean(target.isLocalDemo), login: target.login, sessionId: target.sessionId });
|
||||
if (target.entrySettings && typeof target.entrySettings === 'object') {
|
||||
persistEntrySettings({ ...state.entrySettings, ...target.entrySettings });
|
||||
}
|
||||
return target;
|
||||
} finally {
|
||||
probe.close();
|
||||
}
|
||||
}
|
||||
|
||||
async function closeSavedProfileSessionBestEffort(profile) {
|
||||
if (!profile || profile.isLocalDemo) return;
|
||||
const cleanSessionId = String(profile.sessionId || '').trim();
|
||||
if (!cleanSessionId) return;
|
||||
const normalized = normalizeProfileLogin(profile.login);
|
||||
if (normalized === normalizeProfileLogin(state.session.login) && state.session.isAuthorized) {
|
||||
try { await authService.closeSession(cleanSessionId); } catch {}
|
||||
return;
|
||||
}
|
||||
const targetServer = String(profile?.entrySettings?.shineServer || state.entrySettings.shineServer || DEFAULT_SHINE_SERVER).trim();
|
||||
const probe = new AuthService(targetServer);
|
||||
try {
|
||||
await probe.reconnect(targetServer);
|
||||
await probe.resumeSession(profile.login, cleanSessionId);
|
||||
await probe.closeSession(cleanSessionId);
|
||||
} catch {
|
||||
// Закрытие профиля на устройстве не блокируем из-за недоступного сервера.
|
||||
} finally {
|
||||
probe.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function closeSavedProfile(login) {
|
||||
const normalized = normalizeProfileLogin(login);
|
||||
const items = loadProfileStoreRaw();
|
||||
const index = items.findIndex((item) => normalizeProfileLogin(item.login) === normalized);
|
||||
if (index < 0) return { closed: false, nextProfile: null };
|
||||
const target = items[index];
|
||||
await closeSavedProfileSessionBestEffort(target);
|
||||
await clearStoredMessages(target.login).catch(() => {});
|
||||
|
||||
const nextItems = items.filter((_, itemIndex) => itemIndex !== index);
|
||||
persistProfileStoreRaw(nextItems);
|
||||
const wasActive = normalized === getActiveProfileLoginRaw();
|
||||
if (!wasActive) return { closed: true, nextProfile: null };
|
||||
|
||||
const next = nextItems[index] || nextItems[index - 1] || nextItems[0] || null;
|
||||
if (!next) {
|
||||
setActiveProfileLoginRaw('');
|
||||
clearStoredSession();
|
||||
return { closed: true, nextProfile: null };
|
||||
}
|
||||
setActiveProfileLoginRaw(next.login);
|
||||
persistSession({ isAuthorized: true, isLocalDemo: Boolean(next.isLocalDemo), login: next.login, sessionId: next.sessionId });
|
||||
if (next.entrySettings && typeof next.entrySettings === 'object') {
|
||||
persistEntrySettings({ ...state.entrySettings, ...next.entrySettings });
|
||||
}
|
||||
return { closed: true, nextProfile: next };
|
||||
}
|
||||
|
||||
export async function closeAllSavedProfiles() {
|
||||
const items = loadProfileStoreRaw();
|
||||
for (const item of items) {
|
||||
await closeSavedProfileSessionBestEffort(item);
|
||||
await clearStoredMessages(item.login).catch(() => {});
|
||||
}
|
||||
persistProfileStoreRaw([]);
|
||||
setActiveProfileLoginRaw('');
|
||||
clearStoredSession();
|
||||
authService.close();
|
||||
authService.clearActiveSessionContext();
|
||||
}
|
||||
|
||||
export function prepareAddProfileLogin() {
|
||||
state.loginDraft.login = '';
|
||||
state.loginDraft.password = '';
|
||||
state.authReturnHash = '';
|
||||
}
|
||||
|
||||
export function refreshRegistrationBalance() {
|
||||
const next = (0.005 + Math.random() * 0.03).toFixed(4);
|
||||
state.registrationPayment.balanceSOL = next;
|
||||
|
||||
Reference in New Issue
Block a user