|
1 | | -## MAGE Plugins |
2 | | - |
3 | | -### MAGE EPIC plugin |
4 | | - |
5 | | -The mage-epic plugin will replicate MAGE observation to an ESRI server using [ESRI's REST API](http://resources.arcgis.com/en/help/rest/apiref/). |
6 | | - |
7 | | -Configuration: |
8 | | - |
9 | | -* esri - ESRI server configuration |
10 | | - * url - ESRI url |
11 | | - * host - ESRI root url |
12 | | - * site - ESRI site partial url |
13 | | - * restServices - ESRI rest services partial url |
14 | | - * folder - ESRI folder partial url |
15 | | - * serviceName - ESRI service name partial url |
16 | | - * serviceType - ESRI service type partial url |
17 | | - * layerId - ESRI layerId partial url |
18 | | - * observations - MAGE observation mapping. Add one for each event id (i.e. 7 maps MAGE fields from eventId 7) |
19 | | - * enable - turn on/off sync'ing of this event |
20 | | - * interval - frequency (seconds) at which the plugin will sync this event to the ESRI server. |
21 | | - * fields - array that maps each observation field to an ESRI fields |
22 | | - * type - primitive type |
23 | | - * mage - obseravtion field name in MAGE |
24 | | - * esri - ESRI field name |
25 | | - * attachments |
26 | | - * enable - turn on/off attachment sync'ing |
27 | | - * interval - frequency (seconds) at which the plugin will sync attachments to the ESRI server. |
28 | | -* mongodb |
29 | | - * url - url scheme for the mongodb database. This should be the same mongodb schema as used in your main MAGE configuration. |
30 | | - * poolSize - mongodb connection pool size for this plugin |
| 1 | +# MAGE Plugins |
31 | 2 |
|
32 | | -```json |
| 3 | +The MAGE server supports plugins for both the service and the web app. Both components define hooks which plugin |
| 4 | +packages can use to extend functionality. |
| 5 | + |
| 6 | +## Service plugins |
| 7 | + |
| 8 | +Service plugins can extend the capabilities the MAGE service by adding integrations with external services or |
| 9 | +manipulating data in the MAGE database itself in some useful way. The [Image](./image/) plugin, for example, queries |
| 10 | +the MAGE database for image attachments, rotates them such that they correctly oriented with respect to EXIF meta-data, |
| 11 | +and generates smaller thumbnail versions of the photos so clients do not need to download full-size images unless |
| 12 | +the users requests them. The [NGA-MSI](./nga-msi/) plugin adds an integration with NGA's Maritime Safety Information |
| 13 | +(MSI) web service to provide geospatial feeds of MSI data directly to MAGE clients. |
| 14 | + |
| 15 | +### Hooks |
| 16 | + |
| 17 | +The MAGE service defines several plugin [hook APIs](../service/src/plugins.api/). A service plugin starts with the |
| 18 | +aptly-named [`InitPluginHook`](../service/src/plugins.api/index.ts). Any service plugin must at least implement an |
| 19 | +init hook for the main service module to initialize the plugin. The init hook defines dependencies on core service |
| 20 | +components and performs any initialization tasks the plugin requires, such as database setup. Any Node module can be |
| 21 | +a plugin, as long as the module's export is an object conforming to the `InitPluginHook` type. For example, |
| 22 | +```typescript |
| 23 | +export = { |
| 24 | + inject: { |
| 25 | + mageEventRepository: MageEventRepositoryToken |
| 26 | + }, |
| 27 | + async init({ mageEventRepository: MageEventRepository }): Promise<any> { |
| 28 | + // initialize the plugin ... |
| 29 | + } |
| 30 | +} |
| 31 | +``` |
| 32 | +To inform the MAGE service that a given module is a plugin, you must list the module in the configuration you pass to |
| 33 | +the [`mage.service`](../service/bin/mage.service.js) script. For example, you could create a package called |
| 34 | +`@examples/mage-plugins`. Within the package you can export an `InitPluginHook` object from the root `index.js` module, |
| 35 | +or you could export the hook object from a nested module such as `/plugin1`, or both. For the MAGE service to register |
| 36 | +those plugin hooks, you would start the service with switches like |
| 37 | +```bash |
| 38 | +npx mage.service --plugin @examples/mage-plugins |
| 39 | +# or |
| 40 | +npx mage.service --plugin @examples/mage-plugins/plugin1 |
| 41 | +# or |
| 42 | +npx mage.service --plugin @examples/mage-plugins --plugin @examples/mage-plugins/plugin1 |
| 43 | +``` |
| 44 | + |
| 45 | +For a relatively robust example of dependency injection and initialization, see the [Image plugin's](./image/service/src/index.ts) |
| 46 | +init hook, which injects a plugin-scoped repository to persist plugin configuration, and integrates with the MAGE |
| 47 | +service's web layer to add plugin web routes. |
| 48 | + |
| 49 | +### Dependency injection |
| 50 | + |
| 51 | +As mentioned above, a plugin's init hook defines dependencies on core service components, such as repositories that |
| 52 | +can interact with the database. During startup, the [main service module](../service/src/app.ts) injects the |
| 53 | +dependencies the plugin requests. The object a plugin module exports can include an `inject` key whose value is |
| 54 | +another object hash whose values are injection token symbols the MAGE service defines. When present, the MAGE service |
| 55 | +inspects a plugin's `inject` object and passes an object to the plugin's `init` function with the same keys as the |
| 56 | +`inject` object whose values are the components corresponding to the injection token values. For example, if a plugin |
| 57 | +module exports the following init hook |
| 58 | +```typescript |
| 59 | +export = { |
| 60 | + inject: { |
| 61 | + eventRepo: MageEventRepositoryToken, |
| 62 | + observationRepoForEvent: ObservationRepositoryToken, |
| 63 | + userRepo: UserRepositoryToken |
| 64 | + }, |
| 65 | + async init(injection): Promise<any> { |
| 66 | + // initialize the plugin ... |
| 67 | + } |
| 68 | +} |
| 69 | +``` |
| 70 | +... the `injection` argument the MAGE service passes to the `init` function will be an object with the shape |
| 71 | +```typescript |
33 | 72 | { |
34 | | - "enable": false, |
35 | | - "esri": { |
36 | | - "url": { |
37 | | - "host": "<root ERSI server>", |
38 | | - "site": "<site partial>", |
39 | | - "restServices": "<rest services partial>", |
40 | | - "folder": "<folder partial>", |
41 | | - "serviceName": "<service name partial>", |
42 | | - "serviceType": "<service type partial>", |
43 | | - "layerId": "<layer id partial>" |
44 | | - }, |
45 | | - "observations": { |
46 | | - "7": { |
47 | | - "enable": true, |
48 | | - "interval": 30, |
49 | | - "fields": [{ |
50 | | - "type": "Date", |
51 | | - "mage": "timestamp", |
52 | | - "esri": "EVENTDATE" |
53 | | - },{ |
54 | | - "type": "Type", |
55 | | - "mage": "type", |
56 | | - "esri": "TYPE" |
57 | | - },{ |
58 | | - "type": "String", |
59 | | - "mage": "EVENTLEVEL", |
60 | | - "esri": "EVENTLEVEL" |
61 | | - },{ |
62 | | - "type": "String", |
63 | | - "mage": "TEAM", |
64 | | - "esri": "TEAM" |
65 | | - },{ |
66 | | - "type": "String", |
67 | | - "mage": "DESCRIPTION", |
68 | | - "esri": "DESCRIPTION" |
69 | | - }] |
70 | | - } |
71 | | - }, |
72 | | - "attachments": { |
73 | | - "enable":true, |
74 | | - "interval": 60 |
75 | | - } |
| 73 | + eventRepo: instanceOfMageEventRepository, |
| 74 | + observationRepoForEvent: instanceOfObservationRepositoryFactory, |
| 75 | + userRepo: instanceOfUserRepository |
| 76 | +} |
| 77 | +``` |
| 78 | +This dependency injection token concept is based on [Angular's](https://angular.io/guide/dependency-injection-overview) |
| 79 | +mechanism. |
| 80 | + |
| 81 | +### Creating a service plugin |
| 82 | + |
| 83 | +A service plugin is a Node module contained within an NPM package. To begin developing a plugin, create a new NPM |
| 84 | +package. |
| 85 | +```bash |
| 86 | +mkdir mage-service-plugins |
| 87 | +cd mage-service-plugins |
| 88 | +npm init --scope @examples |
| 89 | +``` |
| 90 | +Follow the prompts to finish initializing your package. |
| 91 | + |
| 92 | +Manually edit the `package.json` file to include a peer dependency on `@ngageoint/mage.service` at your chosen version. |
| 93 | +```json |
| 94 | + "peerDependencies": { |
| 95 | + "@ngageoint/mage.service": "^6.2.0" |
| 96 | + } |
| 97 | +``` |
| 98 | + |
| 99 | +Then, add the MAGE service package to your plugin package. The easiest way is to globally install a release package |
| 100 | +tarball from [GitHub](https://github.com/ngageoint/mage-server/releases). |
| 101 | +```bash |
| 102 | +npm i -g https://github.com/ngageoint/mage-server/releases/download/6.2.0/ngageoint-mage.service-6.2.0.tgz |
| 103 | +npm link @ngageoint/mage.service |
| 104 | +``` |
| 105 | +This installs the package tarball from the given GitHub release URL under NPM's global prefix on you system, then links |
| 106 | +the package from the global prefix location to your plugin package's `node_modules` directory to satisfy the peer |
| 107 | +dependency on `@ngageoint/mage.service`. Now, you should get clean output from `npm ls`. |
| 108 | +```bash |
| 109 | +$ npm ls |
| 110 | +@examples/ [email protected] <... >/mage/examples/mage-service-plugins |
| 111 | +└── @ngageoint/ [email protected] - > ./../../../../.nvm/versions/node/v16.15.1/lib/node_modules/@ngageoint/mage.service |
| 112 | +``` |
| 113 | + |
| 114 | +Now, add TypeScript to take advantage type checking and code completion from the MAGE service types. Ideally you'll |
| 115 | +install the same version as the MAGE service's dependency to ensure compatibility with MAGE's . |
| 116 | +```bash |
| 117 | +npm i --save-dev typescript@^4.6.0 |
| 118 | +``` |
| 119 | +Then you add a [`tsconfig.json`](https://www.typescriptlang.org/tsconfig) to configure TypeScript. You can use |
| 120 | +TypeScript's `tsc` command to generate the file. |
| 121 | +```bash |
| 122 | +npx tsc --init |
| 123 | +``` |
| 124 | +Alternatively, you can copy the configuration from one of the MAGE server [plugin](./nga-msi/tsconfig.json) |
| 125 | +[packages](./image/service/tsconfig.json). Adjust the TypeScript configuration to your liking, but generally you may |
| 126 | +find that placing your `.ts` source files into a `src` directory, and outputting transpiled JavaScript to a separate |
| 127 | +directory like `lib` or `dist` is a manageable arrangement. This is what the TypeScript configurations for the MAGE |
| 128 | +service and open source plugins specify. |
| 129 | + |
| 130 | +Finally, you can start writing the code for your plugin. Begin with a module file that exports the main |
| 131 | +`InitPluginHook`. This will likely be the [main module](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#main) |
| 132 | +of your plugin package. If you configured TypeScript to transpile from the `src` directory and output to the `lib` |
| 133 | +directory as described above, create the file `src/index.ts`, and add the entry ``"main": "lib/index.js"` to |
| 134 | +`package.json`. In `src/index.ts`, add the following. |
| 135 | +```typescript |
| 136 | +export = { |
| 137 | + inject: { |
76 | 138 | }, |
77 | | - "mongodb": { |
78 | | - "url": "mongodb://localhost/magedb", |
79 | | - "poolSize": 1 |
80 | | - } |
| 139 | + async init(injection): Promise<any> { |
| 140 | + } |
81 | 141 | } |
82 | 142 | ``` |
| 143 | +Then you can fill in the functionality of your plugin. |
| 144 | + |
| 145 | +### Deploying a service plugin |
83 | 146 |
|
84 | | -### MAGE RAGE plugin |
| 147 | +#### Create the plugin package |
85 | 148 |
|
86 | | -The mage-rage plugin will replicate all MAGE data to another mage server |
| 149 | +To get your plugin running in a MAGE server, you'll first need a [running instance](../README.md#running-a-mage-server). |
| 150 | +Once you've setup a server instance, you can package your plugin to add to the server instance. If you used TypeScript |
| 151 | +to code your plugin as described above, your plugin should transpile JavaScript to a directory, such as `lib`. Ensure |
| 152 | +your plugin's `package.json` defines the plugin's entry point using the `main` entry as above. You could also use |
| 153 | +[package exports](https://nodejs.org/api/packages.html#package-entry-points). Also ensure that the `package.json` |
| 154 | +[includes](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#files) all of the transpiled JavaScript by adding |
| 155 | +an entry such as |
| 156 | +```json |
| 157 | +"files": [ |
| 158 | + "lib" |
| 159 | +] |
| 160 | +``` |
| 161 | +Now, from the base directory of your plugin package, run the [`npm pack`](https://docs.npmjs.com/cli/v8/commands/npm-pack) |
| 162 | +command to create a package tarball of your plugin. This will create a file like `examples-mage-service-plugins-1.0.0.tgz`. |
| 163 | +Copy the package tarball to a persistent location, like where you have [installed](../README.md#install-mage-server-packages) |
| 164 | +the MAGE server packages. |
| 165 | + |
| 166 | +#### Enable the plugin |
87 | 167 |
|
88 | | -Configuration: |
| 168 | +To enable your plugin, add the ID of the module that exports your `PluginInitHook` to the `servicePlugins` array in your [configuration object](../README.md#configuration-merging). You can do this a few ways. |
| 169 | +1. Use the `--plugin` command line switch: |
| 170 | + ```bash |
| 171 | + mage.service --plugin @examples/mage-service-plugins |
| 172 | + ``` |
| 173 | +2. Use a plugin JSON object from a file: |
| 174 | + ```bash |
| 175 | + mage.service -P plugins.json |
| 176 | + ``` |
| 177 | + where `plugins.json` contains |
| 178 | + ```json |
| 179 | + { |
| 180 | + "servicePlugins": [ |
| 181 | + "@examples/mage-service-plugins" |
| 182 | + ] |
| 183 | + } |
| 184 | + ``` |
| 185 | +3. Use a full MAGE configuration object that references the plugin that exports the full configuration object: |
| 186 | + ```bash |
| 187 | + mage.service -C /etc/mage.js |
| 188 | + ``` |
| 189 | + where `mage.json` contains something like |
| 190 | + ```javascript |
| 191 | + module.exports = { |
| 192 | + mage: { |
| 193 | + // other config options |
| 194 | + plugins: { |
| 195 | + servicePlugins: [ |
| 196 | + '@examples/mage-service-plugins' |
| 197 | + ] |
| 198 | + } |
| 199 | + } |
| 200 | + } |
| 201 | + ``` |
| 202 | +4. Use an environment variable: |
| 203 | + ```bash |
| 204 | + MAGE_PLUGINS='{ "servicePlugins": [ "@examples/mage-service-plugins" ] }' mage.service |
| 205 | + ``` |
89 | 206 |
|
90 | | -* url - MAGE server url to replicate data to. |
91 | | -* credentials - MAGE replication server credentials used for authentication. |
92 | | - * username - username on MAGE replication server |
93 | | - * uid - device uid |
94 | | - * password - password |
95 | | -* interval - frequency (seconds) at which the plugin will sync data to the replication server. |
96 | | -* mongodb |
97 | | - * url - url scheme for the mongodb database. This should be the same mongodb schema as used in your main MAGE configuration. |
98 | | - * poolSize - mongodb connection pool size for this plugin |
| 207 | +#### Multiple package plugins |
99 | 208 |
|
| 209 | +You can provide multiple `PluginInitHook` modules in one package if you wish. To enable them, include the ID of each |
| 210 | +module that exports a `PluginInitHook` in the `servicePlugins` array: |
| 211 | +```bash |
| 212 | +mage.service --plugin @examples/mage-service-plugins/plugin1 --plugin @examples/mage-service-plugins/plugin2 # ... |
| 213 | +``` |
| 214 | +or with plugins JSON file such as `mage.service -P plugins.json`: |
| 215 | +```json |
| 216 | +{ |
| 217 | + "servicePlugins": [ |
| 218 | + "@examples/mage-service-plugins/plugin1", |
| 219 | + "@examples/mage-service-plugins/plugin2" |
| 220 | + ] |
| 221 | +} |
| 222 | +``` |
| 223 | +Be sure that the module IDs you list are [resolvable](https://nodejs.org/api/modules.html#all-together) through your |
| 224 | +package structure. If your plugin package code resides in the `lib` directory and the `package.json` only has a `main` |
| 225 | +entry, such as |
| 226 | +```json |
| 227 | +{ |
| 228 | + "main": "lib/index.js" |
| 229 | +} |
| 230 | +``` |
| 231 | +then the registered plugin module IDs will need to include your package's top-level directory: |
100 | 232 | ```json |
101 | 233 | { |
102 | | - "enable": false, |
103 | | - "url": "", |
104 | | - "credentials": { |
105 | | - "username": "", |
106 | | - "uid": "", |
107 | | - "password": "" |
108 | | - }, |
109 | | - "interval": 60, |
110 | | - "mongodb": { |
111 | | - "url": "mongodb://localhost/magedb", |
112 | | - "poolSize": 1 |
113 | | - } |
| 234 | + "servicePlugins": [ |
| 235 | + "@examples/mage-service-plugins/lib/plugin1", |
| 236 | + "@examples/mage-service-plugins/lib/plugin2" |
| 237 | + ] |
114 | 238 | } |
115 | 239 | ``` |
| 240 | +If your `package.json` includes your plugin modules as [entry points](https://nodejs.org/api/packages.html#package-entry-points) |
| 241 | +in the `exports` entry, as below, then you will not need to include the top-level directory in the module IDs. Be sure |
| 242 | +to also include an entry point to allow the MAGE service to load the `package.json` file from your plugin package. |
| 243 | +```json |
| 244 | +{ |
| 245 | + "exports": { |
| 246 | + ".": "./lib/index.js", |
| 247 | + "./plugin1.js": "./lib/plugin1.js", |
| 248 | + "./plugin2.js": "./lib/plugin2.js", |
| 249 | + "./package.json": "./package.json" |
| 250 | + } |
| 251 | +} |
| 252 | +``` |
| 253 | + |
| 254 | +## Web UI plugins |
| 255 | + |
| 256 | +More to come soon. |
0 commit comments