Skip to content

Commit

Permalink
Merge pull request #1158 from OneSignal/feat/onesignalIdAndExternalId
Browse files Browse the repository at this point in the history
[Feat] Add User change event listener and getters for OneSignal Id and external Id
  • Loading branch information
shepherd-l authored Feb 27, 2024
2 parents 4d149d6 + cd3180e commit e35a9b4
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 21 deletions.
32 changes: 18 additions & 14 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,21 +154,25 @@ OneSignal.User.addAlias("my_alias", "1234");

All user functions are synchronous.

| Function Name | Description | Argument List |
| Property/Function | Description | Argument List |
| --------------- | ---------------------------------------------- | ------------------------------------ |
| `addAlias` | Adds a new alias for the current user. | `label: string, id: string` |
| `addAliases` | Adds multiple aliases for the current user. | `aliases: { [key: string]: string }` |
| `removeAlias` | Removes an alias for the current user. | `label: string` |
| `removeAliases` | Removes multiple aliases for the current user. | `labels: string[]` |
| `addEmail` | Adds an email address for the current user. | `email: string` |
| `removeEmail` | Removes an email address for the current user. | `email: string` |
| `addSms` | Adds an SMS number for the current user. | `smsNumber: string` |
| `removeSms` | Removes an SMS number for the current user. | `smsNumber: string` |
| `addTag` | Adds a tag for the current user. | `key: string, value: string` |
| `addTags` | Adds multiple tags for the current user. | `tags: { [key: string]: string }` |
| `removeTag` | Removes a tag for the current user. | `key: string` |
| `removeTags` | Removes multiple tags for the current user. | `keys: string[]` |
| `getTags` | Gets the current user's tags. | |
| `addAlias()` | Adds a new alias for the current user. | `label: string, id: string` |
| `addAliases()` | Adds multiple aliases for the current user. | `aliases: { [key: string]: string }` |
| `removeAlias()` | Removes an alias for the current user. | `label: string` |
| `removeAliases()` | Removes multiple aliases for the current user. | `labels: string[]` |
| `addEmail()` | Adds an email address for the current user. | `email: string` |
| `removeEmail()` | Removes an email address for the current user. | `email: string` |
| `addSms()` | Adds an SMS number for the current user. | `smsNumber: string` |
| `removeSms()` | Removes an SMS number for the current user. | `smsNumber: string` |
| `addTag()` | Adds a tag for the current user. | `key: string, value: string` |
| `addTags()` | Adds multiple tags for the current user. | `tags: { [key: string]: string }` |
| `removeTag()` | Removes a tag for the current user. | `key: string` |
| `removeTags()` | Removes multiple tags for the current user. | `keys: string[]` |
| `getTags()` | Returns the local tags on the current user. | |
| `onesignalId` | Returns the local OneSignal Id for the current user. | |
| `externalId` | Returns the local external Id for the current user. | |
| `addEventListener()` | Adds an event listener for the `change` event. | - `event` ("change")<br>- `listener` ((change: UserChangeEvent) => void) |
| `removeEventListener()` | Removes an event listener for the `change` event. | - `event` ("change")<br>- `listener` ((change: UserChangeEvent) => void) |

### Notifications Namespace

Expand Down
3 changes: 2 additions & 1 deletion __test__/support/helpers/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import {
DUMMY_GET_USER_REQUEST_WITH_PUSH_SUB,
} from '../constants';
import { RequestService } from '../../../src/core/requestService/RequestService';
import { WindowEnvironmentKind } from '../../../src/shared/models/WindowEnvironmentKind';

export function setupLoginStubs() {
test.stub(
RequestService,
'getUser',
Promise.resolve(DUMMY_GET_USER_REQUEST_WITH_PUSH_SUB),
);
test.stub(SdkEnvironment, 'getWindowEnv');
test.stub(SdkEnvironment, 'getWindowEnv', WindowEnvironmentKind.Host);
test.stub(
MainHelper,
'getCurrentPushToken',
Expand Down
44 changes: 44 additions & 0 deletions api.json
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,50 @@
"isAsync": false,
"args": [],
"returnType": "{ [key: string]: string }"
},
{
"name": "addEventListener",
"isAsync": false,
"args": [
{
"name": "event",
"type": "'change'",
"optional": false
},
{
"name": "listener",
"type": "(change: UserChangeEvent) => void",
"optional": false
}
],
"returnType": "void"
},
{
"name": "removeEventListener",
"isAsync": false,
"args": [
{
"name": "event",
"type": "'change'",
"optional": false
},
{
"name": "listener",
"type": "(change: UserChangeEvent) => void",
"optional": false
}
],
"returnType": "void"
}
],
"properties": [
{
"name": "onesignalId",
"type": "string | undefined"
},
{
"name": "externalId",
"type": "string | undefined"
}
],
"namespaces": ["PushSubscription"]
Expand Down
16 changes: 13 additions & 3 deletions express_webpack/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,22 @@
}
}
});
onesignal.User.PushSubscription.addEventListener('subscriptionChange', function (event) {
showEventAlert('subscriptionChange', { event });
});
});

