Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 232 additions & 0 deletions .github/workflows/ios.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
name: iOS

on:
push:
paths:
- ".github/workflows/ios.yml"
- "iosApp/**"
- "shared/**"
- "gradle/**"
- "build.gradle.kts"
- "settings.gradle.kts"
- "gradle.properties"
pull_request:
paths:
- ".github/workflows/ios.yml"
- "iosApp/**"
- "shared/**"
- "gradle/**"
- "build.gradle.kts"
- "settings.gradle.kts"
- "gradle.properties"
workflow_dispatch:
inputs:
build_signed_ipa:
description: "Build a development-signed IPA for registered iPhones"
required: true
type: boolean
default: false

permissions:
contents: read

concurrency:
group: ios-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
simulator-app:
name: Build iOS Simulator app
runs-on: macos-26
timeout-minutes: 60

steps:
- name: Check out source
uses: actions/checkout@v6

- name: Set up JDK 23
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "23"

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6

- name: Select Xcode 26.4
run: sudo xcode-select --switch /Applications/Xcode_26.4.app/Contents/Developer

- name: Show Apple toolchain
run: xcodebuild -version

- name: Build unsigned Simulator app
run: |
xcodebuild \
-project iosApp/iosApp.xcodeproj \
-scheme iosApp \
-configuration Debug \
-sdk iphonesimulator \
-destination "generic/platform=iOS Simulator" \
-derivedDataPath build/ios-simulator \
CODE_SIGNING_ALLOWED=NO \
TEAM_ID= \
build

- name: Package Simulator app
run: |
APP_PATH="build/ios-simulator/Build/Products/Debug-iphonesimulator/Sync360.app"
test -d "$APP_PATH"
ditto -c -k --sequesterRsrc --keepParent "$APP_PATH" Sync360-iOS-Simulator.zip

- name: Upload Simulator app
uses: actions/upload-artifact@v4
with:
name: Sync360-iOS-Simulator
path: Sync360-iOS-Simulator.zip
if-no-files-found: error
retention-days: 14

signed-iphone-ipa:
name: Build signed iPhone IPA
if: github.event_name == 'workflow_dispatch' && inputs.build_signed_ipa
runs-on: macos-26
timeout-minutes: 60
env:
IOS_DEVELOPMENT_CERTIFICATE_BASE64: ${{ secrets.IOS_DEVELOPMENT_CERTIFICATE_BASE64 }}
IOS_DEVELOPMENT_CERTIFICATE_PASSWORD: ${{ secrets.IOS_DEVELOPMENT_CERTIFICATE_PASSWORD }}
IOS_DEVELOPMENT_PROVISIONING_PROFILE_BASE64: ${{ secrets.IOS_DEVELOPMENT_PROVISIONING_PROFILE_BASE64 }}
IOS_TEAM_ID: ${{ secrets.IOS_TEAM_ID }}

steps:
- name: Check out source
uses: actions/checkout@v6

- name: Set up JDK 23
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: "23"

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6

- name: Select Xcode 26.4
run: sudo xcode-select --switch /Applications/Xcode_26.4.app/Contents/Developer

- name: Require Apple signing secrets
shell: bash
run: |
missing=0
for secret_name in \
IOS_DEVELOPMENT_CERTIFICATE_BASE64 \
IOS_DEVELOPMENT_CERTIFICATE_PASSWORD \
IOS_DEVELOPMENT_PROVISIONING_PROFILE_BASE64 \
IOS_TEAM_ID
do
if [ -z "${!secret_name:-}" ]; then
echo "::error::Missing GitHub Actions secret: $secret_name"
missing=1
fi
done
exit "$missing"

- name: Install development certificate and provisioning profile
shell: bash
run: |
CERTIFICATE_PATH="$RUNNER_TEMP/ios-development.p12"
PROFILE_PATH="$RUNNER_TEMP/ios-development.mobileprovision"
PROFILE_PLIST="$RUNNER_TEMP/ios-development-profile.plist"
KEYCHAIN_PATH="$RUNNER_TEMP/sync360-signing.keychain-db"
KEYCHAIN_PASSWORD="$(openssl rand -hex 24)"

printf '%s' "$IOS_DEVELOPMENT_CERTIFICATE_BASE64" | base64 --decode > "$CERTIFICATE_PATH"
printf '%s' "$IOS_DEVELOPMENT_PROVISIONING_PROFILE_BASE64" | base64 --decode > "$PROFILE_PATH"

security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "$CERTIFICATE_PATH" \
-P "$IOS_DEVELOPMENT_CERTIFICATE_PASSWORD" \
-A \
-t cert \
-f pkcs12 \
-k "$KEYCHAIN_PATH"
security set-key-partition-list \
-S apple-tool:,apple: \
-s \
-k "$KEYCHAIN_PASSWORD" \
"$KEYCHAIN_PATH"
security list-keychains -d user -s "$KEYCHAIN_PATH"

