Skip to main content

Web: Integrating freestanding web apps with the JavaScript adapter

Most times, Finsemble launches your web apps as part of your smart desktop. But there may be some apps that cannot be launched by Finsemble. We call apps launched outside of Finsemble "freestanding" apps.

Examples of freestanding apps include:

  • Apps running in external browsers such as Chrome
  • Apps running within WebViews in .NET or Java applications
  • JavaScript add-ins or extensions running within other desktop applications (such as MS Office or Symphony)

Freestanding apps can participate in FDC3 interop using the appropriate adapter, such as Finsemble's JavaScript adapter or a native adapter.

Loading the JavaScript adapter

You can find the JavaScript adapter and a sample app in the @finsemble/finsemble-core npm module:

  • node_modules/@finsemble/finsemble-core/dist/finsemble-javascript-adapter.js
  • node_modules/@finsemble/finsemble-core/dist/javascript-adapter-example-app.html

Run npm install "@finsemble/finsemble-core" to download the package (see steps below for hosting options if you are not using npm modules).

The JavaScript adapter must be loaded into your freestanding app with one of these methods:

  1. In a <script> tag (hosted where you are hosting your smart desktop):
<script charset="UTF-8" src="http://localhost:3375/build/finsemble/finsemble-javascript-adapter.js"></script>"

This is the simplest approach when your freestanding apps will be interacting with a Finsemble desktop that you maintain. However, be sure to update the script's src before deploying your app to production!

  1. In a <script> tag hosted using "unpkg":
<script charset="UTF-8" src="https://unpkg.com/@finsemble/finsemble-core@6.1.2/dist/finsemble-javascript-adapter.js" />

This is a good solution when your freestanding app may interact with Finsemble desktops run by other firms.

  1. In a <script> tag hosted on your own website:
<script charset="UTF-8" src="https://yoursite.com/finsemble-javascript-adapter.js" />

You must serve <script> as UTF-8 (by setting charset as in these examples or by configuring your web server to serve .js files encoded with UTF-8). Not setting UTF-8 properly will result in the error SyntaxError: Invalid or unexpected token. (Note that this error can also occur when your server is missing the file. Check for "404 Not Found" errors in the debugging console for your app.)

  1. Preload:

Some containers, such as webviews, offer the ability to preload (inject) JavaScript.

This example demonstrates how to preload Finsemble's JavaScript adapter using DotNetBrowser (a popular webview for .NET):

// Load the app in a Browser object
IEngine engine = EngineFactory.Create();
IBrowser browser = engine.CreateBrowser();
browser.Navigation.LoadUrl("https://yourserver.com/yourapp.html");

// Inject the JavaScript adapter
IFrame mainFrame = browser.MainFrame;
mainFrame.ExecuteJavaScript<string>("<script charset='utf-8' src='http://localhost:3375/build/finsemble/finsemble-javascript-adapter.js'></script>");
mainFrame.ExecuteJavaScript<string>("FSBLJSAdapter.startApp()");

// Display in a Browserview
BrowserView view = new BrowserView();
view.InitializeFrom(browser);

The JavaScript adapter is a UMD module. It is not available as an import because it exposes global variables rather than module exports. See "Building With TypeScript" for how to get ambient types for these global variables.

Configuring the JavaScript adapter

When you load the JavaScript adapter as a script or preload, two global objects are added to your app: FSBLJSAdapter and fdc3.

Your code must explicitly start the JavaScript adapter by calling FSBLJSAdapter.startApp(), either in your app or by preloading/injecting the call. This function takes optional parameters:

  • appId (string) - Used to look up your app in Finsemble's AppD configuration (that is, public/configs/application/apps.json). This ID appears in logs and in development tools. appId is required and must match an AppD configuration containing the app's pubic key. (See Static Authentication later in this topic).

  • digitalSigningKey (string) - Provides a digital signing key that enables authenticated communication with Finsemble's interop service. If you specify this parameter, don't specify jwt.

  • windowName (string) - You can give your app a specific window name. This name will appear in log messages. If you don't specify this parameter, Finsemble will generate it for you.

  • routerAddress (string) - You can provide a url for Finsemble's IAC. The default is ws://127.0.0.1:3376. You can configure this address by setting the finsemble.router.transportSettings.FinsembleTransport.serverAddress property in your manifest file, for example public/configs/application/manifest-local.json.

  • jwt (string) - A JSON web token (see Static Authentication Using a JSON Web Token later in this topic). If you specify this parameter, don't specify digitalSigningKey.

