io.Connect Desktop

Data Sharing Mechanisms

A complete guide to every communication pattern — and exactly when to use each one.

Pub/SubShared ContextsColor Channels Workspace ContextsInterop Methods IntentsStreamsWindow Context
Agenda

What We'll Cover

01
Foundation
Gateway architecture, WebSockets, interop clients & servers — the plumbing that powers every mechanism.
02
Think in patterns first
5 communication patterns to reason about before picking an API.
03
Deep Dives × 8
Each mechanism explained: when to use, when to avoid, flow diagram, and API references.
04
Methods vs. Intents
The most confused pair — a direct head-to-head on coupling, targeting, launch, and when each wins.
05
When to Use Which
Side-by-side comparisons, real-world scenarios, and a decision flowchart.
06
Feature Matrix
Full comparison across discovery, persistence, reannouncement, FDC3 mapping, cardinality, and more.
Foundation

What is io.Connect Desktop?

io.Connect Desktop is a real-time UI integration platform that connects web, native, and legacy apps — enabling them to share data and interoperate as a single unified experience.

🖥️

UI Integration

Tab Groups, sticky Windows, and Workspaces give users one unified desktop surface.

Real-Time Functional Integration

A click in one app instantly updates all connected apps — no copy-paste, no delays.

🌐

Multi-Technology

JS · .NET · Java · VBA · Delphi · Python (Dash) · React · Angular · Node.js and more.

📋

FDC3 Compliant

Full FDC3 support — interoperate with any third-party fintech app out of the box.

Foundation

The Gateway, WebSockets & App Roles

🔗 The Gateway

The central message router and bus. Every app connects on startup over a WebSocket. All messages pass through it — no direct app-to-app connections.

🔌 WebSocket

A persistent, full-duplex TCP connection. Unlike HTTP, it stays open so the Gateway can push data at any time with zero polling overhead.

🖥️ Server / Provider

Registers methods, streams, or contexts. Capabilities are advertised to all peers.

📱 Client / Consumer

Discovers and invokes methods, or subscribes to streams and contexts.

💡 Every interop-enabled app is a Gateway client. Within the Interop API, the same app can be both a server and a client simultaneously.

SDK init → WebSocket → Gateway → Peers
SDK
initializes
WS
connects
Gateway
routes
Peers
receive
Runtime message routing
App AJS · provider
connect (WebSocket)
register / publish
GatewayWebSocket
Message Bus
connect (WebSocket)
notify / deliver
App B.NET · consumer
Overview

The 8 Data Sharing Mechanisms

📡

Pub/Sub Legacy

Topic-based broadcast. For migrating existing pub/sub integrations. No persistence, no discovery — prefer higher-level APIs for new work.

🗂️

Shared Contexts Broadcast

Named global key/value object — great for "selected client", "current order", "active instrument". Late joiners get current value immediately.

🎨

Color Channels BroadcastUser-Driven

Shared Contexts plus a Channel Selector UI. Best when users should decide which apps link together — including directional restrictions.

🏢

Workspace Contexts Broadcast

Lets apps inside a Workspace share state with each other — scoped, portable, and persistable in layouts.

🔧

Interop Methods Service Call

Explicit, service-like capability. The caller knows exactly what it needs. Supports targeting and invoke-all.

🎯

Intents Workflow Handoff

Raise an action, not a target. The platform finds matching handlers, can launch an app, and lets the user choose via the Intents Resolver UI.

📶

Streams Streaming

Continuous real-time data push. Best for live alerts, telemetry, and any producer→many-subscribers flow. Supports branches.

🪟

Window Context Scoped

Per-window state and launch context. Best for restoring or initialising a specific window. Not intended for dynamic inter-app messaging.

Mental Model

Think in Patterns Before APIs

Match the interaction pattern you want users to experience — then pick the lightest mechanism that satisfies it.

