Skip to content

Commit a8943dc

Browse files
committed
add HTTP header OneSignal-Install-Id
OneSignal-Install-Id is a UUIDv4 locally generated on the device and added to as an HTTP Header to all calls made to OneSignal's backend. This allows the OneSignal's backend know where traffic is coming from, no matter if the SubscriptionId or OneSignalId changes or isn't available yet. State for the new installId is encapsulated in a new InstallIdService class, where it handles generating the id and persisting it. Tests were also added to ensure the persisting behavior works as expected.
1 parent fda7c3e commit a8943dc

File tree

7 files changed

+98
-1
lines changed

7 files changed

+98
-1
lines changed

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/CoreModule.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import com.onesignal.core.internal.config.impl.ConfigModelStoreListener
1313
import com.onesignal.core.internal.database.IDatabaseProvider
1414
import com.onesignal.core.internal.database.impl.DatabaseProvider
1515
import com.onesignal.core.internal.device.IDeviceService
16+
import com.onesignal.core.internal.device.IInstallIdService
1617
import com.onesignal.core.internal.device.impl.DeviceService
18+
import com.onesignal.core.internal.device.impl.InstallIdService
1719
import com.onesignal.core.internal.http.IHttpClient
1820
import com.onesignal.core.internal.http.impl.HttpClient
1921
import com.onesignal.core.internal.http.impl.HttpConnectionFactory
@@ -53,6 +55,7 @@ internal class CoreModule : IModule {
5355
builder.register<Time>().provides<ITime>()
5456
builder.register<DatabaseProvider>().provides<IDatabaseProvider>()
5557
builder.register<StartupService>().provides<StartupService>()
58+
builder.register<InstallIdService>().provides<IInstallIdService>()
5659

5760
// Params (Config)
5861
builder.register<ConfigModelStore>().provides<ConfigModelStore>()
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.onesignal.core.internal.device
2+
3+
import java.util.UUID
4+
5+
interface IInstallIdService {
6+
/**
7+
* WARNING: This may do disk I/O on the first call, so never call this from
8+
* the main thread.
9+
*/
10+
suspend fun getId(): UUID
11+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.onesignal.core.internal.device.impl
2+
3+
import com.onesignal.core.internal.device.IInstallIdService
4+
import com.onesignal.core.internal.preferences.IPreferencesService
5+
import com.onesignal.core.internal.preferences.PreferenceOneSignalKeys
6+
import com.onesignal.core.internal.preferences.PreferenceStores
7+
import java.util.UUID
8+
9+
/**
10+
* Manages a persistent UUIDv4, generated once when app is first opened.
11+
* Value is for a HTTP header, OneSignal-Install-Id, added on all calls made
12+
* to OneSignal's backend. This allows the OneSignal's backend know where
13+
* traffic is coming from, no matter if the SubscriptionId or OneSignalId
14+
* changes or isn't available yet.
15+
*/
16+
internal class InstallIdService(
17+
private val _prefs: IPreferencesService,
18+
) : IInstallIdService {
19+
private val currentId: UUID by lazy {
20+
val idFromPrefs = _prefs.getString(PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.PREFS_OS_INSTALL_ID)
21+
if (idFromPrefs != null) {
22+
UUID.fromString(idFromPrefs)
23+
} else {
24+
val newId = UUID.randomUUID()
25+
_prefs.saveString(PreferenceStores.ONESIGNAL, PreferenceOneSignalKeys.PREFS_OS_INSTALL_ID, newId.toString())
26+
newId
27+
}
28+
}
29+
30+
/**
31+
* WARNING: This may do disk I/O on the first call, so never call this from
32+
* the main thread. Disk I/O is done inside of "currentId by lazy".
33+
*/
34+
override suspend fun getId() = currentId
35+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.onesignal.common.JSONUtils
66
import com.onesignal.common.OneSignalUtils
77
import com.onesignal.common.OneSignalWrapper
88
import com.onesignal.core.internal.config.ConfigModelStore
9+
import com.onesignal.core.internal.device.IInstallIdService
910
import com.onesignal.core.internal.http.HttpResponse
1011
import com.onesignal.core.internal.http.IHttpClient
1112
import com.onesignal.core.internal.preferences.IPreferencesService
@@ -32,6 +33,7 @@ internal class HttpClient(
3233
private val _prefs: IPreferencesService,
3334
private val _configModelStore: ConfigModelStore,
3435
private val _time: ITime,
36+
private val _installIdService: IInstallIdService,
3537
) : IHttpClient {
3638
/**
3739
* Delay making network requests until we reach this time.
@@ -149,6 +151,8 @@ internal class HttpClient(
149151
con.setRequestProperty("OneSignal-Subscription-Id", subscriptionId)
150152
}
151153

154+
con.setRequestProperty("OneSignal-Install-Id", _installIdService.getId().toString())
155+
152156
if (jsonBody != null) {
153157
con.doInput = true
154158
}

OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/preferences/IPreferencesService.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,13 @@ object PreferenceOneSignalKeys {
215215
*/
216216
const val PREFS_OS_ETAG_PREFIX = "PREFS_OS_ETAG_PREFIX_"
217217

218+
/**
219+
* (String) A install id, a UUIDv4 generated once when app is first opened.
220+
* Value is for a HTTP header, OneSignal-Install-Id, added on all calls
221+
* made to OneSignal's backend.
222+
*/
223+
const val PREFS_OS_INSTALL_ID = "PREFS_OS_INSTALL_ID"
224+
218225
/**
219226
* (String) A prefix key for retrieving the response for a given HTTP GET cache key. The cache
220227
* key should be appended to this prefix.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.onesignal.core.internal.device
2+
3+
import com.onesignal.core.internal.device.impl.InstallIdService
4+
import com.onesignal.mocks.MockPreferencesService
5+
import io.kotest.core.spec.style.FunSpec
6+
import io.kotest.matchers.shouldBe
7+
8+
class InstallIdServiceTests : FunSpec({
9+
test("2 calls result in the same value") {
10+
// Given
11+
val service = InstallIdService(MockPreferencesService())
12+
13+
// When
14+
val value1 = service.getId()
15+
val value2 = service.getId()
16+
17+
// Then
18+
value1 shouldBe value2
19+
}
20+
21+
// Real world scenario we are testing is if we cold restart the app we get
22+
// the same value
23+
test("reads from shared prefs") {
24+
// Given
25+
val sharedPrefs = MockPreferencesService()
26+
27+
// When
28+
val service1 = InstallIdService(sharedPrefs)
29+
val value1 = service1.getId()
30+
val service2 = InstallIdService(sharedPrefs)
31+
val value2 = service2.getId()
32+
33+
// Then
34+
value1 shouldBe value2
35+
}
36+
})

OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/http/HttpClientTests.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.onesignal.core.internal.http
22

33
import com.onesignal.common.OneSignalUtils
4+
import com.onesignal.core.internal.device.impl.InstallIdService
45
import com.onesignal.core.internal.http.impl.HttpClient
56
import com.onesignal.core.internal.time.impl.Time
67
import com.onesignal.debug.LogLevel
@@ -21,7 +22,7 @@ class Mocks {
2122
internal val response = MockHttpConnectionFactory.MockResponse()
2223
internal val factory = MockHttpConnectionFactory(response)
2324
internal val httpClient by lazy {
24-
HttpClient(factory, MockPreferencesService(), mockConfigModel, Time())
25+
HttpClient(factory, MockPreferencesService(), mockConfigModel, Time(), InstallIdService(MockPreferencesService()))
2526
}
2627
}
2728

0 commit comments

Comments
 (0)