diff --git a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java index 06730bfc93..45d069fbed 100644 --- a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java +++ b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java @@ -801,7 +801,12 @@ private void setupSubscriptionSwitch() { // Add a listener to toggle the push notification enablement for the push subscription. pushSubscriptionEnabledSwitch.setOnClickListener(v -> { IPushSubscription subscription = OneSignal.getUser().getSubscriptions().getPush(); - subscription.setEnabled(pushSubscriptionEnabledSwitch.isChecked()); + if(pushSubscriptionEnabledSwitch.isChecked()) { + subscription.optIn(); + } + else { + subscription.optOut(); + } }); } @@ -832,7 +837,7 @@ private void refreshSubscriptionState() { promptPushBottonLayout.setVisibility(isPermissionEnabled ? View.GONE : View.VISIBLE); pushSubscriptionEnabledRelativeLayout.setEnabled(isPermissionEnabled); pushSubscriptionEnabledSwitch.setEnabled(isPermissionEnabled); - pushSubscriptionEnabledSwitch.setChecked(pushSubscription.getEnabled()); + pushSubscriptionEnabledSwitch.setChecked(pushSubscription.getOptedIn()); } private void setupSendNotificationsLayout() { diff --git a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/notification/OneSignalNotificationSender.java b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/notification/OneSignalNotificationSender.java index e8cfeceab5..303455f316 100644 --- a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/notification/OneSignalNotificationSender.java +++ b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/notification/OneSignalNotificationSender.java @@ -35,7 +35,7 @@ public static void sendDeviceNotification(final Notification notification) { new Thread(() -> { IPushSubscription subscription = OneSignal.getUser().getSubscriptions().getPush(); - if (!subscription.getEnabled()) + if (!subscription.getOptedIn()) return; int pos = notification.getTemplatePos(); diff --git a/OneSignalSDK/onesignal/inAppMessages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt b/OneSignalSDK/onesignal/inAppMessages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt index f437605209..263f0412a2 100644 --- a/OneSignalSDK/onesignal/inAppMessages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt +++ b/OneSignalSDK/onesignal/inAppMessages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt @@ -42,6 +42,9 @@ import com.onesignal.session.internal.session.ISessionService import com.onesignal.user.IUserManager import com.onesignal.user.internal.subscriptions.ISubscriptionChangedHandler import com.onesignal.user.internal.subscriptions.ISubscriptionManager +import com.onesignal.user.internal.subscriptions.SubscriptionModel +import com.onesignal.user.subscriptions.IPushSubscription +import com.onesignal.user.subscriptions.ISubscription import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -164,7 +167,13 @@ internal class InAppMessagesManager( } } - override fun onSubscriptionsChanged() { + override fun onSubscriptionAdded(subscription: ISubscription) { } + override fun onSubscriptionRemoved(subscription: ISubscription) { } + override fun onSubscriptionChanged(subscription: ISubscription, args: ModelChangedArgs) { + if (subscription !is IPushSubscription || args.path != SubscriptionModel::id.name) { + return + } + suspendifyOnThread { fetchMessages() } diff --git a/OneSignalSDK/onesignal/inAppMessages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt b/OneSignalSDK/onesignal/inAppMessages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt index fbbdb991b6..4d57d3ab33 100644 --- a/OneSignalSDK/onesignal/inAppMessages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt +++ b/OneSignalSDK/onesignal/inAppMessages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt @@ -21,11 +21,7 @@ internal class InAppBackendService( override suspend fun listInAppMessages(appId: String, subscriptionId: String): List? { // Retrieve any in app messages that might exist - val jsonBody = JSONObject() - jsonBody.put("app_id", appId) - - // TODO: This will be replaced by dedicated iam endpoint once it's available - var response = _httpClient.post("players/$subscriptionId/on_session", jsonBody) + val response = _httpClient.get("apps/$appId/subscriptions/$subscriptionId/iams") if (response.isSuccess) { val jsonResponse = JSONObject(response.payload) diff --git a/OneSignalSDK/onesignal/inAppMessages/src/main/java/com/onesignal/inAppMessages/internal/triggers/TriggerModel.kt b/OneSignalSDK/onesignal/inAppMessages/src/main/java/com/onesignal/inAppMessages/internal/triggers/TriggerModel.kt index dd5d731c88..95e2f13f79 100644 --- a/OneSignalSDK/onesignal/inAppMessages/src/main/java/com/onesignal/inAppMessages/internal/triggers/TriggerModel.kt +++ b/OneSignalSDK/onesignal/inAppMessages/src/main/java/com/onesignal/inAppMessages/internal/triggers/TriggerModel.kt @@ -7,13 +7,13 @@ class TriggerModel : Model() { * The key of this trigger */ var key: String - get() = getProperty(::key.name) { "" } - set(value) { setProperty(::key.name, value) } + get() = getStringProperty(::key.name) { "" } + set(value) { setStringProperty(::key.name, value) } /** * The value of this trigger */ var value: Any - get() = getProperty(::value.name) { "" } - set(value) { setProperty(::value.name, value) } + get() = getAnyProperty(::value.name) { "" } + set(value) { setAnyProperty(::value.name, value) } } diff --git a/OneSignalSDK/onesignal/inAppMessages/src/test/java/com/onesignal/inAppMessages/internal/backend/InAppBackendServiceTests.kt b/OneSignalSDK/onesignal/inAppMessages/src/test/java/com/onesignal/inAppMessages/internal/backend/InAppBackendServiceTests.kt index 11d6ad0f6f..8884605e10 100644 --- a/OneSignalSDK/onesignal/inAppMessages/src/test/java/com/onesignal/inAppMessages/internal/backend/InAppBackendServiceTests.kt +++ b/OneSignalSDK/onesignal/inAppMessages/src/test/java/com/onesignal/inAppMessages/internal/backend/InAppBackendServiceTests.kt @@ -51,7 +51,7 @@ class InAppBackendServiceTests : FunSpec({ /* Given */ val mockHydrator = InAppHydrator(MockHelper.time(1000), MockHelper.propertiesModelStore()) val mockHttpClient = mockk() - coEvery { mockHttpClient.get(any(), any()) } returns HttpResponse(200, "{ in_app_messages: [{id: \"messageId1\", variants:{all: {en: \"content1\"}}, triggers:[[{id: \"triggerId1\", kind: \"custom\", property: \"property1\", operator: \"equal\", value: \"value1\"}]], end_time: \"2020-12-13T23:23:23\", redisplay: { limit: 11111, delay: 22222}] }") + coEvery { mockHttpClient.get(any(), any()) } returns HttpResponse(200, "{ in_app_messages: [{id: \"messageId1\", variants:{all: {en: \"content1\"}}, triggers:[[{id: \"triggerId1\", kind: \"custom\", property: \"property1\", operator: \"equal\", value: \"value1\"}]], end_time: \"2008-09-03T20:56:35.450686Z\", redisplay: { limit: 11111, delay: 22222}}] }") val inAppBackendService = InAppBackendService(mockHttpClient, MockHelper.deviceService(), mockHydrator) @@ -62,9 +62,9 @@ class InAppBackendServiceTests : FunSpec({ response shouldNotBe null response!!.count() shouldBe 1 response[0].messageId shouldBe "messageId1" - response[0].variants.keys shouldBe 1 + response[0].variants.keys.count() shouldBe 1 response[0].variants["all"] shouldNotBe null - response[0].variants["all"]!!.keys shouldBe 1 + response[0].variants["all"]!!.keys.count() shouldBe 1 response[0].variants["all"]!!["en"] shouldBe "content1" response[0].triggers.count() shouldBe 1 response[0].triggers[0].count() shouldBe 1 diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/NotificationsManager.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/NotificationsManager.kt index adef8cd9c4..82ad4e89bf 100644 --- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/NotificationsManager.kt +++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/NotificationsManager.kt @@ -5,13 +5,11 @@ import com.onesignal.common.events.EventProducer import com.onesignal.common.threading.suspendifyOnThread import com.onesignal.core.internal.application.IApplicationLifecycleHandler import com.onesignal.core.internal.application.IApplicationService -import com.onesignal.core.internal.config.ConfigModelStore import com.onesignal.debug.internal.logging.Logging import com.onesignal.notifications.INotificationClickHandler import com.onesignal.notifications.INotificationWillShowInForegroundHandler import com.onesignal.notifications.INotificationsManager import com.onesignal.notifications.IPermissionChangedHandler -import com.onesignal.notifications.internal.backend.INotificationBackendService import com.onesignal.notifications.internal.common.GenerateNotificationOpenIntentFromPushPayload import com.onesignal.notifications.internal.common.NotificationHelper import com.onesignal.notifications.internal.data.INotificationRepository @@ -33,8 +31,6 @@ interface INotificationActivityOpener { */ internal class NotificationsManager( private val _applicationService: IApplicationService, - private val _configModelStore: ConfigModelStore, - private val _backend: INotificationBackendService, private val _notificationPermissionController: INotificationPermissionController, private val _notificationRestoreWorkManager: INotificationRestoreWorkManager, private val _notificationLifecycleService: INotificationLifecycleService, diff --git a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/listeners/DeviceRegistrationListener.kt b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/listeners/DeviceRegistrationListener.kt index 3ba7d76448..3301e7cb86 100644 --- a/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/listeners/DeviceRegistrationListener.kt +++ b/OneSignalSDK/onesignal/notifications/src/main/java/com/onesignal/notifications/internal/listeners/DeviceRegistrationListener.kt @@ -11,8 +11,11 @@ import com.onesignal.notifications.INotificationsManager import com.onesignal.notifications.IPermissionChangedHandler import com.onesignal.notifications.internal.channels.INotificationChannelManager import com.onesignal.notifications.internal.pushtoken.IPushTokenManager +import com.onesignal.user.internal.subscriptions.ISubscriptionChangedHandler import com.onesignal.user.internal.subscriptions.ISubscriptionManager +import com.onesignal.user.internal.subscriptions.SubscriptionModel import com.onesignal.user.internal.subscriptions.SubscriptionStatus +import com.onesignal.user.subscriptions.ISubscription /** * The device registration listener will subscribe to events and at the appropriate time will @@ -28,11 +31,15 @@ internal class DeviceRegistrationListener( private val _subscriptionManager: ISubscriptionManager ) : IStartableService, ISingletonModelStoreChangeHandler, - IPermissionChangedHandler { + IPermissionChangedHandler, + ISubscriptionChangedHandler { override fun start() { _configModelStore.subscribe(this) _notificationsManager.addPermissionChangedHandler(this) + _subscriptionManager.subscribe(this) + + retrievePushTokenAndUpdateSubscription() } override fun onModelReplaced(model: ConfigModel, tag: String) { @@ -44,24 +51,26 @@ internal class DeviceRegistrationListener( _channelManager.processChannelList(model.notificationChannels) - retrievePushTokenAndUpdateSubscription(_notificationsManager.permission) + retrievePushTokenAndUpdateSubscription() } override fun onModelUpdated(args: ModelChangedArgs, tag: String) { } override fun onPermissionChanged(permission: Boolean) { - retrievePushTokenAndUpdateSubscription(permission) + retrievePushTokenAndUpdateSubscription() } - private fun retrievePushTokenAndUpdateSubscription(permission: Boolean) { + private fun retrievePushTokenAndUpdateSubscription() { val pushSubscription = _subscriptionManager.subscriptions.push if (pushSubscription.pushToken.isNotEmpty()) { + val permission = _notificationsManager.permission _subscriptionManager.addOrUpdatePushSubscription(null, if (permission) SubscriptionStatus.SUBSCRIBED else SubscriptionStatus.NO_PERMISSION) } else { suspendifyOnThread { val pushTokenAndStatus = _pushTokenManager.retrievePushToken() + val permission = _notificationsManager.permission _subscriptionManager.addOrUpdatePushSubscription( pushTokenAndStatus.token, if (permission) pushTokenAndStatus.status else SubscriptionStatus.NO_PERMISSION @@ -69,4 +78,16 @@ internal class DeviceRegistrationListener( } } } + + override fun onSubscriptionRemoved(subscription: ISubscription) { } + override fun onSubscriptionAdded(subscription: ISubscription) { } + override fun onSubscriptionChanged(subscription: ISubscription, args: ModelChangedArgs) { + // when going from optedIn=false to optedIn=true and there aren't permissions, automatically drive + // permission request. + if (args.path == SubscriptionModel::optedIn.name && args.oldValue == false && args.newValue == true && !_notificationsManager.permission) { + suspendifyOnThread { + _notificationsManager.requestPermission(true) + } + } + } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/JSONObjectExtensions.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/JSONObjectExtensions.kt index 52c4f7eae0..89ad550d6e 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/JSONObjectExtensions.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/JSONObjectExtensions.kt @@ -1,5 +1,6 @@ package com.onesignal.common +import org.json.JSONArray import org.json.JSONObject /** @@ -33,9 +34,9 @@ fun JSONObject.safeLong(name: String): Long? { } /** - * Retrieve an [Double] from the [JSONObject] safely. + * Retrieve a [Double] from the [JSONObject] safely. * - * @param name The name of the attribute that contains an [Int] value. + * @param name The name of the attribute that contains a [Double] value. * * @return The [Double] value if it exists, null otherwise. */ @@ -92,6 +93,19 @@ fun JSONObject.safeJSONObject(name: String): JSONObject? { return null } +/** + * Create a [Map] from the [JSONObject]. + */ +fun JSONObject.toMap(): Map { + val map = mutableMapOf() + + for (key in this.keys()) { + map[key] = this[key] + } + + return map +} + /** * Expand into a [JSONObject] safely. * @@ -99,8 +113,115 @@ fun JSONObject.safeJSONObject(name: String): JSONObject? { * @param into The lambda method that will be executed to explore the [JSONObject] value, if the * attribute exists. */ -fun JSONObject.expand(name: String, into: (childObject: JSONObject) -> Unit) { +fun JSONObject.expandJSONObject(name: String, into: (childObject: JSONObject) -> Unit) { if (this.has(name)) { into(this.getJSONObject(name)) } } + +fun JSONObject.expandJSONArray(name: String, into: (childObject: JSONObject) -> T?): List { + val listToRet = mutableListOf() + if (this.has(name)) { + val jsonArray = this.getJSONArray(name) + for (index in 0 until jsonArray.length()) { + val itemJSONObject = jsonArray.getJSONObject(index) + val item = into(itemJSONObject) + if (item != null) { + listToRet.add(item) + } + } + } + + return listToRet +} + +/** + * Populate the [JSONObject] with the [Map] provided. + * + * @param map: The map that will contain the name/values. + * + * @return The [JSONObject] itself, to allow for chaining. + */ +fun JSONObject.putMap(map: Map): JSONObject { + for (identity in map) { + this.put(identity.key, identity.value ?: JSONObject.NULL) + } + + return this +} + +/** + * Populate the [JSONObject] as attribute [name] with the [Map] provided. + * + * @param name: The name of the attribute that will contain the [JSONObject] value. + * @param map: The map that will contain the name/values. + * + * @return The [JSONObject] itself, to allow for chaining. + */ +fun JSONObject.putMap(name: String, map: Map?): JSONObject { + if (map != null) { + this.putJSONObject(name) { + it.putMap(map) + } + } + + return this +} + +/** + * Put the attribute named by [name] with a [JSONObject] value, the contents + * of which are determined by the expand. + * + * @param name: The name of the attribute that will contain the [JSONObject] value. + * @param expand: The lambda that will be called to populate the [JSONObject] value. + * + * @return The [JSONObject] itself, to allow for chaining. + */ +fun JSONObject.putJSONObject(name: String, expand: (item: JSONObject) -> Unit): JSONObject { + val childJSONObject = JSONObject() + expand(childJSONObject) + + this.put(name, childJSONObject) + + return this +} + +/** + * Put the attribute named by [name] with a [JSONArray] value, the contenxt of which + * are deteremined by the input. + * + * @param name: The name of the attribute that will contain the [JSONArray] value. + * @param list: The list of items that will be converted into the [JSONArray]. + * @param create: The lambda that will be called for each item in [list], expecting a [JSONObject] to be added to the array. + */ +fun JSONObject.putJSONArray(name: String, list: List?, create: (item: T) -> JSONObject?): JSONObject { + if (list != null) { + val jsonArray = JSONArray() + list.forEach { + val item = create(it) + if (item != null) { + jsonArray.put(item) + } + } + this.put(name, jsonArray) + } + + return this +} + +/** + * Put the name/value pair into the [JSONObject]. If the [value] provided is null, + * nothing will be put into the [JSONObject]. + * + * @param name The name of the attribute the [value] will be saved to. + * @param value The value to put into the [JSONObject]. If not null, the attribute name will not be added. + * + * @return The [JSONObject] itself, to allow for chaining. + */ +fun JSONObject.putSafe(name: String, value: Any?): JSONObject { + if (value != null) { + this.put(name, value) + } + + return this +} diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/MapModel.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/MapModel.kt index 2934129495..ffbeb34ea7 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/MapModel.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/MapModel.kt @@ -35,28 +35,28 @@ open class MapModel( } override fun get(key: String): V { - return getProperty(key) + return getOptAnyProperty(key) as V } override fun clear() { for (property in data.keys) - setProperty(property, null) + setOptAnyProperty(property, null) } override fun put(key: String, value: V): V { - setProperty(key, value) + setOptAnyProperty(key, value) return value } override fun putAll(from: Map) { for (item in from) { - setProperty(item.key, item.value) + setOptAnyProperty(item.key, item.value) } } override fun remove(key: String): V { - val value = getProperty(key) - setProperty(key, null) + val value = getOptAnyProperty(key) as V + setOptAnyProperty(key, null) return value } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/Model.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/Model.kt index 1dd76c2e8c..41671a386f 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/Model.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/Model.kt @@ -4,6 +4,7 @@ import com.onesignal.common.events.EventProducer import com.onesignal.common.events.IEventNotifier import org.json.JSONArray import org.json.JSONObject +import java.math.BigDecimal /** * The base class for a [Model]. A model is effectively a map of data, each key in the map being @@ -19,7 +20,7 @@ import org.json.JSONObject * ------------ * Boolean * String - * Int/Long/Double (note deserialization does not always result in the same type i.e. Number) + * Int/Long/Double/Float * * When a [Model] is nested (a property is a [Model] type or [List] of [Model] types) the child * [Model] is owned and initialized by the parent [Model]. @@ -57,8 +58,8 @@ open class Model( * A unique identifier for this model. */ var id: String - get() = getProperty(::id.name) - set(value) { setProperty(::id.name, value) } + get() = getStringProperty(::id.name) + set(value) { setStringProperty(::id.name, value) } protected val data: MutableMap = mutableMapOf() private val _changeNotifier = EventProducer() @@ -92,7 +93,20 @@ open class Model( data[property] = listOfItems } } else { - data[property] = jsonObject.get(property) + val method = this.javaClass.methods.firstOrNull { it.returnType != Void::class.java && it.name.contains(property, true) } + + if (method == null) { + data[property] = jsonObject.get(property) + } else { + when (method.returnType) { + Double::class.java, java.lang.Double::class.java -> data[property] = jsonObject.getDouble(property) + Long::class.java, java.lang.Long::class.java -> data[property] = jsonObject.getLong(property) + Float::class.java, java.lang.Float::class.java -> data[property] = jsonObject.getDouble(property).toFloat() + Int::class.java, java.lang.Integer::class.java -> data[property] = jsonObject.getInt(property) + Boolean::class.java, java.lang.Boolean::class.java -> data[property] = jsonObject.getBoolean(property) + else -> data[property] = jsonObject.get(property) + } + } } } } @@ -136,31 +150,43 @@ open class Model( */ protected open fun createListForProperty(property: String, jsonArray: JSONArray): List<*>? = null - /** - * Set a property on this model to the provided value, with the ability to prevent - * firing the notification of the change. - * - * Note, this should not be used directly unless you know what you're doing. - * - * @param name: The name of the property that is to be set. - * @param value: The value of the property to set it to. - * @param tag The tag which identifies how/why the property is being set. - */ - fun setProperty(name: String, value: T, tag: String = ModelChangeTags.NORMAL) { + inline fun > setEnumProperty(name: String, value: T, tag: String = ModelChangeTags.NORMAL) = setOptEnumProperty(name, value, tag) + fun setMapModelProperty(name: String, value: MapModel, tag: String = ModelChangeTags.NORMAL) = setOptMapModelProperty(name, value, tag) + fun setListProperty(name: String, value: List, tag: String = ModelChangeTags.NORMAL) = setOptListProperty(name, value, tag) + fun setStringProperty(name: String, value: String, tag: String = ModelChangeTags.NORMAL) = setOptStringProperty(name, value, tag) + fun setBooleanProperty(name: String, value: Boolean, tag: String = ModelChangeTags.NORMAL) = setOptBooleanProperty(name, value, tag) + fun setLongProperty(name: String, value: Long, tag: String = ModelChangeTags.NORMAL) = setOptLongProperty(name, value, tag) + fun setDoubleProperty(name: String, value: Double, tag: String = ModelChangeTags.NORMAL) = setOptDoubleProperty(name, value, tag) + fun setFloatProperty(name: String, value: Float, tag: String = ModelChangeTags.NORMAL) = setOptFloatProperty(name, value, tag) + fun setIntProperty(name: String, value: Int, tag: String = ModelChangeTags.NORMAL) = setOptIntProperty(name, value, tag) + fun setBigDecimalProperty(name: String, value: BigDecimal, tag: String = ModelChangeTags.NORMAL) = setOptBigDecimalProperty(name, value, tag) + fun setAnyProperty(name: String, value: Any, tag: String = ModelChangeTags.NORMAL) = setOptAnyProperty(name, value, tag) + + inline fun > setOptEnumProperty(name: String, value: T?, tag: String = ModelChangeTags.NORMAL) = setOptAnyProperty(name, value?.toString(), tag) + fun setOptMapModelProperty(name: String, value: MapModel?, tag: String = ModelChangeTags.NORMAL) = setOptAnyProperty(name, value, tag) + fun setOptListProperty(name: String, value: List?, tag: String = ModelChangeTags.NORMAL) = setOptAnyProperty(name, value, tag) + fun setOptStringProperty(name: String, value: String?, tag: String = ModelChangeTags.NORMAL) = setOptAnyProperty(name, value, tag) + fun setOptBooleanProperty(name: String, value: Boolean?, tag: String = ModelChangeTags.NORMAL) = setOptAnyProperty(name, value, tag) + fun setOptLongProperty(name: String, value: Long?, tag: String = ModelChangeTags.NORMAL) = setOptAnyProperty(name, value, tag) + fun setOptDoubleProperty(name: String, value: Double?, tag: String = ModelChangeTags.NORMAL) = setOptAnyProperty(name, value, tag) + fun setOptFloatProperty(name: String, value: Float?, tag: String = ModelChangeTags.NORMAL) = setOptAnyProperty(name, value, tag) + fun setOptIntProperty(name: String, value: Int?, tag: String = ModelChangeTags.NORMAL) = setOptAnyProperty(name, value, tag) + fun setOptBigDecimalProperty(name: String, value: BigDecimal?, tag: String = ModelChangeTags.NORMAL) = setOptAnyProperty(name, value?.toString(), tag) + + fun setOptAnyProperty(name: String, value: Any?, tag: String = ModelChangeTags.NORMAL) { val oldValue = data[name] - val newValue = value as Any? - if (oldValue == newValue) { + if (oldValue == value) { return } - if (newValue != null) { - data[name] = newValue + if (value != null) { + data[name] = value } else if (data.containsKey(name)) { data.remove(name) } - notifyChanged(name, name, tag, oldValue, newValue) + notifyChanged(name, name, tag, oldValue, value) } /** @@ -172,24 +198,83 @@ open class Model( */ fun hasProperty(name: String): Boolean = data.containsKey(name) - /** - * Retrieve the property on this model with the provided name. - * - * @param name: The name of the property that is to be retrieved. - * @param create: An optional lambda to provide which will be called to create a default - * value for the property, in the event it doesn't exist yet. - * - * @return The value for this property. - */ - fun getProperty(name: String, create: (() -> T)? = null): T { - return if (data.containsKey(name)) { - data[name] as T - } else if (create != null) { + protected inline fun > getEnumProperty(name: String): T = getOptEnumProperty(name) as T + protected fun getMapModelProperty(name: String, create: (() -> MapModel)? = null): MapModel = getOptMapModelProperty(name, create) as MapModel + protected fun getListProperty(name: String, create: (() -> List)? = null): List = getOptListProperty(name, create) as List + protected fun getStringProperty(name: String, create: (() -> String)? = null): String = getOptStringProperty(name, create) as String + protected fun getBooleanProperty(name: String, create: (() -> Boolean)? = null): Boolean = getOptBooleanProperty(name, create) as Boolean + protected fun getLongProperty(name: String, create: (() -> Long)? = null): Long = getOptLongProperty(name, create) as Long + protected fun getDoubleProperty(name: String, create: (() -> Double)? = null): Double = getOptDoubleProperty(name, create) as Double + protected fun getFloatProperty(name: String, create: (() -> Float)? = null): Float = getOptFloatProperty(name, create) as Float + protected fun getIntProperty(name: String, create: (() -> Int)? = null): Int = getOptIntProperty(name, create) as Int + protected fun getBigDecimalProperty(name: String, create: (() -> BigDecimal)? = null): BigDecimal = getOptBigDecimalProperty(name, create) as BigDecimal + protected fun getAnyProperty(name: String, create: (() -> Any)? = null): Any = getOptAnyProperty(name, create) as Any + + protected inline fun > getOptEnumProperty(name: String): T? { + val value = getOptAnyProperty(name) ?: return null + + if (value is T) return value + if (value is String) return enumValueOf(value) + return value as T + } + + protected fun getOptMapModelProperty(name: String, create: (() -> MapModel?)? = null): MapModel? = getOptAnyProperty(name, create) as MapModel? + protected fun getOptListProperty(name: String, create: (() -> List?)? = null): List? = getOptAnyProperty(name, create) as List? + protected fun getOptStringProperty(name: String, create: (() -> String?)? = null): String? = getOptAnyProperty(name, create) as String? + protected fun getOptBooleanProperty(name: String, create: (() -> Boolean?)? = null): Boolean? = getOptAnyProperty(name, create) as Boolean? + protected fun getOptLongProperty(name: String, create: (() -> Long?)? = null): Long? { + val value = getOptAnyProperty(name, create) ?: return null + + if (value is Long) return value + if (value is Int) return value.toLong() + if (value is Float) return value.toLong() + if (value is Double) return value.toLong() + return value as Long? + } + protected fun getOptFloatProperty(name: String, create: (() -> Float?)? = null): Float? { + val value = getOptAnyProperty(name, create) ?: return null + + if (value is Float) return value + if (value is Double) return value.toFloat() + if (value is Int) return value.toFloat() + if (value is Long) return value.toFloat() + return value as Float? + } + protected fun getOptDoubleProperty(name: String, create: (() -> Double?)? = null): Double? { + val value = getOptAnyProperty(name, create) ?: return null + + if (value is Double) return value + if (value is Float) return value.toDouble() + if (value is Int) return value.toDouble() + if (value is Long) return value.toDouble() + return value as Double? + } + protected fun getOptIntProperty(name: String, create: (() -> Int?)? = null): Int? { + val value = getOptAnyProperty(name, create) ?: return null + + if (value is Int) return value + if (value is Long) return value.toInt() + if (value is Float) return value.toInt() + if (value is Double) return value.toInt() + return value as Int? + } + protected fun getOptBigDecimalProperty(name: String, create: (() -> BigDecimal?)? = null): BigDecimal? { + val value = getOptAnyProperty(name, create) ?: return null + + if (value is Int) return BigDecimal(value) + if (value is Long) return BigDecimal(value) + if (value is Float) return BigDecimal(value.toDouble()) + if (value is Double) return BigDecimal(value) + if (value is String) return BigDecimal(value) + return value as BigDecimal? + } + protected fun getOptAnyProperty(name: String, create: (() -> Any?)? = null): Any? { + return if (data.containsKey(name) || create == null) { + data[name] + } else { val defaultValue = create() data[name] = defaultValue as Any? defaultValue - } else { - data[name] as T } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/ModelStore.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/ModelStore.kt index 5414435cd5..69cc012725 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/ModelStore.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/ModelStore.kt @@ -121,7 +121,7 @@ abstract class ModelStore( } } - private fun persist() { + fun persist() { if (name != null && _prefs != null) { val jsonArray = JSONArray() for (model in _models) { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/SingletonModelStore.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/SingletonModelStore.kt index 922a6da0d2..7e93a3a304 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/SingletonModelStore.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/common/modeling/SingletonModelStore.kt @@ -33,6 +33,7 @@ open class SingletonModelStore( override fun replace(model: TModel, tag: String) { val existingModel = this.model existingModel.initializeFromModel(_singletonId, model) + store.persist() _changeSubscription.fire { it.onModelReplaced(existingModel, tag) } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/impl/ParamsBackendService.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/impl/ParamsBackendService.kt index 1e33b73cee..8a4d79d214 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/impl/ParamsBackendService.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/impl/ParamsBackendService.kt @@ -2,7 +2,7 @@ package com.onesignal.core.internal.backend.impl import com.onesignal.common.IDManager import com.onesignal.common.exceptions.BackendException -import com.onesignal.common.expand +import com.onesignal.common.expandJSONObject import com.onesignal.common.safeBool import com.onesignal.common.safeInt import com.onesignal.common.safeLong @@ -39,13 +39,13 @@ internal class ParamsBackendService( // Process outcomes params var influenceParams: InfluenceParamsObject? = null - responseJson.expand("outcomes") { + responseJson.expandJSONObject("outcomes") { influenceParams = processOutcomeJson(it) } // Process FCM params var fcmParams: FCMParamsObject? = null - responseJson.expand("fcm") { + responseJson.expandJSONObject("fcm") { fcmParams = FCMParamsObject( apiKey = it.safeString("api_key"), appId = it.safeString("app_id"), @@ -82,27 +82,27 @@ internal class ParamsBackendService( var isUnattributedEnabled: Boolean? = null // direct - outcomeJson.expand("direct") { + outcomeJson.expandJSONObject("direct") { isDirectEnabled = it.safeBool("enabled") } // indirect - outcomeJson.expand("indirect") { indirectJSON -> + outcomeJson.expandJSONObject("indirect") { indirectJSON -> isIndirectEnabled = indirectJSON.safeBool("enabled") - indirectJSON.expand("notification_attribution") { + indirectJSON.expandJSONObject("notification_attribution") { indirectNotificationAttributionWindow = it.safeInt("minutes_since_displayed") notificationLimit = it.safeInt("limit") } - indirectJSON.expand("in_app_message_attribution") { + indirectJSON.expandJSONObject("in_app_message_attribution") { indirectIAMAttributionWindow = it.safeInt("minutes_since_displayed") iamLimit = it.safeInt("limit") } } // unattributed - outcomeJson.expand("unattributed") { + outcomeJson.expandJSONObject("unattributed") { isUnattributedEnabled = it.safeBool("enabled") } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt index 8dd8888066..6e4597d7b0 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt @@ -9,79 +9,86 @@ class ConfigModel : Model() { * The current OneSignal application ID provided to the SDK. */ var appId: String - get() = getProperty(::appId.name) - set(value) { setProperty(::appId.name, value) } + get() = getStringProperty(::appId.name) + set(value) { setStringProperty(::appId.name, value) } + + /** + * The API URL String. + */ + var apiUrl: String + get() = getStringProperty(::apiUrl.name) { "https://api.onesignal.com/" } + set(value) { setStringProperty(::apiUrl.name, value) } /** * Whether the SDK requires privacy consent to send data to backend. */ var requiresPrivacyConsent: Boolean? - get() = getProperty(::requiresPrivacyConsent.name) - set(value) { setProperty(::requiresPrivacyConsent.name, value) } + get() = getOptBooleanProperty(::requiresPrivacyConsent.name) + set(value) { setOptBooleanProperty(::requiresPrivacyConsent.name, value) } /** * Whether the SDK has been given consent to privacy. */ var givenPrivacyConsent: Boolean? - get() = getProperty(::givenPrivacyConsent.name) - set(value) { setProperty(::givenPrivacyConsent.name, value) } + get() = getOptBooleanProperty(::givenPrivacyConsent.name) + set(value) { setOptBooleanProperty(::givenPrivacyConsent.name, value) } /** * Whether location is shared. */ var locationShared: Boolean - get() = getProperty(::locationShared.name) { false } - set(value) = setProperty(::locationShared.name, value) + get() = getBooleanProperty(::locationShared.name) { false } + set(value) = setBooleanProperty(::locationShared.name, value) /** * Whether to disable the "GMS is missing" prompt to the user. */ var disableGMSMissingPrompt: Boolean - get() = getProperty(::disableGMSMissingPrompt.name) { false } - set(value) { setProperty(::disableGMSMissingPrompt.name, value) } + get() = getBooleanProperty(::disableGMSMissingPrompt.name) { false } + set(value) { setBooleanProperty(::disableGMSMissingPrompt.name, value) } /** * Whether to disable the "GMS is missing" prompt to the user. */ var userRejectedGMSUpdate: Boolean - get() = getProperty(::userRejectedGMSUpdate.name) { false } - set(value) { setProperty(::userRejectedGMSUpdate.name, value) } + get() = getBooleanProperty(::userRejectedGMSUpdate.name) { false } + set(value) { setBooleanProperty(::userRejectedGMSUpdate.name, value) } /** * Whether to automatically unsubscribe from OneSignal when notifications have been disabled. */ var unsubscribeWhenNotificationsDisabled: Boolean - get() = getProperty(::unsubscribeWhenNotificationsDisabled.name) { false } - set(value) { setProperty(::unsubscribeWhenNotificationsDisabled.name, value) } + get() = getBooleanProperty(::unsubscribeWhenNotificationsDisabled.name) { false } + set(value) { setBooleanProperty(::unsubscribeWhenNotificationsDisabled.name, value) } /** * The timeout in milliseconds for an HTTP connection. */ var httpTimeout: Int - get() = getProperty(::httpTimeout.name) { 120000 } - set(value) { setProperty(::httpTimeout.name, value) } + get() = getIntProperty(::httpTimeout.name) { 120000 } + set(value) { setIntProperty(::httpTimeout.name, value) } /** * The timeout in milliseconds for an HTTP connection GET request. */ var httpGetTimeout: Int - get() = getProperty(::httpGetTimeout.name) { 60000 } - set(value) { setProperty(::httpGetTimeout.name, value) } + get() = getIntProperty(::httpGetTimeout.name) { 60000 } + set(value) { setIntProperty(::httpGetTimeout.name, value) } /** * Maximum time in milliseconds a user can spend out of focus before a new session is created. */ var sessionFocusTimeout: Long - get() = getProperty(::sessionFocusTimeout.name) { 30000 } - set(value) { setProperty(::sessionFocusTimeout.name, value) } + get() = getLongProperty(::sessionFocusTimeout.name) { 30000 } + set(value) { setLongProperty(::sessionFocusTimeout.name, value) } /** * The minimum number of milliseconds required to pass before executing another operation on * the queue. */ var opRepoExecutionInterval: Long - get() = getProperty(::opRepoExecutionInterval.name) { 5000 } - set(value) { setProperty(::opRepoExecutionInterval.name, value) } + get() = getLongProperty(::opRepoExecutionInterval.name) { 5000 } + set(value) { setLongProperty(::opRepoExecutionInterval.name, value) } /** * The number of milliseconds to delay after the operation repo processing has been woken. This @@ -89,83 +96,83 @@ class ConfigModel : Model() { * to be executed in isolation (because that is the one doing the waking). */ var opRepoPostWakeDelay: Long - get() = getProperty(::opRepoPostWakeDelay.name) { 200 } - set(value) { setProperty(::opRepoPostWakeDelay.name, value) } + get() = getLongProperty(::opRepoPostWakeDelay.name) { 200 } + set(value) { setLongProperty(::opRepoPostWakeDelay.name, value) } /** * The minimum number of milliseconds required to pass to allow the fetching of IAM to occur. */ var fetchIAMMinInterval: Long - get() = getProperty(::fetchIAMMinInterval.name) { 30000 } - set(value) { setProperty(::fetchIAMMinInterval.name, value) } + get() = getLongProperty(::fetchIAMMinInterval.name) { 30000 } + set(value) { setLongProperty(::fetchIAMMinInterval.name, value) } /** * The google project number for GMS devices. */ var googleProjectNumber: String? - get() = getProperty(::googleProjectNumber.name) - set(value) { setProperty(::googleProjectNumber.name, value) } + get() = getOptStringProperty(::googleProjectNumber.name) + set(value) { setOptStringProperty(::googleProjectNumber.name, value) } /** * Whether the current application is an enterprise-level */ var enterprise: Boolean - get() = getProperty(::enterprise.name) { false } - set(value) { setProperty(::enterprise.name, value) } + get() = getBooleanProperty(::enterprise.name) { false } + set(value) { setBooleanProperty(::enterprise.name, value) } /** * Whether SMS auth hash should be used. */ var useIdentityVerification: Boolean - get() = getProperty(::useIdentityVerification.name) { false } - set(value) { setProperty(::useIdentityVerification.name, value) } + get() = getBooleanProperty(::useIdentityVerification.name) { false } + set(value) { setBooleanProperty(::useIdentityVerification.name, value) } /** * The notification channel information as a [JSONArray] */ var notificationChannels: JSONArray? - get() = getProperty(::notificationChannels.name) { null } - set(value) { setProperty(::notificationChannels.name, value) } + get() = JSONArray(getOptStringProperty(::notificationChannels.name) { null } ?: "[]") + set(value) { setOptStringProperty(::notificationChannels.name, value?.toString()) } /** * Whether firebase analytics should be used */ var firebaseAnalytics: Boolean - get() = getProperty(::firebaseAnalytics.name) { false } - set(value) { setProperty(::firebaseAnalytics.name, value) } + get() = getBooleanProperty(::firebaseAnalytics.name) { false } + set(value) { setBooleanProperty(::firebaseAnalytics.name, value) } /** * Whether to honor TTL for notifications */ var restoreTTLFilter: Boolean - get() = getProperty(::restoreTTLFilter.name) { true } - set(value) { setProperty(::restoreTTLFilter.name, value) } + get() = getBooleanProperty(::restoreTTLFilter.name) { true } + set(value) { setBooleanProperty(::restoreTTLFilter.name, value) } /** * Whether to track notification receive receipts */ var receiveReceiptEnabled: Boolean - get() = getProperty(::receiveReceiptEnabled.name) { false } - set(value) { setProperty(::receiveReceiptEnabled.name, value) } + get() = getBooleanProperty(::receiveReceiptEnabled.name) { false } + set(value) { setBooleanProperty(::receiveReceiptEnabled.name, value) } /** * Whether to clear group on summary clicks */ var clearGroupOnSummaryClick: Boolean - get() = getProperty(::clearGroupOnSummaryClick.name) { true } - set(value) { setProperty(::clearGroupOnSummaryClick.name, value) } + get() = getBooleanProperty(::clearGroupOnSummaryClick.name) { true } + set(value) { setBooleanProperty(::clearGroupOnSummaryClick.name, value) } /** * The outcomes parameters */ val influenceParams: InfluenceConfigModel - get() = getProperty(::influenceParams.name) { InfluenceConfigModel(this, ::influenceParams.name) } + get() = getAnyProperty(::influenceParams.name) { InfluenceConfigModel(this, ::influenceParams.name) } as InfluenceConfigModel /** * The firebase cloud parameters */ val fcmParams: FCMConfigModel - get() = getProperty(::fcmParams.name) { FCMConfigModel(this, ::fcmParams.name) } + get() = getAnyProperty(::fcmParams.name) { FCMConfigModel(this, ::fcmParams.name) } as FCMConfigModel override fun createModelForProperty(property: String, jsonObject: JSONObject): Model? { if (property == ::influenceParams.name) { @@ -192,50 +199,50 @@ class InfluenceConfigModel(parentModel: Model, parentProperty: String) : Model(p * The number of minutes a push notification can be considered to influence a user. */ var indirectNotificationAttributionWindow: Int - get() = getProperty(::indirectNotificationAttributionWindow.name) { DEFAULT_INDIRECT_ATTRIBUTION_WINDOW } - set(value) { setProperty(::indirectNotificationAttributionWindow.name, value) } + get() = getIntProperty(::indirectNotificationAttributionWindow.name) { DEFAULT_INDIRECT_ATTRIBUTION_WINDOW } + set(value) { setIntProperty(::indirectNotificationAttributionWindow.name, value) } /** * The maximum number of push notifications that can influence at one time. */ var notificationLimit: Int - get() = getProperty(::notificationLimit.name) { DEFAULT_NOTIFICATION_LIMIT } - set(value) { setProperty(::notificationLimit.name, value) } + get() = getIntProperty(::notificationLimit.name) { DEFAULT_NOTIFICATION_LIMIT } + set(value) { setIntProperty(::notificationLimit.name, value) } /** * The number of minutes an IAM can be considered to influence a user. */ var indirectIAMAttributionWindow: Int - get() = getProperty(::indirectIAMAttributionWindow.name) { DEFAULT_INDIRECT_ATTRIBUTION_WINDOW } - set(value) { setProperty(::indirectIAMAttributionWindow.name, value) } + get() = getIntProperty(::indirectIAMAttributionWindow.name) { DEFAULT_INDIRECT_ATTRIBUTION_WINDOW } + set(value) { setIntProperty(::indirectIAMAttributionWindow.name, value) } /** * The maximum number of IAMs that can influence at one time. */ var iamLimit: Int - get() = getProperty(::iamLimit.name) { DEFAULT_NOTIFICATION_LIMIT } - set(value) { setProperty(::iamLimit.name, value) } + get() = getIntProperty(::iamLimit.name) { DEFAULT_NOTIFICATION_LIMIT } + set(value) { setIntProperty(::iamLimit.name, value) } /** * Whether DIRECT influences are enabled. */ var isDirectEnabled: Boolean - get() = getProperty(::isDirectEnabled.name) { false } - set(value) { setProperty(::isDirectEnabled.name, value) } + get() = getBooleanProperty(::isDirectEnabled.name) { false } + set(value) { setBooleanProperty(::isDirectEnabled.name, value) } /** * Whether INDIRECT influences are enabled. */ var isIndirectEnabled: Boolean - get() = getProperty(::isIndirectEnabled.name) { false } - set(value) { setProperty(::isIndirectEnabled.name, value) } + get() = getBooleanProperty(::isIndirectEnabled.name) { false } + set(value) { setBooleanProperty(::isIndirectEnabled.name, value) } /** * Whether UNATTRIBUTED influences are enabled. */ var isUnattributedEnabled: Boolean - get() = getProperty(::isUnattributedEnabled.name) { false } - set(value) { setProperty(::isUnattributedEnabled.name, value) } + get() = getBooleanProperty(::isUnattributedEnabled.name) { false } + set(value) { setBooleanProperty(::isUnattributedEnabled.name, value) } companion object { const val DEFAULT_INDIRECT_ATTRIBUTION_WINDOW = 24 * 60 @@ -251,20 +258,20 @@ class FCMConfigModel(parentModel: Model, parentProperty: String) : Model(parentM * The FCM project ID. */ var projectId: String? - get() = getProperty(::projectId.name) { null } - set(value) { setProperty(::projectId.name, value) } + get() = getOptStringProperty(::projectId.name) { null } + set(value) { setOptStringProperty(::projectId.name, value) } /** * The FCM app ID. */ var appId: String? - get() = getProperty(::appId.name) { null } - set(value) { setProperty(::appId.name, value) } + get() = getOptStringProperty(::appId.name) { null } + set(value) { setOptStringProperty(::appId.name, value) } /** * The FCM api key. */ var apiKey: String? - get() = getProperty(::apiKey.name) { null } - set(value) { setProperty(::apiKey.name, value) } + get() = getOptStringProperty(::apiKey.name) { null } + set(value) { setOptStringProperty(::apiKey.name, value) } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/http/HttpResponse.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/http/HttpResponse.kt index 461247d80c..2bad02f4b4 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/http/HttpResponse.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/http/HttpResponse.kt @@ -26,5 +26,5 @@ class HttpResponse( * Whether the response is a successful one. */ val isSuccess: Boolean - get() = statusCode == HttpURLConnection.HTTP_OK || statusCode == HttpURLConnection.HTTP_ACCEPTED || statusCode == HttpURLConnection.HTTP_NOT_MODIFIED + get() = statusCode == HttpURLConnection.HTTP_OK || statusCode == HttpURLConnection.HTTP_ACCEPTED || statusCode == HttpURLConnection.HTTP_NOT_MODIFIED || statusCode == HttpURLConnection.HTTP_CREATED } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/http/impl/HttpConnectionFactory.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/http/impl/HttpConnectionFactory.kt index b5bc24a462..542846a740 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/http/impl/HttpConnectionFactory.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/http/impl/HttpConnectionFactory.kt @@ -1,16 +1,15 @@ package com.onesignal.core.internal.http.impl +import com.onesignal.core.internal.config.ConfigModelStore import java.io.IOException import java.net.HttpURLConnection import java.net.URL -internal class HttpConnectionFactory : IHttpConnectionFactory { +internal class HttpConnectionFactory( + private val _configModelStore: ConfigModelStore +) : IHttpConnectionFactory { @Throws(IOException::class) override fun newHttpURLConnection(url: String): HttpURLConnection { - return URL(BASE_URL + url).openConnection() as HttpURLConnection - } - - companion object { - private const val BASE_URL = "https://api.onesignal.com/" + return URL(_configModelStore.model.apiUrl + url).openConnection() as HttpURLConnection } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/Operation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/Operation.kt index 722441f3a6..990f1df7e7 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/Operation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/Operation.kt @@ -11,8 +11,8 @@ import com.onesignal.common.modeling.Model */ abstract class Operation(name: String) : Model() { var name: String - get() = getProperty(::name.name) - private set(value) { setProperty(::name.name, value) } + get() = getStringProperty(::name.name) + private set(value) { setStringProperty(::name.name, value) } init { this.name = name diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt index 2f38a3e4ed..3a0c17246a 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt @@ -144,7 +144,10 @@ internal class OperationRepo( val executor = _executorsMap[startingOp.operation.name] ?: throw Exception("Could not find executor for operation ${startingOp.operation.name}") - val response = executor.execute(ops.map { it.operation }) + val operations = ops.map { it.operation } + val response = executor.execute(operations) + + Logging.debug("OperationRepo: execute response = ${response.result}") // if the execution resulted in ID translations, run through the queue so they pick it up. // We also run through the ops just executed in case they are re-added to the queue. @@ -162,6 +165,7 @@ internal class OperationRepo( ops.forEach { it.waiter?.wake(true) } } ExecutionResult.FAIL_NORETRY -> { + Logging.error("Operation execution failed without retry: $operations") // on failure we remove the operation from the store and wake any waiters ops.forEach { _operationModelStore.remove(it.operation.id) } ops.forEach { it.waiter?.wake(false) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/internal/OneSignalImp.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/internal/OneSignalImp.kt index aacebed62e..ad5fe8a180 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/internal/OneSignalImp.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/internal/OneSignalImp.kt @@ -153,7 +153,7 @@ internal class OneSignalImp : IOneSignal, IServiceProvider { var forceCreateUser = false // if the app id was specified as input, update the config model with it if (appId != null) { - if (_configModel!!.appId != appId) { + if (!_configModel!!.hasProperty(ConfigModel::appId.name) || _configModel!!.appId != appId) { forceCreateUser = true } _configModel!!.appId = appId @@ -193,7 +193,7 @@ internal class OneSignalImp : IOneSignal, IServiceProvider { _operationRepo!!.enqueue(LoginUserOperation(_configModel!!.appId, _identityModelStore!!.model.onesignalId, _identityModelStore!!.model.externalId)) } else { Logging.debug("initWithContext: using cached user ${_identityModelStore!!.model.onesignalId}") - // _operationRepo!!.enqueue(GetUserOperation(_configModel!!.appId, _identityModelStore!!.model.onesignalId)) + _operationRepo!!.enqueue(RefreshUserOperation(_configModel!!.appId, _identityModelStore!!.model.onesignalId)) } _startupService!!.start() @@ -297,7 +297,7 @@ internal class OneSignalImp : IOneSignal, IServiceProvider { newPushSubscription.id = IDManager.createLocalId() newPushSubscription.type = SubscriptionType.PUSH - newPushSubscription.enabled = currentPushSubscription?.enabled ?: true + newPushSubscription.optedIn = currentPushSubscription?.optedIn ?: true newPushSubscription.address = currentPushSubscription?.address ?: "" newPushSubscription.status = currentPushSubscription?.status ?: SubscriptionStatus.NO_PERMISSION diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/outcomes/impl/IOutcomeEventsBackendService.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/outcomes/impl/IOutcomeEventsBackendService.kt index 9d2c6b5da0..b620ed1e76 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/outcomes/impl/IOutcomeEventsBackendService.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/outcomes/impl/IOutcomeEventsBackendService.kt @@ -1,7 +1,6 @@ package com.onesignal.session.internal.outcomes.impl import com.onesignal.common.exceptions.BackendException -import com.onesignal.core.internal.device.IDeviceService /** * The backend service for outcomes. @@ -14,9 +13,10 @@ internal interface IOutcomeEventsBackendService { * If there is a non-successful response from the backend, a [BackendException] will be thrown with response data. * * @param appId The ID of the application this outcome event occurred under. - * @param deviceType The device type. + * @param userId The OneSignal user ID that is active during the outcome event. + * @param subscriptionId The subscription ID that is active during the outcome event. * @param direct Whether this outcome event is direct. `true` if it is, `false` if it isn't, `null` if should not be specified. * @param event The outcome event to send up. */ - suspend fun sendOutcomeEvent(appId: String, deviceType: IDeviceService.DeviceType, direct: Boolean?, event: OutcomeEvent) + suspend fun sendOutcomeEvent(appId: String, userId: String, subscriptionId: String, direct: Boolean?, event: OutcomeEvent) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsBackendService.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsBackendService.kt index 2fe0a93aac..b5a548f36b 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsBackendService.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsBackendService.kt @@ -1,17 +1,21 @@ package com.onesignal.session.internal.outcomes.impl import com.onesignal.common.exceptions.BackendException -import com.onesignal.core.internal.device.IDeviceService import com.onesignal.core.internal.http.IHttpClient import org.json.JSONObject internal class OutcomeEventsBackendService(private val _http: IHttpClient) : IOutcomeEventsBackendService { - override suspend fun sendOutcomeEvent(appId: String, deviceType: IDeviceService.DeviceType, direct: Boolean?, event: OutcomeEvent) { + override suspend fun sendOutcomeEvent(appId: String, userId: String, subscriptionId: String, direct: Boolean?, event: OutcomeEvent) { val jsonObject = JSONObject() .put("app_id", appId) - .put("device_type", deviceType.value) + .put("onesignal_id", userId) + .put( + "subscription", + JSONObject() + .put("id", subscriptionId) + ) if (direct != null) { jsonObject.put("direct", direct) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsController.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsController.kt index 7f2d707b15..01cf3fb67c 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsController.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsController.kt @@ -4,7 +4,6 @@ import android.os.Process import com.onesignal.common.exceptions.BackendException import com.onesignal.common.threading.suspendifyOnThread import com.onesignal.core.internal.config.ConfigModelStore -import com.onesignal.core.internal.device.IDeviceService import com.onesignal.core.internal.startup.IStartableService import com.onesignal.core.internal.time.ITime import com.onesignal.debug.internal.logging.Logging @@ -15,6 +14,8 @@ import com.onesignal.session.internal.influence.InfluenceType import com.onesignal.session.internal.outcomes.IOutcomeEventsController import com.onesignal.session.internal.session.ISessionLifecycleHandler import com.onesignal.session.internal.session.ISessionService +import com.onesignal.user.internal.identity.IdentityModelStore +import com.onesignal.user.internal.subscriptions.ISubscriptionManager internal class OutcomeEventsController( private val _session: ISessionService, @@ -23,8 +24,9 @@ internal class OutcomeEventsController( private val _outcomeEventsPreferences: IOutcomeEventsPreferences, private val _outcomeEventsBackend: IOutcomeEventsBackendService, private val _configModelStore: ConfigModelStore, - private val _time: ITime, - private val _deviceService: IDeviceService + private val _identityModelStore: IdentityModelStore, + private val _subscriptionManager: ISubscriptionManager, + private val _time: ITime ) : IOutcomeEventsController, IStartableService, ISessionLifecycleHandler { // Keeps track of unique outcome events sent for UNATTRIBUTED sessions on a per session level private var unattributedUniqueOutcomeEventsSentOnSession: MutableSet = mutableSetOf() @@ -264,8 +266,15 @@ Outcome event was cached and will be reattempted on app cold start""" } private suspend fun requestMeasureOutcomeEvent(eventParams: OutcomeEventParams) { - val deviceType = _deviceService.deviceType val appId: String = _configModelStore.model.appId + val subscriptionId = _subscriptionManager.subscriptions.push.id + + // if we don't have a subscription ID yet, throw an exception. The outcome will be saved and processed + // later, when we do have a subscription ID. + if (subscriptionId.isEmpty()) { + throw BackendException(0) + } + val event = OutcomeEvent.fromOutcomeEventParamstoOutcomeEvent(eventParams) val direct = when (event.session) { InfluenceType.DIRECT -> true @@ -274,6 +283,6 @@ Outcome event was cached and will be reattempted on app cold start""" else -> null } - _outcomeEventsBackend.sendOutcomeEvent(appId, deviceType, direct, event) + _outcomeEventsBackend.sendOutcomeEvent(appId, _identityModelStore.model.onesignalId, subscriptionId, direct, event) } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/session/SessionModel.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/session/SessionModel.kt index 3622b58914..c5116e7f36 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/session/SessionModel.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/session/SessionModel.kt @@ -10,34 +10,34 @@ class SessionModel : Model() { * The ID of the current session. */ var sessionId: String - get() = getProperty(::sessionId.name) - set(value) { setProperty(::sessionId.name, value) } + get() = getStringProperty(::sessionId.name) + set(value) { setStringProperty(::sessionId.name, value) } /** * Whether the session is valid. */ var isValid: Boolean - get() = getProperty(::isValid.name) { true } - set(value) { setProperty(::isValid.name, value) } + get() = getBooleanProperty(::isValid.name) { true } + set(value) { setBooleanProperty(::isValid.name, value) } /** * When this session started, in Unix time milliseconds. */ var startTime: Long - get() = getProperty(::startTime.name) - set(value) { setProperty(::startTime.name, value) } + get() = getLongProperty(::startTime.name) + set(value) { setLongProperty(::startTime.name, value) } /** * When this app was last focused, in Unix time milliseconds. */ var focusTime: Long - get() = getProperty(::focusTime.name) { 0 } - set(value) { setProperty(::focusTime.name, value) } + get() = getLongProperty(::focusTime.name) { 0 } + set(value) { setLongProperty(::focusTime.name, value) } /** * How long this session has spent as active, in milliseconds. */ var activeDuration: Long - get() = getProperty(::activeDuration.name) { 0L } - set(value) { setProperty(::activeDuration.name, value) } + get() = getLongProperty(::activeDuration.name) { 0L } + set(value) { setLongProperty(::activeDuration.name, value) } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/PushSubscription.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/PushSubscription.kt index 24c23c213b..56cbad9d34 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/PushSubscription.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/PushSubscription.kt @@ -12,19 +12,26 @@ internal open class PushSubscription( override val pushToken: String get() = model.address - override var enabled: Boolean - get() = model.enabled && model.status == SubscriptionStatus.SUBSCRIBED - set(value) { model.enabled = value } + override val optedIn: Boolean + get() = model.optedIn && model.status != SubscriptionStatus.NO_PERMISSION + + override fun optIn() { + model.optedIn = true + } + + override fun optOut() { + model.optedIn = false + } } internal class UninitializedPushSubscription() : PushSubscription(createFakePushSub()) { - companion object { fun createFakePushSub(): SubscriptionModel { val pushSubModel = SubscriptionModel() pushSubModel.id = "" pushSubModel.type = SubscriptionType.PUSH - pushSubModel.enabled = false + pushSubModel.optedIn = false + pushSubModel.address = "" return pushSubModel } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/IIdentityBackendService.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/IIdentityBackendService.kt index e3a8b07f6e..f72cad36e0 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/IIdentityBackendService.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/IIdentityBackendService.kt @@ -1,9 +1,10 @@ package com.onesignal.user.internal.backend +import com.onesignal.common.exceptions.BackendException + interface IIdentityBackendService { /** - * Create one or more aliases for the user identified by the [aliasLabel]/[aliasValue] provided. For - * each being created, the [aliasLabel]/[aliasValue] pair must not already exist within the [appId]. + * Set one or more aliases for the user identified by the [aliasLabel]/[aliasValue] provided. * * If there is a non-successful response from the backend, a [BackendException] will be thrown with response data. * @@ -12,20 +13,7 @@ interface IIdentityBackendService { * @param aliasValue The identifier within the [aliasLabel] that identifies the user to retrieve. * @param identities The identities that are to be created. */ - suspend fun createAlias(appId: String, aliasLabel: String, aliasValue: String, identities: Map): Map - - /** - * Update the [aliasLabelToUpdate] from the user identified by the [aliasLabel]/[aliasValue] provided. - * - * If there is a non-successful response from the backend, a [BackendException] will be thrown with response data. - * - * @param appId The ID of the OneSignal application this user exists under. - * @param aliasLabel The alias label to retrieve the user under. - * @param aliasValue The identifier within the [aliasLabel] that identifies the user to retrieve. - * @param aliasLabelToUpdate The alias label to delete from the user identified. - * @param newAliasId The new ID for the [aliasLabelToUpdate]. - */ - suspend fun updateAlias(appId: String, aliasLabel: String, aliasValue: String, aliasLabelToUpdate: String, newAliasId: String) + suspend fun setAlias(appId: String, aliasLabel: String, aliasValue: String, identities: Map): Map /** * Delete the [aliasLabelToDelete] from the user identified by the [aliasLabel]/[aliasValue] provided. diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/ISubscriptionBackendService.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/ISubscriptionBackendService.kt index 3cc1b419d3..fffc3c9b9d 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/ISubscriptionBackendService.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/ISubscriptionBackendService.kt @@ -1,6 +1,6 @@ package com.onesignal.user.internal.backend -import com.onesignal.user.internal.subscriptions.SubscriptionStatus +import com.onesignal.common.exceptions.BackendException interface ISubscriptionBackendService { /** @@ -12,26 +12,20 @@ interface ISubscriptionBackendService { * @param appId The ID of the OneSignal application this user exists under. * @param aliasLabel The alias label to retrieve the user under. * @param aliasValue The identifier within the [aliasLabel] that identifies the user to retrieve. - * @param type The type of subscription to create. - * @param enabled Whether this subscription is enabled. - * @param address The subscription address. - * @param status The subscription status. + * @param subscription The subscription to create. * * @return The ID of the subscription created. */ - suspend fun createSubscription(appId: String, aliasLabel: String, aliasValue: String, type: SubscriptionObjectType, enabled: Boolean, address: String, status: SubscriptionStatus): String + suspend fun createSubscription(appId: String, aliasLabel: String, aliasValue: String, subscription: SubscriptionObject): String /** * Update an existing subscription with the properties provided. * * @param appId The ID of the OneSignal application this subscription exists under. * @param subscriptionId The ID of the subscription to update. - * @param type The new type of the subscription. - * @param enabled Whether this subscription is enabled. - * @param address The subscription address. - * @param status The subscription status. + * @param subscription The subscription updates. Any non-null value will be updated. */ - suspend fun updateSubscription(appId: String, subscriptionId: String, type: SubscriptionObjectType, enabled: Boolean, address: String, status: SubscriptionStatus) + suspend fun updateSubscription(appId: String, subscriptionId: String, subscription: SubscriptionObject) /** * Delete an existing subscription. diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/PropertiesDeltasObject.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/PropertiesDeltasObject.kt index 94268be9b0..160fe90054 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/PropertiesDeltasObject.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/PropertiesDeltasObject.kt @@ -7,7 +7,10 @@ class PropertiesDeltasObject( val sessionCount: Int? = null, val amountSpent: BigDecimal? = null, val purchases: List? = null -) +) { + val hasAtLeastOnePropertySet: Boolean + get() = sessionTime != null || sessionCount != null || amountSpent != null || purchases != null +} class PurchaseObject( val sku: String, diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/PropertiesObject.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/PropertiesObject.kt index 6727a62003..1d5a96245c 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/PropertiesObject.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/PropertiesObject.kt @@ -1,14 +1,13 @@ package com.onesignal.user.internal.backend class PropertiesObject( - val tags: Map? = null, + val tags: Map? = null, val language: String? = null, val timezoneId: String? = null, val country: String? = null, val latitude: Double? = null, - val longitude: Double? = null, - val locationAccuracy: Float? = null, - val locationType: Int? = null, - val locationBackground: Boolean? = null, - val locationTimestamp: Long? = null -) + val longitude: Double? = null +) { + val hasAtLeastOnePropertySet: Boolean + get() = tags != null || language != null || timezoneId != null || country != null || latitude != null || longitude != null +} diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/SubscriptionObject.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/SubscriptionObject.kt index c597367b17..7252683531 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/SubscriptionObject.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/SubscriptionObject.kt @@ -10,10 +10,6 @@ class SubscriptionObject( val deviceModel: String? = null, val deviceOS: String? = null, val rooted: Boolean? = null, - val testType: Int? = null, - val appVersion: String? = null, val netType: Int? = null, - val carrier: String? = null, - val webAuth: String? = null, - val webP256: String? = null + val carrier: String? = null ) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/SubscriptionObjectType.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/SubscriptionObjectType.kt index 06caf55f2d..cb6ce40258 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/SubscriptionObjectType.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/SubscriptionObjectType.kt @@ -25,5 +25,7 @@ enum class SubscriptionObjectType(val value: String) { IDeviceService.DeviceType.Huawei -> HUAWEI_PUSH } } + + fun fromString(type: String): SubscriptionObjectType? = values().firstOrNull() { it.value.equals(type, true) } } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/impl/IdentityBackendService.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/impl/IdentityBackendService.kt index ee6e88053f..a83e9168c5 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/impl/IdentityBackendService.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/impl/IdentityBackendService.kt @@ -1,25 +1,35 @@ package com.onesignal.user.internal.backend.impl +import com.onesignal.common.exceptions.BackendException +import com.onesignal.common.putMap +import com.onesignal.common.toMap import com.onesignal.core.internal.http.IHttpClient import com.onesignal.user.internal.backend.IIdentityBackendService -import kotlinx.coroutines.yield +import org.json.JSONObject internal class IdentityBackendService( private val _httpClient: IHttpClient ) : IIdentityBackendService { - override suspend fun createAlias(appId: String, aliasLabel: String, aliasValue: String, identities: Map): Map { - // TODO: To Implement - yield() - return identities - } + override suspend fun setAlias(appId: String, aliasLabel: String, aliasValue: String, identities: Map): Map { + val requestJSONObject = JSONObject() + .put("identity", JSONObject().putMap(identities)) + + val response = _httpClient.patch("apps/$appId/users/by/$aliasLabel/$aliasValue/identity", requestJSONObject) + + if (!response.isSuccess) { + throw BackendException(response.statusCode, response.payload) + } - override suspend fun updateAlias(appId: String, aliasLabel: String, aliasValue: String, aliasLabelToUpdate: String, newAliasId: String) { - // TODO: To Implement - yield() + val responseJSON = JSONObject(response.payload!!) + + return responseJSON.getJSONObject("identity").toMap().mapValues { it.value.toString() } } override suspend fun deleteAlias(appId: String, aliasLabel: String, aliasValue: String, aliasLabelToDelete: String) { - // TODO: To Implement - yield() + val response = _httpClient.delete("apps/$appId/users/by/$aliasLabel/$aliasValue/identity/$aliasLabelToDelete") + + if (!response.isSuccess) { + throw BackendException(response.statusCode, response.payload) + } } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/impl/JSONConverter.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/impl/JSONConverter.kt new file mode 100644 index 0000000000..9ee4783432 --- /dev/null +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/impl/JSONConverter.kt @@ -0,0 +1,104 @@ +package com.onesignal.user.internal.backend.impl + +import com.onesignal.common.expandJSONArray +import com.onesignal.common.putJSONArray +import com.onesignal.common.putMap +import com.onesignal.common.putSafe +import com.onesignal.common.safeBool +import com.onesignal.common.safeDouble +import com.onesignal.common.safeInt +import com.onesignal.common.safeJSONObject +import com.onesignal.common.safeString +import com.onesignal.common.toMap +import com.onesignal.user.internal.backend.CreateUserResponse +import com.onesignal.user.internal.backend.PropertiesDeltasObject +import com.onesignal.user.internal.backend.PropertiesObject +import com.onesignal.user.internal.backend.SubscriptionObject +import com.onesignal.user.internal.backend.SubscriptionObjectType +import org.json.JSONArray +import org.json.JSONObject + +object JSONConverter { + fun convertToCreateUserResponse(jsonObject: JSONObject): CreateUserResponse { + val respIdentities = jsonObject.safeJSONObject("identity")?.toMap()?.mapValues { it.value.toString() } ?: mapOf() + + val propertiesJSON = jsonObject.safeJSONObject("properties") + val respProperties = PropertiesObject( + propertiesJSON?.safeJSONObject("tags")?.toMap()?.mapValues { it.value.toString() }, + propertiesJSON?.safeString("language"), + propertiesJSON?.safeString("timezone_id"), + propertiesJSON?.safeString("country"), + propertiesJSON?.safeDouble("lat"), + propertiesJSON?.safeDouble("long") + ) + + val respSubscriptions = jsonObject.expandJSONArray("subscriptions") { + val subscriptionType = SubscriptionObjectType.fromString(it.getString("type")) + if (subscriptionType != null) { + return@expandJSONArray SubscriptionObject( + it.getString("id"), + subscriptionType, + it.safeString("token"), + it.safeBool("enabled"), + it.safeInt("notification_types"), + it.safeString("sdk"), + it.safeString("device_model"), + it.safeString("device_os"), + it.safeBool("rooted"), + it.safeInt("test_type"), + it.safeString("app_version") + ) + } + return@expandJSONArray null + } + + return CreateUserResponse(respIdentities, respProperties, respSubscriptions) + } + + fun convertToJSON(properties: PropertiesObject): JSONObject { + return JSONObject() + .putMap("tags", properties.tags) + .putSafe("language", properties.language) + .putSafe("timezone_id", properties.timezoneId) + .putSafe("lat", properties.latitude) + .putSafe("long", properties.longitude) + .putSafe("country", properties.country) + } + + fun convertToJSON(propertiesDeltas: PropertiesDeltasObject): JSONObject { + return JSONObject() + .putSafe("session_time", propertiesDeltas.sessionTime) + .putSafe("session_count", propertiesDeltas.sessionCount) + .putSafe("amount_spent", propertiesDeltas.amountSpent?.toString()) + .putJSONArray("purchases", propertiesDeltas.purchases) { + JSONObject() + .put("sku", it.sku) + .put("iso", it.iso) + .put("amount", it.amount.toString()) + } + } + + fun convertToJSON(subscriptions: List): JSONArray { + val subscriptionsArray = JSONArray() + + for (subscription in subscriptions) { + subscriptionsArray.put(convertToJSON(subscription)) + } + + return subscriptionsArray + } + + fun convertToJSON(subscription: SubscriptionObject): JSONObject { + return JSONObject() + .put("type", subscription.type.value) + .putSafe("token", subscription.token) + .putSafe("enabled", subscription.enabled) + .putSafe("notification_types", subscription.notificationTypes) + .putSafe("sdk", subscription.sdk) + .putSafe("device_model", subscription.deviceModel) + .putSafe("device_os", subscription.deviceOS) + .putSafe("rooted", subscription.rooted) + .putSafe("net_type", subscription.netType) + .putSafe("carrier", subscription.carrier) + } +} diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/impl/SubscriptionBackendService.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/impl/SubscriptionBackendService.kt index 67b25c6ce5..a835f82a8c 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/impl/SubscriptionBackendService.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/impl/SubscriptionBackendService.kt @@ -1,146 +1,52 @@ package com.onesignal.user.internal.backend.impl -import android.content.pm.PackageManager -import android.os.Build -import com.onesignal.common.DeviceUtils -import com.onesignal.common.OneSignalUtils -import com.onesignal.common.RootToolsInternalMethods -import com.onesignal.common.TimeUtils import com.onesignal.common.exceptions.BackendException -import com.onesignal.core.internal.application.IApplicationService -import com.onesignal.core.internal.device.IDeviceService +import com.onesignal.common.safeJSONObject import com.onesignal.core.internal.http.IHttpClient -import com.onesignal.debug.internal.logging.Logging import com.onesignal.user.internal.backend.ISubscriptionBackendService -import com.onesignal.user.internal.backend.SubscriptionObjectType -import com.onesignal.user.internal.subscriptions.SubscriptionStatus -import org.json.JSONException +import com.onesignal.user.internal.backend.SubscriptionObject import org.json.JSONObject -import java.util.Locale -import java.util.UUID internal class SubscriptionBackendService( - private val _application: IApplicationService, - private val _device: IDeviceService, - private val _http: IHttpClient + private val _httpClient: IHttpClient ) : ISubscriptionBackendService { - override suspend fun createSubscription(appId: String, aliasLabel: String, aliasValue: String, type: SubscriptionObjectType, enabled: Boolean, address: String, status: SubscriptionStatus): String { - // TODO: To Implement, temporarily using players endpoint when PUSH - if (type == SubscriptionObjectType.SMS || type == SubscriptionObjectType.EMAIL) { - return UUID.randomUUID().toString() - } - - val json = JSONObject() - try { - json.put("app_id", appId) - json.put("device_type", _device.deviceType.value) - json.put("device_model", Build.MODEL) - json.put("device_os", Build.VERSION.RELEASE) - json.put("timezone", TimeUtils.getTimeZoneOffset()) - json.put("timezone_id", TimeUtils.getTimeZoneId()) - json.put("language", getLanguage()) - json.put("sdk", OneSignalUtils.sdkVersion) - json.put("sdk_type", OneSignalUtils.sdkType) - json.put("android_package", _application.appContext.packageName) - - try { - json.put("game_version", _application.appContext.packageManager.getPackageInfo(_application.appContext.packageName, 0).versionCode) - } catch (e: PackageManager.NameNotFoundException) { - } - - json.put("net_type", DeviceUtils.getNetType(_application.appContext)) - json.put("carrier", DeviceUtils.getCarrierName(_application.appContext)) - json.put("rooted", RootToolsInternalMethods.isRooted) - - json.put("identifier", address) - } catch (e: JSONException) { - Logging.error("Could not create JSON payload for create subscription", e) - } + override suspend fun createSubscription(appId: String, aliasLabel: String, aliasValue: String, subscription: SubscriptionObject): String { + val requestJSON = JSONObject() + .put("subscription", JSONConverter.convertToJSON(subscription)) + .put("retain_previous_owner", true) - val response = _http.post("players", json) + val response = _httpClient.post("apps/$appId/users/by/$aliasLabel/$aliasValue/subscriptions", requestJSON) if (!response.isSuccess) { throw BackendException(response.statusCode, response.payload) } - val responseJSON = JSONObject(response.payload) - if (!responseJSON.has("id")) { + val responseJSON = JSONObject(response.payload!!) + val subscriptionJSON = responseJSON.safeJSONObject("subscription") + if (subscriptionJSON == null || !subscriptionJSON.has("id")) { throw BackendException(response.statusCode, response.payload) } - return responseJSON.getString("id") + return subscriptionJSON.getString("id") } - override suspend fun updateSubscription(appId: String, subscriptionId: String, type: SubscriptionObjectType, enabled: Boolean, address: String, status: SubscriptionStatus) { - // TODO: To Implement, temporarily using players endpoint when PUSH - if (type == SubscriptionObjectType.SMS || type == SubscriptionObjectType.EMAIL) { - return - } - - val json = JSONObject() - try { - json.put("app_id", appId) - json.put("device_type", _device.deviceType.value) - json.put("device_model", Build.MODEL) - json.put("device_os", Build.VERSION.RELEASE) - json.put("timezone", TimeUtils.getTimeZoneOffset()) - json.put("timezone_id", TimeUtils.getTimeZoneId()) - json.put("language", getLanguage()) - json.put("sdk", OneSignalUtils.sdkVersion) - json.put("sdk_type", OneSignalUtils.sdkType) - json.put("android_package", _application.appContext.packageName) - - try { - json.put("game_version", _application.appContext.packageManager.getPackageInfo(_application.appContext.packageName, 0).versionCode) - } catch (e: PackageManager.NameNotFoundException) { - } - - json.put("net_type", DeviceUtils.getNetType(_application.appContext)) - json.put("carrier", DeviceUtils.getCarrierName(_application.appContext)) - json.put("rooted", RootToolsInternalMethods.isRooted) + override suspend fun updateSubscription(appId: String, subscriptionId: String, subscription: SubscriptionObject) { + val requestJSON = JSONObject() + .put("subscription", JSONConverter.convertToJSON(subscription)) - json.put("identifier", address) - } catch (e: JSONException) { - Logging.error("Could not create JSON payload for create subscription", e) - } - - val response = _http.post("players/$subscriptionId/on_session", json) + val response = _httpClient.patch("apps/$appId/subscriptions/$subscriptionId", requestJSON) if (!response.isSuccess) { throw BackendException(response.statusCode, response.payload) } - - val responseJSON = JSONObject(response.payload) - if (!responseJSON.has("id")) { - throw BackendException(response.statusCode, response.payload) - } } override suspend fun deleteSubscription(appId: String, subscriptionId: String) { - // TODO: To Implement - } + val response = _httpClient.delete("apps/$appId/subscriptions/$subscriptionId") - // TODO: Temporary placement until language services hooked back in - private fun getLanguage(): String? { - val language = Locale.getDefault().language - return when (language) { - HEBREW_INCORRECT -> HEBREW_CORRECTED - INDONESIAN_INCORRECT -> INDONESIAN_CORRECTED - YIDDISH_INCORRECT -> YIDDISH_CORRECTED - CHINESE -> language + "-" + Locale.getDefault().country - else -> language + if (!response.isSuccess) { + throw BackendException(response.statusCode, response.payload) } } - - companion object { - private const val ERRORS = "errors" - private const val HEBREW_INCORRECT = "iw" - private const val HEBREW_CORRECTED = "he" - private const val INDONESIAN_INCORRECT = "in" - private const val INDONESIAN_CORRECTED = "id" - private const val YIDDISH_INCORRECT = "ji" - private const val YIDDISH_CORRECTED = "yi" - private const val CHINESE = "zh" - } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/impl/UserBackendService.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/impl/UserBackendService.kt index 1fa6c004c9..48a4bdee02 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/impl/UserBackendService.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/impl/UserBackendService.kt @@ -1,215 +1,74 @@ package com.onesignal.user.internal.backend.impl +import com.onesignal.common.exceptions.BackendException +import com.onesignal.common.putJSONObject +import com.onesignal.common.putMap import com.onesignal.core.internal.http.IHttpClient import com.onesignal.user.internal.backend.CreateUserResponse -import com.onesignal.user.internal.backend.ISubscriptionBackendService import com.onesignal.user.internal.backend.IUserBackendService -import com.onesignal.user.internal.backend.IdentityConstants import com.onesignal.user.internal.backend.PropertiesDeltasObject import com.onesignal.user.internal.backend.PropertiesObject import com.onesignal.user.internal.backend.SubscriptionObject -import com.onesignal.user.internal.backend.SubscriptionObjectType -import com.onesignal.user.internal.subscriptions.SubscriptionStatus -import org.json.JSONArray import org.json.JSONObject -import java.util.UUID internal class UserBackendService( - private val _httpClient: IHttpClient, - private val _subscriptionBackend: ISubscriptionBackendService + private val _httpClient: IHttpClient ) : IUserBackendService { override suspend fun createUser(appId: String, identities: Map, properties: PropertiesObject, subscriptions: List): CreateUserResponse { - // Eventually.... - val propertiesObject = createPropertiesJSON(properties) - val identityObject = JSONObject() - var subscriptionsArray = createSubscriptionArrayJSON(subscriptions) - - // identity - for (identity in identities) { - identityObject.put(identity.key, identity.value) - } - val requestJSON = JSONObject() - .put("properties", propertiesObject) - .put("identity", identityObject) - .put("subscriptions", subscriptionsArray) - - // TODO: Temporarily using players endpoint via subscription backend to register the subscription, so we can drive push/IAMs. - val subscriptionIDs = mutableListOf() - for (subscription in subscriptions) { - val status: SubscriptionStatus = if (subscription.notificationTypes == null) { - SubscriptionStatus.SUBSCRIBED - } else { - SubscriptionStatus.values().firstOrNull { it.value == subscription.notificationTypes } ?: SubscriptionStatus.SUBSCRIBED - } - - val subscriptionId = _subscriptionBackend.createSubscription(appId, "", "", subscription.type, subscription.enabled ?: true, subscription.token ?: "", status) - subscriptionIDs.add(subscriptionId) - } - - // SCAFFOLD: Generate a new onesignal ID from the "backend" - val mutIdentities = identities.toMutableMap() - mutIdentities[IdentityConstants.ONESIGNAL_ID] = UUID.randomUUID().toString() - - return CreateUserResponse(mutIdentities, properties, subscriptionIDs.map { SubscriptionObject(it, SubscriptionObjectType.ANDROID_PUSH) }) - } - - override suspend fun updateUser(appId: String, aliasLabel: String, aliasValue: String, properties: PropertiesObject, refreshDeviceMetadata: Boolean, propertyiesDelta: PropertiesDeltasObject) { - val propertiesObject = createPropertiesJSON(properties) - val deltasObject = createPropertiesDeltaJSON(propertyiesDelta) - - val jsonObject = JSONObject() - .put("properties", propertiesObject) - .put("refresh_device_metadata", refreshDeviceMetadata) - .put("deltas", deltasObject) - - // TODO: To Implement: Call backend with jsonObject and deserialize response - } - - override suspend fun getUser(appId: String, aliasLabel: String, aliasValue: String): CreateUserResponse { -// val response = _http.get("apps/$appId/user/by/$aliasLabel/$aliasValue") -// -// if(!response.isSuccess) { -// throw BackendException(response.statusCode, response.payload) -// } - - // TODO: Implement -// val jsonObject = JSONObject(response.payload) - - val language: String? = null - val timezoneId: UInt? = null - val country: String? = null - return CreateUserResponse(mapOf(IdentityConstants.ONESIGNAL_ID to aliasValue), PropertiesObject(mapOf(), language, timezoneId, country), listOf()) - } - - private fun createPropertiesJSON(properties: PropertiesObject): JSONObject { - val propertiesObject = JSONObject() - if (properties.tags != null && properties.tags.isNotEmpty()) { - val tagsObject = JSONObject() - for (tag in properties.tags) { - tagsObject.put(tag.key, tag.value) - } - propertiesObject.put("tags", tagsObject) + if (identities.isNotEmpty()) { + requestJSON.put("identity", JSONObject().putMap(identities)) } - if (properties.language != null) { - propertiesObject.put("language", properties.language) + if (properties.hasAtLeastOnePropertySet) { + requestJSON.put("properties", JSONConverter.convertToJSON(properties)) } - if (properties.timezoneId != null) { - propertiesObject.put("timezone_id", properties.timezoneId) + if (subscriptions.isNotEmpty()) { + requestJSON + .put("subscriptions", JSONConverter.convertToJSON(subscriptions)) + .putJSONObject("subscription_options") { + it.put("retain_previous_owner", true) + } } - if (properties.latitude != null) { - propertiesObject.put("lat", properties.latitude) - } - - if (properties.longitude != null) { - propertiesObject.put("long", properties.longitude) - } + val response = _httpClient.post("apps/$appId/users", requestJSON) - if (properties.country != null) { - propertiesObject.put("country", properties.country) + if (!response.isSuccess) { + throw BackendException(response.statusCode, response.payload) } - return propertiesObject + return JSONConverter.convertToCreateUserResponse(JSONObject(response.payload!!)) } - private fun createPropertiesDeltaJSON(propertiesDeltas: PropertiesDeltasObject): JSONObject { - val deltasObject = JSONObject() + override suspend fun updateUser(appId: String, aliasLabel: String, aliasValue: String, properties: PropertiesObject, refreshDeviceMetadata: Boolean, propertyiesDelta: PropertiesDeltasObject) { + val jsonObject = JSONObject() + .put("refresh_device_metadata", refreshDeviceMetadata) - if (propertiesDeltas.sessionTime != null) { - deltasObject.put("session_time", propertiesDeltas.sessionTime) + if (properties.hasAtLeastOnePropertySet) { + jsonObject.put("properties", JSONConverter.convertToJSON(properties)) } - if (propertiesDeltas.sessionCount != null) { - deltasObject.put("session_counts", propertiesDeltas.sessionCount) + if (propertyiesDelta.hasAtLeastOnePropertySet) { + jsonObject.put("deltas", JSONConverter.convertToJSON(propertyiesDelta)) } - if (propertiesDeltas.amountSpent != null) { - deltasObject.put("amount_spent", propertiesDeltas.amountSpent) - } + val response = _httpClient.patch("apps/$appId/users/by/$aliasLabel/$aliasValue", jsonObject) - if (propertiesDeltas.purchases != null) { - val purchasesArray = JSONArray() - for (purchase in propertiesDeltas.purchases) { - val jsonItem = JSONObject() - jsonItem.put("sku", purchase.sku) - jsonItem.put("iso", purchase.iso) - jsonItem.put("amount", purchase.amount.toString()) - purchasesArray.put(jsonItem) - } - deltasObject.put("purchases", purchasesArray) + if (!response.isSuccess) { + throw BackendException(response.statusCode, response.payload) } - - return deltasObject } - private fun createSubscriptionArrayJSON(subscriptions: List): JSONArray { - var subscriptionsArray = JSONArray() - - for (subscription in subscriptions) { - val subscriptionObject = JSONObject() - subscriptionObject.put("id", subscription.id) - subscriptionObject.put("type", subscription.type.value) - - if (subscription.token != null) { - subscriptionObject.put("token", subscription.token) - } - - if (subscription.enabled != null) { - subscriptionObject.put("enabled", subscription.enabled) - } - - if (subscription.notificationTypes != null) { - subscriptionObject.put("notification_types", subscription.notificationTypes) - } - - if (subscription.sdk != null) { - subscriptionObject.put("sdk", subscription.sdk) - } - - if (subscription.deviceModel != null) { - subscriptionObject.put("device_model", subscription.deviceModel) - } - - if (subscription.deviceOS != null) { - subscriptionObject.put("device_os", subscription.deviceOS) - } - - if (subscription.rooted != null) { - subscriptionObject.put("rooted", subscription.rooted) - } - - if (subscription.testType != null) { - subscriptionObject.put("test_type", subscription.testType) - } - - if (subscription.appVersion != null) { - subscriptionObject.put("app_version", subscription.appVersion) - } - - if (subscription.netType != null) { - subscriptionObject.put("net_type", subscription.netType) - } - - if (subscription.carrier != null) { - subscriptionObject.put("carrier", subscription.carrier) - } - - if (subscription.webAuth != null) { - subscriptionObject.put("web_auth", subscription.webAuth) - } - - if (subscription.webP256 != null) { - subscriptionObject.put("web_p256", subscription.webP256) - } + override suspend fun getUser(appId: String, aliasLabel: String, aliasValue: String): CreateUserResponse { + val response = _httpClient.get("apps/$appId/users/by/$aliasLabel/$aliasValue") - subscriptionsArray.put(subscriptionObject) + if (!response.isSuccess) { + throw BackendException(response.statusCode, response.payload) } - return subscriptionsArray + return JSONConverter.convertToCreateUserResponse(JSONObject(response.payload)) } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/identity/IdentityModel.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/identity/IdentityModel.kt index e7fbfadc22..fb1cca8895 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/identity/IdentityModel.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/identity/IdentityModel.kt @@ -16,13 +16,13 @@ class IdentityModel : MapModel() { * created on the backend or not. */ var onesignalId: String - get() = getProperty(IdentityConstants.ONESIGNAL_ID) - set(value) { setProperty(IdentityConstants.ONESIGNAL_ID, value) } + get() = getStringProperty(IdentityConstants.ONESIGNAL_ID) + set(value) { setStringProperty(IdentityConstants.ONESIGNAL_ID, value) } /** * The (developer managed) identifier that uniquely identifies this user. */ var externalId: String? - get() = getProperty(IdentityConstants.EXTERNAL_ID) - set(value) { setProperty(IdentityConstants.EXTERNAL_ID, value) } + get() = getOptStringProperty(IdentityConstants.EXTERNAL_ID) + set(value) { setOptStringProperty(IdentityConstants.EXTERNAL_ID, value) } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/CreateSubscriptionOperation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/CreateSubscriptionOperation.kt index 536d40fa27..fd8954ab13 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/CreateSubscriptionOperation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/CreateSubscriptionOperation.kt @@ -16,38 +16,38 @@ class CreateSubscriptionOperation() : Operation(SubscriptionOperationExecutor.CR * The application ID this subscription will be created under. */ var appId: String - get() = getProperty(::appId.name) - private set(value) { setProperty(::appId.name, value) } + get() = getStringProperty(::appId.name) + private set(value) { setStringProperty(::appId.name, value) } /** * The user ID this subscription will be associated with. This ID *may* be locally generated * and can be checked via [IDManager.isLocalId] to ensure correct processing. */ var onesignalId: String - get() = getProperty(::onesignalId.name) - private set(value) { setProperty(::onesignalId.name, value) } + get() = getStringProperty(::onesignalId.name) + private set(value) { setStringProperty(::onesignalId.name, value) } /** * The local ID of the subscription being created. The subscription model with this ID will have its * ID updated with the backend-generated ID post-create. */ var subscriptionId: String - get() = getProperty(::subscriptionId.name) - private set(value) { setProperty(::subscriptionId.name, value) } + get() = getStringProperty(::subscriptionId.name) + private set(value) { setStringProperty(::subscriptionId.name, value) } /** * The type of subscription. */ var type: SubscriptionType - get() = SubscriptionType.valueOf(getProperty(::type.name)) - private set(value) { setProperty(::type.name, value.toString()) } + get() = getEnumProperty(::type.name) + private set(value) { setEnumProperty(::type.name, value) } /** * Whether this subscription is currently enabled. */ var enabled: Boolean - get() = getProperty(::enabled.name) - private set(value) { setProperty(::enabled.name, value) } + get() = getBooleanProperty(::enabled.name) + private set(value) { setBooleanProperty(::enabled.name, value) } /** * The address-specific information for this subscription. Its contents depends on the type @@ -58,18 +58,15 @@ class CreateSubscriptionOperation() : Operation(SubscriptionOperationExecutor.CR * * [SubscriptionType.PUSH]: The push token. */ var address: String - get() = getProperty(::address.name) - private set(value) { setProperty(::address.name, value) } + get() = getStringProperty(::address.name) + private set(value) { setStringProperty(::address.name, value) } /** * The status of this subscription. */ var status: SubscriptionStatus - get() { - val value = getProperty(::status.name) - return SubscriptionStatus.values().first { it.value == value } - } - private set(value) { setProperty(::status.name, value.value) } + get() = getEnumProperty(::status.name) + private set(value) { setEnumProperty(::status.name, value) } override val createComparisonKey: String get() = "$appId.User.$onesignalId" override val modifyComparisonKey: String get() = "$appId.User.$onesignalId.Subscription.$subscriptionId" diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/DeleteAliasOperation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/DeleteAliasOperation.kt index 7ab3c4dea9..cb5ba29cf4 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/DeleteAliasOperation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/DeleteAliasOperation.kt @@ -13,23 +13,23 @@ class DeleteAliasOperation() : Operation(IdentityOperationExecutor.DELETE_ALIAS) * The application ID this alias that is to be deleted. */ var appId: String - get() = getProperty(::appId.name) - private set(value) { setProperty(::appId.name, value) } + get() = getStringProperty(::appId.name) + private set(value) { setStringProperty(::appId.name, value) } /** * The user ID this subscription will be associated with. This ID *may* be locally generated * and can be checked via [IDManager.isLocalId] to ensure correct processing. */ var onesignalId: String - get() = getProperty(::onesignalId.name) - private set(value) { setProperty(::onesignalId.name, value) } + get() = getStringProperty(::onesignalId.name) + private set(value) { setStringProperty(::onesignalId.name, value) } /** * The alias label to be deleted. */ var label: String - get() = getProperty(::label.name) - private set(value) { setProperty(::label.name, value) } + get() = getStringProperty(::label.name) + private set(value) { setStringProperty(::label.name, value) } override val createComparisonKey: String get() = "$appId.User.$onesignalId" override val modifyComparisonKey: String get() = "$appId.User.$onesignalId.Alias.$label" diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/DeleteSubscriptionOperation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/DeleteSubscriptionOperation.kt index ae1b422625..5b06e88998 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/DeleteSubscriptionOperation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/DeleteSubscriptionOperation.kt @@ -13,24 +13,24 @@ class DeleteSubscriptionOperation() : Operation(SubscriptionOperationExecutor.DE * The application ID this subscription that is to be deleted. */ var appId: String - get() = getProperty(::appId.name) - private set(value) { setProperty(::appId.name, value) } + get() = getStringProperty(::appId.name) + private set(value) { setStringProperty(::appId.name, value) } /** * The user ID this subscription will be associated with. This ID *may* be locally generated * and can be checked via [IDManager.isLocalId] to ensure correct processing. */ var onesignalId: String - get() = getProperty(::onesignalId.name) - private set(value) { setProperty(::onesignalId.name, value) } + get() = getStringProperty(::onesignalId.name) + private set(value) { setStringProperty(::onesignalId.name, value) } /** * The subscription ID that is to be deleted. This ID *may* be locally generated * and can be checked via [IDManager.isLocalId] to ensure correct processing. */ var subscriptionId: String - get() = getProperty(::subscriptionId.name) - private set(value) { setProperty(::subscriptionId.name, value) } + get() = getStringProperty(::subscriptionId.name) + private set(value) { setStringProperty(::subscriptionId.name, value) } override val createComparisonKey: String get() = "$appId.User.$onesignalId" override val modifyComparisonKey: String get() = "$appId.User.$onesignalId.Subscription.$subscriptionId" diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/DeleteTagOperation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/DeleteTagOperation.kt index 8f41a0ae29..f6e2081731 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/DeleteTagOperation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/DeleteTagOperation.kt @@ -14,23 +14,23 @@ class DeleteTagOperation() : Operation(UpdateUserOperationExecutor.DELETE_TAG) { * The application ID this subscription will be created under. */ var appId: String - get() = getProperty(::appId.name) - private set(value) { setProperty(::appId.name, value) } + get() = getStringProperty(::appId.name) + private set(value) { setStringProperty(::appId.name, value) } /** * The user ID this subscription will be associated with. This ID *may* be locally generated * and can be checked via [IDManager.isLocalId] to ensure correct processing. */ var onesignalId: String - get() = getProperty(::onesignalId.name) - private set(value) { setProperty(::onesignalId.name, value) } + get() = getStringProperty(::onesignalId.name) + private set(value) { setStringProperty(::onesignalId.name, value) } /** * The tag key to delete. */ var key: String - get() = getProperty(::key.name) - private set(value) { setProperty(::key.name, value) } + get() = getStringProperty(::key.name) + private set(value) { setStringProperty(::key.name, value) } override val createComparisonKey: String get() = "$appId.User.$onesignalId" override val modifyComparisonKey: String get() = createComparisonKey diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/LoginUserOperation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/LoginUserOperation.kt index dcff745eec..46fe079ff7 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/LoginUserOperation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/LoginUserOperation.kt @@ -17,23 +17,23 @@ class LoginUserOperation() : Operation(LoginUserOperationExecutor.LOGIN_USER) { * The application ID the user will exist/be logged in under. */ var appId: String - get() = getProperty(::appId.name) - private set(value) { setProperty(::appId.name, value) } + get() = getStringProperty(::appId.name) + private set(value) { setStringProperty(::appId.name, value) } /** * The local OneSignal ID this user was initially logged in under. The user models with this ID * will have its ID updated with the backend-generated ID post-create. */ var onesignalId: String - get() = getProperty(::onesignalId.name) - private set(value) { setProperty(::onesignalId.name, value) } + get() = getStringProperty(::onesignalId.name) + private set(value) { setStringProperty(::onesignalId.name, value) } /** * The optional external ID of this newly logged-in user. Must be unique for the [appId]. */ var externalId: String? - get() = getProperty(::externalId.name) - private set(value) { setProperty(::externalId.name, value) } + get() = getOptStringProperty(::externalId.name) + private set(value) { setOptStringProperty(::externalId.name, value) } /** * The user ID of an existing user the [externalId] will be attempted to be associated to first. @@ -41,8 +41,8 @@ class LoginUserOperation() : Operation(LoginUserOperationExecutor.LOGIN_USER) { * and can be checked via [IDManager.isLocalId] to ensure correct processing. */ var existingOnesignalId: String? - get() = getProperty(::existingOnesignalId.name) - private set(value) { setProperty(::existingOnesignalId.name, value) } + get() = getOptStringProperty(::existingOnesignalId.name) + private set(value) { setOptStringProperty(::existingOnesignalId.name, value) } override val createComparisonKey: String get() = "$appId.User.$onesignalId" override val modifyComparisonKey: String = "" diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/RefreshUserOperation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/RefreshUserOperation.kt index 05923d3ac3..f1227d9284 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/RefreshUserOperation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/RefreshUserOperation.kt @@ -14,20 +14,20 @@ class RefreshUserOperation() : Operation(RefreshUserOperationExecutor.REFRESH_US * The application ID this subscription will be created under. */ var appId: String - get() = getProperty(::appId.name) - private set(value) { setProperty(::appId.name, value) } + get() = getStringProperty(::appId.name) + private set(value) { setStringProperty(::appId.name, value) } /** * The user ID this subscription will be associated with. This ID *may* be locally generated * and can be checked via [IDManager.isLocalId] to ensure correct processing. */ var onesignalId: String - get() = getProperty(::onesignalId.name) - private set(value) { setProperty(::onesignalId.name, value) } + get() = getStringProperty(::onesignalId.name) + private set(value) { setStringProperty(::onesignalId.name, value) } - override val createComparisonKey: String get() = "" - override val modifyComparisonKey: String get() = "" - override val groupComparisonType: GroupComparisonType = GroupComparisonType.NONE + override val createComparisonKey: String get() = "$appId.User.$onesignalId.Refresh" + override val modifyComparisonKey: String get() = "$appId.User.$onesignalId.Refresh" + override val groupComparisonType: GroupComparisonType = GroupComparisonType.CREATE override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) constructor(appId: String, onesignalId: String) : this() { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/SetAliasOperation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/SetAliasOperation.kt index 04444d4317..28fbddbf25 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/SetAliasOperation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/SetAliasOperation.kt @@ -14,30 +14,30 @@ class SetAliasOperation() : Operation(IdentityOperationExecutor.SET_ALIAS) { * The application ID this subscription will be created under. */ var appId: String - get() = getProperty(::appId.name) - private set(value) { setProperty(::appId.name, value) } + get() = getStringProperty(::appId.name) + private set(value) { setStringProperty(::appId.name, value) } /** * The user ID this subscription will be associated with. This ID *may* be locally generated * and can be checked via [IDManager.isLocalId] to ensure correct processing. */ var onesignalId: String - get() = getProperty(::onesignalId.name) - private set(value) { setProperty(::onesignalId.name, value) } + get() = getStringProperty(::onesignalId.name) + private set(value) { setStringProperty(::onesignalId.name, value) } /** * The alias label. */ var label: String - get() = getProperty(::label.name) - private set(value) { setProperty(::label.name, value) } + get() = getStringProperty(::label.name) + private set(value) { setStringProperty(::label.name, value) } /** * The alias value. */ var value: String - get() = getProperty(::value.name) - private set(value) { setProperty(::value.name, value) } + get() = getStringProperty(::value.name) + private set(value) { setStringProperty(::value.name, value) } override val createComparisonKey: String get() = "$appId.User.$onesignalId" override val modifyComparisonKey: String get() = "$appId.User.$onesignalId.Identity.$label" diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/SetPropertyOperation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/SetPropertyOperation.kt index e2e37c9b17..ca951fcdff 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/SetPropertyOperation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/SetPropertyOperation.kt @@ -13,30 +13,30 @@ class SetPropertyOperation() : Operation(UpdateUserOperationExecutor.SET_PROPERT * The OneSignal appId the purchase was captured under. */ var appId: String - get() = getProperty(::appId.name) - private set(value) { setProperty(::appId.name, value) } + get() = getStringProperty(::appId.name) + private set(value) { setStringProperty(::appId.name, value) } /** * The OneSignal ID the purchase was captured under. This ID *may* be locally generated * and can be checked via [IDManager.isLocalId] to ensure correct processing. */ var onesignalId: String - get() = getProperty(::onesignalId.name) - private set(value) { setProperty(::onesignalId.name, value) } + get() = getStringProperty(::onesignalId.name) + private set(value) { setStringProperty(::onesignalId.name, value) } /** * The property that is to be updated against the user. */ var property: String - get() = getProperty(::property.name) - private set(value) { setProperty(::property.name, value) } + get() = getStringProperty(::property.name) + private set(value) { setStringProperty(::property.name, value) } /** * The value of that property to update it to. */ var value: Any? - get() = getProperty(::value.name) - private set(value) { setProperty(::value.name, value) } + get() = getOptAnyProperty(::value.name) + private set(value) { setOptAnyProperty(::value.name, value) } override val createComparisonKey: String get() = "$appId.User.$onesignalId" override val modifyComparisonKey: String get() = createComparisonKey diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/SetTagOperation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/SetTagOperation.kt index 1d8a156d8c..ab83e69090 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/SetTagOperation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/SetTagOperation.kt @@ -14,30 +14,30 @@ class SetTagOperation() : Operation(UpdateUserOperationExecutor.SET_TAG) { * The application ID this subscription will be created under. */ var appId: String - get() = getProperty(::appId.name) - private set(value) { setProperty(::appId.name, value) } + get() = getStringProperty(::appId.name) + private set(value) { setStringProperty(::appId.name, value) } /** * The user ID this subscription will be associated with. This ID *may* be locally generated * and can be checked via [IDManager.isLocalId] to ensure correct processing. */ var onesignalId: String - get() = getProperty(::onesignalId.name) - private set(value) { setProperty(::onesignalId.name, value) } + get() = getStringProperty(::onesignalId.name) + private set(value) { setStringProperty(::onesignalId.name, value) } /** * The tag key. */ var key: String - get() = getProperty(::key.name) - private set(value) { setProperty(::key.name, value) } + get() = getStringProperty(::key.name) + private set(value) { setStringProperty(::key.name, value) } /** * The new/updated tag value. */ var value: String - get() = getProperty(::value.name) - private set(value) { setProperty(::value.name, value) } + get() = getStringProperty(::value.name) + private set(value) { setStringProperty(::value.name, value) } override val createComparisonKey: String get() = "$appId.User.$onesignalId" override val modifyComparisonKey: String get() = createComparisonKey diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/TrackPurchaseOperation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/TrackPurchaseOperation.kt index 40a1a75a10..235e7fee70 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/TrackPurchaseOperation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/TrackPurchaseOperation.kt @@ -17,37 +17,37 @@ class TrackPurchaseOperation() : Operation(UpdateUserOperationExecutor.TRACK_PUR * The OneSignal appId the purchase was captured under. */ var appId: String - get() = getProperty(::appId.name) - private set(value) { setProperty(::appId.name, value) } + get() = getStringProperty(::appId.name) + private set(value) { setStringProperty(::appId.name, value) } /** * The OneSignal ID the purchase was captured under. This ID *may* be locally generated * and can be checked via [IDManager.isLocalId] to ensure correct processing. */ var onesignalId: String - get() = getProperty(::onesignalId.name) - private set(value) { setProperty(::onesignalId.name, value) } + get() = getStringProperty(::onesignalId.name) + private set(value) { setStringProperty(::onesignalId.name, value) } /** * Whether to treat new purchases as an existing purchase. */ var treatNewAsExisting: Boolean - get() = getProperty(::treatNewAsExisting.name) - private set(value) { setProperty(::treatNewAsExisting.name, value) } + get() = getBooleanProperty(::treatNewAsExisting.name) + private set(value) { setBooleanProperty(::treatNewAsExisting.name, value) } /** * The amount spent by the user. */ var amountSpent: BigDecimal - get() = getProperty(::amountSpent.name) - private set(value) { setProperty(::amountSpent.name, value) } + get() = getBigDecimalProperty(::amountSpent.name) + private set(value) { setBigDecimalProperty(::amountSpent.name, value) } /** * The list of purchases that have been made. */ var purchases: List - get() = getProperty(::purchases.name) - private set(value) { setProperty(::purchases.name, value) } + get() = getListProperty(::purchases.name) + private set(value) { setListProperty(::purchases.name, value) } override val createComparisonKey: String get() = "" override val modifyComparisonKey: String get() = "$appId.User.$onesignalId" @@ -88,16 +88,16 @@ class TrackPurchaseOperation() : Operation(UpdateUserOperationExecutor.TRACK_PUR */ class PurchaseInfo() : Model() { var sku: String - get() = getProperty(::sku.name) - private set(value) { setProperty(::sku.name, value) } + get() = getStringProperty(::sku.name) + private set(value) { setStringProperty(::sku.name, value) } var iso: String - get() = getProperty(::iso.name) - private set(value) { setProperty(::iso.name, value) } + get() = getStringProperty(::iso.name) + private set(value) { setStringProperty(::iso.name, value) } var amount: BigDecimal - get() = getProperty(::amount.name) - private set(value) { setProperty(::amount.name, value) } + get() = getBigDecimalProperty(::amount.name) + private set(value) { setBigDecimalProperty(::amount.name, value) } constructor(sku: String, iso: String, amount: BigDecimal) : this() { this.sku = sku diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/TrackSessionEndOperation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/TrackSessionEndOperation.kt index 06f09a6cf4..e08cf5b49c 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/TrackSessionEndOperation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/TrackSessionEndOperation.kt @@ -13,23 +13,23 @@ class TrackSessionEndOperation() : Operation(UpdateUserOperationExecutor.TRACK_S * The OneSignal appId the session was captured under. */ var appId: String - get() = getProperty(::appId.name) - private set(value) { setProperty(::appId.name, value) } + get() = getStringProperty(::appId.name) + private set(value) { setStringProperty(::appId.name, value) } /** * The OneSignal ID driving the session. This ID *may* be locally generated * and can be checked via [IDManager.isLocalId] to ensure correct processing. */ var onesignalId: String - get() = getProperty(::onesignalId.name) - private set(value) { setProperty(::onesignalId.name, value) } + get() = getStringProperty(::onesignalId.name) + private set(value) { setStringProperty(::onesignalId.name, value) } /** * The amount of active time for the session, in milliseconds. */ var sessionTime: Long - get() = getProperty(::sessionTime.name) - private set(value) { setProperty(::sessionTime.name, value) } + get() = getLongProperty(::sessionTime.name) + private set(value) { setLongProperty(::sessionTime.name, value) } override val createComparisonKey: String get() = "" override val modifyComparisonKey: String get() = "$appId.User.$onesignalId" diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/TrackSessionStartOperation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/TrackSessionStartOperation.kt index 7df91d6206..db2818fdd6 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/TrackSessionStartOperation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/TrackSessionStartOperation.kt @@ -13,16 +13,16 @@ class TrackSessionStartOperation() : Operation(UpdateUserOperationExecutor.TRACK * The OneSignal appId the session was captured under. */ var appId: String - get() = getProperty(::appId.name) - private set(value) { setProperty(::appId.name, value) } + get() = getStringProperty(::appId.name) + private set(value) { setStringProperty(::appId.name, value) } /** * The OneSignal ID driving the session. This ID *may* be locally generated * and can be checked via [IDManager.isLocalId] to ensure correct processing. */ var onesignalId: String - get() = getProperty(::onesignalId.name) - private set(value) { setProperty(::onesignalId.name, value) } + get() = getStringProperty(::onesignalId.name) + private set(value) { setStringProperty(::onesignalId.name, value) } override val createComparisonKey: String get() = "" override val modifyComparisonKey: String get() = "$appId.User.$onesignalId" diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/UpdateSubscriptionOperation.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/UpdateSubscriptionOperation.kt index d302b06ab0..0e0a6853d0 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/UpdateSubscriptionOperation.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/UpdateSubscriptionOperation.kt @@ -15,38 +15,38 @@ class UpdateSubscriptionOperation() : Operation(SubscriptionOperationExecutor.UP * The application ID this subscription that is to be deleted. */ var appId: String - get() = getProperty(::appId.name) - private set(value) { setProperty(::appId.name, value) } + get() = getStringProperty(::appId.name) + private set(value) { setStringProperty(::appId.name, value) } /** * The user ID this subscription will be associated with. This ID *may* be locally generated * and can be checked via [IDManager.isLocalId] to ensure correct processing. */ var onesignalId: String - get() = getProperty(::onesignalId.name) - private set(value) { setProperty(::onesignalId.name, value) } + get() = getStringProperty(::onesignalId.name) + private set(value) { setStringProperty(::onesignalId.name, value) } /** * The subscription ID that is to be deleted. This ID *may* be locally generated * and can be checked via [IDManager.isLocalId] to ensure correct processing. */ var subscriptionId: String - get() = getProperty(::subscriptionId.name) - private set(value) { setProperty(::subscriptionId.name, value) } + get() = getStringProperty(::subscriptionId.name) + private set(value) { setStringProperty(::subscriptionId.name, value) } /** * The type of subscription. */ var type: SubscriptionType - get() = SubscriptionType.valueOf(getProperty(::type.name)) - private set(value) { setProperty(::type.name, value.toString()) } + get() = getEnumProperty(::type.name) + private set(value) { setEnumProperty(::type.name, value) } /** * Whether this subscription is currently enabled. */ var enabled: Boolean - get() = getProperty(::enabled.name) - private set(value) { setProperty(::enabled.name, value) } + get() = getBooleanProperty(::enabled.name) + private set(value) { setBooleanProperty(::enabled.name, value) } /** * The address-specific information for this subscription. Its contents depends on the type @@ -57,18 +57,15 @@ class UpdateSubscriptionOperation() : Operation(SubscriptionOperationExecutor.UP * * [SubscriptionType.PUSH]: The push token. */ var address: String - get() = getProperty(::address.name) - private set(value) { setProperty(::address.name, value) } + get() = getStringProperty(::address.name) + private set(value) { setStringProperty(::address.name, value) } /** * The status of this subscription. */ var status: SubscriptionStatus - get() { - val value = getProperty(::status.name) - return SubscriptionStatus.values().first { it.value == value } - } - private set(value) { setProperty(::status.name, value.value) } + get() = getEnumProperty(::status.name) + private set(value) { setEnumProperty(::status.name, value) } override val createComparisonKey: String get() = "$appId.User.$onesignalId" override val modifyComparisonKey: String get() = "$appId.User.$onesignalId.Subscription.$subscriptionId" diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/IdentityOperationExecutor.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/IdentityOperationExecutor.kt index 7957639a93..f54883ff81 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/IdentityOperationExecutor.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/IdentityOperationExecutor.kt @@ -32,7 +32,7 @@ internal class IdentityOperationExecutor( if (lastOperation is SetAliasOperation) { try { - _identityBackend.createAlias( + _identityBackend.setAlias( lastOperation.appId, IdentityConstants.ONESIGNAL_ID, lastOperation.onesignalId, @@ -41,10 +41,12 @@ internal class IdentityOperationExecutor( // ensure the now created alias is in the model as long as the user is still current. if (_identityModelStore.model.onesignalId == lastOperation.onesignalId) { - _identityModelStore.model.setProperty(lastOperation.label, lastOperation.value, ModelChangeTags.HYDRATE) + _identityModelStore.model.setStringProperty(lastOperation.label, lastOperation.value, ModelChangeTags.HYDRATE) } } catch (ex: BackendException) { - return if (NetworkUtils.shouldRetryNetworkRequest(ex.statusCode)) { + return if (ex.statusCode == 409) { + ExecutionResponse(ExecutionResult.FAIL_NORETRY) + } else if (NetworkUtils.shouldRetryNetworkRequest(ex.statusCode)) { ExecutionResponse(ExecutionResult.FAIL_RETRY) } else { ExecutionResponse(ExecutionResult.FAIL_NORETRY) @@ -56,7 +58,7 @@ internal class IdentityOperationExecutor( // ensure the now deleted alias is not in the model as long as the user is still current. if (_identityModelStore.model.onesignalId == lastOperation.onesignalId) { - _identityModelStore.model.setProperty(lastOperation.label, null, ModelChangeTags.HYDRATE) + _identityModelStore.model.setOptStringProperty(lastOperation.label, null, ModelChangeTags.HYDRATE) } } catch (ex: BackendException) { return if (NetworkUtils.shouldRetryNetworkRequest(ex.statusCode)) { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt index b25ebe60c2..d60c07bbd9 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt @@ -34,7 +34,6 @@ import com.onesignal.user.internal.properties.PropertiesModelStore import com.onesignal.user.internal.subscriptions.SubscriptionModel import com.onesignal.user.internal.subscriptions.SubscriptionModelStore import com.onesignal.user.internal.subscriptions.SubscriptionType -import java.util.UUID internal class LoginUserOperationExecutor( private val _identityOperationExecutor: IdentityOperationExecutor, @@ -79,11 +78,11 @@ internal class LoginUserOperationExecutor( // because the set alias was successful any grouped operations could not be executed, let the // caller know those still need to be executed. if (_identityModelStore.model.onesignalId == loginUserOp.onesignalId) { - _identityModelStore.model.setProperty(IdentityConstants.ONESIGNAL_ID, backendOneSignalId, ModelChangeTags.HYDRATE) + _identityModelStore.model.setStringProperty(IdentityConstants.ONESIGNAL_ID, backendOneSignalId, ModelChangeTags.HYDRATE) } if (_propertiesModelStore.model.onesignalId == loginUserOp.onesignalId) { - _propertiesModelStore.model.setProperty(PropertiesModel::onesignalId.name, backendOneSignalId, ModelChangeTags.HYDRATE) + _propertiesModelStore.model.setStringProperty(PropertiesModel::onesignalId.name, backendOneSignalId, ModelChangeTags.HYDRATE) } ExecutionResponse(ExecutionResult.SUCCESS_STARTING_ONLY, mapOf(loginUserOp.onesignalId to backendOneSignalId)) @@ -138,22 +137,13 @@ internal class LoginUserOperationExecutor( val propertiesModel = _propertiesModelStore.model if (identityModel.onesignalId == createUserOperation.onesignalId) { - identityModel.setProperty(IdentityConstants.ONESIGNAL_ID, backendOneSignalId, ModelChangeTags.HYDRATE) - - // TODO: hydrate any additional aliases from the backend... + identityModel.setStringProperty(IdentityConstants.ONESIGNAL_ID, backendOneSignalId, ModelChangeTags.HYDRATE) } if (propertiesModel.onesignalId == createUserOperation.onesignalId) { - propertiesModel.setProperty(PropertiesModel::onesignalId.name, backendOneSignalId, ModelChangeTags.HYDRATE) - - // TODO: hydrate the models from the backend create response. Temporarily inject dummy stuff to - // show that it's working. -// propertiesModel.setProperty(PropertiesModel::language.name, "en", notify = false) - propertiesModel.setProperty(PropertiesModel::country.name, "US", ModelChangeTags.HYDRATE) - propertiesModel.tags.setProperty("foo", UUID.randomUUID().toString(), ModelChangeTags.HYDRATE) + propertiesModel.setStringProperty(PropertiesModel::onesignalId.name, backendOneSignalId, ModelChangeTags.HYDRATE) } - // TODO: assumption that the response.subscriptionIDs will associate to the input subscriptionList...to confirm for (index in subscriptionList.indices) { if (index >= response.subscriptions.size) { break @@ -164,7 +154,7 @@ internal class LoginUserOperationExecutor( idTranslations[subscriptionList[index].id] = backendSubscription.id val subscriptionModel = _subscriptionsModelStore.get(subscriptionList[index].id) - subscriptionModel?.setProperty(SubscriptionModel::id.name, backendSubscription.id, ModelChangeTags.HYDRATE) + subscriptionModel?.setStringProperty(SubscriptionModel::id.name, backendSubscription.id, ModelChangeTags.HYDRATE) } return ExecutionResponse(ExecutionResult.SUCCESS, idTranslations) @@ -212,13 +202,9 @@ internal class LoginUserOperationExecutor( Build.MODEL, Build.VERSION.RELEASE, RootToolsInternalMethods.isRooted, - null, // TODO: What is test_type? - null, DeviceUtils.getNetType(_application.appContext), - DeviceUtils.getCarrierName(_application.appContext), - null, // TODO: Fill in - null - ) // TODO: Fill in + DeviceUtils.getCarrierName(_application.appContext) + ) // TODO: These are no longer captured? // subscriptionObject.put("sdk_type", OneSignalUtils.sdkType) @@ -245,12 +231,8 @@ internal class LoginUserOperationExecutor( subscriptions[operation.subscriptionId]!!.deviceModel, subscriptions[operation.subscriptionId]!!.deviceOS, subscriptions[operation.subscriptionId]!!.rooted, - subscriptions[operation.subscriptionId]!!.testType, - subscriptions[operation.subscriptionId]!!.appVersion, subscriptions[operation.subscriptionId]!!.netType, - subscriptions[operation.subscriptionId]!!.carrier, - subscriptions[operation.subscriptionId]!!.webAuth, - subscriptions[operation.subscriptionId]!!.webP256 + subscriptions[operation.subscriptionId]!!.carrier ) } // TODO: Is it possible for the Create to be after the Update? diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/PropertyOperationHelper.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/PropertyOperationHelper.kt index 98842ee7f1..c12501ccda 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/PropertyOperationHelper.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/PropertyOperationHelper.kt @@ -19,22 +19,25 @@ internal object PropertyOperationHelper { fun createPropertiesFromOperation(operation: DeleteTagOperation, propertiesObject: PropertiesObject): PropertiesObject { var tags = propertiesObject.tags?.toMutableMap() - tags?.remove(operation.key) + if (tags == null) { + tags = mutableMapOf() + } + tags[operation.key] = null return PropertiesObject(tags, propertiesObject.language, propertiesObject.timezoneId, propertiesObject.country, propertiesObject.latitude, propertiesObject.longitude) } fun createPropertiesFromOperation(operation: SetPropertyOperation, propertiesObject: PropertiesObject): PropertiesObject { return when (operation.property) { - PropertiesModel::language.name -> PropertiesObject(propertiesObject.tags, operation.value?.toString(), propertiesObject.timezoneId, propertiesObject.country, propertiesObject.latitude, propertiesObject.longitude, propertiesObject.locationAccuracy, propertiesObject.locationType, propertiesObject.locationBackground, propertiesObject.locationTimestamp) - PropertiesModel::timezone.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, operation.value?.toString(), propertiesObject.country, propertiesObject.latitude, propertiesObject.longitude, propertiesObject.locationAccuracy, propertiesObject.locationType, propertiesObject.locationBackground, propertiesObject.locationTimestamp) - PropertiesModel::country.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, operation.value?.toString(), propertiesObject.latitude, propertiesObject.longitude, propertiesObject.locationAccuracy, propertiesObject.locationType, propertiesObject.locationBackground, propertiesObject.locationTimestamp) - PropertiesModel::locationLatitude.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, propertiesObject.country, operation.value?.toString()?.toDouble(), propertiesObject.longitude, propertiesObject.locationAccuracy, propertiesObject.locationType, propertiesObject.locationBackground, propertiesObject.locationTimestamp) - PropertiesModel::locationLongitude.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, propertiesObject.country, propertiesObject.latitude, operation.value?.toString()?.toDouble(), propertiesObject.locationAccuracy, propertiesObject.locationType, propertiesObject.locationBackground, propertiesObject.locationTimestamp) - PropertiesModel::locationAccuracy.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, propertiesObject.country, propertiesObject.latitude, propertiesObject.longitude, operation.value?.toString()?.toFloat(), propertiesObject.locationType, propertiesObject.locationBackground, propertiesObject.locationTimestamp) - PropertiesModel::locationType.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, propertiesObject.country, propertiesObject.latitude, propertiesObject.longitude, propertiesObject.locationAccuracy, operation.value?.toString()?.toInt(), propertiesObject.locationBackground, propertiesObject.locationTimestamp) - PropertiesModel::locationBackground.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, propertiesObject.country, propertiesObject.latitude, propertiesObject.longitude, propertiesObject.locationAccuracy, propertiesObject.locationType, operation.value?.toString()?.toBoolean(), propertiesObject.locationTimestamp) - PropertiesModel::locationTimestamp.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, propertiesObject.country, propertiesObject.latitude, propertiesObject.longitude, propertiesObject.locationAccuracy, propertiesObject.locationType, propertiesObject.locationBackground, operation.value?.toString()?.toLong()) - else -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, propertiesObject.country, propertiesObject.latitude, propertiesObject.longitude, propertiesObject.locationAccuracy, propertiesObject.locationType, propertiesObject.locationBackground, propertiesObject.locationTimestamp) + PropertiesModel::language.name -> PropertiesObject(propertiesObject.tags, operation.value?.toString(), propertiesObject.timezoneId, propertiesObject.country, propertiesObject.latitude, propertiesObject.longitude) + PropertiesModel::timezone.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, operation.value?.toString(), propertiesObject.country, propertiesObject.latitude, propertiesObject.longitude) + PropertiesModel::country.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, operation.value?.toString(), propertiesObject.latitude, propertiesObject.longitude) + PropertiesModel::locationLatitude.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, propertiesObject.country, operation.value?.toString()?.toDouble(), propertiesObject.longitude) + PropertiesModel::locationLongitude.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, propertiesObject.country, propertiesObject.latitude, operation.value?.toString()?.toDouble()) +// PropertiesModel::locationAccuracy.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, propertiesObject.country, propertiesObject.latitude, propertiesObject.longitude) +// PropertiesModel::locationType.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, propertiesObject.country, propertiesObject.latitude, propertiesObject.longitude) +// PropertiesModel::locationBackground.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, propertiesObject.country, propertiesObject.latitude, propertiesObject.longitude) +// PropertiesModel::locationTimestamp.name -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, propertiesObject.country, propertiesObject.latitude, propertiesObject.longitude) + else -> PropertiesObject(propertiesObject.tags, propertiesObject.language, propertiesObject.timezoneId, propertiesObject.country, propertiesObject.latitude, propertiesObject.longitude) } } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt index 80de595bab..0519cd10eb 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt @@ -11,6 +11,7 @@ import com.onesignal.debug.LogLevel import com.onesignal.debug.internal.logging.Logging import com.onesignal.user.internal.backend.IUserBackendService import com.onesignal.user.internal.backend.IdentityConstants +import com.onesignal.user.internal.backend.SubscriptionObjectType import com.onesignal.user.internal.identity.IdentityModel import com.onesignal.user.internal.identity.IdentityModelStore import com.onesignal.user.internal.operations.RefreshUserOperation @@ -18,6 +19,7 @@ import com.onesignal.user.internal.properties.PropertiesModel import com.onesignal.user.internal.properties.PropertiesModelStore import com.onesignal.user.internal.subscriptions.SubscriptionModel import com.onesignal.user.internal.subscriptions.SubscriptionModelStore +import com.onesignal.user.internal.subscriptions.SubscriptionStatus import com.onesignal.user.internal.subscriptions.SubscriptionType internal class RefreshUserOperationExecutor( @@ -54,9 +56,6 @@ internal class RefreshUserOperationExecutor( } val identityModel = IdentityModel() - identityModel.onesignalId = op.onesignalId - // TODO: Remove once we can pull this from the backend. - identityModel.externalId = _identityModelStore.model.externalId for (aliasKVP in response.identities) { identityModel[aliasKVP.key] = aliasKVP.value } @@ -65,30 +64,44 @@ internal class RefreshUserOperationExecutor( propertiesModel.onesignalId = op.onesignalId if (response.properties.country != null) { - propertiesModel.country = response.properties.country!! + propertiesModel.country = response.properties.country } - propertiesModel.setProperty( - PropertiesModel::language.name, - response.properties.language, - ModelChangeTags.HYDRATE - ) + if (response.properties.language != null) { + propertiesModel.language = response.properties.language + } if (response.properties.tags != null) { - for (tagKVP in response.properties.tags!!) { - propertiesModel.tags[tagKVP.key] = tagKVP.value + for (tagKVP in response.properties.tags) { + if (tagKVP.value != null) { + propertiesModel.tags[tagKVP.key] = tagKVP.value!! + } } } if (response.properties.timezoneId != null) { - propertiesModel.timezone = response.properties.timezoneId!! + propertiesModel.timezone = response.properties.timezoneId } val subscriptionModels = mutableListOf() - // TODO fill this in for real. Below just copies over the push subscription. - val existingPush = _subscriptionsModelStore.list().firstOrNull { it.type == SubscriptionType.PUSH } - if (existingPush != null) { - subscriptionModels.add(existingPush) + for (subscription in response.subscriptions) { + val subscriptionModel = SubscriptionModel() + subscriptionModel.id = subscription.id + subscriptionModel.address = subscription.token ?: "" + subscriptionModel.status = SubscriptionStatus.fromInt(subscription.notificationTypes ?: SubscriptionStatus.SUBSCRIBED.value) ?: SubscriptionStatus.SUBSCRIBED + subscriptionModel.type = when (subscription.type) { + SubscriptionObjectType.EMAIL -> { + SubscriptionType.EMAIL + } + SubscriptionObjectType.SMS -> { + SubscriptionType.SMS + } + else -> { + SubscriptionType.PUSH + } + } + subscriptionModel.optedIn = subscriptionModel.status != SubscriptionStatus.UNSUBSCRIBE + subscriptionModels.add(subscriptionModel) } _identityModelStore.replace(identityModel, ModelChangeTags.HYDRATE) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt index fe286a2a9f..2a5e91ca36 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt @@ -1,8 +1,13 @@ package com.onesignal.user.internal.operations.impl.executors +import android.os.Build +import com.onesignal.common.DeviceUtils import com.onesignal.common.NetworkUtils +import com.onesignal.common.OneSignalUtils +import com.onesignal.common.RootToolsInternalMethods import com.onesignal.common.exceptions.BackendException import com.onesignal.common.modeling.ModelChangeTags +import com.onesignal.core.internal.application.IApplicationService import com.onesignal.core.internal.device.IDeviceService import com.onesignal.core.internal.operations.ExecutionResponse import com.onesignal.core.internal.operations.ExecutionResult @@ -12,6 +17,7 @@ import com.onesignal.debug.LogLevel import com.onesignal.debug.internal.logging.Logging import com.onesignal.user.internal.backend.ISubscriptionBackendService import com.onesignal.user.internal.backend.IdentityConstants +import com.onesignal.user.internal.backend.SubscriptionObject import com.onesignal.user.internal.backend.SubscriptionObjectType import com.onesignal.user.internal.operations.CreateSubscriptionOperation import com.onesignal.user.internal.operations.DeleteSubscriptionOperation @@ -23,6 +29,7 @@ import com.onesignal.user.internal.subscriptions.SubscriptionType internal class SubscriptionOperationExecutor( private val _subscriptionBackend: ISubscriptionBackendService, private val _deviceService: IDeviceService, + private val _applicationService: IApplicationService, private val _subscriptionModelStore: SubscriptionModelStore ) : IOperationExecutor { @@ -59,34 +66,30 @@ internal class SubscriptionOperationExecutor( val status = lastUpdateOperation?.status ?: createOperation.status try { + val subscription = SubscriptionObject( + createOperation.subscriptionId, + convert(createOperation.type), + address, + enabled, + status.value, + OneSignalUtils.sdkVersion, + Build.MODEL, + Build.VERSION.RELEASE, + RootToolsInternalMethods.isRooted, + DeviceUtils.getNetType(_applicationService.appContext), + DeviceUtils.getCarrierName(_applicationService.appContext) + ) + val backendSubscriptionId = _subscriptionBackend.createSubscription( createOperation.appId, IdentityConstants.ONESIGNAL_ID, createOperation.onesignalId, - convert(createOperation.type), - enabled, - address, - status + subscription ) + // update the subscription model with the new ID, if it's still active. val subscriptionModel = _subscriptionModelStore.get(createOperation.subscriptionId) - if (subscriptionModel != null) { - subscriptionModel.setProperty( - SubscriptionModel::id.name, - backendSubscriptionId, - ModelChangeTags.HYDRATE - ) - } else { - // the subscription model no longer exists, add it back to the model as a HYDRATE - val newSubModel = SubscriptionModel() - newSubModel.id = backendSubscriptionId - newSubModel.type = createOperation.type - newSubModel.address = address - newSubModel.enabled = enabled - newSubModel.status = status - - _subscriptionModelStore.add(newSubModel, ModelChangeTags.HYDRATE) - } + subscriptionModel?.setStringProperty(SubscriptionModel::id.name, backendSubscriptionId, ModelChangeTags.HYDRATE) return ExecutionResponse(ExecutionResult.SUCCESS, mapOf(createOperation.subscriptionId to backendSubscriptionId)) } catch (ex: BackendException) { @@ -102,32 +105,21 @@ internal class SubscriptionOperationExecutor( // the effective enabled/address is the last update performed val lastOperation = operations.last() as UpdateSubscriptionOperation try { - _subscriptionBackend.updateSubscription( - lastOperation.appId, + val subscription = SubscriptionObject( lastOperation.subscriptionId, convert(lastOperation.type), - lastOperation.enabled, lastOperation.address, - lastOperation.status + lastOperation.enabled, + lastOperation.status.value, + OneSignalUtils.sdkVersion, + Build.MODEL, + Build.VERSION.RELEASE, + RootToolsInternalMethods.isRooted, + DeviceUtils.getNetType(_applicationService.appContext), + DeviceUtils.getCarrierName(_applicationService.appContext) ) - // update/create the subscription model, in case it lost it's sync. - val subscriptionModel = _subscriptionModelStore.get(lastOperation.subscriptionId) - if (subscriptionModel != null) { - subscriptionModel.setProperty(SubscriptionModel::type.name, lastOperation.type.toString(), ModelChangeTags.HYDRATE) - subscriptionModel.setProperty(SubscriptionModel::address.name, lastOperation.address, ModelChangeTags.HYDRATE) - subscriptionModel.setProperty(SubscriptionModel::enabled.name, lastOperation.enabled, ModelChangeTags.HYDRATE) - subscriptionModel.setProperty(SubscriptionModel::status.name, lastOperation.status.value, ModelChangeTags.HYDRATE) - } else { - val newSubModel = SubscriptionModel() - newSubModel.id = lastOperation.subscriptionId - newSubModel.type = lastOperation.type - newSubModel.address = lastOperation.address - newSubModel.enabled = lastOperation.enabled - newSubModel.status = lastOperation.status - - _subscriptionModelStore.add(newSubModel, ModelChangeTags.HYDRATE) - } + _subscriptionBackend.updateSubscription(lastOperation.appId, lastOperation.subscriptionId, subscription) } catch (ex: BackendException) { return if (NetworkUtils.shouldRetryNetworkRequest(ex.statusCode)) { ExecutionResponse(ExecutionResult.FAIL_RETRY) @@ -145,7 +137,7 @@ internal class SubscriptionOperationExecutor( SubscriptionObjectType.SMS } SubscriptionType.EMAIL -> { - SubscriptionObjectType.SMS + SubscriptionObjectType.EMAIL } else -> { SubscriptionObjectType.fromDeviceType(_deviceService.deviceType) diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt index 51bb73501f..23e7986dba 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt @@ -124,9 +124,9 @@ internal class UpdateUserOperationExecutor( // go through and make sure any properties are in the correct model state for (operation in ops) { when (operation) { - is SetTagOperation -> _propertiesModelStore.model.tags.setProperty(operation.key, operation.value, ModelChangeTags.HYDRATE) - is DeleteTagOperation -> _propertiesModelStore.model.tags.setProperty(operation.key, null, ModelChangeTags.HYDRATE) - is SetPropertyOperation -> _propertiesModelStore.model.setProperty(operation.property, operation.value, ModelChangeTags.HYDRATE) + is SetTagOperation -> _propertiesModelStore.model.tags.setStringProperty(operation.key, operation.value, ModelChangeTags.HYDRATE) + is DeleteTagOperation -> _propertiesModelStore.model.tags.setOptStringProperty(operation.key, null, ModelChangeTags.HYDRATE) + is SetPropertyOperation -> _propertiesModelStore.model.setOptAnyProperty(operation.property, operation.value, ModelChangeTags.HYDRATE) } } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/listeners/SubscriptionModelStoreListener.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/listeners/SubscriptionModelStoreListener.kt index 1736ce5379..c4a1ada5a0 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/listeners/SubscriptionModelStoreListener.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/operations/impl/listeners/SubscriptionModelStoreListener.kt @@ -10,6 +10,7 @@ import com.onesignal.user.internal.operations.DeleteSubscriptionOperation import com.onesignal.user.internal.operations.UpdateSubscriptionOperation import com.onesignal.user.internal.subscriptions.SubscriptionModel import com.onesignal.user.internal.subscriptions.SubscriptionModelStore +import com.onesignal.user.internal.subscriptions.SubscriptionStatus internal class SubscriptionModelStoreListener( store: SubscriptionModelStore, @@ -19,7 +20,22 @@ internal class SubscriptionModelStoreListener( ) : ModelStoreListener(store, opRepo) { override fun getAddOperation(model: SubscriptionModel): Operation { - return CreateSubscriptionOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId, model.id, model.type, model.enabled, model.address, model.status) + val status: SubscriptionStatus + val enabled: Boolean + + if (model.optedIn && model.status == SubscriptionStatus.SUBSCRIBED && model.address.isNotEmpty()) { + enabled = true + status = SubscriptionStatus.SUBSCRIBED + } else { + enabled = false + status = if (!model.optedIn) { + SubscriptionStatus.UNSUBSCRIBE + } else { + model.status + } + } + + return CreateSubscriptionOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId, model.id, model.type, enabled, model.address, status) } override fun getRemoveOperation(model: SubscriptionModel): Operation { @@ -27,6 +43,21 @@ internal class SubscriptionModelStoreListener( } override fun getUpdateOperation(model: SubscriptionModel, path: String, property: String, oldValue: Any?, newValue: Any?): Operation { - return UpdateSubscriptionOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId, model.id, model.type, model.enabled, model.address, model.status) + val status: SubscriptionStatus + val enabled: Boolean + + if (model.optedIn && model.status == SubscriptionStatus.SUBSCRIBED && model.address.isNotEmpty()) { + enabled = true + status = SubscriptionStatus.SUBSCRIBED + } else { + enabled = false + status = if (!model.optedIn) { + SubscriptionStatus.UNSUBSCRIBE + } else { + model.status + } + } + + return UpdateSubscriptionOperation(_configModelStore.model.appId, _identityModelStore.model.onesignalId, model.id, model.type, enabled, model.address, status) } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/properties/PropertiesModel.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/properties/PropertiesModel.kt index d02aed634c..7000afd0c8 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/properties/PropertiesModel.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/properties/PropertiesModel.kt @@ -9,8 +9,8 @@ class PropertiesModel : Model() { * The OneSignal id for the user that is associated with these properties. */ var onesignalId: String - get() = getProperty(::onesignalId.name) - set(value) { setProperty(::onesignalId.name, value) } + get() = getStringProperty(::onesignalId.name) + set(value) { setStringProperty(::onesignalId.name, value) } /** * The language for this user (ISO 639-1 format). When `null` the device default will be used. @@ -18,8 +18,8 @@ class PropertiesModel : Model() { * @see [https://en.wikipedia.org/wiki/ISO_639-1] */ var language: String? - get() = getProperty(::language.name) - set(value) { setProperty(::language.name, value) } + get() = getOptStringProperty(::language.name) + set(value) { setOptStringProperty(::language.name, value) } /** * The country code for this user (ISO 3166-1 Alpha 2 format). When `null` the default will @@ -28,8 +28,8 @@ class PropertiesModel : Model() { * @see [https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2] */ var country: String - get() = getProperty(::country.name) { "US" } - set(value) { setProperty(::country.name, value) } + get() = getStringProperty(::country.name) { "US" } + set(value) { setStringProperty(::country.name, value) } /** * The timezone for this user (TZ database name). @@ -37,56 +37,56 @@ class PropertiesModel : Model() { * @see [https://en.wikipedia.org/wiki/List_of_tz_database_time_zones] */ var timezone: String? - get() = getProperty(::timezone.name) - set(value) { setProperty(::timezone.name, value) } + get() = getOptStringProperty(::timezone.name) + set(value) { setOptStringProperty(::timezone.name, value) } /** * The data tags for this user. */ val tags: MapModel - get() = getProperty(::tags.name) { MapModel(this, ::tags.name) } + get() = getMapModelProperty(::tags.name) { MapModel(this, ::tags.name) } /** * The user's last known location latitude reading. */ var locationLatitude: Double? - get() = getProperty(::locationLatitude.name) - set(value) { setProperty(::locationLatitude.name, value) } + get() = getOptDoubleProperty(::locationLatitude.name) + set(value) { setOptDoubleProperty(::locationLatitude.name, value) } /** * The user's last known location longitude reading. */ var locationLongitude: Double? - get() = getProperty(::locationLongitude.name) - set(value) { setProperty(::locationLongitude.name, value) } + get() = getOptDoubleProperty(::locationLongitude.name) + set(value) { setOptDoubleProperty(::locationLongitude.name, value) } /** * The user's last location accuracy reading. */ var locationAccuracy: Float? - get() = getProperty(::locationAccuracy.name) - set(value) { setProperty(::locationAccuracy.name, value) } + get() = getOptFloatProperty(::locationAccuracy.name) + set(value) { setOptFloatProperty(::locationAccuracy.name, value) } /** * The user's last location type reading (0 - COARSE, 1 - FINE). */ var locationType: Int? - get() = getProperty(::locationType.name) - set(value) { setProperty(::locationType.name, value) } + get() = getOptIntProperty(::locationType.name) + set(value) { setOptIntProperty(::locationType.name, value) } /** * Whether the user's last location reading was done with the app in the background. */ var locationBackground: Boolean? - get() = getProperty(::locationBackground.name) - set(value) { setProperty(::locationBackground.name, value) } + get() = getOptBooleanProperty(::locationBackground.name) + set(value) { setOptBooleanProperty(::locationBackground.name, value) } /** * When the user's last location reading was. */ var locationTimestamp: Long? - get() = getProperty(::locationTimestamp.name) - set(value) { setProperty(::locationTimestamp.name, value) } + get() = getOptLongProperty(::locationTimestamp.name) + set(value) { setOptLongProperty(::locationTimestamp.name, value) } override fun createModelForProperty(property: String, jsonObject: JSONObject): Model? { if (property == ::tags.name) { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/subscriptions/ISubscriptionManager.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/subscriptions/ISubscriptionManager.kt index 6724ad90e0..d45894a909 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/subscriptions/ISubscriptionManager.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/subscriptions/ISubscriptionManager.kt @@ -1,11 +1,14 @@ package com.onesignal.user.internal.subscriptions import com.onesignal.common.events.IEventNotifier +import com.onesignal.common.modeling.ModelChangedArgs +import com.onesignal.user.subscriptions.ISubscription import com.onesignal.user.subscriptions.SubscriptionList interface ISubscriptionManager : IEventNotifier { var subscriptions: SubscriptionList + val pushSubscriptionModel: SubscriptionModel fun addEmailSubscription(email: String) fun addOrUpdatePushSubscription(pushToken: String?, pushTokenStatus: SubscriptionStatus) fun addSmsSubscription(sms: String) @@ -14,5 +17,7 @@ interface ISubscriptionManager : IEventNotifier { } interface ISubscriptionChangedHandler { - fun onSubscriptionsChanged() + fun onSubscriptionAdded(subscription: ISubscription) + fun onSubscriptionChanged(subscription: ISubscription, args: ModelChangedArgs) + fun onSubscriptionRemoved(subscription: ISubscription) } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/subscriptions/SubscriptionModel.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/subscriptions/SubscriptionModel.kt index c8bf2cba2d..1b9326748a 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/subscriptions/SubscriptionModel.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/subscriptions/SubscriptionModel.kt @@ -66,34 +66,43 @@ enum class SubscriptionStatus(val value: Int) { FIREBASE_FCM_ERROR_IOEXCEPTION_AUTHENTICATION_FAILED(-29), /** The subscription is not enabled due to some other (unknown locally) error */ - ERROR(9999) + ERROR(9999); + + companion object { + fun fromInt(value: Int): SubscriptionStatus? { + return SubscriptionStatus.values().firstOrNull { it.value == value } + } + } } class SubscriptionModel : Model() { - var enabled: Boolean - get() = getProperty(::enabled.name) + var optedIn: Boolean + get() = getBooleanProperty(::optedIn.name) set(value) { - setProperty(::enabled.name, value) + setBooleanProperty(::optedIn.name, value) } var type: SubscriptionType - get() = SubscriptionType.valueOf(getProperty(::type.name)) + get() = getEnumProperty(::type.name) set(value) { - setProperty(::type.name, value.toString()) + setEnumProperty(::type.name, value) } var address: String - get() = getProperty(::address.name) + get() = getStringProperty(::address.name) set(value) { - setProperty(::address.name, value) + setStringProperty(::address.name, value) } var status: SubscriptionStatus get() { - val value = getProperty(::status.name) { SubscriptionStatus.SUBSCRIBED.value } - return SubscriptionStatus.values().first { it.value == value } + if (!hasProperty(::status.name)) { + setEnumProperty(::status.name, SubscriptionStatus.SUBSCRIBED) + } + + return getEnumProperty(::status.name) } set(value) { - setProperty(::status.name, value.value) + setEnumProperty(::status.name, value) } } diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/subscriptions/impl/SubscriptionManager.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/subscriptions/impl/SubscriptionManager.kt index 4fa0c6f2d8..cda0ff375f 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/subscriptions/impl/SubscriptionManager.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/subscriptions/impl/SubscriptionManager.kt @@ -36,6 +36,8 @@ internal class SubscriptionManager( private val _events = EventProducer() override var subscriptions: SubscriptionList = SubscriptionList(listOf(), UninitializedPushSubscription()) + override val pushSubscriptionModel: SubscriptionModel + get() = (subscriptions.push as PushSubscription).model init { for (subscriptionModel in _subscriptionModelStore.list()) { @@ -89,7 +91,7 @@ internal class SubscriptionManager( val subscriptionModel = SubscriptionModel() subscriptionModel.id = IDManager.createLocalId() - subscriptionModel.enabled = true + subscriptionModel.optedIn = true subscriptionModel.type = type subscriptionModel.address = address subscriptionModel.status = status ?: SubscriptionStatus.SUBSCRIBED @@ -139,7 +141,7 @@ internal class SubscriptionManager( } // the model has already been updated, so fire the update event - _events.fire { it.onSubscriptionsChanged() } + _events.fire { it.onSubscriptionChanged(subscription, args) } } } @@ -170,7 +172,7 @@ internal class SubscriptionManager( subscriptions.add(subscription) this.subscriptions = SubscriptionList(subscriptions, UninitializedPushSubscription()) - _events.fire { it.onSubscriptionsChanged() } + _events.fire { it.onSubscriptionAdded(subscription) } } private fun removeSubscriptionFromSubscriptionList(subscription: ISubscription) { @@ -178,7 +180,7 @@ internal class SubscriptionManager( subscriptions.remove(subscription) this.subscriptions = SubscriptionList(subscriptions, UninitializedPushSubscription()) - _events.fire { it.onSubscriptionsChanged() } + _events.fire { it.onSubscriptionRemoved(subscription) } } private fun createSubscriptionFromModel(subscriptionModel: SubscriptionModel): ISubscription { diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/subscriptions/IPushSubscription.kt b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/subscriptions/IPushSubscription.kt index f5a15ed94b..0832e44778 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/subscriptions/IPushSubscription.kt +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/user/subscriptions/IPushSubscription.kt @@ -13,8 +13,25 @@ interface IPushSubscription : ISubscription { val pushToken: String /** - * Whether this subscription is current enabled. When enabled, the user is able to - * receive notifications through this subscription. + * Whether the user of this subscription is opted-in to received notifications. When true, + * the user is able to receive notifications through this subscription. Otherwise, the + * user will not receive notifications through this subscription (even when the user has + * granted app permission). */ - var enabled: Boolean + val optedIn: Boolean + + /** + * Opt the user into this push subscription. If the application does not have permission, + * the user will be prompted by Android to permit push permissions. If the user has + * granted app permission, the user will be able to receive push notification. If the user + * rejects app permission, the user will only be able to receive push notifications once + * the app permission has been granted. + */ + fun optIn() + + /** + * Opt the user out of this push subscription. The user will no longer received push + * notifications, although the app permission state will not be changed. + */ + fun optOut() } diff --git a/OneSignalSDK/onesignal/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsBackendServiceTests.kt b/OneSignalSDK/onesignal/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsBackendServiceTests.kt index 776ad7a954..da76cc8990 100644 --- a/OneSignalSDK/onesignal/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsBackendServiceTests.kt +++ b/OneSignalSDK/onesignal/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsBackendServiceTests.kt @@ -1,7 +1,6 @@ package com.onesignal.session.internal.outcomes import com.onesignal.common.exceptions.BackendException -import com.onesignal.core.internal.device.IDeviceService import com.onesignal.core.internal.http.HttpResponse import com.onesignal.core.internal.http.IHttpClient import com.onesignal.debug.LogLevel @@ -32,7 +31,7 @@ class OutcomeEventsBackendServiceTests : FunSpec({ val outcomeEventsController = OutcomeEventsBackendService(spyHttpClient) /* When */ - outcomeEventsController.sendOutcomeEvent("appId", IDeviceService.DeviceType.Android, null, evnt) + outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", null, evnt) /* Then */ coVerify { @@ -40,7 +39,8 @@ class OutcomeEventsBackendServiceTests : FunSpec({ "outcomes/measure", withArg { it.getString("app_id") shouldBe "appId" - it.getInt("device_type") shouldBe 1 + it.getString("onesignal_id") shouldBe "onesignalId" + it.getJSONObject("subscription").getString("id") shouldBe "subscriptionId" it.getString("id") shouldBe "EVENT_NAME" it.has("direct") shouldBe false it.has("notification_ids") shouldBe false @@ -59,7 +59,7 @@ class OutcomeEventsBackendServiceTests : FunSpec({ val outcomeEventsController = OutcomeEventsBackendService(spyHttpClient) /* When */ - outcomeEventsController.sendOutcomeEvent("appId", IDeviceService.DeviceType.Android, null, evnt) + outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", null, evnt) /* Then */ coVerify { @@ -67,7 +67,8 @@ class OutcomeEventsBackendServiceTests : FunSpec({ "outcomes/measure", withArg { it.getString("app_id") shouldBe "appId" - it.getInt("device_type") shouldBe 1 + it.getString("onesignal_id") shouldBe "onesignalId" + it.getJSONObject("subscription").getString("id") shouldBe "subscriptionId" it.getString("id") shouldBe "EVENT_NAME" it.getInt("weight") shouldBe 1 it.has("direct") shouldBe false @@ -86,7 +87,7 @@ class OutcomeEventsBackendServiceTests : FunSpec({ val outcomeEventsController = OutcomeEventsBackendService(spyHttpClient) /* When */ - outcomeEventsController.sendOutcomeEvent("appId", IDeviceService.DeviceType.Android, false, evnt) + outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", false, evnt) /* Then */ coVerify { @@ -94,7 +95,8 @@ class OutcomeEventsBackendServiceTests : FunSpec({ "outcomes/measure", withArg { it.getString("app_id") shouldBe "appId" - it.getInt("device_type") shouldBe 1 + it.getString("onesignal_id") shouldBe "onesignalId" + it.getJSONObject("subscription").getString("id") shouldBe "subscriptionId" it.getString("id") shouldBe "EVENT_NAME" it.getBoolean("direct") shouldBe false it.has("notification_ids") shouldBe false @@ -113,7 +115,7 @@ class OutcomeEventsBackendServiceTests : FunSpec({ val outcomeEventsController = OutcomeEventsBackendService(spyHttpClient) /* When */ - outcomeEventsController.sendOutcomeEvent("appId", IDeviceService.DeviceType.Android, true, evnt) + outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", true, evnt) /* Then */ coVerify { @@ -121,7 +123,8 @@ class OutcomeEventsBackendServiceTests : FunSpec({ "outcomes/measure", withArg { it.getString("app_id") shouldBe "appId" - it.getInt("device_type") shouldBe 1 + it.getString("onesignal_id") shouldBe "onesignalId" + it.getJSONObject("subscription").getString("id") shouldBe "subscriptionId" it.getString("id") shouldBe "EVENT_NAME" it.getBoolean("direct") shouldBe true it.has("notification_ids") shouldBe false @@ -140,7 +143,7 @@ class OutcomeEventsBackendServiceTests : FunSpec({ val outcomeEventsController = OutcomeEventsBackendService(spyHttpClient) /* When */ - outcomeEventsController.sendOutcomeEvent("appId", IDeviceService.DeviceType.Android, null, evnt) + outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", null, evnt) /* Then */ coVerify { @@ -148,7 +151,8 @@ class OutcomeEventsBackendServiceTests : FunSpec({ "outcomes/measure", withArg { it.getString("app_id") shouldBe "appId" - it.getInt("device_type") shouldBe 1 + it.getString("onesignal_id") shouldBe "onesignalId" + it.getJSONObject("subscription").getString("id") shouldBe "subscriptionId" it.getString("id") shouldBe "EVENT_NAME" it.getInt("timestamp") shouldBe 1111 it.has("notification_ids") shouldBe false @@ -168,7 +172,7 @@ class OutcomeEventsBackendServiceTests : FunSpec({ /* When */ val exception = shouldThrowUnit { - outcomeEventsController.sendOutcomeEvent("appId", IDeviceService.DeviceType.Android, null, evnt) + outcomeEventsController.sendOutcomeEvent("appId", "onesignalId", "subscriptionId", null, evnt) } /* Then */ diff --git a/OneSignalSDK/onesignal/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsControllerTests.kt b/OneSignalSDK/onesignal/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsControllerTests.kt index 3ea1c51743..b4d1c6867e 100644 --- a/OneSignalSDK/onesignal/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsControllerTests.kt +++ b/OneSignalSDK/onesignal/src/test/java/com/onesignal/session/internal/outcomes/OutcomeEventsControllerTests.kt @@ -1,7 +1,6 @@ package com.onesignal.session.internal.outcomes import com.onesignal.common.exceptions.BackendException -import com.onesignal.core.internal.device.IDeviceService import com.onesignal.debug.LogLevel import com.onesignal.debug.internal.logging.Logging import com.onesignal.mocks.MockHelper @@ -17,6 +16,9 @@ import com.onesignal.session.internal.outcomes.impl.OutcomeEventsController import com.onesignal.session.internal.outcomes.impl.OutcomeSource import com.onesignal.session.internal.outcomes.impl.OutcomeSourceBody import com.onesignal.session.internal.session.ISessionService +import com.onesignal.user.internal.PushSubscription +import com.onesignal.user.internal.subscriptions.ISubscriptionManager +import com.onesignal.user.internal.subscriptions.SubscriptionModel import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe @@ -48,6 +50,7 @@ class OutcomeEventsControllerTests : FunSpec({ val mockInfluenceManager = mockk() every { mockInfluenceManager.influences } returns listOf(Influence(InfluenceChannel.NOTIFICATION, InfluenceType.DISABLED, null)) + val mockSubscriptionManager = mockk() val mockOutcomeEventsRepository = spyk() val mockOutcomeEventsPreferences = spyk() @@ -60,8 +63,9 @@ class OutcomeEventsControllerTests : FunSpec({ mockOutcomeEventsPreferences, mockOutcomeEventsBackend, MockHelper.configModelStore(), - MockHelper.time(now), - MockHelper.deviceService() + MockHelper.identityModelStore(), + mockSubscriptionManager, + MockHelper.time(now) ) /* When */ @@ -69,7 +73,7 @@ class OutcomeEventsControllerTests : FunSpec({ /* Then */ evnt shouldBe null - coVerify(exactly = 0) { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any()) } + coVerify(exactly = 0) { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any(), any()) } } test("send outcome with unattributed influences") { @@ -81,6 +85,12 @@ class OutcomeEventsControllerTests : FunSpec({ val mockInfluenceManager = mockk() every { mockInfluenceManager.influences } returns listOf(Influence(InfluenceChannel.NOTIFICATION, InfluenceType.UNATTRIBUTED, null)) + val subscriptionModel = SubscriptionModel() + subscriptionModel.id = "subscriptionId" + + val mockSubscriptionManager = mockk() + every { mockSubscriptionManager.subscriptions.push } returns PushSubscription(subscriptionModel) + val mockOutcomeEventsRepository = spyk() val mockOutcomeEventsPreferences = spyk() val mockOutcomeEventsBackend = spyk() @@ -92,8 +102,9 @@ class OutcomeEventsControllerTests : FunSpec({ mockOutcomeEventsPreferences, mockOutcomeEventsBackend, MockHelper.configModelStore(), - MockHelper.time(now), - MockHelper.deviceService() + MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, + mockSubscriptionManager, + MockHelper.time(now) ) /* When */ @@ -107,7 +118,7 @@ class OutcomeEventsControllerTests : FunSpec({ evnt.session shouldBe InfluenceType.UNATTRIBUTED evnt.timestamp shouldBe 0 // timestamp only set when it had to be saved. - coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, IDeviceService.DeviceType.Android, null, evnt) } + coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", null, evnt) } } test("send outcome with indirect influences") { @@ -120,6 +131,11 @@ class OutcomeEventsControllerTests : FunSpec({ val mockInfluenceManager = mockk() every { mockInfluenceManager.influences } returns listOf(Influence(InfluenceChannel.NOTIFICATION, InfluenceType.INDIRECT, JSONArray(notificationIds))) + val subscriptionModel = SubscriptionModel() + subscriptionModel.id = "subscriptionId" + val mockSubscriptionManager = mockk() + every { mockSubscriptionManager.subscriptions.push } returns PushSubscription(subscriptionModel) + val mockOutcomeEventsRepository = spyk() val mockOutcomeEventsPreferences = spyk() val mockOutcomeEventsBackend = spyk() @@ -131,8 +147,9 @@ class OutcomeEventsControllerTests : FunSpec({ mockOutcomeEventsPreferences, mockOutcomeEventsBackend, MockHelper.configModelStore(), - MockHelper.time(now), - MockHelper.deviceService() + MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, + mockSubscriptionManager, + MockHelper.time(now) ) /* When */ @@ -147,7 +164,7 @@ class OutcomeEventsControllerTests : FunSpec({ evnt.session shouldBe InfluenceType.INDIRECT evnt.timestamp shouldBe 0 // timestamp only set when it had to be saved. - coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, IDeviceService.DeviceType.Android, false, evnt) } + coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", false, evnt) } } test("send outcome with direct influence") { @@ -160,6 +177,11 @@ class OutcomeEventsControllerTests : FunSpec({ val mockInfluenceManager = mockk() every { mockInfluenceManager.influences } returns listOf(Influence(InfluenceChannel.NOTIFICATION, InfluenceType.DIRECT, JSONArray(notificationIds))) + val subscriptionModel = SubscriptionModel() + subscriptionModel.id = "subscriptionId" + val mockSubscriptionManager = mockk() + every { mockSubscriptionManager.subscriptions.push } returns PushSubscription(subscriptionModel) + val mockOutcomeEventsRepository = spyk() val mockOutcomeEventsPreferences = spyk() val mockOutcomeEventsBackend = spyk() @@ -171,8 +193,9 @@ class OutcomeEventsControllerTests : FunSpec({ mockOutcomeEventsPreferences, mockOutcomeEventsBackend, MockHelper.configModelStore(), - MockHelper.time(now), - MockHelper.deviceService() + MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, + mockSubscriptionManager, + MockHelper.time(now) ) /* When */ @@ -187,7 +210,7 @@ class OutcomeEventsControllerTests : FunSpec({ evnt.session shouldBe InfluenceType.DIRECT evnt.timestamp shouldBe 0 // timestamp only set when it had to be saved. - coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, IDeviceService.DeviceType.Android, true, evnt) } + coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", true, evnt) } } test("send outcome with weight") { @@ -200,6 +223,11 @@ class OutcomeEventsControllerTests : FunSpec({ val mockInfluenceManager = mockk() every { mockInfluenceManager.influences } returns listOf(Influence(InfluenceChannel.NOTIFICATION, InfluenceType.UNATTRIBUTED, null)) + val subscriptionModel = SubscriptionModel() + subscriptionModel.id = "subscriptionId" + val mockSubscriptionManager = mockk() + every { mockSubscriptionManager.subscriptions.push } returns PushSubscription(subscriptionModel) + val mockOutcomeEventsRepository = spyk() val mockOutcomeEventsPreferences = spyk() val mockOutcomeEventsBackend = spyk() @@ -211,8 +239,9 @@ class OutcomeEventsControllerTests : FunSpec({ mockOutcomeEventsPreferences, mockOutcomeEventsBackend, MockHelper.configModelStore(), - MockHelper.time(now), - MockHelper.deviceService() + MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, + mockSubscriptionManager, + MockHelper.time(now) ) /* When */ @@ -226,7 +255,7 @@ class OutcomeEventsControllerTests : FunSpec({ evnt.session shouldBe InfluenceType.UNATTRIBUTED evnt.timestamp shouldBe 0 // timestamp only set when it had to be saved. - coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, IDeviceService.DeviceType.Android, null, evnt) } + coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", null, evnt) } } test("send unique outcome with unattributed influences") { @@ -238,6 +267,11 @@ class OutcomeEventsControllerTests : FunSpec({ val mockInfluenceManager = mockk() every { mockInfluenceManager.influences } returns listOf(Influence(InfluenceChannel.NOTIFICATION, InfluenceType.UNATTRIBUTED, null)) + val subscriptionModel = SubscriptionModel() + subscriptionModel.id = "subscriptionId" + val mockSubscriptionManager = mockk() + every { mockSubscriptionManager.subscriptions.push } returns PushSubscription(subscriptionModel) + val mockOutcomeEventsRepository = spyk() val mockOutcomeEventsPreferences = spyk() val mockOutcomeEventsBackend = spyk() @@ -249,8 +283,9 @@ class OutcomeEventsControllerTests : FunSpec({ mockOutcomeEventsPreferences, mockOutcomeEventsBackend, MockHelper.configModelStore(), - MockHelper.time(now), - MockHelper.deviceService() + MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, + mockSubscriptionManager, + MockHelper.time(now) ) /* When */ @@ -267,7 +302,7 @@ class OutcomeEventsControllerTests : FunSpec({ evnt2 shouldBe null - coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, IDeviceService.DeviceType.Android, any(), any()) } + coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", any(), any()) } } test("send unique outcome with same indirect influences") { @@ -285,6 +320,11 @@ class OutcomeEventsControllerTests : FunSpec({ coEvery { mockOutcomeEventsRepository.getNotCachedUniqueInfluencesForOutcome("OUTCOME_1", any()) } returns listOf(notificationInfluence) andThen listOf() coEvery { mockOutcomeEventsRepository.saveUniqueOutcomeEventParams(any()) } + val subscriptionModel = SubscriptionModel() + subscriptionModel.id = "subscriptionId" + val mockSubscriptionManager = mockk() + every { mockSubscriptionManager.subscriptions.push } returns PushSubscription(subscriptionModel) + val mockOutcomeEventsPreferences = spyk() val mockOutcomeEventsBackend = spyk() @@ -295,8 +335,9 @@ class OutcomeEventsControllerTests : FunSpec({ mockOutcomeEventsPreferences, mockOutcomeEventsBackend, MockHelper.configModelStore(), - MockHelper.time(now), - MockHelper.deviceService() + MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, + mockSubscriptionManager, + MockHelper.time(now) ) /* When */ @@ -314,7 +355,7 @@ class OutcomeEventsControllerTests : FunSpec({ evnt2 shouldBe null - coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, IDeviceService.DeviceType.Android, any(), any()) } + coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", any(), any()) } } test("send unique outcome with different indirect influences") { @@ -330,6 +371,11 @@ class OutcomeEventsControllerTests : FunSpec({ val notificationInfluence2 = Influence(InfluenceChannel.NOTIFICATION, InfluenceType.DIRECT, JSONArray(notificationIds2)) every { mockInfluenceManager.influences } returns listOf(notificationInfluence1) andThen listOf(notificationInfluence2) + val subscriptionModel = SubscriptionModel() + subscriptionModel.id = "subscriptionId" + val mockSubscriptionManager = mockk() + every { mockSubscriptionManager.subscriptions.push } returns PushSubscription(subscriptionModel) + val mockOutcomeEventsRepository = mockk() coEvery { mockOutcomeEventsRepository.getNotCachedUniqueInfluencesForOutcome("OUTCOME_1", any()) } returns listOf(notificationInfluence1) andThen listOf(notificationInfluence2) coEvery { mockOutcomeEventsRepository.saveUniqueOutcomeEventParams(any()) } @@ -344,8 +390,9 @@ class OutcomeEventsControllerTests : FunSpec({ mockOutcomeEventsPreferences, mockOutcomeEventsBackend, MockHelper.configModelStore(), - MockHelper.time(now), - MockHelper.deviceService() + MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, + mockSubscriptionManager, + MockHelper.time(now) ) /* When */ @@ -370,8 +417,8 @@ class OutcomeEventsControllerTests : FunSpec({ evnt2.timestamp shouldBe 0 // timestamp only set when it had to be saved. coVerifySequence { - mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, IDeviceService.DeviceType.Android, false, evnt1) - mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, IDeviceService.DeviceType.Android, true, evnt2) + mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", false, evnt1) + mockOutcomeEventsBackend.sendOutcomeEvent(MockHelper.DEFAULT_APP_ID, "onesignalId", "subscriptionId", true, evnt2) } } @@ -384,10 +431,15 @@ class OutcomeEventsControllerTests : FunSpec({ val mockInfluenceManager = mockk() every { mockInfluenceManager.influences } returns listOf(Influence(InfluenceChannel.NOTIFICATION, InfluenceType.UNATTRIBUTED, null)) + val subscriptionModel = SubscriptionModel() + subscriptionModel.id = "subscriptionId" + val mockSubscriptionManager = mockk() + every { mockSubscriptionManager.subscriptions.push } returns PushSubscription(subscriptionModel) + val mockOutcomeEventsRepository = spyk() val mockOutcomeEventsPreferences = spyk() val mockOutcomeEventsBackend = mockk() - coEvery { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any()) } throws BackendException(408, null) + coEvery { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any(), any()) } throws BackendException(408, null) val outcomeEventsController = OutcomeEventsController( mockSessionService, @@ -396,8 +448,9 @@ class OutcomeEventsControllerTests : FunSpec({ mockOutcomeEventsPreferences, mockOutcomeEventsBackend, MockHelper.configModelStore(), - MockHelper.time(now), - MockHelper.deviceService() + MockHelper.identityModelStore(), + mockSubscriptionManager, + MockHelper.time(now) ) /* When */ @@ -423,6 +476,11 @@ class OutcomeEventsControllerTests : FunSpec({ val mockSessionService = mockk() every { mockSessionService.subscribe(any()) } just Runs + val subscriptionModel = SubscriptionModel() + subscriptionModel.id = "subscriptionId" + val mockSubscriptionManager = mockk() + every { mockSubscriptionManager.subscriptions.push } returns PushSubscription(subscriptionModel) + val mockInfluenceManager = mockk() val mockOutcomeEventsRepository = mockk() coEvery { mockOutcomeEventsRepository.cleanCachedUniqueOutcomeEventNotifications() } just runs @@ -433,7 +491,7 @@ class OutcomeEventsControllerTests : FunSpec({ ) val mockOutcomeEventsPreferences = spyk() val mockOutcomeEventsBackend = mockk() - coEvery { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any()) } just runs + coEvery { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any(), any()) } just runs val outcomeEventsController = OutcomeEventsController( mockSessionService, @@ -442,8 +500,9 @@ class OutcomeEventsControllerTests : FunSpec({ mockOutcomeEventsPreferences, mockOutcomeEventsBackend, MockHelper.configModelStore(), - MockHelper.time(now), - MockHelper.deviceService() + MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, + mockSubscriptionManager, + MockHelper.time(now) ) /* When */ @@ -455,7 +514,8 @@ class OutcomeEventsControllerTests : FunSpec({ coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent( "appId", - IDeviceService.DeviceType.Android, + "onesignalId", + "subscriptionId", true, withArg { it.name shouldBe "outcomeId1" @@ -468,7 +528,8 @@ class OutcomeEventsControllerTests : FunSpec({ coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent( "appId", - IDeviceService.DeviceType.Android, + "onesignalId", + "subscriptionId", false, withArg { it.name shouldBe "outcomeId2" @@ -503,6 +564,11 @@ class OutcomeEventsControllerTests : FunSpec({ val mockSessionService = mockk() every { mockSessionService.subscribe(any()) } just Runs + val subscriptionModel = SubscriptionModel() + subscriptionModel.id = "subscriptionId" + val mockSubscriptionManager = mockk() + every { mockSubscriptionManager.subscriptions.push } returns PushSubscription(subscriptionModel) + val mockInfluenceManager = mockk() val mockOutcomeEventsRepository = mockk() coEvery { mockOutcomeEventsRepository.cleanCachedUniqueOutcomeEventNotifications() } just runs @@ -512,7 +578,7 @@ class OutcomeEventsControllerTests : FunSpec({ ) val mockOutcomeEventsPreferences = spyk() val mockOutcomeEventsBackend = mockk() - coEvery { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any()) } throws BackendException(408, null) + coEvery { mockOutcomeEventsBackend.sendOutcomeEvent(any(), any(), any(), any(), any()) } throws BackendException(408, null) val outcomeEventsController = OutcomeEventsController( mockSessionService, @@ -521,8 +587,9 @@ class OutcomeEventsControllerTests : FunSpec({ mockOutcomeEventsPreferences, mockOutcomeEventsBackend, MockHelper.configModelStore(), - MockHelper.time(now), - MockHelper.deviceService() + MockHelper.identityModelStore { it.onesignalId = "onesignalId" }, + mockSubscriptionManager, + MockHelper.time(now) ) /* When */ @@ -534,7 +601,8 @@ class OutcomeEventsControllerTests : FunSpec({ coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent( "appId", - IDeviceService.DeviceType.Android, + "onesignalId", + "subscriptionId", true, withArg { it.name shouldBe "outcomeId1" @@ -547,7 +615,8 @@ class OutcomeEventsControllerTests : FunSpec({ coVerify(exactly = 1) { mockOutcomeEventsBackend.sendOutcomeEvent( "appId", - IDeviceService.DeviceType.Android, + "onesignalId", + "subscriptionId", false, withArg { it.name shouldBe "outcomeId2" diff --git a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/backend/IdentityBackendServiceTests.kt b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/backend/IdentityBackendServiceTests.kt index 15766da0eb..258c1c62d0 100644 --- a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/backend/IdentityBackendServiceTests.kt +++ b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/backend/IdentityBackendServiceTests.kt @@ -24,18 +24,18 @@ class IdentityBackendServiceTests : FunSpec({ val aliasLabel = "onesignal_id" val aliasValue = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() - coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(200, "{ aliasKey1: \"aliasValue1\"}") + coEvery { spyHttpClient.patch(any(), any()) } returns HttpResponse(200, "{ identity: { aliasKey1: \"aliasValue1\"} }") val identityBackendService = IdentityBackendService(spyHttpClient) val identities = mapOf("aliasKey1" to "aliasValue1") /* When */ - val response = identityBackendService.createAlias("appId", aliasLabel, aliasValue, identities) + val response = identityBackendService.setAlias("appId", aliasLabel, aliasValue, identities) /* Then */ response["aliasKey1"] shouldBe "aliasValue1" coVerify { - spyHttpClient.put( - "apps/appId/user/by/$aliasLabel/$aliasValue/identity", + spyHttpClient.patch( + "apps/appId/users/by/$aliasLabel/$aliasValue/identity", withArg { it.has("identity") shouldBe true it.getJSONObject("identity").has("aliasKey1") shouldBe true @@ -51,7 +51,7 @@ class IdentityBackendServiceTests : FunSpec({ val aliasValue = "11111111-1111-1111-1111-111111111111" val aliasToDelete = "aliasKey1" val spyHttpClient = mockk() - coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(200, "") + coEvery { spyHttpClient.delete(any()) } returns HttpResponse(200, "") val identityBackendService = IdentityBackendService(spyHttpClient) /* When */ @@ -59,7 +59,7 @@ class IdentityBackendServiceTests : FunSpec({ /* Then */ coVerify { - spyHttpClient.delete("apps/appId/user/by/$aliasLabel/$aliasValue/identity/$aliasToDelete") + spyHttpClient.delete("apps/appId/users/by/$aliasLabel/$aliasValue/identity/$aliasToDelete") } } }) diff --git a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/backend/SubscriptionBackendServiceTests.kt b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/backend/SubscriptionBackendServiceTests.kt index 418bfa4669..d843be8d05 100644 --- a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/backend/SubscriptionBackendServiceTests.kt +++ b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/backend/SubscriptionBackendServiceTests.kt @@ -6,8 +6,6 @@ import com.onesignal.core.internal.http.IHttpClient import com.onesignal.debug.LogLevel import com.onesignal.debug.internal.logging.Logging import com.onesignal.extensions.RobolectricTest -import com.onesignal.mocks.AndroidMockHelper -import com.onesignal.mocks.MockHelper import com.onesignal.user.internal.backend.impl.SubscriptionBackendService import com.onesignal.user.internal.subscriptions.SubscriptionStatus import io.kotest.assertions.throwables.shouldThrowUnit @@ -32,10 +30,18 @@ class SubscriptionBackendServiceTests : FunSpec({ val aliasValue = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(202, "{id: \"subscriptionId\"}") - val subscriptionBackendService = SubscriptionBackendService(AndroidMockHelper.applicationService(), MockHelper.deviceService(), spyHttpClient) + val subscriptionBackendService = SubscriptionBackendService(spyHttpClient) /* When */ - val response = subscriptionBackendService.createSubscription("appId", aliasLabel, aliasValue, SubscriptionObjectType.ANDROID_PUSH, true, "pushToken", SubscriptionStatus.SUBSCRIBED) + val subscription = SubscriptionObject( + "no-id", + SubscriptionObjectType.ANDROID_PUSH, + "pushToken", + true, + SubscriptionStatus.SUBSCRIBED.value + ) + + val response = subscriptionBackendService.createSubscription("appId", aliasLabel, aliasValue, subscription) /* Then */ response shouldBe "subscriptionId" @@ -59,18 +65,22 @@ class SubscriptionBackendServiceTests : FunSpec({ val aliasValue = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(404, "NOT FOUND") - val subscriptionBackendService = SubscriptionBackendService(AndroidMockHelper.applicationService(), MockHelper.deviceService(), spyHttpClient) + val subscriptionBackendService = SubscriptionBackendService(spyHttpClient) /* When */ + val subscription = SubscriptionObject( + "no-id", + SubscriptionObjectType.ANDROID_PUSH, + "pushToken", + true, + SubscriptionStatus.SUBSCRIBED.value + ) val exception = shouldThrowUnit { subscriptionBackendService.createSubscription( "appId", aliasLabel, aliasValue, - SubscriptionObjectType.ANDROID_PUSH, - true, - "pushToken", - SubscriptionStatus.SUBSCRIBED + subscription ) } diff --git a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/backend/UserBackendServiceTests.kt b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/backend/UserBackendServiceTests.kt index 2080fea1e4..773e69054a 100644 --- a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/backend/UserBackendServiceTests.kt +++ b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/backend/UserBackendServiceTests.kt @@ -25,9 +25,8 @@ class UserBackendServiceTests : FunSpec({ test("create user with nothing throws an exception") { /* Given */ val spyHttpClient = mockk() - val spySubscriptionBackend = mockk() coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(403, "FORBIDDEN") - val userBackendService = UserBackendService(spyHttpClient, spySubscriptionBackend) + val userBackendService = UserBackendService(spyHttpClient) val identities = mapOf() val properties = PropertiesObject() val subscriptions = listOf() @@ -46,9 +45,8 @@ class UserBackendServiceTests : FunSpec({ /* Given */ val osId = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() - val spySubscriptionBackend = mockk() coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(202, "{identity:{onesignal_id: \"$osId\", aliasLabel1: \"aliasValue1\"}}") - val userBackendService = UserBackendService(spyHttpClient, spySubscriptionBackend) + val userBackendService = UserBackendService(spyHttpClient) val identities = mapOf("aliasLabel1" to "aliasValue1") val properties = PropertiesObject() val subscriptions = listOf() @@ -62,7 +60,7 @@ class UserBackendServiceTests : FunSpec({ response.subscriptions.count() shouldBe 0 coVerify { spyHttpClient.post( - "apps/appId/user", + "apps/appId/users", withArg { it.has("identity") shouldBe true it.getJSONObject("identity").has("aliasLabel1") shouldBe true @@ -78,9 +76,8 @@ class UserBackendServiceTests : FunSpec({ /* Given */ val osId = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() - val spySubscriptionBackend = mockk() coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(202, "{identity:{onesignal_id: \"$osId\"}, subscriptions:[{id:\"subscriptionId1\", type:\"AndroidPush\"}]}") - val userBackendService = UserBackendService(spyHttpClient, spySubscriptionBackend) + val userBackendService = UserBackendService(spyHttpClient) val identities = mapOf() val properties = PropertiesObject() val subscriptions = mutableListOf() @@ -97,7 +94,7 @@ class UserBackendServiceTests : FunSpec({ coVerify { spyHttpClient.post( - "apps/appId/user", + "apps/appId/users", withArg { it.has("identity") shouldBe false it.has("properties") shouldBe false @@ -114,9 +111,8 @@ class UserBackendServiceTests : FunSpec({ /* Given */ val osId = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() - val spySubscriptionBackend = mockk() coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(202, "{identity:{onesignal_id: \"$osId\", aliasLabel1: \"aliasValue1\"}, properties: { tags: {tagKey1: tagValue1}}}") - val userBackendService = UserBackendService(spyHttpClient, spySubscriptionBackend) + val userBackendService = UserBackendService(spyHttpClient) val identities = mapOf("aliasLabel1" to "aliasValue1") val properties = PropertiesObject(tags = mapOf("tagkey1" to "tagValue1")) val subscriptions = listOf() @@ -130,7 +126,7 @@ class UserBackendServiceTests : FunSpec({ response.subscriptions.count() shouldBe 0 coVerify { spyHttpClient.post( - "apps/appId/user", + "apps/appId/users", withArg { it.has("identity") shouldBe true it.getJSONObject("identity").has("aliasLabel1") shouldBe true @@ -150,9 +146,8 @@ class UserBackendServiceTests : FunSpec({ val aliasLabel = "onesignal_id" val aliasValue = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() - val spySubscriptionBackend = mockk() - coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(202, "{properties: { tags: {tagKey1: tagValue1}}}") - val userBackendService = UserBackendService(spyHttpClient, spySubscriptionBackend) + coEvery { spyHttpClient.patch(any(), any()) } returns HttpResponse(202, "{properties: { tags: {tagKey1: tagValue1}}}") + val userBackendService = UserBackendService(spyHttpClient) val properties = PropertiesObject(tags = mapOf("tagkey1" to "tagValue1")) val propertiesDelta = PropertiesDeltasObject() @@ -162,7 +157,7 @@ class UserBackendServiceTests : FunSpec({ /* Then */ coVerify { spyHttpClient.patch( - "apps/appId/user/by/$aliasLabel/$aliasValue", + "apps/appId/users/by/$aliasLabel/$aliasValue", withArg { it.has("properties") shouldBe true it.getJSONObject("properties").has("tags") shouldBe true @@ -178,9 +173,8 @@ class UserBackendServiceTests : FunSpec({ val aliasLabel = "onesignal_id" val aliasValue = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() - val spySubscriptionBackend = mockk() - coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(202, "{properties: { tags: {tagKey1: tagValue1}}}") - val userBackendService = UserBackendService(spyHttpClient, spySubscriptionBackend) + coEvery { spyHttpClient.patch(any(), any()) } returns HttpResponse(202, "{properties: { tags: {tagKey1: tagValue1}}}") + val userBackendService = UserBackendService(spyHttpClient) val properties = PropertiesObject(language = "newLanguage") val propertiesDelta = PropertiesDeltasObject() @@ -190,7 +184,7 @@ class UserBackendServiceTests : FunSpec({ /* Then */ coVerify { spyHttpClient.patch( - "apps/appId/user/by/$aliasLabel/$aliasValue", + "apps/appId/users/by/$aliasLabel/$aliasValue", withArg { it.has("properties") shouldBe true it.getJSONObject("properties").has("language") shouldBe true @@ -205,9 +199,8 @@ class UserBackendServiceTests : FunSpec({ val aliasLabel = "onesignal_id" val aliasValue = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() - val spySubscriptionBackend = mockk() - coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(202, "{properties: { timezone_id: \"America/New_York\"}}") - val userBackendService = UserBackendService(spyHttpClient, spySubscriptionBackend) + coEvery { spyHttpClient.patch(any(), any()) } returns HttpResponse(202, "{properties: { timezone_id: \"America/New_York\"}}") + val userBackendService = UserBackendService(spyHttpClient) val properties = PropertiesObject(timezoneId = "America/New_York") val propertiesDelta = PropertiesDeltasObject() @@ -217,7 +210,7 @@ class UserBackendServiceTests : FunSpec({ /* Then */ coVerify { spyHttpClient.patch( - "apps/appId/user/by/$aliasLabel/$aliasValue", + "apps/appId/users/by/$aliasLabel/$aliasValue", withArg { it.has("properties") shouldBe true it.getJSONObject("properties").has("timezone_id") shouldBe true @@ -232,9 +225,8 @@ class UserBackendServiceTests : FunSpec({ val aliasLabel = "onesignal_id" val aliasValue = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() - val spySubscriptionBackend = mockk() - coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(202, "{properties: { country: \"TV\"}}") - val userBackendService = UserBackendService(spyHttpClient, spySubscriptionBackend) + coEvery { spyHttpClient.patch(any(), any()) } returns HttpResponse(202, "{properties: { country: \"TV\"}}") + val userBackendService = UserBackendService(spyHttpClient) val properties = PropertiesObject(country = "TV") val propertiesDelta = PropertiesDeltasObject() @@ -244,7 +236,7 @@ class UserBackendServiceTests : FunSpec({ /* Then */ coVerify { spyHttpClient.patch( - "apps/appId/user/by/$aliasLabel/$aliasValue", + "apps/appId/users/by/$aliasLabel/$aliasValue", withArg { it.has("properties") shouldBe true it.getJSONObject("properties").has("country") shouldBe true @@ -259,10 +251,9 @@ class UserBackendServiceTests : FunSpec({ val aliasLabel = "onesignal_id" val aliasValue = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() - val spySubscriptionBackend = mockk() - coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(202, "{properties: { lat: \"12.34\", long: \"45.67\"}}") - val userBackendService = UserBackendService(spyHttpClient, spySubscriptionBackend) - val properties = PropertiesObject(latitude = 12.34, longitude = 45.67, locationAccuracy = 8.9f, locationType = 1, locationBackground = false, locationTimestamp = 1111) + coEvery { spyHttpClient.patch(any(), any()) } returns HttpResponse(202, "{properties: { lat: 12.34, long: 45.67}}") + val userBackendService = UserBackendService(spyHttpClient) + val properties = PropertiesObject(latitude = 12.34, longitude = 45.67) val propertiesDelta = PropertiesDeltasObject() /* When */ @@ -271,13 +262,13 @@ class UserBackendServiceTests : FunSpec({ /* Then */ coVerify { spyHttpClient.patch( - "apps/appId/user/by/$aliasLabel/$aliasValue", + "apps/appId/users/by/$aliasLabel/$aliasValue", withArg { it.has("properties") shouldBe true it.getJSONObject("properties").has("lat") shouldBe true - it.getJSONObject("properties").getString("lat") shouldBe 12.34 + it.getJSONObject("properties").getDouble("lat") shouldBe 12.34 it.getJSONObject("properties").has("long") shouldBe true - it.getJSONObject("properties").getString("long") shouldBe 45.67 + it.getJSONObject("properties").getDouble("long") shouldBe 45.67 } ) } @@ -288,9 +279,8 @@ class UserBackendServiceTests : FunSpec({ val aliasLabel = "onesignal_id" val aliasValue = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() - val spySubscriptionBackend = mockk() - coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(202, "{properties: {} }") - val userBackendService = UserBackendService(spyHttpClient, spySubscriptionBackend) + coEvery { spyHttpClient.patch(any(), any()) } returns HttpResponse(202, "{properties: {} }") + val userBackendService = UserBackendService(spyHttpClient) val properties = PropertiesObject(tags = mapOf("tagkey1" to "tagValue1")) val propertiesDelta = PropertiesDeltasObject() @@ -300,9 +290,9 @@ class UserBackendServiceTests : FunSpec({ /* Then */ coVerify { spyHttpClient.patch( - "apps/appId/user/by/$aliasLabel/$aliasValue", + "apps/appId/users/by/$aliasLabel/$aliasValue", withArg { - it.has("refresh_device_metadata") shouldBe true + it.getBoolean("refresh_device_metadata") shouldBe true } ) } @@ -313,9 +303,8 @@ class UserBackendServiceTests : FunSpec({ val aliasLabel = "onesignal_id" val aliasValue = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() - val spySubscriptionBackend = mockk() - coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(202, "{properties: {} }") - val userBackendService = UserBackendService(spyHttpClient, spySubscriptionBackend) + coEvery { spyHttpClient.patch(any(), any()) } returns HttpResponse(202, "{properties: {} }") + val userBackendService = UserBackendService(spyHttpClient) val properties = PropertiesObject(tags = mapOf("tagkey1" to "tagValue1")) val propertiesDelta = PropertiesDeltasObject() @@ -325,9 +314,9 @@ class UserBackendServiceTests : FunSpec({ /* Then */ coVerify { spyHttpClient.patch( - "apps/appId/user/by/$aliasLabel/$aliasValue", + "apps/appId/users/by/$aliasLabel/$aliasValue", withArg { - it.has("refresh_device_metadata") shouldBe false + it.getBoolean("refresh_device_metadata") shouldBe false } ) } @@ -338,9 +327,8 @@ class UserBackendServiceTests : FunSpec({ val aliasLabel = "onesignal_id" val aliasValue = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() - val spySubscriptionBackend = mockk() - coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(202, "{properties: { }}") - val userBackendService = UserBackendService(spyHttpClient, spySubscriptionBackend) + coEvery { spyHttpClient.patch(any(), any()) } returns HttpResponse(202, "{properties: { }}") + val userBackendService = UserBackendService(spyHttpClient) val properties = PropertiesObject() val propertiesDelta = PropertiesDeltasObject(sessionTime = 1111, sessionCount = 1) @@ -350,13 +338,13 @@ class UserBackendServiceTests : FunSpec({ /* Then */ coVerify { spyHttpClient.patch( - "apps/appId/user/by/$aliasLabel/$aliasValue", + "apps/appId/users/by/$aliasLabel/$aliasValue", withArg { it.has("deltas") shouldBe true it.getJSONObject("deltas").has("session_time") shouldBe true - it.getJSONObject("deltas").getString("session_time") shouldBe 1111 + it.getJSONObject("deltas").getLong("session_time") shouldBe 1111 it.getJSONObject("deltas").has("session_count") shouldBe true - it.getJSONObject("deltas").getString("session_count") shouldBe 1 + it.getJSONObject("deltas").getInt("session_count") shouldBe 1 } ) } @@ -367,9 +355,8 @@ class UserBackendServiceTests : FunSpec({ val aliasLabel = "onesignal_id" val aliasValue = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() - val spySubscriptionBackend = mockk() - coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(202, "{properties: { }}") - val userBackendService = UserBackendService(spyHttpClient, spySubscriptionBackend) + coEvery { spyHttpClient.patch(any(), any()) } returns HttpResponse(202, "{properties: { }}") + val userBackendService = UserBackendService(spyHttpClient) val properties = PropertiesObject() val propertiesDelta = PropertiesDeltasObject( amountSpent = BigDecimal(1111), @@ -385,11 +372,11 @@ class UserBackendServiceTests : FunSpec({ /* Then */ coVerify { spyHttpClient.patch( - "apps/appId/user/by/$aliasLabel/$aliasValue", + "apps/appId/users/by/$aliasLabel/$aliasValue", withArg { it.has("deltas") shouldBe true it.getJSONObject("deltas").has("amount_spent") shouldBe true - it.getJSONObject("deltas").getString("amount_spent") shouldBe 1111 + it.getJSONObject("deltas").getDouble("amount_spent") shouldBe 1111 it.getJSONObject("deltas").has("purchases") shouldBe true it.getJSONObject("deltas").getJSONArray("purchases").length() shouldBe 2 it.getJSONObject("deltas").getJSONArray("purchases").getJSONObject(0).has("sku") shouldBe true @@ -397,13 +384,13 @@ class UserBackendServiceTests : FunSpec({ it.getJSONObject("deltas").getJSONArray("purchases").getJSONObject(0).has("iso") shouldBe true it.getJSONObject("deltas").getJSONArray("purchases").getJSONObject(0).getString("iso") shouldBe "iso1" it.getJSONObject("deltas").getJSONArray("purchases").getJSONObject(0).has("amount") shouldBe true - it.getJSONObject("deltas").getJSONArray("purchases").getJSONObject(0).getString("amount") shouldBe 2222 + it.getJSONObject("deltas").getJSONArray("purchases").getJSONObject(0).getDouble("amount") shouldBe 2222 it.getJSONObject("deltas").getJSONArray("purchases").getJSONObject(1).has("sku") shouldBe true it.getJSONObject("deltas").getJSONArray("purchases").getJSONObject(1).getString("sku") shouldBe "sku2" it.getJSONObject("deltas").getJSONArray("purchases").getJSONObject(1).has("iso") shouldBe true it.getJSONObject("deltas").getJSONArray("purchases").getJSONObject(1).getString("iso") shouldBe "iso2" it.getJSONObject("deltas").getJSONArray("purchases").getJSONObject(1).has("amount") shouldBe true - it.getJSONObject("deltas").getJSONArray("purchases").getJSONObject(1).getString("amount") shouldBe 4444 + it.getJSONObject("deltas").getJSONArray("purchases").getJSONObject(1).getDouble("amount") shouldBe 4444 } ) } @@ -414,9 +401,8 @@ class UserBackendServiceTests : FunSpec({ val aliasLabel = "onesignal_id" val aliasValue = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() - val spySubscriptionBackend = mockk() - coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(404, "NOT FOUND") - val userBackendService = UserBackendService(spyHttpClient, spySubscriptionBackend) + coEvery { spyHttpClient.patch(any(), any()) } returns HttpResponse(404, "NOT FOUND") + val userBackendService = UserBackendService(spyHttpClient) val properties = PropertiesObject(tags = mapOf("tagkey1" to "tagValue1")) val propertiesDelta = PropertiesDeltasObject() @@ -435,9 +421,8 @@ class UserBackendServiceTests : FunSpec({ val aliasLabel = "onesignal_id" val aliasValue = "11111111-1111-1111-1111-111111111111" val spyHttpClient = mockk() - val spySubscriptionBackend = mockk() - coEvery { spyHttpClient.post(any(), any()) } returns HttpResponse(403, "FORBIDDEN") - val userBackendService = UserBackendService(spyHttpClient, spySubscriptionBackend) + coEvery { spyHttpClient.patch(any(), any()) } returns HttpResponse(403, "FORBIDDEN") + val userBackendService = UserBackendService(spyHttpClient) val properties = PropertiesObject(tags = mapOf("tagkey1" to "tagValue1")) val propertiesDelta = PropertiesDeltasObject() diff --git a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/IdentityOperationExecutorTests.kt b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/IdentityOperationExecutorTests.kt index 606bc47a82..199cf245ab 100644 --- a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/IdentityOperationExecutorTests.kt +++ b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/IdentityOperationExecutorTests.kt @@ -28,11 +28,11 @@ class IdentityOperationExecutorTests : FunSpec({ test("execution of set alias operation") { /* Given */ val mockIdentityBackendService = mockk() - coEvery { mockIdentityBackendService.createAlias(any(), any(), any(), any()) } returns mapOf() + coEvery { mockIdentityBackendService.setAlias(any(), any(), any(), any()) } returns mapOf() val mockIdentityModel = mockk() every { mockIdentityModel.onesignalId } returns "onesignalId" - every { mockIdentityModel.setProperty(any(), any(), any()) } just runs + every { mockIdentityModel.setStringProperty(any(), any(), any()) } just runs val mockIdentityModelStore = mockk() every { mockIdentityModelStore.model } returns mockIdentityModel @@ -45,14 +45,14 @@ class IdentityOperationExecutorTests : FunSpec({ /* Then */ response.result shouldBe ExecutionResult.SUCCESS - coVerify(exactly = 1) { mockIdentityBackendService.createAlias("appId", IdentityConstants.ONESIGNAL_ID, "onesignalId", mapOf("aliasKey1" to "aliasValue1")) } - verify(exactly = 1) { mockIdentityModel.setProperty("aliasKey1", "aliasValue1", ModelChangeTags.HYDRATE) } + coVerify(exactly = 1) { mockIdentityBackendService.setAlias("appId", IdentityConstants.ONESIGNAL_ID, "onesignalId", mapOf("aliasKey1" to "aliasValue1")) } + verify(exactly = 1) { mockIdentityModel.setStringProperty("aliasKey1", "aliasValue1", ModelChangeTags.HYDRATE) } } test("execution of set alias operation with network timeout") { /* Given */ val mockIdentityBackendService = mockk() - coEvery { mockIdentityBackendService.createAlias(any(), any(), any(), any()) } throws BackendException(408, "TIMEOUT") + coEvery { mockIdentityBackendService.setAlias(any(), any(), any(), any()) } throws BackendException(408, "TIMEOUT") val mockIdentityModelStore = MockHelper.identityModelStore() @@ -70,7 +70,7 @@ class IdentityOperationExecutorTests : FunSpec({ test("execution of set alias operation with non-retryable error") { /* Given */ val mockIdentityBackendService = mockk() - coEvery { mockIdentityBackendService.createAlias(any(), any(), any(), any()) } throws BackendException(404, "NOT FOUND") + coEvery { mockIdentityBackendService.setAlias(any(), any(), any(), any()) } throws BackendException(404, "NOT FOUND") val mockIdentityModelStore = MockHelper.identityModelStore() @@ -92,7 +92,7 @@ class IdentityOperationExecutorTests : FunSpec({ val mockIdentityModel = mockk() every { mockIdentityModel.onesignalId } returns "onesignalId" - every { mockIdentityModel.setProperty(any(), any(), any()) } just runs + every { mockIdentityModel.setOptStringProperty(any(), any(), any()) } just runs val mockIdentityModelStore = mockk() every { mockIdentityModelStore.model } returns mockIdentityModel @@ -106,7 +106,7 @@ class IdentityOperationExecutorTests : FunSpec({ /* Then */ response.result shouldBe ExecutionResult.SUCCESS coVerify(exactly = 1) { mockIdentityBackendService.deleteAlias("appId", IdentityConstants.ONESIGNAL_ID, "onesignalId", "aliasKey1") } - verify(exactly = 1) { mockIdentityModel.setProperty("aliasKey1", null, ModelChangeTags.HYDRATE) } + verify(exactly = 1) { mockIdentityModel.setOptStringProperty("aliasKey1", null, ModelChangeTags.HYDRATE) } } test("execution of delete alias operation with network timeout") { diff --git a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/RefreshUserOperationExecutorTests.kt b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/RefreshUserOperationExecutorTests.kt index c611a39035..d6b275f9a9 100644 --- a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/RefreshUserOperationExecutorTests.kt +++ b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/RefreshUserOperationExecutorTests.kt @@ -1,6 +1,7 @@ package com.onesignal.user.internal.operations import com.onesignal.common.exceptions.BackendException +import com.onesignal.common.modeling.ModelChangeTags import com.onesignal.core.internal.operations.ExecutionResult import com.onesignal.core.internal.operations.Operation import com.onesignal.mocks.MockHelper @@ -48,7 +49,7 @@ class RefreshUserOperationExecutorTests : FunSpec({ val mockIdentityModel = IdentityModel() mockIdentityModel.onesignalId = remoteOneSignalId every { mockIdentityModelStore.model } returns mockIdentityModel - every { mockIdentityModelStore.replace(any()) } just runs + every { mockIdentityModelStore.replace(any(), any()) } just runs val mockPropertiesModelStore = MockHelper.propertiesModelStore() val mockPropertiesModel = PropertiesModel() @@ -56,10 +57,10 @@ class RefreshUserOperationExecutorTests : FunSpec({ mockPropertiesModel.country = "VT" mockPropertiesModel.language = "language" every { mockPropertiesModelStore.model } returns mockPropertiesModel - every { mockPropertiesModelStore.replace(any()) } just runs + every { mockPropertiesModelStore.replace(any(), any()) } just runs val mockSubscriptionsModelStore = mockk() - every { mockSubscriptionsModelStore.replaceAll(any()) } just runs + every { mockSubscriptionsModelStore.replaceAll(any(), any()) } just runs val loginUserOperationExecutor = RefreshUserOperationExecutor( mockUserBackendService, @@ -80,26 +81,29 @@ class RefreshUserOperationExecutorTests : FunSpec({ mockIdentityModelStore.replace( withArg { it["aliasLabel1"] shouldBe "aliasValue1" - } + }, + ModelChangeTags.HYDRATE ) mockPropertiesModelStore.replace( withArg { it.country shouldBe "US" it.language shouldBe null - } + }, + ModelChangeTags.HYDRATE ) mockSubscriptionsModelStore.replaceAll( withArg { it.count() shouldBe 2 it[0].id shouldBe remoteSubscriptionId1 it[0].type shouldBe SubscriptionType.PUSH - it[0].enabled shouldBe true + it[0].optedIn shouldBe true it[0].address shouldBe "pushToken" it[1].id shouldBe remoteSubscriptionId2 it[1].type shouldBe SubscriptionType.EMAIL - it[1].enabled shouldBe true + it[1].optedIn shouldBe true it[1].address shouldBe "name@company.com" - } + }, + ModelChangeTags.HYDRATE ) } } diff --git a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt index 78788f8c5a..6c51b8fc2f 100644 --- a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt +++ b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt @@ -3,6 +3,8 @@ package com.onesignal.user.internal.operations import com.onesignal.common.exceptions.BackendException import com.onesignal.core.internal.operations.ExecutionResult import com.onesignal.core.internal.operations.Operation +import com.onesignal.extensions.RobolectricTest +import com.onesignal.mocks.AndroidMockHelper import com.onesignal.mocks.MockHelper import com.onesignal.user.internal.backend.ISubscriptionBackendService import com.onesignal.user.internal.backend.IdentityConstants @@ -24,6 +26,7 @@ import io.mockk.runs import io.mockk.verify import org.junit.runner.RunWith +@RobolectricTest @RunWith(KotestTestRunner::class) class SubscriptionOperationExecutorTests : FunSpec({ val appId = "appId" @@ -34,7 +37,7 @@ class SubscriptionOperationExecutorTests : FunSpec({ test("create subscription successfully creates subscription") { /* Given */ val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any(), any(), any(), any()) } returns remoteSubscriptionId + coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } returns remoteSubscriptionId val mockSubscriptionsModelStore = mockk() val subscriptionModel1 = SubscriptionModel() @@ -43,6 +46,7 @@ class SubscriptionOperationExecutorTests : FunSpec({ val subscriptionOperationExecutor = SubscriptionOperationExecutor( mockSubscriptionBackendService, MockHelper.deviceService(), + AndroidMockHelper.applicationService(), mockSubscriptionsModelStore ) @@ -59,10 +63,12 @@ class SubscriptionOperationExecutorTests : FunSpec({ appId, IdentityConstants.ONESIGNAL_ID, remoteOneSignalId, - SubscriptionObjectType.ANDROID_PUSH, - true, - "pushToken", - SubscriptionStatus.SUBSCRIBED + withArg { + it.type shouldBe SubscriptionObjectType.ANDROID_PUSH + it.enabled shouldBe true + it.token shouldBe "pushToken" + it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value + } ) } } @@ -70,12 +76,13 @@ class SubscriptionOperationExecutorTests : FunSpec({ test("create subscription fails with retry when there is a network condition") { /* Given */ val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any(), any(), any(), any()) } throws BackendException(408) + coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } throws BackendException(408) val mockSubscriptionsModelStore = mockk() val subscriptionOperationExecutor = SubscriptionOperationExecutor( mockSubscriptionBackendService, MockHelper.deviceService(), + AndroidMockHelper.applicationService(), mockSubscriptionsModelStore ) @@ -91,10 +98,12 @@ class SubscriptionOperationExecutorTests : FunSpec({ appId, IdentityConstants.ONESIGNAL_ID, remoteOneSignalId, - SubscriptionObjectType.ANDROID_PUSH, - true, - "pushToken", - SubscriptionStatus.SUBSCRIBED + withArg { + it.type shouldBe SubscriptionObjectType.ANDROID_PUSH + it.enabled shouldBe true + it.token shouldBe "pushToken" + it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value + } ) } } @@ -102,12 +111,13 @@ class SubscriptionOperationExecutorTests : FunSpec({ test("create subscription fails without retry when there is a backend error") { /* Given */ val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any(), any(), any(), any()) } throws BackendException(404) + coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } throws BackendException(404) val mockSubscriptionsModelStore = mockk() val subscriptionOperationExecutor = SubscriptionOperationExecutor( mockSubscriptionBackendService, MockHelper.deviceService(), + AndroidMockHelper.applicationService(), mockSubscriptionsModelStore ) @@ -123,10 +133,12 @@ class SubscriptionOperationExecutorTests : FunSpec({ appId, IdentityConstants.ONESIGNAL_ID, remoteOneSignalId, - SubscriptionObjectType.ANDROID_PUSH, - true, - "pushToken", - SubscriptionStatus.SUBSCRIBED + withArg { + it.type shouldBe SubscriptionObjectType.ANDROID_PUSH + it.enabled shouldBe true + it.token shouldBe "pushToken" + it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value + } ) } } @@ -142,6 +154,7 @@ class SubscriptionOperationExecutorTests : FunSpec({ val subscriptionOperationExecutor = SubscriptionOperationExecutor( mockSubscriptionBackendService, MockHelper.deviceService(), + AndroidMockHelper.applicationService(), mockSubscriptionsModelStore ) @@ -160,7 +173,7 @@ class SubscriptionOperationExecutorTests : FunSpec({ test("create subscription then update subscription successfully creates subscription") { /* Given */ val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any(), any(), any(), any()) } returns remoteSubscriptionId + coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } returns remoteSubscriptionId val mockSubscriptionsModelStore = mockk() val subscriptionModel1 = SubscriptionModel() @@ -169,6 +182,7 @@ class SubscriptionOperationExecutorTests : FunSpec({ val subscriptionOperationExecutor = SubscriptionOperationExecutor( mockSubscriptionBackendService, MockHelper.deviceService(), + AndroidMockHelper.applicationService(), mockSubscriptionsModelStore ) @@ -188,10 +202,12 @@ class SubscriptionOperationExecutorTests : FunSpec({ appId, IdentityConstants.ONESIGNAL_ID, remoteOneSignalId, - SubscriptionObjectType.ANDROID_PUSH, - true, - "pushToken2", - SubscriptionStatus.SUBSCRIBED + withArg { + it.type shouldBe SubscriptionObjectType.ANDROID_PUSH + it.enabled shouldBe true + it.token shouldBe "pushToken2" + it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value + } ) } } @@ -199,7 +215,7 @@ class SubscriptionOperationExecutorTests : FunSpec({ test("update subscription successfully updates subscription") { /* Given */ val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any(), any(), any(), any()) } just runs + coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any()) } just runs val mockSubscriptionsModelStore = mockk() val subscriptionModel1 = SubscriptionModel() @@ -210,6 +226,7 @@ class SubscriptionOperationExecutorTests : FunSpec({ val subscriptionOperationExecutor = SubscriptionOperationExecutor( mockSubscriptionBackendService, MockHelper.deviceService(), + AndroidMockHelper.applicationService(), mockSubscriptionsModelStore ) @@ -228,10 +245,12 @@ class SubscriptionOperationExecutorTests : FunSpec({ mockSubscriptionBackendService.updateSubscription( appId, remoteSubscriptionId, - SubscriptionObjectType.ANDROID_PUSH, - true, - "pushToken3", - SubscriptionStatus.SUBSCRIBED + withArg { + it.type shouldBe SubscriptionObjectType.ANDROID_PUSH + it.enabled shouldBe true + it.token shouldBe "pushToken3" + it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value + } ) } } @@ -239,12 +258,13 @@ class SubscriptionOperationExecutorTests : FunSpec({ test("update subscription fails with retry when there is a network condition") { /* Given */ val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any(), any(), any(), any()) } throws BackendException(408) + coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any()) } throws BackendException(408) val mockSubscriptionsModelStore = mockk() val subscriptionOperationExecutor = SubscriptionOperationExecutor( mockSubscriptionBackendService, MockHelper.deviceService(), + AndroidMockHelper.applicationService(), mockSubscriptionsModelStore ) @@ -261,10 +281,12 @@ class SubscriptionOperationExecutorTests : FunSpec({ mockSubscriptionBackendService.updateSubscription( appId, remoteSubscriptionId, - SubscriptionObjectType.ANDROID_PUSH, - true, - "pushToken2", - SubscriptionStatus.SUBSCRIBED + withArg { + it.type shouldBe SubscriptionObjectType.ANDROID_PUSH + it.enabled shouldBe true + it.token shouldBe "pushToken2" + it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value + } ) } } @@ -272,12 +294,13 @@ class SubscriptionOperationExecutorTests : FunSpec({ test("update subscription fails without retry when there is a backend error") { /* Given */ val mockSubscriptionBackendService = mockk() - coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any(), any(), any(), any()) } throws BackendException(404) + coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any()) } throws BackendException(404) val mockSubscriptionsModelStore = mockk() val subscriptionOperationExecutor = SubscriptionOperationExecutor( mockSubscriptionBackendService, MockHelper.deviceService(), + AndroidMockHelper.applicationService(), mockSubscriptionsModelStore ) @@ -294,10 +317,12 @@ class SubscriptionOperationExecutorTests : FunSpec({ mockSubscriptionBackendService.updateSubscription( appId, remoteSubscriptionId, - SubscriptionObjectType.ANDROID_PUSH, - true, - "pushToken2", - SubscriptionStatus.SUBSCRIBED + withArg { + it.type shouldBe SubscriptionObjectType.ANDROID_PUSH + it.enabled shouldBe true + it.token shouldBe "pushToken2" + it.notificationTypes shouldBe SubscriptionStatus.SUBSCRIBED.value + } ) } } @@ -313,6 +338,7 @@ class SubscriptionOperationExecutorTests : FunSpec({ val subscriptionOperationExecutor = SubscriptionOperationExecutor( mockSubscriptionBackendService, MockHelper.deviceService(), + AndroidMockHelper.applicationService(), mockSubscriptionsModelStore ) @@ -339,6 +365,7 @@ class SubscriptionOperationExecutorTests : FunSpec({ val subscriptionOperationExecutor = SubscriptionOperationExecutor( mockSubscriptionBackendService, MockHelper.deviceService(), + AndroidMockHelper.applicationService(), mockSubscriptionsModelStore ) @@ -363,6 +390,7 @@ class SubscriptionOperationExecutorTests : FunSpec({ val subscriptionOperationExecutor = SubscriptionOperationExecutor( mockSubscriptionBackendService, MockHelper.deviceService(), + AndroidMockHelper.applicationService(), mockSubscriptionsModelStore ) diff --git a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/UpdateUserOperationExecutorTests.kt b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/UpdateUserOperationExecutorTests.kt index 2884e472b8..576d18ab48 100644 --- a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/UpdateUserOperationExecutorTests.kt +++ b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/operations/UpdateUserOperationExecutorTests.kt @@ -103,7 +103,7 @@ class UpdateUserOperationExecutorTests : FunSpec({ IdentityConstants.ONESIGNAL_ID, remoteOneSignalId, withArg { - it.tags shouldBe mapOf("tagKey1" to "tagValue1-2", "tagKey2" to "tagValue2") + it.tags shouldBe mapOf("tagKey1" to "tagValue1-2", "tagKey2" to "tagValue2", "tagKey3" to null) it.country shouldBe "country" it.language shouldBe "lang2" it.timezoneId shouldBe "timezone" diff --git a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/subscriptions/SubscriptionManagerTests.kt b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/subscriptions/SubscriptionManagerTests.kt index 619e729a64..bead9d1780 100644 --- a/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/subscriptions/SubscriptionManagerTests.kt +++ b/OneSignalSDK/onesignal/src/test/java/com/onesignal/user/internal/subscriptions/SubscriptionManagerTests.kt @@ -3,9 +3,12 @@ package com.onesignal.user.internal.subscriptions import com.onesignal.common.modeling.ModelChangeTags import com.onesignal.common.modeling.ModelChangedArgs import com.onesignal.user.internal.subscriptions.impl.SubscriptionManager +import com.onesignal.user.subscriptions.ISmsSubscription import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.should import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.types.beInstanceOf import io.kotest.runner.junit4.KotestTestRunner import io.mockk.every import io.mockk.just @@ -25,21 +28,21 @@ class SubscriptionManagerTests : FunSpec({ pushSubscription.id = "subscription1" pushSubscription.type = SubscriptionType.PUSH pushSubscription.status = SubscriptionStatus.SUBSCRIBED - pushSubscription.enabled = true + pushSubscription.optedIn = true pushSubscription.address = "pushToken" val emailSubscription = SubscriptionModel() emailSubscription.id = "subscription2" emailSubscription.type = SubscriptionType.EMAIL emailSubscription.status = SubscriptionStatus.SUBSCRIBED - emailSubscription.enabled = false + emailSubscription.optedIn = false emailSubscription.address = "email@email.co" val smsSubscription = SubscriptionModel() smsSubscription.id = "subscription3" smsSubscription.type = SubscriptionType.SMS smsSubscription.status = SubscriptionStatus.SUBSCRIBED - smsSubscription.enabled = false + smsSubscription.optedIn = false smsSubscription.address = "+15558675309" val listOfSubscriptions = listOf(pushSubscription, emailSubscription, smsSubscription) @@ -56,7 +59,7 @@ class SubscriptionManagerTests : FunSpec({ subscriptions.push shouldNotBe null subscriptions.push!!.id shouldBe pushSubscription.id subscriptions.push!!.pushToken shouldBe pushSubscription.address - subscriptions.push!!.enabled shouldBe pushSubscription.enabled + subscriptions.push!!.optedIn shouldBe pushSubscription.optedIn subscriptions.emails.count() shouldBe 1 subscriptions.emails[0].id shouldBe emailSubscription.id subscriptions.emails[0].email shouldBe emailSubscription.address @@ -85,7 +88,7 @@ class SubscriptionManagerTests : FunSpec({ withArg { it.type shouldBe SubscriptionType.EMAIL it.address shouldBe "name@company.com" - it.enabled shouldBe true + it.optedIn shouldBe true it.status shouldBe SubscriptionStatus.SUBSCRIBED } ) @@ -112,7 +115,7 @@ class SubscriptionManagerTests : FunSpec({ withArg { it.type shouldBe SubscriptionType.SMS it.address shouldBe "+15558675309" - it.enabled shouldBe true + it.optedIn shouldBe true it.status shouldBe SubscriptionStatus.SUBSCRIBED } ) @@ -139,7 +142,7 @@ class SubscriptionManagerTests : FunSpec({ withArg { it.type shouldBe SubscriptionType.PUSH it.address shouldBe "pushToken" - it.enabled shouldBe true + it.optedIn shouldBe true it.status shouldBe SubscriptionStatus.SUBSCRIBED } ) @@ -154,7 +157,7 @@ class SubscriptionManagerTests : FunSpec({ pushSubscription.id = "subscription1" pushSubscription.type = SubscriptionType.PUSH pushSubscription.status = SubscriptionStatus.SUBSCRIBED - pushSubscription.enabled = true + pushSubscription.optedIn = true pushSubscription.address = "pushToken1" val listOfSubscriptions = listOf(pushSubscription) @@ -182,7 +185,7 @@ class SubscriptionManagerTests : FunSpec({ emailSubscription.id = "subscription1" emailSubscription.type = SubscriptionType.EMAIL emailSubscription.status = SubscriptionStatus.SUBSCRIBED - emailSubscription.enabled = true + emailSubscription.optedIn = true emailSubscription.address = "name@company.com" val listOfSubscriptions = listOf(emailSubscription) @@ -209,7 +212,7 @@ class SubscriptionManagerTests : FunSpec({ emailSubscription.id = "subscription1" emailSubscription.type = SubscriptionType.SMS emailSubscription.status = SubscriptionStatus.SUBSCRIBED - emailSubscription.enabled = true + emailSubscription.optedIn = true emailSubscription.address = "+18458675309" val listOfSubscriptions = listOf(emailSubscription) @@ -230,12 +233,12 @@ class SubscriptionManagerTests : FunSpec({ test("subscription added when model added") { /* Given */ - val emailSubscription = SubscriptionModel() - emailSubscription.id = "subscription1" - emailSubscription.type = SubscriptionType.SMS - emailSubscription.status = SubscriptionStatus.SUBSCRIBED - emailSubscription.enabled = true - emailSubscription.address = "+18458675309" + val smsSubscription = SubscriptionModel() + smsSubscription.id = "subscription1" + smsSubscription.type = SubscriptionType.SMS + smsSubscription.status = SubscriptionStatus.SUBSCRIBED + smsSubscription.optedIn = true + smsSubscription.address = "+18458675309" val mockSubscriptionModelStore = mockk() val listOfSubscriptions = listOf() @@ -249,14 +252,22 @@ class SubscriptionManagerTests : FunSpec({ subscriptionManager.subscribe(spySubscriptionChangedHandler) /* When */ - subscriptionManager.onModelAdded(emailSubscription, ModelChangeTags.NORMAL) + subscriptionManager.onModelAdded(smsSubscription, ModelChangeTags.NORMAL) val subscriptions = subscriptionManager.subscriptions /* Then */ subscriptions.smss.count() shouldBe 1 - subscriptions.smss[0].id shouldBe emailSubscription.id - subscriptions.smss[0].number shouldBe emailSubscription.address - verify(exactly = 1) { spySubscriptionChangedHandler.onSubscriptionsChanged() } + subscriptions.smss[0].id shouldBe smsSubscription.id + subscriptions.smss[0].number shouldBe smsSubscription.address + verify(exactly = 1) { + spySubscriptionChangedHandler.onSubscriptionAdded( + withArg { + it.id shouldBe smsSubscription.id + it should beInstanceOf() + (it as ISmsSubscription).number shouldBe smsSubscription.address + } + ) + } } test("subscription modified when model updated") { @@ -265,7 +276,7 @@ class SubscriptionManagerTests : FunSpec({ emailSubscription.id = "subscription1" emailSubscription.type = SubscriptionType.SMS emailSubscription.status = SubscriptionStatus.SUBSCRIBED - emailSubscription.enabled = true + emailSubscription.optedIn = true emailSubscription.address = "+18458675309" val mockSubscriptionModelStore = mockk() @@ -288,20 +299,20 @@ class SubscriptionManagerTests : FunSpec({ subscriptions.smss.count() shouldBe 1 subscriptions.smss[0].id shouldBe emailSubscription.id subscriptions.smss[0].number shouldBe "+15551234567" - verify(exactly = 1) { spySubscriptionChangedHandler.onSubscriptionsChanged() } + verify(exactly = 1) { spySubscriptionChangedHandler.onSubscriptionChanged(any(), any()) } } test("subscription removed when model removed") { /* Given */ - val emailSubscription = SubscriptionModel() - emailSubscription.id = "subscription1" - emailSubscription.type = SubscriptionType.SMS - emailSubscription.status = SubscriptionStatus.SUBSCRIBED - emailSubscription.enabled = true - emailSubscription.address = "+18458675309" + val smsSubscription = SubscriptionModel() + smsSubscription.id = "subscription1" + smsSubscription.type = SubscriptionType.SMS + smsSubscription.status = SubscriptionStatus.SUBSCRIBED + smsSubscription.optedIn = true + smsSubscription.address = "+18458675309" val mockSubscriptionModelStore = mockk() - val listOfSubscriptions = listOf(emailSubscription) + val listOfSubscriptions = listOf(smsSubscription) every { mockSubscriptionModelStore.subscribe(any()) } just runs every { mockSubscriptionModelStore.list() } returns listOfSubscriptions @@ -312,11 +323,19 @@ class SubscriptionManagerTests : FunSpec({ subscriptionManager.subscribe(spySubscriptionChangedHandler) /* When */ - subscriptionManager.onModelRemoved(emailSubscription, ModelChangeTags.NORMAL) + subscriptionManager.onModelRemoved(smsSubscription, ModelChangeTags.NORMAL) val subscriptions = subscriptionManager.subscriptions /* Then */ subscriptions.smss.count() shouldBe 0 - verify(exactly = 1) { spySubscriptionChangedHandler.onSubscriptionsChanged() } + verify(exactly = 1) { + spySubscriptionChangedHandler.onSubscriptionRemoved( + withArg { + it.id shouldBe smsSubscription.id + it should beInstanceOf() + (it as ISmsSubscription).number shouldBe smsSubscription.address + } + ) + } } })