diff --git a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt index 89460c4b..50ad3b10 100644 --- a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt +++ b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt @@ -11,6 +11,7 @@ import io.heckel.ntfy.db.Repository import io.heckel.ntfy.firebase.FirebaseMessenger import io.heckel.ntfy.msg.NotificationService import io.heckel.ntfy.util.Log +import io.heckel.ntfy.util.topicHash import io.heckel.ntfy.util.topicUrl import java.io.InputStreamReader @@ -117,6 +118,8 @@ class Backuper(val context: Context) { // Subscribe to Firebase topics if (s.baseUrl == appBaseUrl) { messenger.subscribe(s.topic) + } else { + messenger.subscribe(topicHash(s.baseUrl, s.topic)) } // Create dedicated channels diff --git a/app/src/main/java/io/heckel/ntfy/db/Database.kt b/app/src/main/java/io/heckel/ntfy/db/Database.kt index 5f397969..08ac78dc 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -6,6 +6,7 @@ import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import io.heckel.ntfy.util.topicHash import kotlinx.coroutines.flow.Flow import java.lang.reflect.Type @@ -90,7 +91,9 @@ data class SubscriptionWithMetadata( val totalCount: Int, val newCount: Int, val lastActive: Long -) +) { + fun urlHash() = topicHash(baseUrl, topic) +} @Entity(primaryKeys = ["id", "subscriptionId"]) data class Notification( diff --git a/app/src/main/java/io/heckel/ntfy/db/Repository.kt b/app/src/main/java/io/heckel/ntfy/db/Repository.kt index 71b03092..e47400b9 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -9,6 +9,9 @@ import androidx.appcompat.app.AppCompatDelegate import androidx.lifecycle.* import io.heckel.ntfy.util.Log import io.heckel.ntfy.util.validUrl +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.map import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicLong @@ -44,6 +47,13 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas .map { list -> list.map { Pair(it.id, it.instant) }.toSet() } } + suspend fun getSubscriptionByHash(topicHash: String): Subscription? { + return toSubscription( + subscriptionDao.listFlow().map { subs -> subs.first { topicHash == it.urlHash() } } + .first() + ) + } + suspend fun getSubscriptions(): List { return toSubscriptionList(subscriptionDao.list()) } diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt index 851f43ac..14132dd7 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -128,9 +128,11 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra repository.addSubscription(subscription) // Subscribe to Firebase topic if ntfy.sh (even if instant, just to be sure!) + Log.d(TAG, "Subscribing to Firebase topic $topic") if (baseUrl == appBaseUrl) { - Log.d(TAG, "Subscribing to Firebase topic $topic") messenger.subscribe(topic) + } else { + messenger.subscribe(topicHash(baseUrl, topic)) } // Fetch cached messages @@ -610,6 +612,8 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra repository.removeSubscription(subscriptionId) if (subscriptionBaseUrl == appBaseUrl) { messenger.unsubscribe(subscriptionTopic) + } else { + messenger.unsubscribe(topicHash(subscriptionBaseUrl, subscriptionTopic)) } } finish() diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt index e131f32c..5d9465f6 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -466,10 +466,12 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc ) viewModel.add(subscription) - // Subscribe to Firebase topic if ntfy.sh (even if instant, just to be sure!) + // Subscribe to Firebase topic (even if instant, just to be sure!) + Log.d(TAG, "Subscribing to Firebase topic $topic") if (baseUrl == appBaseUrl) { - Log.d(TAG, "Subscribing to Firebase topic $topic") messenger.subscribe(topic) + } else { + messenger.subscribe(topicHash(baseUrl, topic)) } // Fetch cached messages diff --git a/app/src/main/java/io/heckel/ntfy/util/Util.kt b/app/src/main/java/io/heckel/ntfy/util/Util.kt index 2ffbb766..84619b8f 100644 --- a/app/src/main/java/io/heckel/ntfy/util/Util.kt +++ b/app/src/main/java/io/heckel/ntfy/util/Util.kt @@ -59,6 +59,7 @@ fun topicUrlWs(baseUrl: String, topic: String, since: String) = "${topicUrl(base fun topicUrlAuth(baseUrl: String, topic: String) = "${topicUrl(baseUrl, topic)}/auth" fun topicUrlJsonPoll(baseUrl: String, topic: String, since: String) = "${topicUrl(baseUrl, topic)}/json?poll=1&since=$since" fun topicShortUrl(baseUrl: String, topic: String) = shortUrl(topicUrl(baseUrl, topic)) +fun topicHash(baseUrl: String, topic: String) = topicUrl(baseUrl, topic).sha256() fun subscriptionTopicShortUrl(subscription: Subscription) : String { return topicShortUrl(subscription.baseUrl, subscription.topic) diff --git a/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt b/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt index 830b92d4..76169ae9 100644 --- a/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt +++ b/app/src/play/java/io/heckel/ntfy/firebase/FirebaseService.kt @@ -61,23 +61,28 @@ class FirebaseService : FirebaseMessagingService() { } private fun handlePollRequest(remoteMessage: RemoteMessage) { - val baseUrl = getString(R.string.app_base_url) // Everything from Firebase comes from main service URL! - val topic = remoteMessage.data["topic"] ?: return - val constraints = Constraints.Builder() - .setRequiredNetworkType(NetworkType.CONNECTED) - .build() - val workName = "${PollWorker.WORK_NAME_ONCE_SINGE_PREFIX}_${baseUrl}_${topic}" - val workManager = WorkManager.getInstance(this) - val workRequest = OneTimeWorkRequest.Builder(PollWorker::class.java) - .setInputData(workDataOf( - PollWorker.INPUT_DATA_BASE_URL to baseUrl, - PollWorker.INPUT_DATA_TOPIC to topic - )) - .setConstraints(constraints) - .build() - Log.d(TAG, "Poll request for ${topicShortUrl(baseUrl, topic)} received, scheduling unique poll worker with name $workName") - - workManager.enqueueUniqueWork(workName, ExistingWorkPolicy.REPLACE, workRequest) + CoroutineScope(job).launch { + val pollTopic = remoteMessage.data["topic"] ?: return@launch + val subscription = repository.getSubscriptionByHash(pollTopic) + val baseUrl = subscription?.baseUrl ?: getString(R.string.app_base_url) + val topic = subscription?.topic ?: pollTopic + + val constraints = Constraints.Builder() + .setRequiredNetworkType(NetworkType.CONNECTED) + .build() + val workName = "${PollWorker.WORK_NAME_ONCE_SINGE_PREFIX}_${baseUrl}_${topic}" + val workManager = WorkManager.getInstance(this@FirebaseService) + val workRequest = OneTimeWorkRequest.Builder(PollWorker::class.java) + .setInputData(workDataOf( + PollWorker.INPUT_DATA_BASE_URL to baseUrl, + PollWorker.INPUT_DATA_TOPIC to topic + )) + .setConstraints(constraints) + .build() + Log.d(TAG, "Poll request for ${topicShortUrl(baseUrl, topic)} received, scheduling unique poll worker with name $workName") + + workManager.enqueueUniqueWork(workName, ExistingWorkPolicy.REPLACE, workRequest) + } } private fun handleMessage(remoteMessage: RemoteMessage) {