diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md
index cecd41b5..41136a4b 100644
--- a/MIGRATION_GUIDE.md
+++ b/MIGRATION_GUIDE.md
@@ -212,6 +212,22 @@ The User namespace is accessible via `OneSignal.User` and provides access to use
| `OneSignal.User.removeTag("KEY")` | _Remove the data tag with the provided key from the current user._ |
| `OneSignal.User.removeTags(["KEY_01", "KEY_02"])` | _Remove multiple tags with the provided keys from the current user._ |
| `OneSignal.User.getTags()` | _Returns the local tags for the current user._|
+| `OneSignal.User.addEventListener("change", (event: UserChangedState) => void)`
**_See below for usage_** | _Add a User State callback which contains the nullable onesignalId and externalId. The listener will be fired when these values change._|
+| `await OneSignal.User.getOnesignalId()` | _Returns the OneSignal ID for the current user, which can be null if it is not yet available._|
+| `await OneSignal.User.getExternalId()` | _Returns the External ID for the current user, which can be null if not set._|
+
+### User State Listener
+
+```typescript
+ const listener = (event: UserChangedState) => {
+ console.log("User changed: " + (event));
+ };
+
+ OneSignal.User.addEventListener("change", listener);
+ // Remove the listener
+ OneSignal.User.removeEventListener("change", listener);
+```
+
## Push Subscription Namespace
The Push Subscription namespace is accessible via `OneSignal.User.pushSubscription` and provides access to push subscription-scoped functionality.
diff --git a/android/src/main/java/com/onesignal/rnonesignalandroid/RNOneSignal.java b/android/src/main/java/com/onesignal/rnonesignalandroid/RNOneSignal.java
index debabf3e..5c9606f1 100644
--- a/android/src/main/java/com/onesignal/rnonesignalandroid/RNOneSignal.java
+++ b/android/src/main/java/com/onesignal/rnonesignalandroid/RNOneSignal.java
@@ -74,6 +74,9 @@ of this software and associated documentation files (the "Software"), to deal
import com.onesignal.user.subscriptions.IPushSubscriptionObserver;
import com.onesignal.user.subscriptions.PushSubscriptionState;
import com.onesignal.user.subscriptions.PushSubscriptionChangedState;
+import com.onesignal.user.state.UserState;
+import com.onesignal.user.state.UserChangedState;
+import com.onesignal.user.state.IUserStateObserver;
import org.json.JSONException;
import java.util.HashMap;
@@ -82,6 +85,7 @@ of this software and associated documentation files (the "Software"), to deal
public class RNOneSignal extends ReactContextBaseJavaModule implements
IPushSubscriptionObserver,
IPermissionObserver,
+ IUserStateObserver,
LifecycleEventListener,
INotificationLifecycleListener{
private ReactApplicationContext mReactApplicationContext;
@@ -90,6 +94,7 @@ public class RNOneSignal extends ReactContextBaseJavaModule implements
private boolean oneSignalInitDone;
private boolean hasSetPermissionObserver = false;
private boolean hasSetPushSubscriptionObserver = false;
+ private boolean hasSetUserStateObserver = false;
private HashMap notificationWillDisplayCache;
private HashMap preventDefaultCache;
@@ -162,6 +167,7 @@ public void onClick(INotificationClickEvent event) {
private void removeObservers() {
this.removePermissionObserver();
this.removePushSubscriptionObserver();
+ this.removeUserStateObserver();
}
private void removeHandlers() {
@@ -676,6 +682,53 @@ public void removeAliases(ReadableArray aliasLabels) {
OneSignal.getUser().removeAliases(RNUtils.convertReadableArrayIntoStringCollection(aliasLabels));
}
+ @ReactMethod
+ public void getOnesignalId(Promise promise) {
+ String onesignalId = OneSignal.getUser().getOnesignalId();
+ if (onesignalId.isEmpty()) {
+ onesignalId = null;
+ }
+ promise.resolve(onesignalId);
+
+ }
+
+ @ReactMethod
+ public void getExternalId(Promise promise) {
+ String externalId = OneSignal.getUser().getExternalId();
+ if (externalId.isEmpty()) {
+ externalId = null;
+ }
+ promise.resolve(externalId);
+ }
+
+ @ReactMethod
+ public void addUserStateObserver() {
+ if (!hasSetUserStateObserver) {
+ OneSignal.getUser().addObserver(this);
+ hasSetUserStateObserver = true;
+ }
+ }
+
+ @Override
+ public void onUserStateChange(UserChangedState state) {
+ try {
+ sendEvent("OneSignal-userStateChanged",
+ RNUtils.convertHashMapToWritableMap(
+ RNUtils.convertUserChangedStateToMap(state)));
+ Log.i("OneSignal", "sending user state change event");
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @ReactMethod
+ public void removeUserStateObserver() {
+ if (hasSetUserStateObserver) {
+ OneSignal.getUser().removeObserver(this);
+ hasSetUserStateObserver = false;
+ }
+ }
+
/** Added for NativeEventEmitter */
@ReactMethod
public void addListener(String eventName) {
diff --git a/android/src/main/java/com/onesignal/rnonesignalandroid/RNUtils.java b/android/src/main/java/com/onesignal/rnonesignalandroid/RNUtils.java
index a9b034a9..e9989b76 100644
--- a/android/src/main/java/com/onesignal/rnonesignalandroid/RNUtils.java
+++ b/android/src/main/java/com/onesignal/rnonesignalandroid/RNUtils.java
@@ -27,6 +27,8 @@
import com.onesignal.user.subscriptions.IPushSubscription;
import com.onesignal.user.subscriptions.PushSubscriptionState;
import com.onesignal.user.subscriptions.PushSubscriptionChangedState;
+import com.onesignal.user.state.UserState;
+import com.onesignal.user.state.UserChangedState;
import org.json.JSONArray;
import org.json.JSONException;
@@ -197,6 +199,25 @@ public static HashMap convertPushSubscriptionStateToMap(PushSubs
return hash;
}
+ public static HashMap convertUserStateToMap(UserState user) {
+ HashMap hash = new HashMap<>();
+
+ if (user.getExternalId() != null && !user.getExternalId().isEmpty()) {
+ hash.put("externalId", user.getExternalId());
+ }
+ else {
+ hash.put("externalId", JSONObject.NULL);
+ }
+ if (user.getOnesignalId() != null && !user.getOnesignalId().isEmpty()) {
+ hash.put("onesignalId", user.getOnesignalId());
+ }
+ else {
+ hash.put("onesignalId", JSONObject.NULL);
+ }
+
+ return hash;
+ }
+
public static HashMap convertPushSubscriptionChangedStateToMap(PushSubscriptionChangedState state) {
HashMap hash = new HashMap<>();
hash.put("current", convertPushSubscriptionStateToMap(state.getCurrent()));
@@ -205,6 +226,13 @@ public static HashMap convertPushSubscriptionChangedStateToMap(P
return hash;
}
+ public static HashMap convertUserChangedStateToMap(UserChangedState state) {
+ HashMap hash = new HashMap<>();
+ hash.put("current", convertUserStateToMap(state.getCurrent()));
+
+ return hash;
+ }
+
public static HashMap convertJSONObjectToHashMap(JSONObject object) throws JSONException {
HashMap hash = new HashMap<>();
diff --git a/examples/RNOneSignalTS/src/OSButtons.tsx b/examples/RNOneSignalTS/src/OSButtons.tsx
index ce3ebfc4..5d6d3b48 100644
--- a/examples/RNOneSignalTS/src/OSButtons.tsx
+++ b/examples/RNOneSignalTS/src/OSButtons.tsx
@@ -277,8 +277,8 @@ class OSButtons extends React.Component {
});
const getTagsButton = renderButtonView('Get tags', async () => {
- const tags = await OneSignal.User.getTags();
- loggingFunction('Tags:', tags);
+ const tags = await OneSignal.User.getTags();
+ loggingFunction('Tags:', tags);
});
const setLanguageButton = renderButtonView('Set Language', () => {
@@ -342,6 +342,22 @@ class OSButtons extends React.Component {
},
);
+ const getOnesignalIdButton = renderButtonView(
+ 'Get OneSignal Id',
+ async () => {
+ const onesignalId = await OneSignal.User.getOnesignalId();
+ loggingFunction('OneSignal Id: ', onesignalId);
+ },
+ );
+
+ const getExternalIdButton = renderButtonView(
+ 'Get External Id',
+ async () => {
+ const externalId = await OneSignal.User.getExternalId();
+ loggingFunction('External Id:', externalId);
+ },
+ );
+
return [
loginButton,
logoutButton,
@@ -359,6 +375,8 @@ class OSButtons extends React.Component {
removeAliasButton,
addAliasesButton,
removeAliasesButton,
+ getOnesignalIdButton,
+ getExternalIdButton,
];
}
diff --git a/examples/RNOneSignalTS/src/OSDemo.tsx b/examples/RNOneSignalTS/src/OSDemo.tsx
index 825e3ab0..94f2dbf1 100644
--- a/examples/RNOneSignalTS/src/OSDemo.tsx
+++ b/examples/RNOneSignalTS/src/OSDemo.tsx
@@ -99,6 +99,10 @@ class OSDemo extends React.Component {
OneSignal.Notifications.addEventListener('permissionChange', (granted) => {
this.OSLog('OneSignal: permission changed:', granted);
});
+
+ OneSignal.User.addEventListener('change', (event) => {
+ this.OSLog('OneSignal: user changed: ', event);
+ });
}
OSLog = (message: string, optionalArg: any = null) => {
diff --git a/ios/RCTOneSignal/RCTOneSignal.h b/ios/RCTOneSignal/RCTOneSignal.h
index ccf3336e..be93f668 100644
--- a/ios/RCTOneSignal/RCTOneSignal.h
+++ b/ios/RCTOneSignal/RCTOneSignal.h
@@ -5,8 +5,7 @@
#import "../OneSignalFramework.h"
#endif
-@interface RCTOneSignal : NSObject
-
+@interface RCTOneSignal : NSObject
+ (RCTOneSignal *) sharedInstance;
@end
diff --git a/ios/RCTOneSignal/RCTOneSignal.m b/ios/RCTOneSignal/RCTOneSignal.m
index 85118514..eb451540 100644
--- a/ios/RCTOneSignal/RCTOneSignal.m
+++ b/ios/RCTOneSignal/RCTOneSignal.m
@@ -77,6 +77,31 @@ - (void)sendEvent:(NSString *)eventName withBody:(NSDictionary *)body {
[RCTOneSignalEventEmitter sendEventWithName:eventName withBody:body];
}
+- (void)onUserStateDidChangeWithState:(OSUserChangedState * _Nonnull)state {
+ NSString *onesignalId = state.current.onesignalId;
+ NSString *externalId = state.current.externalId;
+
+ NSMutableDictionary *currentDictionary = [NSMutableDictionary dictionary];
+
+ if (onesignalId.length > 0) {
+ [currentDictionary setObject:onesignalId forKey:@"onesignalId"];
+ }
+ else {
+ [currentDictionary setObject:[NSNull null] forKey:@"onesignalId"];
+ }
+
+ if (externalId.length > 0) {
+ [currentDictionary setObject:externalId forKey:@"externalId"];
+ }
+ else {
+ [currentDictionary setObject:[NSNull null] forKey:@"externalId"];
+ }
+
+ NSDictionary *result = @{@"current": currentDictionary};
+
+ [self sendEvent:OSEventString(UserStateChanged) withBody:result];
+}
+
- (void)onPushSubscriptionDidChangeWithState:(OSPushSubscriptionChangedState * _Nonnull)state {
NSMutableDictionary *result = [NSMutableDictionary new];
diff --git a/ios/RCTOneSignal/RCTOneSignalEventEmitter.h b/ios/RCTOneSignal/RCTOneSignalEventEmitter.h
index 458446e8..ca153320 100644
--- a/ios/RCTOneSignal/RCTOneSignalEventEmitter.h
+++ b/ios/RCTOneSignal/RCTOneSignalEventEmitter.h
@@ -15,6 +15,7 @@
typedef NS_ENUM(NSInteger, OSNotificationEventTypes) {
PermissionChanged,
SubscriptionChanged,
+ UserStateChanged,
NotificationWillDisplayInForeground,
NotificationClicked,
InAppMessageClicked,
@@ -24,7 +25,7 @@ typedef NS_ENUM(NSInteger, OSNotificationEventTypes) {
InAppMessageDidDismiss,
};
-#define OSNotificationEventTypesArray @[@"OneSignal-permissionChanged",@"OneSignal-subscriptionChanged",@"OneSignal-notificationWillDisplayInForeground",@"OneSignal-notificationClicked",@"OneSignal-inAppMessageClicked", @"OneSignal-inAppMessageWillDisplay", @"OneSignal-inAppMessageDidDisplay", @"OneSignal-inAppMessageWillDismiss", @"OneSignal-inAppMessageDidDismiss"]
+#define OSNotificationEventTypesArray @[@"OneSignal-permissionChanged",@"OneSignal-subscriptionChanged",@"OneSignal-userStateChanged",@"OneSignal-notificationWillDisplayInForeground",@"OneSignal-notificationClicked",@"OneSignal-inAppMessageClicked", @"OneSignal-inAppMessageWillDisplay", @"OneSignal-inAppMessageDidDisplay", @"OneSignal-inAppMessageWillDismiss", @"OneSignal-inAppMessageDidDismiss"]
#define OSEventString(enum) [OSNotificationEventTypesArray objectAtIndex:enum]
diff --git a/ios/RCTOneSignal/RCTOneSignalEventEmitter.m b/ios/RCTOneSignal/RCTOneSignalEventEmitter.m
index 0d72549c..03b15994 100644
--- a/ios/RCTOneSignal/RCTOneSignalEventEmitter.m
+++ b/ios/RCTOneSignal/RCTOneSignalEventEmitter.m
@@ -9,6 +9,7 @@ @implementation RCTOneSignalEventEmitter {
BOOL _hasListeners;
BOOL _hasSetSubscriptionObserver;
BOOL _hasSetPermissionObserver;
+ BOOL _hasSetUserStateObserver;
BOOL _hasAddedNotificationClickListener;
BOOL _hasAddedNotificationForegroundLifecycleListener;
BOOL _hasAddedInAppMessageClickListener;
@@ -310,6 +311,13 @@ + (void)sendEventWithName:(NSString *)name withBody:(NSDictionary *)body {
}
// OneSignal.User namespace methods
+RCT_EXPORT_METHOD(addUserStateObserver) {
+ if (!_hasSetUserStateObserver) {
+ [OneSignal.User addObserver:[RCTOneSignal sharedInstance]];
+ _hasSetUserStateObserver = true;
+ }
+}
+
RCT_EXPORT_METHOD(addPushSubscriptionObserver) {
if (!_hasSetSubscriptionObserver) {
[OneSignal.User.pushSubscription addObserver:[RCTOneSignal sharedInstance]];
@@ -365,6 +373,30 @@ + (void)sendEventWithName:(NSString *)name withBody:(NSDictionary *)body {
resolve(tags);
}
+RCT_REMAP_METHOD(getOnesignalId,
+ getOnesignalIdResolver:(RCTPromiseResolveBlock)resolve
+ rejecter:(RCTPromiseRejectBlock)reject) {
+ NSString *onesignalId = OneSignal.User.onesignalId;
+
+ if (onesignalId == nil || [onesignalId length] == 0) {
+ resolve([NSNull null]); // Resolve with null if nil or empty
+ } else {
+ resolve(onesignalId);
+ }
+}
+
+RCT_REMAP_METHOD(getExternalId,
+ getExternalIdResolver:(RCTPromiseResolveBlock)resolve
+ rejecter:(RCTPromiseRejectBlock)reject) {
+ NSString *externalId = OneSignal.User.externalId;
+
+ if (externalId == nil || [externalId length] == 0) {
+ resolve([NSNull null]); // Resolve with null if nil or empty
+ } else {
+ resolve(externalId);
+ }
+}
+
RCT_EXPORT_METHOD(addAlias:(NSString *)label :(NSString *)id) {
[OneSignal.User addAliasWithLabel:label id:id];
}
diff --git a/src/events/EventManager.ts b/src/events/EventManager.ts
index 2c4eeaa1..fd81128a 100644
--- a/src/events/EventManager.ts
+++ b/src/events/EventManager.ts
@@ -7,6 +7,7 @@ import NotificationWillDisplayEvent from './NotificationWillDisplayEvent';
import {
PERMISSION_CHANGED,
SUBSCRIPTION_CHANGED,
+ USER_STATE_CHANGED,
NOTIFICATION_WILL_DISPLAY,
NOTIFICATION_CLICKED,
IN_APP_MESSAGE_CLICKED,
@@ -20,6 +21,7 @@ import OSNotification from '../OSNotification';
const eventList = [
PERMISSION_CHANGED,
SUBSCRIPTION_CHANGED,
+ USER_STATE_CHANGED,
NOTIFICATION_WILL_DISPLAY,
NOTIFICATION_CLICKED,
IN_APP_MESSAGE_CLICKED,
diff --git a/src/events/events.ts b/src/events/events.ts
index 3ac0c57d..d2e6541e 100644
--- a/src/events/events.ts
+++ b/src/events/events.ts
@@ -8,3 +8,4 @@ export const IN_APP_MESSAGE_WILL_DISMISS = 'OneSignal-inAppMessageWillDismiss';
export const IN_APP_MESSAGE_DID_DISMISS = 'OneSignal-inAppMessageDidDismiss';
export const PERMISSION_CHANGED = 'OneSignal-permissionChanged';
export const SUBSCRIPTION_CHANGED = 'OneSignal-subscriptionChanged';
+export const USER_STATE_CHANGED = 'OneSignal-userStateChanged';
diff --git a/src/index.ts b/src/index.ts
index 1958466f..3979bd05 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -12,6 +12,7 @@ import {
NOTIFICATION_WILL_DISPLAY,
PERMISSION_CHANGED,
SUBSCRIPTION_CHANGED,
+ USER_STATE_CHANGED,
} from './events/events';
import {
NotificationEventName,
@@ -23,6 +24,7 @@ import {
OSNotificationPermission,
PushSubscriptionChangedState,
} from './models/Subscription';
+import { UserState, UserChangedState } from './models/User';
import NotificationWillDisplayEvent from './events/NotificationWillDisplayEvent';
import {
InAppMessage,
@@ -310,6 +312,51 @@ export namespace OneSignal {
RNOneSignal.optIn();
}
}
+
+ /**
+ * Add a callback that fires when the OneSignal user state changes.
+ * Important: When using the observer to retrieve the onesignalId, check the externalId as well to confirm the values are associated with the expected user.
+ */
+ export function addEventListener(
+ event: 'change',
+ listener: (event: UserChangedState) => void,
+ ) {
+ if (!isNativeModuleLoaded(RNOneSignal)) return;
+
+ isValidCallback(listener);
+ RNOneSignal.addUserStateObserver();
+ eventManager.addEventListener(
+ USER_STATE_CHANGED,
+ listener,
+ );
+ }
+
+ /** Clears current user state observers. */
+ export function removeEventListener(
+ event: 'change',
+ listener: (event: UserChangedState) => void,
+ ) {
+ if (!isNativeModuleLoaded(RNOneSignal)) return;
+
+ eventManager.removeEventListener(USER_STATE_CHANGED, listener);
+ }
+
+ /** Get the nullable OneSignal Id associated with the user. */
+ export async function getOnesignalId(): Promise {
+ if (!isNativeModuleLoaded(RNOneSignal)) {
+ return Promise.reject(new Error('OneSignal native module not loaded'));
+ }
+ return RNOneSignal.getOnesignalId();
+ }
+
+ /** Get the nullable External Id associated with the user. */
+ export async function getExternalId(): Promise {
+ if (!isNativeModuleLoaded(RNOneSignal)) {
+ return Promise.reject(new Error('OneSignal native module not loaded'));
+ }
+ return RNOneSignal.getExternalId();
+ }
+
/** Explicitly set a 2-character language code for the user. */
export function setLanguage(language: string) {
if (!isNativeModuleLoaded(RNOneSignal)) return;
@@ -852,6 +899,8 @@ export {
InAppMessageDidDismissEvent,
PushSubscriptionState,
PushSubscriptionChangedState,
+ UserState,
+ UserChangedState,
OSNotificationPermission,
};
diff --git a/src/models/User.ts b/src/models/User.ts
new file mode 100644
index 00000000..465121f2
--- /dev/null
+++ b/src/models/User.ts
@@ -0,0 +1,8 @@
+export interface UserState {
+ externalId?: string;
+ onesignalId?: string;
+}
+
+export interface UserChangedState {
+ current: UserState;
+}