This document describes how to develop and debug for macOS (formerly known as OS X) and iOS.
You will need:
- An Apple Developer Account. You will need to be invited to your developer team as well.
- XCode 15.2 (download)
- XCode command line tools:
xcode-select --install
NOTE: Should you encounter issues with your build, there may be apple-specific dependencies that are out of date. Run
npm run reset
and try again!
The XCode project is created by Cordova. To create the project and open it on XCode, use npm run action client/src/cordova/setup $PLATFORM
, then open the XCode workspace file (not the project).
For the iOS client and Mac Catalyst client:
npm run action client/src/cordova/build ios && open ./client/src/cordova/apple/ios.xcworkspace
For the macOS client:
SENTRY_DSN=https://[email protected]/1 npm run action client/src/cordova/setup macos -- --buildMode=release --versionName=0.0.0-dev && open ./client/src/cordova/apple/macos.xcworkspace
Note On Apple Silicon the macOS web UI is broken in debug mode, so we need to build it in release mode. We are specifying a fake
SENTRY_DSN
, you should specify your own in your releases.
- In XCode, make sure you are logged into your Apple Developer account. Go to Preferences > Accounts and make sure your account is set.
- Select "Outline" in the left navigation bar.
- Under the "Signing & Capabilities" tab, select the "Jigsaw Operations LLC" for "Team".
For the macOS client, you can run it directly on your macOS computer: Product > Destination > My Mac.
For the iOS client, you have a few options:
- Run on your macOS computer: Product > Destination > My Mac (designed for iPad)
- This is a great option for development, but only available on Apple Silicon computers.
- Run on a physical iOS device
- This is a great option to evaluate how it performs on a real device. You will need to enable development mode and register your device.
- Run on a simulator
- This is helpful to test the UI, but the VPN doesn't work on simulators, so this option is not recommended.
See Devices and Simulator for details on running on a physical iOS device or the simulator.
You may find that your iOS version is too modern for XCode. You'll need to do the following:
- Download a folder corresponding to your iOS version from this community-managed repository.
- Unzip the file and add it to XCode:
Applications >> Xcode >> Right Click >> Show Package Contents >> Contents >> Developer >> Platforms >> iPhoneOS.platform >> DeviceSupport
- Restart XCode.
To run the app, first clean the build (Product > Clean Build Folder… (Cmd+Shift+K)), then run (Product > Run (Cmd+Run)), via the menu or the play button:
Warning
If you don't clean the build first, it will fail with
Command CodeSign failed with a nonzero exit code
.
Most of the Apple-specific development can happen directly on XCode. However, if you edit files in the generated platforms/ios
or platforms/osx
, you will need to copy your changes to the appropriate version-controlled location at src/cordova/apple/xcode
or src/cordova/plugin/apple
.
Changes to the OutlineAppleLib package don't need to be copied, since the package is linked by the XCode workspace and the changes happen in the original location.
The easiest way to inspect logs is to use the log
command. To see the client app logs in real time, you can use log stream
with a --predicate
flag to select the messages sent by the Outline code:
log stream --info --predicate 'senderImagePath contains "Outline.app"'
In the Console app, select the Action > Include Info Messages manu, and set the filter to "Library Path" "contains" "Outline.app":
💡 Tip: You can save searches in the MacOS Console app.
For further debugging, you can include relevant messages from the Network Extension subsystem:
log stream --info --predicate 'senderImagePath contains "Outline.app" or (processImagePath contains "Outline.app" and subsystem contains "com.apple.networkextension")'
To see past logs use log show
and the --last
flag.
For details on Apple logging, see Your Friend the System Log and Mac Logging and the log Command: A Guide for Apple Admins.
The VpnExtension runs in a separate process and its output is not logged to the Xcode console. To view its log statements see the "Inspect Logs" section.
XCode doesn't automatically attach to the VpnExtension because it's started on demand by the system.
- If the Vpn Extension is running:
- In XCode, select Debug > Attach to Process > VpnExtension
- If the VpnExtension is not running:
- In Xcode, select Debug > Attach to Process by PID or Name…
- Fill PID or Process Name with
VpnExtension
and press Attach
You won't see the log messages in the Xcode console. To see the messagges, refer to the "Inspect Logs" instructions. For more info, see Debug, Profile, and Test Your App Extension.
Sometimes the app will refuse to connect, with a VpnStartFailure
error:
If that happens, there are some things you can try.
You can kill the app and extension with the pkill
command:
pkill -9 Outline VpnExtension
Sometimes the processes will not die, even with -9
. For the Outline process, you may need to kill its parent process, usually debugserver
.
You can check running processes with the pgrep
command:
pgrep Outline VpnExtension
The VpnExtension is an application extension that handles the device’s traffic when the VPN is enabled. The system must be aware of the extension in order to invoke it. Normally, running the app is enough to trigger the registration of the VpnExtension. However, the system can get confused in a development environment, failing to register the plugin automatically, or using the extension from the production app, if you have it installed, or from a different build.
In your terminal, use the pluginkit
command to inspect the registered plugins:
pluginkit -mvA | grep outline
You should see an for the VpnExtension in your Xcode project, where the version and binary location match. This should output something similar to:
org.outline.macos.client.VpnExtension(0.0.0-dev) 508D6616-9FCB-4302-B00F-22121C236AAC 2023-07-14 00:05:34 +0000 /Users/$USER/Library/Developer/Xcode/DerivedData/macos-bnidlwvulcdazjfxleynwzkychqi/Build/Products/Debug/Outline.app/Contents/PlugIns/VpnExtension.appex
Note how VpnExtension.appex
is inside Outline.app/
.
It's safe to unregister all the Outline VPN Extensions, since the system will load them on demand. To do so, for each of them, call
pluginkit -r $APP_EXTENSION_PATH
Make sure that you list the registered plugins again after unregistering them, since they may fallback to other versions of it, which you may also need to unregister.
Where the $APP_EXTENSION_PATH
is the location of the VpnExtension.appex
file from the pluginkit command.
If your extenstion is still not loading, you can try to force register it:
- Determine the VpnExtension path
- In XCode, go to Product > Show Build Folder in Finder. That will open the
Build/
folder. - The VpnExtension will be at
Build/Products/Debug/Outline.app/Contents/PlugIns/VpnExtension.appex
- Run
run pluginkit -a <your appex file>
, e.g.pluginkit -a /Users/$USER/Library/Developer/Xcode/DerivedData/macos-bnidlwvulcdazjfxleynwzkychqi/Build/Products/Debug/Outline.app/Contents/PlugIns/VpnExtension.appex
You may need to run the lsregister
garbage collector to make sure old entries
in the Launch Services database are cleared:
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -gc
You may want to also delete the VPN configurations:
- Open the System Settings
- Go to VPN
- Press the Info button for the Outline service
- Press Remove Configuration… and confirm
If all fails, restart your device. That usually takes care of the issue.
To debug the webview:
- You may need to enable the Develop menu first, by selecting Settings > Advanced > Show Develop menu in menu bar
- In your terminal, run
defaults write org.outline.osx.client WebKitDeveloperExtras -bool true
. This is only needed once, to make the Outline webview debuggable. You may need to re-run the whole Outline app (use Cmd+R). - In the Outline Client app, right click → Inspect Context. This will open the Safari debugger
To reload the UI without re-running the application, right-click → Reload.