Skip to content

Commit fa20772

Browse files
committed
Add custom events feature
1 parent dc58e2a commit fa20772

File tree

4 files changed

+141
-0
lines changed

4 files changed

+141
-0
lines changed

iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@
9393
3C6299A92BEEA46C00649187 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3C6299A82BEEA46C00649187 /* PrivacyInfo.xcprivacy */; };
9494
3C6299AB2BEEA4C000649187 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3C6299AA2BEEA4C000649187 /* PrivacyInfo.xcprivacy */; };
9595
3C67F77A2BEB2B710085A0F0 /* SwitchUserIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C67F7792BEB2B710085A0F0 /* SwitchUserIntegrationTests.swift */; };
96+
3C68EFE52D93195E00F0896B /* OSCustomEventsExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C68EFE42D93195E00F0896B /* OSCustomEventsExecutor.swift */; };
97+
3C68EFE72D931BA600F0896B /* OSRequestCustomEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C68EFE62D931BA600F0896B /* OSRequestCustomEvents.swift */; };
9698
3C70FA672D0B68A100031066 /* OneSignalClientError.h in Headers */ = {isa = PBXBuildFile; fileRef = 3C70FA652D0B68A100031066 /* OneSignalClientError.h */; settings = {ATTRIBUTES = (Public, ); }; };
9799
3C70FA682D0B68A100031066 /* OneSignalClientError.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C70FA662D0B68A100031066 /* OneSignalClientError.m */; };
98100
3C789DBD293C2206004CF83D /* OSFocusInfluenceParam.m in Sources */ = {isa = PBXBuildFile; fileRef = 7A600B432453790700514A53 /* OSFocusInfluenceParam.m */; };
@@ -1269,6 +1271,8 @@
12691271
3C6299A82BEEA46C00649187 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
12701272
3C6299AA2BEEA4C000649187 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
12711273
3C67F7792BEB2B710085A0F0 /* SwitchUserIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchUserIntegrationTests.swift; sourceTree = "<group>"; };
1274+
3C68EFE42D93195E00F0896B /* OSCustomEventsExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSCustomEventsExecutor.swift; sourceTree = "<group>"; };
1275+
3C68EFE62D931BA600F0896B /* OSRequestCustomEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSRequestCustomEvents.swift; sourceTree = "<group>"; };
12721276
3C70FA652D0B68A100031066 /* OneSignalClientError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OneSignalClientError.h; sourceTree = "<group>"; };
12731277
3C70FA662D0B68A100031066 /* OneSignalClientError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OneSignalClientError.m; sourceTree = "<group>"; };
12741278
3C7A39D42B7C18EE0082665E /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Platforms/iPhoneOS.platform/Developer/Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
@@ -2144,6 +2148,7 @@
21442148
isa = PBXGroup;
21452149
children = (
21462150
3C8E6E0028AC0BA10031E48A /* OSIdentityOperationExecutor.swift */,
2151+
3C68EFE42D93195E00F0896B /* OSCustomEventsExecutor.swift */,
21472152
3C8E6DFE28AB09AE0031E48A /* OSPropertyOperationExecutor.swift */,
21482153
3CE795FA28DBDCE700736BD4 /* OSSubscriptionOperationExecutor.swift */,
21492154
3C9AD6BB2B2285FB00BC1540 /* OSUserExecutor.swift */,
@@ -2166,6 +2171,7 @@
21662171
3C9AD6C62B228A9800BC1540 /* OSRequestTransferSubscription.swift */,
21672172
3C9AD6C02B22886600BC1540 /* OSRequestUpdateSubscription.swift */,
21682173
3C9AD6C42B228A7300BC1540 /* OSRequestDeleteSubscription.swift */,
2174+
3C68EFE62D931BA600F0896B /* OSRequestCustomEvents.swift */,
21692175
);
21702176
path = Requests;
21712177
sourceTree = "<group>";
@@ -4401,8 +4407,10 @@
44014407
3C277D7E2BD76E0000857606 /* OSIdentityModelRepo.swift in Sources */,
44024408
3CEE90A72BFE6ABD00B0FB5B /* OSPropertiesSupportedProperty.swift in Sources */,
44034409
3C9AD6C12B22886600BC1540 /* OSRequestUpdateSubscription.swift in Sources */,
4410+
3C68EFE72D931BA600F0896B /* OSRequestCustomEvents.swift in Sources */,
44044411
3C0EF49E28A1DBCB00E5434B /* OSUserInternalImpl.swift in Sources */,
44054412
3C8E6DFF28AB09AE0031E48A /* OSPropertyOperationExecutor.swift in Sources */,
4413+
3C68EFE52D93195E00F0896B /* OSCustomEventsExecutor.swift in Sources */,
44064414
3C9AD6CB2B228B5200BC1540 /* OSRequestIdentifyUser.swift in Sources */,
44074415
3C9AD6BC2B2285FB00BC1540 /* OSUserExecutor.swift in Sources */,
44084416
3C9AD6C32B22887700BC1540 /* OSRequestCreateUser.swift in Sources */,

