Skip to content
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
8 changes: 8 additions & 0 deletions iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@
03E56DD328405F4A006AA1DA /* OneSignalAppDelegateOverrider.m in Sources */ = {isa = PBXBuildFile; fileRef = 03E56DD228405F4A006AA1DA /* OneSignalAppDelegateOverrider.m */; };
16664C4C25DDB195003B8A14 /* NSTimeZoneOverrider.m in Sources */ = {isa = PBXBuildFile; fileRef = 16664C4B25DDB195003B8A14 /* NSTimeZoneOverrider.m */; };
1B7E5A0AEB23050C398E6111 /* ExecutorAnonymousPurgeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE740F9E6B87D215510B5982 /* ExecutorAnonymousPurgeTests.swift */; };
D9F1B3E5A7C2495FC1E6B8D3 /* ExecutorRequestAgingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5A1D7F2B9461EB8D4A6C2 /* ExecutorRequestAgingTests.swift */; };
23D66BEB40CE76DFF89744A3 /* RequestPathEncodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3016921C1F6B7B7793F67567 /* RequestPathEncodingTests.swift */; };
257E219608960B8545199057 /* OneSignalUserManagerImpl+Jwt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CCE2C93100CAFEE8EB39C77 /* OneSignalUserManagerImpl+Jwt.swift */; };
2DB99C76F3532383C3B81D09 /* OSUserJwtRepoTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5809B47EB4F2478099697CE /* OSUserJwtRepoTests.swift */; };
322B62F85070DCB5C5599D6D /* OSOperationRepoIdentityVerificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBB451F692A15639A1A08AE /* OSOperationRepoIdentityVerificationTests.swift */; };
32601EF1960CD92605D1ABF9 /* OSRequestAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BF72AAEB5284C97B864A1A8 /* OSRequestAuth.swift */; };
B7D2A9C4E6F1483DA2C5E9F1 /* OSRequestAging.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1C4E7F2B9D3468CA5E1F0B2 /* OSRequestAging.swift */; };
37E6B2BB19D9CAF300D0C601 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37E6B2BA19D9CAF300D0C601 /* UIKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
3C0151922C2E298F0079E076 /* OneSignalInAppMessages.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DEBAAE282A4211D900BF2C1C /* OneSignalInAppMessages.framework */; };
3C01519C2C2E29F90079E076 /* IAMRequestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C01519B2C2E29F90079E076 /* IAMRequestTests.m */; };
Expand Down Expand Up @@ -1719,6 +1721,7 @@
91F60F7B1E80E49A00706E60 /* UncaughtExceptionHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UncaughtExceptionHandler.h; sourceTree = "<group>"; };
91F60F7C1E80E4E400706E60 /* UncaughtExceptionHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UncaughtExceptionHandler.m; sourceTree = "<group>"; };
9BF72AAEB5284C97B864A1A8 /* OSRequestAuth.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OSRequestAuth.swift; sourceTree = "<group>"; };
A1C4E7F2B9D3468CA5E1F0B2 /* OSRequestAging.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OSRequestAging.swift; sourceTree = "<group>"; };
9D1BD95D237663BF00A064F7 /* OSInfluenceDataDefines.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OSInfluenceDataDefines.h; sourceTree = "<group>"; };
9D1BD95E2379E7A900A064F7 /* OSOutcomeEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OSOutcomeEvent.h; sourceTree = "<group>"; };
9D1BD95F2379E7C300A064F7 /* OSOutcomeEvent.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OSOutcomeEvent.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1961,6 +1964,7 @@
F4855B81F170253FB0C1749D /* OSIdentityVerificationServiceTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OSIdentityVerificationServiceTests.swift; sourceTree = "<group>"; };
F83E7BF2B518EA8B0B51B276 /* OSAliasPair.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OSAliasPair.swift; sourceTree = "<group>"; };
FE740F9E6B87D215510B5982 /* ExecutorAnonymousPurgeTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ExecutorAnonymousPurgeTests.swift; sourceTree = "<group>"; };
C3E5A1D7F2B9461EB8D4A6C2 /* ExecutorRequestAgingTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ExecutorRequestAgingTests.swift; sourceTree = "<group>"; };
FF4B19D1EC31C0750F13065A /* OSIamFetchReadyConditionTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OSIamFetchReadyConditionTests.swift; sourceTree = "<group>"; };
FFBB451F692A15639A1A08AE /* OSOperationRepoIdentityVerificationTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OSOperationRepoIdentityVerificationTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
Expand Down Expand Up @@ -2583,6 +2587,7 @@
3C5181A1B2C3D4E5F6A7B801 /* SubscriptionCreateResponseTests.swift */,
3CB331692F281692000E1801 /* OSCustomEventsExecutorTests.swift */,
FE740F9E6B87D215510B5982 /* ExecutorAnonymousPurgeTests.swift */,
C3E5A1D7F2B9461EB8D4A6C2 /* ExecutorRequestAgingTests.swift */,
);
path = Executors;
sourceTree = "<group>";
Expand Down Expand Up @@ -2965,6 +2970,7 @@
4CCE2C93100CAFEE8EB39C77 /* OneSignalUserManagerImpl+Jwt.swift */,
E78603AF16454A648A96DF7B /* OSPushSubscriptionImpl.swift */,
9BF72AAEB5284C97B864A1A8 /* OSRequestAuth.swift */,
A1C4E7F2B9D3468CA5E1F0B2 /* OSRequestAging.swift */,
);
path = Source;
sourceTree = "<group>";
Expand Down Expand Up @@ -4758,6 +4764,7 @@
AE95DD5B72F1D102F6D6062A /* XCTestCase+AsyncWork.swift in Sources */,
99B1615D0132AFAA981A9AD3 /* DeltaOwnershipTests.swift in Sources */,
1B7E5A0AEB23050C398E6111 /* ExecutorAnonymousPurgeTests.swift in Sources */,
D9F1B3E5A7C2495FC1E6B8D3 /* ExecutorRequestAgingTests.swift in Sources */,
AAFA2D46E6C5FD3D14D39F27 /* OSRequestAuthTests.swift in Sources */,
23D66BEB40CE76DFF89744A3 /* RequestPathEncodingTests.swift in Sources */,
7EB69F3B404D0AEF46EC1536 /* UserJwtLifecycleTests.swift in Sources */,
Expand Down Expand Up @@ -4982,6 +4989,7 @@
257E219608960B8545199057 /* OneSignalUserManagerImpl+Jwt.swift in Sources */,
DDE652EF2123473B808F8CCA /* OSPushSubscriptionImpl.swift in Sources */,
32601EF1960CD92605D1ABF9 /* OSRequestAuth.swift in Sources */,
B7D2A9C4E6F1483DA2C5E9F1 /* OSRequestAging.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,23 @@ class OSCustomEventsExecutor: OSOperationExecutor {
private var requestQueue: [OSRequestCustomEvents] = []
private let newRecordsState: OSNewRecordsState
private let auth: OSRequestAuthorizing
private let nowProvider: () -> Date

// The executor dispatch queue, serial. This synchronizes access to `deltaQueue` and `requestQueue`.
private let dispatchQueue = DispatchQueue(label: "OneSignal.OSCustomEventsExecutor", target: .global())

init(newRecordsState: OSNewRecordsState, auth: OSRequestAuthorizing) {
init(newRecordsState: OSNewRecordsState, auth: OSRequestAuthorizing, nowProvider: @escaping () -> Date = { Date() }) {
self.newRecordsState = newRecordsState
self.auth = auth
self.nowProvider = nowProvider
// Read unfinished deltas and requests from cache, if any...
uncacheDeltas()
uncacheRequests()
}

private func uncacheDeltas() {
if var deltaQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_CUSTOM_EVENTS_EXECUTOR_DELTA_QUEUE_KEY, defaultValue: [], maxBytes: UInt(OS_USER_DEFAULTS_MAX_VALUE_BYTES)) as? [OSDelta] {
_ = removeStaleDeltas(from: &deltaQueue)
for (index, delta) in deltaQueue.enumerated().reversed() {
if OneSignalUserManagerImpl.sharedInstance.getIdentityModel(delta.identityModelId) == nil {
// The identity model does not exist, drop this Delta
Expand All @@ -82,6 +85,8 @@ class OSCustomEventsExecutor: OSOperationExecutor {

private func uncacheRequests() {
if var requestQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_CUSTOM_EVENTS_EXECUTOR_REQUEST_QUEUE_KEY, defaultValue: [], maxBytes: UInt(OS_USER_DEFAULTS_MAX_VALUE_BYTES)) as? [OSRequestCustomEvents] {
// Before the hook-up, so it does not restore an identity model for a Request that is dropped anyway.
_ = removeStaleRequests(from: &requestQueue)
// Hook each uncached Request to the model in the store
for (index, request) in requestQueue.enumerated().reversed() {
if let identityModel = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(request.identityModel.modelId) {
Expand Down Expand Up @@ -140,6 +145,9 @@ class OSCustomEventsExecutor: OSOperationExecutor {
/// This method will be used in an upcoming release that combine multiple events.
func processDeltaQueueWithBatching(inBackground: Bool) {
self.dispatchQueue.async {
if self.removeStaleDeltas(from: &self.deltaQueue) {
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_CUSTOM_EVENTS_EXECUTOR_DELTA_QUEUE_KEY, withValue: self.deltaQueue)
}
if self.deltaQueue.isEmpty {
// Delta queue is empty but there may be pending requests
self.processRequestQueue(inBackground: inBackground)
Expand All @@ -148,7 +156,7 @@ class OSCustomEventsExecutor: OSOperationExecutor {
OneSignalLog.onesignalLog(.LL_VERBOSE, message: "OSCustomEventsExecutor processDeltaQueue with queue: \(self.deltaQueue)")

// Holds mapping of identity model ID to the events for it, with the owner the Deltas stamped
var combinedEvents: [String: (events: [[String: Any]], ownerExternalId: String?)] = [:]
var combinedEvents: [String: (events: [[String: Any]], ownerExternalId: String?, timestamp: Date)] = [:]

// 1. Combine the events for every distinct user
for (index, delta) in self.deltaQueue.enumerated().reversed() {
Expand All @@ -174,7 +182,10 @@ class OSCustomEventsExecutor: OSOperationExecutor {
EventConstants.payload: self.addSdkMetadata(properties: properties)
]

combinedEvents[identityModel.modelId, default: ([], delta.externalId)].events.append(event)
var combined = combinedEvents[identityModel.modelId, default: ([], delta.externalId, delta.timestamp)]
combined.events.append(event)
combined.timestamp = min(combined.timestamp, delta.timestamp)
combinedEvents[identityModel.modelId] = combined
self.deltaQueue.remove(at: index)
}

Expand All @@ -190,6 +201,8 @@ class OSCustomEventsExecutor: OSOperationExecutor {
identityModel: identityModel,
ownerExternalId: combined.ownerExternalId
)
// Aged from the oldest event, not from when its user got an onesignal_id.
request.timestamp = combined.timestamp

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Act on (3/3): this stamp is what property and identity skip. They build OSRequestUpdateProperties / alias Requests from Deltas and leave timestamp as Date(), so a Delta that sat in OSOperationRepo (paused, or requirement == .unknown) or in the executor Delta cache restarts at age 0. Age those Deltas and copy the oldest timestamp the way this executor does.

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.

Leaving as is. A Delta only waits past the next flush when the repo is paused after a failed Create User or unknown IV on/off, and then it converts at the next session and ages from there. That stretches the limit by one idle gap in a case that already has a defect. Accepting this going from 90 days to the idle gap plus 90 days as acceptable.

self.requestQueue.append(request)
}

Expand All @@ -203,6 +216,9 @@ class OSCustomEventsExecutor: OSOperationExecutor {

func processDeltaQueue(inBackground: Bool) {
self.dispatchQueue.async {
if self.removeStaleDeltas(from: &self.deltaQueue) {
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_CUSTOM_EVENTS_EXECUTOR_DELTA_QUEUE_KEY, withValue: self.deltaQueue)
}
if self.deltaQueue.isEmpty {
// Delta queue is empty but there may be pending requests
self.processRequestQueue(inBackground: inBackground)
Expand Down Expand Up @@ -240,6 +256,8 @@ class OSCustomEventsExecutor: OSOperationExecutor {
identityModel: identityModel,
ownerExternalId: delta.externalId
)
// Aged from the event, not from when its user got an onesignal_id.
request.timestamp = delta.timestamp
self.requestQueue.append(request)
}

Expand Down Expand Up @@ -271,6 +289,10 @@ class OSCustomEventsExecutor: OSOperationExecutor {

/// This method is called by `processDeltaQueue` only and does not need to be added to the dispatchQueue.
private func processRequestQueue(inBackground: Bool) {
if removeStaleRequests(from: &requestQueue) {
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_CUSTOM_EVENTS_EXECUTOR_REQUEST_QUEUE_KEY, withValue: requestQueue)
}

if requestQueue.isEmpty {
return
}
Expand All @@ -280,6 +302,26 @@ class OSCustomEventsExecutor: OSOperationExecutor {
}
}

/// See `OSRequestAging`. Deltas wait here until their user has an `onesignal_id`, so they are aged too.
private func removeStaleDeltas(from queue: inout [OSDelta]) -> Bool {
return queue.removeStaleDeltas(
typeLimit: OSRequestAging.customEventRequestMaxAge,
now: nowProvider(),
currentExternalId: OSRequestAging.currentExternalId,
executor: "OSCustomEventsExecutor"
)
}

/// See `OSRequestAging`. Returns whether the queue changed, so the caller can rewrite its cache entry.
private func removeStaleRequests(from queue: inout [OSRequestCustomEvents]) -> Bool {
return queue.removeStaleRequests(
typeLimit: OSRequestAging.customEventRequestMaxAge,
now: nowProvider(),
currentExternalId: OSRequestAging.currentExternalId,
executor: "OSCustomEventsExecutor"
)
}

private func executeRequest(_ request: OSRequestCustomEvents, inBackground: Bool) {
guard !request.sentToClient else {
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ class OSIdentityOperationExecutor: OSOperationExecutor {
private var removeRequestQueue: [OSRequestRemoveAlias] = []
private let newRecordsState: OSNewRecordsState
private let auth: OSRequestAuthorizing
private let nowProvider: () -> Date

// The Identity executor dispatch queue, serial. This synchronizes access to the delta and request queues.
private let dispatchQueue = DispatchQueue(label: "OneSignal.OSIdentityOperationExecutor", target: .global())

init(newRecordsState: OSNewRecordsState, auth: OSRequestAuthorizing) {
init(newRecordsState: OSNewRecordsState, auth: OSRequestAuthorizing, nowProvider: @escaping () -> Date = { Date() }) {
self.newRecordsState = newRecordsState
self.auth = auth
self.nowProvider = nowProvider
// Read unfinished deltas and requests from cache, if any...
uncacheDeltas()
uncacheAddAliasRequests()
Expand Down Expand Up @@ -71,6 +73,8 @@ class OSIdentityOperationExecutor: OSOperationExecutor {

private func uncacheAddAliasRequests() {
if var addRequestQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY, defaultValue: [], maxBytes: UInt(OS_USER_DEFAULTS_MAX_VALUE_BYTES)) as? [OSRequestAddAliases] {
// Before the hook-up, so it does not restore an identity model for a Request that is dropped anyway.
_ = removeStaleRequests(from: &addRequestQueue)
// Hook each uncached Request to the model in the store
for (index, request) in addRequestQueue.enumerated().reversed() {
if let identityModel = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(request.identityModel.modelId) {
Expand All @@ -94,6 +98,8 @@ class OSIdentityOperationExecutor: OSOperationExecutor {

private func uncacheRemoveAliasRequests() {
if var removeRequestQueue = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: OS_IDENTITY_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, defaultValue: [], maxBytes: UInt(OS_USER_DEFAULTS_MAX_VALUE_BYTES)) as? [OSRequestRemoveAlias] {
// Before the hook-up, so it does not restore an identity model for a Request that is dropped anyway.
_ = removeStaleRequests(from: &removeRequestQueue)
// Hook each uncached Request to the model in the store
for (index, request) in removeRequestQueue.enumerated().reversed() {
if let identityModel = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(request.identityModel.modelId) {
Expand Down Expand Up @@ -196,6 +202,13 @@ class OSIdentityOperationExecutor: OSOperationExecutor {

/// This method is called by `processDeltaQueue` only and does not need to be added to the dispatchQueue.
private func processRequestQueue(inBackground: Bool) {
if removeStaleRequests(from: &addRequestQueue) {
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_ADD_REQUEST_QUEUE_KEY, withValue: addRequestQueue)
}
if removeStaleRequests(from: &removeRequestQueue) {
OneSignalUserDefaults.initShared().saveCodeableData(forKey: OS_IDENTITY_EXECUTOR_REMOVE_REQUEST_QUEUE_KEY, withValue: removeRequestQueue)
}

let requestQueue: [OneSignalRequest] = addRequestQueue + removeRequestQueue

if requestQueue.isEmpty {
Expand All @@ -216,6 +229,16 @@ class OSIdentityOperationExecutor: OSOperationExecutor {
}
}

/// See `OSRequestAging`. Only the owner rule applies, since an alias change has no limit of its own.
private func removeStaleRequests<T: OSUserRequest>(from queue: inout [T]) -> Bool {
return queue.removeStaleRequests(
typeLimit: nil,
now: nowProvider(),
currentExternalId: OSRequestAging.currentExternalId,
executor: "OSIdentityOperationExecutor"
)
}

func executeAddAliasesRequest(_ request: OSRequestAddAliases, inBackground: Bool) {
guard !request.sentToClient else {
return
Expand Down
Loading
Loading