Skip to content

Latest commit

 

History

History
307 lines (278 loc) · 13.1 KB

README.md

File metadata and controls

307 lines (278 loc) · 13.1 KB

MAGE Plugins

The MAGE server supports plugins for both the service and the web app. Both components define hooks which plugin packages can use to extend functionality.

Service plugins

Service plugins can extend the capabilities the MAGE service by adding integrations with external services or manipulating data in the MAGE database itself in some useful way. The Image plugin, for example, queries the MAGE database for image attachments, rotates them such that they correctly oriented with respect to EXIF meta-data, and generates smaller thumbnail versions of the photos so clients do not need to download full-size images unless the users requests them. The NGA-MSI plugin adds an integration with NGA's Maritime Safety Information (MSI) web service to provide geospatial feeds of MSI data directly to MAGE clients.

Hooks

The MAGE service defines several plugin hook APIs. A service plugin starts with the aptly-named InitPluginHook. Any service plugin must at least implement an init hook for the main service module to initialize the plugin. The init hook defines dependencies on core service components and performs any initialization tasks the plugin requires, such as database setup. Any Node module can be a plugin, as long as the module's export is an object conforming to the InitPluginHook type. For example,

export = {
  inject: {
    mageEventRepository: MageEventRepositoryToken
  },
  async init({ mageEventRepository: MageEventRepository }): Promise<any> {
    // initialize the plugin ...
  }
}

To inform the MAGE service that a given module is a plugin, you must list the module in the configuration you pass to the mage.service script. For example, you could create a package called @examples/mage-plugins. Within the package you can export an InitPluginHook object from the root index.js module, or you could export the hook object from a nested module such as /plugin1, or both. For the MAGE service to register those plugin hooks, you would start the service with switches like

npx mage.service --plugin @examples/mage-plugins
# or
npx mage.service --plugin @examples/mage-plugins/plugin1
# or
npx mage.service --plugin @examples/mage-plugins --plugin @examples/mage-plugins/plugin1

For a relatively robust example of dependency injection and initialization, see the Image plugin's init hook, which injects a plugin-scoped repository to persist plugin configuration, and integrates with the MAGE service's web layer to add plugin web routes.

Dependency injection

As mentioned above, a plugin's init hook defines dependencies on core service components, such as repositories that can interact with the database. During startup, the main service module injects the dependencies the plugin requests. The object a plugin module exports can include an inject key whose value is another object hash whose values are injection token symbols the MAGE service defines. When present, the MAGE service inspects a plugin's inject object and passes an object to the plugin's init function with the same keys as the inject object whose values are the components corresponding to the injection token values. For example, if a plugin module exports the following init hook

export = {
  inject: {
    eventRepo: MageEventRepositoryToken,
    observationRepoForEvent: ObservationRepositoryToken,
    userRepo: UserRepositoryToken
  },
  async init(injection): Promise<any> {
    // initialize the plugin ...
  }
}

... the injection argument the MAGE service passes to the init function will be an object with the shape

{
  eventRepo: instanceOfMageEventRepository,
  observationRepoForEvent: instanceOfObservationRepositoryFactory,
  userRepo: instanceOfUserRepository
}

This dependency injection token concept is based on Angular's mechanism.

Creating a service plugin

A service plugin is a Node module contained within an NPM package. To begin developing a plugin, create a new NPM package.

mkdir mage-service-plugins
cd mage-service-plugins
npm init --scope @examples

Follow the prompts to finish initializing your package.

Manually edit the package.json file to include a peer dependency on @ngageoint/mage.service at your chosen version.

  "peerDependencies": {
    "@ngageoint/mage.service": "^6.2.0"
  }

Then, add the MAGE service package to your plugin package. The easiest way is to globally install a release package tarball from GitHub.

npm i -g https://github.com/ngageoint/mage-server/releases/download/6.2.0/ngageoint-mage.service-6.2.0.tgz
npm link @ngageoint/mage.service

This installs the package tarball from the given GitHub release URL under NPM's global prefix on you system, then links the package from the global prefix location to your plugin package's node_modules directory to satisfy the peer dependency on @ngageoint/mage.service. Now, you should get clean output from npm ls.

$ npm ls
@examples/mage-service-plugins@1.0.0 <...>/mage/examples/mage-service-plugins
└── @ngageoint/mage.service@6.2.0 -> ./../../../../.nvm/versions/node/v16.15.1/lib/node_modules/@ngageoint/mage.service

Now, add TypeScript to take advantage type checking and code completion from the MAGE service types. Ideally you'll install the same version as the MAGE service's dependency to ensure compatibility with MAGE's .

npm i --save-dev typescript@^4.6.0

Then you add a tsconfig.json to configure TypeScript. You can use TypeScript's tsc command to generate the file.

npx tsc --init

Alternatively, you can copy the configuration from one of the MAGE server plugin packages. Adjust the TypeScript configuration to your liking, but generally you may find that placing your .ts source files into a src directory, and outputting transpiled JavaScript to a separate directory like lib or dist is a manageable arrangement. This is what the TypeScript configurations for the MAGE service and open source plugins specify.

