diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store/migrations/mod.rs b/crates/matrix-sdk-indexeddb/src/crypto_store/migrations/mod.rs index 76d04cc73..5dd7afa86 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store/migrations/mod.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store/migrations/mod.rs @@ -17,11 +17,12 @@ use tracing::info; use wasm_bindgen::JsValue; use crate::{ - crypto_store::{indexeddb_serializer::IndexeddbSerializer, keys, Result}, + crypto_store::{indexeddb_serializer::IndexeddbSerializer, Result}, IndexeddbCryptoStoreError, }; mod old_keys; +mod v0_to_v5; mod v5_to_v7; mod v7; mod v7_to_v8; @@ -44,7 +45,7 @@ pub async fn open_and_upgrade_db( // Perform the schema-only migrations if old_version < 5 { - migrate_schema_up_to_v5(name).await?; + v0_to_v5::migrate_schema_up_to_v5(name).await?; } // If we have yet to complete the migration to V7, migrate the schema to V6 @@ -85,115 +86,6 @@ pub async fn open_and_upgrade_db( Ok(IdbDatabase::open_u32(name, 10)?.await?) } -async fn migrate_schema_up_to_v5(name: &str) -> Result<(), DomException> { - do_schema_upgrade(name, 5, |db, old_version| { - // An old_version of 1 could either mean actually the first version of the - // schema, or a completely empty schema that has been created with a - // call to `IdbDatabase::open` with no explicit "version". So, to determine - // if we need to create the V1 stores, we actually check if the schema is empty. - if db.object_store_names().next().is_none() { - migrate_stores_to_v1(db)?; - } - - if old_version < 2 { - migrate_stores_to_v2(db)?; - } - - if old_version < 3 { - migrate_stores_to_v3(db)?; - } - - if old_version < 4 { - migrate_stores_to_v4(db)?; - } - - if old_version < 5 { - migrate_stores_to_v5(db)?; - } - - Ok(()) - }) - .await -} - -fn migrate_stores_to_v1(db: &IdbDatabase) -> Result<(), DomException> { - db.create_object_store(keys::CORE)?; - db.create_object_store(keys::SESSION)?; - - db.create_object_store(old_keys::INBOUND_GROUP_SESSIONS_V1)?; - db.create_object_store(keys::OUTBOUND_GROUP_SESSIONS)?; - db.create_object_store(keys::TRACKED_USERS)?; - db.create_object_store(keys::OLM_HASHES)?; - db.create_object_store(keys::DEVICES)?; - - db.create_object_store(keys::IDENTITIES)?; - db.create_object_store(keys::BACKUP_KEYS)?; - - Ok(()) -} - -fn migrate_stores_to_v2(db: &IdbDatabase) -> Result<(), DomException> { - // We changed how we store inbound group sessions, the key used to - // be a tuple of `(room_id, sender_key, session_id)` now it's a - // tuple of `(room_id, session_id)` - // - // Let's just drop the whole object store. - db.delete_object_store(old_keys::INBOUND_GROUP_SESSIONS_V1)?; - db.create_object_store(old_keys::INBOUND_GROUP_SESSIONS_V1)?; - - db.create_object_store(keys::ROOM_SETTINGS)?; - - Ok(()) -} - -fn migrate_stores_to_v3(db: &IdbDatabase) -> Result<(), DomException> { - // We changed the way we store outbound session. - // ShareInfo changed from a struct to an enum with struct variant. - // Let's just discard the existing outbounds - db.delete_object_store(keys::OUTBOUND_GROUP_SESSIONS)?; - db.create_object_store(keys::OUTBOUND_GROUP_SESSIONS)?; - - // Support for MSC2399 withheld codes - db.create_object_store(keys::DIRECT_WITHHELD_INFO)?; - - Ok(()) -} - -fn migrate_stores_to_v4(db: &IdbDatabase) -> Result<(), DomException> { - db.create_object_store(keys::SECRETS_INBOX)?; - Ok(()) -} - -fn migrate_stores_to_v5(db: &IdbDatabase) -> Result<(), DomException> { - // Create a new store for outgoing secret requests - let object_store = db.create_object_store(keys::GOSSIP_REQUESTS)?; - - let mut params = IdbIndexParameters::new(); - params.unique(false); - object_store.create_index_with_params( - keys::GOSSIP_REQUESTS_UNSENT_INDEX, - &IdbKeyPath::str("unsent"), - ¶ms, - )?; - - let mut params = IdbIndexParameters::new(); - params.unique(true); - object_store.create_index_with_params( - keys::GOSSIP_REQUESTS_BY_INFO_INDEX, - &IdbKeyPath::str("info"), - ¶ms, - )?; - - if db.object_store_names().any(|n| n == "outgoing_secret_requests") { - // Delete the old store names. We just delete any existing requests. - db.delete_object_store("outgoing_secret_requests")?; - db.delete_object_store("unsent_secret_requests")?; - db.delete_object_store("secret_requests_by_info")?; - } - - Ok(()) -} - async fn do_schema_upgrade(name: &str, version: u32, f: F) -> Result<(), DomException> where F: Fn(&IdbDatabase, u32) -> Result<(), JsValue> + 'static, @@ -235,10 +127,11 @@ mod tests { use tracing_subscriber::util::SubscriberInitExt; use web_sys::console; - use super::v7::InboundGroupSessionIndexedDbObject2; + use super::{v0_to_v5, v7::InboundGroupSessionIndexedDbObject2}; use crate::{ crypto_store::{ - indexeddb_serializer::MaybeEncrypted, migrations::*, InboundGroupSessionIndexedDbObject, + indexeddb_serializer::MaybeEncrypted, keys, migrations::*, + InboundGroupSessionIndexedDbObject, }, IndexeddbCryptoStore, }; @@ -626,16 +519,7 @@ mod tests { } async fn create_v5_db(name: &str) -> std::result::Result { - let mut db_req: OpenDbRequest = IdbDatabase::open_u32(name, 5)?; - db_req.set_on_upgrade_needed(Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> { - let db = evt.db(); - migrate_stores_to_v1(db)?; - migrate_stores_to_v2(db)?; - migrate_stores_to_v3(db)?; - migrate_stores_to_v4(db)?; - migrate_stores_to_v5(db)?; - Ok(()) - })); - db_req.await + v0_to_v5::migrate_schema_up_to_v5(name).await?; + IdbDatabase::open_u32(name, 5)?.await } } diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store/migrations/v0_to_v5.rs b/crates/matrix-sdk-indexeddb/src/crypto_store/migrations/v0_to_v5.rs new file mode 100644 index 000000000..8ef0e364b --- /dev/null +++ b/crates/matrix-sdk-indexeddb/src/crypto_store/migrations/v0_to_v5.rs @@ -0,0 +1,131 @@ +// Copyright 2024 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use indexed_db_futures::{IdbDatabase, IdbKeyPath}; +use web_sys::{DomException, IdbIndexParameters}; + +use crate::crypto_store::{ + keys, + migrations::{do_schema_upgrade, old_keys}, + Result, +}; + +pub(crate) async fn migrate_schema_up_to_v5(name: &str) -> Result<(), DomException> { + do_schema_upgrade(name, 5, |db, old_version| { + // An old_version of 1 could either mean actually the first version of the + // schema, or a completely empty schema that has been created with a + // call to `IdbDatabase::open` with no explicit "version". So, to determine + // if we need to create the V1 stores, we actually check if the schema is empty. + if db.object_store_names().next().is_none() { + migrate_stores_to_v1(db)?; + } + + if old_version < 2 { + migrate_stores_to_v2(db)?; + } + + if old_version < 3 { + migrate_stores_to_v3(db)?; + } + + if old_version < 4 { + migrate_stores_to_v4(db)?; + } + + if old_version < 5 { + migrate_stores_to_v5(db)?; + } + + Ok(()) + }) + .await +} + +fn migrate_stores_to_v1(db: &IdbDatabase) -> Result<(), DomException> { + db.create_object_store(keys::CORE)?; + db.create_object_store(keys::SESSION)?; + + db.create_object_store(old_keys::INBOUND_GROUP_SESSIONS_V1)?; + db.create_object_store(keys::OUTBOUND_GROUP_SESSIONS)?; + db.create_object_store(keys::TRACKED_USERS)?; + db.create_object_store(keys::OLM_HASHES)?; + db.create_object_store(keys::DEVICES)?; + + db.create_object_store(keys::IDENTITIES)?; + db.create_object_store(keys::BACKUP_KEYS)?; + + Ok(()) +} + +fn migrate_stores_to_v2(db: &IdbDatabase) -> Result<(), DomException> { + // We changed how we store inbound group sessions, the key used to + // be a tuple of `(room_id, sender_key, session_id)` now it's a + // tuple of `(room_id, session_id)` + // + // Let's just drop the whole object store. + db.delete_object_store(old_keys::INBOUND_GROUP_SESSIONS_V1)?; + db.create_object_store(old_keys::INBOUND_GROUP_SESSIONS_V1)?; + + db.create_object_store(keys::ROOM_SETTINGS)?; + + Ok(()) +} + +fn migrate_stores_to_v3(db: &IdbDatabase) -> Result<(), DomException> { + // We changed the way we store outbound session. + // ShareInfo changed from a struct to an enum with struct variant. + // Let's just discard the existing outbounds + db.delete_object_store(keys::OUTBOUND_GROUP_SESSIONS)?; + db.create_object_store(keys::OUTBOUND_GROUP_SESSIONS)?; + + // Support for MSC2399 withheld codes + db.create_object_store(keys::DIRECT_WITHHELD_INFO)?; + + Ok(()) +} + +fn migrate_stores_to_v4(db: &IdbDatabase) -> Result<(), DomException> { + db.create_object_store(keys::SECRETS_INBOX)?; + Ok(()) +} + +fn migrate_stores_to_v5(db: &IdbDatabase) -> Result<(), DomException> { + // Create a new store for outgoing secret requests + let object_store = db.create_object_store(keys::GOSSIP_REQUESTS)?; + + let mut params = IdbIndexParameters::new(); + params.unique(false); + object_store.create_index_with_params( + keys::GOSSIP_REQUESTS_UNSENT_INDEX, + &IdbKeyPath::str("unsent"), + ¶ms, + )?; + + let mut params = IdbIndexParameters::new(); + params.unique(true); + object_store.create_index_with_params( + keys::GOSSIP_REQUESTS_BY_INFO_INDEX, + &IdbKeyPath::str("info"), + ¶ms, + )?; + + if db.object_store_names().any(|n| n == "outgoing_secret_requests") { + // Delete the old store names. We just delete any existing requests. + db.delete_object_store("outgoing_secret_requests")?; + db.delete_object_store("unsent_secret_requests")?; + db.delete_object_store("secret_requests_by_info")?; + } + + Ok(()) +}