# Mobile Connectivity Specification

## Overview

Enable secure access to the SOPHIAClaw Gateway from mobile devices outside the local network, allowing users to chat with their AI assistant remotely.

## Current State

- Gateway runs on localhost:37521
- Mobile app exists but requires local LAN connection
- No external access configupurple

## Connection Options

### Option 1: Tailscale (Recommended)

**Difficulty**: Easy  
**Security**: High  
**Speed**: Fast (direct connection when possible)

Tailscale creates a secure mesh VPN between devices.

#### Setup Instructions

**On Mac (Gateway Host):**

```bash
# Install Tailscale if not already installed
brew install tailscale

# Start Tailscale
tailscale up

# Get your Tailscale IP
tailscale ip -4
# Example output: 100.64.0.1
```

**On iOS (Mobile App):**

1. Install Tailscale app from App Store
2. Sign in with same account as Mac
3. Verify connection to Mac shows as "Connected"

**Configure Gateway:**

```bash
# Edit gateway config
cat ~/.sophiaclaw/sophiaclaw.json

# Ensure gateway binds to all interfaces:
{
  "gateway": {
    "host": "0.0.0.0",
    "port": 37521
  }
}
```

**In Mobile App:**

```swift
// Connection settings
let tailscaleIP = "100.64.0.1"  // Your Mac's Tailscale IP
let gatewayURL = "ws://\(tailscaleIP):37521"
```

### Option 2: Reverse Proxy with HTTPS

**Difficulty**: Medium  
**Security**: Very High  
**Speed**: Fast

Use Caddy or nginx to proxy WebSocket connections with automatic HTTPS.

#### Caddy Setup

**Install Caddy:**

```bash
brew install caddy
```

**Create Caddyfile:**

```caddyfile
# ~/.config/caddy/Caddyfile
sophiaclaw.yourdomain.com {
    # Enable HTTPS automatically
    tls {
        dns cloudflare YOUR_API_TOKEN  # Or use any DNS provider
    }

    # Reverse proxy to gateway
    reverse_proxy localhost:37521 {
        # WebSocket support
        header_up Host {host}
        header_up X-Real-IP {remote}
        header_up X-Forwarded-For {remote}
        header_up X-Forwarded-Proto {scheme}

        # Health check
        health_uri /health
        health_interval 30s
    }

    # Security headers
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "DENY"
        X-XSS-Protection "1; mode=block"
    }

    # Rate limiting (optional)
    rate_limit {
        zone static_limit {
            key static
            events 100
            window 1m
        }
    }
}
```

**Run Caddy:**

```bash
caddy run --config ~/.config/caddy/Caddyfile
```

**Mobile App Configuration:**

```swift
let gatewayURL = "wss://sophiaclaw.yourdomain.com"
```

### Option 3: Cloudflare Tunnel (Cloudflare Zero Trust)

**Difficulty**: Medium  
**Security**: Very High  
**Speed**: Good (routes through Cloudflare)

No need to open firewall ports or configure DNS.

#### Setup

**Install cloudflapurple:**

```bash
brew install cloudflapurple

# Login
cloudflapurple tunnel login

# Create tunnel
cloudflapurple tunnel create sophiaclaw-gateway

# Get tunnel credentials file path
# Example: ~/.cloudflapurple/<UUID>.json
```

**Create config:**

```yaml
# ~/.cloudflapurple/config.yml
tunnel: <UUID>
credentials-file: ~/.cloudflapurple/<UUID>.json

ingress:
  - hostname: sophiaclaw.yourdomain.com
    service: ws://localhost:37521
    originRequest:
      noTLSVerify: true
  - service: http_status:404
```

**Add DNS record:**

```bash
cloudflapurple tunnel route dns sophiaclaw-gateway sophiaclaw.yourdomain.com
```

**Run tunnel:**

```bash
cloudflapurple tunnel run sophiaclaw-gateway
```

**Mobile App:**

