Skip to content

Commit 7fa1ff4

Browse files
committed
[User Model] Backend Integration
* Implement `UserBackendService`, `IdentityBackendService`, and `SubscriptionBackendService` * Tweak `OutcomeEventsBackendService` to provide `subscriptionId`
1 parent 3d2b5b0 commit 7fa1ff4

27 files changed

+670
-546
lines changed

OneSignalSDK/onesignal/src/main/java/com/onesignal/common/JSONObjectExtensions.kt

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.onesignal.common
22

3+
import org.json.JSONArray
34
import org.json.JSONObject
45

56
/**
@@ -32,6 +33,21 @@ fun JSONObject.safeLong(name: String): Long? {
3233
return null
3334
}
3435

36+
/**
37+
* Retrieve a [Double] from the [JSONObject] safely.
38+
*
39+
* @param name The name of the attribute that contains a [Double] value.
40+
*
41+
* @return The [Double] value if it exists, null otherwise.
42+
*/
43+
fun JSONObject.safeDouble(name: String): Double? {
44+
if (this.has(name)) {
45+
return this.getDouble(name)
46+
}
47+
48+
return null
49+
}
50+
3551
/**
3652
* Retrieve a [Boolean] from the [JSONObject] safely.
3753
*
@@ -77,15 +93,134 @@ fun JSONObject.safeJSONObject(name: String): JSONObject? {
7793
return null
7894
}
7995

96+
/**
97+
* Create a [Map] from the [JSONObject].
98+
*/
99+
fun JSONObject.toMap(): Map<String, Any> {
100+
val map = mutableMapOf<String, Any>()
101+
102+
for(key in this.keys()) {
103+
map[key] = this[key]
104+
}
105+
106+
return map
107+
}
108+
80109
/**
81110
* Expand into a [JSONObject] safely.
82111
*
83112
* @param name The name of the attribute that contains a [JSONObject] value.
84113
* @param into The lambda method that will be executed to explore the [JSONObject] value, if the
85114
* attribute exists.
86115
*/
87-
fun JSONObject.expand(name: String, into: (childObject: JSONObject) -> Unit) {
116+
fun JSONObject.expandJSONObject(name: String, into: (childObject: JSONObject) -> Unit) {
88117
if (this.has(name)) {
89118
into(this.getJSONObject(name))
90119
}
91120
}
121+
122+
fun <T> JSONObject.expandJSONArray(name: String, into: (childObject: JSONObject) -> T?) : List<T> {
123+
val listToRet = mutableListOf<T>()
124+
if(this.has(name)) {
125+
val jsonArray = this.getJSONArray(name)
126+
for (index in 0 until jsonArray.length()) {
127+
val itemJSONObject = jsonArray.getJSONObject(index)
128+
val item = into(itemJSONObject)
129+
if(item != null) {
130+
listToRet.add(item)
131+
}
132+
}
133+
}
134+
135+
return listToRet
136+
}
137+
138+
/**
139+
* Populate the [JSONObject] with the [Map] provided.
140+
*
141+
* @param map: The map that will contain the name/values.
142+
*
143+
* @return The [JSONObject] itself, to allow for chaining.
144+
*/
145+
fun JSONObject.putMap(map: Map<String, Any>) : JSONObject {
146+
for (identity in map) {
147+
this.put(identity.key, identity.value)
148+
}
149+
150+
return this
151+
}
152+
153+
/**
154+
* Populate the [JSONObject] as attribute [name] with the [Map] provided.
155+
*
156+
* @param name: The name of the attribute that will contain the [JSONObject] value.
157+
* @param map: The map that will contain the name/values.
158+
*
159+
* @return The [JSONObject] itself, to allow for chaining.
160+
*/
161+
fun JSONObject.putMap(name: String, map: Map<String, Any>?) : JSONObject {
162+
if(map != null) {
163+
this.putJSONObject(name) {
164+
it.putMap(map)
165+
}
166+
}
167+
168+
return this
169+
}
170+
171+
/**
172+
* Put the attribute named by [name] with a [JSONObject] value, the contents
173+
* of which are determined by the expand.
174+
*
175+
* @param name: The name of the attribute that will contain the [JSONObject] value.
176+
* @param expand: The lambda that will be called to populate the [JSONObject] value.
177+
*
178+
* @return The [JSONObject] itself, to allow for chaining.
179+
*/
180+
fun JSONObject.putJSONObject(name: String, expand: (item: JSONObject) -> Unit ) : JSONObject {
181+
val childJSONObject = JSONObject()
182+
expand(childJSONObject)
183+
184+
this.put(name, childJSONObject)
185+
186+
return this
187+
}
188+
189+
/**
190+
* Put the attribute named by [name] with a [JSONArray] value, the contenxt of which
191+
* are deteremined by the input.
192+
*
193+
* @param name: The name of the attribute that will contain the [JSONArray] value.
194+
* @param list: The list of items that will be converted into the [JSONArray].
195+
* @param create: The lambda that will be called for each item in [list], expecting a [JSONObject] to be added to the array.
196+
*/
197+
fun <T> JSONObject.putJSONArray(name: String, list: List<T>?, create: (item: T) -> JSONObject?): JSONObject {
198+
if(list != null) {
199+
val jsonArray = JSONArray()
200+
list.forEach {
201+
val item = create(it)
202+
if(item != null)
203+
jsonArray.put(item)
204+
}
205+
this.put(name, jsonArray)
206+
}
207+
208+
return this
209+
}
210+
211+
/**
212+
* Put the name/value pair into the [JSONObject]. If the [value] provided is null,
213+
* nothing will be put into the [JSONObject].
214+
*
215+
* @param name The name of the attribute the [value] will be saved to.
216+
* @param value The value to put into the [JSONObject]. If not null, the attribute name will not be added.
217+
*
218+
* @return The [JSONObject] itself, to allow for chaining.
219+
*/
220+
fun JSONObject.putSafe(name: String, value: Any?): JSONObject {
221+
if (value != null) {
222+
this.put(name, value)
223+
}
224+
225+
return this
226+
}

OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/backend/impl/ParamsBackendService.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package com.onesignal.core.internal.backend.impl
22

33
import com.onesignal.common.IDManager
44
import com.onesignal.common.exceptions.BackendException
5-
import com.onesignal.common.expand
5+
import com.onesignal.common.expandJSONObject
66
import com.onesignal.common.safeBool
77
import com.onesignal.common.safeInt
88
import com.onesignal.common.safeLong
@@ -39,13 +39,13 @@ internal class ParamsBackendService(
3939

4040
// Process outcomes params
4141
var influenceParams: InfluenceParamsObject? = null
42-
responseJson.expand("outcomes") {
42+
responseJson.expandJSONObject("outcomes") {
4343
influenceParams = processOutcomeJson(it)
4444
}
4545

4646
// Process FCM params
4747
var fcmParams: FCMParamsObject? = null
48-
responseJson.expand("fcm") {
48+
responseJson.expandJSONObject("fcm") {
4949
fcmParams = FCMParamsObject(
5050
apiKey = it.safeString("api_key"),
5151
appId = it.safeString("app_id"),
@@ -82,27 +82,27 @@ internal class ParamsBackendService(
8282
var isUnattributedEnabled: Boolean? = null
8383

8484
// direct
85-
outcomeJson.expand("direct") {
85+
outcomeJson.expandJSONObject("direct") {
8686
isDirectEnabled = it.safeBool("enabled")
8787
}
8888

8989
// indirect
90-
outcomeJson.expand("indirect") { indirectJSON ->
90+
outcomeJson.expandJSONObject("indirect") { indirectJSON ->
9191
isIndirectEnabled = indirectJSON.safeBool("enabled")
9292

93-
indirectJSON.expand("notification_attribution") {
93+
indirectJSON.expandJSONObject("notification_attribution") {
9494
indirectNotificationAttributionWindow = it.safeInt("minutes_since_displayed")
9595
notificationLimit = it.safeInt("limit")
9696
}
9797

98-
indirectJSON.expand("in_app_message_attribution") {
98+
indirectJSON.expandJSONObject("in_app_message_attribution") {
9999
indirectIAMAttributionWindow = it.safeInt("minutes_since_displayed")
100100
iamLimit = it.safeInt("limit")
101101
}
102102
}
103103

104104
// unattributed
105-
outcomeJson.expand("unattributed") {
105+
outcomeJson.expandJSONObject("unattributed") {
106106
isUnattributedEnabled = it.safeBool("enabled")
107107
}
108108

OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/http/HttpResponse.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,5 @@ class HttpResponse(
2626
* Whether the response is a successful one.
2727
*/
2828
val isSuccess: Boolean
29-
get() = statusCode == HttpURLConnection.HTTP_OK || statusCode == HttpURLConnection.HTTP_ACCEPTED || statusCode == HttpURLConnection.HTTP_NOT_MODIFIED
29+
get() = statusCode == HttpURLConnection.HTTP_OK || statusCode == HttpURLConnection.HTTP_ACCEPTED || statusCode == HttpURLConnection.HTTP_NOT_MODIFIED || statusCode == HttpURLConnection.HTTP_CREATED
3030
}

OneSignalSDK/onesignal/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,10 @@ internal class OperationRepo(
144144
val executor = _executorsMap[startingOp.operation.name]
145145
?: throw Exception("Could not find executor for operation ${startingOp.operation.name}")
146146

147-
val response = executor.execute(ops.map { it.operation })
147+
val operations = ops.map { it.operation }
148+
val response = executor.execute(operations)
149+
150+
Logging.debug("OperationRepo: execute response = ${response.result}")
148151

149152
// if the execution resulted in ID translations, run through the queue so they pick it up.
150153
// We also run through the ops just executed in case they are re-added to the queue.
@@ -162,6 +165,7 @@ internal class OperationRepo(
162165
ops.forEach { it.waiter?.wake(true) }
163166
}
164167
ExecutionResult.FAIL_NORETRY -> {
168+
Logging.error("Operation execution failed without retry: $operations")
165169
// on failure we remove the operation from the store and wake any waiters
166170
ops.forEach { _operationModelStore.remove(it.operation.id) }
167171
ops.forEach { it.waiter?.wake(false) }

OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/outcomes/impl/IOutcomeEventsBackendService.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package com.onesignal.session.internal.outcomes.impl
22

33
import com.onesignal.common.exceptions.BackendException
44
import com.onesignal.core.internal.device.IDeviceService
5+
import com.onesignal.user.internal.subscriptions.SubscriptionModel
6+
import com.onesignal.user.subscriptions.ISubscription
57

68
/**
79
* The backend service for outcomes.
@@ -14,9 +16,10 @@ internal interface IOutcomeEventsBackendService {
1416
* If there is a non-successful response from the backend, a [BackendException] will be thrown with response data.
1517
*
1618
* @param appId The ID of the application this outcome event occurred under.
17-
* @param deviceType The device type.
19+
* @param userId The OneSignal user ID that is active during the outcome event.
20+
* @param subscriptionId The subscription ID that is active during the outcome event.
1821
* @param direct Whether this outcome event is direct. `true` if it is, `false` if it isn't, `null` if should not be specified.
1922
* @param event The outcome event to send up.
2023
*/
21-
suspend fun sendOutcomeEvent(appId: String, deviceType: IDeviceService.DeviceType, direct: Boolean?, event: OutcomeEvent)
24+
suspend fun sendOutcomeEvent(appId: String, userId: String, subscriptionId: String, direct: Boolean?, event: OutcomeEvent)
2225
}

OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsBackendService.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ import org.json.JSONObject
88
internal class OutcomeEventsBackendService(private val _http: IHttpClient) :
99
IOutcomeEventsBackendService {
1010

11-
override suspend fun sendOutcomeEvent(appId: String, deviceType: IDeviceService.DeviceType, direct: Boolean?, event: OutcomeEvent) {
11+
override suspend fun sendOutcomeEvent(appId: String, userId: String, subscriptionId: String, direct: Boolean?, event: OutcomeEvent) {
1212
val jsonObject = JSONObject()
1313
.put("app_id", appId)
14-
.put("device_type", deviceType.value)
14+
.put("onesignal_id", userId)
15+
.put("subscription", JSONObject()
16+
.put("id", subscriptionId)
17+
)
1518

1619
if (direct != null) {
1720
jsonObject.put("direct", direct)

OneSignalSDK/onesignal/src/main/java/com/onesignal/session/internal/outcomes/impl/OutcomeEventsController.kt

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import android.os.Process
44
import com.onesignal.common.exceptions.BackendException
55
import com.onesignal.common.threading.suspendifyOnThread
66
import com.onesignal.core.internal.config.ConfigModelStore
7-
import com.onesignal.core.internal.device.IDeviceService
87
import com.onesignal.core.internal.startup.IStartableService
98
import com.onesignal.core.internal.time.ITime
109
import com.onesignal.debug.internal.logging.Logging
@@ -15,6 +14,8 @@ import com.onesignal.session.internal.influence.InfluenceType
1514
import com.onesignal.session.internal.outcomes.IOutcomeEventsController
1615
import com.onesignal.session.internal.session.ISessionLifecycleHandler
1716
import com.onesignal.session.internal.session.ISessionService
17+
import com.onesignal.user.internal.identity.IdentityModelStore
18+
import com.onesignal.user.internal.subscriptions.ISubscriptionManager
1819

1920
internal class OutcomeEventsController(
2021
private val _session: ISessionService,
@@ -23,8 +24,9 @@ internal class OutcomeEventsController(
2324
private val _outcomeEventsPreferences: IOutcomeEventsPreferences,
2425
private val _outcomeEventsBackend: IOutcomeEventsBackendService,
2526
private val _configModelStore: ConfigModelStore,
27+
private val _identityModelStore: IdentityModelStore,
28+
private val _subscriptionManager: ISubscriptionManager,
2629
private val _time: ITime,
27-
private val _deviceService: IDeviceService
2830
) : IOutcomeEventsController, IStartableService, ISessionLifecycleHandler {
2931
// Keeps track of unique outcome events sent for UNATTRIBUTED sessions on a per session level
3032
private var unattributedUniqueOutcomeEventsSentOnSession: MutableSet<String> = mutableSetOf()
@@ -264,8 +266,15 @@ Outcome event was cached and will be reattempted on app cold start"""
264266
}
265267

266268
private suspend fun requestMeasureOutcomeEvent(eventParams: OutcomeEventParams) {
267-
val deviceType = _deviceService.deviceType
268269
val appId: String = _configModelStore.model.appId
270+
val subscriptionId = _subscriptionManager.subscriptions.push.id
271+
272+
// if we don't have a subscription ID yet, throw an exception. The outcome will be saved and processed
273+
// later, when we do have a subscription ID.
274+
if(subscriptionId.isEmpty()) {
275+
throw BackendException(0)
276+
}
277+
269278
val event = OutcomeEvent.fromOutcomeEventParamstoOutcomeEvent(eventParams)
270279
val direct = when (event.session) {
271280
InfluenceType.DIRECT -> true
@@ -274,6 +283,6 @@ Outcome event was cached and will be reattempted on app cold start"""
274283
else -> null
275284
}
276285

277-
_outcomeEventsBackend.sendOutcomeEvent(appId, deviceType, direct, event)
286+
_outcomeEventsBackend.sendOutcomeEvent(appId, _identityModelStore.model.onesignalId, subscriptionId, direct, event)
278287
}
279288
}

OneSignalSDK/onesignal/src/main/java/com/onesignal/user/internal/backend/IIdentityBackendService.kt

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,6 @@ interface IIdentityBackendService {
1414
*/
1515
suspend fun createAlias(appId: String, aliasLabel: String, aliasValue: String, identities: Map<String, String>): Map<String, String>
1616

17-
/**
18-
* Update the [aliasLabelToUpdate] from the user identified by the [aliasLabel]/[aliasValue] provided.
19-
*
20-
* If there is a non-successful response from the backend, a [BackendException] will be thrown with response data.
21-
*
22-
* @param appId The ID of the OneSignal application this user exists under.
23-
* @param aliasLabel The alias label to retrieve the user under.
24-
* @param aliasValue The identifier within the [aliasLabel] that identifies the user to retrieve.
25-
* @param aliasLabelToUpdate The alias label to delete from the user identified.
26-
* @param newAliasId The new ID for the [aliasLabelToUpdate].
27-
*/
28-
suspend fun updateAlias(appId: String, aliasLabel: String, aliasValue: String, aliasLabelToUpdate: String, newAliasId: String)
29-
3017
/**
3118
* Delete the [aliasLabelToDelete] from the user identified by the [aliasLabel]/[aliasValue] provided.
3219
*

0 commit comments

Comments
 (0)