Skip to content
Open
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 @@ -39,7 +39,6 @@ import com.onesignal.user.state.IUserStateObserver
import com.onesignal.user.state.UserChangedState
import com.onesignal.user.state.UserState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
Expand Down
171 changes: 165 additions & 6 deletions OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,140 @@ object OneSignal {
return oneSignal.initWithContextSuspend(context, appId)
}

/**
* Get the user manager without blocking the calling thread.
* Suspends until the SDK is initialized if initialization is in progress.
* This is the suspend-safe version of the [User] property accessor.
*
* @return The user manager for accessing user-scoped management.
*/
@JvmStatic
suspend fun getUserSuspend(): IUserManager {
return oneSignal.getUser()
}

/**
* Get the session manager without blocking the calling thread.
* Suspends until the SDK is initialized if initialization is in progress.
* This is the suspend-safe version of the [Session] property accessor.
*
* @return The session manager for accessing session-scoped management.
*/
@JvmStatic
suspend fun getSessionSuspend(): ISessionManager {
return oneSignal.getSession()
}

/**
* Get the notifications manager without blocking the calling thread.
* Suspends until the SDK is initialized if initialization is in progress.
* This is the suspend-safe version of the [Notifications] property accessor.
*
* @return The notification manager for accessing device-scoped notification management.
*/
@JvmStatic
suspend fun getNotificationsSuspend(): INotificationsManager {
return oneSignal.getNotifications()
}

/**
* Get the location manager without blocking the calling thread.
* Suspends until the SDK is initialized if initialization is in progress.
* This is the suspend-safe version of the [Location] property accessor.
*
* @return The location manager for accessing device-scoped location management.
*/
@JvmStatic
suspend fun getLocationSuspend(): ILocationManager {
return oneSignal.getLocation()
}

/**
* Get the in-app messages manager without blocking the calling thread.
* Suspends until the SDK is initialized if initialization is in progress.
* This is the suspend-safe version of the [InAppMessages] property accessor.
*
* @return The in-app messaging manager for accessing device-scoped IAM management.
*/
@JvmStatic
suspend fun getInAppMessagesSuspend(): IInAppMessagesManager {
return oneSignal.getInAppMessages()
}

/**
* Get the consent required flag in a thread-safe manner without blocking the calling thread.
* Suspends until the SDK is initialized if initialization is in progress.
* This is the suspend-safe version of the [consentRequired] property accessor.
*
* @return Whether a user must consent to privacy prior to their user data being sent to OneSignal.
*/
@JvmStatic
suspend fun getConsentRequiredSuspend(): Boolean {
return oneSignal.getConsentRequired()
}

/**
* Set the consent required flag in a thread-safe manner without blocking the calling thread.
* Suspends until the SDK is initialized if initialization is in progress.
* This is the suspend-safe version of the [consentRequired] property setter.
*
* @param required Whether a user must consent to privacy prior to their user data being sent to OneSignal.
* Should be set to `true` prior to the invocation of [initWithContext] to ensure compliance.
*/
@JvmStatic
suspend fun setConsentRequiredSuspend(required: Boolean) {
oneSignal.setConsentRequired(required)
}

/**
* Get the consent given flag in a thread-safe manner without blocking the calling thread.
* Suspends until the SDK is initialized if initialization is in progress.
* This is the suspend-safe version of the [consentGiven] property accessor.
*
* @return Whether privacy consent has been granted. This field is only relevant when
* the application has opted into data privacy protections. See [consentRequired].
*/
@JvmStatic
suspend fun getConsentGivenSuspend(): Boolean {
return oneSignal.getConsentGiven()
}

/**
* Set the consent given flag in a thread-safe manner without blocking the calling thread.
* Suspends until the SDK is initialized if initialization is in progress.
* This is the suspend-safe version of the [consentGiven] property setter.
*
* @param value Whether privacy consent has been granted.
*/
@JvmStatic
suspend fun setConsentGivenSuspend(value: Boolean) {
oneSignal.setConsentGiven(value)
}

/**
* Get the disable GMS missing prompt flag in a thread-safe manner without blocking the calling thread.
* Suspends until the SDK is initialized if initialization is in progress.
* This is the suspend-safe version of the [disableGMSMissingPrompt] property accessor.
*
* @return Whether to disable the "GMS is missing" prompt to the user.
*/
@JvmStatic
suspend fun getDisableGMSMissingPromptSuspend(): Boolean {
return oneSignal.getDisableGMSMissingPrompt()
}

/**
* Set the disable GMS missing prompt flag in a thread-safe manner without blocking the calling thread.
* Suspends until the SDK is initialized if initialization is in progress.
* This is the suspend-safe version of the [disableGMSMissingPrompt] property setter.
*
* @param value Whether to disable the "GMS is missing" prompt to the user.
*/
@JvmStatic
suspend fun setDisableGMSMissingPromptSuspend(value: Boolean) {
oneSignal.setDisableGMSMissingPrompt(value)
}

