Lesson 10-Electron Advanced Application Development

Cross-Platform Compatibility

Handling Platform Differences

Platform Detection and Conditional Branching

// Platform detection example
const { platform } = require('process');

function getPlatformSpecificConfig() {
  switch (platform) {
    case 'win32':
      return { 
        configFile: 'config.win.json',
        pathSeparator: '\\'
      };
    case 'darwin':
      return {
        configFile: 'config.macos.json',
        pathSeparator: '/'
      };
    case 'linux':
      return {
        configFile: 'config.linux.json',
        pathSeparator: '/'
      };
    default:
      throw new Error(`Unsupported platform: ${platform}`);
  }
}

// Conditional compilation example (via webpack.DefinePlugin)
// webpack.config.js
new webpack.DefinePlugin({
  IS_WINDOWS: JSON.stringify(process.platform === 'win32'),
  IS_MAC: JSON.stringify(process.platform === 'darwin')
});

// Usage example
if (IS_WINDOWS) {
  // Windows-specific logic
} else if (IS_MAC) {
  // macOS-specific logic
}

Path Handling Best Practices

const path = require('path');

// Use path.join instead of string concatenation
const configPath = path.join(__dirname, 'config', getPlatformSpecificConfig().configFile);

// Handle user directory
const userDataPath = app.getPath('userData');
const dbPath = path.join(userDataPath, 'database.sqlite');

Cross-Platform Encapsulation of Native Modules

Precompiled Binary Management

// package.json configuration example
{
  "name": "native-addon",
  "binary": {
    "module_name": "nativeAddon",
    "module_path": "./lib/binding/{node_abi}-{platform}-{arch}",
    "host": "https://github.com/username/native-addon/releases/download",
    "remote_path": "./{version}",
    "package_name": "{node_abi}-{platform}-{arch}.tar.gz"
  }
}

Platform-Specific Code Isolation

// native-addon.cc
#include <node_api.h>

#ifdef _WIN32
#include <windows.h>
#elif __APPLE__
#include <CoreFoundation/CoreFoundation.h>
#elif __linux__
#include <unistd.h>
#endif

napi_value GetPlatformInfo(napi_env env, napi_callback_info info) {
  napi_value result;
  char platformInfo[50];

#ifdef _WIN32
  snprintf(platformInfo, sizeof(platformInfo), "Windows %d", GetVersion());
#elif __APPLE__
  snprintf(platformInfo, sizeof(platformInfo), "macOS %s", "10.15");
#elif __linux__
  snprintf(platformInfo, sizeof(platformInfo), "Linux %s", "5.4.0");
#endif

  napi_create_string_utf8(env, platformInfo, NAPI_AUTO_LENGTH, &result);
  return result;
}

NAPI_MODULE_INIT() {
  napi_value fn;
  napi_create_function(env, NULL, 0, GetPlatformInfo, NULL, &fn);
  napi_set_named_property(env, exports, "getPlatformInfo", fn);
  return exports;
}

Cross-Platform Compatibility of Third-Party Libraries

Library Selection Principles

  1. Prefer Pure JavaScript Libraries
  2. Check os/cpu Fields in package.json
  3. Verify Native Module Support for Target Platforms

Solutions for Common Issues

// Handle file system differences
const fs = require('fs');
const os = require('os');

function getSafeConfigPath() {
  let configDir;
  switch (process.platform) {
    case 'win32':
      configDir = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
      break;
    case 'darwin':
      configDir = path.join(os.homedir(), 'Library', 'Application Support');
      break;
    case 'linux':
      configDir = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
      break;
  }
  return path.join(configDir, 'myapp', 'config.json');
}

// Handle path separator differences
function normalizePath(filePath) {
  return filePath.replace(/[\\/]+/g, path.sep);
}

Cross-Platform Testing Strategies

Testing Matrix Design

Test TypeWindowsmacOSLinux
Unit Tests
Integration Tests
E2E Tests
Performance Tests
Installation Package Tests

Automated Testing Configuration

# GitHub Actions cross-platform testing configuration
jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [16.x]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test
      - name: Run E2E tests
        uses: cypress-io/github-action@v2
        with:
          start: npm start
          wait-on: 'http://localhost:3000'

Cross-Platform Performance Optimization

Platform-Specific Optimization Strategies

