-
HullClientHullClient -
optionsObject (optional, default{})options.hostSecretstring? secret to sign req.hull.tokenoptions.port(Number | string)? port on which expressjs application should be startedoptions.clientConfigObject? additionalHullClientconfiguration (optional, default{})options.instrumentationObject? override default InstrumentationAgentoptions.cacheObject? override default CacheAgentoptions.queueObject? override default QueueAgentoptions.connectorNamestring? force connector name - if not provided will be taken from manifest.jsonoptions.skipSignatureValidationboolean? skip signature validation on notifications (for testing only)options.timeout(number | string)? global HTTP server timeout - format is parsed bymsnpm packageoptions.segmentFilterSetting
This method applies all features of Hull.Connector to the provided application:
- serving
/manifest.json,/readmeand/endpoints - serving static assets from
/distand/assetsdirectiories - rendering
/views/*.htmlfiles withejsrenderer - timeouting all requests after 25 seconds
- adding Newrelic and Sentry instrumentation
- initiating the wole Context Object
- handling the
hullTokenparameter in a default way
appexpress expressjs application
Returns express expressjs application
This is a supplement method which calls app.listen internally and also terminates instrumentation of the application calls.
appexpress expressjs application
Returns http.Server
General utilities
Extends TransientError
This is an error related to wrong connector configuration. It's a transient error, but it makes sense to retry the payload only after the connector settings update.
Extends Error
This is an error which should be handled by the connector implementation itself.
Rejecting or throwing this error without try/catch block will be treated as unhandled error.
function validationFunction() {
throw new LogicError("Validation error", { action: "validation", payload: });
}Extends TransientError
This is a subclass of TransientError. It have similar nature but it's very common during connector operations so it's treated in a separate class. Usually connector is able to tell more about when exactly the rate limit error will be gone to optimize retry strategy.
Extends TransientError
This error means that 3rd party API resources is out of sync comparing to Hull organization state. For example customer by accident removed a resource which we use to express segment information (for example user tags, user sub lists etc.) So this is a TransientError which could be retried after forcing "reconciliation" operation (which should recreate missing resource)
Extends Error
This is a transient error related to either connectivity issues or temporary 3rd party API unavailability.
When using superagentErrorPlugin it's returned by some errors out-of-the-box.
An object that's available in all action handlers and routers as req.hull.
It's a set of parameters and modules to work in the context of current organization and connector instance.
This is a set of additional helper functions being exposed at req.hull.helpers. They allow to perform common operation in the context of current request. They are similar o req.hull.client.utils, but operate at higher level, ensure good practises and should be used in the first place before falling back to raw utils.
Helper function to handle JSON extract sent to batch endpoint
-
ctxObject Hull request context -
optionsObjectoptions.bodyObject request body object (req.body)options.batchSizeObject size of the chunk we want to pass to handleroptions.handlerFunction callback returning a Promise (will be called with array of elements)options.onResponseFunction callback called on successful inital responseoptions.onErrorFunction callback called during error
Returns Promise
This is a method to request an extract of user base to be sent back to the Connector to a selected path which should be handled by notifHandler.
req.hull.helpers.requestExtract({ segment = null, path, fields = [], additionalQuery = {} });Returns Promise
Allows to update selected settings of the ship private_settings object. This is a wrapper over hullClient.utils.settings.update() call. On top of that it makes sure that the current context ship object is updated, and the ship cache is refreshed.
It will emit ship:update notify event.
req.hull.helpers.updateSettings({ newSettings });Returns Promise
Cache available as req.hull.cache object. This class is being intiated and added to Context Object by QueueAgent.
If you want to customize cache behavior (for example ttl, storage etc.) please @see Infra.QueueAgent
Hull client calls which fetch ship settings could be wrapped with this method to cache the results
Returns Promise
Saves ship data to the cache
Returns Promise
Returns cached information
keystring
Returns Promise
Clears the ship cache. Since Redis stores doesn't return promise for this method, it passes a callback to get a Promise
keystring
Returns any Promise
Metric agent available as req.hull.metric object.
This class is being initiated by InstrumentationAgent.
If you want to change or override metrics behavior please @see Infra.InstrumentationAgent
req.hull.metric.value("metricName", metricValue = 1);
req.hull.metric.increment("metricName", incrementValue = 1); // increments the metric value
req.hull.metric.event("eventName", { text = "", properties = {} });Sets metric value for gauge metric
metricstring metric namevaluenumber metric value (optional, default1)additionalTagsArray additional tags in form of["tag_name:tag_value"](optional, default[])
Returns mixed
Increments value of selected metric
metricstring metric metric namevaluenumber value which we should increment metric by (optional, default1)additionalTagsArray additional tags in form of["tag_name:tag_value"](optional, default[])
Returns mixed
-
optionsObject
Returns mixed
-
queueAdapterObject adapter to run - when using this function in Context this param is bound -
ctxContext Hull Context Object - when using this function in Context this param is bound -
jobNamestring name of specific job to execute -
jobPayloadObject the payload of the job -
optionsObject (optional, default{})options.ttlnumber? job producers can set an expiry value for the time their job can live in active state, so that if workers didn't reply in timely fashion, Kue will fail it with TTL exceeded error message preventing that job from being stuck in active state and spoiling concurrency.options.delaynumber? delayed jobs may be scheduled to be queued for an arbitrary distance in time by invoking the .delay(ms) method, passing the number of milliseconds relative to now. Alternatively, you can pass a JavaScript Date object with a specific time in the future. This automatically flags the Job as "delayed".options.queueNamestring? when you start worker with a different queue name, you can explicitly set it here to queue specific jobs to that queueoptions.priority(number | string)? you can use this param to specify priority of job
// app is Hull.Connector wrapped expressjs app
app.get((req, res) => {
req.hull.enqueue("jobName", { payload: "to-work" })
.then(() => {
res.end("ok");
});
});Returns Promise which is resolved when job is successfully enqueued
Meta
- deprecated: internal connector queue is considered an antipattern, this function is kept only for backward compatiblity
Production ready connectors need some infrastructure modules to support their operation, allow instrumentation, queueing and caching. The Hull.Connector comes with default settings, but also allows to initiate them and set a custom configuration:
const instrumentation = new Instrumentation();
const cache = new Cache();
const queue = new Queue();
const connector = new Hull.Connector({ instrumentation, cache, queue });This is a wrapper over https://github.com/BryanDonovan/node-cache-manager to manage ship cache storage. It is responsible for handling cache key for every ship.
By default it comes with the basic in-memory store, but in case of distributed connectors being run in multiple processes for reliable operation a shared cache solution should be used. The Cache module internally uses node-cache-manager, so any of it's compatibile store like redis or memcache could be used:
The cache instance also exposes contextMiddleware whch adds req.hull.cache to store the ship and segments information in the cache to not fetch it for every request. The req.hull.cache is automatically picked and used by the Hull.Middleware and segmentsMiddleware.
The
req.hull.cachecan be used by the connector developer for any other caching purposes:
ctx.cache.get('object_name');
ctx.cache.set('object_name', object_value);
ctx.cache.wrap('object_name', () => {
return Promise.resolve(object_value);
});There are two
object nameswhich are reserved and cannot be used here:
- any ship id
- "segments"
IMPORTANT internal caching of
ctx.shipobject is refreshed onship:updatenotifications, if the connector doesn't subscribe for notification at all the cache won't be refreshed automatically. In such case disable caching, set short TTL or addnotifHandler
optionsObject passed to node-cache-manager (optional, default{})
const redisStore = require("cache-manager-redis");
const { Cache } = require("hull/lib/infra");
const cache = new Cache({
store: redisStore,
url: 'redis://:XXXX@localhost:6379/0?ttl=600'
});
const connector = new Hull.Connector({ cache });It automatically sends data to DataDog, Sentry and Newrelic if appropriate ENV VARS are set:
- NEW_RELIC_LICENSE_KEY
- DATADOG_API_KEY
- SENTRY_URL
It also exposes the contextMiddleware which adds req.hull.metric agent to add custom metrics to the ship. Right now it doesn't take any custom options, but it's showed here for the sake of completeness.
options(optional, default{})
const { Instrumentation } = require("hull/lib/infra");
const instrumentation = new Instrumentation();
const connector = new Connector.App({ instrumentation });By default it's initiated inside Hull.Connector as a very simplistic in-memory queue, but in case of production grade needs, it comes with a Kue or Bull adapters which you can initiate in a following way:
Options from the constructor of the BullAdapter or KueAdapter are passed directly to the internal init method and can be set with following parameters:
https://github.com/Automattic/kue#redis-connection-settings https://github.com/OptimalBits/bull/blob/master/REFERENCE.md#queue
The queue instance has a contextMiddleware method which adds req.hull.enqueue method to queue jobs - this is done automatically by Hull.Connector().setupApp(app):
req.hull.enqueue((jobName = ''), (jobPayload = {}), (options = {}));By default the job will be retried 3 times and the payload would be removed from queue after successfull completion.
Then the handlers to work on a specific jobs is defined in following way:
connector.worker({
jobsName: (ctx, jobPayload) => {
// process Payload
// this === job (kue job object)
// return Promise
}
});
connector.startWorker();adapterObject
```javascript
const { Queue } = require("hull/lib/infra");
const BullAdapter = require("hull/lib/infra/queue/adapter/bull"); // or KueAdapter
const queueAdapter = new BullAdapter(options);
const queue = new Queue(queueAdapter);
const connector = new Hull.Connector({ queue });
```Meta
- deprecated: internal connector queue is considered an antipattern, this class is kept only for backward compatiblity
This middleware standardizes the instantiation of a Hull Client in the context of authorized HTTP request. It also fetches the entire ship's configuration.
-
HullClientHullClient Hull Client - the version exposed by this library comes with HullClient argument bound -
optionsObjectoptions.hostSecretstring The ship hosted secret - consider this as a private key which is used to encrypt and decryptreq.hull.token. The token is useful for exposing it outside the Connector <-> Hull Platform communication. For example the OAuth flow or webhooks. Thanks to the encryption no 3rd party will get access to Hull Platform credentials.options.clientConfigObject Additional config which will be passed to the new instance of Hull Client (optional, default{})
Returns Function
General utilities
NotifHandler is a packaged solution to receive User and Segment Notifications from Hull. It's built to be used as an express route. Hull will receive notifications if your ship's manifest.json exposes a subscriptions key:
Note : The Smart notifier is the newer, more powerful way to handle data flows. We recommend using it instead of the NotifHandler. This handler is there to support Batch extracts.
{
"subscriptions": [{ "url": "/notify" }]
}-
paramsObject-
params.handlersObject -
params.onSubscribeFunction? -
params.optionsObject?params.options.maxSizenumber? the size of users/account batch chunkparams.options.maxTimenumber? time waited to capture users/account up to maxSizeparams.options.segmentFilterSettingstring? setting from connector's private_settings to mark users as whitelistedparams.options.groupTraitsboolean (optional, defaultfalse)
-
params.userHandlerOptionsObject? deprecated
-
import { notifHandler } from "hull/lib/utils";
const app = express();
const handler = NotifHandler({
options: {
groupTraits: true, // groups traits as in below examples
maxSize: 6,
maxTime: 10000,
segmentFilterSetting: "synchronized_segments"
},
onSubscribe() {} // called when a new subscription is installed
handlers: {
"ship:update": function(ctx, message) {},
"segment:update": function(ctx, message) {},
"segment:delete": function(ctx, message) {},
"account:update": function(ctx, message) {},
"user:update": function(ctx, messages = []) {
console.log('Event Handler here', ctx, messages);
// ctx: Context Object
// messages: [{
// user: { id: '123', ... },
// segments: [{}],
// changes: {},
// events: [{}, {}]
// matchesFilter: true | false
// }]
}
}
})
connector.setupApp(app);
app.use('/notify', handler);Returns Function expressjs router
Meta
- deprecated: use smartNotifierHandler instead, this module is kept for backward compatibility
OAuthHandler is a packaged authentication handler using Passport. You give it the right parameters, it handles the entire auth scenario for you.
It exposes hooks to check if the ship is Set up correctly, inject additional parameters during login, and save the returned settings during callback.
To make it working in Hull dashboard set following line in manifest.json file:
{
"admin": "/auth/"
}For example of the notifications payload see details
-
optionsObjectoptions.namestring The name displayed to the User in the various screens.options.tokenInUrlboolean Some services (like Stripe) require an exact URL match. Some others (like Hubspot) don't pass the state back on the other hand. Setting this flag to false (default: true) removes thetokenQuerystring parameter in the URL to only rely on thestateparam.options.isSetupFunction A method returning a Promise, resolved if the ship is correctly setup, or rejected if it needs to display the Login screen. Lets you define in the Ship the name of the parameters you need to check for. You can return parameters in the Promise resolve and reject methods, that will be passed to the view. This lets you display status and show buttons and more to the customeroptions.onAuthorizeFunction A method returning a Promise, resolved when complete. Best used to save tokens and continue the sequence once saved.options.onLoginFunction A method returning a Promise, resolved when ready. Best used to process form parameters, and place them inreq.authParamsto be submitted to the Login sequence. Useful to add strategy-specific parameters, such as a portal ID for Hubspot for instance.options.StrategyFunction A Passport Strategy.options.viewsObject Required, A hash of view files for the different screens: login, home, failure, successoptions.optionsObject Hash passed to Passport to configure the OAuth Strategy. (See Passport OAuth Configuration)
const { oAuthHandler } = require("hull/lib/utils");
const { Strategy as HubspotStrategy } = require("passport-hubspot");
const app = express();
app.use(
'/auth',
oAuthHandler({
name: 'Hubspot',
tokenInUrl: true,
Strategy: HubspotStrategy,
options: {
clientID: 'xxxxxxxxx',
clientSecret: 'xxxxxxxxx', //Client Secret
scope: ['offline', 'contacts-rw', 'events-rw'] //App Scope
},
isSetup(req) {
if (!!req.query.reset) return Promise.reject();
const { token } = req.hull.ship.private_settings || {};
return !!token
? Promise.resolve({ valid: true, total: 2 })
: Promise.reject({ valid: false, total: 0 });
},
onLogin: req => {
req.authParams = { ...req.body, ...req.query };
return req.hull.client.updateSettings({
portalId: req.authParams.portalId
});
},
onAuthorize: req => {
const { refreshToken, accessToken } = req.account || {};
return req.hull.client.updateSettings({
refresh_token: refreshToken,
token: accessToken
});
},
views: {
login: 'login.html',
home: 'home.html',
failure: 'failure.html',
success: 'success.html'
}
})
);
//each view will receive the following data:
{
name: "The name passed as handler",
urls: {
login: '/auth/login',
success: '/auth/success',
failure: '/auth/failure',
home: '/auth/home',
},
ship: ship //The entire Ship instance's config
}Returns Function OAuth handler to use with expressjs
smartNotifierHandler is a next generation notifHandler cooperating with our internal notification tool. It handles Backpressure, throttling and retries for you and lets you adapt to any external rate limiting pattern.
To enable the smartNotifier for a connector, the
smart-notifiertag should be present inmanifest.jsonfile. Otherwise, regular, unthrottled notifications will be sent without the possibility of flow control.
{
"tags": ["smart-notifier"],
"subscriptions": [
{
"url": "/notify"
}
]
}When performing operations on notification you can set FlowControl settings using ctx.smartNotifierResponse helper.
-
paramsObject-
params.handlersObject -
params.optionsObject?params.options.maxSizenumber? the size of users/account batch chunkparams.options.maxTimenumber? time waited to capture users/account up to maxSizeparams.options.segmentFilterSettingstring? setting from connector's private_settings to mark users as whitelistedparams.options.groupTraitsboolean (optional, defaultfalse)
-
params.userHandlerOptionsObject? deprecated
-
const { smartNotifierHandler } = require("hull/lib/utils");
const app = express();
const handler = smartNotifierHandler({
handlers: {
'ship:update': function(ctx, messages = []) {},
'segment:update': function(ctx, messages = []) {},
'segment:delete': function(ctx, messages = []) {},
'account:update': function(ctx, messages = []) {},
'user:update': function(ctx, messages = []) {
console.log('Event Handler here', ctx, messages);
// ctx: Context Object
// messages: [{
// user: { id: '123', ... },
// segments: [{}],
// changes: {},
// events: [{}, {}]
// matchesFilter: true | false
// }]
// more about `smartNotifierResponse` below
ctx.smartNotifierResponse.setFlowControl({
type: 'next',
size: 100,
in: 5000
});
return Promise.resolve();
}
},
options: {
groupTraits: false
}
});
connector.setupApp(app);
app.use('/notify', handler);Returns Function expressjs router
This is a general error handling SuperAgent plugin.
It changes default superagent retry strategy to rerun the query only on transient
connectivity issues (ECONNRESET, ETIMEDOUT, EADDRINFO, ESOCKETTIMEDOUT, ECONNABORTED).
So any of those errors will be retried according to retries option (defaults to 2).
If the retry fails again due to any of those errors the SuperAgent Promise will be rejected with special error class TransientError to distinguish between logical errors and flaky connection issues.
In case of any other request the plugin applies simple error handling strategy:
every non 2xx or 3xx response is treated as an error and the promise will be rejected.
Every connector ServiceClient should apply it's own error handling strategy by overriding ok handler.
-
optionsObject (optional, default{})
superagent.get("http://test/test")
.use(superagentErrorPlugin())
.ok((res) => {
if (res.status === 401) {
throw new ConfigurationError();
}
if (res.status === 429) {
throw new RateLimitError();
}
return true;
})
.catch((error) => {
// error.constructor.name can be ConfigurationError, RateLimitError coming from `ok` handler above
// or TransientError coming from logic applied by `superagentErrorPlugin`
})Returns Function function to use as superagent plugin
This plugin takes client.logger and metric params from the Context Object and logs following log line:
ship.service_api.requestwith params:url- the original url passed to agent (use withsuperagentUrlTemplatePlugin)responseTime- full response time in msmethod- HTTP verbstatus- response status codevars- when usingsuperagentUrlTemplatePluginit will contain all provided variables
The plugin also issue a metric with the same name ship.service_api.request.
-
optionsObject
const superagent = require('superagent');
const { superagentInstrumentationPlugin } = require('hull/lib/utils');
// const ctx is a Context Object here
const agent = superagent
.agent()
.use(
urlTemplatePlugin({
defaultVariable: 'mainVariable'
})
)
.use(
superagentInstrumentationPlugin({
logger: ctx.client.logger,
metric: ctx.metric
})
);
agent
.get('https://api.url/{{defaultVariable}}/resource/{{resourceId}}')
.tmplVar({
resourceId: 123
})
.then(res => {
assert(res.request.url === 'https://api.url/mainVariable/resource/123');
});
> Above code will produce following log line:
```sh
connector.service_api.call {
responseTime: 880.502444,
method: 'GET',
url: 'https://api.url/{{defaultVariable}}/resource/{{resourceId}}',
status: 200
}
```
> and following metrics:
```javascript
- `ship.service_api.call` - should be migrated to `connector.service_api.call`
- `connector.service_api.responseTime`
```Returns Function function to use as superagent plugin
This plugin allows to pass generic url with variables - this allows better instrumentation and logging on the same REST API endpoint when resource ids varies.
defaultsObject default template variable
const superagent = require('superagent');
const { superagentUrlTemplatePlugin } = require('hull/lib/utils');
const agent = superagent.agent().use(
urlTemplatePlugin({
defaultVariable: 'mainVariable'
})
);
agent
.get('https://api.url/{{defaultVariable}}/resource/{{resourceId}}')
.tmplVar({
resourceId: 123
})
.then(res => {
assert(res.request.url === 'https://api.url/mainVariable/resource/123');
});Returns Function function to use as superagent plugin