Skip to content

Change : handle invalid invite error #4909

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,23 @@ package io.element.android.features.invite.impl
import com.squareup.anvil.annotations.ContributesBinding
import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.features.invite.api.SeenInvitesStore
import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.exception.ClientException
import io.element.android.libraries.matrix.api.exception.ErrorKind
import io.element.android.libraries.matrix.api.room.join.JoinRoom
import io.element.android.libraries.push.api.notifications.NotificationCleaner
import javax.inject.Inject

interface AcceptInvite {
suspend operator fun invoke(roomId: RoomId): Result<RoomId>

sealed class Failures : Exception() {
data object InvalidInvite : Failures()
}
}

@ContributesBinding(SessionScope::class)
Expand All @@ -37,6 +44,15 @@ class DefaultAcceptInvite @Inject constructor(
).onSuccess {
notificationCleaner.clearMembershipNotificationForRoom(client.sessionId, roomId)
seenInvitesStore.markAsUnSeen(roomId)
}.mapFailure {
if (it is ClientException.MatrixApi) {
when (it.kind) {
ErrorKind.Unknown -> AcceptInvite.Failures.InvalidInvite
else -> it
}
} else {
it
}
}.map { roomId }
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
/*
* Copyright 2024 New Vector Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/

package io.element.android.features.invite.api.acceptdecline
package io.element.android.features.invite.impl.acceptdecline

import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.invite.api.InviteData
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
import io.element.android.features.invite.api.acceptdecline.ConfirmingDeclineInvite
import io.element.android.features.invite.impl.AcceptInvite
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.core.RoomId

Expand All @@ -18,26 +22,37 @@ open class AcceptDeclineInviteStateProvider : PreviewParameterProvider<AcceptDec
anAcceptDeclineInviteState(),
anAcceptDeclineInviteState(
declineAction = ConfirmingDeclineInvite(
InviteData(roomId = RoomId("!room:matrix.org"), isDm = true, roomName = "Alice"),
InviteData(
roomId = RoomId("!room:matrix.org"),
isDm = true,
roomName = "Alice"
),
blockUser = false,
),
),
anAcceptDeclineInviteState(
declineAction = ConfirmingDeclineInvite(
InviteData(roomId = RoomId("!room:matrix.org"), isDm = true, roomName = "Alice"),
InviteData(
roomId = RoomId("!room:matrix.org"),
isDm = true,
roomName = "Alice"
),
blockUser = true,
),
),
anAcceptDeclineInviteState(
acceptAction = AsyncAction.Failure(RuntimeException("Error while accepting invite")),
),
anAcceptDeclineInviteState(
acceptAction = AsyncAction.Failure(AcceptInvite.Failures.InvalidInvite),
),
anAcceptDeclineInviteState(
declineAction = AsyncAction.Failure(RuntimeException("Error while declining invite")),
),
)
}

fun anAcceptDeclineInviteState(
private fun anAcceptDeclineInviteState(
acceptAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
declineAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
eventSink: (AcceptDeclineInviteEvents) -> Unit = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.features.invite.api.InviteData
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteStateProvider
import io.element.android.features.invite.api.acceptdecline.ConfirmingDeclineInvite
import io.element.android.features.invite.impl.AcceptInvite
import io.element.android.features.invite.impl.R
import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
Expand All @@ -39,13 +39,29 @@ fun AcceptDeclineInviteView(
onErrorDismiss = {
state.eventSink(InternalAcceptDeclineInviteEvents.DismissAcceptError)
},
errorTitle = {
stringResource(CommonStrings.common_something_went_wrong)
},
errorMessage = { error ->
if (error is AcceptInvite.Failures.InvalidInvite) {
stringResource(CommonStrings.error_invalid_invite)
} else {
stringResource(CommonStrings.error_network_or_server_issue)
}
}
)
AsyncActionView(
async = state.declineAction,
onSuccess = onDeclineInviteSuccess,
onErrorDismiss = {
state.eventSink(InternalAcceptDeclineInviteEvents.DismissDeclineError)
},
errorTitle = {
stringResource(CommonStrings.common_something_went_wrong)
},
errorMessage = {
stringResource(CommonStrings.error_network_or_server_issue)
},
confirmationDialog = { confirming ->
// Note: confirming will always be of type ConfirmingDeclineInvite.
if (confirming is ConfirmingDeclineInvite) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import io.element.android.features.roomdirectory.api.RoomDescription
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runUpdatingState
import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId
Expand Down Expand Up @@ -222,13 +221,7 @@ class JoinRoomPresenter @AssistedInject constructor(
roomIdOrAlias = roomIdOrAlias,
serverNames = serverNames,
trigger = trigger
).mapFailure {
if (it is ClientException.MatrixApi && it.kind == ErrorKind.Forbidden) {
JoinRoomFailures.UnauthorizedJoin
} else {
it
}
}
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.room.RoomType
import io.element.android.libraries.matrix.api.room.join.JoinRoom
import io.element.android.libraries.matrix.ui.model.InviteSender

internal const val MAX_KNOCK_MESSAGE_LENGTH = 500
Expand All @@ -36,7 +37,7 @@ data class JoinRoomState(
val canReportRoom: Boolean,
val eventSink: (JoinRoomEvents) -> Unit
) {
val isJoinActionUnauthorized = joinAction is AsyncAction.Failure && joinAction.error is JoinRoomFailures.UnauthorizedJoin
val isJoinActionUnauthorized = joinAction is AsyncAction.Failure && joinAction.error is JoinRoom.Failures.UnauthorizedJoin
val joinAuthorisationStatus = when (contentState) {
is ContentState.Loaded -> {
when {
Expand Down Expand Up @@ -107,7 +108,3 @@ sealed interface JoinAuthorisationStatus {
data object Unknown : JoinAuthorisationStatus
data object Unauthorized : JoinAuthorisationStatus
}

sealed class JoinRoomFailures : Exception() {
data object UnauthorizedJoin : JoinRoomFailures()
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ package io.element.android.features.joinroom.impl

import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.invite.api.InviteData
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
Expand All @@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
import io.element.android.libraries.matrix.api.exception.ClientException
import io.element.android.libraries.matrix.api.room.RoomType
import io.element.android.libraries.matrix.api.room.join.JoinRoom
import io.element.android.libraries.matrix.ui.model.InviteSender

open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
Expand All @@ -44,7 +45,7 @@ open class JoinRoomStateProvider : PreviewParameterProvider<JoinRoomState> {
),
aJoinRoomState(
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin),
joinAction = AsyncAction.Failure(JoinRoomFailures.UnauthorizedJoin)
joinAction = AsyncAction.Failure(JoinRoom.Failures.UnauthorizedJoin)
),
aJoinRoomState(
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin),
Expand Down Expand Up @@ -198,6 +199,16 @@ fun aJoinRoomState(
eventSink = eventSink
)

internal fun anAcceptDeclineInviteState(
acceptAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
declineAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
eventSink: (AcceptDeclineInviteEvents) -> Unit = {}
) = AcceptDeclineInviteState(
acceptAction = acceptAction,
declineAction = declineAction,
eventSink = eventSink,
)

internal fun anInviteSender(
userId: UserId = UserId("@bob:domain"),
displayName: String = "Bob",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
package io.element.android.features.roomlist.impl

import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteState
import io.element.android.features.invite.api.acceptdecline.anAcceptDeclineInviteState
import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.leaveroom.api.aLeaveRoomState
import io.element.android.features.logout.api.direct.DirectLogoutState
Expand All @@ -22,9 +22,11 @@ import io.element.android.features.roomlist.impl.model.aRoomListRoomSummary
import io.element.android.features.roomlist.impl.model.anInviteSender
import io.element.android.features.roomlist.impl.search.RoomListSearchState
import io.element.android.features.roomlist.impl.search.aRoomListSearchState
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.push.api.battery.aBatteryOptimizationState
Expand Down Expand Up @@ -86,6 +88,16 @@ internal fun aRoomListState(
eventSink = eventSink,
)

internal fun anAcceptDeclineInviteState(
acceptAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
declineAction: AsyncAction<RoomId> = AsyncAction.Uninitialized,
eventSink: (AcceptDeclineInviteEvents) -> Unit = {}
) = AcceptDeclineInviteState(
acceptAction = acceptAction,
declineAction = declineAction,
eventSink = eventSink,
)

internal fun aRoomListRoomSummaryList(): ImmutableList<RoomListRoomSummary> {
return persistentListOf(
aRoomListRoomSummary(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ interface JoinRoom {
serverNames: List<String>,
trigger: JoinedRoom.Trigger,
): Result<Unit>

sealed class Failures : Exception() {
data object UnauthorizedJoin : Failures()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ package io.element.android.libraries.matrix.impl.room.join

import com.squareup.anvil.annotations.ContributesBinding
import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.exception.ClientException
import io.element.android.libraries.matrix.api.exception.ErrorKind
import io.element.android.libraries.matrix.api.room.join.JoinRoom
import io.element.android.libraries.matrix.impl.analytics.toAnalyticsJoinedRoom
import io.element.android.services.analytics.api.AnalyticsService
Expand Down Expand Up @@ -42,6 +45,15 @@ class DefaultJoinRoom @Inject constructor(
if (roomInfo != null) {
analyticsService.capture(roomInfo.toAnalyticsJoinedRoom(trigger))
}
}.mapFailure {
if (it is ClientException.MatrixApi) {
when (it.kind) {
ErrorKind.Forbidden -> JoinRoom.Failures.UnauthorizedJoin
else -> it
}
} else {
it
}
}.map { }
}
}
1 change: 1 addition & 0 deletions libraries/ui-strings/src/main/res/values/localazy.xml
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ Are you sure you want to continue?"</string>
<string name="error_failed_loading_messages">"Failed loading messages"</string>
<string name="error_failed_locating_user">"%1$s could not access your location. Please try again later."</string>
<string name="error_failed_uploading_voice_message">"Failed to upload your voice message."</string>
<string name="error_invalid_invite">"The room no longer exists or the invite is no longer valid."</string>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have this string in a preview please?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I am blind, just ignore this!

<string name="error_message_not_found">"Message not found"</string>
<string name="error_missing_location_auth_android">"%1$s does not have permission to access your location. You can enable access in Settings."</string>
<string name="error_missing_location_rationale_android">"%1$s does not have permission to access your location. Enable access below."</string>
Expand Down
Loading