PrivateChannel
Object representing a private context channel, which is intended to support secure communication between applications, and extends the Channel interface with event handlers which provide information on the connection state of both parties, ensuring that desktop agents do not need to queue or retain messages that are broadcast before a context listener is added and that applications are able to stop broadcasting messages when the other party has disconnected.
It is intended that Desktop Agent implementations:
- SHOULD restrict external apps from listening or publishing on this channel.
- MUST prevent
PrivateChannelsfrom being retrieved viafdc3.getOrCreateChannel. - MUST provide the
idvalue for the channel as required by theChannelinterface.
- TypeScript/JavaScript
- .NET
- Go
- Java
interface PrivateChannel extends Channel {
// functions
addEventListener(type: PrivateChannelEventTypes | null, handler: EventHandler): Promise<Listener>;
disconnect(): Promise<void>;
//deprecated functions
onAddContextListener(handler: (contextType?: string) => void): Listener;
onUnsubscribe(handler: (contextType?: string) => void): Listener;
onDisconnect(handler: () => void): Listener;
}
interface IPrivateChannel : IChannel, IIntentResult
{
//functions
Task<IListener> AddEventListener(string? eventType, Fdc3EventHandler handler);
//deprecated functions
IListener OnAddContextListener(Action<string?> handler);
IListener OnUnsubscribe(Action<string?> handler);
IListener OnDisconnect(Action handler);
void Disconnect();
}
@experimental
type PrivateChannel struct {
Channel
}
@experimental
func (privateChannel *PrivateChannel) AddEventListener(eventType *PrivateChannelEventTypes, handler EventHandler) <-Result[Listener] {
// Implementation here
}
@experimental
func (privateChannel *PrivateChannel) Disconnect() <-Result[any] {
// Implementation here
}
public interface PrivateChannel extends Channel {
CompletionStage<Listener> addEventListener(String type, EventHandler handler);
void disconnect();
// deprecated functions
@Deprecated CompletionStage<Listener> onAddContextListener(EventHandler handler);
@Deprecated CompletionStage<Listener> onUnsubscribe(EventHandler handler);
@Deprecated CompletionStage<Listener> onDisconnect(EventHandler handler);
}
See also:
ChannelListenerDesktopAgent.addIntentListenerDesktopAgent.createPrivateChannelDesktopAgent.raiseIntent
Examples
'Server-side' example
The intent app establishes and returns a PrivateChannel to the client (who is awaiting getResult()). When the client calls addContextListener() on that channel, the intent app receives notice via the handler added with addEventListener() and knows that the client is ready to start receiving quotes.
The Desktop Agent knows that a channel is being returned by inspecting the object returned from the handler (e.g. check constructor or look for private member).
Although this interaction occurs entirely in frontend code, we refer to it as the 'server-side' interaction as it receives a request and initiates a stream of responses.
- TypeScript/JavaScript
- .NET
- Go
- Java
fdc3.addIntentListener("QuoteStream", async (context) => {
const channel: PrivateChannel = await fdc3.createPrivateChannel();
const symbol = context.id.ticker;
// This gets called when the remote side adds a context listener
const addContextListener = channel.addEventListener("addContextListener",
(event: PrivateChannelAddContextListenerEvent) => {
console.log(`remote side added a listener for ${event.contextType}`);
// broadcast price quotes as they come in from our quote feed
feed.onQuote(symbol, (price) => {
channel.broadcast({ type: "price", price});
});
}
);
// This gets called when the remote side calls Listener.unsubscribe()
const unsubscribeListener = channel.addEventListener("unsubscribe",
(event: PrivateChannelUnsubscribeEvent) => {
console.log(`remote side unsubscribed a listener for ${event.contextType}`);
feed.stop(symbol);
}
);
// This gets called if the remote side closes
const disconnectListener = channel.addEventListener("disconnect",
() => {
console.log(`remote side disconnected`);
feed.stop(symbol);
}
);
return channel;
});
_desktopAgent.AddIntentListener<Instrument>("QuoteStream", async (context, metadata) => {
var channel = await _desktopAgent.CreatePrivateChannel();
var symbol = context?.ID?.Ticker;
// This gets called when the remote side adds a context listener
var addContextListener = channel.OnAddContextListener((contextType) => {
// broadcast price quotes as they come in from our quote feed
_feed.OnQuote(symbol, (price) => {
channel.Broadcast(new Price(price));
});
});
// This gets called when the remote side calls Listener.unsubscribe()
var unsubscribeListener = channel.OnUnsubscribe((contextType) => {
_feed.Stop(symbol);
});
// This gets called if the remote side closes
var disconnectListener = channel.OnDisconnect(() => {
_feed.stop(symbol);
});
return channel;
});
<-desktopAgent.AddIntentListener("QuoteStream", func(context types.Context, metadata *ContextMetadata) <-chan IntentResult {
channelResult := <-desktopAgent.CreatePrivateChannel()
if channelResult.Err != nil {
// handle error
}
var symbol string
var exists bool
if symbol, exists = context.Id["ticker"]; !exists {
// handle not having ticker
}
// This gets called when the remote side adds a context listener
addContextlistenerResult := channelResult.Value.OnAddContextListener(func(contextType *string) {
feed.OnQuote(symbol, func(price string) {
channelResult.Value.Broadcast(Context{Type:price})
})
})
// This gets called when the remote side calls Listener.unsubscribe()
unsubscribeListenerResult := channelResult.Value.OnUnsubscribe(func(contextType *string) {
feed.Stop(symbol)
})
// This gets called if the remote side closes
unsubscribeListenerResult := channelResult.Value.OnDisconnect(func() {
feed.Stop(symbol)
})
result := make(chan types.IntentResult)
result <- channelResult.Value
return result
}
)
desktopAgent.addIntentListener("QuoteStream", (context, metadata) -> {
PrivateChannel channel = desktopAgent.createPrivateChannel().toCompletableFuture().get();
String symbol = context.getId().get("ticker");
// This gets called when the remote side adds a context listener
channel.addEventListener("addContextListener", event -> {
PrivateChannelAddContextListenerEvent addEvent = (PrivateChannelAddContextListenerEvent) event;
System.out.println("remote side added listener for " + addEvent.getContextType());
feed.onQuote(symbol, price -> {
Context priceContext = new Context("price");
channel.broadcast(priceContext);
});
});
// This gets called when the remote side calls Listener.unsubscribe()
channel.addEventListener("unsubscribe", event -> {
feed.stop(symbol);
});
// This gets called if the remote side closes
channel.addEventListener("disconnect", event -> {
feed.stop(symbol);
});
return CompletableFuture.completedFuture(channel);
});
'Client-side' example
The 'client' application retrieves a Channel by raising an intent with context and awaiting the result. It adds a ContextListener so that it can receive messages from it. If a PrivateChannel was returned this may in turn trigger a handler added on the 'server-side' with onAddContextListener() and start the stream. The onDisconnect() listener may also be used to clean up when the 'server-side' disconnects the stream.
Although this interaction occurs entirely in frontend code, we refer to it as the 'client-side' interaction as it requests and receives a stream of responses.
- TypeScript/JavaScript
- .NET
- Go
- Java
try {
const resolution3 = await fdc3.raiseIntent("QuoteStream", { type: "fdc3.instrument", id : { ticker: "AAPL" } });
try {
const result = await resolution3.getResult();
//check that we got a result and that it's a channel
if (result && result.addContextListener) {
const listener = result.addContextListener("price", (quote) => console.log(quote));
//if it's a PrivateChannel
if (result.type == "private") {
result.addEventListener("disconnect", () => {
console.warn("Quote feed went down");
});
// Sometime later...
await listener.unsubscribe();
}
} else {
console.warn(`${resolution3.source} did not return a channel`);
}
} catch(channelError) {
console.log(`Error: ${resolution3.source} returned an error: ${channelError}`);
} catch(resultError: ResultError) {
console.log(`Error: ${resolution3.source} returned an error: ${resultError}`);
}
} catch (resolverError: ResolveError) {
console.error(`Error: Intent was not resolved: ${resolverError}`);
}
var resolution = await _desktopAgent.RaiseIntent("QuoteStream", new Instrument(new InstrumentID() { Ticker = "AAPL" }));
try
{
var result = await resolution.GetResult();
//check that we got a result and that it's a channel
if (result is IChannel channel)
{
var listener = await channel.AddContextListener<IContext>("price", (quote, metadata) => System.Diagnostics.Debug.WriteLine(quote));
//if it's a PrivateChannel
if (channel is IPrivateChannel privateChannel)
{
privateChannel.OnDisconnect(() => {
System.Diagnostics.Debug.WriteLine("Quote feed went down");
});
// Sometime later...
listener.Unsubscribe();
}
}
else
{
System.Diagnostics.Debug.WriteLine($" {resolution.Source} did not return a channel");
}
}
catch (Exception ex)
{
// Handle exception
}
resolutionResult := <-desktopAgent.RaiseIntent("QuoteStream", Context{Type:"fdc3.instrument", Id: map[string]string{"ticker": "AAPL"}})
if resolutionResult.Err != nil {
// handle error
}
result := resolutionResult.Value.GetResult()
if channel, ok := result.Value.(Channel); ok {
listenerResult := <-channel.AddContextListener("price", func(quote, metadata) => {
log.Printf("%v", quote)
})
if channel.Type == Private {
if privateChannel, ok := interface{}(channel).(PrivateChannel); ok {
privateChannel.OnDisconnect(() {
log.Println("Quote feed went down")
})
}
} else {
log.Printf("%s did not return a channel", resolutionResult.Value.Source)
}
}
try {
IntentResolution resolution = desktopAgent.raiseIntent("QuoteStream",
new Instrument("fdc3.instrument", Map.of("ticker", "AAPL"))).toCompletableFuture().get();
IntentResult result = resolution.getResult().toCompletableFuture().get();
if (result instanceof Channel) {
Channel channel = (Channel) result;
Listener listener = channel.addContextListener("price", (quote, metadata) -> {
System.out.println(quote);
}).toCompletableFuture().get();
if (channel instanceof PrivateChannel) {
PrivateChannel privateChannel = (PrivateChannel) channel;
privateChannel.addEventListener("disconnect", event -> {
System.out.println("Quote feed went down");
});
// Sometime later...
listener.unsubscribe();
}
} else {
System.out.println(resolution.getSource() + " did not return a channel");
}
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
Functions
addEventListener
- TypeScript/JavaScript
- .NET
- Go
- Java
addEventListener(type: PrivateChannelEventTypes | null, handler: EventHandler): Promise<Listener>;
Task<IListener> AddEventListener(string? eventType, Fdc3EventHandler handler);
func (privateChannel *PrivateChannel) AddEventListener(eventType *PrivateChannelEventTypes, handler EventHandler) <-Result[Listener] {
// Implementation here
}
CompletionStage<Listener> addEventListener(String type, EventHandler handler);
Register a handler for events from the PrivateChannel. Whenever the handler function is called it will be passed an event object with details related to the event.
- TypeScript/JavaScript
- .NET
- Go
- Java
// any event type
const listener: Listener = await myPrivateChannel.addEventListener(null,
(event: PrivateChannelEvent) => {
console.log(`Received event ${event.type}\n\tDetails: ${event.details}`);
}
);
IChannel myPrivateChannel;
var listener = await myPrivateChannel.AddEventListener(null, (event) => {
System.Diagnostics.Debug.WriteLine($"Received event ${event.Type}\n\tDetails: ${event.Details}");
});
listenerResult := AddEventListener(nil, func(event PrivateChannelEvent) {
fmt.Printf("Received event %v\n\tDetails: %v", event.Type, event.Details)
})
Listener listener = privateChannel.addEventListener(null, event -> {
System.out.println("Received event " + event.getType() + "\n\tDetails: " + event.getDetails());
}).toCompletableFuture().get();
See also:
- Events
- EventHandler
- PrivateChannelEvent
- PrivateChannelAddContextListenerEvent
- PrivateChannelUnsubscribeEvent
- PrivateChannelDisconnectEvent
disconnect
- TypeScript/JavaScript
- .NET
- Go
- Java
disconnect(): Promise<void>;
void Disconnect();
func (privateChannel *PrivateChannel) Disconnect() <-Result[any] {
// Implementation here
}
void disconnect();
May be called to indicate that a participant will no longer interact with this channel.
Deprecated Functions
onAddContextListener
- TypeScript/JavaScript
- .NET
- Go
- Java
onAddContextListener(handler: (contextType?: string) => void): Listener;
IListener OnAddContextListener(Action<string?> handler);
func (privateChannel *PrivateChannel) OnAddContextListener(handler func(contextType *ContextType)) Result[Listener] {
// Implementation here
}
/** @deprecated Use addEventListener("addContextListener", handler) instead */
CompletionStage<Listener> onAddContextListener(EventHandler handler);
Deprecated in favour of the async addEventListener("addContextListener", handler) function.
Adds a listener that will be called each time that the remote app invokes addContextListener on this channel.
onUnsubscribe
- TypeScript/JavaScript
- .NET
- Go
- Java
onUnsubscribe(handler: (contextType?: string) => void): Listener;
IListener OnUnsubscribe(Action<string?> handler);
func (privateChannel *PrivateChannel) OnUnsubscribe(handler func(contextType *ContextType)) Result[Listener] {
// Implementation here
}
/** @deprecated Use addEventListener("unsubscribe", handler) instead */
CompletionStage<Listener> onUnsubscribe(EventHandler handler);
Deprecated in favour of the async addEventListener("unsubscribe", handler) function.
Adds a listener that will be called whenever the remote app invokes Listener.unsubscribe() on a context listener that it previously added.
onDisconnect
- TypeScript/JavaScript
- .NET
- Go
- Java
onDisconnect(handler: () => void): Listener;
IListener OnDisconnect(Action handler);
func (privateChannel *PrivateChannel) OnDisconnect(handler func()) Result[Listener] {
// Implementation here
}
/** @deprecated Use addEventListener("disconnect", handler) instead */
CompletionStage<Listener> onDisconnect(EventHandler handler);
Deprecated in favour of the async addEventListener("disconnect", handler) function.
Adds a listener that will be called when the remote app terminates, for example when its window is closed or because disconnect was called.