@magicbutton.cloud/chrome-transport v0.2.0
Chrome Transport Provider for Magic Button Cloud
This package provides a Chrome Transport implementation for the Magic Button Cloud messaging library, enabling seamless communication within Chrome Extensions and between extensions.
Features
- Communication between background scripts, popup, content scripts, and side panels
- Side panel support with full control API (open, close, configure)
- Cross-extension messaging
- Automatic reconnection support
- Error handling and timeouts
- Fully type-safe messaging
- Compatible with the Magic Button Cloud messaging framework
Installation
npm install @magicbutton.cloud/chrome-transportUsage Examples
1. Background Script (Service Worker)
import { Server } from '@magicbutton.cloud/messaging';
import { ChromeTransportFactory } from '@magicbutton.cloud/chrome-transport';
// Create a transport factory
const transportFactory = new ChromeTransportFactory();
// Create a server
const server = Server.create({
transportFactory,
transportConfig: {
connectionName: 'background-server',
debug: true
}
});
// Define request handlers
server.onRequest('getUsers', async (payload, context) => {
// Handle the request
return {
users: [
{ id: 1, name: 'User 1' },
{ id: 2, name: 'User 2' }
]
};
});
// Start the server
server.start().then(() => {
console.log('Background server started');
});2. Popup Script
import { Client } from '@magicbutton.cloud/messaging';
import { ChromeTransportFactory } from '@magicbutton.cloud/chrome-transport';
// Create a transport factory
const transportFactory = new ChromeTransportFactory();
// Create a client
const client = Client.create({
transportFactory,
transportConfig: {
connectionName: 'popup-client',
reconnect: true,
reconnectOptions: {
maxRetries: 3,
backoffFactor: 1.5,
initialDelayMs: 1000
},
debug: true
}
});
// Connect to the background script
client.connect().then(() => {
console.log('Connected to background service worker');
// Make a request
client.request('getUsers', {})
.then(response => {
console.log('Users:', response.users);
// Update UI with users...
})
.catch(error => {
console.error('Error fetching users:', error);
});
});
// Subscribe to events
client.on('userAdded', (user) => {
console.log('New user added:', user);
// Update UI...
});
// Clean up when popup closes
window.addEventListener('unload', () => {
client.disconnect().catch(console.error);
});3. Content Script
import { Client } from '@magicbutton.cloud/messaging';
import { ChromeTransportFactory } from '@magicbutton.cloud/chrome-transport';
// Create a transport factory
const transportFactory = new ChromeTransportFactory();
// Create a client
const client = Client.create({
transportFactory,
transportConfig: {
connectionName: 'content-script-client',
debug: true
}
});
// Connect to the background script
client.connect().then(() => {
console.log('Content script connected to background service worker');
// Set up page observer
const observer = new MutationObserver(() => {
// When page changes, notify the background script
client.request('pageContentChanged', {
url: window.location.href,
title: document.title
}).catch(console.error);
});
// Start observing
observer.observe(document.body, {
childList: true,
subtree: true
});
});
// Clean up when page unloads
window.addEventListener('unload', () => {
client.disconnect().catch(console.error);
});4. Working with Side Panels (Background Script)
import { Server } from '@magicbutton.cloud/messaging';
import { ChromeTransport, ChromeTransportFactory } from '@magicbutton.cloud/chrome-transport';
// Create a transport factory
const transportFactory = new ChromeTransportFactory();
// Create a server in the background script
const server = Server.create({
transportFactory,
transportConfig: {
connectionName: 'background-server',
context: 'background',
sidePanel: {
path: 'sidepanel.html',
initialWidth: 400
},
debug: true
}
});
// Get the transport instance to control the side panel
const transport = server.getTransport() as ChromeTransport;
// Handle show side panel requests
server.onRequest('showSidePanel', async (payload) => {
await transport.openSidePanel();
return { success: true };
});
// Handle hide side panel requests
server.onRequest('hideSidePanel', async (payload) => {
await transport.closeSidePanel();
return { success: true };
});
// Handle get side panel state requests
server.onRequest('getSidePanelState', async (payload) => {
const state = await transport.getSidePanelState();
return state;
});
// Handle set side panel width requests
server.onRequest('setSidePanelWidth', async (payload) => {
await transport.setSidePanelState({
width: payload.width
});
return { success: true };
});
// Start the server
server.start().then(() => {
console.log('Background server started');
});5. Side Panel Implementation
import { Client } from '@magicbutton.cloud/messaging';
import { ChromeTransportFactory } from '@magicbutton.cloud/chrome-transport';
// Create a transport factory
const transportFactory = new ChromeTransportFactory();
// Create a client in the side panel
const client = Client.create({
transportFactory,
transportConfig: {
connectionName: 'sidepanel-client',
context: 'side-panel',
debug: true
}
});
// Connect to the background script
client.connect().then(() => {
console.log('Side panel connected to background service worker');
// Request data when the side panel loads
client.request('getData', { view: 'sidepanel' })
.then(response => {
console.log('Data received:', response);
// Update UI with the data...
})
.catch(error => {
console.error('Error fetching data:', error);
});
// Set up UI handlers
document.getElementById('closeButton').addEventListener('click', () => {
client.request('hideSidePanel', {}).catch(console.error);
});
});
// Clean up when the side panel unloads
window.addEventListener('unload', () => {
client.disconnect().catch(console.error);
});6. Communicating with External Extensions
import { Client } from '@magicbutton.cloud/messaging';
import { ChromeTransportFactory } from '@magicbutton.cloud/chrome-transport';
// Create a transport factory
const transportFactory = new ChromeTransportFactory();
// Create a client that connects to an external extension
const externalClient = Client.create({
transportFactory,
transportConfig: {
extensionId: 'external-extension-id', // ID of the external extension
connectionName: 'external-client',
debug: true
}
});
// Connect to the external extension
externalClient.connect().then(() => {
console.log('Connected to external extension');
// Make requests to the external extension
externalClient.request('getData', { query: 'example' })
.then(response => {
console.log('Data from external extension:', response);
})
.catch(error => {
console.error('Error communicating with external extension:', error);
});
});API Reference
ChromeTransportConfig
interface ChromeTransportConfig {
/**
* Extension ID to connect to. If not provided, will use internal communication
* within the same extension.
*/
extensionId?: string;
/**
* Connection name to identify this connection
*/
connectionName: string;
/**
* Transport context (identifies the part of the extension this transport is running in)
*/
context?: 'background' | 'popup' | 'content-script' | 'side-panel' | 'devtools';
/**
* Whether to reconnect automatically on disconnection
*/
reconnect?: boolean;
/**
* Reconnection options
*/
reconnectOptions?: {
maxRetries: number;
backoffFactor: number;
initialDelayMs: number;
};
/**
* Connection timeout in milliseconds
*/
connectTimeoutMs?: number;
/**
* Side panel specific options
*/
sidePanel?: {
/**
* Path to the side panel HTML file (relative to the extension root)
*/
path?: string;
/**
* Initial width for the side panel in pixels
*/
initialWidth?: number;
};
/**
* Debug mode - will log additional information
*/
debug?: boolean;
}ChromeTransportFactory
The factory class used to create ChromeTransport instances.
class ChromeTransportFactory {
create(config: any): Transport<any, any>;
}ChromeTransport
The Chrome Transport implementation with side panel support.
class ChromeTransport implements Transport<any, any> {
constructor(config: ChromeTransportConfig);
// Transport interface methods
connect(options?: any): Promise<void>;
disconnect(): Promise<void>;
sendRequest(type: string, payload: any, context?: any): Promise<any>;
subscribe<K extends string>(eventType: K, handler: (data: any) => void): () => void;
registerRequestHandler<K extends string>(type: K, handler: (data: any) => Promise<any>): () => void;
publish(eventType: string, payload: any): Promise<void>;
// Side panel methods
openSidePanel(): Promise<void>;
closeSidePanel(): Promise<void>;
setSidePanelState(options: {
path?: string;
enabled?: boolean;
width?: number;
}): Promise<void>;
getSidePanelState(): Promise<chrome.sidePanel.GetPanelOptions>;
}Best Practices
Use unique connection names: Make sure your connection names are descriptive and unique to avoid conflicts.
Specify the context: Always set the
contextproperty in the config to identify which part of the extension the transport is running in (background, popup, content-script, side-panel, etc.).Handle disconnections: Chrome extensions can be unloaded or reloaded at any time. Use the automatic reconnection feature to ensure your messaging remains robust.
Clean up resources: Always call
disconnect()when your component is being unloaded to prevent memory leaks.Error handling: Implement proper error handling for all requests to ensure your extension remains functional even when communication fails.
Side Panel control: Always control side panels from the background script using the dedicated methods.
Security considerations: When communicating with external extensions, validate the source and content of messages to prevent security vulnerabilities.
Manifest configuration: When using side panels, ensure your manifest.json includes the necessary permissions and configurations:
{
"manifest_version": 3,
"name": "My Extension",
"version": "1.0",
"side_panel": {
"default_path": "sidepanel.html"
},
"permissions": [
"sidePanel"
],
"background": {
"service_worker": "background.js",
"type": "module"
}
}License
MIT
6 months ago