Skip to content

Add Custom Events#1336

Merged
fadi-george merged 7 commits intomainfrom
fg/custom-events
Jul 31, 2025
Merged

Add Custom Events#1336
fadi-george merged 7 commits intomainfrom
fg/custom-events

Conversation

@fadi-george
Copy link
Copy Markdown
Contributor

@fadi-george fadi-george commented Jul 23, 2025

Description

1 Line Summary

Add custom events logic for the web sdk.
Reference docs:

Details

  • Adds a track event operation and event operation executor logic similar to Android
  • Updates core director to have both custom event controller and executor
  • Moves env helpers like getDeviceOS and getDeviceModel to Environment class
  • Adds sendCustomEvent request service helper
  • Updates device_os types to just be a string/undefined
  • Updates resetUserModels to update operation local onesignal ids to new local onesignal ids if valid. This is for situations we call trackEvent before login where trackEvent sets a localid but login resets the ids again so we need to update the previous ids. Since we only check for local id, if user was already create then trackEvent; login; for an existing user should use different ids.
  • Updates login call to not use currentOneSignalId if its local since login op wont execute for cases we call trackEvent then login.

Systems Affected

  • WebSDK
  • Backend
  • Dashboard

Screenshots

Screenshot 2025-07-23 at 4 03 17 PM

Validation

Tests

Add tests cases for UserNamespace as well OneSignal.test cases for calling track.

Can test manually by setting app id to Mikes Test App. And setting port to 4001 for vite.config.

Info

Checklist

  • All the automated tests pass or I explained why that is not possible
  • I have personally tested this on my machine or explained why that is not possible
  • I have included test coverage for these changes or explained why they are not needed

Programming Checklist
Interfaces:

  • Don't use default export
  • New interfaces are in model files

Functions:

  • Don't use default export
  • All function signatures have return types
  • Helpers should not access any data but rather be given the data to operate on.

Typescript:

  • No Typescript warnings
  • Avoid silencing null/undefined warnings with the exclamation point

Other:

  • Iteration: refrain from using elem of array syntax. Prefer forEach or use map
  • Avoid using global OneSignal accessor for context if possible. Instead, we can pass it to function/constructor so that we don't call OneSignal.context

Info

Checklist

  • I have included screenshots/recordings of the intended results or explained why they are not needed

Related Tickets



This change is Reviewable

@fadi-george fadi-george force-pushed the fg/custom-events branch 2 times, most recently from 56e3bb5 to b3ff042 Compare July 23, 2025 23:08
@fadi-george fadi-george requested a review from nan-li July 23, 2025 23:48
Comment on lines +109 to +111
model.sdk = sub.sdk;
model.device_os = sub.device_os;
model.device_model = sub.device_model;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this mean if any of these values are missing, they will get saved/sent as undefined?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refresh call should have the sdk, device_os and device_module information

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also updated device_os to be a string.

get timestamp(): string {
return this.getProperty('timestamp');
}
private set timestamp(value: string) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this default to now()?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We set a custom iso string from the caller

Comment on lines -244 to -277
/**
* Lists all aliases for the user identified by the given subscription id
* @param requestMetadata - { appId }
* @param subscriptionId - subscription id
*/
static async getIdentityFromSubscription(
requestMetadata: RequestMetadata,
subscriptionId: string,
) {
const { appId } = requestMetadata;
return OneSignalApiBase.get<{ identity: IUserIdentity }>(
`apps/${appId}/subscriptions/${subscriptionId}/user/identity`,
);
}

