From 8e6ef7c96fec2ea28855c369707100172a3b6f0a Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Tue, 27 Oct 2026 12:59:35 -0400 Subject: [PATCH 1/2] chore!: remove the public API of disabled package traits Gate trait-specific public API with the same `!SWIFT_PACKAGE || ` condition as its implementation, so an SPM app that turns a trait off gets a compile error instead of an option that silently does nothing. - SessionReplay: PostHogConfig.sessionReplay/sessionReplayConfig, PostHogSessionReplayConfig, PostHogSessionReplayConsoleLogConfig, PostHogConsoleLogEntry/Level, start/stopSessionRecording(), isSessionReplayActive(), View.postHogNoMask() and the replay SPI. - CrashReporting: PostHogErrorTrackingConfig.autoCapture. - Surveys: PostHogConfig.surveys/surveysConfig, PostHogSurveysConfig, PostHogSurveysDelegate and its callback typealiases, the display, response and next-question types. Add api/posthog-ios.public-api.no-traits.txt (SPM build with --disable-default-traits), checked by `make apiCheck` in CI and regenerated by `make apiUpdate`. Co-Authored-By: Claude Opus 5.5 --- .changeset/v4-major-release.md | 1 + .github/workflows/build.yml | 2 +- CONTRIBUTING.md | 4 +- Makefile | 4 + .../PostHogErrorTrackingConfig.swift | 54 ++-- .../Surveys/PostHogSurvey+Display.swift | 2 +- PostHog/PostHogConfig.swift | 77 ++--- PostHog/PostHogSDK.swift | 35 ++- .../Console Logs/PostHogConsoleLogEntry.swift | 2 +- .../Console Logs/PostHogConsoleLogLevel.swift | 2 +- PostHog/Replay/PostHogReplayIntegration.swift | 32 +- .../Replay/PostHogSessionReplayConfig.swift | 2 +- ...PostHogSessionReplayConsoleLogConfig.swift | 2 +- .../Surveys/Models/PostHogDisplaySurvey.swift | 98 +++--- .../PostHogDisplaySurveyAppearance.swift | 222 +++++++------- .../Models/PostHogDisplaySurveyQuestion.swift | 278 +++++++++--------- .../Models/PostHogNextSurveyQuestion.swift | 26 +- .../Models/PostHogSurveyResponse.swift | 214 +++++++------- .../Surveys/PostHogSurveyIntegration.swift | 13 - PostHog/Surveys/PostHogSurveysConfig.swift | 186 ++++++------ .../PostHogSurveysDefaultDelegate.swift | 222 +++++++------- .../SwiftUI/PostHogNoMaskViewModifier.swift | 92 +++--- .../PostHogIntegrationInstallationTest.swift | 29 +- .../PostHogOptOutPersistenceTest.swift | 6 +- ...PostHogPushNotificationSwizzlingTest.swift | 6 +- api/posthog-ios.public-api.no-traits.txt | 268 +++++++++++++++++ scripts/check-public-api.sh | 85 ++++-- 27 files changed, 1132 insertions(+), 832 deletions(-) create mode 100644 api/posthog-ios.public-api.no-traits.txt diff --git a/.changeset/v4-major-release.md b/.changeset/v4-major-release.md index 594345bfa2..c96152bcc0 100644 --- a/.changeset/v4-major-release.md +++ b/.changeset/v4-major-release.md @@ -25,6 +25,7 @@ PostHog iOS 4.0. Every breaking change below names what to use instead. **Added** - Add `SessionReplay`, `CrashReporting` and `Surveys` Swift package traits (on by default) so SwiftPM apps can leave out session replay and the vendored libwebp, the vendored PLCrashReporter, or the survey engine and UI. + - Turning a trait off also removes its public API, so code that configures a left-out feature fails to compile instead of silently doing nothing: `sessionReplay`, `sessionReplayConfig`, `startSessionRecording()`, `stopSessionRecording()`, `isSessionReplayActive()` and `postHogNoMask()` need `SessionReplay`; `errorTrackingConfig.autoCapture` needs `CrashReporting`; `surveys`, `surveysConfig` and the survey delegate and display types need `Surveys`. **Removed** diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 303abf7ecd..8bc0ea84bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,7 +64,7 @@ jobs: - uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 with: xcode-version: latest-stable - - name: Check public API snapshot + - name: Check public API snapshots (default build and package traits off) run: make apiCheck build-sdk-min-toolchain: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ffbe42f50f..babbfc5f67 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -78,6 +78,8 @@ This section is for external contributors. PostHog maintainers (members of the P When reviewing or fixing someone else's PR, don't ask for or open an issue. Note an external contributor's public API change when it has neither an agreed issue nor an API-defining published spec; the pre-implementation issue policy above is not a gate on that review work. -`make apiCheck` checks [api/posthog-ios.public-api.txt](api/posthog-ios.public-api.txt), as CI does. `make apiUpdate` regenerates it only after intentional public API changes; review the snapshot diff. Both commands build the SDK and extract its public symbol graph, requiring Xcode with the iOS SDK, xcpretty and Python 3. A diff in that file means your change touches public API. +`make apiCheck` checks [api/posthog-ios.public-api.txt](api/posthog-ios.public-api.txt) and [api/posthog-ios.public-api.no-traits.txt](api/posthog-ios.public-api.no-traits.txt), as CI does. `make apiUpdate` regenerates them only after intentional public API changes; review the snapshot diff. Both commands build the SDK and extract its public symbol graph, requiring Xcode with the iOS SDK, xcpretty and Python 3. A diff in either file means your change touches public API. + +Public API that only works with a package trait (`SessionReplay`, `CrashReporting` or `Surveys`) is gated with the same `#if !SWIFT_PACKAGE || ` condition as its implementation, so it is removed when the trait is off. The `no-traits` snapshot is the Swift Package Manager build with every trait off; a trait-specific symbol showing up there means its gate is missing. Above all, thank you for contributing! diff --git a/Makefile b/Makefile index a1ac45f1a4..faeddc8c13 100644 --- a/Makefile +++ b/Makefile @@ -242,11 +242,15 @@ lint: swiftFormatCheck swiftLintCheck api: periphery scan +# Checks both public API snapshots: the default build, and the Swift Package Manager build with every +# package trait off (api/posthog-ios.public-api.no-traits.txt), where trait-gated public API is removed. apiCheck: scripts/check-public-api.sh + scripts/check-public-api.sh --no-traits apiUpdate: scripts/check-public-api.sh --update + scripts/check-public-api.sh --update --no-traits # requires gem and brew # xcpretty needs 'export LANG=en_US.UTF-8' diff --git a/PostHog/ErrorTracking/PostHogErrorTrackingConfig.swift b/PostHog/ErrorTracking/PostHogErrorTrackingConfig.swift index 0d58b8f7c8..3f89c07e54 100644 --- a/PostHog/ErrorTracking/PostHogErrorTrackingConfig.swift +++ b/PostHog/ErrorTracking/PostHogErrorTrackingConfig.swift @@ -14,32 +14,34 @@ import Foundation @objc public class PostHogErrorTrackingConfig: NSObject { // MARK: - Crash Reporting - /// Enable crash autocapture - /// - /// When enabled, the SDK will capture the following crash types: - /// - Mach exceptions (e.g., `EXC_BAD_ACCESS`, `EXC_CRASH`) - /// - POSIX signals (e.g., `SIGSEGV`, `SIGABRT`, `SIGBUS`) - /// - Uncaught `NSException`s - /// - /// Crashes are persisted to disk and sent as `$exception` events with level "fatal" **on the next app launch** - /// - /// - Note: Crash reporting is automatically disabled when a debugger is attached, - /// as the debugger intercepts signals before the crash handler can process them. - /// - /// Default: false - private var _autoCapture: Bool = false - - /// Whether crash autocapture is enabled. - /// - /// When enabled, fatal crashes are persisted and sent as `$exception` events on the next launch. - /// With Swift Package Manager, this requires the `CrashReporting` package trait, which is enabled by default. - /// Default: `false`. - @available(watchOS, unavailable, message: "Crash autocapture is not available on watchOS") - @available(visionOS, unavailable, message: "Crash autocapture is not available on visionOS") - @objc public var autoCapture: Bool { - get { _autoCapture } - set { _autoCapture = newValue } - } + #if !SWIFT_PACKAGE || CrashReporting + /// Enable crash autocapture + /// + /// When enabled, the SDK will capture the following crash types: + /// - Mach exceptions (e.g., `EXC_BAD_ACCESS`, `EXC_CRASH`) + /// - POSIX signals (e.g., `SIGSEGV`, `SIGABRT`, `SIGBUS`) + /// - Uncaught `NSException`s + /// + /// Crashes are persisted to disk and sent as `$exception` events with level "fatal" **on the next app launch** + /// + /// - Note: Crash reporting is automatically disabled when a debugger is attached, + /// as the debugger intercepts signals before the crash handler can process them. + /// + /// Default: false + private var _autoCapture: Bool = false + + /// Whether crash autocapture is enabled. + /// + /// When enabled, fatal crashes are persisted and sent as `$exception` events on the next launch. + /// With Swift Package Manager, this is only available with the `CrashReporting` package trait, which is enabled by default. + /// Default: `false`. + @available(watchOS, unavailable, message: "Crash autocapture is not available on watchOS") + @available(visionOS, unavailable, message: "Crash autocapture is not available on visionOS") + @objc public var autoCapture: Bool { + get { _autoCapture } + set { _autoCapture = newValue } + } + #endif // MARK: - In-App Detection Configuration diff --git a/PostHog/Models/Surveys/PostHogSurvey+Display.swift b/PostHog/Models/Surveys/PostHogSurvey+Display.swift index cd4bcb7eed..dbe643abed 100644 --- a/PostHog/Models/Surveys/PostHogSurvey+Display.swift +++ b/PostHog/Models/Surveys/PostHogSurvey+Display.swift @@ -1,4 +1,4 @@ -#if os(iOS) || TESTING +#if (os(iOS) || TESTING) && (!SWIFT_PACKAGE || Surveys) import Foundation extension PostHogSurvey { diff --git a/PostHog/PostHogConfig.swift b/PostHog/PostHogConfig.swift index 928185ce9f..cda91eeeaf 100644 --- a/PostHog/PostHogConfig.swift +++ b/PostHog/PostHogConfig.swift @@ -399,10 +399,12 @@ public typealias BeforeSendBlock = @Sendable (PostHogEvent) -> PostHogEvent? /// - iOS does not send `X-POSTHOG-WINDOW-ID` because mobile apps do not have a per-window/tab concept /// - Existing values for these headers will be overwritten @objc public var tracingHeaders: [String]? + #endif + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) /// Enable Recording of Session Replays for iOS /// - /// With Swift Package Manager, this requires the `SessionReplay` package trait, which is enabled by default. + /// With Swift Package Manager, this is only available with the `SessionReplay` package trait, which is enabled by default. /// /// Note: Ingestion controls (sampling, feature flags, and event triggers) are currently applied using AND logic. /// All configured conditions must be satisfied for recording to start. @@ -411,7 +413,7 @@ public typealias BeforeSendBlock = @Sendable (PostHogEvent) -> PostHogEvent? @objc public var sessionReplay: Bool = false /// Session Replay configuration /// - /// With Swift Package Manager, this requires the `SessionReplay` package trait, which is enabled by default. + /// With Swift Package Manager, this is only available with the `SessionReplay` package trait, which is enabled by default. @objc public let sessionReplayConfig: PostHogSessionReplayConfig = .init() #endif @@ -424,35 +426,37 @@ public typealias BeforeSendBlock = @Sendable (PostHogEvent) -> PostHogEvent? /// Mutate fields on `config.logs` before calling `PostHogSDK.setup(_:)`. @objc public let logs: PostHogLogsConfig = .init() - /// Enable mobile surveys - /// - /// With Swift Package Manager, this requires the `Surveys` package trait, which is enabled by default. - /// - /// Default: true - /// - /// Note: Event triggers will only work with the instance that first enables surveys. - /// In case of multiple instances, please make sure you are capturing events on the instance that has config.surveys = true - @available(watchOS, unavailable, message: "Surveys are only available on iOS") - @available(macOS, unavailable, message: "Surveys are only available on iOS") - @available(tvOS, unavailable, message: "Surveys are only available on iOS") - @available(visionOS, unavailable, message: "Surveys are only available on iOS") - @objc public var surveys: Bool { - get { _surveys } - set { _surveys = newValue } - } + #if !SWIFT_PACKAGE || Surveys + /// Enable mobile surveys + /// + /// With Swift Package Manager, this is only available with the `Surveys` package trait, which is enabled by default. + /// + /// Default: true + /// + /// Note: Event triggers will only work with the instance that first enables surveys. + /// In case of multiple instances, please make sure you are capturing events on the instance that has config.surveys = true + @available(watchOS, unavailable, message: "Surveys are only available on iOS") + @available(macOS, unavailable, message: "Surveys are only available on iOS") + @available(tvOS, unavailable, message: "Surveys are only available on iOS") + @available(visionOS, unavailable, message: "Surveys are only available on iOS") + @objc public var surveys: Bool { + get { _surveys } + set { _surveys = newValue } + } - /// Configuration for mobile survey presentation and localization. - /// - /// Mutate fields on `config.surveysConfig` or replace this object before calling setup. - /// With Swift Package Manager, this requires the `Surveys` package trait, which is enabled by default. - @available(watchOS, unavailable, message: "Surveys are only available on iOS") - @available(macOS, unavailable, message: "Surveys are only available on iOS") - @available(tvOS, unavailable, message: "Surveys are only available on iOS") - @available(visionOS, unavailable, message: "Surveys are only available on iOS") - @objc public var surveysConfig: PostHogSurveysConfig { - get { _surveysConfig } - set { _surveysConfig = newValue } - } + /// Configuration for mobile survey presentation and localization. + /// + /// Mutate fields on `config.surveysConfig` or replace this object before calling setup. + /// With Swift Package Manager, this is only available with the `Surveys` package trait, which is enabled by default. + @available(watchOS, unavailable, message: "Surveys are only available on iOS") + @available(macOS, unavailable, message: "Surveys are only available on iOS") + @available(tvOS, unavailable, message: "Surveys are only available on iOS") + @available(visionOS, unavailable, message: "Surveys are only available on iOS") + @objc public var surveysConfig: PostHogSurveysConfig { + get { _surveysConfig } + set { _surveysConfig = newValue } + } + #endif /// Optional custom URLSessionConfiguration for network requests /// If not set, uses URLSessionConfiguration.default @@ -519,7 +523,7 @@ public typealias BeforeSendBlock = @Sendable (PostHogEvent) -> PostHogEvent? func getIntegrations() -> [PostHogIntegration] { var integrations: [PostHogIntegration] = [] - #if os(iOS) || os(macOS) || os(tvOS) + #if (os(iOS) || os(macOS) || os(tvOS)) && (!SWIFT_PACKAGE || CrashReporting) if errorTrackingConfig.autoCapture { integrations.append(PostHogErrorTrackingAutoCaptureIntegration()) } @@ -535,15 +539,18 @@ public typealias BeforeSendBlock = @Sendable (PostHogEvent) -> PostHogEvent? if tracingHeaders?.isEmpty == false { integrations.append(PostHogTracingHeadersIntegration()) } + #endif + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) if sessionReplay { integrations.append(PostHogReplayIntegration()) } + #endif + #if os(iOS) && (!SWIFT_PACKAGE || Surveys) if _surveys { integrations.append(PostHogSurveyIntegration()) } - #endif #if os(iOS) || targetEnvironment(macCatalyst) @@ -581,9 +588,11 @@ public typealias BeforeSendBlock = @Sendable (PostHogEvent) -> PostHogEvent? } #endif - var _surveys: Bool = true // swiftlint:disable:this identifier_name + #if !SWIFT_PACKAGE || Surveys + var _surveys: Bool = true // swiftlint:disable:this identifier_name - var _surveysConfig: PostHogSurveysConfig = .init() // swiftlint:disable:this identifier_name + var _surveysConfig: PostHogSurveysConfig = .init() // swiftlint:disable:this identifier_name + #endif /// Hook that allows to sanitize the event /// The hook is called before the event is cached or sent over the wire diff --git a/PostHog/PostHogSDK.swift b/PostHog/PostHogSDK.swift index 432916837e..de967d0a76 100644 --- a/PostHog/PostHogSDK.swift +++ b/PostHog/PostHogSDK.swift @@ -121,7 +121,7 @@ let maxRetryDelay = 30.0 /// `nil` before `setup(_:)` is called. @objc public private(set) var logger: PostHogLogger? - #if os(iOS) + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) private weak var _replayIntegration: PostHogReplayIntegration? /// Same shape as `queue`/`replayQueue` above: written under `setupLock` by install/uninstall, /// read from any caller thread by `buildProperties`. @@ -130,13 +130,16 @@ let maxRetryDelay = 30.0 set { setupLock.withLock { _replayIntegration = newValue } } } - private weak var surveysIntegration: PostHogSurveyIntegration? /// Whether `optIn()` starts session replay: whether the app wants it, from `config.sessionReplay` /// and the app's last `startSessionRecording()`/`stopSessionRecording()` while opted in, so consent /// doesn't override a stop. Internal stops don't change it. Guarded by `setupLock`. private var startSessionRecordingOnOptIn = true #endif + #if os(iOS) && (!SWIFT_PACKAGE || Surveys) + private weak var surveysIntegration: PostHogSurveyIntegration? + #endif + /// Shared singleton SDK instance used by most applications. /// /// Call `setup(_:)` once with a `PostHogConfig` before using capture APIs. @@ -317,7 +320,7 @@ let maxRetryDelay = 30.0 // this only reconciles an existing local identity. reconcileBootstrapIdentityIfNeeded() - #if os(iOS) + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) // Launched opted out: optIn() starts replay only when the config does startSessionRecordingOnOptIn = config.sessionReplay #endif @@ -750,7 +753,7 @@ let maxRetryDelay = 30.0 } // only Session replay requires $window_id, so we set as the same as $session_id. // the backend might fallback to $session_id if $window_id is not present next. - #if os(iOS) + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) if !appendSharedProps, isSessionReplayActive() { props["$window_id"] = sessionId } @@ -2772,14 +2775,14 @@ let maxRetryDelay = 30.0 } } - #if os(iOS) + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) // Replay restarts in a new session (a no-op under React Native, which owns the session id) if startSessionRecordingOnOptIn { sessionManager.startSession() } #endif installIntegrations() - #if os(iOS) + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) if !startSessionRecordingOnOptIn { // install() starts recording; undo it if the app had stopped replay before opting out replayIntegration?.stop() @@ -2887,7 +2890,7 @@ let maxRetryDelay = 30.0 replayQueue = nil logsQueue = nil pushSubscriptionHandler = nil - #if os(iOS) + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) startSessionRecordingOnOptIn = true #endif replayDebugPropertiesLock.withLock { @@ -2926,7 +2929,7 @@ let maxRetryDelay = 30.0 lastScreenLock.withLock { _lastScreenName = nil } } - #if os(iOS) + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) /** Starts session recording. @@ -3090,7 +3093,7 @@ let maxRetryDelay = 30.0 return postHog } - #if os(iOS) + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) /// Returns whether session replay is currently recording. /// /// - Returns: `true` only when replay is active, a session ID exists, and remote config allows recording. @@ -3356,12 +3359,14 @@ let maxRetryDelay = 30.0 installed.append(integration) - #if os(iOS) - // TODO: Decouple these two integrations from PostHogSDK intance + // TODO: Decouple these two integrations from PostHogSDK intance + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) if let replayIntegration = integration as? PostHogReplayIntegration { self.replayIntegration = replayIntegration } + #endif + #if os(iOS) && (!SWIFT_PACKAGE || Surveys) if let surveysIntegration = integration as? PostHogSurveyIntegration { self.surveysIntegration = surveysIntegration } @@ -3373,7 +3378,7 @@ let maxRetryDelay = 30.0 installedIntegrations = installed } - #if os(iOS) + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) private func installReplayIntegration() { guard replayIntegration == nil else { return } @@ -3428,8 +3433,10 @@ let maxRetryDelay = 30.0 } installedIntegrations = [] - #if os(iOS) + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) replayIntegration = nil + #endif + #if os(iOS) && (!SWIFT_PACKAGE || Surveys) surveysIntegration = nil #endif } @@ -3801,7 +3808,7 @@ let maxRetryDelay = 30.0 } #endif - #if os(iOS) + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) func getReplayIntegration() -> PostHogReplayIntegration? { getIntegration() } diff --git a/PostHog/Replay/Plugins/Console Logs/PostHogConsoleLogEntry.swift b/PostHog/Replay/Plugins/Console Logs/PostHogConsoleLogEntry.swift index f2f01e4563..14be75e31d 100644 --- a/PostHog/Replay/Plugins/Console Logs/PostHogConsoleLogEntry.swift +++ b/PostHog/Replay/Plugins/Console Logs/PostHogConsoleLogEntry.swift @@ -5,7 +5,7 @@ // Created by Ioannis Josephides on 09/05/2025. // -#if os(iOS) +#if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) import Foundation /** diff --git a/PostHog/Replay/Plugins/Console Logs/PostHogConsoleLogLevel.swift b/PostHog/Replay/Plugins/Console Logs/PostHogConsoleLogLevel.swift index c00fd52170..b4428eda68 100644 --- a/PostHog/Replay/Plugins/Console Logs/PostHogConsoleLogLevel.swift +++ b/PostHog/Replay/Plugins/Console Logs/PostHogConsoleLogLevel.swift @@ -5,7 +5,7 @@ // Created by Ioannis Josephides on 09/05/2025. // -#if os(iOS) +#if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) import Foundation /// The severity level of a console log entry. diff --git a/PostHog/Replay/PostHogReplayIntegration.swift b/PostHog/Replay/PostHogReplayIntegration.swift index 5e7970a9b3..0733cd4902 100644 --- a/PostHog/Replay/PostHogReplayIntegration.swift +++ b/PostHog/Replay/PostHogReplayIntegration.swift @@ -1850,34 +1850,10 @@ #endif #elseif os(iOS) - import UIKit - - // Stub for SPM builds with the `SessionReplay` trait disabled, where the replay engine and libwebp - // are not compiled. It is never installed, so the SDK's replay calls see no integration. - final class PostHogReplayIntegration: PostHogIntegration { - var requiresSwizzling: Bool { false } - - func install(_: PostHogSDK) -> PostHogIntegrationInstallResult { - .skipped(.disabledByPackageTrait) - } - - func uninstall(_: PostHogSDK) { /* no-op */ } - func start() { /* no-op */ } - func stop() { /* no-op */ } - func isActive() -> Bool { - false - } - func captureBridgeSnapshot(episodeFirstFrame _: Bool, window _: UIWindow? = nil) -> Bool { - false - } - func debugProperties() -> [String: Any] { - [:] - } - - #if TESTING - static func clearInstalls() { /* no-op */ } - #endif - } + // SPM builds with the `SessionReplay` trait disabled don't compile the replay engine, libwebp or + // the replay API. This type only namespaces the mask-region helpers in PostHogMaskSettleAnalysis.swift, + // which `postHogMask()` still uses to keep autocapture out of masked regions. + enum PostHogReplayIntegration {} #endif // swiftlint:enable cyclomatic_complexity file_length diff --git a/PostHog/Replay/PostHogSessionReplayConfig.swift b/PostHog/Replay/PostHogSessionReplayConfig.swift index 76419a3e5c..316f5bf664 100644 --- a/PostHog/Replay/PostHogSessionReplayConfig.swift +++ b/PostHog/Replay/PostHogSessionReplayConfig.swift @@ -4,7 +4,7 @@ // // Created by Manoel Aranda Neto on 19.03.24. // -#if os(iOS) +#if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) import Foundation /// Configuration for iOS session replay capture. diff --git a/PostHog/Replay/PostHogSessionReplayConsoleLogConfig.swift b/PostHog/Replay/PostHogSessionReplayConsoleLogConfig.swift index a77c6a33da..4e5b7ac6c7 100644 --- a/PostHog/Replay/PostHogSessionReplayConsoleLogConfig.swift +++ b/PostHog/Replay/PostHogSessionReplayConsoleLogConfig.swift @@ -5,7 +5,7 @@ // Created by Ioannis Josephides on 09/05/2025. // -#if os(iOS) +#if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) import Foundation /// Configuration for console log capture inside session replay recordings. diff --git a/PostHog/Surveys/Models/PostHogDisplaySurvey.swift b/PostHog/Surveys/Models/PostHogDisplaySurvey.swift index 444f049d50..216f1c6b85 100644 --- a/PostHog/Surveys/Models/PostHogDisplaySurvey.swift +++ b/PostHog/Surveys/Models/PostHogDisplaySurvey.swift @@ -7,55 +7,57 @@ import Foundation -/// A model representing a PostHog survey to be displayed to users -@objc public class PostHogDisplaySurvey: NSObject, Identifiable { - /// Unique identifier for the survey - public let id: String - /// Name of the survey - public let name: String - /// Array of questions to be presented in the survey - public let questions: [PostHogDisplaySurveyQuestion] - /// The question to show when restoring an unfinished survey. Zero for a new survey. - public let initialQuestionIndex: Int - /// Optional appearance configuration for customizing the survey's look and feel - public let appearance: PostHogDisplaySurveyAppearance? - /// Optional date indicating when the survey should start being shown - public let startDate: Date? - /// Optional date indicating when the survey should stop being shown - public let endDate: Date? +#if !SWIFT_PACKAGE || Surveys + /// A model representing a PostHog survey to be displayed to users + @objc public class PostHogDisplaySurvey: NSObject, Identifiable { + /// Unique identifier for the survey + public let id: String + /// Name of the survey + public let name: String + /// Array of questions to be presented in the survey + public let questions: [PostHogDisplaySurveyQuestion] + /// The question to show when restoring an unfinished survey. Zero for a new survey. + public let initialQuestionIndex: Int + /// Optional appearance configuration for customizing the survey's look and feel + public let appearance: PostHogDisplaySurveyAppearance? + /// Optional date indicating when the survey should start being shown + public let startDate: Date? + /// Optional date indicating when the survey should stop being shown + public let endDate: Date? - init( - id: String, - name: String, - questions: [PostHogDisplaySurveyQuestion], - appearance: PostHogDisplaySurveyAppearance?, - startDate: Date?, - endDate: Date?, - initialQuestionIndex: Int = 0 - ) { - self.id = id - self.name = name - self.questions = questions - self.appearance = appearance - self.startDate = startDate - self.endDate = endDate - self.initialQuestionIndex = initialQuestionIndex - super.init() + init( + id: String, + name: String, + questions: [PostHogDisplaySurveyQuestion], + appearance: PostHogDisplaySurveyAppearance?, + startDate: Date?, + endDate: Date?, + initialQuestionIndex: Int = 0 + ) { + self.id = id + self.name = name + self.questions = questions + self.appearance = appearance + self.startDate = startDate + self.endDate = endDate + self.initialQuestionIndex = initialQuestionIndex + super.init() + } } -} -/// Type of rating display for survey rating questions -@objc public enum PostHogDisplaySurveyRatingType: Int { - /// Display numeric rating options - case number - /// Display emoji rating options - case emoji -} + /// Type of rating display for survey rating questions + @objc public enum PostHogDisplaySurveyRatingType: Int { + /// Display numeric rating options + case number + /// Display emoji rating options + case emoji + } -/// Content type for text-based survey elements -@objc public enum PostHogDisplaySurveyTextContentType: Int { - /// Content should be rendered as HTML - case html - /// Content should be rendered as plain text - case text -} + /// Content type for text-based survey elements + @objc public enum PostHogDisplaySurveyTextContentType: Int { + /// Content should be rendered as HTML + case html + /// Content should be rendered as plain text + case text + } +#endif diff --git a/PostHog/Surveys/Models/PostHogDisplaySurveyAppearance.swift b/PostHog/Surveys/Models/PostHogDisplaySurveyAppearance.swift index 935f8e4a04..78d1d9fa6d 100644 --- a/PostHog/Surveys/Models/PostHogDisplaySurveyAppearance.swift +++ b/PostHog/Surveys/Models/PostHogDisplaySurveyAppearance.swift @@ -7,122 +7,124 @@ import Foundation -/// Model that describes the appearance customization of a PostHog survey -@objc public class PostHogDisplaySurveyAppearance: NSObject { - // General - /// Optional font family to use throughout the survey - public let fontFamily: String? - /// Optional background color as web color (e.g. "#FFFFFF" or "white") - public let backgroundColor: String? - /// Optional border color as web color - public let borderColor: String? +#if !SWIFT_PACKAGE || Surveys + /// Model that describes the appearance customization of a PostHog survey + @objc public class PostHogDisplaySurveyAppearance: NSObject { + // General + /// Optional font family to use throughout the survey + public let fontFamily: String? + /// Optional background color as web color (e.g. "#FFFFFF" or "white") + public let backgroundColor: String? + /// Optional border color as web color + public let borderColor: String? - // Submit button - /// Optional background color for the submit button as web color - public let submitButtonColor: String? - /// Optional custom text for the submit button - public let submitButtonText: String? - /// Optional text color for the submit button as web color - public let submitButtonTextColor: String? + // Submit button + /// Optional background color for the submit button as web color + public let submitButtonColor: String? + /// Optional custom text for the submit button + public let submitButtonText: String? + /// Optional text color for the submit button as web color + public let submitButtonTextColor: String? - // Text colors - /// Optional primary text color as web color (for questions, close button, etc.) - public let textColor: String? - /// Optional color for description text as web color - public let descriptionTextColor: String? + // Text colors + /// Optional primary text color as web color (for questions, close button, etc.) + public let textColor: String? + /// Optional color for description text as web color + public let descriptionTextColor: String? - // Rating buttons - /// Optional color for rating buttons as web color - public let ratingButtonColor: String? - /// Optional color for active/selected rating buttons as web color - public let ratingButtonActiveColor: String? + // Rating buttons + /// Optional color for rating buttons as web color + public let ratingButtonColor: String? + /// Optional color for active/selected rating buttons as web color + public let ratingButtonActiveColor: String? - // Input - /// Optional background color for input fields as web color - public let inputBackground: String? - /// Optional text color for input fields as web color - public let inputTextColor: String? - /// Optional placeholder text for input fields - public let placeholder: String? + // Input + /// Optional background color for input fields as web color + public let inputBackground: String? + /// Optional text color for input fields as web color + public let inputTextColor: String? + /// Optional placeholder text for input fields + public let placeholder: String? - // Display - /// Optional delay in seconds before showing the survey - public let surveyPopupDelaySeconds: TimeInterval? + // Display + /// Optional delay in seconds before showing the survey + public let surveyPopupDelaySeconds: TimeInterval? - // Thank you message - /// Whether to show a thank you message after survey completion - public let displayThankYouMessage: Bool - /// Optional header text for the thank you message - public let thankYouMessageHeader: String? - /// Optional description text for the thank you message - public let thankYouMessageDescription: String? - /// Optional content type for the thank you message description - public let thankYouMessageDescriptionContentType: PostHogDisplaySurveyTextContentType? - /// Optional text for the close button in the thank you message - public let thankYouMessageCloseButtonText: String? + // Thank you message + /// Whether to show a thank you message after survey completion + public let displayThankYouMessage: Bool + /// Optional header text for the thank you message + public let thankYouMessageHeader: String? + /// Optional description text for the thank you message + public let thankYouMessageDescription: String? + /// Optional content type for the thank you message description + public let thankYouMessageDescriptionContentType: PostHogDisplaySurveyTextContentType? + /// Optional text for the close button in the thank you message + public let thankYouMessageCloseButtonText: String? - // Intro screen - /// Whether to show an intro screen before the first question - public let displayIntroScreen: Bool - /// Optional header text for the intro screen - public let introScreenHeader: String? - /// Optional description text for the intro screen - public let introScreenDescription: String? - /// Optional content type for the intro screen description - public let introScreenDescriptionContentType: PostHogDisplaySurveyTextContentType? - /// Optional text for the button that advances past the intro screen - public let introScreenButtonText: String? + // Intro screen + /// Whether to show an intro screen before the first question + public let displayIntroScreen: Bool + /// Optional header text for the intro screen + public let introScreenHeader: String? + /// Optional description text for the intro screen + public let introScreenDescription: String? + /// Optional content type for the intro screen description + public let introScreenDescriptionContentType: PostHogDisplaySurveyTextContentType? + /// Optional text for the button that advances past the intro screen + public let introScreenButtonText: String? - init( - fontFamily: String?, - backgroundColor: String?, - borderColor: String?, - submitButtonColor: String?, - submitButtonText: String?, - submitButtonTextColor: String?, - textColor: String?, - descriptionTextColor: String?, - ratingButtonColor: String?, - ratingButtonActiveColor: String?, - inputBackground: String?, - inputTextColor: String?, - placeholder: String?, - surveyPopupDelaySeconds: TimeInterval?, - displayThankYouMessage: Bool, - thankYouMessageHeader: String?, - thankYouMessageDescription: String?, - thankYouMessageDescriptionContentType: PostHogDisplaySurveyTextContentType?, - thankYouMessageCloseButtonText: String?, - displayIntroScreen: Bool = false, - introScreenHeader: String? = nil, - introScreenDescription: String? = nil, - introScreenDescriptionContentType: PostHogDisplaySurveyTextContentType? = nil, - introScreenButtonText: String? = nil - ) { - self.fontFamily = fontFamily - self.backgroundColor = backgroundColor - self.borderColor = borderColor - self.submitButtonColor = submitButtonColor - self.submitButtonText = submitButtonText - self.submitButtonTextColor = submitButtonTextColor - self.textColor = textColor - self.descriptionTextColor = descriptionTextColor - self.ratingButtonColor = ratingButtonColor - self.ratingButtonActiveColor = ratingButtonActiveColor - self.inputBackground = inputBackground - self.inputTextColor = inputTextColor - self.placeholder = placeholder - self.surveyPopupDelaySeconds = surveyPopupDelaySeconds - self.displayThankYouMessage = displayThankYouMessage - self.thankYouMessageHeader = thankYouMessageHeader - self.thankYouMessageDescription = thankYouMessageDescription - self.thankYouMessageDescriptionContentType = thankYouMessageDescriptionContentType - self.thankYouMessageCloseButtonText = thankYouMessageCloseButtonText - self.displayIntroScreen = displayIntroScreen - self.introScreenHeader = introScreenHeader - self.introScreenDescription = introScreenDescription - self.introScreenDescriptionContentType = introScreenDescriptionContentType - self.introScreenButtonText = introScreenButtonText - super.init() + init( + fontFamily: String?, + backgroundColor: String?, + borderColor: String?, + submitButtonColor: String?, + submitButtonText: String?, + submitButtonTextColor: String?, + textColor: String?, + descriptionTextColor: String?, + ratingButtonColor: String?, + ratingButtonActiveColor: String?, + inputBackground: String?, + inputTextColor: String?, + placeholder: String?, + surveyPopupDelaySeconds: TimeInterval?, + displayThankYouMessage: Bool, + thankYouMessageHeader: String?, + thankYouMessageDescription: String?, + thankYouMessageDescriptionContentType: PostHogDisplaySurveyTextContentType?, + thankYouMessageCloseButtonText: String?, + displayIntroScreen: Bool = false, + introScreenHeader: String? = nil, + introScreenDescription: String? = nil, + introScreenDescriptionContentType: PostHogDisplaySurveyTextContentType? = nil, + introScreenButtonText: String? = nil + ) { + self.fontFamily = fontFamily + self.backgroundColor = backgroundColor + self.borderColor = borderColor + self.submitButtonColor = submitButtonColor + self.submitButtonText = submitButtonText + self.submitButtonTextColor = submitButtonTextColor + self.textColor = textColor + self.descriptionTextColor = descriptionTextColor + self.ratingButtonColor = ratingButtonColor + self.ratingButtonActiveColor = ratingButtonActiveColor + self.inputBackground = inputBackground + self.inputTextColor = inputTextColor + self.placeholder = placeholder + self.surveyPopupDelaySeconds = surveyPopupDelaySeconds + self.displayThankYouMessage = displayThankYouMessage + self.thankYouMessageHeader = thankYouMessageHeader + self.thankYouMessageDescription = thankYouMessageDescription + self.thankYouMessageDescriptionContentType = thankYouMessageDescriptionContentType + self.thankYouMessageCloseButtonText = thankYouMessageCloseButtonText + self.displayIntroScreen = displayIntroScreen + self.introScreenHeader = introScreenHeader + self.introScreenDescription = introScreenDescription + self.introScreenDescriptionContentType = introScreenDescriptionContentType + self.introScreenButtonText = introScreenButtonText + super.init() + } } -} +#endif diff --git a/PostHog/Surveys/Models/PostHogDisplaySurveyQuestion.swift b/PostHog/Surveys/Models/PostHogDisplaySurveyQuestion.swift index 1e2fd49fba..c5a866199d 100644 --- a/PostHog/Surveys/Models/PostHogDisplaySurveyQuestion.swift +++ b/PostHog/Surveys/Models/PostHogDisplaySurveyQuestion.swift @@ -7,154 +7,156 @@ import Foundation -/// Base class for all survey question types -@objc public class PostHogDisplaySurveyQuestion: NSObject { - /// The question ID, empty if none - @objc public let id: String - /// The main question text to display - @objc public let question: String - /// Optional additional description or context for the question - @objc public let questionDescription: String? - /// Content type for the question description (HTML or plain text) - @objc public let questionDescriptionContentType: PostHogDisplaySurveyTextContentType - /// Whether the question can be skipped - @objc public let isOptional: Bool - /// Optional custom text for the question's action button - @objc public let buttonText: String? +#if !SWIFT_PACKAGE || Surveys + /// Base class for all survey question types + @objc public class PostHogDisplaySurveyQuestion: NSObject { + /// The question ID, empty if none + @objc public let id: String + /// The main question text to display + @objc public let question: String + /// Optional additional description or context for the question + @objc public let questionDescription: String? + /// Content type for the question description (HTML or plain text) + @objc public let questionDescriptionContentType: PostHogDisplaySurveyTextContentType + /// Whether the question can be skipped + @objc public let isOptional: Bool + /// Optional custom text for the question's action button + @objc public let buttonText: String? - init( - id: String, - question: String, - questionDescription: String?, - questionDescriptionContentType: PostHogDisplaySurveyTextContentType?, - isOptional: Bool, - buttonText: String? - ) { - self.id = id - self.question = question - self.questionDescription = questionDescription - self.questionDescriptionContentType = questionDescriptionContentType ?? .text - self.isOptional = isOptional - self.buttonText = buttonText - super.init() + init( + id: String, + question: String, + questionDescription: String?, + questionDescriptionContentType: PostHogDisplaySurveyTextContentType?, + isOptional: Bool, + buttonText: String? + ) { + self.id = id + self.question = question + self.questionDescription = questionDescription + self.questionDescriptionContentType = questionDescriptionContentType ?? .text + self.isOptional = isOptional + self.buttonText = buttonText + super.init() + } } -} -/// Represents an open-ended question where users can input free-form text -@objc public class PostHogDisplayOpenQuestion: PostHogDisplaySurveyQuestion { /**/ } + /// Represents an open-ended question where users can input free-form text + @objc public class PostHogDisplayOpenQuestion: PostHogDisplaySurveyQuestion { /**/ } -/// Represents a question with a clickable link -@objc public class PostHogDisplayLinkQuestion: PostHogDisplaySurveyQuestion { - /// The URL that will be opened when the link is clicked - public let link: String? + /// Represents a question with a clickable link + @objc public class PostHogDisplayLinkQuestion: PostHogDisplaySurveyQuestion { + /// The URL that will be opened when the link is clicked + public let link: String? - init( - id: String, - question: String, - questionDescription: String?, - questionDescriptionContentType: PostHogDisplaySurveyTextContentType?, - isOptional: Bool, - buttonText: String?, - link: String? - ) { - self.link = link - super.init( - id: id, - question: question, - questionDescription: questionDescription, - questionDescriptionContentType: questionDescriptionContentType, - isOptional: isOptional, - buttonText: buttonText - ) + init( + id: String, + question: String, + questionDescription: String?, + questionDescriptionContentType: PostHogDisplaySurveyTextContentType?, + isOptional: Bool, + buttonText: String?, + link: String? + ) { + self.link = link + super.init( + id: id, + question: question, + questionDescription: questionDescription, + questionDescriptionContentType: questionDescriptionContentType, + isOptional: isOptional, + buttonText: buttonText + ) + } } -} -/// Represents a rating question where users can select a rating from a scale -@objc public class PostHogDisplayRatingQuestion: PostHogDisplaySurveyQuestion { - /// The type of rating scale (numbers, emoji) - public let ratingType: PostHogDisplaySurveyRatingType - /// The lower bound of the rating scale - public let scaleLowerBound: Int - /// The upper bound of the rating scale - public let scaleUpperBound: Int - /// The label for the lower bound of the rating scale - public let lowerBoundLabel: String - /// The label for the upper bound of the rating scale - public let upperBoundLabel: String - /// Whether selecting a rating submits the answer without a submit button - public let skipSubmitButton: Bool + /// Represents a rating question where users can select a rating from a scale + @objc public class PostHogDisplayRatingQuestion: PostHogDisplaySurveyQuestion { + /// The type of rating scale (numbers, emoji) + public let ratingType: PostHogDisplaySurveyRatingType + /// The lower bound of the rating scale + public let scaleLowerBound: Int + /// The upper bound of the rating scale + public let scaleUpperBound: Int + /// The label for the lower bound of the rating scale + public let lowerBoundLabel: String + /// The label for the upper bound of the rating scale + public let upperBoundLabel: String + /// Whether selecting a rating submits the answer without a submit button + public let skipSubmitButton: Bool - init( - id: String, - question: String, - questionDescription: String?, - questionDescriptionContentType: PostHogDisplaySurveyTextContentType?, - isOptional: Bool, - buttonText: String?, - ratingType: PostHogDisplaySurveyRatingType, - scaleLowerBound: Int, - scaleUpperBound: Int, - lowerBoundLabel: String, - upperBoundLabel: String, - skipSubmitButton: Bool = false - ) { - self.ratingType = ratingType - self.scaleLowerBound = scaleLowerBound - self.scaleUpperBound = scaleUpperBound - self.lowerBoundLabel = lowerBoundLabel - self.upperBoundLabel = upperBoundLabel - self.skipSubmitButton = skipSubmitButton - super.init( - id: id, - question: question, - questionDescription: questionDescription, - questionDescriptionContentType: questionDescriptionContentType, - isOptional: isOptional, - buttonText: buttonText - ) + init( + id: String, + question: String, + questionDescription: String?, + questionDescriptionContentType: PostHogDisplaySurveyTextContentType?, + isOptional: Bool, + buttonText: String?, + ratingType: PostHogDisplaySurveyRatingType, + scaleLowerBound: Int, + scaleUpperBound: Int, + lowerBoundLabel: String, + upperBoundLabel: String, + skipSubmitButton: Bool = false + ) { + self.ratingType = ratingType + self.scaleLowerBound = scaleLowerBound + self.scaleUpperBound = scaleUpperBound + self.lowerBoundLabel = lowerBoundLabel + self.upperBoundLabel = upperBoundLabel + self.skipSubmitButton = skipSubmitButton + super.init( + id: id, + question: question, + questionDescription: questionDescription, + questionDescriptionContentType: questionDescriptionContentType, + isOptional: isOptional, + buttonText: buttonText + ) + } } -} -/// Represents a multiple or single choice question where users can select one or more options -@objc public class PostHogDisplayChoiceQuestion: PostHogDisplaySurveyQuestion { - /// The list of options for the user to choose from - public let choices: [String] - /// Whether the question includes an "other" option for users to input free-form text - public let hasOpenChoice: Bool - /// Whether the options should be shuffled to randomize the order - public let shuffleOptions: Bool - /// Whether the user can select multiple options - public let isMultipleChoice: Bool - /// Whether selection should submit automatically for single-choice questions without an open choice - public let skipSubmitButton: Bool + /// Represents a multiple or single choice question where users can select one or more options + @objc public class PostHogDisplayChoiceQuestion: PostHogDisplaySurveyQuestion { + /// The list of options for the user to choose from + public let choices: [String] + /// Whether the question includes an "other" option for users to input free-form text + public let hasOpenChoice: Bool + /// Whether the options should be shuffled to randomize the order + public let shuffleOptions: Bool + /// Whether the user can select multiple options + public let isMultipleChoice: Bool + /// Whether selection should submit automatically for single-choice questions without an open choice + public let skipSubmitButton: Bool - var shouldAutoSubmit: Bool { skipSubmitButton && !isMultipleChoice && !hasOpenChoice } + var shouldAutoSubmit: Bool { skipSubmitButton && !isMultipleChoice && !hasOpenChoice } - init( - id: String, - question: String, - questionDescription: String?, - questionDescriptionContentType: PostHogDisplaySurveyTextContentType?, - isOptional: Bool, - buttonText: String?, - choices: [String], - hasOpenChoice: Bool, - shuffleOptions: Bool, - isMultipleChoice: Bool, - skipSubmitButton: Bool = false - ) { - self.choices = choices - self.hasOpenChoice = hasOpenChoice - self.shuffleOptions = shuffleOptions - self.isMultipleChoice = isMultipleChoice - self.skipSubmitButton = skipSubmitButton - super.init( - id: id, - question: question, - questionDescription: questionDescription, - questionDescriptionContentType: questionDescriptionContentType, - isOptional: isOptional, - buttonText: buttonText - ) + init( + id: String, + question: String, + questionDescription: String?, + questionDescriptionContentType: PostHogDisplaySurveyTextContentType?, + isOptional: Bool, + buttonText: String?, + choices: [String], + hasOpenChoice: Bool, + shuffleOptions: Bool, + isMultipleChoice: Bool, + skipSubmitButton: Bool = false + ) { + self.choices = choices + self.hasOpenChoice = hasOpenChoice + self.shuffleOptions = shuffleOptions + self.isMultipleChoice = isMultipleChoice + self.skipSubmitButton = skipSubmitButton + super.init( + id: id, + question: question, + questionDescription: questionDescription, + questionDescriptionContentType: questionDescriptionContentType, + isOptional: isOptional, + buttonText: buttonText + ) + } } -} +#endif diff --git a/PostHog/Surveys/Models/PostHogNextSurveyQuestion.swift b/PostHog/Surveys/Models/PostHogNextSurveyQuestion.swift index 4e09350a66..bb5d2839a7 100644 --- a/PostHog/Surveys/Models/PostHogNextSurveyQuestion.swift +++ b/PostHog/Surveys/Models/PostHogNextSurveyQuestion.swift @@ -7,17 +7,19 @@ import Foundation -/// A model representing the next state of the survey progression. -@objc public class PostHogNextSurveyQuestion: NSObject { - /// The index of the next question to be displayed (0-based) - public let questionIndex: Int - /// Whether all questions have been answered and the survey is complete - /// Depending on the survey appearance configuration, you may want to show the "Thank you" message or dismiss the survey at this point - public let isSurveyCompleted: Bool +#if !SWIFT_PACKAGE || Surveys + /// A model representing the next state of the survey progression. + @objc public class PostHogNextSurveyQuestion: NSObject { + /// The index of the next question to be displayed (0-based) + public let questionIndex: Int + /// Whether all questions have been answered and the survey is complete + /// Depending on the survey appearance configuration, you may want to show the "Thank you" message or dismiss the survey at this point + public let isSurveyCompleted: Bool - init(questionIndex: Int, isSurveyCompleted: Bool) { - self.questionIndex = questionIndex - self.isSurveyCompleted = isSurveyCompleted - super.init() + init(questionIndex: Int, isSurveyCompleted: Bool) { + self.questionIndex = questionIndex + self.isSurveyCompleted = isSurveyCompleted + super.init() + } } -} +#endif diff --git a/PostHog/Surveys/Models/PostHogSurveyResponse.swift b/PostHog/Surveys/Models/PostHogSurveyResponse.swift index f39b79ac18..392d9c2129 100644 --- a/PostHog/Surveys/Models/PostHogSurveyResponse.swift +++ b/PostHog/Surveys/Models/PostHogSurveyResponse.swift @@ -7,117 +7,119 @@ import Foundation -/// A model representing a user's response to a survey question -@objc @objcMembers -public class PostHogSurveyResponse: NSObject { - /// The response kind: link, rating, open-ended, single-choice, or multiple-choice. - public let type: PostHogSurveyResponseType - /// Whether a link was clicked (for link questions) - public let linkClicked: Bool? - /// The numeric rating value (for rating questions) - public let ratingValue: Int? - /// The text response (for open questions) - public let textValue: String? - /// The selected options (for multiple or single choice questions) - public let selectedOptions: [String]? +#if !SWIFT_PACKAGE || Surveys + /// A model representing a user's response to a survey question + @objc @objcMembers + public class PostHogSurveyResponse: NSObject { + /// The response kind: link, rating, open-ended, single-choice, or multiple-choice. + public let type: PostHogSurveyResponseType + /// Whether a link was clicked (for link questions) + public let linkClicked: Bool? + /// The numeric rating value (for rating questions) + public let ratingValue: Int? + /// The text response (for open questions) + public let textValue: String? + /// The selected options (for multiple or single choice questions) + public let selectedOptions: [String]? - private init( - type: PostHogSurveyResponseType, - linkClicked: Bool? = nil, - ratingValue: Int? = nil, - textValue: String? = nil, - multipleChoiceValues: [String]? = nil - ) { - self.type = type - self.linkClicked = linkClicked - self.ratingValue = ratingValue - self.textValue = textValue - selectedOptions = multipleChoiceValues - } + private init( + type: PostHogSurveyResponseType, + linkClicked: Bool? = nil, + ratingValue: Int? = nil, + textValue: String? = nil, + multipleChoiceValues: [String]? = nil + ) { + self.type = type + self.linkClicked = linkClicked + self.ratingValue = ratingValue + self.textValue = textValue + selectedOptions = multipleChoiceValues + } - /// Creates a response for a link question. - /// - /// - Parameter clicked: Whether the link was clicked. - /// - Returns: A response with type `.link` and `linkClicked` populated. - public static func link(_ clicked: Bool) -> PostHogSurveyResponse { - PostHogSurveyResponse( - type: .link, - linkClicked: clicked, - ratingValue: nil, - textValue: nil, - multipleChoiceValues: nil - ) - } + /// Creates a response for a link question. + /// + /// - Parameter clicked: Whether the link was clicked. + /// - Returns: A response with type `.link` and `linkClicked` populated. + public static func link(_ clicked: Bool) -> PostHogSurveyResponse { + PostHogSurveyResponse( + type: .link, + linkClicked: clicked, + ratingValue: nil, + textValue: nil, + multipleChoiceValues: nil + ) + } - /// Creates a response for a rating question. - /// - /// - Parameter rating: The selected rating value. - /// - Returns: A response with type `.rating` and `ratingValue` populated. - public static func rating(_ rating: Int?) -> PostHogSurveyResponse { - PostHogSurveyResponse( - type: .rating, - linkClicked: nil, - ratingValue: rating, - textValue: nil, - multipleChoiceValues: nil - ) - } + /// Creates a response for a rating question. + /// + /// - Parameter rating: The selected rating value. + /// - Returns: A response with type `.rating` and `ratingValue` populated. + public static func rating(_ rating: Int?) -> PostHogSurveyResponse { + PostHogSurveyResponse( + type: .rating, + linkClicked: nil, + ratingValue: rating, + textValue: nil, + multipleChoiceValues: nil + ) + } - /// Creates a response for an open-ended question. - /// - /// - Parameter openEnded: The text response. - /// - Returns: A response with type `.openEnded` and `textValue` populated. - public static func openEnded(_ openEnded: String?) -> PostHogSurveyResponse { - PostHogSurveyResponse( - type: .openEnded, - linkClicked: nil, - ratingValue: nil, - textValue: openEnded, - multipleChoiceValues: nil - ) - } + /// Creates a response for an open-ended question. + /// + /// - Parameter openEnded: The text response. + /// - Returns: A response with type `.openEnded` and `textValue` populated. + public static func openEnded(_ openEnded: String?) -> PostHogSurveyResponse { + PostHogSurveyResponse( + type: .openEnded, + linkClicked: nil, + ratingValue: nil, + textValue: openEnded, + multipleChoiceValues: nil + ) + } - /// Creates a response for a single-choice question. - /// - /// - Parameter singleChoice: The selected option. - /// - Returns: A response with type `.singleChoice` and `selectedOptions` containing the option when non-nil. - public static func singleChoice(_ singleChoice: String?) -> PostHogSurveyResponse { - PostHogSurveyResponse( - type: .singleChoice, - linkClicked: nil, - ratingValue: nil, - textValue: nil, - multipleChoiceValues: { - if let singleChoice { [singleChoice] } else { nil } - }() - ) - } + /// Creates a response for a single-choice question. + /// + /// - Parameter singleChoice: The selected option. + /// - Returns: A response with type `.singleChoice` and `selectedOptions` containing the option when non-nil. + public static func singleChoice(_ singleChoice: String?) -> PostHogSurveyResponse { + PostHogSurveyResponse( + type: .singleChoice, + linkClicked: nil, + ratingValue: nil, + textValue: nil, + multipleChoiceValues: { + if let singleChoice { [singleChoice] } else { nil } + }() + ) + } - /// Creates a response for a multiple-choice question. - /// - /// - Parameter multipleChoice: The selected options. - /// - Returns: A response with type `.multipleChoice` and `selectedOptions` populated. - public static func multipleChoice(_ multipleChoice: [String]?) -> PostHogSurveyResponse { - PostHogSurveyResponse( - type: .multipleChoice, - linkClicked: nil, - ratingValue: nil, - textValue: nil, - multipleChoiceValues: multipleChoice - ) + /// Creates a response for a multiple-choice question. + /// + /// - Parameter multipleChoice: The selected options. + /// - Returns: A response with type `.multipleChoice` and `selectedOptions` populated. + public static func multipleChoice(_ multipleChoice: [String]?) -> PostHogSurveyResponse { + PostHogSurveyResponse( + type: .multipleChoice, + linkClicked: nil, + ratingValue: nil, + textValue: nil, + multipleChoiceValues: multipleChoice + ) + } } -} -/// Type of response submitted for a survey question. -@objc public enum PostHogSurveyResponseType: Int { - /// Response for a link question. - case link - /// Response for a rating question. - case rating - /// Response for an open-ended text question. - case openEnded - /// Response for a single-choice question. - case singleChoice - /// Response for a multiple-choice question. - case multipleChoice -} + /// Type of response submitted for a survey question. + @objc public enum PostHogSurveyResponseType: Int { + /// Response for a link question. + case link + /// Response for a rating question. + case rating + /// Response for an open-ended text question. + case openEnded + /// Response for a single-choice question. + case singleChoice + /// Response for a multiple-choice question. + case multipleChoice + } +#endif diff --git a/PostHog/Surveys/PostHogSurveyIntegration.swift b/PostHog/Surveys/PostHogSurveyIntegration.swift index 36a9661e5f..984f44b23a 100644 --- a/PostHog/Surveys/PostHogSurveyIntegration.swift +++ b/PostHog/Surveys/PostHogSurveyIntegration.swift @@ -1179,17 +1179,4 @@ } } #endif -#elseif os(iOS) - // Stub for SPM builds with the `Surveys` trait disabled, where the survey engine and UI are not compiled. - final class PostHogSurveyIntegration: PostHogIntegration { - var requiresSwizzling: Bool { false } - - func install(_: PostHogSDK) -> PostHogIntegrationInstallResult { - .skipped(.disabledByPackageTrait) - } - - func uninstall(_: PostHogSDK) { /* no-op */ } - func start() { /* no-op */ } - func stop() { /* no-op */ } - } #endif diff --git a/PostHog/Surveys/PostHogSurveysConfig.swift b/PostHog/Surveys/PostHogSurveysConfig.swift index edb702d4f7..60e41b3e13 100644 --- a/PostHog/Surveys/PostHogSurveysConfig.swift +++ b/PostHog/Surveys/PostHogSurveysConfig.swift @@ -7,105 +7,107 @@ import Foundation -/// Configuration for mobile survey rendering. -/// -/// Mutate fields on `PostHogConfig.surveysConfig` before SDK setup to customize survey UI, -/// lifecycle handling, or localization. -@objc public class PostHogSurveysConfig: NSObject { - /// Delegate responsible for managing survey presentation in your app. - /// Handles survey rendering, response collection, and lifecycle events. - /// You can provide your own delegate for a custom survey presentation. - /// With Swift Package Manager, the delegate is only called when the `Surveys` package trait is enabled, which it is by default. +#if !SWIFT_PACKAGE || Surveys + /// Configuration for mobile survey rendering. /// - /// Defaults to `PostHogSurveysDefaultDelegate` which provides a standard survey UI. - public var surveysDelegate: PostHogSurveysDelegate = PostHogSurveysDefaultDelegate() + /// Mutate fields on `PostHogConfig.surveysConfig` before SDK setup to customize survey UI, + /// lifecycle handling, or localization. + /// With Swift Package Manager, this is only available with the `Surveys` package trait, which is enabled by default. + @objc public class PostHogSurveysConfig: NSObject { + /// Delegate responsible for managing survey presentation in your app. + /// Handles survey rendering, response collection, and lifecycle events. + /// You can provide your own delegate for a custom survey presentation. + /// + /// Defaults to `PostHogSurveysDefaultDelegate` which provides a standard survey UI. + public var surveysDelegate: PostHogSurveysDelegate = PostHogSurveysDefaultDelegate() - /// Optional explicit override for the language used when rendering surveys. - /// - /// When set, surveys with matching entries in `translations` will be rendered in - /// this language regardless of the device locale or any `language` person property. - /// - /// Format: a language tag such as `"fr"`, `"pt-BR"`, `"zh-CN"`. Matching is - /// case-insensitive and falls back to the base language (e.g. `"pt"` if `"pt-BR"` - /// is requested but only `"pt"` is provided). - /// - /// Blank or `nil` values are treated as unset. - /// - /// Default: `nil`. - @objc public var overrideDisplayLanguage: String? -} + /// Optional explicit override for the language used when rendering surveys. + /// + /// When set, surveys with matching entries in `translations` will be rendered in + /// this language regardless of the device locale or any `language` person property. + /// + /// Format: a language tag such as `"fr"`, `"pt-BR"`, `"zh-CN"`. Matching is + /// case-insensitive and falls back to the base language (e.g. `"pt"` if `"pt-BR"` + /// is requested but only `"pt"` is provided). + /// + /// Blank or `nil` values are treated as unset. + /// + /// Default: `nil`. + @objc public var overrideDisplayLanguage: String? + } -/// To be called when a survey is successfully shown to the user -/// - Parameter survey: The survey that was displayed -public typealias OnPostHogSurveyShown = (_ survey: PostHogDisplaySurvey) -> Void + /// To be called when a survey is successfully shown to the user + /// - Parameter survey: The survey that was displayed + public typealias OnPostHogSurveyShown = (_ survey: PostHogDisplaySurvey) -> Void -/// To be called when a user responds to a survey question -/// - Parameters: -/// - survey: The current survey being displayed -/// - index: The index of the question being answered -/// - response: The user's response to the question -/// - Returns: The next survey state, or `nil` if the attempt is no longer valid. The built-in renderer closes an invalidated survey. -public typealias OnPostHogSurveyResponse = (_ survey: PostHogDisplaySurvey, _ index: Int, _ response: PostHogSurveyResponse) -> PostHogNextSurveyQuestion? + /// To be called when a user responds to a survey question + /// - Parameters: + /// - survey: The current survey being displayed + /// - index: The index of the question being answered + /// - response: The user's response to the question + /// - Returns: The next survey state, or `nil` if the attempt is no longer valid. The built-in renderer closes an invalidated survey. + public typealias OnPostHogSurveyResponse = (_ survey: PostHogDisplaySurvey, _ index: Int, _ response: PostHogSurveyResponse) -> PostHogNextSurveyQuestion? -/// To be called when a survey is dismissed -/// - Parameter survey: The survey that was closed -public typealias OnPostHogSurveyClosed = (_ survey: PostHogDisplaySurvey) -> Void + /// To be called when a survey is dismissed + /// - Parameter survey: The survey that was closed + public typealias OnPostHogSurveyClosed = (_ survey: PostHogDisplaySurvey) -> Void -/// Delegate used by the SDK to present surveys and receive survey lifecycle callbacks. -@objc public protocol PostHogSurveysDelegate { - /// Whether this delegate can resume unfinished surveys. Defaults to `false` when omitted. - /// - /// Return `true` only if the renderer starts at `survey.initialQuestionIndex`. - /// The SDK restores the saved answers and submission ID for that attempt. - /// Delegates that omit this property or return `false` start a fresh attempt at question 0. - /// - /// For example, a renderer that supports the restored position can opt in with: - /// ```swift - /// var supportsSurveyResume: Bool { true } - /// // In renderSurvey, initialize the renderer at survey.initialQuestionIndex. - /// ``` - @objc optional var supportsSurveyResume: Bool { get } + /// Delegate used by the SDK to present surveys and receive survey lifecycle callbacks. + @objc public protocol PostHogSurveysDelegate { + /// Whether this delegate can resume unfinished surveys. Defaults to `false` when omitted. + /// + /// Return `true` only if the renderer starts at `survey.initialQuestionIndex`. + /// The SDK restores the saved answers and submission ID for that attempt. + /// Delegates that omit this property or return `false` start a fresh attempt at question 0. + /// + /// For example, a renderer that supports the restored position can opt in with: + /// ```swift + /// var supportsSurveyResume: Bool { true } + /// // In renderSurvey, initialize the renderer at survey.initialQuestionIndex. + /// ``` + @objc optional var supportsSurveyResume: Bool { get } - /// Called when an activated PostHog survey needs to be rendered on the app's UI - /// Delegates that opt in through `supportsSurveyResume` must start at - /// `survey.initialQuestionIndex` to resume an unfinished survey. - /// - /// - Parameters: - /// - survey: The survey to be displayed to the user - /// - onSurveyShown: To be called when the survey is successfully displayed to the user. - /// - onSurveyResponse: Call when the user submits a response to a question; use the returned state to advance or complete the survey. - /// - onSurveyClosed: To be called when the survey is dismissed - @objc func renderSurvey( - _ survey: PostHogDisplaySurvey, - onSurveyShown: @escaping OnPostHogSurveyShown, - onSurveyResponse: @escaping OnPostHogSurveyResponse, - onSurveyClosed: @escaping OnPostHogSurveyClosed - ) + /// Called when an activated PostHog survey needs to be rendered on the app's UI + /// Delegates that opt in through `supportsSurveyResume` must start at + /// `survey.initialQuestionIndex` to resume an unfinished survey. + /// + /// - Parameters: + /// - survey: The survey to be displayed to the user + /// - onSurveyShown: To be called when the survey is successfully displayed to the user. + /// - onSurveyResponse: Call when the user submits a response to a question; use the returned state to advance or complete the survey. + /// - onSurveyClosed: To be called when the survey is dismissed + @objc func renderSurvey( + _ survey: PostHogDisplaySurvey, + onSurveyShown: @escaping OnPostHogSurveyShown, + onSurveyResponse: @escaping OnPostHogSurveyResponse, + onSurveyClosed: @escaping OnPostHogSurveyClosed + ) - /// Called when the content of the currently displayed survey changes without restarting it, - /// for example when the user's language is updated and a new translation becomes available. - /// - /// Implementations should update the on-screen survey in place — preserving the current - /// question, progress, and any in-progress answers — rather than presenting it again. - /// Optional: delegates that don't support live updates can omit it and the survey simply - /// keeps the language it was first rendered with. - /// - /// Always delivered on the main thread. May arrive *before* the survey is visibly shown — - /// e.g. while a display delay (`surveyPopupDelaySeconds`) is still pending — so a delegate - /// that defers presentation must also apply the update to its pending survey, otherwise the - /// update is dropped and the survey shows the language it was first resolved with. - /// - /// - Parameter survey: The survey with refreshed (e.g. re-translated) content. Its `id` - /// matches the survey passed to `renderSurvey`. - @objc optional func updateSurvey(_ survey: PostHogDisplaySurvey) + /// Called when the content of the currently displayed survey changes without restarting it, + /// for example when the user's language is updated and a new translation becomes available. + /// + /// Implementations should update the on-screen survey in place — preserving the current + /// question, progress, and any in-progress answers — rather than presenting it again. + /// Optional: delegates that don't support live updates can omit it and the survey simply + /// keeps the language it was first rendered with. + /// + /// Always delivered on the main thread. May arrive *before* the survey is visibly shown — + /// e.g. while a display delay (`surveyPopupDelaySeconds`) is still pending — so a delegate + /// that defers presentation must also apply the update to its pending survey, otherwise the + /// update is dropped and the survey shows the language it was first resolved with. + /// + /// - Parameter survey: The survey with refreshed (e.g. re-translated) content. Its `id` + /// matches the survey passed to `renderSurvey`. + @objc optional func updateSurvey(_ survey: PostHogDisplaySurvey) - /// Called when surveys are stopped to clean up any UI elements and reset the survey display state. - /// This method should handle the dismissal of any active surveys and cleanup of associated resources. - @objc func cleanupSurveys() -} + /// Called when surveys are stopped to clean up any UI elements and reset the survey display state. + /// This method should handle the dismissal of any active surveys and cleanup of associated resources. + @objc func cleanupSurveys() + } -struct SurveyCallbacks { - let shown: OnPostHogSurveyShown - let response: OnPostHogSurveyResponse - let closed: OnPostHogSurveyClosed -} + struct SurveyCallbacks { + let shown: OnPostHogSurveyShown + let response: OnPostHogSurveyResponse + let closed: OnPostHogSurveyClosed + } +#endif diff --git a/PostHog/Surveys/PostHogSurveysDefaultDelegate.swift b/PostHog/Surveys/PostHogSurveysDefaultDelegate.swift index 49132efe97..5d4d91b11c 100644 --- a/PostHog/Surveys/PostHogSurveysDefaultDelegate.swift +++ b/PostHog/Surveys/PostHogSurveysDefaultDelegate.swift @@ -11,135 +11,137 @@ import Foundation #endif -final class PostHogSurveysDefaultDelegate: PostHogSurveysDelegate { - var supportsSurveyResume: Bool { true } - - #if os(iOS) && (!SWIFT_PACKAGE || Surveys) - private var surveysWindow: UIWindow? - private var displayController: SurveyDisplayController? - private var pendingDisplayWorkItem: DispatchWorkItem? - private var pendingSurvey: PostHogDisplaySurvey? - private var pendingSurveyClosedHandler: OnPostHogSurveyClosed? - #endif - - func renderSurvey( - _ survey: PostHogDisplaySurvey, - onSurveyShown: @escaping OnPostHogSurveyShown, - onSurveyResponse: @escaping OnPostHogSurveyResponse, - onSurveyClosed: @escaping OnPostHogSurveyClosed - ) { - #if os(iOS) && (!SWIFT_PACKAGE || Surveys) - if displayController == nil { - // setup window for first-time display - setupWindow() - } - - guard let displayController else { - // If we cannot render the survey UI, treat this as a pre-render dismiss so integration state is cleared. - onSurveyClosed(survey) - return - } +#if !SWIFT_PACKAGE || Surveys + final class PostHogSurveysDefaultDelegate: PostHogSurveysDelegate { + var supportsSurveyResume: Bool { true } + + #if os(iOS) + private var surveysWindow: UIWindow? + private var displayController: SurveyDisplayController? + private var pendingDisplayWorkItem: DispatchWorkItem? + private var pendingSurvey: PostHogDisplaySurvey? + private var pendingSurveyClosedHandler: OnPostHogSurveyClosed? + #endif - // Setup handlers - displayController.onSurveyShown = onSurveyShown - displayController.onSurveyResponse = onSurveyResponse - displayController.onSurveyClosed = onSurveyClosed + func renderSurvey( + _ survey: PostHogDisplaySurvey, + onSurveyShown: @escaping OnPostHogSurveyShown, + onSurveyResponse: @escaping OnPostHogSurveyResponse, + onSurveyClosed: @escaping OnPostHogSurveyClosed + ) { + #if os(iOS) + if displayController == nil { + // setup window for first-time display + setupWindow() + } - scheduleSurveyDisplay(survey, displayController: displayController, onSurveyClosed: onSurveyClosed) - #endif - } + guard let displayController else { + // If we cannot render the survey UI, treat this as a pre-render dismiss so integration state is cleared. + onSurveyClosed(survey) + return + } - func updateSurvey(_ survey: PostHogDisplaySurvey) { - #if os(iOS) && (!SWIFT_PACKAGE || Surveys) - // If the survey is still waiting out its display delay, refresh the queued copy so - // it gets shown with the latest content. - if pendingSurvey?.id == survey.id { - pendingSurvey = survey - } + // Setup handlers + displayController.onSurveyShown = onSurveyShown + displayController.onSurveyResponse = onSurveyResponse + displayController.onSurveyClosed = onSurveyClosed - displayController?.updateSurvey(survey) - #endif - } + scheduleSurveyDisplay(survey, displayController: displayController, onSurveyClosed: onSurveyClosed) + #endif + } - func cleanupSurveys() { - #if os(iOS) && (!SWIFT_PACKAGE || Surveys) - dismissPendingSurveyIfNeeded() - displayController?.dismissSurvey() // dismiss any active surveys - surveysWindow?.rootViewController?.dismiss(animated: true) { - self.surveysWindow?.isHidden = true - self.surveysWindow = nil - self.displayController = nil - } - #endif - } + func updateSurvey(_ survey: PostHogDisplaySurvey) { + #if os(iOS) + // If the survey is still waiting out its display delay, refresh the queued copy so + // it gets shown with the latest content. + if pendingSurvey?.id == survey.id { + pendingSurvey = survey + } - #if os(iOS) && (!SWIFT_PACKAGE || Surveys) - private func setupWindow() { - if let activeWindow = UIApplication.getCurrentWindow(), let activeScene = activeWindow.windowScene { - let controller = SurveyDisplayController() - displayController = controller - surveysWindow = SurveysWindow( - controller: controller, - scene: activeScene - ) - surveysWindow?.isHidden = false - surveysWindow?.windowLevel = activeWindow.windowLevel + 1 - } + displayController?.updateSurvey(survey) + #endif } - private func scheduleSurveyDisplay( - _ survey: PostHogDisplaySurvey, - displayController: SurveyDisplayController, - onSurveyClosed: @escaping OnPostHogSurveyClosed - ) { - dismissPendingSurveyIfNeeded() + func cleanupSurveys() { + #if os(iOS) + dismissPendingSurveyIfNeeded() + displayController?.dismissSurvey() // dismiss any active surveys + surveysWindow?.rootViewController?.dismiss(animated: true) { + self.surveysWindow?.isHidden = true + self.surveysWindow = nil + self.displayController = nil + } + #endif + } - let delay = max(survey.appearance?.surveyPopupDelaySeconds ?? 0, 0) - guard delay > 0 else { - // show the survey directly - displayController.showSurvey(survey) - return + #if os(iOS) + private func setupWindow() { + if let activeWindow = UIApplication.getCurrentWindow(), let activeScene = activeWindow.windowScene { + let controller = SurveyDisplayController() + displayController = controller + surveysWindow = SurveysWindow( + controller: controller, + scene: activeScene + ) + surveysWindow?.isHidden = false + surveysWindow?.windowLevel = activeWindow.windowLevel + 1 + } } - hedgeLog("[Surveys] Scheduling survey \(survey.id) display in \(delay) seconds") + private func scheduleSurveyDisplay( + _ survey: PostHogDisplaySurvey, + displayController: SurveyDisplayController, + onSurveyClosed: @escaping OnPostHogSurveyClosed + ) { + dismissPendingSurveyIfNeeded() + + let delay = max(survey.appearance?.surveyPopupDelaySeconds ?? 0, 0) + guard delay > 0 else { + // show the survey directly + displayController.showSurvey(survey) + return + } - // schedule a survey display for now + delay - pendingSurvey = survey - pendingSurveyClosedHandler = onSurveyClosed + hedgeLog("[Surveys] Scheduling survey \(survey.id) display in \(delay) seconds") - let workItem = DispatchWorkItem { [weak self] in - guard let self else { return } - // Read the latest pending copy: `updateSurvey` may have replaced it with a - // re-translated version (same id) while the delay was counting down. Showing - // the captured `survey` here would drop that update. - guard let pending = self.pendingSurvey, pending.id == survey.id else { - // current survey? - return + // schedule a survey display for now + delay + pendingSurvey = survey + pendingSurveyClosedHandler = onSurveyClosed + + let workItem = DispatchWorkItem { [weak self] in + guard let self else { return } + // Read the latest pending copy: `updateSurvey` may have replaced it with a + // re-translated version (same id) while the delay was counting down. Showing + // the captured `survey` here would drop that update. + guard let pending = self.pendingSurvey, pending.id == survey.id else { + // current survey? + return + } + + self.pendingDisplayWorkItem = nil + self.pendingSurvey = nil + self.pendingSurveyClosedHandler = nil + displayController.showSurvey(pending) } - self.pendingDisplayWorkItem = nil - self.pendingSurvey = nil - self.pendingSurveyClosedHandler = nil - displayController.showSurvey(pending) + pendingDisplayWorkItem = workItem + DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem) } - pendingDisplayWorkItem = workItem - DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem) - } + private func dismissPendingSurveyIfNeeded() { + guard let pendingSurvey else { return } - private func dismissPendingSurveyIfNeeded() { - guard let pendingSurvey else { return } - - pendingDisplayWorkItem?.cancel() - pendingDisplayWorkItem = nil - self.pendingSurvey = nil + pendingDisplayWorkItem?.cancel() + pendingDisplayWorkItem = nil + self.pendingSurvey = nil - let pendingClosedHandler = pendingSurveyClosedHandler - pendingSurveyClosedHandler = nil - pendingClosedHandler?(pendingSurvey) - } - #endif -} + let pendingClosedHandler = pendingSurveyClosedHandler + pendingSurveyClosedHandler = nil + pendingClosedHandler?(pendingSurvey) + } + #endif + } +#endif #if os(iOS) && TESTING && (!SWIFT_PACKAGE || Surveys) extension PostHogSurveysDefaultDelegate { diff --git a/PostHog/SwiftUI/PostHogNoMaskViewModifier.swift b/PostHog/SwiftUI/PostHogNoMaskViewModifier.swift index 2a1fa3f516..5954ff8aa7 100644 --- a/PostHog/SwiftUI/PostHogNoMaskViewModifier.swift +++ b/PostHog/SwiftUI/PostHogNoMaskViewModifier.swift @@ -9,53 +9,55 @@ import SwiftUI - /// SwiftUI session replay unmasking helpers. - public extension View { - /** - Marks a SwiftUI View to be excluded from masking in PostHog session replay recordings. - - Note: On iOS 26+ (Xcode 26 SwiftUI rendering engine), SwiftUI views may no longer map - reliably to a backing `UIView`, so this modifier may behave inconsistently. A future SDK - update will try to address this limitation, but for now, we recommend using this modifier - with caution. - - There are cases where PostHog SDK will unintentionally mask some SwiftUI views. - - Because of the nature of how we intercept SwiftUI view hierarchy (and how it maps to UIKit), - we can't always be 100% confident that a view should be masked. For that reason, we prefer to - take a proactive and prefer to mask views if we're not sure. - - Use this modifier to prevent views from being masked in session replay recordings. - - Note: for `.drawingGroup()` or `Canvas` content, apply this modifier to the group itself. - - For example: - ```swift - // This view may be accidentally masked by PostHog SDK - SomeSafeView() - - // This custom view (and all its subviews) will not be masked in recordings - SomeSafeView() - .postHogNoMask() - ``` - - - Returns: A modified view that will not be masked in session replay recordings - */ - func postHogNoMask() -> some View { - modifier( - PostHogTagViewModifier( - onChange: { owner, views, layers in - views.forEach { $0.setPostHogNoMask(true, owner: owner) } - layers.forEach { $0.setPostHogNoMask(true, owner: owner) } - }, - onRemove: { owner, views, layers in - views.forEach { $0.setPostHogNoMask(false, owner: owner) } - layers.forEach { $0.setPostHogNoMask(false, owner: owner) } - } + #if !SWIFT_PACKAGE || SessionReplay + /// SwiftUI session replay unmasking helpers. + public extension View { + /** + Marks a SwiftUI View to be excluded from masking in PostHog session replay recordings. + + Note: On iOS 26+ (Xcode 26 SwiftUI rendering engine), SwiftUI views may no longer map + reliably to a backing `UIView`, so this modifier may behave inconsistently. A future SDK + update will try to address this limitation, but for now, we recommend using this modifier + with caution. + + There are cases where PostHog SDK will unintentionally mask some SwiftUI views. + + Because of the nature of how we intercept SwiftUI view hierarchy (and how it maps to UIKit), + we can't always be 100% confident that a view should be masked. For that reason, we prefer to + take a proactive and prefer to mask views if we're not sure. + + Use this modifier to prevent views from being masked in session replay recordings. + + Note: for `.drawingGroup()` or `Canvas` content, apply this modifier to the group itself. + + For example: + ```swift + // This view may be accidentally masked by PostHog SDK + SomeSafeView() + + // This custom view (and all its subviews) will not be masked in recordings + SomeSafeView() + .postHogNoMask() + ``` + + - Returns: A modified view that will not be masked in session replay recordings + */ + func postHogNoMask() -> some View { + modifier( + PostHogTagViewModifier( + onChange: { owner, views, layers in + views.forEach { $0.setPostHogNoMask(true, owner: owner) } + layers.forEach { $0.setPostHogNoMask(true, owner: owner) } + }, + onRemove: { owner, views, layers in + views.forEach { $0.setPostHogNoMask(false, owner: owner) } + layers.forEach { $0.setPostHogNoMask(false, owner: owner) } + } + ) ) - ) + } } - } + #endif // Ref-counted ownership (see PostHogFlagOwners): overlapping no-mask regions // must not clear each other on teardown. diff --git a/PostHogTests/PostHogIntegrationInstallationTest.swift b/PostHogTests/PostHogIntegrationInstallationTest.swift index de4507d13d..0fd0942323 100644 --- a/PostHogTests/PostHogIntegrationInstallationTest.swift +++ b/PostHogTests/PostHogIntegrationInstallationTest.swift @@ -16,7 +16,7 @@ class PostHogIntegrationInstallationTest { init() { server = MockPostHogServer() server.start() - #if os(iOS) + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) PostHogReplayIntegration.clearInstalls() #endif #if os(iOS) || targetEnvironment(macCatalyst) @@ -59,7 +59,7 @@ class PostHogIntegrationInstallationTest { config.disableReachabilityForTesting = true config.enableSwizzling = enableSwizzling - #if os(iOS) + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) config.sessionReplay = sessionReplay #endif @@ -74,7 +74,9 @@ class PostHogIntegrationInstallationTest { #endif config.captureScreenViews = captureScreenViews - config.errorTrackingConfig.autoCapture = errorTrackingAutoCapture + #if !SWIFT_PACKAGE || CrashReporting + config.errorTrackingConfig.autoCapture = errorTrackingAutoCapture + #endif let storage = PostHogStorage(config) storage.reset() @@ -395,27 +397,6 @@ class PostHogIntegrationInstallationTest { sut.optIn() // triggers re-install with a fetched-but-failed remote config #expect(sut.getErrorTrackingIntegration() != nil) } - #elseif os(iOS) || os(macOS) || os(tvOS) - @Test("error tracking integration is skipped when the CrashReporting trait is disabled") - func errorTrackingIntegrationSkippedWithoutCrashReportingTrait() { - let sut = getSut(projectToken: "test_project_token", errorTrackingAutoCapture: true) - - #expect(sut.getErrorTrackingIntegration() == nil) - #expect(PostHogErrorTrackingAutoCaptureIntegration().install(sut) == .skipped(.disabledByPackageTrait)) - - sut.close() - } - #endif - - #if os(iOS) && SWIFT_PACKAGE && !Surveys - @Test("surveys integration is skipped when the Surveys trait is disabled") - func surveysIntegrationSkippedWithoutSurveysTrait() { - let sut = getSut(projectToken: "test_project_token") - - #expect(PostHogSurveyIntegration().install(sut) == .skipped(.disabledByPackageTrait)) - - sut.close() - } #endif #if os(iOS) || os(macOS) diff --git a/PostHogTests/PostHogOptOutPersistenceTest.swift b/PostHogTests/PostHogOptOutPersistenceTest.swift index cf35ff3711..619f338254 100644 --- a/PostHogTests/PostHogOptOutPersistenceTest.swift +++ b/PostHogTests/PostHogOptOutPersistenceTest.swift @@ -19,8 +19,10 @@ final class PostHogOptOutPersistenceTest { config.disableFlushOnBackgroundForTesting = true config.captureApplicationLifecycleEvents = false config.captureScreenViews = false - config.errorTrackingConfig.autoCapture = false - #if os(iOS) + #if !SWIFT_PACKAGE || CrashReporting + config.errorTrackingConfig.autoCapture = false + #endif + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) config.sessionReplay = false #endif config.optOut = optOut diff --git a/PostHogTests/PostHogPushNotificationSwizzlingTest.swift b/PostHogTests/PostHogPushNotificationSwizzlingTest.swift index 9809334385..0e02409faa 100644 --- a/PostHogTests/PostHogPushNotificationSwizzlingTest.swift +++ b/PostHogTests/PostHogPushNotificationSwizzlingTest.swift @@ -267,8 +267,10 @@ config.disableReachabilityForTesting = true config.captureApplicationLifecycleEvents = false config.captureScreenViews = false - config.errorTrackingConfig.autoCapture = false - #if os(iOS) + #if !SWIFT_PACKAGE || CrashReporting + config.errorTrackingConfig.autoCapture = false + #endif + #if os(iOS) && (!SWIFT_PACKAGE || SessionReplay) config.sessionReplay = false #endif config.optOut = optOut diff --git a/api/posthog-ios.public-api.no-traits.txt b/api/posthog-ios.public-api.no-traits.txt new file mode 100644 index 0000000000..41f9a6a690 --- /dev/null +++ b/api/posthog-ios.public-api.no-traits.txt @@ -0,0 +1,268 @@ +# This file is generated by `scripts/check-public-api.sh --update --no-traits`. +# Do not edit manually. +# Public API scope: public/open symbols emitted for the PostHog iOS symbol graphs, Swift Package Manager build with --disable-default-traits. + +PostHog | BeforeSendBlock | typealias | typealias BeforeSendBlock = @Sendable (PostHogEvent) -> PostHogEvent? | s:7PostHog15BeforeSendBlocka +PostHog | BoxedBeforeSendBlock | class | @objc final class BoxedBeforeSendBlock | c:@M@PostHog@objc(cs)BoxedBeforeSendBlock +PostHog | BoxedBeforeSendBlock.block | property | @objc let block: BeforeSendBlock | c:@M@PostHog@objc(cs)BoxedBeforeSendBlock(py)block +PostHog | BoxedBeforeSendBlock.init(block:) | init | @objc(block:) init(block: @escaping BeforeSendBlock) | c:@M@PostHog@objc(cs)BoxedBeforeSendBlock(im)block: +PostHog | BoxedBeforeSendLogBlock | class | @objc final class BoxedBeforeSendLogBlock | c:@M@PostHog@objc(cs)BoxedBeforeSendLogBlock +PostHog | BoxedBeforeSendLogBlock.block | property | @objc let block: PostHogBeforeSendLogBlock | c:@M@PostHog@objc(cs)BoxedBeforeSendLogBlock(py)block +PostHog | BoxedBeforeSendLogBlock.init(block:) | init | @objc(block:) init(block: @escaping PostHogBeforeSendLogBlock) | c:@M@PostHog@objc(cs)BoxedBeforeSendLogBlock(im)block: +PostHog | PostHogBeforeSendLogBlock | typealias | typealias PostHogBeforeSendLogBlock = @Sendable (PostHogLogRecord) -> PostHogLogRecord? | s:7PostHog0aB18BeforeSendLogBlocka +PostHog | PostHogBootstrapConfig | class | @objc(PostHogBootstrapConfig) class PostHogBootstrapConfig | c:@M@PostHog@objc(cs)PostHogBootstrapConfig +PostHog | PostHogBootstrapConfig.distinctId | property | @objc var distinctId: String? | c:@M@PostHog@objc(cs)PostHogBootstrapConfig(py)distinctId +PostHog | PostHogBootstrapConfig.featureFlagPayloads | property | @objc var featureFlagPayloads: [String : Any]? | c:@M@PostHog@objc(cs)PostHogBootstrapConfig(py)featureFlagPayloads +PostHog | PostHogBootstrapConfig.featureFlags | property | @objc var featureFlags: [String : Any]? | c:@M@PostHog@objc(cs)PostHogBootstrapConfig(py)featureFlags +PostHog | PostHogBootstrapConfig.init() | init | override init() | c:@M@PostHog@objc(cs)PostHogBootstrapConfig(im)init +PostHog | PostHogBootstrapConfig.init(anonymousId:) | init | @objc convenience init(anonymousId: String) | c:@M@PostHog@objc(cs)PostHogBootstrapConfig(im)initWithAnonymousId: +PostHog | PostHogBootstrapConfig.init(distinctId:isIdentifiedId:) | init | @objc convenience init(distinctId: String, isIdentifiedId: Bool) | c:@M@PostHog@objc(cs)PostHogBootstrapConfig(im)initWithDistinctId:isIdentifiedId: +PostHog | PostHogBootstrapConfig.init(distinctId:isIdentifiedId:featureFlags:featureFlagPayloads:) | init | @objc init(distinctId: String?, isIdentifiedId: Bool = false, featureFlags: [String : Any]?, featureFlagPayloads: [String : Any]?) | c:@M@PostHog@objc(cs)PostHogBootstrapConfig(im)initWithDistinctId:isIdentifiedId:featureFlags:featureFlagPayloads: +PostHog | PostHogBootstrapConfig.init(featureFlags:featureFlagPayloads:) | init | @objc convenience init(featureFlags: [String : Any], featureFlagPayloads: [String : Any]?) | c:@M@PostHog@objc(cs)PostHogBootstrapConfig(im)initWithFeatureFlags:featureFlagPayloads: +PostHog | PostHogBootstrapConfig.isIdentifiedId | property | @objc var isIdentifiedId: Bool | c:@M@PostHog@objc(cs)PostHogBootstrapConfig(py)isIdentifiedId +PostHog | PostHogCompression | enum | @objc(PostHogCompression) enum PostHogCompression | c:@M@PostHog@E@PostHogCompression +PostHog | PostHogCompression.gzip | enum.case | case gzip | c:@M@PostHog@E@PostHogCompression@PostHogCompressionGzip +PostHog | PostHogCompression.init(rawValue:) | init | init?(rawValue: Int) | s:7PostHog0aB11CompressionO8rawValueACSgSi_tcfc +PostHog | PostHogCompression.none | enum.case | case none | c:@M@PostHog@E@PostHogCompression@PostHogCompressionNone +PostHog | PostHogConfig | class | @objc(PostHogConfig) class PostHogConfig | c:@M@PostHog@objc(cs)PostHogConfig +PostHog | PostHogConfig.PostHogDataMode | enum | @frozen @objc(PostHogDataMode) enum PostHogDataMode | s:7PostHog0aB6ConfigC0aB8DataModeO +PostHog | PostHogConfig.PostHogDataMode.any | enum.case | case any | s:7PostHog0aB6ConfigC0aB8DataModeO3anyyA2EmF +PostHog | PostHogConfig.PostHogDataMode.init(rawValue:) | init | init?(rawValue: Int) | s:7PostHog0aB6ConfigC0aB8DataModeO8rawValueAESgSi_tcfc +PostHog | PostHogConfig.PostHogDataMode.wifi | enum.case | case wifi | s:7PostHog0aB6ConfigC0aB8DataModeO4wifiyA2EmF +PostHog | PostHogConfig.appGroupIdentifier | property | @objc var appGroupIdentifier: String? | c:@M@PostHog@objc(cs)PostHogConfig(py)appGroupIdentifier +PostHog | PostHogConfig.bootstrap | property | @objc var bootstrap: PostHogBootstrapConfig? | c:@M@PostHog@objc(cs)PostHogConfig(py)bootstrap +PostHog | PostHogConfig.captureApplicationLifecycleEvents | property | @objc var captureApplicationLifecycleEvents: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)captureApplicationLifecycleEvents +PostHog | PostHogConfig.captureAutocaptureElementText | property | @objc var captureAutocaptureElementText: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)captureAutocaptureElementText +PostHog | PostHogConfig.captureElementInteractions | property | @objc var captureElementInteractions: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)captureElementInteractions +PostHog | PostHogConfig.capturePushNotificationOpened | property | @objc var capturePushNotificationOpened: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)capturePushNotificationOpened +PostHog | PostHogConfig.capturePushNotificationSubscriptions | property | @objc var capturePushNotificationSubscriptions: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)capturePushNotificationSubscriptions +PostHog | PostHogConfig.captureScreenViews | property | @objc var captureScreenViews: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)captureScreenViews +PostHog | PostHogConfig.captureSwiftUIElementInteractions | property | @objc var captureSwiftUIElementInteractions: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)captureSwiftUIElementInteractions +PostHog | PostHogConfig.compression | property | @objc var compression: PostHogCompression | c:@M@PostHog@objc(cs)PostHogConfig(py)compression +PostHog | PostHogConfig.dataMode | property | @objc var dataMode: PostHogConfig.PostHogDataMode | c:@M@PostHog@objc(cs)PostHogConfig(py)dataMode +PostHog | PostHogConfig.debug | property | @objc var debug: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)debug +PostHog | PostHogConfig.defaultHost | type.property | static let defaultHost: String | s:7PostHog0aB6ConfigC11defaultHostSSvpZ +PostHog | PostHogConfig.disableGeoIp | property | @objc var disableGeoIp: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)disableGeoIp +PostHog | PostHogConfig.enableSwizzling | property | @objc var enableSwizzling: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)enableSwizzling +PostHog | PostHogConfig.errorTrackingConfig | property | @objc let errorTrackingConfig: PostHogErrorTrackingConfig | c:@M@PostHog@objc(cs)PostHogConfig(py)errorTrackingConfig +PostHog | PostHogConfig.evaluationContexts | property | @objc var evaluationContexts: [String]? | c:@M@PostHog@objc(cs)PostHogConfig(py)evaluationContexts +PostHog | PostHogConfig.featureFlagRequestMaxRetries | property | @objc var featureFlagRequestMaxRetries: Int | c:@M@PostHog@objc(cs)PostHogConfig(py)featureFlagRequestMaxRetries +PostHog | PostHogConfig.flushAt | property | @objc var flushAt: Int | c:@M@PostHog@objc(cs)PostHogConfig(py)flushAt +PostHog | PostHogConfig.flushIntervalSeconds | property | @objc var flushIntervalSeconds: TimeInterval | c:@M@PostHog@objc(cs)PostHogConfig(py)flushIntervalSeconds +PostHog | PostHogConfig.getAnonymousId | property | @objc var getAnonymousId: @Sendable (UUID) -> UUID | c:@M@PostHog@objc(cs)PostHogConfig(py)getAnonymousId +PostHog | PostHogConfig.host | property | @objc let host: URL | c:@M@PostHog@objc(cs)PostHogConfig(py)host +PostHog | PostHogConfig.init(projectToken:) | init | @objc(projectToken:) init(projectToken: String) | c:@M@PostHog@objc(cs)PostHogConfig(im)projectToken: +PostHog | PostHogConfig.init(projectToken:host:) | init | @objc(projectToken:host:) init(projectToken: String, host: String = defaultHost) | c:@M@PostHog@objc(cs)PostHogConfig(im)projectToken:host: +PostHog | PostHogConfig.logs | property | @objc let logs: PostHogLogsConfig | c:@M@PostHog@objc(cs)PostHogConfig(py)logs +PostHog | PostHogConfig.maxBatchSize | property | @objc var maxBatchSize: Int | c:@M@PostHog@objc(cs)PostHogConfig(py)maxBatchSize +PostHog | PostHogConfig.maxQueueSize | property | @objc var maxQueueSize: Int | c:@M@PostHog@objc(cs)PostHogConfig(py)maxQueueSize +PostHog | PostHogConfig.maxRetries | property | @objc var maxRetries: Int | c:@M@PostHog@objc(cs)PostHogConfig(py)maxRetries +PostHog | PostHogConfig.optOut | property | @objc var optOut: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)optOut +PostHog | PostHogConfig.personProfiles | property | @objc var personProfiles: PostHogPersonProfiles | c:@M@PostHog@objc(cs)PostHogConfig(py)personProfiles +PostHog | PostHogConfig.preloadFeatureFlags | property | @objc var preloadFeatureFlags: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)preloadFeatureFlags +PostHog | PostHogConfig.projectToken | property | @objc let projectToken: String | c:@M@PostHog@objc(cs)PostHogConfig(py)projectToken +PostHog | PostHogConfig.pushIdentityProvider | property | @objc var pushIdentityProvider: (@Sendable (String, String, @escaping @Sendable (String?) -> Void) -> Void)? | c:@M@PostHog@objc(cs)PostHogConfig(py)pushIdentityProvider +PostHog | PostHogConfig.rageClickConfig | property | @objc let rageClickConfig: PostHogRageClickConfig | c:@M@PostHog@objc(cs)PostHogConfig(py)rageClickConfig +PostHog | PostHogConfig.requestHeaders | property | @objc var requestHeaders: [String : String]? | c:@M@PostHog@objc(cs)PostHogConfig(py)requestHeaders +PostHog | PostHogConfig.reuseAnonymousId | property | @objc var reuseAnonymousId: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)reuseAnonymousId +PostHog | PostHogConfig.sendFeatureFlagEvent | property | @objc var sendFeatureFlagEvent: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)sendFeatureFlagEvent +PostHog | PostHogConfig.setBeforeSend(_:) | method | @objc func setBeforeSend(_ blocks: [BoxedBeforeSendBlock]) | c:@M@PostHog@objc(cs)PostHogConfig(im)setBeforeSend: +PostHog | PostHogConfig.setBeforeSend(_:) | method | func setBeforeSend(_ blocks: BeforeSendBlock...) | s:7PostHog0aB6ConfigC13setBeforeSendyyAA0aB5EventCSgAFYbcd_tF +PostHog | PostHogConfig.setBeforeSend(_:) | method | func setBeforeSend(_ blocks: [BeforeSendBlock]) | s:7PostHog0aB6ConfigC13setBeforeSendyySayAA0aB5EventCSgAFYbcGF +PostHog | PostHogConfig.setDefaultPersonProperties | property | @objc var setDefaultPersonProperties: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)setDefaultPersonProperties +PostHog | PostHogConfig.tracingHeaders | property | @objc var tracingHeaders: [String]? | c:@M@PostHog@objc(cs)PostHogConfig(py)tracingHeaders +PostHog | PostHogConfig.urlSessionConfiguration | property | @objc var urlSessionConfiguration: URLSessionConfiguration? | c:@M@PostHog@objc(cs)PostHogConfig(py)urlSessionConfiguration +PostHog | PostHogErrorTrackingConfig | class | @objc class PostHogErrorTrackingConfig | c:@M@PostHog@objc(cs)PostHogErrorTrackingConfig +PostHog | PostHogErrorTrackingConfig.exceptionSteps | property | @objc var exceptionSteps: PostHogExceptionStepsConfig | c:@M@PostHog@objc(cs)PostHogErrorTrackingConfig(py)exceptionSteps +PostHog | PostHogErrorTrackingConfig.ignoredExceptionTypes | property | @objc var ignoredExceptionTypes: [String] | c:@M@PostHog@objc(cs)PostHogErrorTrackingConfig(py)ignoredExceptionTypes +PostHog | PostHogErrorTrackingConfig.inAppByDefault | property | @objc var inAppByDefault: Bool | c:@M@PostHog@objc(cs)PostHogErrorTrackingConfig(py)inAppByDefault +PostHog | PostHogErrorTrackingConfig.inAppExcludes | property | @objc var inAppExcludes: [String] | c:@M@PostHog@objc(cs)PostHogErrorTrackingConfig(py)inAppExcludes +PostHog | PostHogErrorTrackingConfig.inAppIncludes | property | @objc var inAppIncludes: [String] | c:@M@PostHog@objc(cs)PostHogErrorTrackingConfig(py)inAppIncludes +PostHog | PostHogErrorTrackingConfig.init() | init | override init() | c:@M@PostHog@objc(cs)PostHogErrorTrackingConfig(im)init +PostHog | PostHogEvent | class | @objc(PostHogEvent) class PostHogEvent | c:@M@PostHog@objc(cs)PostHogEvent +PostHog | PostHogEvent.distinctId | property | @objc var distinctId: String | c:@M@PostHog@objc(cs)PostHogEvent(py)distinctId +PostHog | PostHogEvent.event | property | @objc var event: String | c:@M@PostHog@objc(cs)PostHogEvent(py)event +PostHog | PostHogEvent.properties | property | @objc var properties: [String : Any] | c:@M@PostHog@objc(cs)PostHogEvent(py)properties +PostHog | PostHogEvent.timestamp | property | @objc var timestamp: Date | c:@M@PostHog@objc(cs)PostHogEvent(py)timestamp +PostHog | PostHogEvent.uuid | property | @objc var uuid: UUID { get } | c:@M@PostHog@objc(cs)PostHogEvent(py)uuid +PostHog | PostHogExceptionStepsConfig | class | @objc class PostHogExceptionStepsConfig | c:@M@PostHog@objc(cs)PostHogExceptionStepsConfig +PostHog | PostHogExceptionStepsConfig.enabled | property | @objc var enabled: Bool | c:@M@PostHog@objc(cs)PostHogExceptionStepsConfig(py)enabled +PostHog | PostHogExceptionStepsConfig.maxBytes | property | @objc var maxBytes: Int | c:@M@PostHog@objc(cs)PostHogExceptionStepsConfig(py)maxBytes +PostHog | PostHogFeatureFlagResult | class | @objc(PostHogFeatureFlagResult) class PostHogFeatureFlagResult | c:@M@PostHog@objc(cs)PostHogFeatureFlagResult +PostHog | PostHogFeatureFlagResult.enabled | property | @objc let enabled: Bool | c:@M@PostHog@objc(cs)PostHogFeatureFlagResult(py)enabled +PostHog | PostHogFeatureFlagResult.key | property | @objc let key: String | c:@M@PostHog@objc(cs)PostHogFeatureFlagResult(py)key +PostHog | PostHogFeatureFlagResult.payload | property | @objc let payload: Any? | c:@M@PostHog@objc(cs)PostHogFeatureFlagResult(py)payload +PostHog | PostHogFeatureFlagResult.payloadAs(_:) | method | func payloadAs(_ type: T.Type) -> T? where T : Decodable | s:7PostHog0aB17FeatureFlagResultC9payloadAsyxSgxmSeRzlF +PostHog | PostHogFeatureFlagResult.variant | property | @objc let variant: String? | c:@M@PostHog@objc(cs)PostHogFeatureFlagResult(py)variant +PostHog | PostHogFeatureFlagsLoaded | class | @objc(PostHogFeatureFlagsLoaded) final class PostHogFeatureFlagsLoaded | c:@M@PostHog@objc(cs)PostHogFeatureFlagsLoaded +PostHog | PostHogFeatureFlagsLoaded.errorsLoading | property | @objc let errorsLoading: Bool | c:@M@PostHog@objc(cs)PostHogFeatureFlagsLoaded(py)errorsLoading +PostHog | PostHogFeatureFlagsLoaded.flags | property | @objc let flags: [String] | c:@M@PostHog@objc(cs)PostHogFeatureFlagsLoaded(py)flags +PostHog | PostHogFeatureFlagsLoaded.variants | property | @objc let variants: [String : Any] | c:@M@PostHog@objc(cs)PostHogFeatureFlagsLoaded(py)variants +PostHog | PostHogFeatureFlagsSubscription | class | @objc(PostHogFeatureFlagsSubscription) final class PostHogFeatureFlagsSubscription | c:@M@PostHog@objc(cs)PostHogFeatureFlagsSubscription +PostHog | PostHogFeatureFlagsSubscription.unsubscribe() | method | @objc func unsubscribe() | c:@M@PostHog@objc(cs)PostHogFeatureFlagsSubscription(im)unsubscribe +PostHog | PostHogLogRecord | class | @objc final class PostHogLogRecord | c:@M@PostHog@objc(cs)PostHogLogRecord +PostHog | PostHogLogRecord.attributes | property | @objc var attributes: [String : Any] | c:@M@PostHog@objc(cs)PostHogLogRecord(py)attributes +PostHog | PostHogLogRecord.body | property | @objc var body: String | c:@M@PostHog@objc(cs)PostHogLogRecord(py)body +PostHog | PostHogLogRecord.distinctId | property | @objc var distinctId: String? | c:@M@PostHog@objc(cs)PostHogLogRecord(py)distinctId +PostHog | PostHogLogRecord.featureFlagKeys | property | @objc var featureFlagKeys: [String] | c:@M@PostHog@objc(cs)PostHogLogRecord(py)featureFlagKeys +PostHog | PostHogLogRecord.level | property | @objc var level: PostHogLogSeverity | c:@M@PostHog@objc(cs)PostHogLogRecord(py)level +PostHog | PostHogLogRecord.screenName | property | @objc var screenName: String? | c:@M@PostHog@objc(cs)PostHogLogRecord(py)screenName +PostHog | PostHogLogRecord.sessionId | property | @objc var sessionId: String? | c:@M@PostHog@objc(cs)PostHogLogRecord(py)sessionId +PostHog | PostHogLogRecord.spanId | property | @objc var spanId: String? | c:@M@PostHog@objc(cs)PostHogLogRecord(py)spanId +PostHog | PostHogLogRecord.traceFlags | property | @objc var traceFlags: NSNumber? | c:@M@PostHog@objc(cs)PostHogLogRecord(py)traceFlags +PostHog | PostHogLogRecord.traceId | property | @objc var traceId: String? | c:@M@PostHog@objc(cs)PostHogLogRecord(py)traceId +PostHog | PostHogLogSeverity | enum | @objc enum PostHogLogSeverity | c:@M@PostHog@E@PostHogLogSeverity +PostHog | PostHogLogSeverity.debug | enum.case | case debug | c:@M@PostHog@E@PostHogLogSeverity@PostHogLogSeverityDebug +PostHog | PostHogLogSeverity.error | enum.case | case error | c:@M@PostHog@E@PostHogLogSeverity@PostHogLogSeverityError +PostHog | PostHogLogSeverity.fatal | enum.case | case fatal | c:@M@PostHog@E@PostHogLogSeverity@PostHogLogSeverityFatal +PostHog | PostHogLogSeverity.info | enum.case | case info | c:@M@PostHog@E@PostHogLogSeverity@PostHogLogSeverityInfo +PostHog | PostHogLogSeverity.init(rawValue:) | init | init?(rawValue: Int) | s:7PostHog0aB11LogSeverityO8rawValueACSgSi_tcfc +PostHog | PostHogLogSeverity.trace | enum.case | case trace | c:@M@PostHog@E@PostHogLogSeverity@PostHogLogSeverityTrace +PostHog | PostHogLogSeverity.warn | enum.case | case warn | c:@M@PostHog@E@PostHogLogSeverity@PostHogLogSeverityWarn +PostHog | PostHogLogger | class | @objc final class PostHogLogger | c:@M@PostHog@objc(cs)PostHogLogger +PostHog | PostHogLogger.debug(_:) | method | @objc(debugWithBody:) func debug(_ body: String) | c:@M@PostHog@objc(cs)PostHogLogger(im)debugWithBody: +PostHog | PostHogLogger.debug(_:attributes:) | method | @objc(debugWithBody:attributes:) func debug(_ body: String, attributes: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogLogger(im)debugWithBody:attributes: +PostHog | PostHogLogger.error(_:) | method | @objc(errorWithBody:) func error(_ body: String) | c:@M@PostHog@objc(cs)PostHogLogger(im)errorWithBody: +PostHog | PostHogLogger.error(_:attributes:) | method | @objc(errorWithBody:attributes:) func error(_ body: String, attributes: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogLogger(im)errorWithBody:attributes: +PostHog | PostHogLogger.fatal(_:) | method | @objc(fatalWithBody:) func fatal(_ body: String) | c:@M@PostHog@objc(cs)PostHogLogger(im)fatalWithBody: +PostHog | PostHogLogger.fatal(_:attributes:) | method | @objc(fatalWithBody:attributes:) func fatal(_ body: String, attributes: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogLogger(im)fatalWithBody:attributes: +PostHog | PostHogLogger.info(_:) | method | @objc(infoWithBody:) func info(_ body: String) | c:@M@PostHog@objc(cs)PostHogLogger(im)infoWithBody: +PostHog | PostHogLogger.info(_:attributes:) | method | @objc(infoWithBody:attributes:) func info(_ body: String, attributes: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogLogger(im)infoWithBody:attributes: +PostHog | PostHogLogger.trace(_:) | method | @objc(traceWithBody:) func trace(_ body: String) | c:@M@PostHog@objc(cs)PostHogLogger(im)traceWithBody: +PostHog | PostHogLogger.trace(_:attributes:) | method | @objc(traceWithBody:attributes:) func trace(_ body: String, attributes: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogLogger(im)traceWithBody:attributes: +PostHog | PostHogLogger.warn(_:) | method | @objc(warnWithBody:) func warn(_ body: String) | c:@M@PostHog@objc(cs)PostHogLogger(im)warnWithBody: +PostHog | PostHogLogger.warn(_:attributes:) | method | @objc(warnWithBody:attributes:) func warn(_ body: String, attributes: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogLogger(im)warnWithBody:attributes: +PostHog | PostHogLogsConfig | class | @objc final class PostHogLogsConfig | c:@M@PostHog@objc(cs)PostHogLogsConfig +PostHog | PostHogLogsConfig.environment | property | @objc var environment: String? | c:@M@PostHog@objc(cs)PostHogLogsConfig(py)environment +PostHog | PostHogLogsConfig.flushAt | property | @objc var flushAt: Int | c:@M@PostHog@objc(cs)PostHogLogsConfig(py)flushAt +PostHog | PostHogLogsConfig.flushIntervalSeconds | property | @objc var flushIntervalSeconds: TimeInterval | c:@M@PostHog@objc(cs)PostHogLogsConfig(py)flushIntervalSeconds +PostHog | PostHogLogsConfig.init() | init | override init() | c:@M@PostHog@objc(cs)PostHogLogsConfig(im)init +PostHog | PostHogLogsConfig.maxBatchSize | property | @objc var maxBatchSize: Int | c:@M@PostHog@objc(cs)PostHogLogsConfig(py)maxBatchSize +PostHog | PostHogLogsConfig.maxBufferSize | property | @objc var maxBufferSize: Int | c:@M@PostHog@objc(cs)PostHogLogsConfig(py)maxBufferSize +PostHog | PostHogLogsConfig.rateCapMaxLogs | property | @objc var rateCapMaxLogs: Int | c:@M@PostHog@objc(cs)PostHogLogsConfig(py)rateCapMaxLogs +PostHog | PostHogLogsConfig.rateCapWindowSeconds | property | @objc var rateCapWindowSeconds: TimeInterval | c:@M@PostHog@objc(cs)PostHogLogsConfig(py)rateCapWindowSeconds +PostHog | PostHogLogsConfig.resourceAttributes | property | @objc var resourceAttributes: [String : Any] | c:@M@PostHog@objc(cs)PostHogLogsConfig(py)resourceAttributes +PostHog | PostHogLogsConfig.serviceName | property | @objc var serviceName: String | c:@M@PostHog@objc(cs)PostHogLogsConfig(py)serviceName +PostHog | PostHogLogsConfig.serviceVersion | property | @objc var serviceVersion: String | c:@M@PostHog@objc(cs)PostHogLogsConfig(py)serviceVersion +PostHog | PostHogLogsConfig.setBeforeSend(_:) | method | @objc func setBeforeSend(_ blocks: [BoxedBeforeSendLogBlock]) | c:@M@PostHog@objc(cs)PostHogLogsConfig(im)setBeforeSend: +PostHog | PostHogLogsConfig.setBeforeSend(_:) | method | func setBeforeSend(_ blocks: PostHogBeforeSendLogBlock...) | s:7PostHog0aB10LogsConfigC13setBeforeSendyyAA0aB9LogRecordCSgAFYbcd_tF +PostHog | PostHogLogsConfig.setBeforeSend(_:) | method | func setBeforeSend(_ blocks: [PostHogBeforeSendLogBlock]) | s:7PostHog0aB10LogsConfigC13setBeforeSendyySayAA0aB9LogRecordCSgAFYbcGF +PostHog | PostHogPersonProfiles | enum | @objc(PostHogPersonProfiles) enum PostHogPersonProfiles | c:@M@PostHog@E@PostHogPersonProfiles +PostHog | PostHogPersonProfiles.always | enum.case | case always | c:@M@PostHog@E@PostHogPersonProfiles@PostHogPersonProfilesAlways +PostHog | PostHogPersonProfiles.identifiedOnly | enum.case | case identifiedOnly | c:@M@PostHog@E@PostHogPersonProfiles@PostHogPersonProfilesIdentifiedOnly +PostHog | PostHogPersonProfiles.init(rawValue:) | init | init?(rawValue: Int) | s:7PostHog0aB14PersonProfilesO8rawValueACSgSi_tcfc +PostHog | PostHogPersonProfiles.never | enum.case | case never | c:@M@PostHog@E@PostHogPersonProfiles@PostHogPersonProfilesNever +PostHog | PostHogRageClickConfig | class | @objc(PostHogRageClickConfig) class PostHogRageClickConfig | c:@M@PostHog@objc(cs)PostHogRageClickConfig +PostHog | PostHogRageClickConfig.enabled | property | @objc var enabled: Bool | c:@M@PostHog@objc(cs)PostHogRageClickConfig(py)enabled +PostHog | PostHogRageClickConfig.minimumTapCount | property | @objc var minimumTapCount: Int | c:@M@PostHog@objc(cs)PostHogRageClickConfig(py)minimumTapCount +PostHog | PostHogRageClickConfig.thresholdPoints | property | @objc var thresholdPoints: CGFloat | c:@M@PostHog@objc(cs)PostHogRageClickConfig(py)thresholdPoints +PostHog | PostHogRageClickConfig.timeoutInterval | property | @objc var timeoutInterval: TimeInterval | c:@M@PostHog@objc(cs)PostHogRageClickConfig(py)timeoutInterval +PostHog | PostHogSDK | class | @objc class PostHogSDK | c:@M@PostHog@objc(cs)PostHogSDK +PostHog | PostHogSDK.addExceptionStep(_:) | method | @objc(addExceptionStep:) func addExceptionStep(_ message: String) | c:@M@PostHog@objc(cs)PostHogSDK(im)addExceptionStep: +PostHog | PostHogSDK.addExceptionStep(_:properties:) | method | @objc(addExceptionStep:properties:) func addExceptionStep(_ message: String, properties: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogSDK(im)addExceptionStep:properties: +PostHog | PostHogSDK.alias(_:) | method | @objc func alias(_ alias: String) | c:@M@PostHog@objc(cs)PostHogSDK(im)alias: +PostHog | PostHogSDK.capture(_:) | method | @objc func capture(_ event: String) | c:@M@PostHog@objc(cs)PostHogSDK(im)capture: +PostHog | PostHogSDK.capture(_:distinctId:properties:userProperties:userPropertiesSetOnce:groups:) | method | @objc(captureWithEvent:distinctId:properties:userProperties:userPropertiesSetOnce:groups:) func capture(_ event: String, distinctId: String? = nil, properties: [String : Any]? = nil, userProperties: [String : Any]? = nil, userPropertiesSetOnce: [String : Any]? = nil, groups: [String : String]? = nil) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureWithEvent:distinctId:properties:userProperties:userPropertiesSetOnce:groups: +PostHog | PostHogSDK.capture(_:distinctId:properties:userProperties:userPropertiesSetOnce:groups:timestamp:) | method | @objc(captureWithEvent:distinctId:properties:userProperties:userPropertiesSetOnce:groups:timestamp:) func capture(_ event: String, distinctId: String? = nil, properties: [String : Any]? = nil, userProperties: [String : Any]? = nil, userPropertiesSetOnce: [String : Any]? = nil, groups: [String : String]? = nil, timestamp: Date? = nil) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureWithEvent:distinctId:properties:userProperties:userPropertiesSetOnce:groups:timestamp: +PostHog | PostHogSDK.capture(_:properties:) | method | @objc(captureWithEvent:properties:) func capture(_ event: String, properties: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureWithEvent:properties: +PostHog | PostHogSDK.capture(_:properties:userProperties:) | method | @objc(captureWithEvent:properties:userProperties:) func capture(_ event: String, properties: [String : Any]? = nil, userProperties: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureWithEvent:properties:userProperties: +PostHog | PostHogSDK.capture(_:properties:userProperties:userPropertiesSetOnce:) | method | @objc(captureWithEvent:properties:userProperties:userPropertiesSetOnce:) func capture(_ event: String, properties: [String : Any]? = nil, userProperties: [String : Any]? = nil, userPropertiesSetOnce: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureWithEvent:properties:userProperties:userPropertiesSetOnce: +PostHog | PostHogSDK.capture(_:properties:userProperties:userPropertiesSetOnce:groups:) | method | @objc(captureWithEvent:properties:userProperties:userPropertiesSetOnce:groups:) func capture(_ event: String, properties: [String : Any]? = nil, userProperties: [String : Any]? = nil, userPropertiesSetOnce: [String : Any]? = nil, groups: [String : String]? = nil) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureWithEvent:properties:userProperties:userPropertiesSetOnce:groups: +PostHog | PostHogSDK.captureDeepLink(openURLContexts:) | method | @objc func captureDeepLink(openURLContexts: Set) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureDeepLinkWithOpenURLContexts: +PostHog | PostHogSDK.captureDeepLink(url:) | method | @objc func captureDeepLink(url: URL) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureDeepLinkWithUrl: +PostHog | PostHogSDK.captureDeepLink(url:options:) | method | @objc func captureDeepLink(url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureDeepLinkWithUrl:options: +PostHog | PostHogSDK.captureDeepLink(urls:) | method | @objc func captureDeepLink(urls: [URL]) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureDeepLinkWithUrls: +PostHog | PostHogSDK.captureDeepLink(userActivity:) | method | @objc func captureDeepLink(userActivity: NSUserActivity) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureDeepLinkWithUserActivity: +PostHog | PostHogSDK.captureException(_:) | method | @objc(captureExceptionWithError:) func captureException(_ error: any Error) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureExceptionWithError: +PostHog | PostHogSDK.captureException(_:) | method | @objc(captureExceptionWithNSException:) func captureException(_ exception: NSException) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureExceptionWithNSException: +PostHog | PostHogSDK.captureException(_:properties:) | method | @objc(captureExceptionWithError:properties:) func captureException(_ error: any Error, properties: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureExceptionWithError:properties: +PostHog | PostHogSDK.captureException(_:properties:) | method | @objc(captureExceptionWithNSException:properties:) func captureException(_ exception: NSException, properties: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureExceptionWithNSException:properties: +PostHog | PostHogSDK.captureFeatureInteraction(flag:flagVariant:) | method | @objc func captureFeatureInteraction(flag: String, flagVariant: String?) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureFeatureInteractionWithFlag:flagVariant: +PostHog | PostHogSDK.captureFeatureView(flag:flagVariant:) | method | @objc func captureFeatureView(flag: String, flagVariant: String?) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureFeatureViewWithFlag:flagVariant: +PostHog | PostHogSDK.captureLog(_:) | method | @objc(captureLogWithBody:) func captureLog(_ body: String) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureLogWithBody: +PostHog | PostHogSDK.captureLog(_:level:) | method | @objc(captureLogWithBody:level:) func captureLog(_ body: String, level: PostHogLogSeverity) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureLogWithBody:level: +PostHog | PostHogSDK.captureLog(_:level:attributes:) | method | @objc(captureLogWithBody:level:attributes:) func captureLog(_ body: String, level: PostHogLogSeverity, attributes: [String : Any]?) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureLogWithBody:level:attributes: +PostHog | PostHogSDK.captureLog(_:level:attributes:traceId:spanId:traceFlags:) | method | @objc(captureLogWithBody:level:attributes:traceId:spanId:traceFlags:) func captureLog(_ body: String, level: PostHogLogSeverity, attributes: [String : Any]?, traceId: String?, spanId: String?, traceFlags: NSNumber?) | c:@M@PostHog@objc(cs)PostHogSDK(im)captureLogWithBody:level:attributes:traceId:spanId:traceFlags: +PostHog | PostHogSDK.captureLog(_:level:attributes:traceId:spanId:traceFlags:) | method | func captureLog(_ body: String, level: PostHogLogSeverity = .info, attributes: [String : Any]? = nil, traceId: String? = nil, spanId: String? = nil, traceFlags: Int? = nil) | s:7PostHog0aB3SDKC10captureLog_5level10attributes7traceId04spanI00H5FlagsySS_AA0abE8SeverityOSDySSypGSgSSSgANSiSgtF +PostHog | PostHogSDK.capturePushNotificationOpened(response:) | method | @objc func capturePushNotificationOpened(response: UNNotificationResponse) | c:@M@PostHog@objc(cs)PostHogSDK(im)capturePushNotificationOpenedWithResponse: +PostHog | PostHogSDK.capturePushNotificationOpened(title:subtitle:body:payload:action:) | method | @objc func capturePushNotificationOpened(title: String? = nil, subtitle: String? = nil, body: String? = nil, payload: [AnyHashable : Any]? = nil, action: String? = nil) | c:@M@PostHog@objc(cs)PostHogSDK(im)capturePushNotificationOpenedWithTitle:subtitle:body:payload:action: +PostHog | PostHogSDK.close() | method | @objc func close() | c:@M@PostHog@objc(cs)PostHogSDK(im)close +PostHog | PostHogSDK.debug(_:) | method | @objc func debug(_ enabled: Bool = true) | c:@M@PostHog@objc(cs)PostHogSDK(im)debug: +PostHog | PostHogSDK.didReceiveFeatureFlags | type.property | @objc static let didReceiveFeatureFlags: Notification.Name | c:@CM@PostHog@objc(cs)PostHogSDK(cpy)didReceiveFeatureFlags +PostHog | PostHogSDK.didStartNotification | type.property | @objc static let didStartNotification: Notification.Name | c:@CM@PostHog@objc(cs)PostHogSDK(cpy)didStartNotification +PostHog | PostHogSDK.endSession() | method | @objc func endSession() | c:@M@PostHog@objc(cs)PostHogSDK(im)endSession +PostHog | PostHogSDK.flush() | method | @objc func flush() | c:@M@PostHog@objc(cs)PostHogSDK(im)flush +PostHog | PostHogSDK.getAllFeatureFlags() | method | @objc func getAllFeatureFlags() -> [PostHogFeatureFlagResult]? | c:@M@PostHog@objc(cs)PostHogSDK(im)getAllFeatureFlags +PostHog | PostHogSDK.getAnonymousId() | method | @objc func getAnonymousId() -> String | c:@M@PostHog@objc(cs)PostHogSDK(im)getAnonymousId +PostHog | PostHogSDK.getDeviceId() | method | @objc func getDeviceId() -> String | c:@M@PostHog@objc(cs)PostHogSDK(im)getDeviceId +PostHog | PostHogSDK.getDistinctId() | method | @objc func getDistinctId() -> String | c:@M@PostHog@objc(cs)PostHogSDK(im)getDistinctId +PostHog | PostHogSDK.getFeatureFlag(_:) | method | @objc func getFeatureFlag(_ key: String) -> Any? | c:@M@PostHog@objc(cs)PostHogSDK(im)getFeatureFlag: +PostHog | PostHogSDK.getFeatureFlag(_:sendFeatureFlagEvent:) | method | @objc(getFeatureFlagWithKey:sendFeatureFlagEvent:) func getFeatureFlag(_ key: String, sendFeatureFlagEvent: Bool) -> Any? | c:@M@PostHog@objc(cs)PostHogSDK(im)getFeatureFlagWithKey:sendFeatureFlagEvent: +PostHog | PostHogSDK.getFeatureFlagResult(_:) | method | @objc func getFeatureFlagResult(_ key: String) -> PostHogFeatureFlagResult? | c:@M@PostHog@objc(cs)PostHogSDK(im)getFeatureFlagResult: +PostHog | PostHogSDK.getFeatureFlagResult(_:sendFeatureFlagEvent:) | method | @objc(getFeatureFlagResultWithKey:sendFeatureFlagEvent:) func getFeatureFlagResult(_ key: String, sendFeatureFlagEvent: Bool) -> PostHogFeatureFlagResult? | c:@M@PostHog@objc(cs)PostHogSDK(im)getFeatureFlagResultWithKey:sendFeatureFlagEvent: +PostHog | PostHogSDK.getSessionId() | method | @objc func getSessionId() -> String? | c:@M@PostHog@objc(cs)PostHogSDK(im)getSessionId +PostHog | PostHogSDK.group(type:key:) | method | @objc(groupWithType:key:) func group(type: String, key: String) | c:@M@PostHog@objc(cs)PostHogSDK(im)groupWithType:key: +PostHog | PostHogSDK.group(type:key:groupProperties:) | method | @objc(groupWithType:key:groupProperties:) func group(type: String, key: String, groupProperties: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogSDK(im)groupWithType:key:groupProperties: +PostHog | PostHogSDK.identify(_:) | method | @objc func identify(_ distinctId: String) | c:@M@PostHog@objc(cs)PostHogSDK(im)identify: +PostHog | PostHogSDK.identify(_:userProperties:) | method | @objc(identifyWithDistinctId:userProperties:) func identify(_ distinctId: String, userProperties: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogSDK(im)identifyWithDistinctId:userProperties: +PostHog | PostHogSDK.identify(_:userProperties:userPropertiesSetOnce:) | method | @objc(identifyWithDistinctId:userProperties:userPropertiesSetOnce:) func identify(_ distinctId: String, userProperties: [String : Any]? = nil, userPropertiesSetOnce: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogSDK(im)identifyWithDistinctId:userProperties:userPropertiesSetOnce: +PostHog | PostHogSDK.isAutocaptureActive() | method | @objc func isAutocaptureActive() -> Bool | c:@M@PostHog@objc(cs)PostHogSDK(im)isAutocaptureActive +PostHog | PostHogSDK.isFeatureEnabled(_:) | method | @objc func isFeatureEnabled(_ key: String) -> Bool | c:@M@PostHog@objc(cs)PostHogSDK(im)isFeatureEnabled: +PostHog | PostHogSDK.isFeatureEnabled(_:sendFeatureFlagEvent:) | method | @objc(isFeatureEnabledWithKey:sendFeatureFlagEvent:) func isFeatureEnabled(_ key: String, sendFeatureFlagEvent: Bool) -> Bool | c:@M@PostHog@objc(cs)PostHogSDK(im)isFeatureEnabledWithKey:sendFeatureFlagEvent: +PostHog | PostHogSDK.isOptOut() | method | @objc func isOptOut() -> Bool | c:@M@PostHog@objc(cs)PostHogSDK(im)isOptOut +PostHog | PostHogSDK.isRageClickActive() | method | @objc func isRageClickActive() -> Bool | c:@M@PostHog@objc(cs)PostHogSDK(im)isRageClickActive +PostHog | PostHogSDK.logger | property | @objc var logger: PostHogLogger? { get } | c:@M@PostHog@objc(cs)PostHogSDK(py)logger +PostHog | PostHogSDK.onFeatureFlags(_:) | method | @discardableResult @objc(onFeatureFlags:) func onFeatureFlags(_ callback: @escaping (PostHogFeatureFlagsLoaded) -> Void) -> PostHogFeatureFlagsSubscription | c:@M@PostHog@objc(cs)PostHogSDK(im)onFeatureFlags: +PostHog | PostHogSDK.optIn() | method | @objc func optIn() | c:@M@PostHog@objc(cs)PostHogSDK(im)optIn +PostHog | PostHogSDK.optOut() | method | @objc func optOut() | c:@M@PostHog@objc(cs)PostHogSDK(im)optOut +PostHog | PostHogSDK.prewarmPushNotificationOpenCapture() | type.method | @objc static func prewarmPushNotificationOpenCapture() | c:@M@PostHog@objc(cs)PostHogSDK(cm)prewarmPushNotificationOpenCapture +PostHog | PostHogSDK.register(_:) | method | @objc(registerProperties:) func register(_ properties: [String : Any]) | c:@M@PostHog@objc(cs)PostHogSDK(im)registerProperties: +PostHog | PostHogSDK.registerPushNotificationToken(_:) | method | @objc func registerPushNotificationToken(_ deviceToken: String) | c:@M@PostHog@objc(cs)PostHogSDK(im)registerPushNotificationToken: +PostHog | PostHogSDK.registerPushNotificationToken(_:appId:) | method | @objc func registerPushNotificationToken(_ deviceToken: String, appId: String?) | c:@M@PostHog@objc(cs)PostHogSDK(im)registerPushNotificationToken:appId: +PostHog | PostHogSDK.reloadFeatureFlags() | method | @objc func reloadFeatureFlags() | c:@M@PostHog@objc(cs)PostHogSDK(im)reloadFeatureFlags +PostHog | PostHogSDK.reloadFeatureFlags(_:) | method | @objc(reloadFeatureFlagsWithCallback:) func reloadFeatureFlags(_ callback: @escaping @Sendable (PostHogFeatureFlagsLoaded) -> Void) | c:@M@PostHog@objc(cs)PostHogSDK(im)reloadFeatureFlagsWithCallback: +PostHog | PostHogSDK.reset() | method | @objc func reset() | c:@M@PostHog@objc(cs)PostHogSDK(im)reset +PostHog | PostHogSDK.resetGroupPropertiesForFlags() | method | @objc func resetGroupPropertiesForFlags() | c:@M@PostHog@objc(cs)PostHogSDK(im)resetGroupPropertiesForFlags +PostHog | PostHogSDK.resetGroupPropertiesForFlags(_:) | method | @objc(resetGroupPropertiesForFlagsWithGroupType:) func resetGroupPropertiesForFlags(_ groupType: String) | c:@M@PostHog@objc(cs)PostHogSDK(im)resetGroupPropertiesForFlagsWithGroupType: +PostHog | PostHogSDK.resetGroupPropertiesForFlags(_:reloadFeatureFlags:) | method | @objc(resetGroupPropertiesForFlagsWithGroupType:reloadFeatureFlags:) func resetGroupPropertiesForFlags(_ groupType: String, reloadFeatureFlags: Bool = true) | c:@M@PostHog@objc(cs)PostHogSDK(im)resetGroupPropertiesForFlagsWithGroupType:reloadFeatureFlags: +PostHog | PostHogSDK.resetGroupPropertiesForFlags(reloadFeatureFlags:) | method | @objc(resetGroupPropertiesForFlagsWithReloadFeatureFlags:) func resetGroupPropertiesForFlags(reloadFeatureFlags: Bool = true) | c:@M@PostHog@objc(cs)PostHogSDK(im)resetGroupPropertiesForFlagsWithReloadFeatureFlags: +PostHog | PostHogSDK.resetPersonPropertiesForFlags() | method | @objc func resetPersonPropertiesForFlags() | c:@M@PostHog@objc(cs)PostHogSDK(im)resetPersonPropertiesForFlags +PostHog | PostHogSDK.resetPersonPropertiesForFlags(reloadFeatureFlags:) | method | @objc(resetPersonPropertiesForFlagsWithReloadFeatureFlags:) func resetPersonPropertiesForFlags(reloadFeatureFlags: Bool = true) | c:@M@PostHog@objc(cs)PostHogSDK(im)resetPersonPropertiesForFlagsWithReloadFeatureFlags: +PostHog | PostHogSDK.screen(_:) | method | @objc func screen(_ screenTitle: String) | c:@M@PostHog@objc(cs)PostHogSDK(im)screen: +PostHog | PostHogSDK.screen(_:properties:) | method | @objc(screenWithTitle:properties:) func screen(_ screenTitle: String, properties: [String : Any]? = nil) | c:@M@PostHog@objc(cs)PostHogSDK(im)screenWithTitle:properties: +PostHog | PostHogSDK.setGroupPropertiesForFlags(_:properties:) | method | @objc(setGroupPropertiesForFlags:properties:) func setGroupPropertiesForFlags(_ groupType: String, properties: [String : Any]) | c:@M@PostHog@objc(cs)PostHogSDK(im)setGroupPropertiesForFlags:properties: +PostHog | PostHogSDK.setGroupPropertiesForFlags(_:properties:reloadFeatureFlags:) | method | @objc(setGroupPropertiesForFlags:properties:reloadFeatureFlags:) func setGroupPropertiesForFlags(_ groupType: String, properties: [String : Any], reloadFeatureFlags: Bool = true) | c:@M@PostHog@objc(cs)PostHogSDK(im)setGroupPropertiesForFlags:properties:reloadFeatureFlags: +PostHog | PostHogSDK.setPersonProperties(userPropertiesToSet:) | method | @objc(setPersonPropertiesWithUserPropertiesToSet:) func setPersonProperties(userPropertiesToSet: [String : Any]?) | c:@M@PostHog@objc(cs)PostHogSDK(im)setPersonPropertiesWithUserPropertiesToSet: +PostHog | PostHogSDK.setPersonProperties(userPropertiesToSet:userPropertiesToSetOnce:) | method | @objc(setPersonPropertiesWithUserPropertiesToSet:userPropertiesToSetOnce:) func setPersonProperties(userPropertiesToSet: [String : Any]?, userPropertiesToSetOnce: [String : Any]?) | c:@M@PostHog@objc(cs)PostHogSDK(im)setPersonPropertiesWithUserPropertiesToSet:userPropertiesToSetOnce: +PostHog | PostHogSDK.setPersonPropertiesForFlags(_:) | method | @objc func setPersonPropertiesForFlags(_ properties: [String : Any]) | c:@M@PostHog@objc(cs)PostHogSDK(im)setPersonPropertiesForFlags: +PostHog | PostHogSDK.setPersonPropertiesForFlags(_:reloadFeatureFlags:) | method | @objc(setPersonPropertiesForFlagsWithProperties:reloadFeatureFlags:) func setPersonPropertiesForFlags(_ properties: [String : Any], reloadFeatureFlags: Bool = true) | c:@M@PostHog@objc(cs)PostHogSDK(im)setPersonPropertiesForFlagsWithProperties:reloadFeatureFlags: +PostHog | PostHogSDK.setup(_:) | method | @objc func setup(_ config: PostHogConfig) | c:@M@PostHog@objc(cs)PostHogSDK(im)setup: +PostHog | PostHogSDK.shared | type.property | @objc nonisolated(unsafe) static let shared: PostHogSDK | c:@M@PostHog@objc(cs)PostHogSDK(cpy)shared +PostHog | PostHogSDK.startSession() | method | @objc func startSession() | c:@M@PostHog@objc(cs)PostHogSDK(im)startSession +PostHog | PostHogSDK.unregister(_:) | method | @objc(unregisterProperties:) func unregister(_ key: String) | c:@M@PostHog@objc(cs)PostHogSDK(im)unregisterProperties: +PostHog | PostHogSDK.unregisterPushNotificationToken() | method | @objc func unregisterPushNotificationToken() | c:@M@PostHog@objc(cs)PostHogSDK(im)unregisterPushNotificationToken +PostHog | PostHogSDK.with(_:) | type.method | @objc static func with(_ config: PostHogConfig) -> PostHogSDK | c:@M@PostHog@objc(cs)PostHogSDK(cm)with: +PostHog | UIView.postHogLabel | property | @MainActor var postHogLabel: String? { get set } | s:So6UIViewC7PostHogE04postC5LabelSSSgvp +PostHog | URLSession.postHogData(for:delegate:postHog:) | method | func postHogData(for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil, postHog: PostHogSDK? = nil) async throws -> (Data, URLResponse) | s:So12NSURLSessionC7PostHogE04postC4Data3for8delegate0dC010Foundation0E0V_So13NSURLResponseCtAH10URLRequestV_So0A12TaskDelegate_pSgAC0bC3SDKCSgtYaKF +PostHog | URLSession.postHogData(for:postHog:) | method | func postHogData(for request: URLRequest, postHog: PostHogSDK? = nil) async throws -> (Data, URLResponse) | s:So12NSURLSessionC7PostHogE04postC4Data3for0dC010Foundation0E0V_So13NSURLResponseCtAG10URLRequestV_AC0bC3SDKCSgtYaKF +PostHog | URLSession.postHogData(from:delegate:postHog:) | method | func postHogData(from url: URL, delegate: (any URLSessionTaskDelegate)? = nil, postHog: PostHogSDK? = nil) async throws -> (Data, URLResponse) | s:So12NSURLSessionC7PostHogE04postC4Data4from8delegate0dC010Foundation0E0V_So13NSURLResponseCtAH3URLV_So0A12TaskDelegate_pSgAC0bC3SDKCSgtYaKF +PostHog | URLSession.postHogData(from:postHog:) | method | func postHogData(from url: URL, postHog: PostHogSDK? = nil) async throws -> (Data, URLResponse) | s:So12NSURLSessionC7PostHogE04postC4Data4from0dC010Foundation0E0V_So13NSURLResponseCtAG3URLV_AC0bC3SDKCSgtYaKF +PostHog | URLSession.postHogDownload(for:delegate:postHog:) | method | func postHogDownload(for request: URLRequest, delegate: (any URLSessionTaskDelegate)? = nil, postHog: PostHogSDK? = nil) async throws -> (URL, URLResponse) | s:So12NSURLSessionC7PostHogE04postC8Download3for8delegate0dC010Foundation3URLV_So13NSURLResponseCtAH10URLRequestV_So0A12TaskDelegate_pSgAC0bC3SDKCSgtYaKF +PostHog | URLSession.postHogDownload(from:delegate:postHog:) | method | func postHogDownload(from url: URL, delegate: (any URLSessionTaskDelegate)? = nil, postHog: PostHogSDK? = nil) async throws -> (URL, URLResponse) | s:So12NSURLSessionC7PostHogE04postC8Download4from8delegate0dC010Foundation3URLV_So13NSURLResponseCtAJ_So0A12TaskDelegate_pSgAC0bC3SDKCSgtYaKF +PostHog | URLSession.postHogDownload(resumeFrom:delegate:postHog:) | method | func postHogDownload(resumeFrom resumeData: Data, delegate: (any URLSessionTaskDelegate)? = nil, postHog: PostHogSDK? = nil) async throws -> (URL, URLResponse) | s:So12NSURLSessionC7PostHogE04postC8Download10resumeFrom8delegate0dC010Foundation3URLV_So13NSURLResponseCtAH4DataV_So0A12TaskDelegate_pSgAC0bC3SDKCSgtYaKF +PostHog | URLSession.postHogUpload(for:from:delegate:postHog:) | method | func postHogUpload(for request: URLRequest, from bodyData: Data, delegate: (any URLSessionTaskDelegate)? = nil, postHog: PostHogSDK? = nil) async throws -> (Data, URLResponse) | s:So12NSURLSessionC7PostHogE04postC6Upload3for4from8delegate0dC010Foundation4DataV_So13NSURLResponseCtAI10URLRequestV_AKSo0A12TaskDelegate_pSgAC0bC3SDKCSgtYaKF +PostHog | URLSession.postHogUpload(for:from:postHog:) | method | func postHogUpload(for request: URLRequest, from bodyData: Data, postHog: PostHogSDK? = nil) async throws -> (Data, URLResponse) | s:So12NSURLSessionC7PostHogE04postC6Upload3for4from0dC010Foundation4DataV_So13NSURLResponseCtAH10URLRequestV_AjC0bC3SDKCSgtYaKF +PostHog | URLSession.postHogUpload(for:fromFile:delegate:postHog:) | method | func postHogUpload(for request: URLRequest, fromFile fileURL: URL, delegate: (any URLSessionTaskDelegate)? = nil, postHog: PostHogSDK? = nil) async throws -> (Data, URLResponse) | s:So12NSURLSessionC7PostHogE04postC6Upload3for8fromFile8delegate0dC010Foundation4DataV_So13NSURLResponseCtAI10URLRequestV_AI3URLVSo0A12TaskDelegate_pSgAC0bC3SDKCSgtYaKF +PostHog | URLSession.postHogUpload(for:fromFile:postHog:) | method | func postHogUpload(for request: URLRequest, fromFile fileURL: URL, postHog: PostHogSDK? = nil) async throws -> (Data, URLResponse) | s:So12NSURLSessionC7PostHogE04postC6Upload3for8fromFile0dC010Foundation4DataV_So13NSURLResponseCtAH10URLRequestV_AH3URLVAC0bC3SDKCSgtYaKF +PostHog | View.postHogDeepLinkListener(_:) | method | @MainActor func postHogDeepLinkListener(_ posthog: PostHogSDK? = nil) -> some View + | s:7SwiftUI4ViewP7PostHogE04postE16DeepLinkListeneryQrAD0dE3SDKCSgF +PostHog | View.postHogLabel(_:) | method | @MainActor func postHogLabel(_ label: String?) -> some View + | s:7SwiftUI4ViewP7PostHogE04postE5LabelyQrSSSgF +PostHog | View.postHogMask(_:) | method | @MainActor func postHogMask(_ isEnabled: Bool = true) -> some View + | s:7SwiftUI4ViewP7PostHogE04postE4MaskyQrSbF +PostHog | View.postHogNoRageClick(_:) | method | @MainActor func postHogNoRageClick(_ isEnabled: Bool = true) -> some View + | s:7SwiftUI4ViewP7PostHogE04postE11NoRageClickyQrSbF +PostHog | View.postHogScreenView(_:_:postHog:) | method | @MainActor func postHogScreenView(_ screenName: String? = nil, _ properties: [String : Any]? = nil, postHog: PostHogSDK? = nil) -> some View + | s:7SwiftUI4ViewP7PostHogE04poste6ScreenC0__0fE0QrSSSg_SDySSypGSgAD0dE3SDKCSgtF +PostHog | View.postHogViewSeen(_:_:postHog:) | method | @MainActor func postHogViewSeen(_ event: String, _ properties: [String : Any]? = nil, postHog: PostHogSDK? = nil) -> some View + | s:7SwiftUI4ViewP7PostHogE04posteC4Seen__0fE0QrSS_SDySSypGSgAD0dE3SDKCSgtF diff --git a/scripts/check-public-api.sh b/scripts/check-public-api.sh index e32c17088c..e31e223350 100755 --- a/scripts/check-public-api.sh +++ b/scripts/check-public-api.sh @@ -2,8 +2,8 @@ set -euo pipefail ROOT_DIR="$(git rev-parse --show-toplevel)" -SNAPSHOT_FILE="$ROOT_DIR/api/posthog-ios.public-api.txt" UPDATE=false +NO_TRAITS=false while [[ $# -gt 0 ]]; do case "$1" in @@ -11,12 +11,18 @@ while [[ $# -gt 0 ]]; do UPDATE=true shift ;; + --no-traits) + NO_TRAITS=true + shift + ;; -\?|-h|--help) cat <<'EOF' -Usage: scripts/check-public-api.sh [--update] +Usage: scripts/check-public-api.sh [--update] [--no-traits] Checks the PostHog Swift public API snapshot. Use --update after intentional public API changes. +Use --no-traits to check the Swift Package Manager build with every package trait +disabled against api/posthog-ios.public-api.no-traits.txt. EOF exit ;; @@ -27,46 +33,83 @@ EOF esac done +if $NO_TRAITS; then + SNAPSHOT_FILE="$ROOT_DIR/api/posthog-ios.public-api.no-traits.txt" + UPDATE_COMMAND="scripts/check-public-api.sh --update --no-traits" + SCOPE="public/open symbols emitted for the PostHog iOS symbol graphs, Swift Package Manager build with --disable-default-traits." +else + SNAPSHOT_FILE="$ROOT_DIR/api/posthog-ios.public-api.txt" + UPDATE_COMMAND="scripts/check-public-api.sh --update" + SCOPE="public/open symbols emitted for the PostHog iOS symbol graphs." +fi + TMP_FILE="$(mktemp)" -DERIVED_DATA_DIR="$(mktemp -d)" +BUILD_DIR="$(mktemp -d)" SYMBOLGRAPH_DIR="$(mktemp -d)" cleanup() { rm -f "$TMP_FILE" - rm -rf "$DERIVED_DATA_DIR" "$SYMBOLGRAPH_DIR" + rm -rf "$BUILD_DIR" "$SYMBOLGRAPH_DIR" } trap cleanup EXIT cd "$ROOT_DIR" set -o pipefail -xcrun xcodebuild build \ - -scheme PostHog \ - -destination generic/platform=ios \ - -derivedDataPath "$DERIVED_DATA_DIR" \ - | xcpretty >/dev/null - -PRODUCTS_DIR="$(find "$DERIVED_DATA_DIR/Build/Products" -maxdepth 1 -type d -name '*iphoneos' | head -n 1)" -if [[ -z "$PRODUCTS_DIR" ]]; then - echo "No iOS build products found under $DERIVED_DATA_DIR/Build/Products" >&2 +IOS_SDK="$(xcrun --sdk iphoneos --show-sdk-path)" +EXTRA_EXTRACT_ARGS=() + +if $NO_TRAITS; then + xcrun swift build \ + --target PostHog \ + --disable-default-traits \ + --triple arm64-apple-ios15.0 \ + --sdk "$IOS_SDK" \ + --build-path "$BUILD_DIR" \ + >/dev/null + + PRODUCTS_DIR="$BUILD_DIR/arm64-apple-ios/debug" + MODULES_DIR="$PRODUCTS_DIR/Modules" + if [[ ! -d "$MODULES_DIR" ]]; then + MODULES_DIR="$PRODUCTS_DIR" + fi + EXTRA_EXTRACT_ARGS=( + -I "$MODULES_DIR" + -Xcc -fmodule-map-file="$PRODUCTS_DIR/PostHogObjCExceptionSupport.build/module.modulemap" + ) +else + xcrun xcodebuild build \ + -scheme PostHog \ + -destination generic/platform=ios \ + -derivedDataPath "$BUILD_DIR" \ + | xcpretty >/dev/null + + PRODUCTS_DIR="$(find "$BUILD_DIR/Build/Products" -maxdepth 1 -type d -name '*iphoneos' | head -n 1)" + EXTRA_EXTRACT_ARGS=(-F "$PRODUCTS_DIR") +fi + +if [[ -z "$PRODUCTS_DIR" || ! -d "$PRODUCTS_DIR" ]]; then + echo "No iOS build products found under $BUILD_DIR" >&2 exit 1 fi xcrun swift-symbolgraph-extract \ -module-name PostHog \ -target arm64-apple-ios15.0 \ - -sdk "$(xcrun --sdk iphoneos --show-sdk-path)" \ + -sdk "$IOS_SDK" \ -I "$PRODUCTS_DIR" \ - -F "$PRODUCTS_DIR" \ + "${EXTRA_EXTRACT_ARGS[@]}" \ -minimum-access-level public \ -skip-synthesized-members \ -output-dir "$SYMBOLGRAPH_DIR" -python3 - "$SYMBOLGRAPH_DIR" "$TMP_FILE" <<'PY' +python3 - "$SYMBOLGRAPH_DIR" "$TMP_FILE" "$UPDATE_COMMAND" "$SCOPE" <<'PY' import json import sys from pathlib import Path symbolgraph_dir = Path(sys.argv[1]) out = Path(sys.argv[2]) +update_command = sys.argv[3] +scope = sys.argv[4] if not symbolgraph_dir.exists(): raise SystemExit(f'No Swift symbol graph output found under {symbolgraph_dir}') @@ -90,9 +133,9 @@ if not records: raise SystemExit('No public PostHog symbols found in generated symbol graphs') lines = [ - '# This file is generated by `scripts/check-public-api.sh --update`.', + f'# This file is generated by `{update_command}`.', '# Do not edit manually.', - '# Public API scope: public/open symbols emitted for the PostHog iOS symbol graphs.', + f'# Public API scope: {scope}', '', ] for module, path_components, kind, declaration, precise in sorted(set(records)): @@ -110,13 +153,13 @@ fi if [[ ! -f "$SNAPSHOT_FILE" ]]; then echo "Missing public API snapshot: $SNAPSHOT_FILE" >&2 - echo "Run scripts/check-public-api.sh --update to create it." >&2 + echo "Run $UPDATE_COMMAND to create it." >&2 exit 1 fi if diff -u "$SNAPSHOT_FILE" "$TMP_FILE"; then - echo "Swift public API snapshot is up to date." + echo "Swift public API snapshot $(basename "$SNAPSHOT_FILE") is up to date." else - echo "Swift public API changed. Run scripts/check-public-api.sh --update if intentional." >&2 + echo "Swift public API changed. Run $UPDATE_COMMAND if intentional." >&2 exit 1 fi From 8273066ff84583efa850d0238615b6763d17ff3e Mon Sep 17 00:00:00 2001 From: Anna Garcia Date: Tue, 27 Oct 2026 13:23:07 -0400 Subject: [PATCH 2/2] chore: regenerate the traits-off API snapshot with CI's Xcode 26.3 Xcode 26.4.1 prints @Sendable on five closure types in the SPM symbol graph; CI's Xcode 26.3 doesn't, so apiCheck failed. --- api/posthog-ios.public-api.no-traits.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/api/posthog-ios.public-api.no-traits.txt b/api/posthog-ios.public-api.no-traits.txt index 41f9a6a690..aea274a331 100644 --- a/api/posthog-ios.public-api.no-traits.txt +++ b/api/posthog-ios.public-api.no-traits.txt @@ -2,14 +2,14 @@ # Do not edit manually. # Public API scope: public/open symbols emitted for the PostHog iOS symbol graphs, Swift Package Manager build with --disable-default-traits. -PostHog | BeforeSendBlock | typealias | typealias BeforeSendBlock = @Sendable (PostHogEvent) -> PostHogEvent? | s:7PostHog15BeforeSendBlocka +PostHog | BeforeSendBlock | typealias | typealias BeforeSendBlock = (PostHogEvent) -> PostHogEvent? | s:7PostHog15BeforeSendBlocka PostHog | BoxedBeforeSendBlock | class | @objc final class BoxedBeforeSendBlock | c:@M@PostHog@objc(cs)BoxedBeforeSendBlock PostHog | BoxedBeforeSendBlock.block | property | @objc let block: BeforeSendBlock | c:@M@PostHog@objc(cs)BoxedBeforeSendBlock(py)block PostHog | BoxedBeforeSendBlock.init(block:) | init | @objc(block:) init(block: @escaping BeforeSendBlock) | c:@M@PostHog@objc(cs)BoxedBeforeSendBlock(im)block: PostHog | BoxedBeforeSendLogBlock | class | @objc final class BoxedBeforeSendLogBlock | c:@M@PostHog@objc(cs)BoxedBeforeSendLogBlock PostHog | BoxedBeforeSendLogBlock.block | property | @objc let block: PostHogBeforeSendLogBlock | c:@M@PostHog@objc(cs)BoxedBeforeSendLogBlock(py)block PostHog | BoxedBeforeSendLogBlock.init(block:) | init | @objc(block:) init(block: @escaping PostHogBeforeSendLogBlock) | c:@M@PostHog@objc(cs)BoxedBeforeSendLogBlock(im)block: -PostHog | PostHogBeforeSendLogBlock | typealias | typealias PostHogBeforeSendLogBlock = @Sendable (PostHogLogRecord) -> PostHogLogRecord? | s:7PostHog0aB18BeforeSendLogBlocka +PostHog | PostHogBeforeSendLogBlock | typealias | typealias PostHogBeforeSendLogBlock = (PostHogLogRecord) -> PostHogLogRecord? | s:7PostHog0aB18BeforeSendLogBlocka PostHog | PostHogBootstrapConfig | class | @objc(PostHogBootstrapConfig) class PostHogBootstrapConfig | c:@M@PostHog@objc(cs)PostHogBootstrapConfig PostHog | PostHogBootstrapConfig.distinctId | property | @objc var distinctId: String? | c:@M@PostHog@objc(cs)PostHogBootstrapConfig(py)distinctId PostHog | PostHogBootstrapConfig.featureFlagPayloads | property | @objc var featureFlagPayloads: [String : Any]? | c:@M@PostHog@objc(cs)PostHogBootstrapConfig(py)featureFlagPayloads @@ -49,7 +49,7 @@ PostHog | PostHogConfig.evaluationContexts | property | @objc var evaluationCont PostHog | PostHogConfig.featureFlagRequestMaxRetries | property | @objc var featureFlagRequestMaxRetries: Int | c:@M@PostHog@objc(cs)PostHogConfig(py)featureFlagRequestMaxRetries PostHog | PostHogConfig.flushAt | property | @objc var flushAt: Int | c:@M@PostHog@objc(cs)PostHogConfig(py)flushAt PostHog | PostHogConfig.flushIntervalSeconds | property | @objc var flushIntervalSeconds: TimeInterval | c:@M@PostHog@objc(cs)PostHogConfig(py)flushIntervalSeconds -PostHog | PostHogConfig.getAnonymousId | property | @objc var getAnonymousId: @Sendable (UUID) -> UUID | c:@M@PostHog@objc(cs)PostHogConfig(py)getAnonymousId +PostHog | PostHogConfig.getAnonymousId | property | @objc var getAnonymousId: (UUID) -> UUID | c:@M@PostHog@objc(cs)PostHogConfig(py)getAnonymousId PostHog | PostHogConfig.host | property | @objc let host: URL | c:@M@PostHog@objc(cs)PostHogConfig(py)host PostHog | PostHogConfig.init(projectToken:) | init | @objc(projectToken:) init(projectToken: String) | c:@M@PostHog@objc(cs)PostHogConfig(im)projectToken: PostHog | PostHogConfig.init(projectToken:host:) | init | @objc(projectToken:host:) init(projectToken: String, host: String = defaultHost) | c:@M@PostHog@objc(cs)PostHogConfig(im)projectToken:host: @@ -61,7 +61,7 @@ PostHog | PostHogConfig.optOut | property | @objc var optOut: Bool | c:@M@PostHo PostHog | PostHogConfig.personProfiles | property | @objc var personProfiles: PostHogPersonProfiles | c:@M@PostHog@objc(cs)PostHogConfig(py)personProfiles PostHog | PostHogConfig.preloadFeatureFlags | property | @objc var preloadFeatureFlags: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)preloadFeatureFlags PostHog | PostHogConfig.projectToken | property | @objc let projectToken: String | c:@M@PostHog@objc(cs)PostHogConfig(py)projectToken -PostHog | PostHogConfig.pushIdentityProvider | property | @objc var pushIdentityProvider: (@Sendable (String, String, @escaping @Sendable (String?) -> Void) -> Void)? | c:@M@PostHog@objc(cs)PostHogConfig(py)pushIdentityProvider +PostHog | PostHogConfig.pushIdentityProvider | property | @objc var pushIdentityProvider: ((String, String, @escaping (String?) -> Void) -> Void)? | c:@M@PostHog@objc(cs)PostHogConfig(py)pushIdentityProvider PostHog | PostHogConfig.rageClickConfig | property | @objc let rageClickConfig: PostHogRageClickConfig | c:@M@PostHog@objc(cs)PostHogConfig(py)rageClickConfig PostHog | PostHogConfig.requestHeaders | property | @objc var requestHeaders: [String : String]? | c:@M@PostHog@objc(cs)PostHogConfig(py)requestHeaders PostHog | PostHogConfig.reuseAnonymousId | property | @objc var reuseAnonymousId: Bool | c:@M@PostHog@objc(cs)PostHogConfig(py)reuseAnonymousId @@ -220,7 +220,7 @@ PostHog | PostHogSDK.register(_:) | method | @objc(registerProperties:) func reg PostHog | PostHogSDK.registerPushNotificationToken(_:) | method | @objc func registerPushNotificationToken(_ deviceToken: String) | c:@M@PostHog@objc(cs)PostHogSDK(im)registerPushNotificationToken: PostHog | PostHogSDK.registerPushNotificationToken(_:appId:) | method | @objc func registerPushNotificationToken(_ deviceToken: String, appId: String?) | c:@M@PostHog@objc(cs)PostHogSDK(im)registerPushNotificationToken:appId: PostHog | PostHogSDK.reloadFeatureFlags() | method | @objc func reloadFeatureFlags() | c:@M@PostHog@objc(cs)PostHogSDK(im)reloadFeatureFlags -PostHog | PostHogSDK.reloadFeatureFlags(_:) | method | @objc(reloadFeatureFlagsWithCallback:) func reloadFeatureFlags(_ callback: @escaping @Sendable (PostHogFeatureFlagsLoaded) -> Void) | c:@M@PostHog@objc(cs)PostHogSDK(im)reloadFeatureFlagsWithCallback: +PostHog | PostHogSDK.reloadFeatureFlags(_:) | method | @objc(reloadFeatureFlagsWithCallback:) func reloadFeatureFlags(_ callback: @escaping (PostHogFeatureFlagsLoaded) -> Void) | c:@M@PostHog@objc(cs)PostHogSDK(im)reloadFeatureFlagsWithCallback: PostHog | PostHogSDK.reset() | method | @objc func reset() | c:@M@PostHog@objc(cs)PostHogSDK(im)reset PostHog | PostHogSDK.resetGroupPropertiesForFlags() | method | @objc func resetGroupPropertiesForFlags() | c:@M@PostHog@objc(cs)PostHogSDK(im)resetGroupPropertiesForFlags PostHog | PostHogSDK.resetGroupPropertiesForFlags(_:) | method | @objc(resetGroupPropertiesForFlagsWithGroupType:) func resetGroupPropertiesForFlags(_ groupType: String) | c:@M@PostHog@objc(cs)PostHogSDK(im)resetGroupPropertiesForFlagsWithGroupType: