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.
To prevent unauthorized access, a strict handshake protocol is enforced:
/app https://your-app.com in iCanText.ICT_HELLO via postMessage.instanceId, the localPeerId and the localPseudonym.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>
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);
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... */
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();
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();
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);
}
window.opener before sending data; if the user closes iCanText, the bridge will break.payload received in onData as it comes from a remote peer's application instance.Content-Security-Policy does not have a restrictive frame-ancestors directive, and avoid the X-Frame-Options: DENY header.