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
2 changes: 1 addition & 1 deletion .github/workflows/update_package_swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:

- name: Install dependencies
run: |
sudo xcode-select -s /Applications/Xcode_15.0.app
sudo xcode-select --switch /Applications/Xcode_16.4.app
swift --version
echo "✅ Dependencies installed successfully"

Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Change Log
All notable changes to this project will be documented in this file.

### [Version 7.3.3](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/7.3.3) (September 11, 2025)
#### Fixed
- Fixes a bug where the updated values of variables were not fetched right after a profile push.
- Optimises queuing behaviour of various type of clevertap events in a single batch.

### [Version 7.3.2](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/7.3.2) (August 08, 2025)
#### Added
- Added Swift Package Manager (SPM) support for `CleverTapWatchOS`.
Expand Down
11 changes: 9 additions & 2 deletions CleverTapSDK/CleverTap.m
Original file line number Diff line number Diff line change
Expand Up @@ -2019,8 +2019,9 @@ - (void)clearQueue {

- (void)sendQueues {
if ([self isMuted] || _offline) return;
[self sendQueue:_profileQueue ofType:CTQueueTypeProfile];
[self sendQueue:_eventsQueue ofType:CTQueueTypeEvents];
// Sending profiles and events together
NSArray *combined = [_profileQueue arrayByAddingObjectsFromArray:_eventsQueue];
[self sendQueue:[combined mutableCopy] ofType:CTQueueTypeEvents];
[self sendQueue:_notificationsQueue ofType:CTQueueTypeNotifications];
}

Expand Down Expand Up @@ -2262,6 +2263,12 @@ - (void)sendQueue:(NSMutableArray *)queue ofType:(CTQueueType)queueType {
}

[queue removeObjectsInArray:batch];
if ([_profileQueue count] > 0) {
[_profileQueue removeObjectsInArray:batch];
}
if ([_eventsQueue count] > 0) {
[_eventsQueue removeObjectsInArray:batch];
}

[self parseResponse:responseData responseEncrypted:responseEncrypted];

Expand Down
2 changes: 1 addition & 1 deletion CleverTapSDK/CleverTapBuildInfo.h
Original file line number Diff line number Diff line change
@@ -1 +1 @@
#define WR_SDK_REVISION @"70302"
#define WR_SDK_REVISION @"70303"
142 changes: 84 additions & 58 deletions CleverTapSDK/InApps/CTInAppEvaluationManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -244,12 +244,9 @@ - (void)onBatchSent:(NSArray *)batchWithHeader withSuccess:(BOOL)success withQue
if (success) {
NSDictionary *header = batchWithHeader[0];
if (queueType == CTQueueTypeEvents) {
[self removeSentEvaluatedServerSideInAppIds:header];
[self removeSentSuppressedClientSideInApps:header];
}
else if (queueType == CTQueueTypeProfile) {
[self removeSentEvaluatedServerSideInAppIdsForProfile:header];
[self removeSentSuppressedClientSideInAppsForProfile:header];
// For combined queues, clean up both events and profile arrays proportionally
[self removeSentEvaluatedServerSideInAppIdsForCombined:header];
[self removeSentSuppressedClientSideInAppsForCombined:header];
}
Comment on lines +247 to 250
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Fix non-deterministic dequeue: count-based removal can over-remove.

Between header creation and onBatchSent, new items may be appended; removing by “current counts” can drop unsent items. Dequeue by header contents (identity), and snapshot arrays into the header to make it immutable at send-time. See proposed diffs in the removal methods and header creation below.

Additionally, consider guarding header access to avoid OOB in edge cases:

// outside the changed lines, at the start of onBatchSent:
if (batchWithHeader.count == 0 || ![batchWithHeader[0] isKindOfClass:[NSDictionary class]]) { return; }

🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="CleverTapSDK/InApps/CTInAppEvaluationManager.m"
echo "Repo root: $(pwd)"
if [ ! -f "$FILE" ]; then
  echo "File not found: $FILE"
  echo "Searching for files with similar name..."
  rg -n --hidden --no-ignore "CTInAppEvaluationManager.m" || true
  exit 0
fi
echo "---- Showing file (numbered) ----"
nl -ba "$FILE" | sed -n '1,400p'
echo "---- Searching for relevant symbols across repo ----"
rg -n --hidden --no-ignore -S "removeSentEvaluatedServerSideInAppIdsForCombined|removeSentSuppressedClientSideInAppsForCombined|onBatchSent|batchWithHeader" || true
echo "---- Context for removal methods in this file ----"
rg -n -S "removeSentEvaluatedServerSideInAppIdsForCombined" -C8 "$FILE" || true
rg -n -S "removeSentSuppressedClientSideInAppsForCombined" -C8 "$FILE" || true
echo "---- Context for onBatchSent ----"
rg -n -S "onBatchSent" -C8 "$FILE" || true
echo "---- Search for array manipulations / dequeue logic in file ----"
rg -n -S "removeObjectAtIndex|removeObjectsInRange|subarrayWithRange|count|mutableCopy|snapshot" -C4 "$FILE" || true

Length of output: 105


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="CleverTapSDK/InApps/CTInAppEvaluationManager.m"
echo "Repo root: $(pwd)"
if [ ! -f "$FILE" ]; then
  echo "File not found: $FILE"
  echo "Searching for files named CTInAppEvaluationManager.m..."
  rg -n --hidden --no-ignore "CTInAppEvaluationManager.m" || true
  echo "Searching for symbols containing CTInAppEvaluationManager..."
  rg -n --hidden --no-ignore "CTInAppEvaluationManager" || true
  exit 0
fi
echo "---- Showing file: $FILE (first 400 lines with numbers) ----"
if command -v cat >/dev/null 2>&1; then
  cat -n "$FILE" | sed -n '1,400p' || true
else
  sed -n '1,400p' "$FILE" || true
fi
echo "---- Searching repo for relevant symbols ----"
rg -n --hidden --no-ignore -S "removeSentEvaluatedServerSideInAppIdsForCombined|removeSentSuppressedClientSideInAppsForCombined|onBatchSent|batchWithHeader" || true
echo "---- Context for removal methods in this file ----"
rg -n -S "removeSentEvaluatedServerSideInAppIdsForCombined" -C8 "$FILE" || true
rg -n -S "removeSentSuppressedClientSideInAppsForCombined" -C8 "$FILE" || true
echo "---- Context for onBatchSent ----"
rg -n -S "onBatchSent" -C8 "$FILE" || true
echo "---- Search for array manipulations / dequeue logic in file ----"
rg -n -S "removeObjectAtIndex|removeObjectsInRange|subarrayWithRange|count|mutableCopy|snapshot|NSMakeRange" -C4 "$FILE" || true

Length of output: 37812


Fix non-deterministic dequeue: remove by identity or snapshot per-queue arrays

Removing N elements from the live arrays using current counts (removeObjectsInRange) can erase items appended after header creation. Make the header immutable per-queue or remove specific IDs by identity.

  • In CTInAppEvaluationManager.m — onBatchHeaderCreationForQueue: — snapshot per-queue arrays into the header (separate events vs profile arrays) or include per-queue counts; do not rely only on a single merged array/count.
  • In removeSentEvaluatedServerSideInAppIdsForCombined: and removeSentSuppressedClientSideInAppsForCombined: — remove by identity (iterate header IDs and remove the first matching object from the corresponding live array using indexOfObject/removeObjectAtIndex) instead of removeObjectsInRange(0, N).
  • Add a guard in onBatchSent: before accessing batchWithHeader[0]:

if (batchWithHeader.count == 0 || ![batchWithHeader[0] isKindOfClass:[NSDictionary class]]) { return; }

  • Update CTInAppEvaluationManagerTest.m to cover the race where items are appended between header creation and onBatchSent.
🤖 Prompt for AI Agents
In CleverTapSDK/InApps/CTInAppEvaluationManager.m around lines 247-250, the code
currently removes ranges from live arrays based on merged counts leading to
non-deterministic dequeues when items are appended after header creation; fix by
snapshotting per-queue arrays into the header (store separate events vs profile
arrays or per-queue counts) at onBatchHeaderCreationForQueue:, change
removeSentEvaluatedServerSideInAppIdsForCombined: and
removeSentSuppressedClientSideInAppsForCombined: to iterate header IDs and
remove each matching object from the live arrays by identity (find indexOfObject
and removeObjectAtIndex) instead of using removeObjectsInRange, and add a guard
in onBatchSent: that returns early if batchWithHeader.count == 0 or
batchWithHeader[0] is not an NSDictionary; also add a unit test in
CTInAppEvaluationManagerTest.m that reproduces appending items between header
creation and onBatchSent to validate the fix.

}
}
Expand All @@ -262,43 +259,60 @@ - (void)onAppLaunchedWithSuccess:(BOOL)success {
self.hasAppLaunchedFailed = !success;
}

