From d8eca1d0b30066c7c6bfb203af1383fb61ae37aa Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 5 Nov 2025 14:12:49 +0100 Subject: [PATCH 1/7] feat: add js impl of session repository --- crates/bitwarden-ipc/src/traits/mod.rs | 4 +- crates/bitwarden-ipc/src/wasm/ipc_client.rs | 22 ++-- crates/bitwarden-ipc/src/wasm/mod.rs | 2 + .../src/wasm/session_repository.rs | 101 ++++++++++++++++++ 4 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 crates/bitwarden-ipc/src/wasm/session_repository.rs diff --git a/crates/bitwarden-ipc/src/traits/mod.rs b/crates/bitwarden-ipc/src/traits/mod.rs index e5553d921..938b966a2 100644 --- a/crates/bitwarden-ipc/src/traits/mod.rs +++ b/crates/bitwarden-ipc/src/traits/mod.rs @@ -6,4 +6,6 @@ mod session_repository; pub use communication_backend::tests; pub use communication_backend::{CommunicationBackend, CommunicationBackendReceiver}; pub use crypto_provider::{CryptoProvider, NoEncryptionCryptoProvider}; -pub use session_repository::{InMemorySessionRepository, SessionRepository}; +#[cfg(test)] +pub use session_repository::InMemorySessionRepository; +pub use session_repository::SessionRepository; diff --git a/crates/bitwarden-ipc/src/wasm/ipc_client.rs b/crates/bitwarden-ipc/src/wasm/ipc_client.rs index 141f5989e..ab8020390 100644 --- a/crates/bitwarden-ipc/src/wasm/ipc_client.rs +++ b/crates/bitwarden-ipc/src/wasm/ipc_client.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; use bitwarden_threading::cancellation_token::wasm::{AbortSignal, AbortSignalExt}; use wasm_bindgen::prelude::*; @@ -8,7 +8,8 @@ use crate::{ IpcClient, ipc_client::{IpcClientSubscription, ReceiveError, SubscribeError}, message::{IncomingMessage, OutgoingMessage}, - traits::{InMemorySessionRepository, NoEncryptionCryptoProvider}, + traits::NoEncryptionCryptoProvider, + wasm::{JsSessionRepository, RawJsSessionRepository}, }; /// JavaScript wrapper around the IPC client. For more information, see the @@ -20,14 +21,8 @@ pub struct JsIpcClient { /// that interact with the IPC client, e.g. to register RPC handlers, trigger RPC requests, /// send typed messages, etc. For examples see /// [wasm::ipc_register_discover_handler](crate::wasm::ipc_register_discover_handler). - pub client: Arc< - IpcClient< - NoEncryptionCryptoProvider, - JsCommunicationBackend, - // TODO: Change session provider to a JS-implemented one - InMemorySessionRepository<()>, - >, - >, + pub client: + Arc>, } /// JavaScript wrapper around the IPC client subscription. For more information, see the @@ -53,12 +48,15 @@ impl JsIpcClientSubscription { impl JsIpcClient { #[allow(missing_docs)] #[wasm_bindgen(constructor)] - pub fn new(communication_provider: &JsCommunicationBackend) -> JsIpcClient { + pub fn new( + communication_provider: &JsCommunicationBackend, + session_repository: RawJsSessionRepository, + ) -> JsIpcClient { JsIpcClient { client: IpcClient::new( NoEncryptionCryptoProvider, communication_provider.clone(), - InMemorySessionRepository::new(HashMap::new()), + JsSessionRepository::new(session_repository), ), } } diff --git a/crates/bitwarden-ipc/src/wasm/mod.rs b/crates/bitwarden-ipc/src/wasm/mod.rs index ad63db4a8..33b8b92cf 100644 --- a/crates/bitwarden-ipc/src/wasm/mod.rs +++ b/crates/bitwarden-ipc/src/wasm/mod.rs @@ -2,8 +2,10 @@ mod communication_backend; mod discover; mod ipc_client; mod message; +mod session_repository; // Re-export types to make sure wasm_bindgen picks them up pub use communication_backend::*; pub use discover::*; pub use ipc_client::*; +pub use session_repository::*; diff --git a/crates/bitwarden-ipc/src/wasm/session_repository.rs b/crates/bitwarden-ipc/src/wasm/session_repository.rs new file mode 100644 index 000000000..5378b3937 --- /dev/null +++ b/crates/bitwarden-ipc/src/wasm/session_repository.rs @@ -0,0 +1,101 @@ +use bitwarden_threading::ThreadBoundRunner; +use serde::{Serialize, de::DeserializeOwned}; +use tsify::serde_wasm_bindgen; +use wasm_bindgen::prelude::*; + +use crate::{endpoint::Endpoint, traits::SessionRepository}; + +#[wasm_bindgen(typescript_custom_section)] +const TS_CUSTOM_TYPES: &'static str = r#" +export interface IpcSessionRepository { + get(endpoint: Endpoint): Promise; + save(endpoint: Endpoint, session: any): Promise; + remove(endpoint: Endpoint): Promise; +} +"#; + +#[wasm_bindgen] +extern "C" { + /// JavaScript interface for handling outgoing messages from the IPC framework. + #[wasm_bindgen(js_name = IpcSessionRepository, typescript_type = "IpcSessionRepository")] + pub type RawJsSessionRepository; + + /// Used by the IPC framework to get a session for a specific destination. + #[wasm_bindgen(catch, method, structural)] + pub async fn get(this: &RawJsSessionRepository, endpoint: Endpoint) + -> Result; + + /// Used by the IPC framework to save a session for a specific destination. + #[wasm_bindgen(catch, method, structural)] + pub async fn save( + this: &RawJsSessionRepository, + endpoint: Endpoint, + session: JsValue, + ) -> Result<(), JsValue>; + + /// Used by the IPC framework to remove a session for a specific destination. + #[wasm_bindgen(catch, method, structural)] + pub async fn remove(this: &RawJsSessionRepository, endpoint: Endpoint) -> Result<(), JsValue>; +} + +/// Thread safe JavaScript implementation of the `SessionRepository` trait for IPC sessions. +pub struct JsSessionRepository(ThreadBoundRunner); + +impl JsSessionRepository { + /// Creates a new `JsSessionRepository` instance wrapping the raw JavaScript repository. + pub fn new(repository: RawJsSessionRepository) -> Self { + Self(ThreadBoundRunner::new(repository)) + } +} + +impl Clone for JsSessionRepository { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl SessionRepository for JsSessionRepository +where + Session: Serialize + DeserializeOwned + Send + Sync + 'static, +{ + type GetError = String; + type SaveError = String; + type RemoveError = String; + + async fn get(&self, destination: Endpoint) -> Result, Self::GetError> { + self.0 + .run_in_thread(move |repo| async move { + let js_value = repo.get(destination).await.map_err(|e| format!("{e:?}"))?; + if js_value.is_undefined() || js_value.is_null() { + return Ok(None); + } + + Ok(Some( + serde_wasm_bindgen::from_value(js_value).map_err(|e| e.to_string())?, + )) + }) + .await + .map_err(|e| e.to_string())? + } + + async fn save(&self, destination: Endpoint, session: Session) -> Result<(), Self::SaveError> { + self.0 + .run_in_thread(move |repo| async move { + let js_value = serde_wasm_bindgen::to_value(&session).map_err(|e| e.to_string())?; + repo.save(destination, js_value) + .await + .map_err(|e| format!("{e:?}")) + }) + .await + .map_err(|e| e.to_string())? + } + + async fn remove(&self, destination: Endpoint) -> Result<(), Self::RemoveError> { + self.0 + .run_in_thread(move |repo| async move { + repo.remove(destination).await.map_err(|e| format!("{e:?}")) + }) + .await + .map_err(|e| e.to_string())? + } +} From 7f697c44913876be11cf3faded225d234af7ab7b Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 6 Nov 2025 09:16:12 +0100 Subject: [PATCH 2/7] feat: add ability to choose session repository --- crates/bitwarden-ipc/src/traits/mod.rs | 4 +- .../src/wasm/generic_session_repository.rs | 70 +++++++++++++++++++ crates/bitwarden-ipc/src/wasm/ipc_client.rs | 42 ++++++++--- ...repository.rs => js_session_repository.rs} | 0 crates/bitwarden-ipc/src/wasm/mod.rs | 5 +- 5 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 crates/bitwarden-ipc/src/wasm/generic_session_repository.rs rename crates/bitwarden-ipc/src/wasm/{session_repository.rs => js_session_repository.rs} (100%) diff --git a/crates/bitwarden-ipc/src/traits/mod.rs b/crates/bitwarden-ipc/src/traits/mod.rs index 938b966a2..e5553d921 100644 --- a/crates/bitwarden-ipc/src/traits/mod.rs +++ b/crates/bitwarden-ipc/src/traits/mod.rs @@ -6,6 +6,4 @@ mod session_repository; pub use communication_backend::tests; pub use communication_backend::{CommunicationBackend, CommunicationBackendReceiver}; pub use crypto_provider::{CryptoProvider, NoEncryptionCryptoProvider}; -#[cfg(test)] -pub use session_repository::InMemorySessionRepository; -pub use session_repository::SessionRepository; +pub use session_repository::{InMemorySessionRepository, SessionRepository}; diff --git a/crates/bitwarden-ipc/src/wasm/generic_session_repository.rs b/crates/bitwarden-ipc/src/wasm/generic_session_repository.rs new file mode 100644 index 000000000..15e63ae9b --- /dev/null +++ b/crates/bitwarden-ipc/src/wasm/generic_session_repository.rs @@ -0,0 +1,70 @@ +use std::sync::Arc; + +use crate::{ + traits::{InMemorySessionRepository, SessionRepository}, + wasm::JsSessionRepository, +}; + +// TODO: Change session type when implementing encryption +type Session = (); + +pub enum GenericSessionRepository { + InMemory(Arc>), + JsSessionRepository(Arc), +} + +impl SessionRepository for GenericSessionRepository { + type GetError = String; + type SaveError = String; + type RemoveError = String; + + async fn get( + &self, + endpoint: crate::endpoint::Endpoint, + ) -> Result, Self::GetError> { + match self { + GenericSessionRepository::InMemory(repo) => repo + .get(endpoint) + .await + .map_err(|_| "Unreachable".to_string()), + GenericSessionRepository::JsSessionRepository(repo) => { + >::get(repo.as_ref(), endpoint) + .await + } + } + } + + async fn save( + &self, + endpoint: crate::endpoint::Endpoint, + session: Session, + ) -> Result<(), Self::SaveError> { + match self { + GenericSessionRepository::InMemory(repo) => repo + .save(endpoint, session) + .await + .map_err(|_| "Unreachable".to_string()), + GenericSessionRepository::JsSessionRepository(repo) => { + >::save( + repo.as_ref(), + endpoint, + session, + ) + .await + } + } + } + + async fn remove(&self, endpoint: crate::endpoint::Endpoint) -> Result<(), Self::RemoveError> { + match self { + GenericSessionRepository::InMemory(repo) => repo + .remove(endpoint) + .await + .map_err(|_| "Unreachable".to_string()), + GenericSessionRepository::JsSessionRepository(repo) => { + >::remove(repo.as_ref(), endpoint) + .await + } + } + } +} diff --git a/crates/bitwarden-ipc/src/wasm/ipc_client.rs b/crates/bitwarden-ipc/src/wasm/ipc_client.rs index ab8020390..745af33d7 100644 --- a/crates/bitwarden-ipc/src/wasm/ipc_client.rs +++ b/crates/bitwarden-ipc/src/wasm/ipc_client.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; use bitwarden_threading::cancellation_token::wasm::{AbortSignal, AbortSignalExt}; use wasm_bindgen::prelude::*; @@ -8,8 +8,11 @@ use crate::{ IpcClient, ipc_client::{IpcClientSubscription, ReceiveError, SubscribeError}, message::{IncomingMessage, OutgoingMessage}, - traits::NoEncryptionCryptoProvider, - wasm::{JsSessionRepository, RawJsSessionRepository}, + traits::{InMemorySessionRepository, NoEncryptionCryptoProvider}, + wasm::{ + JsSessionRepository, RawJsSessionRepository, + generic_session_repository::GenericSessionRepository, + }, }; /// JavaScript wrapper around the IPC client. For more information, see the @@ -21,8 +24,10 @@ pub struct JsIpcClient { /// that interact with the IPC client, e.g. to register RPC handlers, trigger RPC requests, /// send typed messages, etc. For examples see /// [wasm::ipc_register_discover_handler](crate::wasm::ipc_register_discover_handler). - pub client: - Arc>, + pub client: Arc< + IpcClient, + >, + // Arc>, } /// JavaScript wrapper around the IPC client subscription. For more information, see the @@ -46,9 +51,26 @@ impl JsIpcClientSubscription { #[wasm_bindgen(js_class = IpcClient)] impl JsIpcClient { - #[allow(missing_docs)] - #[wasm_bindgen(constructor)] - pub fn new( + /// Create a new `IpcClient` instance with an in-memory session repository for saving + /// sessions within the SDK. + #[wasm_bindgen(js_name = newWithSdkInMemorySessions)] + pub fn new_with_sdk_in_memory_sessions( + communication_provider: &JsCommunicationBackend, + ) -> JsIpcClient { + JsIpcClient { + client: IpcClient::new( + NoEncryptionCryptoProvider, + communication_provider.clone(), + GenericSessionRepository::InMemory(Arc::new(InMemorySessionRepository::new( + HashMap::new(), + ))), + ), + } + } + /// Create a new `IpcClient` instance with a client-managed session repository for saving + /// sessions using State Provider. + #[wasm_bindgen(js_name = newWithClientManagedSessions)] + pub fn new_with_client_managed_sessions( communication_provider: &JsCommunicationBackend, session_repository: RawJsSessionRepository, ) -> JsIpcClient { @@ -56,7 +78,9 @@ impl JsIpcClient { client: IpcClient::new( NoEncryptionCryptoProvider, communication_provider.clone(), - JsSessionRepository::new(session_repository), + GenericSessionRepository::JsSessionRepository(Arc::new(JsSessionRepository::new( + session_repository, + ))), ), } } diff --git a/crates/bitwarden-ipc/src/wasm/session_repository.rs b/crates/bitwarden-ipc/src/wasm/js_session_repository.rs similarity index 100% rename from crates/bitwarden-ipc/src/wasm/session_repository.rs rename to crates/bitwarden-ipc/src/wasm/js_session_repository.rs diff --git a/crates/bitwarden-ipc/src/wasm/mod.rs b/crates/bitwarden-ipc/src/wasm/mod.rs index 33b8b92cf..c1b491e8c 100644 --- a/crates/bitwarden-ipc/src/wasm/mod.rs +++ b/crates/bitwarden-ipc/src/wasm/mod.rs @@ -1,11 +1,12 @@ mod communication_backend; mod discover; +mod generic_session_repository; mod ipc_client; +mod js_session_repository; mod message; -mod session_repository; // Re-export types to make sure wasm_bindgen picks them up pub use communication_backend::*; pub use discover::*; pub use ipc_client::*; -pub use session_repository::*; +pub use js_session_repository::*; From 1a19e9891cd6517bded8f05fff34cd7719513b78 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 6 Nov 2025 10:22:34 +0100 Subject: [PATCH 3/7] chore: clean up --- crates/bitwarden-ipc/src/wasm/ipc_client.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bitwarden-ipc/src/wasm/ipc_client.rs b/crates/bitwarden-ipc/src/wasm/ipc_client.rs index 745af33d7..2ba68c17b 100644 --- a/crates/bitwarden-ipc/src/wasm/ipc_client.rs +++ b/crates/bitwarden-ipc/src/wasm/ipc_client.rs @@ -27,7 +27,6 @@ pub struct JsIpcClient { pub client: Arc< IpcClient, >, - // Arc>, } /// JavaScript wrapper around the IPC client subscription. For more information, see the From f1669a0038b76bfcc72a09c39ba743424d71057a Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 6 Nov 2025 11:08:03 +0100 Subject: [PATCH 4/7] chore: apply claude error mapping suggestion --- crates/bitwarden-ipc/src/wasm/generic_session_repository.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-ipc/src/wasm/generic_session_repository.rs b/crates/bitwarden-ipc/src/wasm/generic_session_repository.rs index 15e63ae9b..870844543 100644 --- a/crates/bitwarden-ipc/src/wasm/generic_session_repository.rs +++ b/crates/bitwarden-ipc/src/wasm/generic_session_repository.rs @@ -26,7 +26,7 @@ impl SessionRepository for GenericSessionRepository { GenericSessionRepository::InMemory(repo) => repo .get(endpoint) .await - .map_err(|_| "Unreachable".to_string()), + .map_err(|_| unreachable!("InMemorySessionRepository::get never fails")), GenericSessionRepository::JsSessionRepository(repo) => { >::get(repo.as_ref(), endpoint) .await @@ -43,7 +43,7 @@ impl SessionRepository for GenericSessionRepository { GenericSessionRepository::InMemory(repo) => repo .save(endpoint, session) .await - .map_err(|_| "Unreachable".to_string()), + .map_err(|_| unreachable!("InMemorySessionRepository::save never fails")), GenericSessionRepository::JsSessionRepository(repo) => { >::save( repo.as_ref(), @@ -60,7 +60,7 @@ impl SessionRepository for GenericSessionRepository { GenericSessionRepository::InMemory(repo) => repo .remove(endpoint) .await - .map_err(|_| "Unreachable".to_string()), + .map_err(|_| unreachable!("InMemorySessionRepository::remove never fails")), GenericSessionRepository::JsSessionRepository(repo) => { >::remove(repo.as_ref(), endpoint) .await From 584ad45a2ef65d816c00fc677362914989a9ffed Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 6 Nov 2025 11:09:55 +0100 Subject: [PATCH 5/7] chore: apply claude consistent param naming suggerstion --- .../src/wasm/js_session_repository.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/bitwarden-ipc/src/wasm/js_session_repository.rs b/crates/bitwarden-ipc/src/wasm/js_session_repository.rs index 5378b3937..d69b6c136 100644 --- a/crates/bitwarden-ipc/src/wasm/js_session_repository.rs +++ b/crates/bitwarden-ipc/src/wasm/js_session_repository.rs @@ -20,12 +20,12 @@ extern "C" { #[wasm_bindgen(js_name = IpcSessionRepository, typescript_type = "IpcSessionRepository")] pub type RawJsSessionRepository; - /// Used by the IPC framework to get a session for a specific destination. + /// Used by the IPC framework to get a session for a specific endpoint. #[wasm_bindgen(catch, method, structural)] pub async fn get(this: &RawJsSessionRepository, endpoint: Endpoint) -> Result; - /// Used by the IPC framework to save a session for a specific destination. + /// Used by the IPC framework to save a session for a specific endpoint. #[wasm_bindgen(catch, method, structural)] pub async fn save( this: &RawJsSessionRepository, @@ -33,7 +33,7 @@ extern "C" { session: JsValue, ) -> Result<(), JsValue>; - /// Used by the IPC framework to remove a session for a specific destination. + /// Used by the IPC framework to remove a session for a specific endpoint. #[wasm_bindgen(catch, method, structural)] pub async fn remove(this: &RawJsSessionRepository, endpoint: Endpoint) -> Result<(), JsValue>; } @@ -62,10 +62,10 @@ where type SaveError = String; type RemoveError = String; - async fn get(&self, destination: Endpoint) -> Result, Self::GetError> { + async fn get(&self, endpoint: Endpoint) -> Result, Self::GetError> { self.0 .run_in_thread(move |repo| async move { - let js_value = repo.get(destination).await.map_err(|e| format!("{e:?}"))?; + let js_value = repo.get(endpoint).await.map_err(|e| format!("{e:?}"))?; if js_value.is_undefined() || js_value.is_null() { return Ok(None); } @@ -78,11 +78,11 @@ where .map_err(|e| e.to_string())? } - async fn save(&self, destination: Endpoint, session: Session) -> Result<(), Self::SaveError> { + async fn save(&self, endpoint: Endpoint, session: Session) -> Result<(), Self::SaveError> { self.0 .run_in_thread(move |repo| async move { let js_value = serde_wasm_bindgen::to_value(&session).map_err(|e| e.to_string())?; - repo.save(destination, js_value) + repo.save(endpoint, js_value) .await .map_err(|e| format!("{e:?}")) }) @@ -90,10 +90,10 @@ where .map_err(|e| e.to_string())? } - async fn remove(&self, destination: Endpoint) -> Result<(), Self::RemoveError> { + async fn remove(&self, endpoint: Endpoint) -> Result<(), Self::RemoveError> { self.0 .run_in_thread(move |repo| async move { - repo.remove(destination).await.map_err(|e| format!("{e:?}")) + repo.remove(endpoint).await.map_err(|e| format!("{e:?}")) }) .await .map_err(|e| e.to_string())? From a4828f4d8eda0b9472a0162d10a7de3f0f0be1fb Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 6 Nov 2025 11:19:11 +0100 Subject: [PATCH 6/7] chore: apply claude module doc suggestion --- .../bitwarden-ipc/src/wasm/generic_session_repository.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/bitwarden-ipc/src/wasm/generic_session_repository.rs b/crates/bitwarden-ipc/src/wasm/generic_session_repository.rs index 870844543..8959d2c2a 100644 --- a/crates/bitwarden-ipc/src/wasm/generic_session_repository.rs +++ b/crates/bitwarden-ipc/src/wasm/generic_session_repository.rs @@ -1,3 +1,11 @@ +//! Generic session repository abstraction allowing IPC clients to choose between +//! SDK-managed (in-memory) and client-managed (JavaScript-backed) session storage. +//! +//! This is a workaround because wasm-bindgen does not handle generics. +//! +//! Use SDK-managed when state providers might not make sense, for example if they +//! will use insecure IPC to save the data, defeating the whole point of a secure session. + use std::sync::Arc; use crate::{ From 3957ea5e0ba0e48a2813cc5172e76b46334c5130 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 6 Nov 2025 11:26:58 +0100 Subject: [PATCH 7/7] chore: revert claude suggestion --- crates/bitwarden-ipc/src/wasm/generic_session_repository.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bitwarden-ipc/src/wasm/generic_session_repository.rs b/crates/bitwarden-ipc/src/wasm/generic_session_repository.rs index 8959d2c2a..b6c46f1d0 100644 --- a/crates/bitwarden-ipc/src/wasm/generic_session_repository.rs +++ b/crates/bitwarden-ipc/src/wasm/generic_session_repository.rs @@ -34,7 +34,7 @@ impl SessionRepository for GenericSessionRepository { GenericSessionRepository::InMemory(repo) => repo .get(endpoint) .await - .map_err(|_| unreachable!("InMemorySessionRepository::get never fails")), + .map_err(|_| "InMemorySessionRepository::get should never fail".to_owned()), GenericSessionRepository::JsSessionRepository(repo) => { >::get(repo.as_ref(), endpoint) .await @@ -51,7 +51,7 @@ impl SessionRepository for GenericSessionRepository { GenericSessionRepository::InMemory(repo) => repo .save(endpoint, session) .await - .map_err(|_| unreachable!("InMemorySessionRepository::save never fails")), + .map_err(|_| "InMemorySessionRepository::save should never fail".to_owned()), GenericSessionRepository::JsSessionRepository(repo) => { >::save( repo.as_ref(), @@ -68,7 +68,7 @@ impl SessionRepository for GenericSessionRepository { GenericSessionRepository::InMemory(repo) => repo .remove(endpoint) .await - .map_err(|_| unreachable!("InMemorySessionRepository::remove never fails")), + .map_err(|_| "InMemorySessionRepository::remove should never fail".to_owned()), GenericSessionRepository::JsSessionRepository(repo) => { >::remove(repo.as_ref(), endpoint) .await