feat: add API server

This commit is contained in:
Anthony Laibe 2019-01-07 16:13:48 +00:00
parent df3435f02b
commit d67863cff6
24 changed files with 414 additions and 131 deletions

View File

@ -92,7 +92,6 @@
"start": "node scripts/start.js",
"test": "node scripts/test.js"
},
"homepage": "http://localhost:8000/embark",
"eslintConfig": {
"extends": "react-app",
"rules": {

View File

@ -16,7 +16,7 @@ const Accounts = ({accounts}) => (
{accounts.map(account => (
<div className="explorer-row border-top" key={account.address}>
<CardTitleIdenticon id={account.address}>Account&nbsp;
<Link to={`/embark/explorer/accounts/${account.address}`}>{account.address}</Link>
<Link to={`/explorer/accounts/${account.address}`}>{account.address}</Link>
</CardTitleIdenticon>
<Row>
<Col>

View File

@ -17,7 +17,7 @@ const Blocks = ({blocks, changePage, currentPage, numberOfPages}) => (
{blocks.map(block => (
<div className="explorer-row border-top" key={block.number}>
<CardTitleIdenticon id={block.hash}>Block&nbsp;
<Link to={`/embark/explorer/blocks/${block.number}`}>
<Link to={`/explorer/blocks/${block.number}`}>
{block.number}
</Link>
</CardTitleIdenticon>

View File

@ -21,7 +21,7 @@ const Contracts = ({contracts, title = "Contracts"}) => (
return (
<div className="explorer-row border-top" key={contract.address}>
<CardTitleIdenticon id={contract.className}>
<Link to={`/embark/explorer/contracts/${contract.className}`}>{contract.className}</Link>
<Link to={`/explorer/contracts/${contract.className}`}>{contract.className}</Link>
</CardTitleIdenticon>
<Row>
<Col>

View File

@ -19,7 +19,7 @@ const ContractsList = ({contracts}) => (
const contractDisplay = formatContractForDisplay(contract);
return (
<tr key={contract.className} className={contractDisplay.stateColor}>
<td><Link to={`/embark/explorer/contracts/${contract.className}`}>{contract.className}</Link></td>
<td><Link to={`/explorer/contracts/${contract.className}`}>{contract.className}</Link></td>
<td>{contractDisplay.address}</td>
<td>{contractDisplay.state}</td>
</tr>

View File

@ -7,7 +7,7 @@ import {withRouter} from "react-router-dom";
class DebugButton extends React.Component {
onClick() {
this.props.history.push(`/embark/editor?debuggerTransactionHash=${this.props.transaction.hash}`);
this.props.history.push(`/editor?debuggerTransactionHash=${this.props.transaction.hash}`);
this.props.onClick();
}

View File

@ -13,14 +13,14 @@ import TransactionContainer from '../containers/TransactionContainer';
const ExplorerLayout = () => (
<React.Fragment>
<Switch>
<Route exact path="/embark/explorer/accounts" component={AccountsContainer}/>
<Route exact path="/embark/explorer/accounts/:address" component={AccountContainer}/>
<Route exact path="/embark/explorer/blocks" component={BlocksContainer}/>
<Route exact path="/embark/explorer/blocks/:blockNumber" component={BlockContainer}/>
<Route exact path="/embark/explorer/contracts" component={ContractsContainer} />
<Route exact path="/embark/explorer/contracts/:contractName" component={ContractLayoutContainer} />
<Route exact path="/embark/explorer/transactions" component={TransactionsContainer}/>
<Route exact path="/embark/explorer/transactions/:hash" component={TransactionContainer}/>
<Route exact path="/explorer/accounts" component={AccountsContainer}/>
<Route exact path="/explorer/accounts/:address" component={AccountContainer}/>
<Route exact path="/explorer/blocks" component={BlocksContainer}/>
<Route exact path="/explorer/blocks/:blockNumber" component={BlockContainer}/>
<Route exact path="/explorer/contracts" component={ContractsContainer} />
<Route exact path="/explorer/contracts/:contractName" component={ContractLayoutContainer} />
<Route exact path="/explorer/transactions" component={TransactionsContainer}/>
<Route exact path="/explorer/transactions/:hash" component={TransactionContainer}/>
</Switch>
</React.Fragment>
);

View File

@ -39,27 +39,27 @@ import logo from '../images/logo-new.svg';
import './Layout.css';
const HEADER_NAV_ITEMS = [
{name: "Dashboard", to: "/embark", icon: 'tachometer'},
{name: "Deployment", to: "/embark/deployment", icon: "arrow-up"},
{name: "Explorer", to: "/embark/explorer/overview", icon: "compass"},
{name: "Editor", to: "/embark/editor", icon: "codepen"},
{name: "Utils", to: "/embark/utilities/converter", icon: "cog"}
{name: "Dashboard", to: "/", icon: 'tachometer'},
{name: "Deployment", to: "/deployment", icon: "arrow-up"},
{name: "Explorer", to: "/explorer/overview", icon: "compass"},
{name: "Editor", to: "/editor", icon: "codepen"},
{name: "Utils", to: "/utilities/converter", icon: "cog"}
];
const SIDEBAR_NAV_ITEMS = {
"/embark/explorer" : {items: [
{url: "/embark/explorer/overview", icon: "fa fa-signal", name: "Overview"},
{url: "/embark/explorer/accounts", icon: "fa fa-users", name: "Accounts"},
{url: "/embark/explorer/blocks", icon: "fa fa-stop", name: "Blocks"},
{url: "/embark/explorer/contracts", icon: "fa fa-file-code-o", name: "Contracts"},
{url: "/embark/explorer/transactions", icon: "fa fa-exchange", name: "Transactions"}
"/explorer" : {items: [
{url: "/explorer/overview", icon: "fa fa-signal", name: "Overview"},
{url: "/explorer/accounts", icon: "fa fa-users", name: "Accounts"},
{url: "/explorer/blocks", icon: "fa fa-stop", name: "Blocks"},
{url: "/explorer/contracts", icon: "fa fa-file-code-o", name: "Contracts"},
{url: "/explorer/transactions", icon: "fa fa-exchange", name: "Transactions"}
]},
"/embark/utilities/": {items: [
{url: "/embark/utilities/converter", icon: "fa fa-plug", name: "Converter"},
{url: "/embark/utilities/communication", icon: "fa fa-phone", name: "Communication"},
{url: "/embark/utilities/ens", icon: "fa fa-circle", name: "ENS"},
{url: "/embark/utilities/sign-and-verify", icon: "fa fa-edit", name: "Sign & Verify"},
{url: "/embark/utilities/transaction-decoder", icon: "fa fa-edit", name: "Transaction Decoder"}
"/utilities/": {items: [
{url: "/utilities/converter", icon: "fa fa-plug", name: "Converter"},
{url: "/utilities/communication", icon: "fa fa-phone", name: "Communication"},
{url: "/utilities/ens", icon: "fa fa-circle", name: "ENS"},
{url: "/utilities/sign-and-verify", icon: "fa fa-edit", name: "Sign & Verify"},
{url: "/utilities/transaction-decoder", icon: "fa fa-edit", name: "Transaction Decoder"}
]}
};
@ -96,19 +96,19 @@ class Layout extends React.Component {
}
if (nextProps.searchResult.className) {
this.props.history.push(`/embark/explorer/contracts/${nextProps.searchResult.className}`);
this.props.history.push(`/explorer/contracts/${nextProps.searchResult.className}`);
return false;
}
if (nextProps.searchResult.address) {
this.props.history.push(`/embark/explorer/accounts/${nextProps.searchResult.address}`);
this.props.history.push(`/explorer/accounts/${nextProps.searchResult.address}`);
return false;
}
if (nextProps.searchResult.hasOwnProperty('transactionIndex')) {
this.props.history.push(`/embark/explorer/transactions/${nextProps.searchResult.hash}`);
this.props.history.push(`/explorer/transactions/${nextProps.searchResult.hash}`);
return false;
}
if (nextProps.searchResult.hasOwnProperty('number')) {
this.props.history.push(`/embark/explorer/blocks/${nextProps.searchResult.number}`);
this.props.history.push(`/explorer/blocks/${nextProps.searchResult.number}`);
return false;
}
// Returned something we didn't know existed

View File

@ -23,7 +23,7 @@ const Transaction = ({transaction, contracts}) => (
</CardHeader>
<CardBody>
<dl className="row">
<Description label="Block" value={<Link to={`/embark/explorer/blocks/${transaction.blockNumber}`}>{transaction.blockNumber}</Link>} />
<Description label="Block" value={<Link to={`/explorer/blocks/${transaction.blockNumber}`}>{transaction.blockNumber}</Link>} />
<Description label="From" value={transaction.from} />
<Description label="To" value={transaction.to} />
<Description label="Value" value={`${utils.fromWei(transaction.value)} Ether`}/>

View File

@ -18,7 +18,7 @@ const Transactions = ({transactions, contracts, changePage, currentPage, numberO
{transactions.map(transaction => (
<div className="explorer-row border-top" key={transaction.hash}>
<CardTitleIdenticon id={transaction.hash}>Transaction&nbsp;
<Link to={`/embark/explorer/transactions/${transaction.hash}`}>
<Link to={`/explorer/transactions/${transaction.hash}`}>
{transaction.hash}
</Link>
</CardTitleIdenticon>

View File

@ -9,11 +9,11 @@ import TransactionDecoderContainer from '../containers/TransactionDecoderContain
const UtilsLayout = () => (
<Switch>
<Route exact path="/embark/utilities/converter" component={ConverterContainer} />
<Route exact path="/embark/utilities/communication" component={CommunicationContainer} />
<Route exact path="/embark/utilities/ens" component={EnsContainer} />
<Route exact path="/embark/utilities/sign-and-verify" component={SignAndVerifyContainer} />
<Route exact path="/embark/utilities/transaction-decoder" component={TransactionDecoderContainer} />
<Route exact path="/utilities/converter" component={ConverterContainer} />
<Route exact path="/utilities/communication" component={CommunicationContainer} />
<Route exact path="/utilities/ens" component={EnsContainer} />
<Route exact path="/utilities/sign-and-verify" component={SignAndVerifyContainer} />
<Route exact path="/utilities/transaction-decoder" component={TransactionDecoderContainer} />
</Switch>
);

View File

@ -7,7 +7,7 @@ export const DEPLOYMENT_PIPELINES = {
injectedWeb3: 'injectedWeb3',
embark: 'embark'
};
export const DEFAULT_HOST = process.env.NODE_ENV === 'development' ? 'localhost:8000' : window.location.host;
export const DEFAULT_HOST = 'localhost:55555';
export const OPERATIONS = {
MORE: 1,
LESS: -1

View File

@ -12,12 +12,12 @@ import UtilsLayout from './components/UtilsLayout';
const routes = (
<React.Fragment>
<Switch>
<Route exact path="/embark/" component={HomeContainer} />
<Route exact path="/embark/explorer/overview" component={ExplorerDashboardLayout} />
<Route path="/embark/explorer" component={ExplorerLayout} />
<Route path="/embark/deployment/" component={DeploymentContainer} />
<Route path="/embark/editor" component={EditorContainer} />
<Route path="/embark/utilities" component={UtilsLayout} />
<Route exact path="/" component={HomeContainer} />
<Route exact path="/explorer/overview" component={ExplorerDashboardLayout} />
<Route path="/explorer" component={ExplorerLayout} />
<Route path="/deployment/" component={DeploymentContainer} />
<Route path="/editor" component={EditorContainer} />
<Route path="/utilities" component={UtilsLayout} />
<Route component={NoMatch} />
</Switch>
</React.Fragment>

View File

@ -77,7 +77,6 @@
"@babel/preset-react": "7.0.0",
"@babel/preset-typescript": "7.1.0",
"@babel/runtime-corejs2": "7.1.2",
"@types/pretty-ms": "3.2.0",
"ajv": "6.5.5",
"ascii-table": "0.0.9",
"async": "2.6.1",
@ -192,11 +191,17 @@
"@babel/cli": "7.1.2",
"@babel/plugin-proposal-optional-chaining": "7.0.0",
"@types/async": "2.0.50",
"@types/body-parser": "1.17.0",
"@types/cors": "2.8.4",
"@types/express": "4.16.0",
"@types/express-ws": "3.0.0",
"@types/globule": "1.1.3",
"@types/handlebars": "4.0.39",
"@types/helmet": "0.0.42",
"@types/i18n": "0.8.3",
"@types/node": "10.11.7",
"@types/os-locale": "2.1.0",
"@types/pretty-ms": "3.2.0",
"@types/web3": "1.0.12",
"babel-plugin-dynamic-import-node": "2.2.0",
"chai": "4.1.2",

View File

@ -127,6 +127,7 @@ class EmbarkController {
engine.startService("storage");
engine.startService("codeGenerator");
engine.startService("console");
engine.startService("cockpit");
engine.startService("pluginCommand");
engine.events.on('check:backOnline:Ethereum', function () {
@ -300,6 +301,7 @@ class EmbarkController {
engine.startService("storage");
engine.startService("codeGenerator");
engine.startService("console");
engine.startService("cockpit");
engine.startService("pluginCommand");
engine.events.once('check:backOnline:Ethereum', () => callback());
},

View File

@ -61,6 +61,7 @@ class Engine {
let services = {
"serviceMonitor": this.serviceMonitor,
"pipeline": this.pipelineService,
"cockpit": this.cockpitService,
"codeRunner": this.codeRunnerService,
"codeGenerator": this.codeGeneratorService,
"compiler": this.setupCompilerAndContractsManagerService,
@ -172,7 +173,6 @@ class Engine {
config: this.config,
forceRegister: options.forceRegister
});
this.registerModule('authenticator');
}
codeRunnerService(_options) {
@ -257,8 +257,13 @@ class Engine {
this.events.request('watcher:start');
}
cockpitService() {
this.registerModule('authenticator');
this.registerModule('api', {plugins: this.plugins});
}
webServerService() {
this.registerModule('webserver', {plugins: this.plugins});
this.registerModule('webserver');
}
storageService(_options) {

View File

@ -0,0 +1,109 @@
import { Embark } from "../../../typings/embark";
import {canonicalHost} from "../../utils/host.js";
import {findNextPort} from "../../utils/network";
import Server from "./server";
const utils = require("../../utils/utils.js");
const DEFAULT_PORT = 55555;
export default class Api {
private port!: number;
private api!: Server;
private apiUrl!: string;
constructor(private embark: Embark, private options: any) {
this.embark.events.emit("status", __("Starting API"));
findNextPort(DEFAULT_PORT).then((port) => {
this.port = port;
this.apiUrl = "http://" + canonicalHost("127.0.0.1") + ":" + this.port;
this.api = new Server(this.embark, this.port, options.plugins);
this.listenToCommands();
this.registerConsoleCommands();
this.embark.events.request("processes:register", "api", {
launchFn: (cb: (error: Error | null, message: string) => void) => {
this.api.start()
.then(() => cb(null, __("API available at %s", this.apiUrl)))
.catch((error: Error) => cb(error, ""));
},
stopFn: (cb: (error: Error | null, message: string) => void) => {
this.api.stop()
.then(() => cb(null, __("API stopped")))
.catch((error: Error) => cb(error, ""));
},
});
this.embark.events.request("processes:launch", "api", (error: Error | null, message: string) => {
if (error) {
this.embark.logger.error(error.message);
} else {
this.embark.logger.info(message);
}
this.setServiceCheck();
});
});
}
private setServiceCheck() {
this.embark.events.request("services:register", "api", (cb: (options: object) => any) => {
utils.checkIsAvailable(this.apiUrl, (isAvailable: boolean) => {
const devServer = __("API") + " (" + this.apiUrl + ")";
const serverStatus = (isAvailable ? "on" : "off");
return cb({name: devServer, status: serverStatus});
});
});
this.embark.events.on("check:wentOffline:api", () => {
this.embark.logger.info(__("API is offline"));
});
}
private listenToCommands() {
this.embark.events.setCommandHandler("api:url", (cb) => cb(this.apiUrl));
this.embark.events.setCommandHandler("start-api", (cb) => this.embark.events.request("processes:launch", "api", cb));
this.embark.events.setCommandHandler("stop-api", (cb) => this.embark.events.request("processes:stop", "api", cb));
this.embark.events.setCommandHandler("logs:api:turnOn", (cb) => {
this.api.enableLogging();
cb();
});
this.embark.events.setCommandHandler("logs:api:turnOff", (cb) => {
this.api.disableLogging();
cb();
});
}
private registerConsoleCommands() {
this.embark.registerConsoleCommand({
description: __("Start or stop the API"),
matches: ["api start"],
process: (cmd: string, callback: () => void) => {
this.embark.events.request("start-api", callback);
},
usage: "api start/stop",
});
this.embark.registerConsoleCommand({
matches: ["api stop"],
process: (cmd: string, callback: () => void) => {
this.embark.events.request("stop-api", callback);
},
});
this.embark.registerConsoleCommand({
matches: ["log api on"],
process: (cmd: string, callback: () => void) => {
this.embark.events.request("logs:api:turnOn", callback);
},
});
this.embark.registerConsoleCommand({
matches: ["log api off"],
process: (cmd: string, callback: () => void) => {
this.embark.events.request("logs:api:turnOff", callback);
},
});
}
}

View File

@ -0,0 +1,130 @@
import bodyParser from "body-parser";
import cors from "cors";
import express, { NextFunction, Request, Response } from "express";
import expressWs from "express-ws";
import helmet from "helmet";
import * as http from "http";
import * as path from "path";
import * as ws from "ws";
import { Embark, Plugins } from "../../../typings/embark";
type Method = "get" | "post" | "ws" | "delete";
interface CallDescription {
method: Method;
endpoint: string;
cb(req: Request | ws, res: Response | Request): void;
}
export default class Server {
private isLogging: boolean = false;
private expressInstance: expressWs.Instance;
private server?: http.Server;
constructor(private embark: Embark, private port: number, private plugins: Plugins) {
this.expressInstance = this.initApp();
}
public enableLogging() {
this.isLogging = true;
}
public disableLogging() {
this.isLogging = false;
}
public start() {
return new Promise<void>((resolve, reject) => {
if (this.server) {
const message = __("API is already running");
return reject(new Error(message));
}
this.server = this.expressInstance.app.listen(this.port, () => {
resolve();
});
});
}
public stop() {
return new Promise<void>((resolve, reject) => {
if (!this.server) {
const message = __("API is not running");
return reject(new Error(message));
}
this.server.close(() => {
this.server = undefined;
resolve();
});
});
}
private initApp() {
const instance = expressWs(express());
instance.app.use((req: Request, res: Response, next: NextFunction) => {
if (!this.isLogging) {
return next();
}
if (!req.headers.upgrade) {
this.embark.logger.info(`API > ${req.method} ${req.originalUrl}`);
}
next();
});
instance.app.use(helmet.noCache());
instance.app.use(cors());
instance.app.use(bodyParser.json());
instance.app.use(bodyParser.urlencoded({extended: true}));
instance.app.ws("/logs", (websocket: ws, _req: Request) => {
this.embark.events.on("log", (level: string, message: string) => {
websocket.send(JSON.stringify({msg: message, msg_clear: message.stripColors, logLevel: level}), () => {});
});
});
if (this.plugins) {
instance.app.get("/embark-api/plugins", (req: Request, res: Response) => {
res.send(JSON.stringify(this.plugins.plugins.map((plugin) => ({name: plugin.name}))));
});
const callDescriptions: CallDescription[] = this.plugins.getPluginsProperty("apiCalls", "apiCalls");
callDescriptions.forEach((callDescription) => this.registerCallDescription(instance, callDescription));
}
this.embark.events.on("plugins:register:api", (callDescription: CallDescription) => this.registerCallDescription(instance, callDescription));
const ui = express.static(path.join(__dirname, "../../../../embark-ui/build"));
instance.app.use("/", ui);
instance.app.use("/*", ui);
return instance;
}
private registerCallDescription(instance: expressWs.Instance, callDescription: CallDescription) {
if (callDescription.method === "ws") {
instance.app.ws(callDescription.endpoint, this.applyWSFunction.bind(this, callDescription.cb));
} else {
instance.app[callDescription.method].apply(instance.app, [callDescription.endpoint, this.applyHTTPFunction.bind(this, callDescription.cb)]);
}
}
private applyHTTPFunction(cb: (req: Request, res: Response) => void, req: Request, res: Response) {
this.embark.events.request("authenticator:authorize", req, res, (err: Error) => {
if (err) {
return res.send(err);
}
cb(req, res);
});
}
private applyWSFunction(cb: (ws: ws, req: Request) => void, websocket: ws, req: Request) {
this.embark.events.request("authenticator:authorize", websocket, req, (err: Error) => {
if (!err) {
cb(websocket, req);
}
});
}
}

View File

@ -81,20 +81,15 @@ class Authenticator {
}
registerEvents() {
let self = this;
this.events.once('outputDone', () => {
this.events.request('authenticator:displayUrl', true);
});
this.events.setCommandHandler('authenticator:displayUrl', (firstOutput) => {
const {protocol, port, host, enabled} = this.embark.config.webServerConfig;
if (enabled) {
if(!firstOutput) this.logger.info(__('Previous token has now been used.'));
this.logger.info(__('Access the web backend with the following url: %s',
(`${protocol}://${host}:${port}/embark?token=${this.authToken}`.underline)));
}
this.events.request('api:url', (apiUrl) => {
this.logger.info(__('Access the web backend with the following url: %s', (`${apiUrl}?token=${this.authToken}`.underline)));
});
});
this.events.setCommandHandler('authenticator:authorize', (req, res, cb) => {
@ -115,7 +110,7 @@ class Authenticator {
});
authenticated = (hash === computedHash);
} else {
const hash = self.generateRequestHash(req);
const hash = this.generateRequestHash(req);
authenticated = (hash === req.headers['x-embark-request-hash']);
}

View File

@ -1,21 +1,23 @@
import {findNextPort} from "../../utils/network";
const fs = require('../../core/fs.js');
var {canonicalHost} = require('../../utils/host.js');
var utils = require('../../utils/utils.js');
var Server = require('./server.js');
const opn = require('opn');
require('ejs');
const Templates = {
embark_building_placeholder: require('./templates/embark-building-placeholder.html.ejs')
};
class WebServer {
constructor(embark, options) {
constructor(embark, _options) {
this.embark = embark;
this.logger = embark.logger;
this.events = embark.events;
this.buildDir = embark.config.buildDir;
this.plugins = options.plugins;
this.webServerConfig = embark.config.webServerConfig;
if (!this.webServerConfig.enabled) {
return;
@ -40,7 +42,6 @@ class WebServer {
events: this.events,
host: this.host,
port: this.port,
plugins: this.plugins,
openBrowser: this.webServerConfig.openBrowser,
protocol: this.webServerConfig.protocol,
certOptions : this.webServerConfig.certOptions
@ -72,7 +73,8 @@ class WebServer {
});
});
this.testPort(() => {
findNextPort(this.port).then((newPort) => {
this.server.port = newPort;
this.events.request('processes:launch', 'webserver', (_err, message, port) => {
this.logger.info(message);
this.port = port;
@ -81,23 +83,6 @@ class WebServer {
});
}
testPort(done) {
if (this.port === 0) {
this.logger.warn(__('Assigning an available port'));
this.server.port = 0;
return done();
}
utils.pingEndpoint(this.host, this.port, this.protocol, this.protocol, '', (err) => {
if (err) { // Port is ok
return done();
}
this.logger.warn(__('Webserver already running on port %s. Assigning an available port', this.port));
this.port = 0;
this.server.port = 0;
done();
});
}
setServiceCheck() {
const self = this;

View File

@ -5,10 +5,7 @@ const expressWebSocket = require('express-ws');
const express = require('express');
const fs = require('../../core/fs');
const https = require('https');
var cors = require('cors');
let path = require('path');
var bodyParser = require('body-parser');
const helmet = require('helmet');
class Server {
constructor(options) {
@ -22,7 +19,6 @@ class Server {
this.opened = false;
this.openBrowser = options.openBrowser;
this.logging = false;
this.plugins = options.plugins;
this.enableCatchAll = options.enableCatchAll;
this.protocol = options.protocol || 'http';
@ -71,36 +67,11 @@ class Server {
next();
});
this.app.use(helmet.noCache());
this.app.use(cors());
this.app.use(main);
this.app.use('/coverage', coverage);
this.app.use(coverageStyle);
this.app.use(express.static(path.join(fs.dappPath(this.dist)), {'index': ['index.html', 'index.htm']}));
this.app.use('/embark', express.static(path.join(__dirname, '../../../../embark-ui/build')));
this.app.use(bodyParser.json()); // support json encoded bodies
this.app.use(bodyParser.urlencoded({extended: true})); // support encoded bodies
this.app.ws('/logs', function(ws, _req) {
self.events.on("log", function(logLevel, logMsg) {
ws.send(JSON.stringify({msg: logMsg, msg_clear: logMsg.stripColors, logLevel: logLevel}), () => {});
});
});
if (self.plugins) {
let apiCalls = self.plugins.getPluginsProperty("apiCalls", "apiCalls");
this.app.get('/embark-api/plugins', function(req, res) {
res.send(JSON.stringify(self.plugins.plugins.map((plugin) => {
return {name: plugin.name};
})));
});
for (let apiCall of apiCalls) {
this.app[apiCall.method].apply(this.app, [apiCall.endpoint, this.applyAPIFunction.bind(this, apiCall.cb)]);
}
}
this.app.ws('/', () => {});
const wss = expressWs.getWss('/');
@ -117,15 +88,6 @@ class Server {
});
});
this.events.on('plugins:register:api', (apiCall) => {
self.app[apiCall.method].apply(self.app, [apiCall.endpoint, this.applyAPIFunction.bind(this, apiCall.cb)]);
});
this.app.get('/embark/*', function(req, res) {
self.logger.trace('webserver> GET ' + req.path);
res.sendFile(path.join(__dirname, '../../../../embark-ui/build', 'index.html'));
});
if (this.enableCatchAll === true) {
this.app.get('/*', function(req, res) {
self.logger.trace('webserver> GET ' + req.path);
@ -176,16 +138,6 @@ class Server {
(this.protocol + '://' + canonicalHost(this.hostname) + ':' + this.port).bold.underline.green;
}
applyAPIFunction(cb, req, res) {
this.events.request('authenticator:authorize', req, res, (err) => {
if (err) {
const send = res.send ? res.send.bind(res) : req.send.bind(req); // WS only has the first params
return send(err);
}
cb(req, res);
});
}
stop(callback) {
callback = callback || function () {};
if (!this.server || !this.server.listening) {

10
src/lib/utils/network.ts Normal file
View File

@ -0,0 +1,10 @@
import * as net from "net";
export function findNextPort(port: number) {
const server = net.createServer();
return new Promise<number>((resolve) => {
server.once("close", () => resolve(port));
server.on("error", () => resolve(findNextPort(port + 1)));
server.listen(port, () => server.close());
});
}

View File

@ -8,6 +8,15 @@ export interface Events {
setCommandHandler(name: string, callback: (options: any, cb: () => void) => void): void;
}
export interface Plugins {
getPluginsProperty(pluginType: string, property: string, sub_property?: string): any[];
plugins: Plugin[];
}
export interface Plugin {
name: string;
}
export interface Embark {
events: Events;
registerAPICall: any;

View File

@ -838,6 +838,28 @@
dependencies:
"@types/node" "*"
"@types/body-parser@*", "@types/body-parser@1.17.0":
version "1.17.0"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.0.tgz#9f5c9d9bd04bb54be32d5eb9fc0d8c974e6cf58c"
integrity sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==
dependencies:
"@types/connect" "*"
"@types/node" "*"
"@types/connect@*":
version "3.4.32"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28"
integrity sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==
dependencies:
"@types/node" "*"
"@types/cors@2.8.4":
version "2.8.4"
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.4.tgz#50991a759a29c0b89492751008c6af7a7c8267b0"
integrity sha512-ipZjBVsm2tF/n8qFGOuGBkUij9X9ZswVi9G3bx/6dz7POpVa6gVHcj1wsX/LVEn9MMF41fxK/PnZPPoTD1UFPw==
dependencies:
"@types/express" "*"
"@types/debug@^0.0.30":
version "0.0.30"
resolved "https://registry.yarnpkg.com/@types/debug/-/debug-0.0.30.tgz#dc1e40f7af3b9c815013a7860e6252f6352a84df"
@ -848,6 +870,33 @@
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"
integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==
"@types/express-serve-static-core@*":
version "4.16.0"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.16.0.tgz#fdfe777594ddc1fe8eb8eccce52e261b496e43e7"
integrity sha512-lTeoCu5NxJU4OD9moCgm0ESZzweAx0YqsAcab6OB0EB3+As1OaHtKnaGJvcngQxYsi9UNv0abn4/DRavrRxt4w==
dependencies:
"@types/events" "*"
"@types/node" "*"
"@types/range-parser" "*"
"@types/express-ws@3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/express-ws/-/express-ws-3.0.0.tgz#89674edba2e9141916fc4d4d30fbd4f810e6b80b"
integrity sha512-GxsWec7Vp6h7sJuK0PwnZHeXNZnOwQn8kHAbCfvii66it5jXHTWzSg5cgHVtESwJfBLOe9SJ5wmM7C6gsDoyQw==
dependencies:
"@types/express" "*"
"@types/express-serve-static-core" "*"
"@types/ws" "*"
"@types/express@*", "@types/express@4.16.0":
version "4.16.0"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.16.0.tgz#6d8bc42ccaa6f35cf29a2b7c3333cb47b5a32a19"
integrity sha512-TtPEYumsmSTtTetAPXlJVf3kEqb6wZK0bZojpJQrnD/djV4q1oB6QQ8aKvKqwNPACoe02GNiy5zDzcYivR5Z2w==
dependencies:
"@types/body-parser" "*"
"@types/express-serve-static-core" "*"
"@types/serve-static" "*"
"@types/fs-extra@^5.0.2":
version "5.0.4"
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.4.tgz#b971134d162cc0497d221adde3dbb67502225599"
@ -877,6 +926,13 @@
resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.0.39.tgz#961fb54db68030890942e6aeffe9f93a957807bd"
integrity sha512-vjaS7Q0dVqFp85QhyPSZqDKnTTCemcSHNHFvDdalO1s0Ifz5KuE64jQD5xoUkfdWwF4WpqdJEl7LsWH8rzhKJA==
"@types/helmet@0.0.42":
version "0.0.42"
resolved "https://registry.yarnpkg.com/@types/helmet/-/helmet-0.0.42.tgz#845954fb171000c9b9caa367febf6769bd589eaa"
integrity sha512-xQjlolRfr7LVa55sGnjtJGcV6/Hf7cfD1IuZo1Do+o3UobOoJJSLIbwkhd9tW18F1Kp4uB77T8TsJ2XKUDwp7g==
dependencies:
"@types/express" "*"
"@types/i18n@0.8.3":
version "0.8.3"
resolved "https://registry.yarnpkg.com/@types/i18n/-/i18n-0.8.3.tgz#f602164f2fae486ea87590f6be5d6dd5db1664e6"
@ -887,6 +943,11 @@
resolved "https://registry.yarnpkg.com/@types/lockfile/-/lockfile-1.0.0.tgz#76a7c19c50fe8ee2b1666d653ff5d557c30fe0ff"
integrity sha512-pD6JuijPmrfi84qF3/TzGQ7zi0QIX+d7ZdetD6jUA6cp+IsCzAquXZfi5viesew+pfpOTIdAVKuh1SHA7KeKzg==
"@types/mime@*":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b"
integrity sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA==
"@types/minimatch@*":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
@ -919,11 +980,24 @@
resolved "https://registry.yarnpkg.com/@types/pretty-ms/-/pretty-ms-3.2.0.tgz#cdd35f7edac2310bbe2af86f5244625db7a101b1"
integrity sha512-jF8PYR5Nm2w148Icj+Xf4CcVO+YrrpVyRSZPmSG67W2H3WrAAET7jP22txHpk6rnwPGJ8asW7syiyQTPcWWPAQ==
"@types/range-parser@*":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
"@types/semver@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
"@types/serve-static@*":
version "1.13.2"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.2.tgz#f5ac4d7a6420a99a6a45af4719f4dcd8cd907a48"
integrity sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==
dependencies:
"@types/express-serve-static-core" "*"
"@types/mime" "*"
"@types/tar@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/tar/-/tar-4.0.0.tgz#e3239d969eeb693a012200613860d0eb871c94f0"
@ -949,6 +1023,14 @@
"@types/bn.js" "*"
"@types/underscore" "*"
"@types/ws@*":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.1.tgz#ca7a3f3756aa12f62a0a62145ed14c6db25d5a28"
integrity sha512-EzH8k1gyZ4xih/MaZTXwT2xOkPiIMSrhQ9b8wrlX88L0T02eYsddatQlwVFlEPyEqV0ChpdpNnE51QPH6NVT4Q==
dependencies:
"@types/events" "*"
"@types/node" "*"
"@webassemblyjs/ast@1.7.6":
version "1.7.6"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.6.tgz#3ef8c45b3e5e943a153a05281317474fef63e21e"