diff --git a/src/app_logic.js b/src/app_logic.js index 8bbe116..1a63322 100644 --- a/src/app_logic.js +++ b/src/app_logic.js @@ -19,6 +19,8 @@ function open_page(params) { EventListeners.QRScanner = []; EventListeners.Custom.forEach((ev) => ev()); EventListeners.Custom = []; + EventListeners.DB.forEach((ev) => DB.unlisten(ev)); + EventListeners.DB = []; if (SUB_LOGGED_IN != true && params != 'login,setup' && !params.startsWith('login,onboarding')) { PAGES['login'].index(); diff --git a/src/app_modules.js b/src/app_modules.js index b3505e4..6c3076f 100644 --- a/src/app_modules.js +++ b/src/app_modules.js @@ -15,11 +15,13 @@ window.PRECIOS_CAFE = { // Cargar precios desde la base de datos al iniciar if (typeof DB !== 'undefined') { - DB.get('config', 'precios_cafe').then((precios) => { - if (precios) { - Object.assign(window.PRECIOS_CAFE, precios); - console.log('Precios del café cargados:', window.PRECIOS_CAFE); - } + DB.get('config', 'precios_cafe').then((raw) => { + TS_decrypt(raw, SECRET, (precios) => { + if (precios) { + Object.assign(window.PRECIOS_CAFE, precios); + console.log('Precios del café cargados:', window.PRECIOS_CAFE); + } + }); }).catch(() => { console.log('Usando precios por defecto'); }); @@ -1259,7 +1261,7 @@ function TS_IndexElement( } // Subscribe to dataset updates using DB.map (PouchDB) when `ref` is a table name string if (typeof ref === 'string') { - DB.map(ref, (data, key) => { + EventListeners.DB.push(DB.map(ref, (data, key) => { function add_row(data, key) { if (data != null) { data['_key'] = key; @@ -1274,49 +1276,21 @@ function TS_IndexElement( data, SECRET, (data, wasEncrypted) => { - data['_encrypted__'] = wasEncrypted; - add_row(data, key); + if (data != null && typeof data === 'object') { + data['_encrypted__'] = wasEncrypted; + add_row(data, key); + } }, ref, key ); } else { - data['_encrypted__'] = false; + if (data != null && typeof data === 'object') { + data['_encrypted__'] = false; + } add_row(data, key); } - }); - } else if (ref && typeof ref.map === 'function') { - // Legacy: try to use ref.map().on if available (for backwards compatibility) - try { - ref.map().on((data, key, _msg, _ev) => { - function add_row(data, key) { - if (data != null) { - data['_key'] = key; - rows[key] = data; - } else { - delete rows[key]; - } - debounce(debounce_load, render, 200, [rows]); - } - if (typeof data == 'string') { - TS_decrypt( - data, - SECRET, - (data, wasEncrypted) => { - data['_encrypted__'] = wasEncrypted; - add_row(data, key); - }, - undefined, - undefined - ); - } else { - data['_encrypted__'] = false; - add_row(data, key); - } - }); - } catch (e) { - console.warn('TS_IndexElement: cannot subscribe to ref', e); - } + })); } } @@ -1326,7 +1300,7 @@ function BuildQR(mid, label) { dim: 150, pad: 0, mtx: -1, - ecl: 'L', + ecl: 'S', ecb: 0, pal: ['#000000', '#ffffff'], vrb: 0, diff --git a/src/config.js b/src/config.js index 47d0501..ffb6604 100644 --- a/src/config.js +++ b/src/config.js @@ -8,6 +8,7 @@ var EventListeners = { Interval: [], QRScanner: [], Custom: [], + DB: [], }; // Safe UUID for html element IDs: generates a unique identifier with a specified prefix, ensuring it is safe for use in HTML element IDs. It uses crypto.randomUUID if available, with a fallback to a random string generation method for environments that do not support it. The generated ID is prefixed to avoid collisions and ensure uniqueness across the application. @@ -131,7 +132,7 @@ var TTS_RATE = parseFloat(urlParams.get('tts_rate')) || 0.75; function TS_SayTTS(msg) { try { if (window.speechSynthesis) { - let utterance = new SpeechSynthesisUtterance(tts_msg); + let utterance = new SpeechSynthesisUtterance(msg); utterance.rate = TTS_RATE; speechSynthesis.speak(utterance); } diff --git a/src/db.js b/src/db.js index 3bba9e2..81e0223 100644 --- a/src/db.js +++ b/src/db.js @@ -9,7 +9,8 @@ var DB = (function () { let changes = null; let repPush = null; let repPull = null; - let callbacks = {}; // table -> [cb] + let callbacks = {}; // table -> [{ id, cb }] + let callbackSeq = 0; let docCache = {}; // _id -> last data snapshot (stringified) function ensureLocal() { @@ -34,6 +35,11 @@ var DB = (function () { return table + ':' + id; } + function makeCallbackId(table) { + callbackSeq += 1; + return table + '#' + callbackSeq; + } + function init(opts) { const localName = 'telesec'; try { @@ -126,7 +132,8 @@ var DB = (function () { if (change.deleted || doc._deleted) { delete docCache[doc._id]; if (callbacks[table]) { - callbacks[table].forEach((cb) => { + callbacks[table].forEach((listener) => { + const cb = listener.cb; try { cb(null, id); } catch (e) { @@ -148,7 +155,8 @@ var DB = (function () { } if (callbacks[table]) { - callbacks[table].forEach((cb) => { + callbacks[table].forEach((listener) => { + const cb = listener.cb; try { cb(doc.data, id); } catch (e) { @@ -348,12 +356,25 @@ var DB = (function () { function map(table, cb) { ensureLocal(); + const callbackId = makeCallbackId(table); callbacks[table] = callbacks[table] || []; - callbacks[table].push(cb); - list(table).then((rows) => rows.forEach((r) => cb(r.data, r.id))); - return () => { - callbacks[table] = callbacks[table].filter((x) => x !== cb); - }; + callbacks[table].push({ id: callbackId, cb: cb }); + list(table).then((rows) => { + const stillListening = (callbacks[table] || []).some((listener) => listener.id === callbackId); + if (!stillListening) return; + rows.forEach((r) => cb(r.data, r.id)); + }); + return callbackId; + } + + function unlisten(callbackId) { + if (!callbackId) return false; + for (const table of Object.keys(callbacks)) { + const before = callbacks[table].length; + callbacks[table] = callbacks[table].filter((listener) => listener.id !== callbackId); + if (callbacks[table].length !== before) return true; + } + return false; } return { @@ -363,6 +384,7 @@ var DB = (function () { del, list, map, + unlisten, replicateToRemote, listAttachments, deleteAttachment,