Example: Starting the JavaScript adapter with custom values

FSBLJSAdapter.startApp({
appId: "TradeBlotter",
digitalSigningKey: privateKey,
windowName: "TradeBlotterWebview",
routerAddress: "ws://127.0.0.1:4765"
});

FSBLJSAdapter.startApp() is promisified. You can await its return, which resolves when the JavaScript adapter is initialized. But, awaiting is completely optional: FDC3 calls are safe to use immediately.

FSBLJSAdapter.startApp() can also be preloaded/injected. We recommend this approach when you're loading 3rd party FDC3 apps into webviews.

If Finsemble is not yet running when your freestanding app is started then the JavaScript adapter will continue retrying until it connects. Your app's FDC3 calls will queue until that connection is established.

Using the JavaScript adapter

Your code can make FDC3 calls as soon as the JavaScript adapter is loaded and startApp() has been called.

Example: YourApp.html

FSBLJSAdapter.startApp();

const channels = await fdc3.getSystemChannels();
fdc3.joinChannel(channels[0].id);
fdc3.broadcast({type:"symbol", id: {"symbol":"AAPL"}});
note

Finsemble's full desktop API (FSBL.Clients) is not currently supported in the JavaScript adapter. Only FDC3 API calls are currently supported.

Building with Typescript

The JavaScript adapter adds the global variables FSBL, FSBLJSAdapter and fdc3 to your freestanding app. Since these variables aren't imported, Typescript needs to be told where to find the type declarations that describe them. Those type declarations are available in the "@finsemble/finsemble-core" module. You can add this module using yarn add or npm install.

Import "@finsemble/finsemble-core" in your source file to gain access to these type definitions:

import Finsemble from "@finsemble/finsemble-core";

FSBLJSAdapter.startApp();
fdc3.addContextListener("account", (context) => {
// do something with the context you receive
});

Advanced: Static authentication

Finsemble only discovers the identity of freestanding apps if those apps provide an appId parameter to FSBLJSAdapter.startApp(), but even if an identity is provided, Finsemble considers that identity as unverified unless a digitalSigningKey parameter is also passed to FSBLJSAdapter.startApp(). This process of providing a digital signed authentication token is called static authentication.

note

Static authentication is required only for freestanding apps that need to interact with other apps that use the authorize selectConnect rule to restrict data access.

To configure a statically authenticated app:

  1. Generate a key pair.

Run Finsemble (desktop) and then open a developer console. Enter into your console:

> FSBL.createKeys();

This command will generate a public key and a private key.

Generating key pairs in developer console

  1. Set the public key in apps.json.

Enter into your console:

> copy(FSBLPublicKey)

This will copy the public key to your clipboard. Paste it into your app's entry in apps.json:

{
...
"apps": [
...
{
"appId": "TradeBlotter",
...
"hostManifests" : {
"Finsemble": {
...
"signatureKey": {
"alg": "RS256",
"e": "AQAB",
"ext": true,
"key_ops": [
"verify"
],
"kty": "RSA",
"n": "kv8uq_7Or89Xdu2XZTImNBUiZ4SBN5Kx_dOUlIH0LyWaz4SG4epDZhQkJ0xs8kcRS5akBHF0dz0ZMlSOlkr1dXwv1PEgt2rbcqPSNXnSJbUaWGCcmuqfOeX2uh2SpVFeYpCLzjBwq6J-iVRjdqRXeRXBv_D95wuY8ebpnmg3TQk-NRQT3nQQ_IPeGrRYbag08ZGcSERH55MNhvOotF73HRk9xqgexmTkc706-x7qcmvONNbKNrAYr6x_Me9Txo5X93UyNl5472QQE3fhgnJt8wxC2yvkKydnZ3bfu3sob4kyXyGI4ZcuuMfUA2bOAPvManwWTL9cHRYe-wnwirQBWw"
}
}
},
...
},
  1. Pass the private key at run time:

Enter into your console:

> copy(FSBLPrivateKey)

This will copy the private key to your clipboard. This private key must be passed to your app at runtime in the digitalSigningKey parameter. It is up to you to decide where to store your private key and how to get it into your app.

FSBLJSAdapter.startApp({
appId: "TradeBlotter",
digitalSigningKey: {
"alg": "RS256",
"d": "AxbEAyQ_G_hWRQRYHphvr2opnMBEO4fkdUouwJzR0J3Qg3-BmsS39ttXKSY_fICGz1j9MwSz_FTMhJS8oabgl-qXUg5hGsmZjplSWleoKq0EE-BahMeHt16VkTKm5BIc0s2-dQOVPTNpx6FTgmjmAlWKipSErrK9MN7NK6F9VqviEAN1zwL4A8J6c6R98p3KI2F6-vjSU0RVaL8zqGi8aoqqs6jWWJP7iJcY8IeILuDwwybfFCGNkV6eQbnLukimU60nXhDrc4qggmj2MWTHOQqVJt-OmuxOc7tVZRJJzgun_x9obSYe_XAk4PKz_M8-QNYYRRY3_Dec5M0Z-LfuuQ",
"dp": "BarnqWf2S4zqNYz6h3bbIPLcrFIDDiRzvlF7OYkYkCXqTFhG4Zu-iU6rYOwVSmNxk2UG9SBvGpxNSGPGTrEL4LAXAPnbRVoe3RGJaLJMfu4gUxItzfJ7SncJD2E-D--moG1Djg0tSOL4qqsEgDFWowrP3bXXOC15NxPomtUnEb8",
"dq": "bp0ylMki-WugVy6FLjgqum1n_X_WURBPwoTpWczw_op4jWRqpmm_ltvwio5gcDi1_X0jBxbKV13eAi34R6nvqJbnToeeIDUBvpJI5l4sbGNm2H8cU17RMeDI5FNse9pNoSTaboiSzNgMLAyN0wnMr36zFFIvPHp6Z16ZZIJx1sE",
"e": "AQAB",
"ext": true,
"key_ops": [
"sign"
],
"kty": "RSA",
"n": "kv8uq_7Or89Xdu2XZTImNBUiZ4SBN5Kx_dOUlIH0LyWaz4SG4epDZhQkJ0xs8kcRS5akBHF0dz0ZMlSOlkr1dXwv1PEgt2rbcqPSNXnSJbUaWGCcmuqfOeX2uh2SpVFeYpCLzjBwq6J-iVRjdqRXeRXBv_D95wuY8ebpnmg3TQk-NRQT3nQQ_IPeGrRYbag08ZGcSERH55MNhvOotF73HRk9xqgexmTkc706-x7qcmvONNbKNrAYr6x_Me9Txo5X93UyNl5472QQE3fhgnJt8wxC2yvkKydnZ3bfu3sob4kyXyGI4ZcuuMfUA2bOAPvManwWTL9cHRYe-wnwirQBWw",
"p": "x2A79GfXzvj1e7KCml4SYu-YjaEZcUw_ly9YOKh7SvSn9ELzesuqZiA_tBL-AOzlC3z05AWHOGUfA05InjXxrCx6t_n83kAQYMspZK7z8GuhEytnw5PjjFfhAHQsrwZCyxS_Lzuu72iCfhn-hHGd8Ii2jlHAPjSu3WOUpSMG21M",
"q": "vL6wMJnwjbic_9UazKINdFmY8o_IOyANWXPXd24urh7CyQEsYgka0zucOV4UkrJ1Iw21Dp9WGmVZGgw6KKV0UAbuYvV5JZzwuGpEhf4ZWFqQDJ-tyy26QliWMH-op0OEDH5zaXAlQUkO53oDhDySfBp3CQvqIQSUCOlX16kgiNk",
"qi": "TKQzuZHZOiPYyl1hKNuK7v01z9qHGea18a7XiL3hDSW61IH3mpdjAnWSaUfaqVIgdSCIcwrUAbR1WkB6XjNytH_9pcRz03qFFNSREw7A6AhTk-0aLdsyjekBcG2AqxInwJ6256j1RzCBIOvjlB7DZh8xJJTR1qIhAzJEikK4Yh4"
}
});

Your app will now be authenticated when it registers with Finsemble and will be able to interact with apps that have set the authorize selectConnect rule.

The private key is never transmitted. Finsemble's JavaScript adapter uses the browser's crypto API to locally generate a signed authentication token. Only that token is transmitted.

Static authentication using a JSON Web Token

For even more security in static authentication, Finsemble supports using a JSON Web Token (JWT) that you generate remotely ahead of time. It's the app's responsibility to fetch its own JWT from a remote server and input it as a parameter in the app's startup sequence like this:

FSBLJSAdapter.startApp({
appId,
jwt
});

You must generate the JWT input to FSBLJSAdapter.startApp using the private key that matches the app's public key in Finsemble's AppD configuration. The JWT payload data must include only the appId of the app: { appId: "testApp" }. For Finsemble to verify the JWT, it must be created using a RSA signature with SHA-256 (RS256).

note

You don't need to set an expiration time when creating a JWT. Finsemble uses its own 30-second expiration timer for all its authentication tokens, which are verified when an app registers. For a JWT, Finsemble uses the iot field (which is automatically set to the current time when the JTW is created) to calculate the expiration time.

Here's an example function that shows how to generate a JWT for the app named testApp on the remote Node.js server using the jsonwebtoken module.

async function getJSONWebTokenForApp() {
const jwt = require('jsonwebtoken');
const cryptoAPI = require("crypto").webcrypto.subtle;

const signatureData = { appId: "testApp" };

// For Finsemble public/private key generation see https://documentation.finsemble.com/docs/add-apps/freestanding/StaticAuthentication.html
const digitalSigningKey = {
alg: "RS256",
d: "4VMHGzvRpw-Rzbct3TzG6GTkQzf8yyHLfB8SiK4HV8RWKQTGH3FzKyNz7n6eiUIytlpbx8SbPjOe7BYh6Wo9wOf4LNbwxFaK26lB7zJyRK8sipn76k0HOJmXPVNbY4VNT1S00JIvKiKzDPbhNCJZq6Syr5kzpGNQmze4JlU0IPleIXdtrdO1cV9lDKshViUpaJbfdPBG1Jsc0UWNNX_jFsYb3ij-QgquPQ8li32Lmoc0aL9tCbJdnJxGnGJpuy3on7lncFMZdZR_YyPxbNysPH1cuqpKI7jp6iP1hzVvfGe9PYKxcONohqHpl7tp-CRcYT5kp5rUd0__W6B5wYvB",
dp: "QYuflX5ucEoh0aqZje60sKIaTaKW9qRbKUdpXE0RaHwaZ3DNtE1r2dBV3weKsPy-NQFIKtOMHn6cT7LVGSrzTPqruPBhS4LJ3FKSmH1eFMMI-Tiao2b35pZLe5h73JxOjdkllvvP-tRO4Q25qUy_sseFAYEDl8tNzrD1VxhAvUE",
dq: "pZaLXAk2isRpcd8u0zIqrmfgWzKeMMW8Jmb9tnx7LBOTK86KtzOfJR1mSb2JTmwtAGqRIKuRNL2bG9gTwIrG3ODxt-U6o9Aq0aWI-UsydL_HsgmvL-aCTF37agqUpnT1fij0NXg13RFOiumusfmsxURvNEOJEP0CEOAFVHu_RTE",
e: "AQAB",
ext: true,
key_ops: ["sign"],
kty: "RSA",
n: "q0vCwCa2vlc2cRuLXmR0bBRfhnNckIfDpv3z9GiTf16XmymSQexKNIWCPSgjwM9VsPopiF0KFMKCeEU4ca_qFQSKa2ybiyPGX4-TvjUftlvw96g3ILcqx0WbJoRZfMVrQCYFz_AI6-hUhk0br22zc1tQpG4lDY_WLyhZOT7LtOGFAeMWdlFS_VeQ3lGNUOTNBYd2475ABQfy8j6nAajUudUBhLdU7srl5vYSLfWBvGRJQ0LUqSsVGPF5RR8GIQ56DEiH_O73OVg-GSImcE2Ig7zuzN39opLnYelFFghBN5egQL1Q46shpU_c5k8eeIxbrCncEPqJY0iQkml81r7mbQ",
p: "4m6yATANXnstYOh4DWPMcWO_ta-ATocMULsDKtK5Bw0Ispl_2KONgRNHcZLM7mjJJHDvmxznbW9aKNJ3sbvcNlPc6NvTMpBvLk4CY6sTH4YycfCkQKHeNE506bhyLSd1obmUSlhtcoC7Q9gKInFkdWPCpE5_XrOTlj3-PxgBC8E",
q: "wanvFxB6A-M4L0JkeyvLAhOe3qCGXZ4APPnu_mI2PVu2rpjo6OA2yelbF3_geisRsSCpoj1MqSNuuoTG3DHP5r6XT_GcmClwaVUv2qrE4is4T3YllBmX9g9sFzLjE7mXFcsy9XniYjyvodEnJCp2hSZN7GLi8mCJe9DO9HlbNa0",
qi: "a3VzKzraZmnDsiKnGjbMHSnUdHISo_mvYMqJ-3YHeS--KzRisKjZkmJjRqhO8ckfbUPn9NFZhH5xTm_HywW3HTLTphZrJMoKfAgGCUNwL9pLhlvHzzO1IvDNi1oq3ae3ZzJLlx5_RXp5EjRwtzF13FCQ4_rZSVKBXhe3G8caG3Q",
};

const importedPrivateKey = await cryptoAPI.importKey(
"jwk",
digitalSigningKey,
{
name: CRYPTO_ALGORITHM,
hash: CRYPTO_HASH,
},
true,
["sign"]
);

const jwToken = await jwt.sign(signatureData, importedPrivateKey, { algorithm: 'RS256' });

return jwToken;

}

Using a URL to specify the public key

To improve security for your apps, you might want to use a server to periodically change the public/private key pair. To make this update easier, you can specify a public key by using a URL. Typically you use this with a JSON Web Token.

When you use a URL to specify the public key, instead of retriving the public key from AppD config, Finsemble's static authentication fetches the public key from the URL using HTTP. You configure this option by defining a signatureKeyURL property under the AppD manifest similar to this:

{
...
"apps": [
...
{
"appId": "TradeBlotter",
...
"hostManifests" : {
"Finsemble": {
...
"signatureKeyURL": "https://https://yoursite.com/apps/TradeBlotter/key.json"
}
},
...
},

When the app registers, Finsemble fetches the public key from the URL to authenticate the app. The key must be returned in a JSON format as shown here:

{
"alg": "RS256",
"e": "AQAB",
"ext": true,
"key_ops": ["verify"],
"kty": "RSA",
"n": "q0vCwCa2vlc2cRuLXmR0bBRfhnNckIfDpv3z9GiTf16XmymSQexKNIWCPSgjwM9VsPopiF0KFMKCeEU4ca_qFQSKa2ybiyPGX4-TvjUftlvw96g3ILcqx0WbJoRZfMVrQCYFz_AI6-hUhk0br22zc1tQpG4lDY_WLyhZOT7LtOGFAeMWdlFS_VeQ3lGNUOTNBYd2475ABQfy8j6nAajUudUBhLdU7srl5vYSLfWBvGRJQ0LUqSsVGPF5RR8GIQ56DEiH_O73OVg-GSImcE2Ig7zuzN39opLnYelFFghBN5egQL1Q46shpU_c5k8eeIxbrCncEPqJY0iQkml81r7mbQ"
}

When you specify the public key this way, it gets used the next time an app needs to authenticate. Follow the security policy of your company to decide how often you should make this update.

Advanced: Detecting connectivity failures

The JavaScript adapter loops until it connects to a running Finsemble instance. The proprietary function fdc3.onDisconnect() can be used to call a handler when the connection to Finsemble is lost. This would typically only occur if Finsemble is closed independent of your freestanding app.

The JavaScript adapter does not currently have logic for re-establishing fdc3 state, so if your app is disconnected it will have to call FSBLJSAdapter.startApp() again and reestablish any fdc3 context or intent listeners. It may be easier to trigger an app reload or ask your end user to restart the app if it disconnects unexpectedly.

Example onDisconnect() usage:

fdc3.onDisconnect(() => {
alert("This app has lost connectivity. Please restart.");
});
caution

fdc3.onDisconnect() is not a standard FDC3 function. This API may change in future versions.

A caveat on JavaScript adapter functionality

caution

Some advanced Finsemble features, like window-client operations or persisting app state across restarts or workspace reloads, are not currently supported for the JavaScript Adapter.

See also

Authenticating apps

Integrating freestanding native apps with the .NET adapter