Finally, you can start writing the code for your plugin. Begin with a module file that exports the main InitPluginHook. This will likely be the main module of your plugin package. If you configured TypeScript to transpile from the src directory and output to the lib directory as described above, create the file src/index.ts, and add the entry "main": "lib/index.js" to package.json. In src/index.ts, add the following.

export = {
  inject: {
  },
  async init(injection): Promise<any> {
  }
}

Then you can fill in the functionality of your plugin.

Deploying a service plugin

Create the plugin package

To get your plugin running in a MAGE server, you'll first need a running instance. Once you've setup a server instance, you can package your plugin to add to the server instance. If you used TypeScript to code your plugin as described above, your plugin should transpile JavaScript to a directory, such as lib. Ensure your plugin's package.json defines the plugin's entry point using the main entry as above. You could also use package exports. Also ensure that the package.json includes all of the transpiled JavaScript by adding an entry such as

"files": [
  "lib"
]

Now, from the base directory of your plugin package, run the npm pack command to create a package tarball of your plugin. This will create a file like examples-mage-service-plugins-1.0.0.tgz. Copy the package tarball to a persistent location, like where you have installed the MAGE server packages.

Install the plugin

You install plugin packages as dependencies of yourMAGE instance package, alongside the MAGE core packages. Assuming you instance package resides in a directory called mage, copy your plugin package tarball to the mage directory. Then, from the mage directory, run the following.

npm install --omit dev ./examples-mage-service-plugins-1.0.0.tgz

Your plugin package files should now be present in mage/node_modules/@examples/mage-service-plugins.

Enable the plugin

To enable your plugin, add the ID of the module that exports your PluginInitHook to the servicePlugins array in your configuration object. You can do this a few ways.

  1. Use the --plugin command line switch:
    mage.service --plugin @examples/mage-service-plugins
  2. Use a plugin JSON object from a file:
    mage.service -P plugins.json
    where plugins.json contains
    {
      "servicePlugins": [
        "@examples/mage-service-plugins"
      ]
    }
  3. Use a full MAGE configuration object that references the plugin that exports the full configuration object:
    mage.service -C /etc/mage.js
    where mage.json contains something like
    module.exports = {
      mage: {
        // other config options
        plugins: {
          servicePlugins: [
            '@examples/mage-service-plugins'
          ]
        }
      }
    }
  4. Use an environment variable:
    MAGE_PLUGINS='{ "servicePlugins": [ "@examples/mage-service-plugins" ] }' mage.service

Multiple package plugins

You can provide multiple PluginInitHook modules in one package if you wish. To enable them, include the ID of each module that exports a PluginInitHook in the servicePlugins array:

mage.service --plugin @examples/mage-service-plugins/plugin1 --plugin @examples/mage-service-plugins/plugin2 # ...

or with plugins JSON file such as mage.service -P plugins.json:

{
  "servicePlugins": [
    "@examples/mage-service-plugins/plugin1",
    "@examples/mage-service-plugins/plugin2"
  ]
}

Be sure that the module IDs you list are resolvable through your package structure. If your plugin package code resides in the lib directory and the package.json only has a main entry, such as

{
  "main": "lib/index.js"
}

then the registered plugin module IDs will need to include your package's top-level directory:

{
  "servicePlugins": [
    "@examples/mage-service-plugins/lib/plugin1",
    "@examples/mage-service-plugins/lib/plugin2"
  ]
}

If your package.json includes your plugin modules as entry points in the exports entry, as below, then you will not need to include the top-level directory in the module IDs. Be sure to also include an entry point to allow the MAGE service to load the package.json file from your plugin package.

{
  "exports": {
    ".": "./lib/index.js",
    "./plugin1.js": "./lib/plugin1.js",
    "./plugin2.js": "./lib/plugin2.js",
    "./package.json": "./package.json"
  }
}

Web UI plugins

MAGE provides a library with Angular CLI integration to help create plugin packages for the MAGE web app. Below are the basic steps to create a MAGE web UI plugin.

  1. Initialize an Angular workspace.
    mkdir example
    cd example
    git init . # if you want version control
    npx @angular/cli@14 new --directory . --create-application false @example/mage-plugins.workspace
  2. Add the MAGE web core library.
    npm run ng -- add @ngageoint/mage.web-core-lib
  3. Generate a new MAGE web UI plugin library.
    npm run ng -- generate @ngageoint/mage.web-core-lib:plugin-library @example/mage-plugins.plugin1
    That creates a new Angular library project in projects/example/mage-plugins.plugin1
  4. Add content to your plugin library.
  5. Build your plugin bundle.
    npm run ng -- build @example/mage-plugins.plugin1
    The output from the build will be in dist/example/mage-plugins.plugin1.
  6. Package your plugin.
    npm pack dist/example/mage-plugins.plugin1
    
    Take note that you must create the package from the project's output subdirectory of the dist directory, not from the project root like a typical Node.js project. This will create an NPM package tarball like example-mage-plugins.plugin1-0.0.1.tgz, which contains the AMD module bundle that the MAGE web app can load. You can publish this tarball to NPM or install the tarball directly to your MAGE instance.
  7. Install your plugin to your MAGE instance. TODO: ref above

Hooks

See the available plugin hook definitions.

Testing

TODO: create a showcase app; run a dev server