remix-project mirror
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
remix-project/apps/remix-ide/src/remixAppManager.js

271 lines
9.1 KiB

import { PluginManager } from '@remixproject/engine'
import { EventEmitter } from 'events'
3 years ago
import { QueryParams } from '@remix-project/remix-lib'
3 years ago
import { IframePlugin } from '@remixproject/engine-web'
const _paq = window._paq = window._paq || []
// requiredModule removes the plugin from the plugin manager list on UI
const requiredModules = [ // services + layout views + system views
'manager', 'config', 'compilerArtefacts', 'compilerMetadata', 'contextualListener', 'editor', 'offsetToLineColumnConverter', 'network', 'theme', 'locale',
3 years ago
'fileManager', 'contentImport', 'blockchain', 'web3Provider', 'scriptRunner', 'fetchAndCompile', 'mainPanel', 'hiddenPanel', 'sidePanel', 'menuicons',
'filePanel', 'terminal', 'settings', 'pluginManager', 'tabs', 'udapp', 'dGitProvider', 'solidity', 'solidity-logic', 'gistHandler', 'layout',
'notification', 'permissionhandler', 'walkthrough', 'storage', 'restorebackupzip', 'link-libraries', 'deploy-libraries', 'openzeppelin-proxy',
2 years ago
'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected', 'injected-trustwallet', 'injected-optimism-provider', 'injected-arbitrum-one-provider', 'vm-custom-fork', 'vm-goerli-fork', 'vm-mainnet-fork', 'vm-sepolia-fork', 'vm-merge', 'vm-london', 'vm-berlin',
2 years ago
'vm-shanghai',
'compileAndRun', 'search', 'recorder', 'fileDecorator', 'codeParser', 'codeFormatter', 'solidityumlgen', 'contractflattener', 'solidity-script']
// dependentModules shouldn't be manually activated (e.g hardhat is activated by remixd)
const dependentModules = ['foundry', 'hardhat', 'truffle', 'slither']
const loadLocalPlugins = ["doc-gen", "doc-viewer", "etherscan", "vyper", "walletconnect"]
3 years ago
const sensitiveCalls = {
'fileManager': ['writeFile', 'copyFile', 'rename', 'copyDir'],
3 years ago
'contentImport': ['resolveAndSave'],
'web3Provider': ['sendAsync'],
3 years ago
}
export function isNative(name) {
// nativePlugin allows to bypass the permission request
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'solidity-logic', 'solidityStaticAnalysis', 'solidityUnitTesting',
'layout', 'notification', 'hardhat-provider', 'ganache-provider', 'foundry-provider', 'basic-http-provider', 'injected-optimism-provider',
'tabs', 'injected-arbitrum-one-provider', 'injected', 'doc-gen', 'doc-viewer']
return nativePlugins.includes(name) || requiredModules.includes(name)
}
/**
* Checks if plugin caller 'from' is allowed to activate plugin 'to'
* The caller can have 'canActivate' as a optional property in the plugin profile.
* This is an array containing the 'name' property of the plugin it wants to call.
* canActivate = ['plugin1-to-call','plugin2-to-call',....]
* or the plugin is allowed by default because it is native
*
* @param {any, any}
* @returns {boolean}
*/
3 years ago
export function canActivate(from, to) {
return ['ethdoc'].includes(from.name) ||
3 years ago
isNative(from.name) ||
(to && from && from.canActivate && from.canActivate.includes(to.name))
}
5 years ago
export class RemixAppManager extends PluginManager {
3 years ago
constructor() {
5 years ago
super()
this.event = new EventEmitter()
this.pluginsDirectory = 'https://raw.githubusercontent.com/ethereum/remix-plugins-directory/master/build/metadata.json'
this.pluginLoader = new PluginLoader()
}
3 years ago
async canActivatePlugin(from, to) {
return canActivate(from, to)
5 years ago
}
3 years ago
async canDeactivatePlugin(from, to) {
if (requiredModules.includes(to.name)) return false
return isNative(from.name)
5 years ago
}
3 years ago
async canDeactivate(from, to) {
return this.canDeactivatePlugin(from, to)
3 years ago
}
3 years ago
async deactivatePlugin(name) {
const profile = await this.getProfile(name)
const [to, from] = [
profile,
await this.getProfile(this.requestFrom)
]
if (this.canDeactivatePlugin(from, to)) {
if (profile.methods.includes('deactivate')) {
try {
await this.call(name, 'deactivate')
} catch (e) {
console.log(e)
}
}
await this.toggleActive(name)
}
}
3 years ago
async canCall(from, to, method, message) {
const isSensitiveCall = sensitiveCalls[to] && sensitiveCalls[to].includes(method)
5 years ago
// Make sure the caller of this methods is the target plugin
if (to !== this.currentRequest.from) {
5 years ago
return false
}
// skipping native plugins' requests
3 years ago
if (isNative(from)) {
return true
}
3 years ago
// ask the user for permission
3 years ago
return await this.call('permissionhandler', 'askPermission', this.profiles[from], this.profiles[to], method, message, isSensitiveCall)
5 years ago
}
3 years ago
onPluginActivated(plugin) {
this.pluginLoader.set(plugin, this.actives.filter((plugin) => !this.isDependent(plugin)))
this.event.emit('activate', plugin)
this.emit('activate', plugin)
if (!requiredModules.includes(plugin.name)) _paq.push(['trackEvent', 'pluginManager', 'activate', plugin.name])
}
3 years ago
getAll() {
return Object.keys(this.profiles).map((p) => {
return this.profiles[p]
})
}
3 years ago
getIds() {
return Object.keys(this.profiles)
}
3 years ago
onPluginDeactivated(plugin) {
this.pluginLoader.set(plugin, this.actives.filter((plugin) => !this.isDependent(plugin)))
this.event.emit('deactivate', plugin)
_paq.push(['trackEvent', 'pluginManager', 'deactivate', plugin.name])
}
3 years ago
isDependent(name) {
return dependentModules.includes(name)
}
3 years ago
isRequired(name) {
// excluding internal use plugins
return requiredModules.includes(name)
}
3 years ago
async registeredPlugins() {
let plugins
try {
const res = await fetch(this.pluginsDirectory)
plugins = await res.json()
4 years ago
plugins = plugins.filter((plugin) => {
if (plugin.targets && Array.isArray(plugin.targets) && plugin.targets.length > 0) {
return (plugin.targets.includes('remix'))
}
return true
})
localStorage.setItem('plugins-directory', JSON.stringify(plugins))
} catch (e) {
console.log('getting plugins list from localstorage...')
const savedPlugins = localStorage.getItem('plugins-directory')
if (savedPlugins) {
try {
4 years ago
plugins = JSON.parse(savedPlugins)
} catch (e) {
console.error(e)
}
}
}
2 years ago
const testPluginName = localStorage.getItem('test-plugin-name')
const testPluginUrl = localStorage.getItem('test-plugin-url')
for (let plugin of loadLocalPlugins) {
// fetch the profile from the local plugin
try {
const profile = await fetch(`plugins/${plugin}/profile.json`)
const profileJson = await profile.json()
// remove duplicates
plugins = plugins.filter((p) => p.name !== profileJson.name && p.displayName !== profileJson.displayName)
// change url
profileJson.url = `plugins/${plugin}/index.html`
// add the local plugin
plugins.push(profileJson)
} catch (e) {
console.log(e)
}
}
return plugins.map(plugin => {
2 years ago
if (plugin.name === testPluginName) plugin.url = testPluginUrl
2 years ago
return new IframePlugin(plugin)
})
}
3 years ago
async registerContextMenuItems() {
await this.call('filePanel', 'registerContextMenuItem', {
id: 'contractflattener',
name: 'flattenAContract',
label: 'Flatten',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
sticky: true
})
await this.call('filePanel', 'registerContextMenuItem', {
id: 'nahmii-compiler',
name: 'compileCustomAction',
label: 'Compile for Nahmii',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
sticky: true
})
2 years ago
await this.call('filePanel', 'registerContextMenuItem', {
id: 'solidityumlgen',
name: 'generateCustomAction',
label: 'Generate UML',
2 years ago
type: [],
extension: ['.sol'],
path: [],
pattern: [],
sticky: true
})
await this.call('filePanel', 'registerContextMenuItem', {
id: 'doc-gen',
name: 'generateDocsCustomAction',
label: 'Generate Docs',
type: [],
extension: ['.sol'],
path: [],
pattern: [],
sticky: true
})
}
6 years ago
}
5 years ago
/** @class Reference loaders.
* A loader is a get,set based object which load a workspace from a defined sources.
* (localStorage, queryParams)
**/
class PluginLoader {
3 years ago
get currentLoader() {
5 years ago
return this.loaders[this.current]
}
5 years ago
3 years ago
constructor() {
const queryParams = new QueryParams()
this.donotAutoReload = ['remixd'] // that would be a bad practice to force loading some plugins at page load.
this.loaders = {}
this.loaders.localStorage = {
set: (plugin, actives) => {
const saved = actives.filter((name) => !this.donotAutoReload.includes(name))
localStorage.setItem('workspace', JSON.stringify(saved))
},
5 years ago
get: () => { return JSON.parse(localStorage.getItem('workspace')) }
}
this.loaders.queryParams = {
3 years ago
set: () => { /* Do nothing. */ },
get: () => {
const { activate } = queryParams.get()
if (!activate) return []
return activate.split(',')
}
}
this.current = queryParams.get().activate ? 'queryParams' : 'localStorage'
}
3 years ago
set(plugin, actives) {
5 years ago
this.currentLoader.set(plugin, actives)
}
3 years ago
get() {
5 years ago
return this.currentLoader.get()
}
}