Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Koniverse/SubWallet-Extension/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The messaging system enables communication between the extension’s UI layer and background service. It uses Chrome’s runtime messaging API with a port-based connection that supports both one-time requests and long-lived subscriptions.
Core Architecture
Message Types
Messages are strongly typed using TypeScript discriminated unions:
export type MessageTypes = keyof RequestSignatures;
export type MessageTypesWithSubscriptions = keyof SubscriptionMessageTypes;
export type MessageTypesWithNoSubscriptions = Exclude<MessageTypes, keyof SubscriptionMessageTypes>;
export type MessageTypesWithNullRequest = NullKeys<RequestTypes>;
Message Structure
Unique identifier generated for each message
The message type identifier (e.g., 'pri(accounts.create.suriV2)')
request
RequestTypes[TMessageType]
required
The request payload specific to the message type
origin
'page' | 'extension' | string
required
Origin of the message sender
Core Functions
sendMessage
Send a message to the background service and receive a response.
export function sendMessage<TMessageType extends MessageTypes>(
message: TMessageType,
request?: RequestTypes[TMessageType],
subscriber?: (data: unknown) => void
): Promise<ResponseTypes[TMessageType]>
The message type identifier
request
RequestTypes[TMessageType]
Request payload (optional for null-request messages)
Callback function for subscription-based messages
Returns: Promise<ResponseTypes[TMessageType]>
Example: Simple Request
import { sendMessage } from '@subwallet/extension-koni-ui/messaging/base';
// Ping the background service
const response = await sendMessage('pri(ping)', null);
Example: Request with Payload
// Create account with seed phrase
const result = await sendMessage('pri(accounts.create.suriV2)', {
name: 'My Account',
password: 'secure-password',
suri: 'seed phrase words...',
types: ['sr25519']
});
Example: Subscription
// Subscribe to notifications
await sendMessage(
'pri(notifications.subscribe)',
null,
(notifications) => {
console.log('Received notifications:', notifications);
}
);
subscribeMessage
Create a subscription that starts immediately and returns an unsubscribe function.
export function subscribeMessage<TMessageType extends MessageTypesWithSubscriptions>(
message: TMessageType,
request: RequestTypes[TMessageType],
callback: (data: ResponseTypes[TMessageType]) => void,
subscriber: (data: SubscriptionMessageTypes[TMessageType]) => void
): {
promise: Promise<ResponseTypes[TMessageType]>,
unsub: () => void
}
The subscription message type
request
RequestTypes[TMessageType]
required
Request payload
Called once when subscription is established
Called for each subscription update
Returns: Object with promise and unsub function
Example
import { subscribeMessage } from '@subwallet/extension-koni-ui/messaging/base';
const { promise, unsub } = subscribeMessage(
'pri(transaction.history.subscribe)',
{ address: '0x...', chain: 'polkadot' },
(initial) => console.log('Initial data:', initial),
(update) => console.log('Update:', update)
);
// Later, unsubscribe
unsub();
lazySubscribeMessage
Create a subscription that can be started manually.
export function lazySubscribeMessage<TMessageType extends MessageTypesWithSubscriptions>(
message: TMessageType,
request: RequestTypes[TMessageType],
callback: (data: ResponseTypes[TMessageType]) => void,
subscriber: (data: SubscriptionMessageTypes[TMessageType]) => void
): {
promise: Promise<ResponseTypes[TMessageType]>,
start: () => void,
unsub: () => void
}
Returns: Object with promise, start, and unsub functions
Example
const { promise, start, unsub } = lazySubscribeMessage(
'pri(price.subscribeCurrentTokenPrice)',
'bitcoin-price-id',
(initial) => console.log('Initial price:', initial),
(update) => console.log('Price update:', update)
);
// Start subscription when needed
start();
// Stop when done
unsub();
Message Categories
Account Messages
// Create account from seed/private key
await sendMessage('pri(accounts.create.suriV2)', request);
// Export account as JSON
await sendMessage('pri(accounts.export.json)', { address, password });
// Save current account
await sendMessage('pri(accounts.saveCurrentProxy)', { address });
Settings Messages
// Subscribe to settings changes
await sendMessage('pri(settings.subscribe)', data, callback);
// Save theme preference
await sendMessage('pri(settings.saveTheme)', 'dark');
// Toggle balance visibility
await sendMessage('pri(settings.changeBalancesVisibility)', null);
Transaction Messages
// Get transaction details
await sendMessage('pri(transactions.getOne)', { hash, chain });
// Subscribe to transaction history
await sendMessage(
'pri(transaction.history.subscribe)',
{ address, chain },
callback
);
Price & Chart Messages
// Subscribe to current token price
await sendMessage('pri(price.subscribeCurrentTokenPrice)', priceId, callback);
// Get historical price data
await sendMessage('pri(price.getHistory)', { priceId, timeframe });
// Check if chart is available
await sendMessage('pri(price.checkCoinGeckoPriceSupport)', priceId);
Port Connection
The messaging system maintains a persistent connection to the background service:
const PORT_EXTENSION = 'subwallet-extension';
let port: chrome.runtime.Port;
port = chrome.runtime.connect({ name: PORT_EXTENSION });
Auto-Reconnection
The port automatically reconnects if the connection is lost:
port.onDisconnect.addListener(() => {
const err = chrome.runtime.lastError;
if (err) {
console.warn(`${err.message}, Reconnecting to the port.`);
setTimeout(onConnectPort, 1000);
}
});
Message Handler
Internal handler structure for managing pending requests:
interface Handler {
resolve: (data: any) => void;
reject: (error: Error) => void;
subscriber?: (data: any) => void;
}
type Handlers = Record<string, Handler>;
Handlers are stored by message ID and automatically cleaned up after one-time requests.
Error Handling
port.onMessage.addListener((data) => {
const handler = handlers[data.id];
if (!handler) {
console.error(`Unknown response: ${JSON.stringify(data)}`);
return;
}
if (data.error) {
handler.reject(new Error(data.error));
} else {
handler.resolve(data.response);
}
});
Best Practices
-
Use typed messages: Always use the predefined message type constants to ensure type safety
-
Handle errors: Wrap
sendMessage calls in try-catch blocks
try {
const result = await sendMessage('pri(accounts.create.suriV2)', request);
} catch (error) {
console.error('Account creation failed:', error);
}
- Cleanup subscriptions: Always call
unsub() when subscriptions are no longer needed
useEffect(() => {
const { unsub } = subscribeMessage(...);
return () => unsub();
}, []);
- Batch related operations: Combine related requests to reduce message overhead
Common Patterns
Request-Response Pattern
// One-time request
const data = await sendMessage('pri(someMessage)', request);
Subscribe-Update Pattern
// Long-lived subscription
const { unsub } = subscribeMessage(
'pri(someMessage.subscribe)',
request,
(initial) => setState(initial),
(update) => setState(update)
);
Lazy Initialization Pattern
// Create subscription but start later
const { start, unsub } = lazySubscribeMessage(...);
// Start when component mounts
useEffect(() => {
start();
return () => unsub();
}, []);