Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -131,24 +131,19 @@ internal class OneSignalImp : IOneSignal,
override val debug: IDebugManager = DebugManager()

override val session: ISessionManager
get() =
waitAndReturn { services.getService() }
get() = if (isReadyFor("Session")) services.getService() else UninitializedSessionManager

override val notifications: INotificationsManager
get() =
waitAndReturn { services.getService() }
get() = if (isReadyFor("Notifications")) services.getService() else UninitializedNotificationsManager

override val location: ILocationManager
get() =
waitAndReturn { services.getService() }
get() = if (isReadyFor("Location")) services.getService() else UninitializedLocationManager

override val inAppMessages: IInAppMessagesManager
get() =
waitAndReturn { services.getService() }
get() = if (isReadyFor("InAppMessages")) services.getService() else UninitializedInAppMessagesManager

override val user: IUserManager
get() =
waitAndReturn { services.getService() }
get() = if (isReadyFor("User")) services.getService() else UninitializedUserManager

// Services required by this class
// WARNING: OperationRepo depends on OperationModelStore which in-turn depends
Expand Down Expand Up @@ -491,9 +486,7 @@ internal class OneSignalImp : IOneSignal,
) {
Logging.log(LogLevel.DEBUG, "Calling deprecated login(externalId: $externalId, jwtBearerToken: ...${jwtBearerToken?.takeLast(8)})")

waitForInit(operationName = "login")

if (isMissing(externalId, "login: externalId")) return
if (!isReadyFor("login") || isMissing(externalId, "login: externalId")) return

val context = loginHelper.switchUser(externalId, jwtBearerToken).context ?: return

Expand All @@ -503,7 +496,7 @@ internal class OneSignalImp : IOneSignal,
override fun logout() {
Logging.log(LogLevel.DEBUG, "Calling deprecated logout()")

waitForInit(operationName = "logout")
if (!isReadyFor("logout")) return

val context = logoutHelper.switchUser() ?: return

Expand All @@ -516,7 +509,7 @@ internal class OneSignalImp : IOneSignal,
) {
Logging.log(LogLevel.DEBUG, "updateUserJwt(externalId: $externalId, token: ...${token.takeLast(8)})")

waitForInit(operationName = "updateUserJwt")
if (!isReadyFor("updateUserJwt")) return

if (isMissing(externalId, "updateUserJwt: externalId") || isMissing(token, "updateUserJwt: token")) return

Expand All @@ -526,12 +519,12 @@ internal class OneSignalImp : IOneSignal,
}

override fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
waitForInit(operationName = "addUserJwtInvalidatedListener")
if (!isReadyFor("addUserJwtInvalidatedListener")) return
jwtTokenStore.addUserJwtInvalidatedListener(listener)
}

override fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
waitForInit(operationName = "removeUserJwtInvalidatedListener")
if (!isReadyFor("removeUserJwtInvalidatedListener")) return
jwtTokenStore.removeUserJwtInvalidatedListener(listener)
}

Expand All @@ -543,6 +536,30 @@ internal class OneSignalImp : IOneSignal,

override fun <T> getAllServices(c: Class<T>): List<T> = services.getAllServices(c)

// Log and return false instead of throwing when init never started or failed, so callers can
// skip the call or hand out a stand-in manager.
private fun isReadyFor(operationName: String): Boolean =
ignoreIfNotInitialized(operationName) { waitForInit(operationName) } == null

private suspend fun isReadyForSuspend(operationName: String): Boolean =
ignoreIfNotInitialized(operationName) { suspendUntilInit(operationName) } == null

/** Runs [wait] and returns the init failure it threw, after logging it, or null once init succeeded. */
private inline fun ignoreIfNotInitialized(
operationName: String,
wait: () -> Unit,
): IllegalStateException? =
try {
wait()
null
} catch (e: CancellationException) {
// A subclass of IllegalStateException, so it must be rethrown before the catch below.
throw e
} catch (e: IllegalStateException) {
Logging.error("'$operationName' ignored: ${e.message}", e)
e
}

