Skip to content

Commit ff6c8df

Browse files
authored
Merge pull request #2709 from element-hq/feature/bma/permalinkParser
Parse permalink using `parseMatrixEntityFrom` from the SDK
2 parents 89d2f43 + 9ee36e9 commit ff6c8df

File tree

15 files changed

+92
-421
lines changed

15 files changed

+92
-421
lines changed

changelog.d/2709.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Parse permalink using parseMatrixEntityFrom from the SDK

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package io.element.android.features.messages.impl
1818

1919
import android.content.Context
20-
import android.net.Uri
2120
import androidx.compose.runtime.Composable
2221
import androidx.compose.runtime.CompositionLocalProvider
2322
import androidx.compose.ui.Modifier
@@ -108,13 +107,19 @@ class MessagesNode @AssistedInject constructor(
108107
context: Context,
109108
url: String,
110109
) {
111-
when (val permalink = permalinkParser.parse(Uri.parse(url))) {
110+
when (val permalink = permalinkParser.parse(url)) {
112111
is PermalinkData.UserLink -> {
113-
callback?.onUserDataClicked(UserId(permalink.userId))
112+
callback?.onUserDataClicked(permalink.userId)
114113
}
115114
is PermalinkData.RoomLink -> {
116115
// TODO Implement room link handling
117116
}
117+
is PermalinkData.EventIdAliasLink -> {
118+
// TODO Implement room and Event link handling
119+
}
120+
is PermalinkData.EventIdLink -> {
121+
// TODO Implement room and Event link handling
122+
}
118123
is PermalinkData.FallbackLink,
119124
is PermalinkData.RoomEmailInviteLink -> {
120125
context.openUrlInExternalApp(url)

features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerStateProvider.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package io.element.android.features.messages.impl.messagecomposer
1818

19-
import android.net.Uri
2019
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
2120
import io.element.android.features.messages.impl.mentions.MentionSuggestion
2221
import io.element.android.libraries.matrix.api.core.UserId
@@ -49,7 +48,6 @@ fun aMessageComposerState(
4948
richTextEditorState = richTextEditorState,
5049
permalinkParser = object : PermalinkParser {
5150
override fun parse(uriString: String): PermalinkData = TODO()
52-
override fun parse(uri: Uri): PermalinkData = TODO()
5351
},
5452
isFullScreen = isFullScreen,
5553
mode = mode,

libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ package io.element.android.libraries.matrix.api.permalink
1818

1919
import android.net.Uri
2020
import androidx.compose.runtime.Immutable
21+
import io.element.android.libraries.matrix.api.core.EventId
2122
import io.element.android.libraries.matrix.api.core.RoomId
23+
import io.element.android.libraries.matrix.api.core.UserId
2224
import kotlinx.collections.immutable.ImmutableList
2325

2426
/**
@@ -27,28 +29,44 @@ import kotlinx.collections.immutable.ImmutableList
2729
*/
2830
@Immutable
2931
sealed interface PermalinkData {
30-
data class RoomLink(
31-
val roomIdOrAlias: String,
32-
val isRoomAlias: Boolean,
33-
val eventId: String?,
32+
sealed interface RoomLink : PermalinkData {
3433
val viaParameters: ImmutableList<String>
35-
) : PermalinkData {
36-
fun getRoomId(): RoomId? {
37-
return roomIdOrAlias.takeIf { !isRoomAlias }?.let(::RoomId)
38-
}
34+
}
35+
36+
data class RoomIdLink(
37+
val roomId: RoomId,
38+
override val viaParameters: ImmutableList<String>
39+
) : RoomLink
40+
41+
data class RoomAliasLink(
42+
val roomAlias: String,
43+
override val viaParameters: ImmutableList<String>
44+
) : RoomLink
3945

40-
fun getRoomAlias(): String? {
41-
return roomIdOrAlias.takeIf { isRoomAlias }
42-
}
46+
sealed interface EventLink : PermalinkData {
47+
val eventId: EventId
48+
val viaParameters: ImmutableList<String>
4349
}
4450

51+
data class EventIdLink(
52+
val roomId: RoomId,
53+
override val eventId: EventId,
54+
override val viaParameters: ImmutableList<String>
55+
) : EventLink
56+
57+
data class EventIdAliasLink(
58+
val roomAlias: String,
59+
override val eventId: EventId,
60+
override val viaParameters: ImmutableList<String>
61+
) : EventLink
62+
4563
/*
4664
* &room_name=Team2
4765
* &room_avatar_url=mxc:
4866
* &inviter_name=bob
4967
*/
5068
data class RoomEmailInviteLink(
51-
val roomId: String,
69+
val roomId: RoomId,
5270
val email: String,
5371
val signUrl: String,
5472
val roomName: String?,
@@ -60,7 +78,7 @@ sealed interface PermalinkData {
6078
val roomType: String?
6179
) : PermalinkData
6280

63-
data class UserLink(val userId: String) : PermalinkData
81+
data class UserLink(val userId: UserId) : PermalinkData
6482

6583
data class FallbackLink(val uri: Uri, val isLegacyGroupLink: Boolean = false) : PermalinkData
6684
}

libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package io.element.android.libraries.matrix.api.permalink
1818

19-
import android.net.Uri
20-
2119
/**
2220
* This class turns a uri to a [PermalinkData].
2321
* element-based domains (e.g. https://app.element.io/#/user/@chagai95:matrix.org) permalinks
@@ -27,12 +25,7 @@ import android.net.Uri
2725
interface PermalinkParser {
2826
/**
2927
* Turns a uri string to a [PermalinkData].
30-
*/
31-
fun parse(uriString: String): PermalinkData
32-
33-
/**
34-
* Turns a uri to a [PermalinkData].
3528
* https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md
3629
*/
37-
fun parse(uri: Uri): PermalinkData
30+
fun parse(uriString: String): PermalinkData
3831
}

libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/Mention.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,6 @@ import io.element.android.libraries.matrix.api.core.UserId
2222
sealed interface Mention {
2323
data class User(val userId: UserId) : Mention
2424
data object AtRoom : Mention
25-
data class Room(val roomId: RoomId?, val roomAlias: String?) : Mention
25+
data class Room(val roomId: RoomId) : Mention
26+
data class RoomAlias(val roomAlias: String?) : Mention
2627
}

libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkDataTest.kt

Lines changed: 0 additions & 47 deletions
This file was deleted.

libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/permalink/DefaultPermalinkParser.kt

Lines changed: 33 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,17 @@
1717
package io.element.android.libraries.matrix.impl.permalink
1818

1919
import android.net.Uri
20-
import android.net.UrlQuerySanitizer
2120
import com.squareup.anvil.annotations.ContributesBinding
2221
import io.element.android.libraries.di.AppScope
23-
import io.element.android.libraries.matrix.api.core.MatrixPatterns
22+
import io.element.android.libraries.matrix.api.core.EventId
23+
import io.element.android.libraries.matrix.api.core.RoomId
24+
import io.element.android.libraries.matrix.api.core.UserId
2425
import io.element.android.libraries.matrix.api.permalink.MatrixToConverter
2526
import io.element.android.libraries.matrix.api.permalink.PermalinkData
2627
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
2728
import kotlinx.collections.immutable.toImmutableList
28-
import timber.log.Timber
29-
import java.net.URLDecoder
29+
import org.matrix.rustcomponents.sdk.MatrixId
30+
import org.matrix.rustcomponents.sdk.parseMatrixEntityFrom
3031
import javax.inject.Inject
3132

3233
/**
@@ -41,118 +42,45 @@ class DefaultPermalinkParser @Inject constructor(
4142
) : PermalinkParser {
4243
/**
4344
* Turns a uri string to a [PermalinkData].
45+
* https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md
4446
*/
4547
override fun parse(uriString: String): PermalinkData {
4648
val uri = Uri.parse(uriString)
47-
return parse(uri)
48-
}
49-
50-
/**
51-
* Turns a uri to a [PermalinkData].
52-
* https://github.com/matrix-org/matrix-doc/blob/master/proposals/1704-matrix.to-permalinks.md
53-
*/
54-
override fun parse(uri: Uri): PermalinkData {
5549
// the client or element-based domain permalinks (e.g. https://app.element.io/#/user/@chagai95:matrix.org) don't have the
5650
// mxid in the first param (like matrix.to does - https://matrix.to/#/@chagai95:matrix.org) but rather in the second after /user/ so /user/mxid
5751
// so convert URI to matrix.to to simplify parsing process
5852
val matrixToUri = matrixToConverter.convert(uri) ?: return PermalinkData.FallbackLink(uri)
5953

60-
// We can't use uri.fragment as it is decoding to early and it will break the parsing
61-
// of parameters that represents url (like signurl)
62-
val fragment = matrixToUri.toString().substringAfter("#") // uri.fragment
63-
if (fragment.isEmpty()) {
64-
return PermalinkData.FallbackLink(uri)
65-
}
66-
val safeFragment = fragment.substringBefore('?')
67-
val viaQueryParameters = fragment.getViaParameters()
68-
69-
// we are limiting to 2 params
70-
val params = safeFragment
71-
.split(MatrixPatterns.SEP_REGEX)
72-
.filter { it.isNotEmpty() }
73-
.take(2)
74-
75-
val decodedParams = params
76-
.map { URLDecoder.decode(it, "UTF-8") }
77-
78-
val identifier = params.getOrNull(0)
79-
val decodedIdentifier = decodedParams.getOrNull(0)
80-
val extraParameter = decodedParams.getOrNull(1)
81-
return when {
82-
identifier.isNullOrEmpty() || decodedIdentifier.isNullOrEmpty() -> PermalinkData.FallbackLink(uri)
83-
MatrixPatterns.isUserId(decodedIdentifier) -> PermalinkData.UserLink(userId = decodedIdentifier)
84-
MatrixPatterns.isRoomId(decodedIdentifier) -> {
85-
handleRoomIdCase(fragment, decodedIdentifier, matrixToUri, extraParameter, viaQueryParameters)
86-
}
87-
MatrixPatterns.isRoomAlias(decodedIdentifier) -> {
88-
PermalinkData.RoomLink(
89-
roomIdOrAlias = decodedIdentifier,
90-
isRoomAlias = true,
91-
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) },
92-
viaParameters = viaQueryParameters.toImmutableList()
54+
val result = runCatching {
55+
parseMatrixEntityFrom(matrixToUri.toString())
56+
}.getOrNull()
57+
return if (result == null) {
58+
PermalinkData.FallbackLink(uri)
59+
} else {
60+
val viaParameters = result.via.toImmutableList()
61+
when (val id = result.id) {
62+
is MatrixId.Room -> PermalinkData.RoomIdLink(
63+
roomId = RoomId(id.id),
64+
viaParameters = viaParameters,
9365
)
94-
}
95-
else -> PermalinkData.FallbackLink(uri, MatrixPatterns.isGroupId(identifier))
96-
}
97-
}
98-
99-
private fun handleRoomIdCase(fragment: String, identifier: String, uri: Uri, extraParameter: String?, viaQueryParameters: List<String>): PermalinkData {
100-
// Can't rely on built in parsing because it's messing around the signurl
101-
val paramList = safeExtractParams(fragment)
102-
val signUrl = paramList.firstOrNull { it.first == "signurl" }?.second
103-
val email = paramList.firstOrNull { it.first == "email" }?.second
104-
return if (signUrl.isNullOrEmpty().not() && email.isNullOrEmpty().not()) {
105-
try {
106-
val signValidUri = Uri.parse(signUrl)
107-
val identityServerHost = signValidUri.authority ?: throw IllegalArgumentException("missing `authority`")
108-
val token = signValidUri.getQueryParameter("token") ?: throw IllegalArgumentException("missing `token`")
109-
val privateKey = signValidUri.getQueryParameter("private_key") ?: throw IllegalArgumentException("missing `private_key`")
110-
PermalinkData.RoomEmailInviteLink(
111-
roomId = identifier,
112-
email = email!!,
113-
signUrl = signUrl!!,
114-
roomName = paramList.firstOrNull { it.first == "room_name" }?.second,
115-
inviterName = paramList.firstOrNull { it.first == "inviter_name" }?.second,
116-
roomAvatarUrl = paramList.firstOrNull { it.first == "room_avatar_url" }?.second,
117-
roomType = paramList.firstOrNull { it.first == "room_type" }?.second,
118-
identityServer = identityServerHost,
119-
token = token,
120-
privateKey = privateKey
66+
is MatrixId.User -> PermalinkData.UserLink(
67+
userId = UserId(id.id),
68+
)
69+
is MatrixId.RoomAlias -> PermalinkData.RoomAliasLink(
70+
roomAlias = id.alias,
71+
viaParameters = viaParameters,
72+
)
73+
is MatrixId.EventOnRoomId -> PermalinkData.EventIdLink(
74+
roomId = RoomId(id.roomId),
75+
eventId = EventId(id.eventId),
76+
viaParameters = viaParameters,
77+
)
78+
is MatrixId.EventOnRoomAlias -> PermalinkData.EventIdAliasLink(
79+
roomAlias = id.alias,
80+
eventId = EventId(id.eventId),
81+
viaParameters = viaParameters,
12182
)
122-
} catch (failure: Throwable) {
123-
Timber.i("## Permalink: Failed to parse permalink $signUrl")
124-
PermalinkData.FallbackLink(uri)
125-
}
126-
} else {
127-
PermalinkData.RoomLink(
128-
roomIdOrAlias = identifier,
129-
isRoomAlias = false,
130-
eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) },
131-
viaParameters = viaQueryParameters.toImmutableList()
132-
)
133-
}
134-
}
135-
136-
private fun safeExtractParams(fragment: String) =
137-
fragment.substringAfter("?").split('&').mapNotNull {
138-
val splitNameValue = it.split("=")
139-
if (splitNameValue.size == 2) {
140-
Pair(splitNameValue[0], URLDecoder.decode(splitNameValue[1], "UTF-8"))
141-
} else {
142-
null
14383
}
14484
}
145-
146-
private fun String.getViaParameters(): List<String> {
147-
return runCatching {
148-
UrlQuerySanitizer(this)
149-
.parameterList
150-
.filter {
151-
it.mParameter == "via"
152-
}
153-
.map {
154-
URLDecoder.decode(it.mValue, "UTF-8")
155-
}
156-
}.getOrDefault(emptyList())
15785
}
15886
}

0 commit comments

Comments
 (0)