Skip to content

Commit

Permalink
Merge pull request #1470 from OneSignal/improve/delay_updates_after_c…
Browse files Browse the repository at this point in the history
…reates

[Fix] Handle incorrect `404`, delay making requests to new IDs
  • Loading branch information
nan-li authored Aug 23, 2024
2 parents caee127 + 529a148 commit eaf4855
Show file tree
Hide file tree
Showing 27 changed files with 561 additions and 139 deletions.
5 changes: 4 additions & 1 deletion .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
line_length: 150
line_length: 150
disabled_rules:
# Track https://github.com/realm/SwiftLint/pull/5521
- opening_brace
20 changes: 20 additions & 0 deletions iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@
3CEE93542B7C78EC008440BD /* OneSignalUser.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DE69E19B282ED8060090BB3D /* OneSignalUser.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3CEE93572B7C78FD008440BD /* OneSignalCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DE7D17E627026B95002D3A5D /* OneSignalCore.framework */; };
3CEE93582B7C78FE008440BD /* OneSignalCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DE7D17E627026B95002D3A5D /* OneSignalCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3CF11E3D2C6D6155002856F5 /* UserExecutorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF11E3C2C6D6155002856F5 /* UserExecutorTests.swift */; };
3CF11E402C6E6DE2002856F5 /* MockNewRecordsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF11E3F2C6E6DE2002856F5 /* MockNewRecordsState.swift */; };
3CF1A5632C669EA40056B3AA /* OSNewRecordsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF1A5622C669EA40056B3AA /* OSNewRecordsState.swift */; };
3CF8629E28A183F900776CA4 /* OSIdentityModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF8629D28A183F900776CA4 /* OSIdentityModel.swift */; };
3CF862A028A1964F00776CA4 /* OSPropertiesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF8629F28A1964F00776CA4 /* OSPropertiesModel.swift */; };
3CF862A228A197D200776CA4 /* OSPropertiesModelStoreListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CF862A128A197D200776CA4 /* OSPropertiesModelStoreListener.swift */; };
Expand Down Expand Up @@ -1294,6 +1297,9 @@
3CE92279289FA88B001B1062 /* OSIdentityModelStoreListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSIdentityModelStoreListener.swift; sourceTree = "<group>"; };
3CEE90A62BFE6ABD00B0FB5B /* OSPropertiesSupportedProperty.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSPropertiesSupportedProperty.swift; sourceTree = "<group>"; };
3CEE90A82C000BD500B0FB5B /* OneSignalRequest+UnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OneSignalRequest+UnitTests.swift"; sourceTree = "<group>"; };
3CF11E3C2C6D6155002856F5 /* UserExecutorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserExecutorTests.swift; sourceTree = "<group>"; };
3CF11E3F2C6E6DE2002856F5 /* MockNewRecordsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNewRecordsState.swift; sourceTree = "<group>"; };
3CF1A5622C669EA40056B3AA /* OSNewRecordsState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSNewRecordsState.swift; sourceTree = "<group>"; };
3CF8629D28A183F900776CA4 /* OSIdentityModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSIdentityModel.swift; sourceTree = "<group>"; };
3CF8629F28A1964F00776CA4 /* OSPropertiesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSPropertiesModel.swift; sourceTree = "<group>"; };
3CF862A128A197D200776CA4 /* OSPropertiesModelStoreListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSPropertiesModelStoreListener.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2051,6 +2057,7 @@
3C115188289ADEA300565C41 /* OSModelStore.swift */,
3C115186289ADE7700565C41 /* OSModelStoreListener.swift */,
3C115184289ADE4F00565C41 /* OSModel.swift */,
3CF1A5622C669EA40056B3AA /* OSNewRecordsState.swift */,
3C11518A289ADEEB00565C41 /* OSEventProducer.swift */,
3C11518C289AF5E800565C41 /* OSModelChangedHandler.swift */,
3C4F9E4328A4466C009F453A /* OSOperationRepo.swift */,
Expand All @@ -2070,6 +2077,7 @@
children = (
3C8544B82C5AEFF700F542A9 /* OneSignalOSCoreMocks.h */,
3C8544C22C5AF18B00F542A9 /* OSCoreMocks.swift */,
3CF11E3F2C6E6DE2002856F5 /* MockNewRecordsState.swift */,
);
path = OneSignalOSCoreMocks;
sourceTree = "<group>";
Expand Down Expand Up @@ -2150,6 +2158,7 @@
isa = PBXGroup;
children = (
3CDE664A2BFC2A55006DA114 /* OneSignalUserTests-Bridging-Header.h */,
3CF11E3E2C6D61AC002856F5 /* Executors */,
3CC063ED2B6D7FE8002BB07F /* OneSignalUserTests.swift */,
3CC890342C5BF9A7002CB4CC /* UserConcurrencyTests.swift */,
3C67F7792BEB2B710085A0F0 /* SwitchUserIntegrationTests.swift */,
Expand All @@ -2166,6 +2175,14 @@
path = Support;
sourceTree = "<group>";
};
3CF11E3E2C6D61AC002856F5 /* Executors */ = {
isa = PBXGroup;
children = (
3CF11E3C2C6D6155002856F5 /* UserExecutorTests.swift */,
);
path = Executors;
sourceTree = "<group>";
};
3E2400391D4FFC31008BDE70 /* OneSignalFramework */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4038,6 +4055,7 @@
3C115165289A259500565C41 /* OneSignalOSCore.docc in Sources */,
3C115189289ADEA300565C41 /* OSModelStore.swift in Sources */,
3C115185289ADE4F00565C41 /* OSModel.swift in Sources */,
3CF1A5632C669EA40056B3AA /* OSNewRecordsState.swift in Sources */,
3C448BA22936B474002F96BC /* OSBackgroundTaskManager.swift in Sources */,
3C115187289ADE7700565C41 /* OSModelStoreListener.swift in Sources */,
3CE5F9E3289D88DC004A156E /* OSModelStoreChangedHandler.swift in Sources */,
Expand All @@ -4054,6 +4072,7 @@
buildActionMask = 2147483647;
files = (
3C8544C32C5AF18B00F542A9 /* OSCoreMocks.swift in Sources */,
3CF11E402C6E6DE2002856F5 /* MockNewRecordsState.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -4092,6 +4111,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3CF11E3D2C6D6155002856F5 /* UserExecutorTests.swift in Sources */,
3C67F77A2BEB2B710085A0F0 /* SwitchUserIntegrationTests.swift in Sources */,
3CC063EE2B6D7FE8002BB07F /* OneSignalUserTests.swift in Sources */,
3CC890352C5BF9A7002CB4CC /* UserConcurrencyTests.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,13 @@ typedef enum {GET, POST, HEAD, PUT, DELETE, OPTIONS, CONNECT, TRACE, PATCH} HTTP

// Flush interval for operation repo in milliseconds
#define POLL_INTERVAL_MS 5000

/**
The number of seconds to delay after an operation completes that creates or changes IDs.
This is a "cold down" period to avoid a caveat with OneSignal's backend replication, where you may
incorrectlyget a 404 when attempting a GET or PATCH REST API call on something just after it is created.
*/
#define OP_REPO_POST_CREATE_DELAY_SECONDS 3
#else
// Test defines for API Client
#define REATTEMPT_DELAY 0.004
Expand All @@ -279,6 +286,8 @@ typedef enum {GET, POST, HEAD, PUT, DELETE, OPTIONS, CONNECT, TRACE, PATCH} HTTP
// Reduce flush interval for operation repo in tests
#define POLL_INTERVAL_MS 100

// Reduce delay in tests
#define OP_REPO_POST_CREATE_DELAY_SECONDS 0
#endif

// A max timeout for a request, which might include multiple reattempts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,6 @@ public class MockOneSignalClient: NSObject, IOneSignalClient {
public func execute(_ request: OneSignalRequest, onSuccess successBlock: @escaping OSResultSuccessBlock, onFailure failureBlock: @escaping OSFailureBlock) {
print("🧪 MockOneSignalClient execute called")

lock.withLock {
executedRequests.append(request)
}

if executeInstantaneously {
finishExecutingRequest(request, onSuccess: successBlock, onFailure: failureBlock)
} else {
Expand Down Expand Up @@ -127,6 +123,9 @@ public class MockOneSignalClient: NSObject, IOneSignalClient {
print("🧪 completing HTTP request: \(request)")

// TODO: Check for existence of app_id in the request and fail if not.
lock.withLock {
executedRequests.append(request)
}

self.didCompleteRequest(request)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
Modified MIT License
Copyright 2024 OneSignal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. All copies of substantial portions of the Software may only be used in connection
with services provided by OneSignal.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

import OneSignalCore

/**
* Purpose: Keeps track of IDs that were just created on the backend.
* This list gets used to delay network calls to ensure upcoming
* requests are ready to be accepted by the backend.
*/
public class OSNewRecordsState {
/**
Params:
- Key - a string ID such as onesignal ID or subscription ID
- Value - a Date timestamp of when the ID was created
*/
private var records: [String: Date] = [:]
private let lock = NSRecursiveLock()

public init() { }

/**
Only add a new record with the current timestamp if overwriting is requested, or it is not already present
*/
public func add(_ key: String, _ overwrite: Bool = false) {
lock.withLock {
if overwrite || records[key] == nil {
records[key] = Date()
}
}
}

public func canAccess(_ key: String) -> Bool {
lock.withLock {
guard let timeLastMovedOrCreated = records[key] else {
return true
}

let minimumTime = timeLastMovedOrCreated.addingTimeInterval(TimeInterval(OP_REPO_POST_CREATE_DELAY_SECONDS))

return Date() >= minimumTime
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
Modified MIT License
Copyright 2024 OneSignal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
1. The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
2. All copies of substantial portions of the Software may only be used in connection
with services provided by OneSignal.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

@testable import OneSignalOSCore

public class MockNewRecordsState: OSNewRecordsState {
public struct MockNewRecord {
let key: String
let overwrite: Bool
}

public var records: [MockNewRecord] = []

override public func add(_ key: String, _ overwrite: Bool = false) {
let record = MockNewRecord(key: key, overwrite: overwrite)
records.append(record)

super.add(key, overwrite)
}

override public func canAccess(_ key: String) -> Bool {
return super.canAccess(key)
}

public func get(_ key: String?) -> [MockNewRecord] {
return records.filter { $0.key == key }
}

public func contains(_ key: String?) -> Bool {
return get(key).count > 0
}

public func wasOverwritten(_ key: String?) -> Bool {
return records.filter { $0.key == key && $0.overwrite }.count > 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ class OSIdentityOperationExecutor: OSOperationExecutor {
// To simplify uncaching, we maintain separate request queues for each type
var addRequestQueue: [OSRequestAddAliases] = []
var removeRequestQueue: [OSRequestRemoveAlias] = []
let newRecordsState: OSNewRecordsState

// 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() {
init(newRecordsState: OSNewRecordsState) {
self.newRecordsState = newRecordsState
// Read unfinished deltas and requests from cache, if any...
uncacheDeltas()
uncacheAddAliasRequests()
Expand Down Expand Up @@ -72,7 +74,7 @@ class OSIdentityOperationExecutor: OSOperationExecutor {
if let identityModel = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(request.identityModel.modelId) {
// 1. The model exists in the repo, so set it to be the Request's models
request.identityModel = identityModel
} else if request.prepareForExecution() {
} else if request.prepareForExecution(newRecordsState: newRecordsState) {
// 2. The request can be sent, add the model to the repo
OneSignalUserManagerImpl.sharedInstance.addIdentityModelToRepo(request.identityModel)
} else {
Expand All @@ -95,7 +97,7 @@ class OSIdentityOperationExecutor: OSOperationExecutor {
if let identityModel = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(request.identityModel.modelId) {
// 1. The model exists in the repo, so set it to be the Request's model
request.identityModel = identityModel
} else if request.prepareForExecution() {
} else if request.prepareForExecution(newRecordsState: newRecordsState) {
// 2. The request can be sent, add the model to the repo
OneSignalUserManagerImpl.sharedInstance.addIdentityModelToRepo(request.identityModel)
} else {
Expand Down Expand Up @@ -191,7 +193,7 @@ class OSIdentityOperationExecutor: OSOperationExecutor {
guard !request.sentToClient else {
return
}
guard request.prepareForExecution() else {
guard request.prepareForExecution(newRecordsState: newRecordsState) else {
return
}
request.sentToClient = true
Expand Down Expand Up @@ -250,7 +252,7 @@ class OSIdentityOperationExecutor: OSOperationExecutor {
guard !request.sentToClient else {
return
}
guard request.prepareForExecution() else {
guard request.prepareForExecution(newRecordsState: newRecordsState) else {
return
}
request.sentToClient = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,13 @@ class OSPropertyOperationExecutor: OSOperationExecutor {
var supportedDeltas: [String] = [OS_UPDATE_PROPERTIES_DELTA]
var deltaQueue: [OSDelta] = []
var updateRequestQueue: [OSRequestUpdateProperties] = []
let newRecordsState: OSNewRecordsState

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

init() {
init(newRecordsState: OSNewRecordsState) {
self.newRecordsState = newRecordsState
// Read unfinished deltas and requests from cache, if any...
// Note that we should only have deltas for the current user as old ones are flushed..
uncacheDeltas()
Expand Down Expand Up @@ -98,7 +100,7 @@ class OSPropertyOperationExecutor: OSOperationExecutor {
if let identityModel = OneSignalUserManagerImpl.sharedInstance.getIdentityModel(request.identityModel.modelId) {
// 1. The identity model exist in the repo, set it to be the Request's model
request.identityModel = identityModel
} else if request.prepareForExecution() {
} else if request.prepareForExecution(newRecordsState: newRecordsState) {
// 2. The request can be sent, add the model to the repo
OneSignalUserManagerImpl.sharedInstance.addIdentityModelToRepo(request.identityModel)
} else {
Expand Down Expand Up @@ -233,7 +235,7 @@ class OSPropertyOperationExecutor: OSOperationExecutor {
guard !request.sentToClient else {
return
}
guard request.prepareForExecution() else {
guard request.prepareForExecution(newRecordsState: newRecordsState) else {
return
}
request.sentToClient = true
Expand Down
Loading

0 comments on commit eaf4855

Please sign in to comment.