1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 13:57:10 +09:00

DROID-3397 Deep links | Fix | Misc. deep link fixes (#2145)

This commit is contained in:
Evgenii Kozlov 2025-03-10 15:48:20 +01:00 committed by GitHub
parent 3932c2fe87
commit ec9cdd1c60
Signed by: github
GPG key ID: B5690EEEBB952194
2 changed files with 126 additions and 52 deletions

View file

@ -18,21 +18,19 @@ const val DEEP_LINK_TO_OBJECT_BASE_URL = "https://object.any.coop"
* Regex pattern for matching
*/
const val DEEP_LINK_INVITE_REG_EXP = "invite.any.coop/([a-zA-Z0-9]+)#([a-zA-Z0-9]+)"
const val DEEP_LINK_TO_OBJECT_REG_EXP = """object\.any\.coop/([a-zA-Z0-9?=&._-]+)"""
const val DEE_LINK_INVITE_CUSTOM_REG_EXP = "anytype://invite/\\?cid=([a-zA-Z0-9]+)&key=([a-zA-Z0-9]+)"
const val MAIN_PATH = "main"
const val OBJECT_PATH = "object"
const val IMPORT_PATH = "import"
const val INVITE_PATH = "invite"
const val MEMBERSHIP_PATH = "membership"
const val TYPE_PARAM = "type"
const val OBJECT_ID_PARAM = "objectId"
const val SPACE_ID_PARAM = "spaceId"
const val CONTENT_ID_PARAM = "cid"
const val INVITE_ID_PARAM = "inviteID"
const val ENCRYPTION_KEY_PARAM = "key"
const val SOURCE_PARAM = "source"
const val TYPE_VALUE_EXPERIENCE = "experience"
const val TIER_ID_PARAM = "tier"
@ -42,60 +40,78 @@ const val IMPORT_EXPERIENCE_DEEPLINK = "$DEEP_LINK_PATTERN$MAIN_PATH/$IMPORT_PAT
object DefaultDeepLinkResolver : DeepLinkResolver {
private val defaultInviteRegex = Regex(DEEP_LINK_INVITE_REG_EXP)
private val defaultLinkToObjectRegex = Regex(DEEP_LINK_TO_OBJECT_REG_EXP)
override fun resolve(
deeplink: String
): DeepLinkResolver.Action = when {
deeplink.contains(IMPORT_EXPERIENCE_DEEPLINK) -> {
try {
val type = Uri.parse(deeplink).getQueryParameter(TYPE_PARAM)
val source = Uri.parse(deeplink).getQueryParameter(SOURCE_PARAM)
DeepLinkResolver.Action.Import.Experience(
type = type.orEmpty(),
source = source.orEmpty()
)
} catch (e: Exception) {
DeepLinkResolver.Action.Unknown
}
override fun resolve(deeplink: String): DeepLinkResolver.Action {
val uri = Uri.parse(deeplink)
return when {
deeplink.contains(IMPORT_EXPERIENCE_DEEPLINK) -> resolveImportExperience(uri)
defaultInviteRegex.containsMatchIn(deeplink) -> DeepLinkResolver.Action.Invite(deeplink)
defaultLinkToObjectRegex.containsMatchIn(deeplink) -> resolveDeepLinkToObject(uri)
deeplink.contains(OBJECT_PATH) -> resolveObjectPath(uri)
deeplink.contains(MEMBERSHIP_PATH) -> resolveMembershipPath(uri)
else -> DeepLinkResolver.Action.Unknown
}.also {
Timber.d("Resolving deep link: $deeplink")
}
deeplink.contains(INVITE_PATH) -> {
DeepLinkResolver.Action.Invite(deeplink)
}
private fun resolveImportExperience(uri: Uri): DeepLinkResolver.Action {
return try {
val type = uri.getQueryParameter(TYPE_PARAM).orEmpty()
val source = uri.getQueryParameter(SOURCE_PARAM).orEmpty()
DeepLinkResolver.Action.Import.Experience(type, source)
} catch (e: Exception) {
DeepLinkResolver.Action.Unknown
}
defaultInviteRegex.containsMatchIn(deeplink) -> {
DeepLinkResolver.Action.Invite(deeplink)
}
deeplink.contains(OBJECT_PATH) -> {
val uri = Uri.parse(deeplink)
val obj = uri.getQueryParameter(OBJECT_ID_PARAM)
val space = uri.getQueryParameter(SPACE_ID_PARAM)
if (!obj.isNullOrEmpty() && !space.isNullOrEmpty()) {
val cid = uri.getQueryParameter(CONTENT_ID_PARAM)
val key = uri.getQueryParameter(ENCRYPTION_KEY_PARAM)
DeepLinkResolver.Action.DeepLinkToObject(
obj = obj,
space = SpaceId(space),
invite = if (!cid.isNullOrEmpty() && !key.isNullOrEmpty()) {
DeepLinkResolver.Action.DeepLinkToObject.Invite(
cid = cid,
key = key
)
} else {
null
}
)
} else {
DeepLinkResolver.Action.Unknown
}
}
deeplink.contains(MEMBERSHIP_PATH) -> {
val uri = Uri.parse(deeplink)
DeepLinkResolver.Action.DeepLinkToMembership(
tierId = uri.getQueryParameter(TIER_ID_PARAM)
}
private fun resolveDeepLinkToObject(uri: Uri): DeepLinkResolver.Action {
val obj = uri.pathSegments.getOrNull(0) ?: return DeepLinkResolver.Action.Unknown
val space = uri.getQueryParameter(SPACE_ID_PARAM)?.takeIf { it.isNotEmpty() }
?: return DeepLinkResolver.Action.Unknown // Ensure spaceId is required
return DeepLinkResolver.Action.DeepLinkToObject(
obj = obj,
space = SpaceId(space),
invite = parseInvite(uri)
)
}
private fun resolveObjectPath(uri: Uri): DeepLinkResolver.Action {
val obj = uri.getQueryParameter(OBJECT_ID_PARAM)?.takeIf { it.isNotEmpty() }
val space = uri.getQueryParameter(SPACE_ID_PARAM)?.takeIf { it.isNotEmpty() }
?: return DeepLinkResolver.Action.Unknown // Ensure spaceId is required
return if (obj != null) {
DeepLinkResolver.Action.DeepLinkToObject(
obj = obj,
space = SpaceId(space),
invite = parseInvite(uri)
)
} else {
DeepLinkResolver.Action.Unknown
}
}
private fun resolveMembershipPath(uri: Uri): DeepLinkResolver.Action {
return DeepLinkResolver.Action.DeepLinkToMembership(
tierId = uri.getQueryParameter(TIER_ID_PARAM)
)
}
private fun parseInvite(uri: Uri): DeepLinkResolver.Action.DeepLinkToObject.Invite? {
val inviteId = uri.getQueryParameter(INVITE_ID_PARAM)?.takeIf { it.isNotEmpty() }
val encryption = uri.fragment?.takeIf { it.isNotEmpty() }
return if (inviteId != null && encryption != null) {
DeepLinkResolver.Action.DeepLinkToObject.Invite(
key = encryption,
cid = inviteId
)
} else {
null
}
else -> DeepLinkResolver.Action.Unknown
}.also {
Timber.d("Resolving deep link: $deeplink")
}
override fun createObjectDeepLink(obj: Id, space: SpaceId): Url {

View file

@ -1,12 +1,14 @@
package com.anytypeio.anytype.other
import android.os.Build
import androidx.compose.runtime.key
import com.anytypeio.anytype.core_models.primitives.SpaceId
import com.anytypeio.anytype.domain.misc.DeepLinkResolver
import com.anytypeio.anytype.test_utils.MockDataFactory
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
@ -51,6 +53,62 @@ class DefaultDeepLinkResolverTest {
)
}
@Test
fun `resolve https deep link to object`() {
// Given
val obj = MockDataFactory.randomUuid()
val space = MockDataFactory.randomUuid()
val deeplink = "https://object.any.coop/$obj?spaceId=$space"
// When
val result = deepLinkResolver.resolve(deeplink)
// Then
assertEquals(
DeepLinkResolver.Action.DeepLinkToObject(
space = SpaceId(space),
obj = obj
),
result
)
}
@Test
fun `resolve https deep link to object with invite`() {
// Given
val obj = MockDataFactory.randomUuid()
val space = MockDataFactory.randomUuid()
val cid = MockDataFactory.randomUuid()
val encryption = MockDataFactory.randomUuid()
val invite = "$cid#$encryption"
val deeplink = "https://object.any.coop/$obj?spaceId=$space&inviteID=$invite"
// When
val result = deepLinkResolver.resolve(deeplink)
// Then
assertEquals(
DeepLinkResolver.Action.DeepLinkToObject(
space = SpaceId(space),
obj = obj,
invite = DeepLinkResolver.Action.DeepLinkToObject.Invite(
cid = cid,
key = encryption
)
),
result
)
}
@Test
fun `resolve returns Invite with deeplink for invite deep links`() {
// Given