add basic plugin API

pull/1/head
yann300 6 years ago
parent a91c94dc49
commit 06e81ca845
  1. 8
      src/app.js
  2. 41
      src/app/panels/righthand-panel.js
  3. 41
      src/app/plugin/plugin.md
  4. 50
      src/app/plugin/pluginAPI.js
  5. 80
      src/app/plugin/pluginManager.js
  6. 5
      src/app/tabs/plugin-tab.js
  7. 13
      src/app/tabs/tabbed-menu.js
  8. 23
      src/universal-dapp.js
  9. 45
      test-browser/plugin/remix.js

@ -578,6 +578,14 @@ Please make a backup of your contracts and start using http://remix.ethereum.org
if (queryParams.get().debugtx) {
self.startdebugging(queryParams.get().debugtx)
}
if (queryParams.get().pluginurl) {
var title = queryParams.get().plugintitle
var url = queryParams.get().pluginurl
modalDialogCustom.confirm(null, `Remix is going to load the extension "${title}" located at ${queryParams.get().pluginurl}. Are you sure to load this external extension?`, () => {
self._components.righthandpanel.loadPlugin({title, url})
})
}
})
// chrome app

@ -15,6 +15,7 @@ const SupportTab = require('../tabs/support-tab')
const PluginTab = require('../tabs/plugin-tab')
const TestTab = require('../tabs/test-tab')
const RunTab = require('../tabs/run-tab')
const PluginAPI = require('../plugin/pluginAPI')
const EventManager = remixLib.EventManager
const styles = styleguide.chooser()
@ -24,6 +25,7 @@ module.exports = class RighthandPanel {
const self = this
self._components = {}
self._components.registry = localRegistry || globalRegistry
self._components.registry.put({api: this, name: 'righthandpanel'})
self.event = new EventManager()
self._view = {
element: null,
@ -32,11 +34,32 @@ module.exports = class RighthandPanel {
dragbar: null
}
self._components.registry.put({api: this, name: 'righthandpanel'})
self._deps = {
fileProviders: self._components.registry.get('fileproviders').api,
compiler: self._components.registry.get('compiler').api,
udapp: self._components.registry.get('udapp').api,
app: self._components.registry.get('app').api,
txlistener: self._components.registry.get('txlistener').api
}
var tabbedMenu = new TabbedMenu(self._components.registry)
var pluginAPI = new PluginAPI(
self._deps.fileProviders,
self._deps.compiler,
self._deps.udapp,
tabbedMenu
)
var pluginManager = new PluginManager(
pluginAPI,
self._deps.app,
self._deps.compiler,
self._deps.txlistener)
self._components = {
pluginManager: new PluginManager(self._components.registry),
tabbedMenu: new TabbedMenu(self._components.registry),
pluginManager: pluginManager,
tabbedMenu: tabbedMenu,
compile: new CompileTab(self._components.registry),
run: new RunTab(self._components.registry),
settings: new SettingsTab(self._components.registry),
@ -47,12 +70,16 @@ module.exports = class RighthandPanel {
}
self.event.register('plugin-loadRequest', json => {
const tab = new PluginTab({}, self._events, json)
const content = tab.render()
self._components.tabbedMenu.addTab(json.title, 'plugin', content)
self._components.pluginManager.register(json, content)
self.loadPlugin(json)
})
self.loadPlugin = function (json) {
var tab = new PluginTab(json)
var content = tab.render()
self._components.tabbedMenu.addTab(json.title, json.title + ' plugin', content)
self.pluginManager.register(json, content)
}
self._view.dragbar = yo`<div id="dragbar" class=${css.dragbar}></div>`
self._view.element = yo`
<div id="righthand-panel" class=${css.righthandpanel}>

@ -0,0 +1,41 @@
plugin api
# current APIs:
## 1) notifications
### app (key: app)
- unfocus `[]`
- focus `[]`
### compiler (key: compiler)
- compilationFinished `[success (bool), data (obj), source (obj)]`
- compilationData `[compilationResult]`
### transaction listener (key: txlistener)
- newTransaction `tx (obj)`
## 2) interactions
### app
- getExecutionContextProvider `@return {String} provider (injected | web3 | vm)`
- updateTitle `@param {String} title`
### config
- setConfig `@param {String} path, @param {String} content`
- getConfig `@param {String} path`
- removeConfig `@param {String} path`
### compiler
- getCompilationResult `@return {Object} compilation result`
### udapp (only VM)
- runTx `@param {Object} tx`
- getAccounts `@return {Array} acccounts`
- createVMAccount `@param {String} privateKey, @param {String} balance (hex)`

@ -1,26 +1,62 @@
'use strict'
var executionContext = require('../../execution-context')
/*
Defines available API. `key` / `type`
*/
module.exports = (registry) => {
module.exports = (fileProviders, compiler, udapp, tabbedMenu) => {
return {
app: {
getExecutionContextProvider: (mod, cb) => {
cb(null, executionContext.getProvider())
},
updateTitle: (mod, title, cb) => {
tabbedMenu.updateTabTitle(mod, title)
}
},
config: {
setConfig: (mod, path, content, cb) => {
registry.get('fileproviders/config').api.set(mod + '/' + path, content)
fileProviders['config'].set(mod + '/' + path, content)
cb()
},
getConfig: (mod, path, cb) => {
cb(null, registry.get('fileproviders/config').get(mod + '/' + path))
cb(null, fileProviders['config'].get(mod + '/' + path))
},
removeConfig: (mod, path, cb) => {
cb(null, registry.get('fileproviders/config').api.remove(mod + '/' + path))
cb(null, fileProviders['config'].remove(mod + '/' + path))
if (cb) cb()
}
},
compiler: {
getCompilationResult: () => {
return registry.get('compiler').api.lastCompilationResult
getCompilationResult: (mod, cb) => {
cb(null, compiler.lastCompilationResult)
}
},
udapp: {
runTx: (mod, tx, cb) => {
if (executionContext.getProvider() !== 'vm') return cb('plugin API does not allow sending a transaction through a web3 connection. Only vm mode is allowed')
udapp.silentRunTx(tx, (error, result) => {
if (error) return cb(error)
cb(null, {
transactionHash: result.transactionHash,
status: result.result.status,
gasUsed: '0x' + result.result.gasUsed.toString('hex'),
error: result.result.vm.exceptionError,
return: result.result.vm.return ? '0x' + result.result.vm.return.toString('hex') : '0x',
createdAddress: result.result.createdAddress ? '0x' + result.result.createdAddress.toString('hex') : undefined
})
})
},
getAccounts: (mod, cb) => {
if (executionContext.getProvider() !== 'vm') return cb('plugin API does not allow retrieving accounts through a web3 connection. Only vm mode is allowed')
udapp.getAccounts(cb)
},
createVMAccount: (mod, privateKey, balance, cb) => {
if (executionContext.getProvider() !== 'vm') return cb('plugin API does not allow creating a new account through web3 connection. Only vm mode is allowed')
udapp.createVMAccount(privateKey, balance, (error, address) => {
cb(error, address)
})
}
}
}
}
}

@ -1,7 +1,5 @@
'use strict'
var globalRegistry = require('../../global/registry')
var PluginAPI = require('./pluginAPI')
var executionContext = require('../../execution-context')
/**
* Register and Manage plugin:
*
@ -79,19 +77,13 @@ var PluginAPI = require('./pluginAPI')
*
*/
module.exports = class PluginManager {
constructor (localRegistry) {
constructor (pluginAPI, app, compiler, txlistener) {
const self = this
self.plugins = {}
self._components = {}
self._components.registry = localRegistry || globalRegistry
self._components.pluginAPI = new PluginAPI(self._components.registry)
self._deps = {
compiler: self._components.registry.get('compiler').api,
app: self._components.registry.get('app').api
}
self.origins = {}
self.inFocus
self.allowedapi = {'setConfig': 1, 'getConfig': 1, 'removeConfig': 1}
self._deps.compiler.event.register('compilationFinished', (success, data, source) => {
compiler.event.register('compilationFinished', (success, data, source) => {
if (self.inFocus) {
// trigger to the current focus
self.post(self.inFocus, JSON.stringify({
@ -103,7 +95,17 @@ module.exports = class PluginManager {
}
})
self._deps.app.event.register('tabChanged', (tabName) => {
txlistener.event.register('newTransaction', (tx) => {
if (executionContext.getProvider() !== 'vm') return
self.broadcast(JSON.stringify({
action: 'notification',
key: 'txlistener',
type: 'newTransaction',
value: [tx]
}))
})
app.event.register('tabChanged', (tabName) => {
if (self.inFocus && self.inFocus !== tabName) {
// trigger unfocus
self.post(self.inFocus, JSON.stringify({
@ -122,18 +124,25 @@ module.exports = class PluginManager {
value: []
}))
self.inFocus = tabName
self.post(tabName, JSON.stringify({
action: 'notification',
key: 'compiler',
type: 'compilationData',
value: [self._deps.compiler.getCompilationResult()]
}))
pluginAPI.compiler.getCompilationResult(tabName, (error, data) => {
if (!error) return
self.post(tabName, JSON.stringify({
action: 'notification',
key: 'compiler',
type: 'compilationData',
value: [data]
}))
})
}
})
window.addEventListener('message', (event) => {
if (event.type !== 'message') return
var extension = self.origins[event.origin]
if (!extension) return
function response (key, type, callid, error, result) {
self.post(self.inFocus, JSON.stringify({
self.postToOrigin(event.origin, JSON.stringify({
id: callid,
action: 'response',
key: key,
@ -142,21 +151,30 @@ module.exports = class PluginManager {
value: [ result ]
}))
}
if (event.type === 'message' && self.inFocus && self.plugins[self.inFocus] && self.plugins[self.inFocus].origin === event.origin) {
var data = JSON.parse(event.data)
data.value.unshift(self.inFocus)
if (self.allowedapi[data.type]) {
data.value.push((error, result) => {
response(data.key, data.type, data.id, error, result)
})
self._components.pluginAPI[data.key][data.type].apply({}, data.value)
}
}
var data = JSON.parse(event.data)
data.value.unshift(extension)
// if (self.allowedapi[data.type]) {
data.value.push((error, result) => {
response(data.key, data.type, data.id, error, result)
})
pluginAPI[data.key][data.type].apply({}, data.value)
// }
}, false)
}
register (desc, content) {
const self = this
self.plugins[desc.title] = {content, origin: desc.url}
self.origins[desc.url] = desc.title
}
broadcast (value) {
for (var plugin in this.plugins) {
this.post(plugin, value)
}
}
postToOrigin (origin, value) {
if (this.origins[origin]) {
this.post(this.origins[origin], value)
}
}
post (name, value) {
const self = this
@ -164,4 +182,4 @@ module.exports = class PluginManager {
self.plugins[name].content.querySelector('iframe').contentWindow.postMessage(value, self.plugins[name].origin)
}
}
}
}

@ -6,10 +6,11 @@ var globalRegistry = require('../../global/registry')
var EventManager = remixLib.EventManager
module.exports = class plugintab {
constructor (localRegistry) {
constructor (json, localRegistry) {
const self = this
self.event = new EventManager()
self._view = { el: null }
self.data = { json }
self._components = {}
self._components.registry = localRegistry || globalRegistry
}
@ -18,7 +19,7 @@ module.exports = class plugintab {
if (self._view.el) return self._view.el
self._view.el = yo`
<div class="${css.pluginTabView}" id="pluginView">
<iframe class="${css.iframe}" src="${self._opts.url}/index.html"></iframe>
<iframe class="${css.iframe}" src="${self.data.json.url}/index.html"></iframe>
</div>`
return self._view.el
}

@ -46,13 +46,24 @@ module.exports = class TabbedMenu {
if (self._view.el) self._view.el.appendChild(self._view.tabs[title])
if (self._view.viewport) self._view.viewport.appendChild(self._view.contents[title])
}
getTabByClass (tabClass) {
const self = this
return self._view.el.querySelector(`li.${tabClass}`)
}
updateTabTitle (tabClass, title) {
const self = this
var tab = self.getTabByClass(tabClass)
if (tab) tab.innerHTML = title
}
selectTabByTitle (title) {
const self = this
self.selectTab(self._view.tabs[title])
}
selectTabByClassName (tabClass) {
const self = this
self.selectTab(self._view.el.querySelector(`li.${tabClass}`))
var tab = self.getTabByClass(tabClass)
if (tab) self.selectTab(tab)
return tab
}
selectTab (el) {
const self = this

@ -80,6 +80,12 @@ UniversalDApp.prototype.resetAPI = function (transactionContextAPI) {
this.transactionContextAPI = transactionContextAPI
}
UniversalDApp.prototype.createVMAccount = function (privateKey, balance, cb) {
this._addAccount(privateKey, balance)
privateKey = new Buffer(privateKey, 'hex')
cb(null, '0x' + ethJSUtil.privateToAddress(privateKey).toString('hex'))
}
UniversalDApp.prototype.newAccount = function (password, cb) {
if (!executionContext.isVM()) {
if (!this._deps.config.get('settings/personal-mode')) {
@ -282,6 +288,23 @@ UniversalDApp.prototype.getInputs = function (funABI) {
return txHelper.inputParametersDeclarationToString(funABI.inputs)
}
/**
* This function send a tx without alerting the user (if mainnet or if gas estimation too high).
* SHOULD BE TAKEN CAREFULLY!
*
* @param {Object} tx - transaction.
* @param {Function} callback - callback.
*/
UniversalDApp.prototype.silentRunTx = function (tx, cb) {
if (!executionContext.isVM()) return cb('Cannot silently send transaction through a web3 provider')
this.txRunner.rawRun(
tx,
(network, tx, gasEstimation, continueTxExecution, cancelCb) => { continueTxExecution() },
(error, continueTxExecution, cancelCb) => { if (error) { cb(error) } else { continueTxExecution() } },
(okCb, cancelCb) => { okCb() },
cb)
}
UniversalDApp.prototype.runTx = function (args, cb) {
const self = this
async.waterfall([

@ -1,4 +1,17 @@
/*
test contract creation
*/
var addrResolverByteCode = '0x6060604052341561000f57600080fd5b33600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555061033c8061005f6000396000f300606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806338cc483114610067578063767800de146100bc578063a6f9dae114610111578063d1d80fdf1461014a575b600080fd5b341561007257600080fd5b61007a610183565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34156100c757600080fd5b6100cf6101ac565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561011c57600080fd5b610148600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101d1565b005b341561015557600080fd5b610181600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610271565b005b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561022d57600080fd5b80600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161415156102cd57600080fd5b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505600a165627a7a723058201b23355f578cb9a23c0a43a440ab2631b62df7be0a8e759812a70f01344224da0029'
const addrResolverTx = {
gasLimit: '0x2710',
from: '0xca35b7d915458ef540ade6068dfe2f44e8fa733c',
data: addrResolverByteCode,
value: '0x00',
useCall: false
}
function receiveMessage (event) {
console.log('receiveMessage', event.data, event.source, event.origin)
document.getElementById('compilationdata').innerHTML += event.data + '<br>'
@ -35,4 +48,34 @@ window.onload = function () {
id: 36
}), '*')
})
}
document.querySelector('input#testcontractcreation').addEventListener('click', function () {
window.parent.postMessage(JSON.stringify({
action: 'request',
key: 'udapp',
type: 'runTx',
value: [addrResolverTx],
id: 37
}), '*')
})
document.querySelector('input#testaccountcreation').addEventListener('click', function () {
window.parent.postMessage(JSON.stringify({
action: 'request',
key: 'udapp',
type: 'createVMAccount',
value: ['71975fbf7fe448e004ac7ae54cad0a383c3906055a75468714156a07385e96ce', '0x56BC75E2D63100000'],
id: 38
}), '*')
})
var k = 0
document.querySelector('input#testchangetitle').addEventListener('click', function () {
window.parent.postMessage(JSON.stringify({
action: 'request',
key: 'app',
type: 'updateTitle',
value: ['changed title ' + k++],
id: 39
}), '*')
})
}
Loading…
Cancel
Save