Communication and Sharing APIs
1. Broadcast Channel API for Tab Communication
| Method/Property | Description | Browser Support |
|---|---|---|
| new BroadcastChannel(name) | Creates channel with specified name. All same-origin contexts with same name can communicate. | All Modern Browsers |
| channel.postMessage(data) | Sends message to all listeners on same channel. Data is cloned using structured clone algorithm. | All Modern Browsers |
| channel.onmessage | Event handler for incoming messages. Receives MessageEvent with data property. |
All Modern Browsers |
| channel.close() | Closes channel and stops receiving messages. Should be called when done to free resources. | All Modern Browsers |
| channel.name | Read-only property returning channel name. | All Modern Browsers |
Example: Tab synchronization with BroadcastChannel
// Create broadcast channel for tab sync
const channel = new BroadcastChannel("app-sync");
// Listen for messages
channel.onmessage = (event) => {
console.log("Received from another tab:", event.data);
const { "type": type, "payload": payload } = event.data;
switch (type) {
case "USER_LOGGED_IN":
updateUIForLoggedInUser(payload.user);
break;
case "USER_LOGGED_OUT":
updateUIForLoggedOutUser();
break;
case "CART_UPDATED":
updateCart(payload.items);
break;
case "THEME_CHANGED":
applyTheme(payload.theme);
break;
default:
console.log("Unknown message type:", type);
}
};
// Error handling
channel.onerror = (event) => {
console.error("BroadcastChannel error:", event);
};
// Send message to other tabs
function notifyOtherTabs(type, payload) {
channel.postMessage({
"type": type,
"payload": payload,
"timestamp": Date.now(),
"tabId": sessionStorage.getItem("tabId")
});
}
// Example usage
document.getElementById("loginBtn").addEventListener("click", () => {
const user = { "id": "123", "name": "John Doe" };
// Update current tab
updateUIForLoggedInUser(user);
// Notify other tabs
notifyOtherTabs("USER_LOGGED_IN", { "user": user });
});
// Clean up when page unloads
window.addEventListener("beforeunload", () => {
channel.close();
});
Example: Real-time notification sync across tabs
// Notification synchronization across tabs
class TabNotificationSync {
constructor() {
this.channel = new BroadcastChannel("notifications");
this.notifications = [];
this.channel.onmessage = this.handleMessage.bind(this);
}
handleMessage(event) {
const { "action": action, "notification": notification } = event.data;
switch (action) {
case "ADD":
this.addNotification(notification, false); // false = don't broadcast again
break;
case "REMOVE":
this.removeNotification(notification.id, false);
break;
case "CLEAR_ALL":
this.clearAll(false);
break;
case "MARK_READ":
this.markAsRead(notification.id, false);
break;
}
}
addNotification(notification, broadcast = true) {
this.notifications.push(notification);
this.updateUI();
if (broadcast) {
this.channel.postMessage({
"action": "ADD",
"notification": notification
});
}
}
removeNotification(id, broadcast = true) {
this.notifications = this.notifications.filter(n => n.id !== id);
this.updateUI();
if (broadcast) {
this.channel.postMessage({
"action": "REMOVE",
"notification": { "id": id }
});
}
}
markAsRead(id, broadcast = true) {
const notification = this.notifications.find(n => n.id === id);
if (notification) {
notification.read = true;
this.updateUI();
if (broadcast) {
this.channel.postMessage({
"action": "MARK_READ",
"notification": { "id": id }
});
}
}
}
clearAll(broadcast = true) {
this.notifications = [];
this.updateUI();
if (broadcast) {
this.channel.postMessage({ "action": "CLEAR_ALL" });
}
}
updateUI() {
const badge = document.getElementById("notification-badge");
const unreadCount = this.notifications.filter(n => !n.read).length;
badge.textContent = unreadCount || "";
badge.style.display = unreadCount > 0 ? "block" : "none";
}
destroy() {
this.channel.close();
}
}
// Initialize
const notificationSync = new TabNotificationSync();
// Add notification
notificationSync.addNotification({
"id": Date.now(),
"message": "New message received",
"read": false
});
Note: BroadcastChannel enables same-origin tab communication without a server. Messages are not persistent - lost if no tabs listening. Use for real-time sync (login state, cart, settings). Works across tabs, windows, iframes. Data must be structured-cloneable (no functions).
2. Web Share API for Native Sharing
| Method/Property | Description | Browser Support |
|---|---|---|
| navigator.canShare(data) | Returns boolean indicating if data can be shared. Check before calling share(). |
Modern Browsers |
| navigator.share(data) | Opens native share dialog. Data object can include title, text, url, files. |
Modern Browsers |
| Share Data Property | Type | Description |
|---|---|---|
| title | string |
Title for shared content. Optional. |
| text | string |
Text/description to share. Optional. |
| url | string |
URL to share. Optional. |
| files | File[] |
Array of File objects to share. Optional. Check support with canShare(). |
Example: Share text and URL
// Check if Web Share API is supported
if (navigator.share) {
console.log("Web Share API supported");
} else {
console.log("Web Share API not supported - show fallback UI");
}
// Share content
async function shareContent() {
// Must be called from user gesture (click, tap)
const shareData = {
"title": "Amazing Article",
"text": "Check out this great article I found!",
"url": "https://example.com/article/123"
};
try {
await navigator.share(shareData);
console.log("Content shared successfully");
} catch (error) {
if (error.name === "AbortError") {
console.log("User cancelled share");
} else {
console.error("Error sharing:", error);
// Show fallback share options (copy link, social media buttons)
showFallbackShareUI(shareData);
}
}
}
// Add event listener
document.getElementById("shareBtn").addEventListener("click", shareContent);
Example: Share files
// Share image file
async function shareImage() {
try {
// Get or create file
const response = await fetch("/images/photo.jpg");
const blob = await response.blob();
const file = new File([blob], "photo.jpg", { "type": "image/jpeg" });
// Check if files can be shared
const shareData = {
"files": [file],
"title": "Beautiful Photo",
"text": "Check out this photo!"
};
if (!navigator.canShare) {
throw new Error("Web Share API not supported");
}
if (!navigator.canShare(shareData)) {
throw new Error("Cannot share this file type");
}
// Share
await navigator.share(shareData);
console.log("File shared successfully");
} catch (error) {
console.error("Error sharing file:", error);
// Fallback: download file or copy link
showDownloadOption(file);
}
}
// Share multiple files
async function shareMultipleFiles(files) {
const shareData = {
"files": files,
"title": "My Files"
};
if (navigator.canShare && navigator.canShare(shareData)) {
try {
await navigator.share(shareData);
console.log("Files shared");
} catch (error) {
console.error("Share failed:", error);
}
} else {
console.log("Cannot share files - browser limitation");
// Fallback to individual downloads
}
}
// Share canvas as image
async function shareCanvas(canvas) {
canvas.toBlob(async (blob) => {
const file = new File([blob], "drawing.png", { "type": "image/png" });
if (navigator.canShare && navigator.canShare({ "files": [file] })) {
await navigator.share({
"files": [file],
"title": "My Drawing"
});
}
});
}
Note: Web Share API provides native sharing - opens OS share sheet on mobile. Must be triggered by user gesture (click/tap). Only works on HTTPS. Always provide fallback for unsupported browsers. File sharing support varies by platform.
Warning: Always check
navigator.share availability before using. Use canShare() to verify data is shareable. User can cancel share - handle AbortError. Desktop browsers have limited support - mobile is primary use case. Provide copy-link fallback.
3. Web Share Target API for Receiving Shares
| Manifest Property | Description |
|---|---|
| share_target | Defines how app receives shared content. Object with action, method, enctype, params. |
| share_target.action | URL to handle share (e.g., /share). Relative to app scope. |
| share_target.method | HTTP method: "GET" or "POST". Default: "GET". |
| share_target.enctype | For POST: "application/x-www-form-urlencoded" or "multipart/form-data" (for files). |
| share_target.params | Maps share data to query/form parameters: title, text, url, files. |
Example: Web app manifest for share target
{
"name": "My Sharing App",
"short_name": "ShareApp",
"start_url": "/",
"display": "standalone",
"share_target": {
"action": "/share",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"title": "title",
"text": "text",
"url": "url",
"files": [
{
"name": "media",
"accept": ["image/*", "video/*", "audio/*"]
}
]
}
}
}
Example: Handle shared content in service worker
// service-worker.js - Handle share target
self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);
// Check if this is a share request
if (url.pathname === "/share" && event.request.method === "POST") {
event.respondWith(handleShare(event.request));
}
});
async function handleShare(request) {
try {
const formData = await request.formData();
const title = formData.get("title") || "";
const text = formData.get("text") || "";
const url = formData.get("url") || "";
const mediaFiles = formData.getAll("media");
// Store shared content
const shareData = {
"title": title,
"text": text,
"url": url,
"files": [],
"timestamp": Date.now()
};
// Process files
for (const file of mediaFiles) {
// Store file (could use IndexedDB, Cache API, etc.)
const arrayBuffer = await file.arrayBuffer();
shareData.files.push({
"name": file.name,
"type": file.type,
"size": file.size,
"data": arrayBuffer
});
}
// Store in IndexedDB
const db = await openDatabase();
await storeSharedContent(db, shareData);
// Redirect to app with share ID
const shareId = Date.now();
return Response.redirect(`/app?share=${shareId}`, 303);
} catch (error) {
console.error("Error handling share:", error);
return new Response("Error processing shared content", { "status": 500 });
}
}
Example: Process shared content in app
// app.js - Process shared content
async function checkForSharedContent() {
const params = new URLSearchParams(window.location.search);
const shareId = params.get("share");
if (shareId) {
try {
// Retrieve shared content from IndexedDB
const db = await openDatabase();
const shareData = await getSharedContent(db, shareId);
if (shareData) {
console.log("Received shared content:", shareData);
// Display shared content
if (shareData.title) {
document.getElementById("title").value = shareData.title;
}
if (shareData.text) {
document.getElementById("content").value = shareData.text;
}
if (shareData.url) {
document.getElementById("link").value = shareData.url;
}
if (shareData.files.length > 0) {
displaySharedFiles(shareData.files);
}
// Clear URL parameter
window.history.replaceState({}, "", "/app");
}
} catch (error) {
console.error("Error loading shared content:", error);
}
}
}
// Run on page load
checkForSharedContent();
Note: Web Share Target API makes PWAs share targets - appear in OS share sheet. Requires web app manifest with
share_target field. App must be installed to appear as share target. Use service worker to handle POST requests with files.
Warning: Only works for installed PWAs - not regular web pages. Limited browser support (mainly Chrome/Edge on Android). Files require POST with multipart/form-data. Test thoroughly on target platforms. Provide clear UI feedback for shared content.
4. MessageChannel and MessagePort APIs
| Class/Method | Description | Browser Support |
|---|---|---|
| new MessageChannel() | Creates channel with two MessagePort objects for bidirectional communication. | All Browsers |
| channel.port1 | First MessagePort of the channel. | All Browsers |
| channel.port2 | Second MessagePort of the channel. | All Browsers |
| port.postMessage(data, [transfer]) | Sends message through port. Optional transfer array for transferable objects. | All Browsers |
| port.onmessage | Event handler for messages. Port must be started via start() or setting onmessage. |
All Browsers |
| port.start() | Starts port (enables message delivery). Called automatically when setting onmessage. |
All Browsers |
| port.close() | Closes port and stops message delivery. | All Browsers |
Example: Communication between iframe and parent
// Parent page
const iframe = document.getElementById("myFrame");
// Create channel
const channel = new MessageChannel();
// Listen on port1
channel.port1.onmessage = (event) => {
console.log("Parent received:", event.data);
// Send response
channel.port1.postMessage({
"type": "RESPONSE",
"data": "Hello from parent"
});
};
// Transfer port2 to iframe
iframe.contentWindow.postMessage(
{ "type": "INIT", "message": "Here's your port" },
"*",
[channel.port2] // Transfer port ownership
);
// Send message to iframe
setTimeout(() => {
channel.port1.postMessage({
"type": "REQUEST",
"data": "What's your status?"
});
}, 1000);
// ===== iframe page =====
// Listen for port from parent
window.addEventListener("message", (event) => {
if (event.data.type === "INIT") {
// Get transferred port
const port = event.ports[0];
// Listen on port
port.onmessage = (e) => {
console.log("Iframe received:", e.data);
if (e.data.type === "REQUEST") {
// Send response
port.postMessage({
"type": "STATUS",
"data": "All systems operational"
});
}
};
// Send initial message
port.postMessage({
"type": "READY",
"data": "Iframe initialized"
});
}
});
Example: Worker communication with MessageChannel
// Main thread
const worker = new Worker("worker.js");
const channel = new MessageChannel();
// Listen on port1
channel.port1.onmessage = (event) => {
console.log("Main thread received:", event.data);
document.getElementById("result").textContent = event.data.result;
};
// Send port2 to worker
worker.postMessage(
{ "type": "INIT_PORT" },
[channel.port2]
);
// Send work request via channel
function processData(data) {
channel.port1.postMessage({
"type": "PROCESS",
"data": data
});
}
document.getElementById("processBtn").addEventListener("click", () => {
processData([1, 2, 3, 4, 5]);
});
// ===== worker.js =====
let port = null;
self.onmessage = (event) => {
if (event.data.type === "INIT_PORT") {
// Get transferred port
port = event.ports[0];
// Listen on port
port.onmessage = (e) => {
if (e.data.type === "PROCESS") {
// Process data
const result = e.data.data.reduce((sum, num) => sum + num, 0);
// Send result back
port.postMessage({
"type": "RESULT",
"result": result
});
}
};
// Notify ready
port.postMessage({
"type": "READY",
"message": "Worker ready for processing"
});
}
};
Example: Dedicated communication channels
// Create multiple channels for different purposes
class ChannelManager {
constructor() {
this.channels = new Map();
}
createChannel(name) {
const channel = new MessageChannel();
this.channels.set(name, channel);
return channel;
}
getChannel(name) {
return this.channels.get(name);
}
sendMessage(channelName, portNumber, data) {
const channel = this.channels.get(channelName);
if (channel) {
const port = portNumber === 1 ? channel.port1 : channel.port2;
port.postMessage(data);
}
}
cleanup() {
this.channels.forEach(channel => {
channel.port1.close();
channel.port2.close();
});
this.channels.clear();
}
}
// Usage
const manager = new ChannelManager();
// Create channels for different modules
const authChannel = manager.createChannel("auth");
const dataChannel = manager.createChannel("data");
// Setup listeners
authChannel.port1.onmessage = (event) => {
console.log("Auth message:", event.data);
};
dataChannel.port1.onmessage = (event) => {
console.log("Data message:", event.data);
};
// Transfer ports to worker
worker.postMessage(
{ "type": "SETUP" },
[authChannel.port2, dataChannel.port2]
);
// Clean up when done
window.addEventListener("beforeunload", () => {
manager.cleanup();
});
Note: MessageChannel creates dedicated communication pipe with two ports. Use for structured communication between contexts (workers, iframes). Ports are transferable - ownership can be transferred. More efficient than
postMessage for ongoing communication.
5. Cross-Origin Communication Patterns
| Method | Description | Use Case |
|---|---|---|
| window.postMessage(message, targetOrigin, [transfer]) | Sends message to another window/iframe. Always specify targetOrigin for security. |
Cross-origin iframe communication |
| window.addEventListener("message", handler) | Listens for postMessage events. Always validate event.origin. |
Receiving cross-origin messages |
| event.origin | Origin of message sender. Validate before processing message. | Security check |
| event.source | Reference to window that sent message. Use to send response. | Bidirectional communication |
| event.data | Message data (structured clone). | Payload |
| event.ports | Array of transferred MessagePort objects. | Channel establishment |
Example: Secure cross-origin iframe communication
// Parent page (https://parent.com)
const iframe = document.getElementById("childFrame");
const ALLOWED_ORIGIN = "https://child.com";
// Listen for messages from iframe
window.addEventListener("message", (event) => {
// CRITICAL: Always validate origin
if (event.origin !== ALLOWED_ORIGIN) {
console.warn("Message from unauthorized origin:", event.origin);
return;
}
console.log("Received from iframe:", event.data);
// Process message
const { "type": type, "payload": payload } = event.data;
switch (type) {
case "REQUEST_USER_DATA":
// Send user data to iframe
event.source.postMessage({
"type": "USER_DATA",
"payload": {
"userId": "123",
"name": "John Doe"
}
}, ALLOWED_ORIGIN);
break;
case "HEIGHT_CHANGED":
// Resize iframe
iframe.style.height = payload.height + "px";
break;
default:
console.log("Unknown message type:", type);
}
});
// Send message to iframe
function sendToIframe(data) {
iframe.contentWindow.postMessage(data, ALLOWED_ORIGIN);
}
// Wait for iframe to load
iframe.addEventListener("load", () => {
sendToIframe({
"type": "INIT",
"payload": { "theme": "dark" }
});
});
// ===== Iframe page (https://child.com) =====
const PARENT_ORIGIN = "https://parent.com";
// Listen for messages from parent
window.addEventListener("message", (event) => {
// Validate origin
if (event.origin !== PARENT_ORIGIN) {
console.warn("Message from unauthorized origin:", event.origin);
return;
}
const { "type": type, "payload": payload } = event.data;
switch (type) {
case "INIT":
// Initialize with config from parent
applyTheme(payload.theme);
// Request user data
window.parent.postMessage({
"type": "REQUEST_USER_DATA"
}, PARENT_ORIGIN);
break;
case "USER_DATA":
// Use received data
displayUserInfo(payload);
break;
}
});
// Notify parent of height changes
const resizeObserver = new ResizeObserver((entries) => {
const height = entries[0].contentRect.height;
window.parent.postMessage({
"type": "HEIGHT_CHANGED",
"payload": { "height": height }
}, PARENT_ORIGIN);
});
resizeObserver.observe(document.body);
Example: Cross-origin authentication flow
// Main app - open OAuth popup
function initiateOAuth() {
const authWindow = window.open(
"https://auth-provider.com/oauth",
"oauth",
"width=600,height=700"
);
// Listen for auth result
const messageHandler = (event) => {
// Validate origin
if (event.origin !== "https://auth-provider.com") {
return;
}
if (event.data.type === "OAUTH_SUCCESS") {
console.log("OAuth successful:", event.data.token);
// Store token
localStorage.setItem("authToken", event.data.token);
// Close popup
authWindow.close();
// Clean up listener
window.removeEventListener("message", messageHandler);
// Update UI
onAuthenticationSuccess(event.data.user);
} else if (event.data.type === "OAUTH_ERROR") {
console.error("OAuth failed:", event.data.error);
authWindow.close();
window.removeEventListener("message", messageHandler);
onAuthenticationError(event.data.error);
}
};
window.addEventListener("message", messageHandler);
}
// ===== OAuth provider page =====
function completeOAuth(token, user) {
// Send result to opener
if (window.opener) {
window.opener.postMessage({
"type": "OAUTH_SUCCESS",
"token": token,
"user": user
}, "https://main-app.com"); // Specify target origin
// Close self
window.close();
}
}
function failOAuth(error) {
if (window.opener) {
window.opener.postMessage({
"type": "OAUTH_ERROR",
"error": error
}, "https://main-app.com");
window.close();
}
}
Note:
postMessage enables secure cross-origin communication. Always specify targetOrigin when sending. Always validate event.origin when receiving. Use structured data with type field. Common for iframes, popups, worker communication.
Warning: NEVER use
targetOrigin: "*" with sensitive data - major security risk. Always validate event.origin before processing messages. Don't trust message content - validate and sanitize. Be aware of timing attacks. Use MessageChannel for long-term communication.
6. Channel Messaging API for Worker Communication
| Pattern | Description | Use Case |
|---|---|---|
| Worker postMessage | Direct message to worker via worker.postMessage(). Simple but limited to one-way request-response. |
Simple worker tasks |
| MessageChannel with Workers | Create dedicated channel for bidirectional communication. More structured than direct postMessage. | Complex worker interactions |
| SharedWorker Communication | Multiple tabs/windows connect to same worker. Each gets own MessagePort. | Cross-tab shared state |
| BroadcastChannel in Worker | Workers can use BroadcastChannel to communicate with main thread and other workers. | Multi-worker coordination |
Example: SharedWorker with MessagePort
// shared-worker.js
const connections = new Set();
self.addEventListener("connect", (event) => {
const port = event.ports[0];
connections.add(port);
console.log("New connection. Total:", connections.size);
port.addEventListener("message", (e) => {
const { "type": type, "data": data } = e.data;
switch (type) {
case "BROADCAST":
// Send to all connected ports except sender
connections.forEach(p => {
if (p !== port) {
p.postMessage({
"type": "MESSAGE",
"data": data
});
}
});
break;
case "GET_CONNECTION_COUNT":
port.postMessage({
"type": "CONNECTION_COUNT",
"count": connections.size
});
break;
}
});
port.start();
// Handle disconnect
port.addEventListener("close", () => {
connections.delete(port);
console.log("Connection closed. Remaining:", connections.size);
});
});
// ===== Main thread usage =====
// Connect to shared worker
const worker = new SharedWorker("shared-worker.js");
const port = worker.port;
port.addEventListener("message", (event) => {
console.log("Received from shared worker:", event.data);
if (event.data.type === "MESSAGE") {
displayMessage(event.data.data);
}
});
port.start();
// Send broadcast message
function broadcastMessage(message) {
port.postMessage({
"type": "BROADCAST",
"data": message
});
}
// Get connection count
function getConnectionCount() {
port.postMessage({ "type": "GET_CONNECTION_COUNT" });
}
Example: Multi-worker coordination
// Main thread - coordinate multiple workers
class WorkerPool {
constructor(scriptUrl, size) {
this.workers = [];
this.channels = [];
this.taskQueue = [];
this.busyWorkers = new Set();
// Create workers and channels
for (let i = 0; i < size; i++) {
const worker = new Worker(scriptUrl);
const channel = new MessageChannel();
// Setup worker communication
worker.postMessage(
{ "type": "INIT", "workerId": i },
[channel.port2]
);
// Listen on port1
channel.port1.onmessage = (event) => {
this.handleWorkerMessage(i, event.data);
};
this.workers.push(worker);
this.channels.push(channel);
}
}
handleWorkerMessage(workerId, data) {
if (data.type === "TASK_COMPLETE") {
console.log(`Worker ${workerId} completed task:`, data.result);
// Mark worker as available
this.busyWorkers.delete(workerId);
// Process callback
if (data.taskId && this.callbacks.has(data.taskId)) {
this.callbacks.get(data.taskId)(data.result);
this.callbacks.delete(data.taskId);
}
// Process next task
this.processNextTask();
}
}
async executeTask(taskData) {
return new Promise((resolve) => {
const taskId = Date.now() + Math.random();
this.taskQueue.push({
"id": taskId,
"data": taskData,
"callback": resolve
});
this.callbacks = this.callbacks || new Map();
this.callbacks.set(taskId, resolve);
this.processNextTask();
});
}
processNextTask() {
if (this.taskQueue.length === 0) return;
// Find available worker
const availableWorker = this.workers.findIndex(
(_, index) => !this.busyWorkers.has(index)
);
if (availableWorker === -1) return; // All busy
const task = this.taskQueue.shift();
this.busyWorkers.add(availableWorker);
// Send task to worker
this.channels[availableWorker].port1.postMessage({
"type": "TASK",
"taskId": task.id,
"data": task.data
});
}
terminate() {
this.workers.forEach(worker => worker.terminate());
this.channels.forEach(channel => {
channel.port1.close();
});
}
}
// Usage
const pool = new WorkerPool("task-worker.js", 4);
// Execute tasks
async function processManyTasks() {
const tasks = Array.from({ "length": 100 }, (_, i) => ({
"operation": "calculate",
"value": i
}));
const results = await Promise.all(
tasks.map(task => pool.executeTask(task))
);
console.log("All tasks completed:", results);
}
processManyTasks();
Note: Use MessageChannel for structured worker communication. SharedWorker enables cross-tab communication via MessagePorts. Each connection gets dedicated port. Good for coordinating work across tabs or managing worker pools.
Warning: SharedWorker has limited browser support (no Safari). Always call
port.start() for SharedWorker ports. Clean up ports and workers when done to prevent memory leaks. Test worker communication error scenarios.
Communication and Sharing Best Practices
- Use BroadcastChannel for simple same-origin tab sync - logout, cart updates
- Web Share API must be triggered by user gesture - provide fallback
- Check
canShare()before attempting to share files - Web Share Target requires installed PWA - test on target platforms
- MessageChannel more efficient than postMessage for ongoing communication
- Always validate
event.originin postMessage handlers - critical security - Never use
targetOrigin: "*"with sensitive data - SharedWorker enables cross-tab state but limited browser support
- Use structured message format with type field for all communication
- Call
port.start()when using addEventListener instead of onmessage - Clean up channels, ports, and listeners to prevent memory leaks
- Provide clear error handling for communication failures