SHA256
142 lines
5.2 KiB
JavaScript
142 lines
5.2 KiB
JavaScript
const DB_NAME = 'shine-ui-messages-v1';
|
|
const DB_VERSION = 3;
|
|
const STORE_MESSAGES = 'messages_by_profile';
|
|
const LEGACY_STORE_MESSAGES = 'messages';
|
|
const ACTIVE_PROFILE_STORAGE_KEY = 'shine-ui-active-profile-v1';
|
|
const LEGACY_SESSION_STORAGE_KEY = 'shine-ui-current-session-v1';
|
|
|
|
function normalizeOwnerLogin(value) {
|
|
return String(value || '').trim().toLowerCase();
|
|
}
|
|
|
|
function storageKey(ownerLogin, messageKey) {
|
|
const owner = normalizeOwnerLogin(ownerLogin);
|
|
const key = String(messageKey || '').trim();
|
|
return owner && key ? `${owner}|${key}` : '';
|
|
}
|
|
|
|
function migrationOwnerLogin() {
|
|
try {
|
|
const active = normalizeOwnerLogin(localStorage.getItem(ACTIVE_PROFILE_STORAGE_KEY));
|
|
if (active) return active;
|
|
const legacy = JSON.parse(localStorage.getItem(LEGACY_SESSION_STORAGE_KEY) || '{}');
|
|
return normalizeOwnerLogin(legacy?.login);
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function ensureIndexes(store) {
|
|
if (!store.indexNames.contains('by_chat')) store.createIndex('by_chat', 'chatId', { unique: false });
|
|
if (!store.indexNames.contains('by_ts')) store.createIndex('by_ts', 'ts', { unique: false });
|
|
if (!store.indexNames.contains('by_owner')) store.createIndex('by_owner', 'ownerLogin', { unique: false });
|
|
}
|
|
|
|
function openDb() {
|
|
return new Promise((resolve, reject) => {
|
|
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
request.onupgradeneeded = () => {
|
|
const db = request.result;
|
|
let store;
|
|
if (!db.objectStoreNames.contains(STORE_MESSAGES)) {
|
|
store = db.createObjectStore(STORE_MESSAGES, { keyPath: 'storageKey' });
|
|
} else {
|
|
store = request.transaction.objectStore(STORE_MESSAGES);
|
|
}
|
|
ensureIndexes(store);
|
|
|
|
// Однократная миграция старого single-profile кэша в пространство текущего профиля.
|
|
if (db.objectStoreNames.contains(LEGACY_STORE_MESSAGES)) {
|
|
const owner = migrationOwnerLogin();
|
|
if (owner) {
|
|
const legacy = request.transaction.objectStore(LEGACY_STORE_MESSAGES);
|
|
const cursorReq = legacy.openCursor();
|
|
cursorReq.onsuccess = () => {
|
|
const cursor = cursorReq.result;
|
|
if (!cursor) return;
|
|
const row = cursor.value || {};
|
|
const messageKey = String(row.messageKey || '').trim();
|
|
const rowOwner = normalizeOwnerLogin(row.ownerLogin) || owner;
|
|
const key = storageKey(rowOwner, messageKey);
|
|
if (key) store.put({ ...row, ownerLogin: rowOwner, storageKey: key });
|
|
cursor.continue();
|
|
};
|
|
}
|
|
}
|
|
};
|
|
request.onsuccess = () => resolve(request.result);
|
|
request.onerror = () => reject(request.error || new Error('IndexedDB open failed'));
|
|
});
|
|
}
|
|
|
|
async function withStore(mode, callback) {
|
|
const db = await openDb();
|
|
try {
|
|
return await new Promise((resolve, reject) => {
|
|
const tx = db.transaction(STORE_MESSAGES, mode);
|
|
const store = tx.objectStore(STORE_MESSAGES);
|
|
const result = callback(store, tx);
|
|
tx.oncomplete = () => resolve(result);
|
|
tx.onerror = () => reject(tx.error || new Error('IndexedDB transaction failed'));
|
|
tx.onabort = () => reject(tx.error || new Error('IndexedDB transaction aborted'));
|
|
});
|
|
} finally {
|
|
db.close();
|
|
}
|
|
}
|
|
|
|
export async function putStoredMessage(record) {
|
|
if (!record || !record.messageKey) return;
|
|
const ownerLogin = normalizeOwnerLogin(record.ownerLogin);
|
|
const key = storageKey(ownerLogin, record.messageKey);
|
|
if (!key) return;
|
|
await withStore('readwrite', (store) => {
|
|
store.put({ ...record, ownerLogin, storageKey: key });
|
|
});
|
|
}
|
|
|
|
export async function deleteStoredMessage(messageKey, ownerLogin = '') {
|
|
const key = storageKey(ownerLogin, messageKey);
|
|
if (!key) return;
|
|
await withStore('readwrite', (store) => {
|
|
store.delete(key);
|
|
});
|
|
}
|
|
|
|
export async function listStoredMessages(ownerLogin = '') {
|
|
const owner = normalizeOwnerLogin(ownerLogin);
|
|
if (!owner) return [];
|
|
return withStore('readonly', (store) => new Promise((resolve, reject) => {
|
|
const req = store.index('by_owner').getAll(owner);
|
|
req.onsuccess = () => resolve(Array.isArray(req.result) ? req.result : []);
|
|
req.onerror = () => reject(req.error || new Error('IndexedDB getAll by owner failed'));
|
|
}));
|
|
}
|
|
|
|
export async function clearStoredMessages(ownerLogin = '') {
|
|
const owner = normalizeOwnerLogin(ownerLogin);
|
|
if (!owner) {
|
|
await new Promise((resolve, reject) => {
|
|
const request = indexedDB.deleteDatabase(DB_NAME);
|
|
request.onsuccess = () => resolve();
|
|
request.onerror = () => reject(request.error || new Error('IndexedDB delete failed'));
|
|
request.onblocked = () => reject(new Error('IndexedDB delete blocked'));
|
|
});
|
|
return;
|
|
}
|
|
await withStore('readwrite', (store) => new Promise((resolve, reject) => {
|
|
const index = store.index('by_owner');
|
|
const req = index.openKeyCursor(IDBKeyRange.only(owner));
|
|
req.onsuccess = () => {
|
|
const cursor = req.result;
|
|
if (!cursor) {
|
|
resolve();
|
|
return;
|
|
}
|
|
store.delete(cursor.primaryKey);
|
|
cursor.continue();
|
|
};
|
|
req.onerror = () => reject(req.error || new Error('IndexedDB clear by owner failed'));
|
|
}));
|
|
}
|