node-red-node-watson
Version:
A collection of Node-RED nodes for IBM Watson services
339 lines (289 loc) • 9.72 kB
JavaScript
/**
* Copyright 2016, 2022 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
module.exports = function(RED) {
const SERVICE_IDENTIFIER = 'assistant',
OLD_SERVICE_IDENTIFIER = 'conversation',
AssistantV1 = require('ibm-watson/assistant/v1'),
{ IamAuthenticator } = require('ibm-watson/auth');
var pkg = require('../../package.json'),
serviceutils = require('../../utilities/service-utils'),
payloadutils = require('../../utilities/payload-utils'),
service = null,
sApikey = null;
service = serviceutils.getServiceCreds(SERVICE_IDENTIFIER);
if (!service) {
service = serviceutils.getServiceCreds(OLD_SERVICE_IDENTIFIER);
}
if (service) {
sApikey = service.apikey ? service.apikey : '';
}
RED.httpAdmin.get('/watson-conversation/vcap', function(req, res) {
res.json(service ? {
bound_service: true
} : null);
});
function verifyPayload(node, msg, config) {
if (!(msg.payload || config['empty-payload'])) {
node.status({
fill: 'red',
shape: 'ring',
text: 'missing payload'
});
return Promise.reject('Missing property: msg.payload');
}
return Promise.resolve();
}
function verifyOptionalInputs(node, msg, config, params) {
// next 3 not documented but present in WDC Node SDK.
// optional output
if (msg.params && msg.params.output) {
params.output = msg.params.output;
}
if (msg.params && msg.params.entities) {
params.entities = msg.params.entities;
}
if (msg.params && msg.params.intents) {
params.intents = msg.params.intents;
}
}
function setContextParams(node, msg, config, params) {
if (config.context) {
if (config.multiuser) {
if (msg.user) {
params.context = node.context().flow.get('context-' + msg.user);
} else {
var warning = 'Missing msg.user property, using global context. ' +
'Multiuser conversation may not function as expected.';
node.warn(warning, msg);
params.context = node.context().flow.get('context');
}
} else {
params.context = node.context().flow.get('context');
}
}
if (msg.additional_context) {
params.context = params.context ? params.context : {};
for (prop in msg.additional_context) {
if (msg.additional_context.hasOwnProperty(prop)) {
params.context[prop] = msg.additional_context[prop];
}
}
}
}
function setWorkspaceParams(node, msg, config, params) {
// workspaceid can be either configured in the node,
// or sent into msg.params.workspace_id
if (config.workspaceid) {
params.workspaceId = config.workspaceid;
}
if (msg.params && msg.params.workspace_id) {
params.workspaceId = msg.params.workspace_id;
}
}
function setSavedContextParams(node, msg, config, params) {
// option context in JSON format
if (msg.params && msg.params.context) {
if (config.context) {
node.warn('Overridden saved context');
if (msg.user) {
node.context().flow.set('context-' + msg.user, null);
} else {
node.context().flow.set('context', null);
}
}
params.context = msg.params.context;
}
}
function setAlternativeIntentsParams(node, msg, config, params) {
// optional alternate_intents : boolean
if (msg.params && msg.params.alternate_intents) {
params.alternateIntents = msg.params.alternate_intents;
}
}
function verifyInputs(node, msg, config, params) {
return new Promise(function resolver(resolve, reject) {
if (!config.workspaceid && (!msg || !msg.params ||!msg.params.workspace_id)) {
reject('Missing workspace_id. check node documentation.');
}
// mandatory message
params.input = {
text: msg.payload
};
var prop = null;
// Invoke each of the functions building up the params in turn
[setContextParams, setWorkspaceParams, setSavedContextParams,
setAlternativeIntentsParams, verifyOptionalInputs].forEach((f) => {
f(node, msg, config, params);
});
resolve();
});
}
function verifyCredentials(msg, k) {
if ( !(k) ) {
if ( (!msg.params) ||
(!(msg.params.apikey)) ) {
return false;
}
}
return true;
}
function verifyServiceCredentials(node, msg, config) {
// If it is present the newly provided user entered key
// takes precedence over the existing one.
// If msg.params contain credentials then these will Overridde
// the bound or configured credentials.
return new Promise(function resolver(resolve, reject) {
let authSettings = {},
serviceSettings = {
headers: {
'User-Agent': pkg.name + '-' + pkg.version
}
};
let apiKey = sApikey || node.credentials.apikey,
endpoint = '',
optoutLearning = false,
version = '2021-11-27';
if (!verifyCredentials(msg, apiKey)) {
reject('Missing Watson Assistant API service credentials');
}
if (msg.params) {
if (msg.params.apikey) {
apiKey = msg.params.apikey;
}
if (msg.params.version) {
version = msg.params.version;
}
if (msg.params.customerId) {
serviceSettings.headers['X-Watson-Metadata'] = msg.params.customerId;
}
}
if (apiKey) {
authSettings.apikey = apiKey;
}
serviceSettings.authenticator = new IamAuthenticator(authSettings);
serviceSettings.version = version;
serviceSettings.version_date = version;
if (service) {
endpoint = service.url;
}
if (config['service-endpoint']) {
endpoint = config['service-endpoint'];
}
if (msg.params && msg.params.endpoint) {
endpoint = msg.params.endpoint;
}
if (endpoint) {
serviceSettings.url = endpoint;
}
if ((msg.params && msg.params['optout_learning'])){
optoutLearning = true;
} else if (config['optout-learning']){
optoutLearning = true;
}
if (optoutLearning){
serviceSettings.headers = serviceSettings.headers || {};
serviceSettings.headers['X-Watson-Learning-Opt-Out'] = '1';
}
if (config['timeout'] && config['timeout'] !== '0' && isFinite(config['timeout'])){
serviceSettings.timeout = parseInt(config['timeout']);
}
if (msg.params && msg.params.timeout !== '0' && isFinite(msg.params.timeout)){
serviceSettings.timeout = parseInt(msg.params.timeout);
}
if (msg.params && msg.params.disable_ssl_verification){
serviceSettings.disable_ssl_verification = true;
}
node.service = new AssistantV1(serviceSettings);
resolve();
});
}
function processResponse(response, node, msg, config) {
return new Promise(function resolver(resolve, reject) {
if (response === null || typeof (response) === 'undefined') {
reject('call to watson conversation service failed');
}
msg.payload = response;
if (response && response.result) {
msg.payload = response.result;
}
let body = msg.payload;
if (config.context && body && body.context) {
if (config.multiuser && msg.user) {
node.context().flow.set('context-' + msg.user, body.context);
} else {
if (msg.user) {
node.warn('msg.user ignored when multiple users not set in node');
}
node.context().flow.set('context', body.context);
}
}
resolve();
});
}
function execute(params, node, msg, config) {
return new Promise(function resolver(resolve, reject) {
node.status({
fill: 'blue',
shape: 'dot',
text: 'Calling Conversation service ...'
});
// call POST /message through SDK
node.service.message(params)
.then((response) => {
return processResponse(response, node, msg, config);
})
.then(() => {
resolve();
})
.catch((err) => {
reject(err);
});
});
}
// This is the Watson Conversation V1 (GA) Node
function WatsonConversationV1Node(config) {
var node = this;
RED.nodes.createNode(this, config);
this.on('input', function(msg, send, done) {
var params = {};
node.status({});
verifyPayload(node, msg, config)
.then(() => {
return verifyInputs(node, msg, config, params);
})
.then(() => {
return verifyServiceCredentials(node, msg, config);
})
.then(() => {
return execute(params, node, msg, config);
})
.then(() => {
send(msg);
node.status({});
done();
})
.catch(function(err){
let errMsg = payloadutils.reportError(node, msg, err);
done(errMsg);
});
});
}
RED.nodes.registerType('watson-conversation-v1', WatsonConversationV1Node, {
credentials: {
apikey: {type: 'password'}
}
});
};