Service Call
⚡ Direct capability
🔧 Interop Methods
Call a known function in another app and get a result back.
📶 Streams
Same family, but for continuous real-time updates instead of a single response.
Shared State
🗂️ Named data object
🗂️ Shared Contexts
A named object many apps observe — developer-driven, no UI.
🎨 Channels
Shared Contexts with a user-driven grouping UI. The user decides who syncs.
Workflow Handoff
🎯 Action request
🎯 Intents
Raise "handle this action" — loose coupling, user choice, app launch.
Scoped Context
🏢🪟 Local state containers
🪟 Window Context
Per-window state or startup payload. Keep it small — not for dynamic interop.
🏢 Workspace Context
Lets apps inside a Workspace share state with each other. Best for multi-tasking workflows.
Legacy Compatibility
📡 Topic bus
📡 Pub/Sub
For porting existing topic-based integrations only. Not for new designs.
💡 Rule of thumb: prefer the highest-level mechanism that matches the UX. Start with the narrowest scope that satisfies the workflow.
Deep Dive · 1 of 8

📡 Pub / Sub

publish()subscribe()

Apps publish messages on a named topic. All currently-subscribed apps receive it. No persistence — a late joiner never receives messages sent before they subscribed.

✅ Use when

  • Migrating apps already built on pub/sub
  • Fire-and-forget events, no state needed

🚫 Avoid when

  • Building new integrations — use Shared Contexts
  • Late joiners need the last-known value
// Provider io.bus.publish("mkt.update", { ticker: "AAPL" }); // Consumer await io.bus.subscribe("mkt.update", data => updateUI(data));
Fire-and-forget messaging
App Apublisher
publish("mkt.update", {ticker: "AAPL"})
Gatewayfan-out broker
(no state)
subscribe("mkt.update")
{ticker: "AAPL"} delivered
App Bsubscriber
no history — missed
App Clate joiner
Deep Dive · 2 of 8

🗂️ Shared Contexts

set()get()update()subscribe()all()setPath()setPaths()destroy()

A named global object stored in the Gateway. Any app can read, write, or subscribe. Late joiners receive the current value immediately. Context persists if a client disconnects and reconnects.

✅ Use when

  • Syncing a selected entity across apps (developer-driven)
  • Late-joining apps must get current state immediately
  • Multiple apps read and write the same shared data

🚫 Avoid when

  • You need user-driven grouping → use Color Channels
  • You need Workspace-level isolation → use Workspace Contexts
// Provider await io.contexts.update("selectedClient", { id: "C-001" }); // Consumer await io.contexts.subscribe("selectedClient", ctx => load(ctx)); // set() replaces fully; update() merges; setPath() targets one key
Stateful context sharing
App Awriter
update("selectedClient", {id: "C-001"})
X
X
Gatewaystores state
persists
notify subscribers
App Bsubscribed
subscribe("selectedClient")
{id: "C-001"} immediately ✓
App Clate joiner
Deep Dive · 3 of 8

🎨 Color Channels

all()join()leave()publish()subscribe()get()

Shared Contexts plus a Channel Selector UI. Users assign apps to a named colour; all apps on the same colour synchronise automatically.

✅ Use when

  • The user decides which apps are linked at runtime
  • Multiple independent sync groups coexist on screen
  • FDC3 User Channels compatibility is needed

🚫 Avoid when

  • Linking is fixed in code → use Shared Contexts instead
  • Full global visibility across all apps is required
Note: Color Channels are syntactic sugar around Shared Contexts — each channel maps to a dedicated shared context managed by the platform.

↔️ Directional Mode

The Channel Selector supports a directional mode — users can restrict a window to publish-only or subscribe-only.

// Provider await io.channels.join("Red"); await io.channels.publish({ ticker: "AAPL" }); // Consumer io.channels.subscribe(data => updateUI(data));
User assigns both apps to "Red"
App Ajoins 🔴
join("Red")
publish({ticker: "AAPL"})
GatewayRed channel
context
join("Red")
{ticker: "AAPL"} to Red members
App Bjoins 🔴
wrong channel — no delivery
App Con 🔵 Blue
Deep Dive · 4 of 8

🏢 Workspace Contexts

workspace.setContext()workspace.getContext()workspace.updateContext()workspace.onContextUpdated()

Lets apps inside a Workspace share state with each other. The context is scoped to that Workspace, travels with it, and can be persisted in layouts.

✅ Use when

  • Running parallel workflows (e.g. two client portfolios in separate Workspaces)
  • Context should save and restore with the Workspace Layout
  • Hard isolation between Workspaces is required

🚫 Avoid when

  • Apps outside the Workspace need the data → use Shared Contexts
  • Global visibility across all apps is required
