Skip to content

Commit 3df328b

Browse files
committed
Parse permalink using parseMatrixEntityFrom.
Create new PermalinkData type for link to Events. Keep matrixToConverter for now to first convert to matrix.to link. At some point it may be done by the SDK. Remove parse(Uri)
1 parent 89d2f43 commit 3df328b

File tree

14 files changed

+123
-239
lines changed

14 files changed

+123
-239
lines changed

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)