/**
* Login to OneSignal under the user identified by the [externalId] provided. The act of
* logging a user into the OneSignal SDK will switch the [User] context to that specific user.
Expand Down Expand Up @@ -226,24 +360,49 @@ object OneSignal {
}

/**
* Login a user with external ID and optional JWT token (suspend version).
* Login a user with external ID and optional JWT token without blocking the calling thread.
* Suspends until the SDK is initialized if initialization is in progress.
* This is the suspend-safe version of the [login] method.
*
* The act of logging a user into the OneSignal SDK will switch the [User] context to that specific user.
*
* @param externalId External user ID for login
* @param jwtBearerToken Optional JWT token for authentication
* * If the [externalId] exists the user will be retrieved and the context set from that
* user information. If operations have already been performed under a guest user, they
* *will not* be applied to the now logged in user (they will be lost).
* * If the [externalId] does not exist the user will be created and the context set from
* the current local state. If operations have already been performed under a guest user
* those operations *will* be applied to the newly created user.
*
* *Push Notifications and In App Messaging*
* Logging in a new user will automatically transfer push notification and in app messaging
* subscriptions from the current user (if there is one) to the newly logged in user. This is
* because both Push and IAM are owned by the device.
*
* @param externalId The external ID of the user that is to be logged in.
* @param jwtBearerToken The optional JWT bearer token generated by your backend to establish
* trust for the login operation. Required when identity verification has been enabled. See
* [Identity Verification | OneSignal](https://documentation.onesignal.com/docs/identity-verification)
*/
@JvmStatic
suspend fun loginSuspend(
externalId: String,
jwtBearerToken: String? = null,
) {
oneSignal.login(externalId, jwtBearerToken)
oneSignal.loginSuspend(externalId, jwtBearerToken)
}

