Skip to content

Commit

Permalink
add basic plugin API
Browse files Browse the repository at this point in the history
  • Loading branch information
yann300 committed Jul 4, 2018
1 parent a91c94d commit 06e81ca
Show file tree
Hide file tree
Showing 9 changed files with 257 additions and 49 deletions.
8 changes: 8 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 34 additions & 7 deletions src/app/panels/righthand-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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,
Expand All @@ -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),
Expand All @@ -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}>
Expand Down
41 changes: 41 additions & 0 deletions src/app/plugin/plugin.md
Original file line number Diff line number Diff line change
@@ -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)`

50 changes: 43 additions & 7 deletions src/app/plugin/pluginAPI.js
Original file line number Diff line number Diff line change
@@ -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)
})
}
}
}
}
}
80 changes: 49 additions & 31 deletions src/app/plugin/pluginManager.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
'use strict'

var globalRegistry = require('../../global/registry')
var PluginAPI = require('./pluginAPI')
var executionContext = require('../../execution-context')
/**
* Register and Manage plugin:
*
Expand Down Expand Up @@ -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({
Expand All @@ -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({
Expand All @@ -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,
Expand All @@ -142,26 +151,35 @@ 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
if (self.plugins[name]) {
self.plugins[name].content.querySelector('iframe').contentWindow.postMessage(value, self.plugins[name].origin)
}
}
}
}
5 changes: 3 additions & 2 deletions src/app/tabs/plugin-tab.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
13 changes: 12 additions & 1 deletion src/app/tabs/tabbed-menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading

0 comments on commit 06e81ca

Please sign in to comment.