Compare commits
15 commits
slack-auth
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
85640ffce1 | ||
|
|
25a0f7c421 | ||
|
|
e4b0f1660b | ||
|
|
3fbb520a10 | ||
|
|
fa1ef6fcda | ||
|
|
62a5739d86 | ||
|
|
aa634d03e2 | ||
|
|
951b4ddae2 | ||
|
|
3dedca4de3 | ||
|
|
3c70bc2a5c | ||
|
|
bca07c33b8 | ||
|
|
abf1101484 | ||
|
|
df549d48e6 | ||
|
|
ec8cc533ab | ||
|
|
a97063f9b7 |
1
.github/actions/archive/action.yml
vendored
|
|
@ -35,7 +35,6 @@ runs:
|
|||
-authenticationKeyID ${{ inputs.app-store-key-id }} \
|
||||
-authenticationKeyIssuerID ${{ inputs.app-store-key-issuer-id }} \
|
||||
-authenticationKeyPath "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" \
|
||||
-onlyUsePackageVersionsFromResolvedFile \
|
||||
-scheme '${{ inputs.scheme }}' \
|
||||
-destination '${{ inputs.destination }}' \
|
||||
-archivePath '${{ inputs.archive-path }}' \
|
||||
|
|
|
|||
2
.github/actions/build-for-testing/action.yml
vendored
|
|
@ -27,7 +27,9 @@ runs:
|
|||
Apple/DerivedData
|
||||
key: ${{ runner.os }}-${{ inputs.scheme }}-${{ hashFiles('**/Package.resolved') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ inputs.scheme }}-${{ hashFiles('**/Package.resolved') }}
|
||||
${{ runner.os }}-${{ inputs.scheme }}-
|
||||
${{ runner.os }}-
|
||||
- name: Build
|
||||
shell: bash
|
||||
working-directory: Apple
|
||||
|
|
|
|||
30
.github/actions/download-profiles/action.yml
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
name: Download Provisioning Profiles
|
||||
inputs:
|
||||
app-store-key:
|
||||
description: App Store key in PEM PKCS#8 format
|
||||
required: true
|
||||
app-store-key-id:
|
||||
description: App Store key ID
|
||||
required: true
|
||||
app-store-key-issuer-id:
|
||||
description: App Store key issuer ID
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- shell: bash
|
||||
env:
|
||||
FASTLANE_OPT_OUT_USAGE: 'YES'
|
||||
run: |
|
||||
APP_STORE_KEY=$(echo "${{ inputs.app-store-key }}" | jq -sR .)
|
||||
cat << EOF > api-key.json
|
||||
{
|
||||
"key_id": "${{ inputs.app-store-key-id }}",
|
||||
"issuer_id": "${{ inputs.app-store-key-issuer-id }}",
|
||||
"key": $APP_STORE_KEY
|
||||
}
|
||||
EOF
|
||||
|
||||
fastlane sigh download_all --api_key_path api-key.json
|
||||
|
||||
rm -rf api-key.json
|
||||
10
.github/actions/export/action.yml
vendored
|
|
@ -12,11 +12,8 @@ inputs:
|
|||
archive-path:
|
||||
description: Xcode archive path
|
||||
required: true
|
||||
destination:
|
||||
description: The Xcode export destination. This can either be "export" or "upload"
|
||||
required: true
|
||||
method:
|
||||
description: The Xcode export method. This can be one of app-store, validation, ad-hoc, package, enterprise, development, developer-id, or mac-application.
|
||||
export-options:
|
||||
description: The export options in JSON format
|
||||
required: true
|
||||
export-path:
|
||||
description: The path to export the archive to
|
||||
|
|
@ -29,8 +26,7 @@ runs:
|
|||
run: |
|
||||
echo "${{ inputs.app-store-key }}" > AuthKey_${{ inputs.app-store-key-id }}.p8
|
||||
|
||||
echo '{"destination":"${{ inputs.destination }}","method":"${{ inputs.method }}"}' \
|
||||
| plutil -convert xml1 -o ExportOptions.plist -
|
||||
echo '${{ inputs.export-options }}' | plutil -convert xml1 -o ExportOptions.plist -
|
||||
|
||||
xcodebuild \
|
||||
-exportArchive \
|
||||
|
|
|
|||
35
.github/actions/notarize/action.yml
vendored
|
|
@ -9,12 +9,6 @@ inputs:
|
|||
app-store-key-issuer-id:
|
||||
description: App Store key issuer ID
|
||||
required: true
|
||||
archive-path:
|
||||
description: Xcode archive path
|
||||
required: true
|
||||
export-path:
|
||||
description: The path to export the archive to
|
||||
required: true
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
|
|
@ -24,29 +18,8 @@ runs:
|
|||
run: |
|
||||
echo "${{ inputs.app-store-key }}" > AuthKey_${{ inputs.app-store-key-id }}.p8
|
||||
|
||||
echo '{"destination":"export","method":"developer-id"}' \
|
||||
| plutil -convert xml1 -o ExportOptions.plist -
|
||||
ditto -c -k --keepParent Release/Burrow.app Upload.zip
|
||||
xcrun notarytool submit --wait --issuer ${{ inputs.app-store-key-issuer-id }} --key-id ${{ inputs.app-store-key-id }} --key "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" Upload.zip
|
||||
xcrun stapler staple Release/Burrow.app
|
||||
|
||||
xcodebuild \
|
||||
-exportArchive \
|
||||
-allowProvisioningUpdates \
|
||||
-allowProvisioningDeviceRegistration \
|
||||
-skipPackagePluginValidation \
|
||||
-skipMacroValidation \
|
||||
-archivePath Wallet.xcarchive \
|
||||
-exportPath Release \
|
||||
-exportOptionsPlist ExportOptions.plist
|
||||
|
||||
rm ExportOptions.plist
|
||||
|
||||
NOTARY_AUTH=""
|
||||
|
||||
ditto -c -k --keepParent Release/Wallet.app Upload.zip
|
||||
SUBMISSION_ID=$(xcrun notarytool submit --issuer ${{ inputs.app-store-key-issuer-id }} --key-id ${{ inputs.app-store-key-id }} --key "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8" Upload.zip | awk '/ id:/ { print $2; exit }')
|
||||
|
||||
xcrun notarytool wait $SUBMISSION_ID --issuer ${{ inputs.app-store-key-issuer-id }} --key-id ${{ inputs.app-store-key-id }} --key "${PWD}/AuthKey_${{ inputs.app-store-key-id }}.p8"
|
||||
xcrun stapler staple Release/Wallet.app
|
||||
|
||||
aa archive -a lzma -b 8m -d Release -subdir Wallet.app -o Wallet.app.aar
|
||||
|
||||
rm -rf Upload.zip Release AuthKey_${{ inputs.app-store-key-id }}.p8 ExportOptions.plist
|
||||
rm -rf AuthKey_${{ inputs.app-store-key-id }}.p8 Release
|
||||
|
|
|
|||
3
.github/workflows/build-appimage.yml
vendored
|
|
@ -6,6 +6,9 @@ on:
|
|||
pull_request:
|
||||
branches:
|
||||
- "*"
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
appimage:
|
||||
name: Build AppImage
|
||||
|
|
|
|||
12
.github/workflows/build-apple.yml
vendored
|
|
@ -1,7 +1,7 @@
|
|||
name: Build Apple Apps
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
rust-targets:
|
||||
- aarch64-apple-ios
|
||||
- scheme: App
|
||||
destination: platform=iOS Simulator,OS=17.2,name=iPhone 15 Pro
|
||||
destination: platform=iOS Simulator,OS=18.0,name=iPhone 15 Pro
|
||||
platform: iOS Simulator
|
||||
sdk-name: iphonesimulator
|
||||
rust-targets:
|
||||
|
|
@ -38,7 +38,8 @@ jobs:
|
|||
- x86_64-apple-darwin
|
||||
- aarch64-apple-darwin
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.2.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer
|
||||
PROTOC_PATH: /opt/homebrew/bin/protoc
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
|
@ -54,6 +55,9 @@ jobs:
|
|||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ join(matrix.rust-targets, ', ') }}
|
||||
- name: Install Protobuf
|
||||
shell: bash
|
||||
run: brew install protobuf
|
||||
- name: Build
|
||||
id: build
|
||||
uses: ./.github/actions/build-for-testing
|
||||
|
|
@ -82,4 +86,4 @@ jobs:
|
|||
destination: ${{ matrix.destination }}
|
||||
test-plan: ${{ matrix.xcode-ui-test }}
|
||||
artifact-prefix: ui-tests-${{ matrix.sdk-name }}
|
||||
check-name: Xcode UI Tests (${{ matrix.platform }})
|
||||
check-name: Xcode UI Tests (${{ matrix.platform }})
|
||||
3
.github/workflows/build-docker.yml
vendored
|
|
@ -6,6 +6,9 @@ on:
|
|||
pull_request:
|
||||
branches:
|
||||
- "*"
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
build:
|
||||
name: Build Docker Image
|
||||
|
|
|
|||
2
.github/workflows/build-rpm.yml
vendored
|
|
@ -5,7 +5,7 @@ jobs:
|
|||
name: Build RPM
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Install RPM
|
||||
run: cargo install cargo-generate-rpm
|
||||
|
|
|
|||
23
.github/workflows/build-rust.yml
vendored
|
|
@ -21,15 +21,21 @@ jobs:
|
|||
- x86_64-unknown-linux-gnu
|
||||
targets:
|
||||
- aarch64-unknown-linux-gnu
|
||||
- os: macos-12
|
||||
platform: macOS
|
||||
- os: macos-13
|
||||
platform: macOS (Intel)
|
||||
xcode: /Applications/Xcode_15.2.app
|
||||
test-targets:
|
||||
- x86_64-apple-darwin
|
||||
targets:
|
||||
- x86_64-apple-ios
|
||||
- os: macos-14
|
||||
platform: macOS
|
||||
xcode: /Applications/Xcode_16.0.app
|
||||
test-targets:
|
||||
- aarch64-apple-darwin
|
||||
targets:
|
||||
- aarch64-apple-ios
|
||||
- aarch64-apple-ios-sim
|
||||
- x86_64-apple-ios
|
||||
- os: windows-2022
|
||||
platform: Windows
|
||||
test-targets:
|
||||
|
|
@ -38,10 +44,11 @@ jobs:
|
|||
- aarch64-pc-windows-msvc
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_14.2.app/Contents/Developer
|
||||
DEVELOPER_DIR: ${{ matrix.xcode }}/Contents/Developer
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
|
||||
RUST_BACKTRACE: short
|
||||
PROTOC_VERSION: 3.25.1
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
|
@ -54,10 +61,14 @@ jobs:
|
|||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ${{ join(matrix.packages, ' ') }}
|
||||
- name: Install Windows Deps
|
||||
- name: Configure LLVM
|
||||
if: matrix.os == 'windows-2022'
|
||||
shell: bash
|
||||
run: echo "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\Llvm\x64\bin" >> $GITHUB_PATH
|
||||
- name: Install protoc
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: protoc@${{ env.PROTOC_VERSION }}
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
|
|
@ -71,4 +82,4 @@ jobs:
|
|||
run: cargo build --verbose --workspace --all-features --target ${{ join(matrix.targets, ' --target ') }} --target ${{ join(matrix.test-targets, ' --target ') }}
|
||||
- name: Test
|
||||
shell: bash
|
||||
run: cargo test --verbose --workspace --all-features --target ${{ join(matrix.test-targets, ' --target ') }}
|
||||
run: cargo test --verbose --workspace --all-features --target ${{ join(matrix.test-targets, ' --target ') }}
|
||||
2
.github/workflows/lint-swift.yml
vendored
|
|
@ -13,4 +13,4 @@ jobs:
|
|||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Lint
|
||||
run: swiftlint lint --reporter github-actions-logging
|
||||
run: swiftlint lint --strict --reporter github-actions-logging
|
||||
|
|
|
|||
76
.github/workflows/release-apple.yml
vendored
|
|
@ -13,17 +13,16 @@ jobs:
|
|||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- destination: generic/platform=iOS
|
||||
platform: iOS
|
||||
- platform: iOS
|
||||
rust-targets:
|
||||
- aarch64-apple-ios
|
||||
- destination: generic/platform=macOS
|
||||
platform: macOS
|
||||
- platform: macOS
|
||||
rust-targets:
|
||||
- x86_64-apple-darwin
|
||||
- aarch64-apple-darwin
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_15.2.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer
|
||||
PROTOC_PATH: /opt/homebrew/bin/protoc
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
|
@ -34,41 +33,56 @@ jobs:
|
|||
with:
|
||||
certificate: ${{ secrets.DEVELOPER_CERT }}
|
||||
password: ${{ secrets.DEVELOPER_CERT_PASSWORD }}
|
||||
- name: Download Provisioning Profiles
|
||||
uses: ./.github/actions/download-profiles
|
||||
with:
|
||||
app-store-key: ${{ secrets.APPSTORE_KEY }}
|
||||
app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
||||
app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }}
|
||||
- name: Install Provisioning Profiles
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles/
|
||||
cp -f Apple/Profiles/* ~/Library/MobileDevice/Provisioning\ Profiles/
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ join(matrix.rust-targets, ', ') }}
|
||||
- name: Configure Version
|
||||
- name: Install Protobuf
|
||||
shell: bash
|
||||
run: Tools/version.sh
|
||||
run: brew install protobuf
|
||||
- name: Configure Version
|
||||
id: version
|
||||
shell: bash
|
||||
run: echo "BUILD_NUMBER=$(Tools/version.sh)" >> $GITHUB_OUTPUT
|
||||
- name: Archive
|
||||
uses: ./.github/actions/archive
|
||||
with:
|
||||
scheme: App
|
||||
destination: ${{ matrix.destination }}
|
||||
destination: generic/platform=${{ matrix.platform }}
|
||||
app-store-key: ${{ secrets.APPSTORE_KEY }}
|
||||
app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
||||
app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }}
|
||||
archive-path: Burrow.xcarchive
|
||||
- name: Notarize (macOS)
|
||||
- name: Export
|
||||
uses: ./.github/actions/export
|
||||
with:
|
||||
method: ${{ matrix.platform == 'macOS' && 'developer-id' || 'ad-hoc' }}
|
||||
destination: export
|
||||
app-store-key: ${{ secrets.APPSTORE_KEY }}
|
||||
app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
||||
app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }}
|
||||
archive-path: Burrow.xcarchive
|
||||
export-options: |
|
||||
{"teamID":"P6PV2R9443","destination":"export","method":"developer-id","provisioningProfiles":{"com.hackclub.burrow":"Burrow Developer ID","com.hackclub.burrow.network":"Burrow Network Developer ID"},"signingCertificate":"Developer ID Application","signingStyle":"manual"}
|
||||
export-path: Release
|
||||
- name: Notarize
|
||||
if: ${{ matrix.platform == 'macOS' }}
|
||||
uses: ./.github/actions/notarize
|
||||
with:
|
||||
app-store-key: ${{ secrets.APPSTORE_KEY }}
|
||||
app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
||||
app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }}
|
||||
archive-path: Burrow.xcarchive
|
||||
- name: Export IPA (iOS)
|
||||
if: ${{ matrix.platform == 'iOS' }}
|
||||
uses: ./.github/actions/export
|
||||
with:
|
||||
method: ad-hoc
|
||||
destination: export
|
||||
app-store-key: ${{ secrets.APPSTORE_KEY }}
|
||||
app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
||||
app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }}
|
||||
archive-path: Burrow.xcarchive
|
||||
export-path: Release
|
||||
- name: Compress (iOS)
|
||||
if: ${{ matrix.platform == 'iOS' }}
|
||||
shell: bash
|
||||
|
|
@ -83,33 +97,23 @@ jobs:
|
|||
aa archive -a lzma -b 8m -d Apple/Release -subdir Burrow.app -o Burrow.app.aar
|
||||
aa archive -a lzma -b 8m -d Apple -subdir Burrow.xcarchive -o Burrow-${{ matrix.platform }}.xcarchive.aar
|
||||
rm -rf Apple/Release
|
||||
- name: Upload to GitHub (iOS)
|
||||
if: ${{ matrix.platform == 'iOS' }}
|
||||
- name: Upload to GitHub
|
||||
uses: SierraSoftworks/gh-releases@v1.0.7
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
release_tag: ${{ github.ref_name }}
|
||||
overwrite: 'true'
|
||||
files: |
|
||||
Burrow.ipa
|
||||
Burrow-${{ matrix.platform }}.xcarchive.aar
|
||||
- name: Upload to GitHub (macOS)
|
||||
if: ${{ matrix.platform == 'macOS' }}
|
||||
uses: SierraSoftworks/gh-releases@v1.0.7
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
release_tag: ${{ github.ref_name }}
|
||||
overwrite: 'true'
|
||||
files: |
|
||||
Burrow.aap.aar
|
||||
${{ matrix.platform == 'macOS' && 'Burrow.aap.aar' || 'Burrow.ipa' }}
|
||||
Burrow-${{ matrix.platform }}.xcarchive.aar
|
||||
- name: Upload to App Store Connect
|
||||
if: ${{ matrix.platform == 'iOS' }}
|
||||
uses: ./.github/actions/export
|
||||
with:
|
||||
method: app-store
|
||||
destination: upload
|
||||
app-store-key: ${{ secrets.APPSTORE_KEY }}
|
||||
app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
||||
app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }}
|
||||
archive-path: Burrow.xcarchive
|
||||
export-options: |
|
||||
{"method": "app-store", "destination": "upload"}
|
||||
export-path: Release
|
||||
|
|
|
|||
2
.github/workflows/release-if-needed.yaml
vendored
|
|
@ -9,6 +9,8 @@ jobs:
|
|||
create:
|
||||
name: Create Release If Needed
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
name: Release (AppImage)
|
||||
name: Release (Linux)
|
||||
on:
|
||||
release:
|
||||
types:
|
||||
|
|
@ -13,17 +13,17 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build
|
||||
- name: Build AppImage
|
||||
run: |
|
||||
docker build -t appimage-builder . -f burrow-gtk/build-aux/Dockerfile
|
||||
docker create --name temp appimage-builder
|
||||
docker cp temp:/app/burrow-gtk/build-appimage/Burrow-x86_64.AppImage .
|
||||
docker rm temp
|
||||
- name: Upload to GitHub
|
||||
- name: Attach Artifacts
|
||||
uses: SierraSoftworks/gh-releases@v1.0.7
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
release_tag: ${{ github.ref_name }}
|
||||
overwrite: 'true'
|
||||
overwrite: "true"
|
||||
files: |
|
||||
Burrow-x86_64.AppImage
|
||||
9
.gitignore
vendored
|
|
@ -1,8 +1,17 @@
|
|||
# Xcode
|
||||
xcuserdata
|
||||
|
||||
# Swift
|
||||
Apple/Package/.swiftpm/
|
||||
|
||||
# Rust
|
||||
target/
|
||||
.env
|
||||
|
||||
.DS_STORE
|
||||
.idea/
|
||||
|
||||
tmp/
|
||||
|
||||
*.db
|
||||
*.sock
|
||||
|
|
@ -30,7 +30,6 @@ opt_in_rules:
|
|||
- function_default_parameter_at_end
|
||||
- ibinspectable_in_extension
|
||||
- identical_operands
|
||||
- implicitly_unwrapped_optional
|
||||
- indentation_width
|
||||
- joined_default_parameter
|
||||
- last_where
|
||||
|
|
|
|||
42
.vscode/settings.json
vendored
|
|
@ -1,18 +1,26 @@
|
|||
{
|
||||
"files.autoSave": "onFocusChange",
|
||||
"files.defaultLanguage": "rust",
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.formatOnSave": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"editor.suggest.preview": true,
|
||||
"editor.acceptSuggestionOnEnter": "on",
|
||||
"rust-analyzer.restartServerOnConfigChange": true,
|
||||
"rust-analyzer.cargo.features": "all",
|
||||
"rust-analyzer.rustfmt.extraArgs": [
|
||||
"+nightly"
|
||||
],
|
||||
"[rust]": {
|
||||
"editor.defaultFormatter": "rust-lang.rust-analyzer",
|
||||
},
|
||||
"rust-analyzer.inlayHints.typeHints.enable": false
|
||||
}
|
||||
"files.autoSave": "onFocusChange",
|
||||
"files.defaultLanguage": "rust",
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.formatOnSave": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"editor.suggest.preview": true,
|
||||
"editor.acceptSuggestionOnEnter": "on",
|
||||
"rust-analyzer.restartServerOnConfigChange": true,
|
||||
"rust-analyzer.cargo.features": "all",
|
||||
"rust-analyzer.rustfmt.extraArgs": ["+nightly"],
|
||||
"[rust]": {
|
||||
"editor.defaultFormatter": "rust-lang.rust-analyzer"
|
||||
},
|
||||
"rust-analyzer.inlayHints.typeHints.enable": false,
|
||||
"rust-analyzer.linkedProjects": [
|
||||
"./burrow/Cargo.toml"
|
||||
],
|
||||
"[yaml]": {
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabSize": 2,
|
||||
"editor.autoIndent": "advanced",
|
||||
"diffEditor.ignoreTrimWhitespace": false,
|
||||
"editor.formatOnSave": false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
#if os(macOS)
|
||||
import AppKit
|
||||
import BurrowUI
|
||||
import SwiftUI
|
||||
|
||||
@main
|
||||
@MainActor
|
||||
@NSApplicationMain
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
private let quitItem: NSMenuItem = {
|
||||
let quitItem = NSMenuItem(
|
||||
|
|
|
|||
|
|
@ -1,18 +1,13 @@
|
|||
#if !os(macOS)
|
||||
import BurrowUI
|
||||
import SwiftUI
|
||||
|
||||
#if !os(macOS)
|
||||
@MainActor
|
||||
@main
|
||||
struct BurrowApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
BurrowView()
|
||||
.onOpenURL { url in
|
||||
print(url)
|
||||
}
|
||||
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in
|
||||
print(userActivity.webpageURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23091" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22689"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23091"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
import SwiftUI
|
||||
|
||||
protocol Network {
|
||||
associatedtype Label: View
|
||||
|
||||
var id: String { get }
|
||||
var backgroundColor: Color { get }
|
||||
|
||||
var label: Label { get }
|
||||
}
|
||||
|
|
@ -1,364 +0,0 @@
|
|||
import AuthenticationServices
|
||||
import SwiftUI
|
||||
import Foundation
|
||||
|
||||
enum OAuth2 {
|
||||
enum Error: Swift.Error {
|
||||
case unknown
|
||||
case invalidAuthorizationURL
|
||||
case invalidCallbackURL
|
||||
case invalidRedirectURI
|
||||
case invalidScopes(Set<String>)
|
||||
}
|
||||
|
||||
enum TokenType: Codable, LosslessStringConvertible {
|
||||
case bearer
|
||||
case unknown(String)
|
||||
|
||||
init?(_ description: String) {
|
||||
self = switch description {
|
||||
case "bearer": .bearer
|
||||
default: .unknown(description)
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .bearer: "bearer"
|
||||
case .unknown(let type): type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum GrantType: Codable, LosslessStringConvertible {
|
||||
case authorizationCode
|
||||
case unknown(String)
|
||||
|
||||
init?(_ description: String) {
|
||||
self = switch description {
|
||||
case "authorization_code": .authorizationCode
|
||||
default: .unknown(description)
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .authorizationCode: "authorization_code"
|
||||
case .unknown(let type): type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ResponseType: Codable, LosslessStringConvertible {
|
||||
case code
|
||||
case idToken
|
||||
case unknown(String)
|
||||
|
||||
init?(_ description: String) {
|
||||
self = switch description {
|
||||
case "code": .code
|
||||
case "id_token": .idToken
|
||||
default: .unknown(description)
|
||||
}
|
||||
}
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .code: "code"
|
||||
case .idToken: "id_token"
|
||||
case .unknown(let type): type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct AccessTokenRequest: Codable {
|
||||
var clientID: String
|
||||
var grantType: GrantType
|
||||
var code: String?
|
||||
var redirectURI: URL?
|
||||
}
|
||||
|
||||
struct AccessTokenResponse: Codable {
|
||||
var accessToken: String
|
||||
var tokenType: TokenType
|
||||
var expiresIn: Double?
|
||||
var refreshToken: String?
|
||||
}
|
||||
|
||||
fileprivate struct CodeResponse: Codable {
|
||||
var code: String
|
||||
var state: String?
|
||||
}
|
||||
|
||||
fileprivate static var decoder: JSONDecoder {
|
||||
let decoder = JSONDecoder()
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
return decoder
|
||||
}
|
||||
|
||||
fileprivate static var encoder: JSONEncoder {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.keyEncodingStrategy = .convertToSnakeCase
|
||||
return encoder
|
||||
}
|
||||
}
|
||||
|
||||
enum OpenID {
|
||||
struct Configuration: Codable {
|
||||
var issuer: URL
|
||||
var authorizationEndpoint: URL
|
||||
var tokenEndpoint: URL
|
||||
var userinfoEndpoint: URL
|
||||
var scopesSupported: Set<String>
|
||||
|
||||
static func load(from issuerURL: URL) async throws -> Self {
|
||||
let configurationURL = issuerURL
|
||||
.appending(component: ".well-known")
|
||||
.appending(component: "openid-configuration")
|
||||
let (data, _) = try await URLSession.shared.data(from: configurationURL)
|
||||
return try OAuth2.decoder.decode(Self.self, from: data)
|
||||
}
|
||||
}
|
||||
|
||||
struct Session {
|
||||
var authorizationEndpoint: URL
|
||||
var tokenEndpoint: URL
|
||||
var redirectURI: URL
|
||||
var responseType = OAuth2.ResponseType.code
|
||||
var scopes: Set<String>
|
||||
var clientID: String
|
||||
|
||||
init(issuerURL: URL, redirectURI: URL, scopes: Set<String>, clientID: String) async throws {
|
||||
async let configuration = Configuration.load(from: issuerURL)
|
||||
try await self.init(
|
||||
configuration: configuration,
|
||||
redirectURI: redirectURI,
|
||||
scopes: scopes,
|
||||
clientID: clientID
|
||||
)
|
||||
}
|
||||
|
||||
init(configuration: Configuration, redirectURI: URL, scopes: Set<String>, clientID: String) throws {
|
||||
guard scopes.isSubset(of: configuration.scopesSupported) else {
|
||||
throw OAuth2.Error.invalidScopes(scopes.subtracting(configuration.scopesSupported))
|
||||
}
|
||||
|
||||
self.authorizationEndpoint = configuration.authorizationEndpoint
|
||||
self.tokenEndpoint = configuration.tokenEndpoint
|
||||
self.redirectURI = redirectURI
|
||||
self.scopes = scopes
|
||||
self.clientID = clientID
|
||||
}
|
||||
|
||||
private var authorizationURL: URL {
|
||||
get throws {
|
||||
var queryItems: [URLQueryItem] = [
|
||||
.init(name: "client_id", value: clientID),
|
||||
.init(name: "response_type", value: responseType.description),
|
||||
.init(name: "redirect_uri", value: redirectURI.absoluteString),
|
||||
]
|
||||
if !scopes.isEmpty {
|
||||
queryItems.append(.init(name: "scope", value: scopes.joined(separator: ",")))
|
||||
}
|
||||
guard var components = URLComponents(url: authorizationEndpoint, resolvingAgainstBaseURL: false) else {
|
||||
throw OAuth2.Error.invalidAuthorizationURL
|
||||
}
|
||||
components.queryItems = queryItems
|
||||
guard let authorizationURL = components.url else { throw OAuth2.Error.invalidAuthorizationURL }
|
||||
return authorizationURL
|
||||
}
|
||||
}
|
||||
|
||||
private func handle(callbackURL: URL) async throws -> OAuth2.AccessTokenResponse {
|
||||
switch responseType {
|
||||
case .code:
|
||||
guard let components = URLComponents(url: callbackURL, resolvingAgainstBaseURL: false) else {
|
||||
throw OAuth2.Error.invalidCallbackURL
|
||||
}
|
||||
return try await handle(response: try components.decode(OAuth2.CodeResponse.self))
|
||||
default:
|
||||
throw OAuth2.Error.invalidCallbackURL
|
||||
}
|
||||
}
|
||||
|
||||
private func handle(response: OAuth2.CodeResponse) async throws -> OAuth2.AccessTokenResponse {
|
||||
let body = OAuth2.AccessTokenRequest(clientID: clientID, grantType: .authorizationCode, code: response.code)
|
||||
|
||||
var request = URLRequest(url: tokenEndpoint)
|
||||
request.httpMethod = "POST"
|
||||
request.httpBody = try OAuth2.encoder.encode(body)
|
||||
|
||||
let session = URLSession(configuration: .ephemeral)
|
||||
let (data, _) = try await session.data(for: request)
|
||||
let response = try OAuth2.decoder.decode(OAuth2.AccessTokenResponse.self, from: data)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
func authorize(
|
||||
configure: (ASWebAuthenticationSession) -> Void
|
||||
) async throws -> OAuth2.AccessTokenResponse {
|
||||
let authorizationURL = try authorizationURL
|
||||
let callbackURL = try await ASWebAuthenticationSession.start(
|
||||
url: authorizationURL,
|
||||
redirectURI: redirectURI,
|
||||
configure: configure
|
||||
)
|
||||
return try await handle(callbackURL: callbackURL)
|
||||
}
|
||||
|
||||
func authorize(_ session: WebAuthenticationSession) async throws -> OAuth2.AccessTokenResponse {
|
||||
let authorizationURL = try authorizationURL
|
||||
let callbackURL = try await session.start(
|
||||
url: authorizationURL,
|
||||
redirectURI: redirectURI
|
||||
)
|
||||
return try await handle(callbackURL: callbackURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension WebAuthenticationSession {
|
||||
func start(url: URL, redirectURI: URL) async throws -> URL {
|
||||
if #available(iOS 17.4, macOS 14.4, tvOS 17.4, watchOS 10.4, *) {
|
||||
return try await authenticate(
|
||||
using: url,
|
||||
callback: try ASWebAuthenticationSession.callback(for: redirectURI),
|
||||
additionalHeaderFields: [:]
|
||||
)
|
||||
} else {
|
||||
let callbackURLScheme = try ASWebAuthenticationSession.callbackURLScheme(for: redirectURI) ?? ""
|
||||
return try await authenticate(using: url, callbackURLScheme: callbackURLScheme)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ASWebAuthenticationSession {
|
||||
static func start(url: URL, redirectURI: URL, configure: (ASWebAuthenticationSession) -> Void) async throws -> URL {
|
||||
try await withUnsafeThrowingContinuation { continuation in
|
||||
do {
|
||||
let session: ASWebAuthenticationSession
|
||||
if #available(iOS 17.4, macOS 14.4, tvOS 17.4, watchOS 10.4, *) {
|
||||
session = ASWebAuthenticationSession(
|
||||
url: url,
|
||||
callback: try callback(for: redirectURI),
|
||||
completionHandler: completionHandler(for: continuation)
|
||||
)
|
||||
} else {
|
||||
session = ASWebAuthenticationSession(
|
||||
url: url,
|
||||
callbackURLScheme: try callbackURLScheme(for: redirectURI),
|
||||
completionHandler: completionHandler(for: continuation)
|
||||
)
|
||||
}
|
||||
configure(session)
|
||||
session.start()
|
||||
} catch {
|
||||
continuation.resume(throwing: error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static func completionHandler(for continuation: UnsafeContinuation<URL, Error>) -> CompletionHandler {
|
||||
return { url, error in
|
||||
if let url {
|
||||
continuation.resume(returning: url)
|
||||
} else {
|
||||
continuation.resume(throwing: error ?? OAuth2.Error.unknown)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ASWebAuthenticationSession {
|
||||
@available(iOS 17.4, macOS 14.4, tvOS 17.4, watchOS 10.4, *)
|
||||
fileprivate static func callback(for redirectURI: URL) throws -> Callback {
|
||||
switch redirectURI.scheme {
|
||||
case "https":
|
||||
guard let host = redirectURI.host else { throw OAuth2.Error.invalidRedirectURI }
|
||||
return .https(host: host, path: redirectURI.path)
|
||||
case "http":
|
||||
throw OAuth2.Error.invalidRedirectURI
|
||||
case .some(let scheme):
|
||||
return .customScheme(scheme)
|
||||
case .none:
|
||||
throw OAuth2.Error.invalidRedirectURI
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate static func callbackURLScheme(for redirectURI: URL) throws -> String? {
|
||||
switch redirectURI.scheme {
|
||||
case "http", .none:
|
||||
return nil
|
||||
case "https":
|
||||
#if os(macOS)
|
||||
if
|
||||
let host = url.host,
|
||||
let associatedDomains = try? Task.current.associatedDomains,
|
||||
!associatedDomains.contains(host) {
|
||||
throw OAuth2.Error.invalidCallbackURL
|
||||
}
|
||||
#endif
|
||||
return "https"
|
||||
case .some(let scheme):
|
||||
return scheme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension URLComponents {
|
||||
fileprivate func decode<T: Decodable>(_ type: T.Type) throws -> T {
|
||||
guard let queryItems else {
|
||||
throw DecodingError.valueNotFound(
|
||||
T.self,
|
||||
.init(codingPath: [], debugDescription: "Missing query items")
|
||||
)
|
||||
}
|
||||
let data = try JSONEncoder().encode(try queryItems.values)
|
||||
return try JSONDecoder().decode(T.self, from: data)
|
||||
}
|
||||
}
|
||||
|
||||
extension Sequence where Element == URLQueryItem {
|
||||
fileprivate var values: [String: String?] {
|
||||
get throws {
|
||||
try Dictionary(map { ($0.name, $0.value) }) { _, _ in
|
||||
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "Duplicate query items"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
import Security
|
||||
|
||||
private struct Task {
|
||||
enum Error: Swift.Error {
|
||||
case unknown
|
||||
}
|
||||
|
||||
static var current: Self {
|
||||
get throws {
|
||||
guard let task = SecTaskCreateFromSelf(nil) else { throw Error.unknown }
|
||||
return Self(task: task)
|
||||
}
|
||||
}
|
||||
|
||||
var task: SecTask
|
||||
|
||||
var associatedDomains: [String] {
|
||||
get throws {
|
||||
var error: Unmanaged<CFError>?
|
||||
let value = SecTaskCopyValueForEntitlement(
|
||||
task,
|
||||
"com.apple.developer.associated-domains" as CFString,
|
||||
&error
|
||||
)
|
||||
if let error = error?.takeRetainedValue() {
|
||||
throw error
|
||||
}
|
||||
return value as! [String] // swiftlint:disable:this force_cast
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
import SwiftUI
|
||||
|
||||
protocol Tunnel {
|
||||
var status: TunnelStatus { get }
|
||||
|
||||
func start()
|
||||
func stop()
|
||||
func enable()
|
||||
}
|
||||
|
||||
enum TunnelStatus: Equatable, Hashable {
|
||||
case unknown
|
||||
case permissionRequired
|
||||
case disabled
|
||||
case connecting
|
||||
case connected(Date)
|
||||
case disconnecting
|
||||
case disconnected
|
||||
case reasserting
|
||||
case invalid
|
||||
case configurationReadWriteFailed
|
||||
}
|
||||
|
||||
struct TunnelKey: EnvironmentKey {
|
||||
static let defaultValue: any Tunnel = NetworkExtensionTunnel()
|
||||
}
|
||||
|
||||
extension EnvironmentValues {
|
||||
var tunnel: any Tunnel {
|
||||
get { self[TunnelKey.self] }
|
||||
set { self[TunnelKey.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
@Observable
|
||||
class PreviewTunnel: Tunnel {
|
||||
var status: TunnelStatus = .permissionRequired
|
||||
|
||||
func start() {
|
||||
status = .connected(.now)
|
||||
}
|
||||
func stop() {
|
||||
status = .disconnected
|
||||
}
|
||||
func enable() {
|
||||
status = .disconnected
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,86 +1,123 @@
|
|||
{
|
||||
"originHash" : "fa512b990383b7e309c5854a5279817052294a8191a6d3c55c49cfb38e88c0c3",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "collectionconcurrencykit",
|
||||
"identity" : "grpc-swift",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git",
|
||||
"location" : "https://github.com/grpc/grpc-swift.git",
|
||||
"state" : {
|
||||
"revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95",
|
||||
"version" : "0.2.0"
|
||||
"revision" : "6a90b7e77e29f9bda6c2b3a4165a40d6c02cfda1",
|
||||
"version" : "1.23.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "cryptoswift",
|
||||
"identity" : "swift-async-algorithms",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
|
||||
"location" : "https://github.com/apple/swift-async-algorithms.git",
|
||||
"state" : {
|
||||
"revision" : "7892a123f7e8d0fe62f9f03728b17bbd4f94df5c",
|
||||
"version" : "1.8.1"
|
||||
"revision" : "6ae9a051f76b81cc668305ceed5b0e0a7fd93d20",
|
||||
"version" : "1.0.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sourcekitten",
|
||||
"identity" : "swift-atomics",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/jpsim/SourceKitten.git",
|
||||
"location" : "https://github.com/apple/swift-atomics.git",
|
||||
"state" : {
|
||||
"revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56",
|
||||
"version" : "0.34.1"
|
||||
"revision" : "cd142fd2f64be2100422d658e7411e39489da985",
|
||||
"version" : "1.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-argument-parser",
|
||||
"identity" : "swift-collections",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-argument-parser.git",
|
||||
"location" : "https://github.com/apple/swift-collections.git",
|
||||
"state" : {
|
||||
"revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531",
|
||||
"version" : "1.2.3"
|
||||
"revision" : "9bf03ff58ce34478e66aaee630e491823326fd06",
|
||||
"version" : "1.1.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-syntax",
|
||||
"identity" : "swift-http-types",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-syntax.git",
|
||||
"location" : "https://github.com/apple/swift-http-types",
|
||||
"state" : {
|
||||
"revision" : "64889f0c732f210a935a0ad7cda38f77f876262d",
|
||||
"version" : "509.1.1"
|
||||
"revision" : "ae67c8178eb46944fd85e4dc6dd970e1f3ed6ccd",
|
||||
"version" : "1.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftlint",
|
||||
"identity" : "swift-log",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/realm/SwiftLint.git",
|
||||
"location" : "https://github.com/apple/swift-log.git",
|
||||
"state" : {
|
||||
"branch" : "main",
|
||||
"revision" : "7595ad3fafc1a31086dc40ba01fd898bf6b42d5f"
|
||||
"revision" : "9cb486020ebf03bfa5b5df985387a14a98744537",
|
||||
"version" : "1.6.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swiftytexttable",
|
||||
"identity" : "swift-nio",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/scottrhoyt/SwiftyTextTable.git",
|
||||
"location" : "https://github.com/apple/swift-nio.git",
|
||||
"state" : {
|
||||
"revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3",
|
||||
"version" : "0.9.0"
|
||||
"revision" : "9746cf80e29edfef2a39924a66731249223f42a3",
|
||||
"version" : "2.72.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swxmlhash",
|
||||
"identity" : "swift-nio-extras",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/drmohundro/SWXMLHash.git",
|
||||
"location" : "https://github.com/apple/swift-nio-extras.git",
|
||||
"state" : {
|
||||
"revision" : "a853604c9e9a83ad9954c7e3d2a565273982471f",
|
||||
"version" : "7.0.2"
|
||||
"revision" : "d1ead62745cc3269e482f1c51f27608057174379",
|
||||
"version" : "1.24.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "yams",
|
||||
"identity" : "swift-nio-http2",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/jpsim/Yams.git",
|
||||
"location" : "https://github.com/apple/swift-nio-http2.git",
|
||||
"state" : {
|
||||
"revision" : "0d9ee7ea8c4ebd4a489ad7a73d5c6cad55d6fed3",
|
||||
"version" : "5.0.6"
|
||||
"revision" : "b5f7062b60e4add1e8c343ba4eb8da2e324b3a94",
|
||||
"version" : "1.34.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-nio-ssl",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio-ssl.git",
|
||||
"state" : {
|
||||
"revision" : "7b84abbdcef69cc3be6573ac12440220789dcd69",
|
||||
"version" : "2.27.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-nio-transport-services",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio-transport-services.git",
|
||||
"state" : {
|
||||
"revision" : "38ac8221dd20674682148d6451367f89c2652980",
|
||||
"version" : "1.21.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-protobuf",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-protobuf.git",
|
||||
"state" : {
|
||||
"revision" : "edb6ed4919f7756157fe02f2552b7e3850a538e5",
|
||||
"version" : "1.28.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-system",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-system.git",
|
||||
"state" : {
|
||||
"revision" : "d2ba781702a1d8285419c15ee62fd734a9437ff5",
|
||||
"version" : "1.3.2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
"version" : 3
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1520"
|
||||
LastUpgradeVersion = "1600"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1520"
|
||||
LastUpgradeVersion = "1600"
|
||||
wasCreatedForAppExtension = "YES"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
LD_EXPORT_SYMBOLS = NO
|
||||
SKIP_INSTALL = NO
|
||||
MERGED_BINARY_TYPE = manual
|
||||
|
||||
LD_RUNPATH_SEARCH_PATHS[sdk=iphone*] = $(inherited) @executable_path/Frameworks
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks
|
||||
LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks
|
||||
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor
|
||||
|
||||
ENABLE_PREVIEWS = YES
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
#include "Identity.xcconfig"
|
||||
#include "Debug.xcconfig"
|
||||
#include "Version.xcconfig"
|
||||
|
||||
SDKROOT = auto
|
||||
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES
|
||||
|
||||
SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx
|
||||
SWIFT_VERSION = 6.0
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0
|
||||
MACOSX_DEPLOYMENT_TARGET = 14.0
|
||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO
|
||||
SUPPORTS_MACCATALYST = NO
|
||||
|
||||
ALWAYS_SEARCH_USER_PATHS = NO
|
||||
PRODUCT_NAME = $(TARGET_NAME:c99extidentifier)
|
||||
PRODUCT_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).$(PRODUCT_NAME)
|
||||
MARKETING_VERSION = 0.1
|
||||
|
|
@ -19,43 +20,25 @@ SKIP_INSTALL = YES
|
|||
|
||||
CODE_SIGN_IDENTITY = Apple Development
|
||||
|
||||
INFOPLIST_FILE = Configuration/Info.plist
|
||||
GENERATE_INFOPLIST_FILE = YES
|
||||
INFOPLIST_FILE = Configuration/Info.plist
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2023-2024 Hack Club
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Burrow
|
||||
|
||||
ENABLE_BITCODE = NO
|
||||
|
||||
ENABLE_APP_SANDBOX[sdk=macosx*] = YES
|
||||
ENABLE_HARDENED_RUNTIME[sdk=macosx*] = YES
|
||||
COMBINE_HIDPI_IMAGES = YES
|
||||
COPY_PHASE_STRIP = NO
|
||||
|
||||
ENABLE_BITCODE = NO
|
||||
ALWAYS_SEARCH_USER_PATHS = NO
|
||||
COMBINE_HIDPI_IMAGES = YES
|
||||
EAGER_LINKING = YES
|
||||
FUSE_BUILD_SCRIPT_PHASES = YES
|
||||
SWIFT_EMIT_LOC_STRINGS = YES
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES
|
||||
ENABLE_DEBUG_DYLIB = NO
|
||||
|
||||
APP_GROUP_IDENTIFIER = group.$(APP_BUNDLE_IDENTIFIER)
|
||||
APP_GROUP_IDENTIFIER[sdk=macosx*] = $(DEVELOPMENT_TEAM).$(APP_BUNDLE_IDENTIFIER)
|
||||
NETWORK_EXTENSION_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).network
|
||||
|
||||
// Swift
|
||||
SWIFT_VERSION = 5.0
|
||||
SWIFT_EMIT_LOC_STRINGS = YES
|
||||
|
||||
// Release
|
||||
DEBUG_INFORMATION_FORMAT = dwarf-with-dsym
|
||||
SWIFT_COMPILATION_MODE = wholemodule
|
||||
SWIFT_OPTIMIZATION_LEVEL = -Osize
|
||||
LLVM_LTO = YES
|
||||
DEAD_CODE_STRIPPING = YES
|
||||
VALIDATE_PRODUCT = YES
|
||||
|
||||
// Debug
|
||||
ONLY_ACTIVE_ARCH[config=Debug] = YES
|
||||
DEBUG_INFORMATION_FORMAT[config=Debug] = dwarf
|
||||
ENABLE_TESTABILITY[config=Debug] = YES
|
||||
SWIFT_OPTIMIZATION_LEVEL[config=Debug] = -Onone
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Debug] = DEBUG
|
||||
SWIFT_COMPILATION_MODE[config=Debug] = singlefile
|
||||
LLVM_LTO[config=Debug] = NO
|
||||
DEAD_CODE_STRIPPING[config=Debug] = NO
|
||||
VALIDATE_PRODUCT[config=Debug] = NO
|
||||
// https://github.com/grpc/grpc-swift/issues/683#issuecomment-1130118953
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -Xcc -fmodule-map-file=$(GENERATED_MODULEMAP_DIR)/CNIOAtomics.modulemap -Xcc -fmodule-map-file=$(GENERATED_MODULEMAP_DIR)/CNIODarwin.modulemap -Xcc -fmodule-map-file=$(GENERATED_MODULEMAP_DIR)/CGRPCZlib.modulemap
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
PRODUCT_NAME = BurrowShared
|
||||
MERGEABLE_LIBRARY = YES
|
||||
#include "Framework.xcconfig"
|
||||
|
||||
SWIFT_INCLUDE_PATHS = $(PROJECT_DIR)/Shared/Constants
|
||||
SWIFT_INCLUDE_PATHS = $(PROJECT_DIR)/Configuration/Constants
|
||||
GCC_PREPROCESSOR_DEFINITIONS = APP_BUNDLE_IDENTIFIER=$(APP_BUNDLE_IDENTIFIER) APP_GROUP_IDENTIFIER=$(APP_GROUP_IDENTIFIER) NETWORK_EXTENSION_BUNDLE_IDENTIFIER=$(NETWORK_EXTENSION_BUNDLE_IDENTIFIER)
|
||||
38
Apple/Configuration/Constants/Constants.swift
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
@_implementationOnly import CConstants
|
||||
import OSLog
|
||||
|
||||
public enum Constants {
|
||||
enum Error: Swift.Error {
|
||||
case invalidAppGroupIdentifier
|
||||
}
|
||||
|
||||
public static let bundleIdentifier = AppBundleIdentifier
|
||||
public static let appGroupIdentifier = AppGroupIdentifier
|
||||
public static let networkExtensionBundleIdentifier = NetworkExtensionBundleIdentifier
|
||||
|
||||
public static var socketURL: URL {
|
||||
get throws {
|
||||
try groupContainerURL.appending(component: "burrow.sock", directoryHint: .notDirectory)
|
||||
}
|
||||
}
|
||||
public static var databaseURL: URL {
|
||||
get throws {
|
||||
try groupContainerURL.appending(component: "burrow.db", directoryHint: .notDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
private static var groupContainerURL: URL {
|
||||
get throws { try _groupContainerURL.get() }
|
||||
}
|
||||
private static let _groupContainerURL: Result<URL, Error> = {
|
||||
switch FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) {
|
||||
case .some(let url): .success(url)
|
||||
case .none: .failure(.invalidAppGroupIdentifier)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
extension Logger {
|
||||
@_dynamicReplacement(for: subsystem)
|
||||
public static var subsystem: String { Constants.bundleIdentifier }
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
module Constants {
|
||||
module CConstants {
|
||||
header "Constants.h"
|
||||
export *
|
||||
}
|
||||
26
Apple/Configuration/Debug.xcconfig
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Release
|
||||
DEBUG_INFORMATION_FORMAT = dwarf-with-dsym
|
||||
SWIFT_COMPILATION_MODE = wholemodule
|
||||
SWIFT_OPTIMIZATION_LEVEL = -Osize
|
||||
LLVM_LTO = YES
|
||||
DEAD_CODE_STRIPPING = YES
|
||||
STRIP_INSTALLED_PRODUCT = YES
|
||||
STRIP_SWIFT_SYMBOLS = YES
|
||||
COPY_PHASE_STRIP = NO
|
||||
VALIDATE_PRODUCT = YES
|
||||
ENABLE_MODULE_VERIFIER = YES
|
||||
|
||||
// Debug
|
||||
ONLY_ACTIVE_ARCH[config=Debug] = YES
|
||||
DEBUG_INFORMATION_FORMAT[config=Debug] = dwarf
|
||||
ENABLE_TESTABILITY[config=Debug] = YES
|
||||
GCC_PREPROCESSOR_DEFINITIONS[config=Debug] = DEBUG=1 $(inherited)
|
||||
SWIFT_OPTIMIZATION_LEVEL[config=Debug] = -Onone
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=Debug] = DEBUG
|
||||
SWIFT_COMPILATION_MODE[config=Debug] = singlefile
|
||||
LLVM_LTO[config=Debug] = NO
|
||||
DEAD_CODE_STRIPPING[config=Debug] = NO
|
||||
VALIDATE_PRODUCT[config=Debug] = NO
|
||||
STRIP_INSTALLED_PRODUCT[config=Debug] = NO
|
||||
STRIP_SWIFT_SYMBOLS[config=Debug] = NO
|
||||
ENABLE_MODULE_VERIFIER[config=Debug] = NO
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
MERGED_BINARY_TYPE = manual
|
||||
LD_EXPORT_SYMBOLS = NO
|
||||
|
||||
OTHER_SWIFT_FLAGS = $(inherited) -Xfrontend -disable-autolink-framework -Xfrontend UIKit -Xfrontend -disable-autolink-framework -Xfrontend AppKit -Xfrontend -disable-autolink-framework -Xfrontend SwiftUI
|
||||
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @executable_path/../../Frameworks
|
||||
LD_RUNPATH_SEARCH_PATHS[sdk=macos*] = $(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks
|
||||
LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks
|
||||
|
|
|
|||
14
Apple/Configuration/Framework.xcconfig
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
PRODUCT_NAME = Burrow$(TARGET_NAME:c99extidentifier)
|
||||
PRODUCT_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).$(TARGET_NAME:c99extidentifier)
|
||||
APPLICATION_EXTENSION_API_ONLY = YES
|
||||
SWIFT_INSTALL_OBJC_HEADER = NO
|
||||
SWIFT_SKIP_AUTOLINKING_FRAMEWORKS = YES
|
||||
SWIFT_SKIP_AUTOLINKING_LIBRARIES = YES
|
||||
|
||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks @loader_path/Frameworks
|
||||
LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks @loader_path/Frameworks
|
||||
|
||||
DYLIB_INSTALL_NAME_BASE = @rpath
|
||||
DYLIB_COMPATIBILITY_VERSION = 1
|
||||
DYLIB_CURRENT_VERSION = 1
|
||||
VERSIONING_SYSTEM =
|
||||
32
Apple/Core/Client.swift
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import GRPC
|
||||
import NIOTransportServices
|
||||
|
||||
public typealias TunnelClient = Burrow_TunnelAsyncClient
|
||||
public typealias NetworksClient = Burrow_NetworksAsyncClient
|
||||
|
||||
public protocol Client {
|
||||
init(channel: GRPCChannel)
|
||||
}
|
||||
|
||||
extension Client {
|
||||
public static func unix(socketURL: URL) -> Self {
|
||||
let group = NIOTSEventLoopGroup()
|
||||
let configuration = ClientConnection.Configuration.default(
|
||||
target: .unixDomainSocket(socketURL.path),
|
||||
eventLoopGroup: group
|
||||
)
|
||||
return Self(channel: ClientConnection(configuration: configuration))
|
||||
}
|
||||
}
|
||||
|
||||
extension TunnelClient: Client {
|
||||
public init(channel: any GRPCChannel) {
|
||||
self.init(channel: channel, defaultCallOptions: .init(), interceptors: .none)
|
||||
}
|
||||
}
|
||||
|
||||
extension NetworksClient: Client {
|
||||
public init(channel: any GRPCChannel) {
|
||||
self.init(channel: channel, defaultCallOptions: .init(), interceptors: .none)
|
||||
}
|
||||
}
|
||||
1
Apple/Core/Client/burrow.proto
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../proto/burrow.proto
|
||||
11
Apple/Core/Client/grpc-swift-config.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"invocations": [
|
||||
{
|
||||
"protoFiles": [
|
||||
"burrow.proto",
|
||||
],
|
||||
"server": false,
|
||||
"visibility": "public"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
Apple/Core/Client/swift-protobuf-config.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"invocations": [
|
||||
{
|
||||
"protoFiles": [
|
||||
"burrow.proto",
|
||||
],
|
||||
"visibility": "public"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import os
|
|||
extension Logger {
|
||||
private static let loggers: OSAllocatedUnfairLock<[String: Logger]> = OSAllocatedUnfairLock(initialState: [:])
|
||||
|
||||
public static let subsystem = Constants.bundleIdentifier
|
||||
public dynamic static var subsystem: String { "com.hackclub.burrow" }
|
||||
|
||||
public static func logger(for type: Any.Type) -> Logger {
|
||||
let category = String(describing: type)
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
import BurrowShared
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
final class Client {
|
||||
let connection: NWConnection
|
||||
|
||||
private let logger = Logger.logger(for: Client.self)
|
||||
private var generator = SystemRandomNumberGenerator()
|
||||
|
||||
convenience init() throws {
|
||||
self.init(url: try Constants.socketURL)
|
||||
}
|
||||
|
||||
init(url: URL) {
|
||||
let endpoint: NWEndpoint
|
||||
if url.isFileURL {
|
||||
endpoint = .unix(path: url.path(percentEncoded: false))
|
||||
} else {
|
||||
endpoint = .url(url)
|
||||
}
|
||||
|
||||
let parameters = NWParameters.tcp
|
||||
parameters.defaultProtocolStack
|
||||
.applicationProtocols
|
||||
.insert(NWProtocolFramer.Options(definition: NewlineProtocolFramer.definition), at: 0)
|
||||
connection = NWConnection(to: endpoint, using: parameters)
|
||||
connection.start(queue: .global())
|
||||
}
|
||||
|
||||
func request<U: Decodable>(_ request: any Request, type: U.Type = U.self) async throws -> U {
|
||||
do {
|
||||
var copy = request
|
||||
copy.id = generator.next(upperBound: UInt.max)
|
||||
let content = try JSONEncoder().encode(copy)
|
||||
logger.debug("> \(String(decoding: content, as: UTF8.self))")
|
||||
|
||||
try await self.connection.send(content: content)
|
||||
let (response, _, _) = try await connection.receiveMessage()
|
||||
|
||||
logger.debug("< \(String(decoding: response, as: UTF8.self))")
|
||||
return try JSONDecoder().decode(U.self, from: response)
|
||||
} catch {
|
||||
logger.error("\(error, privacy: .public)")
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
connection.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
extension Constants {
|
||||
static var socketURL: URL {
|
||||
get throws {
|
||||
try groupContainerURL.appending(component: "burrow.sock", directoryHint: .notDirectory)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
import Foundation
|
||||
|
||||
// swiftlint:disable identifier_name
|
||||
enum BurrowError: Error {
|
||||
case addrDoesntExist
|
||||
case resultIsError
|
||||
case cantParseResult
|
||||
case resultIsNone
|
||||
}
|
||||
|
||||
protocol Request: Codable where Command: Codable {
|
||||
associatedtype Command
|
||||
|
||||
var id: UInt { get set }
|
||||
var command: Command { get set }
|
||||
}
|
||||
|
||||
struct BurrowSingleCommand: Request {
|
||||
var id: UInt
|
||||
var command: String
|
||||
}
|
||||
|
||||
struct BurrowRequest<T>: Request where T: Codable {
|
||||
var id: UInt
|
||||
var command: T
|
||||
}
|
||||
|
||||
struct BurrowStartRequest: Codable {
|
||||
struct TunOptions: Codable {
|
||||
let name: String?
|
||||
let no_pi: Bool
|
||||
let tun_excl: Bool
|
||||
let tun_retrieve: Bool
|
||||
let address: [String]
|
||||
}
|
||||
struct StartOptions: Codable {
|
||||
let tun: TunOptions
|
||||
}
|
||||
let Start: StartOptions
|
||||
}
|
||||
|
||||
struct Response<T>: Decodable where T: Decodable {
|
||||
var id: UInt
|
||||
var result: T
|
||||
}
|
||||
|
||||
struct BurrowResult<T>: Codable where T: Codable {
|
||||
var Ok: T?
|
||||
var Err: String?
|
||||
}
|
||||
|
||||
struct ServerConfigData: Codable {
|
||||
struct InternalConfig: Codable {
|
||||
let address: [String]
|
||||
let name: String?
|
||||
let mtu: Int32?
|
||||
}
|
||||
let ServerConfig: InternalConfig
|
||||
}
|
||||
|
||||
// swiftlint:enable identifier_name
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import Foundation
|
||||
import Network
|
||||
|
||||
extension NWConnection {
|
||||
// swiftlint:disable:next large_tuple
|
||||
func receiveMessage() async throws -> (Data, NWConnection.ContentContext?, Bool) {
|
||||
try await withUnsafeThrowingContinuation { continuation in
|
||||
receiveMessage { completeContent, contentContext, isComplete, error in
|
||||
if let error {
|
||||
continuation.resume(throwing: error)
|
||||
} else {
|
||||
guard let completeContent = completeContent else {
|
||||
fatalError("Both error and completeContent were nil")
|
||||
}
|
||||
continuation.resume(returning: (completeContent, contentContext, isComplete))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func send(content: Data) async throws {
|
||||
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
||||
send(content: content, completion: .contentProcessed { error in
|
||||
if let error {
|
||||
continuation.resume(throwing: error)
|
||||
} else {
|
||||
continuation.resume(returning: ())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import Foundation
|
||||
import Network
|
||||
|
||||
final class NewlineProtocolFramer: NWProtocolFramerImplementation {
|
||||
private static let delimeter: UInt8 = 10 // `\n`
|
||||
|
||||
static let definition = NWProtocolFramer.Definition(implementation: NewlineProtocolFramer.self)
|
||||
static let label = "Lines"
|
||||
|
||||
init(framer: NWProtocolFramer.Instance) { }
|
||||
|
||||
func start(framer: NWProtocolFramer.Instance) -> NWProtocolFramer.StartResult { .ready }
|
||||
func stop(framer: NWProtocolFramer.Instance) -> Bool { true }
|
||||
|
||||
func wakeup(framer: NWProtocolFramer.Instance) { }
|
||||
func cleanup(framer: NWProtocolFramer.Instance) { }
|
||||
|
||||
func handleInput(framer: NWProtocolFramer.Instance) -> Int {
|
||||
while true {
|
||||
var result: [Data] = []
|
||||
let parsed = framer.parseInput(minimumIncompleteLength: 1, maximumLength: 16_000) { buffer, _ in
|
||||
guard let buffer else { return 0 }
|
||||
var lines = buffer
|
||||
.split(separator: Self.delimeter, omittingEmptySubsequences: false)
|
||||
.map { Data($0) }
|
||||
guard lines.count > 1 else { return 0 }
|
||||
_ = lines.popLast()
|
||||
|
||||
result = lines
|
||||
return lines.reduce(lines.count) { $0 + $1.count }
|
||||
}
|
||||
|
||||
guard parsed && !result.isEmpty else { break }
|
||||
|
||||
for line in result {
|
||||
framer.deliverInput(data: line, message: .init(instance: framer), isComplete: true)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func handleOutput(
|
||||
framer: NWProtocolFramer.Instance,
|
||||
message: NWProtocolFramer.Message,
|
||||
messageLength: Int,
|
||||
isComplete: Bool
|
||||
) {
|
||||
do {
|
||||
try framer.writeOutputNoCopy(length: messageLength)
|
||||
framer.writeOutput(data: [Self.delimeter])
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +1,74 @@
|
|||
import BurrowShared
|
||||
import AsyncAlgorithms
|
||||
import BurrowConfiguration
|
||||
import BurrowCore
|
||||
import libburrow
|
||||
import NetworkExtension
|
||||
import os
|
||||
|
||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
enum Error: Swift.Error {
|
||||
case missingTunnelConfiguration
|
||||
}
|
||||
|
||||
private let logger = Logger.logger(for: PacketTunnelProvider.self)
|
||||
|
||||
private var client: TunnelClient {
|
||||
get throws { try _client.get() }
|
||||
}
|
||||
private let _client: Result<TunnelClient, Swift.Error> = Result {
|
||||
try TunnelClient.unix(socketURL: Constants.socketURL)
|
||||
}
|
||||
|
||||
override init() {
|
||||
do {
|
||||
libburrow.spawnInProcess(socketPath: try Constants.socketURL.path)
|
||||
libburrow.spawnInProcess(
|
||||
socketPath: try Constants.socketURL.path(percentEncoded: false),
|
||||
databasePath: try Constants.databaseURL.path(percentEncoded: false)
|
||||
)
|
||||
} catch {
|
||||
logger.error("Failed to spawn: \(error)")
|
||||
logger.error("Failed to spawn networking thread: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
override func startTunnel(options: [String: NSObject]? = nil) async throws {
|
||||
do {
|
||||
let client = try Client()
|
||||
|
||||
let command = BurrowRequest(id: 0, command: "ServerConfig")
|
||||
let data = try await client.request(command, type: Response<BurrowResult<ServerConfigData>>.self)
|
||||
|
||||
let encoded = try JSONEncoder().encode(data.result)
|
||||
self.logger.log("Received final data: \(String(decoding: encoded, as: UTF8.self))")
|
||||
guard let serverconfig = data.result.Ok else {
|
||||
throw BurrowError.resultIsError
|
||||
let configuration = try await Array(client.tunnelConfiguration(.init()).prefix(1)).first
|
||||
guard let settings = configuration?.settings else {
|
||||
throw Error.missingTunnelConfiguration
|
||||
}
|
||||
guard let tunNs = generateTunSettings(from: serverconfig) else {
|
||||
throw BurrowError.addrDoesntExist
|
||||
}
|
||||
try await self.setTunnelNetworkSettings(tunNs)
|
||||
self.logger.info("Set remote tunnel address to \(tunNs.tunnelRemoteAddress)")
|
||||
|
||||
let startRequest = BurrowRequest(
|
||||
id: .random(in: (.min)..<(.max)),
|
||||
command: BurrowStartRequest(
|
||||
Start: BurrowStartRequest.StartOptions(
|
||||
tun: BurrowStartRequest.TunOptions(
|
||||
name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: []
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
let response = try await client.request(startRequest, type: Response<BurrowResult<String>>.self)
|
||||
self.logger.log("Received start server response: \(String(describing: response.result))")
|
||||
try await setTunnelNetworkSettings(settings)
|
||||
_ = try await client.tunnelStart(.init())
|
||||
logger.log("Started tunnel with network settings: \(settings)")
|
||||
} catch {
|
||||
self.logger.error("Failed to start tunnel: \(error)")
|
||||
logger.error("Failed to start tunnel: \(error)")
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
override func stopTunnel(with reason: NEProviderStopReason) async {
|
||||
do {
|
||||
let client = try Client()
|
||||
let command = BurrowRequest(id: 0, command: "Stop")
|
||||
let data = try await client.request(command, type: Response<BurrowResult<String>>.self)
|
||||
self.logger.log("Stopped client.")
|
||||
_ = try await client.tunnelStop(.init())
|
||||
logger.log("Stopped client")
|
||||
} catch {
|
||||
self.logger.error("Failed to stop tunnel: \(error)")
|
||||
logger.error("Failed to stop tunnel: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func generateTunSettings(from: ServerConfigData) -> NETunnelNetworkSettings? {
|
||||
let cfig = from.ServerConfig
|
||||
let nst = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
|
||||
var v4Addresses = [String]()
|
||||
var v6Addresses = [String]()
|
||||
for addr in cfig.address {
|
||||
if IPv4Address(addr) != nil {
|
||||
v6Addresses.append(addr)
|
||||
}
|
||||
if IPv6Address(addr) != nil {
|
||||
v4Addresses.append(addr)
|
||||
}
|
||||
}
|
||||
nst.ipv4Settings = NEIPv4Settings(addresses: v4Addresses, subnetMasks: v4Addresses.map { _ in
|
||||
"255.255.255.0"
|
||||
})
|
||||
nst.ipv6Settings = NEIPv6Settings(addresses: v6Addresses, networkPrefixLengths: v6Addresses.map { _ in 64 })
|
||||
logger.log("Initialized ipv4 settings: \(nst.ipv4Settings)")
|
||||
return nst
|
||||
}
|
||||
}
|
||||
|
||||
extension Burrow_TunnelConfigurationResponse {
|
||||
fileprivate var settings: NEPacketTunnelNetworkSettings {
|
||||
let ipv6Addresses = addresses.filter { IPv6Address($0) != nil }
|
||||
|
||||
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
|
||||
settings.mtu = NSNumber(value: mtu)
|
||||
settings.ipv4Settings = NEIPv4Settings(
|
||||
addresses: addresses.filter { IPv4Address($0) != nil },
|
||||
subnetMasks: ["255.255.255.0"]
|
||||
)
|
||||
settings.ipv6Settings = NEIPv6Settings(
|
||||
addresses: ipv6Addresses,
|
||||
networkPrefixLengths: ipv6Addresses.map { _ in 64 }
|
||||
)
|
||||
return settings
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,9 +68,12 @@ else
|
|||
CARGO_PATH="$(dirname $(readlink -f $(which cargo))):/usr/bin"
|
||||
fi
|
||||
|
||||
PROTOC=$(readlink -f $(which protoc))
|
||||
CARGO_PATH="$(dirname $PROTOC):$CARGO_PATH"
|
||||
|
||||
# Run cargo without the various environment variables set by Xcode.
|
||||
# Those variables can confuse cargo and the build scripts it runs.
|
||||
env -i PATH="$CARGO_PATH" CARGO_TARGET_DIR="${CONFIGURATION_TEMP_DIR}/target" cargo build "${CARGO_ARGS[@]}"
|
||||
env -i PATH="$CARGO_PATH" PROTOC="$PROTOC" CARGO_TARGET_DIR="${CONFIGURATION_TEMP_DIR}/target" IPHONEOS_DEPLOYMENT_TARGET="$IPHONEOS_DEPLOYMENT_TARGET" MACOSX_DEPLOYMENT_TARGET="$MACOSX_DEPLOYMENT_TARGET" cargo build "${CARGO_ARGS[@]}"
|
||||
|
||||
mkdir -p "${BUILT_PRODUCTS_DIR}"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,2 +1,2 @@
|
|||
__attribute__((__swift_name__("spawnInProcess(socketPath:)")))
|
||||
extern void spawn_in_process(const char * __nullable path);
|
||||
__attribute__((__swift_name__("spawnInProcess(socketPath:databasePath:)")))
|
||||
extern void spawn_in_process(const char * __nullable socket_path, const char * __nullable db_path);
|
||||
|
|
|
|||
BIN
Apple/Profiles/Burrow_Developer_ID.provisionprofile
Normal file
BIN
Apple/Profiles/Burrow_Network_Developer_ID.provisionprofile
Normal file
|
|
@ -1,23 +0,0 @@
|
|||
@_implementationOnly import Constants
|
||||
|
||||
public enum Constants {
|
||||
enum Error: Swift.Error {
|
||||
case invalidAppGroupIdentifier
|
||||
}
|
||||
|
||||
public static let bundleIdentifier = AppBundleIdentifier
|
||||
public static let appGroupIdentifier = AppGroupIdentifier
|
||||
public static let networkExtensionBundleIdentifier = NetworkExtensionBundleIdentifier
|
||||
|
||||
public static var groupContainerURL: URL {
|
||||
get throws { try _groupContainerURL.get() }
|
||||
}
|
||||
|
||||
private static let _groupContainerURL: Result<URL, Error> = {
|
||||
guard let groupContainerURL = FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) else {
|
||||
return .failure(.invalidAppGroupIdentifier)
|
||||
}
|
||||
return .success(groupContainerURL)
|
||||
}()
|
||||
}
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 684 B After Width: | Height: | Size: 684 B |
|
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 927 B After Width: | Height: | Size: 927 B |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 4 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
|
@ -2,11 +2,11 @@ import AuthenticationServices
|
|||
import SwiftUI
|
||||
|
||||
#if !os(macOS)
|
||||
struct BurrowView: View {
|
||||
public struct BurrowView: View {
|
||||
@Environment(\.webAuthenticationSession)
|
||||
private var webAuthenticationSession
|
||||
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
NavigationStack {
|
||||
VStack {
|
||||
HStack {
|
||||
|
|
@ -31,30 +31,36 @@ struct BurrowView: View {
|
|||
.padding(.bottom)
|
||||
}
|
||||
.padding()
|
||||
.handleOAuth2Callback()
|
||||
}
|
||||
}
|
||||
|
||||
public init() {
|
||||
}
|
||||
|
||||
private func addHackClubNetwork() {
|
||||
guard
|
||||
let issuerURL = URL(string: "https://slack.com"),
|
||||
let redirectURI = URL(string: "https://burrow.rs/callback/oauth2") else { return }
|
||||
Task {
|
||||
do {
|
||||
let session = try await OpenID.Session(
|
||||
issuerURL: issuerURL,
|
||||
redirectURI: redirectURI,
|
||||
scopes: ["openid", "profile"],
|
||||
clientID: "2210535565.6884042183125"
|
||||
)
|
||||
let response = try await session.authorize(webAuthenticationSession)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
try await authenticateWithSlack()
|
||||
}
|
||||
}
|
||||
|
||||
private func addWireGuardNetwork() {
|
||||
}
|
||||
|
||||
private func authenticateWithSlack() async throws {
|
||||
guard
|
||||
let authorizationEndpoint = URL(string: "https://slack.com/openid/connect/authorize"),
|
||||
let tokenEndpoint = URL(string: "https://slack.com/api/openid.connect.token"),
|
||||
let redirectURI = URL(string: "https://burrow.rs/callback/oauth2") else { return }
|
||||
let session = OAuth2.Session(
|
||||
authorizationEndpoint: authorizationEndpoint,
|
||||
tokenEndpoint: tokenEndpoint,
|
||||
redirectURI: redirectURI,
|
||||
scopes: ["openid", "profile"],
|
||||
clientID: "2210535565.6884042183125",
|
||||
clientSecret: "2793c8a5255cae38830934c664eeb62d"
|
||||
)
|
||||
let response = try await session.authorize(webAuthenticationSession)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -7,11 +7,11 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
struct MenuItemToggleView: View {
|
||||
public struct MenuItemToggleView: View {
|
||||
@Environment(\.tunnel)
|
||||
var tunnel: Tunnel
|
||||
|
||||
var body: some View {
|
||||
public var body: some View {
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
Text("Burrow")
|
||||
|
|
@ -30,10 +30,13 @@ struct MenuItemToggleView: View {
|
|||
.padding(10)
|
||||
.frame(minWidth: 300, minHeight: 32, maxHeight: 32)
|
||||
}
|
||||
|
||||
public init() {
|
||||
}
|
||||
}
|
||||
|
||||
extension Tunnel {
|
||||
fileprivate var toggleDisabled: Bool {
|
||||
@MainActor fileprivate var toggleDisabled: Bool {
|
||||
switch status {
|
||||
case .disconnected, .permissionRequired, .connected, .disconnecting:
|
||||
false
|
||||
|
|
@ -42,7 +45,7 @@ extension Tunnel {
|
|||
}
|
||||
}
|
||||
|
||||
var toggleIsOn: Binding<Bool> {
|
||||
@MainActor var toggleIsOn: Binding<Bool> {
|
||||
Binding {
|
||||
switch status {
|
||||
case .connecting, .reasserting, .connected:
|
||||
|
|
@ -2,10 +2,10 @@ import SwiftUI
|
|||
|
||||
struct NetworkCarouselView: View {
|
||||
var networks: [any Network] = [
|
||||
HackClub(id: "1"),
|
||||
HackClub(id: "2"),
|
||||
WireGuard(id: "4"),
|
||||
HackClub(id: "5"),
|
||||
HackClub(id: 1),
|
||||
HackClub(id: 2),
|
||||
WireGuard(id: 4),
|
||||
HackClub(id: 5)
|
||||
]
|
||||
|
||||
var body: some View {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import NetworkExtension
|
||||
|
||||
extension NEVPNManager {
|
||||
extension NEVPNManager: @unchecked @retroactive Sendable {
|
||||
func remove() async throws {
|
||||
_ = try await withUnsafeThrowingContinuation { continuation in
|
||||
removeFromPreferences(completionHandler: completion(continuation))
|
||||
|
|
@ -14,7 +14,7 @@ extension NEVPNManager {
|
|||
}
|
||||
}
|
||||
|
||||
extension NETunnelProviderManager {
|
||||
extension NETunnelProviderManager: @unchecked @retroactive Sendable {
|
||||
class var managers: [NETunnelProviderManager] {
|
||||
get async throws {
|
||||
try await withUnsafeThrowingContinuation { continuation in
|
||||
|
|
@ -34,7 +34,7 @@ private func completion(_ continuation: UnsafeContinuation<Void, Error>) -> (Err
|
|||
}
|
||||
}
|
||||
|
||||
private func completion<T>(_ continuation: UnsafeContinuation<T, Error>) -> (T?, Error?) -> Void {
|
||||
private func completion<T: Sendable>(_ continuation: UnsafeContinuation<T, Error>) -> (T?, Error?) -> Void {
|
||||
return { value, error in
|
||||
if let error {
|
||||
continuation.resume(throwing: error)
|
||||
|
|
@ -1,22 +1,23 @@
|
|||
import BurrowShared
|
||||
import BurrowCore
|
||||
import NetworkExtension
|
||||
|
||||
@Observable
|
||||
class NetworkExtensionTunnel: Tunnel {
|
||||
@MainActor private(set) var status: TunnelStatus = .unknown
|
||||
private var error: NEVPNError?
|
||||
public final class NetworkExtensionTunnel: Tunnel {
|
||||
@MainActor public private(set) var status: TunnelStatus = .unknown
|
||||
@MainActor private var error: NEVPNError?
|
||||
|
||||
private let logger = Logger.logger(for: Tunnel.self)
|
||||
private let bundleIdentifier: String
|
||||
private var tasks: [Task<Void, Error>] = []
|
||||
private let configurationChanged: Task<Void, Error>
|
||||
private let statusChanged: Task<Void, Error>
|
||||
|
||||
// Each manager corresponds to one entry in the Settings app.
|
||||
// Our goal is to maintain a single manager, so we create one if none exist and delete any extra.
|
||||
private var managers: [NEVPNManager]? {
|
||||
@MainActor private var managers: [NEVPNManager]? {
|
||||
didSet { Task { await updateStatus() } }
|
||||
}
|
||||
|
||||
private var currentStatus: TunnelStatus {
|
||||
@MainActor private var currentStatus: TunnelStatus {
|
||||
guard let managers = managers else {
|
||||
guard let error = error else {
|
||||
return .unknown
|
||||
|
|
@ -41,35 +42,40 @@ class NetworkExtensionTunnel: Tunnel {
|
|||
return manager.connection.tunnelStatus
|
||||
}
|
||||
|
||||
convenience init() {
|
||||
self.init(Constants.networkExtensionBundleIdentifier)
|
||||
}
|
||||
|
||||
init(_ bundleIdentifier: String) {
|
||||
public init(bundleIdentifier: String) {
|
||||
self.bundleIdentifier = bundleIdentifier
|
||||
|
||||
let center = NotificationCenter.default
|
||||
let configurationChanged = Task { [weak self] in
|
||||
for try await _ in center.notifications(named: .NEVPNConfigurationChange).map({ _ in () }) {
|
||||
await self?.update()
|
||||
let tunnel: OSAllocatedUnfairLock<NetworkExtensionTunnel?> = .init(initialState: .none)
|
||||
configurationChanged = Task {
|
||||
for try await _ in center.notifications(named: .NEVPNConfigurationChange) {
|
||||
try Task.checkCancellation()
|
||||
await tunnel.withLock { $0 }?.update()
|
||||
}
|
||||
}
|
||||
let statusChanged = Task { [weak self] in
|
||||
for try await _ in center.notifications(named: .NEVPNStatusDidChange).map({ _ in () }) {
|
||||
await self?.updateStatus()
|
||||
statusChanged = Task {
|
||||
for try await _ in center.notifications(named: .NEVPNStatusDidChange) {
|
||||
try Task.checkCancellation()
|
||||
await tunnel.withLock { $0 }?.updateStatus()
|
||||
}
|
||||
}
|
||||
tasks = [configurationChanged, statusChanged]
|
||||
tunnel.withLock { $0 = self }
|
||||
|
||||
Task { await update() }
|
||||
}
|
||||
|
||||
private func update() async {
|
||||
do {
|
||||
managers = try await NETunnelProviderManager.managers
|
||||
let result = try await NETunnelProviderManager.managers
|
||||
await MainActor.run {
|
||||
managers = result
|
||||
status = currentStatus
|
||||
}
|
||||
await self.updateStatus()
|
||||
} catch let vpnError as NEVPNError {
|
||||
error = vpnError
|
||||
await MainActor.run {
|
||||
error = vpnError
|
||||
}
|
||||
} catch {
|
||||
logger.error("Failed to update VPN configurations: \(error)")
|
||||
}
|
||||
|
|
@ -82,12 +88,7 @@ class NetworkExtensionTunnel: Tunnel {
|
|||
}
|
||||
|
||||
func configure() async throws {
|
||||
if managers == nil {
|
||||
await update()
|
||||
}
|
||||
|
||||
guard let managers = managers else { return }
|
||||
|
||||
let managers = try await NETunnelProviderManager.managers
|
||||
if managers.count > 1 {
|
||||
try await withThrowingTaskGroup(of: Void.self, returning: Void.self) { group in
|
||||
for manager in managers.suffix(from: 1) {
|
||||
|
|
@ -110,9 +111,9 @@ class NetworkExtensionTunnel: Tunnel {
|
|||
try await manager.save()
|
||||
}
|
||||
|
||||
func start() {
|
||||
guard let manager = managers?.first else { return }
|
||||
public func start() {
|
||||
Task {
|
||||
guard let manager = try await NETunnelProviderManager.managers.first else { return }
|
||||
do {
|
||||
if !manager.isEnabled {
|
||||
manager.isEnabled = true
|
||||
|
|
@ -125,12 +126,14 @@ class NetworkExtensionTunnel: Tunnel {
|
|||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
guard let manager = managers?.first else { return }
|
||||
manager.connection.stopVPNTunnel()
|
||||
public func stop() {
|
||||
Task {
|
||||
guard let manager = try await NETunnelProviderManager.managers.first else { return }
|
||||
manager.connection.stopVPNTunnel()
|
||||
}
|
||||
}
|
||||
|
||||
func enable() {
|
||||
public func enable() {
|
||||
Task {
|
||||
do {
|
||||
try await configure()
|
||||
|
|
@ -141,7 +144,8 @@ class NetworkExtensionTunnel: Tunnel {
|
|||
}
|
||||
|
||||
deinit {
|
||||
tasks.forEach { $0.cancel() }
|
||||
configurationChanged.cancel()
|
||||
statusChanged.cancel()
|
||||
}
|
||||
}
|
||||
|
||||