Skip to content

Commit

Permalink
Added 2.0 format loader
Browse files Browse the repository at this point in the history
  • Loading branch information
robmoffat committed Oct 27, 2022
1 parent 2e1042f commit 541d041
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 31 deletions.
51 changes: 51 additions & 0 deletions packages/main/src/directory/directory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { DirectoryApp } from '../types/FDC3Data';

/**
* A loader takes a URL and attempts to load it into an array of DirectoryApp.
* If it can't do this for any reason, it returns the empty array.
*/
type Loader = (url: string) => Promise<DirectoryApp[]>;

export class Directory {
loaders: Loader[];
urls: string[];
apps: DirectoryApp[] = [];

constructor(urls: string[], loaders: Loader[]) {
this.loaders = loaders;
this.urls = urls;
this.reload();
}

/**
* Asynchronously reloads the entire app list
*/
reload() {
const newApps: DirectoryApp[] = [];
this.urls.forEach((url) => this.load(url));
this.apps = newApps;

Promise.all(this.urls.map((u) => this.load(u)))
.then((data) => data.flatMap((d) => d))
.then((result) => {
this.apps = result;
});
}

/**
* Loads from a given url, using available loaders. Places loaded apps into 'into'
*/
load(url: string): Promise<DirectoryApp[]> {
return Promise.all(this.loaders.map((l) => l(url))).then((data) =>
data.flatMap((d) => d),
);
}

/**
* Generic retrieve that returns a filtered list of apps based on a
* filter function.
*/
retrieve(filter: (d: DirectoryApp) => boolean): DirectoryApp[] {
return this.apps.filter(filter);
}
}
43 changes: 43 additions & 0 deletions packages/main/src/directory/fdc3-2-loaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { DirectoryApp } from '../types/FDC3Data';
import { readFile } from 'fs/promises';

const convertToDirectoryList = (data: unknown) => data as DirectoryApp[];
const convertSingleApp = (data: unknown) => [data] as DirectoryApp[];

/**
* Load data in FDC3 2.0 Directory format. Here, we make the assumption
* that the data is formatted correctly.
*/
export function fdc3AppDirectoryLoader(u: string): Promise<DirectoryApp[]> {
let converter;
console.log('IN: ' + process.cwd());

if (u.endsWith('/v2/apps') || u.endsWith('/v2/apps/')) {
// whole directory
converter = convertToDirectoryList;
} else if (u.endsWith('.json') || u.includes('/v2/apps')) {
// single app
converter = convertSingleApp;
} else {
// not handled by this loader
return new Promise((resolve) => {
resolve([]);
});
}

if (u.startsWith('http')) {
return loadRemotely().then(converter);
} else {
return loadLocally().then(converter);
}

function loadRemotely() {
return fetch(u).then((response) => response.json());
}

function loadLocally() {
return readFile(u)
.then((buf) => buf.toString('utf8'))
.then((data) => JSON.parse(data));
}
}
56 changes: 56 additions & 0 deletions packages/main/src/directory/fdc3-legacy-loaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { DirectoryApp } from '../types/FDC3Data';
//import { DirectoryApp as OldDirectoryApp, DirectoryIcon as OldIcon } from "../handlers/fdc3/1.2/types/FDC3Data";
import { readFile } from 'fs/promises';

// function convertAppEntryTo2_0(record: object) : DirectoryApp {
// const o = record as OldDirectoryApp

// return {
// appId: o.appId,
// name: o.name,
// title: o.title,
// description: o.description,
// icons: null,

// } as DirectoryApp
// }

const convertToDirectoryList = (data: unknown) => data as DirectoryApp[];
const convertSingleApp = (data: unknown) => [data] as DirectoryApp[];