/**
* Blocking version that waits for initialization to complete.
* Uses runBlocking to bridge to the suspend implementation.
Expand Down Expand Up @@ -680,13 +697,6 @@ internal class OneSignalImp : IOneSignal,
return getter()
}

// Blocks until init completes; [waitForInit] throws on NOT_STARTED / FAILED and
// returns once initState == SUCCESS.
private fun <T> waitAndReturn(getter: () -> T): T {
waitForInit()
return getter()
}

private fun <T> blockingGet(getter: () -> T): T {
try {
if (AndroidUtils.isRunningOnMainThread()) {
Expand All @@ -713,27 +723,27 @@ internal class OneSignalImp : IOneSignal,

override suspend fun getSession(): ISessionManager =
withContext(ioDispatcher) {
suspendAndReturn { services.getService() }
if (isReadyForSuspend("Session")) services.getService() else UninitializedSessionManager
}

override suspend fun getNotifications(): INotificationsManager =
withContext(ioDispatcher) {
suspendAndReturn { services.getService() }
if (isReadyForSuspend("Notifications")) services.getService() else UninitializedNotificationsManager
}

override suspend fun getLocation(): ILocationManager =
withContext(ioDispatcher) {
suspendAndReturn { services.getService() }
if (isReadyForSuspend("Location")) services.getService() else UninitializedLocationManager
}

override suspend fun getInAppMessages(): IInAppMessagesManager =
withContext(ioDispatcher) {
suspendAndReturn { services.getService() }
if (isReadyForSuspend("InAppMessages")) services.getService() else UninitializedInAppMessagesManager
}

override suspend fun getUser(): IUserManager =
withContext(ioDispatcher) {
suspendAndReturn { services.getService() }
if (isReadyForSuspend("User")) services.getService() else UninitializedUserManager
}

override suspend fun getConsentRequired(): Boolean =
Expand Down Expand Up @@ -841,9 +851,7 @@ internal class OneSignalImp : IOneSignal,
) = withContext(ioDispatcher) {
Logging.log(LogLevel.DEBUG, "login(externalId: $externalId, jwtBearerToken: ...${jwtBearerToken?.takeLast(8)})")

// suspendUntilInit throws on NOT_STARTED / FAILED (preserving initFailureException as the
// cause), and only returns once initState == SUCCESS — so no post-check is needed here.
suspendUntilInit(operationName = "login")
if (!isReadyForSuspend("login")) return@withContext

if (isMissing(externalId, "login: externalId")) return@withContext

Expand Down Expand Up @@ -898,17 +906,8 @@ internal class OneSignalImp : IOneSignal,

/** Waits for init; returns a NOT_INITIALIZED failure instead of throwing when it never started or failed. */
internal suspend fun awaitInitForLogin(): OneSignalResult<LoginData>? =
try {
suspendUntilInit(operationName = "login")
null
} catch (e: CancellationException) {
// A subclass of IllegalStateException, so it must be rethrown before the catch below.
throw e
} catch (e: IllegalStateException) {
// Callers can ignore the result, so the failure has to show up in the log.
Logging.error("login(externalId, profile) skipped: ${e.message}", e)
OneSignalResult.failure(ErrorCode.NOT_INITIALIZED, e.message, cause = e)
}
ignoreIfNotInitialized("login") { suspendUntilInit(operationName = "login") }
?.let { OneSignalResult.failure(ErrorCode.NOT_INITIALIZED, it.message, cause = it) }

