Lesson 18-Tauri Application Fundamentals

State Management and Frontend Framework Integration

React/Vue/Svelte State Management

React + Redux Integration Example

// src/store/index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer
  }
});

// In component usage
import { useSelector, useDispatch } from 'react-redux';
import { increment } from './store/counterSlice';

function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <button onClick={() => dispatch(increment())}>
      Count: {count}
    </button>
  );
}

Vue + Pinia Integration Example

// src/stores/counter.js
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  actions: {
    increment() {
      this.count++;
    }
  }
});

// In component usage
<script setup>
import { useCounterStore } from '@/stores/counter';

const counter = useCounterStore();
</script>

<template>
  <button @click="counter.increment">Count: {{ counter.count }}</button>
</template>

Svelte + Store Integration Example

// src/stores/counter.js
import { writable } from 'svelte/store';

export const count = writable(0);

// In component usage
<script>
  import { count } from './stores/counter';
</script>

<button on:click={() => $count++}>Count: {$count}</button>

Frontend State Synchronization with Rust Backend

State Synchronization Architecture

// src-tauri/src/main.rs
#[tauri::command]
fn get_server_data() -> Result<Vec<String>, String> {
    // Fetch data from database
    Ok(vec![
        "Item 1".to_string(),
        "Item 2".to_string()
    ])
}

#[tauri::command]
fn save_to_server(data: String) -> Result<(), String> {
    // Save data to database
    Ok(())
}

Frontend Synchronization Implementation (React Example)

// src/hooks/useServerData.js
import { useEffect, useState } from 'react';
import { invoke } from '@tauri-apps/api/tauri';

export function useServerData() {
  const [data, setData] = useState([]);

  useEffect(() => {
    async function fetchData() {
      const serverData = await invoke('get_server_data');
      setData(serverData);
    }

    fetchData();
  }, []);

  const saveData = async (newData) => {
    await invoke('save_to_server', { data: newData });
    // Optional: Re-fetch latest data
    const updatedData = await invoke('get_server_data');
    setData(updatedData);
  };

  return [data, saveData];
}

Data Persistence

localStorage Integration

// src/utils/storage.js
export const saveToLocalStorage = (key, value) => {
  localStorage.setItem(key, JSON.stringify(value));
};

export const loadFromLocalStorage = (key) => {
  const item = localStorage.getItem(key);
  return item ? JSON.parse(item) : null;
};

// In component usage
import { saveToLocalStorage, loadFromLocalStorage } from '@/utils/storage';

// Save state
saveToLocalStorage('appState', currentState);

// Load state
const savedState = loadFromLocalStorage('appState');

IndexedDB Integration

// src/utils/indexedDB.js
export class IDBStorage {
  constructor(dbName, storeName) {
    this.dbName = dbName;
    this.storeName = storeName;
    this.db = null;
  }

  async open() {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);

      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        if (!db.objectStoreNames.contains(this.storeName)) {
          db.createObjectStore(this.storeName);
        }
      };

      request.onsuccess = (event) => {
        this.db = event.target.result;
        resolve(this.db);
      };

      request.onerror = (event) => {
        reject(event.target.error);
      };
    });
  }

  async set(key, value) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(this.storeName, 'readwrite');
      const store = transaction.objectStore(this.storeName);
      const request = store.put(value, key);

      request.onsuccess = () => resolve();
      request.onerror = (event) => reject(event.target.error);
    });
  }

  async get(key) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction(this.storeName, 'readonly');
      const store = transaction.objectStore(this.storeName);
      const request = store.get(key);

      request.onsuccess = () => resolve(request.result);
      request.onerror = (event) => reject(event.target.error);
    });
  }
}

File Storage and Database Integration

SQLite Integration

// src-tauri/src/main.rs
use rusqlite::{Connection, params};

