Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] Add User change event listener and getters for OneSignal Id and external Id #1158

Merged
merged 8 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 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,11 +24,12 @@
* 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());

Check failure on line 32 in src/shared/services/OneSignalEvent.ts

View workflow job for this annotation

GitHub Actions / test

'env' is never reassigned. Use 'const' instead

if (displayData || displayData === false) {
Log.debug(`(${env}) » ${eventName}:`, displayData);
Expand All @@ -42,7 +44,11 @@
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);
}
}
}
}
Loading