{"id":51296775,"url":"https://github.com/off-grid-ai/sync","last_synced_at":"2026-06-30T15:02:59.539Z","repository":{"id":335432165,"uuid":"1145152672","full_name":"off-grid-ai/sync","owner":"off-grid-ai","description":"Privacy-First Local Network File \u0026 Text Sharing Between Android and macOS","archived":false,"fork":false,"pushed_at":"2026-06-19T15:21:53.000Z","size":1111,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-19T17:18:26.078Z","etag":null,"topics":["android-to-mac","file-share","local","mac-to-android","privacy","private-file-share"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/off-grid-ai.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-29T13:46:51.000Z","updated_at":"2026-06-19T15:21:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/off-grid-ai/sync","commit_stats":null,"previous_names":["alichherawalla/easy-share-across-mobile-and-desktop","off-grid-ai/sync"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/off-grid-ai/sync","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/off-grid-ai%2Fsync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/off-grid-ai%2Fsync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/off-grid-ai%2Fsync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/off-grid-ai%2Fsync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/off-grid-ai","download_url":"https://codeload.github.com/off-grid-ai/sync/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/off-grid-ai%2Fsync/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34971633,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-30T02:00:05.919Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android-to-mac","file-share","local","mac-to-android","privacy","private-file-share"],"created_at":"2026-06-30T15:02:45.750Z","updated_at":"2026-06-30T15:02:59.522Z","avatar_url":"https://github.com/off-grid-ai.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EasyShare\n\n**Truly Private, Fully Offline File \u0026 Text Sharing**\n\nEasyShare is a cross-platform application that enables seamless file and text sharing between your devices over your local network. No cloud servers, no accounts, no internet required — just secure, direct device-to-device communication.\n\n**Not a single packet leaves your device outside your local network.** No analytics. No telemetry. No tracking. No phone-home. The code is open source — verify it yourself.\n\n---\n\n\u003e **Status: work in progress.** This repo is EasyShare, the app that's becoming **Off Grid Sync**, part of the [Off Grid](https://github.com/off-grid-ai) ecosystem. What's documented below is what works today. Expect changes as the rebrand and integration land.\n\n## Where Off Grid Sync is headed\n\nEasyShare solved a specific itch: moving sensitive text and files between my own devices without a third party in the middle. Think AirDrop, but Android to macOS and back. Private, open source, no data collected. I built it because I needed it.\n\nIt's becoming Off Grid Sync: the backbone that moves everything between your devices, privately, with no cloud in between. In the Off Grid ecosystem, Sync is what lets Off Grid Mobile in your pocket and Off Grid Desktop on your laptop share one context that stays on your devices and never touches a server you don't control.\n\nToday it does the private file and text transfer documented below. The move to a general device-to-device backbone is in progress, so parts of this will change before they settle.\n\n- Off Grid Mobile, on-device intelligence in your pocket: https://github.com/off-grid-ai/mobile\n- Off Grid Desktop, the intelligence layer for your laptop: https://github.com/off-grid-ai/desktop\n- The full thesis: https://github.com/off-grid-ai\n\n---\n\n### Supported Platforms\n\n| Platform | Status |\n|----------|--------|\n| **Android** | Available now |\n| **macOS** | Available now |\n| **iOS** | Coming soon |\n| **Windows** | Coming soon |\n\n---\n\n## Screenshots\n\n### Android App\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"screenshots/android-devices.jpg\" alt=\"Android Devices\" width=\"300\"/\u003e\n  \u0026nbsp;\u0026nbsp;\u0026nbsp;\n  \u003cimg src=\"screenshots/android-connected.jpg\" alt=\"Android Connected\" width=\"300\"/\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cem\u003eLeft: Discover and connect to paired devices \u0026nbsp;|\u0026nbsp; Right: Send files and text while connected\u003c/em\u003e\n\u003c/p\u003e\n\n### macOS App\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"screenshots/mac-devices.png\" alt=\"macOS Devices\" width=\"400\"/\u003e\n  \u0026nbsp;\u0026nbsp;\n  \u003cimg src=\"screenshots/mac-connected.png\" alt=\"macOS Connected\" width=\"400\"/\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"screenshots/mac-file-received.png\" alt=\"macOS File Received\" width=\"500\"/\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cem\u003eDevice pairing, real-time file transfers, and transfer history\u003c/em\u003e\n\u003c/p\u003e\n\n---\n\n## Table of Contents\n\n- [Screenshots](#screenshots)\n- [Why EasyShare?](#why-easyshare)\n- [Features](#features)\n- [Privacy \u0026 Security](#privacy--security)\n- [Architecture Overview](#architecture-overview)\n- [Requirements](#requirements)\n- [Installation](#installation)\n  - [From Pre-built Releases](#from-pre-built-releases)\n  - [Building From Source](#building-from-source)\n- [Running in Development](#running-in-development)\n- [Building for Production](#building-for-production)\n- [Configuration](#configuration)\n- [How It Works](#how-it-works)\n- [Project Structure](#project-structure)\n- [Troubleshooting](#troubleshooting)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Why EasyShare?\n\nMost file-sharing solutions require cloud accounts, internet connectivity, or third-party servers that can access your data. EasyShare takes a fundamentally different approach:\n\n| Traditional Solutions | EasyShare |\n|-----------------------|-----------|\n| Requires internet | Works completely offline |\n| Data passes through cloud servers | Direct device-to-device only |\n| Requires account creation | No accounts, no sign-ups |\n| Company can access your files | Zero knowledge—only you have your data |\n| Subscription fees | Free and open source |\n| Proprietary protocols | Transparent, auditable code |\n\n**EasyShare is designed for users who value their privacy and want full control over their data.**\n\n---\n\n## Features\n\n### Fast Transfers for Any File Size\n- **HTTP-Accelerated Large Files** — Files over 5MB transfer via HTTP streaming, bypassing bridge overhead for native-speed throughput\n- **Handles Massive Files** — Send 400MB, 1GB, or larger files without memory issues thanks to streaming I/O\n- **Small Files Stay Simple** — Files under 5MB use efficient chunk-based transfer over the existing TCP connection\n- **Real-Time Progress** — Per-file progress bars with percentage, speed, and duration tracking\n- **Integrity Verified** — SHA-512 streaming checksums verify every byte without loading files into memory\n\n### Instant Device Discovery\n- **Zero-Configuration Networking** — Devices find each other automatically using mDNS/Bonjour with no manual IP entry\n- **Periodic Re-Scanning** — Automatic discovery refresh every 15 seconds catches devices that join the network late\n- **Works Instantly** — Devices typically appear within 1-2 seconds of launching the app\n\n### Multi-File Transfers with Queue\n- **Batch File Sending** — Select multiple files at once from the file picker or drag and drop on desktop\n- **Visual Transfer Queue** — See every file's status: queued, transferring with progress bar, completed, or failed\n- **Sequential Reliability** — Files transfer one at a time to avoid overwhelming the connection, with the queue showing overall progress\n\n### Secure Pairing \u0026 Encryption\n- **Passphrase-Based Pairing** — Simple passphrase entry on both devices establishes a shared secret\n- **Challenge-Response Protocol** — Passphrases are never sent over the network; only cryptographic proofs\n- **End-to-End Encryption** — All data encrypted using NaCl secretbox (XSalsa20-Poly1305)\n- **Remember Paired Devices** — Reconnect instantly to previously paired devices without re-pairing\n\n### Truly Private \u0026 Offline\n- **Zero External Network Traffic** — Not a single packet leaves your local network. No analytics, no crash reporting, no update checks, no DNS lookups to external servers. Nothing.\n- **No Internet Required** — Works in airplane mode, isolated networks, or anywhere without internet\n- **No Cloud Services** — Zero dependency on any external servers or services\n- **No Accounts** — No registration, no email, no personal information collected\n- **No Telemetry** — No analytics, no tracking, no usage data collection of any kind\n- **Fully Auditable** — Open source code you can inspect to verify every claim on this page\n\n### Connection Resilience\n- **Auto-Reconnect** — If the connection drops (e.g., Android backgrounding during file selection), the app automatically reconnects when returning to foreground\n- **Keepalive System** — Application-level ping/pong with 2-minute timeout, paused during transfers and when the app is backgrounded\n- **Connection Retry** — Initial connections retry up to 3 times with backoff for reliability on flaky networks\n\n### User Experience\n- **Modern Dark UI** — Beautiful, minimal interface with smooth animations on both platforms\n- **Cross-Platform** — Native apps for both Android and macOS with shared TypeScript core\n- **Transfer History** — View recent transfers with speed, duration, and file details\n- **Auto-Accept Option** — Optionally auto-accept transfers from trusted devices\n- **Configurable Save Location** — Choose where received files are saved\n\n---\n\n## Privacy \u0026 Security\n\nEasyShare was built with security as a foundational principle, not an afterthought. Every network operation is confined to your local network — there are no analytics SDKs, no crash reporters, no auto-updaters, and no external API calls anywhere in the codebase.\n\n### Encryption\n\nAll communication between devices is encrypted using **NaCl secretbox (XSalsa20-Poly1305)**, a modern authenticated encryption algorithm that provides both confidentiality and integrity protection.\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                    ENCRYPTION DETAILS                       │\n├─────────────────────────────────────────────────────────────┤\n│  Algorithm:     XSalsa20-Poly1305 (NaCl secretbox)          │\n│  Key Derivation: PBKDF2 with 100,000 iterations             │\n│  Salt:          Device ID (unique per device pair)          │\n│  Nonce:         Randomly generated per connection           │\n│  Key Size:      256-bit                                     │\n└─────────────────────────────────────────────────────────────┘\n```\n\n### Pairing Security\n\nWhen you pair two devices:\n1. You enter a shared passphrase on both devices\n2. Each device derives a cryptographic key using PBKDF2 (100,000 iterations)\n3. A challenge-response protocol verifies both devices have the same key\n4. The shared secret is stored locally (never transmitted)\n5. **Passphrases are never sent over the network**\n\n### File Integrity\n\n- Small files are split into 64KB chunks for reliable transmission\n- Large files stream via HTTP at native speed with progress tracking\n- SHA-512 streaming checksums verify integrity without loading entire files into memory\n- Any corruption is detected and the transfer fails safely\n\n### Local Storage\n\n| Platform | Storage Method | Security |\n|----------|---------------|----------|\n| macOS | electron-store | OS-level encryption |\n| Android | AsyncStorage | Platform secure storage |\n\n**What's stored locally:**\n- Device name and ID\n- Paired device information (encrypted shared secrets)\n- Transfer history (last 100 transfers)\n- App preferences\n\n**What's never stored:**\n- Passphrases (only used for key derivation)\n- File contents after transfer\n- Any personal information\n\n### Network Security\n\n- **Local network only** — Cannot communicate over the internet\n- **Dynamic ports** — Each connection uses a randomly assigned port\n- **No listening on fixed ports** — Reduces attack surface\n- **Connection timeout** — Idle connections are terminated\n\n---\n\n## Architecture Overview\n\nEasyShare uses a monorepo architecture with three packages:\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│                     ARCHITECTURE                            │\n├─────────────────────────────────────────────────────────────┤\n│                                                             │\n│   ┌─────────────────┐         ┌─────────────────┐          │\n│   │   macOS App     │◄───────►│   Android App   │          │\n│   │   (Electron)    │  Local  │ (React Native)  │          │\n│   │                 │ Network │                 │          │\n│   └────────┬────────┘         └────────┬────────┘          │\n│            │                           │                    │\n│            └───────────┬───────────────┘                    │\n│                        │                                    │\n│              ┌─────────▼─────────┐                          │\n│              │   Shared Core     │                          │\n│              │   (TypeScript)    │                          │\n│              │                   │                          │\n│              │ • Crypto          │                          │\n│              │ • Protocol        │                          │\n│              │ • Discovery       │                          │\n│              │ • Pairing         │                          │\n│              │ • Transfer        │                          │\n│              └───────────────────┘                          │\n│                                                             │\n└─────────────────────────────────────────────────────────────┘\n```\n\n### Technology Stack\n\n| Component | Technology |\n|-----------|------------|\n| **Shared Core** | TypeScript 5.3, tweetnacl (encryption), js-sha512 (streaming checksums), tsup |\n| **Desktop (macOS)** | Electron 33, React 18, Tailwind CSS, Framer Motion |\n| **Mobile (Android)** | React Native 0.73, NativeWind, react-native-tcp-socket |\n| **Build Tools** | Vite, Metro, electron-builder |\n| **Discovery** | bonjour-service (macOS), react-native-zeroconf (Android) |\n\n---\n\n## Requirements\n\n### General Requirements\n\n| Requirement | Version |\n|-------------|---------|\n| Node.js | 18.0.0 or higher |\n| npm | 9.0.0 or higher |\n| Git | Any recent version |\n\n### macOS Development\n\n| Requirement | Details |\n|-------------|---------|\n| macOS | 10.15 (Catalina) or higher |\n| Xcode Command Line Tools | `xcode-select --install` |\n| Xcode (optional) | For code signing and notarization |\n\n### Android Development\n\n| Requirement | Details |\n|-------------|---------|\n| Android Studio | Latest stable version |\n| JDK | Version 17 |\n| Android SDK | API Level 24+ (Android 7.0+) |\n| Android device or emulator | With USB debugging enabled |\n\n---\n\n## Installation\n\n### From Pre-built Releases\n\n*(Coming soon — check the Releases page)*\n\n### Building From Source\n\n#### Step 1: Clone the Repository\n\n```bash\ngit clone https://github.com/off-grid-ai/sync.git\ncd sync\n```\n\n#### Step 2: Install Dependencies\n\n```bash\n# Install all dependencies for the monorepo\nnpm install\n```\n\nThis will install dependencies for:\n- Root workspace\n- `packages/shared` — TypeScript core library\n- `packages/desktop` — Electron app\n- `packages/mobile` — React Native app\n\n#### Step 3: Build the Shared Core\n\nThe shared core must be built before running either platform:\n\n```bash\nnpm run build:shared\n```\n\nThis compiles the TypeScript core library that both platforms depend on.\n\n#### Step 4: Continue to Platform-Specific Instructions\n\n- [Running the Desktop App](#desktop-macos)\n- [Running the Mobile App](#mobile-android)\n\n---\n\n## Running in Development\n\n### Desktop (macOS)\n\nStart the Electron app in development mode with hot reload:\n\n```bash\nnpm run dev:desktop\n```\n\nThis will:\n1. Start the Vite development server for the renderer process\n2. Launch the Electron app\n3. Enable hot module replacement for rapid development\n\nThe app window will open automatically. Changes to React components will hot reload instantly.\n\n**Development shortcuts:**\n- `Cmd + Option + I` — Open Developer Tools\n- `Cmd + R` — Force reload the app\n\n### Mobile (Android)\n\nRunning the Android app requires two terminal windows:\n\n**Terminal 1 — Start Metro Bundler:**\n\n```bash\nnpm run dev:mobile\n```\n\nThis starts the Metro bundler on port 8082.\n\n**Terminal 2 — Run on Device/Emulator:**\n\n```bash\ncd packages/mobile\nnpm run android\n```\n\nThis will:\n1. Build the Android app\n2. Install it on your connected device or running emulator\n3. Launch the app\n\n**Development notes:**\n- Ensure USB debugging is enabled on your Android device\n- For emulator: Start it from Android Studio before running the command\n- Shake the device to open the React Native developer menu\n- Press `R` twice in the Metro terminal to reload the app\n\n**Connecting a physical device:**\n\n```bash\n# List connected devices\nadb devices\n\n# If device shows as \"unauthorized\", check your phone for the USB debugging prompt\n```\n\n---\n\n## Building for Production\n\n### Desktop (macOS)\n\n#### Build the Application\n\n```bash\nnpm run build:desktop\n```\n\nThis creates an optimized production build in `packages/desktop/dist/`.\n\n#### Package as DMG/ZIP\n\n```bash\ncd packages/desktop\nnpm run package\n```\n\nThis creates distributable files in `packages/desktop/release/`:\n- `.dmg` — Disk image for macOS distribution\n- `.zip` — Compressed archive\n\n**Build configuration** is in `packages/desktop/package.json` under the `build` key:\n\n```json\n{\n  \"build\": {\n    \"appId\": \"com.easyshare.desktop\",\n    \"productName\": \"EasyShare\",\n    \"mac\": {\n      \"category\": \"public.app-category.utilities\",\n      \"target\": [\"dmg\", \"zip\"]\n    }\n  }\n}\n```\n\n#### Code Signing (Optional)\n\nFor distribution outside the App Store, you'll need to sign and notarize the app:\n\n```bash\n# Set environment variables\nexport APPLE_ID=\"your-apple-id@email.com\"\nexport APPLE_ID_PASSWORD=\"app-specific-password\"\nexport APPLE_TEAM_ID=\"your-team-id\"\n\n# Build with signing\ncd packages/desktop\nnpm run package\n```\n\n### Mobile (Android)\n\n#### Build Release APK\n\n```bash\ncd packages/mobile\nnpm run build:android\n```\n\nThe APK will be created at:\n```\npackages/mobile/android/app/build/outputs/apk/release/app-release.apk\n```\n\n#### Signing the APK\n\nFor production releases, you need to sign the APK:\n\n1. **Generate a keystore** (one-time):\n   ```bash\n   keytool -genkeypair -v -storetype PKCS12 -keystore easyshare.keystore -alias easyshare -keyalg RSA -keysize 2048 -validity 10000\n   ```\n\n2. **Configure signing** in `packages/mobile/android/gradle.properties`:\n   ```properties\n   MYAPP_UPLOAD_STORE_FILE=easyshare.keystore\n   MYAPP_UPLOAD_KEY_ALIAS=easyshare\n   MYAPP_UPLOAD_STORE_PASSWORD=your-store-password\n   MYAPP_UPLOAD_KEY_PASSWORD=your-key-password\n   ```\n\n3. **Build signed APK**:\n   ```bash\n   cd packages/mobile/android\n   ./gradlew assembleRelease\n   ```\n\n---\n\n## Configuration\n\n### Application Settings\n\nBoth platforms store settings locally. Available settings include:\n\n| Setting | Description | Default |\n|---------|-------------|---------|\n| Device Name | How your device appears to others | System hostname |\n| Auto-Accept | Automatically accept from paired devices | Off |\n| Save Directory | Where received files are saved | Downloads folder |\n| Notifications | Show transfer notifications | On |\n\n### Build Configuration\n\n#### Shared Package (`packages/shared/tsconfig.json`)\n\n```json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"ESNext\",\n    \"strict\": true,\n    \"declaration\": true,\n    \"outDir\": \"./dist\"\n  }\n}\n```\n\n#### Desktop Package (`packages/desktop/electron.vite.config.ts`)\n\nKey configurations:\n- Path aliases for clean imports\n- Separate configs for main, preload, and renderer\n- Tailwind CSS processing\n\n#### Mobile Package (`packages/mobile/metro.config.js`)\n\nKey configurations:\n- Monorepo workspace support\n- Custom port (8082) to avoid conflicts\n- Path resolution for shared package\n\n### Network Configuration\n\nEasyShare uses the following network resources:\n\n| Resource | Purpose | Configurable |\n|----------|---------|--------------|\n| mDNS (UDP 5353) | Device discovery | No |\n| TCP (dynamic port) | Control messages, small file transfer | No |\n| HTTP (dynamic port) | Large file transfer (\u003e= 5MB) | No |\n| Service name: `_easyshare._tcp` | mDNS service type | No |\n\n**Firewall requirements:**\n- Allow incoming/outgoing UDP on port 5353 (mDNS)\n- Allow incoming/outgoing TCP on ephemeral ports (49152-65535)\n\n---\n\n## How It Works\n\n### Device Discovery\n\n```\n┌─────────────┐                              ┌─────────────┐\n│   Device A  │                              │   Device B  │\n└──────┬──────┘                              └──────┬──────┘\n       │                                            │\n       │  1. Broadcast mDNS: \"_easyshare._tcp\"      │\n       │────────────────────────────────────────────►\n       │                                            │\n       │  2. mDNS Response: \"DeviceB @ 192.168.1.5\" │\n       │◄────────────────────────────────────────────\n       │                                            │\n       │  3. Devices now visible to each other      │\n       ▼                                            ▼\n```\n\nBoth apps advertise themselves on the local network using mDNS (Bonjour on macOS, NSD on Android) with service type `_easyshare._tcp`. Each service includes:\n- Device ID (unique identifier)\n- Device name\n- Platform (macos/android)\n- App version\n\n### Pairing Protocol\n\n```\n┌─────────────┐                              ┌─────────────┐\n│   Device A  │                              │   Device B  │\n└──────┬──────┘                              └──────┬──────┘\n       │                                            │\n       │  User enters passphrase on both devices    │\n       │                                            │\n       │  1. PAIR_REQUEST (Device A info)           │\n       │────────────────────────────────────────────►\n       │                                            │\n       │  2. PAIR_CHALLENGE (random challenge)      │\n       │◄────────────────────────────────────────────\n       │                                            │\n       │     Both derive key: PBKDF2(passphrase,    │\n       │     deviceId, 100000 iterations)           │\n       │                                            │\n       │  3. PAIR_RESPONSE (signed challenge)       │\n       │────────────────────────────────────────────►\n       │                                            │\n       │     Device B verifies signature            │\n       │                                            │\n       │  4. PAIR_CONFIRM (success + counter-proof) │\n       │◄────────────────────────────────────────────\n       │                                            │\n       │  Devices are now paired!                   │\n       │  Shared secret stored locally              │\n       ▼                                            ▼\n```\n\n### Communication Protocol\n\nAfter pairing:\n- TCP socket connection on dynamically assigned port\n- Messages are length-prefixed JSON with binary data for files\n- All communication encrypted with NaCl secretbox (XSalsa20-Poly1305)\n- Maximum message size: 10MB\n\n### File Transfer Protocol\n\nEasyShare uses two transfer modes, chosen automatically based on file size:\n\n**Small files (\u003c 5MB) — Chunk-based over TCP:**\n\n```\n┌─────────────┐                              ┌─────────────┐\n│   Sender    │                              │   Receiver  │\n└──────┬──────┘                              └──────┬──────┘\n       │                                            │\n       │  1. FILE_REQUEST (name, size, checksum)    │\n       │────────────────────────────────────────────►\n       │                                            │\n       │  2. FILE_ACCEPT                            │\n       │◄────────────────────────────────────────────\n       │                                            │\n       │  3. FILE_CHUNK (64KB chunks)               │\n       │────────────────────────────────────────────►\n       │         ... repeat for all chunks ...      │\n       │                                            │\n       │  4. FILE_COMPLETE (checksum)               │\n       │────────────────────────────────────────────►\n       ▼                                            ▼\n```\n\n**Large files (\u003e= 5MB) — HTTP streaming:**\n\n```\n┌─────────────┐                              ┌─────────────┐\n│   Sender    │                              │   Receiver  │\n└──────┬──────┘                              └──────┬──────┘\n       │                                            │\n       │  1. FILE_REQUEST (name, size, httpUrl)     │\n       │────────────────────────────────────────────►\n       │                                            │\n       │  2. FILE_ACCEPT (uploadUrl)                │\n       │◄────────────────────────────────────────────\n       │                                            │\n       │  3. HTTP transfer (native speed)           │\n       │────────── file data via HTTP ─────────────►\n       │                                            │\n       │  4. FILE_ACK (success/failure)             │\n       │◄────────────────────────────────────────────\n       ▼                                            ▼\n```\n\nThe HTTP path avoids the React Native bridge bottleneck for large files, achieving native transfer speeds. Both paths verify file integrity — small files use full SHA-512 checksums, large files verify by file size to avoid reading the entire file back through the bridge.\n\n---\n\n## Project Structure\n\n```\neasy-android-to-mac/\n├── packages/\n│   ├── shared/                      # Shared TypeScript core\n│   │   ├── src/\n│   │   │   ├── types/               # TypeScript type definitions\n│   │   │   │   └── index.ts         # Device, Message, Transfer types\n│   │   │   ├── crypto/              # Cryptographic utilities\n│   │   │   │   ├── encryption.ts    # XSalsa20-Poly1305 encrypt/decrypt\n│   │   │   │   ├── keys.ts          # PBKDF2 key derivation\n│   │   │   │   └── checksum.ts      # SHA-512 file checksums\n│   │   │   ├── discovery/           # mDNS protocol helpers\n│   │   │   │   └── index.ts         # Service type, TXT records\n│   │   │   ├── pairing/             # Device pairing logic\n│   │   │   │   ├── state.ts         # Pairing state machine\n│   │   │   │   └── challenge.ts     # Challenge-response protocol\n│   │   │   ├── transfer/            # File/text transfer\n│   │   │   │   ├── chunker.ts       # File chunking logic\n│   │   │   │   └── progress.ts      # Progress tracking\n│   │   │   └── protocol/            # Message protocol\n│   │   │       ├── messages.ts      # Message types and handlers\n│   │   │       └── framing.ts       # Length-prefixed framing\n│   │   ├── package.json\n│   │   └── tsconfig.json\n│   │\n│   ├── desktop/                     # Electron macOS app\n│   │   ├── src/\n│   │   │   ├── main/                # Main process (Node.js)\n│   │   │   │   ├── index.ts         # App entry point\n│   │   │   │   ├── connection.ts    # TCP connection handling\n│   │   │   │   ├── discovery.ts     # Bonjour/mDNS integration\n│   │   │   │   └── storage.ts       # Persistent storage\n│   │   │   ├── preload/             # Preload scripts (IPC bridge)\n│   │   │   │   └── index.ts         # Secure API exposure\n│   │   │   └── renderer/            # Renderer process (React)\n│   │   │       ├── App.tsx          # Root component\n│   │   │       ├── components/      # UI components\n│   │   │       ├── hooks/           # React hooks\n│   │   │       └── styles/          # Tailwind CSS\n│   │   ├── electron.vite.config.ts  # Build configuration\n│   │   ├── tailwind.config.js       # Tailwind theme\n│   │   └── package.json\n│   │\n│   └── mobile/                      # React Native Android app\n│       ├── src/\n│       │   ├── screens/             # App screens\n│       │   │   ├── DiscoveryScreen.tsx\n│       │   │   ├── ConnectedScreen.tsx\n│       │   │   ├── HistoryScreen.tsx\n│       │   │   └── SettingsScreen.tsx\n│       │   ├── components/          # Shared components\n│       │   ├── hooks/               # Custom hooks\n│       │   │   ├── useDiscovery.ts  # mDNS discovery\n│       │   │   ├── useConnection.ts # TCP connection\n│       │   │   └── useStorage.ts    # AsyncStorage\n│       │   └── native/              # Native module bridges\n│       ├── android/                 # Android native code\n│       ├── App.tsx                  # Root component\n│       ├── metro.config.js          # Metro bundler config\n│       └── package.json\n│\n├── package.json                     # Monorepo workspace config\n├── tsconfig.base.json               # Shared TypeScript config\n└── README.md                        # This file\n```\n\n---\n\n## Troubleshooting\n\n### General Issues\n\n#### Devices not discovering each other\n\n**Symptoms:** Devices on the same network don't appear in the discovery list.\n\n**Solutions:**\n1. **Check network:** Ensure both devices are on the same Wi-Fi network (not using cellular or different VLANs)\n2. **Firewall:** Allow mDNS traffic (UDP port 5353) in your firewall\n3. **Router isolation:** Disable \"AP Isolation\" or \"Client Isolation\" in router settings\n4. **Restart discovery:** Close and reopen the app on both devices\n\n#### Pairing fails\n\n**Symptoms:** Devices see each other but pairing doesn't complete.\n\n**Solutions:**\n1. **Passphrase mismatch:** Ensure the exact same passphrase is entered on both devices (case-sensitive)\n2. **Timing:** Enter the passphrase on both devices within 30 seconds\n3. **Retry:** Cancel and try pairing again\n\n#### File transfer stuck or fails\n\n**Symptoms:** Transfer starts but doesn't complete or shows an error.\n\n**Solutions:**\n1. **File size:** For very large files, ensure stable network connection\n2. **Disk space:** Ensure receiving device has enough storage space\n3. **Firewall:** Allow TCP traffic on ephemeral ports (49152-65535)\n4. **Retry:** Cancel and attempt the transfer again\n\n### macOS-Specific Issues\n\n#### App won't open (unidentified developer)\n\n**Solution:** Right-click the app, select \"Open\", then click \"Open\" in the dialog.\n\nOr via Terminal:\n```bash\nxattr -cr /Applications/EasyShare.app\n```\n\n#### mDNS not working\n\n**Solution:** Ensure Bonjour is enabled:\n```bash\n# Check if mDNSResponder is running\nsudo launchctl list | grep mDNS\n```\n\n### Android-Specific Issues\n\n#### App crashes on startup\n\n**Solutions:**\n1. **Clear app data:** Settings → Apps → EasyShare → Clear Data\n2. **Reinstall:** Uninstall and reinstall the app\n3. **Check Android version:** Requires Android 7.0 (API 24) or higher\n\n#### Cannot find devices\n\n**Solutions:**\n1. **Location permission:** Grant location permission (required for Wi-Fi scanning on Android 10+)\n2. **Wi-Fi:** Ensure Wi-Fi is enabled and connected\n3. **Battery optimization:** Disable battery optimization for EasyShare\n\n### Development Issues\n\n#### `npm install` fails\n\n**Solutions:**\n```bash\n# Clear npm cache\nnpm cache clean --force\n\n# Delete node_modules and lock file\nrm -rf node_modules package-lock.json\nrm -rf packages/*/node_modules\n\n# Reinstall\nnpm install\n```\n\n#### Shared package not found\n\n**Solutions:**\n```bash\n# Rebuild the shared package\nnpm run build:shared\n\n# If still failing, check the symlink\nls -la packages/desktop/node_modules/@easyshare\n```\n\n#### Metro bundler port conflict\n\n**Solution:** The mobile package uses port 8082 by default. If it conflicts:\n```bash\n# Kill process on port 8082\nlsof -ti:8082 | xargs kill -9\n\n# Or use a different port\nnpx react-native start --port 8083\n```\n\n#### Android build fails with Java errors\n\n**Solutions:**\n```bash\n# Ensure JDK 17 is installed and active\njava -version  # Should show version 17\n\n# Set JAVA_HOME if needed\nexport JAVA_HOME=$(/usr/libexec/java_home -v 17)\n\n# Clean Android build\ncd packages/mobile/android\n./gradlew clean\n```\n\n---\n\n## Contributing\n\nContributions are welcome! Please follow these guidelines:\n\n### Development Setup\n\n1. Fork the repository\n2. Clone your fork\n3. Create a feature branch: `git checkout -b feature/your-feature`\n4. Make your changes\n5. Run type checking: `npm run typecheck`\n6. Commit with clear messages\n7. Push and create a Pull Request\n\n### Code Style\n\n- TypeScript strict mode is enforced\n- Use functional React components with hooks\n- Follow existing code patterns and naming conventions\n- Add types for all function parameters and returns\n\n### Testing Changes\n\nBefore submitting:\n```bash\n# Type check all packages\nnpm run typecheck\n\n# Build all packages\nnpm run build\n\n# Test on both platforms if possible\nnpm run dev:desktop\nnpm run dev:mobile\n```\n\n### Security Considerations\n\nIf you discover a security vulnerability:\n1. **Do not** open a public issue\n2. Email the maintainers directly with details\n3. Allow time for a fix before public disclosure\n\n---\n\n## Frequently Asked Questions\n\n### Is my data sent to any servers?\n\n**No.** EasyShare is completely local. Your files and text are transferred directly between your devices over your local network. No data ever leaves your network or touches any external servers. There are zero analytics, zero telemetry, and zero external network calls in the entire codebase — not even crash reporting or update checks. You can audit the source code yourself to verify this.\n\n### Does it work without internet?\n\n**Yes.** EasyShare only requires a local network connection. You can use it:\n- In airplane mode (with Wi-Fi enabled)\n- On networks without internet access\n- On isolated private networks\n\n### How secure is the encryption?\n\nEasyShare uses XSalsa20-Poly1305, the same encryption used by Signal, WhatsApp, and other security-focused applications. Keys are derived using PBKDF2 with 100,000 iterations, making brute-force attacks impractical.\n\n### Can someone on my network intercept my files?\n\nThe encryption protects against passive interception. Even if someone captures the network traffic, they cannot decrypt it without knowing your pairing passphrase.\n\n### Why do I need to enter a passphrase?\n\nThe passphrase is used to derive the encryption key. It ensures that only devices where you've entered the same passphrase can communicate. This prevents unauthorized devices from connecting to you.\n\n### Can I use this on iOS or Windows?\n\nCurrently, EasyShare supports **Android** and **macOS**. Support for **iOS** and **Windows** is coming soon — contributions are welcome!\n\n---\n\n## License\n\nLicensed under the **GNU Affero General Public License v3.0 (AGPL-3.0)**. See [LICENSE](LICENSE).\n\nIn short: you're free to use, study, modify, and share this code, but any version you distribute or run as a network service must also be released as open source under the same license. Copyright (C) 2026 Mohammed Ali Chherawalla.\n\n---\n\n## Acknowledgments\n\n- [tweetnacl](https://tweetnacl.js.org/) — Cryptographic library\n- [Electron](https://www.electronjs.org/) — Desktop framework\n- [React Native](https://reactnative.dev/) — Mobile framework\n- [Tailwind CSS](https://tailwindcss.com/) — Styling\n- [bonjour-service](https://github.com/onlxltd/bonjour-service) — mDNS for Node.js\n\n---\n\n\u003cp align=\"center\"\u003e\n  \u003cstrong\u003eBuilt with privacy in mind. Verify it yourself.\u003c/strong\u003e\u003cbr\u003e\n  Your data. Your devices. Your network. Not a single packet leaves.\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foff-grid-ai%2Fsync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Foff-grid-ai%2Fsync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Foff-grid-ai%2Fsync/lists"}