#[tauri::command]
fn init_db() -> Result<(), String> {
    let conn = Connection::open("app.db")?;

    conn.execute(
        "CREATE TABLE IF NOT EXISTS items (
            id INTEGER PRIMARY KEY,
            name TEXT NOT NULL
        )",
        [],
    )?;

    Ok(())
}

#[tauri::command]
fn get_items() -> Result<Vec<String>, String> {
    let conn = Connection::open("app.db")?;
    let mut stmt = conn.prepare("SELECT name FROM items")?;
    let items = stmt.query_map([], |row| row.get(0))?
        .collect::<Result<Vec<_>, _>>()?;

    Ok(items)
}

NeDB Integration (via Node.js)

// src/utils/database.js
import Datastore from 'nedb-promises';

const db = Datastore.create({
  filename: 'data.db',
  autoload: true
});

export async function getItems() {
  return await db.find({});
}

export async function addItem(item) {
  return await db.insert(item);
}

Secure Storage

Encryption Storage Implementation

// src-tauri/src/main.rs
use aes_gcm::{
    aead::{Aead, KeyInit, OsRng},
    Aes256Gcm, Nonce
};
use generic_array::GenericArray;

#[tauri::command]
fn encrypt_data(data: String, key: String) -> Result<String, String> {
    let key = GenericArray::from_slice(key.as_bytes());
    let cipher = Aes256Gcm::new(key);
    let nonce = Nonce::generate(&mut OsRng);

    let ciphertext = cipher.encrypt(&nonce, data.as_bytes())
        .map_err(|e| e.to_string())?;

    // Combine nonce and ciphertext
    let mut result = nonce.to_vec();
    result.extend(ciphertext);

    Ok(base64::encode(result))
}

#[tauri::command]
fn decrypt_data(encrypted_data: String, key: String) -> Result<String, String> {
    let data = base64::decode(encrypted_data)
        .map_err(|e| e.to_string())?;

    if data.len() < 12 {
        return Err("Invalid encrypted data".into());
    }

    let (nonce_bytes, ciphertext) = data.split_at(12);
    let key = GenericArray::from_slice(key.as_bytes());
    let cipher = Aes256Gcm::new(key);
    let nonce = Nonce::from_slice(nonce_bytes);

    let plaintext = cipher.decrypt(nonce, ciphertext)
        .map_err(|e| e.to_string())?;

    Ok(String::from_utf8(plaintext)
        .map_err(|e| e.to_string())?)
}

Key Management Strategy

// src/utils/keyManagement.js
import { generateKey, deriveKey } from './cryptoUtils';

// Generate or retrieve encryption key
export async function getEncryptionKey(password) {
  // Attempt to retrieve key from secure storage
  let key = await secureStorage.getKey('app_key');

  if (!key) {
    // Derive new key
    key = await deriveKey(password);
    // Store key securely
    await secureStorage.setKey('app_key', key);
  }

  return key;
}

// Secure storage implementation (with Tauri secure APIs)
export const secureStorage = {
  async setKey(name, key) {
    // Use Tauri's secure storage API
    await invoke('set_secure_data', { name, data: key });
  },

  async getKey(name) {
    // Retrieve from Tauri's secure storage
    return await invoke('get_secure_data', { name });
  }
};

Networking and Communication

HTTP Requests and API Calls

Fetch API Example

// src/api/http.js
export async function fetchData(url, options = {}) {
  try {
    const response = await fetch(url, {
      headers: {
        'Content-Type': 'application/json',
        ...options.headers
      },
      ...options
    });

    if (!response.ok) {
      throw new Error(`HTTP error ${response.status}`);
    }

    return await response.json();
  } catch (error) {
    console.error('Fetch error:', error);
    throw error;
  }
}

// Usage example
import { fetchData } from '@/api/http';

const data = await fetchData('https://api.example.com/data');

Axios Integration Example

// src/api/axios.js
import axios from 'axios';

const api = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 10000,
  headers: {
    'Content-Type': 'application/json'
  }
});

