Skip to content

Commit

Permalink
Merge pull request #9 from finos-labs/nested-iframe
Browse files Browse the repository at this point in the history
Nested iframe
  • Loading branch information
robmoffat authored Sep 21, 2023
2 parents a732960 + 44fd37e commit 0ea2eb1
Show file tree
Hide file tree
Showing 23 changed files with 6,748 additions and 183 deletions.
23 changes: 15 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,20 @@ This is a minimal proof-of-concept for FDC3 For the Web.
- 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` (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.
- **For the App**: `load` (in `load.ts`): Called (with options) by an FDC3 Aoo to retrieve the API. This retrieves `details` from the desktop agent and initialises a `DesktopAgent` API implementation, returning it in a promise. There are various options available:
- _strategies_: This allows plugable strategies for getting the DA. Two exist:
- `electron-event` which waits for `window.fdc3` to be set and
- `post-message` which fires a post message up to the opening window/iframe (or whatever is set in the `frame` option) asking for details of how to construct a `DesktopAgent` API implementation.
- _methods_: The app can also suggest methods ways in which it might want to construct the `DesktopAgent` API. The desktop agent is obliged to try and return a response back to the app detailing one of the approaches that the app asks for. Two exist so far:
- `js-inject` : The desktop agent responds with a URL of some javascript, loaded by the app to create a `DesktopAgent` API. (See App2 which does this)
- `post-message-protocol` : The app expects to communicate with the desktop agent via standard [Desktop-Agent-Bridging](https://fdc3.finos.org/docs/next/agent-bridging/spec) messages, sent and received via post-message.
- `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.
- **For the desktop agent**: `supply` (in `agent/supply.ts`): Called by the desktop agent on startup, allows it to supply FDC3 APIs to apps when they ask for one via the `post-message` strategy. This takes the following parameters:
- A `checker`, which checks the origin window for the API request. It should be a window that the Desktop Agent is aware of.
- A map of `detailsResolver`s, which returns a map of properties to send to the API requestor (the app) that should be used to instantiate the API. This map is keyed by the names of the _methods_ above.
- 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 @@ -39,14 +47,13 @@ This is a minimal proof-of-concept for FDC3 For the Web.
- Since this uses Vite, you can modify the code and see it running the browser immediately.
- This currently only supports FDC3 2.0
- This supports cross-origin, (at least on my machine!) you can configure hostnames in `dummy-desktop-agent.ts` to try this out.
- Also supports the difference between frames and tabs.
## TO DO
- 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
3 changes: 2 additions & 1 deletion src/demo/app2.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { load } from '../lib/load'
import { JS_INJECT } from '../lib/types';