iOS_SDK/OneSignalSDK/OneSignalCore/Source/OneSignalCommonDefines.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ typedef enum {ATTRIBUTED, NOT_ATTRIBUTED} FocusAttributionState;
205205
#define IDENTITY_EXECUTOR_BACKGROUND_TASK @"IDENTITY_EXECUTOR_BACKGROUND_TASK_"
206206
#define PROPERTIES_EXECUTOR_BACKGROUND_TASK @"PROPERTIES_EXECUTOR_BACKGROUND_TASK_"
207207
#define SUBSCRIPTION_EXECUTOR_BACKGROUND_TASK @"SUBSCRIPTION_EXECUTOR_BACKGROUND_TASK_"
208+
#define CUSTOM_EVENTS_EXECUTOR_BACKGROUND_TASK @"CUSTOM_EVENTS_EXECUTOR_BACKGROUND_TASK_"
208209

209210
// OneSignal constants
210211
#define OS_PUSH @"push"
@@ -339,6 +340,8 @@ typedef enum {GET, POST, HEAD, PUT, DELETE, OPTIONS, CONNECT, TRACE, PATCH} HTTP
339340
#define OS_REMOVE_SUBSCRIPTION_DELTA @"OS_REMOVE_SUBSCRIPTION_DELTA"
340341
#define OS_UPDATE_SUBSCRIPTION_DELTA @"OS_UPDATE_SUBSCRIPTION_DELTA"
341342

343+
#define OS_CUSTOM_EVENT_DELTA @"OS_CUSTOM_EVENT_DELTA"
344+
342345
// Operation Repo
343346
#define OS_OPERATION_REPO_DELTA_QUEUE_KEY @"OS_OPERATION_REPO_DELTA_QUEUE_KEY"
344347

@@ -361,6 +364,10 @@ typedef enum {GET, POST, HEAD, PUT, DELETE, OPTIONS, CONNECT, TRACE, PATCH} HTTP
361364
#define OS_SUBSCRIPTION_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY @"OS_SUBSCRIPTION_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY"
362365
#define OS_SUBSCRIPTION_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY @"OS_SUBSCRIPTION_EXECUTOR_UPDATE_REQUEST_QUEUE_KEY"
363366

367+
// Custom Events Executor
368+
#define OS_CUSTOM_EVENTS_EXECUTOR_DELTA_QUEUE_KEY @"OS_CUSTOM_EVENTS_EXECUTOR_DELTA_QUEUE_KEY"
369+
#define OS_CUSTOM_EVENTS_EXECUTOR_REQUEST_QUEUE_KEY @"OS_CUSTOM_EVENTS_EXECUTOR_REQUEST_QUEUE_KEY"
370+
364371
// Live Activies Executor
365372
#define OS_LIVE_ACTIVITIES_EXECUTOR_UPDATE_TOKENS_KEY @"OS_LIVE_ACTIVITIES_EXECUTOR_UPDATE_TOKENS_KEY"
366373
#define OS_LIVE_ACTIVITIES_EXECUTOR_START_TOKENS_KEY @"OS_LIVE_ACTIVITIES_EXECUTOR_START_TOKENS_KEY"

iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ import OneSignalNotifications
7575
func removeSms(_ number: String)
7676
// Language
7777
func setLanguage(_ language: String)
78+
// Events
79+
/**
80+
Track an event performed by the current user.
81+
- Parameters:
82+
- name: Name of the event, e.g., 'Started Free Trial'
83+
- properties: Optional properties specific to the event. For example, an event with the name 'Started Free Trial' might have properties like promo code used or expiration date.
84+
*/
85+
func trackEvent(name: String, properties: [String: Any]?)
86+
// ^ TODO: After alpha feedback, confirm value type for properties dict
7887
// JWT Token Expire
7988
typealias OSJwtCompletionBlock = (_ newJwtToken: String) -> Void
8089
typealias OSJwtExpiredHandler = (_ externalId: String, _ completion: OSJwtCompletionBlock) -> Void
@@ -182,6 +191,7 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager {
182191
var propertyExecutor: OSPropertyOperationExecutor?
183192
var identityExecutor: OSIdentityOperationExecutor?
184193
var subscriptionExecutor: OSSubscriptionOperationExecutor?
194+
var customEventsExecutor: OSCustomEventsExecutor?
185195

186196
private override init() {
187197
self.identityModelStoreListener = OSIdentityModelStoreListener(store: identityModelStore)
@@ -231,12 +241,15 @@ public class OneSignalUserManagerImpl: NSObject, OneSignalUserManager {
231241
let propertyExecutor = OSPropertyOperationExecutor(newRecordsState: newRecordsState)
232242
let identityExecutor = OSIdentityOperationExecutor(newRecordsState: newRecordsState)
233243
let subscriptionExecutor = OSSubscriptionOperationExecutor(newRecordsState: newRecordsState)
244+
let customEventsExecutor = OSCustomEventsExecutor(newRecordsState: newRecordsState)
234245
self.propertyExecutor = propertyExecutor
235246
self.identityExecutor = identityExecutor
236247
self.subscriptionExecutor = subscriptionExecutor
248+
self.customEventsExecutor = customEventsExecutor
237249
OSOperationRepo.sharedInstance.addExecutor(identityExecutor)
238250
OSOperationRepo.sharedInstance.addExecutor(propertyExecutor)
239251
OSOperationRepo.sharedInstance.addExecutor(subscriptionExecutor)
252+
OSOperationRepo.sharedInstance.addExecutor(customEventsExecutor)
240253

241254
// Path 2. There is a legacy player to migrate
242255
if let legacyPlayerId = OneSignalUserDefaults.initShared().getSavedString(forKey: OSUD_LEGACY_PLAYER_ID, defaultValue: nil) {
@@ -797,6 +810,32 @@ extension OneSignalUserManagerImpl: OSUser {
797810

798811
user.setLanguage(language)
799812
}
813+
814+
public func trackEvent(name: String, properties: [String: Any]?) {
815+
guard !OneSignalConfigManager.shouldAwaitAppIdAndLogMissingPrivacyConsent(forMethod: "trackEvent") else {
816+
return
817+
}
818+
819+
let processedProperties = properties ?? [:]
820+
821+
// Make sure the properties are serializable as JSON object
822+
guard JSONSerialization.isValidJSONObject(processedProperties) else {
823+
OneSignalLog.onesignalLog(.LL_ERROR, message: "trackEvent called with invalid properties \(processedProperties), dropping this event.")
824+
return
825+
}
826+
827+
// Get the identity model of the current user
828+
let identityModel = user.identityModel
829+
830+
let delta = OSDelta(
831+
name: OS_CUSTOM_EVENT_DELTA,
832+
identityModelId: identityModel.modelId,
833+
model: identityModel,
834+
property: name,
835+
value: processedProperties
836+
)
837+
OSOperationRepo.sharedInstance.enqueueDelta(delta)
838+
}
800839
}
801840

802841
extension OneSignalUserManagerImpl {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
Modified MIT License
3+
4+
Copyright 2025 OneSignal
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
1. The above copyright notice and this permission notice shall be included in
14+
all copies or substantial portions of the Software.
15+
16+
2. All copies of substantial portions of the Software may only be used in connection
17+
with services provided by OneSignal.
18+
19+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
THE SOFTWARE.
26+
*/
27+
28+
import OneSignalCore
29+
import OneSignalOSCore
30+
31+
class OSRequestCustomEvents: OneSignalRequest, OSUserRequest {
32+
var sentToClient = false
33+
let stringDescription: String
34+
override var description: String {
35+
return stringDescription
36+
}
37+
38+
var identityModel: OSIdentityModel
39+
40+
func prepareForExecution(newRecordsState: OSNewRecordsState) -> Bool {
41+
if let onesignalId = identityModel.onesignalId,
42+
newRecordsState.canAccess(onesignalId),
43+
let appId = OneSignalConfigManager.getAppId()
44+
{
45+
_ = self.addPushSubscriptionIdToAdditionalHeaders()
46+
self.path = "apps/\(appId)/integrations/custom_events"
47+
return true
48+
} else {
49+
return false
50+
}
51+
}
52+
53+
init(events: [[String: Any]], identityModel: OSIdentityModel) {
54+
self.identityModel = identityModel
55+
self.stringDescription = "<OSRequestCustomEvents with events: \(events)>"
56+
super.init()
57+
self.parameters = [
58+
"events": events
59+
]
60+
self.method = POST
61+
}
62+
63+
func encode(with coder: NSCoder) {
64+
coder.encode(identityModel, forKey: "identityModel")
65+
coder.encode(parameters, forKey: "parameters")
66+
coder.encode(method.rawValue, forKey: "method") // Encodes as String
67+
coder.encode(timestamp, forKey: "timestamp")
68+
}
69+
70+
required init?(coder: NSCoder) {
71+
guard
72+
let identityModel = coder.decodeObject(forKey: "identityModel") as? OSIdentityModel,
73+
let rawMethod = coder.decodeObject(forKey: "method") as? UInt32,
74+
let parameters = coder.decodeObject(forKey: "parameters") as? [String: Any],
75+
let timestamp = coder.decodeObject(forKey: "timestamp") as? Date
76+
else {
77+
// Log error
78+
return nil
79+
}
80+
self.identityModel = identityModel
81+
self.stringDescription = "<OSRequestCustomEvents with parameters: \(parameters)>"
82+
super.init()
83+
self.parameters = parameters
84+
self.method = HTTPMethod(rawValue: rawMethod)
85+
self.timestamp = timestamp
86+
}
87+
}

0 commit comments

Comments
 (0)