See below how to develop plugins for the in-house bot. This is a lightweight alternative to developing your own chat bot.
Table of Contents
The in-house bot has a number of lifecycle methods that can be implemented by plugins. The bot will call your implementation at the appropriate time, with the predetermined (but not yet well-documented) arguments.
There are two components to plugins, code and configuration. If the code says "call the FBI and check if the applicant's kids eat their vegetables," the configuration says when to make this call, the API key to the FBI's Veggie Spy API, etc. The code and configuration are deployed and managed separately.
When developing a plugin, it's currently easiest to develop it inside this project itself. Later, you can always export it to a separate npm module. Place your plugin into the folder ../src/in-house-bot/plugins/
Plugins are attached to the in-house bot in in-house-bot/index.ts. Eventually they will be attached automatically based on some configuration object, but today you need to add something like this:
if (handleMessages) {
// this will enable your plugin to handle incoming messages from customers (e.g. forms)
attachPlugin({ name: 'lens' })
}
Plugins can implement (export) the following lifecycle methods (see ../src/in-house-bot/plugin-types.d.ts
for typings):
-
willSend
- called before a message is sent to a user -
willRequestForm
- called before a user is asked to fill out a form. If you want to prefill the form for the user, this is where to do it. -
willCreateApplication
- called before a new application is created for a user -
willDenyApplication
- called before an application is denied -
willApproveApplication
- called before an application is approved -
willDenyApplication
- called before an application is denied -
getRequiredForms
- called to determine which forms are required from a user for a product -
validateForm
- called to validate a user-sent form -
onFormsCollected
- called when the forms for a product have been collected from a user -
onmessage
- handle an incoming message (the most general handler for user message) -
onmessage:[model.id]
- handle a type of incoming message, e.g. tradle.CustomerWaiting, or tradle.ProductRequest -
onmessage:[model.subClassOf]
- handle incoming messages that subclass a type, e.g. to handle all subclasses of tradle.Form, export 'onmessage:tradle.Form' -
onmessage
- handle an incoming message (the most general handler for user message)
onResourceChanged
- called when a resource changed, e.g. thelastName
value changed on atradle.Name
formonCheckStatusChanged
- a special case ofonResourceChanged
, for convenience purposes
The configuration for plugins is kept separate and managed and deployed with tradleconf. Configuration is deployed separately, and more rapidly than code.
For example, the configuration for the lens plugin in Safe-Re (a default Tradle Sandbox provider, see https://app.tradle.io) looks like this:
"plugins" {
...
"lens": {
"nl.tradle.DigitalPassport": {
"tradle.PhotoID": "io.safere.lens.PhotoID"
},
"tradle.pg.CustomerOnboarding": {
"tradle.Address": "io.safere.lens.Address"
}
},
...
}
The format of your configuration object is up to you. To make sure someone doesn't deploy invalid configuration, you should provide a validateConf
export in your plugin. For example, see the validateConf export in the Lens plugin.
The plugin below intercepts inbound messages carrying tradle.Shmortz
, whatever that is. It calls a third party API to evaluate the received data, and potentially approves the application.
Before building your plugin, you probably want to create some kind of API wrapper for the plugin to use, to make things more easily testable.
import { Conf, IPluginOpts } from '../types'
export const createPlugin = (
// other components
{
// src/bot/index.ts Bot instance
bot,
// products strategy api
productsAPI
},
// plugin-specific
{
// a logger for the plugin to use
logger,
// configuration as designed for this plugin
conf
}
) => {
const myApi = new MyApi(conf.credentials)
const handleShmortz = async (req) => {
const {
user,
application,
payload
} = req
const result = await myApi.askMyDataSource({
glopz: Math.sqrt(payload.googa)
fleg: payload.pantsColor / payload.brainTriangles
})
if (result.success) {
await productsAPI.approveApplication({ req, user, application })
} else {
await productsAPI.sendSimpleMessage({
req,
to: user,
message: 'your shmortz is below the pink average. Have a grafkl, it usually helps'
})
}
}
const didApproveApplication = async ({ req, user, application }) => {
console.log('whatever')
}
return {
didApproveApplication,
[`onmessage:tradle.Shmortz`]: handleShmortz
}
}
export const validateConf = async ({ conf, pluginConf }: {
conf: Conf,
pluginConf: any
}) => {
// .. validate your plugin configuration
}
See some simple examples in src/in-house-bot/plugins
: set-name
, ts-and-cs
, keep-fresh
, lens
, form-prefill
, required-forms
See more complex examples: centrix
, complyAdvantage
, onfido
, deployment
, remediation
productsAPI
doesn't have typescript typings yet, but once it does, you'll have auto-completion for your plugin coding.
To take advantage of static type checking as it becomes more available, use the template below for building your plugin in src/in-house-bot/plugins/
.
import { Conf, CreatePlugin, IPluginLifecycleMethods } from '../types'
import { MyApi } from 'my-api'
export const createPlugin:CreatePlugin<MyApi> = (
// other components
{
// src/bot/index.ts Bot instance
bot,
// products strategy api
productsAPI
},
// plugin-specific
{
// a logger for the plugin to use
logger,
// configuration as designed for this plugin
conf
}
) => {
const { bot, productsAPI } = components
// "conf" is from the block you put in conf/bot.json and deployed
const { conf, logger } = opts
const api = new MyApi({ whatever: 'opts' })
const plugin:IPluginLifecycleMethods = {
['onmessage:tradle.Form']: async (req) => {
const { user, payload } = req
await api.runSomeQuery({ user, payload })
},
onFormsCollected: async ({ req }) => {
const { user, payload } = req
await api.runSomeOtherQuery({ user, payload })
}
}
return {
api,
plugin
}
}
export const validateConf = ({
conf: Conf
pluginConf: any
}) => {
// validate pluginConf
}