/**
* This demonstrates using the API via a promise
*/
load().then(fdc3 => {
load({"methods" : [JS_INJECT]}).then(fdc3 => {
console.log("in promise")
const log = document.getElementById("log");
const msg = document.createElement("p");
Expand Down
66 changes: 53 additions & 13 deletions src/demo/dummy-desktop-agent.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { AppIdentifier } from "@finos/fdc3";
import { supply } from "../lib/supply";
import { supply } from "../lib/agent/supply";
import { AppChecker, DesktopAgentDetailResolver } from "../lib/types";
import { RequestMessageType } from "../lib/BridgingTypes";

enum Approach { Tab, Frame, Nested }

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

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

const instances : AppIdentifierAndWindow[] = []

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

function openTab(url: string) : Window {
function openFrame(url: string) : Window {
var ifrm = document.createElement("iframe");
ifrm.setAttribute("src", url);
ifrm.style.width = "640px";
Expand All @@ -26,12 +30,34 @@ window.addEventListener("load", () => {
return ifrm.contentWindow!!;
}

function openFrame(url: string) : Window {
function openTab(url: string) : Window {
return window.open(url, "_blank")!!;
}

function openNested(url: string) : Window {
var ifrm = document.createElement("iframe");
ifrm.setAttribute("src", "nested.html?url="+url);
ifrm.style.width = "640px";
ifrm.style.height = "480px";
document.body.appendChild(ifrm);
return ifrm.contentWindow!!;
}

function open(url: string): Window {
const approach = getApproach();
switch (approach) {
case Approach.Tab:
return openTab(url);
case Approach.Nested:
return openNested(url);
case Approach.Frame:
return openFrame(url);
}
throw new Error("unsupported")
}

function launch(url: string, appId: string) {
const w = useFrames() ? openTab(url): openFrame(url);
const w = open(url);
const instance = currentInstance++;
w.name = "App"+instance;
instances.push({
Expand All @@ -44,34 +70,48 @@ 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) != undefined;
const daDetailResolver : DesktopAgentDetailResolver = (o) => {
const jsInject : DesktopAgentDetailResolver = (o) => {
const appIdentifier = instances.find(i => i.window ==o)!!
return {
url: "/src/demo/implementation.ts",
apiId : currentApiInstance++,
apikey: "Abc",
appId: appIdentifier.appId,
instanceId: appIdentifier.instanceId!!
}
}

const postMessageProtocol : DesktopAgentDetailResolver = (o) => {
const appIdentifier = instances.find(i => i.window ==o)!!
return {
apiId : currentApiInstance++,
appId: appIdentifier.appId,
instanceId: appIdentifier.instanceId!!,
provider: "Dummy",
origin: window.origin
}
}

// set up desktop agent handler here using FDC3 Web Loader (or whatever we call it)
supply("/src/demo/implementation.js", appChecker, daDetailResolver);
supply(appChecker, {
"js-inject": jsInject,
"post-message-protocol": postMessageProtocol
});

// hook up the buttons
document.getElementById("app1")?.addEventListener("click", () => launch("/static/app1/index.html", "1"));
document.getElementById("app2")?.addEventListener("click", () => launch("http://robs-pro:8080/static/app2/index.html", "2"));
document.getElementById("app3")?.addEventListener("click", () => launch("http://localhost:8080/static/app3/index.html", "3"));


// implementation of broadcast, desktop-agent side
// implementation of broadcast, desktop-agent side (post-message-protocol version)
window.addEventListener(
"message",
(event) => {
const data = event.data;
if (data.type == "Broadcast") {
if (data.type == RequestMessageType.BroadcastRequest) {
const origin = event.origin;
const source = event.source as Window
console.log(`Broadcast Origin: ${origin} Source: ${source} From ${JSON.stringify(data.from)}`);
console.log(`${RequestMessageType.BroadcastRequest} Origin: ${origin} Source: ${source} From ${JSON.stringify(data.from)}`);
if (appChecker(source)) {
instances
.forEach(i => {
Expand Down
55 changes: 0 additions & 55 deletions src/demo/implementation.js

This file was deleted.

45 changes: 45 additions & 0 deletions src/demo/implementation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { DesktopAgent } from "@finos/fdc3";
import { AbstractDesktopAgent } from "../lib/agent/AbstractDesktopAgent";
import { FDC3Initialiser } from "../lib/methods/js-inject";
import { DesktopAgentDetails, Options } from "../lib/types";
import { BroadcastAgentRequest, RequestMessageType } from "../lib/BridgingTypes";

/**
* This dummy desktop agent just implements broadcast and addContextListener for the
* purposes of the demo. Communication is also via post-message.
*/
class DummyDesktopAgent extends AbstractDesktopAgent {

constructor(details: DesktopAgentDetails, options: Options) {
super(details, options);

// set up the post message listener for events coming from the server
window.addEventListener("message", (event) => {
const data = event.data;

if (data.type == RequestMessageType.BroadcastRequest) {
const typedData = data as BroadcastAgentRequest;
const meta = typedData.meta;
const payload = typedData.payload;
const context = payload.context;
this.listeners.forEach(l => l.handle(context, meta))
}
});
}

postInternal(m: object) {
// this DA is a bit sloppy about frame origin, whereas the other one isn't.
this.options.frame!!.postMessage(m, "*");
}

getIcon() {
return "https://cosaic.io/wp-content/uploads/2022/09/fdc3-check.png";
}

}

const init : FDC3Initialiser = (details: DesktopAgentDetails, options: Options) => {
return new DummyDesktopAgent(details, options) as any as DesktopAgent;
}

export default init;
25 changes: 25 additions & 0 deletions src/demo/nested.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* This instantiates a page where a nested iframe strategy is used.
*/

const parentWindow = window.parent; // opener of iframe.
const child = (document.getElementById("iframe") as HTMLIFrameElement)
const childWindow = child?.contentWindow!!;

// implementation of broadcast, desktop-agent side (post-message-protocol version)
window.addEventListener(
"message",
(event) => {
const data = event.data;
if (event.source == parentWindow) {
// from the parent
childWindow.postMessage(event.data, "*");
} else if (event.source == childWindow) {
parentWindow.postMessage(event.data, "*");
}
});

// next, set up the child
const url = window.location.href;
const childLocation = url.substring(url.lastIndexOf("url=")+4);
child?.setAttribute("src", childLocation);
Loading

0 comments on commit 0ea2eb1

Please sign in to comment.