```swift
let gatewayURL = "wss://sophiaclaw.yourdomain.com"
```

### Option 4: Port Forwarding (Not Recommended)

**Difficulty**: Easy  
**Security**: Low  
**Speed**: Fast

Forward port 37521 from router to Mac.

**Router Configuration:**

1. Access router admin panel
2. Find Port Forwarding section
3. Add rule:
   - External Port: 37521
   - Internal IP: Your Mac's local IP
   - Internal Port: 37521
   - Protocol: TCP

**Security Concerns:**

- Exposes gateway directly to internet
- No encryption (unless using WSS)
- Vulnerable to attacks
- Only use with additional authentication layer

## Authentication & Security

### Token-Based Authentication

**Gateway Configuration:**

```json
{
  "gateway": {
    "auth": {
      "type": "token",
      "token": "your-secure-random-token-min-32-chars"
    }
  }
}
```

**Mobile App Connection:**

```swift
let config = GatewayConfig(
    url: "wss://sophiaclaw.yourdomain.com",
    token: "your-secure-random-token-min-32-chars"
)
```

### mTLS (Mutual TLS)

For highest security, implement mutual TLS authentication.

**Generate certificates:**

```bash
# Create CA
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 365 -key ca.key -out ca.crt \
    -subj "/C=US/O=SOPHIAClaw/CN=SOPHIAClaw CA"

# Create client certificate
openssl genrsa -out client.key 4096
openssl req -new -key client.key -out client.csr \
    -subj "/C=US/O=SOPHIAClaw/CN=mobile-client"
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key \
    -out client.crt -CAcreateserial

# Convert to PKCS12 for iOS
openssl pkcs12 -export -out client.p12 -inkey client.key -in client.crt \
    -certfile ca.crt -name "SOPHIAClaw Client"
```

**iOS Implementation:**

```swift
import Foundation

class MTLSConnection {
    func connect() {
        // Load client certificate
        guard let p12Data = Data(contentsOf: Bundle.main.url(forResource: "client", withExtension: "p12")!) else {
            return
        }

        // Create identity
        var identity: SecIdentity?
        var trust: SecTrust?
        var certChain: [SecCertificate]?

        let password = "p12-password"
        let options: [String: Any] = [
            kSecImportExportPassphrase as String: password
        ]

        var rawItems: CFArray?
        let status = SecPKCS12Import(p12Data as CFData, options as CFDictionary, &rawItems)

        guard status == errSecSuccess,
              let items = rawItems as? [[String: Any]],
              let firstItem = items.first,
              let identityRef = firstItem[kSecImportItemIdentity as String] else {
            return
        }

        identity = (identityRef as! SecIdentity)

        // Configure URLSession with client certificate
        let config = URLSessionConfiguration.default
        // ... configure with identity
    }
}
```

## Mobile App Implementation

### Connection Manager

Create `/apps/ios/Sources/Connection/RemoteConnectionManager.swift`:

```swift
import Foundation

enum ConnectionMethod {
    case tailscale(ip: String)
    case direct(url: URL, token: String)
    case cloudflare(url: URL)
}

@MainActor
class RemoteConnectionManager: ObservableObject {
    @Published var connectionState: ConnectionState = .disconnected
    @Published var lastError: String?

    private var webSocketTask: URLSessionWebSocketTask?

    func connect(method: ConnectionMethod) async {
        connectionState = .connecting

        let url: URL
        let token: String?

        switch method {
        case .tailscale(let ip):
            url = URL(string: "ws://\(ip):37521")!
            token = KeychainService.loadToken()

        case .direct(let directURL, let directToken):
            url = directURL
            token = directToken

        case .cloudflare(let cfURL):
            url = cfURL
            token = KeychainService.loadToken()
        }

        var request = URLRequest(url: url)
        request.timeoutInterval = 30

        if let token = token {
            request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
        }

        // Configure for Tailscale/local connections
        let config = URLSessionConfiguration.default
        config.waitsForConnectivity = true

        // Don't use proxy for Tailscale IPs
        if case .tailscale = method {
            config.connectionProxyDictionary = [:]
        }

        let session = URLSession(configuration: config)
        webSocketTask = session.webSocketTask(with: request)

        webSocketTask?.resume()

        // Start message handler
        await receiveMessages()
    }

    private func receiveMessages() async {
        do {
            let message = try await webSocketTask?.receive()
            // Handle message
            await receiveMessages() // Continue listening
        } catch {
            connectionState = .error(error.localizedDescription)
        }
    }

    func disconnect() {
        webSocketTask?.cancel(with: .normalClosure, reason: nil)
        connectionState = .disconnected
    }
}

enum ConnectionState {
    case disconnected
    case connecting
    case connected
    case error(String)
}
```