/**
* Load data in FDC3 1.x Directory format.
*/
export function fdc3AppDirectoryLoader(u: string): Promise<DirectoryApp[]> {
let converter;
console.log('IN: ' + process.cwd());

if (u.endsWith('/v1/apps') || u.endsWith('/v1/apps/')) {
// whole directory
converter = convertToDirectoryList;
} else if (u.endsWith('.json') || u.includes('/v1/apps')) {
// single app
converter = convertSingleApp;
} else {
// not handled by this loader
return new Promise((resolve) => {
resolve([]);
});
}

if (u.startsWith('http')) {
return loadRemotely().then(converter);
} else {
return loadLocally().then(converter);
}

function loadRemotely() {
return fetch(u).then((response) => response.json());
}

function loadLocally() {
return readFile(u)
.then((buf) => buf.toString('utf8'))
.then((data) => JSON.parse(data));
}
}
62 changes: 33 additions & 29 deletions packages/main/src/handlers/runtime/directory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,43 @@ import { WebContents } from 'electron';
import utils from '../../utils';
import fetch from 'electron-fetch';
import { RUNTIME_TOPICS } from './topics';
import { Runtime } from '/@/runtime';

export const fetchFromDirectory = async (message: RuntimeMessage) => {
const runtime = getRuntime();
const directoryUrl = await utils.getDirectoryUrl();
export function initFetchFromDirectory(
runtime: Runtime = getRuntime(),
): (message: RuntimeMessage) => Promise<void> {
return async (message: RuntimeMessage) => {
const directoryUrl = await utils.getDirectoryUrl();

const response = await fetch(`${directoryUrl}${message.data.query}`);
const result = await response.json();
const response = await fetch(`${directoryUrl}${message.data.query}`);
const result = await response.json();

//request can come frome 2 types of (priviledged) sources: the workspace UI and views
//if the sourceType is View. We need to check that the view is a 'system' view and can access the directory
//through this API. Today, this is only the 'home' view.
let wContents: WebContents | undefined = undefined;
//request can come frome 2 types of (priviledged) sources: the workspace UI and views
//if the sourceType is View. We need to check that the view is a 'system' view and can access the directory
//through this API. Today, this is only the 'home' view.
let wContents: WebContents | undefined = undefined;

if (message.data.sourceType && message.data.sourceType === 'view') {
//ensure this is a view that has permissions for this api
const sourceView = runtime.getView(message.source);
if (message.data.sourceType && message.data.sourceType === 'view') {
//ensure this is a view that has permissions for this api
const sourceView = runtime.getView(message.source);

if (sourceView && sourceView.isSystemView() && sourceView.content) {
wContents = sourceView.content.webContents;
if (sourceView && sourceView.isSystemView() && sourceView.content) {
wContents = sourceView.content.webContents;
}
} else {
const sourceWS = runtime.getWorkspace(message.source);
if (sourceWS && sourceWS.window) {
wContents = sourceWS.window.webContents;
}
}
} else {
const sourceWS = runtime.getWorkspace(message.source);
if (sourceWS && sourceWS.window) {
wContents = sourceWS.window.webContents;
}
}

if (wContents) {
wContents.send(
`${RUNTIME_TOPICS.FETCH_FROM_DIRECTORY}-${message.data.query}`,
{
data: result,
},
);
}
};
if (wContents) {
wContents.send(
`${RUNTIME_TOPICS.FETCH_FROM_DIRECTORY}-${message.data.query}`,
{
data: result,
},
);
}
};
}
7 changes: 5 additions & 2 deletions packages/main/src/handlers/runtime/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
dropTab,
} from './tabs';
import { openToolsMenu } from './toolbar';
import { fetchFromDirectory } from './directory';
import { initFetchFromDirectory } from './directory';
import { pickChannel, joinChannel } from './channelPicker';
import { loadSearchResults } from './search';
import { resolveIntent } from '../fdc3/resolveIntent';
Expand All @@ -22,7 +22,10 @@ export const register = (runtime: Runtime) => {
runtime.addHandler(RUNTIME_TOPICS.NEW_TAB, newTab);
runtime.addHandler(RUNTIME_TOPICS.TEAR_OUT_TAB, tearOutTab);
runtime.addHandler(RUNTIME_TOPICS.CLOSE_TAB, closeTab);
runtime.addHandler(RUNTIME_TOPICS.FETCH_FROM_DIRECTORY, fetchFromDirectory);
runtime.addHandler(
RUNTIME_TOPICS.FETCH_FROM_DIRECTORY,
initFetchFromDirectory(runtime),
);
runtime.addHandler(RUNTIME_TOPICS.OPEN_TOOLS_MENU, openToolsMenu);
runtime.addHandler(RUNTIME_TOPICS.OPEN_CHANNEL_PICKER, pickChannel);
runtime.addHandler(RUNTIME_TOPICS.JOIN_WORKSPACE_TO_CHANNEL, joinChannel);
Expand Down
24 changes: 24 additions & 0 deletions packages/main/tests/directory.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { expect, test } from 'vitest';
import { fdc3AppDirectoryLoader } from '../src/directory/fdc3_3-loaders';

test('Test Remote Directory Load in FDC3 2.0 Format', async () => {
const results = await fdc3AppDirectoryLoader(
'https://directory.fdc3.finos.org/v2/apps',
);
expect(results.length > 3, 'loaded several directory entries');
console.log(JSON.stringify(results));
});

test('Test Single App Load in FDC3 2.0 Format', async () => {
const results = await fdc3AppDirectoryLoader(
'https://directory.fdc3.finos.org/v2/apps/fdc3-workbench/',
);
expect(results.length == 1, 'loaded single entry');
expect(results[0].appId == 'fdc3-workbench');
});

test('Test Local App Load in FDC3 2.0 Format', async () => {
const results = await fdc3AppDirectoryLoader('tests/single-appd-record.json');
expect(results.length == 1, 'loaded single entry');
expect(results[0].appId == 'fdc3-workbench');
});
87 changes: 87 additions & 0 deletions packages/main/tests/single-appd-record.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
{
"appId": "fdc3-workbench",
"name": "fdc3-workbench",
"title": "FDC3 Workbench",
"description": "Development and test tool for FDC3 desktop agents and apps",
"categories": [
"developer tools",
"training"
],
"version": "1.0.0",
"tooltip": "FDC3 Workbench",
"lang": "en-US",
"icons": [
{
"src": "https://fdc3.finos.org/toolbox/fdc3-workbench/fdc3-icon-256.png"
}
],
"screenshots": [
{
"src": "https://fdc3.finos.org/docs/assets/fdc3-logo.png",
"label": "FDC3 logo"
}
],
"contactEmail": "fdc3@finos.org,",
"supportEmail": "fdc3-maintainers@finos.org,",
"publisher": "FDC3",
"type": "web",
"details": {
"url": "https://fdc3.finos.org/toolbox/fdc3-workbench/"
},
"hostManifests": {
"Glue42": {
"type": "window",
"icon": "https://fdc3.finos.org/docs/assets/fdc3-logo.png",
"details": {
"height": 640,
"width": 560,
"left": 120,
"top": 120,
"mode": "tab",
"allowChannels": true,
"loader": {
"enabled": true,
"hideOnLoad": true
}
},
"customProperties": {
"folder": "FDC3 Toolbox"
}
},
"Finsemble": {
"window": {
"left": 120,
"top": 120,
"width": 800,
"height": 750,
"options": {
"minWidth": 75
}
},
"foreign": {
"components": {
"App Launcher": {
"launchableByUser": true
},
"Toolbar": {
"iconURL": "http://fdc3.finos.org/toolbox/fdc3-workbench/fdc3-icon-256.png"
},
"Window Manager": {
"FSBLHeader": true,
"persistWindowState": true
}
}
},
"interop": {
"autoConnect": true
}
},
"Web App Manifest": "https://example.com/fdc3-workbench.json"
},
"localizedVersions": {
"fr-FR": {
"title": "FDC3 Table de travail",
"description": "Outil de développement et de test pour les desktop agents et applications FDC3"
}
}
}

0 comments on commit 541d041

Please sign in to comment.