private fun loginFailureMessage(wait: OperationWaitResult): String =
wait.httpStatusCode?.let { "Login did not complete (HTTP $it)." } ?: "Login did not complete."
Expand All @@ -919,11 +918,7 @@ internal class OneSignalImp : IOneSignal,
) = withContext(ioDispatcher) {
Logging.log(LogLevel.DEBUG, "updateUserJwtSuspend(externalId: $externalId, token: ...${token.takeLast(8)})")

suspendUntilInit(operationName = "updateUserJwt")

if (!isInitialized) {
throw IllegalStateException("'initWithContext failed' before 'updateUserJwt'")
}
if (!isReadyForSuspend("updateUserJwt")) return@withContext

if (isMissing(externalId, "updateUserJwt: externalId") || isMissing(token, "updateUserJwt: token")) {
return@withContext
Expand All @@ -937,7 +932,7 @@ internal class OneSignalImp : IOneSignal,
withContext(ioDispatcher) {
Logging.log(LogLevel.DEBUG, "logoutSuspend()")

suspendUntilInit(operationName = "logout")
if (!isReadyForSuspend("logout")) return@withContext

val context = logoutHelper.switchUser() ?: return@withContext
logoutHelper.enqueueLogout(context)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package com.onesignal.internal

import com.onesignal.inAppMessages.IInAppMessageClickListener
import com.onesignal.inAppMessages.IInAppMessageLifecycleListener
import com.onesignal.inAppMessages.IInAppMessagesManager
import com.onesignal.location.ILocationManager
import com.onesignal.notifications.INotificationClickListener
import com.onesignal.notifications.INotificationLifecycleListener
import com.onesignal.notifications.INotificationsManager
import com.onesignal.notifications.IPermissionObserver
import com.onesignal.session.ISessionManager
import com.onesignal.user.IUserManager
import com.onesignal.user.state.IUserStateObserver
import com.onesignal.user.subscriptions.IPushSubscription
import com.onesignal.user.subscriptions.IPushSubscriptionObserver

// Handed out by the accessors when init never started or failed, so calls do nothing instead of
// crashing. The accessor already logged the call, so these stay silent.

@Suppress("TooManyFunctions")
internal object UninitializedUserManager : IUserManager {
override val pushSubscription: IPushSubscription = UninitializedPushSubscription
override val onesignalId = ""
override val externalId = ""

override fun setLanguage(value: String) {}

override fun addAlias(
label: String,
id: String,
) {}

override fun addAliases(aliases: Map<String, String>) {}

override fun removeAlias(label: String) {}

override fun removeAliases(labels: Collection<String>) {}

override fun addEmail(email: String) {}

override fun removeEmail(email: String) {}

override fun addSms(sms: String) {}

override fun removeSms(sms: String) {}

override fun addTag(
key: String,
value: String,
) {}

override fun addTags(tags: Map<String, String>) {}

override fun removeTag(key: String) {}

override fun removeTags(keys: Collection<String>) {}

override fun getTags(): Map<String, String> = emptyMap()

override fun addObserver(observer: IUserStateObserver) {}

override fun removeObserver(observer: IUserStateObserver) {}

override fun trackEvent(
name: String,
properties: Map<String, Any?>?,
) {}
}

internal object UninitializedPushSubscription : IPushSubscription {
override val id = ""
override val token = ""
override val optedIn = false

override fun optIn() {}

override fun optOut() {}

override fun addObserver(observer: IPushSubscriptionObserver) {}

override fun removeObserver(observer: IPushSubscriptionObserver) {}
}

@Suppress("TooManyFunctions")
internal object UninitializedNotificationsManager : INotificationsManager {
override val permission = false
override val canRequestPermission = false

override suspend fun requestPermission(fallbackToSettings: Boolean) = false

override fun removeNotification(id: Int) {}

override fun removeGroupedNotifications(group: String) {}

override fun clearAllNotifications() {}

override fun addPermissionObserver(observer: IPermissionObserver) {}

override fun removePermissionObserver(observer: IPermissionObserver) {}

override fun addForegroundLifecycleListener(listener: INotificationLifecycleListener) {}

override fun removeForegroundLifecycleListener(listener: INotificationLifecycleListener) {}

override fun addClickListener(listener: INotificationClickListener) {}

override fun removeClickListener(listener: INotificationClickListener) {}
}

internal object UninitializedInAppMessagesManager : IInAppMessagesManager {
override var paused: Boolean
get() = false
set(_) {}

override fun addTrigger(
key: String,
value: String,
) {}

override fun addTriggers(triggers: Map<String, String>) {}

override fun removeTrigger(key: String) {}

override fun removeTriggers(keys: Collection<String>) {}

override fun clearTriggers() {}

override fun addLifecycleListener(listener: IInAppMessageLifecycleListener) {}

override fun removeLifecycleListener(listener: IInAppMessageLifecycleListener) {}

override fun addClickListener(listener: IInAppMessageClickListener) {}

override fun removeClickListener(listener: IInAppMessageClickListener) {}
}

internal object UninitializedLocationManager : ILocationManager {
override var isShared: Boolean
get() = false
set(_) {}

override suspend fun requestPermission() = false
}

internal object UninitializedSessionManager : ISessionManager {
override fun addOutcome(name: String) {}

override fun addUniqueOutcome(name: String) {}

override fun addOutcomeWithValue(
name: String,
value: Float,
) {}
}
Loading
Loading