Skip to content
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
159 changes: 53 additions & 106 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,125 +9,72 @@ on:
- main

jobs:
build-and-test:
swiftpm-test:
name: SwiftPM Tests
runs-on: macos-15
strategy:
fail-fast: false
matrix:
include:
- xcode: "16.2"
ios: "18"

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Select Xcode ${{ matrix.xcode }}
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
- name: Select latest Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable

- name: Show Xcode and Swift version
run: |
xcodebuild -version
swift --version

- name: List available simulators
run: xcrun simctl list devices available
- name: Cache SwiftPM
uses: actions/cache@v4
with:
path: |
.build
~/Library/Caches/org.swift.swiftpm
key: ${{ runner.os }}-swiftpm-${{ hashFiles('Package.swift', 'Package.resolved') }}
restore-keys: |
${{ runner.os }}-swiftpm-

- name: List SwiftPM schemes
run: xcodebuild -workspace .swiftpm/xcode/package.xcworkspace -list
- name: Run SwiftPM Tests
run: swift test

- name: Install xcpretty
run: sudo gem install xcpretty
android-test:
name: Android Tests
runs-on: ubuntu-latest

- name: Select iOS simulator for ${{ matrix.ios }}
env:
IOS_VERSION: ${{ matrix.ios }}
run: |
set -euo pipefail
RUNTIME_JSON=$(xcrun simctl list runtimes --json 2>/dev/null || true)
if [ -z "$RUNTIME_JSON" ]; then
echo "Failed to read simctl runtimes JSON"
xcrun simctl list runtimes || true
exit 1
fi
export RUNTIME_JSON
RUNTIME_ID=$(python3 - <<'PY'
import json, os, sys
data = json.loads(os.environ["RUNTIME_JSON"])
target = os.environ["IOS_VERSION"]
runtimes = [
r for r in data.get("runtimes", [])
if r.get("platform") == "iOS"
and r.get("isAvailable")
and (
r.get("version", "") == target
or r.get("version", "").startswith(target + ".")
)
]
if not runtimes:
print(f"Missing iOS runtime for {target}", file=sys.stderr)
sys.exit(1)
print(runtimes[0]["identifier"])
PY
)
export RUNTIME_ID
DEVICE_JSON=$(xcrun simctl list devices --json 2>/dev/null || true)
if [ -z "$DEVICE_JSON" ]; then
echo "Failed to read simctl devices JSON"
xcrun simctl list devices || true
exit 1
fi
export DEVICE_JSON
DEVICE_ID=$(python3 - <<'PY'
import json, os, sys
data = json.loads(os.environ["DEVICE_JSON"])
runtime = os.environ["RUNTIME_ID"]
devices = data.get("devices", {}).get(runtime, [])
for device in devices:
if device.get("isAvailable") and "iPhone 16" in device.get("name", ""):
print(device["udid"])
sys.exit(0)
for device in devices:
if device.get("isAvailable") and "iPhone" in device.get("name", ""):
print(device["udid"])
sys.exit(0)
print("")
PY
)
if [ -z "$DEVICE_ID" ]; then
DEVICE_TYPES_JSON=$(xcrun simctl list devicetypes --json 2>/dev/null || true)
if [ -z "$DEVICE_TYPES_JSON" ]; then
echo "Failed to read simctl device types JSON"
xcrun simctl list devicetypes || true
exit 1
fi
export DEVICE_TYPES_JSON
DEVICE_TYPE=$(python3 - <<'PY'
import json, sys, os
data = json.loads(os.environ["DEVICE_TYPES_JSON"])
devicetypes = [d for d in data.get("devicetypes", []) if d.get("name", "").startswith("iPhone")]
for device in devicetypes:
if device.get("name") == "iPhone 16":
print(device["identifier"])
sys.exit(0)
if devicetypes:
print(devicetypes[0]["identifier"])
sys.exit(0)
print("", end="")
sys.exit(1)
PY
)
DEVICE_ID=$(xcrun simctl create "CI-iPhone-${IOS_VERSION}" "$DEVICE_TYPE" "$RUNTIME_ID")
fi
echo "SIMULATOR_ID=$DEVICE_ID" >> "$GITHUB_ENV"
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Build and Test on iOS ${{ matrix.ios }}
run: |
set -o pipefail
xcodebuild test \
-workspace .swiftpm/xcode/package.xcworkspace \
-scheme Atlantis \
-destination "id=${SIMULATOR_ID}" \
-skipPackagePluginValidation \
-skipMacroValidation \
| xcpretty --color
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

- name: Cache Gradle packages
uses: actions/cache@v4
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-

- name: Grant execute permission for gradlew
working-directory: atlantis-android
run: chmod +x gradlew

- name: Run Android Unit Tests
working-directory: atlantis-android
run: ./gradlew :atlantis:test --no-daemon

- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: android-test-results
path: atlantis-android/atlantis/build/reports/tests/
167 changes: 167 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- [x] ✅ Capture WS/WSS Traffic from URLSessionWebSocketTask
- [x] Capture gRPC traffic (Advanced)
- [x] Support iOS Physical Devices and Simulators, including iPhone, iPad, Apple Watch, Apple TV
- [x] **NEW:** Support Android with OkHttp, Retrofit, and Apollo
- [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
Expand All @@ -29,11 +30,23 @@
- If you want to use debugging tools, please use normal Proxy.

## Requirement

### iOS
- macOS Proxyman app
- iOS 16.0+ / macOS 11+ / Mac Catalyst 13.0+ / tvOS 13.0+ / watchOS 10.0+
- Xcode 14+
- Swift 5.0+

### Android
- macOS Proxyman app
- Android API 26+ (Android 8.0 Oreo)
- OkHttp 4.x or 5.x
- Kotlin 1.9+

---

# iOS Integration

## 👉 How to use
### 1. Install Atlantis framework
### Swift Packages Manager (Recommended)
Expand Down Expand Up @@ -472,6 +485,160 @@ Atlantis.start()

</details>

---

# Android Integration

Atlantis for Android captures HTTP/HTTPS traffic from OkHttp (including Retrofit and Apollo) and sends it to Proxyman for debugging.

## 1. Install Atlantis Android

### Gradle (Kotlin DSL)

Add to your app's `build.gradle.kts`:

```kotlin
dependencies {
debugImplementation("com.proxyman:atlantis-android:1.0.0")

// You must include OkHttp in your project
implementation("com.squareup.okhttp3:okhttp:4.12.0")
}
```

### Gradle (Groovy)

```groovy
dependencies {
debugImplementation 'com.proxyman:atlantis-android:1.0.0'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
}
```

### JitPack (Alternative)

Add JitPack repository to your `settings.gradle.kts`:

```kotlin
dependencyResolutionManagement {
repositories {
maven { url = uri("https://jitpack.io") }
}
}
```

Then add the dependency:

```kotlin
debugImplementation("com.github.ProxymanApp:atlantis:1.0.0")
```

## 2. Initialize Atlantis

### In your Application class

```kotlin
import android.app.Application
import com.proxyman.atlantis.Atlantis

class MyApplication : Application() {
override fun onCreate() {
super.onCreate()

// Only enable in debug builds
if (BuildConfig.DEBUG) {
// Simple start - discovers all Proxyman apps on network
Atlantis.start(this)

// Or with specific hostname (find it in Proxyman -> Certificate menu)
// Atlantis.start(this, "MacBook-Pro.local")
}
}
}
```

## 3. Add Interceptor to OkHttpClient

```kotlin
import com.proxyman.atlantis.Atlantis
import okhttp3.OkHttpClient

// Create OkHttpClient with Atlantis interceptor
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(Atlantis.getInterceptor())
.build()
```

### With Retrofit

```kotlin
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient) // Use the OkHttpClient with Atlantis
.addConverterFactory(GsonConverterFactory.create())
.build()
```

### With Apollo Kotlin

```kotlin
import com.apollographql.apollo3.ApolloClient

val apolloClient = ApolloClient.Builder()
.serverUrl("https://api.example.com/graphql")
.okHttpClient(okHttpClient) // Use the OkHttpClient with Atlantis
.build()
```

## 4. Required Permissions

Atlantis requires these permissions (automatically added by the library):

```xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
```

## 5. Start Debugging

1. Open **Proxyman** on your Mac
2. Make sure your Android device/emulator and Mac are on the **same Wi-Fi network**
- For emulators: Atlantis automatically connects to `10.0.2.2:10909`
- For physical devices: Uses Network Service Discovery (NSD/mDNS)
3. Run your Android app
4. All HTTP/HTTPS traffic will appear in Proxyman!

## Android Sample App

A sample Android app is included in `atlantis-android/sample/`. To run it:

1. Open `atlantis-android/` in Android Studio
2. Run the `sample` module
3. Tap the buttons to make network requests
4. View the traffic in Proxyman

## Android Troubleshooting

### Traffic not appearing in Proxyman?

1. **Emulator**: Make sure Proxyman is running on your Mac. Atlantis connects to `10.0.2.2:10909`.

2. **Physical device**:
- Ensure both devices are on the same Wi-Fi network
- Try specifying the hostname: `Atlantis.start(this, "Your-Mac.local")`

3. **Check logs**: Look for `[Atlantis]` logs in Logcat for connection status.

### OkHttp version compatibility

Atlantis supports OkHttp 4.x and 5.x. If you're using an older version, please upgrade.

---

## ❓ FAQ
#### 1. How does Atlantis work?
Expand Down
Loading