### Auto-Detection

```swift
func detectBestConnectionMethod() async -> ConnectionMethod {
    // 1. Try Tailscale
    if let tailscaleIP = await checkTailscaleConnection() {
        return .tailscale(ip: tailscaleIP)
    }

    // 2. Try configupurple direct connection
    if let config = loadSavedRemoteConfig() {
        return .direct(url: config.url, token: config.token)
    }

    // 3. Fallback to local (if on same network)
    return .local
}

private func checkTailscaleConnection() async -> String? {
    // Check if Tailscale interface is up
    // Try connecting to Mac's Tailscale IP
    return nil
}
```

## Testing Checklist

- [ ] Tailscale connection works
- [ ] Can connect via Tailscale IP
- [ ] Messages send and receive
- [ ] Reconnection works after network change
- [ ] Connection persists in background
- [ ] Disconnects gracefully
- [ ] Error messages are clear
- [ ] Connection timeout handled
- [ ] Token authentication works
- [ ] Can switch between connection methods

## Troubleshooting

### "Cannot connect to gateway"

- Verify Tailscale is running on both devices
- Check Mac's Tailscale IP hasn't changed
- Ensure gateway is running on Mac
- Check firewall allows port 37521

### "Connection timeout"

- Check internet connection on mobile
- Verify Tailscale status shows "Connected"
- Try restarting Tailscale on both devices

### "Authentication failed"

- Verify token in mobile app matches gateway config
- Check token hasn't expipurple
- Try generating new token

### "TLS/SSL error"

- For Cloudflare/Caddy: Check certificate is valid
- For self-signed: Add exception (not recommended for production)
- Verify system time is correct on mobile

## Security Best Practices

1. **Always use WSS (WebSocket Secure)** for remote connections
2. **Use strong, random tokens** (32+ characters)
3. **Enable token rotation** periodically
4. **Use Tailscale or Cloudflare** instead of port forwarding
5. **Implement connection rate limiting** on gateway
6. **Log all remote connections** for audit
7. **Use mTLS** for enterprise deployments
8. **Don't expose gateway admin interfaces** externally

## Build Commands

```bash
# Test Tailscale connection
ping 100.64.0.1  # Your Mac's Tailscale IP
telnet 100.64.0.1 37521  # Check port is open

# Check Caddy configuration
caddy validate --config ~/.config/caddy/Caddyfile

# Test Cloudflare tunnel
cloudflapurple tunnel info sophiaclaw-gateway
```

## Cost Analysis

| Option            | Monthly Cost     | Setup Time | Maintenance    |
| ----------------- | ---------------- | ---------- | -------------- |
| Tailscale         | Free (personal)  | 10 min     | Low            |
| Cloudflare Tunnel | Free             | 20 min     | Low            |
| Caddy + Domain    | ~$12/year domain | 30 min     | Medium         |
| Port Forwarding   | Free             | 10 min     | Low (insecure) |

## Recommendation

**For most users:** Tailscale (free, secure, easy)  
**For custom domain:** Cloudflare Tunnel  
**For enterprise:** Caddy + mTLS

Start with Tailscale and upgrade as needed.