// Request interceptor
api.interceptors.request.use(config => {
  // Add authentication token
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
}, error => {
  return Promise.reject(error);
});

// Response interceptor
api.interceptors.response.use(response => {
  return response.data;
}, error => {
  // Unified error handling
  if (error.response?.status === 401) {
    // Handle unauthorized
  }
  return Promise.reject(error);
});

export default api;

// Usage example
import api from '@/api/axios';

const data = await api.get('/data');

WebSocket Communication

WebSocket Client Implementation

// src/api/websocket.js
export class WebSocketClient {
  constructor(url) {
    this.url = url;
    this.socket = null;
    this.listeners = {};
  }

  connect() {
    this.socket = new WebSocket(this.url);

    this.socket.onopen = () => {
      this.emit('open');
    };

    this.socket.onmessage = (event) => {
      try {
        const data = JSON.parse(event.data);
        this.emit('message', data);
      } catch (error) {
        this.emit('error', error);
      }
    };

    this.socket.onerror = (error) => {
      this.emit('error', error);
    };

    this.socket.onclose = () => {
      this.emit('close');
    };
  }

  send(data) {
    if (this.socket && this.socket.readyState === WebSocket.OPEN) {
      this.socket.send(JSON.stringify(data));
    } else {
      this.emit('error', new Error('WebSocket not connected'));
    }
  }

  on(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event].push(callback);
  }

  emit(event, ...args) {
    if (this.listeners[event]) {
      this.listeners[event].forEach(callback => callback(...args));
    }
  }

  disconnect() {
    if (this.socket) {
      this.socket.close();
    }
  }
}

// Usage example
import { WebSocketClient } from '@/api/websocket';

const ws = new WebSocketClient('ws://localhost:8080/ws');
ws.on('message', (data) => {
  console.log('Received:', data);
});
ws.connect();

Proxy Configuration and Network Debugging

Development Environment Proxy Configuration

// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3001',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }
});

Network Debugging Tools Integration

// src/utils/networkDebugger.js
export function setupNetworkDebugger() {
  if (import.meta.env.DEV) {
    // Use browser developer tools
    console.log('Network debugging enabled in development mode');

    // Can integrate professional tools like Charles Proxy or Fiddler
  } else {
    // Disable debugging in production
    console.log('Network debugging disabled in production');
  }
}

// Call during app initialization
import { setupNetworkDebugger } from '@/utils/networkDebugger';
setupNetworkDebugger();

Secure Communication

HTTPS Configuration

