Skip to content
Closed
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
4 changes: 0 additions & 4 deletions OneSignalSDK/detekt/detekt-baseline-core.xml
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,6 @@
<ID>ConstructorParameterNaming:SessionListener.kt$SessionListener$private val _propertiesModelStore: PropertiesModelStore</ID>
<ID>ConstructorParameterNaming:SessionListener.kt$SessionListener$private val _sessionService: ISessionService</ID>
<ID>ConstructorParameterNaming:SessionManager.kt$SessionManager$private val _outcomeController: IOutcomeEventsController</ID>
<ID>ConstructorParameterNaming:SessionService.kt$SessionService$private val _applicationService: IApplicationService</ID>
<ID>ConstructorParameterNaming:SessionService.kt$SessionService$private val _configModelStore: ConfigModelStore</ID>
<ID>ConstructorParameterNaming:SessionService.kt$SessionService$private val _sessionModelStore: SessionModelStore</ID>
<ID>ConstructorParameterNaming:SessionService.kt$SessionService$private val _time: ITime</ID>
<ID>ConstructorParameterNaming:SimpleModelStore.kt$SimpleModelStore$/** * Will be called whenever a new [TModel] needs to be instantiated. */ private val _create: () -> TModel</ID>
<ID>ConstructorParameterNaming:SimpleModelStore.kt$SimpleModelStore$_prefs: IPreferencesService? = null</ID>
<ID>ConstructorParameterNaming:SubscriptionBackendService.kt$SubscriptionBackendService$private val _httpClient: IHttpClient</ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,10 @@ interface ITime {
* Returns how long the app has been running.
*/
val processUptimeMillis: Long

/**
* Milliseconds since boot, including deep sleep. Monotonic, so safe for measuring
* durations when the wall clock is changed. Resets on reboot.
*/
val elapsedRealtimeMillis: Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ internal class Time : ITime {
override val processUptimeMillis: Long
@RequiresApi(Build.VERSION_CODES.N)
get() = SystemClock.uptimeMillis() - android.os.Process.getStartUptimeMillis()
override val elapsedRealtimeMillis: Long
get() = SystemClock.elapsedRealtime()
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import com.onesignal.user.UserModule
import com.onesignal.user.internal.LoginHelper
import com.onesignal.user.internal.LogoutHelper
import com.onesignal.user.internal.UserSwitcher
import com.onesignal.user.internal.applyResolvedAppId
import com.onesignal.user.internal.identity.IdentityModelStore
import com.onesignal.user.internal.jwt.JwtTokenStore
import com.onesignal.user.internal.properties.PropertiesModelStore
Expand Down Expand Up @@ -448,7 +449,7 @@ internal class OneSignalImp : IOneSignal,
completeInit(InitState.FAILED)
return false
}
configModel.appId = result.appId!! // safe because failed is false
applyResolvedAppId(configModel, result.appId!!) // safe because failed is false
val forceCreateUser = result.forceCreateUser

updateConfig()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ import com.onesignal.session.internal.outcomes.impl.OutcomeEventsPreferences
import com.onesignal.session.internal.outcomes.impl.OutcomeEventsRepository
import com.onesignal.session.internal.session.ISessionService
import com.onesignal.session.internal.session.SessionModelStore
import com.onesignal.session.internal.session.backend.ISessionsBackendService
import com.onesignal.session.internal.session.backend.impl.SessionsBackendService
import com.onesignal.session.internal.session.impl.SessionListener
import com.onesignal.session.internal.session.impl.SessionService
import com.onesignal.session.internal.session.queue.ISessionsApiQueue
import com.onesignal.session.internal.session.queue.SessionRecordStore
import com.onesignal.session.internal.session.queue.impl.SessionsApiQueue

internal class SessionModule : IModule {
override fun register(builder: ServiceBuilder) {
Expand All @@ -43,5 +48,10 @@ internal class SessionModule : IModule {
.provides<IBootstrapService>()
builder.register<SessionListener>().provides<IStartableService>()
builder.register<SessionManager>().provides<ISessionManager>()
builder.register<SessionsBackendService>().provides<ISessionsBackendService>()
builder.register<SessionRecordStore>().provides<SessionRecordStore>()
builder.register<SessionsApiQueue>()
.provides<ISessionsApiQueue>()
.provides<IStartableService>()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,36 @@ class SessionModel : Model() {
set(value) {
setLongProperty(::activeDuration.name, value)
}

/**
* [focusTime] from a monotonic clock ([com.onesignal.core.internal.time.ITime.elapsedRealtimeMillis]).
* Used instead of [focusTime] to measure [activeDuration] when [usesSessionsApi].
*/
var focusElapsedRealtime: Long
get() = getLongProperty(::focusElapsedRealtime.name) { 0L }
set(value) {
setLongProperty(::focusElapsedRealtime.name, value)
}

/**
* Whether this session reports through the sessions API instead of the legacy session paths.
* Decided by [com.onesignal.features.FeatureFlag.SDK_SESSIONS_V2_API_CUTOVER] when the session
* starts and fixed until it ends, so a session never mixes paths. Read this rather than the
* feature manager, whose value can change mid-session.
*/
var usesSessionsApi: Boolean
get() = getBooleanProperty(::usesSessionsApi.name) { false }
set(value) {
setBooleanProperty(::usesSessionsApi.name, value)
}

/**
* When this app was last unfocused, in Unix time milliseconds. Earlier than [focusTime] if
* the app was killed while focused.
*/
var unfocusTime: Long
get() = getLongProperty(::unfocusTime.name) { 0L }
set(value) {
setLongProperty(::unfocusTime.name, value)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.onesignal.session.internal.session.backend

import com.onesignal.common.exceptions.BackendException

/**
* The backend service for the public sessions API.
*
* If there is a non-successful response from the backend, a [BackendException] will be thrown with response data.
*/
internal interface ISessionsBackendService {
/**
* Create a session.
*
* @return The backend session ID, or null if the success response did not include one.
*/
suspend fun createSession(
appId: String,
request: CreateSessionRequest,
): String?

/**
* Update an existing session with its cumulative duration, and end it when [UpdateSessionRequest.endTime] is set.
*/
suspend fun updateSession(
appId: String,
sessionId: String,
request: UpdateSessionRequest,
)
}

internal data class CreateSessionRequest(
val onesignalId: String,
val subscriptionId: String,
val deviceType: Int,
/** Unix time in seconds, sent as ISO 8601 UTC. */
val startTime: Long,
val idempotencyKey: String,
val directAttributionId: String? = null,
)

internal data class UpdateSessionRequest(
val onesignalId: String,
val subscriptionId: String,
val durationSeconds: Long,
val idempotencyKey: String,
/** Unix time in seconds, sent as ISO 8601 UTC. */
val endTime: Long? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.onesignal.session.internal.session.backend.impl

import com.onesignal.common.DateUtils
import com.onesignal.common.exceptions.BackendException
import com.onesignal.core.internal.http.IHttpClient
import com.onesignal.debug.internal.logging.Logging
import com.onesignal.session.internal.session.backend.CreateSessionRequest
import com.onesignal.session.internal.session.backend.ISessionsBackendService
import com.onesignal.session.internal.session.backend.UpdateSessionRequest
import org.json.JSONException
import org.json.JSONObject
import java.util.Date

internal class SessionsBackendService(
private val httpClient: IHttpClient,
) : ISessionsBackendService {
override suspend fun createSession(
appId: String,
request: CreateSessionRequest,
): String? {
val body =
JSONObject()
.put("onesignal_id", request.onesignalId)
.put("subscription_id", request.subscriptionId)
.put("device_type", request.deviceType)
.put("start_time", toIso8601(request.startTime))
.put("idempotency_key", request.idempotencyKey)
request.directAttributionId?.let { body.put("direct_attribution_id", it) }

val response = httpClient.post("apps/$appId/sessions", body)
if (!response.isSuccess) {
throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds)
}

return parseSessionId(response.payload)
}

override suspend fun updateSession(
appId: String,
sessionId: String,
request: UpdateSessionRequest,
) {
val body =
JSONObject()
.put("onesignal_id", request.onesignalId)
.put("subscription_id", request.subscriptionId)
.put("duration_seconds", request.durationSeconds)
.put("idempotency_key", request.idempotencyKey)
request.endTime?.let { body.put("end_time", toIso8601(it)) }

val response = httpClient.patch("apps/$appId/sessions/$sessionId", body)
if (!response.isSuccess) {
throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds)
}
}

private fun toIso8601(epochSeconds: Long): String = DateUtils.iso8601Format().format(Date(epochSeconds * MILLIS_PER_SECOND))

private fun parseSessionId(payload: String?): String? =
try {
val sessionId = payload?.let { JSONObject(it) }?.optJSONObject("data")?.opt("session_id") as? String
sessionId?.takeIf { it.isNotEmpty() }
} catch (e: JSONException) {
Logging.warn("SessionsBackendService: unable to parse create session response", e)
null
}

private companion object {
const val MILLIS_PER_SECOND = 1000L
}
}
Loading
Loading