Skip to content

Commit

Permalink
Merge pull request #12 from finos-labs/full-da
Browse files Browse the repository at this point in the history
Now contains iframe and port-based connection approaches in demo
  • Loading branch information
robmoffat authored Dec 15, 2023
2 parents 10c8491 + dfedf0d commit fc4da1c
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 43 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ target/
**/*.buildinfo
/**/dist
/**/node_modules
**/tsconfig.buildinfo
packages/server/tsconfig.tsbuildinfo
packages/fdc3-common/tsconfig.tsbuildinfo
packages/client/tsconfig.tsbuildinfo
36 changes: 22 additions & 14 deletions packages/client/src/messaging/message-port.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,9 @@ import { MessagePortMessaging } from "./MessagePortMessaging";
import { ConnectionStep2Hello, ConnectionStep3Handshake } from "@finos/fdc3/dist/bridging/BridgingTypes";

/**
* Initialises the desktop agent by opening an iframe
* on the desktop agent host and communicating via a messsage port to it.
*
* It is up to the desktop agent to arrange communucation between other
* windows.
* Given a message port, constructs a desktop agent to communicate via that.
*/
export async function messagePortInit(data: APIResponseMessage, options: Options) : Promise<DesktopAgent> {

const action = data.uri ? () => {
return openFrame(data.uri!!);
} : () => {
return messageParentWindow(options.frame)
}

const mp = await exchangeForMessagePort(window, FDC3_PORT_TRANSFER_RESPONSE_TYPE, action) as MessagePort
export async function messagePortInit(mp: MessagePort, data: APIResponseMessage) : Promise<DesktopAgent> {
mp.start()

const handshakeData = (await exchange(mp, "handshake", () => sendHello(mp, data))).data as ConnectionStep3Handshake
Expand All @@ -35,6 +23,26 @@ export async function messagePortInit(data: APIResponseMessage, options: Options
data.provider);
}

/**
* Initialises the desktop agent by opening an iframe
* on the desktop agent host and communicating via a messsage port to it.
*
* It is up to the desktop agent to arrange communucation between other
* windows.
*/
export async function messagePortIFrameInit(data: APIResponseMessage, options: Options) : Promise<DesktopAgent> {

const action = data.uri ? () => {
return openFrame(data.uri!!);
} : () => {
return messageParentWindow(options.frame)
}

const mp = await exchangeForMessagePort(window, FDC3_PORT_TRANSFER_RESPONSE_TYPE, action) as MessagePort

return messagePortInit(mp, data);
}

/**
* If the desktop agent doesn't provide an opener URL, we message another iframe asking for the port.
*/
Expand Down
13 changes: 9 additions & 4 deletions packages/client/src/strategies/post-message.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DesktopAgent } from '@finos/fdc3'
import { APIResponseMessage, Loader, Options, FDC3_API_RESPONSE_MESSAGE_TYPE, FDC3_API_REQUEST_MESSAGE_TYPE } from 'fdc3-common'
import { messagePortInit } from '../messaging/message-port';
import { messagePortIFrameInit, messagePortInit } from '../messaging/message-port';

const loader: Loader = (options: Options) => {

Expand All @@ -9,10 +9,15 @@ const loader: Loader = (options: Options) => {
window.addEventListener("message", (event) => {
const data: APIResponseMessage = event.data;
if ((data.type == FDC3_API_RESPONSE_MESSAGE_TYPE) && (data.method == 'message-port')) {
resolve(messagePortInit(data, options))
} else {
reject("Incorrect API Response Message");
if (event.ports.length == 1) {
resolve(messagePortInit(event.ports[0], data));
} else if (data.uri) {
resolve(messagePortIFrameInit(data, options))
}
}

// need either a port or a uri
reject("Incorrect API Response Message: "+JSON.stringify(data));
}, { once: true });
});

Expand Down
2 changes: 1 addition & 1 deletion packages/client/tsconfig.tsbuildinfo

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions packages/demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
<option value="Nested">Nested Frame</option>
</select>

<select name="approach" id="approach">
<option value="IFRAME" default>Create Iframe</option>
<option value="PARENT_POST_MESSAGE">Parent Post-Message</option>
</select>

