UNPKG

6.88 kBMarkdownView Raw
1# Engine Core
2
3This is the core library used to create a new plugin engine.
4
5| Name | Latest Version |
6| -----------------------------------------------| :------------------: |
7| [@remixproject/engine](.) | [![badge](https://img.shields.io/npm/v/@remixproject/engine.svg?style=flat-square)](https://www.npmjs.com/package/@remixproject/engine) |
8
9Use this library if you want to create an engine **for a new environment**.
10
11If you want to create an engine for an existing envrionment, use the specific library. For example :
12- Engine on the web : [@remixproject/engine-web](../web)
13- Engine on node : [@remixproject/engine-node](../node)
14- Engine on vscode : [@remixproject/engine-vscode](../vscode)
15
16## Tutorial
17
181. [Getting Started](doc/tutorial/1-getting-started.md)
192. [Plugin Communication](doc/tutorial/2-plugin-communication.md)
203. [Host a Plugin with UI](doc/tutorial/3-hosted-plugin.md)
214. [External Plugins](doc/tutorial/4-external-plugins.md)
225. [Plugin Service](doc/tutorial/5-plugin-service.md)
23
24## API
25
26| API | Description |
27| ----------------------------| :----------------------------------: |
28| [Engine](./api/engine.md) | Register plugins & redirect messages |
29| [Manager](./api/manager.md) | Activate & Deactive plugins |
30
31
32## Connector
33
34The plugin connector is the main component of `@remixproject/engine`, it defines how an external plugin can connect to the engine. Checkout the [documentation](./doc/connector).
35
36--------------
37
38## Getting started
39```
40npm install @remixproject/engine
41```
42
43The engine works a with two classes :
44- `PluginManager`: manage activation/deactivation
45- `Engine`: manage registration & communication
46
47```typescript
48import { PluginManager, Engine, Plugin } from '@remixproject/engine'
49
50const manager = new PluginManager()
51const engine = new Engine()
52const plugin = new Plugin({ name: 'plugin-name' })
53
54// Wait for the manager to be loaded
55await engine.onload()
56
57// Register plugins
58engine.register([manager, plugin])
59
60// Activate plugins
61manager.activatePlugin('plugin-name')
62```
63
64### Registration
65The registration makes the plugin available for activation in the engine.
66
67To register a plugin you need:
68- `Profile`: The ID card of your plugin.
69- `Plugin`: A class that expose the logic of the plugin.
70
71```typescript
72const profile = {
73 name: 'console',
74 methods: ['print']
75}
76
77class Console extends Plugin {
78 constructor() {
79 super(profile)
80 }
81 print(msg: string) {
82 console.log(msg)
83 }
84}
85const consolePlugin = new Console()
86
87// Register plugins
88engine.register(consolePlugin)
89```
90
91> In the future, this part will be manage by a `Marketplace` plugin.
92
93### Activation
94The activation process is managed by the `PluginManager`.
95
96Activating a plugin makes it visible to other plugins. Now they can communicate.
97
98```typescript
99manager.activatePlugin('console')
100```
101
102> The `PluginManager` is a plugin itself.
103
104### Communication
105`Plugin` exposes a simple interface for communicate between plugins :
106
107- `call`: Call a method exposed by another plugin (This returns always a Promise).
108- `on`: Listen on event emitted by another plugin.
109- `emit`: Emit an event broadcasted to all listeners.
110
111This code will call the method `print` from the plugin `console` with the parameter `'My message'`.
112```typescript
113plugin.call('console', 'print', 'My message')
114```
115
116### Full code example
117```typescript
118import { PluginManager, Engine, Plugin } from '@remixproject/engine'
119const profile = {
120 name: 'console',
121 methods: ['print']
122}
123
124class Console extends Plugin {
125 constructor() {
126 super(profile)
127 }
128 print(msg: string) {
129 console.log(msg)
130 }
131}
132
133const manager = new PluginManager()
134const engine = new Engine()
135const emptyPlugin = new Plugin({ name: 'empty' })
136const consolePlugin = new Console()
137
138// Register plugins
139engine.register([manager, plugin, consolePlugin])
140
141// Activate plugins
142manager.activatePlugin(['empty', 'console'])
143
144// Plugin communication
145emptyPlugin.call('console', 'print', 'My message')
146```
147
148--------------
149
150## Permission
151The Engine comes with a permission system to protect the user from hostile plugins.
152There are two levels:
153- **Global**: at the `PluginManager` level.
154- **Local**: at the `Plugin` level.
155
156### Global Permission
157Communication between plugins goes through the `PluginManager`'s permission system :
158
159```typescript
160canActivatePlugin(from: Profile, to: Profile): Promise<boolean>
161```
162Used when a plugin attempts to activate another one. By default when plugin "A" calls plugin "B", if "B" is not deactivated, "A" will attempt to active it before performing the call.
163
164```typescript
165canDeactivatePlugin(from: Profile, to: Profile): Promise<boolean>
166```
167Used when a plugin attempts to deactivate another one. By default only the `manager` and the plugin itself can deactivate a plugin.
168
169```typescript
170canCall(from: Profile, to: Profile, method: string, message: string): Promise<boolean>
171```
172Used by a plugin to protect a method (see Local Permission below).
173
174**Tip**: Each method returns a `Promise`. It's good practice to ask the user's permission through a GUI.
175
176
177### Local Permission
178A plugin can protect some critical API by asking for user's permission:
179
180```typescript
181askUserPermission(method: string, message: string): Promise<boolean>
182```
183This method will call the `canCall` method from the `PluginManager` under the hood with the right params.
184
185In this example, a FileSystem plugin protects the `write` method :
186```typescript
187class FileSystemPlugin extends Plugin {
188
189 async write() {
190 const from = this.currentRequest
191 const canCall = await this.askUserPermission('write')
192 if (!canCall) {
193 throw new Error('You do not have the permission to call method "canCall" from "fs"')
194 }
195 }
196}
197```
198
199### ⚠️ When currentRequest is Mistaken ⚠️
200The permission system heavily relies on a queue of calls managed by the `Engine` and the property `currentRequest`.
201If you're calling a method from the plugin directly (without using the `Engine`) it will bypass the permission system. In this case, the results of `currentRequest` may **NOT** be correct.
202
203Example :
204```typescript
205const fs = new FileSystemPlugin()
206const manager = new PluginManager()
207...
208fs.call('manager', 'activatePlugin', 'terminal') // At this point `currentRequest` in manager is "fs"
209manager.deactivatePlugin('editor') // This will fail
210```
211
212In the code above :
2131. call to "activatePlugin" to enter the queue of the manager.
2142. manager's `currentRequest` is "fs".
2153. manager calls its own `deactivatePlugin` method.
2164. **as the call doesn't use the Engine, it doesn't enter in the queue**: so `currentRequest` is still "fs".
2175. `deactivatePlugin` checks the `currentRequest`. So now `currentRequest` incorrectly thinks that "fs" is trying to deactivate "terminal" and will not allow it.
\No newline at end of file