Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
8d74c11
fix: restore API TLS verification
shameondev Aug 4, 2026
5298036
feat: persist Remote Config last-known-good
shameondev Aug 4, 2026
8c04fb2
fix: complete concurrent Remote Config fallbacks
shameondev Aug 4, 2026
cd401ac
test: make Remote Config enum tables deterministic
shameondev Aug 4, 2026
6954bce
fix: terminate Remote Config lists on identity failure
shameondev Aug 5, 2026
f4b7660
fix: serialize identity-bound remote config requests
shameondev Aug 5, 2026
c3d2d0a
test: pin same-user identity terminal boundary
shameondev Aug 5, 2026
77f6bb0
test: model asynchronous same-user identity response
shameondev Aug 5, 2026
2e7fe9d
fix: serialize logout and identity terminal boundaries
shameondev Aug 5, 2026
2f73d96
test: cover reentrant logout ownership
shameondev Aug 5, 2026
6166eca
fix: preserve outer logout ownership
shameondev Aug 5, 2026
5236eca
test: cover restore ownership and callback lock order
shameondev Aug 5, 2026
212c22a
fix: isolate identity callback locks
shameondev Aug 5, 2026
ff874bd
test: cover transaction restore ownership
shameondev Aug 5, 2026
4fff8c2
fix: reject superseded restore scope commits
shameondev Aug 5, 2026
2508829
test: cover launch ownership hardening
shameondev Aug 5, 2026
1b53c40
fix: guard all launch state writes
shameondev Aug 5, 2026
6ec36db
test: cover terminal launch drains
shameondev Aug 5, 2026
bd485ef
fix: drain terminal launch waiters
shameondev Aug 5, 2026
5c89f6d
fix: preserve terminal launch error ownership
shameondev Aug 5, 2026
208a1d1
fix: atomically publish terminal launch state
shameondev Aug 5, 2026
520da34
fix: cancel waiters for superseded identity launches
shameondev Aug 5, 2026
853e28a
test: cover inactive identity launch cancellation
shameondev Aug 5, 2026
bd78bf0
fix: preserve user state on waiterless stale launch
shameondev Aug 5, 2026
9a33704
feat: add bundled Remote Config defaults
shameondev Aug 5, 2026
ef62a3e
fix: return boolean from fallback JSON validator
shameondev Aug 5, 2026
dfeeafa
feat: add durable remote config snapshot core
shameondev Aug 5, 2026
724fe52
feat: validate and durably admit remote config snapshots
shameondev Aug 6, 2026
d8e43d7
feat: add resilient remote config fetch policy
shameondev Aug 6, 2026
b6a15b1
feat: guard remote config reads before activation
shameondev Aug 6, 2026
ef8784d
fix: honor remote config decoder errors
shameondev Aug 6, 2026
9ce971e
feat: bind remote config fetch policy to the gateway routes
shameondev Aug 7, 2026
fc81d0a
fix: harden the remote config gateway transport contract
shameondev Aug 7, 2026
bfd719a
fix: look up response headers without the iOS 13 API
shameondev Aug 7, 2026
5b4ee1c
feat: expose the experimental Remote Config fetch/activate surface
shameondev Aug 7, 2026
2fc9118
fix: never let a slow fetch spend the app's read-before-activate budget
shameondev Aug 7, 2026
2e95e09
fix: harden the experimental Remote Config surface after review
shameondev Aug 7, 2026
cd3276a
feat: pin the remote config context on first use instead of pre-provi…
shameondev Aug 7, 2026
e99166f
fix: pin the remote config context only once a release is actually ad…
shameondev Aug 7, 2026
492fa12
fix: treat the context fingerprint as a per-response tag, never a pin
shameondev Aug 7, 2026
1b0ca7d
feat: learn the project id from the session bootstrap, never from the…
shameondev Aug 7, 2026
22f40f3
fix: key the project-identity ledger by deployment, and let only a bo…
shameondev Aug 7, 2026
232cf17
test: give the identify snapshot test load-proof waits
shameondev Aug 7, 2026
0f39d5f
feat(remote-config): ack every activation that changes the served rel…
shameondev Aug 7, 2026
ec47812
feat(remote-config): report decode failures and read-guard events to …
shameondev Aug 10, 2026
bb73555
feat(remote-config): public production enable API for Remote Config v2
shameondev Aug 10, 2026
9f58849
Force a targeting re-read when identify resolves to the already-bound…
shameondev Aug 12, 2026
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
6 changes: 6 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jobs:
EXPECTED_URL="https://api2.qonversion.io/"

MAIN_URL=$(grep 'kAPIBase = @"' Sources/Qonversion/Qonversion/Constants/QNAPIConstants/QNAPIConstants.m | sed 's/.*@"//;s/".*//')
REMOTE_CONFIG_V2_URL=$(grep 'kRemoteConfigV2APIBase = @"' Sources/Qonversion/Qonversion/Constants/QNAPIConstants/QNAPIConstants.m | sed 's/.*@"//;s/".*//')
NOCODES_URL=$(grep 'baseURL = "' Sources/NoCodes/Assemblies/ServicesAssembly.swift | sed 's/.*= "//;s/".*//')

FAILED=0
Expand All @@ -60,6 +61,11 @@ jobs:
FAILED=1
fi