Note: Workspace Contexts are syntactic sugar around Shared Contexts — each Workspace gets its own scoped shared context managed by the platform.
// Provider await workspace.setContext({ clientId: "C-001" }); // Consumer workspace.onContextUpdated(ctx => load(ctx.clientId));
Two isolated Workspaces
App AWS1 🟦
setContext({clientId: "C-001"}) — WS1
X
X
Gatewayscoped
routing
notify WS1 members
App BWS1 🟦
WS2 isolated — no delivery
setContext({clientId: "C-042"}) — WS2
App CWS2 🟩
Deep Dive · 5 of 8

🔧 Interop Methods

register()unregister()invoke()methods()servers()

Explicit, service-like capability. The caller knows exactly what it needs. An app registers a named function and other apps can discover and invoke it — receiving a response back.

✅ Use when

  • You need a well-defined service contract with a return value
  • The target app is already running
  • Precise functions: pricing, calculations, trade execution

🚫 Avoid when

  • Target may not be running → use Intents instead
  • You need to broadcast state — not a service call pattern
// Provider await io.interop.register("calcRisk", args => ({ risk: calc(args.id) })); // Consumer const res = await io.interop.invoke("calcRisk", { id: "C-001" });
Request / response call
Risk Appserver
register("calcRisk")
invoke routed to server
return {risk: 0.73}
Gatewaymethod
registry
invoke("calcRisk", {id: "C-001"})
response delivered
Blotterclient
Deep Dive · 6 of 8

🎯 Intents

register()raise()all()

Raise an action, not a target. The caller says "handle this" — the platform finds matching handlers, can launch the app if not running, and routes context to it.

✅ Use when

  • App-to-app handoffs, launch/activate workflows, user choice
  • The caller doesn't need to know the exact destination app
  • Handler may need to be launched first

🧠 Best mental model

Phone "Share / Open with…" UI for desktop workflows.

Note: When an intent is ambiguous (multiple handlers match), the Intents Resolver UI is displayed, letting the user choose the target app.
// Provider await io.intents.register("ViewChart", ctx => showChart(ctx.data)); // Consumer await io.intents.raise({ intent: "ViewChart", context: { type: "Instrument", data: { RIC: "AAPL" } } });
Intent-based app discovery & launch
Chart Appnot running
launch + deliver {RIC: "AAPL"}
Gatewayintent registry
+ launcher
raise("ViewChart", {RIC: "AAPL"})
Blotterraiser
Head-to-Head

🔧 Methods vs. 🎯 Intents — The Most Confused Pair

Both are request/response patterns — the difference is coupling, targeting, and who resolves the handler.

Direct service call
🔧 Interop Methods
Explicit, service-like capability. The caller knows exactly what it needs.
Can target the best app, a specific instance, a set, or all providers.
Target app must already be running.
Use Methods when…
you know the service you need and want a well-defined contract.
Action request
🎯 Intents
Raise an action like "ViewChart" — not a concrete target app.
Platform finds handlers and can launch the app if needed.
Only mechanism supporting interception.
Use Intents when…
you want flexibility, discovery, app-to-app handoff, or launch capability.
Deep Dive · 7 of 8

📶 Streams

createStream()stream.push()stream.branches()subscribe()subscription.close()stream.close()

Continuous, real-time data push. Best for live alerts, telemetry, and any producer→many-subscribers flow. The server controls who can subscribe via branches.

✅ Use when

  • Data changes continuously and polling would be wasteful
  • Many consumers need the same live data
  • Per-subscriber filtering is needed

🚫 Avoid when

  • Data updates only occasionally → use Shared Contexts
  • You need a one-off response → use Interop Methods

🌿 Branches

Named sub-channels within a stream (e.g. per region). The provider pushes to a branch only.

// Provider const stream = await io.interop.createStream("prices"); stream.push({ ticker: "AAPL", bid: 182.50, ask: 182.55 }); // Consumer const sub = await io.interop.subscribe("prices"); sub.onData(({ data }) => updateGrid(data));
Continuous data streaming
Price Svcprovider
createStream("prices")
push({ticker, bid}) ∞
Gatewaystream
registry
subscribe("prices")
fan-out continuously
Blotter &Chart App
Deep Dive · 8 of 8

🪟 Window Context

window.setContext()window.getContext()window.updateContext()window.onContextUpdated()

Per-window state and launch context. Scoped to one window. Best for restoring or initialising a specific window. Can be saved as part of a Global Layout.

✅ Use when

  • Passing initial launch payload to a window
  • Context should persist with a saved Global Layout
  • FDC3 Private Channels scenario

🚫 Avoid when

  • Dynamic inter-app messaging is needed after launch
  • Multiple apps need access to the same data
// Provider await io.windows.open("chartApp", url, { context: { ticker: "AAPL" } }); await win.updateContext({ ticker: "MSFT" }); // Consumer const ctx = await io.windows.my().getContext(); io.windows.my().onContextUpdated(ctx => load(ctx));
Window lifecycle + context updates
Blotteropener
open("chartApp", {ticker: "AAPL"})
updateContext({ticker: "MSFT"})
Gatewaywindow
launcher
launch + attach {ticker: "AAPL"}
getContext() → {ticker: "AAPL"}
onContextUpdated → {ticker: "MSFT"}
Chart App🚀 launched
When to Use · Broadcast Family

Broadcast Mechanisms — Choosing the Right One

📡
Pub / Sub
One-shot topic messages · no persistence · no reannouncement

✅ Use when

  • Migrating existing pub/sub apps
  • Fire-and-forget events

🚫 Avoid when

  • Building new integrations
  • Late joiners need last value
🗂️
Shared Contexts
Global named state · late joiners get current value · persists

✅ Use when

  • Syncing selected entity (developer-driven)
  • Late joiners must get current state

🚫 Avoid when

  • User controls grouping → Channels
  • Need workspace isolation
🎨
Color Channels
Shared Contexts + Channel Selector UI · directional mode

✅ Use when

  • User decides which apps link
  • FDC3 User Channels needed

🚫 Avoid when

  • Linking fixed in code → Shared Contexts
  • Full global visibility needed
🏢
Workspace Contexts
Workspace-scoped state · persists with Layout

✅ Use when

  • Parallel workflows must be isolated
  • Context saves with Workspace Layout

🚫 Avoid when

  • Apps outside Workspace need data
  • Global visibility required
When to Use · Request/Response & Streaming

Invocation & Streaming — Choosing the Right One

🔧
Interop Methods
Direct service call · target must be running

✅ Use when

  • Need a direct result back
  • Target app is already running

🚫 Avoid when

  • Target may not be running → Intents
  • You need shared state, not a service call
🎯
Intents
Workflow handoff · can launch handler · interception

✅ Use when

  • App handoffs, launch workflows
  • User picks handler via Resolver UI

🚫 Avoid when

  • Service contract well-known → Methods
  • Streaming or broadcast needed
📶
Streams
Continuous push · branches for targeted delivery

✅ Use when

  • Continuous data feed (order updates, analytics)
  • Polling would be wasteful

🚫 Avoid when

  • Occasional updates → Shared Contexts
  • Single request/response → Methods
🪟
Window Context
Launch-time parameters · window-scoped · Layout-aware

✅ Use when

  • Passing initial payload to a window
  • Context persists with Global Layout

🚫 Avoid when

  • Dynamic messaging needed post-launch
  • Multiple apps need the data
Real-World Scenarios

Common Scenarios — Which Mechanism Fits?

👤 User clicks a client — all open apps should update
🗂️
Shared Contexts
Global; late joiners get current value
Or 🎨 Channels if user controls grouping
📢 Alert service pushes live notifications to many apps
📶
Streams
Continuous push; branches for filtering
Methods if one-off price lookup
📊 "View Chart" should open the right charting app
🎯
Intents
Loose coupling; launches app; user picks
Methods if chart app always running
🏢 Two portfolios — each with independent context
🏢
Workspace Contexts
Isolated per Workspace
Or 🎨 Channels if Workspaces aren't in use
🎨 User wants to link two specific apps
🎨
Color Channels
User assigns apps to same colour
Or 🗂️ Shared Contexts with a named context for programmatic linking
🔧 Risk app exposes a calculation
🔧
Interop Methods
Precise service call; result returned
🎯 Intents if callers shouldn't depend on a specific app; 📶 Streams for continuous recalculation
🪟 Open chart pre-loaded with a ticker
🪟
Window Context
Pass launch params at open time
🎯 Intents — raise "ViewChart" with context (discovery + launch + payload)
📡 Existing pub/sub app connecting
📡
Pub / Sub
Compatibility bridge
Migrate to Shared Contexts long-term
🌐 Interop with third-party FDC3 app
🎨
Channels → FDC3 User Channels
Direct FDC3 mapping
🎯 Intents → FDC3 Intents if interaction is action-based
Visual Guide

