1#![cfg_attr(not(test), allow(unused))]
16
17use std::{
18 collections::{HashMap, HashSet},
19 rc::Rc,
20 time::Duration,
21};
22
23use indexed_db_futures::{Build, database::Database};
24#[cfg(target_family = "wasm")]
25use matrix_sdk_base::{
26 cross_process_lock::{CrossProcessLockGeneration, FIRST_CROSS_PROCESS_LOCK_GENERATION},
27 event_cache::thread::ThreadInfo,
28};
29use matrix_sdk_base::{
30 event_cache::{Event, Gap, store::EventCacheStore},
31 linked_chunk::{
32 ChunkIdentifier, ChunkIdentifierGenerator, ChunkMetadata, LinkedChunkId, Position,
33 RawChunk, Update,
34 },
35 timer,
36};
37use ruma::{
38 EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, RoomId, events::relation::RelationType,
39};
40use tracing::{error, instrument, trace};
41use web_sys::IdbTransactionMode;
42
43use crate::{
44 event_cache_store::{
45 migrations::current::keys,
46 transaction::IndexeddbEventCacheStoreTransaction,
47 types::{ChunkType, InBandEvent, Lease, OutOfBandEvent, Thread},
48 },
49 serializer::indexed_type::{IndexedTypeSerializer, traits::Indexed},
50 transaction::TransactionError,
51};
52
53mod builder;
54mod error;
55#[cfg(all(test, target_family = "wasm"))]
56mod integration_tests;
57mod migrations;
58mod serializer;
59mod transaction;
60mod types;
61
62pub use builder::IndexeddbEventCacheStoreBuilder;
63pub use error::IndexeddbEventCacheStoreError;
64
65#[derive(Debug, Clone)]
71pub struct IndexeddbEventCacheStore {
72 inner: Rc<Database>,
74 serializer: IndexedTypeSerializer,
76}
77
78impl IndexeddbEventCacheStore {
79 pub fn builder() -> IndexeddbEventCacheStoreBuilder {
82 IndexeddbEventCacheStoreBuilder::default()
83 }
84
85 pub fn transaction<'a>(
89 &'a self,
90 stores: &[&str],
91 mode: IdbTransactionMode,
92 ) -> Result<IndexeddbEventCacheStoreTransaction<'a>, IndexeddbEventCacheStoreError> {
93 Ok(IndexeddbEventCacheStoreTransaction::new(
94 self.inner
95 .transaction(stores)
96 .with_mode(mode)
97 .build()
98 .map_err(TransactionError::from)?,
99 &self.serializer,
100 ))
101 }
102}
103
104#[cfg(target_family = "wasm")]
105#[async_trait::async_trait(?Send)]
106impl EventCacheStore for IndexeddbEventCacheStore {
107 type Error = IndexeddbEventCacheStoreError;
108
109 #[instrument(skip(self))]
110 async fn try_take_leased_lock(
111 &self,
112 lease_duration_ms: u32,
113 key: &str,
114 holder: &str,
115 ) -> Result<Option<CrossProcessLockGeneration>, IndexeddbEventCacheStoreError> {
116 let transaction =
117 self.transaction(&[Lease::OBJECT_STORE], IdbTransactionMode::Readwrite)?;
118
119 let now = Duration::from_millis(MilliSecondsSinceUnixEpoch::now().get().into());
120 let expiration = now + Duration::from_millis(lease_duration_ms.into());
121
122 let lease = match transaction.get_lease_by_id(key).await? {
123 Some(mut lease) => {
124 if lease.holder == holder {
125 lease.expiration = expiration;
127
128 Some(lease)
129 } else {
130 if lease.expiration < now {
132 lease.holder = holder.to_owned();
134 lease.expiration = expiration;
135 lease.generation += 1;
136
137 Some(lease)
138 } else {
139 None
141 }
142 }
143 }
144 None => {
145 let lease = Lease {
146 key: key.to_owned(),
147 holder: holder.to_owned(),
148 expiration,
149 generation: FIRST_CROSS_PROCESS_LOCK_GENERATION,
150 };
151
152 Some(lease)
153 }
154 };
155
156 Ok(if let Some(lease) = lease {
157 transaction.put_lease(&lease)?;
158 transaction.commit().await?;
159
160 Some(lease.generation)
161 } else {
162 None
163 })
164 }
165
166 #[instrument(skip(self, updates))]
167 async fn handle_linked_chunk_updates(
168 &self,
169 linked_chunk_id: LinkedChunkId<'_>,
170 updates: Vec<Update<Event, Gap>>,
171 ) -> Result<(), IndexeddbEventCacheStoreError> {
172 let _timer = timer!("method");
173
174 let transaction = self.transaction(
175 &[keys::LINKED_CHUNKS, keys::GAPS, keys::EVENTS],
176 IdbTransactionMode::Readwrite,
177 )?;
178
179 let is_complete_event = |event: &Event| {
182 let Some(event_id) = event.event_id() else {
183 error!("Found event with no ID");
184 return false;
185 };
186 if event.kind.event_type().is_none() {
187 error!(%event_id, "Found an event with no event type");
188 return false;
189 }
190 true
191 };
192
193 for update in updates {
194 match update {
195 Update::NewItemsChunk { previous, new, next } => {
196 trace!(%linked_chunk_id, "Inserting new chunk (prev={previous:?}, new={new:?}, next={next:?})");
197 transaction
198 .add_chunk(&types::Chunk {
199 linked_chunk_id: linked_chunk_id.to_owned(),
200 identifier: new.index(),
201 previous: previous.map(|i| i.index()),
202 next: next.map(|i| i.index()),
203 chunk_type: ChunkType::Event,
204 })
205 .await?;
206 }
207 Update::NewGapChunk { previous, new, next, gap } => {
208 trace!(%linked_chunk_id, "Inserting new gap (prev={previous:?}, new={new:?}, next={next:?})");
209 transaction.add_item(&types::Gap {
210 linked_chunk_id: linked_chunk_id.to_owned(),
211 chunk_identifier: new.index(),
212 token: gap.token,
213 })?;
214 transaction
215 .add_chunk(&types::Chunk {
216 linked_chunk_id: linked_chunk_id.to_owned(),
217 identifier: new.index(),
218 previous: previous.map(|i| i.index()),
219 next: next.map(|i| i.index()),
220 chunk_type: ChunkType::Gap,
221 })
222 .await?;
223 }
224 Update::RemoveChunk(chunk_id) => {
225 trace!(%linked_chunk_id, "Removing chunk {chunk_id:?}");
226 transaction.delete_chunk_by_id(linked_chunk_id, chunk_id).await?;
227 }
228 Update::PushItems { at, items } => {
229 let chunk_identifier = at.chunk_identifier().index();
230
231 trace!(%linked_chunk_id, "pushing {} items @ {chunk_identifier}", items.len());
232
233 for (i, item) in items.into_iter().filter(is_complete_event).enumerate() {
234 transaction
235 .add_event(&types::Event::InBand(InBandEvent {
236 linked_chunk_id: linked_chunk_id.to_owned(),
237 content: item,
238 position: types::Position {
239 chunk_identifier,
240 index: at.index() + i,
241 },
242 }))
243 .await?;
244 }
245 }
246 Update::ReplaceItem { at, item } => {
247 let chunk_id = at.chunk_identifier().index();
248 let index = at.index();
249
250 trace!(%linked_chunk_id, "replacing item @ {chunk_id}:{index}");
251
252 if !is_complete_event(&item) {
253 continue;
254 }
255
256 transaction
257 .put_event(&types::Event::InBand(InBandEvent {
258 linked_chunk_id: linked_chunk_id.to_owned(),
259 content: item,
260 position: at.into(),
261 }))
262 .await?;
263 }
264 Update::RemoveItem { at } => {
265 let chunk_id = at.chunk_identifier().index();
266 let index = at.index();
267
268 trace!(%linked_chunk_id, "removing item @ {chunk_id}:{index}");
269
270 transaction.delete_event_by_position(linked_chunk_id, at.into()).await?;
271 }
272 Update::DetachLastItems { at } => {
273 let chunk_id = at.chunk_identifier().index();
274 let index = at.index();
275
276 trace!(%linked_chunk_id, "detaching last items @ {chunk_id}:{index}");
277
278 transaction
279 .delete_events_by_chunk_from_index(linked_chunk_id, at.into())
280 .await?;
281 }
282 Update::StartReattachItems | Update::EndReattachItems => {
283 }
285 Update::Clear => {
286 trace!(%linked_chunk_id, "clearing room");
287 transaction.delete_chunks_by_linked_chunk_id(linked_chunk_id).await?;
288 transaction.delete_events_by_linked_chunk_id(linked_chunk_id).await?;
289 transaction.delete_gaps_by_linked_chunk_id(linked_chunk_id).await?;
290 }
291 }
292 }
293 transaction.commit().await?;
294 Ok(())
295 }
296
297 #[instrument(skip(self))]
298 async fn load_all_chunks(
299 &self,
300 linked_chunk_id: LinkedChunkId<'_>,
301 ) -> Result<Vec<RawChunk<Event, Gap>>, IndexeddbEventCacheStoreError> {
302 let _ = timer!("method");
303
304 let transaction = self.transaction(
305 &[keys::LINKED_CHUNKS, keys::GAPS, keys::EVENTS],
306 IdbTransactionMode::Readonly,
307 )?;
308
309 let mut raw_chunks = Vec::new();
310 let chunks = transaction.get_chunks_by_linked_chunk_id(linked_chunk_id).await?;
311 for chunk in chunks {
312 if let Some(raw_chunk) = transaction
313 .load_chunk_by_id(linked_chunk_id, ChunkIdentifier::new(chunk.identifier))
314 .await?
315 {
316 raw_chunks.push(raw_chunk);
317 }
318 }
319 Ok(raw_chunks)
320 }
321
322 #[instrument(skip(self))]
323 async fn load_all_chunks_metadata(
324 &self,
325 linked_chunk_id: LinkedChunkId<'_>,
326 ) -> Result<Vec<ChunkMetadata>, IndexeddbEventCacheStoreError> {
327 let _ = timer!("method");
338
339 let transaction = self.transaction(
340 &[keys::LINKED_CHUNKS, keys::EVENTS, keys::GAPS],
341 IdbTransactionMode::Readonly,
342 )?;
343
344 let mut raw_chunks = Vec::new();
345 let chunks = transaction.get_chunks_by_linked_chunk_id(linked_chunk_id).await?;
346 for chunk in chunks {
347 let chunk_id = ChunkIdentifier::new(chunk.identifier);
348 let num_items =
349 transaction.get_events_count_by_chunk(linked_chunk_id, chunk_id).await?;
350 raw_chunks.push(ChunkMetadata {
351 num_items,
352 previous: chunk.previous.map(ChunkIdentifier::new),
353 identifier: ChunkIdentifier::new(chunk.identifier),
354 next: chunk.next.map(ChunkIdentifier::new),
355 });
356 }
357 Ok(raw_chunks)
358 }
359
360 #[instrument(skip(self))]
361 async fn load_last_chunk(
362 &self,
363 linked_chunk_id: LinkedChunkId<'_>,
364 ) -> Result<
365 (Option<RawChunk<Event, Gap>>, ChunkIdentifierGenerator),
366 IndexeddbEventCacheStoreError,
367 > {
368 let _timer = timer!("method");
369
370 let transaction = self.transaction(
371 &[keys::LINKED_CHUNKS, keys::EVENTS, keys::GAPS],
372 IdbTransactionMode::Readonly,
373 )?;
374
375 if transaction.get_chunks_count_by_linked_chunk_id(linked_chunk_id).await? == 0 {
376 return Ok((None, ChunkIdentifierGenerator::new_from_scratch()));
377 }
378 match transaction.get_chunk_by_next_chunk_id(linked_chunk_id, None).await {
382 Err(TransactionError::ItemIsNotUnique) => {
383 Err(IndexeddbEventCacheStoreError::ChunksContainDisjointLists)
387 }
388 Err(e) => {
389 Err(e.into())
392 }
393 Ok(None) => {
394 Err(IndexeddbEventCacheStoreError::ChunksContainCycle)
398 }
399 Ok(Some(last_chunk)) => {
400 let last_chunk_identifier = ChunkIdentifier::new(last_chunk.identifier);
401
402 let (last_raw_chunk, max_chunk_id) = futures_util::future::try_join(
403 transaction.load_chunk_by_id(linked_chunk_id, last_chunk_identifier),
404 transaction.get_max_chunk_by_id(linked_chunk_id),
405 )
406 .await?;
407
408 let last_raw_chunk =
409 last_raw_chunk.ok_or(IndexeddbEventCacheStoreError::UnableToLoadChunk)?;
410 let max_chunk_id = max_chunk_id
411 .map(|chunk| ChunkIdentifier::new(chunk.identifier))
412 .ok_or(IndexeddbEventCacheStoreError::NoMaxChunkId)?;
413 let generator =
414 ChunkIdentifierGenerator::new_from_previous_chunk_identifier(max_chunk_id);
415 Ok((Some(last_raw_chunk), generator))
416 }
417 }
418 }
419
420 #[instrument(skip(self))]
421 async fn load_previous_chunk(
422 &self,
423 linked_chunk_id: LinkedChunkId<'_>,
424 before_chunk_identifier: ChunkIdentifier,
425 ) -> Result<Option<RawChunk<Event, Gap>>, IndexeddbEventCacheStoreError> {
426 let _timer = timer!("method");
427
428 let transaction = self.transaction(
429 &[keys::LINKED_CHUNKS, keys::EVENTS, keys::GAPS],
430 IdbTransactionMode::Readonly,
431 )?;
432 if let Some(chunk) =
433 transaction.get_chunk_by_id(linked_chunk_id, before_chunk_identifier).await?
434 && let Some(previous_identifier) = chunk.previous
435 {
436 let previous_identifier = ChunkIdentifier::new(previous_identifier);
437 Ok(transaction.load_chunk_by_id(linked_chunk_id, previous_identifier).await?)
438 } else {
439 Ok(None)
440 }
441 }
442
443 #[instrument(skip(self))]
444 async fn load_thread_info(
445 &self,
446 room_id: &RoomId,
447 thread_id: &EventId,
448 insert_default_if_missing: bool,
449 ) -> Result<Option<ThreadInfo>, Self::Error> {
450 let _timer = timer!("method");
451
452 let transaction = self.transaction(&[keys::THREADS], IdbTransactionMode::Readonly)?;
453
454 if let Some(thread) = transaction.load_thread_info(room_id, thread_id).await? {
455 return Ok(Some(thread.info));
456 } else if !insert_default_if_missing {
457 return Ok(None);
458 }
459
460 drop(transaction);
461
462 let transaction = self.transaction(&[keys::THREADS], IdbTransactionMode::Readwrite)?;
463
464 let thread = Thread {
465 room_id: room_id.to_owned(),
466 thread_id: thread_id.to_owned(),
467 info: ThreadInfo::new(),
468 };
469 transaction.update_thread_info(&thread)?;
470 transaction.commit().await?;
471
472 Ok(Some(thread.info))
473 }
474
475 #[instrument(skip(self))]
476 async fn update_thread_info(
477 &self,
478 room_id: &RoomId,
479 thread_id: &EventId,
480 thread_info: &ThreadInfo,
481 ) -> Result<(), Self::Error> {
482 let _timer = timer!("method");
483
484 let transaction = self.transaction(&[keys::THREADS], IdbTransactionMode::Readwrite)?;
485
486 let thread = Thread {
487 room_id: room_id.to_owned(),
488 thread_id: thread_id.to_owned(),
489 info: thread_info.clone(),
490 };
491 transaction.update_thread_info(&thread)?;
492 transaction.commit().await?;
493
494 Ok(())
495 }
496
497 #[instrument(skip(self))]
498 async fn clear_all_events(
499 &self,
500 room_id: Option<&RoomId>,
501 ) -> Result<(), IndexeddbEventCacheStoreError> {
502 let _timer = timer!("method");
503
504 let transaction = self.transaction(
505 &[keys::LINKED_CHUNKS, keys::EVENTS, keys::GAPS, keys::THREADS],
506 IdbTransactionMode::Readwrite,
507 )?;
508
509 match room_id {
510 None => {
512 transaction.clear::<types::Chunk>()?;
513 transaction.clear::<types::Event>()?;
514 transaction.clear::<types::Gap>()?;
515 transaction.commit().await?;
516 }
517
518 Some(room_id) => {
520 {
522 for linked_chunk_id in
523 [LinkedChunkId::Room(room_id), LinkedChunkId::PinnedEvents(room_id)]
524 {
525 transaction.delete_chunks_by_linked_chunk_id(linked_chunk_id).await?;
528 transaction.delete_gaps_by_linked_chunk_id(linked_chunk_id).await?;
529 transaction.delete_events_by_linked_chunk_id(linked_chunk_id).await?;
530 }
531 }
532
533 {
535 for thread in transaction.get_threads_by_room_id(room_id).await? {
536 let linked_chunk_id = thread.linked_chunk();
537
538 transaction.delete_chunks_by_linked_chunk_id(linked_chunk_id).await?;
541 transaction.delete_gaps_by_linked_chunk_id(linked_chunk_id).await?;
542 transaction.delete_events_by_linked_chunk_id(linked_chunk_id).await?;
543 }
544 }
545
546 transaction.commit().await?;
548 }
549 }
550 Ok(())
551 }
552
553 #[instrument(skip(self, events))]
554 async fn filter_duplicated_events(
555 &self,
556 linked_chunk_id: LinkedChunkId<'_>,
557 events: Vec<OwnedEventId>,
558 ) -> Result<Vec<(OwnedEventId, Position)>, IndexeddbEventCacheStoreError> {
559 let _timer = timer!("method");
560
561 if events.is_empty() {
562 return Ok(Vec::new());
563 }
564
565 let transaction = self.transaction(&[keys::EVENTS], IdbTransactionMode::Readonly)?;
566 let mut duplicated = Vec::new();
567 for event_id in events {
568 if let Some(types::Event::InBand(event)) =
569 transaction.get_event_by_id(linked_chunk_id, &event_id).await?
570 {
571 duplicated.push((event_id, event.position.into()));
572 }
573 }
574 Ok(duplicated)
575 }
576
577 #[instrument(skip(self, event_id))]
578 async fn find_event(
579 &self,
580 room_id: &RoomId,
581 event_id: &EventId,
582 ) -> Result<Option<Event>, IndexeddbEventCacheStoreError> {
583 let _timer = timer!("method");
584
585 let transaction = self.transaction(&[keys::EVENTS], IdbTransactionMode::Readonly)?;
586 transaction
587 .get_events_by_room(room_id, event_id)
588 .await
589 .map(|mut events| events.pop().map(Into::into))
590 .map_err(Into::into)
591 }
592
593 #[instrument(skip(self, event_id, filters))]
594 async fn find_event_relations(
595 &self,
596 room_id: &RoomId,
597 event_id: &EventId,
598 filters: Option<&[RelationType]>,
599 ) -> Result<Vec<(Event, Option<Position>)>, IndexeddbEventCacheStoreError> {
600 let _timer = timer!("method");
601
602 let transaction = self.transaction(&[keys::EVENTS], IdbTransactionMode::Readonly)?;
603
604 let mut related_events = HashMap::<OwnedEventId, types::Event>::new();
605 match filters {
606 Some(relation_types) if !relation_types.is_empty() => {
607 for relation_type in relation_types {
608 let relation = (event_id, relation_type);
609 let events = transaction.get_events_by_relation(room_id, relation).await?;
610 for event in events {
611 let Some(event_id) = event.event_id() else {
612 return Err(IndexeddbEventCacheStoreError::EventWithoutId);
613 };
614 match event.linked_chunk_id() {
615 LinkedChunkId::Room(_) => {
616 related_events.insert(event_id.to_owned(), event);
619 }
620 _ => {
621 related_events
624 .entry(event_id.to_owned())
625 .or_insert_with(|| event.into_out_of_band_event());
626 }
627 }
628 }
629 }
630 }
631 _ => {
632 for event in transaction.get_events_by_related_event(room_id, event_id).await? {
633 let Some(event_id) = event.event_id() else {
634 return Err(IndexeddbEventCacheStoreError::EventWithoutId);
635 };
636 match event.linked_chunk_id() {
637 LinkedChunkId::Room(_) => {
638 related_events.insert(event_id.to_owned(), event);
641 }
642 _ => {
643 related_events
646 .entry(event_id.to_owned())
647 .or_insert_with(|| event.into_out_of_band_event());
648 }
649 }
650 }
651 }
652 }
653 Ok(related_events
654 .into_values()
655 .map(|event| {
656 let position = event.position().map(Into::into);
657 (event.into(), position)
658 })
659 .collect())
660 }
661
662 #[instrument(skip(self))]
663 async fn find_events_before_timestamp(
664 &self,
665 room_id: &RoomId,
666 cutoff_ms: u64,
667 ) -> Result<Vec<Event>, IndexeddbEventCacheStoreError> {
668 let _timer = timer!("method");
669
670 let transaction = self.transaction(&[keys::EVENTS], IdbTransactionMode::Readonly)?;
671 let events = transaction.get_events_before_timestamp(room_id, cutoff_ms).await?;
672
673 let mut seen_event_ids = HashSet::new();
674 let mut results = Vec::new();
675 for event in events {
676 let Some(event_id) = event.event_id().map(ToOwned::to_owned) else {
677 continue;
678 };
679
680 if seen_event_ids.insert(event_id) {
681 results.push(Event::from(event));
682 }
683 }
684
685 Ok(results)
686 }
687
688 #[instrument(skip(self))]
689 async fn get_room_events(
690 &self,
691 room_id: &RoomId,
692 event_type: Option<&str>,
693 session_id: Option<&str>,
694 ) -> Result<Vec<Event>, IndexeddbEventCacheStoreError> {
695 let _timer = timer!("method");
696
697 let transaction = self.transaction(&[keys::EVENTS], IdbTransactionMode::Readonly)?;
701 transaction
702 .get_room_events(room_id)
703 .await
704 .map(|mut vec| {
705 vec.dedup_by(|a, b| {
706 if let (Some(a), Some(b)) = (a.event_id(), b.event_id()) {
707 a == b
708 } else {
709 false
710 }
711 });
712 vec.into_iter()
713 .map(Event::from)
714 .filter(|e| {
715 event_type.is_none_or(|event_type| {
716 Some(event_type) == e.kind.event_type().as_deref()
717 })
718 })
719 .filter(|e| session_id.is_none_or(|s| Some(s) == e.kind.session_id()))
720 .collect()
721 })
722 .map_err(Into::into)
723 }
724
725 #[instrument(skip(self, event))]
726 async fn save_event(
727 &self,
728 room_id: &RoomId,
729 event: Event,
730 ) -> Result<(), IndexeddbEventCacheStoreError> {
731 let _timer = timer!("method");
732
733 let Some(event_id) = event.event_id() else {
734 error!(%room_id, "Trying to save an event with no ID");
735 return Ok(());
736 };
737 let transaction = self.transaction(&[keys::EVENTS], IdbTransactionMode::Readwrite)?;
738
739 let mut events = transaction
740 .get_events_by_room(room_id, event_id)
741 .await?
742 .into_iter()
743 .map(|e| e.with_content(event.clone()))
744 .collect::<Vec<types::Event>>();
745 if events.is_empty() {
746 events.push(types::Event::OutOfBand(OutOfBandEvent {
747 linked_chunk_id: LinkedChunkId::Room(room_id).to_owned(),
748 content: event,
749 position: (),
750 }));
751 }
752 for event in events {
753 transaction.put_event(&event).await?;
754 }
755 transaction.commit().await?;
756 Ok(())
757 }
758
759 async fn optimize(&self) -> Result<(), Self::Error> {
760 Ok(())
761 }
762
763 async fn get_size(&self) -> Result<Option<usize>, Self::Error> {
764 Ok(None)
765 }
766
767 async fn close(&self) -> Result<(), Self::Error> {
768 Ok(())
769 }
770
771 async fn reopen(&self) -> Result<(), Self::Error> {
772 Ok(())
773 }
774}
775
776#[cfg(all(test, target_family = "wasm"))]
777mod tests {
778 use matrix_sdk_base::{
779 event_cache::store::EventCacheStoreError, event_cache_store_integration_tests,
780 event_cache_store_integration_tests_time,
781 };
782 use uuid::Uuid;
783
784 use crate::{
785 event_cache_store::IndexeddbEventCacheStore, indexeddb_event_cache_store_integration_tests,
786 };
787
788 mod unencrypted {
789 use super::*;
790
791 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
792
793 async fn get_event_cache_store() -> Result<IndexeddbEventCacheStore, EventCacheStoreError> {
794 let name = format!("test-event-cache-store-{}", Uuid::new_v4().as_hyphenated());
795 Ok(IndexeddbEventCacheStore::builder().database_name(name).build().await?)
796 }
797
798 event_cache_store_integration_tests!();
799 event_cache_store_integration_tests_time!();
800
801 indexeddb_event_cache_store_integration_tests!();
802 }
803
804 mod encrypted {
805 use std::sync::Arc;
806
807 use matrix_sdk_store_encryption::StoreCipher;
808
809 use super::*;
810
811 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
812
813 async fn get_event_cache_store() -> Result<IndexeddbEventCacheStore, EventCacheStoreError> {
814 let name = format!("test-event-cache-store-{}", Uuid::new_v4().as_hyphenated());
815 Ok(IndexeddbEventCacheStore::builder()
816 .database_name(name)
817 .store_cipher(Arc::new(StoreCipher::new().expect("store cipher")))
818 .build()
819 .await?)
820 }
821
822 event_cache_store_integration_tests!();
823 event_cache_store_integration_tests_time!();
824
825 indexeddb_event_cache_store_integration_tests!();
826 }
827}