package org.meshbay.client.notify import android.app.job.JobParameters import android.app.job.JobService import android.content.Context import android.util.Log import okhttp3.MediaType.Companion.toMediaType import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import org.json.JSONObject import org.meshbay.client.bridge.Bridge import org.meshbay.client.hub.HubClient import java.util.concurrent.TimeUnit /** * One fetch of what is new (`POST /v1/push/poll`), page running or not. * * Authenticated by the row's poll secret, never a session: what this keeps for * running in the background reads notification lines and nothing else. It goes * to the hub the application is signed in to, and only there. */ class PollJob : JobService() { @Volatile private var worker: Thread? = null override fun onStartJob(params: JobParameters): Boolean { worker = Thread { try { fetch() } catch (e: Exception) { Log.w(Bridge.TAG, "poll failed: ${e.javaClass.simpleName}") } jobFinished(params, false) }.apply { start() } return true } override fun onStopJob(params: JobParameters): Boolean { worker?.interrupt() return false } private fun fetch() { val state = PushState(getSharedPreferences(PushState.PREFS, Context.MODE_PRIVATE)) val target = state.pollTarget() ?: return val base = HubClient(getSharedPreferences("shell", Context.MODE_PRIVATE)).base val url = base.toHttpUrlOrNull()?.newBuilder()?.encodedPath("/v1/push/poll")?.build() ?: return val body = JSONObject().put("id", target.id).put("secret", target.secret).put("since", target.since) .toString().toRequestBody("application/json".toMediaType()) val http = OkHttpClient.Builder().callTimeout(30, TimeUnit.SECONDS).followRedirects(false).build() http.newCall(Request.Builder().url(url).post(body).build()).execute().use { r -> when { // The row is gone — signed out elsewhere, or deleted: the page // registers again when it next runs, if push is still on. r.code == 404 -> state.forgetSubscription() r.isSuccessful -> { val list = JSONObject(r.body.string()).optJSONArray("notifications") ?: return for (i in 0 until list.length()) { val raw = list.optJSONObject(i)?.toString()?.toByteArray() ?: continue val shown = Notifier.parse(raw) ?: continue Notifier.deliver(this, state, shown) state.advance(shown.createdAt) } } } } } }