diff --git a/apps/basic-integration/swift/hackers-ios/.env.example b/apps/basic-integration/swift/hackers-ios/.env.example new file mode 100644 index 000000000..745d269ae --- /dev/null +++ b/apps/basic-integration/swift/hackers-ios/.env.example @@ -0,0 +1,2 @@ +POSTHOG_PROJECT_TOKEN=your_posthog_project_token +POSTHOG_HOST=https://your-posthog-host diff --git a/apps/basic-integration/swift/hackers-ios/App/AppDelegate.swift b/apps/basic-integration/swift/hackers-ios/App/AppDelegate.swift index 6e1933209..afe1f3516 100644 --- a/apps/basic-integration/swift/hackers-ios/App/AppDelegate.swift +++ b/apps/basic-integration/swift/hackers-ios/App/AppDelegate.swift @@ -6,6 +6,7 @@ // import Data +import PostHog import Shared import UIKit @@ -13,6 +14,8 @@ class AppDelegate: NSObject, UIApplicationDelegate { func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + configurePostHog() + // Configure a modest shared URL cache to limit on-disk growth from image/HTTP caching // This affects system components like AsyncImage that use URLSession.shared let memoryCapacity = 64 * 1024 * 1024 // 64 MB @@ -36,4 +39,37 @@ class AppDelegate: NSObject, UIApplicationDelegate { return true } + + private func configurePostHog() { + guard let projectToken = Bundle.main.object(forInfoDictionaryKey: "POSTHOG_PROJECT_TOKEN") as? String, + !projectToken.isEmpty, + !projectToken.hasPrefix("$(") + else { + #if DEBUG + assertionFailure("POSTHOG_PROJECT_TOKEN variable required by PostHog is missing or un-configured, this causes events to be silently missed. This error stops appearing once POSTHOG_PROJECT_TOKEN is configured") + #endif + return + } + + guard let host = Bundle.main.object(forInfoDictionaryKey: "POSTHOG_HOST") as? String, + !host.isEmpty, + !host.hasPrefix("$(") + else { + #if DEBUG + assertionFailure("POSTHOG_HOST variable required by PostHog is missing or un-configured, this causes events to be silently missed. This error stops appearing once POSTHOG_HOST is configured") + #endif + return + } + + let config = PostHogConfig(projectToken: projectToken, host: host) + config.errorTrackingConfig.autoCapture = true + config.logs.serviceName = Bundle.main.bundleIdentifier ?? "Hackers" + #if DEBUG + config.logs.environment = "debug" + #else + config.logs.environment = "production" + #endif + PostHogSDK.shared.setup(config) + PostHogSDK.shared.logger?.info("PostHog log capture initialized") + } } diff --git a/apps/basic-integration/swift/hackers-ios/App/ContentView.swift b/apps/basic-integration/swift/hackers-ios/App/ContentView.swift index a9959bced..224c420c1 100644 --- a/apps/basic-integration/swift/hackers-ios/App/ContentView.swift +++ b/apps/basic-integration/swift/hackers-ios/App/ContentView.swift @@ -10,6 +10,7 @@ import Comments import DesignSystem import Domain import Feed +import PostHog import Settings import Shared import SwiftUI @@ -79,12 +80,8 @@ struct MainContentView: View { viewModel: settingsViewModel, isAuthenticated: sessionService.authenticationState == .authenticated, currentUsername: sessionService.username, - onLogin: { username, password in - _ = try await sessionService.authenticate(username: username, password: password) - }, - onLogout: { - sessionService.unauthenticate() - }, + onLogin: authenticate, + onLogout: logout, onShowOnboarding: { showOnboarding = true } @@ -101,12 +98,8 @@ struct MainContentView: View { LoginView( isAuthenticated: sessionService.authenticationState == .authenticated, currentUsername: sessionService.username, - onLogin: { username, password in - _ = try await sessionService.authenticate(username: username, password: password) - }, - onLogout: { - sessionService.unauthenticate() - }, + onLogin: authenticate, + onLogout: logout, textSize: settingsViewModel.textSize ) .textScaling(for: settingsViewModel.textSize) @@ -117,12 +110,8 @@ struct MainContentView: View { viewModel: settingsViewModel, isAuthenticated: sessionService.authenticationState == .authenticated, currentUsername: sessionService.username, - onLogin: { username, password in - _ = try await sessionService.authenticate(username: username, password: password) - }, - onLogout: { - sessionService.unauthenticate() - }, + onLogin: authenticate, + onLogout: logout, onShowOnboarding: { showOnboarding = true } @@ -132,9 +121,7 @@ struct MainContentView: View { } .sheet(isPresented: $showOnboarding) { onboardingCoordinator - .makeOnboardingView { - showOnboarding = false - } + .makeOnboardingView(onDismiss: onboardingDismissed) .textScaling(for: settingsViewModel.textSize) .toastOverlay(toastPresenter) } @@ -143,6 +130,32 @@ struct MainContentView: View { showOnboarding = true } } + .onReceive(NotificationCenter.default.publisher(for: .bookmarksDidChange)) { notification in + guard let isBookmarked = notification.userInfo?["isBookmarked"] as? Bool else { return } + PostHogSDK.shared.capture(isBookmarked ? "bookmark_added" : "bookmark_removed") + PostHogSDK.shared.logger?.info( + "Bookmark state changed", + attributes: ["is_bookmarked": isBookmarked] + ) + } + } + + private func authenticate(username: String, password: String) async throws { + _ = try await sessionService.authenticate(username: username, password: password) + PostHogSDK.shared.capture("login_succeeded") + PostHogSDK.shared.logger?.info("Authentication completed") + } + + private func logout() { + PostHogSDK.shared.capture("logout_requested") + PostHogSDK.shared.logger?.info("Logout requested") + sessionService.unauthenticate() + } + + private func onboardingDismissed() { + PostHogSDK.shared.capture("onboarding_dismissed") + PostHogSDK.shared.logger?.info("Onboarding dismissed") + showOnboarding = false } private var isPresentingModal: Bool { diff --git a/apps/basic-integration/swift/hackers-ios/App/Supporting Files/Hackers-Info.plist b/apps/basic-integration/swift/hackers-ios/App/Supporting Files/Hackers-Info.plist index eb20b4468..cd761d2cc 100644 --- a/apps/basic-integration/swift/hackers-ios/App/Supporting Files/Hackers-Info.plist +++ b/apps/basic-integration/swift/hackers-ios/App/Supporting Files/Hackers-Info.plist @@ -39,6 +39,10 @@ LSRequiresIPhoneOS + POSTHOG_HOST + $(POSTHOG_HOST) + POSTHOG_PROJECT_TOKEN + $(POSTHOG_PROJECT_TOKEN) NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.pbxproj b/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.pbxproj index 25a264601..e6b90cc55 100644 --- a/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.pbxproj +++ b/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ 24F39F8B18AFB1150055F8DC /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24F39F8A18AFB1150055F8DC /* CoreGraphics.framework */; }; 24F39F8D18AFB1150055F8DC /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24F39F8C18AFB1150055F8DC /* UIKit.framework */; }; DA78360FD99A4A1D9C1E759C /* Authentication in Frameworks */ = {isa = PBXBuildFile; productRef = B2F5BEA03AC34B9DB788D8E2 /* Authentication */; }; + F0A1B2C3D4E5F60718293A4B /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = F0A1B2C3D4E5F60718293A4A /* PostHog */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -102,6 +103,7 @@ 2406B38728AD083500CB396B /* SwiftSoup in Frameworks */, 241E7B412E511BC20013EEA1 /* Data in Frameworks */, 248256FB2E59D2540068E91E /* Networking in Frameworks */, + F0A1B2C3D4E5F60718293A4B /* PostHog in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -192,6 +194,7 @@ 248256FD2E59D2660068E91E /* Feed */, 2482576D2E5B38190068E91E /* Onboarding */, B2F5BEA03AC34B9DB788D8E2 /* Authentication */, + F0A1B2C3D4E5F60718293A4A /* PostHog */, ); productName = Hackers2; productReference = 24F39F8518AFB1150055F8DC /* Hackers.app */; @@ -237,6 +240,7 @@ 248256FC2E59D2660068E91E /* XCLocalSwiftPackageReference "Features/Feed" */, 2482576C2E5B38190068E91E /* XCLocalSwiftPackageReference "Features/Onboarding" */, 52A3FC99203E47A9B5690643 /* XCLocalSwiftPackageReference "Features/Authentication" */, + F0A1B2C3D4E5F60718293A49 /* XCRemoteSwiftPackageReference "PostHog" */, ); productRefGroup = 24F39F8618AFB1150055F8DC /* Products */; projectDirPath = ""; @@ -510,6 +514,8 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 5.2.3; + POSTHOG_HOST = https://us.i.posthog.com; + POSTHOG_PROJECT_TOKEN = phx_API_KEY_IS_HARDCODED; PRODUCT_BUNDLE_IDENTIFIER = com.weiranzhang.Hackers; PRODUCT_NAME = Hackers; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -541,6 +547,8 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 5.2.3; + POSTHOG_HOST = https://us.i.posthog.com; + POSTHOG_PROJECT_TOKEN = phx_API_KEY_IS_HARDCODED; PRODUCT_BUNDLE_IDENTIFIER = com.weiranzhang.Hackers; PRODUCT_NAME = Hackers; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -631,6 +639,14 @@ minimumVersion = 2.0.0; }; }; + F0A1B2C3D4E5F60718293A49 /* XCRemoteSwiftPackageReference "PostHog" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/PostHog/posthog-ios.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 3.92.7; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -671,6 +687,11 @@ isa = XCSwiftPackageProductDependency; productName = Authentication; }; + F0A1B2C3D4E5F60718293A4A /* PostHog */ = { + isa = XCSwiftPackageProductDependency; + package = F0A1B2C3D4E5F60718293A49 /* XCRemoteSwiftPackageReference "PostHog" */; + productName = PostHog; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 24F39F7D18AFB1150055F8DC /* Project object */; diff --git a/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index aedcb4505..96a65be7d 100644 --- a/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/apps/basic-integration/swift/hackers-ios/Hackers.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,6 +1,15 @@ { - "originHash" : "944ecf5d94500e9cadaca6ec72c8aa21820f6f136372c503cd78c667c3a9c5be", + "originHash" : "8c1fc4163eedffd94bd8664fb0ed4270742d7b7806448a31b0b28523cb8a6f31", "pins" : [ + { + "identity" : "posthog-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/PostHog/posthog-ios.git", + "state" : { + "revision" : "2b6b6b4301c14c3b51cb1a7827c954bb17d1b083", + "version" : "3.92.7" + } + }, { "identity" : "swiftsoup", "kind" : "remoteSourceControl",