How it works

The iCanText App Bridge allows third-party web applications (running in a separate tab or window) to securely leverage the iCanText P2P network. It acts as a Decentralized OS where iCanText provides the identity, discovery, and encrypted transport layer.

Hyper-Privacy: Messages sent via the bridge are End-to-End Encrypted (E2EE) between peers and wrapped in a Sealed Forward envelope. Intermediate nodes cannot see the content nor the metadata of your application data.

The Security Handshake

To prevent unauthorized access, a strict handshake protocol is enforced:

  1. Launch: A user types /app https://your-app.com in iCanText.
  2. Hello: Your app opens and sends an ICT_HELLO via postMessage.
  3. Init: iCanText verifies the origin and returns an instanceId, the localPeerId and the localPseudonym.
  4. Ready: The bridge is established. All future communications are locked to this instance.

ICTBridgeSDK.js

We provide a lightweight, promise-based SDK to handle the complexity of postMessage communication and handshaking.

Include it in your HTML:

<script src="https://icantext.com/ICTBridgeSDK.js"></script>

API Reference

sdk.init(timeoutMs): Starts the security handshake. Returns a Promise resolving to the connection state.

const connection = await sdk.init();
if (connection.status === 'READY') {
    console.log("Connected as:", connection.localPseudonym);
}

sdk.getPeers(): Returns an array of available peers in the current workspace.

const peers = await sdk.getPeers();
// peer: {id, pseudonym, verified, isActiveOnApp}
const activePeers = peers.filter(p => p.isActiveOnApp);

sdk.sendData(targetIds, payload): Transmits a JSON payload to specific PeerIDs via E2EE transport.

const targets = ['peer_xyz...'];
const data = { action: 'move', x: 10, y: 20 };
sdk.sendData(targets, data);

sdk.onData(callback): Registers a listener for incoming P2P data packets from other instances of your app.

sdk.onData((fromPeerId, payload) => {
    console.log(`Message from ${fromPeerId}:`, payload);
});

sdk.onProfileUpdate(callback): Listens for local pseudonym changes in iCanText to keep your UI in sync.

sdk.onProfileUpdate((newPseudonym) => {
    document.getElementById('display-name').textContent = newPseudonym;
});

sdk.getServerTime(): Fetches a synchronized timestamp from the iCanText server to prevent clock drift issues.

const timeStr = await sdk.getServerTime(); 
// Returns string format: YYYYMMDDHHMMSS (e.g. "20260202101030")

sdk.closeApp(): Requests iCanText to close the current application session (works for both iframes and popups).

// Close the app when the user clicks a "Quit" button
document.getElementById('quit-btn').onclick = () => sdk.closeApp();

sdk.postMessage(payload): Posts a message or a file to the channel under the user's identity. Returns the new messageId.

// Post Text
const id = await sdk.postMessage({ text: "Hello!" });

// Post Image/File
sdk.postMessage({ 
    file: { 
        name: "image.png", 
        type: "image/png", 
        data: "data:image/png;base64,..." 
    } 
});

sdk.editMessage(messageId, newText): Updates the content of a previously posted text message. Only works for messages owned by the user.

await sdk.editMessage("msg_123...", "Updated text content");

sdk.postAppContent(url, initialContext): Creates a new interactive block in the channel. The initialContext is a JSON object representing the app's state (e.g., poll questions).

// From a configuration popup:
sdk.postAppContent("https://vote.app/", { 
    question: "Pizza or Tacos?", 
    votes: {} 
});

sdk.getAppContext(): Used by the app running inside a message. Fetches the current JSON state associated with this specific message instance.

// On app startup inside the chat:
const state = await sdk.getAppContext();
console.log("Question:", state.question);

sdk.updateAppContext(newContext): Updates the shared state for everyone. This triggers a signed P2P update. Perfect for real-time collaboration (votes, counters, etc.).

// When a user interacts (e.g. votes):
state.votes[myId] = "Pizza";
sdk.updateAppContext(state); 
// Result: All channel members see the update instantly.

sdk.getDisplayMode(): Identifies where the app is currently rendered. Returns 'HEADER', 'POPUP', or 'MESSAGE'.

const mode = sdk.getDisplayMode();

if (mode === 'MESSAGE') {
    renderCompactView(); // Simplified UI for the chat timeline
} else {
    renderFullEditor();  // Full UI for configuration or popups
}

sdk.onContextUpdate(callback): Essential for real-time apps. Registers a listener that triggers whenever another user updates the context of this message (e.g., someone else voted).

sdk.onContextUpdate((updatedContext) => {
    console.log("New votes received!");
    refreshUI(updatedContext.votes);
});

sdk.getMessageId(): (New) Returns the unique iCanText ID of the message containing the app. Useful for advanced local caching or logging.

const mid = sdk.getMessageId();
console.log("This app instance belongs to message:", mid);

SDK Source Code (Open Source)

The ICTBridgeSDK is a zero-dependency JavaScript class. It is designed to be robust against standalone execution and handles the multiplexing of messages between iCanText and your application logic.

ICTBridgeSDK.js (Full Implementation)

/* SDK code loading... */

Usage Example: Shared Sticky Notes

Below is a minimal example of a collaborative app where users send notes to each other.

Implementation Example

const sdk = new ICTBridgeSDK('my_collab_app');

async function startApp() {
    const connection = await sdk.init();
    
    if (connection.status === 'READY') {
        console.log(`Connected as: ${connection.localPseudonym}`);

        // Listen for incoming notes
        sdk.onData((from, payload) => {
            alert(`Note from ${from}: ${payload.text}`);
        });

        // Send a note to all known peers
        const peers = await sdk.getPeers();
        const peerIds = peers.map(p => p.id);
        sdk.sendData(peerIds, { text: 'Hello decentralized world!' });
    }
}

startApp();

Complete Usage Example

This example shows a simple "Status Update" app. When launched through iCanText, it fetches your contacts and allows you to broadcast your current activity to them.

index.html (Application Logic)

// 1. Initialize the SDK with a unique Application ID
		const sdk = new ICTBridgeSDK('p2p_status_tracker');

		async function setup() {
		    // 2. Start the handshake with the parent iCanText window
		    const connection = await sdk.init();

		    if (connection.status === 'STANDALONE') {
			showUIError('Please launch this app from within iCanText using /app');
			return;
		    }

		    // 3. Handle incoming data from other peers
		    sdk.onData((senderId, payload) => {
			console.log(`Peer ${senderId} is now: ${payload.status}`);
			updateStatusBoard(senderId, payload.status);
		    });

		    // 4. Discovery: Fetch the list of peers in the current Workspace
		    const peers = await sdk.getPeers();
		    renderContactList(peers);
		}

		// 5. Sending Data: Encrypted P2P transmission
		function broadcastMyStatus(newStatus) {
		    sdk.getPeers().then(peers => {
			const targetIds = peers.map(p => p.id);
			
			// This JSON object will be E2EE encrypted and routed via Sealed Forward
			const packet = { 
			    type: 'STATUS_UPDATE', 
			    status: newStatus,
			    timestamp: Date.now() 
			};

			sdk.sendData(targetIds, packet);
		    });
		}

		setup();

Example: Self-Editing Bot

Micro-apps can act as assistants. This function posts a message and updates it after a short delay.

Implementation

async function sayHello() {
    // 1. Create the message
    const msgId = await sdk.postMessage({ 
        text: "I am computing the results..." 
    });
    
    // 2. Simulate work and update the same message
    setTimeout(async () => {
        await sdk.editMessage(msgId, "Computation complete: 42 ✅");
    }, 3000);
}

Best Practices