Skip to main content

dkms_wasm/database/indexed_db/
logging.rs

1use keri_core::database::LogDatabase;
2
3use wasm_bindgen::prelude::*;
4use wasm_bindgen_futures::spawn_local;
5
6use super::IndexedDbError;
7use std::cell::{Cell, RefCell};
8use std::rc::Rc;
9use std::sync::Arc;
10use std::collections::HashMap;
11use keri_core::event_message::signature::{Nontransferable, Transferable};
12use keri_core::event::KeyEvent;
13use keri_core::event_message::msg::KeriEvent;
14use keri_core::event_message::signed_event_message::{ SignedEventMessage, SignedNontransferableReceipt };
15use keri_core::database::timestamped::TimestampedSignedEventMessage;
16use keri_core::prefix::IndexedSignature;
17use said::SelfAddressingIdentifier;
18
19const DB_NAME: &str = "log_db";
20
21pub struct IndexedDbLogDatabase {
22    db_ref: Rc<RefCell<Option<web_sys::IdbDatabase>>>,
23    events: Rc<RefCell<HashMap<String, TimestampedSignedEventMessage>>>,
24    signatures: Rc<RefCell<HashMap<String, Vec<IndexedSignature>>>>,
25    trans_receipts: Rc<RefCell<HashMap<String, Vec<Transferable>>>>,
26    nontrans_receipts: Rc<RefCell<HashMap<String, Vec<Nontransferable>>>>,
27    pending_operations: Rc<RefCell<Vec<PendingOperation>>>,
28    flush_in_progress: Rc<Cell<bool>>,
29}
30
31// SAFETY: In WebAssembly context, there's no true threading, so these are safe
32unsafe impl Send for IndexedDbLogDatabase {}
33unsafe impl Sync for IndexedDbLogDatabase {}
34
35enum PendingOperation {
36    StoreEvent {
37        said: String, 
38        event: Box<TimestampedSignedEventMessage>,
39        signatures: Vec<IndexedSignature>,
40    },
41    StoreNontransReceipt { said: String, receipts: Vec<Nontransferable> },
42    StoreTransReceipt { said: String, receipts: Vec<Transferable> },
43    RemoveReceipt {
44        said: String,
45        remaining_receipts: Vec<Nontransferable>,
46    },
47}
48
49impl IndexedDbLogDatabase {
50    fn init_db(&mut self) {
51        let window = web_sys::window().expect("should have a window");
52        
53        // Get IndexedDB factory
54        if let Ok(Some(factory)) = window.indexed_db() {
55            let db_ref = self.db_ref.clone();
56            let events = self.events.clone();
57            let signatures = self.signatures.clone();
58            let trans_receipts = self.trans_receipts.clone();
59            let nontrans_receipts = self.nontrans_receipts.clone();
60            let pending_operations = self.pending_operations.clone();
61            let flush_in_progress = self.flush_in_progress.clone();
62
63            // Open database
64            if let Ok(request) = factory.open(DB_NAME) {
65                // Handle database upgrade needed (first time opening)
66                let upgrade_needed_cb = Closure::wrap(Box::new(move |event: web_sys::IdbVersionChangeEvent| {
67                    if let Some(db) = event.target()
68                        .and_then(|t| t.dyn_into::<web_sys::IdbOpenDbRequest>().ok())
69                        .and_then(|r| r.result().ok())
70                        .and_then(|r| r.dyn_into::<web_sys::IdbDatabase>().ok()) 
71                    {
72                        // Create stores for log database
73                        let _ = db.create_object_store("events");
74                        let _ = db.create_object_store("trans_receipts");
75                        let _ = db.create_object_store("nontrans_receipts");
76                    }
77                }) as Box<dyn FnMut(_)>);
78
79                request.set_onupgradeneeded(Some(upgrade_needed_cb.as_ref().unchecked_ref()));
80                upgrade_needed_cb.forget();
81
82                // Handle successful open
83                let success_cb = Closure::wrap(Box::new(move |event: web_sys::Event| {
84                    if let Some(db) = event.target()
85                        .and_then(|t| t.dyn_into::<web_sys::IdbOpenDbRequest>().ok())
86                        .and_then(|r| r.result().ok())
87                        .and_then(|r| r.dyn_into::<web_sys::IdbDatabase>().ok())
88                    {
89                        *db_ref.borrow_mut() = Some(db.clone());
90
91                        // Load data from IndexedDB
92                        load_events(&db, events.clone());
93                        load_signatures(&db, signatures.clone());
94                        load_trans_receipts(&db, trans_receipts.clone());
95                        load_nontrans_receipts(&db, nontrans_receipts.clone());
96
97                        // Set up background flush
98                        setup_background_flush(db, pending_operations.clone(), flush_in_progress.clone());
99                    }
100                }) as Box<dyn FnMut(_)>);
101
102                request.set_onsuccess(Some(success_cb.as_ref().unchecked_ref()));
103                success_cb.forget();
104
105                // Handle errors
106                let error_cb = Closure::wrap(Box::new(|event: web_sys::Event| {
107                    log::error!("Failed to open IndexedDB Log Database: {:?}", event);
108                }) as Box<dyn FnMut(_)>);
109
110                request.set_onerror(Some(error_cb.as_ref().unchecked_ref()));
111                error_cb.forget();
112            }
113        }
114    }
115}
116
117impl LogDatabase<'_> for IndexedDbLogDatabase {
118    type DatabaseType = ();
119    type Error = IndexedDbError;
120    type TransactionType = ();
121
122    fn new(_db: Arc<Self::DatabaseType>) -> Result<Self, IndexedDbError> {
123        let db_ref = Rc::new(RefCell::new(None));
124        let events = Rc::new(RefCell::new(HashMap::new()));
125        let signatures = Rc::new(RefCell::new(HashMap::new()));
126        let trans_receipts = Rc::new(RefCell::new(HashMap::new()));
127        let nontrans_receipts = Rc::new(RefCell::new(HashMap::new()));
128        let pending_operations = Rc::new(RefCell::new(Vec::new()));
129        let flush_in_progress = Rc::new(Cell::new(false));
130
131        let mut db = Self {
132            db_ref,
133            events,
134            signatures,
135            trans_receipts,
136            nontrans_receipts,
137            pending_operations,
138            flush_in_progress,
139        };
140
141        db.init_db();
142
143        Ok(db)
144    }
145
146    fn log_event(
147        &self,
148        _txn: &Self::TransactionType,
149        signed_event: &SignedEventMessage,
150    ) -> Result<(), Self::Error> {
151        self.log_event_with_new_transaction(signed_event)
152    }
153
154    fn log_event_with_new_transaction(
155        &self,
156        signed_event: &SignedEventMessage,
157    ) -> Result<(), Self::Error> {
158        let digest = signed_event
159            .event_message
160            .digest()
161            .map_err(|_| IndexedDbError::MissingDigest)?;
162
163        let said_str = digest.to_string();
164        
165        // Store the event in memory
166        let timestamped_event = TimestampedSignedEventMessage::new(signed_event.clone());
167        self.events
168            .borrow_mut()
169            .insert(said_str.clone(), timestamped_event.clone());
170
171        // Store the signatures
172        self.signatures
173            .borrow_mut()
174            .insert(said_str.clone(), signed_event.signatures.clone());
175
176        // Store the witness receipts if present
177        if let Some(receipts) = &signed_event.witness_receipts {
178            self.nontrans_receipts
179                .borrow_mut()
180                .insert(said_str.clone(), receipts.clone());
181        }
182
183        // Queue for IndexedDB persistence
184        let mut pending = self.pending_operations.borrow_mut();
185        pending.push(PendingOperation::StoreEvent {
186            said: said_str.clone(),
187            event: Box::new(timestamped_event),
188            signatures: signed_event.signatures.clone(),
189        });
190
191        Ok(())
192    }
193
194    fn log_receipt(
195        &self,
196        _txn: &Self::TransactionType,
197        signed_receipt: &SignedNontransferableReceipt,
198    ) -> Result<(), Self::Error> {
199        self.log_receipt_with_new_transaction(signed_receipt)
200    }
201
202    fn log_receipt_with_new_transaction(
203        &self,
204        signed_receipt: &SignedNontransferableReceipt,
205    ) -> Result<(), Self::Error> {
206        let digest = &signed_receipt.body.receipted_event_digest;
207        let said_str = digest.to_string();
208
209        let mut receipts_map = self.nontrans_receipts
210            .borrow_mut();
211        
212        // Get existing receipts or create new vector
213        let receipts = receipts_map.entry(said_str.clone()).or_default();
214
215        // Add new receipts that aren't already present
216        let mut new_receipts = Vec::new();
217        for receipt in &signed_receipt.signatures {
218            if !receipts.contains(receipt) {
219                receipts.push(receipt.clone());
220                new_receipts.push(receipt.clone());
221            }
222        }
223
224        if !new_receipts.is_empty() {
225            // Queue for IndexedDB persistence
226            let mut pending = self.pending_operations.borrow_mut();
227            pending.push(PendingOperation::StoreNontransReceipt {
228                said: said_str.clone(),
229                receipts: new_receipts,
230            });
231        }
232
233        Ok(())
234    }
235
236    fn get_signed_event(
237        &self,
238        said: &SelfAddressingIdentifier,
239    ) -> Result<Option<TimestampedSignedEventMessage>, Self::Error> {
240        let events = self.events.borrow();
241
242        let r = events.get(&said.to_string()).cloned();
243        Ok(r)
244    }
245
246    fn get_event(
247        &self,
248        said: &SelfAddressingIdentifier,
249    ) -> Result<Option<KeriEvent<KeyEvent>>, Self::Error> {
250        let events = self.events.borrow();
251
252        match events.get(&said.to_string()) {
253            Some(signed_event) => Ok(Some(signed_event.signed_event_message.event_message.clone())),
254            None => Ok(None),
255        }
256    }
257
258    fn get_signatures(
259        &self,
260        said: &SelfAddressingIdentifier,
261    ) -> Result<Option<impl Iterator<Item = IndexedSignature>>, Self::Error> {
262        let signatures = self.signatures.borrow();
263
264        match signatures.get(&said.to_string()) {
265            Some(sigs) => Ok(Some(sigs.clone().into_iter())),
266            None => Ok(None),
267        }
268    }
269
270    fn get_nontrans_couplets(
271        &self,
272        said: &SelfAddressingIdentifier,
273    ) -> Result<Option<impl Iterator<Item = Nontransferable>>, Self::Error> {
274        let receipts = self.nontrans_receipts.borrow();
275
276        match receipts.get(&said.to_string()) {
277            Some(receipts) => Ok(Some(receipts.clone().into_iter())),
278            None => Ok(None),
279        }
280    }
281
282    fn get_trans_receipts(
283        &self,
284        said: &SelfAddressingIdentifier,
285    ) -> Result<impl DoubleEndedIterator<Item = Transferable>, Self::Error> {
286        let receipts = self.trans_receipts.borrow();
287
288        match receipts.get(&said.to_string()) {
289            Some(receipts) => Ok(receipts.clone().into_iter()),
290            None => Ok(Vec::new().into_iter()),
291        }
292    }
293
294    fn remove_nontrans_receipt(
295        &self,
296        _txn_mode: &Self::TransactionType,
297        said: &SelfAddressingIdentifier,
298        nontrans: impl IntoIterator<Item = Nontransferable>,
299    ) -> Result<(), Self::Error> {
300        let said_str = said.to_string();
301        let mut receipts_map = self.nontrans_receipts.borrow_mut();
302
303        if let Some(receipts) = receipts_map.get_mut(&said_str) {
304            let receipts_to_remove: Vec<Nontransferable> = nontrans.into_iter().collect();
305            receipts.retain(|r| !receipts_to_remove.contains(r));
306
307            // Queue for IndexedDB persistence - include the REMAINING receipts after removal
308            let mut pending = self.pending_operations.borrow_mut();
309            pending.push(PendingOperation::RemoveReceipt {
310                said: said_str,
311                remaining_receipts: receipts.clone(),
312            });
313        }
314
315        Ok(())
316    }
317
318    fn remove_nontrans_receipt_with_new_transaction(
319        &self,
320        said: &SelfAddressingIdentifier,
321        nontrans: impl IntoIterator<Item = Nontransferable>,
322    ) -> Result<(), Self::Error> {
323        self.remove_nontrans_receipt(&(), said, nontrans)
324    }
325}
326
327impl IndexedDbLogDatabase {
328    // Insert transferable receipts
329    pub fn insert_trans_receipt(
330        &self, 
331        said: &SelfAddressingIdentifier, 
332        receipts: &[Transferable]
333    ) -> Result<(), IndexedDbError> {
334        let said_str = said.to_string();
335        let mut receipts_map = self.trans_receipts.borrow_mut();
336
337        // Get existing receipts or create new vector
338        let existing_receipts = receipts_map.entry(said_str.clone()).or_default();
339
340        // Add new receipts that aren't already present
341        let mut new_receipts = Vec::new();
342        for receipt in receipts {
343            if !existing_receipts.contains(receipt) {
344                existing_receipts.push(receipt.clone());
345                new_receipts.push(receipt.clone());
346            }
347        }
348
349        // Queue for IndexedDB persistence if we have new receipts
350        if !new_receipts.is_empty() {
351            let mut pending = self.pending_operations.borrow_mut();
352            pending.push(PendingOperation::StoreTransReceipt {
353                said: said_str,
354                receipts: new_receipts,
355            });
356        }
357
358        Ok(())
359    }
360
361    // Insert non-transferable receipts
362    pub fn insert_nontrans_receipt(
363        &self,
364        said: &SelfAddressingIdentifier,
365        receipts: &[Nontransferable]
366    ) -> Result<(), IndexedDbError> {
367        let said_str = said.to_string();
368        let mut receipts_map = self.nontrans_receipts.borrow_mut();
369
370        // Get existing receipts or create new vector
371        let existing_receipts = receipts_map.entry(said_str.clone()).or_default();
372
373        // Add new receipts that aren't already present
374        let mut new_receipts = Vec::new();
375        for receipt in receipts {
376            if !existing_receipts.contains(receipt) {
377                existing_receipts.push(receipt.clone());
378                new_receipts.push(receipt.clone());
379            }
380        }
381
382        // Queue for IndexedDB persistence
383        if !new_receipts.is_empty() {
384            let mut pending = self.pending_operations.borrow_mut();
385            pending.push(PendingOperation::StoreNontransReceipt {
386                said: said_str.clone(),
387                receipts: new_receipts,
388            });
389        }
390
391        Ok(())
392    }
393
394    pub fn get_nontrans_couplets_by_key(
395        &self,
396        key_prefix: &SelfAddressingIdentifier
397    ) -> Result<Option<impl Iterator<Item = Nontransferable>>, IndexedDbError> {
398        let key_str = key_prefix.to_string();
399        let receipts_map = self.nontrans_receipts.borrow();
400
401        if let Some(receipts) = receipts_map.get(&key_str) {
402            if receipts.is_empty() {
403                Ok(None)
404            } else {
405                // Return cloned receipts
406                Ok(Some(receipts.clone().into_iter()))
407            }
408        } else {
409            Ok(None)
410        }
411    }
412}
413
414// Helper function to flush pending operations to IndexedDB
415fn flush_pending_operations(
416    db: &web_sys::IdbDatabase,
417    operations: &Vec<PendingOperation>,
418) {
419    for op in operations {
420        match op {
421            PendingOperation::StoreEvent { said, event, signatures } => {
422                // Store event
423                if let Ok(transaction) = db.transaction_with_str_and_mode(
424                    "events",
425                    web_sys::IdbTransactionMode::Readwrite,
426                ) {
427                    if let Ok(store) = transaction.object_store("events") {
428                        // Convert event to JsValue
429                        let value = js_sys::Object::new();
430                        let _ = js_sys::Reflect::set(&value, &"said".into(), &said.clone().into());
431                        let _ = js_sys::Reflect::set(
432                            &value,
433                            &"event".into(),
434                            &JsValue::from_str(&format!("{:?}", serde_cbor::to_vec(&event).unwrap())),
435                        );
436
437                        if let Err(e) = store.put_with_key(&value, &said.into()) {
438                            log::error!("Failed to store event in IndexedDB: {:?}", e);
439                        }
440                    }
441                }
442
443                // Store signatures
444                if let Ok(transaction) = db.transaction_with_str_and_mode(
445                    "signatures",
446                    web_sys::IdbTransactionMode::Readwrite,
447                ) {
448                    if let Ok(store) = transaction.object_store("signatures") {
449                        let value = js_sys::Object::new();
450                        let _ = js_sys::Reflect::set(&value, &"said".into(), &said.into());
451                        let _ = js_sys::Reflect::set(
452                            &value,
453                            &"signatures".into(),
454                            &JsValue::from_str(&serde_json::to_string(&signatures).unwrap_or_default()),
455                        );
456
457                        if let Err(e) = store.put_with_key(&value, &said.into()) {
458                            log::error!("Failed to store signatures in IndexedDB: {:?}", e);
459                        }
460                    }
461                }
462            },
463            PendingOperation::StoreNontransReceipt { said, receipts } => {
464                if let Ok(transaction) = db.transaction_with_str_and_mode(
465                    "nontrans_receipts",
466                    web_sys::IdbTransactionMode::Readwrite,
467                ) {
468                    if let Ok(store) = transaction.object_store("nontrans_receipts") {
469                        // Trust our in-memory representation and just update the database
470                        // This works because we load all data at startup and keep it in sync
471                        let value = js_sys::Object::new();
472                        let _ = js_sys::Reflect::set(&value, &"said".into(), &said.into());
473                        let _ = js_sys::Reflect::set(
474                            &value,
475                            &"receipts".into(),
476                            &JsValue::from_str(&serde_json::to_string(&receipts).unwrap_or_default()),
477                        );
478
479                        if let Err(e) = store.put_with_key(&value, &said.into()) {
480                            log::error!("Failed to store receipts in IndexedDB: {:?}", e);
481                        }
482                    }
483                }
484            },
485            PendingOperation::StoreTransReceipt { said, receipts } => {
486                if let Ok(transaction) = db.transaction_with_str_and_mode(
487                    "trans_receipts",
488                    web_sys::IdbTransactionMode::Readwrite,
489                ) {
490                    if let Ok(store) = transaction.object_store("trans_receipts") {
491                        // Trust our in-memory representation and just update the database
492                        // This works because we load all data at startup and keep it in sync
493                        let value = js_sys::Object::new();
494                        let _ = js_sys::Reflect::set(&value, &"said".into(), &said.into());
495                        let _ = js_sys::Reflect::set(
496                            &value,
497                            &"receipts".into(),
498                            &JsValue::from_str(&serde_json::to_string(&receipts).unwrap_or_default()),
499                        );
500
501                        if let Err(e) = store.put_with_key(&value, &said.into()) {
502                            log::error!("Failed to store transferable receipts in IndexedDB: {:?}", e);
503                        }
504                    }
505                }
506            },
507            PendingOperation::RemoveReceipt { said, remaining_receipts } => {
508                if let Ok(transaction) = db.transaction_with_str_and_mode(
509                    "nontrans_receipts",
510                    web_sys::IdbTransactionMode::Readwrite,
511                ) {
512                    if let Ok(store) = transaction.object_store("nontrans_receipts") {
513                        // We already have the remaining receipts, so we can just update/delete
514                        if remaining_receipts.is_empty() {
515                            // Delete the entire entry if no receipts remain
516                            if let Err(e) = store.delete(&said.into()) {
517                                log::error!("Failed to delete receipts from IndexedDB: {:?}", e);
518                            }
519                        } else {
520                            // Update with remaining receipts
521                            let value = js_sys::Object::new();
522                            let _ = js_sys::Reflect::set(&value, &"said".into(), &said.into());
523                            let _ = js_sys::Reflect::set(
524                                &value,
525                                &"receipts".into(),
526                                &JsValue::from_str(&serde_json::to_string(&remaining_receipts).unwrap_or_default()),
527                            );
528
529                            if let Err(e) = store.put_with_key(&value, &said.into()) {
530                                log::error!("Failed to update receipts in IndexedDB: {:?}", e);
531                            }
532                        }
533                    }
534                }
535            }
536        }
537    }
538}
539
540// Helper function to load events from IndexedDB
541fn load_events(db: &web_sys::IdbDatabase, events: Rc<RefCell<HashMap<String, TimestampedSignedEventMessage>>>) {
542    if let Ok(transaction) = db.transaction_with_str_and_mode(
543        "events",
544        web_sys::IdbTransactionMode::Readwrite,
545    ) {
546        if let Ok(store) = transaction.object_store("events") {
547            if let Ok(request) = store.get_all() {
548                let callback = Closure::wrap(Box::new(move |event: web_sys::Event| {
549                    if let Some(result) = event.target()
550                        .and_then(|t| t.dyn_into::<web_sys::IdbRequest>().ok())
551                        .and_then(|r| r.result().ok())
552                    {
553                        if let Ok(array) = result.dyn_into::<js_sys::Array>() {
554                            let mut events_map = events.borrow_mut();
555                            for i in 0..array.length() {
556                                if let Ok(item) = array.get(i).dyn_into::<js_sys::Object>() {
557                                    if let (Some(said), Some(event_str)) = (
558                                        js_sys::Reflect::get(&item, &"said".into())
559                                            .ok()
560                                            .and_then(|v| v.as_string()),
561                                        js_sys::Reflect::get(&item, &"event".into())
562                                            .ok()
563                                            .and_then(|v| v.as_string()),
564                                    ) {
565                                        let mut ev_vec: Vec<u8> = vec![];
566                                        let mut ev_str = event_str.clone();
567                                        ev_str.remove(0);
568                                        ev_str.pop();
569                                        for e in ev_str.split(", ") {
570                                            ev_vec.push(e.parse().unwrap());
571                                        }
572                                        if let Ok(event) = serde_cbor::from_slice::<TimestampedSignedEventMessage>(&ev_vec) {
573                                            // let mut events_mut = events.borrow_mut();
574                                            events_map.insert(said, event);
575                                        } else {
576                                            log::warn!("Failed to parse event: {}", event_str);
577                                        }
578                                    }
579                                }
580                            }
581                        }
582                    }
583                }) as Box<dyn FnMut(_)>);
584
585                request.set_onsuccess(Some(callback.as_ref().unchecked_ref()));
586                callback.forget();
587            }
588        }
589    }
590}
591
592fn load_signatures(db: &web_sys::IdbDatabase, signatures: Rc<RefCell<HashMap<String, Vec<IndexedSignature>>>>) {
593    if let Ok(transaction) = db.transaction_with_str_and_mode(
594        "signatures",
595        web_sys::IdbTransactionMode::Readwrite,
596    ) {
597        if let Ok(store) = transaction.object_store("signatures") {
598            if let Ok(request) = store.get_all() {
599                let callback = Closure::wrap(Box::new(move |event: web_sys::Event| {
600                    if let Some(result) = event.target()
601                        .and_then(|t| t.dyn_into::<web_sys::IdbRequest>().ok())
602                        .and_then(|r| r.result().ok())
603                    {
604                        if let Ok(array) = result.dyn_into::<js_sys::Array>() {
605                            let mut sigs_map = signatures.borrow_mut();
606                            for i in 0..array.length() {
607                                if let Ok(item) = array.get(i).dyn_into::<js_sys::Object>() {
608                                    if let (Ok(digest), Ok(sigs_json)) = (
609                                        js_sys::Reflect::get(&item, &"digest".into()),
610                                        js_sys::Reflect::get(&item, &"signatures".into())
611                                    ) {
612                                        if let (Some(digest_str), Some(sigs_str)) = (
613                                            digest.as_string(),
614                                            sigs_json.as_string()
615                                        ) {
616                                            if let Ok(sig_data) = serde_json::from_str::<Vec<IndexedSignature>>(&sigs_str) {
617                                                sigs_map.insert(digest_str, sig_data);
618                                            }
619                                        }
620                                    }
621                                }
622                            }
623                        }
624                    }
625                }) as Box<dyn FnMut(_)>);
626
627                request.set_onsuccess(Some(callback.as_ref().unchecked_ref()));
628                callback.forget();
629            }
630        }
631    }
632}
633
634// Helper function to load transferable receipts from IndexedDB
635fn load_trans_receipts(db: &web_sys::IdbDatabase, receipts: Rc<RefCell<std::collections::HashMap<String, Vec<Transferable>>>>) {
636    if let Ok(transaction) = db.transaction_with_str_and_mode(
637        "trans_receipts",
638        web_sys::IdbTransactionMode::Readwrite,
639    ) {
640        if let Ok(store) = transaction.object_store("trans_receipts") {
641            if let Ok(request) = store.get_all() {
642                let callback = Closure::wrap(Box::new(move |event: web_sys::Event| {
643                    if let Some(result) = event.target()
644                        .and_then(|t| t.dyn_into::<web_sys::IdbRequest>().ok())
645                        .and_then(|r| r.result().ok())
646                    {
647                        if let Ok(array) = result.dyn_into::<js_sys::Array>() {
648                            let mut receipts_map = receipts.borrow_mut();
649                            for i in 0..array.length() {
650                                if let Ok(item) = array.get(i).dyn_into::<js_sys::Object>() {
651                                    if let (Ok(digest), Ok(receipts_json)) = (
652                                        js_sys::Reflect::get(&item, &"digest".into()),
653                                        js_sys::Reflect::get(&item, &"receipts".into())
654                                    ) {
655                                        if let (Some(digest_str), Some(receipts_str)) = (
656                                            digest.as_string(),
657                                            receipts_json.as_string()
658                                        ) {
659                                            if let Ok(trans_receipts) = serde_json::from_str::<Vec<Transferable>>(&receipts_str) {
660                                                receipts_map.insert(digest_str, trans_receipts);
661                                            }
662                                        }
663                                    }
664                                }
665                            }
666                        }
667                    }
668                }) as Box<dyn FnMut(_)>);
669
670                request.set_onsuccess(Some(callback.as_ref().unchecked_ref()));
671                callback.forget();
672            }
673        }
674    }
675}
676
677// Helper function to load non-transferable receipts from IndexedDB
678fn load_nontrans_receipts(db: &web_sys::IdbDatabase, receipts: Rc<RefCell<HashMap<String, Vec<Nontransferable>>>>) {
679    if let Ok(transaction) = db.transaction_with_str_and_mode(
680        "nontrans_receipts",
681        web_sys::IdbTransactionMode::Readwrite,
682    ) {
683        if let Ok(store) = transaction.object_store("nontrans_receipts") {
684            if let Ok(request) = store.get_all() {
685                let callback = Closure::wrap(Box::new(move |event: web_sys::Event| {
686                    if let Some(result) = event.target()
687                        .and_then(|t| t.dyn_into::<web_sys::IdbRequest>().ok())
688                        .and_then(|r| r.result().ok())
689                    {
690                        if let Ok(array) = result.dyn_into::<js_sys::Array>() {
691                            let mut receipts_map = receipts.borrow_mut();
692                            for i in 0..array.length() {
693                                if let Ok(item) = array.get(i).dyn_into::<js_sys::Object>() {
694                                    if let (Ok(digest), Ok(receipts_json)) = (
695                                        js_sys::Reflect::get(&item, &"digest".into()),
696                                        js_sys::Reflect::get(&item, &"receipts".into())
697                                    ) {
698                                        if let (Some(digest_str), Some(receipts_str)) = (
699                                            digest.as_string(),
700                                            receipts_json.as_string()
701                                        ) {
702                                            if let Ok(nontrans_receipts) = serde_json::from_str::<Vec<Nontransferable>>(&receipts_str) {
703                                                receipts_map.insert(digest_str, nontrans_receipts);
704                                            }
705                                        }
706                                    }
707                                }
708                            }
709                        }
710                    }
711                }) as Box<dyn FnMut(_)>);
712
713                request.set_onsuccess(Some(callback.as_ref().unchecked_ref()));
714                callback.forget();
715            }
716        }
717    }
718}
719
720// Set up background flush for IndexedDB
721fn setup_background_flush(
722    db: web_sys::IdbDatabase,
723    pending_ops: Rc<RefCell<Vec<PendingOperation>>>,
724    flush_flag: Rc<Cell<bool>>
725) {
726    // Create interval function
727    let interval_callback = Closure::wrap(Box::new(move || {
728        if !flush_flag.get() {
729            flush_flag.set(true);
730
731            spawn_local({
732                let db = db.clone();
733                let pending = pending_ops.clone();
734                let flag = flush_flag.clone();
735
736                async move {
737                    // Get operations to process
738                    let ops_to_flush = {
739                        let mut pending_borrow = pending.borrow_mut();
740                        if pending_borrow.is_empty() {
741                            Vec::new()
742                        } else {
743                            pending_borrow.drain(..).collect::<Vec<_>>()
744                        }
745                    };
746
747                    // Process operations
748                    if !ops_to_flush.is_empty() {
749                        flush_pending_operations(&db, &ops_to_flush);
750                    }
751
752                    flag.set(false);
753                }
754            });
755        }
756    }) as Box<dyn FnMut()>);
757
758    // Set up interval
759    let window = web_sys::window().unwrap();
760    let _ = window.set_interval_with_callback_and_timeout_and_arguments_0(
761        interval_callback.as_ref().unchecked_ref(),
762        1000
763    );
764
765    interval_callback.forget();
766}