mirror of
https://codeberg.org/NextPush/nextpush-android.git
synced 2025-01-23 07:50:23 +01:00
Add VAPID support
This commit is contained in:
parent
b093c7869b
commit
6ee69319fd
@ -10,6 +10,21 @@ import java.util.concurrent.atomic.AtomicReference
|
||||
class Database(val context: Context) :
|
||||
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
|
||||
|
||||
/** No record has been found. */
|
||||
class NoRecordException : Throwable() {
|
||||
private fun readResolve(): Any = WrongPackageNameException()
|
||||
}
|
||||
|
||||
/** A record has been found for another packageName. */
|
||||
class WrongPackageNameException : Throwable() {
|
||||
private fun readResolve(): Any = WrongPackageNameException()
|
||||
}
|
||||
|
||||
/** A record has been found with another vapid key. */
|
||||
class WrongVapidException : Exception() {
|
||||
private fun readResolve(): Any = WrongVapidException()
|
||||
}
|
||||
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
db.execSQL(CREATE_TABLE_APPS)
|
||||
}
|
||||
@ -20,19 +35,21 @@ class Database(val context: Context) :
|
||||
while (v < newVersion) {
|
||||
when (v) {
|
||||
1 -> db?.execSQL(UPGRADE_1_2)
|
||||
2 -> db?.execSQL(UPGRADE_2_3)
|
||||
else -> throw IllegalStateException("Upgrade not supported")
|
||||
}
|
||||
v++
|
||||
}
|
||||
}
|
||||
|
||||
fun registerApp(packageName: String, connectorToken: String, appToken: String, title: String = "") {
|
||||
fun registerApp(packageName: String, connectorToken: String, appToken: String, title: String?, vapidKey: String?) {
|
||||
val db = writableDatabase
|
||||
val values = ContentValues().apply {
|
||||
put(FIELD_PACKAGE_NAME, packageName)
|
||||
put(FIELD_CONNECTOR_TOKEN, connectorToken)
|
||||
put(FIELD_APP_TOKEN, appToken)
|
||||
put(FIELD_NOTIFICATION_TITLE, title) // Used for non-UnifiedPush notif
|
||||
put(FIELD_VAPID, vapidKey)
|
||||
}
|
||||
db.insert(TABLE_APPS, null, values)
|
||||
RegistrationCountCache.refresh(context)
|
||||
@ -46,20 +63,32 @@ class Database(val context: Context) :
|
||||
RegistrationCountCache.refresh(context)
|
||||
}
|
||||
|
||||
fun isRegistered(packageName: String, connectorToken: String): Boolean {
|
||||
/**
|
||||
* Assert [connectorToken] is registered for [packageName] with the [vapidKey].
|
||||
*
|
||||
* @throws [NoRecordException] if the [connectorToken] isn't registered
|
||||
* @throws [WrongPackageNameException] if the [packageName] does not match the record
|
||||
* @throws [WrongVapidException] if the [vapidKey] does not match the record
|
||||
*/
|
||||
fun assertIsRegistered(connectorToken: String, packageName: String, vapidKey: String?) {
|
||||
val db = readableDatabase
|
||||
val selection = "$FIELD_PACKAGE_NAME = ? AND $FIELD_CONNECTOR_TOKEN = ?"
|
||||
val selectionArgs = arrayOf(packageName, connectorToken)
|
||||
val projection = arrayOf(FIELD_PACKAGE_NAME, FIELD_VAPID)
|
||||
val selection = "$FIELD_CONNECTOR_TOKEN = ?"
|
||||
val selectionArgs = arrayOf(connectorToken)
|
||||
return db.query(
|
||||
TABLE_APPS,
|
||||
null,
|
||||
projection,
|
||||
selection,
|
||||
selectionArgs,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
).use { cursor ->
|
||||
(cursor != null && cursor.count > 0)
|
||||
val packageNameColumn = cursor.getColumnIndex(FIELD_PACKAGE_NAME)
|
||||
val vapidColumn = cursor.getColumnIndex(FIELD_VAPID)
|
||||
if (!cursor.moveToFirst() || packageNameColumn < 0 || vapidColumn < 0) throw NoRecordException()
|
||||
if (cursor.getString(packageNameColumn) != packageName) throw WrongPackageNameException()
|
||||
if (cursor.getString(vapidColumn) != vapidKey) throw WrongVapidException()
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,14 +198,17 @@ class Database(val context: Context) :
|
||||
private const val FIELD_CONNECTOR_TOKEN = "connectorToken"
|
||||
private const val FIELD_APP_TOKEN = "appToken"
|
||||
private const val FIELD_NOTIFICATION_TITLE = "notificationTitle" // Used for non-UnifiedPush notif
|
||||
private const val FIELD_VAPID = "vapid"
|
||||
private const val CREATE_TABLE_APPS = "CREATE TABLE $TABLE_APPS (" +
|
||||
"$FIELD_PACKAGE_NAME TEXT," +
|
||||
"$FIELD_CONNECTOR_TOKEN TEXT," +
|
||||
"$FIELD_APP_TOKEN TEXT," +
|
||||
"$FIELD_NOTIFICATION_TITLE TEXT," +
|
||||
"$FIELD_VAPID TEXT," +
|
||||
"PRIMARY KEY ($FIELD_CONNECTOR_TOKEN));"
|
||||
|
||||
private const val UPGRADE_1_2 = "ALTER TABLE $TABLE_APPS ADD COLUMN $FIELD_NOTIFICATION_TITLE TEXT"
|
||||
private const val UPGRADE_2_3 = "ALTER TABLE $TABLE_APPS ADD COLUMN $FIELD_VAPID TEXT"
|
||||
|
||||
private val db: AtomicReference<Database?> = AtomicReference(null)
|
||||
|
||||
|
@ -8,9 +8,9 @@ import java.util.UUID
|
||||
|
||||
object LocalNotification {
|
||||
fun createChannel(context: Context, title: String, block: () -> Unit) {
|
||||
Api(context).apiCreateApp(context.getString(R.string.local_notif_title).format(title)) { nextpushToken ->
|
||||
Api(context).apiCreateApp(context.getString(R.string.local_notif_title).format(title), null) { nextpushToken ->
|
||||
nextpushToken?.let {
|
||||
getDb(context).registerApp(context.packageName, UUID.randomUUID().toString(), it, title)
|
||||
getDb(context).registerApp(context.packageName, UUID.randomUUID().toString(), it, title, null)
|
||||
}
|
||||
block()
|
||||
}
|
||||
|
@ -147,6 +147,7 @@ class Api(context: Context) {
|
||||
|
||||
fun apiCreateApp(
|
||||
appName: String,
|
||||
vapid: String?,
|
||||
block: (String?) -> Unit
|
||||
) {
|
||||
tryWithDeviceId { deviceId ->
|
||||
@ -155,6 +156,9 @@ class Api(context: Context) {
|
||||
"deviceId" to deviceId,
|
||||
"appName" to appName
|
||||
)
|
||||
vapid?.let {
|
||||
parameters.put("vapid", it)
|
||||
}
|
||||
try {
|
||||
withApiProvider { apiProvider, then ->
|
||||
apiProvider.createApp(parameters)
|
||||
|
@ -3,6 +3,7 @@ package org.unifiedpush.distributor.nextpush.distributor
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import org.unifiedpush.distributor.nextpush.Database
|
||||
import org.unifiedpush.distributor.nextpush.Database.Companion.getDb
|
||||
import org.unifiedpush.distributor.nextpush.LocalNotification
|
||||
import org.unifiedpush.distributor.nextpush.account.AccountFactory.getAccount
|
||||
@ -90,19 +91,23 @@ object Distributor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the [connectorToken] is known and correspond to the [app].
|
||||
* Check if the [connectorToken] is known and correspond to the [app], or if the vapid as been
|
||||
* updated.
|
||||
*
|
||||
* @return [ConnectorTokenValidity]
|
||||
* @return [RegistrationStatus]
|
||||
*/
|
||||
fun checkToken(context: Context, connectorToken: String, app: String): ConnectorTokenValidity {
|
||||
fun checkRegistration(context: Context, connectorToken: String, app: String, vapid: String?): RegistrationStatus {
|
||||
val db = getDb(context)
|
||||
if (connectorToken !in db.listTokens()) {
|
||||
return ConnectorTokenValidity.TOKEN_NEW
|
||||
try {
|
||||
db.assertIsRegistered(connectorToken, app, vapid)
|
||||
} catch (e: Database.NoRecordException) {
|
||||
return RegistrationStatus.NEW
|
||||
} catch (e: Database.WrongPackageNameException) {
|
||||
return RegistrationStatus.ERROR
|
||||
} catch (e: Database.WrongVapidException) {
|
||||
return RegistrationStatus.UPDATED
|
||||
}
|
||||
if (db.isRegistered(app, connectorToken)) {
|
||||
return ConnectorTokenValidity.TOKEN_REGISTERED_OK
|
||||
}
|
||||
return ConnectorTokenValidity.TOKEN_NOK
|
||||
return RegistrationStatus.REGISTERED_OK
|
||||
}
|
||||
|
||||
fun deleteDevice(context: Context, block: () -> Unit = {}) {
|
||||
@ -119,17 +124,17 @@ object Distributor {
|
||||
*
|
||||
* [block]'s parameter is `true` if we have successfully created the registration.
|
||||
*/
|
||||
fun createApp(context: Context, appName: String, connectorToken: String, block: (Boolean) -> Unit) {
|
||||
Api(context).apiCreateApp(appName) { nextpushToken ->
|
||||
fun createApp(context: Context, appName: String, connectorToken: String, vapid: String?, block: (Boolean) -> Unit) {
|
||||
Api(context).apiCreateApp(appName, vapid) { nextpushToken ->
|
||||
nextpushToken?.let {
|
||||
getDb(context).registerApp(appName, connectorToken, it)
|
||||
getDb(context).registerApp(appName, connectorToken, it, null, vapid)
|
||||
block(true)
|
||||
} ?: block(false)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteApp(context: Context, connectorToken: String, block: () -> Unit) {
|
||||
sendUnregistered(context, connectorToken)
|
||||
fun deleteApp(context: Context, connectorToken: String, sendIntent: Boolean = true, block: () -> Unit) {
|
||||
if (sendIntent) sendUnregistered(context, connectorToken)
|
||||
val db = getDb(context)
|
||||
db.getAppToken(
|
||||
connectorToken
|
||||
|
@ -3,13 +3,16 @@ package org.unifiedpush.distributor.nextpush.distributor
|
||||
/**
|
||||
* Validity of a connection token received during registration.
|
||||
*/
|
||||
enum class ConnectorTokenValidity {
|
||||
enum class RegistrationStatus {
|
||||
/** This is a new token. */
|
||||
TOKEN_NEW,
|
||||
NEW,
|
||||
|
||||
/** This is a known token, which match the provided application. */
|
||||
TOKEN_REGISTERED_OK,
|
||||
REGISTERED_OK,
|
||||
|
||||
/** The registration need to be updated, vapid doesn't match */
|
||||
UPDATED,
|
||||
|
||||
/** This is a known token, but it doesn't match the application. */
|
||||
TOKEN_NOK
|
||||
ERROR
|
||||
}
|
@ -16,6 +16,7 @@ const val ACTION_UNREGISTER = "org.unifiedpush.android.distributor.UNREGISTER"
|
||||
const val EXTRA_APPLICATION = "application"
|
||||
const val EXTRA_PI = "pi"
|
||||
const val EXTRA_TOKEN = "token"
|
||||
const val EXTRA_VAPID = "vapid"
|
||||
const val EXTRA_ENDPOINT = "endpoint"
|
||||
const val EXTRA_FAILED_REASON = "reason"
|
||||
const val EXTRA_BYTES_MESSAGE = "bytesMessage"
|
||||
|
@ -13,7 +13,7 @@ import org.unifiedpush.distributor.nextpush.Database.Companion.getDb
|
||||
import org.unifiedpush.distributor.nextpush.WakeLock
|
||||
import org.unifiedpush.distributor.nextpush.account.AccountFactory
|
||||
import org.unifiedpush.distributor.nextpush.distributor.* // ktlint-disable no-wildcard-imports
|
||||
import org.unifiedpush.distributor.nextpush.distributor.Distributor.checkToken
|
||||
import org.unifiedpush.distributor.nextpush.distributor.Distributor.checkRegistration
|
||||
import org.unifiedpush.distributor.nextpush.distributor.Distributor.createApp
|
||||
import org.unifiedpush.distributor.nextpush.distributor.Distributor.deleteApp
|
||||
import org.unifiedpush.distributor.nextpush.distributor.Distributor.sendEndpoint
|
||||
@ -112,7 +112,8 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
|
||||
Log.w(TAG, "Trying to register an app without packageName")
|
||||
return
|
||||
}
|
||||
onRegister(context, connectorToken, application)
|
||||
val vapid = intent.getStringExtra(EXTRA_VAPID)?.trim()?.takeIf { it.length == 87 }
|
||||
onRegister(context, connectorToken, application, vapid)
|
||||
}
|
||||
ACTION_UNREGISTER -> {
|
||||
Log.i(TAG, "UNREGISTER")
|
||||
@ -126,12 +127,13 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
|
||||
/**
|
||||
* Register the app on the server and send the new endpoint
|
||||
*/
|
||||
private fun onRegister(context: Context, connectorToken: String, application: String) {
|
||||
private fun onRegister(context: Context, connectorToken: String, application: String, vapid: String?) {
|
||||
if (!AppCompanion.createQueue.containsTokenElseAdd(connectorToken)) {
|
||||
when (checkToken(context, connectorToken, application)) {
|
||||
ConnectorTokenValidity.TOKEN_REGISTERED_OK -> onRegisterKnownToken(context, connectorToken)
|
||||
ConnectorTokenValidity.TOKEN_NOK -> onRegisterNokToken(context, connectorToken, application)
|
||||
ConnectorTokenValidity.TOKEN_NEW -> onRegisterNewToken(context, connectorToken, application)
|
||||
when (checkRegistration(context, connectorToken, application, vapid)) {
|
||||
RegistrationStatus.REGISTERED_OK -> onRegisterKnownToken(context, connectorToken)
|
||||
RegistrationStatus.ERROR -> onRegisterNokToken(context, connectorToken, application)
|
||||
RegistrationStatus.UPDATED -> onRegisterUpdatedToken(context, connectorToken, application, vapid)
|
||||
RegistrationStatus.NEW -> onRegisterNewToken(context, connectorToken, application, vapid)
|
||||
}
|
||||
AppCompanion.createQueue.removeToken(connectorToken)
|
||||
} else {
|
||||
@ -160,11 +162,23 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registering a new token.
|
||||
* Update a know token.
|
||||
*
|
||||
* Remove the previous app and register a new one.
|
||||
*/
|
||||
private fun onRegisterUpdatedToken(context: Context, connectorToken: String, application: String, vapid: String?) {
|
||||
Log.d(TAG, "Updating registration for $application")
|
||||
deleteApp(context, connectorToken, false) {
|
||||
onRegisterNewToken(context, connectorToken, application, vapid, toastOnSuccess = false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new token.
|
||||
*
|
||||
* If we are not connected to Nextcloud, we send registration failed with [FailedReason.ACTION_REQUIRED]
|
||||
*/
|
||||
private fun onRegisterNewToken(context: Context, connectorToken: String, application: String) {
|
||||
private fun onRegisterNewToken(context: Context, connectorToken: String, application: String, vapid: String?, toastOnSuccess: Boolean = true) {
|
||||
val appName = context.getApplicationName(application) ?: application
|
||||
when {
|
||||
AccountFactory.getAccount(context)?.connected != true -> registrationFailedWithToast(
|
||||
@ -184,13 +198,16 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
|
||||
else -> createApp(
|
||||
context,
|
||||
application,
|
||||
connectorToken
|
||||
connectorToken,
|
||||
vapid
|
||||
) { success ->
|
||||
when (success) {
|
||||
true -> {
|
||||
sendEndpoint(context, connectorToken)
|
||||
Toast.makeText(context, "$appName registered.", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
if (toastOnSuccess) {
|
||||
Toast.makeText(context, "$appName registered.", Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
false -> registrationFailedWithToast(
|
||||
|
Loading…
Reference in New Issue
Block a user