Skip to content

Commit

Permalink
Tidied up code after @novavi's review
Browse files Browse the repository at this point in the history
  • Loading branch information
robmoffat committed Jul 4, 2023
1 parent e4127d4 commit 69823fa
Show file tree
Hide file tree
Showing 13 changed files with 141 additions and 113 deletions.
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ This is a minimal proof-of-concept for FDC3 For the Web.
## What This Project Contains
- In `\lib`: A minimal implementation called `webc3.ts`. This relies on the `post-message` strategy for communicating between apps and the desktop agent. This supports two functions:
- In `\lib`: A minimal implementation of web api retrieval. This relies on the `post-message` strategy for communicating between apps and the desktop agent. This supports two functions:
- `supply`: Called by the desktop agent on startup, allows it to supply FDC3 APIs to apps when they ask for one. This takes a parameter of a url of a piece of javascript that the App will load in order to initialise it's API.
- `supply` (in `supply.ts`): Called by the desktop agent on startup, allows it to supply FDC3 APIs to apps when they ask for one. This takes the following parameters:
- A `url` of a piece of javascript that the App will load in order to initialise it's API.
- A `checker`, which checks the origin window for the API request. It should be a window that the Desktop Agent is aware of.
- A `detailsResolver`, which returns a map of properties to send to the API requestor that should be used to instantiate the API.
- `load`: Called (with options) by an FDC3 Aoo to retrieve the API.
- `load` (in `load.ts`): Called (with options) by an FDC3 Aoo to retrieve the API. This retrieves the `url` and `details` from the desktop agent and initialises a `DesktopAgent` implementation, returning it in a promise.
- In `\demo`: A fixture for demonstrating the above, containing two apps, `app1` and `app2` and a rudimentary `dummy-desktop-agent` all of which use the `webc3.ts` library.
Expand All @@ -43,5 +46,7 @@ This is a minimal proof-of-concept for FDC3 For the Web.
- Figure out options, setting global
- Fallback strategy in case FDC3 API isn't available (currently promise never resolves)
- Sanitisation of response from the Desktop Agent
- Handing of fdc3Ready
- Handling of Electron container injection approach
2 changes: 1 addition & 1 deletion src/demo/app1.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { load } from '../lib/webc3'
import { load } from '../lib/load'

