mirror of
https://codeberg.org/NextPush/nextpush-android.git
synced 2025-02-05 05:27:43 +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) :
|
class Database(val context: Context) :
|
||||||
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
|
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) {
|
override fun onCreate(db: SQLiteDatabase) {
|
||||||
db.execSQL(CREATE_TABLE_APPS)
|
db.execSQL(CREATE_TABLE_APPS)
|
||||||
}
|
}
|
||||||
@ -20,19 +35,21 @@ class Database(val context: Context) :
|
|||||||
while (v < newVersion) {
|
while (v < newVersion) {
|
||||||
when (v) {
|
when (v) {
|
||||||
1 -> db?.execSQL(UPGRADE_1_2)
|
1 -> db?.execSQL(UPGRADE_1_2)
|
||||||
|
2 -> db?.execSQL(UPGRADE_2_3)
|
||||||
else -> throw IllegalStateException("Upgrade not supported")
|
else -> throw IllegalStateException("Upgrade not supported")
|
||||||
}
|
}
|
||||||
v++
|
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 db = writableDatabase
|
||||||
val values = ContentValues().apply {
|
val values = ContentValues().apply {
|
||||||
put(FIELD_PACKAGE_NAME, packageName)
|
put(FIELD_PACKAGE_NAME, packageName)
|
||||||
put(FIELD_CONNECTOR_TOKEN, connectorToken)
|
put(FIELD_CONNECTOR_TOKEN, connectorToken)
|
||||||
put(FIELD_APP_TOKEN, appToken)
|
put(FIELD_APP_TOKEN, appToken)
|
||||||
put(FIELD_NOTIFICATION_TITLE, title) // Used for non-UnifiedPush notif
|
put(FIELD_NOTIFICATION_TITLE, title) // Used for non-UnifiedPush notif
|
||||||
|
put(FIELD_VAPID, vapidKey)
|
||||||
}
|
}
|
||||||
db.insert(TABLE_APPS, null, values)
|
db.insert(TABLE_APPS, null, values)
|
||||||
RegistrationCountCache.refresh(context)
|
RegistrationCountCache.refresh(context)
|
||||||
@ -46,20 +63,32 @@ class Database(val context: Context) :
|
|||||||
RegistrationCountCache.refresh(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 db = readableDatabase
|
||||||
val selection = "$FIELD_PACKAGE_NAME = ? AND $FIELD_CONNECTOR_TOKEN = ?"
|
val projection = arrayOf(FIELD_PACKAGE_NAME, FIELD_VAPID)
|
||||||
val selectionArgs = arrayOf(packageName, connectorToken)
|
val selection = "$FIELD_CONNECTOR_TOKEN = ?"
|
||||||
|
val selectionArgs = arrayOf(connectorToken)
|
||||||
return db.query(
|
return db.query(
|
||||||
TABLE_APPS,
|
TABLE_APPS,
|
||||||
null,
|
projection,
|
||||||
selection,
|
selection,
|
||||||
selectionArgs,
|
selectionArgs,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null
|
null
|
||||||
).use { cursor ->
|
).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_CONNECTOR_TOKEN = "connectorToken"
|
||||||
private const val FIELD_APP_TOKEN = "appToken"
|
private const val FIELD_APP_TOKEN = "appToken"
|
||||||
private const val FIELD_NOTIFICATION_TITLE = "notificationTitle" // Used for non-UnifiedPush notif
|
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 (" +
|
private const val CREATE_TABLE_APPS = "CREATE TABLE $TABLE_APPS (" +
|
||||||
"$FIELD_PACKAGE_NAME TEXT," +
|
"$FIELD_PACKAGE_NAME TEXT," +
|
||||||
"$FIELD_CONNECTOR_TOKEN TEXT," +
|
"$FIELD_CONNECTOR_TOKEN TEXT," +
|
||||||
"$FIELD_APP_TOKEN TEXT," +
|
"$FIELD_APP_TOKEN TEXT," +
|
||||||
"$FIELD_NOTIFICATION_TITLE TEXT," +
|
"$FIELD_NOTIFICATION_TITLE TEXT," +
|
||||||
|
"$FIELD_VAPID TEXT," +
|
||||||
"PRIMARY KEY ($FIELD_CONNECTOR_TOKEN));"
|
"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_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)
|
private val db: AtomicReference<Database?> = AtomicReference(null)
|
||||||
|
|
||||||
|
@ -8,9 +8,9 @@ import java.util.UUID
|
|||||||
|
|
||||||
object LocalNotification {
|
object LocalNotification {
|
||||||
fun createChannel(context: Context, title: String, block: () -> Unit) {
|
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 {
|
nextpushToken?.let {
|
||||||
getDb(context).registerApp(context.packageName, UUID.randomUUID().toString(), it, title)
|
getDb(context).registerApp(context.packageName, UUID.randomUUID().toString(), it, title, null)
|
||||||
}
|
}
|
||||||
block()
|
block()
|
||||||
}
|
}
|
||||||
|
@ -147,6 +147,7 @@ class Api(context: Context) {
|
|||||||
|
|
||||||
fun apiCreateApp(
|
fun apiCreateApp(
|
||||||
appName: String,
|
appName: String,
|
||||||
|
vapid: String?,
|
||||||
block: (String?) -> Unit
|
block: (String?) -> Unit
|
||||||
) {
|
) {
|
||||||
tryWithDeviceId { deviceId ->
|
tryWithDeviceId { deviceId ->
|
||||||
@ -155,6 +156,9 @@ class Api(context: Context) {
|
|||||||
"deviceId" to deviceId,
|
"deviceId" to deviceId,
|
||||||
"appName" to appName
|
"appName" to appName
|
||||||
)
|
)
|
||||||
|
vapid?.let {
|
||||||
|
parameters.put("vapid", it)
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
withApiProvider { apiProvider, then ->
|
withApiProvider { apiProvider, then ->
|
||||||
apiProvider.createApp(parameters)
|
apiProvider.createApp(parameters)
|
||||||
|
@ -3,6 +3,7 @@ package org.unifiedpush.distributor.nextpush.distributor
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import org.unifiedpush.distributor.nextpush.Database
|
||||||
import org.unifiedpush.distributor.nextpush.Database.Companion.getDb
|
import org.unifiedpush.distributor.nextpush.Database.Companion.getDb
|
||||||
import org.unifiedpush.distributor.nextpush.LocalNotification
|
import org.unifiedpush.distributor.nextpush.LocalNotification
|
||||||
import org.unifiedpush.distributor.nextpush.account.AccountFactory.getAccount
|
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)
|
val db = getDb(context)
|
||||||
if (connectorToken !in db.listTokens()) {
|
try {
|
||||||
return ConnectorTokenValidity.TOKEN_NEW
|
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 RegistrationStatus.REGISTERED_OK
|
||||||
return ConnectorTokenValidity.TOKEN_REGISTERED_OK
|
|
||||||
}
|
|
||||||
return ConnectorTokenValidity.TOKEN_NOK
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteDevice(context: Context, block: () -> Unit = {}) {
|
fun deleteDevice(context: Context, block: () -> Unit = {}) {
|
||||||
@ -119,17 +124,17 @@ object Distributor {
|
|||||||
*
|
*
|
||||||
* [block]'s parameter is `true` if we have successfully created the registration.
|
* [block]'s parameter is `true` if we have successfully created the registration.
|
||||||
*/
|
*/
|
||||||
fun createApp(context: Context, appName: String, connectorToken: String, block: (Boolean) -> Unit) {
|
fun createApp(context: Context, appName: String, connectorToken: String, vapid: String?, block: (Boolean) -> Unit) {
|
||||||
Api(context).apiCreateApp(appName) { nextpushToken ->
|
Api(context).apiCreateApp(appName, vapid) { nextpushToken ->
|
||||||
nextpushToken?.let {
|
nextpushToken?.let {
|
||||||
getDb(context).registerApp(appName, connectorToken, it)
|
getDb(context).registerApp(appName, connectorToken, it, null, vapid)
|
||||||
block(true)
|
block(true)
|
||||||
} ?: block(false)
|
} ?: block(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteApp(context: Context, connectorToken: String, block: () -> Unit) {
|
fun deleteApp(context: Context, connectorToken: String, sendIntent: Boolean = true, block: () -> Unit) {
|
||||||
sendUnregistered(context, connectorToken)
|
if (sendIntent) sendUnregistered(context, connectorToken)
|
||||||
val db = getDb(context)
|
val db = getDb(context)
|
||||||
db.getAppToken(
|
db.getAppToken(
|
||||||
connectorToken
|
connectorToken
|
||||||
|
@ -3,13 +3,16 @@ package org.unifiedpush.distributor.nextpush.distributor
|
|||||||
/**
|
/**
|
||||||
* Validity of a connection token received during registration.
|
* Validity of a connection token received during registration.
|
||||||
*/
|
*/
|
||||||
enum class ConnectorTokenValidity {
|
enum class RegistrationStatus {
|
||||||
/** This is a new token. */
|
/** This is a new token. */
|
||||||
TOKEN_NEW,
|
NEW,
|
||||||
|
|
||||||
/** This is a known token, which match the provided application. */
|
/** 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. */
|
/** 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_APPLICATION = "application"
|
||||||
const val EXTRA_PI = "pi"
|
const val EXTRA_PI = "pi"
|
||||||
const val EXTRA_TOKEN = "token"
|
const val EXTRA_TOKEN = "token"
|
||||||
|
const val EXTRA_VAPID = "vapid"
|
||||||
const val EXTRA_ENDPOINT = "endpoint"
|
const val EXTRA_ENDPOINT = "endpoint"
|
||||||
const val EXTRA_FAILED_REASON = "reason"
|
const val EXTRA_FAILED_REASON = "reason"
|
||||||
const val EXTRA_BYTES_MESSAGE = "bytesMessage"
|
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.WakeLock
|
||||||
import org.unifiedpush.distributor.nextpush.account.AccountFactory
|
import org.unifiedpush.distributor.nextpush.account.AccountFactory
|
||||||
import org.unifiedpush.distributor.nextpush.distributor.* // ktlint-disable no-wildcard-imports
|
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.createApp
|
||||||
import org.unifiedpush.distributor.nextpush.distributor.Distributor.deleteApp
|
import org.unifiedpush.distributor.nextpush.distributor.Distributor.deleteApp
|
||||||
import org.unifiedpush.distributor.nextpush.distributor.Distributor.sendEndpoint
|
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")
|
Log.w(TAG, "Trying to register an app without packageName")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
onRegister(context, connectorToken, application)
|
val vapid = intent.getStringExtra(EXTRA_VAPID)?.trim()?.takeIf { it.length == 87 }
|
||||||
|
onRegister(context, connectorToken, application, vapid)
|
||||||
}
|
}
|
||||||
ACTION_UNREGISTER -> {
|
ACTION_UNREGISTER -> {
|
||||||
Log.i(TAG, "UNREGISTER")
|
Log.i(TAG, "UNREGISTER")
|
||||||
@ -126,12 +127,13 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
|
|||||||
/**
|
/**
|
||||||
* Register the app on the server and send the new endpoint
|
* 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)) {
|
if (!AppCompanion.createQueue.containsTokenElseAdd(connectorToken)) {
|
||||||
when (checkToken(context, connectorToken, application)) {
|
when (checkRegistration(context, connectorToken, application, vapid)) {
|
||||||
ConnectorTokenValidity.TOKEN_REGISTERED_OK -> onRegisterKnownToken(context, connectorToken)
|
RegistrationStatus.REGISTERED_OK -> onRegisterKnownToken(context, connectorToken)
|
||||||
ConnectorTokenValidity.TOKEN_NOK -> onRegisterNokToken(context, connectorToken, application)
|
RegistrationStatus.ERROR -> onRegisterNokToken(context, connectorToken, application)
|
||||||
ConnectorTokenValidity.TOKEN_NEW -> onRegisterNewToken(context, connectorToken, application)
|
RegistrationStatus.UPDATED -> onRegisterUpdatedToken(context, connectorToken, application, vapid)
|
||||||
|
RegistrationStatus.NEW -> onRegisterNewToken(context, connectorToken, application, vapid)
|
||||||
}
|
}
|
||||||
AppCompanion.createQueue.removeToken(connectorToken)
|
AppCompanion.createQueue.removeToken(connectorToken)
|
||||||
} else {
|
} 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]
|
* 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
|
val appName = context.getApplicationName(application) ?: application
|
||||||
when {
|
when {
|
||||||
AccountFactory.getAccount(context)?.connected != true -> registrationFailedWithToast(
|
AccountFactory.getAccount(context)?.connected != true -> registrationFailedWithToast(
|
||||||
@ -184,13 +198,16 @@ class RegisterBroadcastReceiver : BroadcastReceiver() {
|
|||||||
else -> createApp(
|
else -> createApp(
|
||||||
context,
|
context,
|
||||||
application,
|
application,
|
||||||
connectorToken
|
connectorToken,
|
||||||
|
vapid
|
||||||
) { success ->
|
) { success ->
|
||||||
when (success) {
|
when (success) {
|
||||||
true -> {
|
true -> {
|
||||||
sendEndpoint(context, connectorToken)
|
sendEndpoint(context, connectorToken)
|
||||||
Toast.makeText(context, "$appName registered.", Toast.LENGTH_SHORT)
|
if (toastOnSuccess) {
|
||||||
.show()
|
Toast.makeText(context, "$appName registered.", Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
false -> registrationFailedWithToast(
|
false -> registrationFailedWithToast(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user