// src-tauri/src/main.rs
fn main() {
    tauri::Builder::default()
        // Enforce HTTPS
        .setup(|app| {
            let window = app.get_window("main").unwrap();

            // Monitor all navigation requests
            window.webview().set_navigation_callback(|nav| {
                if !nav.url().scheme() == "https" {
                    // Block non-HTTPS requests
                    return false;
                }
                true
            });

            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Certificate Verification

// src/api/secureFetch.js
export async function secureFetch(url, options = {}) {
  // Verify certificate
  if (!url.startsWith('https://')) {
    throw new Error('Only HTTPS connections are allowed');
  }

  // Check certificate validity (requires backend cooperation)
  const response = await fetch(url, {
    ...options,
    // Can add custom certificate verification headers
    headers: {
      'X-Certificate-Verification': 'required',
      ...options.headers
    }
  });

  // Validate response
  if (!response.ok) {
    throw new Error(`HTTP error ${response.status}`);
  }

  return response.json();
}

Cross-Origin Issues and Solutions

CORS Configuration (Backend)

// src-tauri/src/main.rs
use tauri::http::header;

#[tauri::command]
fn handle_cors_request() -> Result<(), String> {
    // In real applications, this should be handled by backend API
    // This is just an example
    Ok(())
}

// More complete CORS handling requires backend API support

Frontend Proxy Solution

// vite.config.js
export default defineConfig({
  server: {
    proxy: {
      '/api': {
        target: 'http://backend-server.com',
        changeOrigin: true,
        configure: (proxy, options) => {
          proxy.on('proxyReq', (proxyReq, req, res) => {
            // Add necessary CORS headers
            proxyReq.setHeader('Access-Control-Allow-Origin', '*');
            proxyReq.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE');
            proxyReq.setHeader('Access-Control-Allow-Headers', 'Content-Type');
          });
        }
      }
    }
  }
});

JSONP Alternative (GET Requests Only)

// src/api/jsonp.js
export function jsonp(url, callbackName = 'jsonpCallback') {
  return new Promise((resolve, reject) => {
    // Create script tag
    const script = document.createElement('script');

    // Define callback function
    window[callbackName] = (data) => {
      resolve(data);
      cleanup();
    };

    // Error handling
    script.onerror = (error) => {
      reject(error);
      cleanup();
    };

    // Set src
    script.src = `${url}${url.includes('?') ? '&' : '?'}callback=${callbackName}`;

    // Add to document
    document.body.appendChild(script);

    // Cleanup function
    const cleanup = () => {
      document.body.removeChild(script);
      delete window[callbackName];
    };
  });
}

// Usage example (requires backend JSONP support)
jsonp('http://api.example.com/data?param=value')
  .then(data => console.log(data))
  .catch(error => console.error(error));

Application Security

Least Privilege Principle and Permission Configuration

Tauri Permission Configuration

// tauri.conf.json
{
  "tauri": {
    "allowlist": {
      "all": false, // Disable all permissions by default
      "fs": {
        "scope": ["$APPDATA/*", "$HOME/Documents/*"], // Allow access to specific directories only
        "allow": ["read_file", "write_file"] // Allow read/write operations only
      },
      "dialog": {
        "open": true, // Allow opening file dialogs
        "save": true, // Allow saving file dialogs
        "alert": true, // Allow alert dialogs
        "confirm": true, // Allow confirm dialogs
        "prompt": false // Disable prompt dialogs
      },
      "network": {
        "allowlist": ["https://api.example.com/*"] // Allow access to specific APIs only
      }
    }
  }
}

Dynamic Permission Requests

// src/utils/permissionManager.js
export async function requestPermission(permission) {
  try {
    const granted = await invoke('request_permission', { permission });
    return granted;
  } catch (error) {
    console.error('Permission request failed:', error);
    return false;
  }
}

// Usage example
import { requestPermission } from '@/utils/permissionManager';

const canAccessFiles = await requestPermission('fs.read_file');
if (canAccessFiles) {
  // Perform file operations
} else {
  // Show error or fallback
}

Data Encryption and Secure Storage

End-to-End Encryption Implementation

// src-tauri/src/main.rs
use ring::{aead, rand};

#[tauri::command]
fn encrypt_data(data: String, key: String) -> Result<String, String> {
    // Generate random nonce
    let rng = rand::SystemRandom::new();
    let mut nonce_bytes = [0u8; 12];
    rng.fill(&mut nonce_bytes).map_err(|e| e.to_string())?;

    // Create encryption key
    let key_bytes = base64::decode(key).map_err(|e| e.to_string())?;
    let unbound_key = aead::UnboundKey::new(&aead::AES_256_GCM, &key_bytes)
        .map_err(|e| e.to_string())?;
    let sealing_key = aead::LessSafeKey::new(unbound_key);

    // Encrypt data
    let mut in_out = data.as_bytes().to_vec();
    let nonce = aead::Nonce::assume_unique_for_key(nonce_bytes);

    sealing_key.seal_in_place_append_tag(nonce, aead::Aad::empty(), &mut in_out)
        .map_err(|e| e.to_string())?;

    // Combine nonce and ciphertext
    let mut result = nonce_bytes.to_vec();
    result.extend(in_out);

    // Base64 encode
    Ok(base64::encode(result))
}

#[tauri::command]
fn decrypt_data(encrypted_data: String, key: String) -> Result<String, String> {
    // Base64 decode
    let data = base64::decode(encrypted_data).map_err(|e| e.to_string())?;

    if data.len() < 12 {
        return Err("Invalid encrypted data".into());
    }

    // Split nonce and ciphertext
    let (nonce_bytes, ciphertext) = data.split_at(12);
    let key_bytes = base64::decode(key).map_err(|e| e.to_string())?;
    let unbound_key = aead::UnboundKey::new(&aead::AES_256_GCM, &key_bytes)
        .map_err(|e| e.to_string())?;
    let opening_key = aead::LessSafeKey::new(unbound_key);

    // Decrypt data
    let mut in_out = ciphertext.to_vec();
    let nonce = aead::Nonce::assume_unique_for_key(nonce_bytes);

    opening_key.open_in_place(nonce, aead::Aad::empty(), &mut in_out)
        .map_err(|e| e.to_string())?;

    // Remove padding tag
    in_out.truncate(in_out.len() - 16); // AES-GCM tag size is 16 bytes

    Ok(String::from_utf8(in_out).map_err(|e| e.to_string())?)
}

Secure Storage Strategy

// src/utils/secureStorage.js
import { encryptData, decryptData } from './crypto';

export async function secureSetItem(key, value, encryptionKey) {
  try {
    // Encrypt data
    const encrypted = await encryptData(JSON.stringify(value), encryptionKey);

    // Store in secure location
    await invoke('set_secure_data', { key, data: encrypted });
  } catch (error) {
    console.error('Secure set item failed:', error);
    throw error;
  }
}

export async function secureGetItem(key, encryptionKey) {
  try {
    // Retrieve from secure location
    const encrypted = await invoke('get_secure_data', { key });

    if (!encrypted) return null;

    // Decrypt data
    const decrypted = await decryptData(encrypted, encryptionKey);
    return JSON.parse(decrypted);
  } catch (error) {
    console.error('Secure get item failed:', error);
    throw error;
  }
}

// Usage example
import { secureSetItem, secureGetItem } from '@/utils/secureStorage';

const encryptionKey = 'your-256-bit-encryption-key-base64-encoded';
await secureSetItem('user_token', 'abc123', encryptionKey);
const token = await secureGetItem('user_token', encryptionKey);

Preventing XSS, CSRF, and Other Frontend Attacks

XSS Protection Measures

// Input sanitization function
export function sanitizeInput(input) {
  // Create safe HTML element
  const div = document.createElement('div');
  div.textContent = input; // Automatically escapes HTML

  // Return sanitized text
  return div.innerHTML;
}

// Usage example
const userInput = '<script>alert("XSS")</script>';
const safeOutput = sanitizeInput(userInput);
document.getElementById('output').innerHTML = safeOutput;
// Output: <script>alert("XSS")</script>

// Safer alternative - Use DOMPurify library
import DOMPurify from 'dompurify';

const clean = DOMPurify.sanitize(userInput);
document.getElementById('output').innerHTML = clean;

CSRF Protection Implementation

// src/api/csrf.js
let csrfToken = null;

export async function getCsrfToken() {
  if (!csrfToken) {
    // Fetch CSRF token from server
    const response = await fetch('/api/csrf-token', {
      credentials: 'include' // Include cookies
    });

    if (!response.ok) {
      throw new Error('Failed to get CSRF token');
    }

    const data = await response.json();
    csrfToken = data.token;
  }

  return csrfToken;
}

export async function secureFetchWithCSRF(url, options = {}) {
  const token = await getCsrfToken();

  // Add CSRF token to request headers
  const headers = {
    'X-CSRF-Token': token,
    ...options.headers
  };

  return fetch(url, {
    ...options,
    headers,
    credentials: 'include' // Include cookies
  });
}

// Usage example
import { secureFetchWithCSRF } from '@/api/csrf';

const response = await secureFetchWithCSRF('/api/protected-action', {
  method: 'POST',
  body: JSON.stringify({ data: 'value' })
});

Rust Backend Security Practices

Input Validation

// src-tauri/src/main.rs
use validator::Validate;

#[derive(Validate, Deserialize)]
struct UserInput {
    #[validate(length(min = 3, max = 50))]
    username: String,

    #[validate(email)]
    email: String,

    #[validate(range(min = 8, max = 128))]
    password: String,
}

#[tauri::command]
fn create_user(user_input: String) -> Result<(), String> {
    // Deserialize and validate input
    let user_input: UserInput = serde_json::from_str(&user_input)
        .map_err(|e| e.to_string())?;

    user_input.validate()
        .map_err(|e| e.to_string())?;

    // Input validation passed, proceed with business logic
    Ok(())
}

Secure Dependency Management

# Cargo.toml
[dependencies]
# Use audited libraries
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }

# Security-related libraries
aes-gcm = "0.10" # Encryption
ring = "0.16"    # Cryptographic primitives
validator = "0.16" # Input validation

# Avoid unsafe dependencies
# Do not use: unsafe Rust code libraries
# Do not use: libraries with known vulnerabilities

Secure HTTP Headers Configuration

// src-tauri/src/main.rs
use tauri::http::header;

#[tauri::command]
fn handle_request() -> Result<(), String> {
    // In real applications, this should be handled by web server (e.g., Actix-web)
    // This is just an example
    
    // Set secure HTTP headers
    let headers = [
        (header::X_CONTENT_TYPE_OPTIONS, "nosniff"),
        (header::X_FRAME_OPTIONS, "DENY"),
        (header::X_XSS_PROTECTION, "1; mode=block"),
        (header::STRICT_TRANSPORT_SECURITY, "max-age=63072000; includeSubDomains; preload"),
        (header::CONTENT_SECURITY_POLICY, "default-src 'self'"),
    ];
    
    // In real applications, these headers should be set by server responses
    Ok(())
}

Security Auditing and Vulnerability Fixes

Security Audit Process

Dependency Audit:

    # Use cargo-audit to check Rust dependency vulnerabilities
    cargo install cargo-audit
    cargo audit
        ```
    
    2. **Static Code Analysis**:
    
    ```bash
    # Use clippy for Rust code analysis
    cargo clippy -- -D warnings
     ```
    
    3. **Frontend Security Scanning**:
    
    ```bash
    # Use npm audit to check JavaScript dependencies
    npm audit

    Automated Security Testing:

    # Use snyk for security scanning
    npm install -g snyk
    snyk test

    Vulnerability Fix Strategies

    // Example: Fixing a known vulnerability
    // Assume a dependency has an XSS vulnerability
    
    // 1. Update dependency version
    // Cargo.toml
    [dependencies]
    # Old version (vulnerable)
    # some-crate = "1.2.3"
    
    // New version (fixed)
    some-crate = "1.2.4"
    
    // 2. Rebuild application
    cargo build --release
    
    // 3. Test fix effectiveness
    // Run security tests to ensure vulnerability is resolved

    Security Update Mechanism

    // src/utils/securityUpdate.js
    export async function checkForSecurityUpdates() {
      try {
        // Check for security updates
        const updateInfo = await invoke('check_security_updates');
        
        if (updateInfo.needsUpdate) {
          // Notify user
          if (confirm(`Security update found (${updateInfo.version}). Update now?`)) {
            // Apply update
            await invoke('apply_security_update');
            // Restart application
            window.location.reload();
          }
        }
      } catch (error) {
        console.error('Security update check failed:', error);
      }
    }
    
    // Periodic check (e.g., daily)
    setInterval(checkForSecurityUpdates, 24 * 60 * 60 * 1000);
    Share your love