Tauri Plugin iOS Window

A Tauri plugin for opening modal windows with WebView on iOS, perfect for OAuth flows and external authentication.

Features

  • 🪟 Modal Presentation - Opens as popover on iPad, modal on iPhone
  • 🎨 Customizable UI - Set custom titles, follows Apple HIG
  • 📊 Progress Tracking - Built-in loading indicator and progress bar
  • 🔄 Navigation - Back/forward swipe gestures enabled
  • Native Feel - Follows iOS Human Interface Guidelines
  • 🔐 OAuth Ready - Perfect for Sign in with Apple, OAuth flows

Installation

# Install the plugin
npm install tauri-plugin-ios-window-api
# or
yarn add tauri-plugin-ios-window-api
# or
pnpm add tauri-plugin-ios-window-api

Add the plugin to your Tauri app's src-tauri/Cargo.toml:

[dependencies]
tauri-plugin-ios-window = { git = "https://github.com/yourusername/tauri-plugin-ios-window" }

Register the plugin in your Tauri app's src-tauri/src/lib.rs:

fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_ios_window::init())
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Usage

Basic Example

import { open } from "tauri-plugin-ios-window-api";

// Open a URL with default title
await open("https://example.com");

// Open with custom title
await open("https://example.com", "My Custom Title");

OAuth Flow Example (SplitFire Pattern)

This example shows how to integrate with an OAuth provider like Sign in with Apple:

import { open } from "tauri-plugin-ios-window-api";

async function signInWithApple() {
    try {
        // Your OAuth configuration
        const baseUrl = "https://splitfire.ai";
        const redirectUri = "your-app://oauth/callback";

        // Build OAuth URL
        const oauthUrl = `${baseUrl}/synch-apple/auth?redirect_uri=${encodeURIComponent(redirectUri)}`;

        // Open OAuth window
        await open(oauthUrl, "Sign in with Apple");

        // Handle the callback (implement based on your needs)
        // Option 1: Deep link handler
        // Option 2: Local server listener
        // Option 3: Custom URL scheme

    } catch (error) {
        console.error("OAuth failed:", error);
    }
}

Complete OAuth Implementation

import { open } from "tauri-plugin-ios-window-api";
import { listen } from "@tauri-apps/api/event";

async function startOAuthFlow(provider: "apple" | "spotify" | "youtube") {
    try {
        // Start local OAuth server (Rust backend)
        const port = await invoke("start_oauth_server");

        // Configure redirect
        const redirectUri = `http://localhost:${port}`;
        const baseUrl = "https://splitfire.ai";
        const oauthUrl = `${baseUrl}/synch-${provider}/auth?redirect_uri=${redirectUri}`;

        // Open OAuth window
        await open(oauthUrl, `Sign in with ${provider}`);

        // Listen for OAuth callback
        const unlisten = await listen("oauth-callback", (event) => {
            const { code, state } = event.payload;

            // Exchange code for access token
            invoke("exchange_oauth_token", { code, provider })
                .then((response) => {
                    console.log("OAuth successful:", response);
                    // Handle successful authentication
                })
                .catch((error) => {
                    console.error("Token exchange failed:", error);
                });
        });

    } catch (error) {
        console.error("OAuth flow failed:", error);
    }
}

API Reference

open(url: string, title?: string): Promise<void>

Opens a new modal window with a WebView.

Parameters: - url (string, required) - The URL to open in the WebView - title (string, optional) - Custom title for the navigation bar. Defaults to "Sign in"

Returns: - Promise<void> - Resolves when the window is presented

Example: ```typescript // Basic usage await open("https://example.com");

// With custom title await open("https://appleid.apple.com", "Sign in with Apple"); ```

close(): Promise<void>

Programmatically closes the currently opened window.

Parameters: - None

Returns: - Promise<void> - Resolves when the window is dismissed

Example: ```typescript // Close the window await close();

// Open and auto-close after delay await open("https://example.com", "Loading..."); setTimeout(async () => { await close(); }, 3000); ```

UI/UX Features

  • Clean, native iOS design
  • Close button on leading edge (per Apple HIG)
  • Centered title
  • Activity indicator during loading
  • Subtle border separator

Progress Indicator

  • 2px blue progress bar
  • Smooth animations
  • Auto-hides when complete
  • Tracks page load progress

WebView

  • Full-screen immersive experience
  • Back/forward swipe gestures
  • Inline media playback
  • Proper dark mode support
  • Error state handling
  • iPad: Popover (400x600)
  • iPhone: Full-screen modal
  • Smooth dismiss animations
  • Safe area aware

Platform Support

  • ✅ iOS (iPhone & iPad)
  • ⚠️ Desktop (no-op, falls through to default behavior)
  • ❌ Android (not supported)

Best Practices

OAuth Flows

  1. Use HTTPS in production typescript const baseUrl = process.env.NODE_ENV === "production" ? "https://splitfire.ai" : "https://localhost:3333";

  2. Handle redirects properly

    • Use custom URL schemes for mobile: your-app://oauth/callback
    • Use localhost for testing: http://localhost:8080/callback
    • Always encode redirect URIs
  3. Security

    • Use PKCE for OAuth 2.0
    • Validate state parameters
    • Store tokens securely
    • Use short-lived access tokens
  4. Error Handling typescript try { await open(oauthUrl, "Sign in"); } catch (error) { // Handle network errors // Show user-friendly message // Log for debugging }

UI/UX

  1. Use descriptive titles typescript await open(url, "Sign in with Apple"); // ✅ Good await open(url, "Sign in"); // ⚠️ Generic await open(url); // ❌ Uses default

  2. Provide feedback

    • Show loading states before opening
    • Handle success/error after OAuth
    • Give users clear next steps
  3. Test on both devices

    • iPad (popover experience)
    • iPhone (modal experience)

Programmatic Closing

  1. After OAuth completion ```typescript // Listen for OAuth callback listen("oauth-callback", async (event) => { // Process OAuth response await handleOAuthToken(event.payload);

    // Close the window await close(); }); ```

  2. Timeout scenarios ```typescript // Auto-close if user doesn't complete within time limit await open(oauthUrl, "Sign in");

const timeout = setTimeout(async () => { await close(); showMessage("Authentication timed out"); }, 60000); // 1 minute

// Clear timeout if user completes listen("oauth-complete", () => clearTimeout(timeout)); ```

  1. Error handling ```typescript try { await open(url, "Sign in"); } catch (error) { // If window fails to open, don't try to close console.error("Failed to open:", error); return; }

// Later, close if needed try { await close(); } catch (error) { // Window may already be closed by user console.log("Window already closed"); } ```

Example App

Check out the complete example in examples/tauri-app:

cd examples/tauri-app
npm install
npm run tauri ios dev

Development

# Clone the repository
git clone https://github.com/yourusername/tauri-plugin-ios-window

# Install dependencies
npm install

# Build the plugin
npm run build

# Run example
cd examples/tauri-app
npm run tauri ios dev

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT

Credits

Built for SplitFire - OAuth pattern inspired by production usage.