// Adjust rendering strategy based on platform
function getRenderConfig() {
  const config = {
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true
    }
  };

  if (process.platform === 'win32') {
    // Windows-specific optimization
    config.webPreferences.offscreen = true; // Offscreen rendering
  } else if (process.platform === 'darwin') {
    // macOS-specific optimization
    config.webPreferences.backgroundThrottling = false; // Disable background throttling
  }

  return config;
}

// Create optimized window
const win = new BrowserWindow(getRenderConfig());

Real-Time Application Development

WebSocket and Real-Time Data Updates

WebSocket Integration Solution

// Main process WebSocket management
const { ipcMain } = require('electron');
const WebSocket = require('ws');

let wsClient = null;

ipcMain.handle('ws:connect', (event, url) => {
  wsClient = new WebSocket(url);

  wsClient.on('message', (data) => {
    // Broadcast to all renderer processes
    mainWindow.webContents.send('ws:message', data);
  });
});

ipcMain.handle('ws:send', (event, message) => {
  if (wsClient && wsClient.readyState === WebSocket.OPEN) {
    wsClient.send(JSON.stringify(message));
  }
});

// Renderer process usage
window.electronAPI.onWSMessage((event, data) => {
  console.log('Received WebSocket message:', data);
});

function sendWSMessage(message) {
  window.electronAPI.sendWSMessage(message);
}

GraphQL Integration with Electron

Apollo Client Integration

// src/renderer/apolloClient.js
import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
import { split } from '@apollo/client';
import { WebSocketLink } from '@apollo/client/link/ws';
import { getMainDefinition } from '@apollo/client/utilities';

const httpLink = new HttpLink({
  uri: 'http://localhost:4000/graphql',
});

const wsLink = new WebSocketLink({
  uri: `ws://localhost:4000/graphql`,
  options: {
    reconnect: true
  }
});

const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink
);

export const apolloClient = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache()
});

Real-Time Collaborative Editing Implementation

CRDT Algorithm Integration

// Implementing CRDT collaboration with Yjs
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';

// Create shared document
const ydoc = new Y.Doc();
const provider = new WebsocketProvider(
  'ws://localhost:1234',
  'room-name',
  ydoc
);

// Get shared type
const ytext = ydoc.getText('document');

// Monitor local changes
ytext.observe(event => {
  console.log('Local modification:', event);
});

// Monitor remote changes
provider.on('sync', (isSynced) => {
  if (isSynced) {
    console.log('Document synchronized');
  }
});

// Usage in React component
function CollaborativeEditor() {
  const [content, setContent] = useState('');

  useEffect(() => {
    // Sync Yjs document to local state
    const updateContent = () => {
      setContent(ytext.toString());
    };

    ytext.observe(updateContent);
    return () => ytext.unobserve(updateContent);
  }, []);

  const handleChange = (e) => {
    const newText = e.target.value;
    ytext.delete(0, ytext.length);
    ytext.insert(0, newText);
  };

  return <textarea value={content} onChange={handleChange} />;
}

Real-Time Communication Performance Optimization

Message Compression and Batching

// Message batching implementation
class MessageBatcher {
  constructor(sendFn, delay = 50) {
    this.batch = [];
    this.sendFn = sendFn;
    this.timer = null;
    this.delay = delay;
  }

  add(message) {
    this.batch.push(message);

    if (!this.timer) {
      this.timer = setTimeout(() => this.flush(), this.delay);
    }
  }

  flush() {
    if (this.batch.length === 0) return;

    // Compress messages
    const compressed = this.compressMessages(this.batch);
    this.sendFn(compressed);

    this.batch = [];
    this.timer = null;
  }

  compressMessages(messages) {
    // Simple implementation: JSON.stringify
    // Real projects may use more efficient compression algorithms
    return JSON.stringify(messages);
  }
}

// Usage example
const batcher = new MessageBatcher((data) => {
  wsClient.send(data);
});

// Add message
batcher.add({ type: 'cursor', userId: '123', position: { x: 10, y: 20 } });

Real-Time Application Security and Permissions

Authentication and Authorization Implementation

// WebSocket authentication middleware
const jwt = require('jsonwebtoken');

function authenticateWS(socket, next) {
  const token = socket.handshake.auth.token;

  if (!token) {
    return next(new Error('Authentication error'));
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    socket.user = decoded;
    next();
  } catch (err) {
    next(new Error('Invalid token'));
  }
}