security cms -D -i "$PROFILE_PATH" > "$PROFILE_PLIST"
PROFILE_UUID="$(/usr/libexec/PlistBuddy -c 'Print :UUID' "$PROFILE_PLIST")"
PROFILE_NAME="$(/usr/libexec/PlistBuddy -c 'Print :Name' "$PROFILE_PLIST")"
APPLICATION_IDENTIFIER="$(/usr/libexec/PlistBuddy -c 'Print :Entitlements:application-identifier' "$PROFILE_PLIST")"
BUNDLE_IDENTIFIER="${APPLICATION_IDENTIFIER#*.}"

mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
cp "$PROFILE_PATH" "$HOME/Library/MobileDevice/Provisioning Profiles/$PROFILE_UUID.mobileprovision"

echo "IOS_PROFILE_NAME=$PROFILE_NAME" >> "$GITHUB_ENV"
echo "IOS_BUNDLE_IDENTIFIER=$BUNDLE_IDENTIFIER" >> "$GITHUB_ENV"
echo "IOS_ARCHIVE_PATH=$RUNNER_TEMP/Sync360.xcarchive" >> "$GITHUB_ENV"
echo "IOS_EXPORT_PATH=$RUNNER_TEMP/ios-export" >> "$GITHUB_ENV"

security find-identity -v -p codesigning "$KEYCHAIN_PATH"

- name: Archive signed iPhone app
shell: bash
run: |
xcodebuild \
-project iosApp/iosApp.xcodeproj \
-scheme iosApp \
-configuration Debug \
-destination "generic/platform=iOS" \
-archivePath "$IOS_ARCHIVE_PATH" \
TEAM_ID="$IOS_TEAM_ID" \
PRODUCT_BUNDLE_IDENTIFIER="$IOS_BUNDLE_IDENTIFIER" \
DEVELOPMENT_TEAM="$IOS_TEAM_ID" \
CODE_SIGN_STYLE=Manual \
CODE_SIGN_IDENTITY="Apple Development" \
PROVISIONING_PROFILE_SPECIFIER="$IOS_PROFILE_NAME" \
archive

- name: Export signed IPA
shell: bash
run: |
EXPORT_OPTIONS="$RUNNER_TEMP/ExportOptions.plist"
cat > "$EXPORT_OPTIONS" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>debugging</string>
<key>signingStyle</key>
<string>manual</string>
<key>teamID</key>
<string>$IOS_TEAM_ID</string>
<key>provisioningProfiles</key>
<dict>
<key>$IOS_BUNDLE_IDENTIFIER</key>
<string>$IOS_PROFILE_NAME</string>
</dict>
</dict>
</plist>
EOF

xcodebuild \
-exportArchive \
-archivePath "$IOS_ARCHIVE_PATH" \
-exportOptionsPlist "$EXPORT_OPTIONS" \
-exportPath "$IOS_EXPORT_PATH"

test -n "$(find "$IOS_EXPORT_PATH" -maxdepth 1 -name '*.ipa' -print -quit)"