- (void)removeSentEvaluatedServerSideInAppIds:(NSDictionary *)header {
NSArray *inapps_eval = header[CLTAP_INAPP_SS_EVAL_META_KEY];
if (inapps_eval && [inapps_eval count] > 0) {
NSUInteger len = inapps_eval.count > self.evaluatedServerSideInAppIds.count ? self.evaluatedServerSideInAppIds.count : inapps_eval.count;
[self.evaluatedServerSideInAppIds removeObjectsInRange:NSMakeRange(0, len)];
[self saveEvaluatedServerSideInAppIds];
}
}

- (void)removeSentSuppressedClientSideInApps:(NSDictionary *)header {
NSArray *suppresed_inapps = header[CLTAP_INAPP_SUPPRESSED_META_KEY];
if (suppresed_inapps && [suppresed_inapps count] > 0) {
NSUInteger len = suppresed_inapps.count > self.suppressedClientSideInApps.count ? self.suppressedClientSideInApps.count : suppresed_inapps.count;
[self.suppressedClientSideInApps removeObjectsInRange:NSMakeRange(0, len)];
[self saveSuppressedClientSideInApps];
}
}

- (void)removeSentEvaluatedServerSideInAppIdsForProfile:(NSDictionary *)header {
- (void)removeSentEvaluatedServerSideInAppIdsForCombined:(NSDictionary *)header {
NSArray *inapps_eval = header[CLTAP_INAPP_SS_EVAL_META_KEY];
if (inapps_eval && [inapps_eval count] > 0) {
NSUInteger len = inapps_eval.count > self.evaluatedServerSideInAppIdsForProfile.count ? self.evaluatedServerSideInAppIdsForProfile.count : inapps_eval.count;
[self.evaluatedServerSideInAppIdsForProfile removeObjectsInRange:NSMakeRange(0, len)];
[self saveEvaluatedServerSideInAppIdsForProfile];
// Remove from events array first, then profiles
NSUInteger eventsCount = [self.evaluatedServerSideInAppIds count];
NSUInteger profilesCount = [self.evaluatedServerSideInAppIdsForProfile count];
NSUInteger totalToRemove = [inapps_eval count];

// Remove from events array
NSUInteger removeFromEvents = MIN(eventsCount, totalToRemove);
if (removeFromEvents > 0) {
[self.evaluatedServerSideInAppIds removeObjectsInRange:NSMakeRange(0, removeFromEvents)];
[self saveEvaluatedServerSideInAppIds];
totalToRemove -= removeFromEvents;
}

// Remove remaining from profiles array
if (totalToRemove > 0) {
NSUInteger removeFromProfiles = MIN(profilesCount, totalToRemove);
if (removeFromProfiles > 0) {
[self.evaluatedServerSideInAppIdsForProfile removeObjectsInRange:NSMakeRange(0, removeFromProfiles)];
[self saveEvaluatedServerSideInAppIdsForProfile];
}
}
}
}
Comment on lines +262 to 287
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Dequeue by identity (not dynamic counts) to avoid dropping unsent IDs.

 - (void)removeSentEvaluatedServerSideInAppIdsForCombined:(NSDictionary *)header {
-    NSArray *inapps_eval = header[CLTAP_INAPP_SS_EVAL_META_KEY];
-    if (inapps_eval && [inapps_eval count] > 0) {
-        // Remove from events array first, then profiles
-        NSUInteger eventsCount = [self.evaluatedServerSideInAppIds count];
-        NSUInteger profilesCount = [self.evaluatedServerSideInAppIdsForProfile count];
-        NSUInteger totalToRemove = [inapps_eval count];
-
-        // Remove from events array
-        NSUInteger removeFromEvents = MIN(eventsCount, totalToRemove);
-        if (removeFromEvents > 0) {
-            [self.evaluatedServerSideInAppIds removeObjectsInRange:NSMakeRange(0, removeFromEvents)];
-            [self saveEvaluatedServerSideInAppIds];
-            totalToRemove -= removeFromEvents;
-        }
-
-        // Remove remaining from profiles array
-        if (totalToRemove > 0) {
-            NSUInteger removeFromProfiles = MIN(profilesCount, totalToRemove);
-            if (removeFromProfiles > 0) {
-                [self.evaluatedServerSideInAppIdsForProfile removeObjectsInRange:NSMakeRange(0, removeFromProfiles)];
-                [self saveEvaluatedServerSideInAppIdsForProfile];
-            }
-        }
-    }
+    NSArray *inapps_eval = header[CLTAP_INAPP_SS_EVAL_META_KEY];
+    if (![inapps_eval isKindOfClass:[NSArray class]] || inapps_eval.count == 0) return;
+
+    BOOL eventsChanged = NO, profilesChanged = NO;
+    for (id obj in inapps_eval) {
+        NSUInteger idx = [self.evaluatedServerSideInAppIds indexOfObject:obj];
+        if (idx != NSNotFound) {
+            [self.evaluatedServerSideInAppIds removeObjectAtIndex:idx];
+            eventsChanged = YES;
+            continue;
+        }
+        NSUInteger pidx = [self.evaluatedServerSideInAppIdsForProfile indexOfObject:obj];
+        if (pidx != NSNotFound) {
+            [self.evaluatedServerSideInAppIdsForProfile removeObjectAtIndex:pidx];
+            profilesChanged = YES;
+        }
+    }
+    if (eventsChanged)   [self.saveEvaluatedServerSideInAppIds self];
+    if (profilesChanged) [self saveEvaluatedServerSideInAppIdsForProfile];
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In CleverTapSDK/InApps/CTInAppEvaluationManager.m around lines 262 to 287, the
method currently drops the first N entries from local evaluated lists based on
the count of header inapps_eval, which can remove the wrong IDs; instead,
iterate the inapps_eval array and remove each specific ID from
evaluatedServerSideInAppIds and evaluatedServerSideInAppIdsForProfile by
identity/equality (not by positional ranges), updating and saving each list only
when an actual removal occurs; ensure you remove from events list first then
profiles, and call saveEvaluatedServerSideInAppIds and
saveEvaluatedServerSideInAppIdsForProfile after modifications.


- (void)removeSentSuppressedClientSideInAppsForProfile:(NSDictionary *)header {
NSArray *suppresed_inapps = header[CLTAP_INAPP_SUPPRESSED_META_KEY];
if (suppresed_inapps && [suppresed_inapps count] > 0) {
NSUInteger len = suppresed_inapps.count > self.suppressedClientSideInAppsForProfile.count ? self.suppressedClientSideInAppsForProfile.count : suppresed_inapps.count;
[self.suppressedClientSideInAppsForProfile removeObjectsInRange:NSMakeRange(0, len)];
[self saveSuppressedClientSideInAppsForProfile];
- (void)removeSentSuppressedClientSideInAppsForCombined:(NSDictionary *)header {
NSArray *suppressed_inapps = header[CLTAP_INAPP_SUPPRESSED_META_KEY];
if (suppressed_inapps && [suppressed_inapps count] > 0) {
// Remove from events array first, then profiles
NSUInteger eventsCount = [self.suppressedClientSideInApps count];
NSUInteger profilesCount = [self.suppressedClientSideInAppsForProfile count];
NSUInteger totalToRemove = [suppressed_inapps count];

// Remove from events array
NSUInteger removeFromEvents = MIN(eventsCount, totalToRemove);
if (removeFromEvents > 0) {
[self.suppressedClientSideInApps removeObjectsInRange:NSMakeRange(0, removeFromEvents)];
[self saveSuppressedClientSideInApps];
totalToRemove -= removeFromEvents;
}

// Remove remaining from profiles array
if (totalToRemove > 0) {
NSUInteger removeFromProfiles = MIN(profilesCount, totalToRemove);
if (removeFromProfiles > 0) {
[self.suppressedClientSideInAppsForProfile removeObjectsInRange:NSMakeRange(0, removeFromProfiles)];
[self saveSuppressedClientSideInAppsForProfile];
}
}
}
}
Comment on lines +289 to 314
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Apply the same identity-based removal for suppressed in-app entries.

 - (void)removeSentSuppressedClientSideInAppsForCombined:(NSDictionary *)header {
-    NSArray *suppressed_inapps = header[CLTAP_INAPP_SUPPRESSED_META_KEY];
-    if (suppressed_inapps && [suppressed_inapps count] > 0) {
-        // Remove from events array first, then profiles
-        NSUInteger eventsCount = [self.suppressedClientSideInApps count];
-        NSUInteger profilesCount = [self.suppressedClientSideInAppsForProfile count];
-        NSUInteger totalToRemove = [suppressed_inapps count];
-
-        // Remove from events array
-        NSUInteger removeFromEvents = MIN(eventsCount, totalToRemove);
-        if (removeFromEvents > 0) {
-            [self.suppressedClientSideInApps removeObjectsInRange:NSMakeRange(0, removeFromEvents)];
-            [self saveSuppressedClientSideInApps];
-            totalToRemove -= removeFromEvents;
-        }
-
-        // Remove remaining from profiles array
-        if (totalToRemove > 0) {
-            NSUInteger removeFromProfiles = MIN(profilesCount, totalToRemove);
-            if (removeFromProfiles > 0) {
-                [self.suppressedClientSideInAppsForProfile removeObjectsInRange:NSMakeRange(0, removeFromProfiles)];
-                [self saveSuppressedClientSideInAppsForProfile];
-            }
-        }
-    }
+    NSArray *suppressed_inapps = header[CLTAP_INAPP_SUPPRESSED_META_KEY];
+    if (![suppressed_inapps isKindOfClass:[NSArray class]] || suppressed_inapps.count == 0) return;
+
+    BOOL eventsChanged = NO, profilesChanged = NO;
+    for (id obj in suppressed_inapps) {
+        NSUInteger idx = [self.suppressedClientSideInApps indexOfObject:obj];
+        if (idx != NSNotFound) {
+            [self.suppressedClientSideInApps removeObjectAtIndex:idx];
+            eventsChanged = YES;
+            continue;
+        }
+        NSUInteger pidx = [self.suppressedClientSideInAppsForProfile indexOfObject:obj];
+        if (pidx != NSNotFound) {
+            [self.suppressedClientSideInAppsForProfile removeObjectAtIndex:pidx];
+            profilesChanged = YES;
+        }
+    }
+    if (eventsChanged)   [self saveSuppressedClientSideInApps];
+    if (profilesChanged) [self saveSuppressedClientSideInAppsForProfile];
 }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
CleverTapSDK/InApps/CTInAppEvaluationManager.m around lines 289-314: the current
implementation removes a fixed range of suppressed entries by FIFO count; change
it to identity-based removal instead — iterate the suppressed_inapps array and
for each suppressed entry read its in-app identifier (use the existing
identifier key, e.g. CLTAP_INAPP_ID or "id"), then try to find and remove the
matching entry from self.suppressedClientSideInApps (remove the found object and
call saveSuppressedClientSideInApps); if not found there, try to find and remove
it from self.suppressedClientSideInAppsForProfile (remove and call
saveSuppressedClientSideInAppsForProfile); ensure you only remove exact
identifier matches and handle persistence after each array modification.



- (BOOL)shouldSuppress:(NSDictionary *)inApp {
return [inApp[CLTAP_INAPP_IS_SUPPRESSED] boolValue];
}
Expand Down Expand Up @@ -380,29 +394,41 @@ - (void)updateTTL:(NSMutableDictionary *)inApp {
}

- (BatchHeaderKeyPathValues)onBatchHeaderCreationForQueue:(CTQueueType)queueType {
// Evaluation is done for events only at the moment,
// send the evaluated and suppressed ids in that queue header
if (queueType != CTQueueTypeEvents && queueType != CTQueueTypeProfile) {
return [NSMutableDictionary new];
}
NSMutableDictionary *header = [NSMutableDictionary new];
if (queueType == CTQueueTypeProfile) {
if ([self.evaluatedServerSideInAppIdsForProfile count] > 0) {
header[CLTAP_INAPP_SS_EVAL_META_KEY] = self.evaluatedServerSideInAppIdsForProfile;
}
if ([self.suppressedClientSideInAppsForProfile count] > 0) {
header[CLTAP_INAPP_SUPPRESSED_META_KEY] = self.suppressedClientSideInAppsForProfile;
}
}
else {
if ([self.evaluatedServerSideInAppIds count] > 0) {
header[CLTAP_INAPP_SS_EVAL_META_KEY] = self.evaluatedServerSideInAppIds;
}
if ([self.suppressedClientSideInApps count] > 0) {
header[CLTAP_INAPP_SUPPRESSED_META_KEY] = self.suppressedClientSideInApps;
}
}
return header;
// Evaluation is done for events and profiles,
// send the evaluated and suppressed ids in that queue header
if (queueType != CTQueueTypeEvents && queueType != CTQueueTypeProfile) {
return [NSMutableDictionary new];
}

NSMutableDictionary *header = [NSMutableDictionary new];

// For combined queues, merge both events and profile arrays
if (queueType == CTQueueTypeEvents) {
// Combine evaluated IDs from both events and profiles
NSMutableArray *combinedEvaluatedIds = [NSMutableArray array];
if ([self.evaluatedServerSideInAppIds count] > 0) {
[combinedEvaluatedIds addObjectsFromArray:self.evaluatedServerSideInAppIds];
}
if ([self.evaluatedServerSideInAppIdsForProfile count] > 0) {
[combinedEvaluatedIds addObjectsFromArray:self.evaluatedServerSideInAppIdsForProfile];
}
if ([combinedEvaluatedIds count] > 0) {
header[CLTAP_INAPP_SS_EVAL_META_KEY] = combinedEvaluatedIds;
}

// Combine suppressed IDs from both events and profiles
NSMutableArray *combinedSuppressedIds = [NSMutableArray array];
if ([self.suppressedClientSideInApps count] > 0) {
[combinedSuppressedIds addObjectsFromArray:self.suppressedClientSideInApps];
}
if ([self.suppressedClientSideInAppsForProfile count] > 0) {
[combinedSuppressedIds addObjectsFromArray:self.suppressedClientSideInAppsForProfile];
}
if ([combinedSuppressedIds count] > 0) {
header[CLTAP_INAPP_SUPPRESSED_META_KEY] = combinedSuppressedIds;
}
}
return header;
}
Comment on lines 396 to 432
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Snapshot header arrays to avoid mutation between build and send.

Insert immutable copies into the header; otherwise later appends mutate the same arrays referenced by the header.

-       if ([combinedEvaluatedIds count] > 0) {
-           header[CLTAP_INAPP_SS_EVAL_META_KEY] = combinedEvaluatedIds;
-       }
+       if (combinedEvaluatedIds.count > 0) {
+           header[CLTAP_INAPP_SS_EVAL_META_KEY] = [combinedEvaluatedIds copy];
+       }-       if ([combinedSuppressedIds count] > 0) {
-           header[CLTAP_INAPP_SUPPRESSED_META_KEY] = combinedSuppressedIds;
-       }
+       if (combinedSuppressedIds.count > 0) {
+           header[CLTAP_INAPP_SUPPRESSED_META_KEY] = [combinedSuppressedIds copy];
+       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- (BatchHeaderKeyPathValues)onBatchHeaderCreationForQueue:(CTQueueType)queueType {
// Evaluation is done for events only at the moment,
// send the evaluated and suppressed ids in that queue header
if (queueType != CTQueueTypeEvents && queueType != CTQueueTypeProfile) {
return [NSMutableDictionary new];
}
NSMutableDictionary *header = [NSMutableDictionary new];
if (queueType == CTQueueTypeProfile) {
if ([self.evaluatedServerSideInAppIdsForProfile count] > 0) {
header[CLTAP_INAPP_SS_EVAL_META_KEY] = self.evaluatedServerSideInAppIdsForProfile;
}
if ([self.suppressedClientSideInAppsForProfile count] > 0) {
header[CLTAP_INAPP_SUPPRESSED_META_KEY] = self.suppressedClientSideInAppsForProfile;
}
}
else {
if ([self.evaluatedServerSideInAppIds count] > 0) {
header[CLTAP_INAPP_SS_EVAL_META_KEY] = self.evaluatedServerSideInAppIds;
}
if ([self.suppressedClientSideInApps count] > 0) {
header[CLTAP_INAPP_SUPPRESSED_META_KEY] = self.suppressedClientSideInApps;
}
}
return header;
// Evaluation is done for events and profiles,
// send the evaluated and suppressed ids in that queue header
if (queueType != CTQueueTypeEvents && queueType != CTQueueTypeProfile) {
return [NSMutableDictionary new];
}
NSMutableDictionary *header = [NSMutableDictionary new];
// For combined queues, merge both events and profile arrays
if (queueType == CTQueueTypeEvents) {
// Combine evaluated IDs from both events and profiles
NSMutableArray *combinedEvaluatedIds = [NSMutableArray array];
if ([self.evaluatedServerSideInAppIds count] > 0) {
[combinedEvaluatedIds addObjectsFromArray:self.evaluatedServerSideInAppIds];
}
if ([self.evaluatedServerSideInAppIdsForProfile count] > 0) {
[combinedEvaluatedIds addObjectsFromArray:self.evaluatedServerSideInAppIdsForProfile];
}
if ([combinedEvaluatedIds count] > 0) {
header[CLTAP_INAPP_SS_EVAL_META_KEY] = combinedEvaluatedIds;
}
// Combine suppressed IDs from both events and profiles
NSMutableArray *combinedSuppressedIds = [NSMutableArray array];
if ([self.suppressedClientSideInApps count] > 0) {
[combinedSuppressedIds addObjectsFromArray:self.suppressedClientSideInApps];
}
if ([self.suppressedClientSideInAppsForProfile count] > 0) {
[combinedSuppressedIds addObjectsFromArray:self.suppressedClientSideInAppsForProfile];
}
if ([combinedSuppressedIds count] > 0) {
header[CLTAP_INAPP_SUPPRESSED_META_KEY] = combinedSuppressedIds;
}
}
return header;
}
- (BatchHeaderKeyPathValues)onBatchHeaderCreationForQueue:(CTQueueType)queueType {
// Evaluation is done for events and profiles,
// send the evaluated and suppressed ids in that queue header
if (queueType != CTQueueTypeEvents && queueType != CTQueueTypeProfile) {
return [NSMutableDictionary new];
}
NSMutableDictionary *header = [NSMutableDictionary new];
// For combined queues, merge both events and profile arrays
if (queueType == CTQueueTypeEvents) {
// Combine evaluated IDs from both events and profiles
NSMutableArray *combinedEvaluatedIds = [NSMutableArray array];
if ([self.evaluatedServerSideInAppIds count] > 0) {
[combinedEvaluatedIds addObjectsFromArray:self.evaluatedServerSideInAppIds];
}
if ([self.evaluatedServerSideInAppIdsForProfile count] > 0) {
[combinedEvaluatedIds addObjectsFromArray:self.evaluatedServerSideInAppIdsForProfile];
}
if (combinedEvaluatedIds.count > 0) {
header[CLTAP_INAPP_SS_EVAL_META_KEY] = [combinedEvaluatedIds copy];
}
// Combine suppressed IDs from both events and profiles
NSMutableArray *combinedSuppressedIds = [NSMutableArray array];
if ([self.suppressedClientSideInApps count] > 0) {
[combinedSuppressedIds addObjectsFromArray:self.suppressedClientSideInApps];
}
if ([self.suppressedClientSideInAppsForProfile count] > 0) {
[combinedSuppressedIds addObjectsFromArray:self.suppressedClientSideInAppsForProfile];
}
if (combinedSuppressedIds.count > 0) {
header[CLTAP_INAPP_SUPPRESSED_META_KEY] = [combinedSuppressedIds copy];
}
}
return header;
}
🤖 Prompt for AI Agents
In CleverTapSDK/InApps/CTInAppEvaluationManager.m around lines 396 to 432, the
header currently stores mutable arrays which can be mutated later; replace
assignments that put NSMutableArray instances into header with immutable copies
(e.g., use -copy or create NSArray copies) so the header contains immutable
snapshots. Specifically, when setting CLTAP_INAPP_SS_EVAL_META_KEY and
CLTAP_INAPP_SUPPRESSED_META_KEY assign [combinedEvaluatedIds copy] and
[combinedSuppressedIds copy] (or equivalent immutable copies) instead of the
mutable arrays to prevent later mutations from changing the header contents.


- (void)saveEvaluatedServerSideInAppIds {
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ let package = Package(
),
.binaryTarget(
name: "CleverTapSDK",
url: "https://d1new0xr8otir0.cloudfront.net/CleverTapSDK-7.3.2.xcframework.zip",
checksum: "e605b779f9725d52c9c73c6e317e098325a613116c03bce4376ad92a87d22f9f"
url: "https://d1new0xr8otir0.cloudfront.net/CleverTapSDK-7.3.3.xcframework.zip",
checksum: "9a3a8b7d8118e8d8c5b32744e3d979d051c634d3bddf7542089fb7f3dc3de500"
),
.target(
name: "CleverTapLocation",
Expand Down
2 changes: 1 addition & 1 deletion sdk-version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7.3.2
7.3.3