Skip to content

DROID-3690 Notifications | Encryption update #2556

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 15 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ dependencies {
implementation libs.navigationCompose
implementation libs.playBilling
implementation libs.lifecycleProcess
implementation libs.kotlinxSerializationJson

implementation libs.lifecycleViewModel
implementation libs.lifecycleRuntime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ import com.anytypeio.anytype.presentation.notifications.CryptoService
import com.anytypeio.anytype.presentation.notifications.CryptoServiceImpl
import com.anytypeio.anytype.presentation.notifications.DecryptionPushContentService
import com.anytypeio.anytype.presentation.notifications.DecryptionPushContentServiceImpl
import com.anytypeio.anytype.presentation.notifications.PushKeyProvider
import com.anytypeio.anytype.domain.notifications.PushKeyProvider
import dagger.Component
import dagger.Module
import dagger.Provides
import javax.inject.Named
import javax.inject.Singleton
import kotlinx.coroutines.CoroutineScope
import kotlinx.serialization.json.Json

@Singleton
@Component(
Expand Down Expand Up @@ -61,9 +62,11 @@ object PushContentModule {
fun provideDecryptionPushContentService(
pushKeyProvider: PushKeyProvider,
cryptoService: CryptoService,
json: Json
): DecryptionPushContentService = DecryptionPushContentServiceImpl(
pushKeyProvider = pushKeyProvider,
cryptoService = cryptoService,
json = json
)
}

Expand All @@ -75,4 +78,5 @@ interface PushContentDependencies : ComponentDependencies {
fun dispatchers(): AppCoroutineDispatchers
fun provider(): StringResourceProvider
fun notificationBuilder(): NotificationBuilder
fun json(): Json
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import com.anytypeio.anytype.data.auth.event.EventDataChannel
import com.anytypeio.anytype.data.auth.event.EventRemoteChannel
import com.anytypeio.anytype.data.auth.event.FileLimitsDataChannel
import com.anytypeio.anytype.data.auth.event.FileLimitsRemoteChannel
import com.anytypeio.anytype.data.auth.event.PushKeyRemoteChannel
import com.anytypeio.anytype.data.auth.event.SubscriptionDataChannel
import com.anytypeio.anytype.data.auth.event.SubscriptionEventRemoteChannel
import com.anytypeio.anytype.data.auth.status.SyncAndP2PStatusEventsStore
Expand Down Expand Up @@ -129,14 +128,12 @@ object EventModule {
logger: MiddlewareProtobufLogger,
@Named(DEFAULT_APP_COROUTINE_SCOPE) scope: CoroutineScope,
channel: EventHandlerChannel,
syncP2PStore: SyncAndP2PStatusEventsStore,
pushKeyRemoteChannel: PushKeyRemoteChannel
syncP2PStore: SyncAndP2PStatusEventsStore
): EventProxy = EventHandler(
scope = scope,
logger = logger,
channel = channel,
syncP2PStore = syncP2PStore,
pushKeyRemoteChannel = pushKeyRemoteChannel
syncP2PStore = syncP2PStore
)

@JvmStatic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import android.content.SharedPreferences
import com.anytypeio.anytype.app.AnytypeNotificationService
import com.anytypeio.anytype.data.auth.event.NotificationsDateChannel
import com.anytypeio.anytype.data.auth.event.NotificationsRemoteChannel
import com.anytypeio.anytype.data.auth.event.PushKeyDataChannel
import com.anytypeio.anytype.data.auth.event.PushKeyRemoteChannel
import com.anytypeio.anytype.device.NotificationBuilderImpl
import com.anytypeio.anytype.domain.account.AwaitAccountStartManager
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
Expand All @@ -17,24 +15,21 @@ import com.anytypeio.anytype.domain.notifications.SystemNotificationService
import com.anytypeio.anytype.domain.resources.StringResourceProvider
import com.anytypeio.anytype.domain.workspace.NotificationsChannel
import com.anytypeio.anytype.middleware.EventProxy
import com.anytypeio.anytype.middleware.interactor.EventHandlerChannel
import com.anytypeio.anytype.middleware.interactor.NotificationsMiddlewareChannel
import com.anytypeio.anytype.middleware.interactor.events.PushKeyMiddlewareChannel
import com.anytypeio.anytype.presentation.notifications.CryptoService
import com.anytypeio.anytype.presentation.notifications.CryptoServiceImpl
import com.anytypeio.anytype.presentation.notifications.DecryptionPushContentService
import com.anytypeio.anytype.presentation.notifications.DecryptionPushContentServiceImpl
import com.anytypeio.anytype.presentation.notifications.NotificationPermissionManager
import com.anytypeio.anytype.presentation.notifications.NotificationPermissionManagerImpl
import com.anytypeio.anytype.presentation.notifications.NotificationsProvider
import com.anytypeio.anytype.presentation.notifications.PushKeyProvider
import com.anytypeio.anytype.domain.notifications.PushKeyProvider
import com.anytypeio.anytype.presentation.notifications.PushKeyProviderImpl
import com.google.gson.Gson
import dagger.Module
import dagger.Provides
import javax.inject.Named
import javax.inject.Singleton
import kotlinx.coroutines.CoroutineScope
import com.anytypeio.anytype.domain.chats.PushKeySpaceViewChannel
import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer
import kotlinx.serialization.json.Json

@Module
object NotificationsModule {
Expand Down Expand Up @@ -87,38 +82,21 @@ object NotificationsModule {
dispatchers: AppCoroutineDispatchers,
@Named(ConfigModule.DEFAULT_APP_COROUTINE_SCOPE) scope: CoroutineScope,
channel: PushKeyChannel,
gson: Gson
): PushKeyProvider {
return PushKeyProviderImpl(
sharedPreferences = sharedPreferences,
dispatchers = dispatchers,
scope = scope,
channel = channel,
gson = gson
)
}

@JvmStatic
@Provides
@Singleton
fun providePushKeyRemoteChannel(
channel: EventHandlerChannel,
@Named(ConfigModule.DEFAULT_APP_COROUTINE_SCOPE) scope: CoroutineScope,
dispatchers: AppCoroutineDispatchers
): PushKeyRemoteChannel = PushKeyMiddlewareChannel(
channel = channel,
json: Json
): PushKeyProvider = PushKeyProviderImpl(
sharedPreferences = sharedPreferences,
dispatchers = dispatchers,
scope = scope,
dispatcher = dispatchers.io
channel = channel,
json = json
)

@JvmStatic
@Provides
@Singleton
fun providePushKeyChannel(
channel: PushKeyRemoteChannel
): PushKeyChannel = PushKeyDataChannel(
channel = channel
)
spaceViewSubscriptionContainer: SpaceViewSubscriptionContainer
): PushKeyChannel = PushKeySpaceViewChannel(spaceViewSubscriptionContainer)

@Provides
@Singleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.anytypeio.anytype.domain.multiplayer.ActiveSpaceMemberSubscriptionCon
import com.anytypeio.anytype.domain.multiplayer.DefaultUserPermissionProvider
import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer
import com.anytypeio.anytype.domain.multiplayer.UserPermissionProvider
import com.anytypeio.anytype.domain.notifications.PushKeyProvider
import com.anytypeio.anytype.domain.notifications.RegisterDeviceToken
import com.anytypeio.anytype.domain.objects.DefaultStoreOfObjectTypes
import com.anytypeio.anytype.domain.objects.DefaultStoreOfRelations
Expand Down Expand Up @@ -233,7 +234,8 @@ object SubscriptionsModule {
profileSubscriptionManager: ProfileSubscriptionManager,
networkConnectionStatus: NetworkConnectionStatus,
deviceTokenStoringService: DeviceTokenStoringService,
chatPreviewContainer: ChatPreviewContainer
chatPreviewContainer: ChatPreviewContainer,
pushKeyProvider: PushKeyProvider
): GlobalSubscriptionManager = GlobalSubscriptionManager.Default(
types = types,
relations = relations,
Expand All @@ -242,7 +244,8 @@ object SubscriptionsModule {
profile = profileSubscriptionManager,
networkConnectionStatus = networkConnectionStatus,
deviceTokenStoringService = deviceTokenStoringService,
chatPreviewContainer = chatPreviewContainer
chatPreviewContainer = chatPreviewContainer,
pushKeyProvider = pushKeyProvider
)

@JvmStatic
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import dagger.Module
import dagger.Provides
import java.time.ZoneId
import javax.inject.Singleton
import kotlinx.serialization.json.Json

@Module(includes = [UtilModule.Bindings::class])
object UtilModule {
Expand Down Expand Up @@ -113,6 +114,13 @@ object UtilModule {
@Singleton
fun provideGson(): Gson = Gson()

@JvmStatic
@Provides
@Singleton
fun provideJson(): Json = Json {
ignoreUnknownKeys = true
}

@Module
interface Bindings {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ sealed class Command {
class UploadFile(
val space: SpaceId,
val path: String,
val type: Block.Content.File.Type?,
val createTypeWidgetIfMissing: Boolean = true
val type: Block.Content.File.Type?
)

class FileDrop(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ sealed class ObjectWrapper {
val writersLimit: Double? by default
val readersLimit: Double? by default

val spacePushNotificationEncryptionKey: String? by default

val sharedSpaceLimit: Int
get() {
val value = getValue<Double?>(Relations.SHARED_SPACES_LIMIT)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ object Relations {

const val AUTO_WIDGET_DISABLED = "autoWidgetDisabled"

const val SPACE_PUSH_NOTIFICATIONS_KEY = "spacePushNotificationEncryptionKey"

val systemRelationKeys = listOf(
"id",
"name",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.anytypeio.anytype.presentation.notifications

import kotlinx.serialization.Serializable

@Serializable
data class PushKey(
val id: String,
val value: String
) {
companion object {
val EMPTY = PushKey(value = "", id = "")
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.anytypeio.anytype.domain.chats

import com.anytypeio.anytype.core_models.chats.PushKeyUpdate
import com.anytypeio.anytype.domain.multiplayer.SpaceViewSubscriptionContainer
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import java.security.MessageDigest
import java.util.Base64
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi

class PushKeySpaceViewChannel @Inject constructor(
private val spaceViewSubscriptionContainer: SpaceViewSubscriptionContainer
) : PushKeyChannel {
@OptIn(ExperimentalCoroutinesApi::class)
override fun observe(): Flow<PushKeyUpdate> =
spaceViewSubscriptionContainer.observe().flatMapLatest { spaceViews ->
flow {
spaceViews.forEach { spaceView ->
val key = spaceView.spacePushNotificationEncryptionKey
if (!key.isNullOrEmpty()) {
val id = key.computePushKeyId()
emit(PushKeyUpdate(encryptionKeyId = id, encryptionKey = key))
}
}
}
}
}

@OptIn(ExperimentalStdlibApi::class)
internal fun String.computePushKeyId(): String {
val decodedKey = Base64.getDecoder().decode(this)
val digest = MessageDigest.getInstance("SHA-256")
val hash = digest.digest(decodedKey)
return hash.toHexString()
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ class SetDocumentImageIcon @Inject constructor(
command = Command.UploadFile(
path = params.path,
type = Block.Content.File.Type.IMAGE,
space = params.spaceId,
createTypeWidgetIfMissing = params.createTypeWidgetIfMissing
space = params.spaceId
)
)
val payload = repo.setDocumentImageIcon(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ abstract class SetImageIcon<T> : BaseUseCase<Pair<Payload, Hash>, SetImageIcon.P
data class Params<T>(
val target: T,
val path: String,
val spaceId: SpaceId,
val createTypeWidgetIfMissing: Boolean = true
val spaceId: SpaceId
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ class SetTextBlockImage(
command = Command.UploadFile(
path = params.path,
type = Block.Content.File.Type.IMAGE,
space = params.spaceId,
createTypeWidgetIfMissing = params.createTypeWidgetIfMissing
space = params.spaceId
)
)
val payload = repo.setTextIcon(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,13 @@ class UploadFile @Inject constructor(
command = Command.UploadFile(
path = params.path,
type = params.type,
space = params.space,
createTypeWidgetIfMissing = params.createTypeWidgetIfMissing
space = params.space
)
)

data class Params(
val path: String,
val space: SpaceId,
val type: Block.Content.File.Type = Block.Content.File.Type.FILE,
val createTypeWidgetIfMissing: Boolean = true
val type: Block.Content.File.Type = Block.Content.File.Type.FILE
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ interface SpaceViewSubscriptionContainer {
Relations.CREATED_DATE,
Relations.CREATOR,
Relations.ICON_IMAGE,
Relations.ICON_OPTION
Relations.ICON_OPTION,
Relations.SPACE_PUSH_NOTIFICATIONS_KEY
),
filters = listOf(
DVFilter(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.anytypeio.anytype.domain.notifications

import com.anytypeio.anytype.presentation.notifications.PushKey

interface PushKeyProvider {
fun start()
fun stop()
fun getPushKey(): Map<String, PushKey>
}
Loading