Files
ihaleist/src/services/offlineMutationQueue.js
T

128 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const PREFIX = 'ihaleist_offline_mutations:';
const LIMIT = 100;
const activeReplays = new Map();
const ownerKey = owner => `${PREFIX}${encodeURIComponent(String(owner || '').trim().toLowerCase())}`;
function readStorage(owner) {
if (typeof localStorage === 'undefined' || !String(owner || '').trim()) return [];
try {
const parsed = JSON.parse(localStorage.getItem(ownerKey(owner)) || '[]');
return Array.isArray(parsed) ? parsed.filter(item => item && typeof item === 'object') : [];
} catch { return []; }
}
function writeStorage(owner, rows) {
if (typeof localStorage === 'undefined' || !String(owner || '').trim()) return false;
try {
localStorage.setItem(ownerKey(owner), JSON.stringify(rows));
return true;
} catch { return false; }
}
export function offlineMutationCount(owner) {
return readStorage(owner).length;
}
export function listOfflineMutations(owner) {
return readStorage(owner).map(item => ({ ...item }));
}
/**
* Adds a tender-state write to the account-scoped durable queue. Consecutive
* changes for the same tender are merged so reconnecting never replays stale
* intermediate drafts. Tokens and document bytes are deliberately excluded.
*/
export function enqueueTenderMutation(owner, { key, patch, snapshot, version }) {
if (!String(owner || '').trim() || !String(key || '').trim() || !patch || typeof patch !== 'object' || Array.isArray(patch)) {
throw new Error('Çevrimdışı kayıt için ihale ve değişiklik bilgisi gerekli.');
}
const rows = readStorage(owner);
const index = rows.findIndex(item => item.kind === 'tender-state' && item.key === key);
const previous = index >= 0 ? rows[index] : null;
if (!previous && rows.length >= LIMIT) {
throw new Error('Çevrimdışı kayıt kuyruğu dolu. Bağlantı kurulduktan sonra yeniden deneyin.');
}
const next = {
id: previous?.id || `offline-${crypto.randomUUID()}`,
kind: 'tender-state',
key,
patch: { ...(previous?.patch || {}), ...patch },
snapshot: snapshot && typeof snapshot === 'object' ? snapshot : {},
version: Number.isInteger(version) && version >= 0 ? version : 0,
createdAt: previous?.createdAt || new Date().toISOString(),
updatedAt: new Date().toISOString(),
attempts: previous?.attempts || 0,
revision: (Number.isInteger(previous?.revision) ? previous.revision : 0) + 1,
};
if (index >= 0) rows[index] = next; else rows.push(next);
if (!writeStorage(owner, rows)) throw new Error('Çevrimdışı kayıt kuyruğu yazılamadı.');
return next;
}
export function removeOfflineMutation(owner, id) {
const rows = readStorage(owner);
return writeStorage(owner, rows.filter(item => item.id !== id));
}
export function markOfflineMutationAttempt(owner, id) {
const rows = readStorage(owner);
const next = rows.map(item => item.id === id ? { ...item, attempts: (item.attempts || 0) + 1, updatedAt: new Date().toISOString() } : item);
writeStorage(owner, next);
return next.find(item => item.id === id) || null;
}
function acknowledgeOfflineMutation(owner, item, record) {
const rows = readStorage(owner);
const index = rows.findIndex(row => row.id === item.id);
if (index < 0) return true;
const current = rows[index];
if ((current.revision || 0) === (item.revision || 0)) {
rows.splice(index, 1);
} else {
const nextVersion = Number(record?.version);
rows[index] = {
...current,
version: Number.isInteger(nextVersion) && nextVersion >= 0 ? nextVersion : current.version,
};
}
return writeStorage(owner, rows);
}
async function runReplay(owner, send) {
const rows = listOfflineMutations(owner);
const result = { sent: 0, pending: rows.length, error: null };
for (const item of rows) {
markOfflineMutationAttempt(owner, item.id);
try {
const record = await send(item);
if (!acknowledgeOfflineMutation(owner, item, record)) {
throw new Error('Çevrimdışı kayıt kuyruğu güncellenemedi.');
}
result.sent += 1;
} catch (error) {
result.error = error;
break;
}
}
result.pending = offlineMutationCount(owner);
return result;
}
/** Replay in order; concurrent reconnect signals share one replay operation. */
export function replayOfflineMutations(owner, send) {
const replayKey = ownerKey(owner);
const active = activeReplays.get(replayKey);
if (active) return active;
const replay = runReplay(owner, send).finally(() => {
if (activeReplays.get(replayKey) === replay) activeReplays.delete(replayKey);
});
activeReplays.set(replayKey, replay);
return replay;
}
export function clearOfflineMutations(owner) {
if (typeof localStorage === 'undefined') return false;
try { localStorage.removeItem(ownerKey(owner)); return true; } catch { return false; }
}