- name: Upload signed IPA
uses: actions/upload-artifact@v4
with:
name: Sync360-iPhone-Development-IPA
path: ${{ runner.temp }}/ios-export/*.ipa
if-no-files-found: error
retention-days: 14
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ captures/

# Signing secrets
*.jks
*.jkis
*.keystore
keystore.properties

Expand Down
22 changes: 19 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

- Android-first manual rebuild with shared Compose Multiplatform Send and Receive UI.
- Android DNS-SD/mDNS discovery and registration through `NsdManager`.
- Desktop DNS-SD/mDNS discovery and registration through JmDNS.
- Windows DNS-SD/mDNS discovery and registration through the operating system `dnsapi.dll` API on all interfaces.
- Current macOS/Linux DNS-SD/mDNS discovery and registration through JmDNS on eligible IPv4 and IPv6 LAN addresses.
- Separate discovery and registration lifecycle states shared by Android, Desktop, the controller, and UI.
- Stable per-install device identity and advertised dynamic HTTP/file-transfer ports.
- Text offers, receiver Accept/Decline, text transfer, Copy, and Clear.
- Android and Desktop multiple-file selection and metadata offers.
Expand All @@ -22,6 +24,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Shared transfer buffer/timeout constants, currently using a 512 KiB payload buffer.
- Compose Desktop startup, platform DI implementations, native file dialog, clipboard, and Downloads actions.
- Navigation 3 adaptive 50/50 Send/Receive scene for wider windows.
- Application-lifetime network startup and state-driven connection repair.
- Enabled iOS device and Apple-silicon Simulator targets with native Bonjour discovery, document selection, clipboard, Files-visible storage, and streamed TCP transfer implementations.
- Added an iOS-only GitHub Actions workflow for an unsigned Simulator app and optional development-signed iPhone IPA.
- Prepared version `0.1.0` across Android, Desktop, and iOS; added private Android release signing configuration and a permanent Windows MSI upgrade identity.
- Public architecture, development, roadmap, security, privacy, and contribution documentation.

### Changed
Expand All @@ -30,13 +36,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Separated Ktor HTTP offer/control messages from raw TCP file bytes.
- Reused one TCP connection for the complete accepted multi-file batch instead of opening one connection per file.
- Removed per-file flush-and-acknowledgement waits so an accepted batch can stream continuously before one final receiver result.
- Moved network startup from the Send ViewModel to the Android and Desktop application entry points.
- Derived the 60-second discovery window from the platform-reported running state.
- Made Android repair advance through NSD callbacks and made JVM cleanup retain JmDNS instances that fail to close.
- Restricted discovery Reload and full connection repair to compatible discovery and registration states.
- Selected the Windows-native discovery backend at Desktop DI startup while retaining JmDNS for macOS and Linux.
- Used the JDK Foreign Function and Memory API for Windows interop without adding a third-party native bridge.
- Made Windows discovery process native add and TTL-zero removal notifications so the nearby-device list can update during an active browse.
- Moved Windows discovery out of its initial loading state as soon as the operating system accepts the asynchronous browse request.
- Confirmed in an initial Windows 11 Ethernet test that Android and Windows advertisements appeared promptly and were removed after the corresponding app closed.
- Aligned Kotlin 2.4.10, Android Gradle Plugin 9.1.1, and Gradle 9.3.1 within their documented compatibility ranges while retaining Android API 37.
- Positioned the project around direct local-network nearby sharing rather than chat or cloud sync.

### Known limitations

- No stable public release yet.
- No authentication, encryption, transfer/session token, or cryptographic integrity verification.
- No byte percentage, speed, ETA, retry, pause/resume, or interrupted-transfer recovery.
- No speed, ETA, retry, pause/resume, or interrupted-transfer recovery; transfer progress currently shows batch-wide whole-byte percentage.
- Foreground/background and network-change lifecycle handling are incomplete.
- Desktop support needs broader operating-system, adapter, firewall, and router validation.
- Automated transfer coverage is minimal; iOS is inactive.
- Automated transfer coverage is minimal; iOS physical-device discovery and transfer are unverified.
2 changes: 1 addition & 1 deletion CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Project maintainers may remove comments, close issues, reject contributions, or

If you see a problem, report it privately to the maintainer.

Maintainer contact: TODO: add private contact email
Until a dedicated contact email is added, contact the maintainer privately through the GitHub or LinkedIn profile linked in `README.md`.

## Scope

Expand Down
34 changes: 19 additions & 15 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,29 @@ The project is not looking for giant rewrites right now. The most useful contrib

## Current project status

Current working slice:
Implemented today:

- Android NSD discovery.
- Dynamic Ktor server port advertisement.
- Ktor client/server ping request.
- Experimental receiver Accept/Decline state.
- Android nearby discovery and registration through `NsdManager`.
- Windows nearby discovery and registration through the system DNS-SD API.
- Current macOS/Linux discovery and registration through JmDNS.
- Direct text and multi-file transfer between nearby devices.
- Shared Compose UI for Android and Desktop.
- Enabled iOS source implementation for Bonjour discovery, text/file transfer, selection, clipboard, and Files-visible storage.

Not built yet:
Important current limitations:

- Real file transfer.
- Direct text sending.
- Desktop rebuilt networking flow.
- Security/session validation.
- Production-ready UX.
- Local transfers are not authenticated or encrypted.
- Background and automatic network-change lifecycle handling is incomplete.
- Desktop networking has not been broadly validated across operating systems, adapters, VPNs, and routers.
- iOS physical-device discovery and transfer behavior is not yet validated.

Please keep that status in mind when opening issues or PRs.

## Local setup

Prerequisites:

- JDK 17
- JDK 23
- Android Studio or IntelliJ IDEA
- Android SDK
- Gradle wrapper from this repository
Expand All @@ -53,13 +54,13 @@ On Windows:
./gradlew.bat :androidApp:assembleDebug
```

Desktop shell:
Run Desktop:

```bash
./gradlew :desktopApp:run
```

The rebuilt networking flow is currently Android-first, so desktop behavior may lag behind Android.
Android remains the primary reference implementation. Platform networking behavior can differ where operating-system APIs require it.

## Before you start

Expand All @@ -69,7 +70,7 @@ For anything large, open an issue first. Examples:

- changing architecture boundaries
- changing discovery behavior
- adding file transfer
- changing the transfer protocol
- adding security
- changing Gradle/KMP target setup
- adding persistence/database code
Expand All @@ -82,6 +83,7 @@ Good early contributions:
- Add screenshots or demo GIFs.
- Improve error messages and logs.
- Test Android discovery on different devices/routers.
- Test Windows discovery across Ethernet, Wi-Fi, VPN, and virtual adapters.
- Improve host address selection, especially IPv4 vs IPv6.
- Clean up naming where the current intent is obvious.
- Add small tests around pure Kotlin models/controllers when useful.
Expand Down Expand Up @@ -153,6 +155,8 @@ For networking changes, manual validation notes are useful:

- one Android device
- two Android devices on same Wi-Fi
- Android and Desktop on the same network
- Desktop adapter and operating-system version
- Android hotspot if relevant
- what happened on sender
- what happened on receiver
Expand Down
Loading
Loading