diff --git a/CHANGELOG.md b/CHANGELOG.md index 22c59f033..cd62c3c68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Added support for event listening outside of intent or context listnener. Added new function `addEventListener`, type `EventHandler`, enum `FDC3EventType` and interfaces `FDC3Event` and `FDC3ChannelChangedEvent`. ([#1207](https://github.com/finos/FDC3/pull/1207)) * Added new `CreateOrUpdateProfile` intent. ([#1359](https://github.com/finos/FDC3/pull/1359)) * Added conformance tests into the FDC3 API documentation in the current version and backported into 2.0 and 2.1. Removed outdated 1.2 conformance tests (which are preserved in the older 2.0 and 2.1 versions). ([#1417](https://github.com/finos/FDC3/pull/1417)). +* Added Go language binding. ([#1483](https://github.com/finos/FDC3/pull/1483)) ### Changed diff --git a/docs/api/ref/Channel.md b/docs/api/ref/Channel.md index 4f949f09d..ce53abc27 100644 --- a/docs/api/ref/Channel.md +++ b/docs/api/ref/Channel.md @@ -60,6 +60,32 @@ interface IChannel: IIntentResult } ``` + + + +```go +type IChannel interface { + Broadcast(context Context) <-chan Result[any] + GetCurrentContext(contextType string) <-chan Result[IContext] + AddContextListener(contextType string, handler ContextHandler) <-chan Result[Listener] +} + +type Channel struct { + Id string `json:"id"` + Type ChannelType `json:"type"` + DisplayMetadata *DisplayMetadata `json:"displayMetadata"` +} + +type ChannelType string + +const ( + App ChannelType = "app" + Private ChannelType = "private" + User ChannelType = "user" + System ChannelType = "system" +) +``` + @@ -89,6 +115,13 @@ public readonly id: string; string Id { get; } ``` + + + +```go +Id string +``` + @@ -117,6 +150,18 @@ public enum ChannelType } ``` + + + +```go +type ChannelType string + +const ( + App ChannelType = "app" + System ChannelType = "system" + Private ChannelType = "private" +) +``` @@ -138,6 +183,12 @@ public readonly displayMetadata?: DisplayMetadata; IDisplayMetadata? DisplayMetadata { get; } ``` + + + +```go +DisplayMetadata *DisplayMetadata +``` @@ -165,6 +216,15 @@ public addContextListener(contextType: string | null, handler: ContextHandler): Task AddContextListener(string? contextType, ContextHandler handler) where T : IContext; ``` + + + +```go +func (ch *Channel) AddContextListener(contextType string, handler ContextHandler) <-chan Result[Listener] { + // Implementation here +} +``` + @@ -213,6 +273,26 @@ var listener = await channel.AddContextListener(null, (context, metada listener.Unsubscribe(); ``` + + + +```go +listenerResult := <-channel.AddContextListener("", func(contextInt IContext, contextMetadata *ContextMetadata) { + if context, ok := contextInt.(Context); ok { + if context.Type == "fdc3.contact" { + // handle the contact + } else if context.Type == "fdc3.instrument" { + // handle the instrument + } + } + }) + +// later +if listenerResult.Value != nil { + listenerResult.Value.Unsubscribe() +} +``` + @@ -252,6 +332,26 @@ contactListener.unsubscribe(); instrumentListener.unsubscribe(); ``` + + + +```go +listenerResultContact := <-channel.AddContextListener("fdc3.contact", func(context IContext, contextMetadata *ContextMetadata) { + // handle the contact +}) +listenerResultInstrument := <-channel.AddContextListener("fdc3.instrument", func(context IContext, contextMetadata *ContextMetadata) { + // handle the instrument +}) + +// later +if listenerResultContact.Value != nil { + listenerResultContact.Value.Unsubscribe() +} +if listenerResultInstrument.Value != nil { + listenerResultInstrument.Value.Unsubscribe() +} +``` + @@ -278,6 +378,15 @@ public broadcast(context: Context): Promise; Task Broadcast(IContext context); ``` + + + +```go +func (channel *Channel) Broadcast(context IContext) <-chan Result[any] { + // Implementation here +} +``` + @@ -327,6 +436,21 @@ catch (Exception ex) } ``` + + + +```go +result := <-myChannel.Broadcast(types.Context{ + Type: "fdc3.instrument", + Id: map[string]string{ + "ticker": "AAPL", + }, +}) +if result.Err != null { + // handle error +} +``` + @@ -352,6 +476,15 @@ public getCurrentContext(contextType?: string): Promise; Task GetCurrentContext(string? contextType); ``` + + + +```go +func (channel *Channel) GetCurrentContext(contextType string) <-chan Result[Context] { + // Implementation here +} +``` + @@ -392,6 +525,16 @@ catch (Exception ex) } ``` + + + +```go +result := <-myChannel.GetCurrentContext("") +if result.Err != null { + // handle error +} +``` + @@ -422,6 +565,16 @@ catch (Exception ex) } ``` + + + +```go +result := <-myChannel.GetCurrentContext("fdc3.contact") +if result.Err != null { + // handle error +} +``` + @@ -452,6 +605,13 @@ public addContextListener(handler: ContextHandler): Promise; Not implemented ``` + + + +``` +Not implemented +``` + diff --git a/docs/api/ref/DesktopAgent.md b/docs/api/ref/DesktopAgent.md index 9525418b7..966c9ecef 100644 --- a/docs/api/ref/DesktopAgent.md +++ b/docs/api/ref/DesktopAgent.md @@ -100,6 +100,52 @@ interface IDesktopAgent } ``` + + + +```go +type Result[T any] struct { + Value *T + Err error +} + +type DesktopAgent struct {} + +type IDesktopAgent interface { + // Apps + Open(appIdentifier AppIdentifier, context *IContext) <-chan Result[AppIdentifier] + FindInstances(appIdentifier AppIdentifier) <-chan Result[[]AppIdentifier] + GetAppMetadata(appIdentifier AppIdentifier) <-chan Result[AppIdentifier] + + // Context + Broadcast(context IContext) <-chan Result[any] + AddContextListener(contextType string, handler ContextHandler) <-chan Result[Listener] + + // Intents + FindIntent(intent string, context *IContext, resultType *string) <-chan Result[AppIntent] + FindIntentsByContext(context IContext, resultType *string) <-chan Result[[]AppIntent] + RaiseIntent(intent string, context IContext, appIdentifier *AppIdentifier) <-chan Result[IntentResolution] + RaiseIntentForContext(context IContext, appIdentifier *AppIdentifier) <-chan Result[IntentResolution] + AddIntentListener(intent string, handler IntentHandler) <-chan Result[Listener] + + // Channels + GetOrCreateChannel(channelId string) <-chan Result[Channel] + CreatePrivateChannel() <-chan Result[PrivateChannel] + GetUserChannels() <-chan Result[[]Channel] + + // OPTIONAL channel management functions + JoinUserChannel(channelId string) <-chan Result[any] + GetCurrentChannel() <-chan Result[Channel] + LeaveCurrentChannel() <-chan Result[any] + + // non-context events + AddEventListener(type *FDC3EventTypes, handler EventHandler) <-Result[Listener]; + + //implementation info + GetInfo() <-chan Result[ImplementationMetadata] +} +``` + @@ -121,6 +167,15 @@ addContextListener(contextType: string | null, handler: ContextHandler): Promise Task AddContextListener(string? contextType, ContextHandler handler) where T : IContext; ``` + + + +```go +func (desktopAgent *DesktopAgent) AddContextListener(contextType string, handler ContextHandler) <-chan Result[Listener] { + // Implementation here +} +``` + @@ -168,6 +223,26 @@ var contactListener = await _desktopAgent.AddContextListener("fdc3.cont }); ``` + + + +```go +// any context +listenerResult := <-desktopAgent.AddContextListener("", func(context IContext, contextMetadata *ContextMetadata) { ... }) + +// listener for a specific type +listenerResult := <-desktopAgent.AddContextListener("fdc3.contact", func(context IContext, contextMetadata *ContextMetadata) { ... }) + +// listener that logs metadata for the message of a specific type +listenerResult := <-desktopAgent.AddContextListener("fdc3.contact", func(context IContext, contextMetadata *ContextMetadata) { + if contextMetadata != nil { + log.Printf("Received context message\nContext: %v\nOriginating app: %v", context, contextMetadata.Source) +} else { + log.Printf("Received context message\nContext: %v", context) +} +}) +``` + @@ -193,6 +268,15 @@ addEventListener(type: FDC3EventTypes | null, handler: EventHandler): Promise + + +```go +func (desktopAgent *DesktopAgent) AddEventListener(type *FDC3EventTypes, handler EventHandler) <-Result[Listener] { + // Implmentation here +} +``` + @@ -247,6 +331,15 @@ addIntentListener(intent: string, handler: IntentHandler): Promise; Task AddIntentListener(string intent, IntentHandler handler) where T : IContext; ``` + + + +```go +func (desktopAgent *DesktopAgent) AddIntentListener(intent string, handler IntentHandler) <-chan Result[Listener] { + // Implementation here +} +``` + @@ -329,6 +422,34 @@ var listener = await _desktopAgent.AddIntentListener("StartChat", (con }); ``` + + + +```go +//Handle a raised intent +listenerResult := <-desktopAgent.AddIntentListener("StartChat", func(context IContext, contextMetadata *ContextMetadata) { + // start chat has been requested by another application +}) + +//Handle a raised intent and log the originating app metadata +listenerResult := <-desktopAgent.AddIntentListener("StartChat", func(context IContext, contextMetadata *ContextMetadata) { + if contextMetadata != nil { + log.Printf("Received intent StartChat\nContext: %v\nOriginating app: %v", context, contextMetadata.Source) + } else { + log.Printf("Received intent StartChat\nContext: %v", context) +} +}) + +// listener that logs metadata for the message of a specific type +listenerResult := <-desktopAgent.AddIntentListener("fdc3.contact", func(context IContext, contextMetadata *ContextMetadata) { + if contextMetadata != nil { + log.Printf("Received context message\nContext: %v\nOriginating app: %v", context, contextMetadata.Source) +} else { + log.Printf("Received context message\nContext: %v", context) +} +}) +``` + @@ -356,6 +477,15 @@ broadcast(context: Context): Promise; Task Broadcast(IContext context); ``` + + + +```go +func (desktopAgent *DesktopAgent) Broadcast(context IContext) <-chan Result[any] { + // Implmentation here +} +``` + @@ -397,6 +527,19 @@ Instrument instrument = new Instrument( _desktopAgent.Broadcast(instrument); ``` + + + +```go +context := types.Context{ + Type: "fdc3.instrument", + Id: map[string]string{ + "ticker": "AAPL", + }, + } +desktopAgent.Broadcast(context) +``` + @@ -420,6 +563,15 @@ createPrivateChannel(): Promise; Task CreatePrivateChannel(); ``` + + + +```go +func (desktopAgent *DesktopAgent) CreatePrivateChannel() <-chan Result[PrivateChannel] { + // Implementation here +} +``` + @@ -497,6 +649,23 @@ _desktopAgent.AddIntentListener("QuoteStream", async (context, metad }); ``` + + + +```go +desktopAgent.AddIntentListener("fdc3.contact", func(context IContext, contextMetadata *ContextMetadata) { + channelResult := <-desktopAgent.CreatePrivateChannel() + symbol := context.Id["ticker"] + + if channelResult.Err != nil { + return + } + channel := channelResult.Value + channel.OnAddContextListener +}) + +``` + @@ -522,6 +691,16 @@ findInstances(app: AppIdentifier): Promise>; Task> FindInstances(IAppIdentifier app); ``` + + + +```go +func (desktopAgent *DesktopAgent) FindInstances(appIdentifier AppIdentifier) <-chan Result[[]AppIdentifier] { + // Implementation here +} + +``` + @@ -555,6 +734,20 @@ var instances = await _desktopAgent.FindInstances(new AppIdentifier("MyAppId")); var resolution = await _desktopAgent.RaiseIntent("ViewInstrument", context, instances.First()); ``` + + + +```go +// Retrieve a list of instances of an application +findInstancesResult := <-desktopAgent.FindInstances(AppIdentifier{AppId: "MyAppId"}) +if findInstancesResult.Err != nil || len(findInstancesResult.Value) == 0 { + // handle error +} + +// Target a raised intent at a specific instance +resolutionResult := <-desktopAgent.RaiseIntent("ViewInstrument", context, findInstancesResult.Value[0]) +``` + @@ -574,6 +767,16 @@ findIntent(intent: string, context?: Context, resultType?: string): Promise FindIntent(string intent, IContext? context = null, string? resultType = null); ``` + + + +```go +func (desktopAgent *DesktopAgent) FindIntent(intent string, context *IContext, resultType *string) <-chan Result[AppIntent] { + // Implmentation here +} + +``` + @@ -632,6 +835,19 @@ var appIntent = await _desktopAgent.FindIntent("StartChat"); await _desktopAgent.RaiseIntent(appIntent.Intent.Name, context, appIntent.Apps.First()); ``` + + + +```go +findIntentResult := <-desktopAgent.FindIntent("StartChat", nil, nil) +if findIntentResult.Err != nil { + // handle error +} + +// raise the intent against a particular app +<-desktopAgent.RaiseIntent(findIntentResult.Value.Intent.Name, context, findInstancesResult.Value.Apps[0]) +``` + @@ -682,7 +898,34 @@ var appIntent = await _desktopAgent.FindIntent("ViewContact", "fdc3.ContactList" // Apps: { AppId: "MyCRM", ResultType: "fdc3.ContactList"}] // } -var appIntent = await _desktopAgent.fFindIntent("QuoteStream", instrument, "channel"); +var appIntent = await _desktopAgent.FindIntent("QuoteStream", instrument, "channel"); +// returns only apps that return a channel which will receive the specified input and result types: +// { +// Intent: { Name: "QuoteStream" }, +// Apps: { AppId: "MyOMS", ResultType: "channel"}] +// } +``` + + + + +```go +findIntentResult := <-desktopAgent.FindIntent("StartChat", &contact, nil) +// returns only apps that support the type of the specified input context: +// { +// Intent: { Name: "StartChat" }, +// Apps: { Name: "Symphony" }] +// } + +resultType := "fdc3.ContactList" +findIntentResult := <-desktopAgent.FindIntent("ViewContact", &nil, &resultType) +// returns only apps that return the specified result type: +// { +// Intent: { Name: "ViewContact" }, +// Apps: { AppId: "MyCRM", ResultType: "fdc3.ContactList"}] +// } + +findIntentResult := <-desktopAgent.FindIntent("QuoteStream", &instrument, "channel"); // returns only apps that return a channel which will receive the specified input and result types: // { // Intent: { Name: "QuoteStream" }, @@ -713,6 +956,16 @@ findIntentsByContext(context: Context, resultType?: string): Promise> FindIntentsByContext(IContext context, string? resultType = null); ``` + + + +```go +func (desktopAgent *DesktopAgent) FindIntentsByContext(context IContext, resultType *string) <-chan Result[[]AppIntent] { + // Implmentation here +} + +``` + @@ -764,6 +1017,13 @@ const appIntents = await fdc3.findIntentsByContext(context); var appIntents = await _desktopAgent.FindIntentsByContext(context); ``` + + + +```go +findIntentResult := <-desktopAgent.FindIntentsByContext(context, nil) +``` + @@ -811,6 +1071,30 @@ var selectedApp = startChat.Apps.First(); await _desktopAgent.RaiseIntent(startChat.Intent.Name, context, selectedApp); ``` + + + +```go +resultType := "fdc3.ContactList" +findIntentResult := <-desktopAgent.FindIntentsByContext(context, &resultType) +// returns for example: +// [{ +// Intent: { Name: "ViewContact" }, +// Apps: [{ AppId: "Symphony" }, { AppId: "MyCRM", ResultType: "fdc3.ContactList"}] +// }]; +if findIntentResult.Err != nil || len(findIntentResult.Value) == 0 { + // handle error or no results +} +// select a particular intent to raise +startChat := findIntentResult.Value[0] + +// target a particular app or instance +selectedApp := startChat.Apps[0] + +// raise the intent, passing the given context, targeting the app +<-desktopAgent.RaiseIntent(startChat.Intent.Name, context, selectedApp) +``` + @@ -835,6 +1119,16 @@ getAppMetadata(app: AppIdentifier): Promise; Task GetAppMetadata(IAppIdentifier app); ``` + + + +```go +func (desktopAgent *DesktopAgent) GetAppMetadata(appIdentifier AppIdentifier) <-chan Result[AppIdentifier] { + // Implementation here +} + +``` + @@ -860,6 +1154,14 @@ var appIdentifier = new AppIdentifier("MyAppId@my.appd.com"); var appMetadata = await _desktopAgent.GetAppMetadata(appIdentifier); ``` + + + +```go +appIdentifier := AppIdentifier{AppId: "MyAppId@my.appd.com"} +appMetadataResult := <-desktopAgent.GetAppMetadata(appIdentifier) +``` + @@ -884,6 +1186,16 @@ getCurrentChannel() : Promise; Task GetCurrentChannel(); ``` + + + +```go +func (desktopAgent *DesktopAgent) GetCurrentChannel() <-chan Result[Channel] { + // Implementation here +} + +``` + @@ -909,6 +1221,13 @@ let current = await fdc3.getCurrentChannel(); var current = await _desktopAgent.GetCurrentChannel(); ``` + + + +```go +currentResult := <-desktopAgent.GetCurrentChannel() +``` + @@ -932,6 +1251,16 @@ getInfo(): Promise; Task GetInfo(); ``` + + + +```go +func (desktopAgent *DesktopAgent) GetInfo() <-chan Result[ImplementationMetadata] { + // Implementation here +} + +``` + @@ -961,6 +1290,16 @@ if (fdc3.getInfo && versionIsAtLeast(await fdc3.getInfo(), "1.2")) { var version = (await _desktopAgent.GetInfo()).Fdc3Version; ``` + + + +```go +infoResult := <-desktopAgent.GetInfo() +if infoResult.Value != nil { + version := infoResult.Fdc3Version +} +``` + @@ -983,6 +1322,17 @@ var appId = implementationMetadata.AppMetadata.AppId; var instanceId = implementationMetadata.AppMetadata.InstanceId; ``` + + + +```go +implementationMetadataResult := <-desktopAgent.GetInfo() +if implementationMetadataResult.Value != nil { + appId := implementationMetadataResult.AppMetadata.AppId + instanceId := implementationMetadataResult.AppMetadata.InstanceId +} +``` + @@ -1007,6 +1357,16 @@ getOrCreateChannel(channelId: string): Promise; Task GetOrCreateChannel(string channelId); ``` + + + +```go +func (desktopAgent *DesktopAgent) GetOrCreateChannel(channelId string) <-chan Result[Channel] { + // Implementation here +} + +``` + @@ -1044,6 +1404,22 @@ catch (Exception ex) } ``` + + + +```go +myChannelResult := <-desktopAgent.GetOrCreateChannel("myChannel") +if myChannelResult.Err != nil { + //app could not register the channel +} +myChannel := myChannelResult.Value +<-myChannel.AddContextListener("", func(context IContext, metadata *ContextMetadata) { + // do something with context +}) + + +``` + @@ -1067,6 +1443,16 @@ getUserChannels() : Promise>; Task> GetUserChannels(); ``` + + + +```go +func (desktopAgent *DesktopAgent) GetUserChannels() <-chan Result[[]Channel] { + // Implementation here +} + +``` + @@ -1090,6 +1476,18 @@ var userChannels = await _desktopAgent.GetUserChannels(); var redChannel = userChannels.First(c => c.Id == "red"); ``` + + + +```go +import "slices" // This is for go 1.21+, before that use `golang.org/x/exp/slices` library +userChannelsResult := <-desktopAgent.GetUserChannels() +if userChannelsResult.Err != nil { + // handle error +} +redChannel := slices.IndexFunc(userChannelsResult.Value, func(c Channel) bool { return c.Id == "red" }) +``` + @@ -1113,6 +1511,16 @@ joinUserChannel(channelId: string) : Promise; Task JoinUserChannel(string channelId); ``` + + + +```go +func (desktopAgent *DesktopAgent) JoinUserChannel(channelId string) <-chan Result[any] { + // Implementation here +} + +``` + @@ -1154,6 +1562,18 @@ var channels = await _desktopAgent.GetUserChannels(); _desktopAgent.JoinUserChannel(selectedChannel.Id); ``` + + + +```go +userChannelsResult := <-desktopAgent.GetUserChannels() +if userChannelsResult.Err != nil { + // handle error +} +<-desktopAgent.JoinUserChannel(userChannelsResult.Value.Id) + +``` + @@ -1177,6 +1597,15 @@ leaveCurrentChannel() : Promise; Task LeaveCurrentChannel(); ``` + + + +```go +func (desktopAgent *DesktopAgent) LeaveCurrentChannel() <-chan Result[any] { + // Implementation here +} +``` + @@ -1215,6 +1644,21 @@ await _desktopAgent.LeaveCurrentChannel(); redChannel.AddContextListener(null, channelListener); ``` + + + +```go +//desktop-agent scope context listener +listenerResult := <-desktopAgent.AddContextListener("", func(context IContext, contextMetadata *ContextMetadata) { ... }) + + +<-desktopAgent.LeaveCurrentChannel() +//the fdc3Listener will now cease receiving context + +//listening on a specific channel though, will continue to work +<-redChannel.AddContextListener("", channelListener); +``` + @@ -1234,6 +1678,15 @@ open(app: AppIdentifier, context?: Context): Promise; Task Open(IAppIdentifier app, IContext? context = null); ``` + + + +```go +func (desktopAgent *DesktopAgent) Open(appIdentifier AppIdentifier, context *IContext) <-chan Result[AppIdentifier] { + // Implementation here +} +``` + @@ -1275,6 +1728,18 @@ var instanceIdentifier = await _desktopAgent.Open(appIdentifier); var instanceIdentifier = await _desktopAgent.Open(appIdentifier, context); ``` + + + +```go +// Open an app without context, using an AppIdentifier object to specify the target +appIdentifier := AppIdentifier{AppId: "myApp-v1.0.1"} +instanceIdentifierResult := <-desktopAgent.Open(appIdentifier, nil) + +// Open an app with context +instanceIdentifierResult := <-desktopAgent.Open(appIdentifier, &context) +``` + @@ -1301,6 +1766,15 @@ raiseIntent(intent: string, context: Context, app?: AppIdentifier): Promise RaiseIntent(string intent, IContext context, IAppIdentifier? app = null); ``` + + + +```go +func (desktopAgent *DesktopAgent) RaiseIntent(intent string, context IContext, appIdentifier *AppIdentifier) <-chan Result[IntentResolution] { + // Implementation here +} +``` + @@ -1376,6 +1850,31 @@ await _desktopAgent.RaiseIntent("StartChat", ContextType.Nothing); IIntentResolution resolution = await _desktopAgent.RaiseIntent("intentName", context); ``` + + + +```go +// raise an intent for resolution by the desktop agent +// a resolver UI may be displayed, or another method of resolving the intent to a +// target applied, if more than one application can resolve the intent +<-desktopAgent.RaiseIntent("StartChat", context, nil) + +// or find apps to resolve an intent to start a chat with a given contact +appIntentResult := <-desktopAgent.FindIntent("StartChat", &context, nil); +if appIntentResult.Err != nil || len(appIntentResult.Vlaue.Apps) == 0 { + // handle error or no apps returned +} + +// use the metadata of an app or app instance to describe the target app for the intent +<-desktopAgent.RaiseIntent("StartChat", context, appIntentResult.Vlaue.Apps[0]) + +//Raise an intent without a context by using the null context type +<-desktopAgent.RaiseIntent("StartChat", Context{Type: "fdc3.nothing"}, nil) + +//Raise an intent and retrieve a result from the IntentResolution +resolutionResult := <-desktopAgent.RaiseIntent("intentName", context, nil); +``` + @@ -1405,6 +1904,15 @@ raiseIntentForContext(context: Context, app?: AppIdentifier): Promise RaiseIntentForContext(IContext context, IAppIdentifier? app = null); ``` + + + +```go +func (desktopAgent *DesktopAgent) RaiseIntentForContext(context IContext, appIdentifier *AppIdentifier) <-chan Result[IntentResolution] { + // Implementation here +} +``` + @@ -1443,6 +1951,17 @@ var intentResolution = await _desktopAgent.RaiseIntentForContext(context); await _desktopAgent.RaiseIntentForContext(context, targetAppIdentifier); ``` + + + +```go +// Display a resolver UI for the user to select an intent and application to resolve it +intentResolutionResult := <-desktopAgent.RaiseIntentForContext(context, nil) + +// Resolve against all intents registered by a specific target app for the specified context +intentResolutionResult := <-desktopAgent.RaiseIntentForContext(context, &targetAppIdentifier) +``` + @@ -1473,6 +1992,13 @@ addContextListener(handler: ContextHandler): Promise; Not implemented ``` + + + +``` +Not implemented +``` + @@ -1498,6 +2024,13 @@ getSystemChannels() : Promise>; Not implemented ``` + + + +``` +Not implemented +``` + @@ -1522,6 +2055,13 @@ joinChannel(channelId: string) : Promise; Not implemented ``` + + + +``` +Not implemented +``` + @@ -1547,6 +2087,13 @@ open(name: string, context?: Context): Promise; Not implemented ``` + + + +``` +Not implemented +``` + @@ -1572,6 +2119,13 @@ raiseIntent(intent: string, context: Context, name: string): Promise + + +``` +Not implemented +``` + @@ -1597,6 +2151,13 @@ raiseIntentForContext(context: Context, name: string): Promise Not implemented ``` + + + +``` +Not implemented +``` + diff --git a/docs/api/ref/Errors.md b/docs/api/ref/Errors.md index 3470a2697..f0b4f29f6 100644 --- a/docs/api/ref/Errors.md +++ b/docs/api/ref/Errors.md @@ -36,6 +36,32 @@ enum AgentError { } ``` + + + +```go +var AgentError = struct { + // Returned if no Desktop Agent was found by any means available or + // if the Agent previously connected to is not contactable on a + // subsequent connection attempt. + AgentNotFound string + // Returned if validation of the app identity by the Desktop Agent + // failed or the app is not being allowed to connect to the Desktop Agent for another reason. + AccessDenied string + // Returned if an error or exception occurs while trying to set + // up communication with a Desktop Agent. + ErrorOnConnect string + // Returned if the failover function is not a function, or it did not + // resolve to one of the allowed types. + InvalidFailover string +}{ + AgentNotFound: "AgentNotFound", + AccessDenied: "AccessDenied", + ErrorOnConnect: "ErrorOnConnect", + InvalidFailover: "InvalidFailover", +} +``` + @@ -106,6 +132,33 @@ public static class ChannelError } ``` + + + +```go +var ChannelError = struct { + // Returned if the specified channel is not found when attempting to join a + // channel via the `joinUserChannel` function of the DesktopAgent. + NoChannelFound string + // SHOULD be returned when a request to join a user channel or to a retrieve + // a Channel object via the `JoinUserChannel` or `GetOrCreateChannel` methods + // of the DesktopAgent object is denied. + AccessDenied string + // SHOULD be returned when a channel cannot be created or retrieved via the + // `GetOrCreateChannel` method of the DesktopAgent. + CreationFailed string + // Returned if a call to the `broadcast` functions is made with an invalid + // context argument. Contexts should be Objects with at least a `type` field + // that has a `string` value. + MalformedContext string +}{ + NoChannelFound: "NoChannelFound", + AccessDenied: "AccessDenied", + CreationFailed: "CreationFailed", + MalformedContext: "MalformedContext", +} +``` + @@ -192,6 +245,36 @@ public static class OpenError } ``` + + + +```go +var OpenError = struct { + // Returned if the specified application is not found. + AppNotFound string + // Returned if the specified application fails to launch correctly. + ErrorOnLaunch string + // Returned if the specified application launches but fails to add a context + // listener in order to receive the context passed to the `fdc3.open` call. + AppTimeout string + // Returned if the FDC3 desktop agent implementation is not currently able to handle the request. + ResolverUnavailable string + // Returned if a call to the `Open` function is made with an invalid + // context argument.Contexts should be Objects with at least a `Type` field + // that has a `string` value. + MalformedContext string + // Experimental: Returned if the specified Desktop Agent is not found, via a connected Desktop Agent Bridge. + DesktopAgentNotFound string, +}{ + AppNotFound: "AppNotFound", + ErrorOnLaunch: "ErrorOnLaunch", + AppTimeout: "AppTimeout", + ResolverUnavailable: "ResolverUnavailable", + MalformedContext: "MalformedContext", + DesktopAgentNotFound: "DesktopAgentNotFound", +} +``` + @@ -316,6 +399,46 @@ public static class ResolveError } ``` + + + +```go +var ResolveError = struct { + // SHOULD be returned if no apps are available that can resolve the intent and context combination + NoAppsFound string + // Returned if the FDC3 desktop agent implementation is not currently able to handle the request + ResolverUnavailable string + // Returned if the user cancelled the resolution request, + // for example by closing or cancelling a resolver UI + UserCancelled string + // SHOULD be returned if a timeout cancels an intent resolution that required user interaction. + // Please use `ResolverUnavailable` instead for situations where a resolver UI or similar fails + ResolverTimeout string + // Returned if a specified target application is not available or + // a new instance of it cannot be opened. + TargetAppUnavailable string + // Returned if a specified target application instance is not available, + // for example because it has been closed + TargetInstanceUnavailable string + // Returned if the intent and context could not be delivered to the selected + // application or instance, for example because it has not added an intent handler within a timeout + IntentDeliveryFailed string + // Returned if a call to one of the `RaiseIntent` functions is made with an + // invalid context argument. Contexts should be Objects with at least a `Type` + // field that has a `string` value. + MalformedContext string +}{ + NoAppsFound: "NoAppsFound", + ResolverUnavailable: "ResolverUnavailable", + UserCancelled: "UserCancelledResolution", + ResolverTimeout: "ResolverTimeout", + TargetAppUnavailable: "TargetAppUnavailable", + TargetInstanceUnavailable: "TargetInstanceUnavailable", + IntentDeliveryFailed: "IntentDeliveryFailed", + MalformedContext: "MalformedContext", +} +``` + @@ -367,6 +490,23 @@ public static class ResultError } ``` + + + +```go +var ResultError = struct { + // IntentHandlerRejected Returned if the `IntentHandler` function processing the raised intent + // throws an error or rejects the Promise it returned. + IntentHandlerRejected string + // NoResultReturned Returned if the `IntentHandler` exited without returning a Promise or that + // Promise was not resolved with a Context or Channel object. + NoResultReturned string +}{ + IntentHandlerRejected: "IntentHandlerRejected", + NoResultReturned: "NoResultReturned", +} +``` + @@ -415,7 +555,34 @@ Not implemented ``` + + +```go +var BridgingError = struct { + // Experimental: Returned if a Desktop Agent did not return a response, via + // Desktop Agent Bridging, within the allotted timeout. + ResponseTimedOut string + // Experimental: Returned if a Desktop Agent that has been targeted by a + // particular request has been disconnected from the Bridge before a + // response has been received from it. + AgentDisconnected string + // Experimental: Returned for FDC3 API calls that are specified with + // arguments indicating that a remote Desktop agent should be targeted + // (e.g. raiseIntent with an app on a remote DesktopAgent targeted), + // when the local Desktop Agent is not connected to a bridge. + NotConnectedToBridge string + // Experimental: Returned if a message to a Bridge deviates from the schema + // for that message sufficiently that it could not be processed. + MalformedMessage string +}{ + ResponseTimedOut: "ResponseTimedOut", + AgentDisconnected: "AgentDisconnected", + NotConnectedToBridge: "NotConnectedToBridge", + MalformedMessage: "MalformedMessage", +} +``` + Contains constants representing the errors that can be encountered when queries are forwarded to a Desktop Agent Bridge, but one or more remote Desktop Agents connected to it disconnects, times-out or a malformed message is encountered while a particular request is in flight. These errors may be returned via the FDC3 API when a Desktop Agent is (or was) connected to a Desktop Agent Bridge. diff --git a/docs/api/ref/Events.md b/docs/api/ref/Events.md index 3eb89230c..35df7cd17 100644 --- a/docs/api/ref/Events.md +++ b/docs/api/ref/Events.md @@ -2,12 +2,18 @@ title: Events --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + In addition to intent and context events, the FDC3 API and PrivateChannel API may be used to listen for other types of events via their `addEventListener()` functions. ## `ApiEvent` Type defining a basic event object that may be emitted by an FDC3 API interface such as DesktopAgent or PrivateChannel. There are more specific event types defined for each interface. + + + ```ts interface ApiEvent { readonly type: string; @@ -15,6 +21,19 @@ interface ApiEvent { } ``` + + + +```go +type ApiEvent struct { + Type string + Details any +} +``` + + + + **See also:** - [`FDC3Event`](#fdc3event) @@ -22,10 +41,23 @@ interface ApiEvent { ## `EventHandler` + + + ```ts type EventHandler = (event: ApiEvent) => void; ``` + + + +```go +type EventHandler func(ApiEvent) +``` + + + + Describes a callback that handles non-context and non-intent events. Provides the details of the event. Used when attaching listeners to events. @@ -38,10 +70,27 @@ Used when attaching listeners to events. ## `FDC3EventTypes` + + + ```ts type FDC3EventTypes = "userChannelChanged"; ``` + + + +```go +type FDC3EventTypes string + +const ( + UserChannelChanged FDC3EventTypes = "userChannelChanged" +) +``` + + + + Type defining valid type strings for DesktopAgent interface events. **See also:** @@ -50,6 +99,9 @@ Type defining valid type strings for DesktopAgent interface events. ## `FDC3Event` + + + ```ts interface FDC3Event extends ApiEvent{ readonly type: FDC3EventTypes; @@ -57,6 +109,19 @@ interface FDC3Event extends ApiEvent{ } ``` + + + +```go +type FDC3Event struct { + ApiEvent + Type FDC3EventTypes +} +``` + + + + Type representing the format of event objects that may be received via the FDC3 API's `addEventListener` function. Events will always include both `type` and `details` properties, which describe the type of the event and any additional details respectively. @@ -68,6 +133,9 @@ Events will always include both `type` and `details` properties, which describe ### `FDC3ChannelChangedEvent` + + + ```ts interface FDC3ChannelChangedEvent extends FDC3Event { readonly type: "userChannelChanged"; @@ -77,16 +145,51 @@ interface FDC3ChannelChangedEvent extends FDC3Event { } ``` + + + +```go +type FDC3ChannelChangedEvent struct { + FDC3Event + Details FDC3ChannelChangedEventDetails +} + +type FDC3ChannelChangedEventDetails struct { + currentChannelId *string +} +``` + + + + Type representing the format of `userChannelChanged` events. The identity of the channel joined is provided as `details.currentChannelId`, which will be `null` if the app is no longer joined to any channel. ## `PrivateChannelEventTypes` + + + ```ts type PrivateChannelEventTypes = "addContextListener" | "unsubscribe" | "disconnect"; ``` + + + +```go +type PrivateChannelEventTypes string +const ( + AddContextListenerPrivateChannelEventType PrivateChannelEventTypes = "addContextListener" + UnsubscribePrivateChannelEventType PrivateChannelEventTypes = "unsubscribe" + DisconnectPrivateChannelEventType PrivateChannelEventTypes = "disconnect" +) +``` + + + + Type defining valid type strings for Private Channel events. **See also:** @@ -95,6 +198,9 @@ Type defining valid type strings for Private Channel events. ## `PrivateChannelEvent` + + + ```ts interface PrivateChannelEvent extends ApiEvent { readonly type: PrivateChannelEventTypes; @@ -102,6 +208,19 @@ interface PrivateChannelEvent extends ApiEvent { } ``` + + + +```go +type PrivateChannelEvent struct { + ApiEvent + Type PrivateChannelEventTypes +} +``` + + + + Type defining the format of event objects that may be received via a PrivateChannel's `addEventListener` function. **See also:** @@ -111,6 +230,9 @@ Type defining the format of event objects that may be received via a PrivateChan ### `PrivateChannelAddContextListenerEvent` + + + ```ts interface PrivateChannelAddContextListenerEvent extends PrivateChannelEvent { readonly type: "addContextListener"; @@ -120,12 +242,32 @@ interface PrivateChannelAddContextListenerEvent extends PrivateChannelEvent { } ``` + + + +```go +type PrivateChannelAddContextListenerEvent struct { + PrivateChannelEvent + Details PrivateChannelAddContextListenerEventDetails +} + +type PrivateChannelAddContextListenerEventDetails struct { + contextType *string +} +``` + + + + Type defining the format of events representing a context listener being added to the channel (`addContextListener`). Desktop Agents MUST fire this event for each invocation of `addContextListener` on the channel, including those that occurred before this handler was registered (to prevent race conditions). The context type of the listener added is provided as `details.contextType`, which will be `null` if all event types are being listened to. ### `PrivateChannelUnsubscribeEvent` + + + ```ts interface PrivateChannelUnsubscribeEvent extends PrivateChannelEvent { readonly type: "unsubscribe"; @@ -135,12 +277,32 @@ interface PrivateChannelUnsubscribeEvent extends PrivateChannelEvent { } ``` + + + +```go +type PrivateChannelUnsubscribeEvent struct { + PrivateChannelEvent + Details PrivateChannelUnsubscribeEventDetails +} + +type PrivateChannelUnsubscribeEventDetails struct { + contextType *string +} +``` + + + + Type defining the format of events representing a context listener removed from the channel (`Listener.unsubscribe()`). Desktop Agents MUST call this when `disconnect()` is called by the other party, for each listener that they had added. The context type of the listener removed is provided as `details.contextType`, which will be `null` if all event types were being listened to. ### `PrivateChannelDisconnectEvent` + + + ```ts export interface PrivateChannelDisconnectEvent extends PrivateChannelEvent { readonly type: "disconnect"; @@ -148,6 +310,18 @@ export interface PrivateChannelDisconnectEvent extends PrivateChannelEvent { } ``` + + + +```go +type PrivateChannelDisconnectEvent struct { + PrivateChannelEvent +} +``` + + + + Type defining the format of events representing a remote app being terminated or is otherwise disconnecting from the PrivateChannel. This event is fired in addition to unsubscribe events that will also be fired for any context listeners the disconnecting app had added. No details are provided. diff --git a/docs/api/ref/Metadata.md b/docs/api/ref/Metadata.md index 8279d4d4c..a8c3dea96 100644 --- a/docs/api/ref/Metadata.md +++ b/docs/api/ref/Metadata.md @@ -43,6 +43,19 @@ interface IAppIntent } ``` + + + +```go +type AppIntent struct { + // Details of the intent whose relationship to resolving applications is being described. + Intent IntentMetadata `json:"intent"` + + // Details of applications that can resolve the intent. + Apps []AppMetadata `json:"apps"` +} +``` + @@ -161,6 +174,36 @@ interface IAppMetadata : IAppIdentifier } ``` + + + +```go +type AppMetadata struct { + AppIdentifier + // The unique app name that can be used with the open and raiseIntent calls. + Name string `json:"name"` + // The Version of the application. + Version string `json:"version"` + // A more user-friendly application title that can be used to render UI elements. + Title string `json:"title"` + // A tooltip for the application that can be used to render UI elements. + Tooltip string `json:"tooltip"` + // A longer, multi-paragraph description for the application that could include markup. + Description string `json:"description"` + // A list of icon URLs for the application that can be used to render UI elements. + Icons []app_dir.Icon `json:"icons"` + // A list of image URLs for the application that can be used to render UI elements. + Screenshots []app_dir.Screenshot `json:"screenshots"` + // The type of output returned for any intent specified during resolution. May express a particular context type, + // channel, or channel with specified type + ResultType string `json:"resultType"` + // An optional set of, implementation specific, metadata fields that can be + // used to disambiguate instances, such as a window title or screen position. + // Must only be set if `instanceId` is set. + InstanceMetadata map[string]interface{} `json:"instanceMetadata"` +} +``` + @@ -207,6 +250,16 @@ interface IContextMetadata } ``` + + + +```go +type ContextMetadata struct { + // Identifier for the app instance that sent the context and/or intent. + Source AppIdentifier `json:"source"` +} +``` + @@ -270,6 +323,22 @@ interface IDisplayMetadata } ``` + + + +```go +type DisplayMetadata struct { + // A user-readable name for this channel, e.g: Red. + Name string `json:"name"` + // The color that should be associated within this channel when displaying + // this channel in a UI, e.g: `#FF0000`. May be any color value supported by + // CSS, e.g. name, hex, rgba, etc.. + Color string `json:"color"` + // A URL of an image that can be used to display this channel. + Glyph string `json:"glyph"` +} +``` + @@ -316,6 +385,20 @@ interface IIcon } ``` + + + +```go +type Icon struct { + // The icon url + Src string `json:"src"` + // The icon dimensions, formatted as '{height}x{width}' + Size string `json:"size"` + // Icon media type. If not present, the Desktop Agent may use the src file extension. + Type string `json:"type"` +} +``` + @@ -350,6 +433,24 @@ AppMetadata includes an icons property allowing multiple icon types to be specif IIcon? icon = appMetadata?.Icons.Where(icon => icon.Size == "48x48").First(); ``` + + + +```go +icons := []Icon{ + { + Src: "https://app.foo.icon/app_icons/lowres.webp", + Size: "48x48", + Type: "image/webp", + }, + { + Src: "https://app.foo.icon/app_icons/hd_hi.svg", + Size: "72x72", + Type: "image/svg+xml", + }, + } +``` + @@ -406,6 +507,22 @@ interface IImage } ``` + + + +```go +type Image struct { + // The icon url + Src string `json:"src"` + // The icon dimensions, formatted as '{height}x{width}' + Size string `json:"size"` + // Icon media type. If not present, the Desktop Agent may use the src file extension. + Type string `json:"type"` + // Caption for the image + Label string `json:"label"` +} +``` + @@ -445,6 +562,26 @@ foreach (IImage image in appMetadata.Screenshots) } ``` + + + +```go +icons := []Image{ + { + Src: "https://app.foo.icon/app_screenshots/dashboard.png", + Size: "800x600", + Type: "image/png", + Label: "Example app dashboard", + }, + { + Src: "https://app.foo.icon/app_screenshots/notifications.png", + Size: "800x600", + Type: "image/png", + Label: "Order notifications view", + }, + } +``` + @@ -552,6 +689,32 @@ class OptionalDesktopAgentFeatures } ``` + + + +```go +type ImplementationMetadata struct { + // The version number of the FDC3 specification that the implementation provides. + // The string must be a numeric semver version, e.g. 1.2 or 1.2.1. + Fdc3Version string `json:"fdc3Version"` + // The name of the provider of the FDC3 Desktop Agent Implementation (e.g. Finsemble, Glue42, OpenFin etc.). + Provider string `json:"provider"` + // The version of the provider of the FDC3 Desktop Agent Implementation (e.g. 5.3.0). + ProviderVersion string `json:"providerVersion"` + // Metadata indicating whether the Desktop Agent implements optional features of the Desktop Agent API. + OptionalFeatures struct { + // Used to indicate whether the exposure of 'originating app metadata' for context and intent + // messages is supported by the Desktop Agent. + OriginatingAppMetadata bool `json:"OriginatingAppMetadata"` + // Used to indicate whether the optional 'JoinUserChannel', 'GetCurrentChannel', and 'LeaveCurrentChannel' + // are implemented by the Desktop Agent. + UserChannelMembershipAPIs bool `json:"UserChannelMembershipAPIs"` + } `json:"optionalFeatures"` + // The calling application instance's own metadata according to the Desktop Agent + AppMetadata AppMetadata `json:"appMetadata"` +} +``` + @@ -598,6 +761,20 @@ interface IIntentMetadata } ``` + + + +```go +type IntentMetadata struct { + // The unique name of the intent that can be invoked by the raiseIntent call. + Name string `json:"name"` + + // A friendly display name for the intent that should be used to render UI elements. + DisplayName string `json:"displayName"` +} + +``` + @@ -669,6 +846,26 @@ interface IIntentResolution } ``` + + + +```go +type IntentResolution struct { + // The application that resolved the intent. + Source AppIdentifier `json:"source"` + // The intent that was raised. + Intent string `json:"intent"` + // The version number of the Intents schema being used. + Version string `json:"version"` +} + +type IntentResult any + +func (ir *IntentResolution) GetResult() <-chan Result[IntentResult] { + // implementation here +} +``` + @@ -745,6 +942,23 @@ catch (Exception ex) } ``` + + + +```go +resolutionResult := <-desktopAgent.RaiseIntent("QuoteStream", context, nil) +if resolutionResult.Err != nil { + // handle error +} +if channel, ok := resolutionResult.Value.(Channel); ok { + log.Println("The result is a channel") +} else if context, ok := resolutionResult.Value.(Context); ok { + log.Println("The result is a context") +} else { + log.Error("The result is of incorrect data type!") +} +``` + diff --git a/docs/api/ref/PrivateChannel.md b/docs/api/ref/PrivateChannel.md index ddcb07f62..43d0a9122 100644 --- a/docs/api/ref/PrivateChannel.md +++ b/docs/api/ref/PrivateChannel.md @@ -47,6 +47,35 @@ interface IPrivateChannel : IChannel, IIntentResult } ``` + + + +```go +type PrivateChannel struct { + Channel +} + +func (privateChannel *PrivateChannel) AddEventListener(eventType *PrivateChannelEventTypes, handler EventHandler) <-Result[Listener] { + // Implementation here +} +func (privateChannel *PrivateChannel) Disconnect() <-Result[any] { + // Implementation here +} + +// Deprecated +func (ch *PrivateChannel) OnAddContextListener(handler func(contextType *ContextType)) Result[Listener] { + // Implementation here +} + +func (ch *PrivateChannel) OnUnsubscribe(handler func(contextType *ContextType)) Result[Listener] { + // Implementation here +} + +func (ch *PrivateChannel) OnDisconnect(handler func()) Result[Listener] { + // Implementation here +} +``` + @@ -137,6 +166,43 @@ _desktopAgent.AddIntentListener("QuoteStream", async (context, metad }); ``` + + + +```go +<-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 + } +) +``` + @@ -215,6 +281,32 @@ catch (Exception ex) } ``` + + + +```go +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) + } +} +``` + @@ -236,6 +328,15 @@ addEventListener(type: PrivateChannelEventTypes | null, handler: EventHandler): Not implemented ``` + + + +```go +func (privateChannel *PrivateChannel) AddEventListener(eventType *PrivateChannelEventTypes, handler EventHandler) <-Result[Listener] { + // Implementation here +} +``` + @@ -260,6 +361,15 @@ const listener: Listener = await myPrivateChannel.addEventListener(null, Not implemented ``` + + + +```go +listenerResult := AddEventListener(nil, func(event PrivateChannelEvent) { + fmt.Printf("Received event %v\n\tDetails: %v", event.Type, event.Details) +}) +``` + @@ -288,6 +398,15 @@ disconnect(): Promise; void Disconnect(); ``` + + + +```go +func (privateChannel *PrivateChannel) Disconnect() <-Result[any] { + // Implementation here +} +``` + @@ -311,6 +430,15 @@ onAddContextListener(handler: (contextType?: string) => void): Listener; IListener OnAddContextListener(Action handler); ``` + + + +```go +func (privateChannel *PrivateChannel) OnAddContextListener(handler func(contextType *ContextType)) Result[Listener] { + // Implementation here +} +``` + @@ -334,6 +462,15 @@ onUnsubscribe(handler: (contextType?: string) => void): Listener; IListener OnUnsubscribe(Action handler); ``` + + + +```go +func (privateChannel *PrivateChannel) OnUnsubscribe(handler func(contextType *ContextType)) Result[Listener] { + // Implementation here +} +``` + @@ -357,6 +494,15 @@ onDisconnect(handler: () => void): Listener; IListener OnDisconnect(Action handler); ``` + + + +```go +func (privateChannel *PrivateChannel) OnDisconnect(handler func()) Result[Listener] { + // Implementation here +} +``` + diff --git a/docs/api/ref/Types.md b/docs/api/ref/Types.md index d77f942ab..272e787b9 100644 --- a/docs/api/ref/Types.md +++ b/docs/api/ref/Types.md @@ -54,6 +54,18 @@ interface IAppIdentifier } ``` + + + +```go +type AppIdentifier struct { + // The unique application identifier located within a specific application directory instance. An example of an appId might be 'app@sub.root'. + AppId string `json:"appId"` + // An optional instance identifier, indicating that this object represents a specific instance of the application described. + InstanceId string `json:"instanceId"` +} +``` + @@ -102,6 +114,21 @@ interface IDynamicContext } ``` + + + +```go +type Context struct { + Type string `json:"type"` + Name string `json:"name"` + Id map[string]string `json:"id"` +} + +type IContext interface { + // TODO: include at least one method in here +} +``` + @@ -140,6 +167,13 @@ type ContextHandler = (context: Context, metadata?: ContextMetadata) => void; delegate void ContextHandler(T context, IContextMetadata? metadata = null) where T : IContext; ``` + + + +```go +type ContextHandler func(IContext, *ContextMetadata) +``` + @@ -178,7 +212,15 @@ Not implemented ``` + +```go +type DesktopAgentIdentifier struct { + DesktopAgent string +} +``` + + (Experimental) Identifies a particular Desktop Agent in Desktop Agent Bridging scenarios where a request needs to be directed to a Desktop Agent rather than a specific app, or a response message is returned by the Desktop Agent (or more specifically its resolver) rather than a specific app. Used as a substitute for `AppIdentifier` in cases where no app details are available or are appropriate. @@ -203,6 +245,13 @@ type IntentHandler = (context: Context, metadata?: ContextMetadata) => Promise IntentHandler(T context, IContextMetadata? metadata = null) where T : IContext; ``` + + + +```go +type IntentHandler func(IContext, *ContextMetadata) <-chan IntentResult +``` + @@ -236,6 +285,20 @@ type IntentResult = Context | Channel | void; interface IIntentResult { /* Marker interface implemented by IContext and Channel */ } ``` + + + +```go +type IntentResult any + +type IntentResultType string +const ( + ChannelIntentResult IntentResultType = "Channel" + ContextIntentResult IntentResultType = "Context" + PrivateChannelIntentResult IntentResultType = "PrivateChannel" +) +``` + @@ -275,6 +338,16 @@ interface IListener } ``` + + + +```go +type IListener interface { + Unsubscribe() +} +type Listener struct {} +``` + @@ -294,6 +367,15 @@ unsubscribe(): Promise; void Unsubscribe(); ``` + + + +```go +func (l *Listener) Unsubscribe() { + // Implementation here +} +``` + diff --git a/docs/api/supported-platforms.md b/docs/api/supported-platforms.md index 369231006..9c6651a85 100644 --- a/docs/api/supported-platforms.md +++ b/docs/api/supported-platforms.md @@ -123,6 +123,48 @@ For a .NET application to be FDC3-enabled, it needs to run in the context of a p FDC3 offers the [`Finos.Fdc3` NuGet package](https://www.nuget.org/packages/Finos.Fdc3) that can be used by .NET applications to target operations from the [API Specification](./spec) in a consistent way. Each FDC3-compliant desktop agent that the application runs in, can then provide an implementation of the FDC3 API operations. +### GO + + +For a Go application to be FDC3-enabled, it needs to run in the context of a platform provider that makes the FDC3 API available to Go applications. The Go language API binding varies from the JavaScript/TypeScript implementation in a number of ways due to the specifics of the Go language. Namely: +- A `Result` type, as described in [the Desktop Agent specs](../api/ref/DesktopAgent.md#desktopagent), is returned by API calls to accommodate for error handling via golang Channels. Channel is the closest equivalent to `Promise`. Result type has `Value` and `Err` fields, where `Value` type corresponds with the return type expected from this function, and `Err` would contain the golang `error` type error for handling: + + ```go + type Result[T any] struct { + Value *T + Err error + } + ``` + +- In order to create [additional contexts](../context/ref/), please use the provided type [Context](../api/ref/Types.md/#context) as a field in the custom contexts. This way the base fields ("type", "name", "id") will be embedded in the resulting json: + + ```go + type TimeRange struct { + Context + StartTime string `json:"startTime"` + EndTime string `json:"endTime"` + } + ``` + + However, this is a trade-off, since this is optimized for the ability to create the correct json, however, it introduces the need for each of the specific context classes to implement the interface [IContext](../api/ref/Types.md/#context) which is ultimately not required. Add at least one method in your implementation of IContext, and implement it for Context type as well as any other specific contexts you create, for example: + + ```go + type IContext interface { + MarshalContext() []byte, err + } + + func (context *Context) MarshalContext() []byte, err { + + } + + func (timeRangeContext *TimeRange) MarshalContext() []byte, err { + + } + ``` + +- Golang has no strict requirement for a type to declare that it implements an interface: if a type implements a specific method, then it implements that interface implicitly. The Go language binding for FDC3 includes `interface`, `struct` and `func` types for all of the entities defined in the FDC3 API Part (i.e. `DesktopAgent`, `Channel`, `AppIdentifier`, `ContextHandler` etc.) . However, to be able to use the interfaces, specific types need to be created with the implementation of that interface, even if they are empty structs. Hence, types for these are defined alongside the interface (ex. [`DesktopAgent` is an empty struct, but it would implement methods of the IDesktopAgent interface](../api/ref/DesktopAgent.md#desktopagent)). +- Deprecated functions with the same name as other functions are omitted in golang, as it does not allow function/method overloading. In the event that additional function/method overloads are added to FDC3 these should be handled in the Go binding with a different function name to the overloaded function. + ## Hybrid In a hybrid application, a standalone native application incorporates a web view, within which a web application runs. This may be considered a special case of the web platform where all platform-provider requirements for web applications must be satisfied, but it is the responsibility of the associated native application, rather than a platform provider, to ensure they are fulfilled. This may be achieved via either of the defined web interfaces, i.e. by injecting an implementation of the DesktopAgent API at `window.fdc3` or via the FDC3 Web Connection Protocol (`postMessage`). diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 13cadf52a..a5798280e 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -51,7 +51,7 @@ module.exports={ "plugins": [], "themeConfig": { "prism": { - "additionalLanguages": ["typescript","javascript","json","csharp"], + "additionalLanguages": ["typescript","javascript","json","csharp", "go"], "theme": require('prism-react-renderer/themes/vsDark') }, "algolia": {