/* E V E N T L I S T E N E R S */

OneSignalDeferred.push(function(onesignal) {
onesignal.User.PushSubscription.addEventListener('change', function (event) {
showEventAlert('change', { event });
});
});

OneSignalDeferred.push(function(onesignal) {
onesignal.User.addEventListener('change', function (event) {
showEventAlert('change', { event });
});
});

OneSignalDeferred.push(function(onesignal) {
onesignal.Notifications.addEventListener('permissionChange', function (isSubscribed) {
showEventAlert('permissionChange', { isSubscribed });
Expand Down
2 changes: 2 additions & 0 deletions src/core/CoreModuleDirector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import FuturePushSubscriptionRecord from '../page/userModel/FuturePushSubscripti
import User from '../onesignal/User';
import OneSignal from '../onesignal/OneSignal';
import Database from '../shared/services/Database';
import EventHelper from '../shared/helpers/EventHelper';

/* Contains OneSignal User-Model-specific logic*/

Expand Down Expand Up @@ -78,6 +79,7 @@ export class CoreModuleDirector {
user.subscriptions as SubscriptionModel[],
onesignalId,
);
EventHelper.checkAndTriggerUserChanged();
} catch (e) {
Log.error(`Error hydrating user: ${e}`);
}
Expand Down
32 changes: 31 additions & 1 deletion src/onesignal/UserNamespace.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import User from './User';
import PushSubscriptionNamespace from './PushSubscriptionNamespace';
import { Subscription } from '../shared/models/Subscription';
import { EventListenerBase } from '../page/userModel/EventListenerBase';
import UserChangeEvent from '../page/models/UserChangeEvent';
import Emitter from '../shared/libraries/Emitter';

export default class UserNamespace {
export default class UserNamespace extends EventListenerBase {
private _currentUser?: User;

readonly PushSubscription = new PushSubscriptionNamespace(false);

static emitter = new Emitter();

constructor(
initialize: boolean,
subscription?: Subscription,
permission?: NotificationPermission,
) {
super();
if (initialize) {
this._currentUser = User.createOrGetInstance();
this.PushSubscription = new PushSubscriptionNamespace(
Expand All @@ -23,6 +29,16 @@ export default class UserNamespace {
}

/* P U B L I C A P I */

get onesignalId(): string | undefined {
return this._currentUser?.onesignalId;
}

get externalId(): string | undefined {
const identityModel = OneSignal.coreDirector.getIdentityModel();
return identityModel?.data?.external_id;
}

public addAlias(label: string, id: string): void {
this._currentUser?.addAlias(label, id);
}
Expand Down Expand Up @@ -74,4 +90,18 @@ export default class UserNamespace {
public getTags(): { [key: string]: string } {
return this._currentUser?.getTags() || {};
}

addEventListener(
event: 'change',
listener: (userChange: UserChangeEvent) => void,
): void {
UserNamespace.emitter.on(event, listener);
}

removeEventListener(
event: 'change',
listener: (userChange: UserChangeEvent) => void,
): void {
UserNamespace.emitter.on(event, listener);
}
}
10 changes: 10 additions & 0 deletions src/page/models/UserChangeEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type UserNamespaceProperties = {
onesignalId: string | undefined;
externalId: string | undefined;
};

type UserChangeEvent = {
current: UserNamespaceProperties;
};

export default UserChangeEvent;
41 changes: 41 additions & 0 deletions src/shared/helpers/EventHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import OneSignalUtils from '../utils/OneSignalUtils';
import PromptsHelper from './PromptsHelper';
import OneSignalEvent from '../services/OneSignalEvent';
import SubscriptionChangeEvent from '../../page/models/SubscriptionChangeEvent';
import UserChangeEvent from '../../page/models/UserChangeEvent';
import MainHelper from './MainHelper';
import {
NotificationClickEvent,
NotificationClickEventInternal,
} from '../models/NotificationEvent';
import { awaitOneSignalInitAndSupported } from '../utils/utils';
import UserNamespace from '../../onesignal/UserNamespace';

export default class EventHelper {
static onNotificationPermissionChange() {
Expand Down Expand Up @@ -224,6 +226,14 @@ export default class EventHelper {
OneSignalEvent.trigger(OneSignal.EVENTS.SUBSCRIPTION_CHANGED, change);
}

static triggerUserChanged(change: UserChangeEvent) {
OneSignalEvent.trigger(
OneSignal.EVENTS.SUBSCRIPTION_CHANGED,
change,
UserNamespace.emitter,
);
}

static triggerNotificationClick(
event: NotificationClickEventInternal,
): Promise<void> {
Expand Down Expand Up @@ -315,4 +325,35 @@ export default class EventHelper {
}
}
}

static async checkAndTriggerUserChanged() {
OneSignalUtils.logMethodCall('checkAndTriggerUserChanged');

const userState = await Database.getUserState();
const { previousOneSignalId, previousExternalId } = userState;

const identityModel = await OneSignal.coreDirector.getIdentityModel();
const currentOneSignalId = identityModel?.onesignalId;
const currentExternalId = identityModel?.data?.external_id;

const didStateChange =
currentOneSignalId !== previousOneSignalId ||
currentExternalId !== previousExternalId;
if (!didStateChange) {
return;
}

userState.previousOneSignalId = currentOneSignalId;
userState.previousExternalId = currentExternalId;
await Database.setUserState(userState);

const change: UserChangeEvent = {
current: {
onesignalId: currentOneSignalId,
externalId: currentExternalId,
},
};
Log.info('User state changed: ', change);
EventHelper.triggerUserChanged(change);
}
}
6 changes: 6 additions & 0 deletions src/shared/models/UserState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class UserState {
previousOneSignalId: string | undefined;
previousExternalId: string | undefined;
}

export { UserState };
37 changes: 37 additions & 0 deletions src/shared/services/Database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import IndexedDb from './IndexedDb';

import { AppConfig } from '../models/AppConfig';
import { AppState, PendingNotificationClickEvents } from '../models/AppState';
import { UserState } from '../models/UserState';
import { IOSNotification } from '../models/OSNotification';
import {
OutcomesNotificationClicked,
Expand Down Expand Up @@ -246,6 +247,34 @@ export default class Database {
}
}

async getUserState(): Promise<UserState> {
const userState = new UserState();
userState.previousOneSignalId = '';
userState.previousExternalId = '';
// previous<OneSignalId|ExternalId> are used to track changes to the user's state.
// Displayed in the `current` & `previous` fields of the `userChange` event.
userState.previousOneSignalId = await this.get<string>(
'Options',
'previousOneSignalId',
);
userState.previousExternalId = await this.get<string>(
'Options',
'previousExternalId',
);
return userState;
}

async setUserState(userState: UserState) {
await this.put('Options', {
key: 'previousOneSignalId',
value: userState.previousOneSignalId,
});
await this.put('Options', {
key: 'previousExternalId',
value: userState.previousExternalId,
});
}

async getSubscription(): Promise<Subscription> {
const subscription = new Subscription();
subscription.deviceId = await this.get<string>('Ids', 'userId');
Expand Down Expand Up @@ -524,6 +553,14 @@ export default class Database {
return await Database.singletonInstance.getAppState();
}

static async setUserState(userState: UserState) {
return await Database.singletonInstance.setUserState(userState);
}

static async getUserState(): Promise<UserState> {
return await Database.singletonInstance.getUserState();
}

static async setAppConfig(appConfig: AppConfig) {
return await Database.singletonInstance.setAppConfig(appConfig);
}
Expand Down
10 changes: 8 additions & 2 deletions src/shared/services/OneSignalEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Environment from '../helpers/Environment';
import SdkEnvironment from '../managers/SdkEnvironment';
import Utils from '../context/Utils';
import Log from '../libraries/Log';
import Emitter from '../libraries/Emitter';

const SILENT_EVENTS = [
'notifyButtonHovering',
Expand All @@ -23,8 +24,9 @@ export default class OneSignalEvent {
* Triggers an internal event with optional custom data.
* @param eventName The string event name to be emitted.
* @param data Any JavaScript variable to be passed with the event.
* @param emitter Emitter to emit the event from.
*/
static async trigger(eventName: string, data?: any) {
static async trigger(eventName: string, data?: any, emitter?: Emitter) {
if (!Utils.contains(SILENT_EVENTS, eventName)) {
const displayData = data;
let env = Utils.capitalize(SdkEnvironment.getWindowEnv().toString());
Expand All @@ -42,7 +44,11 @@ export default class OneSignalEvent {
if (OneSignal.initialized) return;
else OneSignal.initialized = true;
}
await OneSignal.emitter.emit(eventName, data);
if (emitter) {
await emitter.emit(eventName, data);
} else {
await OneSignal.emitter.emit(eventName, data);
}
}
}
}

0 comments on commit e35a9b4

Please sign in to comment.