Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 82177253 authored by Philipp Heckel's avatar Philipp Heckel
Browse files

Implement poll_request for Firebase to account for protected topics

parent 28bfd087
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -422,9 +422,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
        private const val TAG = "NtfyRepository"
        private var instance: Repository? = null

        fun getInstance(activity: Activity): Repository {
            val database = Database.getInstance(activity.applicationContext)
            val sharedPrefs = activity.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE)
        fun getInstance(context: Context): Repository {
            val database = Database.getInstance(context.applicationContext)
            val sharedPrefs = context.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE)
            return getInstance(sharedPrefs, database)
        }

+21 −35
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@ import java.nio.charset.StandardCharsets.UTF_8
import java.util.concurrent.TimeUnit
import kotlin.random.Random


class ApiService {
    private val client = OkHttpClient.Builder()
        .callTimeout(15, TimeUnit.SECONDS) // Total timeout for entire request
@@ -29,27 +28,23 @@ class ApiService {
        .build()
    private val parser = NotificationParser()

    fun publish(baseUrl: String, topic: String, message: String, title: String, priority: Int, tags: List<String>, delay: String) {
    fun publish(baseUrl: String, topic: String, user: User?, message: String, title: String, priority: Int, tags: List<String>, delay: String) {
        val url = topicUrl(baseUrl, topic)
        Log.d(TAG, "Publishing to $url")

        // XXXXXXXXXXXx

        var builder = Request.Builder()
            .url(url)
        val builder = builder(url, user)
            .put(message.toRequestBody())
            .addHeader("User-Agent", USER_AGENT)
        if (priority in 1..5) {
            builder = builder.addHeader("X-Priority", priority.toString())
            builder.addHeader("X-Priority", priority.toString())
        }
        if (tags.isNotEmpty()) {
            builder = builder.addHeader("X-Tags", tags.joinToString(","))
            builder.addHeader("X-Tags", tags.joinToString(","))
        }
        if (title.isNotEmpty()) {
            builder = builder.addHeader("X-Title", title)
            builder.addHeader("X-Title", title)
        }
        if (delay.isNotEmpty()) {
            builder = builder.addHeader("X-Delay", delay)
            builder.addHeader("X-Delay", delay)
        }
        client.newCall(builder.build()).execute().use { response ->
            if (!response.isSuccessful) {
@@ -59,18 +54,12 @@ class ApiService {
        }
    }

    fun poll(subscriptionId: Long, baseUrl: String, topic: String, since: Long = 0L): List<Notification> {
    fun poll(subscriptionId: Long, baseUrl: String, topic: String, user: User?, since: Long = 0L): List<Notification> {
        val sinceVal = if (since == 0L) "all" else since.toString()
        val url = topicUrlJsonPoll(baseUrl, topic, sinceVal)
        Log.d(TAG, "Polling topic $url")

        val request = Request.Builder()
            .url(url)
            .addHeader("User-Agent", USER_AGENT)
            .build()

        // XXXXXXXXXXXx

        val request = builder(url, user).build()
        client.newCall(request).execute().use { response ->
            if (!response.isSuccessful) {
                throw Exception("Unexpected response ${response.code} when polling topic $url")
@@ -97,14 +86,7 @@ class ApiService {
        val sinceVal = if (since == 0L) "all" else since.toString()
        val url = topicUrlJson(baseUrl, topics, sinceVal)
        Log.d(TAG, "Opening subscription connection to $url")
        val builder = Request.Builder()
            .get()
            .url(url)
            .addHeader("User-Agent", USER_AGENT)
        if (user != null) {
            builder.addHeader("Authorization", Credentials.basic(user.username, user.password, UTF_8))
        }
        val request = builder.build()
        val request = builder(url, user).build()
        val call = subscriberClient.newCall(request)
        call.enqueue(object : Callback {
            override fun onResponse(call: Call, response: Response) {
@@ -140,14 +122,7 @@ class ApiService {
            Log.d(TAG, "Checking read access for user ${user.username} against ${topicUrl(baseUrl, topic)}")
        }
        val url = topicUrlAuth(baseUrl, topic)
        val builder = Request.Builder()
            .get()
            .url(url)
            .addHeader("User-Agent", USER_AGENT)
        if (user != null) {
            builder.addHeader("Authorization", Credentials.basic(user.username, user.password, UTF_8))
        }
        val request = builder.build()
        val request = builder(url, user).build()
        client.newCall(request).execute().use { response ->
            return if (user == null) {
                response.isSuccessful || response.code == 404 // Treat 404 as success (old server; to be removed in future versions)
@@ -157,6 +132,16 @@ class ApiService {
        }
    }

    private fun builder(url: String, user: User?): Request.Builder {
        val builder = Request.Builder()
            .url(url)
            .addHeader("User-Agent", USER_AGENT)
        if (user != null) {
            builder.addHeader("Authorization", Credentials.basic(user.username, user.password, UTF_8))
        }
        return builder
    }

    companion object {
        val USER_AGENT = "ntfy/${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}; Android ${Build.VERSION.RELEASE}; SDK ${Build.VERSION.SDK_INT})"
        private const val TAG = "NtfyApiService"
@@ -165,5 +150,6 @@ class ApiService {
        const val CONTROL_TOPIC = "~control"
        const val EVENT_MESSAGE = "message"
        const val EVENT_KEEPALIVE = "keepalive"
        const val EVENT_POLL_REQUEST = "poll_request"
    }
}
+8 −2
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent
import io.heckel.ntfy.R
import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.log.Log
import io.heckel.ntfy.util.joinTagsMap
@@ -65,9 +66,12 @@ class BroadcastService(private val ctx: Context) {
            }
            val delay = getStringExtra(intent,"delay") ?: ""
            GlobalScope.launch(Dispatchers.IO) {
                val repository = Repository.getInstance(ctx)
                val user = repository.getUser(baseUrl) // May be null
                api.publish(
                    baseUrl = baseUrl,
                    topic = topic,
                    user = user,
                    message = message,
                    title = title,
                    priority = priority,
@@ -94,8 +98,10 @@ class BroadcastService(private val ctx: Context) {

    companion object {
        private const val TAG = "NtfyBroadcastService"
        private const val MESSAGE_RECEIVED_ACTION = "io.heckel.ntfy.MESSAGE_RECEIVED"
        private const val MESSAGE_SEND_ACTION = "io.heckel.ntfy.SEND_MESSAGE" // If changed, change in manifest too!
        private const val DOES_NOT_EXIST = -2586000

        // These constants cannot be changed without breaking the contract; also see manifest
        private const val MESSAGE_RECEIVED_ACTION = "io.heckel.ntfy.MESSAGE_RECEIVED"
        private const val MESSAGE_SEND_ACTION = "io.heckel.ntfy.SEND_MESSAGE"
    }
}
+4 −2
Original line number Diff line number Diff line
@@ -269,6 +269,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra

        lifecycleScope.launch(Dispatchers.IO) {
            try {
                val user = repository.getUser(subscriptionBaseUrl) // May be null
                val possibleTags = listOf(
                    "warning", "skull", "success", "triangular_flag_on_post", "de",  "dog", "rotating_light", "cat", "bike", // Emojis
                    "backup", "rsync", "de-server1", "this-is-a-tag"
@@ -277,7 +278,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
                val tags = possibleTags.shuffled().take(Random.nextInt(0, 4))
                val title = if (Random.nextBoolean()) getString(R.string.detail_test_title) else ""
                val message = getString(R.string.detail_test_message, priority)
                api.publish(subscriptionBaseUrl, subscriptionTopic, message, title, priority, tags, delay = "")
                api.publish(subscriptionBaseUrl, subscriptionTopic, user, message, title, priority, tags, delay = "")
            } catch (e: Exception) {
                runOnUiThread {
                    Toast
@@ -339,7 +340,8 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra

        lifecycleScope.launch(Dispatchers.IO) {
            try {
                val notifications = api.poll(subscriptionId, subscriptionBaseUrl, subscriptionTopic)
                val user = repository.getUser(subscriptionBaseUrl) // May be null
                val notifications = api.poll(subscriptionId, subscriptionBaseUrl, subscriptionTopic, user)
                val newNotifications = repository.onlyNewNotifications(subscriptionId, notifications)
                val toastMessage = if (newNotifications.isEmpty()) {
                    getString(R.string.refresh_message_no_results)
+6 −4
Original line number Diff line number Diff line
@@ -193,10 +193,10 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
        val work = PeriodicWorkRequestBuilder<PollWorker>(POLL_WORKER_INTERVAL_MINUTES, TimeUnit.MINUTES)
            .setConstraints(constraints)
            .addTag(PollWorker.TAG)
            .addTag(PollWorker.WORK_NAME_PERIODIC)
            .addTag(PollWorker.WORK_NAME_PERIODIC_ALL)
            .build()
        Log.d(TAG, "Poll worker: Scheduling period work every $POLL_WORKER_INTERVAL_MINUTES minutes")
        workManager!!.enqueueUniquePeriodicWork(PollWorker.WORK_NAME_PERIODIC, workPolicy, work)
        workManager!!.enqueueUniquePeriodicWork(PollWorker.WORK_NAME_PERIODIC_ALL, workPolicy, work)
    }

    private fun startPeriodicServiceRestartWorker() {
@@ -375,7 +375,8 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
        // Fetch cached messages
        lifecycleScope.launch(Dispatchers.IO) {
            try {
                val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic)
                val user = repository.getUser(subscription.baseUrl) // May be null
                val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic, user)
                notifications.forEach { notification -> repository.addNotification(notification) }
            } catch (e: Exception) {
                Log.e(TAG, "Unable to fetch notifications: ${e.stackTrace}")
@@ -418,7 +419,8 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
            var newNotificationsCount = 0
            repository.getSubscriptions().forEach { subscription ->
                try {
                    val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic)
                    val user = repository.getUser(subscription.baseUrl) // May be null
                    val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic, user)
                    val newNotifications = repository.onlyNewNotifications(subscription.id, notifications)
                    newNotifications.forEach { notification ->
                        newNotificationsCount++
Loading