function createContext(i: number) {
return {
Expand Down
4 changes: 2 additions & 2 deletions src/demo/app2.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { load } from '../lib/webc3'
import { load } from '../lib/load'

/**
* This demonstrates using the API via a promise
Expand All @@ -7,7 +7,7 @@ load().then(fdc3 => {
console.log("in promise")
const log = document.getElementById("log");
const msg = document.createElement("p");
msg.textContent = "FDC Loaded: "+JSON.stringify(fdc3.getInfo());
msg.textContent = "FDC3 Loaded: "+JSON.stringify(fdc3.getInfo());
log?.appendChild(msg);

fdc3.addContextListener(null, context => {
Expand Down
20 changes: 11 additions & 9 deletions src/demo/dummy-desktop-agent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AppIdentifier } from "@finos/fdc3";
import { supply } from "../lib/webc3";
import { AppIdentifierResolver, DesktopAgentDetailResolver } from "../lib/types";
import { supply } from "../lib/supply";
import { AppChecker, DesktopAgentDetailResolver } from "../lib/types";


window.addEventListener("load", () => {
Expand All @@ -25,16 +25,19 @@ window.addEventListener("load", () => {
}

// for a given window, allows us to determine which app it is (if any)
const appIdentifierResolver : AppIdentifierResolver = o => instances.find(i => i.window ==o);
const daDetailResolver : DesktopAgentDetailResolver = () => {
const appChecker : AppChecker = o => instances.find(i => i.window ==o) != undefined;
const daDetailResolver : DesktopAgentDetailResolver = (o) => {
const appIdentifier = instances.find(i => i.window ==o)!!
return {
apiId : currentApiInstance++,
apikey: "Abc"
}
apikey: "Abc",
appId: appIdentifier.appId,
instanceId: appIdentifier.instanceId!!
}
}

// set up desktop agent handler here using FDC3 Web Loader (or whatever we call it)
supply("/src/demo/implementation.js", appIdentifierResolver, daDetailResolver);
supply("/src/demo/implementation.js", appChecker, daDetailResolver);

// hook up the buttons
document.getElementById("app1")?.addEventListener("click", () => launch("/static/app1/index.html", "1"));
Expand All @@ -51,8 +54,7 @@ window.addEventListener("load", () => {
const origin = event.origin;
const source = event.source as Window
console.log(`Broadcast Origin: ${origin} Source: ${source} From ${JSON.stringify(data.from)}`);
const appIdentifier = appIdentifierResolver(source);
if (appIdentifier != undefined) {
if (appChecker(source)) {
instances
.forEach(i => {
i.window.postMessage(data, "*")
Expand Down
14 changes: 9 additions & 5 deletions src/demo/implementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
*/
class DummyDesktopAgent {

constructor(id, details) {
this.id = id;
constructor(details) {
this.details = details;
this.id = {
appId: this.details.appId,
instanceId: this.details.instanceId
}
}

broadcast(context) {
Expand All @@ -31,11 +34,12 @@ class DummyDesktopAgent {

getInfo() {
return {
fdc3Version: "2.0"
fdc3Version: "2.0",
id: this.id
}
}
}

export default (id, details) => {
return new DummyDesktopAgent(id, details);
export default (details) => {
return new DummyDesktopAgent(details);
}
24 changes: 24 additions & 0 deletions src/lib/load.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { DesktopAgent} from '@finos/fdc3'
import { loader as loader1 } from './strategies/post-message-load-js'
import { loader as loader2 } from './strategies/electron-event';

import { DEFAULT_OPTIONS, Options } from './types';

/**
* This return an FDC3 API. Called by Apps.
*/
export function load(options: Options = DEFAULT_OPTIONS) : Promise<DesktopAgent> {

function handleGenericOptions(da: DesktopAgent) {
if ((options.setWindowGlobal) && (window.fdc3 == null)) {
window.fdc3 = da;
}

return da;
}

const strategies = [ loader1(options), loader2(options) ];

return Promise.any(strategies)
.then(da => handleGenericOptions(da))
}
4 changes: 2 additions & 2 deletions src/lib/loaders/load-with-import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { APIResponseMessage, FDC3Initialiser } from "../types";
/**
* This loads the script using an import
*/
export function load(data: APIResponseMessage) : Promise<DesktopAgent> {
export function loadJS(data: APIResponseMessage) : Promise<DesktopAgent> {
return import(/* @vite-ignore */ data.url).then(ns => {
const init = ns.default as FDC3Initialiser;
const da = init(data.appIdentifier, data.daDetails);
const da = init(data.details);
return da;
})
}
19 changes: 19 additions & 0 deletions src/lib/strategies/electron-event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DesktopAgent } from "@finos/fdc3";
import { Loader, Options, } from "../types";
import { fdc3Ready } from "@finos/fdc3";

/**
* This approach will resolve the loader promise if the fdc3Ready event occurs.
* This is done by electron implementations setting window.fdc3.
*/
export const loader: Loader = (_options: Options) => {

const out = new Promise<DesktopAgent>((resolve) => {
fdc3Ready().then(() => {
resolve(window.fdc3);
})

});

return out;
}
90 changes: 42 additions & 48 deletions src/lib/strategies/post-message-load-js.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,63 @@
import { AppIdentifier, DesktopAgent} from '@finos/fdc3'
import { APIResponseMessage, AppIdentifierResolver, DesktopAgentDetailResolver, Options, Strategy } from '../types'
import { load } from '../loaders/load-with-import';
import { DesktopAgent } from '@finos/fdc3'
import { APIResponseMessage, AppChecker, DesktopAgentDetailResolver, Loader, Options, Supplier } from '../types'
import { loadJS } from '../loaders/load-with-import';

const FDC3_API_REQUEST_MESSAGE_TYPE = 'FDC3-API-Request';
const FDC3_API_RESPONSE_MESSAGE_TYPE = 'FDC3-API-Response';


export const strategy : Strategy = {

supply : (url: string, resolver: AppIdentifierResolver, detailsResolver: DesktopAgentDetailResolver) => {
function createResponseMessage(appIdentifier: AppIdentifier) : APIResponseMessage {
export const supplier: Supplier = (url: string, checker: AppChecker, detailsResolver: DesktopAgentDetailResolver) => {
function createResponseMessage(source: Window): APIResponseMessage {
return {
type: FDC3_API_RESPONSE_MESSAGE_TYPE,
url,
appIdentifier : {
appId: appIdentifier.appId,
instanceId: appIdentifier.instanceId
},
daDetails: detailsResolver(appIdentifier)
url,
details: detailsResolver(source)
}
}

window.addEventListener(
"message",
(event) => {
console.log("Received: "+JSON.stringify(event));
const data = event.data;
if (data == FDC3_API_REQUEST_MESSAGE_TYPE) {
const origin = event.origin;
const source = event.source as Window
const appIdentifier = resolver(source);
if (appIdentifier != null) {
console.log(`API Request Origin: ${origin} Source: ${source}`);
source.postMessage(createResponseMessage(appIdentifier), origin);
console.log("Received: " + JSON.stringify(event));
const data = event.data;
if (data == FDC3_API_REQUEST_MESSAGE_TYPE) {
const origin = event.origin;
const source = event.source as Window
if (checker(source)) {
console.log(`API Request Origin: ${origin} Source: ${source}`);
source.postMessage(createResponseMessage(source), origin);
}
}
}
});
},
}

load : (options: Options) => {

function handleOptions(da: DesktopAgent) {
return da;
}

const out = new Promise<DesktopAgent>((resolve, reject) => {
// setup listener for message and retrieve JS URL from it
window.addEventListener("message", (event) => {
const data : APIResponseMessage = event.data ;
if (data.type == FDC3_API_RESPONSE_MESSAGE_TYPE) {
load(data)
.then(da => handleOptions(da))
.then(da => resolve(da))
} else {
reject("Incorrect API Response Message");
}
}, {once: true});
});

const da = window.opener;
export const loader: Loader = (_options: Options) => {

if (da != null) {
window.opener.postMessage(FDC3_API_REQUEST_MESSAGE_TYPE, "*");
}
function handleOptions(da: DesktopAgent) {
return da;
}

const out = new Promise<DesktopAgent>((resolve, reject) => {
// setup listener for message and retrieve JS URL from it
window.addEventListener("message", (event) => {
const data: APIResponseMessage = event.data;
if (data.type == FDC3_API_RESPONSE_MESSAGE_TYPE) {
loadJS(data)
.then(da => handleOptions(da))
.then(da => resolve(da))
} else {
reject("Incorrect API Response Message");
}
}, { once: true });
});

const da = window.opener;

return out;
if (da != null) {
window.opener.postMessage(FDC3_API_REQUEST_MESSAGE_TYPE, "*");
}

}
return out;
}
11 changes: 11 additions & 0 deletions src/lib/supply.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { supplier } from './strategies/post-message-load-js'
import { AppChecker, DesktopAgentDetailResolver } from './types';

/**
* This configures the postMessage listener to respond to requests for desktop agent APIs.
* Called by the desktop agent
*/
export function supply(url: string, appIdResolver: AppChecker, detailsResolver: DesktopAgentDetailResolver) {
supplier(url, appIdResolver, detailsResolver);
}

21 changes: 10 additions & 11 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,40 +4,39 @@ import { AppIdentifier, DesktopAgent} from '@finos/fdc3'
* We need to add options here.
*/
export type Options = {
setWindowGlobal: boolean
setWindowGlobal: boolean,
fireFdc3Ready: boolean
}

export const DEFAULT_OPTIONS : Options = {
setWindowGlobal: false
setWindowGlobal: false,
fireFdc3Ready: false
}

export type AppIdentifierResolver = (o: Window) => AppIdentifier | undefined;
export type AppChecker = (o: Window) => boolean;

export type Strategy = {
supply: (url: string, idResolver: AppIdentifierResolver, detailsResolver: DesktopAgentDetailResolver) => void
load: (options: Options) => Promise<DesktopAgent>
}
export type Supplier = (url: string, checker: AppChecker, detailsResolver: DesktopAgentDetailResolver) => void
export type Loader = (options: Options) => Promise<DesktopAgent>

/**
* These are details such as login information sent from the desktop back to the
* app in order to initialise the api.
*/
export type DesktopAgentDetails = { [key: string] : string | number | boolean }

export type DesktopAgentDetailResolver = (a: AppIdentifier) => DesktopAgentDetails
export type DesktopAgentDetailResolver = (o: Window) => DesktopAgentDetails

/**
* When writing an FDC3 implementation, this is the shape of the function
* that should be returned by the DesktopAgent's supply url.
*/
export type FDC3Initialiser = (id: AppIdentifier, daDetails: DesktopAgentDetails) => DesktopAgent
export type FDC3Initialiser = (details: DesktopAgentDetails) => DesktopAgent

/**
* This is the object that the desktop agent must get back to the App.
*/
export type APIResponseMessage = {
type: string,
url: string,
appIdentifier: AppIdentifier,
daDetails: DesktopAgentDetails
details: DesktopAgentDetails
}
30 changes: 0 additions & 30 deletions src/lib/webc3.ts

This file was deleted.

Loading

0 comments on commit 69823fa

Please sign in to comment.