Decision Flowchart

Answer each question top-to-bottom — first Yes wins.

📡 Legacy pub/sub? → Pub/Sub (migration only) No ↓ No ↓ No ↓ No ↓ No ↓ Continuous real-time data push? Yes 📶 Streams Need a response or trigger an action? Yes Target known & running? Yes 🔧 Interop Methods No 🎯 Intents can launch app User controls which apps link together? Yes 🎨 Color Channels Scoped to one Workspace only? Yes 🏢 Workspace Contexts Launch payload or per-window state? Yes 🪟 Window Context ⬇ DEFAULT 🗂️ Shared Contexts 📌 DEFAULT PATH All No → Shared Contexts Best global programmatic sync 🧠 MNEMONIC Action/handoff→ Intents Service call→ Methods Live feed→ Streams Shared state→ Contexts / Channels Legacy topic bus→ Pub/Sub 🌐 FDC3 MAPPINGS Channels → User Channels Intents → FDC3 Intents Streams → Private Channels 📡 Pub/Sub = legacy migration only 💡 RULE OF THUMB Narrowest scope that satisfies the workflow → start there
Comparison

Feature Matrix

Hover cells with a indicator for additional details.

Feature 📡 Pub/Sub 🗂️ Shared Ctx 🎨 Channels 🏢 Workspace Ctx 🔧 Methods 🎯 Intents 📶 Streams 🪟 Window Ctx
Communication Model Broadcast Broadcast Broadcast Broadcast Req/Resp Req/Resp Streaming Window-specific
Publish Data ✓*Based on Channel Restrictions ✗*Not intended for dynamic interop
Read Data ✓*Based on Channel Restrictions ✓*Servers could restrict access to data ✓*Servers could restrict access to data ✓*Servers could restrict access to data ✗*Not intended for dynamic interop
Discovery
User Controlled ✓*Users assign Application Instances to Color Channels ✓*Users control the Workspace grouping of Windows ✓*Users are responsible for resolving ambiguous Intents through the Intents Resolver UI
Defined Statically ✓*Could be defined as part of a Workspace Layout ✓*Intents could also be defined dynamically ✓*Could be defined as part of a Global Layout
Global ✗*By design, scoped to the Application Instances joined on the same Color Channel ✗*By design, scoped to the Applications Instances within a Workspace
Persist (restarts) ✓*Data could be persisted as part of a Workspace Layout ✓*Data could be persisted as part of a Global Layout
Persist (disconnect) ✓*The Harmony Workspaces Application could automatically destroy a Workspace Context when the Workspace is closed, based on configuration
Reannouncement (e.g., after a machine state change)
Caller / Updater ID
Cardinality 1→Many 1→Many 1→Many 1→Many 1→Many 1→1 1→Many 1→1
Instance Launch ✓*Can launch new Instances of Applications, if needed ✓*Can be provided as an initial context by the launching Application Instance via the AppManager API
io.Connect API publish()
subscribe()
all()
set()
get()
subscribe()
all()
join()
publish()
subscribe()
setContext()
updateContext()
getContext()
onContextUpdated()
methods()
register()
invoke()
unregister()
all()
register()
raise()
createStream()
stream.push()
subscribe()
stream.close()
setContext()
updateContext()
getContext()
onContextUpdated()
Interception
FDC3 Mapping App Channels User Channels Intents Private Channels
Summary

Key Takeaways

🔗
Everything flows through the Gateway
All mechanisms ride through the io.Connect Gateway over WebSockets. No direct app-to-app connections.
🧩
Think in patterns first
Match the interaction pattern (service call, shared state, workflow handoff, scoped context) before picking an API.
⬇️
Start narrow, go wider
Window → Workspace → Shared Context / Channels. Start with the narrowest scope that satisfies the workflow.
🏆
Prefer higher-level mechanisms
Reserve Pub/Sub for porting legacy systems. For new development, pick the mechanism that fits each use case — most apps end up using more than one.
io.Connect Desktop

Useful Links

Keep exploring io.Connect data sharing