diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java index 24b23d92cd..15626b5d57 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignal.java @@ -1414,6 +1414,41 @@ public void run() { emailLogout.run(); } + public static void setExternalUserId(final String externalId) { + + if (shouldLogUserPrivacyConsentErrorMessageForMethodName("setExternalId()")) + return; + + Runnable runSetExternalUserId = new Runnable() { + @Override + public void run() { + try { + OneSignalStateSynchronizer.setExternalUserId(externalId); + } catch (JSONException exception) { + String operation = externalId == "" ? "remove" : "set"; + onesignalLog(LOG_LEVEL.ERROR, "Attempted to " + operation + " external ID but encountered a JSON exception"); + exception.printStackTrace(); + } + } + }; + + // If either the app context is null or the waiting queue isn't done (to preserve operation order) + if (appContext == null || shouldRunTaskThroughQueue()) { + addTaskToQueue(new PendingTaskRunnable(runSetExternalUserId)); + return; + } + + runSetExternalUserId.run(); + } + + public static void removeExternalUserId() { + if (shouldLogUserPrivacyConsentErrorMessageForMethodName("removeExternalUserId()")) + return; + + // to remove the external user ID, the API requires an empty string + setExternalUserId(""); + } + /** * Tag a user based on an app event of your choosing so later you can create * OneSignal Segments diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalStateSynchronizer.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalStateSynchronizer.java index b9742a24ef..e5ab2a64d1 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalStateSynchronizer.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/OneSignalStateSynchronizer.java @@ -165,4 +165,9 @@ static void logoutEmail() { getPushStateSynchronizer().logoutEmail(); getEmailStateSynchronizer().logoutEmail(); } + + static void setExternalUserId(String externalId) throws JSONException { + getPushStateSynchronizer().setExternalUserId(externalId); + getEmailStateSynchronizer().setExternalUserId(externalId); + } } \ No newline at end of file diff --git a/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStateSynchronizer.java b/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStateSynchronizer.java index 6728f96f50..8f23b5dcc3 100644 --- a/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStateSynchronizer.java +++ b/OneSignalSDK/onesignal/src/main/java/com/onesignal/UserStateSynchronizer.java @@ -454,6 +454,10 @@ void syncHashedEmail(JSONObject emailFields) { generateJsonDiff(syncValues, emailFields, syncValues, null); } + void setExternalUserId(final String externalId) throws JSONException { + getUserStateForModification().syncValues.put("external_user_id", externalId); + } + abstract void setSubscription(boolean enable); private void handlePlayerDeletedFromServer() { diff --git a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/MainOneSignalClassRunner.java b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/MainOneSignalClassRunner.java index 130ceed7ff..2736c094ae 100644 --- a/OneSignalSDK/unittest/src/test/java/com/test/onesignal/MainOneSignalClassRunner.java +++ b/OneSignalSDK/unittest/src/test/java/com/test/onesignal/MainOneSignalClassRunner.java @@ -3236,6 +3236,108 @@ public void notificationReceived(OSNotification notification) { assertNull(ShadowFirebaseAnalytics.lastEventString); } + @Test + public void shouldSendExternalUserIdAfterRegistration() throws Exception { + OneSignalInit(); + threadAndTaskWait(); + + String testExternalId = "test_ext_id"; + + OneSignal.setExternalUserId(testExternalId); + + threadAndTaskWait(); + + assertEquals(3, ShadowOneSignalRestClient.networkCallCount); + + ShadowOneSignalRestClient.Request externalIdRequest = ShadowOneSignalRestClient.requests.get(2); + assertEquals(ShadowOneSignalRestClient.REST_METHOD.PUT, externalIdRequest.method); + assertEquals(testExternalId, externalIdRequest.payload.getString("external_user_id")); + } + + @Test + public void shouldSendExternalUserIdBeforeRegistration() throws Exception { + String testExternalId = "test_ext_id"; + + OneSignal.setExternalUserId(testExternalId); + + OneSignalInit(); + threadAndTaskWait(); + + assertEquals(2, ShadowOneSignalRestClient.networkCallCount); + + ShadowOneSignalRestClient.Request registrationRequest = ShadowOneSignalRestClient.requests.get(1); + assertEquals(ShadowOneSignalRestClient.REST_METHOD.POST, registrationRequest.method); + assertEquals(testExternalId, registrationRequest.payload.getString("external_user_id")); + } + + @Test + public void shouldRemoveExternalUserId() throws Exception { + OneSignal.setExternalUserId("test_ext_id"); + + OneSignalInit(); + threadAndTaskWait(); + + OneSignal.removeExternalUserId(); + threadAndTaskWait(); + + assertEquals(3, ShadowOneSignalRestClient.networkCallCount); + + ShadowOneSignalRestClient.Request removeIdRequest = ShadowOneSignalRestClient.requests.get(2); + assertEquals(ShadowOneSignalRestClient.REST_METHOD.PUT, removeIdRequest.method); + assertEquals(removeIdRequest.payload.getString("external_user_id"), ""); + } + + @Test + public void doesNotSendSameExternalId() throws Exception { + String testExternalId = "test_ext_id"; + + OneSignal.setExternalUserId(testExternalId); + + OneSignalInit(); + threadAndTaskWait(); + + assertEquals(2, ShadowOneSignalRestClient.networkCallCount); + + OneSignal.setExternalUserId(testExternalId); + threadAndTaskWait(); + + // Setting the same ID again should not generate a duplicate API request + // The SDK should detect it is the same and not generate a request + assertEquals(2, ShadowOneSignalRestClient.networkCallCount); + } + + @Test + public void sendsExternalIdOnEmailPlayers() throws Exception { + String testExternalId = "test_ext_id"; + + OneSignalInit(); + threadAndTaskWait(); + + OneSignal.setEmail("brad@onesignal.com"); + threadAndTaskWait(); + + int currentRequestCount = ShadowOneSignalRestClient.networkCallCount; + + OneSignal.setExternalUserId(testExternalId); + threadAndTaskWait(); + + // the SDK should have made two additional API calls + // One to set extID on the push player record, + // and another for the email player record + assertEquals(ShadowOneSignalRestClient.networkCallCount, currentRequestCount + 2); + + int externalIdRequests = 0; + + for (ShadowOneSignalRestClient.Request request : ShadowOneSignalRestClient.requests) { + if (request.payload != null && request.payload.has("external_user_id")) { + externalIdRequests += 1; + assertEquals(request.payload.getString("external_user_id"), testExternalId); + } + } + + assertEquals(externalIdRequests, 2); + } + // ####### Unit test helper methods ######## private static OSNotification createTestOSNotification() throws Exception {