Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions firebase-sessions/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Unreleased

* [changed] Use Dagger for dependency injection
* [changed] Updated datastore dependency to `1.1.3` to
fix [CVE-2024-7254](https://github.com/advisories/GHSA-735f-pc8j-v9w8).

Expand Down
10 changes: 9 additions & 1 deletion firebase-sessions/firebase-sessions.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

plugins {
id("firebase-library")
id("firebase-vendor")
id("kotlin-android")
id("kotlin-kapt")
}
Expand Down Expand Up @@ -67,12 +68,18 @@ dependencies {
exclude(group = "com.google.firebase", module = "firebase-common")
exclude(group = "com.google.firebase", module = "firebase-components")
}
implementation("com.google.android.datatransport:transport-api:3.2.0")

api("com.google.firebase:firebase-annotations:16.2.0")
api("com.google.firebase:firebase-encoders:17.0.0")
api("com.google.firebase:firebase-encoders-json:18.0.1")

implementation("com.google.android.datatransport:transport-api:3.2.0")
implementation(libs.javax.inject)
implementation(libs.androidx.annotation)
implementation(libs.androidx.datastore.preferences)

vendor(libs.dagger.dagger) { exclude(group = "javax.inject", module = "javax.inject") }

compileOnly(libs.errorprone.annotations)

runtimeOnly("com.google.firebase:firebase-installations:18.0.0") {
Expand All @@ -85,6 +92,7 @@ dependencies {
}

kapt(project(":encoders:firebase-encoders-processor"))
kapt(libs.dagger.compiler)

testImplementation(project(":integ-testing")) {
exclude(group = "com.google.firebase", module = "firebase-common")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import com.google.android.datatransport.Encoding
import com.google.android.datatransport.Event
import com.google.android.datatransport.TransportFactory
import com.google.firebase.inject.Provider
import javax.inject.Inject
import javax.inject.Singleton

/**
* The [EventGDTLoggerInterface] is for testing purposes so that we can mock EventGDTLogger in other
Expand All @@ -38,19 +40,17 @@ internal fun interface EventGDTLoggerInterface {
*
* @hide
*/
internal class EventGDTLogger(private val transportFactoryProvider: Provider<TransportFactory>) :
@Singleton
internal class EventGDTLogger
@Inject
constructor(private val transportFactoryProvider: Provider<TransportFactory>) :
EventGDTLoggerInterface {

// Logs a [SessionEvent] to FireLog
override fun log(sessionEvent: SessionEvent) {
transportFactoryProvider
.get()
.getTransport(
AQS_LOG_SOURCE,
SessionEvent::class.java,
Encoding.of("json"),
this::encode,
)
.getTransport(AQS_LOG_SOURCE, SessionEvent::class.java, Encoding.of("json"), this::encode)
.send(Event.ofData(sessionEvent))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,24 @@ import android.app.Application
import android.util.Log
import com.google.firebase.Firebase
import com.google.firebase.FirebaseApp
import com.google.firebase.annotations.concurrent.Background
import com.google.firebase.app
import com.google.firebase.sessions.api.FirebaseSessionsDependencies
import com.google.firebase.sessions.settings.SessionsSettings
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

/** Responsible for initializing AQS */
internal class FirebaseSessions(
@Singleton
internal class FirebaseSessions
@Inject
constructor(
private val firebaseApp: FirebaseApp,
private val settings: SessionsSettings,
backgroundDispatcher: CoroutineContext,
@Background backgroundDispatcher: CoroutineContext,
lifecycleServiceBinder: SessionLifecycleServiceBinder,
) {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.firebase.sessions

import android.content.Context
import com.google.android.datatransport.TransportFactory
import com.google.firebase.FirebaseApp
import com.google.firebase.annotations.concurrent.Background
import com.google.firebase.annotations.concurrent.Blocking
import com.google.firebase.inject.Provider
import com.google.firebase.installations.FirebaseInstallationsApi
import com.google.firebase.sessions.settings.SessionsSettings
import dagger.Binds
import dagger.BindsInstance
import dagger.Component
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
import kotlin.coroutines.CoroutineContext

/** Dagger component to provide [FirebaseSessions] and its dependencies. */
@Singleton
@Component(modules = [FirebaseSessionsComponent.MainModule::class])
internal interface FirebaseSessionsComponent {
val firebaseSessions: FirebaseSessions

val sessionDatastore: SessionDatastore
val sessionFirelogPublisher: SessionFirelogPublisher
val sessionGenerator: SessionGenerator
val sessionsSettings: SessionsSettings

@Component.Builder
interface Builder {
@BindsInstance fun appContext(appContext: Context): Builder

@BindsInstance
fun backgroundDispatcher(@Background backgroundDispatcher: CoroutineContext): Builder

@BindsInstance fun blockingDispatcher(@Blocking blockingDispatcher: CoroutineContext): Builder

@BindsInstance fun firebaseApp(firebaseApp: FirebaseApp): Builder

@BindsInstance
fun firebaseInstallationsApi(firebaseInstallationsApi: FirebaseInstallationsApi): Builder

@BindsInstance
fun transportFactoryProvider(transportFactoryProvider: Provider<TransportFactory>): Builder

fun build(): FirebaseSessionsComponent
}

@Module
interface MainModule {
@Binds @Singleton fun eventGDTLoggerInterface(impl: EventGDTLogger): EventGDTLoggerInterface

@Binds @Singleton fun sessionDatastore(impl: SessionDatastoreImpl): SessionDatastore

@Binds
@Singleton
fun sessionFirelogPublisher(impl: SessionFirelogPublisherImpl): SessionFirelogPublisher

@Binds
@Singleton
fun sessionLifecycleServiceBinder(
impl: SessionLifecycleServiceBinderImpl
): SessionLifecycleServiceBinder

companion object {
@Provides @Singleton fun sessionGenerator() = SessionGenerator(timeProvider = WallClock)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.firebase.sessions

import android.content.Context
import androidx.annotation.Keep
import com.google.android.datatransport.TransportFactory
import com.google.firebase.FirebaseApp
Expand All @@ -28,7 +29,6 @@ import com.google.firebase.components.Qualified.qualified
import com.google.firebase.components.Qualified.unqualified
import com.google.firebase.installations.FirebaseInstallationsApi
import com.google.firebase.platforminfo.LibraryVersionComponent
import com.google.firebase.sessions.settings.SessionsSettings
import kotlinx.coroutines.CoroutineDispatcher

/**
Expand All @@ -42,87 +42,41 @@ internal class FirebaseSessionsRegistrar : ComponentRegistrar {
listOf(
Component.builder(FirebaseSessions::class.java)
.name(LIBRARY_NAME)
.add(Dependency.required(firebaseApp))
.add(Dependency.required(sessionsSettings))
.add(Dependency.required(backgroundDispatcher))
.add(Dependency.required(sessionLifecycleServiceBinder))
.factory { container ->
FirebaseSessions(
container[firebaseApp],
container[sessionsSettings],
container[backgroundDispatcher],
container[sessionLifecycleServiceBinder],
)
}
.add(Dependency.required(firebaseSessionsComponent))
.factory { container -> container[firebaseSessionsComponent].firebaseSessions }
.eagerInDefaultApp()
.build(),
Component.builder(SessionGenerator::class.java)
.name("session-generator")
.factory { SessionGenerator(timeProvider = WallClock) }
.build(),
Component.builder(SessionFirelogPublisher::class.java)
.name("session-publisher")
.add(Dependency.required(firebaseApp))
.add(Dependency.required(firebaseInstallationsApi))
.add(Dependency.required(sessionsSettings))
.add(Dependency.requiredProvider(transportFactory))
Component.builder(FirebaseSessionsComponent::class.java)
.name("fire-sessions-component")
.add(Dependency.required(appContext))
.add(Dependency.required(backgroundDispatcher))
.factory { container ->
SessionFirelogPublisherImpl(
container[firebaseApp],
container[firebaseInstallationsApi],
container[sessionsSettings],
EventGDTLogger(container.getProvider(transportFactory)),
container[backgroundDispatcher],
)
}
.build(),
Component.builder(SessionsSettings::class.java)
.name("sessions-settings")
.add(Dependency.required(firebaseApp))
.add(Dependency.required(blockingDispatcher))
.add(Dependency.required(backgroundDispatcher))
.add(Dependency.required(firebaseInstallationsApi))
.factory { container ->
SessionsSettings(
container[firebaseApp],
container[blockingDispatcher],
container[backgroundDispatcher],
container[firebaseInstallationsApi],
)
}
.build(),
Component.builder(SessionDatastore::class.java)
.name("sessions-datastore")
.add(Dependency.required(firebaseApp))
.add(Dependency.required(backgroundDispatcher))
.add(Dependency.required(firebaseInstallationsApi))
.add(Dependency.requiredProvider(transportFactory))
.factory { container ->
SessionDatastoreImpl(
container[firebaseApp].applicationContext,
container[backgroundDispatcher],
)
DaggerFirebaseSessionsComponent.builder()
.appContext(container[appContext])
.backgroundDispatcher(container[backgroundDispatcher])
.blockingDispatcher(container[blockingDispatcher])
.firebaseApp(container[firebaseApp])
.firebaseInstallationsApi(container[firebaseInstallationsApi])
.transportFactoryProvider(container.getProvider(transportFactory))
.build()
}
.build(),
Component.builder(SessionLifecycleServiceBinder::class.java)
.name("sessions-service-binder")
.add(Dependency.required(firebaseApp))
.factory { container -> SessionLifecycleServiceBinderImpl(container[firebaseApp]) }
.build(),
LibraryVersionComponent.create(LIBRARY_NAME, BuildConfig.VERSION_NAME),
)

private companion object {
private const val LIBRARY_NAME = "fire-sessions"
const val LIBRARY_NAME = "fire-sessions"

private val firebaseApp = unqualified(FirebaseApp::class.java)
private val firebaseInstallationsApi = unqualified(FirebaseInstallationsApi::class.java)
private val backgroundDispatcher =
qualified(Background::class.java, CoroutineDispatcher::class.java)
private val blockingDispatcher =
qualified(Blocking::class.java, CoroutineDispatcher::class.java)
private val transportFactory = unqualified(TransportFactory::class.java)
private val sessionsSettings = unqualified(SessionsSettings::class.java)
private val sessionLifecycleServiceBinder =
unqualified(SessionLifecycleServiceBinder::class.java)
val appContext = unqualified(Context::class.java)
val firebaseApp = unqualified(FirebaseApp::class.java)
val firebaseInstallationsApi = unqualified(FirebaseInstallationsApi::class.java)
val backgroundDispatcher = qualified(Background::class.java, CoroutineDispatcher::class.java)
val blockingDispatcher = qualified(Blocking::class.java, CoroutineDispatcher::class.java)
val transportFactory = unqualified(TransportFactory::class.java)
val firebaseSessionsComponent = unqualified(FirebaseSessionsComponent::class.java)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ import androidx.datastore.preferences.core.emptyPreferences
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import com.google.firebase.Firebase
import com.google.firebase.annotations.concurrent.Background
import com.google.firebase.app
import com.google.firebase.sessions.ProcessDetailsProvider.getProcessName
import java.io.IOException
import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
Expand All @@ -53,13 +56,16 @@ internal interface SessionDatastore {

companion object {
val instance: SessionDatastore
get() = Firebase.app[SessionDatastore::class.java]
get() = Firebase.app[FirebaseSessionsComponent::class.java].sessionDatastore
}
}

internal class SessionDatastoreImpl(
private val context: Context,
private val backgroundDispatcher: CoroutineContext,
@Singleton
internal class SessionDatastoreImpl
@Inject
constructor(
private val appContext: Context,
@Background private val backgroundDispatcher: CoroutineContext,
) : SessionDatastore {

/** Most recent session from datastore is updated asynchronously whenever it changes */
Expand All @@ -70,7 +76,7 @@ internal class SessionDatastoreImpl(
}

private val firebaseSessionDataFlow: Flow<FirebaseSessionsData> =
context.dataStore.data
appContext.dataStore.data
.catch { exception ->
Log.e(TAG, "Error reading stored session data.", exception)
emit(emptyPreferences())
Expand All @@ -86,24 +92,19 @@ internal class SessionDatastoreImpl(
override fun updateSessionId(sessionId: String) {
CoroutineScope(backgroundDispatcher).launch {
try {
context.dataStore.edit { preferences ->
appContext.dataStore.edit { preferences ->
preferences[FirebaseSessionDataKeys.SESSION_ID] = sessionId
}
} catch (e: IOException) {
Log.w(
TAG,
"Failed to update session Id: $e",
)
Log.w(TAG, "Failed to update session Id: $e")
}
}
}

override fun getCurrentSessionId() = currentSessionFromDatastore.get()?.sessionId

private fun mapSessionsData(preferences: Preferences): FirebaseSessionsData =
FirebaseSessionsData(
preferences[FirebaseSessionDataKeys.SESSION_ID],
)
FirebaseSessionsData(preferences[FirebaseSessionDataKeys.SESSION_ID])

private companion object {
private const val TAG = "FirebaseSessionsRepo"
Expand Down
Loading
Loading