<ul>
<li>
<a type="button" href="#" id="app1" >Open App 1 on MAIN HOST</a>
Expand Down
4 changes: 2 additions & 2 deletions packages/demo/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@


export const MAIN_HOST = 'http://robs-pro:8080'
export const SECOND_HOST = 'http://localhost:8080'
export const MAIN_HOST = 'http://localhost:8080'
export const SECOND_HOST = 'http://robs-pro:8080'
63 changes: 50 additions & 13 deletions packages/demo/src/dummy-desktop-agent.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { AppIdentifier } from "@finos/fdc3";
import { AppChecker, DesktopAgentDetailResolver, FDC3_PORT_TRANSFER_REQUEST_TYPE, } from "fdc3-common";
import { AppChecker, DesktopAgentDetailResolver, DesktopAgentDetails, DesktopAgentPortResolver, FDC3_PORT_TRANSFER_REQUEST_TYPE, } from "fdc3-common";
import { supply } from "server";
import { MAIN_HOST, SECOND_HOST } from "./constants";

enum Approach { Tab, Frame, Nested }
enum Opener { Tab, Frame, Nested }

enum Approach { IFRAME, PARENT_POST_MESSAGE }

window.addEventListener("load", () => {

Expand All @@ -14,9 +16,16 @@ window.addEventListener("load", () => {

const instances : AppIdentifierAndWindow[] = []

function getApproach() : Approach {
function getOpener() : Opener {
const cb = document.getElementById("opener") as HTMLInputElement;
const val = cb.value
var out : Opener = Opener[val as keyof typeof Opener]; //Works with --noImplicitAny
return out;
}

function getApproach() : Approach {
const cb = document.getElementById("approach") as HTMLInputElement;
const val = cb.value
var out : Approach = Approach[val as keyof typeof Approach]; //Works with --noImplicitAny
return out;
}
Expand Down Expand Up @@ -44,13 +53,13 @@ window.addEventListener("load", () => {
}

function open(url: string): Window {
const approach = getApproach();
switch (approach) {
case Approach.Tab:
const opener = getOpener();
switch (opener) {
case Opener.Tab:
return openTab(url);
case Approach.Nested:
case Opener.Nested:
return openNested(url);
case Approach.Frame:
case Opener.Frame:
return openFrame(url);
}
throw new Error("unsupported")
Expand All @@ -71,17 +80,45 @@ window.addEventListener("load", () => {
// for a given window, allows us to determine which app it is (if any)
const appChecker : AppChecker = o => instances.find(i => i.window == o)

// this is for when the API is using an iframe, and needs to know the address to load
const detailsResolver : DesktopAgentDetailResolver = (o, a) => {
const apiKey = "ABC"+ (currentApiInstance++)
return {
apiKey
if (getApproach() == Approach.IFRAME) {
return {
apiKey,
uri: MAIN_HOST+ "/static/embed/index.html"
}
} else {
return {
apiKey
} as DesktopAgentDetails
}
}

// set up desktop agent handler here using FDC3 Web Loader (or whatever we call it)
supply(appChecker, detailsResolver, {
uri: MAIN_HOST+ "/static/embed/index.html"
// this is for when the api isn't using an iframe, and just requests a port from this host directly.
const sw = new SharedWorker(MAIN_HOST+'/src/server/SimpleServer.ts', {
type: "module",
name: "Demo FDC3 Server"
})

sw.port.start()

const portResolver : DesktopAgentPortResolver = (o, a) => {
if (getApproach() == Approach.IFRAME) {
return null;
} else {
const sw = new SharedWorker(MAIN_HOST+'/src/server/SimpleServer.ts', {
type: "module",
name: "Demo FDC3 Server"
})

sw.port.start()
return sw.port;
}
}

// set up desktop agent handler here using FDC3 Web Loader (or whatever we call it)
supply(appChecker, detailsResolver, portResolver)

// hook up the buttons
document.getElementById("app1")?.addEventListener("click", () => launch(MAIN_HOST+"/static/app1/index.html", "1"));
Expand Down
15 changes: 12 additions & 3 deletions packages/fdc3-common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ export { exchange, exchangePostMessage, exchangeForMessagePort }

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

export type Supplier = (checker: AppChecker, detailsResolver: DesktopAgentDetailResolver, staticDetails: DesktopAgentDetails) => void
export type Supplier = (
checker: AppChecker,
detailsResolver: DesktopAgentDetailResolver,
portResolver?: DesktopAgentPortResolver) => void;

export type Loader = (options: Options) => Promise<DesktopAgent>

Expand All @@ -26,9 +29,15 @@ export type Loader = (options: Options) => Promise<DesktopAgent>
*/
export type DesktopAgentDetails = { [key: string] : string | number | boolean }

/**
* Use these to return details specific to the window/app needing a connection
*/
export type DesktopAgentDetailResolver = (o: Window, a: AppIdentifier) => DesktopAgentDetails

export type Method = (r: APIResponseMessage, options: Options) => Promise<DesktopAgent>
/**
* Return a MessagePort specific to the window/app in question
*/
export type DesktopAgentPortResolver = (o: Window, a: AppIdentifier) => MessagePort | null

/**
* This is the object that the desktop agent must get back to the App.
Expand All @@ -38,7 +47,7 @@ export type Method = (r: APIResponseMessage, options: Options) => Promise<Deskto
export type APIResponseMessage = {
type: string,
method: "message-port",
uri?: string,
uri?: string, /* Supplied when an embedded iframe should be loaded */
appIdentifier: AppIdentifier,
fdc3Version: string,
supportedFDC3Versions: string[],
Expand Down
27 changes: 21 additions & 6 deletions packages/server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { AppIdentifier } from '@finos/fdc3';
import { APIResponseMessage, AppChecker, DesktopAgentDetailResolver, Supplier, FDC3_API_RESPONSE_MESSAGE_TYPE, FDC3_API_REQUEST_MESSAGE_TYPE, DesktopAgentDetails } from 'fdc3-common';
import { APIResponseMessage, AppChecker, DesktopAgentDetailResolver, Supplier, FDC3_API_RESPONSE_MESSAGE_TYPE, FDC3_API_REQUEST_MESSAGE_TYPE, DesktopAgentPortResolver } from 'fdc3-common';

/**
* This configures the postMessage listener to respond to requests for desktop agent APIs.
* Called by the server-side desktop agent.
*/
export const supply: Supplier = (checker: AppChecker, detailsResolver: DesktopAgentDetailResolver, staticDetails: DesktopAgentDetails) => {
export const supply: Supplier = (
checker: AppChecker,
detailsResolver: DesktopAgentDetailResolver,
portResolver: DesktopAgentPortResolver = () => null) => {

function createResponseMessage(source: Window, appId: AppIdentifier): APIResponseMessage {
return {
Expand All @@ -16,7 +19,6 @@ export const supply: Supplier = (checker: AppChecker, detailsResolver: DesktopAg
supportedFDC3Versions: [ '2.0'],
fdc3Version: "2.0",
type: FDC3_API_RESPONSE_MESSAGE_TYPE,
...staticDetails,
...detailsResolver(source, appId),

method: "message-port",
Expand All @@ -29,19 +31,32 @@ export const supply: Supplier = (checker: AppChecker, detailsResolver: DesktopAg
}
}

function createTransferrableArray(source: Window, appId: AppIdentifier): Transferable[] {
const port = portResolver(source, appId);
if (port) {
return [ port ]
} else {
return []
}
}

window.addEventListener(
"message",
(event) => {
console.log("Received: " + JSON.stringify(event));
const data = event.data;
console.log("Received: " + JSON.stringify(event.data));
if (data.type == FDC3_API_REQUEST_MESSAGE_TYPE) {
const origin = event.origin;
const source = event.source as Window
const appDetails = checker(source)
if (appDetails) {
console.log(`API Request Origin: ${origin}`);

source.postMessage(createResponseMessage(source,appDetails), origin);
const message = createResponseMessage(source,appDetails)
const transferrables = createTransferrableArray(source, appDetails)
source.postMessage(message, {
targetOrigin: origin,
transfer: transferrables
})
}
}
});
Expand Down

0 comments on commit fc4da1c

Please sign in to comment.