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

WIP: web-wrapper demo loads the site in a same-origin iframe #360

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
485 changes: 47 additions & 438 deletions x/examples/web-wrapper/README.md

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion x/examples/web-wrapper/android/app/capacitor.build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-camera')
implementation project(':capacitor-splash-screen')
implementation 'androidx.core:core-ktx:+'

}

Expand Down
2 changes: 1 addition & 1 deletion x/examples/web-wrapper/capacitor.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
}
},
"server": {
"url": "https://www.radiozamaneh.com/"
"url": "https://local.dev:3000"
},
"ios": {
"limitsNavigationsToAppBoundDomains": true
Expand Down
220 changes: 220 additions & 0 deletions x/examples/web-wrapper/docs/android.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
# Building a Censorship-Resistant Android App with CapacitorJS and the Outline SDK Mobileproxy

## Prerequisites

* A website you want to make censorship resistant.
* Make sure your development environment is set up with the following.
* [Node.js](https://nodejs.org/en/), for the Capacitor build system.
* [GoLang](https://go.dev/), to build the Outline Mobile Proxy.
* [OpenJDK 17](https://stackoverflow.com/a/70649641) and [Android Studio](https://developer.android.com/studio/) [Please follow CapacitorJS's environment setup guide](https://capacitorjs.com/docs/getting-started/environment-setup#android-requirements)
* [Wireshark](https://www.wireshark.org/), to confirm traffic is going through the Outline proxy

## Important Notes

* Replace `"www.your-website-url.com"` with your actual website URL in all the relevant code snippets.
* This code lab provides a basic framework. You might need to adjust it depending on your website's specific requirements and the Outline SDK's updates.
* **Security:** Keep your key store file and passwords secure. Losing them can prevent you from updating your app in the future.
* **Testing:** Thoroughly test your app on different devices and Android versions before releasing it.

## Create your app

**1. Set up the Capacitor Project**

Follow the CapacitorJS Getting Started guide to initialize a new project: [https://capacitorjs.com/docs/getting-started](https://capacitorjs.com/docs/getting-started)

> [!NOTE]
> This will create a new directory containing your project. Make sure you run these commands relative to where you want your project to live.

```bash
npm init @capacitor/app # follow the instructions - make sure to pick an app ID that's unique!
cd <my-app-dir>
npm install
```

Add Android platform to your project:

```bash
npm install @capacitor/android
npx cap add android

mkdir android/app/src/main/assets # sync will fail without this folder
npm run build # this builds the stock web app that the default project ships with
npx cap sync android
```

**2. Confirm that the default app is able to run**

Open the Android project in Android Studio with `npx cap open android`.

Ensure you have an emulator with Android API 35 or later (check `Tools > Device Manager`), then press the ▶️ button.

**3. Configure Capacitor to Load Your Website**

Update `capacitor.config.json`:

```json
{
"appId": "com.yourcompany.yourapp",
"appName": "YourAppName",
"bundledWebRuntime": false,
"server": {
"url": "https://www.your-website-url.com"
}
}
```

> [!NOTE]
> Capacitor will silently fail to read your config if the JSON is invalid. Be sure to check `capacitor.config.json` for any hanging commas!

**4. Update your Icon assets and Splash Screen**

First, install the capacitor assets plugin:

```bash
npm install @capacitor/assets --save-dev
```
You'll need to create an `assets` folder in your project root with the following assets:

- A 1024x1024 png titled `icon.png` containing your app icon.
- A 2732x2732 png titled `splash.png` containing your splash screen.
- Another 2732x2732 png titled `splash-dark.png` containing your splash screen in dark mode.

Finally, run the following command to generate and place the assets in the appropriate places in your Android project:

```bash
npx capacitor-assets generate --android
```

**5. Integrate the Outline SDK Mobileproxy Library**

**Build the Mobileproxy library:**
[Follow the instructions in the Outline SDK repository](https://github.com/Jigsaw-Code/outline-sdk/tree/main/x/mobileproxy#build-the-go-mobile-binaries-with-go-build):

```bash
git clone https://github.com/Jigsaw-Code/outline-sdk.git
cd outline-sdk/x
go build -o "$(pwd)/out/" golang.org/x/mobile/cmd/gomobile golang.org/x/mobile/cmd/gobind

PATH="$(pwd)/out:$PATH" gomobile bind -ldflags='-s -w' -target=android -androidapi=21 -o "$(pwd)/out/mobileproxy.aar" github.com/Jigsaw-Code/outline-sdk/x/mobileproxy
```

**Convert your Android project to Kotlin.** Open the Android project with `npx cap open android`.

* Navigate to `java/<your app ID>/MainActivity`
* Right click on the file and select "Convert Java File to Kotlin File". Confirm the following dialogs.
* Once done, you will need to right click the `MainActivity` a second time and select "Convert Java File to Kotlin File"

[See the official instructions if you encounter any issues.](https://developer.android.com/kotlin/add-kotlin)

**Update your Gradle files for Kotlin compatibility.**

* Inside: `/android/app/build.gradle`, add `apply plugin: 'kotlin-android'` on line 2, directly under `apply plugin: 'com.android.application'`.
* Inside: `/android/variables.gradle`, update the SDK variables to:

```kotlin
minSdkVersion = 26
compileSdkVersion = 35
targetSdkVersion = 35
```

**Import dependencies:**

* Right click on `app` and select "Open Module Settings"
* In the sidebar, navigate to "Dependencies"
* Click the `+` button and select a Library Dependency
* Search for `androidx.webkit` and select it.
* Next we need to import the `mobileproxy.aar`. Click the `+` button again and select `JAR/AAR Dependency`.
* Type in the path `../../outline-sdk/x/out/mobileproxy.aar`
* Click `Apply`.
* **Important:** The two added dependencies are initially placed in `/android/app/capacitor.build.gradle`, which is a generated file that gets reset frequently. To avoid losing these dependencies, manually move them to `/android/app/build.gradle`.
* In the head of your new `MainActivity.kt`, import the following:

```kotlin
import android.os.*
import mobileproxy.*
import androidx.webkit.*
```

Now, in your `MainActivity.kt`, confirm proxy override is available in `onCreate`:

```kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
// Proxy override is supported
}
}
```

Initialize `mobileproxy` with a smart dialer in `onCreate`. Don't forget to replace `www.your-website-url.com`!:

```kotlin
private var proxy: Proxy? = null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

if (WebViewFeature.isFeatureSupported(WebViewFeature.PROXY_OVERRIDE)) {
this.proxy = Mobileproxy.runProxy(
"127.0.0.1:0",
Mobileproxy.newSmartStreamDialer(
Mobileproxy.newListFromLines("www.your-website-url.com"),
"{\"dns\":[{\"https\":{\"name\":\"9.9.9.9\"}}],\"tls\":[\"\",\"split:1\",\"split:2\",\"tlsfrag:1\"]}",
Mobileproxy.newStderrLogWriter()
)
)
}
}
```

Proxy all app requests after the proxy is initialized using `ProxyController`:

```kotlin
// NOTE: This affects all requests in the application
ProxyController.getInstance()
.setProxyOverride(
ProxyConfig.Builder()
.addProxyRule(this.proxy!!.address())
.build(),
{
runOnUiThread {
// Capacitor does not expose a way to defer the loading of the webview,
// so we simply refresh the page
this.bridge.webView.reload()
}
},
{}
)
```

Turn off the proxy in `onDestroy`:

```kotlin
override fun onDestroy() {
this.proxy?.stop(3000)
this.proxy = null

super.onDestroy()
}
```

**6. Verify with Packet Tracing**

Start the emulator with `npx cap run android`. Use Wireshark to capture network traffic. Filter by `ip.addr == 9.9.9.9` (your chosen DNS server). You should see TCP and TLS traffic, indicating that your app is using DNS over HTTPS (DoH).

**7. Building and Distributing your App**

First, generate a Key Store and use it to sign your app with Android Studio - follow these instructions: [https://developer.android.com/studio/publish/app-signing#generate-key](https://developer.android.com/studio/publish/app-signing#generate-key)

Note that you can choose to release your app as either an android app bundle (`.aab`) or an APK (`.apk`).

You need an android app bundle (`.aab`) to release your app in the Google Play Store. For this you will have to have a [Google Play Developer Account](https://play.google.com/console/u/0/developers) and at least twenty trusted testers to unlock production access.

* Create a new application.
* Fill in the required information (store listing, pricing, etc.).
* Navigate to "App releases" and select a release track (e.g., internal testing, closed testing, open testing, or production).
* Upload your `.aab` file.
* Follow the instructions to complete the release process.

APKs (`.apk`) can be freely sideloaded onto your user's devices. For an APK, you will have to take care of distribution yourself. **Note that users need to enable "Install unknown apps"** on their Android devices to install apps from sources other than the Google Play Store. This setting is usually found under `Settings > Security` or `Settings > Apps & notifications > Special app access`.
Loading