├── .github └── workflows │ └── test.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── dns ├── doc.go ├── resolver.go ├── resolver_net_test.go ├── resolver_test.go ├── stream_dialer.go └── stream_dialer_test.go ├── go.mod ├── go.sum ├── internal └── slicepool │ ├── slicepool.go │ └── slicepool_test.go ├── network ├── delegate_packet_proxy.go ├── delegate_packet_proxy_test.go ├── device.go ├── dnstruncate │ ├── doc.go │ ├── packet_proxy.go │ └── packet_proxy_test.go ├── doc.go ├── errors.go ├── lwip2transport │ ├── device.go │ ├── device_test.go │ ├── doc.go │ ├── tcp.go │ ├── udp.go │ └── udp_test.go ├── packet_listener_proxy.go ├── packet_listener_proxy_test.go └── packet_proxy.go ├── run_on_podman.sh ├── tools.go ├── transport ├── address.go ├── address_test.go ├── doc.go ├── happyeyeballs.go ├── happyeyeballs_test.go ├── packet.go ├── packet_test.go ├── shadowsocks │ ├── cipher.go │ ├── cipher_test.go │ ├── client_testing.go │ ├── compatibility_test.go │ ├── doc.go │ ├── packet.go │ ├── packet_listener.go │ ├── packet_listener_test.go │ ├── packet_test.go │ ├── salt.go │ ├── salt_test.go │ ├── stream.go │ ├── stream_dialer.go │ ├── stream_dialer_test.go │ └── stream_test.go ├── socks5 │ ├── packet_listener.go │ ├── packet_listener_test.go │ ├── socks5.go │ ├── socks5_test.go │ ├── stream_dialer.go │ └── stream_dialer_test.go ├── split │ ├── stream_dialer.go │ ├── writer.go │ └── writer_test.go ├── stream.go ├── stream_test.go ├── tls │ ├── doc.go │ ├── stream_dialer.go │ └── stream_dialer_test.go └── tlsfrag │ ├── buffer.go │ ├── buffer_test.go │ ├── doc.go │ ├── record_len_writer.go │ ├── record_len_writer_test.go │ ├── stream_dialer.go │ ├── stream_dialer_test.go │ ├── tls.go │ └── writer.go └── x ├── LICENSE ├── README.md ├── Taskfile.yml ├── configurl ├── config.go ├── disorder.go ├── dns.go ├── doc.go ├── module.go ├── module_test.go ├── override.go ├── override_test.go ├── shadowsocks.go ├── shadowsocks_test.go ├── socks5.go ├── split.go ├── tls.go ├── tls_test.go ├── tlsfrag.go └── websocket.go ├── connectivity ├── connectivity.go ├── connectivity_test.go ├── errors.go ├── errors_unix.go └── errors_windows.go ├── disorder ├── stream_dialer.go └── writer.go ├── examples ├── connectivity-app │ ├── .gitignore │ ├── .yarn │ │ └── releases │ │ │ └── yarn-4.0.1.cjs │ ├── .yarnrc.yml │ ├── README.md │ ├── app_desktop │ │ ├── app.go │ │ ├── app.ts │ │ ├── generated │ │ │ └── wailsjs │ │ │ │ ├── go │ │ │ │ ├── main │ │ │ │ │ ├── App.d.ts │ │ │ │ │ └── App.js │ │ │ │ └── models.ts │ │ │ │ └── runtime │ │ │ │ ├── package.json │ │ │ │ ├── runtime.d.ts │ │ │ │ └── runtime.js │ │ ├── index.html │ │ ├── main.go │ │ ├── package.json │ │ ├── package.json.md5 │ │ ├── vite.config.ts │ │ └── wails.json │ ├── app_mobile │ │ ├── android │ │ │ ├── .gitignore │ │ │ ├── app │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ ├── capacitor.build.gradle │ │ │ │ ├── proguard-rules.pro │ │ │ │ └── src │ │ │ │ │ ├── androidTest │ │ │ │ │ └── java │ │ │ │ │ │ └── com │ │ │ │ │ │ └── getcapacitor │ │ │ │ │ │ └── myapp │ │ │ │ │ │ └── ExampleInstrumentedTest.java │ │ │ │ │ ├── main │ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ │ ├── java │ │ │ │ │ │ └── org │ │ │ │ │ │ │ └── getoutline │ │ │ │ │ │ │ └── sdk │ │ │ │ │ │ │ └── example │ │ │ │ │ │ │ └── connectivity │ │ │ │ │ │ │ ├── FrontendRequest.kt │ │ │ │ │ │ │ ├── FrontendResponse.kt │ │ │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ │ │ └── MobileBackendPlugin.kt │ │ │ │ │ └── res │ │ │ │ │ │ ├── drawable-land-hdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ │ ├── drawable-land-mdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ │ ├── drawable-land-xhdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ │ ├── drawable-land-xxhdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ │ ├── drawable-land-xxxhdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ │ ├── drawable-port-hdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ │ ├── drawable-port-mdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ │ ├── drawable-port-xhdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ │ ├── drawable-port-xxhdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ │ ├── drawable-port-xxxhdpi │ │ │ │ │ │ └── splash.png │ │ │ │ │ │ ├── drawable-v24 │ │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ │ │ ├── drawable │ │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ │ └── splash.png │ │ │ │ │ │ ├── layout │ │ │ │ │ │ └── activity_main.xml │ │ │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ │ ├── values │ │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ │ ├── strings.xml │ │ │ │ │ │ └── styles.xml │ │ │ │ │ │ └── xml │ │ │ │ │ │ └── file_paths.xml │ │ │ │ │ └── test │ │ │ │ │ └── java │ │ │ │ │ └── com │ │ │ │ │ └── getcapacitor │ │ │ │ │ └── myapp │ │ │ │ │ └── ExampleUnitTest.java │ │ │ ├── build.gradle │ │ │ ├── capacitor.settings.gradle │ │ │ ├── gradle.properties │ │ │ ├── gradle │ │ │ │ └── wrapper │ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ │ └── gradle-wrapper.properties │ │ │ ├── gradlew │ │ │ ├── gradlew.bat │ │ │ ├── settings.gradle │ │ │ └── variables.gradle │ │ ├── app.ts │ │ ├── capacitor.config.ts │ │ ├── index.html │ │ ├── ios │ │ │ ├── .gitignore │ │ │ └── App │ │ │ │ ├── App.xcodeproj │ │ │ │ ├── project.pbxproj │ │ │ │ └── project.xcworkspace │ │ │ │ │ └── contents.xcworkspacedata │ │ │ │ ├── App.xcworkspace │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata │ │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ │ ├── App │ │ │ │ ├── App-Bridging-Header.h │ │ │ │ ├── AppDelegate.swift │ │ │ │ ├── Assets.xcassets │ │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ │ ├── AppIcon-512@2x.png │ │ │ │ │ │ └── Contents.json │ │ │ │ │ ├── Contents.json │ │ │ │ │ └── Splash.imageset │ │ │ │ │ │ ├── Contents.json │ │ │ │ │ │ ├── splash-2732x2732-1.png │ │ │ │ │ │ ├── splash-2732x2732-2.png │ │ │ │ │ │ └── splash-2732x2732.png │ │ │ │ ├── Base.lproj │ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ │ └── Main.storyboard │ │ │ │ ├── Info.plist │ │ │ │ ├── MobileBackend.m │ │ │ │ └── MobileBackend.swift │ │ │ │ ├── Podfile │ │ │ │ └── Podfile.lock │ │ ├── manifest.json │ │ ├── package.json │ │ └── vite.config.ts │ ├── go.mod │ ├── go.sum │ ├── package.json │ ├── shared_backend │ │ ├── handle_request.go │ │ ├── main.go │ │ └── package.json │ ├── shared_frontend │ │ ├── assets │ │ │ └── fonts │ │ │ │ └── jigsaw_sans │ │ │ │ ├── bold.woff2 │ │ │ │ └── regular.woff2 │ │ ├── index.ts │ │ ├── package.json │ │ ├── pages │ │ │ ├── connectivity_test │ │ │ │ ├── generated │ │ │ │ │ └── messages │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── locales │ │ │ │ │ │ ├── es-419.ts │ │ │ │ │ │ ├── fa-IR.ts │ │ │ │ │ │ └── zh-Hans.ts │ │ │ │ ├── index.ts │ │ │ │ ├── localize.json │ │ │ │ └── types.ts │ │ │ └── index.ts │ │ ├── theme.css │ │ └── tsconfig.json │ ├── tsconfig.google.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── yarn.lock ├── fetch-proxy │ └── main.go ├── fetch-psiphon │ ├── README.md │ └── main.go ├── fetch-speed │ ├── README.md │ └── main.go ├── fetch │ ├── README.md │ └── main.go ├── fyne-proxy │ ├── FyneApp.toml │ ├── Icon.png │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── main.go │ └── tools.go ├── fyne-tools │ ├── FyneApp.toml │ ├── Icon.png │ ├── README.md │ ├── cname_desktop.go │ ├── cname_mobile.go │ ├── go.mod │ ├── go.sum │ ├── main.go │ └── tools.go ├── http2transport │ ├── README.md │ └── main.go ├── measure │ ├── main.go │ └── make_soax_config.sh ├── outline-cli │ ├── README.md │ ├── app.go │ ├── app_linux.go │ ├── app_other.go │ ├── dns_linux.go │ ├── ipv6_linux.go │ ├── main.go │ ├── outline_device.go │ ├── outline_packet_proxy.go │ ├── routing_linux.go │ └── tun_device_linux.go ├── resolve │ ├── README.md │ └── main.go ├── run-mobileproxy │ └── main.go ├── smart-proxy │ ├── config.yaml │ ├── config_broken.yaml │ ├── file_cache.go │ └── main.go ├── test-connectivity │ ├── README.md │ └── main.go ├── website-wrapper-app │ ├── .gitignore │ ├── .scripts │ │ └── build_mobileproxy.sh │ ├── LICENSE │ ├── README.md │ ├── basic_navigator_example │ │ ├── .scripts │ │ │ └── start.mjs │ │ └── src │ │ │ └── index.html │ ├── doctor │ ├── package-lock.json │ ├── package.json │ └── wrapper_app_project │ │ ├── .scripts │ │ └── build.mjs │ │ └── template │ │ ├── .gitignore │ │ ├── README.md.handlebars │ │ ├── android │ │ ├── .gitignore │ │ ├── app │ │ │ ├── .gitignore │ │ │ ├── build.gradle.handlebars │ │ │ ├── capacitor.build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src │ │ │ │ ├── androidTest │ │ │ │ └── java │ │ │ │ │ └── com │ │ │ │ │ └── getcapacitor │ │ │ │ │ └── myapp │ │ │ │ │ └── ExampleInstrumentedTest.java │ │ │ │ ├── main │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── assets │ │ │ │ │ └── .keep │ │ │ │ ├── java │ │ │ │ │ └── org │ │ │ │ │ │ └── getoutline │ │ │ │ │ │ └── pwa │ │ │ │ │ │ ├── Config.kt.handlebars │ │ │ │ │ │ └── MainActivity.kt.handlebars │ │ │ │ └── res │ │ │ │ │ ├── drawable-land-hdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-land-mdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-land-xhdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-land-xxhdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-land-xxxhdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-port-hdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-port-mdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-port-xhdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-port-xxhdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-port-xxxhdpi │ │ │ │ │ └── splash.png │ │ │ │ │ ├── drawable-v24 │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ │ ├── drawable │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ └── splash.png │ │ │ │ │ ├── layout │ │ │ │ │ └── activity_main.xml │ │ │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ └── ic_launcher_round.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ ├── ic_launcher.png │ │ │ │ │ ├── ic_launcher_foreground.png │ │ │ │ │ └── ic_launcher_round.png │ │ │ │ │ ├── values │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ ├── strings.xml.handlebars │ │ │ │ │ └── styles.xml │ │ │ │ │ └── xml │ │ │ │ │ └── file_paths.xml │ │ │ │ └── test │ │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── getcapacitor │ │ │ │ └── myapp │ │ │ │ └── ExampleUnitTest.java │ │ ├── build.gradle │ │ ├── capacitor.settings.gradle │ │ ├── gradle.properties │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── settings.gradle │ │ └── variables.gradle │ │ ├── assets │ │ └── .keep │ │ ├── capacitor.config.json.handlebars │ │ ├── ios │ │ ├── .gitignore │ │ └── App │ │ │ ├── App.xcodeproj │ │ │ └── project.pbxproj.handlebars │ │ │ ├── App.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ │ ├── App │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets │ │ │ │ ├── AppIcon.appiconset │ │ │ │ │ ├── AppIcon-512@2x.png │ │ │ │ │ └── Contents.json │ │ │ │ ├── Contents.json │ │ │ │ └── Splash.imageset │ │ │ │ │ ├── Contents.json │ │ │ │ │ ├── splash-2732x2732-1.png │ │ │ │ │ ├── splash-2732x2732-2.png │ │ │ │ │ └── splash-2732x2732.png │ │ │ ├── Base.lproj │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── Config.swift.handlebars │ │ │ ├── Info.plist.handlebars │ │ │ └── OutlineBridgeViewController.swift │ │ │ ├── Podfile │ │ │ └── Podfile.lock │ │ └── package.json └── ws2endpoint │ ├── README.md │ └── main.go ├── go.mod ├── go.sum ├── httpconnect ├── connect_client.go ├── connect_client_test.go ├── doc.go └── pipe_conn.go ├── httpproxy ├── connect_handler.go ├── connect_handler_test.go ├── doc.go ├── forward_handler.go ├── path_handler.go └── proxy_handler.go ├── mobileproxy ├── .gitignore ├── README.md ├── mobileproxy.go └── tools.go ├── psiphon ├── build_tag.go ├── doc.go ├── psiphon.go ├── psiphon_integration_test.go ├── psiphon_test.go └── testdata │ └── integration_test_config.yaml ├── report ├── report.go └── report_test.go ├── smart ├── README.md ├── cache.go ├── cname.go ├── cname_unix.go ├── dns.go ├── doc.go ├── psiphon_dialer.go ├── psiphon_dialer_unimplemented.go ├── racer.go ├── slice_helper.go ├── slice_helper_test.go ├── strategy_result.go ├── strategy_result_test.go ├── stream_dialer.go ├── stream_dialer_integration_test.go └── stream_dialer_test.go ├── sockopt ├── sockopt.go └── sockopt_test.go ├── sysproxy ├── doc.go ├── sysproxy_darwin.go ├── sysproxy_linux.go ├── sysproxy_other.go ├── sysproxy_test.go └── sysproxy_windows.go └── websocket ├── endpoint.go └── endpoint_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .trunk 3 | .vscode 4 | 5 | 6 | # MacOS files 7 | .DS_Store 8 | .idea 9 | 10 | # Xcode 11 | App.xcscheme 12 | 13 | # Android Studio 14 | output-metadata.json 15 | 16 | # Not using Zero-Installs 17 | .yarn/* 18 | !.yarn/releases 19 | !.yarn/plugins 20 | .pnp.* 21 | .yarn-integrity 22 | 23 | # using Zero Installs 24 | #.yarn/unplugged 25 | #.yarn/build-state.yml 26 | 27 | # Tasks 28 | .task 29 | out -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Jigsaw-Code/outline-sdk 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/eycorsican/go-tun2socks v1.16.11 7 | github.com/google/go-licenses v1.6.0 8 | github.com/google/gopacket v1.1.19 9 | github.com/shadowsocks/go-shadowsocks2 v0.1.5 10 | github.com/stretchr/testify v1.8.4 11 | github.com/things-go/go-socks5 v0.0.5 12 | golang.org/x/crypto v0.18.0 13 | golang.org/x/net v0.20.0 14 | ) 15 | 16 | require ( 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/emirpasic/gods v1.12.0 // indirect 19 | github.com/go-logr/logr v1.2.0 // indirect 20 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 21 | github.com/google/licenseclassifier v0.0.0-20210722185704-3043a050f148 // indirect 22 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 23 | github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect 24 | github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect 25 | github.com/mitchellh/go-homedir v1.1.0 // indirect 26 | github.com/otiai10/copy v1.6.0 // indirect 27 | github.com/pmezard/go-difflib v1.0.0 // indirect 28 | github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect 29 | github.com/sergi/go-diff v1.2.0 // indirect 30 | github.com/spf13/cobra v1.6.0 // indirect 31 | github.com/spf13/pflag v1.0.5 // indirect 32 | github.com/src-d/gcfg v1.4.0 // indirect 33 | github.com/xanzy/ssh-agent v0.2.1 // indirect 34 | go.opencensus.io v0.23.0 // indirect 35 | golang.org/x/mod v0.8.0 // indirect 36 | golang.org/x/sys v0.16.0 // indirect 37 | golang.org/x/text v0.14.0 // indirect 38 | golang.org/x/tools v0.6.0 // indirect 39 | gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect 40 | gopkg.in/src-d/go-git.v4 v4.13.1 // indirect 41 | gopkg.in/warnings.v0 v0.1.2 // indirect 42 | gopkg.in/yaml.v3 v3.0.1 // indirect 43 | k8s.io/klog/v2 v2.80.1 // indirect 44 | ) 45 | -------------------------------------------------------------------------------- /internal/slicepool/slicepool_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package slicepool 16 | 17 | import ( 18 | "testing" 19 | ) 20 | 21 | func TestPool(t *testing.T) { 22 | pool := MakePool(10) 23 | slice := pool.LazySlice() 24 | buf := slice.Acquire() 25 | if len(buf) != 10 { 26 | t.Errorf("Wrong slice length: %d", len(buf)) 27 | } 28 | slice.Release() 29 | } 30 | 31 | func BenchmarkPool(b *testing.B) { 32 | pool := MakePool(10) 33 | for i := 0; i < b.N; i++ { 34 | slice := pool.LazySlice() 35 | slice.Acquire() 36 | slice.Release() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /network/dnstruncate/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package dnstruncate functions as an alternative implementation that handles DNS requests if the remote server doesn't 17 | support UDP traffic. This is done by always setting the TC (truncated) bit in the DNS response header; it tells the 18 | caller to resend the DNS request using TCP instead of UDP. As a result, no UDP requests are made to the remote server. 19 | 20 | This implementation is ported from the [go-tun2socks' dnsfallback.NewUDPHandler]. 21 | 22 | Note that UDP traffic that are not DNS requests are dropped. 23 | 24 | To create a [network.PacketProxy] that handles DNS requests locally: 25 | 26 | proxy, err := dnstruncate.NewPacketProxy() 27 | if err != nil { 28 | // handle error 29 | } 30 | 31 | This `proxy` can then be used in, for example, lwip2transport.ConfigureDevice. 32 | 33 | [go-tun2socks' dnsfallback.NewUDPHandler]: https://github.com/eycorsican/go-tun2socks/blob/master/proxy/dnsfallback/udp.go 34 | */ 35 | package dnstruncate 36 | -------------------------------------------------------------------------------- /network/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | The network package defines interfaces and provides utilities for network layer (OSI layer 3) functionalities. For 17 | example, you can use the [IPDevice] interface to read and write IP packets from a physical or virtual network device. 18 | 19 | In addition, the sub-packages include user-space network stack implementations (such as [network/lwip2transport]) that 20 | can translate raw IP packets into TCP/UDP flows. You can implement a [PacketProxy] to handle UDP traffic, and a 21 | [transport.StreamDialer] to handle TCP traffic. 22 | */ 23 | package network 24 | -------------------------------------------------------------------------------- /network/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package network 16 | 17 | import ( 18 | "errors" 19 | ) 20 | 21 | // Portable analogs of some common errors. 22 | // 23 | // Errors returned from this package and all sub-packages can be tested against these errors using [errors.Is]. 24 | var ( 25 | // ErrClosed is the error returned by an I/O call on a network device or proxy that has already been closed, or that is 26 | // closed by another goroutine before the I/O is completed. This can be wrapped in another error, and should normally 27 | // be tested using errors.Is(err, network.ErrClosed). 28 | ErrClosed = errors.New("network device already closed") 29 | 30 | // ErrPortUnreachable is an error that indicates a remote server's port cannot be reached. This can be wrapped in 31 | // another error, and should normally be tested using errors.Is(err, network.ErrPortUnreachable). 32 | ErrPortUnreachable = errors.New("port is not reachable") 33 | ) 34 | -------------------------------------------------------------------------------- /network/lwip2transport/device_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package lwip2transport 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "net" 21 | "os" 22 | "syscall" 23 | "testing" 24 | 25 | "github.com/Jigsaw-Code/outline-sdk/network" 26 | "github.com/Jigsaw-Code/outline-sdk/transport" 27 | "github.com/stretchr/testify/require" 28 | ) 29 | 30 | func TestStackClosedWriteError(t *testing.T) { 31 | h := &errTcpUdpHandler{err: errors.New("not supported")} 32 | t2s := reConfigurelwIPDeviceForTest(t, h, h) 33 | 34 | t2s.stack.Close() // close the underlying stack without calling Close 35 | n, err := t2s.Write([]byte{0x01}) 36 | require.Exactly(t, 0, n) 37 | require.ErrorIs(t, err, network.ErrClosed) 38 | 39 | // network.ErrClosed should not wrap golang's ErrClosed errors 40 | require.NotErrorIs(t, err, os.ErrClosed) 41 | require.NotErrorIs(t, err, net.ErrClosed) 42 | require.NotErrorIs(t, err, syscall.ESHUTDOWN) 43 | } 44 | 45 | func reConfigurelwIPDeviceForTest(t *testing.T, sd transport.StreamDialer, pp network.PacketProxy) *lwIPDevice { 46 | t2s, err := ConfigureDevice(sd, pp) 47 | require.NoError(t, err) 48 | t2sInternal, ok := t2s.(*lwIPDevice) 49 | require.True(t, ok) 50 | return t2sInternal 51 | } 52 | 53 | type errTcpUdpHandler struct { 54 | err error 55 | } 56 | 57 | func (h *errTcpUdpHandler) DialStream(context.Context, string) (transport.StreamConn, error) { 58 | return nil, h.err 59 | } 60 | 61 | func (h *errTcpUdpHandler) NewSession(network.PacketResponseReceiver) (network.PacketRequestSender, error) { 62 | return nil, h.err 63 | } 64 | -------------------------------------------------------------------------------- /network/lwip2transport/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | The network/lwip2transport package translates between IP packets and TCP/UDP protocols. It uses a [modified lwIP go 17 | library], which is based on the original [lwIP library] (A Lightweight TCP/IP stack). The device is singleton, so only 18 | one instance can be created per process. 19 | 20 | To configure the instance with TCP/UDP handlers: 21 | 22 | // tcpHandler will be used to handle TCP streams, and udpHandler to handle UDP packets 23 | t2s, err := lwip2transport.ConfigureDevice(tcpHandler, udpHandler) 24 | if err != nil { 25 | // handle error 26 | } 27 | 28 | [modified lwIP go library]: https://github.com/eycorsican/go-tun2socks 29 | [lwIP library]: https://savannah.nongnu.org/projects/lwip/ 30 | */ 31 | package lwip2transport 32 | -------------------------------------------------------------------------------- /network/packet_listener_proxy_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package network 16 | 17 | import ( 18 | "testing" 19 | "time" 20 | 21 | "github.com/Jigsaw-Code/outline-sdk/transport" 22 | "github.com/stretchr/testify/require" 23 | ) 24 | 25 | func TestWithWriteTimeoutOptionWorks(t *testing.T) { 26 | pl := &transport.UDPListener{} 27 | 28 | defProxy, err := NewPacketProxyFromPacketListener(pl) 29 | require.NoError(t, err) 30 | require.NotNil(t, defProxy) 31 | require.Equal(t, 30*time.Second, defProxy.writeIdleTimeout) // default timeout is 30s 32 | 33 | altProxy, err := NewPacketProxyFromPacketListener(pl, WithPacketListenerWriteIdleTimeout(5*time.Minute)) 34 | require.NoError(t, err) 35 | require.NotNil(t, altProxy) 36 | require.Equal(t, 5*time.Minute, altProxy.writeIdleTimeout) 37 | } 38 | -------------------------------------------------------------------------------- /run_on_podman.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright 2023 The Outline Authors 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | function main() { 18 | declare -r bin="$1" 19 | # Remove the binary name from the args 20 | shift 1 21 | # We are using Google's ~2MB minimal image. See https://github.com/GoogleContainerTools/distroless. 22 | podman run --arch $(uname -m) --rm -it -v "${bin}":/outline/bin gcr.io/distroless/static-debian11 /outline/bin "$@" 23 | } 24 | 25 | main "$@" 26 | -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build tools 16 | // +build tools 17 | 18 | // See https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 19 | // and https://github.com/go-modules-by-example/index/blob/master/010_tools/README.md 20 | 21 | package tools 22 | 23 | import ( 24 | _ "github.com/google/go-licenses" 25 | ) 26 | -------------------------------------------------------------------------------- /transport/address_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package transport 16 | 17 | import ( 18 | "net" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | func TestMakeNetAddrType(t *testing.T) { 25 | for _, address := range []string{"example.com:53", "127.0.0.1:443", "[::1]:443"} { 26 | for _, network := range []string{"tcp", "udp"} { 27 | netAddr, err := MakeNetAddr(network, address) 28 | require.NoError(t, err) 29 | require.Equal(t, network, netAddr.Network()) 30 | require.Equal(t, address, netAddr.String()) 31 | if address == "example.com:53" { 32 | require.IsType(t, &domainAddr{}, netAddr) 33 | } else { 34 | switch network { 35 | case "udp": 36 | require.IsType(t, &net.UDPAddr{}, netAddr) 37 | case "tcp": 38 | require.IsType(t, &net.TCPAddr{}, netAddr) 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | func TestMakeNetAddrDomainCase(t *testing.T) { 46 | netAddr, err := MakeNetAddr("tcp", "Example.Com:83") 47 | require.NoError(t, err) 48 | require.Equal(t, "Example.Com:83", netAddr.String()) 49 | } 50 | 51 | func TestMakeNetAddrIP4(t *testing.T) { 52 | netAddr, err := MakeNetAddr("tcp", "127.0.0.1:83") 53 | require.NoError(t, err) 54 | require.Equal(t, "127.0.0.1:83", netAddr.String()) 55 | } 56 | 57 | func TestMakeNetAddrIP6(t *testing.T) { 58 | netAddr, err := MakeNetAddr("tcp", "[0000:0000:0000::0001]:83") 59 | require.NoError(t, err) 60 | require.Equal(t, "[::1]:83", netAddr.String()) 61 | } 62 | 63 | func TestMakeNetAddrResolvePort(t *testing.T) { 64 | netAddr, err := MakeNetAddr("udp", "example.com:domain") 65 | require.NoError(t, err) 66 | require.Equal(t, "example.com:53", netAddr.String()) 67 | } 68 | -------------------------------------------------------------------------------- /transport/shadowsocks/client_testing.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package shadowsocks 16 | 17 | import ( 18 | "bytes" 19 | "io" 20 | "testing" 21 | ) 22 | 23 | const ( 24 | testTargetAddr = "test.local:1111" 25 | ) 26 | 27 | // Writes `payload` to `conn` and reads it into `buf`, which we take as a parameter to avoid 28 | // reallocations in benchmarks and memory profiles. Fails the test if the read payload does not match. 29 | func expectEchoPayload(conn io.ReadWriter, payload, buf []byte, t testing.TB) { 30 | _, err := conn.Write(payload) 31 | if err != nil { 32 | t.Fatalf("Failed to write payload: %v", err) 33 | } 34 | n, err := conn.Read(buf) 35 | if err != nil { 36 | t.Fatalf("Failed to read payload: %v", err) 37 | } 38 | if !bytes.Equal(payload, buf[:n]) { 39 | t.Fatalf("Expected output '%v'. Got '%v'", payload, buf[:n]) 40 | } 41 | } 42 | 43 | func makeTestKey(tb testing.TB) *EncryptionKey { 44 | key, err := NewEncryptionKey(CHACHA20IETFPOLY1305, "testPassword") 45 | if err != nil { 46 | tb.Fatalf("Failed to create key: %v", err) 47 | } 48 | return key 49 | } 50 | 51 | // makeTestPayload returns a slice of `size` arbitrary bytes. 52 | func makeTestPayload(size int) []byte { 53 | payload := make([]byte, size) 54 | for i := 0; i < size; i++ { 55 | payload[i] = byte(i) 56 | } 57 | return payload 58 | } 59 | -------------------------------------------------------------------------------- /transport/shadowsocks/compatibility_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package shadowsocks 16 | 17 | import ( 18 | "io" 19 | "net" 20 | "sync" 21 | "testing" 22 | 23 | "github.com/shadowsocks/go-shadowsocks2/core" 24 | "github.com/shadowsocks/go-shadowsocks2/shadowaead" 25 | "github.com/stretchr/testify/require" 26 | ) 27 | 28 | func TestCompatibility(t *testing.T) { 29 | cipherName := "chacha20-ietf-poly1305" 30 | secret := "secret" 31 | fromLeft := "payload1" 32 | fromRight := "payload2" 33 | left, right := net.Pipe() 34 | 35 | var wait sync.WaitGroup 36 | wait.Add(1) 37 | key, err := NewEncryptionKey(cipherName, secret) 38 | require.NoError(t, err, "NewCipher failed: %v", err) 39 | ssWriter := NewWriter(left, key) 40 | go func() { 41 | defer wait.Done() 42 | var err error 43 | ssWriter.Write([]byte(fromLeft)) 44 | 45 | ssReader := NewReader(left, key) 46 | receivedByLeft := make([]byte, len(fromRight)) 47 | _, err = ssReader.Read(receivedByLeft) 48 | require.NoError(t, err, "Read failed: %v", err) 49 | require.Equal(t, fromRight, string(receivedByLeft)) 50 | left.Close() 51 | }() 52 | 53 | otherCipher, err := core.PickCipher(cipherName, []byte{}, secret) 54 | require.NoError(t, err) 55 | rightSSConn := shadowaead.NewConn(right, otherCipher.(shadowaead.Cipher)) 56 | receivedByRight := make([]byte, len(fromLeft)) 57 | _, err = io.ReadFull(rightSSConn, receivedByRight) 58 | require.NoError(t, err) 59 | require.Equal(t, fromLeft, string(receivedByRight)) 60 | 61 | _, err = rightSSConn.Write([]byte(fromRight)) 62 | require.NoError(t, err, "Write failed: %v", err) 63 | 64 | rightSSConn.Close() 65 | wait.Wait() 66 | } 67 | -------------------------------------------------------------------------------- /transport/split/stream_dialer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package split 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | 21 | "github.com/Jigsaw-Code/outline-sdk/transport" 22 | ) 23 | 24 | // splitDialer is a [transport.StreamDialer] that implements the split strategy. 25 | // Use [NewStreamDialer] to create new instances. 26 | type splitDialer struct { 27 | dialer transport.StreamDialer 28 | nextSplit SplitIterator 29 | } 30 | 31 | var _ transport.StreamDialer = (*splitDialer)(nil) 32 | 33 | // NewStreamDialer creates a [transport.StreamDialer] that splits the outgoing stream according to nextSplit. 34 | func NewStreamDialer(dialer transport.StreamDialer, nextSplit SplitIterator) (transport.StreamDialer, error) { 35 | if dialer == nil { 36 | return nil, errors.New("argument dialer must not be nil") 37 | } 38 | if nextSplit == nil { 39 | return nil, errors.New("argument nextSplit must not be nil") 40 | } 41 | return &splitDialer{dialer: dialer, nextSplit: nextSplit}, nil 42 | } 43 | 44 | // DialStream implements [transport.StreamDialer].DialStream. 45 | func (d *splitDialer) DialStream(ctx context.Context, remoteAddr string) (transport.StreamConn, error) { 46 | innerConn, err := d.dialer.DialStream(ctx, remoteAddr) 47 | if err != nil { 48 | return nil, err 49 | } 50 | return transport.WrapConn(innerConn, innerConn, NewWriter(innerConn, d.nextSplit)), nil 51 | } 52 | -------------------------------------------------------------------------------- /transport/tls/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package tls provides the TLS transport. 16 | package tls 17 | -------------------------------------------------------------------------------- /transport/tlsfrag/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package tlsfrag provides tools to split the [TLS handshake record] containing 17 | the [Client Hello message] into multiple [TLS records]. This technique, 18 | known as TLS record fragmentation, forces censors to maintain state and 19 | allocate memory for potential reassembly, making censorship more difficult and 20 | resource-intensive. For detailed explanation on how this technique works, refer 21 | to [Circumventing the GFW with TLS Record Fragmentation]. 22 | 23 | This package offers convenient helper functions to create a TLS 24 | [transport.StreamDialer] that fragments the [TLS handshake record]: 25 | - [NewFixedLenStreamDialer] creates a [transport.StreamDialer] that splits 26 | the [Client Hello message] into two records. One of the records will have 27 | the specified length of splitLen bytes. 28 | - [NewStreamDialerFunc] offers a more flexible way to fragment [Client Hello 29 | message]. It accepts a callback function that determines the split point, 30 | enabling advanced splitting logic such as splitting based on the SNI 31 | extension. 32 | 33 | [Circumventing the GFW with TLS Record Fragmentation]: https://upb-syssec.github.io/blog/2023/record-fragmentation/#tls-record-fragmentation 34 | [TLS records]: https://datatracker.ietf.org/doc/html/rfc8446#section-5.1 35 | [TLS handshake record]: https://datatracker.ietf.org/doc/html/rfc8446#appendix-B.3 36 | [Client Hello message]: https://datatracker.ietf.org/doc/html/rfc8446#section-4.1.2 37 | */ 38 | package tlsfrag 39 | -------------------------------------------------------------------------------- /x/README.md: -------------------------------------------------------------------------------- 1 | # Outline Experimental 2 | 3 | This module contains experimental code with no stability guarantees. -------------------------------------------------------------------------------- /x/configurl/disorder.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package configurl 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "strconv" 21 | 22 | "github.com/Jigsaw-Code/outline-sdk/transport" 23 | "github.com/Jigsaw-Code/outline-sdk/x/disorder" 24 | ) 25 | 26 | func registerDisorderDialer(r TypeRegistry[transport.StreamDialer], typeID string, newSD BuildFunc[transport.StreamDialer]) { 27 | r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { 28 | sd, err := newSD(ctx, config.BaseConfig) 29 | if err != nil { 30 | return nil, err 31 | } 32 | disorderPacketNStr := config.URL.Opaque 33 | disorderPacketN, err := strconv.Atoi(disorderPacketNStr) 34 | if err != nil { 35 | return nil, fmt.Errorf("disoder: could not parse splice position: %v", err) 36 | } 37 | return disorder.NewStreamDialer(sd, disorderPacketN) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /x/configurl/tls.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package configurl 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "net/url" 21 | "strings" 22 | 23 | "github.com/Jigsaw-Code/outline-sdk/transport" 24 | "github.com/Jigsaw-Code/outline-sdk/transport/tls" 25 | ) 26 | 27 | func registerTLSStreamDialer(r TypeRegistry[transport.StreamDialer], typeID string, newSD BuildFunc[transport.StreamDialer]) { 28 | r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { 29 | sd, err := newSD(ctx, config.BaseConfig) 30 | if err != nil { 31 | return nil, err 32 | } 33 | options, err := parseOptions(config.URL) 34 | if err != nil { 35 | return nil, err 36 | } 37 | return tls.NewStreamDialer(sd, options...) 38 | }) 39 | } 40 | 41 | func parseOptions(configURL url.URL) ([]tls.ClientOption, error) { 42 | query := configURL.Opaque 43 | values, err := url.ParseQuery(query) 44 | if err != nil { 45 | return nil, err 46 | } 47 | options := []tls.ClientOption{} 48 | for key, values := range values { 49 | switch strings.ToLower(key) { 50 | case "sni": 51 | if len(values) != 1 { 52 | return nil, fmt.Errorf("sni option must has one value, found %v", len(values)) 53 | } 54 | options = append(options, tls.WithSNI(values[0])) 55 | case "certname": 56 | if len(values) != 1 { 57 | return nil, fmt.Errorf("certName option must has one value, found %v", len(values)) 58 | } 59 | options = append(options, tls.WithCertVerifier(&tls.StandardCertVerifier{CertificateName: values[0]})) 60 | default: 61 | return nil, fmt.Errorf("unsupported option %v", key) 62 | 63 | } 64 | } 65 | return options, nil 66 | } 67 | -------------------------------------------------------------------------------- /x/configurl/tlsfrag.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package configurl 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "strconv" 21 | 22 | "github.com/Jigsaw-Code/outline-sdk/transport" 23 | "github.com/Jigsaw-Code/outline-sdk/transport/tlsfrag" 24 | ) 25 | 26 | func registerTLSFragStreamDialer(r TypeRegistry[transport.StreamDialer], typeID string, newSD BuildFunc[transport.StreamDialer]) { 27 | r.RegisterType(typeID, func(ctx context.Context, config *Config) (transport.StreamDialer, error) { 28 | sd, err := newSD(ctx, config.BaseConfig) 29 | if err != nil { 30 | return nil, err 31 | } 32 | lenStr := config.URL.Opaque 33 | fixedLen, err := strconv.Atoi(lenStr) 34 | if err != nil { 35 | return nil, fmt.Errorf("invalid tlsfrag option: %v. It should be in tlsfrag: format", lenStr) 36 | } 37 | return tlsfrag.NewFixedLenStreamDialer(sd, fixedLen) 38 | }) 39 | } 40 | -------------------------------------------------------------------------------- /x/connectivity/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package connectivity 16 | 17 | import ( 18 | "fmt" 19 | "syscall" 20 | ) 21 | 22 | func errnoName(errno syscall.Errno) string { 23 | if name := systemErrnoName(errno); len(name) > 0 { 24 | return name 25 | } 26 | return fmt.Sprintf("Error %d (0x%x)", int(errno), int(errno)) 27 | } 28 | -------------------------------------------------------------------------------- /x/connectivity/errors_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build unix 16 | 17 | package connectivity 18 | 19 | import ( 20 | "syscall" 21 | 22 | "golang.org/x/sys/unix" 23 | ) 24 | 25 | func systemErrnoName(errno syscall.Errno) string { 26 | return unix.ErrnoName(errno) 27 | } 28 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/.gitignore: -------------------------------------------------------------------------------- 1 | output 2 | 3 | # vscode 4 | .vscode 5 | 6 | # node 7 | node_modules 8 | 9 | # yarn 10 | .yarn/cache 11 | .yarn/sdks 12 | .yarn/unplugged 13 | .yarn/install-state.gz 14 | .pnp.* 15 | 16 | # apple 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: false 4 | 5 | yarnPath: .yarn/releases/yarn-4.0.1.cjs 6 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_desktop/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "errors" 21 | 22 | "github.com/Jigsaw-Code/outline-sdk/x/examples/outline-connectivity-app/shared_backend" 23 | ) 24 | 25 | // App struct 26 | type App struct { 27 | ctx context.Context 28 | } 29 | 30 | // NewApp creates a new App application struct 31 | func NewApp() *App { 32 | return &App{} 33 | } 34 | 35 | // startup is called when the app starts. The context is saved 36 | // so we can call the runtime methods 37 | func (a *App) startup(ctx context.Context) { 38 | a.ctx = ctx 39 | } 40 | 41 | func (a *App) Request(resourceName string, parameters string) (shared_backend.Response, error) { 42 | var response shared_backend.Response 43 | 44 | request := shared_backend.Request{ResourceName: resourceName, Parameters: parameters} 45 | 46 | rawRequest, requestSerializeError := json.Marshal(request) 47 | 48 | if requestSerializeError != nil { 49 | return response, errors.New("DesktopBackend.Request: failed to serialize request") 50 | } 51 | 52 | // TODO: make this non-blocking with goroutines/channels 53 | responseParseError := json.Unmarshal(shared_backend.HandleRequest(rawRequest), &response) 54 | 55 | if responseParseError != nil { 56 | return response, errors.New("DesktopBackend.Request: failed to parse response") 57 | } 58 | 59 | return response, nil 60 | } 61 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_desktop/app.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // backend 16 | import * as DesktopBackend from "./generated/wailsjs/go/main/App"; 17 | 18 | async function requestBackend(resourceName: string, parameters: T): Promise { 19 | const response = await DesktopBackend.Request(resourceName, JSON.stringify(parameters)); 20 | 21 | if (response.error) { 22 | throw new Error(response.error); 23 | } 24 | 25 | return JSON.parse(response.body); 26 | } 27 | 28 | // frontend 29 | import * as SharedFrontend from "shared_frontend"; 30 | import type { ConnectivityTestRequest, ConnectivityTestResponse } from "shared_frontend"; 31 | import { LitElement, html } from "lit"; 32 | import { customElement } from "lit/decorators.js"; 33 | 34 | SharedFrontend.registerAllElements(); 35 | 36 | // main 37 | @customElement("app-main") 38 | export class AppMain extends LitElement { 39 | render() { 40 | return html` 42 | requestBackend( 43 | "ConnectivityTest", parameters 44 | ) 45 | } />`; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_desktop/generated/wailsjs/go/main/App.d.ts: -------------------------------------------------------------------------------- 1 | // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL 2 | // This file is automatically generated. DO NOT EDIT 3 | import {shared_backend} from '../models'; 4 | 5 | export function Request(arg1:string,arg2:string):Promise; 6 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_desktop/generated/wailsjs/go/main/App.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL 3 | // This file is automatically generated. DO NOT EDIT 4 | 5 | export function Request(arg1, arg2) { 6 | return window['go']['main']['App']['Request'](arg1, arg2); 7 | } 8 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_desktop/generated/wailsjs/go/models.ts: -------------------------------------------------------------------------------- 1 | export namespace shared_backend { 2 | 3 | export class Response { 4 | body: string; 5 | error: string; 6 | 7 | static createFrom(source: any = {}) { 8 | return new Response(source); 9 | } 10 | 11 | constructor(source: any = {}) { 12 | if ('string' === typeof source) source = JSON.parse(source); 13 | this.body = source["body"]; 14 | this.error = source["error"]; 15 | } 16 | } 17 | 18 | } 19 | 20 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_desktop/generated/wailsjs/runtime/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wailsapp/runtime", 3 | "version": "2.0.0", 4 | "description": "Wails Javascript runtime library", 5 | "main": "runtime.js", 6 | "types": "runtime.d.ts", 7 | "scripts": { 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/wailsapp/wails.git" 12 | }, 13 | "keywords": [ 14 | "Wails", 15 | "Javascript", 16 | "Go" 17 | ], 18 | "author": "Lea Anthony ", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/wailsapp/wails/issues" 22 | }, 23 | "homepage": "https://github.com/wailsapp/wails#readme" 24 | } 25 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_desktop/index.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | Outline Connectivity Test 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_desktop/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "embed" 19 | 20 | "github.com/wailsapp/wails/v2" 21 | "github.com/wailsapp/wails/v2/pkg/options" 22 | "github.com/wailsapp/wails/v2/pkg/options/assetserver" 23 | ) 24 | 25 | //go:embed all:output/frontend 26 | var assets embed.FS 27 | 28 | func main() { 29 | // Create an instance of the app structure 30 | app := NewApp() 31 | 32 | // Create application with options 33 | err := wails.Run(&options.App{ 34 | Title: "@outline_sdk_connectivity_test/app_desktop", 35 | Width: 1024, 36 | Height: 768, 37 | AssetServer: &assetserver.Options{ 38 | Assets: assets, 39 | }, 40 | OnStartup: app.startup, 41 | Bind: []interface{}{ 42 | app, 43 | }, 44 | }) 45 | 46 | if err != nil { 47 | println("Error:", err.Error()) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_desktop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "lit": "^2.7.6", 4 | "shared_backend": "workspace:^", 5 | "shared_frontend": "workspace:^" 6 | }, 7 | "devDependencies": { 8 | "concurrently": "^8.2.0", 9 | "typescript": "^4.6.4", 10 | "vite": "^3.0.7" 11 | }, 12 | "name": "app_desktop", 13 | "scripts": { 14 | "setup": "yarn build:frontend", 15 | "build": "concurrently -n linux,macos,windows 'yarn build:linux' 'yarn build:macos' 'yarn build:windows'", 16 | "build:linux": "yarn setup && wails build -platform linux", 17 | "build:macos": "yarn setup && wails build -platform darwin", 18 | "build:frontend": "vite build", 19 | "build:windows": "yarn setup && wails build -platform windows", 20 | "clean": "rm -rf output node_modules", 21 | "go": "go", 22 | "watch": "wails dev", 23 | "watch:frontend": "vite" 24 | }, 25 | "type": "module", 26 | "version": "0.0.0" 27 | } 28 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_desktop/package.json.md5: -------------------------------------------------------------------------------- 1 | aed771ab869052336e77915c2062445d -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_desktop/vite.config.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import {defineConfig} from 'vite' 16 | 17 | // https://vitejs.dev/config/ 18 | export default defineConfig({ 19 | build: { 20 | outDir: './output/frontend' 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_desktop/wails.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://wails.io/schemas/config.v2.json", 3 | "name": "OutlineConnectivityTest", 4 | "build:dir": "output", 5 | "frontend:dir": ".", 6 | "frontend:install": "yarn", 7 | "frontend:build": "yarn build:frontend", 8 | "frontend:dev:watcher": "yarn watch:frontend", 9 | "frontend:dev:serverUrl": "auto", 10 | "wailsjsdir": "generated", 11 | "author": { 12 | "name": "Daniel LaCosse", 13 | "email": "daniellacosse@google.com" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build/* 2 | !/build/.npmkeep 3 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/capacitor.build.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | 3 | android { 4 | compileOptions { 5 | sourceCompatibility JavaVersion.VERSION_17 6 | targetCompatibility JavaVersion.VERSION_17 7 | } 8 | } 9 | 10 | apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" 11 | dependencies { 12 | 13 | 14 | } 15 | 16 | 17 | if (hasProperty('postBuildExtras')) { 18 | postBuildExtras() 19 | } 20 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import android.content.Context; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | import androidx.test.platform.app.InstrumentationRegistry; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see Testing documentation 15 | */ 16 | @RunWith(AndroidJUnit4.class) 17 | public class ExampleInstrumentedTest { 18 | 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 23 | 24 | assertEquals("com.getcapacitor.app", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/java/org/getoutline/sdk/example/connectivity/FrontendRequest.kt: -------------------------------------------------------------------------------- 1 | package org.getoutline.sdk.example.connectivity 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class FrontendRequest( 7 | val resourceName: String, 8 | val parameters: String 9 | ) 10 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/java/org/getoutline/sdk/example/connectivity/FrontendResponse.kt: -------------------------------------------------------------------------------- 1 | package org.getoutline.sdk.example.connectivity 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class FrontendResponse( 7 | val body: String, 8 | val error: String 9 | ) -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/java/org/getoutline/sdk/example/connectivity/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package org.getoutline.sdk.example.connectivity 2 | 3 | import android.os.Bundle 4 | import com.getcapacitor.BridgeActivity 5 | 6 | class MainActivity : BridgeActivity() { 7 | override fun onCreate(state: Bundle?) { 8 | registerPlugin(MobileBackendPlugin::class.java) 9 | super.onCreate(state) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/java/org/getoutline/sdk/example/connectivity/MobileBackendPlugin.kt: -------------------------------------------------------------------------------- 1 | package org.getoutline.sdk.example.connectivity 2 | 3 | import com.getcapacitor.JSObject 4 | import com.getcapacitor.Plugin 5 | import com.getcapacitor.PluginCall 6 | import com.getcapacitor.PluginMethod 7 | import com.getcapacitor.annotation.CapacitorPlugin 8 | 9 | import kotlinx.serialization.json.Json 10 | import kotlinx.serialization.encodeToString 11 | 12 | import shared_backend.Shared_backend 13 | import java.nio.charset.Charset 14 | 15 | @CapacitorPlugin(name = "MobileBackend") 16 | class MobileBackendPlugin: Plugin() { 17 | @PluginMethod 18 | fun Request(call: PluginCall) { 19 | val output = JSObject() 20 | val response: FrontendResponse 21 | 22 | try { 23 | // TODO: encode directly to byte array 24 | val rawInputMessage = Json.encodeToString( 25 | FrontendRequest( 26 | call.getString("resourceName")!!, 27 | call.getString("parameters") ?: "{}" 28 | ) 29 | ) 30 | 31 | response = Json.decodeFromString( 32 | Shared_backend.handleRequest( 33 | rawInputMessage.toByteArray(Charsets.UTF_8) 34 | ).toString(Charsets.UTF_8) 35 | ) 36 | } catch (error: Exception) { 37 | output.put("error", error.message) 38 | 39 | return call.resolve(output) 40 | } 41 | 42 | output.put("body", response.body) 43 | output.put("error", response.error) 44 | 45 | return call.resolve(output) 46 | } 47 | } -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-land-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-land-hdpi/splash.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-land-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-land-mdpi/splash.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-land-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-land-xhdpi/splash.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-land-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-land-xxhdpi/splash.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-land-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-land-xxxhdpi/splash.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-port-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-port-hdpi/splash.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-port-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-port-mdpi/splash.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-port-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-port-xhdpi/splash.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-port-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-port-xxhdpi/splash.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-port-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-port-xxxhdpi/splash.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/drawable/splash.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @outline_sdk_connectivity_demo/app_mobile 4 | @outline_sdk_connectivity_demo/app_mobile 5 | org.getoutline.sdk.example.connectivity 6 | org.getoutline.sdk.example.connectivity 7 | 8 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 17 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | 14 | @Test 15 | public void addition_isCorrect() throws Exception { 16 | assertEquals(4, 2 + 2); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = "1.8.0" 5 | 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:8.1.4' 13 | classpath "com.google.gms:google-services:4.3.15" 14 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 15 | classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" 16 | 17 | // NOTE: Do not place your application dependencies here; they belong 18 | // in the individual module build.gradle files 19 | } 20 | } 21 | 22 | apply from: "variables.gradle" 23 | 24 | allprojects { 25 | repositories { 26 | google() 27 | mavenCentral() 28 | } 29 | } 30 | 31 | task clean(type: Delete) { 32 | delete rootProject.buildDir 33 | } 34 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/capacitor.settings.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | include ':capacitor-android' 3 | project(':capacitor-android').projectDir = new File('../../.yarn/unplugged/@capacitor-android-virtual-f08a65e5d8/node_modules/@capacitor/android/capacitor') 4 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | # AndroidX package structure to make it clearer which packages are bundled with the 20 | # Android operating system, and which are packaged with your app's APK 21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 22 | android.useAndroidX=true 23 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':capacitor-cordova-android-plugins' 3 | project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') 4 | 5 | apply from: 'capacitor.settings.gradle' -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/android/variables.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | minSdkVersion = 22 3 | compileSdkVersion = 33 4 | targetSdkVersion = 33 5 | androidxActivityVersion = '1.7.0' 6 | androidxAppCompatVersion = '1.6.1' 7 | androidxCoordinatorLayoutVersion = '1.2.0' 8 | androidxCoreVersion = '1.10.0' 9 | androidxFragmentVersion = '1.5.6' 10 | coreSplashScreenVersion = '1.0.0' 11 | androidxWebkitVersion = '1.6.1' 12 | junitVersion = '4.13.2' 13 | androidxJunitVersion = '1.1.5' 14 | androidxEspressoCoreVersion = '3.5.1' 15 | cordovaAndroidVersion = '10.1.1' 16 | } -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/app.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // backend 16 | import { registerPlugin } from "@capacitor/core"; 17 | 18 | // Capacitor requires passing in a root object to each native call, 19 | // that is then accessed via APIs like `call.getString("objectKey")`. 20 | const MobileBackend = registerPlugin<{ 21 | Request(request: { resourceName: string; parameters: string }): Promise<{ error: string; body: string }>; 22 | }>("MobileBackend"); 23 | 24 | async function requestBackend(resourceName: string, parameters: T): Promise { 25 | const response = await MobileBackend.Request({ 26 | resourceName, parameters: JSON.stringify(parameters) 27 | }); 28 | 29 | if (response.error) { 30 | throw new Error(response.error); 31 | } 32 | 33 | return JSON.parse(response.body); 34 | } 35 | 36 | 37 | // frontend 38 | import { LitElement, html } from "lit"; 39 | import type { ConnectivityTestRequest, ConnectivityTestResponse } from "shared_frontend"; 40 | import { customElement } from "lit/decorators.js"; 41 | import * as SharedFrontend from "shared_frontend"; 42 | 43 | SharedFrontend.registerAllElements(); 44 | 45 | // main 46 | @customElement("app-main") 47 | export class AppMain extends LitElement { 48 | render() { 49 | return html` requestBackend("Platform", void 0)} 51 | .onSubmit=${(parameters: ConnectivityTestRequest) => 52 | requestBackend( 53 | "ConnectivityTest", parameters 54 | ) 55 | } />`; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/capacitor.config.ts: -------------------------------------------------------------------------------- 1 | import { CapacitorConfig } from '@capacitor/cli'; 2 | 3 | let config: CapacitorConfig = { 4 | appId: "org.getoutline.sdk.example.connectivity", 5 | appName: "@outline_sdk_connectivity_demo/app_mobile", 6 | webDir: "output/frontend", 7 | server: { 8 | url: "http://10.0.2.2:3000" 9 | } 10 | } 11 | 12 | switch (process.env.CAPACITOR_PLATFORM) { 13 | case "android": 14 | config.server = { 15 | url: "http://10.0.2.2:3000", 16 | cleartext: true 17 | }; 18 | break; 19 | case "ios": 20 | default: 21 | config.server = { 22 | url: "http://localhost:3000" 23 | }; 24 | break; 25 | } 26 | 27 | export default config; 28 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/index.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | Outline Connectivity Test 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/.gitignore: -------------------------------------------------------------------------------- 1 | App/build 2 | App/Pods 3 | App/output 4 | App/App/public 5 | DerivedData 6 | xcuserdata 7 | 8 | # Cordova plugins for Capacitor 9 | capacitor-cordova-ios-plugins 10 | 11 | # Generated Config files 12 | App/App/capacitor.config.json 13 | App/App/config.xml 14 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App/App-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-512@2x.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "splash-2732x2732-2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "splash-2732x2732-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "splash-2732x2732.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/app_mobile/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | @outline_sdk_connectivity_demo/app_mobile 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UIStatusBarStyle 34 | UIStatusBarStyleLightContent 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UIViewControllerBasedStatusBarAppearance 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App/MobileBackend.m: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #import 16 | #import 17 | 18 | CAP_PLUGIN(MobileBackendPlugin, "MobileBackend", CAP_PLUGIN_METHOD(Request, CAPPluginReturnPromise);) 19 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/App/MobileBackend.swift: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import Foundation 16 | import Capacitor 17 | import SharedBackend 18 | 19 | struct FrontendRequest: Codable { 20 | var resourceName: String 21 | var parameters: String 22 | } 23 | 24 | struct FrontendResponse: Decodable { 25 | var body: String 26 | var error: String 27 | } 28 | 29 | @objc(MobileBackendPlugin) 30 | public class MobileBackendPlugin: CAPPlugin { 31 | @objc func Request(_ call: CAPPluginCall) { 32 | let response: FrontendResponse 33 | 34 | let encoder = JSONEncoder() 35 | let decoder = JSONDecoder() 36 | 37 | do { 38 | let rawRequest = try encoder.encode( 39 | FrontendRequest( 40 | resourceName: call.getString("resourceName")!, 41 | parameters: call.getString("parameters") ?? "{}" 42 | ) 43 | ) 44 | 45 | response = try decoder.decode( 46 | FrontendResponse.self, 47 | // TODO: make this non blocking: https://stackoverflow.com/a/69381330 48 | from: Shared_backendHandleRequest(rawRequest)! 49 | ) 50 | } catch { 51 | return call.resolve([ 52 | "error": error 53 | ]) 54 | } 55 | 56 | return call.resolve([ 57 | "body": response.body, 58 | "error": response.error 59 | ]) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/Podfile: -------------------------------------------------------------------------------- 1 | require_relative '../../../.yarn/unplugged/@capacitor-ios-virtual-38deb32d0d/node_modules/@capacitor/ios/scripts/pods_helpers' 2 | 3 | platform :ios, '13.0' 4 | use_frameworks! 5 | 6 | # workaround to avoid Xcode caching of Pods that requires 7 | # Product -> Clean Build Folder after new Cordova plugins installed 8 | # Requires CocoaPods 1.6 or newer 9 | install! 'cocoapods', :disable_input_output_paths => true 10 | 11 | def capacitor_pods 12 | pod 'Capacitor', :path => '../../../.yarn/unplugged/@capacitor-ios-virtual-38deb32d0d/node_modules/@capacitor/ios' 13 | pod 'CapacitorCordova', :path => '../../../.yarn/unplugged/@capacitor-ios-virtual-38deb32d0d/node_modules/@capacitor/ios' 14 | 15 | end 16 | 17 | target 'App' do 18 | capacitor_pods 19 | # Add your Pods here 20 | end 21 | 22 | post_install do |installer| 23 | assertDeploymentTarget(installer) 24 | end 25 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/ios/App/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Capacitor (5.2.2): 3 | - CapacitorCordova 4 | - CapacitorCordova (5.2.2) 5 | 6 | DEPENDENCIES: 7 | - "Capacitor (from `../../../.yarn/unplugged/@capacitor-ios-virtual-38deb32d0d/node_modules/@capacitor/ios`)" 8 | - "CapacitorCordova (from `../../../.yarn/unplugged/@capacitor-ios-virtual-38deb32d0d/node_modules/@capacitor/ios`)" 9 | 10 | EXTERNAL SOURCES: 11 | Capacitor: 12 | :path: "../../../.yarn/unplugged/@capacitor-ios-virtual-38deb32d0d/node_modules/@capacitor/ios" 13 | CapacitorCordova: 14 | :path: "../../../.yarn/unplugged/@capacitor-ios-virtual-38deb32d0d/node_modules/@capacitor/ios" 15 | 16 | SPEC CHECKSUMS: 17 | Capacitor: 070b18988e0f566728ae9a5eb3a7a974595f1626 18 | CapacitorCordova: 3773395d5331add072300ff6041ca2cf7b93cb0b 19 | 20 | PODFILE CHECKSUM: cdd812409e2f471dbc636fb3fb180d3d61733604 21 | 22 | COCOAPODS: 1.14.2 23 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Outline Connectivity App", 3 | "short_name": "outline connect", 4 | "start_url": "index.html", 5 | "display": "standalone", 6 | "background_color": "#31d53d", 7 | "theme_color": "#31d53d" 8 | } 9 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Daniel LaCosse", 3 | "dependencies": { 4 | "@capacitor/android": "^5.3.0", 5 | "@capacitor/core": "latest", 6 | "@capacitor/ios": "^5.2.2", 7 | "lit": "^2.7.6", 8 | "shared_backend": "workspace:^", 9 | "shared_frontend": "workspace:^" 10 | }, 11 | "devDependencies": { 12 | "@capacitor/cli": "latest", 13 | "concurrently": "^8.2.0", 14 | "typescript": "^5.1.6", 15 | "vite": "^2.9.13" 16 | }, 17 | "name": "app_mobile", 18 | "scripts": { 19 | "build": "concurrently 'yarn:build:*'", 20 | "build:android": "yarn setup:android && cap build android", 21 | "build:frontend": "vite build", 22 | "build:ios": "yarn setup:ios && cap build ios", 23 | "clean": "rm -rf output node_modules ios/capacitor-cordova-ios-plugins ios/App/App.xcarchive ios/App/App.xcodeproj/xcuserdata ios/App/App.xcworkspace/xcuserdata ios/App/Pods ios/App/App/public ios/App/App/capacitor.config.json ios/App/App/config.xml", 24 | "setup": "concurrently 'yarn:setup:*'", 25 | "setup:android": "yarn build:frontend && CAPACITOR_PLATFORM=android cap sync android", 26 | "setup:ios": "yarn build:frontend && CAPACITOR_PLATFORM=ios cap sync ios", 27 | "watch": "concurrently 'yarn:watch:*'", 28 | "watch:android": "yarn setup:android && cap open android", 29 | "watch:frontend": "vite", 30 | "watch:ios": "yarn setup:ios && cap open ios" 31 | }, 32 | "version": "0.0.0" 33 | } 34 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/app_mobile/vite.config.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { defineConfig } from "vite"; 16 | 17 | export default defineConfig({ 18 | build: { 19 | outDir: './output/frontend', 20 | }, 21 | server: { 22 | port: 3000, 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Jigsaw-Code/outline-sdk/x/examples/outline-connectivity-app 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/Jigsaw-Code/outline-sdk v0.0.6 7 | github.com/Jigsaw-Code/outline-sdk/x v0.0.0-20230907224418-a5564f78bcef 8 | github.com/wailsapp/wails/v2 v2.5.1 9 | golang.org/x/mobile v0.0.0-20230905140555-fbe1c053b6a9 10 | ) 11 | 12 | require ( 13 | github.com/bep/debounce v1.2.1 // indirect 14 | github.com/go-ole/go-ole v1.2.6 // indirect 15 | github.com/google/uuid v1.1.2 // indirect 16 | github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect 17 | github.com/labstack/echo/v4 v4.9.0 // indirect 18 | github.com/labstack/gommon v0.3.1 // indirect 19 | github.com/leaanthony/go-ansi-parser v1.0.1 // indirect 20 | github.com/leaanthony/gosod v1.0.3 // indirect 21 | github.com/leaanthony/slicer v1.5.0 // indirect 22 | github.com/mattn/go-colorable v0.1.11 // indirect 23 | github.com/mattn/go-isatty v0.0.14 // indirect 24 | github.com/miekg/dns v1.1.54 // indirect 25 | github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 // indirect 26 | github.com/pkg/errors v0.9.1 // indirect 27 | github.com/samber/lo v1.27.1 // indirect 28 | github.com/shadowsocks/go-shadowsocks2 v0.1.5 // indirect 29 | github.com/tkrajina/go-reflector v0.5.5 // indirect 30 | github.com/valyala/bytebufferpool v1.0.0 // indirect 31 | github.com/valyala/fasttemplate v1.2.1 // indirect 32 | github.com/wailsapp/mimetype v1.4.1 // indirect 33 | golang.org/x/crypto v0.17.0 // indirect 34 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect 35 | golang.org/x/mod v0.12.0 // indirect 36 | golang.org/x/net v0.17.0 // indirect 37 | golang.org/x/sys v0.15.0 // indirect 38 | golang.org/x/text v0.14.0 // indirect 39 | golang.org/x/tools v0.12.1-0.20230818130535-1517d1a3ba60 // indirect 40 | ) 41 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Daniel LaCosse", 3 | "dependencies": { 4 | "lit": "^2.7.6" 5 | }, 6 | "description": "A cross-platform connectivity test app using the Outline SDK", 7 | "devDependencies": { 8 | "@yarnpkg/sdks": "^3.0.0-rc.48", 9 | "concurrently": "^8.2.2", 10 | "typescript": "^5.1.6" 11 | }, 12 | "engines": { 13 | "node": ">=18.0.0" 14 | }, 15 | "name": "outline_sdk_connectivity_test", 16 | "packageManager": "yarn@4.0.1", 17 | "private": true, 18 | "scripts": { 19 | "app_desktop": "yarn workspace app_desktop", 20 | "app_mobile": "yarn workspace app_mobile", 21 | "build": "yarn workspaces foreach -ptivW run build", 22 | "clean": "yarn workspaces foreach -pivW run clean && rm -rf node_modules .yarn/cache .yarn/sdks .yarn/unplugged .yarn/install-state.", 23 | "reset": "yarn clean && yarn && yarn sdks vscode", 24 | "setup": "yarn && yarn workspaces foreach -pivW run setup", 25 | "setup:vscode": "yarn sdks vscode", 26 | "shared_backend": "yarn workspace shared_backend", 27 | "shared_frontend": "yarn workspace shared_frontend", 28 | "watch": "yarn workspaces foreach -pivW run watch", 29 | "watch:android": "concurrently -n frontend,android 'yarn app_mobile watch:frontend' 'yarn workspaces foreach -pivW run watch:android'", 30 | "watch:ios": "concurrently -n frontend,ios 'yarn app_mobile watch:frontend' 'yarn workspaces foreach -pivW run watch:ios'" 31 | }, 32 | "version": "0.0.0", 33 | "workspaces": [ 34 | "shared_backend", 35 | "shared_frontend", 36 | "app_desktop", 37 | "app_mobile" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/shared_backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "concurrently": "^8.2.0", 4 | "nodemon": "^3.0.1" 5 | }, 6 | "module": "index.ts", 7 | "name": "shared_backend", 8 | "scripts": { 9 | "build": "concurrently 'yarn:build:*'", 10 | "build:android": "mkdir -p output; gomobile bind -target android -androidapi 21 -o output/SharedBackend.aar github.com/Jigsaw-Code/outline-sdk/x/examples/outline-connectivity-app/shared_backend", 11 | "build:ios": "mkdir -p output; gomobile bind -target ios,iossimulator -o output/SharedBackend.xcframework github.com/Jigsaw-Code/outline-sdk/x/examples/outline-connectivity-app/shared_backend", 12 | "clean": "rm -rf output", 13 | "go": "go", 14 | "setup": "yarn build", 15 | "watch": "nodemon --exec 'yarn build' --ext go", 16 | "watch:android": "nodemon --exec 'yarn build:android' --ext go", 17 | "watch:ios": "nodemon --exec 'yarn build:ios' --ext go" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/shared_frontend/assets/fonts/jigsaw_sans/bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/shared_frontend/assets/fonts/jigsaw_sans/bold.woff2 -------------------------------------------------------------------------------- /x/examples/connectivity-app/shared_frontend/assets/fonts/jigsaw_sans/regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/connectivity-app/shared_frontend/assets/fonts/jigsaw_sans/regular.woff2 -------------------------------------------------------------------------------- /x/examples/connectivity-app/shared_frontend/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import "./theme.css"; 16 | import { ConnectivityTestPage } from "./pages"; 17 | 18 | export * from "./pages"; 19 | 20 | export function registerAllElements() { 21 | window.customElements.define("connectivity-test-page", ConnectivityTestPage); 22 | } 23 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/shared_frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@lit/localize": "^0.11.4", 4 | "lit": "^2.7.6", 5 | "shared_backend": "workspace:^" 6 | }, 7 | "devDependencies": { 8 | "@lit/localize-tools": "^0.6.9" 9 | }, 10 | "module": "index.ts", 11 | "name": "shared_frontend", 12 | "scripts": { 13 | "_translations:export": "lit-localize extract --config ./pages/main/localize.json", 14 | "_translations:import": "lit-localize build --config ./pages/main/localize.json", 15 | "clean": "rm -rf node_modules output" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/shared_frontend/pages/connectivity_test/generated/messages/index.ts: -------------------------------------------------------------------------------- 1 | // Do not modify this file by hand! 2 | // Re-generate this file by running lit-localize. 3 | 4 | /** 5 | * The locale code that templates in this source code are written in. 6 | */ 7 | export const sourceLocale = `en`; 8 | 9 | /** 10 | * The other locale codes that this application is localized into. Sorted 11 | * lexicographically. 12 | */ 13 | export const targetLocales = [ 14 | `es-419`, 15 | `zh-Hans`, 16 | `fa-IR`, 17 | ] as const; 18 | 19 | /** 20 | * All valid project locale codes. Sorted lexicographically. 21 | */ 22 | export const allLocales = [ 23 | `en`, 24 | `es-419`, 25 | `zh-Hans`, 26 | `fa-IR`, 27 | ] as const; 28 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/shared_frontend/pages/connectivity_test/generated/messages/locales/es-419.ts: -------------------------------------------------------------------------------- 1 | 2 | // Do not modify this file by hand! 3 | // Re-generate this file by running lit-localize 4 | 5 | 6 | 7 | 8 | /* eslint-disable no-irregular-whitespace */ 9 | /* eslint-disable @typescript-eslint/no-explicit-any */ 10 | 11 | export const templates = { 12 | 's0260cc39b0a41c0f': `Resultados de las pruebas`, 13 | 's0adc3d6bfa41bcaa': `Dominio a probar`, 14 | 's1bc81712252b8fa7': `Probando...`, 15 | 's48e186fb300e5464': `Tiempo`, 16 | 's4997e567d26705e6': `Prueba de funcionamiento`, 17 | 's5f343a43e7ea9f91': `Error`, 18 | 's6054cde6ff29afb6': `Esquema Clave de acceso`, 19 | 's61e136c0658e27d5': `Protocolo`, 20 | 's669b18c6d2d9c95b': `Ninguno`, 21 | 's72831c8d95de9149': `Un resolver DNS es un servicio en línea que devuelve la dirección IP directa de un dominio web determinado.`, 22 | 's8c34dccae2549cc0': `Esquema de la prueba de conectividad`, 23 | 's9518e2a9ee5b3e73': `Protocolos que deben comprobarse`, 24 | 's9d69ab87c2c35a6c': `Prefijo de flujo TCP`, 25 | 's9ef43c441392e71f': `El prefijo de flujo TCP es una cadena de texto sin formato que se añade al principio de la carga útil TCP cifrada, haciendo que la transferencia de datos parezca un método aceptable.`, 26 | 'sa81e2cdaf6921adc': `Sistema`, 27 | 'saab875d8cfcfe712': `Tema`, 28 | 'sd37ac9609ccb18cb': `La principal diferencia entre TCP (protocolo de control de transmisiones) y UDP (protocolo de datagramas de usuario) es que TCP es un protocolo basado en conexiones y UDP es sin conexiones. Aunque TCP es más fiable, transfiere los datos más lentamente. UDP es menos fiable pero funciona más rápidamente.`, 29 | 'sdac5e23a9ca3ea3d': `Resolvers DNS que puede probar`, 30 | 'sefa832f16108ab03': `Resolver`, 31 | 'sefcf950b3cc4fc3b': `Idioma`, 32 | }; 33 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/shared_frontend/pages/connectivity_test/generated/messages/locales/fa-IR.ts: -------------------------------------------------------------------------------- 1 | 2 | // Do not modify this file by hand! 3 | // Re-generate this file by running lit-localize 4 | 5 | 6 | 7 | 8 | /* eslint-disable no-irregular-whitespace */ 9 | /* eslint-disable @typescript-eslint/no-explicit-any */ 10 | 11 | export const templates = { 12 | 's0260cc39b0a41c0f': `نتایج آزمون`, 13 | 's0adc3d6bfa41bcaa': `دامنه برای تست`, 14 | 's1bc81712252b8fa7': `آزمایش کردن...`, 15 | 's48e186fb300e5464': `زمان`, 16 | 's4997e567d26705e6': `تست را اجرا کنید`, 17 | 's5f343a43e7ea9f91': `خطا`, 18 | 's6054cde6ff29afb6': `کلید دسترسی Outline`, 19 | 's61e136c0658e27d5': `پروتکل`, 20 | 's669b18c6d2d9c95b': `هیچ یک`, 21 | 's72831c8d95de9149': `حل‌کننده DNS یک سرویس آنلاین است که آدرس IP مستقیم یک دامنه وب‌سایت معین را برمی‌گرداند.`, 22 | 's8c34dccae2549cc0': `تست اتصال طرح کلی`, 23 | 's9518e2a9ee5b3e73': `پروتکل هایی برای بررسی`, 24 | 's9d69ab87c2c35a6c': `پیشوند جریان TCP`, 25 | 's9ef43c441392e71f': `پیشوند جریان TCP یک رشته متن ساده است که به ابتدای بار TCP رمزگذاری شده اضافه شده است و باعث می شود انتقال داده مانند یک روش قابل قبول به نظر برسد.`, 26 | 'sa81e2cdaf6921adc': `سیستم`, 27 | 'saab875d8cfcfe712': `موضوع`, 28 | 'sd37ac9609ccb18cb': `تفاوت اصلی بین TCP (پروتکل کنترل انتقال) و UDP (پروتکل دیتاگرام کاربر) این است که TCP یک پروتکل مبتنی بر اتصال است و UDP بدون اتصال است. در حالی که TCP قابل اعتمادتر است، داده ها را کندتر انتقال می دهد. UDP کمتر قابل اعتماد است اما سریعتر کار می کند.`, 29 | 'sdac5e23a9ca3ea3d': `حل‌کننده‌های DNS برای امتحان`, 30 | 'sefa832f16108ab03': `حل کننده`, 31 | 'sefcf950b3cc4fc3b': `زبان`, 32 | }; 33 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/shared_frontend/pages/connectivity_test/generated/messages/locales/zh-Hans.ts: -------------------------------------------------------------------------------- 1 | 2 | // Do not modify this file by hand! 3 | // Re-generate this file by running lit-localize 4 | 5 | 6 | 7 | 8 | /* eslint-disable no-irregular-whitespace */ 9 | /* eslint-disable @typescript-eslint/no-explicit-any */ 10 | 11 | export const templates = { 12 | 's0260cc39b0a41c0f': `检测结果`, 13 | 's0adc3d6bfa41bcaa': `要测试的域`, 14 | 's1bc81712252b8fa7': `测试...`, 15 | 's48e186fb300e5464': `时间`, 16 | 's4997e567d26705e6': `运行测试`, 17 | 's5f343a43e7ea9f91': `错误`, 18 | 's6054cde6ff29afb6': `概要访问键`, 19 | 's61e136c0658e27d5': `协议`, 20 | 's669b18c6d2d9c95b': `没有任何`, 21 | 's72831c8d95de9149': `DNS 解析器是一种在线服务,可返回给定网站域的直接 IP 地址。`, 22 | 's8c34dccae2549cc0': `概要连接测试`, 23 | 's9518e2a9ee5b3e73': `要检查的协议`, 24 | 's9d69ab87c2c35a6c': `TCP 流前缀`, 25 | 's9ef43c441392e71f': `TCP 流前缀是附加到加密 TCP 有效负载开头的明文字符串,使数据传输看起来像是一种可接受的方法。`, 26 | 'sa81e2cdaf6921adc': `系统`, 27 | 'saab875d8cfcfe712': `主题`, 28 | 'sd37ac9609ccb18cb': `TCP(传输控制协议)和UDP(用户数据报协议)之间的主要区别在于TCP是基于连接的协议,而UDP是无连接的。虽然 TCP 更可靠,但它传输数据的速度更慢。UDP 不太可靠,但工作速度更快。`, 29 | 'sdac5e23a9ca3ea3d': `值得尝试的 DNS 解析器`, 30 | 'sefa832f16108ab03': `旋转变压器`, 31 | 'sefcf950b3cc4fc3b': `语言`, 32 | }; 33 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/shared_frontend/pages/connectivity_test/localize.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/lit/lit/main/packages/localize-tools/config.schema.json", 3 | "sourceLocale": "en", 4 | "targetLocales": ["es-419", "zh-Hans", "fa"], 5 | "tsConfig": "../../tsconfig.json", 6 | "output": { 7 | "mode": "runtime", 8 | "localeCodesModule": "./generated/messages/index.ts", 9 | "outputDir": "./generated/messages/locales" 10 | }, 11 | "interchange": { 12 | "format": "xliff", 13 | "xliffDir": "../../output/pages/main/translations" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/shared_frontend/pages/connectivity_test/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export interface ConnectivityTestRequest { 16 | accessKey: string; 17 | domain: string; 18 | resolvers: string[]; 19 | protocols: { 20 | tcp: boolean; 21 | udp: boolean; 22 | }; 23 | } 24 | 25 | export interface ConnectivityTestResult { 26 | time: string; 27 | durationMs: number; 28 | proto: string; 29 | resolver: string; 30 | error?: { 31 | operation: string; 32 | posixError: string; 33 | message: string; 34 | }; 35 | } 36 | 37 | export type ConnectivityTestResponse = ConnectivityTestResult[] | Error | null; 38 | 39 | export enum OperatingSystem { 40 | ANDROID = "android", 41 | IOS = "ios", 42 | LINUX = "linux", 43 | MACOS = "darwin", 44 | WINDOWS = "windows" 45 | } 46 | 47 | export interface PlatformMetadata { 48 | operatingSystem: OperatingSystem; 49 | } -------------------------------------------------------------------------------- /x/examples/connectivity-app/shared_frontend/pages/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export * from "./connectivity_test"; -------------------------------------------------------------------------------- /x/examples/connectivity-app/shared_frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.google.json", 3 | "compilerOptions": { 4 | "experimentalDecorators": true, 5 | "useDefineForClassFields": false, 6 | "target": "es5", 7 | "lib": [ 8 | "ES2020", 9 | "DOM", 10 | "DOM.Iterable" 11 | ], 12 | }, 13 | "include": [ 14 | "pages/**/*.ts" 15 | ] 16 | } -------------------------------------------------------------------------------- /x/examples/connectivity-app/tsconfig.google.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowUnreachableCode": false, 4 | "allowUnusedLabels": false, 5 | "declaration": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "lib": ["es2018"], 8 | "module": "commonjs", 9 | "noEmitOnError": true, 10 | "noFallthroughCasesInSwitch": true, 11 | "noImplicitReturns": true, 12 | "pretty": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "target": "es2018" 16 | }, 17 | "exclude": [ 18 | "node_modules" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.google.json", 3 | "compilerOptions": { 4 | "module": "ESNext", 5 | "lib": [ 6 | "ES2020", 7 | "DOM", 8 | "DOM.Iterable" 9 | ], 10 | "allowSyntheticDefaultImports": true, 11 | "experimentalDecorators": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "isolatedModules": true, 14 | "moduleResolution": "Node", 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitReturns": true, 17 | "noUnusedLocals": true, 18 | "noUnusedParameters": true, 19 | "skipLibCheck": true, 20 | "strict": true, 21 | "target": "ES5", 22 | "useDefineForClassFields": false, 23 | }, 24 | "references": [ 25 | { 26 | "path": "./tsconfig.node.json" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /x/examples/connectivity-app/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.google.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": [ 10 | "app_desktop/vite.config.ts", 11 | "app_mobile/vite.config.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /x/examples/fetch-proxy/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "io" 20 | "log" 21 | "net/http" 22 | "net/url" 23 | "os" 24 | 25 | "github.com/Jigsaw-Code/outline-sdk/x/mobileproxy" 26 | ) 27 | 28 | func main() { 29 | transportFlag := flag.String("transport", "", "Transport config") 30 | flag.Parse() 31 | 32 | urlToFetch := flag.Arg(0) 33 | if urlToFetch == "" { 34 | log.Fatal("Need to pass the URL to fetch in the command-line") 35 | } 36 | 37 | dialer, err := mobileproxy.NewStreamDialerFromConfig(*transportFlag) 38 | if err != nil { 39 | log.Fatalf("NewStreamDialerFromConfig failed: %v", err) 40 | } 41 | proxy, err := mobileproxy.RunProxy("localhost:0", dialer) 42 | if err != nil { 43 | log.Fatalf("RunProxy failed: %v", err) 44 | } 45 | 46 | httpClient := &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(&url.URL{Scheme: "http", Host: proxy.Address()})}} 47 | 48 | resp, err := httpClient.Get(urlToFetch) 49 | if err != nil { 50 | log.Fatalf("URL GET failed: %v", err) 51 | } 52 | defer resp.Body.Close() 53 | 54 | _, err = io.Copy(os.Stdout, resp.Body) 55 | if err != nil { 56 | log.Fatalf("Read of page body failed: %v", err) 57 | } 58 | 59 | proxy.Stop(5) 60 | } 61 | -------------------------------------------------------------------------------- /x/examples/fetch-psiphon/README.md: -------------------------------------------------------------------------------- 1 | # Using the Psiphon StreamDialer example 2 | 3 | This fetch tool illustrates how to use Psiphon as a stream dialer. 4 | 5 | Usage: 6 | 7 | ```sh 8 | go run -tags psiphon github.com/Jigsaw-Code/outline-sdk/x/examples/fetch-psiphon@latest -config config.json https://ipinfo.io 9 | ``` 10 | 11 | You will need a config file of a Psiphon server. You can run one yourself and generate the config as per the 12 | [official instructions](https://github.com/Psiphon-Labs/psiphon-tunnel-core/tree/master#generate-configuration-data), 13 | or obtain a server from the Psiphon team. 14 | -------------------------------------------------------------------------------- /x/examples/fetch-speed/README.md: -------------------------------------------------------------------------------- 1 | # Outline Fetch Speed 2 | 3 | This app illustrates how to use different transports to fetch a URL in Go and calculate the download speed. 4 | 5 | Direct fetch: 6 | 7 | ```sh 8 | $ go run github.com/Jigsaw-Code/outline-sdk/x/examples/fetch@latest http://speedtest.ftp.otenet.gr/files/test10Mb.db 9 | 10 | Downloaded 10.00 MB in 1.51s 11 | 12 | Downloaded Speed: 6.64 MB/s 13 | ``` 14 | 15 | Using a Shadowsocks server: 16 | 17 | ```sh 18 | $ go run github.com/Jigsaw-Code/outline-sdk/x/examples/fetch@latest -transport ss://[redacted]@[redacted]:80 http://speedtest.ftp.otenet.gr/files/test10Mb.db 19 | 20 | Downloaded 10.00 MB in 1.78s 21 | 22 | Downloaded Speed: 5.61 MB/s 23 | ``` 24 | -------------------------------------------------------------------------------- /x/examples/fetch/README.md: -------------------------------------------------------------------------------- 1 | # Outline Fetch 2 | 3 | This app illustrates how to use different transports to fetch a URL in Go. 4 | 5 | Direct fetch: 6 | 7 | ```sh 8 | $ go run github.com/Jigsaw-Code/outline-sdk/x/examples/fetch@latest https://ipinfo.io 9 | { 10 | ... 11 | "city": "Amsterdam", 12 | "region": "North Holland", 13 | "country": "NL", 14 | ... 15 | } 16 | ``` 17 | 18 | Using a Shadowsocks server: 19 | 20 | ```sh 21 | $ go run github.com/Jigsaw-Code/outline-sdk/x/examples/fetch@latest -transport ss://[redacted]@[redacted]:80 https://ipinfo.io 22 | { 23 | ... 24 | "region": "New Jersey", 25 | "country": "US", 26 | "org": "AS14061 DigitalOcean, LLC", 27 | ... 28 | } 29 | ``` 30 | 31 | Using a SOCKS5 server: 32 | 33 | ```sh 34 | $ go run github.com/Jigsaw-Code/outline-sdk/x/examples/fetch@latest -transport socks5://[redacted]:5703 https://ipinfo.io 35 | { 36 | ... 37 | "city": "Berlin", 38 | "region": "Berlin", 39 | "country": "DE", 40 | ... 41 | } 42 | ``` 43 | 44 | Using packet splitting: 45 | 46 | ```sh 47 | $ go run github.com/Jigsaw-Code/outline-sdk/x/examples/fetch@latest -transport split:3 https://ipinfo.io 48 | { 49 | ... 50 | "city": "Amsterdam", 51 | "region": "North Holland", 52 | "country": "NL", 53 | ... 54 | } 55 | ``` 56 | 57 | You should see this on Wireshark: 58 | 59 | image 60 | 61 | -------------------------------------------------------------------------------- /x/examples/fyne-proxy/FyneApp.toml: -------------------------------------------------------------------------------- 1 | [Details] 2 | Icon = "Icon.png" 3 | Name = "Local Proxy" 4 | ID = "org.getoutline.sdk.examples.fyne_proxy" 5 | Build = 1 6 | -------------------------------------------------------------------------------- /x/examples/fyne-proxy/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/fyne-proxy/Icon.png -------------------------------------------------------------------------------- /x/examples/fyne-proxy/tools.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build tools 16 | // +build tools 17 | 18 | // See https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 19 | // and https://github.com/go-modules-by-example/index/blob/master/010_tools/README.md 20 | 21 | package tools 22 | 23 | import ( 24 | _ "fyne.io/fyne/v2/cmd/fyne" 25 | ) 26 | -------------------------------------------------------------------------------- /x/examples/fyne-tools/FyneApp.toml: -------------------------------------------------------------------------------- 1 | [Details] 2 | Icon = "Icon.png" 3 | Name = "Net Tools" 4 | ID = "org.getoutline.sdk.examples.fyne_tools" 5 | Build = 9 6 | -------------------------------------------------------------------------------- /x/examples/fyne-tools/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/fyne-tools/Icon.png -------------------------------------------------------------------------------- /x/examples/fyne-tools/README.md: -------------------------------------------------------------------------------- 1 | # DNS System Resolver Lookup Utility 2 | 3 | To run: 4 | 5 | ```console 6 | go run github.com/Jigsaw-Code/outline-sdk/x/examples/fyne-tools@latest 7 | ``` 8 | 9 | ## Android 10 | 11 | Package for Android and install on emulator or device: 12 | ``` 13 | go run fyne.io/fyne/v2/cmd/fyne package -os android && adb install Net_Tools.apk 14 | ``` 15 | 16 | Note: the generated APK is around 85MB. 17 | 18 | 19 | ## Windows 20 | 21 | If you are on Windows, you can just use the regular `go build` or `go run` command. 22 | 23 | Because the app uses cgo, we need to cross-compilation tools to build from other platforms. 24 | 25 | If you are on macOS, you can build the Windows app with [MinGW-x64](https://www.mingw-w64.org/). 26 | 27 | First install MinGW-w64. MacPorts is the [official channel](https://www.mingw-w64.org/downloads/#macports): 28 | 29 | ``` 30 | sudo port install x86_64-w64-mingw32-gcc 31 | ``` 32 | 33 | With Homebrew (unofficial): 34 | 35 | ``` 36 | brew install mingw-w64 37 | ``` 38 | 39 | Build the app (64-bit): 40 | 41 | ``` 42 | GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC="x86_64-w64-mingw32-gcc" go build . 43 | ``` 44 | 45 | The first build will take minutes, since there's a lot of platform code to be built. 46 | Subsequent builds will be incremental and take a few seconds. 47 | 48 | ## Screenshots 49 | 50 | image 51 | 52 | image -------------------------------------------------------------------------------- /x/examples/fyne-tools/cname_desktop.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !(android || ios) 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "net" 22 | ) 23 | 24 | func lookupCNAME(ctx context.Context, domain string) (string, error) { 25 | return net.DefaultResolver.LookupCNAME(ctx, domain) 26 | } 27 | -------------------------------------------------------------------------------- /x/examples/fyne-tools/cname_mobile.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build android || ios 16 | 17 | package main 18 | 19 | /* 20 | #include 21 | #include 22 | #include 23 | #include 24 | */ 25 | import "C" 26 | 27 | import ( 28 | "context" 29 | "fmt" 30 | "unsafe" 31 | ) 32 | 33 | func lookupCNAME(ctx context.Context, domain string) (string, error) { 34 | type result struct { 35 | cname string 36 | err error 37 | } 38 | 39 | results := make(chan result) 40 | go func() { 41 | cname, err := lookupCNAMEBlocking(domain) 42 | results <- result{cname, err} 43 | }() 44 | 45 | select { 46 | case r := <-results: 47 | return r.cname, r.err 48 | case <-ctx.Done(): 49 | return "", ctx.Err() 50 | } 51 | } 52 | 53 | func lookupCNAMEBlocking(host string) (string, error) { 54 | var hints C.struct_addrinfo 55 | var result *C.struct_addrinfo 56 | 57 | chost := C.CString(host) 58 | defer C.free(unsafe.Pointer(chost)) 59 | 60 | hints.ai_family = C.AF_UNSPEC 61 | hints.ai_flags = C.AI_CANONNAME 62 | 63 | // Call getaddrinfo 64 | res := C.getaddrinfo(chost, nil, &hints, &result) 65 | if res != 0 { 66 | return "", fmt.Errorf("getaddrinfo error: %s", C.GoString(C.gai_strerror(res))) 67 | } 68 | defer C.freeaddrinfo(result) 69 | 70 | // Extract canonical name 71 | cname := C.GoString(result.ai_canonname) 72 | return cname, nil 73 | } 74 | -------------------------------------------------------------------------------- /x/examples/fyne-tools/tools.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build tools 16 | // +build tools 17 | 18 | // See https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 19 | // and https://github.com/go-modules-by-example/index/blob/master/010_tools/README.md 20 | 21 | package tools 22 | 23 | import ( 24 | _ "fyne.io/fyne/v2/cmd/fyne" 25 | ) 26 | -------------------------------------------------------------------------------- /x/examples/http2transport/README.md: -------------------------------------------------------------------------------- 1 | # HTTP-to-Transport 2 | 3 | This app runs a local HTTP-CONNECT proxy that dials the target using the transport configured in the command-line. 4 | 5 | Flags: 6 | - `-transport` for the transport to use. 7 | - `-addr` for the local address to listen on, in host:port format. Use `localhost:0` if you want the system to dynamically pick a port for you. 8 | 9 | Example: 10 | ``` 11 | KEY=ss://ENCRYPTION_KEY@HOST:PORT/ 12 | go run github.com/Jigsaw-Code/outline-sdk/x/examples/http2transport@latest -transport "$KEY" -localAddr localhost:54321 13 | ``` 14 | -------------------------------------------------------------------------------- /x/examples/measure/make_soax_config.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2024 The Outline Authors 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | SESSION_ID=$RANDOM 16 | cat <" 9 | ``` 10 | 11 | - `-transport` : the Outline server access key from the service provider, it should start with "ss://" 12 | 13 | ### Build 14 | 15 | You can use the following command to build the CLI. 16 | 17 | 18 | ``` 19 | cd outline-sdk/x/examples/ 20 | go build -o outline-cli -ldflags="-extldflags=-static" ./outline-cli 21 | ``` 22 | 23 | > 💡 `cgo` will pull in the C runtime. By default, the C runtime is linked as a dynamic library. Sometimes this can cause problems when running the binary on different versions or distributions of Linux. To avoid this, we have added the `-ldflags="-extldflags=-static"` option. But if you only need to run the binary on the same machine, you can omit this option. 24 | -------------------------------------------------------------------------------- /x/examples/outline-cli/app.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | type App struct { 18 | TransportConfig *string 19 | RoutingConfig *RoutingConfig 20 | } 21 | 22 | type RoutingConfig struct { 23 | TunDeviceName string 24 | TunDeviceIP string 25 | TunDeviceMTU int 26 | TunGatewayCIDR string 27 | RoutingTableID int 28 | RoutingTablePriority int 29 | DNSServerIP string 30 | } 31 | -------------------------------------------------------------------------------- /x/examples/outline-cli/app_other.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !linux 16 | 17 | package main 18 | 19 | import "errors" 20 | 21 | func (App) Run() error { 22 | return errors.New("platform not supported") 23 | } 24 | -------------------------------------------------------------------------------- /x/examples/outline-cli/ipv6_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | ) 21 | 22 | const disableIPv6ProcFile = "/proc/sys/net/ipv6/conf/all/disable_ipv6" 23 | 24 | // enableIPv6 enables or disables the IPv6 support for the Linux system. 25 | // It returns the previous setting value so the caller can restore it. 26 | // Non-nil error means we cannot find the IPv6 setting. 27 | func enableIPv6(enabled bool) (bool, error) { 28 | disabledStr, err := os.ReadFile(disableIPv6ProcFile) 29 | if err != nil { 30 | return false, fmt.Errorf("failed to read IPv6 config: %w", err) 31 | } 32 | if disabledStr[0] != '0' && disabledStr[0] != '1' { 33 | return false, fmt.Errorf("invalid IPv6 config value: %v", disabledStr) 34 | } 35 | 36 | prevEnabled := disabledStr[0] == '0' 37 | 38 | if enabled { 39 | disabledStr[0] = '0' 40 | } else { 41 | disabledStr[0] = '1' 42 | } 43 | if err := os.WriteFile(disableIPv6ProcFile, disabledStr, 0o644); err != nil { 44 | return prevEnabled, fmt.Errorf("failed to write IPv6 config: %w", err) 45 | } 46 | 47 | logging.Info.Printf("updated global IPv6 support: %v\n", enabled) 48 | return prevEnabled, nil 49 | } 50 | -------------------------------------------------------------------------------- /x/examples/outline-cli/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "fmt" 20 | "io" 21 | "log" 22 | "os" 23 | ) 24 | 25 | var logging = &struct { 26 | Debug, Info, Warn, Err *log.Logger 27 | }{ 28 | Debug: log.New(io.Discard, "[DEBUG] ", log.LstdFlags), 29 | Info: log.New(os.Stdout, "[INFO] ", log.LstdFlags), 30 | Warn: log.New(os.Stderr, "[WARN] ", log.LstdFlags), 31 | Err: log.New(os.Stderr, "[ERROR] ", log.LstdFlags), 32 | } 33 | 34 | // ./app -transport "ss://..." 35 | func main() { 36 | fmt.Println("OutlineVPN CLI (experimental)") 37 | 38 | app := App{ 39 | TransportConfig: flag.String("transport", "", "Transport config"), 40 | RoutingConfig: &RoutingConfig{ 41 | TunDeviceName: "outline233", 42 | TunDeviceIP: "10.233.233.1", 43 | TunDeviceMTU: 1500, // todo: read this from netlink 44 | TunGatewayCIDR: "10.233.233.2/32", 45 | RoutingTableID: 233, 46 | RoutingTablePriority: 23333, 47 | DNSServerIP: "9.9.9.9", 48 | }, 49 | } 50 | flag.Parse() 51 | 52 | if err := app.Run(); err != nil { 53 | logging.Err.Printf("%v\n", err) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /x/examples/run-mobileproxy/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "flag" 19 | "log" 20 | "os" 21 | "os/signal" 22 | 23 | "github.com/Jigsaw-Code/outline-sdk/x/mobileproxy" 24 | ) 25 | 26 | func main() { 27 | transportFlag := flag.String("transport", "", "Transport config") 28 | addrFlag := flag.String("localAddr", "localhost:8080", "Local proxy address") 29 | urlProxyPrefixFlag := flag.String("proxyPath", "/proxy", "Path where to run the URL proxy. Set to empty (\"\") to disable it.") 30 | flag.Parse() 31 | 32 | dialer, err := mobileproxy.NewStreamDialerFromConfig(*transportFlag) 33 | if err != nil { 34 | log.Fatalf("NewStreamDialerFromConfig failed: %v", err) 35 | } 36 | proxy, err := mobileproxy.RunProxy(*addrFlag, dialer) 37 | if err != nil { 38 | log.Fatalf("RunProxy failed: %v", err) 39 | } 40 | if *urlProxyPrefixFlag != "" { 41 | proxy.AddURLProxy(*urlProxyPrefixFlag, dialer) 42 | } 43 | log.Printf("Proxy listening on %v", proxy.Address()) 44 | 45 | // Wait for interrupt signal to stop the proxy. 46 | sig := make(chan os.Signal, 1) 47 | signal.Notify(sig, os.Interrupt) 48 | <-sig 49 | log.Print("Shutting down") 50 | proxy.Stop(2) 51 | } 52 | -------------------------------------------------------------------------------- /x/examples/smart-proxy/config.yaml: -------------------------------------------------------------------------------- 1 | # DNS strategies 2 | dns: 3 | # Use the system resolver by default 4 | - system: {} 5 | 6 | # DNS-over-HTTPS 7 | 8 | # Quad9 9 | - https: { name: 2620:fe::fe } 10 | - https: { name: 9.9.9.9 } 11 | # Google 12 | - https: { name: 2001:4860:4860::8888 } 13 | - https: { name: 8.8.8.8 } 14 | # Cloudflare 15 | - https: { name: 2606:4700:4700::1111 } 16 | - https: { name: 1.1.1.1 } 17 | # Wikimedia DNS 18 | - https: { name: 2001:67c:930::1 } 19 | - https: { name: 185.71.138.138 } 20 | 21 | # DNS-over-TLS 22 | 23 | # Quad9 24 | - tls: { name: 2620:fe::fe } 25 | - tls: { name: 9.9.9.9 } 26 | # Google 27 | - tls: { name: 2001:4860:4860::8888 } 28 | - tls: { name: 8.8.8.8 } 29 | # Cloudflare 30 | - tls: { name: 2606:4700:4700::1111 } 31 | - tls: { name: 1.1.1.1 } 32 | # Wikimedia DNS 33 | - tls: { name: 2001:67c:930::1 } 34 | - tls: { name: 185.71.138.138 } 35 | 36 | # DNS-over-TCP 37 | 38 | # Quad9 39 | - tcp: { address: 2620:fe::fe } 40 | - tcp: { address: 9.9.9.9 } 41 | # Google 42 | - tcp: { address: 2001:4860:4860::8888 } 43 | - tcp: { address: 8.8.8.8 } 44 | # Cloudflare 45 | - tcp: { address: 2606:4700:4700::1111 } 46 | - tcp: { address: 1.1.1.1 } 47 | 48 | # DNS-over-UDP 49 | 50 | # Quad9 51 | - udp: { address: 2620:fe::fe } 52 | - udp: { address: 9.9.9.9 } 53 | # Google 54 | - udp: { address: 2001:4860:4860::8888 } 55 | - udp: { address: 8.8.8.8 } 56 | # Cloudflare 57 | - udp: { address: 2606:4700:4700::1111 } 58 | - udp: { address: 1.1.1.1 } 59 | 60 | # TLS strategies 61 | tls: 62 | - "" # Direct dialer 63 | - split:1 # TCP stream split at position 1 64 | - split:2,20*5 # TCP stream split at position 2, followed by 20 blocks of length 5. 65 | - split:200|disorder:1 # TCP stream split at position 1, and send the second packet (packet #1) with zero TTL at first. 66 | - tlsfrag:1 # TLS Record Fragmentation at position 1 67 | -------------------------------------------------------------------------------- /x/examples/smart-proxy/config_broken.yaml: -------------------------------------------------------------------------------- 1 | dns: 2 | # We get censored DNS responses when we send queries to an IP in China. 3 | - udp: { address: china.cn } 4 | # We get censored DNS responses when we send queries to a resolver in Iran. 5 | - udp: { address: ns1.tic.ir } 6 | - tcp: { address: ns1.tic.ir } 7 | # We get censored DNS responses when we send queries to an IP in Turkmenistan. 8 | - udp: { address: tmcell.tm } 9 | # We get censored DNS responses when we send queries to a resolver in Russia. 10 | - udp: { address: dns1.transtelecom.net. } 11 | # Testing captive portal. 12 | - tls: 13 | name: captive-portal.badssl.com 14 | address: captive-portal.badssl.com:443 15 | # Testing forged TLS certificate. 16 | - https: { name: mitm-software.badssl.com } 17 | 18 | tls: 19 | - "" 20 | - split:1 21 | - split:2 22 | - split:5 23 | - tlsfrag:1 24 | 25 | fallback: 26 | # Nonexistent Outline Server 27 | - ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTprSzdEdHQ0MkJLOE9hRjBKYjdpWGFK@1.2.3.4:9999/?outline=1 28 | # Nonexistant Psiphon Config JSON 29 | - psiphon: { 30 | "PropagationChannelId":"ID1", 31 | "SponsorId":"ID2", 32 | "DisableLocalSocksProxy" : true, 33 | "DisableLocalHTTPProxy" : true, 34 | "EstablishTunnelTimeoutSeconds": 1, 35 | # URL points to google.com 36 | "RemoteServerListURLs" : [{"URL": "aHR0cHM6Ly9nb29nbGUuY29t", "OnlyAfterAttempts": 0, "SkipVerify": false}], 37 | } 38 | # Nonexistant local socks5 proxy 39 | - socks5://192.168.1.10:1080 40 | 41 | -------------------------------------------------------------------------------- /x/examples/test-connectivity/README.md: -------------------------------------------------------------------------------- 1 | # Connectivity Test 2 | 3 | This app illustrates the use of the Shadowsocks transport to resolve a domain name over TCP or UDP. 4 | 5 | Example: 6 | ``` 7 | # From https://www.reddit.com/r/outlinevpn/wiki/index/prefixing/ 8 | KEY=ss://ENCRYPTION_KEY@HOST:PORT/ 9 | COLLECTOR_URL=https://collector.example.com/metrics 10 | for PREFIX in POST%20 HTTP%2F1.1%20 %05%C3%9C_%C3%A0%01%20 %16%03%01%40%00%01 %13%03%03%3F %16%03%03%40%00%02; do 11 | go run github.com/Jigsaw-Code/outline-sdk/x/examples/test-connectivity@latest -transport="$KEY?prefix=$PREFIX" -proto tcp -resolver 8.8.8.8 -report-to $COLLECTOR_URL -report-success-rate 0.2 -report-failure-rate 1.0 && echo Prefix "$PREFIX" works! 12 | done 13 | ``` -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/.scripts/build_mobileproxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2025 The Outline Authors 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | PLATFORM="$1" 18 | OUTPUT="${2:-$(pwd)/output}" 19 | 20 | if [[ "$OUTPUT" = "/" ]] || [[ "$OUTPUT" = "*" ]]; then 21 | echo "Error: OUTPUT cannot be '/' or '*'. These are dangerous values." 22 | exit 1 23 | fi 24 | 25 | mkdir -p "$OUTPUT/mobileproxy" 26 | 27 | go build -o "$OUTPUT/mobileproxy" golang.org/x/mobile/cmd/gomobile golang.org/x/mobile/cmd/gobind 28 | 29 | if [[ "$PLATFORM" = "ios" ]]; then 30 | echo "Building for iOS..." 31 | PATH="$OUTPUT/mobileproxy/:$PATH" gomobile bind -ldflags='-s -w' -target=ios -iosversion=11.0 -o "$OUTPUT/mobileproxy/mobileproxy.xcframework" github.com/Jigsaw-Code/outline-sdk/x/mobileproxy 32 | elif [[ "$PLATFORM" = "android" ]]; then 33 | echo "Building for Android..." 34 | PATH="$OUTPUT/mobileproxy/:$PATH" gomobile bind -ldflags='-s -w' -target=android -androidapi=21 -o "$OUTPUT/mobileproxy/mobileproxy.aar" github.com/Jigsaw-Code/outline-sdk/x/mobileproxy 35 | else 36 | echo "Invalid platform: $PLATFORM. Must be 'ios' or 'android'." 37 | exit 1 38 | fi 39 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/basic_navigator_example/src/index.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | My News Site 29 | 46 | 47 | 48 | 49 | 50 | 51 | 58 | 59 | My Site Navigation 60 | 61 | 62 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@ngrok/ngrok": "1.4.1", 4 | "archiver": "^7.0.1", 5 | "chalk": "^5.4.1", 6 | "http-proxy": "^1.18.1", 7 | "minimist": "^1.2.8", 8 | "serve-handler": "6.1.6" 9 | }, 10 | "devDependencies": { 11 | "glob": "^11.0.1", 12 | "handlebars": "^4.7.8", 13 | "vite": "^6.2.0" 14 | }, 15 | "license": "Apache-2.0", 16 | "scripts": { 17 | "build:mobileproxy": "bash ./.scripts/build_mobileproxy.sh", 18 | "build:project": "node ./wrapper_app_project/.scripts/build.mjs", 19 | "clean": "npx rimraf node_modules/ output/", 20 | "doctor": "./doctor", 21 | "open:android": "cd output/wrapper_app_project && npm run open:android", 22 | "open:ios": "cd output/wrapper_app_project && npm run open:ios", 23 | "reset": "npm run clean && npm ci", 24 | "start:navigator": "node ./basic_navigator_example/.scripts/start.mjs" 25 | }, 26 | "private": true 27 | } 28 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | mobileproxy/ -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/README.md.handlebars: -------------------------------------------------------------------------------- 1 | # My Outline Web Wrapper Project 2 | 3 | ## Starting the project 4 | 5 | ```sh 6 | npm run open:{{platform}} 7 | ``` 8 | 9 | ## Adding icon and splash screen assets to the project 10 | 11 | You'll need to add the following images to the `assets` folder: 12 | 13 | A 1024x1024 png titled `icon.png` containing your app icon. 14 | A 2732x2732 png titled `splash.png` containing your splash screen. 15 | Another 2732x2732 png titled `splash-dark.png` containing your splash screen in dark mode. 16 | 17 | Then, run the following command to generate and place the assets in the appropriate places in your iOS project: 18 | 19 | ```sh 20 | npx capacitor-assets generate --{{platform}} 21 | ``` 22 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build/* 2 | !/build/.npmkeep 3 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/capacitor.build.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | 3 | android { 4 | compileOptions { 5 | sourceCompatibility JavaVersion.VERSION_17 6 | targetCompatibility JavaVersion.VERSION_17 7 | } 8 | } 9 | 10 | apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" 11 | dependencies { 12 | implementation project(':capacitor-camera') 13 | implementation project(':capacitor-splash-screen') 14 | 15 | } 16 | 17 | 18 | if (hasProperty('postBuildExtras')) { 19 | postBuildExtras() 20 | } 21 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import android.content.Context; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | import androidx.test.platform.app.InstrumentationRegistry; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see Testing documentation 15 | */ 16 | @RunWith(AndroidJUnit4.class) 17 | public class ExampleInstrumentedTest { 18 | 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 23 | 24 | assertEquals("com.getcapacitor.app", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/assets/.keep -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/java/org/getoutline/pwa/Config.kt.handlebars: -------------------------------------------------------------------------------- 1 | package {{appId}} 2 | 3 | object Config { 4 | var domainList: String = "{{domainList}}" 5 | var smartDialer: String = "{{{smartDialerConfig}}}" 6 | } 7 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-land-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-land-hdpi/splash.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-land-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-land-mdpi/splash.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-land-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-land-xhdpi/splash.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-land-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-land-xxhdpi/splash.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-land-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-land-xxxhdpi/splash.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-port-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-port-hdpi/splash.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-port-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-port-mdpi/splash.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-port-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-port-xhdpi/splash.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-port-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-port-xxhdpi/splash.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-port-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-port-xxxhdpi/splash.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/drawable/splash.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/values/strings.xml.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{appName}} 4 | {{appName}} 5 | {{appId}} 6 | {{appId}} 7 | 8 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 11 | 12 | 17 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.myapp; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | 14 | @Test 15 | public void addition_isCorrect() throws Exception { 16 | assertEquals(4, 2 + 2); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | ext { 6 | kotlin_version = '1.9.22' 7 | } 8 | repositories { 9 | google() 10 | mavenCentral() 11 | } 12 | dependencies { 13 | classpath 'com.android.tools.build:gradle:8.2.1' 14 | classpath 'com.google.gms:google-services:4.4.0' 15 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 16 | 17 | // NOTE: Do not place your application dependencies here; they belong 18 | // in the individual module build.gradle files 19 | } 20 | } 21 | 22 | apply from: "variables.gradle" 23 | 24 | allprojects { 25 | repositories { 26 | google() 27 | mavenCentral() 28 | } 29 | } 30 | 31 | task clean(type: Delete) { 32 | delete rootProject.buildDir 33 | } 34 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/capacitor.settings.gradle: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN 2 | include ':capacitor-android' 3 | project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') 4 | 5 | include ':capacitor-camera' 6 | project(':capacitor-camera').projectDir = new File('../node_modules/@capacitor/camera/android') 7 | 8 | include ':capacitor-splash-screen' 9 | project(':capacitor-splash-screen').projectDir = new File('../node_modules/@capacitor/splash-screen/android') 10 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | # AndroidX package structure to make it clearer which packages are bundled with the 20 | # Android operating system, and which are packaged with your app's APK 21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 22 | android.useAndroidX=true 23 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':capacitor-cordova-android-plugins' 3 | project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') 4 | 5 | apply from: 'capacitor.settings.gradle' -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/android/variables.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | minSdkVersion = 26 3 | compileSdkVersion = 35 4 | targetSdkVersion = 35 5 | androidxActivityVersion = '1.8.0' 6 | androidxAppCompatVersion = '1.6.1' 7 | androidxCoordinatorLayoutVersion = '1.2.0' 8 | androidxCoreVersion = '1.12.0' 9 | androidxFragmentVersion = '1.6.2' 10 | coreSplashScreenVersion = '1.0.1' 11 | androidxWebkitVersion = '1.9.0' 12 | junitVersion = '4.13.2' 13 | androidxJunitVersion = '1.1.5' 14 | androidxEspressoCoreVersion = '3.5.1' 15 | cordovaAndroidVersion = '10.1.1' 16 | } -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/assets/.keep -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/capacitor.config.json.handlebars: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "{{appId}}", 3 | "appName": "{{appName}}", 4 | "bundledWebRuntime": false, 5 | "plugins": { 6 | "SplashScreen": { 7 | "launchShowDuration": 0 8 | } 9 | }, 10 | "server": { 11 | "url": "{{entryUrl}}" 12 | }, 13 | "ios": { 14 | "limitsNavigationsToAppBoundDomains": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/.gitignore: -------------------------------------------------------------------------------- 1 | App/build 2 | App/Pods 3 | App/output 4 | App/App/public 5 | DerivedData 6 | xcuserdata 7 | 8 | # Cordova plugins for Capacitor 9 | capacitor-cordova-ios-plugins 10 | 11 | # Generated Config files 12 | App/App/capacitor.config.json 13 | App/App/config.xml 14 | App/App/Config.swift 15 | App/App/Info.plist 16 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon-512@2x.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "splash-2732x2732-2.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "splash-2732x2732-1.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "splash-2732x2732.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jigsaw-Code/outline-sdk/0f8f1e1fb64a869b2685d67ccfaf58f123d5bc6d/x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/Config.swift.handlebars: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct Config { 4 | static var domainList: String = "{{domainList}}" 5 | static var smartDialer: String = "{{{smartDialerConfig}}}" 6 | static var proxyHost: String = "127.0.0.1" 7 | static var proxyPort: String = "0" 8 | } -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/Info.plist.handlebars: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | {{appName}} 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UIViewControllerBasedStatusBarAppearance 47 | 48 | WKAppBoundDomains 49 | 50 | {{entryDomain}} 51 | {{#each additionalDomains}} 52 | {{this}} 53 | {{/each}} 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/App/OutlineBridgeViewController.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Capacitor 3 | 4 | class OutlineBridgeViewController: CAPBridgeViewController { 5 | override func webView(with frame: CGRect, configuration: WKWebViewConfiguration) -> WKWebView { 6 | if #available(iOS 17.0, *) { 7 | let endpoint = NWEndpoint.hostPort( 8 | host: NWEndpoint.Host(Config.proxyHost), 9 | port: NWEndpoint.Port(Config.proxyPort)! 10 | ) 11 | let proxyConfig = ProxyConfiguration.init(httpCONNECTProxy: endpoint) 12 | 13 | let websiteDataStore = WKWebsiteDataStore.default() 14 | websiteDataStore.proxyConfigurations = [proxyConfig] 15 | 16 | configuration.websiteDataStore = websiteDataStore 17 | } else { 18 | // TODO: use scheme handler 19 | } 20 | 21 | return super.webView(with: frame, configuration: configuration) 22 | } 23 | 24 | override open func viewWillLayoutSubviews() { 25 | super.viewWillLayoutSubviews() 26 | 27 | guard let webView = self.webView else { return } 28 | 29 | if let safeAreaInsets = self.view.window?.safeAreaInsets { 30 | webView.frame.origin = CGPoint(x: safeAreaInsets.left, y: safeAreaInsets.top) 31 | webView.frame.size = CGSize( 32 | width: UIScreen.main.bounds.width - safeAreaInsets.left - safeAreaInsets.right, 33 | height: UIScreen.main.bounds.height - safeAreaInsets.top - safeAreaInsets.bottom 34 | ) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/Podfile: -------------------------------------------------------------------------------- 1 | require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers' 2 | 3 | platform :ios, '13.0' 4 | use_frameworks! 5 | 6 | # workaround to avoid Xcode caching of Pods that requires 7 | # Product -> Clean Build Folder after new Cordova plugins installed 8 | # Requires CocoaPods 1.6 or newer 9 | install! 'cocoapods', :disable_input_output_paths => true 10 | 11 | def capacitor_pods 12 | pod 'Capacitor', :path => '../../node_modules/@capacitor/ios' 13 | pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios' 14 | pod 'CapacitorCamera', :path => '../../node_modules/@capacitor/camera' 15 | pod 'CapacitorSplashScreen', :path => '../../node_modules/@capacitor/splash-screen' 16 | end 17 | 18 | target 'App' do 19 | capacitor_pods 20 | # Add your Pods here 21 | end 22 | 23 | post_install do |installer| 24 | assertDeploymentTarget(installer) 25 | end 26 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/ios/App/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Capacitor (6.1.2): 3 | - CapacitorCordova 4 | - CapacitorCamera (6.0.2): 5 | - Capacitor 6 | - CapacitorCordova (6.1.2) 7 | - CapacitorSplashScreen (6.0.2): 8 | - Capacitor 9 | 10 | DEPENDENCIES: 11 | - "Capacitor (from `../../node_modules/@capacitor/ios`)" 12 | - "CapacitorCamera (from `../../node_modules/@capacitor/camera`)" 13 | - "CapacitorCordova (from `../../node_modules/@capacitor/ios`)" 14 | - "CapacitorSplashScreen (from `../../node_modules/@capacitor/splash-screen`)" 15 | 16 | EXTERNAL SOURCES: 17 | Capacitor: 18 | :path: "../../node_modules/@capacitor/ios" 19 | CapacitorCamera: 20 | :path: "../../node_modules/@capacitor/camera" 21 | CapacitorCordova: 22 | :path: "../../node_modules/@capacitor/ios" 23 | CapacitorSplashScreen: 24 | :path: "../../node_modules/@capacitor/splash-screen" 25 | 26 | SPEC CHECKSUMS: 27 | Capacitor: 679f9673fdf30597493a6362a5d5bf233d46abc2 28 | CapacitorCamera: ed022171dbf3853e68eec877b4d78995378af6b7 29 | CapacitorCordova: f48c89f96c319101cd2f0ce8a2b7449b5fb8b3dd 30 | CapacitorSplashScreen: 250df9ef8014fac5c7c1fd231f0f8b1d8f0b5624 31 | 32 | PODFILE CHECKSUM: c23ed3fd935a944047d8284cab144b362bbd00cb 33 | 34 | COCOAPODS: 1.15.2 35 | -------------------------------------------------------------------------------- /x/examples/website-wrapper-app/wrapper_app_project/template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@website-wrapper-app/wrapper-app", 3 | "version": "1.0.0", 4 | "keywords": [ 5 | "capacitor", 6 | "mobile" 7 | ], 8 | "dependencies": { 9 | "@capacitor/android": "^6.1.2", 10 | "@capacitor/camera": "^6.1.2", 11 | "@capacitor/core": "^6.1.2", 12 | "@capacitor/ios": "^6.1.2", 13 | "@capacitor/splash-screen": "^6" 14 | }, 15 | "devDependencies": { 16 | "@capacitor/assets": "^3.0.5", 17 | "@capacitor/cli": "^6.1.2" 18 | }, 19 | "author": "Daniel LaCosse ", 20 | "license": "Apache-2.0", 21 | "scripts": { 22 | "setup": "npm ci && npx cap sync", 23 | "open:ios": "npx cap open ios", 24 | "open:android": "npx cap open android" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /x/httpconnect/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package httpconnect contains an HTTP CONNECT client implementation. 16 | package httpconnect 17 | -------------------------------------------------------------------------------- /x/httpconnect/pipe_conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package httpconnect 16 | 17 | import ( 18 | "errors" 19 | "github.com/Jigsaw-Code/outline-sdk/transport" 20 | "io" 21 | ) 22 | 23 | var _ transport.StreamConn = (*pipeConn)(nil) 24 | 25 | // pipeConn is a [transport.StreamConn] that overrides [Read], [Write] (and corresponding [Close]) functions with the given [reader] and [writer] 26 | type pipeConn struct { 27 | reader io.ReadCloser 28 | writer io.WriteCloser 29 | transport.StreamConn 30 | } 31 | 32 | func (p *pipeConn) Read(b []byte) (n int, err error) { 33 | return p.reader.Read(b) 34 | } 35 | 36 | func (p *pipeConn) Write(b []byte) (n int, err error) { 37 | return p.writer.Write(b) 38 | } 39 | 40 | func (p *pipeConn) CloseRead() error { 41 | return errors.Join(p.reader.Close(), p.StreamConn.CloseRead()) 42 | } 43 | 44 | func (p *pipeConn) CloseWrite() error { 45 | return errors.Join(p.writer.Close(), p.StreamConn.CloseWrite()) 46 | } 47 | 48 | func (p *pipeConn) Close() error { 49 | return errors.Join(p.reader.Close(), p.writer.Close(), p.StreamConn.Close()) 50 | } 51 | -------------------------------------------------------------------------------- /x/httpproxy/connect_handler_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package httpproxy 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "net/http/httptest" 21 | "testing" 22 | 23 | "github.com/Jigsaw-Code/outline-sdk/transport" 24 | "github.com/stretchr/testify/require" 25 | ) 26 | 27 | func TestNewConnectHandler(t *testing.T) { 28 | h := NewConnectHandler(transport.FuncStreamDialer(func(ctx context.Context, addr string) (transport.StreamConn, error) { 29 | return nil, errors.New("not implemented") 30 | })) 31 | 32 | ch, ok := h.(*connectHandler) 33 | require.True(t, ok) 34 | require.NotNil(t, ch.dialer) 35 | 36 | req := httptest.NewRequest("CONNECT", "example.invalid:0", nil) 37 | resp := httptest.NewRecorder() 38 | h.ServeHTTP(resp, req) 39 | require.Equal(t, 503, resp.Result().StatusCode) 40 | } 41 | -------------------------------------------------------------------------------- /x/httpproxy/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package httpproxy provides HTTP handlers for routing HTTP traffic through a local web proxy. 17 | 18 | # Important Security Considerations 19 | 20 | This package is designed primarily for use with private, internal forward proxies typically integrated within an application. 21 | It is not suitable for public-facing proxies due to the following security concerns: 22 | 23 | - Authentication: Public proxies must restrict access to only authorized users. This package does not provide built-in authentication mechanisms. 24 | - Probing Resistance: A public proxy should ideally not reveal its identity as a proxy, even under targeted probing. Implementing authentication can aid in this. 25 | - Protection of Local Resources: The dialer used by the proxy handlers should prevent connections to both localhost and the local network to avoid unintended access by clients. 26 | - Resource Limits: Implement limits on resources (number of connections, time connected, memory used, etc.) per user. This helps prevent denial-of-service attacks. 27 | 28 | If you intend to build a public-facing proxy, you will need to address these security issues using additional libraries or custom solutions. 29 | */ 30 | package httpproxy 31 | -------------------------------------------------------------------------------- /x/httpproxy/proxy_handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package httpproxy 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/Jigsaw-Code/outline-sdk/transport" 21 | ) 22 | 23 | type ProxyHandler struct { 24 | // Handler to fallback to if the request is not a proxy request (CONNECT method of absolute URL). 25 | // If FallbackHandler is absent, ProxyHandler returns a 404. 26 | FallbackHandler http.Handler 27 | connectHandler http.Handler 28 | forwardHandler http.Handler 29 | } 30 | 31 | // ServeHTTP implements [http.Handler].ServeHTTP for CONNECT and absolute URL requests, using the internal [transport.StreamDialer]. 32 | func (h *ProxyHandler) ServeHTTP(proxyResp http.ResponseWriter, proxyReq *http.Request) { 33 | // TODO(fortuna): For public services (not local), we need authentication and drain on failures to avoid fingerprinting. 34 | if proxyReq.Method == http.MethodConnect { 35 | h.connectHandler.ServeHTTP(proxyResp, proxyReq) 36 | return 37 | } 38 | if proxyReq.URL.Host != "" { 39 | h.forwardHandler.ServeHTTP(proxyResp, proxyReq) 40 | return 41 | } 42 | if h.FallbackHandler != nil { 43 | h.FallbackHandler.ServeHTTP(proxyResp, proxyReq) 44 | return 45 | } 46 | http.NotFound(proxyResp, proxyReq) 47 | } 48 | 49 | // NewProxyHandler creates a [http.Handler] that works as a web proxy using the given dialer to deach the destination. 50 | // You can use [ProxyHandler].FallbackHandler to specify how to handle non-proxy requests. 51 | func NewProxyHandler(dialer transport.StreamDialer) *ProxyHandler { 52 | return &ProxyHandler{ 53 | connectHandler: NewConnectHandler(dialer), 54 | forwardHandler: NewForwardHandler(dialer), 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /x/mobileproxy/.gitignore: -------------------------------------------------------------------------------- 1 | /out 2 | -------------------------------------------------------------------------------- /x/mobileproxy/tools.go: -------------------------------------------------------------------------------- 1 | // Copyright 2023 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build tools 16 | // +build tools 17 | 18 | // See https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module 19 | // and https://github.com/go-modules-by-example/index/blob/master/010_tools/README.md 20 | 21 | package tools 22 | 23 | import ( 24 | _ "github.com/Jigsaw-Code/outline-sdk/x/mobileproxy" 25 | _ "golang.org/x/mobile/cmd/gobind" 26 | _ "golang.org/x/mobile/cmd/gomobile" 27 | ) 28 | -------------------------------------------------------------------------------- /x/psiphon/build_tag.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build psiphon 16 | 17 | package psiphon 18 | 19 | // Fake variable used in the package to require the build tag, without preventing the rest of the code 20 | // from being processed by the Go documentation service. 21 | var mustSetPsiphonBuildTag = struct{}{} 22 | -------------------------------------------------------------------------------- /x/psiphon/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package psiphon provides adaptors to create StreamDialers that leverage [Psiphon] technology and 17 | infrastructure to bypass network interference. 18 | 19 | You will need to provide your own Psiphon config file, which you must acquire from the Psiphon team. 20 | See the [Psiphon End-User License Agreement]. For more details, email them at sponsor@psiphon.ca. 21 | 22 | For testing, you can [generate a Psiphon config yourself]. 23 | 24 | # License restrictions 25 | 26 | Psiphon code is licensed as GPLv3, which you will have to take into account if you incorporate Psiphon logic into your app. 27 | If you don't want your app to be GPL, consider acquiring an appropriate license when acquiring their services. 28 | 29 | Note that a few of Psiphon's dependencies may impose additional restrictions. You can use [go-licenses] to analyze the licenses of your Go code dependencies. 30 | 31 | To prevent accidental inclusion of unvetted licenses, you must use the "psiphon" build tag in order to use this package. Typically you do that with 32 | "-tags psiphon". 33 | 34 | [Psiphon]: https://psiphon.ca 35 | [Psiphon End-User License Agreement]: https://psiphon.ca/en/license.html 36 | [go-licenses]: https://github.com/google/go-licenses 37 | [generate a Psiphon config yourself]: https://github.com/Psiphon-Labs/psiphon-tunnel-core/tree/master?tab=readme-ov-file#generate-configuration-data 38 | */ 39 | package psiphon 40 | 41 | var _ = mustSetPsiphonBuildTag 42 | -------------------------------------------------------------------------------- /x/psiphon/testdata/integration_test_config.yaml: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /x/smart/cname.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !unix 16 | 17 | package smart 18 | 19 | import ( 20 | "context" 21 | "net" 22 | ) 23 | 24 | func lookupCNAME(ctx context.Context, domain string) (string, error) { 25 | return net.DefaultResolver.LookupCNAME(ctx, domain) 26 | } 27 | -------------------------------------------------------------------------------- /x/smart/cname_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build unix 16 | 17 | package smart 18 | 19 | /* 20 | #include 21 | #include 22 | #include 23 | #include 24 | */ 25 | import "C" 26 | 27 | import ( 28 | "context" 29 | "fmt" 30 | "unsafe" 31 | ) 32 | 33 | // lookupCNAME provides functionality equivalent to net.DefaultResolver.LookupCNAME. However, 34 | // net.DefaultResolver.LookupCNAME uses libresolv on unix, and, on Android and iOS, it tries 35 | // to connect to [::1]:53 (probably from /etc/resolv.conf) and the connection is refused. 36 | // Instead, we use getaddrinfo to get the canonical name. 37 | func lookupCNAME(ctx context.Context, domain string) (string, error) { 38 | type result struct { 39 | cname string 40 | err error 41 | } 42 | 43 | results := make(chan result) 44 | go func() { 45 | cname, err := lookupCNAMEBlocking(domain) 46 | results <- result{cname, err} 47 | }() 48 | 49 | select { 50 | case r := <-results: 51 | return r.cname, r.err 52 | case <-ctx.Done(): 53 | return "", ctx.Err() 54 | } 55 | } 56 | 57 | func lookupCNAMEBlocking(host string) (string, error) { 58 | var hints C.struct_addrinfo 59 | var result *C.struct_addrinfo 60 | 61 | chost := C.CString(host) 62 | defer C.free(unsafe.Pointer(chost)) 63 | 64 | hints.ai_family = C.AF_UNSPEC 65 | hints.ai_flags = C.AI_CANONNAME 66 | 67 | res := C.getaddrinfo(chost, nil, &hints, &result) 68 | if res != 0 { 69 | return "", fmt.Errorf("getaddrinfo error: %s", C.GoString(C.gai_strerror(res))) 70 | } 71 | defer C.freeaddrinfo(result) 72 | 73 | // Extract canonical name 74 | cname := C.GoString(result.ai_canonname) 75 | return cname, nil 76 | } 77 | -------------------------------------------------------------------------------- /x/smart/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Package smart provides utilities to dynamically find serverless strategies for circumvention. 17 | */ 18 | package smart 19 | -------------------------------------------------------------------------------- /x/smart/psiphon_dialer.go: -------------------------------------------------------------------------------- 1 | //go:build psiphon 2 | // +build psiphon 3 | 4 | // If the build tag `psiphon` is set, allow importing and calling psiphon 5 | 6 | package smart 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | "os" 12 | "path" 13 | 14 | "github.com/Jigsaw-Code/outline-sdk/transport" 15 | "github.com/Jigsaw-Code/outline-sdk/x/psiphon" 16 | ) 17 | 18 | func newPsiphonDialer(finder *StrategyFinder, ctx context.Context, psiphonJSON []byte) (transport.StreamDialer, error) { 19 | config := &psiphon.DialerConfig{ProviderConfig: psiphonJSON} 20 | 21 | cacheBaseDir, err := os.UserCacheDir() 22 | if err != nil { 23 | return nil, fmt.Errorf("Failed to get the user cache directory: %w", err) 24 | } 25 | 26 | config.DataRootDirectory = path.Join(cacheBaseDir, "psiphon") 27 | if err := os.MkdirAll(config.DataRootDirectory, 0700); err != nil { 28 | return nil, fmt.Errorf("Failed to create storage directory: %w", err) 29 | } 30 | finder.logCtx(ctx, "Using data store in %v\n", config.DataRootDirectory) 31 | 32 | dialer := psiphon.GetSingletonDialer() 33 | if err := dialer.Start(ctx, config); err != nil { 34 | return nil, fmt.Errorf("failed to start psiphon dialer: %w", err) 35 | } 36 | 37 | return dialer, nil 38 | } 39 | -------------------------------------------------------------------------------- /x/smart/psiphon_dialer_unimplemented.go: -------------------------------------------------------------------------------- 1 | //go:build !psiphon 2 | 3 | // If the build tag `psiphon` is not set, create a stub function to avoid pulling in GPL'd code 4 | 5 | package smart 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | "fmt" 11 | 12 | "github.com/Jigsaw-Code/outline-sdk/transport" 13 | ) 14 | 15 | func newPsiphonDialer(_ *StrategyFinder, _ context.Context, _ []byte) (transport.StreamDialer, error) { 16 | return nil, fmt.Errorf("attempted to start psiphon tunnel but library was built without psiphon support. Please build using -tag psiphon: %w", errors.ErrUnsupported) 17 | } 18 | -------------------------------------------------------------------------------- /x/smart/slice_helper.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package smart 16 | 17 | func moveToFront[S ~[]E, E any](s S, idx int) { 18 | if idx > 0 && idx < len(s) { 19 | entry := s[idx] 20 | copy(s[1:], s[:idx]) 21 | s[0] = entry 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /x/sockopt/sockopt_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sockopt 16 | 17 | import ( 18 | "net" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | func TestTCPOptions(t *testing.T) { 25 | type Params struct { 26 | Net string 27 | Addr string 28 | } 29 | for _, params := range []Params{{Net: "tcp4", Addr: "127.0.0.1:0"}, {Net: "tcp6", Addr: "[::1]:0"}} { 30 | l, err := net.Listen(params.Net, params.Addr) 31 | require.NoError(t, err) 32 | defer l.Close() 33 | 34 | conn, err := net.Dial("tcp", l.Addr().String()) 35 | require.NoError(t, err) 36 | tcpConn, ok := conn.(*net.TCPConn) 37 | require.True(t, ok) 38 | 39 | opts, err := NewTCPOptions(tcpConn) 40 | require.NoError(t, err) 41 | 42 | require.NoError(t, opts.SetHopLimit(1)) 43 | 44 | hoplim, err := opts.HopLimit() 45 | require.NoError(t, err) 46 | require.Equal(t, 1, hoplim) 47 | 48 | require.NoError(t, opts.SetHopLimit(20)) 49 | 50 | hoplim, err = opts.HopLimit() 51 | require.NoError(t, err) 52 | require.Equal(t, 20, hoplim) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /x/sysproxy/sysproxy_other.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 The Outline Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !linux && !windows && !darwin 16 | 17 | package sysproxy 18 | 19 | import "errors" 20 | 21 | // SetProxy does nothing on unsupported platforms. 22 | func SetWebProxy(ip string, port string) error { 23 | return errors.New("unsupported platform") 24 | } 25 | 26 | // SetProxy does nothing on unsupported platforms. 27 | func DisableWebProxy() error { 28 | return errors.New("unsupported platform") 29 | } 30 | 31 | // SetProxy does nothing on unsupported platforms. 32 | func SetSOCKSProxy(ip string, port string) error { 33 | return errors.New("unsupported platform") 34 | } 35 | 36 | // SetProxy does nothing on unsupported platforms. 37 | func DisableSOCKSProxy() error { 38 | return errors.New("unsupported platform") 39 | } 40 | --------------------------------------------------------------------------------