if [ "$REMOTE_CONFIG_V2_URL" != "$EXPECTED_URL" ]; then
echo "::error::Remote Config v2 API URL mismatch: expected '$EXPECTED_URL', got '$REMOTE_CONFIG_V2_URL'"
FAILED=1
fi

if [ "$NOCODES_URL" != "$EXPECTED_URL" ]; then
echo "::error::NoCodes API URL mismatch: expected '$EXPECTED_URL', got '$NOCODES_URL'"
FAILED=1
Expand Down
3 changes: 3 additions & 0 deletions Framework/QonversionFramework.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
#import <Qonversion/QONRemoteConfigurationSource.h>
#import <Qonversion/QONSubscriptionPeriod.h>
#import <Qonversion/QONRemoteConfig.h>
#import <Qonversion/QONRemoteConfigValue.h>
#import <Qonversion/QONRemoteConfigSnapshot.h>
#import <Qonversion/QONRemoteConfigUpdate.h>
#import <Qonversion/QONUserProperty.h>
#import <Qonversion/QONUserProperties.h>
#import <Qonversion/QONStoreKit2PurchaseModel.h>
Expand Down
4 changes: 4 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ let sources: [String] = ["Qonversion/IDFA",
"Qonversion/Qonversion/Core/QNKeyedArchiver",
"Qonversion/Qonversion/Core/QNRequestBuilder",
"Qonversion/Qonversion/Core/QNRequestSerializer",
"Qonversion/Qonversion/Core/QONRemoteConfigV2Store",
"Qonversion/Qonversion/Core/QNUserDefaultsStorage",
"Qonversion/Qonversion/Main",
"Qonversion/Qonversion/Main/QNAttributionManager",
Expand All @@ -25,6 +26,7 @@ let sources: [String] = ["Qonversion/IDFA",
"Qonversion/Qonversion/Main/QNUserPropertiesManager",
"Qonversion/Qonversion/Main/QONRedemptionManager",
"Qonversion/Qonversion/Main/QONRemoteConfigManager",
"Qonversion/Qonversion/Main/QONRemoteConfigV2Manager",
"Qonversion/Qonversion/Mappers",
"Qonversion/Qonversion/Mappers/QNErrorsMapper",
"Qonversion/Qonversion/Mappers/QNMapper",
Expand All @@ -42,6 +44,8 @@ let sources: [String] = ["Qonversion/IDFA",
"Qonversion/Qonversion/Services/QNIdentityService",
"Qonversion/Qonversion/Services/QNStoreKitService",
"Qonversion/Qonversion/Services/QONFallbackService",
"Qonversion/Qonversion/Services/QONRemoteConfigFallbackStore",
"Qonversion/Qonversion/Services/QONRemoteConfigV2Transport",
"Qonversion/Qonversion/Services/QNUserInfoService",
"Qonversion/Qonversion/Services/QONRemoteConfigService",
"Qonversion/Qonversion/Services/QONExceptionManager",
Expand Down
265 changes: 259 additions & 6 deletions Qonversion.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions QonversionTests/APIClientTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,27 @@ - (void)testThatClientDetectBrokenData {
[self waitForExpectationsWithTimeout:keyQNTestTimeout handler:nil];
}

- (void)testThatServerTrustUsesSystemValidation {
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]
initWithHost:@"attacker.invalid"
port:443
protocol:NSURLProtectionSpaceHTTPS
realm:nil
authenticationMethod:NSURLAuthenticationMethodServerTrust];
id challenge = OCMClassMock([NSURLAuthenticationChallenge class]);
OCMStub([challenge protectionSpace]).andReturn(protectionSpace);

__block NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengeUseCredential;
__block NSURLCredential *credential = [NSURLCredential credentialWithUser:@"unexpected" password:@"unexpected" persistence:NSURLCredentialPersistenceNone];

[self.client URLSession:nil didReceiveChallenge:challenge completionHandler:^(NSURLSessionAuthChallengeDisposition receivedDisposition, NSURLCredential *receivedCredential) {
disposition = receivedDisposition;
credential = receivedCredential;
}];

XCTAssertEqual(disposition, NSURLSessionAuthChallengePerformDefaultHandling);
XCTAssertNil(credential);
[challenge stopMocking];
}

@end
4 changes: 0 additions & 4 deletions QonversionTests/Managers/QNIdentityManagerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,6 @@ - (void)testSuccessIdentity {
OCMStub([self.mockUserInfoService obtainUserID]).andReturn(anonUserID);
OCMStub([self.mockIdentityService identify:userID anonUserID:anonUserID completion:OCMOCK_ANY]).andDo(testBlock);

OCMExpect([self.mockUserInfoService storeIdentity:identityID]);

// when
[self.manager identify:userID completion:^(NSString * _Nullable result, NSError * _Nullable error) {
resultString = result;
Expand All @@ -77,8 +75,6 @@ - (void)testSuccessIdentity {
XCTAssertEqual(randomError, resultError);

OCMVerify([self.mockUserInfoService obtainUserID]);
OCMVerify([self.mockUserInfoService storeIdentity:identityID]);

OCMVerify([self.mockIdentityService identify:userID anonUserID:anonUserID completion:OCMOCK_ANY]);
}

Expand Down
Loading
Loading