From 401b0594532b0ef850659bede28a3b0bdf7db133 Mon Sep 17 00:00:00 2001 From: Justin Doody Date: Thu, 1 May 2025 19:58:53 -0400 Subject: [PATCH 1/2] UIDevice is not available on macOS, limit function loading to OS's that can import UIKit --- Sources/OpenPanel/OpenPanel.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/OpenPanel/OpenPanel.swift b/Sources/OpenPanel/OpenPanel.swift index e0da6a7..88f7624 100644 --- a/Sources/OpenPanel/OpenPanel.swift +++ b/Sources/OpenPanel/OpenPanel.swift @@ -24,6 +24,7 @@ internal class DeviceInfo { return Bundle.main.bundlePath.hasSuffix(".appex") } + #if canImport(UIKit) private static func getiOSUserAgent() -> String { if !isRunningInExtension() { let webView = WKWebView(frame: .zero) @@ -68,6 +69,7 @@ internal class DeviceInfo { return userAgent } + #endif private static func getMacOSUserAgent() -> String { let processInfo = ProcessInfo.processInfo From c9200e1d8adf5d8df31fdefa6b5e056e417127dd Mon Sep 17 00:00:00 2001 From: Justin Doody Date: Fri, 2 May 2025 18:30:44 -0400 Subject: [PATCH 2/2] Added CI to ensure compilation on (iOS, macOS, and tvOS). Fixed conditional code block compilation errors. Refactored approach to providing device metadata. --- .gitignore | 7 +- OpenPanel-Swift-SDK.xcodeproj/project.pbxproj | 497 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../Sources}/OpenPanel/OpenPanel.swift | 145 ++++- .../OpenPanel_Swift_SDKTests.swift | 17 + Package.swift | 8 +- scripts/test-builds.sh | 23 + 7 files changed, 684 insertions(+), 20 deletions(-) create mode 100644 OpenPanel-Swift-SDK.xcodeproj/project.pbxproj create mode 100644 OpenPanel-Swift-SDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename {Sources => OpenPanel-Swift-SDK/Sources}/OpenPanel/OpenPanel.swift (84%) create mode 100644 OpenPanel-Swift-SDKTests/OpenPanel_Swift_SDKTests.swift create mode 100755 scripts/test-builds.sh diff --git a/.gitignore b/.gitignore index 8c45da7..e681836 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ .DS_Store -.build/* \ No newline at end of file +.build/* + +## User settings +xcuserdata/ + +.swiftpm diff --git a/OpenPanel-Swift-SDK.xcodeproj/project.pbxproj b/OpenPanel-Swift-SDK.xcodeproj/project.pbxproj new file mode 100644 index 0000000..075406b --- /dev/null +++ b/OpenPanel-Swift-SDK.xcodeproj/project.pbxproj @@ -0,0 +1,497 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + D06380072DC47A6F00E2B156 /* OpenPanel_Swift_SDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0637FFD2DC47A6F00E2B156 /* OpenPanel_Swift_SDK.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + D06380082DC47A6F00E2B156 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D0637FF42DC47A6F00E2B156 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D0637FFC2DC47A6F00E2B156; + remoteInfo = "OpenPanel-Swift-SDK"; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + D0637FFD2DC47A6F00E2B156 /* OpenPanel_Swift_SDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OpenPanel_Swift_SDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D06380062DC47A6F00E2B156 /* OpenPanel-Swift-SDKTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "OpenPanel-Swift-SDKTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + D0637FFF2DC47A6F00E2B156 /* OpenPanel-Swift-SDK */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = "OpenPanel-Swift-SDK"; + sourceTree = ""; + }; + D063800A2DC47A6F00E2B156 /* OpenPanel-Swift-SDKTests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = "OpenPanel-Swift-SDKTests"; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + D0637FFA2DC47A6F00E2B156 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D06380032DC47A6F00E2B156 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D06380072DC47A6F00E2B156 /* OpenPanel_Swift_SDK.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D0637FF32DC47A6F00E2B156 = { + isa = PBXGroup; + children = ( + D0637FFF2DC47A6F00E2B156 /* OpenPanel-Swift-SDK */, + D063800A2DC47A6F00E2B156 /* OpenPanel-Swift-SDKTests */, + D0637FFE2DC47A6F00E2B156 /* Products */, + ); + sourceTree = ""; + }; + D0637FFE2DC47A6F00E2B156 /* Products */ = { + isa = PBXGroup; + children = ( + D0637FFD2DC47A6F00E2B156 /* OpenPanel_Swift_SDK.framework */, + D06380062DC47A6F00E2B156 /* OpenPanel-Swift-SDKTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D0637FF82DC47A6F00E2B156 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D0637FFC2DC47A6F00E2B156 /* OpenPanel-Swift-SDK */ = { + isa = PBXNativeTarget; + buildConfigurationList = D063800F2DC47A6F00E2B156 /* Build configuration list for PBXNativeTarget "OpenPanel-Swift-SDK" */; + buildPhases = ( + D0637FF82DC47A6F00E2B156 /* Headers */, + D0637FF92DC47A6F00E2B156 /* Sources */, + D0637FFA2DC47A6F00E2B156 /* Frameworks */, + D0637FFB2DC47A6F00E2B156 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + D0637FFF2DC47A6F00E2B156 /* OpenPanel-Swift-SDK */, + ); + name = "OpenPanel-Swift-SDK"; + packageProductDependencies = ( + ); + productName = "OpenPanel-Swift-SDK"; + productReference = D0637FFD2DC47A6F00E2B156 /* OpenPanel_Swift_SDK.framework */; + productType = "com.apple.product-type.framework"; + }; + D06380052DC47A6F00E2B156 /* OpenPanel-Swift-SDKTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D06380122DC47A6F00E2B156 /* Build configuration list for PBXNativeTarget "OpenPanel-Swift-SDKTests" */; + buildPhases = ( + D06380022DC47A6F00E2B156 /* Sources */, + D06380032DC47A6F00E2B156 /* Frameworks */, + D06380042DC47A6F00E2B156 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D06380092DC47A6F00E2B156 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + D063800A2DC47A6F00E2B156 /* OpenPanel-Swift-SDKTests */, + ); + name = "OpenPanel-Swift-SDKTests"; + packageProductDependencies = ( + ); + productName = "OpenPanel-Swift-SDKTests"; + productReference = D06380062DC47A6F00E2B156 /* OpenPanel-Swift-SDKTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D0637FF42DC47A6F00E2B156 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1630; + LastUpgradeCheck = 1630; + TargetAttributes = { + D0637FFC2DC47A6F00E2B156 = { + CreatedOnToolsVersion = 16.3; + }; + D06380052DC47A6F00E2B156 = { + CreatedOnToolsVersion = 16.3; + }; + }; + }; + buildConfigurationList = D0637FF72DC47A6F00E2B156 /* Build configuration list for PBXProject "OpenPanel-Swift-SDK" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = D0637FF32DC47A6F00E2B156; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = D0637FFE2DC47A6F00E2B156 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D0637FFC2DC47A6F00E2B156 /* OpenPanel-Swift-SDK */, + D06380052DC47A6F00E2B156 /* OpenPanel-Swift-SDKTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D0637FFB2DC47A6F00E2B156 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D06380042DC47A6F00E2B156 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D0637FF92DC47A6F00E2B156 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D06380022DC47A6F00E2B156 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + D06380092DC47A6F00E2B156 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D0637FFC2DC47A6F00E2B156 /* OpenPanel-Swift-SDK */; + targetProxy = D06380082DC47A6F00E2B156 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + D063800D2DC47A6F00E2B156 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + D063800E2DC47A6F00E2B156 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + D06380102DC47A6F00E2B156 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 18.4; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 15.4; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = "dev.openpanel.OpenPanel-Swift-SDK"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = auto; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_MODULE = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,7"; + XROS_DEPLOYMENT_TARGET = 2.4; + }; + name = Debug; + }; + D06380112DC47A6F00E2B156 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 18.4; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 15.4; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = "dev.openpanel.OpenPanel-Swift-SDK"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = auto; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_MODULE = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,3,7"; + XROS_DEPLOYMENT_TARGET = 2.4; + }; + name = Release; + }; + D06380132DC47A6F00E2B156 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.4; + MACOSX_DEPLOYMENT_TARGET = 15.4; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.openpanel.OpenPanel-Swift-SDKTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 2.4; + }; + name = Debug; + }; + D06380142DC47A6F00E2B156 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 18.4; + MACOSX_DEPLOYMENT_TARGET = 15.4; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "dev.openpanel.OpenPanel-Swift-SDKTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = auto; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 2.4; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D0637FF72DC47A6F00E2B156 /* Build configuration list for PBXProject "OpenPanel-Swift-SDK" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D063800D2DC47A6F00E2B156 /* Debug */, + D063800E2DC47A6F00E2B156 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D063800F2DC47A6F00E2B156 /* Build configuration list for PBXNativeTarget "OpenPanel-Swift-SDK" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D06380102DC47A6F00E2B156 /* Debug */, + D06380112DC47A6F00E2B156 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D06380122DC47A6F00E2B156 /* Build configuration list for PBXNativeTarget "OpenPanel-Swift-SDKTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D06380132DC47A6F00E2B156 /* Debug */, + D06380142DC47A6F00E2B156 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = D0637FF42DC47A6F00E2B156 /* Project object */; +} diff --git a/OpenPanel-Swift-SDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/OpenPanel-Swift-SDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/OpenPanel-Swift-SDK.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Sources/OpenPanel/OpenPanel.swift b/OpenPanel-Swift-SDK/Sources/OpenPanel/OpenPanel.swift similarity index 84% rename from Sources/OpenPanel/OpenPanel.swift rename to OpenPanel-Swift-SDK/Sources/OpenPanel/OpenPanel.swift index 88f7624..3de2727 100644 --- a/Sources/OpenPanel/OpenPanel.swift +++ b/OpenPanel-Swift-SDK/Sources/OpenPanel/OpenPanel.swift @@ -1,6 +1,8 @@ import Foundation -#if os(iOS) +#if os(iOS) || os(tvOS) import UIKit +#endif +#if os(iOS) import WebKit #elseif os(macOS) import AppKit @@ -10,11 +12,37 @@ import WebKit // MARK: - DeviceInfo internal class DeviceInfo { + struct Info { + var brand: String = "Apple" + var os: String // tvOS + var osVersion: String // 18.4 + var device: String // smarttv + var model: String // AppleTV6,2 + + var osVersionUnderscored: String { + self.osVersion.replacingOccurrences(of: ".", with: "_") + } + } + + static func getInfo() -> Info { + #if os(iOS) + return getiOSInfo() + #elseif os(macOS) + return getMacOSInfo() + #elseif os(tvOS) + return getTvOSInfo() + #else + return getGenericInfo() + #endif + } + static func getUserAgent() -> String { #if os(iOS) - return getiOSUserAgent() + return getiOSUserAgent(getiOSInfo()) #elseif os(macOS) - return getMacOSUserAgent() + return getMacOSUserAgent(getMacOSInfo()) + #elseif os(tvOS) + return getTvOSUserAgent(getTvOSInfo()) #else return getGenericUserAgent() #endif @@ -24,8 +52,19 @@ internal class DeviceInfo { return Bundle.main.bundlePath.hasSuffix(".appex") } - #if canImport(UIKit) - private static func getiOSUserAgent() -> String { + #if os(iOS) + static func getiOSInfo() -> Info { + let device = UIDevice.current + + // TODO: Get specific iPhone model + return Info( + os: "iOS", + osVersion: device.systemVersion, + device: "mobile", + model: "iPhone") + } + + private static func getiOSUserAgent(_ info: Info) -> String { if !isRunningInExtension() { let webView = WKWebView(frame: .zero) var userAgent = "" @@ -42,19 +81,23 @@ internal class DeviceInfo { } _ = semaphore.wait(timeout: .now() + 1.0) - - userAgent += " OpenPanel/\(OpenPanel.sdkVersion)" if userAgent.isEmpty { - userAgent = getBasicUserAgent() + userAgent = "Mozilla/5.0 (iPhone; U)" } + + userAgent += " OpenPanel/\(OpenPanel.sdkVersion)" return userAgent } else { return getBasicUserAgent() } } - + + + #endif + + #if canImport(UIKit) private static func getBasicUserAgent() -> String { let device = UIDevice.current let systemVersion = device.systemVersion @@ -71,21 +114,81 @@ internal class DeviceInfo { } #endif - private static func getMacOSUserAgent() -> String { + #if os(macOS) + static func getMacOSInfo() -> Info { let processInfo = ProcessInfo.processInfo let osVersion = processInfo.operatingSystemVersionString let versionParts = osVersion.components(separatedBy: " ") let version = versionParts.count > 1 ? versionParts[1] : "Unknown" - let userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X \(version.replacingOccurrences(of: ".", with: "_"))) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15" - - return userAgent + " OpenPanel/\(OpenPanel.sdkVersion)" + return Info( + os: "Mac OS", + osVersion: version, + device: "desktop", + model: getMacModelIdentifier() ?? "Unknown") } + private static func getMacOSUserAgent(_ info: Info) -> String { + let userAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X \(info.osVersionUnderscored); \(info.model)) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15" + + return userAgent + " OpenPanel/\(OpenPanel.sdkVersion)" + } + + static func getMacModelIdentifier() -> String? { + var size: Int = 0 + sysctlbyname("hw.model", nil, &size, nil, 0) + var machine = [CChar](repeating: 0, count: Int(size)) + if sysctlbyname("hw.model", &machine, &size, nil, 0) != 0 { + return nil + } + let code: String = String(cString:machine) + + return code + } + #endif private static func getGenericUserAgent() -> String { let osName = ProcessInfo.processInfo.operatingSystemVersionString return "OpenPanel/\(OpenPanel.sdkVersion) (\(osName))" } + + static func getGenericInfo() -> Info { + return Info( + os: "Unknown", + osVersion: "Unknown", + device: "Unknown", + model: "Unknown") + } + + #if os(tvOS) + static func getAppleTVModelIdentifier() -> String { + var systemInfo = utsname() + uname(&systemInfo) + let mirror = Mirror(reflecting: systemInfo.machine) + let identifier = mirror.children.reduce(into: "") { id, child in + guard let byte = child.value as? Int8, byte != 0 else { return } + id.append(String(UnicodeScalar(UInt8(byte)))) + } + return identifier + } + + static func getTvOSInfo() -> Info { + let device = UIDevice.current + return Info( + os: "tvOS", + osVersion: device.systemVersion, + device: "smarttv", + model: getAppleTVModelIdentifier()) + } + + private static func getTvOSUserAgent(_ info: Info) -> String { + // Construct a user agent string for tvOS + var userAgent = "Mozilla/5.0 (Apple TV; \(info.model); \(info.os) \(info.osVersionUnderscored)) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/\(info.osVersion)" + + userAgent += " OpenPanel/\(OpenPanel.sdkVersion)" + + return userAgent + } + #endif } // MARK: - Payload Types @@ -362,6 +465,20 @@ public class OpenPanel { defaultHeaders: defaultHeaders )) + + let info = DeviceInfo.getInfo() + shared.globalQueue.async(flags: .barrier) { + if shared._global == nil { + shared._global = [:] + } + shared._global?["__brand"] = info.brand + shared._global?["__device"] = info.device + shared._global?["__os"] = info.os + shared._global?["__osVersion"] = info.osVersion + shared._global?["__model"] = info.model + } + + if options.automaticTracking == true { shared.setupAutomaticTracking() } @@ -457,7 +574,7 @@ public class OpenPanel { if let global = shared._global { var mergedProperties = global if let payloadProperties = payload.properties { - mergedProperties.merge(payloadProperties) { (_, new) in (new as AnyObject).value } + mergedProperties.merge(payloadProperties) { (_, new) in new } } updatedPayload.properties = mergedProperties.mapValues { AnyCodable($0) } } diff --git a/OpenPanel-Swift-SDKTests/OpenPanel_Swift_SDKTests.swift b/OpenPanel-Swift-SDKTests/OpenPanel_Swift_SDKTests.swift new file mode 100644 index 0000000..46ed3cd --- /dev/null +++ b/OpenPanel-Swift-SDKTests/OpenPanel_Swift_SDKTests.swift @@ -0,0 +1,17 @@ +// +// OpenPanel_Swift_SDKTests.swift +// OpenPanel-Swift-SDKTests +// +// Created by Justin Doody on 5/1/25. +// + +import Testing +@testable import OpenPanel_Swift_SDK + +struct OpenPanel_Swift_SDKTests { + + @Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. + } + +} diff --git a/Package.swift b/Package.swift index 8c1f4a1..17961c9 100644 --- a/Package.swift +++ b/Package.swift @@ -5,19 +5,17 @@ let package = Package( name: "OpenPanel", platforms: [ .iOS(.v13), - .macOS(.v10_15) + .macOS(.v10_15), + .tvOS(.v15) ], products: [ .library( name: "OpenPanel", targets: ["OpenPanel"]), ], - dependencies: [ - // Add any external dependencies here - ], targets: [ .target( name: "OpenPanel", - dependencies: []), + path: "OpenPanel-Swift-SDK") ] ) diff --git a/scripts/test-builds.sh b/scripts/test-builds.sh new file mode 100755 index 0000000..2d84852 --- /dev/null +++ b/scripts/test-builds.sh @@ -0,0 +1,23 @@ + #!/bin/bash + # Exit immediately if a command exits with a non-zero status. + set -e + + echo "--- Cleaning ---" + swift package clean + # Alternatively, use: xcodebuild clean -scheme OpenPanel + + echo "--- Building for macOS ---" + # Builds for the native architecture of your Mac (Intel or Apple Silicon) + set -o pipefail && xcodebuild build -scheme OpenPanel-Swift-SDK -sdk macosx | xcpretty + + echo "--- Building for iOS Device (ARM64) ---" + # Uses CODE_SIGNING_ALLOWED=NO to bypass code signing issues for compilation check + set -o pipefail && xcodebuild build -scheme OpenPanel-Swift-SDK -sdk iphoneos CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO | xcpretty + + echo "--- Building for tvOS Device (ARM64) ---" + set -o pipefail && xcodebuild build -scheme OpenPanel-Swift-SDK -sdk appletvos CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO | xcpretty + + # echo "--- Building for watchOS Device ---" + # xcodebuild build -scheme OpenPanel -sdk watchos CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO | xcpretty + + echo "--- All targeted builds completed successfully! ---"