Skip to main content

🎨 Thin Client Pattern

SuperSafe Wallet's frontend implements a Thin Client Pattern where all business logic resides in the background service worker. The React application is purely presentational, communicating with the background via Chrome streams.

Overview​

Frontend Metrics​

Total Frontend Files: 61 JSX components
Main App Component: 1,569 lines
Total Frontend Code: ~8,000 lines
Framework: React 18.2.0
Styling: TailwindCSS 3.3.3
Build Tool: Vite 6.3.6

Key Principles​

  • βœ… Thin Client: Zero business logic in frontend
  • βœ… Stream Communication: Long-lived connections to background
  • βœ… Presentational Components: Pure UI rendering
  • βœ… Centralized State: WalletProvider context
  • βœ… Adapter Pattern: Background communication abstraction

Architecture Overview​

Component Hierarchy​

Frontend (React)
β”‚
β”‚ chrome.runtime.connect({ name: 'session' })
↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ StreamConnectionManager β”‚
β”‚ β€’ Manages long-lived connections β”‚
β”‚ β€’ Handles reconnection β”‚
β”‚ β€’ Message queuing β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ sendRequest('session', { type: 'GET_SESSION_STATE' })
↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Adapters (Thin Client Layer) β”‚
β”‚ β€’ FrontendSessionAdapter β”‚
β”‚ β€’ SwapAdapter β”‚
β”‚ β€’ RelayAdapter β”‚
β”‚ β€’ SendAdapter β”‚
β”‚ β€’ BlockchainAdapter β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ Delegates to background
↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Background Service Worker β”‚
β”‚ β€’ All business logic β”‚
β”‚ β€’ Crypto operations β”‚
β”‚ β€’ State management β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Adapter Pattern​

Frontend Adapters​

Purpose: Abstract background communication from components.

FrontendSessionAdapter​

Location: src/utils/FrontendSessionAdapter.js

class FrontendSessionAdapter {
static async getSessionState() {
return await StreamConnectionManager.sendRequest('session', {
type: 'GET_SESSION_STATE'
});
}

static async unlock(password) {
return await StreamConnectionManager.sendRequest('session', {
type: 'UNLOCK',
payload: { password }
});
}

static async createWallet(name, emoji) {
return await StreamConnectionManager.sendRequest('session', {
type: 'CREATE_WALLET',
payload: { name, emoji }
});
}

static async switchWallet(index) {
return await StreamConnectionManager.sendRequest('session', {
type: 'SWITCH_WALLET',
payload: { index }
});
}
}

SwapAdapter​

Location: src/utils/SwapAdapter.js

class SwapAdapter {
static async getSwapQuote(params) {
return await StreamConnectionManager.sendRequest('swap', {
type: 'SWAP_GET_QUOTE',
payload: params
});
}

static async signAndSubmitOrder(quote, takerAddress, networkKey) {
return await StreamConnectionManager.sendRequest('swap', {
type: 'SWAP_SIGN_AND_SUBMIT',
payload: { quote, takerAddress, networkKey }
});
}

static async checkOrderStatus(quoteId, networkKey) {
return await StreamConnectionManager.sendRequest('swap', {
type: 'SWAP_CHECK_STATUS',
payload: { quoteId, networkKey }
});
}
}

RelayAdapter​

Location: src/utils/RelayAdapter.js

class RelayAdapter {
static async getQuote(params) {
return await StreamConnectionManager.sendRequest('relay', {
type: 'RELAY_GET_QUOTE',
payload: params
});
}

static async executeSwap(params) {
return await StreamConnectionManager.sendRequest('relay', {
type: 'RELAY_EXECUTE_SWAP',
payload: params
});
}

static async checkStatus(params) {
return await StreamConnectionManager.sendRequest('relay', {
type: 'RELAY_GET_STATUS',
payload: params
});
}
}

Stream Connection Manager​

Location: src/utils/StreamConnectionManager.js

Purpose: Manage long-lived Chrome connections to background

Features:

  • βœ… Automatic reconnection
  • βœ… Message queuing
  • βœ… Connection health monitoring
  • βœ… Error handling
class StreamConnectionManager {
constructor() {
this.connections = new Map(); // channel -> port
this.messageQueue = new Map(); // channel -> messages[]
this.reconnectAttempts = new Map();
}

async connect(channel) {
const port = chrome.runtime.connect({ name: channel });

port.onDisconnect.addListener(() => {
console.warn(`[StreamConnection] ${channel} disconnected`);
this.connections.delete(channel);
this.reconnect(channel);
});

this.connections.set(channel, port);

// Process queued messages
this.processQueue(channel);

return port;
}

async sendRequest(channel, message) {
let port = this.connections.get(channel);

if (!port) {
port = await this.connect(channel);
}

return new Promise((resolve, reject) => {
const messageId = this.generateMessageId();

const listener = (response) => {
if (response.id === messageId) {
port.onMessage.removeListener(listener);

if (response.success) {
resolve(response.data);
} else {
reject(new Error(response.error));
}
}
};

port.onMessage.addListener(listener);

port.postMessage({
...message,
id: messageId
});

// Timeout after 30 seconds
setTimeout(() => {
port.onMessage.removeListener(listener);
reject(new Error('Request timeout'));
}, 30000);
});
}
}

Architecture Compliance​

βœ… ARCHITECTURE.md Compliance:

  • βœ… NO ethers imports: Frontend never imports ethers.js
  • βœ… Uses Adapters only: All crypto operations via adapters
  • βœ… Thin client pattern: Zero business logic in frontend
  • βœ… All crypto in background: Private keys never exposed

Document Status: βœ… Current as of November 15, 2025
Code Version: v3.0.0+
Maintenance: Review after major frontend changes