Skip to content
Open
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
31 changes: 30 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,33 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: android-test-results
path: atlantis-android/atlantis/build/reports/tests/
path: atlantis-android/atlantis/build/reports/tests/

react-native-test:
name: React Native Tests
runs-on: ubuntu-latest

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

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Cache npm packages
uses: actions/cache@v4
with:
path: atlantis-react-native/node_modules
key: ${{ runner.os }}-npm-${{ hashFiles('atlantis-react-native/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-

- name: Install dependencies
working-directory: atlantis-react-native
run: npm install

- name: Run Tests
working-directory: atlantis-react-native
run: npx jest --ci --coverage
2 changes: 1 addition & 1 deletion Sources/Atlantis.swift
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ extension Atlantis {
---------------------------------------------------------------------------------
--------- ⚠️ [Atlantis] MISSING REQUIRED CONFIG from Info.plist for iOS 14+ --------
---------------------------------------------------------------------------------
Read more at: https://docs.proxyman.io/atlantis/atlantis-for-ios
Read more at: https://docs.proxyman.com/atlantis/atlantis-for-ios
Please add the following config to your MainApp's Info.plist

\(instruction.joined(separator: "\n"))
Expand Down
2 changes: 1 addition & 1 deletion Sources/NetworkInjector+URLSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import Foundation

func logError(name: String) {
print("❌ [Atlantis] Could not swizzle this func: \(name)! It looks like the latest iOS (beta) has changed, please contact support@proxyman.io")
print("❌ [Atlantis] Could not swizzle this func: \(name)! It looks like the latest iOS (beta) has changed, please contact support@proxyman.com")
}

extension NetworkInjector {
Expand Down
6 changes: 3 additions & 3 deletions atlantis-android/PUBLISHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,9 @@ afterEvaluate {

developers {
developer {
id.set("nicksantamaria")
name.set("Nghia Tran")
email.set("nicksantamaria@proxyman.io")
id.set("proxymanllc")
name.set("Proxyman LLC")
email.set("support@proxyman.com")
}
}

Expand Down
2 changes: 1 addition & 1 deletion atlantis-android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Capture HTTP/HTTPS traffic from Android apps and send to Proxyman for debugging.

## Overview

Atlantis Android is a companion library to [Proxyman](https://proxyman.io) that allows you to capture and inspect network traffic from your Android applications without configuring a proxy or installing certificates.
Atlantis Android is a companion library to [Proxyman](https://proxyman.com) that allows you to capture and inspect network traffic from your Android applications without configuring a proxy or installing certificates.

## Features

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ import java.util.concurrent.atomic.AtomicBoolean
* - Network Service Discovery to find Proxyman
* - Direct connection support for emulators
*
* @see <a href="https://proxyman.io">Proxyman</a>
* @see <a href="https://github.com/nicksantamaria/atlantis">GitHub Repository</a>
* @see <a href="https://proxyman.com">Proxyman</a>
* @see <a href="https://github.com/ProxymanApp/atlantis">GitHub Repository</a>
*/
object Atlantis {

Expand Down Expand Up @@ -421,7 +421,7 @@ object Atlantis {
private fun printStartupMessage(hostName: String?) {
Log.i(TAG, "---------------------------------------------------------------------------------")
Log.i(TAG, "---------- \uD83E\uDDCA Atlantis Android is running (version $BUILD_VERSION)")
Log.i(TAG, "---------- GitHub: https://github.com/nicksantamaria/atlantis")
Log.i(TAG, "---------- GitHub: https://github.com/ProxymanApp/atlantis")
if (hostName != null) {
Log.i(TAG, "---------- Looking for Proxyman with hostname: $hostName")
} else {
Expand Down
7 changes: 7 additions & 0 deletions atlantis-react-native/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules/
lib/
ios/build/
android/build/
android/.gradle/
*.tgz
.DS_Store
125 changes: 125 additions & 0 deletions atlantis-react-native/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# react-native-atlantis

Capture HTTP/HTTPS traffic from React Native apps and send to [Proxyman](https://proxyman.com) for debugging.

Atlantis for React Native is a thin bridge over the existing [Atlantis iOS](https://github.com/nicksantamaria/atlantis) and [Atlantis Android](https://github.com/nicksantamaria/atlantis/tree/main/atlantis-android) libraries. All network requests made through `fetch()` are automatically captured and forwarded to the Proxyman macOS app over the local network.

## How It Works

- **iOS**: Atlantis swizzles `NSURLSession` methods to intercept all network traffic. Since React Native uses `NSURLSession` under the hood, all `fetch()` calls are captured automatically.
- **Android**: Atlantis injects an OkHttp interceptor into React Native's networking layer via `OkHttpClientProvider`. All `fetch()` calls are captured automatically.

## Requirements

- React Native >= 0.65
- iOS 13.0+
- Android API 26+ (Android 8.0 Oreo)
- [Proxyman macOS app](https://proxyman.com) running on the same network

## Installation

```bash
npm install react-native-atlantis
```

### iOS

```bash
cd ios && pod install
```

Add the following keys to your `Info.plist` to enable Bonjour discovery (required on iOS 14+):

```xml
<key>NSLocalNetworkUsageDescription</key>
<string>Atlantis uses the local network to send HTTP traffic to Proxyman for debugging.</string>
<key>NSBonjourServices</key>
<array>
<string>_Proxyman._tcp</string>
</array>
```

### Android

No additional configuration required. The library auto-links and handles interceptor injection.

## Usage

```typescript
import { start, stop, isRunning } from 'react-native-atlantis';

// Start capturing traffic (in development only)
if (__DEV__) {
start();
}

// Optionally connect to a specific Mac
start('MacBook-Pro.local');

// Stop capturing
stop();

// Check if running
const running = await isRunning();
```

## API

### `start(hostName?: string): void`

Start Atlantis and begin capturing HTTP/HTTPS traffic. Traffic is sent to the Proxyman macOS app over the local network.

| Parameter | Type | Description |
|-----------|------|-------------|
| `hostName` | `string` (optional) | Hostname of the Mac running Proxyman. If omitted, Atlantis auto-discovers Proxyman on the network. |

### `stop(): void`

Stop Atlantis and cease capturing traffic. Disconnects from Proxyman.

### `isRunning(): Promise<boolean>`

Check if Atlantis is currently running and capturing traffic.

## Example App

The `example/` directory contains a React Native app that demonstrates all supported HTTP methods.

### Setup

```bash
cd atlantis-react-native/example
npm install

# iOS
cd ios && pod install && cd ..
npx react-native run-ios

# Android
npx react-native run-android
```

The example app provides buttons for GET, POST, PUT, DELETE, and error (404) requests to `httpbin.proxyman.app`. Start Atlantis, then tap any button to see the traffic appear in Proxyman.

## Troubleshooting

### iOS: Traffic not showing in Proxyman

1. Ensure your iPhone/simulator and Mac are on the same network
2. Check that `NSLocalNetworkUsageDescription` and `NSBonjourServices` are in `Info.plist`
3. On real devices, accept the local network permission dialog

### Android: Traffic not showing in Proxyman

1. On emulators, Atlantis connects directly to `10.0.2.2:10909` (host machine)
2. On real devices, ensure the device and Mac are on the same Wi-Fi network
3. Check logcat for `Atlantis` tag messages

### General

- Ensure Proxyman is running on your Mac before calling `start()`
- Atlantis only captures `fetch()` traffic. Third-party HTTP libraries that bypass React Native's networking layer may not be captured.

## License

MIT
40 changes: 40 additions & 0 deletions atlantis-react-native/android/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("com.facebook.react")
}

android {
namespace = "com.proxyman.atlantis.reactnative"
compileSdk = 36

defaultConfig {
minSdk = 26
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = "17"
}

sourceSets {
getByName("main") {
java.srcDirs("src/main/kotlin")
}
}
}

dependencies {
// React Native (provided by the host app)
compileOnly("com.facebook.react:react-android:+")

// Atlantis Android library
implementation("com.proxyman:atlantis-android:1.33.0")

// OkHttp (provided by React Native / host app)
compileOnly("com.squareup.okhttp3:okhttp:4.12.0")
}
3 changes: 3 additions & 0 deletions atlantis-react-native/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.proxyman.atlantis.reactnative">
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.proxyman.atlantis.reactnative

import com.facebook.react.modules.network.OkHttpClientFactory
import com.facebook.react.modules.network.OkHttpClientProvider
import com.proxyman.atlantis.Atlantis
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit

/**
* Custom OkHttpClientFactory that injects the AtlantisInterceptor
* into React Native's OkHttpClient.
*
* When registered via OkHttpClientProvider.setOkHttpClientFactory(),
* all HTTP/HTTPS requests made through React Native's fetch() API
* will be captured by Atlantis and forwarded to Proxyman.
*/
class AtlantisOkHttpInterceptorFactory : OkHttpClientFactory {

override fun createNewNetworkModuleClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(0, TimeUnit.MILLISECONDS)
.readTimeout(0, TimeUnit.MILLISECONDS)
.writeTimeout(0, TimeUnit.MILLISECONDS)
.cookieJar(java.net.CookieManager().let { manager ->
com.facebook.react.modules.network.ReactCookieJarContainer().also {
it.setCookieJar(okhttp3.JavaNetCookieJar(manager))
}
})
.addInterceptor(Atlantis.getInterceptor())
.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.proxyman.atlantis.reactnative

import com.facebook.fbreact.specs.NativeAtlantisReactNativeSpec
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.modules.network.OkHttpClientProvider
import com.proxyman.atlantis.Atlantis

/**
* React Native TurboModule that bridges to the Atlantis Android library.
*
* Exposes start(), stop(), and isRunning() to JavaScript.
* On start(), it registers an OkHttpClientFactory that injects the
* AtlantisInterceptor into React Native's networking layer.
*
* Extends the codegen-generated NativeAtlantisReactNativeSpec for
* New Architecture (TurboModule) compatibility.
*/
class AtlantisReactNativeModule(
private val reactContext: ReactApplicationContext
) : NativeAtlantisReactNativeSpec(reactContext) {

override fun getName(): String = NAME

companion object {
const val NAME = "AtlantisReactNative"
}

/**
* Start Atlantis and begin capturing HTTP/HTTPS traffic.
*
* This will:
* 1. Register the AtlantisInterceptor with React Native's OkHttpClient
* 2. Start Atlantis NSD discovery (real device) or direct connection (emulator)
* 3. Begin forwarding captured traffic to Proxyman
*
* @param hostName Optional hostname to connect to a specific Proxyman instance
*/
override fun start(hostName: String?) {
// Inject AtlantisInterceptor into React Native's OkHttp client
OkHttpClientProvider.setOkHttpClientFactory(
AtlantisOkHttpInterceptorFactory()
)

// Start Atlantis with optional hostname filter
val resolvedHostName = if (hostName.isNullOrEmpty()) null else hostName
Atlantis.start(reactContext.applicationContext, resolvedHostName)
}

/**
* Stop Atlantis and cease capturing traffic.
*/
override fun stop() {
Atlantis.stop()
}

/**
* Check if Atlantis is currently running.
*/
override fun isRunning(promise: Promise) {
promise.resolve(Atlantis.isRunning())
}
}
Loading