Compare commits
2 commits
main
...
grpc-serve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
095e2a6894 | ||
|
|
d3ff21545c |
2
.github/actions/build-for-testing/action.yml
vendored
|
|
@ -27,9 +27,7 @@ runs:
|
||||||
Apple/DerivedData
|
Apple/DerivedData
|
||||||
key: ${{ runner.os }}-${{ inputs.scheme }}-${{ hashFiles('**/Package.resolved') }}
|
key: ${{ runner.os }}-${{ inputs.scheme }}-${{ hashFiles('**/Package.resolved') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-${{ inputs.scheme }}-${{ hashFiles('**/Package.resolved') }}
|
|
||||||
${{ runner.os }}-${{ inputs.scheme }}-
|
${{ runner.os }}-${{ inputs.scheme }}-
|
||||||
${{ runner.os }}-
|
|
||||||
- name: Build
|
- name: Build
|
||||||
shell: bash
|
shell: bash
|
||||||
working-directory: Apple
|
working-directory: Apple
|
||||||
|
|
|
||||||
30
.github/actions/download-profiles/action.yml
vendored
|
|
@ -1,30 +0,0 @@
|
||||||
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,8 +12,11 @@ inputs:
|
||||||
archive-path:
|
archive-path:
|
||||||
description: Xcode archive path
|
description: Xcode archive path
|
||||||
required: true
|
required: true
|
||||||
export-options:
|
destination:
|
||||||
description: The export options in JSON format
|
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.
|
||||||
required: true
|
required: true
|
||||||
export-path:
|
export-path:
|
||||||
description: The path to export the archive to
|
description: The path to export the archive to
|
||||||
|
|
@ -26,7 +29,8 @@ runs:
|
||||||
run: |
|
run: |
|
||||||
echo "${{ inputs.app-store-key }}" > AuthKey_${{ inputs.app-store-key-id }}.p8
|
echo "${{ inputs.app-store-key }}" > AuthKey_${{ inputs.app-store-key-id }}.p8
|
||||||
|
|
||||||
echo '${{ inputs.export-options }}' | plutil -convert xml1 -o ExportOptions.plist -
|
echo '{"destination":"${{ inputs.destination }}","method":"${{ inputs.method }}"}' \
|
||||||
|
| plutil -convert xml1 -o ExportOptions.plist -
|
||||||
|
|
||||||
xcodebuild \
|
xcodebuild \
|
||||||
-exportArchive \
|
-exportArchive \
|
||||||
|
|
|
||||||
3
.github/workflows/build-appimage.yml
vendored
|
|
@ -6,9 +6,6 @@ on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- "*"
|
- "*"
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
jobs:
|
||||||
appimage:
|
appimage:
|
||||||
name: Build AppImage
|
name: Build AppImage
|
||||||
|
|
|
||||||
8
.github/workflows/build-apple.yml
vendored
|
|
@ -1,7 +1,7 @@
|
||||||
name: Build Apple Apps
|
name: Build Apple Apps
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
|
|
@ -39,7 +39,6 @@ jobs:
|
||||||
- aarch64-apple-darwin
|
- aarch64-apple-darwin
|
||||||
env:
|
env:
|
||||||
DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer
|
||||||
PROTOC_PATH: /opt/homebrew/bin/protoc
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
@ -55,9 +54,6 @@ jobs:
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: ${{ join(matrix.rust-targets, ', ') }}
|
targets: ${{ join(matrix.rust-targets, ', ') }}
|
||||||
- name: Install Protobuf
|
|
||||||
shell: bash
|
|
||||||
run: brew install protobuf
|
|
||||||
- name: Build
|
- name: Build
|
||||||
id: build
|
id: build
|
||||||
uses: ./.github/actions/build-for-testing
|
uses: ./.github/actions/build-for-testing
|
||||||
|
|
@ -86,4 +82,4 @@ jobs:
|
||||||
destination: ${{ matrix.destination }}
|
destination: ${{ matrix.destination }}
|
||||||
test-plan: ${{ matrix.xcode-ui-test }}
|
test-plan: ${{ matrix.xcode-ui-test }}
|
||||||
artifact-prefix: ui-tests-${{ matrix.sdk-name }}
|
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,9 +6,6 @@ on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- "*"
|
- "*"
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Build Docker Image
|
name: Build Docker Image
|
||||||
|
|
|
||||||
15
.github/workflows/build-rust.yml
vendored
|
|
@ -21,16 +21,14 @@ jobs:
|
||||||
- x86_64-unknown-linux-gnu
|
- x86_64-unknown-linux-gnu
|
||||||
targets:
|
targets:
|
||||||
- aarch64-unknown-linux-gnu
|
- aarch64-unknown-linux-gnu
|
||||||
- os: macos-13
|
- os: macos-12
|
||||||
platform: macOS (Intel)
|
platform: macOS (Intel)
|
||||||
xcode: /Applications/Xcode_15.2.app
|
|
||||||
test-targets:
|
test-targets:
|
||||||
- x86_64-apple-darwin
|
- x86_64-apple-darwin
|
||||||
targets:
|
targets:
|
||||||
- x86_64-apple-ios
|
- x86_64-apple-ios
|
||||||
- os: macos-14
|
- os: macos-14
|
||||||
platform: macOS
|
platform: macOS
|
||||||
xcode: /Applications/Xcode_16.0.app
|
|
||||||
test-targets:
|
test-targets:
|
||||||
- aarch64-apple-darwin
|
- aarch64-apple-darwin
|
||||||
targets:
|
targets:
|
||||||
|
|
@ -44,11 +42,10 @@ jobs:
|
||||||
- aarch64-pc-windows-msvc
|
- aarch64-pc-windows-msvc
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
env:
|
env:
|
||||||
DEVELOPER_DIR: ${{ matrix.xcode }}/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
|
||||||
CARGO_INCREMENTAL: 0
|
CARGO_INCREMENTAL: 0
|
||||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
|
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
|
||||||
RUST_BACKTRACE: short
|
RUST_BACKTRACE: short
|
||||||
PROTOC_VERSION: 3.25.1
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
@ -61,14 +58,10 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y ${{ join(matrix.packages, ' ') }}
|
sudo apt-get install -y ${{ join(matrix.packages, ' ') }}
|
||||||
- name: Configure LLVM
|
- name: Install Windows Deps
|
||||||
if: matrix.os == 'windows-2022'
|
if: matrix.os == 'windows-2022'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: echo "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\Llvm\x64\bin" >> $GITHUB_PATH
|
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
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
|
|
@ -82,4 +75,4 @@ jobs:
|
||||||
run: cargo build --verbose --workspace --all-features --target ${{ join(matrix.targets, ' --target ') }} --target ${{ join(matrix.test-targets, ' --target ') }}
|
run: cargo build --verbose --workspace --all-features --target ${{ join(matrix.targets, ' --target ') }} --target ${{ join(matrix.test-targets, ' --target ') }}
|
||||||
- name: Test
|
- name: Test
|
||||||
shell: bash
|
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
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: swiftlint lint --strict --reporter github-actions-logging
|
run: swiftlint lint --reporter github-actions-logging
|
||||||
|
|
|
||||||
29
.github/workflows/release-appimage.yml
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
name: Release (AppImage)
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- created
|
||||||
|
jobs:
|
||||||
|
appimage:
|
||||||
|
name: Build AppImage
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: docker
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Build
|
||||||
|
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
|
||||||
|
uses: SierraSoftworks/gh-releases@v1.0.7
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
release_tag: ${{ github.ref_name }}
|
||||||
|
overwrite: 'true'
|
||||||
|
files: |
|
||||||
|
Burrow-x86_64.AppImage
|
||||||
35
.github/workflows/release-apple.yml
vendored
|
|
@ -13,16 +13,18 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- platform: iOS
|
-
|
||||||
|
destination: generic/platform=iOS
|
||||||
|
platform: iOS
|
||||||
rust-targets:
|
rust-targets:
|
||||||
- aarch64-apple-ios
|
- aarch64-apple-ios
|
||||||
- platform: macOS
|
- destination: generic/platform=macOS
|
||||||
|
platform: macOS
|
||||||
rust-targets:
|
rust-targets:
|
||||||
- x86_64-apple-darwin
|
- x86_64-apple-darwin
|
||||||
- aarch64-apple-darwin
|
- aarch64-apple-darwin
|
||||||
env:
|
env:
|
||||||
DEVELOPER_DIR: /Applications/Xcode_16.0.app/Contents/Developer
|
DEVELOPER_DIR: /Applications/Xcode_15.4.app/Contents/Developer
|
||||||
PROTOC_PATH: /opt/homebrew/bin/protoc
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
@ -33,33 +35,18 @@ jobs:
|
||||||
with:
|
with:
|
||||||
certificate: ${{ secrets.DEVELOPER_CERT }}
|
certificate: ${{ secrets.DEVELOPER_CERT }}
|
||||||
password: ${{ secrets.DEVELOPER_CERT_PASSWORD }}
|
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
|
- name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
targets: ${{ join(matrix.rust-targets, ', ') }}
|
targets: ${{ join(matrix.rust-targets, ', ') }}
|
||||||
- name: Install Protobuf
|
|
||||||
shell: bash
|
|
||||||
run: brew install protobuf
|
|
||||||
- name: Configure Version
|
- name: Configure Version
|
||||||
id: version
|
|
||||||
shell: bash
|
shell: bash
|
||||||
run: echo "BUILD_NUMBER=$(Tools/version.sh)" >> $GITHUB_OUTPUT
|
run: Tools/version.sh
|
||||||
- name: Archive
|
- name: Archive
|
||||||
uses: ./.github/actions/archive
|
uses: ./.github/actions/archive
|
||||||
with:
|
with:
|
||||||
scheme: App
|
scheme: App
|
||||||
destination: generic/platform=${{ matrix.platform }}
|
destination: ${{ matrix.destination }}
|
||||||
app-store-key: ${{ secrets.APPSTORE_KEY }}
|
app-store-key: ${{ secrets.APPSTORE_KEY }}
|
||||||
app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
||||||
app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }}
|
app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }}
|
||||||
|
|
@ -73,8 +60,6 @@ jobs:
|
||||||
app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
||||||
app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }}
|
app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }}
|
||||||
archive-path: Burrow.xcarchive
|
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
|
export-path: Release
|
||||||
- name: Notarize
|
- name: Notarize
|
||||||
if: ${{ matrix.platform == 'macOS' }}
|
if: ${{ matrix.platform == 'macOS' }}
|
||||||
|
|
@ -110,10 +95,10 @@ jobs:
|
||||||
if: ${{ matrix.platform == 'iOS' }}
|
if: ${{ matrix.platform == 'iOS' }}
|
||||||
uses: ./.github/actions/export
|
uses: ./.github/actions/export
|
||||||
with:
|
with:
|
||||||
|
method: app-store
|
||||||
|
destination: upload
|
||||||
app-store-key: ${{ secrets.APPSTORE_KEY }}
|
app-store-key: ${{ secrets.APPSTORE_KEY }}
|
||||||
app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
app-store-key-id: ${{ secrets.APPSTORE_KEY_ID }}
|
||||||
app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }}
|
app-store-key-issuer-id: ${{ secrets.APPSTORE_KEY_ISSUER_ID }}
|
||||||
archive-path: Burrow.xcarchive
|
archive-path: Burrow.xcarchive
|
||||||
export-options: |
|
|
||||||
{"method": "app-store", "destination": "upload"}
|
|
||||||
export-path: Release
|
export-path: Release
|
||||||
|
|
|
||||||
2
.github/workflows/release-if-needed.yaml
vendored
|
|
@ -9,8 +9,6 @@ jobs:
|
||||||
create:
|
create:
|
||||||
name: Create Release If Needed
|
name: Create Release If Needed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ github.token }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
|
||||||
43
.github/workflows/release-linux.yml
vendored
|
|
@ -2,28 +2,33 @@ name: Release (Linux)
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types:
|
types:
|
||||||
- created
|
- created
|
||||||
jobs:
|
jobs:
|
||||||
appimage:
|
appimage:
|
||||||
name: Build AppImage
|
name: Build AppImage
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
container: docker
|
container: docker
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Build AppImage
|
- name: Build AppImage
|
||||||
run: |
|
run: |
|
||||||
docker build -t appimage-builder . -f burrow-gtk/build-aux/Dockerfile
|
docker build -t appimage-builder . -f burrow-gtk/build-aux/Dockerfile
|
||||||
docker create --name temp appimage-builder
|
docker create --name temp appimage-builder
|
||||||
docker cp temp:/app/burrow-gtk/build-appimage/Burrow-x86_64.AppImage .
|
docker cp temp:/app/burrow-gtk/build-appimage/Burrow-x86_64.AppImage .
|
||||||
docker rm temp
|
docker rm temp
|
||||||
- name: Attach Artifacts
|
- name: Get Build Number
|
||||||
uses: SierraSoftworks/gh-releases@v1.0.7
|
id: version
|
||||||
with:
|
shell: bash
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
run: |
|
||||||
release_tag: ${{ github.ref_name }}
|
echo "BUILD_NUMBER=$(Tools/version.sh)" >> $GITHUB_OUTPUT
|
||||||
overwrite: "true"
|
- name: Attach Artifacts
|
||||||
files: |
|
uses: SierraSoftworks/gh-releases@v1.0.7
|
||||||
Burrow-x86_64.AppImage
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
release_tag: builds/${{ steps.version.outputs.BUILD_NUMBER }}
|
||||||
|
overwrite: "true"
|
||||||
|
files: |
|
||||||
|
Burrow-x86_64.AppImage
|
||||||
|
|
|
||||||
8
.gitignore
vendored
|
|
@ -1,17 +1,9 @@
|
||||||
# Xcode
|
# Xcode
|
||||||
xcuserdata
|
xcuserdata
|
||||||
|
|
||||||
# Swift
|
|
||||||
Apple/Package/.swiftpm/
|
|
||||||
|
|
||||||
# Rust
|
# Rust
|
||||||
target/
|
target/
|
||||||
.env
|
.env
|
||||||
|
|
||||||
.DS_STORE
|
.DS_STORE
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
tmp/
|
|
||||||
|
|
||||||
*.db
|
|
||||||
*.sock
|
|
||||||
|
|
@ -30,6 +30,7 @@ opt_in_rules:
|
||||||
- function_default_parameter_at_end
|
- function_default_parameter_at_end
|
||||||
- ibinspectable_in_extension
|
- ibinspectable_in_extension
|
||||||
- identical_operands
|
- identical_operands
|
||||||
|
- implicitly_unwrapped_optional
|
||||||
- indentation_width
|
- indentation_width
|
||||||
- joined_default_parameter
|
- joined_default_parameter
|
||||||
- last_where
|
- last_where
|
||||||
|
|
|
||||||
9
.vscode/settings.json
vendored
|
|
@ -15,12 +15,5 @@
|
||||||
"rust-analyzer.inlayHints.typeHints.enable": false,
|
"rust-analyzer.inlayHints.typeHints.enable": false,
|
||||||
"rust-analyzer.linkedProjects": [
|
"rust-analyzer.linkedProjects": [
|
||||||
"./burrow/Cargo.toml"
|
"./burrow/Cargo.toml"
|
||||||
],
|
]
|
||||||
"[yaml]": {
|
|
||||||
"editor.insertSpaces": true,
|
|
||||||
"editor.tabSize": 2,
|
|
||||||
"editor.autoIndent": "advanced",
|
|
||||||
"diffEditor.ignoreTrimWhitespace": false,
|
|
||||||
"editor.formatOnSave": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
import AppKit
|
import AppKit
|
||||||
import BurrowUI
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
@main
|
@MainActor @main
|
||||||
@MainActor
|
|
||||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
private let quitItem: NSMenuItem = {
|
private let quitItem: NSMenuItem = {
|
||||||
let quitItem = NSMenuItem(
|
let quitItem = NSMenuItem(
|
||||||
|
|
|
||||||
|
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 |
|
|
@ -1,7 +1,6 @@
|
||||||
#if !os(macOS)
|
|
||||||
import BurrowUI
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
#if !os(macOS)
|
||||||
@MainActor
|
@MainActor
|
||||||
@main
|
@main
|
||||||
struct BurrowApp: App {
|
struct BurrowApp: App {
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ import AuthenticationServices
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
#if !os(macOS)
|
#if !os(macOS)
|
||||||
public struct BurrowView: View {
|
struct BurrowView: View {
|
||||||
@Environment(\.webAuthenticationSession)
|
@Environment(\.webAuthenticationSession)
|
||||||
private var webAuthenticationSession
|
private var webAuthenticationSession
|
||||||
|
|
||||||
public var body: some View {
|
var body: some View {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
VStack {
|
VStack {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
@ -35,9 +35,6 @@ public struct BurrowView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public init() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private func addHackClubNetwork() {
|
private func addHackClubNetwork() {
|
||||||
Task {
|
Task {
|
||||||
try await authenticateWithSlack()
|
try await authenticateWithSlack()
|
||||||
|
|
@ -45,6 +42,7 @@ public struct BurrowView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func addWireGuardNetwork() {
|
private func addWireGuardNetwork() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func authenticateWithSlack() async throws {
|
private func authenticateWithSlack() async throws {
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23091" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="23077.2" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23091"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="23077.2"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<objects>
|
<objects>
|
||||||
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
public struct MenuItemToggleView: View {
|
struct MenuItemToggleView: View {
|
||||||
@Environment(\.tunnel)
|
@Environment(\.tunnel)
|
||||||
var tunnel: Tunnel
|
var tunnel: Tunnel
|
||||||
|
|
||||||
public var body: some View {
|
var body: some View {
|
||||||
HStack {
|
HStack {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("Burrow")
|
Text("Burrow")
|
||||||
|
|
@ -30,13 +30,10 @@ public struct MenuItemToggleView: View {
|
||||||
.padding(10)
|
.padding(10)
|
||||||
.frame(minWidth: 300, minHeight: 32, maxHeight: 32)
|
.frame(minWidth: 300, minHeight: 32, maxHeight: 32)
|
||||||
}
|
}
|
||||||
|
|
||||||
public init() {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Tunnel {
|
extension Tunnel {
|
||||||
@MainActor fileprivate var toggleDisabled: Bool {
|
fileprivate var toggleDisabled: Bool {
|
||||||
switch status {
|
switch status {
|
||||||
case .disconnected, .permissionRequired, .connected, .disconnecting:
|
case .disconnected, .permissionRequired, .connected, .disconnecting:
|
||||||
false
|
false
|
||||||
|
|
@ -45,7 +42,7 @@ extension Tunnel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor var toggleIsOn: Binding<Bool> {
|
var toggleIsOn: Binding<Bool> {
|
||||||
Binding {
|
Binding {
|
||||||
switch status {
|
switch status {
|
||||||
case .connecting, .reasserting, .connected:
|
case .connecting, .reasserting, .connected:
|
||||||
|
|
@ -2,10 +2,10 @@ import SwiftUI
|
||||||
|
|
||||||
struct NetworkCarouselView: View {
|
struct NetworkCarouselView: View {
|
||||||
var networks: [any Network] = [
|
var networks: [any Network] = [
|
||||||
HackClub(id: 1),
|
HackClub(id: "1"),
|
||||||
HackClub(id: 2),
|
HackClub(id: "2"),
|
||||||
WireGuard(id: 4),
|
WireGuard(id: "4"),
|
||||||
HackClub(id: 5)
|
HackClub(id: "5"),
|
||||||
]
|
]
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
|
|
||||||
extension NEVPNManager: @unchecked @retroactive Sendable {
|
extension NEVPNManager {
|
||||||
func remove() async throws {
|
func remove() async throws {
|
||||||
_ = try await withUnsafeThrowingContinuation { continuation in
|
_ = try await withUnsafeThrowingContinuation { continuation in
|
||||||
removeFromPreferences(completionHandler: completion(continuation))
|
removeFromPreferences(completionHandler: completion(continuation))
|
||||||
|
|
@ -14,7 +14,7 @@ extension NEVPNManager: @unchecked @retroactive Sendable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NETunnelProviderManager: @unchecked @retroactive Sendable {
|
extension NETunnelProviderManager {
|
||||||
class var managers: [NETunnelProviderManager] {
|
class var managers: [NETunnelProviderManager] {
|
||||||
get async throws {
|
get async throws {
|
||||||
try await withUnsafeThrowingContinuation { continuation in
|
try await withUnsafeThrowingContinuation { continuation in
|
||||||
|
|
@ -34,7 +34,7 @@ private func completion(_ continuation: UnsafeContinuation<Void, Error>) -> (Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func completion<T: Sendable>(_ continuation: UnsafeContinuation<T, Error>) -> (T?, Error?) -> Void {
|
private func completion<T>(_ continuation: UnsafeContinuation<T, Error>) -> (T?, Error?) -> Void {
|
||||||
return { value, error in
|
return { value, error in
|
||||||
if let error {
|
if let error {
|
||||||
continuation.resume(throwing: error)
|
continuation.resume(throwing: error)
|
||||||
|
|
@ -1,23 +1,22 @@
|
||||||
import BurrowCore
|
import BurrowShared
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
|
|
||||||
@Observable
|
@Observable
|
||||||
public final class NetworkExtensionTunnel: Tunnel {
|
class NetworkExtensionTunnel: Tunnel {
|
||||||
@MainActor public private(set) var status: TunnelStatus = .unknown
|
@MainActor private(set) var status: TunnelStatus = .unknown
|
||||||
@MainActor private var error: NEVPNError?
|
private var error: NEVPNError?
|
||||||
|
|
||||||
private let logger = Logger.logger(for: Tunnel.self)
|
private let logger = Logger.logger(for: Tunnel.self)
|
||||||
private let bundleIdentifier: String
|
private let bundleIdentifier: String
|
||||||
private let configurationChanged: Task<Void, Error>
|
private var tasks: [Task<Void, Error>] = []
|
||||||
private let statusChanged: Task<Void, Error>
|
|
||||||
|
|
||||||
// Each manager corresponds to one entry in the Settings app.
|
// 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.
|
// Our goal is to maintain a single manager, so we create one if none exist and delete any extra.
|
||||||
@MainActor private var managers: [NEVPNManager]? {
|
private var managers: [NEVPNManager]? {
|
||||||
didSet { Task { await updateStatus() } }
|
didSet { Task { await updateStatus() } }
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor private var currentStatus: TunnelStatus {
|
private var currentStatus: TunnelStatus {
|
||||||
guard let managers = managers else {
|
guard let managers = managers else {
|
||||||
guard let error = error else {
|
guard let error = error else {
|
||||||
return .unknown
|
return .unknown
|
||||||
|
|
@ -42,40 +41,35 @@ public final class NetworkExtensionTunnel: Tunnel {
|
||||||
return manager.connection.tunnelStatus
|
return manager.connection.tunnelStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(bundleIdentifier: String) {
|
convenience init() {
|
||||||
|
self.init(Constants.networkExtensionBundleIdentifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(_ bundleIdentifier: String) {
|
||||||
self.bundleIdentifier = bundleIdentifier
|
self.bundleIdentifier = bundleIdentifier
|
||||||
|
|
||||||
let center = NotificationCenter.default
|
let center = NotificationCenter.default
|
||||||
let tunnel: OSAllocatedUnfairLock<NetworkExtensionTunnel?> = .init(initialState: .none)
|
let configurationChanged = Task { [weak self] in
|
||||||
configurationChanged = Task {
|
for try await _ in center.notifications(named: .NEVPNConfigurationChange).map({ _ in () }) {
|
||||||
for try await _ in center.notifications(named: .NEVPNConfigurationChange) {
|
await self?.update()
|
||||||
try Task.checkCancellation()
|
|
||||||
await tunnel.withLock { $0 }?.update()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
statusChanged = Task {
|
let statusChanged = Task { [weak self] in
|
||||||
for try await _ in center.notifications(named: .NEVPNStatusDidChange) {
|
for try await _ in center.notifications(named: .NEVPNStatusDidChange).map({ _ in () }) {
|
||||||
try Task.checkCancellation()
|
await self?.updateStatus()
|
||||||
await tunnel.withLock { $0 }?.updateStatus()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tunnel.withLock { $0 = self }
|
tasks = [configurationChanged, statusChanged]
|
||||||
|
|
||||||
Task { await update() }
|
Task { await update() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private func update() async {
|
private func update() async {
|
||||||
do {
|
do {
|
||||||
let result = try await NETunnelProviderManager.managers
|
managers = try await NETunnelProviderManager.managers
|
||||||
await MainActor.run {
|
|
||||||
managers = result
|
|
||||||
status = currentStatus
|
|
||||||
}
|
|
||||||
await self.updateStatus()
|
await self.updateStatus()
|
||||||
} catch let vpnError as NEVPNError {
|
} catch let vpnError as NEVPNError {
|
||||||
await MainActor.run {
|
error = vpnError
|
||||||
error = vpnError
|
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
logger.error("Failed to update VPN configurations: \(error)")
|
logger.error("Failed to update VPN configurations: \(error)")
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +82,12 @@ public final class NetworkExtensionTunnel: Tunnel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func configure() async throws {
|
func configure() async throws {
|
||||||
let managers = try await NETunnelProviderManager.managers
|
if managers == nil {
|
||||||
|
await update()
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let managers = managers else { return }
|
||||||
|
|
||||||
if managers.count > 1 {
|
if managers.count > 1 {
|
||||||
try await withThrowingTaskGroup(of: Void.self, returning: Void.self) { group in
|
try await withThrowingTaskGroup(of: Void.self, returning: Void.self) { group in
|
||||||
for manager in managers.suffix(from: 1) {
|
for manager in managers.suffix(from: 1) {
|
||||||
|
|
@ -111,9 +110,9 @@ public final class NetworkExtensionTunnel: Tunnel {
|
||||||
try await manager.save()
|
try await manager.save()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func start() {
|
func start() {
|
||||||
|
guard let manager = managers?.first else { return }
|
||||||
Task {
|
Task {
|
||||||
guard let manager = try await NETunnelProviderManager.managers.first else { return }
|
|
||||||
do {
|
do {
|
||||||
if !manager.isEnabled {
|
if !manager.isEnabled {
|
||||||
manager.isEnabled = true
|
manager.isEnabled = true
|
||||||
|
|
@ -126,14 +125,12 @@ public final class NetworkExtensionTunnel: Tunnel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func stop() {
|
func stop() {
|
||||||
Task {
|
guard let manager = managers?.first else { return }
|
||||||
guard let manager = try await NETunnelProviderManager.managers.first else { return }
|
manager.connection.stopVPNTunnel()
|
||||||
manager.connection.stopVPNTunnel()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func enable() {
|
func enable() {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
try await configure()
|
try await configure()
|
||||||
|
|
@ -144,8 +141,7 @@ public final class NetworkExtensionTunnel: Tunnel {
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
configurationChanged.cancel()
|
tasks.forEach { $0.cancel() }
|
||||||
statusChanged.cancel()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
import BurrowCore
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct HackClub: Network {
|
struct HackClub: Network {
|
||||||
typealias NetworkType = Burrow_WireGuardNetwork
|
var id: String
|
||||||
static let type: Burrow_NetworkType = .hackClub
|
|
||||||
|
|
||||||
var id: Int32
|
|
||||||
var backgroundColor: Color { .init("HackClub") }
|
var backgroundColor: Color { .init("HackClub") }
|
||||||
|
|
||||||
@MainActor var label: some View {
|
var label: some View {
|
||||||
GeometryReader { reader in
|
GeometryReader { reader in
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Image("HackClub")
|
Image("HackClub")
|
||||||
10
Apple/App/Networks/Network.swift
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
protocol Network {
|
||||||
|
associatedtype Label: View
|
||||||
|
|
||||||
|
var id: String { get }
|
||||||
|
var backgroundColor: Color { get }
|
||||||
|
|
||||||
|
var label: Label { get }
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,10 @@
|
||||||
import BurrowCore
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct WireGuard: Network {
|
struct WireGuard: Network {
|
||||||
typealias NetworkType = Burrow_WireGuardNetwork
|
var id: String
|
||||||
static let type: BurrowCore.Burrow_NetworkType = .wireGuard
|
|
||||||
|
|
||||||
var id: Int32
|
|
||||||
var backgroundColor: Color { .init("WireGuard") }
|
var backgroundColor: Color { .init("WireGuard") }
|
||||||
|
|
||||||
@MainActor var label: some View {
|
var label: some View {
|
||||||
GeometryReader { reader in
|
GeometryReader { reader in
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import AuthenticationServices
|
import AuthenticationServices
|
||||||
import Foundation
|
|
||||||
import os
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import Foundation
|
||||||
|
|
||||||
enum OAuth2 {
|
enum OAuth2 {
|
||||||
enum Error: Swift.Error {
|
enum Error: Swift.Error {
|
||||||
|
|
@ -26,22 +25,17 @@ enum OAuth2 {
|
||||||
var clientID: String
|
var clientID: String
|
||||||
var clientSecret: String
|
var clientSecret: String
|
||||||
|
|
||||||
fileprivate static let queue: OSAllocatedUnfairLock<[Int: CheckedContinuation<URL, Swift.Error>]> = {
|
fileprivate static var queue: [Int: CheckedContinuation<URL, Swift.Error>] = [:]
|
||||||
.init(initialState: [:])
|
|
||||||
}()
|
|
||||||
|
|
||||||
fileprivate static func handle(url: URL) {
|
fileprivate static func handle(url: URL) {
|
||||||
let continuations = queue.withLock { continuations in
|
let continuations = queue
|
||||||
let copy = continuations
|
queue.removeAll()
|
||||||
continuations.removeAll()
|
|
||||||
return copy
|
|
||||||
}
|
|
||||||
for (_, continuation) in continuations {
|
for (_, continuation) in continuations {
|
||||||
continuation.resume(returning: url)
|
continuation.resume(returning: url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init(
|
public init(
|
||||||
authorizationEndpoint: URL,
|
authorizationEndpoint: URL,
|
||||||
tokenEndpoint: URL,
|
tokenEndpoint: URL,
|
||||||
redirectURI: URL,
|
redirectURI: URL,
|
||||||
|
|
@ -62,7 +56,7 @@ enum OAuth2 {
|
||||||
var queryItems: [URLQueryItem] = [
|
var queryItems: [URLQueryItem] = [
|
||||||
.init(name: "client_id", value: clientID),
|
.init(name: "client_id", value: clientID),
|
||||||
.init(name: "response_type", value: responseType.rawValue),
|
.init(name: "response_type", value: responseType.rawValue),
|
||||||
.init(name: "redirect_uri", value: redirectURI.absoluteString)
|
.init(name: "redirect_uri", value: redirectURI.absoluteString),
|
||||||
]
|
]
|
||||||
if !scopes.isEmpty {
|
if !scopes.isEmpty {
|
||||||
queryItems.append(.init(name: "scope", value: scopes.joined(separator: ",")))
|
queryItems.append(.init(name: "scope", value: scopes.joined(separator: ",")))
|
||||||
|
|
@ -131,11 +125,7 @@ enum OAuth2 {
|
||||||
var refreshToken: String?
|
var refreshToken: String?
|
||||||
|
|
||||||
var credential: Credential {
|
var credential: Credential {
|
||||||
.init(
|
.init(accessToken: accessToken, refreshToken: refreshToken, expirationDate: expiresIn.map { Date.init(timeIntervalSinceNow: $0) })
|
||||||
accessToken: accessToken,
|
|
||||||
refreshToken: refreshToken,
|
|
||||||
expirationDate: expiresIn.map { Date(timeIntervalSinceNow: $0) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -212,28 +202,8 @@ enum OAuth2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension WebAuthenticationSession: @unchecked @retroactive Sendable {
|
|
||||||
}
|
|
||||||
|
|
||||||
extension WebAuthenticationSession {
|
extension WebAuthenticationSession {
|
||||||
#if canImport(BrowserEngineKit)
|
func start(url: URL, redirectURI: URL) async throws -> URL {
|
||||||
@available(iOS 17.4, macOS 14.4, tvOS 17.4, watchOS 10.4, *)
|
|
||||||
fileprivate static func callback(for redirectURI: URL) throws -> ASWebAuthenticationSession.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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
fileprivate func start(url: URL, redirectURI: URL) async throws -> URL {
|
|
||||||
#if canImport(BrowserEngineKit)
|
#if canImport(BrowserEngineKit)
|
||||||
if #available(iOS 17.4, macOS 14.4, tvOS 17.4, watchOS 10.4, *) {
|
if #available(iOS 17.4, macOS 14.4, tvOS 17.4, watchOS 10.4, *) {
|
||||||
return try await authenticate(
|
return try await authenticate(
|
||||||
|
|
@ -252,15 +222,32 @@ extension WebAuthenticationSession {
|
||||||
let id = Int.random(in: 0..<Int.max)
|
let id = Int.random(in: 0..<Int.max)
|
||||||
group.addTask {
|
group.addTask {
|
||||||
return try await withCheckedThrowingContinuation { continuation in
|
return try await withCheckedThrowingContinuation { continuation in
|
||||||
OAuth2.Session.queue.withLock { $0[id] = continuation }
|
OAuth2.Session.queue[id] = continuation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
guard let url = try await group.next() else { throw OAuth2.Error.invalidCallbackURL }
|
guard let url = try await group.next() else { throw OAuth2.Error.invalidCallbackURL }
|
||||||
group.cancelAll()
|
group.cancelAll()
|
||||||
OAuth2.Session.queue.withLock { $0[id] = nil }
|
OAuth2.Session.queue[id] = nil
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if canImport(BrowserEngineKit)
|
||||||
|
@available(iOS 17.4, macOS 14.4, tvOS 17.4, watchOS 10.4, *)
|
||||||
|
fileprivate static func callback(for redirectURI: URL) throws -> ASWebAuthenticationSession.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
50
Apple/App/Tunnel.swift
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
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
|
||||||
|
|
@ -21,7 +21,7 @@ struct TunnelButton: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Tunnel {
|
extension Tunnel {
|
||||||
@MainActor fileprivate var action: TunnelButton.Action? {
|
fileprivate var action: TunnelButton.Action? {
|
||||||
switch status {
|
switch status {
|
||||||
case .permissionRequired, .invalid:
|
case .permissionRequired, .invalid:
|
||||||
.enable
|
.enable
|
||||||
|
|
@ -10,7 +10,7 @@ struct TunnelStatusView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TunnelStatus: CustomStringConvertible {
|
extension TunnelStatus: CustomStringConvertible {
|
||||||
public var description: String {
|
var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .unknown:
|
case .unknown:
|
||||||
"Unknown"
|
"Unknown"
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
{
|
|
||||||
"originHash" : "fa512b990383b7e309c5854a5279817052294a8191a6d3c55c49cfb38e88c0c3",
|
|
||||||
"pins" : [
|
|
||||||
{
|
|
||||||
"identity" : "grpc-swift",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/grpc/grpc-swift.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "6a90b7e77e29f9bda6c2b3a4165a40d6c02cfda1",
|
|
||||||
"version" : "1.23.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-async-algorithms",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-async-algorithms.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "6ae9a051f76b81cc668305ceed5b0e0a7fd93d20",
|
|
||||||
"version" : "1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-atomics",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-atomics.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "cd142fd2f64be2100422d658e7411e39489da985",
|
|
||||||
"version" : "1.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-collections",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-collections.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "9bf03ff58ce34478e66aaee630e491823326fd06",
|
|
||||||
"version" : "1.1.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-http-types",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-http-types",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "ae67c8178eb46944fd85e4dc6dd970e1f3ed6ccd",
|
|
||||||
"version" : "1.3.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-log",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-log.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "9cb486020ebf03bfa5b5df985387a14a98744537",
|
|
||||||
"version" : "1.6.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-nio",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-nio.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "9746cf80e29edfef2a39924a66731249223f42a3",
|
|
||||||
"version" : "2.72.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-nio-extras",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-nio-extras.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "d1ead62745cc3269e482f1c51f27608057174379",
|
|
||||||
"version" : "1.24.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-nio-http2",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-nio-http2.git",
|
|
||||||
"state" : {
|
|
||||||
"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" : 3
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1600"
|
LastUpgradeVersion = "1520"
|
||||||
version = "1.7">
|
version = "1.7">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES"
|
buildImplicitDependencies = "YES">
|
||||||
buildArchitectures = "Automatic">
|
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1600"
|
LastUpgradeVersion = "1520"
|
||||||
wasCreatedForAppExtension = "YES"
|
|
||||||
version = "2.0">
|
version = "2.0">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES"
|
buildImplicitDependencies = "YES">
|
||||||
buildArchitectures = "Automatic">
|
|
||||||
<BuildActionEntries>
|
<BuildActionEntries>
|
||||||
<BuildActionEntry
|
<BuildActionEntry
|
||||||
buildForTesting = "YES"
|
buildForTesting = "YES"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
LD_EXPORT_SYMBOLS = NO
|
|
||||||
SKIP_INSTALL = NO
|
SKIP_INSTALL = NO
|
||||||
|
MERGED_BINARY_TYPE = manual
|
||||||
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = $(inherited) @executable_path/Frameworks
|
LD_RUNPATH_SEARCH_PATHS[sdk=iphone*] = $(inherited) @executable_path/Frameworks
|
||||||
LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks
|
LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks
|
||||||
|
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor
|
||||||
|
|
||||||
|
ENABLE_PREVIEWS = YES
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
#include "Identity.xcconfig"
|
#include "Identity.xcconfig"
|
||||||
#include "Debug.xcconfig"
|
|
||||||
#include "Version.xcconfig"
|
#include "Version.xcconfig"
|
||||||
|
|
||||||
SDKROOT = auto
|
SDKROOT = auto
|
||||||
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES
|
ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES
|
||||||
|
|
||||||
SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx
|
SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx
|
||||||
SWIFT_VERSION = 6.0
|
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0
|
IPHONEOS_DEPLOYMENT_TARGET = 17.0
|
||||||
MACOSX_DEPLOYMENT_TARGET = 14.0
|
MACOSX_DEPLOYMENT_TARGET = 14.0
|
||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO
|
||||||
SUPPORTS_MACCATALYST = NO
|
SUPPORTS_MACCATALYST = NO
|
||||||
|
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO
|
||||||
PRODUCT_NAME = $(TARGET_NAME:c99extidentifier)
|
PRODUCT_NAME = $(TARGET_NAME:c99extidentifier)
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).$(PRODUCT_NAME)
|
PRODUCT_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).$(PRODUCT_NAME)
|
||||||
MARKETING_VERSION = 0.1
|
MARKETING_VERSION = 0.1
|
||||||
|
|
@ -20,25 +19,43 @@ SKIP_INSTALL = YES
|
||||||
|
|
||||||
CODE_SIGN_IDENTITY = Apple Development
|
CODE_SIGN_IDENTITY = Apple Development
|
||||||
|
|
||||||
GENERATE_INFOPLIST_FILE = YES
|
|
||||||
INFOPLIST_FILE = Configuration/Info.plist
|
INFOPLIST_FILE = Configuration/Info.plist
|
||||||
|
GENERATE_INFOPLIST_FILE = YES
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2023-2024 Hack Club
|
INFOPLIST_KEY_NSHumanReadableCopyright = Copyright © 2023-2024 Hack Club
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = Burrow
|
INFOPLIST_KEY_CFBundleDisplayName = Burrow
|
||||||
|
|
||||||
ENABLE_APP_SANDBOX[sdk=macosx*] = YES
|
|
||||||
|
|
||||||
ENABLE_BITCODE = NO
|
ENABLE_BITCODE = NO
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO
|
|
||||||
|
ENABLE_APP_SANDBOX[sdk=macosx*] = YES
|
||||||
|
ENABLE_HARDENED_RUNTIME[sdk=macosx*] = YES
|
||||||
COMBINE_HIDPI_IMAGES = YES
|
COMBINE_HIDPI_IMAGES = YES
|
||||||
EAGER_LINKING = YES
|
COPY_PHASE_STRIP = NO
|
||||||
|
|
||||||
FUSE_BUILD_SCRIPT_PHASES = 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 = group.$(APP_BUNDLE_IDENTIFIER)
|
||||||
APP_GROUP_IDENTIFIER[sdk=macosx*] = $(DEVELOPMENT_TEAM).$(APP_BUNDLE_IDENTIFIER)
|
APP_GROUP_IDENTIFIER[sdk=macosx*] = $(DEVELOPMENT_TEAM).$(APP_BUNDLE_IDENTIFIER)
|
||||||
NETWORK_EXTENSION_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).network
|
NETWORK_EXTENSION_BUNDLE_IDENTIFIER = $(APP_BUNDLE_IDENTIFIER).network
|
||||||
|
|
||||||
// https://github.com/grpc/grpc-swift/issues/683#issuecomment-1130118953
|
// Swift
|
||||||
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
|
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
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
// 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,6 +1,4 @@
|
||||||
LD_EXPORT_SYMBOLS = NO
|
MERGED_BINARY_TYPE = manual
|
||||||
|
|
||||||
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 = $(inherited) @executable_path/Frameworks @executable_path/../../Frameworks
|
||||||
LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks
|
LD_RUNPATH_SEARCH_PATHS[sdk=macos*] = $(inherited) @executable_path/../Frameworks @executable_path/../../../../Frameworks
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
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 =
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
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 +0,0 @@
|
||||||
../../../proto/burrow.proto
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"invocations": [
|
|
||||||
{
|
|
||||||
"protoFiles": [
|
|
||||||
"burrow.proto",
|
|
||||||
],
|
|
||||||
"server": false,
|
|
||||||
"visibility": "public"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"invocations": [
|
|
||||||
{
|
|
||||||
"protoFiles": [
|
|
||||||
"burrow.proto",
|
|
||||||
],
|
|
||||||
"visibility": "public"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,74 +1,92 @@
|
||||||
import AsyncAlgorithms
|
import BurrowShared
|
||||||
import BurrowConfiguration
|
|
||||||
import BurrowCore
|
|
||||||
import libburrow
|
import libburrow
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
import os
|
import os
|
||||||
|
|
||||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
enum Error: Swift.Error {
|
|
||||||
case missingTunnelConfiguration
|
|
||||||
}
|
|
||||||
|
|
||||||
private let logger = Logger.logger(for: PacketTunnelProvider.self)
|
private let logger = Logger.logger(for: PacketTunnelProvider.self)
|
||||||
|
private var client: Client?
|
||||||
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() {
|
override init() {
|
||||||
do {
|
do {
|
||||||
libburrow.spawnInProcess(
|
libburrow.spawnInProcess(
|
||||||
socketPath: try Constants.socketURL.path(percentEncoded: false),
|
socketPath: try Constants.socketURL.path(percentEncoded: false),
|
||||||
databasePath: try Constants.databaseURL.path(percentEncoded: false)
|
dbPath: try Constants.dbURL.path(percentEncoded: false)
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
logger.error("Failed to spawn networking thread: \(error)")
|
logger.error("Failed to spawn: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func startTunnel(options: [String: NSObject]? = nil) async throws {
|
override func startTunnel(options: [String: NSObject]? = nil) async throws {
|
||||||
do {
|
do {
|
||||||
let configuration = try await Array(client.tunnelConfiguration(.init()).prefix(1)).first
|
let client = try Client()
|
||||||
guard let settings = configuration?.settings else {
|
self.client = client
|
||||||
throw Error.missingTunnelConfiguration
|
register_events(client)
|
||||||
}
|
|
||||||
try await setTunnelNetworkSettings(settings)
|
_ = try await self.loadTunSettings()
|
||||||
_ = try await client.tunnelStart(.init())
|
let startRequest = Start(
|
||||||
logger.log("Started tunnel with network settings: \(settings)")
|
tun: Start.TunOptions(
|
||||||
|
name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: []
|
||||||
|
)
|
||||||
|
)
|
||||||
|
let response = try await client.request(startRequest, type: BurrowResult<AnyResponseData>.self)
|
||||||
|
self.logger.log("Received start server response: \(String(describing: response))")
|
||||||
} catch {
|
} catch {
|
||||||
logger.error("Failed to start tunnel: \(error)")
|
self.logger.error("Failed to start tunnel: \(error)")
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func stopTunnel(with reason: NEProviderStopReason) async {
|
override func stopTunnel(with reason: NEProviderStopReason) async {
|
||||||
do {
|
do {
|
||||||
_ = try await client.tunnelStop(.init())
|
let client = try Client()
|
||||||
logger.log("Stopped client")
|
_ = try await client.single_request("Stop", type: BurrowResult<AnyResponseData>.self)
|
||||||
|
self.logger.log("Stopped client.")
|
||||||
} catch {
|
} catch {
|
||||||
logger.error("Failed to stop tunnel: \(error)")
|
self.logger.error("Failed to stop tunnel: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func loadTunSettings() async throws -> ServerConfig {
|
||||||
|
guard let client = self.client else {
|
||||||
|
throw BurrowError.noClient
|
||||||
|
}
|
||||||
|
let srvConfig = try await client.single_request("ServerConfig", type: BurrowResult<ServerConfig>.self)
|
||||||
|
guard let serverconfig = srvConfig.Ok else {
|
||||||
|
throw BurrowError.resultIsError
|
||||||
|
}
|
||||||
|
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)")
|
||||||
|
return serverconfig
|
||||||
|
}
|
||||||
|
private func generateTunSettings(from: ServerConfig) -> NETunnelNetworkSettings? {
|
||||||
|
// Using a makeshift remote tunnel address
|
||||||
|
let nst = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
|
||||||
|
var v4Addresses = [String]()
|
||||||
|
var v6Addresses = [String]()
|
||||||
|
for addr in from.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
|
||||||
|
}
|
||||||
|
func register_events(_ client: Client) {
|
||||||
|
client.on_event(.ConfigChange) { (cfig: ServerConfig) in
|
||||||
|
self.logger.info("Config Change Notification: \(String(describing: cfig))")
|
||||||
|
self.setTunnelNetworkSettings(self.generateTunSettings(from: cfig))
|
||||||
|
self.logger.info("Updated Tunnel Network Settings.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,12 +68,9 @@ else
|
||||||
CARGO_PATH="$(dirname $(readlink -f $(which cargo))):/usr/bin"
|
CARGO_PATH="$(dirname $(readlink -f $(which cargo))):/usr/bin"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
PROTOC=$(readlink -f $(which protoc))
|
|
||||||
CARGO_PATH="$(dirname $PROTOC):$CARGO_PATH"
|
|
||||||
|
|
||||||
# Run cargo without the various environment variables set by Xcode.
|
# Run cargo without the various environment variables set by Xcode.
|
||||||
# Those variables can confuse cargo and the build scripts it runs.
|
# Those variables can confuse cargo and the build scripts it runs.
|
||||||
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[@]}"
|
env -i PATH="$CARGO_PATH" 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}"
|
mkdir -p "${BUILT_PRODUCTS_DIR}"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
__attribute__((__swift_name__("spawnInProcess(socketPath:databasePath:)")))
|
__attribute__((__swift_name__("spawnInProcess(socketPath:dbPath:)")))
|
||||||
extern void spawn_in_process(const char * __nullable socket_path, const char * __nullable db_path);
|
extern void spawn_in_process(const char * __nullable socket_path, const char * __nullable db_path);
|
||||||
|
|
|
||||||
106
Apple/Shared/Client.swift
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
import Foundation
|
||||||
|
import Network
|
||||||
|
|
||||||
|
public final class Client {
|
||||||
|
let connection: NWConnection
|
||||||
|
|
||||||
|
private let logger = Logger.logger(for: Client.self)
|
||||||
|
private var generator = SystemRandomNumberGenerator()
|
||||||
|
private var continuations: [UInt: UnsafeContinuation<Data, Error>] = [:]
|
||||||
|
private var eventMap: [NotificationType: [(Data) throws -> Void]] = [:]
|
||||||
|
private var task: Task<Void, Error>?
|
||||||
|
|
||||||
|
public convenience init() throws {
|
||||||
|
self.init(url: try Constants.socketURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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)
|
||||||
|
let connection = NWConnection(to: endpoint, using: parameters)
|
||||||
|
connection.start(queue: .global())
|
||||||
|
self.connection = connection
|
||||||
|
self.task = Task { [weak self] in
|
||||||
|
while true {
|
||||||
|
let (data, _, _) = try await connection.receiveMessage()
|
||||||
|
let peek = try JSONDecoder().decode(MessagePeek.self, from: data)
|
||||||
|
switch peek.type {
|
||||||
|
case .Response:
|
||||||
|
let response = try JSONDecoder().decode(ResponsePeek.self, from: data)
|
||||||
|
self?.logger.info("Received response for \(response.id)")
|
||||||
|
guard let continuations = self?.continuations else {return}
|
||||||
|
self?.logger.debug("All keys in continuation table: \(continuations.keys)")
|
||||||
|
guard let continuation = self?.continuations[response.id] else { return }
|
||||||
|
self?.logger.debug("Got matching continuation")
|
||||||
|
continuation.resume(returning: data)
|
||||||
|
case .Notification:
|
||||||
|
let peek = try JSONDecoder().decode(NotificationPeek.self, from: data)
|
||||||
|
guard let handlers = self?.eventMap[peek.method] else { continue }
|
||||||
|
_ = try handlers.map { try $0(data) }
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private func send<T: Request, U: Decodable>(_ request: T) async throws -> U {
|
||||||
|
let data: Data = try await withUnsafeThrowingContinuation { continuation in
|
||||||
|
continuations[request.id] = continuation
|
||||||
|
do {
|
||||||
|
let data = try JSONEncoder().encode(request)
|
||||||
|
let completion: NWConnection.SendCompletion = .contentProcessed { error in
|
||||||
|
guard let error = error else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continuation.resume(throwing: error)
|
||||||
|
}
|
||||||
|
connection.send(content: data, completion: completion)
|
||||||
|
} catch {
|
||||||
|
continuation.resume(throwing: error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.logger.debug("Got response data: \(String(describing: data.base64EncodedString()))")
|
||||||
|
let res = try JSONDecoder().decode(Response<U>.self, from: data)
|
||||||
|
self.logger.debug("Got response data decoded: \(String(describing: res))")
|
||||||
|
return res.result
|
||||||
|
}
|
||||||
|
public func request<T: Codable, U: Decodable>(_ request: T, type: U.Type = U.self) async throws -> U {
|
||||||
|
let req = BurrowRequest(
|
||||||
|
id: generator.next(upperBound: UInt.max),
|
||||||
|
command: request
|
||||||
|
)
|
||||||
|
return try await send(req)
|
||||||
|
}
|
||||||
|
public func single_request<U: Decodable>(_ request: String, type: U.Type = U.self) async throws -> U {
|
||||||
|
let req = BurrowSimpleRequest(
|
||||||
|
id: generator.next(upperBound: UInt.max),
|
||||||
|
command: request
|
||||||
|
)
|
||||||
|
return try await send(req)
|
||||||
|
}
|
||||||
|
public func on_event<T: Codable>(_ event: NotificationType, callable: @escaping (T) throws -> Void) {
|
||||||
|
let action = { data in
|
||||||
|
let decoded = try JSONDecoder().decode(Notification<T>.self, from: data)
|
||||||
|
try callable(decoded.params)
|
||||||
|
}
|
||||||
|
if eventMap[event] != nil {
|
||||||
|
eventMap[event]?.append(action)
|
||||||
|
} else {
|
||||||
|
eventMap[event] = [action]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
connection.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
@_implementationOnly import CConstants
|
@_implementationOnly import Constants
|
||||||
import OSLog
|
|
||||||
|
|
||||||
public enum Constants {
|
public enum Constants {
|
||||||
enum Error: Swift.Error {
|
enum Error: Swift.Error {
|
||||||
|
|
@ -10,29 +9,25 @@ public enum Constants {
|
||||||
public static let appGroupIdentifier = AppGroupIdentifier
|
public static let appGroupIdentifier = AppGroupIdentifier
|
||||||
public static let networkExtensionBundleIdentifier = NetworkExtensionBundleIdentifier
|
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)
|
||||||
|
}()
|
||||||
public static var socketURL: URL {
|
public static var socketURL: URL {
|
||||||
get throws {
|
get throws {
|
||||||
try groupContainerURL.appending(component: "burrow.sock", directoryHint: .notDirectory)
|
try groupContainerURL.appending(component: "burrow.sock", directoryHint: .notDirectory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static var databaseURL: URL {
|
public static var dbURL: URL {
|
||||||
get throws {
|
get throws {
|
||||||
try groupContainerURL.appending(component: "burrow.db", directoryHint: .notDirectory)
|
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 CConstants {
|
module Constants {
|
||||||
header "Constants.h"
|
header "Constants.h"
|
||||||
export *
|
export *
|
||||||
}
|
}
|
||||||
139
Apple/Shared/DataTypes.swift
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
// swiftlint:disable identifier_name raw_value_for_camel_cased_codable_enum
|
||||||
|
public enum BurrowError: Error {
|
||||||
|
case addrDoesntExist
|
||||||
|
case resultIsError
|
||||||
|
case cantParseResult
|
||||||
|
case resultIsNone
|
||||||
|
case noClient
|
||||||
|
}
|
||||||
|
|
||||||
|
public protocol Request: Codable where Params: Codable {
|
||||||
|
associatedtype Params
|
||||||
|
|
||||||
|
var id: UInt { get set }
|
||||||
|
var method: String { get set }
|
||||||
|
var params: Params? { get set }
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MessageType: String, Codable {
|
||||||
|
case Request
|
||||||
|
case Response
|
||||||
|
case Notification
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct MessagePeek: Codable {
|
||||||
|
public var type: MessageType
|
||||||
|
public init(type: MessageType) {
|
||||||
|
self.type = type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct BurrowSimpleRequest: Request {
|
||||||
|
public var id: UInt
|
||||||
|
public var method: String
|
||||||
|
public var params: String?
|
||||||
|
public init(id: UInt, command: String, params: String? = nil) {
|
||||||
|
self.id = id
|
||||||
|
self.method = command
|
||||||
|
self.params = params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct BurrowRequest<T>: Request where T: Codable {
|
||||||
|
public var id: UInt
|
||||||
|
public var method: String
|
||||||
|
public var params: T?
|
||||||
|
public init(id: UInt, command: T) {
|
||||||
|
self.id = id
|
||||||
|
self.method = "\(T.self)"
|
||||||
|
self.params = command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Response<T>: Decodable where T: Decodable {
|
||||||
|
public var id: UInt
|
||||||
|
public var result: T
|
||||||
|
public init(id: UInt, result: T) {
|
||||||
|
self.id = id
|
||||||
|
self.result = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ResponsePeek: Codable {
|
||||||
|
public var id: UInt
|
||||||
|
public init(id: UInt) {
|
||||||
|
self.id = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum NotificationType: String, Codable {
|
||||||
|
case ConfigChange
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Notification<T>: Codable where T: Codable {
|
||||||
|
public var method: NotificationType
|
||||||
|
public var params: T
|
||||||
|
public init(method: NotificationType, params: T) {
|
||||||
|
self.method = method
|
||||||
|
self.params = params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct NotificationPeek: Codable {
|
||||||
|
public var method: NotificationType
|
||||||
|
public init(method: NotificationType) {
|
||||||
|
self.method = method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AnyResponseData: Codable {
|
||||||
|
public var type: String
|
||||||
|
public init(type: String) {
|
||||||
|
self.type = type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct BurrowResult<T>: Codable where T: Codable {
|
||||||
|
public var Ok: T?
|
||||||
|
public var Err: String?
|
||||||
|
public init(Ok: T, Err: String? = nil) {
|
||||||
|
self.Ok = Ok
|
||||||
|
self.Err = Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ServerConfig: Codable {
|
||||||
|
public let address: [String]
|
||||||
|
public let name: String?
|
||||||
|
public let mtu: Int32?
|
||||||
|
public init(address: [String], name: String?, mtu: Int32?) {
|
||||||
|
self.address = address
|
||||||
|
self.name = name
|
||||||
|
self.mtu = mtu
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Start: Codable {
|
||||||
|
public struct TunOptions: Codable {
|
||||||
|
public let name: String?
|
||||||
|
public let no_pi: Bool
|
||||||
|
public let tun_excl: Bool
|
||||||
|
public let tun_retrieve: Bool
|
||||||
|
public let address: [String]
|
||||||
|
public init(name: String?, no_pi: Bool, tun_excl: Bool, tun_retrieve: Bool, address: [String]) {
|
||||||
|
self.name = name
|
||||||
|
self.no_pi = no_pi
|
||||||
|
self.tun_excl = tun_excl
|
||||||
|
self.tun_retrieve = tun_retrieve
|
||||||
|
self.address = address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public let tun: TunOptions
|
||||||
|
public init(tun: TunOptions) {
|
||||||
|
self.tun = tun
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// swiftlint:enable identifier_name raw_value_for_camel_cased_codable_enum
|
||||||
|
|
@ -4,7 +4,7 @@ import os
|
||||||
extension Logger {
|
extension Logger {
|
||||||
private static let loggers: OSAllocatedUnfairLock<[String: Logger]> = OSAllocatedUnfairLock(initialState: [:])
|
private static let loggers: OSAllocatedUnfairLock<[String: Logger]> = OSAllocatedUnfairLock(initialState: [:])
|
||||||
|
|
||||||
public dynamic static var subsystem: String { "com.hackclub.burrow" }
|
public static let subsystem = Constants.bundleIdentifier
|
||||||
|
|
||||||
public static func logger(for type: Any.Type) -> Logger {
|
public static func logger(for type: Any.Type) -> Logger {
|
||||||
let category = String(describing: type)
|
let category = String(describing: type)
|
||||||
32
Apple/Shared/NWConnection+Async.swift
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
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: ())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
Apple/Shared/NewlineProtocolFramer.swift
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
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 {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||