Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: identify mac catalyst #287

Merged
merged 6 commits into from
Feb 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Next

- fix: identify macOS when running Mac Catalyst or iOS on Mac ([#287](https://github.com/PostHog/posthog-ios/pull/287))

## 3.19.2 - 2025-01-30

- fix: XCFramework builds failing ([#288](https://github.com/PostHog/posthog-ios/pull/288))
Expand Down
6 changes: 3 additions & 3 deletions PostHog.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2021,11 +2021,11 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited)";
MACOSX_DEPLOYMENT_TARGET = 10.15;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogExample.yj;
PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogExample;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
Expand Down Expand Up @@ -2060,7 +2060,7 @@
PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PostHogExample;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
SUPPORTS_MACCATALYST = NO;
SUPPORTS_MACCATALYST = YES;
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
Expand Down
110 changes: 86 additions & 24 deletions PostHog/PostHogContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,38 +53,85 @@ class PostHogContext {
properties["$is_emulator"] = false
#endif

// iOS app running in compatibility mode (Designed for iPad/iPhone)
var isiOSAppOnMac = false
#if os(iOS)
if #available(iOS 14.0, *) {
isiOSAppOnMac = ProcessInfo.processInfo.isiOSAppOnMac
}
#endif

// iPad app running on Mac Catalyst
#if targetEnvironment(macCatalyst)
let isMacCatalystApp = true
#else
let isMacCatalystApp = false
#endif

properties["$is_ios_running_on_mac"] = isiOSAppOnMac
properties["$is_mac_catalyst_app"] = isMacCatalystApp

#if os(iOS) || os(tvOS)
let device = UIDevice.current
// use https://github.com/devicekit/DeviceKit
properties["$device_name"] = device.model
properties["$os_name"] = device.systemName
properties["$os_version"] = device.systemVersion

var deviceType: String?
switch device.userInterfaceIdiom {
case UIUserInterfaceIdiom.phone:
deviceType = "Mobile"
case UIUserInterfaceIdiom.pad:
deviceType = "Tablet"
case UIUserInterfaceIdiom.tv:
deviceType = "TV"
case UIUserInterfaceIdiom.carPlay:
deviceType = "CarPlay"
case UIUserInterfaceIdiom.mac:
deviceType = "Desktop"
default:
deviceType = nil
}
if deviceType != nil {
properties["$device_type"] = deviceType
let processInfo = ProcessInfo.processInfo

if isMacCatalystApp || isiOSAppOnMac {
let underlyingOS = device.systemName
let underlyingOSVersion = device.systemVersion
let macOSVersion = processInfo.operatingSystemVersionString

if isMacCatalystApp {
let osVersion = ProcessInfo.processInfo.operatingSystemVersion
properties["$os_version"] = "\(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)"
} else {
let osVersionString = processInfo.operatingSystemVersionString
if let versionRange = osVersionString.range(of: #"\d+\.\d+\.\d+"#, options: .regularExpression) {
properties["$os_version"] = osVersionString[versionRange]
} else {
// fallback to full version string in case formatting changes
properties["$os_version"] = osVersionString
}
}
// device.userInterfaceIdiom reports .pad here, so we use a static value instead
// - For an app deployable on iPad, the idiom type is always .pad (instead of .mac)
//
// Source: https://developer.apple.com/documentation/apple-silicon/adapting-ios-code-to-run-in-the-macos-environment#Handle-unknown-device-types-gracefully
properties["$os_name"] = "macOS"
properties["$device_type"] = "Desktop"
properties["$device_name"] = processInfo.hostName
Copy link
Member

Choose a reason for hiding this comment

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

what does hostName return here? this is about the device name (Model), and not the user's name or device's nickname

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is equivalent to Host.current().localizedName that we already use on macOS targets (Host is not available on Catalyst).

I think both return the computer name which could leak PII actually, which I agree is concerning. We describe $device_name, however, as "Name of the device" in taxonomy

Regarding the actual device model, we capture as $device_model

Copy link
Member

Choose a reason for hiding this comment

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

this should be the info of the physical device.
So if we're using Host.current().localizedName because device.model isn't possible, then lets keep it

} else {
// use https://github.com/devicekit/DeviceKit
properties["$os_name"] = device.systemName
properties["$os_version"] = device.systemVersion
properties["$device_name"] = device.model

var deviceType: String?
switch device.userInterfaceIdiom {
case UIUserInterfaceIdiom.phone:
deviceType = "Mobile"
case UIUserInterfaceIdiom.pad:
deviceType = "Tablet"
case UIUserInterfaceIdiom.tv:
deviceType = "TV"
case UIUserInterfaceIdiom.carPlay:
deviceType = "CarPlay"
case UIUserInterfaceIdiom.mac:
deviceType = "Desktop"
default:
deviceType = nil
}
if deviceType != nil {
properties["$device_type"] = deviceType
}
}
#elseif os(macOS)
let deviceName = Host.current().localizedName
if (deviceName?.isEmpty) != nil {
properties["$device_name"] = deviceName
}
let processInfo = ProcessInfo.processInfo
properties["$os_name"] = "macOS \(processInfo.operatingSystemVersionString)" // eg Version 14.2.1 (Build 23C71)
properties["$os_name"] = "macOS"
let osVersion = processInfo.operatingSystemVersion
properties["$os_version"] = "\(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)"
properties["$device_type"] = "Desktop"
Expand Down Expand Up @@ -134,10 +181,25 @@ class PostHogContext {
}

private func platform() -> String {
var sysctlName = "hw.machine"

// In case of mac catalyst or iOS running on mac:
// - "hw.machine" returns underlying iPad/iPhone model
// - "hw.model" returns mac model
#if targetEnvironment(macCatalyst)
sysctlName = "hw.model"
#elseif os(iOS)
if #available(iOS 14.0, *) {
if ProcessInfo.processInfo.isiOSAppOnMac {
sysctlName = "hw.model"
}
}
#endif

var size = 0
sysctlbyname("hw.machine", nil, &size, nil, 0)
sysctlbyname(sysctlName, nil, &size, nil, 0)
var machine = [CChar](repeating: 0, count: size)
sysctlbyname("hw.machine", &machine, &size, nil, 0)
sysctlbyname(sysctlName, &machine, &size, nil, 0)
return String(cString: machine)
}

Expand Down
Loading