Skip to content

Latest commit

 

History

History
147 lines (113 loc) · 8.11 KB

File metadata and controls

147 lines (113 loc) · 8.11 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Development Commands

Setup

  • Run scripts/setup.sh to initialize the development environment - installs SwiftLint and sets up git hooks
  • Install xcodegen if not already installed: brew install xcodegen
  • Pre-commit hooks automatically run xcodegen and update podspec version from Constants.swift

Building and Testing

  • Build:
    • Xcode: Open SuperwallKit.xcodeproj in Xcode (auto-generated from project.yml)
    • Command Line: Use scripts/build.sh to build the framework via xcodebuild (automatically runs xcodegen)
  • Tests:
    • Xcode: Run tests using the SuperwallKitTests scheme in Xcode
    • Command Line: Use scripts/test.sh to run tests via xcodebuild (automatically runs xcodegen)
  • Linting: Use scripts/lint.sh to run SwiftLint with configuration from .swiftlint.yml
  • Project Generation: Run xcodegen to regenerate Xcode project from project.yml

Package Management

  • Swift Package Manager: Primary dependency management via Package.swift
  • CocoaPods: Also supported via SuperwallKit.podspec
  • Dependencies: superscript-ios-next at exact version 1.0.15 (slim binary-target distribution; replaces the legacy Superscript-iOS repo whose committed xcframework bloated clones)

Architecture Overview

SuperwallKit is an iOS SDK for remote paywall configuration and A/B testing. The architecture follows a dependency injection pattern centered around DependencyContainer.

Core Components

  • Superwall.swift: Main SDK entry point and public API
  • DependencyContainer: Central dependency injection container managing all core services
  • ConfigManager: Handles remote configuration from Superwall dashboard
  • PaywallManager: Manages paywall presentation and caching
  • StoreKitManager: Handles App Store purchases and transactions
  • IdentityManager: Manages user identity and attributes
  • NetworkManager: API communication with Superwall backend

Key Directories

  • Sources/SuperwallKit/: Main SDK source code
  • Sources/SuperwallKit/Paywall/: Paywall presentation, caching, and web view handling
  • Sources/SuperwallKit/StoreKit/: Purchase flow and transaction management
  • Sources/SuperwallKit/Config/: Remote configuration and feature flags
  • Sources/SuperwallKit/Analytics/: Event tracking and attribution
  • Sources/SuperwallKit/Dependencies/: Dependency injection framework
  • Tests/SuperwallKitTests/: Unit tests with mocks and test utilities

Data Flow

  1. SDK configuration happens through Superwall.configure()
  2. Remote config is fetched and managed by ConfigManager
  3. Paywall requests go through PaywallRequestManager -> PaywallManager
  4. Purchases are handled by StoreKitManager with automatic retry logic
  5. Events are tracked through the analytics system

Minimum Toolchain and Platforms

  • Xcode 26 / Swift 6.2 (swift-tools-version:6.2), iOS 15, macOS 12, watchOS 8. tvOS isn't supported (WebKit, SafariServices and Superscript have no tvOS build). CI builds iOS (tests), Mac Catalyst and visionOS (build-platforms.yml). Apple requires the iOS 26 SDK for App Store Connect uploads, so there's no reason to support older Xcodes.
  • Don't add #if compiler(...) checks for anything below 6.2; only gate APIs newer than that (e.g. compiler(>=6.3.2) for iOS 26.4 StoreKit APIs).
  • Don't add @available/#available checks or fallback branches for iOS 15 or older (StoreKit 2, sheet detents, UIMenu buttons and friends are always there).
  • The package pins swiftLanguageModes: [.v5]. Moving to Swift 6 language mode is a separate change.
  • Keep Package.swift, SuperwallKit.podspec and project.yml deployment targets in sync.

Code Conventions

  • 2-space indentation (enforced by SwiftLint)
  • Prefer Logger over print statements (enforced by custom lint rule)
  • Force unwrapping allowed but discouraged
  • Extensive use of protocol factories for dependency injection
  • Uses both StoreKit 1 and StoreKit 2 APIs with abstraction layer

Version Management

When bumping the version, update all three files:

  1. Sources/SuperwallKit/Misc/Constants.swift (line 21)
  2. SuperwallKit.podspec (s.version)
  3. CHANGELOG.md (add new version entry at top)
  • Follows semantic versioning
  • Never use an ## Unreleased heading in CHANGELOG.md. Unreleased changes always live under the next concrete version number (e.g. ## 4.16.4).
  • To pick that number, compare the version on develop with the version on master:
    • If develop's version is above master's, a release is already staged — add your entries to that existing top section. Do not bump again.
    • If develop's version equals master's, start the next release: add a new version section and bump all three files together (patch/minor/major per the change).

Testing

  • Mock objects follow naming pattern *Mock.swift
  • Tests are organized to mirror source structure
  • Uses combine publishers for async testing
  • Core Data testing uses in-memory store

Testing

  • This project uses Swift's Testing framework (not XCTest) for all unit tests.
  • Always use the Testing framework when writing new tests.

Workflows

  • When making changes to the SDK, always write a unit test for the new functionality.
  • Make sure to run the tests, ensuring they pass.
  • ALWAYS run scripts/lint.sh after making any code changes to check for formatting issues.
  • ALWAYS run swiftlint --fix on any files that have linting violations.
  • Remember: No trailing whitespace is allowed on any lines.
  • Update CHANGELOG.md for customer-facing changes: Include new API additions, bug fixes, and crash fixes. Focus on what the change does for developers, not internal implementation details. For example: "Added setIntegrationAttribute() method to enable setting individual attribution provider IDs" or "Fixed crash when handling expired subscriptions".

Pull Requests

When creating PRs, always include the checklist from .github/PULL_REQUEST_TEMPLATE.md:

  • All unit tests pass.
  • All UI tests pass.
  • Demo project builds and runs on iOS.
  • Demo project builds and runs on Mac Catalyst.
  • Demo project builds and runs on visionOS.
  • I added/updated tests or detailed why my change isn't tested.
  • I added an entry to the CHANGELOG.md for any breaking changes, enhancements, or bug fixes.
  • I have run swiftlint in the main directory and fixed any issues.
  • I have updated the SDK documentation as well as the online docs.
  • I have reviewed the contributing guide

Device IP enrichment

DeviceIPCollector owns session-local, timestamped IP observations. Collection is for the MMP and stays off unless the backend's attributionOptions.mmp.enabled is on; SuperwallKit's privacy manifest doesn't declare it, so apps that turn the MMP on declare it themselves. Keep collection independent of enrichment success and purchase/configuration latency, preserve each family separately, and filter stale cached ipV4/ipV6 fields before exposing device attributes. Do not add customer attributes or authentication headers to the public IP lookup requests. The IPv6 lookup must stay on a connection that may only use IPv6 (NWConnection with the IP version set): its host is reachable over both, so URLSession could quietly report IPv4.

Integration device identifiers

AttributionFetcher refreshes IDFV/IDFA/ATT when setting integration attributes and on app activation after an integration has been configured. Compare the complete refreshed snapshot, not just provider IDs. Sync device values into user attributes (the server integration router reads those) only when that snapshot changes, since every sync costs a user_attributes event; explicit nulls clear stale IDs after ATT revocation. ATT is serialized as a numeric string in integration attributes. idfv, idfa and attStatus are SDK-owned user-attribute keys — document any change to that set in the changelog and the online docs. Don't gate the IDFA on the ATT status: identifierForAdvertisers already filters the all-zero id.