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
31unsafe 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 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 if let Ok(request) = factory.open(DB_NAME) {
65 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 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 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_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 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 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 let timestamped_event = TimestampedSignedEventMessage::new(signed_event.clone());
167 self.events
168 .borrow_mut()
169 .insert(said_str.clone(), timestamped_event.clone());
170
171 self.signatures
173 .borrow_mut()
174 .insert(said_str.clone(), signed_event.signatures.clone());
175
176 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 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 let receipts = receipts_map.entry(said_str.clone()).or_default();
214
215 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 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 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 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 let existing_receipts = receipts_map.entry(said_str.clone()).or_default();
339
340 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 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 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 let existing_receipts = receipts_map.entry(said_str.clone()).or_default();
372
373 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 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 Ok(Some(receipts.clone().into_iter()))
407 }
408 } else {
409 Ok(None)
410 }
411 }
412}
413
414fn 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 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 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 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 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 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 if remaining_receipts.is_empty() {
515 if let Err(e) = store.delete(&said.into()) {
517 log::error!("Failed to delete receipts from IndexedDB: {:?}", e);
518 }
519 } else {
520 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
540fn 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 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
634fn 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
677fn 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
720fn setup_background_flush(
722 db: web_sys::IdbDatabase,
723 pending_ops: Rc<RefCell<Vec<PendingOperation>>>,
724 flush_flag: Rc<Cell<bool>>
725) {
726 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 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 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 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}