/**
* Upserts one or more aliases for the user identified by the given subscription id
* @param requestMetadata - { appId }
* @param subscriptionId - subscription id
* @param identity - identity label & id
*/
static async identifyUserForSubscription(
requestMetadata: RequestMetadata,
subscriptionId: string,
identity: IdentityModel,
): Promise<OneSignalApiBaseResponse> {
const { appId } = requestMetadata;
return OneSignalApiBase.patch<{ identity: IUserIdentity }>(
`apps/${appId}/users/by/subscriptions/${subscriptionId}/identity`,
{ identity },
requestMetadata.jwtHeader,
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why was this removed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused

Comment on lines +75 to +115
export const getSubscriptionType = (): SubscriptionTypeValue => {
const browser = OneSignalUtils.redetectBrowserUserAgent();
if (browser.firefox) {
return SubscriptionType.FirefoxPush;
}
if (useSafariVapidPush()) {
return SubscriptionType.SafariPush;
}
if (useSafariLegacyPush) {
return SubscriptionType.SafariLegacyPush;
}
// Other browsers, like Edge, are Chromium based so we consider them "Chrome".
return SubscriptionType.ChromePush;
};

/**
* Get the legacy player.device_type
* NOTE: Use getSubscriptionType() instead when possible.
*/
export function getDeviceType(): DeliveryPlatformKindValue {
switch (getSubscriptionType()) {
case SubscriptionType.FirefoxPush:
return DeliveryPlatformKind.Firefox;
case SubscriptionType.SafariLegacyPush:
return DeliveryPlatformKind.SafariLegacy;
case SubscriptionType.SafariPush:
return DeliveryPlatformKind.SafariVapid;
}
return DeliveryPlatformKind.ChromeLike;
}

export function getDeviceOS(): string {
const environment = EnvironmentInfoHelper.getEnvironmentInfo();
return String(
isNaN(environment.browserVersion) ? -1 : environment.browserVersion,
);
}

export function getDeviceModel(): string {
return navigator.platform;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow: why was this moved from FuturePushSubscriptionRecord?

Copy link
Copy Markdown
Contributor Author

@fadi-george fadi-george Jul 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because its used in multiple places like custom events and subscription op

@fadi-george fadi-george requested a review from sherwinski July 30, 2025 18:18
flesh out the track event operation and create custom logic

add tests for custom events

add subscription type for custom events
@sherwinski
Copy link
Copy Markdown
Contributor

sherwinski commented Jul 30, 2025

If I enqueue a trackEvent call and then refresh the page (before the operation gets executed) I get the following error:

ReplayCallsOnOneSignal.ts:15 TypeError: Cannot read properties of undefined (reading 'appId')
    at new TrackEventOperation (TrackEventOperation.ts:29:46)
    at OperationModelStore.create (OperationModelStore.ts:71:21)
    at OperationModelStore.load (ModelStore.ts:169:29)
    at async OperationRepo.loadSavedOperations (OperationRepo.ts:361:11)
    at async OperationRepo.start (OperationRepo.ts:94:5)
    at async OneSignal2._initializeCoreModuleAndOSNamespaces (OneSignal.ts:42:5)
    at async OneSignal2.init (OneSignal.ts:145:5)
    at async ?app_id=77e32082-ea27-42e3-a898-c72e141824ef:40:7
    at async ReplayCallsOnOneSignal.processOneSignalDeferredArray (ReplayCallsOnOneSignal.ts:12:15)

@sherwinski
Copy link
Copy Markdown
Contributor

Sending a custom event without a onesignalId or externalId results in a 403. In this case it should await the onesignalId like addTag.

@fadi-george fadi-george force-pushed the fg/custom-events branch 4 times, most recently from 09132b1 to 9d1cf40 Compare July 31, 2025 00:58
@fadi-george
Copy link
Copy Markdown
Contributor Author

If I enqueue a trackEvent call and then refresh the page (before the operation gets executed) I get the following error:

ReplayCallsOnOneSignal.ts:15 TypeError: Cannot read properties of undefined (reading 'appId')
    at new TrackEventOperation (TrackEventOperation.ts:29:46)
    at OperationModelStore.create (OperationModelStore.ts:71:21)
    at OperationModelStore.load (ModelStore.ts:169:29)
    at async OperationRepo.loadSavedOperations (OperationRepo.ts:361:11)
    at async OperationRepo.start (OperationRepo.ts:94:5)
    at async OneSignal2._initializeCoreModuleAndOSNamespaces (OneSignal.ts:42:5)
    at async OneSignal2.init (OneSignal.ts:145:5)
    at async ?app_id=77e32082-ea27-42e3-a898-c72e141824ef:40:7
    at async ReplayCallsOnOneSignal.processOneSignalDeferredArray (ReplayCallsOnOneSignal.ts:12:15)

Should be fixed now

@fadi-george
Copy link
Copy Markdown
Contributor Author

Sending a custom event without a onesignalId or externalId results in a 403. In this case it should await the onesignalId like addTag.

Try it now

Comment thread src/core/executors/LoginUserOperationExecutor.ts Outdated
Comment thread src/core/operations/TrackEventOperation.ts Outdated
Comment thread src/onesignal/User.ts Outdated
Comment thread src/onesignal/UserDirector.ts Outdated
Comment thread vite.config.ts Outdated
set local id for identity if it doesnt exist, allow to execut after login is called
@fadi-george fadi-george force-pushed the fg/custom-events branch 2 times, most recently from 3ef0768 to f5930c6 Compare July 31, 2025 17:38
Comment on lines +69 to +73
override get createComparisonKey(): string {
return this.key;
}
override get modifyComparisonKey(): string {
return this.key;
Copy link
Copy Markdown

@nan-li nan-li Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createComparisonKey and modifyComparisonKey should both be nothing (aka empty string). Having this set to a value can be misleading or confusing down the line if someone was trying to figure out how we were using these keys to batch. It is effectively no different than using a random UUID.

If we do batch in the future, it'll probably be more like ${this.appId}.User.${this.onesignalId}.CustomEvent dropping the name identifier and batch per user.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In our meeting, we said this is fine for now.

add more test cases
@fadi-george fadi-george merged commit 467f7b4 into main Jul 31, 2025
4 checks passed
@fadi-george fadi-george deleted the fg/custom-events branch July 31, 2025 22:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants