Füge Unterstützung für die Installation-ID in Push-Token-Registrierung hinzu: Aktualisiere API- und Datenbank-Logik zur Speicherung der Installation-ID.
All checks were successful
Code Analysis and Production Deploy / analyze (push) Successful in 3m36s
Code Analysis and Production Deploy / deploy-production (push) Has been skipped
Code Analysis and Production Deploy / deploy-test (push) Successful in 2m25s

This commit is contained in:
Torsten Schulz (local)
2026-09-09 14:04:41 +02:00
parent 5e8006323b
commit dc08ae98ea
6 changed files with 18 additions and 8 deletions

View File

@@ -283,6 +283,7 @@ data class PushTokenRequest(
val token: String, val token: String,
val platform: String = "android", val platform: String = "android",
val appVersion: String? = null, val appVersion: String? = null,
val installationId: String? = null,
) )
data class BirthdayDto( data class BirthdayDto(
val name: String = "", val name: String = "",

View File

@@ -21,7 +21,7 @@ class HarheimerMessagingService : FirebaseMessagingService() {
override fun onNewToken(token: String) { override fun onNewToken(token: String) {
super.onNewToken(token) super.onNewToken(token)
serviceScope.launch { serviceScope.launch {
pushTokenRepository.registerToken(token) pushTokenRepository.registerCurrentDevice()
} }
} }

View File

@@ -1,6 +1,7 @@
package de.harheimertc.repositories package de.harheimertc.repositories
import android.util.Log import android.util.Log
import com.google.firebase.installations.FirebaseInstallations
import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessaging
import de.harheimertc.BuildConfig import de.harheimertc.BuildConfig
import de.harheimertc.data.ApiService import de.harheimertc.data.ApiService
@@ -15,16 +16,18 @@ class PushTokenRepository @Inject constructor(
) { ) {
suspend fun registerCurrentDevice(): Result<Unit> = runCatching { suspend fun registerCurrentDevice(): Result<Unit> = runCatching {
val token = FirebaseMessaging.getInstance().token.await() val token = FirebaseMessaging.getInstance().token.await()
registerToken(token).getOrThrow() val installationId = FirebaseInstallations.getInstance().id.await()
registerToken(token, installationId).getOrThrow()
} }
suspend fun registerToken(token: String): Result<Unit> = runCatching { suspend fun registerToken(token: String, installationId: String? = null): Result<Unit> = runCatching {
if (token.isBlank()) return@runCatching if (token.isBlank()) return@runCatching
retryOnNetworkFailure { retryOnNetworkFailure {
val response = api.registerPushToken( val response = api.registerPushToken(
PushTokenRequest( PushTokenRequest(
token = token, token = token,
appVersion = "${BuildConfig.VERSION_NAME}+${BuildConfig.VERSION_CODE}", appVersion = "${BuildConfig.VERSION_NAME}+${BuildConfig.VERSION_CODE}",
installationId = installationId,
), ),
) )
if (!response.isSuccessful) { if (!response.isSuccessful) {

View File

@@ -22,7 +22,8 @@ export default defineEventHandler(async (event) => {
upsertPushToken(users[userIndex], { upsertPushToken(users[userIndex], {
token: body.token, token: body.token,
platform: body.platform || 'android', platform: body.platform || 'android',
appVersion: body.appVersion || null appVersion: body.appVersion || null,
installationId: body.installationId || null
}) })
await writeUsers(users) await writeUsers(users)
return { success: true, message: 'Push-Token gespeichert.' } return { success: true, message: 'Push-Token gespeichert.' }

View File

@@ -85,16 +85,21 @@ function pushTokensForUser(user) {
: [] : []
} }
export function upsertPushToken(user, { token, platform = 'android', appVersion = null }) { export function upsertPushToken(user, { token, platform = 'android', appVersion = null, installationId = null }) {
const normalizedToken = String(token || '').trim() const normalizedToken = String(token || '').trim()
if (!normalizedToken) return user if (!normalizedToken) return user
const normalizedInstallationId = String(installationId || '').trim().slice(0, 200) || null
const now = new Date().toISOString() const now = new Date().toISOString()
const tokens = Array.isArray(user.pushTokens) ? user.pushTokens : [] const tokens = Array.isArray(user.pushTokens) ? user.pushTokens : []
const next = tokens.filter(entry => entry?.token !== normalizedToken) // A Firebase token can rotate for the same app installation. Retain tokens
// from other devices, but replace the previous token of this installation.
const next = tokens.filter(entry => entry?.token !== normalizedToken &&
(!normalizedInstallationId || entry?.installationId !== normalizedInstallationId))
next.push({ next.push({
token: normalizedToken, token: normalizedToken,
platform: String(platform || 'android').slice(0, 30), platform: String(platform || 'android').slice(0, 30),
appVersion: appVersion ? String(appVersion).slice(0, 80) : null, appVersion: appVersion ? String(appVersion).slice(0, 80) : null,
installationId: normalizedInstallationId,
updatedAt: now, updatedAt: now,
createdAt: tokens.find(entry => entry?.token === normalizedToken)?.createdAt || now createdAt: tokens.find(entry => entry?.token === normalizedToken)?.createdAt || now
}) })

View File

@@ -247,7 +247,7 @@ describe('Config & Profil Endpoints', () => {
it('speichert Android-Push-Token am Benutzer', async () => { it('speichert Android-Push-Token am Benutzer', async () => {
const event = createEvent({ headers: { authorization: 'Bearer android-token' } }) const event = createEvent({ headers: { authorization: 'Bearer android-token' } })
mockSuccessReadBody({ token: 'fcm-token', platform: 'android', appVersion: '1.0+1' }) mockSuccessReadBody({ token: 'fcm-token', platform: 'android', appVersion: '1.0+1', installationId: 'firebase-installation-id' })
const users = [{ id: '1', email: 'max@test.de', roles: ['mitglied'] }] const users = [{ id: '1', email: 'max@test.de', roles: ['mitglied'] }]
authUtils.verifyToken.mockReturnValue({ id: '1' }) authUtils.verifyToken.mockReturnValue({ id: '1' })
authUtils.getUserFromToken.mockResolvedValue(users[0]) authUtils.getUserFromToken.mockResolvedValue(users[0])
@@ -260,7 +260,7 @@ describe('Config & Profil Endpoints', () => {
expect(authUtils.writeUsers).toHaveBeenCalledWith([ expect(authUtils.writeUsers).toHaveBeenCalledWith([
expect.objectContaining({ expect.objectContaining({
id: '1', id: '1',
pushTokens: [expect.objectContaining({ token: 'fcm-token', platform: 'android', appVersion: '1.0+1' })] pushTokens: [expect.objectContaining({ token: 'fcm-token', platform: 'android', appVersion: '1.0+1', installationId: 'firebase-installation-id' })]
}) })
]) ])
}) })