diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/Placeholder.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/Placeholder.kt index d29ccab..a58bebe 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/Placeholder.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/Placeholder.kt @@ -1,6 +1,5 @@ package com.artemchep.keyguard.common.service.placeholder -import arrow.core.Option import com.artemchep.keyguard.common.io.IO // See: @@ -9,6 +8,15 @@ interface Placeholder { operator fun get(key: String): IO? interface Factory { - + fun createOrNull( + scope: PlaceholderScope, + ): Placeholder? } } + +fun List.create( + scope: PlaceholderScope, +): List = this + .mapNotNull { placeholder -> + placeholder.createOrNull(scope) + } diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/PlaceholderScope.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/PlaceholderScope.kt index 8def516..0b3bb2a 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/PlaceholderScope.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/PlaceholderScope.kt @@ -4,6 +4,7 @@ import com.artemchep.keyguard.common.model.DSecret import kotlinx.datetime.Instant data class PlaceholderScope( - val cipher: DSecret, val now: Instant, + val cipher: DSecret, + val url: String? = null, ) diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/CipherPlaceholder.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/CipherPlaceholder.kt index b34b376..e65df2d 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/CipherPlaceholder.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/CipherPlaceholder.kt @@ -2,34 +2,49 @@ package com.artemchep.keyguard.common.service.placeholder.impl import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.io -import com.artemchep.keyguard.common.io.map -import com.artemchep.keyguard.common.io.toIO +import com.artemchep.keyguard.common.io.ioEffect import com.artemchep.keyguard.common.model.DSecret import com.artemchep.keyguard.common.service.placeholder.Placeholder -import com.artemchep.keyguard.common.usecase.GetTotpCode +import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope +import com.artemchep.keyguard.common.service.totp.TotpService +import kotlinx.coroutines.Dispatchers +import kotlinx.datetime.Instant +import org.kodein.di.DirectDI +import org.kodein.di.instance class CipherPlaceholder( -// private val getTotpCode: GetTotpCode, + private val totpService: TotpService, private val cipher: DSecret, + private val now: Instant, ) : Placeholder { override fun get( key: String, ): IO? = when { key.equals("uuid", ignoreCase = true) -> cipher.service.remote?.id.let(::io) + key.equals("title", ignoreCase = true) -> cipher.name.let(::io) + key.equals("username", ignoreCase = true) -> cipher.login?.username.let(::io) + key.equals("password", ignoreCase = true) -> cipher.login?.password.let(::io) -// key.equals("otp", ignoreCase = true) -> run { -// val token = cipher.login?.totp?.token -// ?: return@run null.let(::io) -// getTotpCode(token) -// .toIO() -// .map { code -> code.code } -// } + + key.equals("otp", ignoreCase = true) -> run { + val token = cipher.login?.totp?.token + ?: return@run null.let(::io) + ioEffect(Dispatchers.Default) { + totpService + .generate( + token = token, + timestamp = now, + ) + .code + } + } + key.equals("notes", ignoreCase = true) -> cipher.notes.let(::io) // extras @@ -39,7 +54,21 @@ class CipherPlaceholder( else -> null } - class Factory { + class Factory( + private val totpService: TotpService, + ) : Placeholder.Factory { + constructor( + directDI: DirectDI, + ) : this( + totpService = directDI.instance(), + ) + override fun createOrNull( + scope: PlaceholderScope, + ) = CipherPlaceholder( + totpService = totpService, + cipher = scope.cipher, + now = scope.now, + ) } } diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/CommentPlaceholder.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/CommentPlaceholder.kt index 486a810..3117552 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/CommentPlaceholder.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/CommentPlaceholder.kt @@ -3,6 +3,8 @@ package com.artemchep.keyguard.common.service.placeholder.impl import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.io import com.artemchep.keyguard.common.service.placeholder.Placeholder +import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope +import org.kodein.di.DirectDI class CommentPlaceholder( ) : Placeholder { @@ -15,4 +17,16 @@ class CommentPlaceholder( // unknown else -> null } + + class Factory( + ) : Placeholder.Factory { + constructor( + directDI: DirectDI, + ) : this( + ) + + override fun createOrNull( + scope: PlaceholderScope, + ) = CommentPlaceholder() + } } diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/CustomPlaceholder.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/CustomPlaceholder.kt index c4a3cfa..9bcf67b 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/CustomPlaceholder.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/CustomPlaceholder.kt @@ -4,6 +4,8 @@ import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.ioEffect import com.artemchep.keyguard.common.model.DSecret import com.artemchep.keyguard.common.service.placeholder.Placeholder +import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope +import org.kodein.di.DirectDI class CustomPlaceholder( private val cipher: DSecret, @@ -26,4 +28,18 @@ class CustomPlaceholder( // unknown else -> null } + + class Factory( + ) : Placeholder.Factory { + constructor( + directDI: DirectDI, + ) : this( + ) + + override fun createOrNull( + scope: PlaceholderScope, + ) = CustomPlaceholder( + cipher = scope.cipher, + ) + } } diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/DateTimePlaceholder.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/DateTimePlaceholder.kt index fed93a9..65e0ce8 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/DateTimePlaceholder.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/DateTimePlaceholder.kt @@ -1,21 +1,19 @@ package com.artemchep.keyguard.common.service.placeholder.impl -import arrow.core.None -import arrow.core.Option -import arrow.core.some import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.io import com.artemchep.keyguard.common.service.placeholder.Placeholder -import kotlinx.datetime.Clock +import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope +import kotlinx.datetime.Instant import kotlinx.datetime.TimeZone import kotlinx.datetime.toJavaLocalDateTime import kotlinx.datetime.toLocalDateTime +import org.kodein.di.DirectDI import java.time.format.DateTimeFormatter class DateTimePlaceholder( + private val now: Instant, ) : Placeholder { - private val now = Clock.System.now() - private val localDateTime by lazy { val tz = TimeZone.currentSystemDefault() now.toLocalDateTime(tz) @@ -105,4 +103,18 @@ class DateTimePlaceholder( // unknown else -> null } + + class Factory( + ) : Placeholder.Factory { + constructor( + directDI: DirectDI, + ) : this( + ) + + override fun createOrNull( + scope: PlaceholderScope, + ) = DateTimePlaceholder( + now = scope.now, + ) + } } diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/EnvironmentPlaceholder.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/EnvironmentPlaceholder.kt index 78a60e5..b0c0b0d 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/EnvironmentPlaceholder.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/EnvironmentPlaceholder.kt @@ -3,6 +3,8 @@ package com.artemchep.keyguard.common.service.placeholder.impl import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.ioEffect import com.artemchep.keyguard.common.service.placeholder.Placeholder +import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope +import org.kodein.di.DirectDI class EnvironmentPlaceholder( ) : Placeholder { @@ -22,4 +24,16 @@ class EnvironmentPlaceholder( null } } + + class Factory( + ) : Placeholder.Factory { + constructor( + directDI: DirectDI, + ) : this( + ) + + override fun createOrNull( + scope: PlaceholderScope, + ) = EnvironmentPlaceholder() + } } diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/TextReplaceRegexPlaceholder.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/TextReplaceRegexPlaceholder.kt index 5776b0e..22ade36 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/TextReplaceRegexPlaceholder.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/TextReplaceRegexPlaceholder.kt @@ -3,7 +3,11 @@ package com.artemchep.keyguard.common.service.placeholder.impl import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.ioEffect import com.artemchep.keyguard.common.service.placeholder.Placeholder +import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope import com.artemchep.keyguard.common.service.placeholder.util.Parser +import com.artemchep.keyguard.common.usecase.GetTotpCode +import org.kodein.di.DirectDI +import org.kodein.di.instance class TextReplaceRegexPlaceholder( ) : Placeholder { @@ -31,4 +35,16 @@ class TextReplaceRegexPlaceholder( rg.replace(params.value, replacement) } } + + class Factory( + ) : Placeholder.Factory { + constructor( + directDI: DirectDI, + ) : this( + ) + + override fun createOrNull( + scope: PlaceholderScope, + ) = TextReplaceRegexPlaceholder() + } } diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/TextTransformPlaceholder.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/TextTransformPlaceholder.kt index 06720a5..3fa3017 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/TextTransformPlaceholder.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/TextTransformPlaceholder.kt @@ -3,8 +3,10 @@ package com.artemchep.keyguard.common.service.placeholder.impl import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.io import com.artemchep.keyguard.common.service.placeholder.Placeholder +import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope import com.artemchep.keyguard.common.service.placeholder.util.Parser import io.ktor.util.* +import org.kodein.di.DirectDI import java.net.URLDecoder import java.net.URLEncoder import java.util.Locale @@ -85,4 +87,16 @@ class TextTransformPlaceholder( ): String { return URLDecoder.decode(value, "UTF-8") } + + class Factory( + ) : Placeholder.Factory { + constructor( + directDI: DirectDI, + ) : this( + ) + + override fun createOrNull( + scope: PlaceholderScope, + ) = TextTransformPlaceholder() + } } diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/UrlPlaceholder.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/UrlPlaceholder.kt index aa1a35f..126e2e1 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/UrlPlaceholder.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/common/service/placeholder/impl/UrlPlaceholder.kt @@ -3,7 +3,9 @@ package com.artemchep.keyguard.common.service.placeholder.impl import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.io import com.artemchep.keyguard.common.service.placeholder.Placeholder +import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope import io.ktor.http.* +import org.kodein.di.DirectDI class UrlPlaceholder( private val url: String, @@ -90,7 +92,15 @@ class UrlPlaceholder( else -> null } - class Factory { + class Factory( + ) : Placeholder.Factory { + constructor( + directDI: DirectDI, + ) : this( + ) + override fun createOrNull( + scope: PlaceholderScope, + ) = scope.url?.let(::UrlPlaceholder) } } diff --git a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/screen/VaultViewStateProducer.kt b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/screen/VaultViewStateProducer.kt index 2faa8dc..ea78431 100644 --- a/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/screen/VaultViewStateProducer.kt +++ b/common/src/commonMain/kotlin/com/artemchep/keyguard/feature/home/vault/screen/VaultViewStateProducer.kt @@ -80,6 +80,9 @@ import com.artemchep.keyguard.common.service.execute.ExecuteCommand import com.artemchep.keyguard.common.service.extract.LinkInfoExtractor import com.artemchep.keyguard.common.service.extract.LinkInfoRegistry import com.artemchep.keyguard.common.service.passkey.PassKeyService +import com.artemchep.keyguard.common.service.placeholder.Placeholder +import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope +import com.artemchep.keyguard.common.service.placeholder.create import com.artemchep.keyguard.common.service.placeholder.impl.CipherPlaceholder import com.artemchep.keyguard.common.service.placeholder.impl.CommentPlaceholder import com.artemchep.keyguard.common.service.placeholder.impl.CustomPlaceholder @@ -266,6 +269,7 @@ fun vaultViewScreenState( addCipherOpenedHistory = instance(), getJustDeleteMeByUrl = instance(), windowCoroutineScope = instance(), + placeholderFactories = allInstances(), linkInfoExtractors = allInstances(), mode = mode, contentColor = contentColor, @@ -340,6 +344,7 @@ fun vaultViewScreenState( addCipherOpenedHistory: AddCipherOpenedHistory, getJustDeleteMeByUrl: GetJustDeleteMeByUrl, windowCoroutineScope: WindowCoroutineScope, + placeholderFactories: List, linkInfoExtractors: List>, itemId: String, accountId: String, @@ -490,15 +495,8 @@ fun vaultViewScreenState( val canEdit = canAddSecret && secretOrNull.canEdit() && !hasCanNotWriteCiphers val canDelete = canAddSecret && secretOrNull.canDelete() && !hasCanNotWriteCiphers - val placeholders = listOf( - CipherPlaceholder(secretOrNull), - CommentPlaceholder(), - CustomPlaceholder(secretOrNull), - DateTimePlaceholder(), - TextReplaceRegexPlaceholder(), - TextTransformPlaceholder(), - EnvironmentPlaceholder(), - ) + val now = Clock.System.now() + val extractors = LinkInfoRegistry(linkInfoExtractors) val cipherUris = secretOrNull .uris @@ -516,16 +514,28 @@ fun vaultViewScreenState( } else -> { + val newUriPlaceholders = placeholderFactories + .create( + scope = PlaceholderScope( + now = now, + cipher = secretOrNull, + ), + ) val newUriString = kotlin.runCatching { - uri.uri.placeholderFormat(placeholders) + uri.uri.placeholderFormat(newUriPlaceholders) }.getOrElse { uri.uri } val newUri = uri.copy(uri = newUriString) // Process URL overrides val urlOverridePlaceholders by lazy { - placeholders + listOf( - UrlPlaceholder(newUriString), - ) + placeholderFactories + .create( + scope = PlaceholderScope( + now = now, + cipher = secretOrNull, + url = newUriString, + ), + ) } val urlOverrideList = urlOverrides .filter { override -> diff --git a/common/src/jvmMain/kotlin/com/artemchep/keyguard/di/GlobalModuleJvm.kt b/common/src/jvmMain/kotlin/com/artemchep/keyguard/di/GlobalModuleJvm.kt index 0e1eecc..ec86167 100644 --- a/common/src/jvmMain/kotlin/com/artemchep/keyguard/di/GlobalModuleJvm.kt +++ b/common/src/jvmMain/kotlin/com/artemchep/keyguard/di/GlobalModuleJvm.kt @@ -28,6 +28,14 @@ import com.artemchep.keyguard.common.service.license.impl.LicenseServiceImpl import com.artemchep.keyguard.common.service.logging.LogRepository import com.artemchep.keyguard.common.service.passkey.PassKeyService import com.artemchep.keyguard.common.service.passkey.impl.PassKeyServiceImpl +import com.artemchep.keyguard.common.service.placeholder.impl.CipherPlaceholder +import com.artemchep.keyguard.common.service.placeholder.impl.CommentPlaceholder +import com.artemchep.keyguard.common.service.placeholder.impl.CustomPlaceholder +import com.artemchep.keyguard.common.service.placeholder.impl.DateTimePlaceholder +import com.artemchep.keyguard.common.service.placeholder.impl.EnvironmentPlaceholder +import com.artemchep.keyguard.common.service.placeholder.impl.TextReplaceRegexPlaceholder +import com.artemchep.keyguard.common.service.placeholder.impl.TextTransformPlaceholder +import com.artemchep.keyguard.common.service.placeholder.impl.UrlPlaceholder import com.artemchep.keyguard.common.service.relays.di.emailRelayDiModule import com.artemchep.keyguard.common.service.review.ReviewLog import com.artemchep.keyguard.common.service.review.impl.ReviewLogImpl @@ -952,6 +960,46 @@ fun globalModuleJvm() = DI.Module( ) } import(emailRelayDiModule()) + bindSingleton { + CipherPlaceholder.Factory( + directDI = this, + ) + } + bindSingleton { + CommentPlaceholder.Factory( + directDI = this, + ) + } + bindSingleton { + CustomPlaceholder.Factory( + directDI = this, + ) + } + bindSingleton { + DateTimePlaceholder.Factory( + directDI = this, + ) + } + bindSingleton { + EnvironmentPlaceholder.Factory( + directDI = this, + ) + } + bindSingleton { + TextReplaceRegexPlaceholder.Factory( + directDI = this, + ) + } + bindSingleton { + TextTransformPlaceholder.Factory( + directDI = this, + ) + } + bindSingleton { + UrlPlaceholder.Factory( + directDI = this, + ) + } bindSingleton { DeeplinkServiceImpl( directDI = this,