/**
* Logout the current user (suspend version).
* Logout the current user without blocking the calling thread.
* Suspends until the SDK is initialized if initialization is in progress.
* This is the suspend-safe version of the [logout] method.
*
* The [User] property now references a new device-scoped user. A device-scoped user has no
* user identity that can later be retrieved, except through this device as long as the app
* remains installed and the app data is not cleared.
*/
@JvmStatic
suspend fun logoutSuspend() {
oneSignal.logout()
oneSignal.logoutSuspend()
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package com.onesignal

import com.onesignal.inAppMessages.IInAppMessagesManager
import com.onesignal.location.ILocationManager
import com.onesignal.notifications.INotificationsManager
import com.onesignal.session.ISessionManager
import com.onesignal.user.IUserManager
import io.kotest.core.spec.style.FunSpec
import kotlin.reflect.full.memberFunctions

/**
* Simple compilation tests to verify that all suspend methods exist in OneSignal class
* with correct signatures. These tests verify the API surface but don't execute the methods.
*/
class OneSignalSuspendMethodsExistTest : FunSpec({

test("initWithContextSuspend exists with correct signature") {
// This test compiles only if the method exists with correct signature
val method: suspend (android.content.Context, String) -> Boolean = OneSignal::initWithContextSuspend

// Verify using reflection that it's actually a suspend function
val kFunction = OneSignal::class.memberFunctions
.find { it.name == "initWithContextSuspend" }

assert(kFunction != null) { "initWithContextSuspend not found" }
assert(kFunction!!.isSuspend) { "initWithContextSuspend is not a suspend function" }
}

test("getUserSuspend exists and returns IUserManager") {
// Compilation check - this fails if method doesn't exist or has wrong return type
val method: suspend () -> IUserManager = OneSignal::getUserSuspend

val kFunction = OneSignal::class.memberFunctions
.find { it.name == "getUserSuspend" }

assert(kFunction != null) { "getUserSuspend not found" }
assert(kFunction!!.isSuspend) { "getUserSuspend is not a suspend function" }
}

test("getSessionSuspend exists and returns ISessionManager") {
val method: suspend () -> ISessionManager = OneSignal::getSessionSuspend

val kFunction = OneSignal::class.memberFunctions
.find { it.name == "getSessionSuspend" }

assert(kFunction != null) { "getSessionSuspend not found" }
assert(kFunction!!.isSuspend) { "getSessionSuspend is not a suspend function" }
}

test("getNotificationsSuspend exists and returns INotificationsManager") {
val method: suspend () -> INotificationsManager = OneSignal::getNotificationsSuspend

val kFunction = OneSignal::class.memberFunctions
.find { it.name == "getNotificationsSuspend" }

assert(kFunction != null) { "getNotificationsSuspend not found" }
assert(kFunction!!.isSuspend) { "getNotificationsSuspend is not a suspend function" }
}

test("getLocationSuspend exists and returns ILocationManager") {
val method: suspend () -> ILocationManager = OneSignal::getLocationSuspend

val kFunction = OneSignal::class.memberFunctions
.find { it.name == "getLocationSuspend" }

assert(kFunction != null) { "getLocationSuspend not found" }
assert(kFunction!!.isSuspend) { "getLocationSuspend is not a suspend function" }
}

test("getInAppMessagesSuspend exists and returns IInAppMessagesManager") {
val method: suspend () -> IInAppMessagesManager = OneSignal::getInAppMessagesSuspend

val kFunction = OneSignal::class.memberFunctions
.find { it.name == "getInAppMessagesSuspend" }

assert(kFunction != null) { "getInAppMessagesSuspend not found" }
assert(kFunction!!.isSuspend) { "getInAppMessagesSuspend is not a suspend function" }
}

test("getConsentRequiredSuspend exists and returns Boolean") {
val method: suspend () -> Boolean = OneSignal::getConsentRequiredSuspend

val kFunction = OneSignal::class.memberFunctions
.find { it.name == "getConsentRequiredSuspend" }

assert(kFunction != null) { "getConsentRequiredSuspend not found" }
assert(kFunction!!.isSuspend) { "getConsentRequiredSuspend is not a suspend function" }
}

test("setConsentRequiredSuspend exists with Boolean parameter") {
val method: suspend (Boolean) -> Unit = OneSignal::setConsentRequiredSuspend

val kFunction = OneSignal::class.memberFunctions
.find { it.name == "setConsentRequiredSuspend" }

assert(kFunction != null) { "setConsentRequiredSuspend not found" }
assert(kFunction!!.isSuspend) { "setConsentRequiredSuspend is not a suspend function" }
}

test("getConsentGivenSuspend exists and returns Boolean") {
val method: suspend () -> Boolean = OneSignal::getConsentGivenSuspend

val kFunction = OneSignal::class.memberFunctions
.find { it.name == "getConsentGivenSuspend" }

assert(kFunction != null) { "getConsentGivenSuspend not found" }
assert(kFunction!!.isSuspend) { "getConsentGivenSuspend is not a suspend function" }
}

test("setConsentGivenSuspend exists with Boolean parameter") {
val method: suspend (Boolean) -> Unit = OneSignal::setConsentGivenSuspend

val kFunction = OneSignal::class.memberFunctions
.find { it.name == "setConsentGivenSuspend" }

assert(kFunction != null) { "setConsentGivenSuspend not found" }
assert(kFunction!!.isSuspend) { "setConsentGivenSuspend is not a suspend function" }
}

test("getDisableGMSMissingPromptSuspend exists and returns Boolean") {
val method: suspend () -> Boolean = OneSignal::getDisableGMSMissingPromptSuspend

val kFunction = OneSignal::class.memberFunctions
.find { it.name == "getDisableGMSMissingPromptSuspend" }

assert(kFunction != null) { "getDisableGMSMissingPromptSuspend not found" }
assert(kFunction!!.isSuspend) { "getDisableGMSMissingPromptSuspend is not a suspend function" }
}

test("setDisableGMSMissingPromptSuspend exists with Boolean parameter") {
val method: suspend (Boolean) -> Unit = OneSignal::setDisableGMSMissingPromptSuspend

val kFunction = OneSignal::class.memberFunctions
.find { it.name == "setDisableGMSMissingPromptSuspend" }

assert(kFunction != null) { "setDisableGMSMissingPromptSuspend not found" }
assert(kFunction!!.isSuspend) { "setDisableGMSMissingPromptSuspend is not a suspend function" }
}

test("loginSuspend exists with String and optional String parameters") {
// Verify the method exists with correct signature using reflection
// Note: There's only one loginSuspend with a default parameter for jwtBearerToken
val kFunction = OneSignal::class.memberFunctions
.find { it.name == "loginSuspend" }

assert(kFunction != null) { "loginSuspend not found" }
assert(kFunction!!.isSuspend) { "loginSuspend is not a suspend function" }
assert(kFunction.parameters.size >= 2) { "loginSuspend should have at least 2 parameters (receiver + externalId)" }
}

test("logoutSuspend exists with no parameters") {
val method: suspend () -> Unit = OneSignal::logoutSuspend

val kFunction = OneSignal::class.memberFunctions
.find { it.name == "logoutSuspend" }

assert(kFunction != null) { "logoutSuspend not found" }
assert(kFunction!!.isSuspend) { "logoutSuspend is not a suspend function" }
}

test("all suspend methods are marked with @JvmStatic") {
// Get all suspend methods we added
val suspendMethodNames = listOf(
"getUserSuspend",
"getSessionSuspend",
"getNotificationsSuspend",
"getLocationSuspend",
"getInAppMessagesSuspend",
"getConsentRequiredSuspend",
"setConsentRequiredSuspend",
"getConsentGivenSuspend",
"setConsentGivenSuspend",
"getDisableGMSMissingPromptSuspend",
"setDisableGMSMissingPromptSuspend",
"loginSuspend",
"logoutSuspend"
)

// Verify each exists and is a static method (accessible via companion object)
suspendMethodNames.forEach { methodName ->
val kFunction = OneSignal::class.memberFunctions
.find { it.name == methodName }

assert(kFunction != null) { "$methodName not found" }
assert(kFunction!!.isSuspend) { "$methodName is not a suspend function" }
}
}
})
Loading