Support Watch OS + VisionOS (#180)

* Support Watch OS

* Add apple watch sample app

* Support VisionOS

* Update atlantis-proxyman.podspec
This commit is contained in:
Noah Tran
2025-10-30 11:03:01 +01:00
committed by GitHub
parent aae8dd0f37
commit a9fb8f7f5f
12 changed files with 448 additions and 8 deletions
@@ -7,10 +7,37 @@
objects = {
/* Begin PBXBuildFile section */
BA0355D32EB24DB300DDD23C /* AtlantisWatchOSApp Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = BA0355C92EB24DB200DDD23C /* AtlantisWatchOSApp Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
BA0355DA2EB24E9900DDD23C /* Atlantis in Frameworks */ = {isa = PBXBuildFile; productRef = BA0355D92EB24E9900DDD23C /* Atlantis */; };
BA820CA12D90254E00D79025 /* Atlantis in Frameworks */ = {isa = PBXBuildFile; productRef = BA820CA02D90254E00D79025 /* Atlantis */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
BA0355D12EB24DB300DDD23C /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = BA820C7D2D90252300D79025 /* Project object */;
proxyType = 1;
remoteGlobalIDString = BA0355C82EB24DB200DDD23C;
remoteInfo = "AtlantisWatchOSApp Watch App";
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
BA0355D42EB24DB300DDD23C /* Embed Watch Content */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
dstSubfolderSpec = 16;
files = (
BA0355D32EB24DB300DDD23C /* AtlantisWatchOSApp Watch App.app in Embed Watch Content */,
);
name = "Embed Watch Content";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
BA0355C92EB24DB200DDD23C /* AtlantisWatchOSApp Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "AtlantisWatchOSApp Watch App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
BA820C852D90252300D79025 /* AtlantisSwiftUIApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AtlantisSwiftUIApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
@@ -25,6 +52,11 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
BA0355CA2EB24DB200DDD23C /* AtlantisWatchOSApp Watch App */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = "AtlantisWatchOSApp Watch App";
sourceTree = "<group>";
};
BA820C872D90252300D79025 /* AtlantisSwiftUIApp */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
@@ -36,6 +68,14 @@
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
BA0355C62EB24DB200DDD23C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
BA0355DA2EB24E9900DDD23C /* Atlantis in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
BA820C822D90252300D79025 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -47,10 +87,19 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
BA0355D82EB24E9900DDD23C /* Frameworks */ = {
isa = PBXGroup;
children = (
);
name = Frameworks;
sourceTree = "<group>";
};
BA820C7C2D90252300D79025 = {
isa = PBXGroup;
children = (
BA820C872D90252300D79025 /* AtlantisSwiftUIApp */,
BA0355CA2EB24DB200DDD23C /* AtlantisWatchOSApp Watch App */,
BA0355D82EB24E9900DDD23C /* Frameworks */,
BA820C862D90252300D79025 /* Products */,
);
sourceTree = "<group>";
@@ -59,6 +108,7 @@
isa = PBXGroup;
children = (
BA820C852D90252300D79025 /* AtlantisSwiftUIApp.app */,
BA0355C92EB24DB200DDD23C /* AtlantisWatchOSApp Watch App.app */,
);
name = Products;
sourceTree = "<group>";
@@ -66,6 +116,29 @@
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
BA0355C82EB24DB200DDD23C /* AtlantisWatchOSApp Watch App */ = {
isa = PBXNativeTarget;
buildConfigurationList = BA0355D72EB24DB300DDD23C /* Build configuration list for PBXNativeTarget "AtlantisWatchOSApp Watch App" */;
buildPhases = (
BA0355C52EB24DB200DDD23C /* Sources */,
BA0355C62EB24DB200DDD23C /* Frameworks */,
BA0355C72EB24DB200DDD23C /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
BA0355CA2EB24DB200DDD23C /* AtlantisWatchOSApp Watch App */,
);
name = "AtlantisWatchOSApp Watch App";
packageProductDependencies = (
BA0355D92EB24E9900DDD23C /* Atlantis */,
);
productName = "AtlantisWatchOSApp Watch App";
productReference = BA0355C92EB24DB200DDD23C /* AtlantisWatchOSApp Watch App.app */;
productType = "com.apple.product-type.application";
};
BA820C842D90252300D79025 /* AtlantisSwiftUIApp */ = {
isa = PBXNativeTarget;
buildConfigurationList = BA820C932D90252400D79025 /* Build configuration list for PBXNativeTarget "AtlantisSwiftUIApp" */;
@@ -73,10 +146,12 @@
BA820C812D90252300D79025 /* Sources */,
BA820C822D90252300D79025 /* Frameworks */,
BA820C832D90252300D79025 /* Resources */,
BA0355D42EB24DB300DDD23C /* Embed Watch Content */,
);
buildRules = (
);
dependencies = (
BA0355D22EB24DB300DDD23C /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
BA820C872D90252300D79025 /* AtlantisSwiftUIApp */,
@@ -96,9 +171,12 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1620;
LastSwiftUpdateCheck = 2600;
LastUpgradeCheck = 1620;
TargetAttributes = {
BA0355C82EB24DB200DDD23C = {
CreatedOnToolsVersion = 26.0.1;
};
BA820C842D90252300D79025 = {
CreatedOnToolsVersion = 16.2;
};
@@ -122,11 +200,19 @@
projectRoot = "";
targets = (
BA820C842D90252300D79025 /* AtlantisSwiftUIApp */,
BA0355C82EB24DB200DDD23C /* AtlantisWatchOSApp Watch App */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
BA0355C72EB24DB200DDD23C /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
BA820C832D90252300D79025 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -137,6 +223,13 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
BA0355C52EB24DB200DDD23C /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
BA820C812D90252300D79025 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -146,7 +239,81 @@
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
BA0355D22EB24DB300DDD23C /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = BA0355C82EB24DB200DDD23C /* AtlantisWatchOSApp Watch App */;
targetProxy = BA0355D12EB24DB300DDD23C /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
BA0355D52EB24DB300DDD23C /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 57SKMSUCY8;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = AtlantisWatchOSApp;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.proxyman.AtlantisSwiftUIApp;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.proxyman.AtlantisSwiftUIApp.watchkitapp;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = watchos;
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 26.0;
};
name = Debug;
};
BA0355D62EB24DB300DDD23C /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 57SKMSUCY8;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_CFBundleDisplayName = AtlantisWatchOSApp;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = com.proxyman.AtlantisSwiftUIApp;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.proxyman.AtlantisSwiftUIApp.watchkitapp;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = watchos;
SKIP_INSTALL = YES;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = 4;
WATCHOS_DEPLOYMENT_TARGET = 26.0;
};
name = Release;
};
BA820C912D90252400D79025 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -331,6 +498,15 @@
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
BA0355D72EB24DB300DDD23C /* Build configuration list for PBXNativeTarget "AtlantisWatchOSApp Watch App" */ = {
isa = XCConfigurationList;
buildConfigurations = (
BA0355D52EB24DB300DDD23C /* Debug */,
BA0355D62EB24DB300DDD23C /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
BA820C802D90252300D79025 /* Build configuration list for PBXProject "AtlantisSwiftUIApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -359,6 +535,11 @@
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
BA0355D92EB24E9900DDD23C /* Atlantis */ = {
isa = XCSwiftPackageProductDependency;
package = BA820C9F2D90254E00D79025 /* XCLocalSwiftPackageReference "../../../atlantis" */;
productName = Atlantis;
};
BA820CA02D90254E00D79025 /* Atlantis */ = {
isa = XCSwiftPackageProductDependency;
productName = Atlantis;
@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,13 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "watchos",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,37 @@
//
// AtlantisWatchOSAppApp.swift
// AtlantisWatchOSApp Watch App
//
// Created by nghiatran on 29/10/25.
//
import SwiftUI
#if DEBUG
// 1. Import Atlantis
import Atlantis
#endif
@main
struct AtlantisWatchOSApp_Watch_AppApp: App {
init() {
// 2. Connect to your Macbook
#if DEBUG
Atlantis.start()
// 3. (Optional)
// If you have many Macbooks on the same WiFi Network, you can specify your Macbook's name
// Find your Macbook's name by opening Proxyman App -> Certificate Menu -> Install Certificate for iOS -> With Atlantis ->
// Click on "How to start Atlantis" -> Select "SwiftUI" Tab
// Atlantis.start("Your's Macbook Pro")
#endif
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
@@ -0,0 +1,172 @@
//
// ContentView.swift
// AtlantisWatchOSApp Watch App
//
// Created by nghiatran on 29/10/25.
//
import SwiftUI
struct ContentView: View {
@State private var outputText: String = "Response will appear here..."
@State private var isLoading: Bool = false
var body: some View {
ScrollView {
VStack(spacing: 12) {
// Title
Text("HTTP Tester - Proxyman")
.font(.headline)
.padding(.top, 8)
// Buttons
HStack(spacing: 10) {
Button(action: {
makeGETRequest()
}) {
Label("GET", systemImage: "arrow.down.circle.fill")
.font(.caption)
}
.buttonStyle(.borderedProminent)
.disabled(isLoading)
Button(action: {
makePOSTRequest()
}) {
Label("POST", systemImage: "arrow.up.circle.fill")
.font(.caption)
}
.buttonStyle(.bordered)
.disabled(isLoading)
}
.padding(.horizontal, 4)
// Loading indicator
if isLoading {
ProgressView()
.padding(.vertical, 4)
}
// Output text view
VStack(alignment: .leading, spacing: 4) {
Text("Output:")
.font(.caption2)
.foregroundStyle(.secondary)
Text(outputText)
.font(.system(.caption2, design: .monospaced))
.frame(maxWidth: .infinity, alignment: .leading)
.padding(8)
.background(Color.gray.opacity(0.2))
.cornerRadius(8)
}
.padding(.horizontal, 4)
}
.padding(.bottom, 8)
}
}
// MARK: - HTTP Requests
func makeGETRequest() {
isLoading = true
outputText = "Loading..."
guard let url = URL(string: "https://httpbin.proxyman.app/get?name=AppleWatch&platform=watchOS") else {
outputText = "Invalid URL"
isLoading = false
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
isLoading = false
if let error = error {
outputText = "Error: \(error.localizedDescription)"
return
}
if let httpResponse = response as? HTTPURLResponse {
var result = "Status: \(httpResponse.statusCode)\n\n"
if let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: []),
let jsonData = try? JSONSerialization.data(withJSONObject: json, options: .prettyPrinted),
let jsonString = String(data: jsonData, encoding: .utf8) {
result += jsonString
} else if let data = data,
let rawString = String(data: data, encoding: .utf8) {
result += rawString
}
outputText = result
}
}
}.resume()
}
func makePOSTRequest() {
isLoading = true
outputText = "Loading..."
guard let url = URL(string: "https://httpbin.proxyman.app/post") else {
outputText = "Invalid URL"
isLoading = false
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let jsonBody: [String: Any] = [
"device": "Apple Watch",
"platform": "watchOS",
"action": "test",
"timestamp": Date().timeIntervalSince1970
]
guard let httpBody = try? JSONSerialization.data(withJSONObject: jsonBody, options: []) else {
outputText = "Failed to encode JSON"
isLoading = false
return
}
request.httpBody = httpBody
URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
isLoading = false
if let error = error {
outputText = "Error: \(error.localizedDescription)"
return
}
if let httpResponse = response as? HTTPURLResponse {
var result = "Status: \(httpResponse.statusCode)\n\n"
if let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: []),
let jsonData = try? JSONSerialization.data(withJSONObject: json, options: .prettyPrinted),
let jsonString = String(data: jsonData, encoding: .utf8) {
result += jsonString
} else if let data = data,
let rawString = String(data: data, encoding: .utf8) {
result += rawString
}
outputText = result
}
}
}.resume()
}
}
#Preview {
ContentView()
}
+3 -1
View File
@@ -7,7 +7,9 @@ let package = Package(
name: "Atlantis",
platforms: [.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13)],
.tvOS(.v13),
.watchOS(.v10),
.visionOS(.v1)],
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
+2 -2
View File
@@ -17,7 +17,7 @@
- [x]**No Proxy or trust any Certificates**
- [x] ✅ Capture WS/WSS Traffic from URLSessionWebSocketTask
- [x] Capture gRPC traffic (Advanced)
- [x] Support iOS Physical Devices and Simulators
- [x] Support iOS Physical Devices and Simulators, including iPhone, iPad, Apple Watch, Apple TV
- [x] Review traffic log from macOS [Proxyman](https://proxyman.com) app ([Github](https://github.com/ProxymanApp/Proxyman))
- [x] Categorize the log by project and devices.
- [x] Ready for Production
@@ -30,7 +30,7 @@
## Requirement
- macOS Proxyman app
- iOS 16.0+ / macOS 11+ / Mac Catalyst 13.0+
- iOS 16.0+ / macOS 11+ / Mac Catalyst 13.0+ / tvOS 13.0+ / watchOS 10.0+
- Xcode 14+
- Swift 5.0+
+1 -1
View File
@@ -81,7 +81,7 @@ public final class Atlantis: NSObject {
/// Build version of Atlantis
/// It's essential for Proxyman to known if it's compatible with this version
/// Instead of receving the number from the info.plist, we should hardcode here because the info file doesn't exist in SPM
public static let buildVersion: String = "1.30.1"
public static let buildVersion: String = "1.31.0"
/// Start Swizzle all network functions and monitoring the traffic
/// It also starts looking Bonjour network from Proxyman app.
+17 -1
View File
@@ -14,6 +14,10 @@ typealias Image = NSImage
#elseif os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || os(visionOS)
import UIKit
typealias Image = UIImage
#elseif os(watchOS)
import UIKit
import WatchKit
typealias Image = UIImage
#endif
struct ConnectionPackage: Codable, Serializable {
@@ -222,6 +226,12 @@ struct Device: Codable {
let device = UIDevice.current
name = device.name
model = "\(device.name) (\(device.systemName) \(device.systemVersion))"
#elseif os(watchOS)
let device = WKInterfaceDevice.current()
name = device.name
let systemVersion = ProcessInfo.processInfo.operatingSystemVersion
let versionString = "\(systemVersion.majorVersion).\(systemVersion.minorVersion).\(systemVersion.patchVersion)"
model = "\(device.name) (watchOS \(versionString))"
#endif
}
}
@@ -478,6 +488,12 @@ extension Image {
let iconFiles = primaryIconsDictionary["CFBundleIconFiles"] as? [String],
let lastIcon = iconFiles.last else { return nil }
return Image(named: lastIcon)
#elseif os(watchOS)
guard let iconsDictionary = Bundle.main.infoDictionary?["CFBundleIcons"] as? [String: Any],
let primaryIconsDictionary = iconsDictionary["CFBundlePrimaryIcon"] as? [String: Any],
let iconFiles = primaryIconsDictionary["CFBundleIconFiles"] as? [String],
let lastIcon = iconFiles.last else { return nil }
return Image(named: lastIcon)
#endif
}
@@ -488,7 +504,7 @@ extension Image {
// Resize, we don't need 1024px size
newRep.size = CGSize(width: 64, height: 64)
return newRep.representation(using: .png, properties: [:])
#elseif os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || os(visionOS)
#elseif os(iOS) || targetEnvironment(macCatalyst) || os(tvOS) || os(visionOS) || os(watchOS)
// It's already by 64px
return self.pngData()
#endif
+1 -1
View File
@@ -254,7 +254,7 @@ extension NetServiceTransport {
case .tls:
break
@unknown default:
fatalError()
break
}
case .setup:
break
+3 -1
View File
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "atlantis-proxyman"
spec.version = "1.30.1"
spec.version = "1.31.0"
spec.summary = "A iOS framework for intercepting HTTP/HTTPS Traffic without Proxy and Certificate config"
spec.description = <<-DESC
✅ A iOS framework (Developed and Maintained by Proxyman Team) for intercepting HTTP/HTTPS Traffic from your app. No more messing around with proxy, certificate config.
@@ -21,6 +21,8 @@ Pod::Spec.new do |spec|
spec.ios.deployment_target = "13.0"
spec.osx.deployment_target = "10.15"
spec.tvos.deployment_target = '13.0'
spec.watchos.deployment_target = '10.0'
spec.visionos.deployment_target = '1.0'
spec.module_name = "Atlantis"
spec.source = { :git => "https://github.com/ProxymanApp/atlantis.git", :tag => "#{spec.version}" }