diff --git a/.eslintrc.json b/.eslintrc.json
index 7cde01d..6612481 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,3 +1,7 @@
{
- "extends": "airbnb-base"
+ "extends": "airbnb-base",
+ "globals": {
+ "document": false,
+ "window": false
+ }
}
diff --git a/.gitignore b/.gitignore
index b6a2f7f..342fd5c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,6 @@
/node_modules/
-/browser/
-/components/*/
+/dist/
flowhub.json
fbp.json
package-lock.json
*~
-gh-pages/
diff --git a/.travis.yml b/.travis.yml
index e40cd1d..388c190 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,7 +4,7 @@ node_js:
deploy:
provider: pages
skip_cleanup: true
- local-dir: browser
+ local-dir: dist
keep-history: true
skip_cleanup: true
committer-from-gh: true
diff --git a/app.js b/app.js
new file mode 100644
index 0000000..f80e370
--- /dev/null
+++ b/app.js
@@ -0,0 +1,24 @@
+const runtime = require('./runtime');
+const pkg = require('./package.json');
+const graph = require('./graphs/main.json');
+
+function main() {
+ return runtime(graph, {
+ runtimeOptions: {
+ baseDir: pkg.name,
+ label: pkg.name,
+ namespace: pkg.name,
+ repository: pkg.repository ? pkg.repository.url : null,
+ },
+ debugButton: document.getElementById('flowhub_debug_url'),
+ });
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ main()
+ .catch((err) => {
+ const message = document.querySelector('body p');
+ message.innerHTML = `ERROR: ${err.message}`;
+ message.parentElement.classList.add('error');
+ });
+});
diff --git a/assets/app.css b/assets/app.css
new file mode 100644
index 0000000..87a1d02
--- /dev/null
+++ b/assets/app.css
@@ -0,0 +1,22 @@
+#flowhub_debug_url.nodebug {
+ display: none;
+}
+#flowhub_debug_url.debug {
+ display: block;
+ position: fixed;
+ z-index: 999;
+ width: 20em;
+ height: 1.5em;
+ right: -6.5em;
+ top: 3em;
+ text-align: center;
+ background-color: hsla(210, 98%, 46%, .8) !important;
+ border-color: hsl(210, 98%, 46%) !important;
+ color: white !important;
+ text-decoration: none;
+ padding: 1em;
+ box-sizing: content-box;
+ cursor: pointer;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
+ transform: rotate(45deg);
+}
diff --git a/assets/index.html b/assets/index.html
new file mode 100644
index 0000000..cfee2f1
--- /dev/null
+++ b/assets/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+ noflo-browser-app
+
+
+
+
+ Debug in Flowhub
+
+
+
+
diff --git a/package.json b/package.json
index baf8e3d..16c8f1f 100644
--- a/package.json
+++ b/package.json
@@ -12,22 +12,34 @@
},
"license": "MIT",
"scripts": {
- "pretest": "eslint *.js components/*.js",
- "test": "fbp-spec --address ws://localhost:3569 --command \"noflo-runtime-headless -f browser/noflo-browser-app.js\" spec/*.yaml"
+ "build": "webpack",
+ "lint": "eslint *.js components/*.js",
+ "lint-fix": "eslint --fix *.js components/*.js",
+ "pretest": "npm run lint && npm run build",
+ "test": "fbp-spec --address ws://localhost:3569 --command \"noflo-runtime-headless -f dist/test.js\" spec/*.yaml",
+ "postinstall": "noflo-cache-preheat"
},
"dependencies": {
"noflo": "^1.0.0",
"noflo-core": "^0.5.0",
"noflo-dom": "^0.3.0",
- "noflo-interaction": "^0.3.0"
+ "noflo-interaction": "^0.3.0",
+ "noflo-runtime-postmessage": "^0.10.0"
},
"devDependencies": {
+ "@babel/core": "^7.1.6",
+ "@babel/preset-env": "^7.1.6",
+ "babel-loader": "^8.0.4",
+ "coffee-loader": "^0.9.0",
+ "copy-webpack-plugin": "^4.6.0",
"eslint": "^5.6.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-plugin-import": "^2.14.0",
"fbp-spec": "^0.6.6",
+ "noflo-component-loader": "^0.3.2",
"noflo-runtime-headless": "^0.1.0",
- "noflo-runtime-postmessage": "^0.10.0"
+ "webpack": "^4.26.1",
+ "webpack-cli": "^3.1.2"
},
"keywords": [
"noflo"
diff --git a/runtime.js b/runtime.js
new file mode 100644
index 0000000..b8003a7
--- /dev/null
+++ b/runtime.js
@@ -0,0 +1,95 @@
+const noflo = require('noflo');
+const postMessageRuntime = require('noflo-runtime-postmessage');
+const qs = require('querystring');
+
+const defaultPermissions = [
+ 'protocol:graph',
+ 'protocol:component',
+ 'protocol:network',
+ 'protocol:runtime',
+ 'component:getsource',
+ 'component:setsource',
+];
+
+function getParameterByName(name) {
+ const params = (new URL(document.location)).searchParams;
+ return params.get(name);
+}
+
+function getIdeUrl(ide = 'https://app.flowhub.io') {
+ const query = qs.stringify({
+ protocol: 'opener',
+ address: window.location.href,
+ });
+ return `${ide}#runtime/endpoint?${query}`;
+}
+
+function loadGraph(json) {
+ return new Promise((resolve, reject) => {
+ noflo.graph.loadJSON(json, (err, graphInstance) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+ resolve(graphInstance);
+ });
+ });
+}
+
+function startRuntime(graph, options = {}) {
+ return new Promise((resolve, reject) => {
+ const protocol = options.protocol || getParameterByName('fbp_protocol') || 'opener';
+
+ const runtimeOptions = {
+ ...options.runtimeOptions,
+ };
+ if (!runtimeOptions.defaultPermissions) {
+ runtimeOptions.defaultPermissions = defaultPermissions;
+ }
+
+ switch (protocol) {
+ case 'opener': {
+ if (!options.debugButton) {
+ reject(new Error('No debug button defined'));
+ return;
+ }
+ const button = options.debugButton;
+ button.classList.replace('nodebug', 'debug');
+ button.href = getIdeUrl(options.ide);
+ resolve(postMessageRuntime.opener(runtimeOptions, button));
+ return;
+ }
+ case 'iframe': {
+ resolve(postMessageRuntime.iframe(runtimeOptions));
+ return;
+ }
+ default: {
+ reject(new Error(`Unknown FBP protocol ${protocol}`));
+ }
+ }
+ });
+}
+
+function startNetwork(runtime, graph, options) {
+ const noLoad = options.noLoad || (getParameterByName('fbp_noload') === 'true');
+ if (noLoad) {
+ return Promise.resolve(runtime);
+ }
+ return new Promise((resolve, reject) => {
+ const graphName = `${runtime.options.namespace || 'default'}/${graph.name || 'main'}`;
+ runtime.graph.registerGraph(graphName, graph);
+ // eslint-disable-next-line no-underscore-dangle
+ runtime.network._startNetwork(graph, graphName, 'none', (err) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+ runtime.runtime.setMainGraph(graphName, graph);
+ resolve(runtime);
+ });
+ });
+}
+
+module.exports = (graph, options) => loadGraph(graph)
+ .then(graphInstance => startRuntime(graphInstance, options)
+ .then(runtime => startNetwork(runtime, graphInstance, options)));
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 0000000..367e923
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,91 @@
+const path = require('path');
+
+const CopyWebpackPlugin = require('copy-webpack-plugin');
+
+module.exports = {
+ entry: {
+ app: './app.js',
+ test: './node_modules/noflo-runtime-headless/spec/build/webpack.entry.js',
+ },
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ filename: '[name].js',
+ },
+ mode: 'production',
+ devtool: 'source-map',
+ module: {
+ rules: [
+ {
+ test: /noflo([\\]+|\/)lib([\\]+|\/)loader([\\]+|\/)register.js$/,
+ use: [
+ {
+ loader: 'noflo-component-loader',
+ options: {
+ graph: null,
+ debug: true,
+ baseDir: __dirname,
+ manifest: {
+ runtimes: ['noflo'],
+ discover: true,
+ },
+ runtimes: [
+ 'noflo',
+ 'noflo-browser',
+ ],
+ },
+ },
+ ],
+ },
+ {
+ test: /\.s$/,
+ // exclude: /(node_modules|bower_components)/,
+ use: {
+ loader: 'babel-loader',
+ options: {
+ presets: ['@babel/preset-env'],
+ },
+ },
+ },
+ {
+ test: /\.coffee$/,
+ use: [
+ {
+ loader: 'coffee-loader',
+ options: {
+ transpile: {
+ presets: ['@babel/preset-env'],
+ },
+ },
+ },
+ ],
+ },
+ {
+ test: /\.fbp$/,
+ use: [
+ {
+ loader: 'fbp-loader',
+ },
+ ],
+ },
+ ],
+ },
+ plugins: [
+ new CopyWebpackPlugin([
+ {
+ from: 'assets/*.html',
+ flatten: true,
+ },
+ {
+ from: 'assets/*.css',
+ flatten: true,
+ },
+ ]),
+ ],
+ resolve: {
+ extensions: ['.coffee', '.js'],
+ },
+ node: {
+ child_process: 'empty',
+ fs: 'empty',
+ },
+};