Release 2.15.2#740
Open
itmindbox wants to merge 61 commits into
Open
Conversation
Merge 'master' into 'develop' after release
…709) CocoaPods trunk sometimes returns 504 Gateway Timeout while the spec still lands in CocoaPods/Specs. Re-running the failed publish job then fails again with 409 "Unable to accept duplicate entry", keeping the release red even though it is actually done. Wrap pod trunk push in .github/pod-trunk-push.sh and treat 409 as a successful no-op so a single re-run closes the release cleanly.
…ailure (#706) * MOBILE-182: Pin DateFormatter to en_US_POSIX in MindboxLogger ISO-8601 primitive * MOBILE-182: Collapse duplicate ISO-8601 formatter into the MindboxLogger primitive * MOBILE-182: Assert dateTimeUtc literal shape on InAppShowFailure * MOBILE-182: Replace incremental pbxproj IDs with random 24-char hex --------- Co-authored-by: Vailence <utekeshev@mindbox.cloud>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
…device UUID (#714) * MOBILE-183 Unify date formatting on a single cached, thread-safe formatter * MOBILE-183 Persist MBPersistenceStorage dates as fixed-pattern UTC * MOBILE-183 Add DateFormatMigration for legacy persisted date strings * MOBILE-183 Snapshot isInstalled after migration in CoreController * MOBILE-183 Stabilize installed state and device UUID generation * MOBILE-183 Add detailed logging to DateFormatMigration * MOBILE-183 Handle 12h/24h hour cycle in DateFormatMigration * MOBILE-183 Run DateFormatMigration through the standard migration chain * MOBILE-183: Add deviceUUID/instanceId initialization tests * MOBILE-183: Align mock isInstalled with production and test date round-trips * MOBILE-183: Keep mock installationDate precision, derive isInstalled from marker * MOBILE-183: Address PR review — formatter caching, key reuse, Swift Testing migration - Replace NSLock+mutable cache with an immutable static formatter dictionary - Precompute Date+Extension formatters as static let - Derive DateFormatMigration date keys from UserDefaultsWrapper.Key; make formatters lazy - Rewrite DateFormatMigrationTests on Swift Testing; restore global defaults in deinit - Use an independent hour-cycle oracle (Locale.Components) instead of the production expression - Fix configutaion -> configuration typo in CoreController * MOBILE-183: Avoid force-unwrap in DateFormat formatter lookup Use a CaseIterable-built formatter cache with an on-demand fallback instead of force-unwrapping the dictionary lookup, satisfying the force_unwrapping rule. --------- Co-authored-by: Vailence <utekeshev@mindbox.cloud>
A delay of 0 (nil / "00:00:00" / invalid string all fall back to 0) arms the production timer for .now(), so it can present the in-app and clean up on its own before the test inspects state. The tests asserted a "scheduled but not yet presented" intermediate window that does not deterministically exist on that path, so they raced the timer — CI saw count==0, a nil presentationTime, and presentCallsCount==1. Assert only the deterministic end state and trigger showEligibleInapp only when the timer hasn't already; removal happens under `queue`, so presentation stays idempotent and fires exactly once on either path. Also add the missing isEmpty cleanup assertion to the zero/invalid-delay tests and an explicit getDelay(nil)==0 check, for parity with the no-delay case.
Swift Testing results are invisible in the legacy reporting path: HTML comes from xcpretty, which cannot parse them. Instead, scan now emits the junit report (trainer builds it from the xcresult, formatter-independent) plus the xcresult bundle itself; the workflow publishes a junit-based check/summary and uploads the xcresult as an artifact for full inspection in Xcode.
* MOBILE-0000: Publish develop coverage to GitHub Pages Coverage HTML is generated from the test run's xcresult by a dependency-free script (test bundles excluded from the numbers) and deployed with the official upload-pages-artifact/deploy-pages flow on every push to develop - no gh-pages branch, so the repo SPM clients mirror-clone does not grow. The trend survives full-site redeploys by carrying the published history.json forward into each new site (~150 bytes per deploy, capped at 365 points). The page is fully self-contained - inline CSS/JS, no external resources - with file search, expand/collapse, source links at the measured commit, dark theme and viewer-local timestamps. * MOBILE-0000: Add CSV/JSON export links to coverage site Raise the history cap from 365 to 1000 points: it bounds records, not days, and develop gets ~190 pushes/year, so 365 only covered ~2 years. 1000 points is still a ~220 KB JSON. coverage.csv is generated at build time next to index.html, so the export needs no JS and the page stays self-contained.
Since Flutter 3.29 the UI and platform threads are merged on iOS, so the synchronous work in public operations - JSON validation, body encoding, the CoreData write, in-app matching - runs on the host UI thread: 1-2.5 ms per call, 24-83 ms per burst of 25 on an iPhone 12, surfacing as >33ms interaction-delay hangs. Route the post-validation body of executeAsync/SyncOperation (all 6 overloads) and pushClicked (x2) through one serial eventQueue: callers return in ~2-4 us, order of public calls = order of DB writes = order of delivery. Bodies are still encoded on the caller (they are mutable classes the host may reuse right after the call), and track() stays synchronous on purpose: its skipNextDirectTrackVisit write must happen-before the didBecomeActive-driven trackDirect dispatch. executeSyncOperation completions now reach main on every path: the network result has hopped to main since 2021, the early validation errors (no configuration / no deviceUUID) were the inconsistent exception. observe() resolves the value under observeSemaphore but invokes the host completion after unlocking, removing a latent self-deadlock when a completion re-enters getDeviceUUID/getAPNSToken; delivery thread is unchanged. Nil-dependency paths log an .error instead of dropping events silently. Operation-name validation switches from a per-call NSRegularExpression (~3 us + allocations) to an allocation-free scalar scan (~16 ns), staying synchronous as a fail-fast guard; it is intentionally stricter for names with a trailing line terminator, which the ICU $-quirk used to accept (verified side-by-side; numbers and method in Profiling/MOBILE-208-operations.md on profiling/MOBILE-208-operations). Device A/B (iPhone 12, Release, 9 runs per side): per-call caller cost 0.8-2.6 ms -> 2-4 us; burst of 25 on main 24-83 ms -> 0.1-0.2 ms; interaction-delay hangs 27 -> 1; process CPU and thread count unchanged.
Swift Testing: OperationNameValidatorTests - parameterized charset cases (the three legacy cases migrate with the same polarity: TEST.- valid, тест/TESт invalid), the exact ASCII range-boundary neighbors of the scalar scan, unicode lookalikes/invisibles, and the intentional trailing-line-terminator tightening. MindboxOperationsTests - burst call order equals DB write order, body snapshotted at call time (host mutation after return doesn't leak), invalid name/JSON dropped with a sentinel proving the pipeline survives, executeSyncOperation completion on main for both the network and the early-error path. XCTest (needs the suite's init machinery): observe() re-entrancy regression - getAPNSToken called from inside the getDeviceUUID completion deadlocked while the completion was invoked under observeSemaphore. Verified against the old implementation: fails there with three timeouts (controller queue wedged), passes on the fix.
The suite resets process-wide singletons (PersistenceStorage.reset(), DB erase(), Mindbox.shared.assembly(), canScheduleOperations), so it must not interleave with other tests. The Mindbox scheme already runs the target serially (parallelizable=NO), but mark the suite .serialized to match the other stateful suites and drop the implicit dependency on that scheme flag.
…ad-operations Mobile-208: UI thread operations
Add Mindbox.xctestplan covering MindboxTests, MindboxNotificationsTests and MindboxLoggerTests, and point the scheme + unitTestLane at it. Now every test target runs in one simulator boot/build - MindboxNotificationsTests was never executed in CI before - while MindboxTests stays serial (shared singletons). Code coverage stays enabled via the plan, now also covering MindboxNotifications.framework.
The MindboxLoggerTests target sat empty while its tests lived in MindboxTests. Move the 4 pure-MindboxLogger test files plus their 2 mocks into it and drop a dead `@testable import Mindbox` from MBLoggerCoreDataManagerTests (it only exercises MindboxLogger types). Relocate the .dateFormatting tag - used solely by the moved DateFormatTests - into a Logger-local Tag+Extensions, and raise the target to iOS 13 since Swift Testing requires it. SDKLogManagerTests stays in MindboxTests: it tests SDKLogsManager, a Mindbox type, and genuinely needs the Mindbox import. All three targets run via the shared test plan; MindboxLoggerTests is now non-empty and green, MindboxTests still passes. pbxproj edits done with the xcodeproj gem, which also reflowed three unrelated PBXFileSystemSynchronizedRootGroup entries (semantically a no-op).
Keep the default Mindbox plan Unit-Tests-only so local Cmd+U and CI stay fast, and give Thread and Address Sanitizer their own plans to run on demand — a multi-config plan runs every config on Cmd+U and the sanitizer-instrumented runner is slow and flaky to launch. Move all three plans under TestPlans/ (also pulling AddressSanitizer back into the repo — it had been saved above the project root), and scope the Mindbox plan's coverage to the three product frameworks so test bundles are no longer instrumented. Published coverage is unchanged (~53.5%); a new product framework must be added to codeCoverageTargets to be measured.
Clear DEVELOPMENT_TEAM (was N39VVWZXXP) across all build configs so the project is team-agnostic: simulator builds and CI unit tests need no team, and nothing in CI signs for a device (only build/unit-test lanes, both on the simulator). Removes the signing warnings the hardcoded team produced for anyone not on it.
Align bundle identifiers on the cloud.<Target> scheme: the Logger targets still carried the cloud.organization.* Xcode template placeholder and MindboxTests used Mindbox.MindboxTests. These IDs are dev/CI-only — the SDK ships via CocoaPods and SPM (which set their own) and nothing reads them by string (no Bundle(identifier:)). Also set SUPPORTS_MACCATALYST = NO on MindboxLogger to match Mindbox and MindboxNotifications; it was the only framework still offering a Mac Catalyst destination.
Add threadSanitizerLane and addressSanitizerLane that run the unit tests under the Thread/Address Sanitizer test plans, each with its own output directory. Scaffolding for a future scheduled job — deliberately kept out of the PR/develop pipeline, since ThreadSanitizer stays red until the surfaced data races are fixed.
All three unit-test targets now deploy to iOS 13 — the minimum for Swift Testing — so new tests here build instead of failing, matching MindboxTests and MindboxLoggerTests. Product frameworks stay at iOS 12. (Xcode also re-sorted SUPPORTS_MACCATALYST alphabetically in the Logger config; no behavior change.)
MOBILE-0000: Unit tests via shared test plan + Thread/Address Sanitizer plans
Rewrite the three MindboxNotifications suites from XCTest to Swift Testing and
add suites for the previously untested push-format pipeline (strategy factory,
legacy/current strategies, formatter, validator), ImageFormat, the public
isMindboxPush/getMindboxPushData API, and MBPushNotification decoding. Target
coverage goes 64.6% -> 95.9% (6 -> 43 tests); no production code changed.
The async download test awaits a continuation instead of a 1s expectation,
removing the CI-starvation flakiness without needing a URLSession seam. Shared
payload/request builders and the UN test doubles live in NotificationTestFixtures;
suites carry subject tags (.pushParsing / .imageFormat / .notificationService /
.notificationContent) plus a .network tag so CI can exclude the networked test.
Known gap (flagged, no test): legacy defaultIosWithoutTitle pushes, where
aps.alert is a String rather than a {title, body} dict, are not recognized by
isMindboxPush/getMindboxPushData -- LegacyFormatStrategy expects an alert dict.
…ations MindboxNotifications links and depends on MindboxLogger but never imports or uses it, so the framework is dead weight in integrators' NSE/NCE extensions. Remove the link, build file, container proxy and target dependency. The main Mindbox target keeps its own MindboxLogger dependency.
…ons-coverage MOBILE-0000: Raise `MindboxNotifications` coverage to ~96%; drop unused `logger` dep
ios-sdk was the only SDK repo without Gitleaks; aligns secret scanning with android/flutter/react-native. Requires the org MINDBOX_GITLEAKS_LICENSE secret (Actions + Dependabot).
* MOBILE-206: Fix infinite layout loop in modal in-app element rebuild * MOBILE-206: Add regression tests for modal layout rebuild fix * MOBILE-206: PR Comm Fix --------- Co-authored-by: Vailence <utekeshev@mindbox.cloud>
…726) Full `bundle update` to clear Dependabot alerts. concurrent-ruby 1.3.6->1.3.7 fixes GHSA-h8w8/6wx8/wv3x. faraday 1.10.5->1.10.6 backports the GHSA-98m9-hrrm-r99r (CVE-2026-54297) NestedParamsEncoder fix to the 1.x line -- fastlane still pins faraday ~>1.0, so 2.14.3 is unreachable. fastlane 2.235.0->2.236.1; Gemfile and BUNDLED WITH unchanged.
MindboxLoggerTests was a manual PBXGroup, so each new test file required four hand-edited pbxproj entries. Convert it to a PBXFileSystemSynchronizedRootGroup (matching MindboxTests) so files added under the folder compile automatically.
Swift Testing suites covering MindboxError and the error/decodable models, the Logger/MBLogger facade, Core Data context helpers, log primitives, URL attributes and persistence internals; tags split by subsystem. Raises MindboxLogger framework line coverage from 54% to 91% via MindboxLoggerTests alone.
Convert LogStoreTrimmer/LoggerDatabaseLoader/MBLoggerCoreDataManager tests from XCTest to Swift Testing (async waits via continuations) and drop the two baseline-less measure perf tests. The manager suite is .serialized because its background-notification tests post to the global NotificationCenter that every manager observes — parallel execution would flip writesImmediately across tests. Adds a .trimming tag.
Stop FileManagerStoreURLTests deleting the shared Caches store (cross-suite flake under parallel run); make the LoggerPersistenceInternals setter round-trip assert a distinct value instead of a tautology; rename the assertion-free MBLogger tests as honest smoke tests; fix the .serialized rationale comment.
Convert both manual PBXGroups to PBXFileSystemSynchronizedRootGroups (matching MindboxTests/MindboxLoggerTests). MindboxNotificationsTests uses a generated Info.plist (GENERATE_INFOPLIST_FILE) like MindboxLoggerTests, so no membership exception is needed; TestPlans is target-less so schemes resolve plans by path.
Mobile-187: Logger coverage
… cycle (#728) * MOBILE-230: Fix executeSyncOperation dropping completion on invalid JSON Previously, passing an invalid JSON string to executeSyncOperation(json:completion:) caused the operation to be silently dropped on eventQueue without ever invoking the completion handler. This violated the public contract (completion always arrives on the main thread, either as .success or .failure). Fix: deliver .failure(.internalError(.parsing)) on the main thread when JSON validation fails, matching the delivery pattern used by MBEventRepository.send<T> for all other error paths. Test: syncInvalidJSONDeliversFailureOnMain asserts the completion is called as .failure on the main thread, and will fail (timeout ~10s) rather than hang if the regression recurs. * MOBILE-230: Deliver .validationError on invalid name/JSON for all sync overloads All three executeSyncOperation overloads now call completion(.failure(.validationError(...))) on the main thread when an invalid operation name is passed, matching the DoD requirement. The invalid-JSON branch in enqueueSyncEvent is unified through the same failSyncOperation helper, so both client-side validation paths produce a consistent .validationError with a ValidationMessage describing the failure location. Added test: syncInvalidNameDeliversFailureOnMain pins the regression for the name path. * MOBILE-230: Fix hour-cycle override in DateFormatMigration Locale.current encodes the device 12h/24h setting as an @Hours= keyword, so appending a second hours= produced a duplicate that ICU resolved to the first occurrence — defeating the override and leaving every legacy formatter on the device's own hour cycle. Strip any pre-existing hours/hc keyword before forcing the cycle so values written under the opposite cycle parse. * MOBILE-230: Distinguish sync operation validation error by location Parameterize failSyncOperation location so invalid JSON reports operationBody instead of operationSystemName, fix the misleading .parsing doc comment, and assert the error type and location in tests. --------- Co-authored-by: Vailence <utekeshev@mindbox.cloud>
#730) * MOBILE-213: Add MobileSdkShouldSendInAppTags feature toggle and gatedTags helper Introduce the MobileSdkShouldSendInAppTags feature flag (same semantics as MobileSdkShouldSendInAppShowError: disabled only when config sends false) and a single pure gatedTags(isTagsFeatureEnabled:) helper that collapses disabled or empty tag dictionaries to nil. Register the new source and test files. * MOBILE-213: Send in-app tags in Show and Click operations Gate tags inside InAppMessagesTracker via the injected FeatureToggleManager and add tags to the Inapp.Click body (InAppBody). Thread the message's tags into the click-tracking call site. Tracker method signatures for trackTargeting are also updated here; their call sites are wired in the Targeting/ShowFailure change. * MOBILE-213: Send in-app tags in Targeting and ShowFailure operations Add tags to the Inapp.Targeting body and to each Inapp.ShowFailure item, gated by the tags feature flag inside InappShowFailureManager. Thread tags through the InAppConfigurationDataFacade (trackTargeting, collectTargetingFailures via tagsByInappId, downloadImage) and the InappMapper call sites, plus the presentation error path in InappScheduleManager. * MOBILE-213: Merge in-app tags into WebView JS-bridge operations Add JSONValue.mergingInAppTags implementing the merge contract (set when absent or null, fill only missing keys when tags is an object, leave non-object tags untouched) and apply it to sync/async custom operation bodies in TransparentView, gated by the tags feature flag. Thread tags from InAppFormData through the ViewFactory into WebViewController and TransparentView. * MOBILE-213: Apply review fixes for in-app tags - Guard FeatureToggleManager.featureToggles with NSLock (cross-thread access) - Centralize tags gating in FeatureToggleManager.gatedTags(_:) - Drop hand-written encode(to:) in InAppShowBody/InAppBody - Collapse trackClick/trackTargeting into shared track helper - Build tagsByInappId without nested optionality, only for failed in-apps - Parse explicit MobileSdkShouldSendInAppTags=true in config fixture - Construct Settings.FeatureToggles directly in test helpers * MOBILE-213: Cover JS-bridge operation tags merging with TransparentView tests * MOBILE-213: Cover tagsByInappId building and tags propagation in InappMapper tests * MOBILE-213: Assert in-app tags propagate into show failure on presentation error * MOBILE-213: Use a fresh FeatureToggleManager per parsing test to avoid shared-state leakage * MOBILE-213: Fold Optional tags gating into a static FeatureToggleManager helper * MOBILE-213: Log tag merge key collisions and non-object tags for Android parity * MOBILE-213: Reject empty operation names when parsing JS-bridge operations * MOBILE-213: Make JS-bridge operation error message reflect all validation failures * MOBILE-213: Require explicit tags argument in WebView init to avoid silently dropping them * MOBILE-213: Assert absence of tags key (not null) in tracker omit cases * MOBILE-213: Conform EventRepositorySpy to cancelAllRequests requirement --------- Co-authored-by: Vailence <utekeshev@mindbox.cloud>
Standalone robustness for the existing show, independent of the cache/ prewarm work that follows in later PRs: - WebViewReadyChecker replaces the one-shot post-load bridge probe with a short poll, so a page whose module scripts evaluate a beat after `load` is not failed; a give-up after `init` closes the show instead of leaving a dead bridge on screen. - Init timeout reports webview_presentation_failed (bridge never appeared) vs webview_load_failed (page never loaded) via timeoutError. - Network and WebView build the User-Agent through one SDKUserAgent (fixes the "unknow" fallback typo). - MBContainer guards DI registration/resolution with a recursive lock. - The cached in-app config is written atomically.
WebViewReadyCheckerTests covers ready-first, retry-then-ready, give-up after the full budget, error-retries, and silent cancel. WebViewTimeout ErrorTests pins the presentation-failed vs load-failed split.
- InAppWebViewDataStore: one shared WKWebsiteDataStore for prewarm and shows — iOS 17+ an isolated persistent store under a fixed UUIDv5; below 17 .default() (product decision); cache kill-switch → .nonPersistent(). - InAppWebViewHTMLFetcher: the single HTML transport — a cookie-less session with its own small URLCache and revalidation when caching is on, else a cache-less ephemeral session. - InAppWebViewFactory: the one place a Mindbox WKWebView is configured. - The facade builds its WKWebView via the factory and fetches via the fetcher. - Feature toggles MobileSdkShouldPrewarmInAppWebView / MobileSdkShould CacheInAppWebView (absent key ⇒ on, kill switch); fetchDecodedConfig FromCache centralizes the lenient launch-time decode.
Pins the shared-store contract (one isolated persistent instance, exact UUIDv5), the cache toggle kill-switch semantics, the cookie-less revalidating fetch session, and the factory's store/UA wiring.
SDKUserAgentTests pins the User-Agent segment order/format and the "unknown" fallbacks — the network layer and the WebView share this one builder, and the backend slices traffic by the string. MBContainer ConcurrencyTests proves a container-scoped singleton is minted exactly once under 32 concurrent resolves (the recursive lock); verified non-flaky over 25 iterations and red when the lock is removed. Adds .userAgent and .dependencyInjection test tags.
A pre-warmed WKWebView can deliver navigation callbacks and JS messages left over from a previous load. Filter callbacks by navigation identity and gate messages until the show's own document commits. Inert until the prewarm path arms it via expectContentNavigation (PR3b).
Cold WebView shows pay for a WKWebView spin-up plus a full resource fetch every time. Prewarm resources into a shared, reusable instance in two stages off the init path — a cached-config head start (stage 1) and a fresh-config confirmation (stage 2) — then let the show borrow that warm instance instead of building a cold one. Navigation is pinned to SDK-issued documents, learned hosts are preconnected, and the instance is parked (blank load) or released under memory pressure. Gated by the prewarm feature toggle; borrowed loads arm the bridge staleness filter.
DataStore: the pre-iOS-17 product decision is already stated in full in the type doc; the inline note at `.default()` only repeated it (and its toggle-off line describes `shared()`, not this `instance` closure). LearnedHostsStore: "backed by PersistenceStorage" is obvious from the initializer.
These `// Add X` / `// Serialize` labels predate this feature but sit in a file it already reworks; each just echoes the descriptively named method below it. Kept the localState one — it carries a why (JS-side migration), not a restatement.
The develop rebase unioned Settings.FeatureToggles to four toggles (shouldSendInAppTags from MOBILE-213 plus the webview cache/prewarm pair). The memberwise-init call sites in four test files predated the union and were missing the added arguments — a semantic merge break the textual rebase couldn't catch. Compile-verified via the test target.
Refresh all gems via a lockless bundle install; fastlane 2.236.1 -> 2.237.0, cocoapods 1.16.2 -> 1.17.0. Lock now records Bundler 4.0.15 with CHECKSUMS, aligning with ios-app and CI's unpinned bundler.
Slowest unit test is ~7s and everything else is sub-second, so 60s keeps wide headroom while catching a real hang faster. 60s is also the effective floor — the allowance rounds up to the nearest minute.
The JS-bridge onError contract must match onValidationError: the
contents of data directly, without the {type, data} envelope.
createJSON() is unchanged - public API used by wrapper SDKs.
The convertToString() fallback had unquoted keys, trailing commas and misspelled errroKey/errroName — unparseable by any JS consumer. Flagged by Copilot on the PR; pre-existing, but it belongs to this contract.
Contributor
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Updates the release version to 2.15.2. Automated PR: merge release/2.15.2 into master