// Create secure WebSocket server
const wss = new WebSocket.Server({
  port: 1234,
  verifyClient: (info, callback) => {
    // Verification during HTTP upgrade request
    const token = info.req.headers['sec-websocket-protocol'];
    // ...verification logic
  }
});

wss.on('connection', authenticateWS, (ws) => {
  // Authenticated connection
  ws.on('message', (message) => {
    // Handle messages...
  });
});

Enterprise-Grade Application Development

Complex State Management

Redux Saga Implementation

// src/renderer/store/sagas.js
import { call, put, takeEvery } from 'redux-saga/effects';
import { fetchDataSuccess, fetchDataFailure } from './actions';
import api from '../api';

function* fetchDataSaga(action) {
  try {
    const data = yield call(api.fetchData, action.payload);
    yield put(fetchDataSuccess(data));
  } catch (error) {
    yield put(fetchDataFailure(error.message));
  }
}

export default function* rootSaga() {
  yield takeEvery('FETCH_DATA_REQUEST', fetchDataSaga);
}

// Redux Observable implementation
import { ofType } from 'redux-observable';
import { mergeMap, map, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
import api from '../api';

const fetchDataEpic = (action$) =>
  action$.pipe(
    ofType('FETCH_DATA_REQUEST'),
    mergeMap((action) =>
      api.fetchData(action.payload).pipe(
        map((response) => fetchDataSuccess(response.data)),
        catchError((error) => of(fetchDataFailure(error.message)))
      )
    )
  );

Data Synchronization and Conflict Resolution

Eventual Consistency Strategy

// Data synchronization manager
class DataSyncManager {
  constructor() {
    this.localData = {};
    this.remoteData = {};
    this.pendingChanges = [];
  }

  // Local update
  localUpdate(key, value) {
    this.localData[key] = value;
    this.pendingChanges.push({ key, value, timestamp: Date.now() });
    this.trySync();
  }

  // Remote update
  remoteUpdate(key, value, timestamp) {
    if (!this.localData[key] || this.localData[key].timestamp < timestamp) {
      this.remoteData[key] = { value, timestamp };
      this.applyUpdate(key, value);
    } else {
      // Conflict detection
      this.handleConflict(key, value, timestamp);
    }
  }

  // Attempt synchronization
  trySync() {
    if (navigator.onLine && this.pendingChanges.length > 0) {
      const changes = [...this.pendingChanges];
      this.pendingChanges = [];

      api.syncChanges(changes).then((response) => {
        response.conflicts.forEach((conflict) => {
          this.handleConflict(conflict.key, conflict.serverValue, conflict.serverTimestamp);
        });
      }).catch(() => {
        // Sync failure, re-queue changes
        this.pendingChanges.unshift(...changes);
      });
    }
  }

  // Handle conflict
  handleConflict(key, serverValue, serverTimestamp) {
    // Simple strategy: Last write wins
    if (serverTimestamp > this.localData[key].timestamp) {
      this.applyUpdate(key, serverValue);
    }
    // Can be extended for more complex merge strategies
  }

  // Apply update
  applyUpdate(key, value) {
    this.localData[key] = { value, timestamp: Date.now() };
    // Notify UI update
    window.dispatchEvent(new CustomEvent('data-updated', { detail: { key, value } }));
  }
}

Offline Support and Data Persistence

Offline-First Strategy

// Offline manager implementation
class OfflineManager {
  constructor() {
    this.isOnline = navigator.onLine;
    this.queue = [];

    window.addEventListener('online', () => this.handleOnline());
    window.addEventListener('offline', () => this.handleOffline());
  }

  // Execute operation (automatically handles offline queue)
  async executeOperation(operation) {
    if (this.isOnline) {
      try {
        return await operation.execute();
      } catch (error) {
        if (error.isNetworkError) {
          return this.queueOperation(operation);
        }
        throw error;
      }
    } else {
      return this.queueOperation(operation);
    }
  }

  // Queue operation
  queueOperation(operation) {
    return new Promise((resolve, reject) => {
      this.queue.push({
        operation,
        resolve,
        reject
      });
    });
  }

  // Handle network recovery
  async handleOnline() {
    this.isOnline = true;
    while (this.queue.length > 0) {
      const { operation, resolve, reject } = this.queue.shift();
      try {
        const result = await operation.execute();
        resolve(result);
      } catch (error) {
        reject(error);
      }
    }
  }

  // Handle offline state
  handleOffline() {
    this.isOnline = false;
  }
}

// Data persistence strategy
class DataPersistence {
  constructor() {
    this.db = new Dexie('AppDatabase');
    this.initDB();
  }

  initDB() {
    this.db.version(1).stores({
      documents: '++id, title, content, updatedAt',
      settings: 'key'
    });
  }

  // Save document
  async saveDocument(doc) {
    await this.db.documents.put(doc);
    // Sync to server if online
    if (navigator.onLine) {
      await api.saveDocument(doc);
    }
  }

  // Get document
  async getDocument(id) {
    // Get from local first
    let doc = await this.db.documents.get(id);

    // If not found locally and online, fetch from server
    if (!doc && navigator.onLine) {
      doc = await api.getDocument(id);
      if (doc) {
        await this.db.documents.put(doc);
      }
    }

    return doc;
  }
}

Security and Permission Control

Enterprise-Grade Authentication Solution

// JWT authentication integration
import jwtDecode from 'jwt-decode';

class AuthService {
  constructor() {
    this.token = localStorage.getItem('token');
    this.user = this.token ? jwtDecode(this.token) : null;
  }

  // Login
  async login(credentials) {
    const response = await api.login(credentials);
    this.setToken(response.token);
    return response;
  }

  // Set token
  setToken(token) {
    this.token = token;
    this.user = jwtDecode(token);
    localStorage.setItem('token', token);
    // Set default authorization header for Axios
    api.defaults.headers.common['Authorization'] = `Bearer ${token}`;
  }

  // Logout
  logout() {
    this.token = null;
    this.user = null;
    localStorage.removeItem('token');
    delete api.defaults.headers.common['Authorization'];
  }

  // Check permission
  hasPermission(requiredPermission) {
    if (!this.user) return false;
    return this.user.permissions.includes(requiredPermission);
  }
}

// OAuth integration
class OAuthService {
  constructor(provider) {
    this.provider = provider;
    this.redirectUri = `${window.location.origin}/oauth/callback`;
  }

  // Start OAuth flow
  startLogin() {
    const authUrl = `https://${this.provider}.com/oauth/authorize?` +
      `client_id=${process.env.OAUTH_CLIENT_ID}&` +
      `redirect_uri=${encodeURIComponent(this.redirectUri)}&` +
      `response_type=code&` +
      `scope=openid profile email`;

    window.location.href = authUrl;
  }

  // Handle callback
  async handleCallback(code) {
    const tokenResponse = await api.exchangeCodeForToken(code);
    this.setToken(tokenResponse.access_token);

    // Get user info
    const userInfo = await api.getUserInfo();
    return userInfo;
  }
}

Enterprise-Grade Application Architecture Design

Modular Architecture Example

src/
├── core/                   # Core infrastructure
│   ├── auth/               # Authentication and authorization
│   ├── ipc/                # Inter-process communication
│   ├── network/            # Network communication
│   └── storage/            # Data storage
├── modules/                # Business modules
│   ├── document/           # Document management module
│   ├── collaboration/      # Collaboration module
│   └── admin/              # Admin module
├── shared/                 # Shared code
│   ├── components/         # Global UI components
│   ├── hooks/              # Custom hooks
│   └── utils/              # Utility functions
├── App.tsx                 # Application entry point
└── main.ts                 # Main process entry point

Micro-Frontend Integration Solution

// Main app integrating sub-apps
import { loadMicroApp } from 'qiankun';

// Define sub-apps
const subApps = [
  {
    name: 'document-module',
    entry: '//localhost:7100',
    container: '#document-container',
    activeRule: '/documents'
  },
  {
    name: 'collaboration-module',
    entry: '//localhost:7200',
    container: '#collaboration-container',
    activeRule: '/collaborate'
  }
];

// Start micro-frontend
subApps.forEach(app => {
  loadMicroApp(app);
});

// Routing configuration in renderer process
<BrowserRouter>
  <Routes>
    <Route path="/documents/*" element={<DocumentModule />} />
    <Route path="/collaborate/*" element={<CollaborationModule />} />
  </Routes>
</BrowserRouter>
Share your love