Socket
Socket
Sign inDemoInstall

clang

Package Overview
Dependencies
118
Maintainers
1
Versions
34
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.2.10 to 1.0.1

.eslintrc.json

2

index.js

@@ -1,1 +0,1 @@

module.exports = require('./lib/clang');
module.exports = require('./lib/clang')
'use strict'
var _ = require('lodash');
var soap = require('soap');
var async = require('async');
var mock = require('./mock');
const soap = require('soap')
const async = require('async')
const path = require('path')
const moment = require('moment');
const prompt = require('prompt');
const mock = require('./mock')
const fields = require('./fields');
function Clang(config) {
var me = this;
class Clang {
constructor(config) {
const me = this
if (!(me instanceof Clang)) {
return new Clang(config);
}
me.config = _.clone(config || {});
me.config.normalizeOptionFields = me.config.normalizeOptionFields == undefined ? true : me.config.normalizeOptionFields
me.config = Object.assign({}, config)
me.wsdl = 'https://secure.myclang.com/app/api/soap/public/index.php?wsdl';
me.wsdl = 'https://secure.myclang.com/app/api/soap/public/index.php?wsdl'
if (!me.config.version) {
//By default this version is locked, because I now it works
me.wsdl += '&version=1.23';
} else if (me.config.version !== '*') {
//Get a specific version by supplying the version number
me.wsdl += '&version=' + me.config.version;
}
me.api = null;
if (!me.config.version) {
//By default this version is locked, because I now it works
me.wsdl += '&version=1.23'
} else if (me.config.version !== '*') {
//Get a specific version by supplying the version number
me.wsdl += '&version=' + me.config.version
}
me.api = null
if (me.config.mock) {
mock(me)
if (me.config.mock) {
me.wsdl = path.join(__dirname, 'wsdl/version-1.24.xml')
}
}
}
Clang.prototype.request = function(methodName, args, callback) {
var me = this;
var requestArgs, pagingArgs = {};
request(/*methodName, args(optional), cb(optional)*/) {
const me = this
let requestArgs, normalizedArgs, pagingArgs = {}
let methodName, cb
let promiseMode
if (arguments.length === 2) {
callback = args;
requestArgs = {
uuid: me.config.uuid
};
} else if (arguments.length === 3) {
requestArgs = _.assign({
uuid: me.config.uuid
}, args || {});
} else {
callback = methodName;
return setImmediate(function() {
callback(new Error('request takes 3 arguments: methodName, args (object) and callback function'));
});
}
Array.prototype.slice.call(arguments).forEach(arg => {
if (typeof arg === 'string')
methodName = arg
else if (arg && typeof arg === 'object')
requestArgs = arg
else if (typeof arg === 'function')
cb = arg
})
if (!cb) {
promiseMode = true
cb = function() {}
}
if (!requestArgs.uuid) {
return setImmediate(function() {
callback(new Error('uuid missing. Provide it with the 2nd arguments object or with client instantiation. Hint: `var clang = new Clang({uuid: \'12345678-...\'})`'));
});
}
return new Promise((resolve, reject) => {
pagingArgs.offset = requestArgs._offset || 0;
pagingArgs.size = requestArgs._size || 50;
delete requestArgs._offset;
delete requestArgs._size;
const errAway = (error) => {
if (promiseMode) {
reject(error)
}
setImmediate(cb.bind(null, error))
}
async.waterfall([
function async_init(callback){
if (me.api) {
console.log('SOAP client already created');
return setImmediate(callback);
requestArgs = requestArgs || {}
requestArgs.uuid = requestArgs.uuid || me.config.uuid
if (!methodName) {
return errAway(`methodName argument (string) missing`)
}
if (me.config.debug) {
console.log('Creating SOAP client');
if (!requestArgs.uuid && !me.config.mock) {
return errAway(`uuid missing. Provide it with the 2nd arguments object or with client instantiation. Hint: 'let clang = new Clang({uuid: '12345678', ...})'`)
}
soap.createClient(me.wsdl, function(err, result) {
if (err) {
return callback(err);
}
pagingArgs.offset = requestArgs._offset || 0
pagingArgs.size = requestArgs._size || 50
delete requestArgs._offset
delete requestArgs._size
if (me.config.debug) {
console.log('SOAP client created');
}
me.api = result;
me.description = result.describe();
async.autoInject({
init: (callback) => {
if (me.api) {
if (me.config.debug) {
console.log(`SOAP client already created`)
}
return setImmediate(callback)
}
if (me.config.debug) {
console.log(`Creating SOAP client...`)
}
callback();
});
},
function async_request(callback){
var fn = me.api[methodName];
soap.createClient(me.wsdl, (err, result) => {
if (err) {
return callback(err)
}
if (!fn) {
return setImmediate(function() {
callback(new Error('Undefined method `' + methodName + '`'));
});
}
if (me.config.debug) {
console.log(`SOAP client created`)
}
me.api = result
me.description = result.describe()
var args;
callback()
})
},
request: (init, cb) => {
let fn = me.config.mock ? mock.api(me, methodName) : me.api[methodName]
if (me.config.normalizeOptionFields) {
args = normalizeInput(me.config, me.description, methodName, requestArgs);
} else {
args = requestArgs;
}
if (!fn) {
return setImmediate(cb.bind(null, `Undefined method '${methodName}'`))
}
fn(args, buildCallback(requestArgs.uuid, me.config, me.api, pagingArgs, callback));
}
], function(err, result) {
if (err) {
if (err.root && err.root.Envelope && err.root.Envelope.Body) {
/**
* err.root.Envelope.Body:
* {
* "Fault": {
* "faultcode": "105",
* "faultstring": "Size must be between 1 and 50"
* }
* }
*/
return callback(err.root.Envelope.Body);
}
return callback(err);
}
return callback(null, result);
});
};
Clang.prototype.send = function(customer, options, callback) {
var me = this;
if (me.config.normalizeOptionFields) {
normalizedArgs = me.normalizeInput(methodName, requestArgs)
} else {
normalizedArgs = requestArgs
}
var customerGetMethodName;
var mergeMethodName;
var sendMethodName;
if (me.config.debug) {
console.log(`Making SOAP request to ${methodName}...`)
}
if (arguments.length !== 3) {
callback = customer;
return setImmediate(function() {
callback(new Error('send takes 3 arguments: customer, options and callback function'));
});
fn(normalizedArgs, cb)
},
output: ['request', (result, cb) => {
// result is an array with [json result, raw xml response, soapHeader ]
let [obj, raw, ] = result
if (me.config.logPayload) {
console.log('payload outgoing', me.api.lastRequest)
console.log('payload incoming', raw)
}
if (me.config.debug) {
console.log(`Incoming SOAP response ${JSON.stringify(obj).slice(0,50)}...`)
}
//Check for No SOAP Fault, but result code not 0
if (obj.code != 0) {
return setImmediate(cb.bind(null, new Error(`No SOAP Fault, but result code not 0: ${JSON.stringify(obj.msg)}`)))
}
//Check for No SOAP Fault, but obj.msg = false (probably delete that didn't work out)
if (obj.msg === false) {
return setImmediate(cb.bind(null, new Error(`No SOAP Fault, but result msg is false (delete failed?): ${JSON.stringify(obj.msg)}`)))
}
if (obj.msg === null) {
// this occurs for example when doing a customer_getByExternalId with a externalId that is not found
if (me.config.debug) {
console.log(`No records found in SOAP response (msg=null)`)
}
return setImmediate(cb.bind(null, null, []))
}
if (obj.msg === undefined) {
return setImmediate(cb.bind(null, new Error(`No SOAP Fault, but result msg is undefined (unexpected): ${JSON.stringify(obj.msg)}`)))
}
// result is id of resource of which the result is not there yet
if (typeof obj.msg === 'string' && obj.msg.match(/^\d+$/)) {
return getResourceSet(me, obj.msg, methodName, normalizedArgs, pagingArgs, cb)
}
return setImmediate(cb.bind(null, null, me.normalizeOutput(methodName, obj)))
}]
}, (err, result) => {
// result is an object with the keys of `init` and `request` (as per autoInject)
if (err) {
let faultBody = err.root && err.root.Envelope && err.root.Envelope.Body
if (faultBody) {
/**
* err.root.Envelope.Body:
* {
* "Fault": {
* "faultcode": "105",
* "faultstring": "Size must be between 1 and 50"
* }
* }
*/
if (
faultBody.Fault.faultcode == 107 ||
faultBody.Fault.faultstring.match(/customer already member of/)
) {
console.log(`Incoming SOAP Fault ${JSON.stringify(faultBody)}, reduced to warning`)
return cb(null, [{msg: true, warning: err.body}])
}
console.log(`Incoming SOAP Fault ${JSON.stringify(faultBody)}`)
return errAway(faultBody)
}
return errAway(err)
}
if (promiseMode) {
return resolve(result.output)
}
cb(null, result.output)
})
})
}
if (!customer || typeof customer !== 'object' || !Object.keys(customer).length) {
return setImmediate(function() {
callback(new Error('The customer argument must be an non-empty object'));
});
}
options.lookup = options.lookup || 'externalId';
send(customer, options, callback) {
const me = this
customer = me.transformFields(customer, options)
let customerGetMethodName
let sendMethodName
let lookupOption
var customerArgs = {};
if (options.lookup === 'externalId') {
customerGetMethodName = 'customer_getByExternalId';
customerArgs.externalId = customer.externalId;
if (!customerArgs.externalId) {
return setImmediate(function() {
callback(new Error('customer.externalId is required with lookup method `' + options.lookup + '`'));
});
if (arguments.length !== 3) {
callback = customer
return setImmediate(callback.bind(null, new Error(`send takes 3 arguments: customer, options and callback function`)))
}
}
else if (options.lookup === 'customerId') {
customerGetMethodName = 'customer_getById';
customerArgs.customerId = customer.id || customer.customerId;
if (!customerArgs.customerId) {
return setImmediate(function() {
callback(new Error('customer.id/customerId is required with lookup method `' + options.lookup + '`'));
});
if (!customer || typeof customer !== 'object' || !Object.keys(customer).length) {
return setImmediate(callback.bind(null, new Error(`The customer argument must be an non-empty object`)))
}
}
else if (options.lookup === 'email') {
customerGetMethodName = 'customer_getByEmailAddress';
customerArgs.emailAddress = customer.email || customer.emailAddress;
if (!customerArgs.emailAddress) {
return setImmediate(function() {
callback(new Error('customer.email/emailAddress is required with lookup method `' + options.lookup + '`'));
});
lookupOption = (options.lookup || 'externalId').toLowerCase()
try {
// errors may be thrown while transforming
customer = me.transformFields(customer, options)
} catch (e) {
return setImmediate(callback.bind(null, e))
}
}
else {
return setImmediate(function() {
callback(new Error('lookup method `' + options.lookup + '` is not supported'));
});
}
if (options.context && !options.contextId) {
return setImmediate(function() {
callback(new Error('When a send context is used, a contextId is mandatory'));
});
}
let customerArgs = {}
if (lookupOption === 'externalid') {
customerGetMethodName = 'customer_getByExternalId'
customerArgs.externalId = customer.externalId
if (!customerArgs.externalId) {
return setImmediate(callback.bind(null, new Error(`customer.externalId is required with lookup method '${lookupOption}'`)))
}
}
else if (lookupOption === 'customerid') {
customerGetMethodName = 'customer_getById'
customerArgs.customerId = customer.id || customer.customerId
if (!customerArgs.customerId) {
return setImmediate(callback.bind(null, new Error(`customer.id/customerId is required with lookup method '${lookupOption}'`)))
}
}
else if (lookupOption === 'email' || lookupOption === 'emailaddress') {
customerGetMethodName = 'customer_getByEmailAddress'
customerArgs.emailAddress = customer.email || customer.emailAddress
if (!customerArgs.emailAddress) {
return setImmediate(callback.bind(null, new Error(`customer.email/emailAddress is required with lookup method '${lookupOption}'`)))
}
}
else {
return setImmediate(callback.bind(null, new Error(`lookup option '${lookupOption}' is not supported`)))
}
// only support send to group for now. Needs a backing Clang campaign listening to the group
if (options.context && ['group'].indexOf(options.context) === -1 ) {
return setImmediate(function() {
callback(new Error('send to context `' + options.context + '` is not supported'));
});
}
if (options.context && !options.contextId) {
return setImmediate(callback.bind(null, new Error(`When a send context is used, a contextId is mandatory`)))
}
async.waterfall([
function asyncWtfGet(callback) {
me.request(customerGetMethodName, customerArgs, function(err, result) {
if (
// Geen record gevonden zouden we via customer_getById als volgt weten
err && err.Fault && (err.Fault.faultcode == 213 || err.Fault.faultstring.match(/not found/i)) ||
// via customer_getByExternalId als volgt
result && !result.length
) {
if (!options.create) {
return callback(new Error('Customer nog found and create is not allowed'));
// only support send to group for now. Needs a backing Clang campaign listening to the group
if (options.context && ['group', 'email'].indexOf(options.context) === -1 ) {
return setImmediate(callback.bind(null, new Error(`send to context '${options.context}' is not supported`)))
}
async.autoInject({
get: (cb) => {
me.request(customerGetMethodName, customerArgs, (err, result) => {
let isArray = result instanceof Array
let numRecords
if (
err &&
err.Fault &&
(
err.Fault.faultcode == 213 ||
err.Fault.faultstring.match(/not found/i)
)
) {
// No record found with customer_getById
numRecords = 0
}
else if (err) {
return cb(err)
}
else {
numRecords = isArray ? result.length : result ? 1 : 0
}
if (numRecords === 0 && !options.create) {
return cb(new Error(`Customer not found and create is not allowed`))
}
if (numRecords > 1) {
return cb(new Error(`Multiple customers returned after get`))
}
cb(null, isArray ? result[0] : result)
})
},
upsertMethodName: (get, cb) => {
setImmediate(cb.bind(null, null, get ? 'customer_update' : 'customer_insert'))
},
upsert: (get, upsertMethodName, cb) => {
if (get) {
// The found record should be included in the supplied data
// to make sure that record is updated.
customer.id = get.id
}
else if (err) {
return callback(err);
}
if (result && result.length > 1) {
return callback(new Error('Multiple customers returned after get'));
}
callback(null, result.length ? result[0] : null);
});
},
function asyncWtfMerge(record, callback) {
if (record) {
// The found record should be included in the supplied data
// to make sure that record is updated.
customer.id = record.id;
mergeMethodName = 'customer_update';
} else {
mergeMethodName = 'customer_insert';
}
me.request(mergeMethodName, {customer: customer}, function(err, result) {
if (err) {
return callback(err);
me.request(upsertMethodName, {customer}, cb)
},
send: ['upsert', (upsert, cb) => {
let args = {}
if (options.context === 'group') {
sendMethodName = 'group_addMember'
args.group = {
id: options.contextId
}
args.customer = {
id: upsert.id
}
}
if (!result || !result.length) {
return callback(new Error('No customer returned after ' + mergeMethodName));
else if (options.context === 'email') {
sendMethodName = 'email_sendToCustomer'
args = Object.assign({}, customer, {
emailId: options.contextId,
customerId: upsert.id
})
}
if (result.length > 1) {
return callback(new Error('Multiple customers returned after ' + mergeMethodName));
if (sendMethodName) {
return me.request(sendMethodName, args, cb)
}
callback(null, result[0]);
});
},
function asyncWtfSend(record, callback) {
var args = {};
if (options.context === 'group') {
sendMethodName = 'group_addMember';
args.group = {
id: options.contextId
};
args.customer = {
id: record.id
};
console.warn('No context found, just synced customer', JSON.stringify(customerArgs), ' with Clang')
setImmediate(cb)
}]
}, callback)
// result to final callback is an object with the keys of
// `get`, `upsertMethodName`, `upsert` and `send` (as per autoInject)
}
transformFields(customer, options) {
const me = this
options = options || {}
options.fieldMap = Object.assign({
// default field mappings
particle: 'prefix',
email: 'emailAddress',
gender: {
fieldName: 'gender',
values: {
M: 'MAN',
F: 'WOMAN'
}
}
else if (options.context === 'email') {
// UNTESTED !!!!!!!!!!!!!!!!!!!!!!!!!!
sendMethodName = 'email_sendToCustomer';
args = _.clone(customer);
args.emailId = options.contextId;
}
}, options.fieldMap)
if (sendMethodName) {
return me.request(sendMethodName, args, callback);
// create a new object with altered keys and values based on the fieldMap configuration
let transformedCustomer = {}
Object.keys(customer).forEach((key) => {
let value = customer[key]
let fieldMapTo = options.fieldMap[key]
if (typeof fieldMapTo === 'string') {
// set the value on the new key
transformedCustomer[fieldMapTo] = me.safeValue('customer', fieldMapTo, value)
}
else if (typeof fieldMapTo === 'object') {
// take the fieldName and values configuration of this fieldMap to transform the key and value
transformedCustomer[fieldMapTo.fieldName] = me.safeValue('customer', fieldMapTo.fieldName, fieldMapTo.values[value] || value)
}
else {
// set the value on the same key
transformedCustomer[key] = me.safeValue('customer', key, value)
}
})
return transformedCustomer
}
console.warn('No context found, just synced customer', JSON.stringify(customerArgs), ' with Clang');
setImmediate(function() {
callback(null, record);
});
safeValue(resourceName, fieldName, value) {
if (isObject(value) || value instanceof Array) {
value = JSON.stringify(value)
}
], callback);
};
Clang.prototype.transformFields = function(customer, options) {
options = options || {}
options.fieldMap = Object.assign({
// default field mappings
particle: 'prefix',
email: 'emailAddress',
gender: {
fieldName: 'gender',
values: {
M: 'MAN',
F: 'WOMAN'
let fieldDefinition = fields[resourceName] && fields[resourceName][fieldName]
if (!fieldDefinition) {
if (value instanceof Date) {
return moment(value).format('YYYY-MM-DD HH:mm:ss')
}
return value
}
}, options.fieldMap || {})
// create a new object with altered keys and values based on the fieldMap configuration
let transformedCustomer = {}
Object.keys(customer).forEach((key) => {
let value = customer[key]
let fieldMapTo = options.fieldMap[key]
if (typeof fieldMapTo === 'string') {
// set the value on the new key
transformedCustomer[fieldMapTo] = value
if (fieldDefinition.type === 'date') {
return moment(value).format('YYYY-MM-DD')
}
else if (typeof fieldMapTo === 'object') {
// take the fieldName and values configuration of this fieldMap to transform the key and value
transformedCustomer[fieldMapTo.fieldName] = fieldMapTo.values[value] || value
if (fieldDefinition.type === 'datetime') {
return moment(value).format('YYYY-MM-DD HH:mm:ss')
}
else {
// set the value on the same key
transformedCustomer[key] = value
if (value && fieldDefinition.enum && fieldDefinition.enum.indexOf(value) === -1) {
throw new Error(`Value of '${value}' is not one of the allowed values (${fieldDefinition.enum.join(',')}) of ${resourceName}.${fieldName}`)
}
})
return transformedCustomer
};
if (value && fieldDefinition.maxlength && (value.length > fieldDefinition.maxlength)) {
throw new Error(`Length of '${value}' (${value.length}) is longer than the allowed maxlength (${fieldDefinition.maxlength}) of ${resourceName}.${fieldName}`)
}
//Private stuff starts here
var setMethods = {
CUSTOMER_SET: 'customerSet_getCustomers',
MAILING_SET : 'mailingSet_getMailings',
OPEN_SET : 'openSet_getOpens',
CLICK_SET : 'clickSet_getClicks',
BOUNCE_SET : 'bounceSet_getBounces'
};
return value
}
/**
* Move arguments to an options object
*/
var normalizeInput = function(config, description, clangMethodName, args) {
/**
* Move arguments to an options object
*/
normalizeInput (clangMethodName, args) {
const me = this
var inputDefinition = description.clangAPI.clangPort[clangMethodName].input;
let inputDefinition = me.description.clangAPI.clangPort[clangMethodName].input
//To understand this code better, check how the entire interface definition
// looks like in JSON created by createClient in wsdl 1.18.json
//To understand this code better, check how the entire interface definition
// looks like in JSON created by createClient in wsdl/version-1.24.json
var optionsIdentifier, optionIdentifier;
let optionsIdentifier, optionIdentifier
var clangObjectName;
if (clangMethodName.match('^email_sendTo')) {
optionsIdentifier = 'manualOptions';
optionIdentifier = 'Option';
clangObjectName = 'email';
} else if (clangMethodName.match('^customer')) {
optionsIdentifier = 'options';
optionIdentifier = 'CustomerOption';
clangObjectName = 'customer';
} else {
return args;
}
let clangObjectName
if (clangMethodName.match('^email_sendTo')) {
optionsIdentifier = 'manualOptions'
optionIdentifier = 'Option'
clangObjectName = 'email'
} else if (clangMethodName.match('^customer')) {
optionsIdentifier = 'options'
optionIdentifier = 'CustomerOption'
clangObjectName = 'customer'
} else {
return args
}
var normalizedArgs = _.clone(args);
let normalizedArgs = Object.assign({}, args)
var moveOptions = function (object, inputDefinition) {
Object.keys(object).forEach(function(key) {
if (typeof inputDefinition[key] !== 'string') {
if (!object[optionsIdentifier]) {
object[optionsIdentifier] = {};
object[optionsIdentifier][optionIdentifier] = [];
const moveOptions = (object, inputDefinition) => {
Object.keys(object).forEach((key) => {
if (typeof inputDefinition[key] !== 'string') {
if (!object[optionsIdentifier]) {
object[optionsIdentifier] = {}
object[optionsIdentifier][optionIdentifier] = []
}
object[optionsIdentifier][optionIdentifier].push({
name : key,
value: object[key]
})
delete object[key]
}
object[optionsIdentifier][optionIdentifier].push({
name : key,
value: object[key]
});
delete object[key];
}
});
};
})
}
if (args[clangObjectName] && Object.prototype.toString.call(args[clangObjectName]) === '[object Object]') {
moveOptions(normalizedArgs[clangObjectName], inputDefinition[clangObjectName]);
} else {
moveOptions(normalizedArgs, inputDefinition);
if (args[clangObjectName] && Object.prototype.toString.call(args[clangObjectName]) === '[object Object]') {
moveOptions(normalizedArgs[clangObjectName], inputDefinition[clangObjectName])
} else {
moveOptions(normalizedArgs, inputDefinition)
}
return normalizedArgs
}
return normalizedArgs;
};
/**
* Basically the result is always {code: [int], msg: [object]}
* Some methods return an immediate object as the msg.
* For example: group_getById outputs:
* {
* code: 0,
* msg: { id: '60', parentId: '0', name: 'test', description: '', ... }
* }
*
* But group_getByObject outputs:
* {
* code: 0,
* msg: {
* Group: [
* { id: '60', parentId: '0', name: 'test', description: ', ... },
* ...
* ]
* }
* }
* And some methods like customer_delete output:
* {
* code: 0,
* msg: true
* }
*/
normalizeOutput (clangMethodName, result) {
const me = this
const outputDefinition = me.description.clangAPI.clangPort[clangMethodName].output
/**
* Normalizes response to an array of objects
*/
var normalizeResponse = function(config, result) {
if (result.msg === true) {
if (config.debug) {
console.log('SOAP success');
}
return [{msg: true}];
}
outputDefinition.outputType = outputDefinition.outputType || (() => {
let outputKeys, match
if (typeof outputDefinition.msg === 'string') {// like "xsd:boolean"
return 'primitive'
}
var msgKeys = Object.keys(result.msg);
if (msgKeys.length === 0) {
if (config.debug) {
console.log('No records found in SOAP response');
outputKeys = Object.keys(outputDefinition.msg)
.filter(key=>(
key !== 'targetNSAlias' &&
key !== 'targetNamespace'
))
if (outputKeys.length === 1 && (match = outputKeys[0].match(/(.*)\[\]$/))) {
// Matches Group[]
return match[1]
}
return 'object'
})()
if (outputDefinition.outputType === 'primitive') {
return result.msg
}
return [];
}
if (msgKeys.length === 1 && result.msg[msgKeys[0]] instanceof Array) {
if (config.debug) {
console.log('SOAP msg array returned'); //{ code: 0, msg: { Group: [Object] } }
if (outputDefinition.outputType === 'object') {
return result.msg
}
return result.msg[msgKeys[0]];
return result.msg[outputDefinition.outputType]
}
if (result.msg) { // 1 record from x_getById or x_create
if (config.debug) {
console.log('SOAP msg object returned');
}
return [result.msg];
}
};
/**
* Factory for clang method callback builder. It deals with:
* - getting a resource id status
* - getting the data set when the resource is READY
* - normalizing the response
* @param config - a reference to the configuration passed when instantiating Clang
* @param api - a reference to a previously created soap client with Clang's wsdl and node-soap
* @param callback - the method for handling the final data set
*/
var buildCallback = function (uuid, config, api, pagingArgs, callback) {
deleteAll(resourceName, cb) {
const me = this
return function apiCallback(err, result) {
if (config.logRequests) {
console.log(api.lastRequest);
}
/*
first result that indicate there's a recource looks like this: { code: 0, msg: '210577' }
second result of fetching resource status looks like this result looks like: {
code: 0,
msg: {
id : "147082",
type : "CUSTOMER_SET",
status: "READY",
size : "1"
async.autoInject({
getAll: (cb) => {
me.request(`${resourceName}_getAll`, cb)
},
prompt: (getAll, cb) => {
if (me.config.mock) {
return setImmediate(cb.bind(null, null, {answer: 'y'}))
}
prompt.start()
prompt.get({
name: 'answer',
message: `Sure you want to delete all ${getAll.length} ${resourceName}s`,
validator: /y[es]*|n[o]?/i,
warning: 'Must respond [Y]es or [N]o',
required: false,
default: 'No'
}, cb)
},
deleteAll: (getAll, prompt, cb) => {
console.log('prompt', prompt)
if (!prompt.answer.match(/y[es]*/i)) {
return setImmediate(cb)
}
console.log(`Deleting ${getAll.length} ${resourceName}s...`)
async.map(getAll, (item, cb) => {
if (resourceName === 'customer') {
me.request(`${resourceName}_delete`, {[resourceName]: item}, cb)
} else {
me.request(`${resourceName}_delete`, {[resourceName+'Id']: item.id}, cb)
}
}, cb)
}
*/
var resourceId;
if (err) {
console.log('SOAP error arrived', err.body);
}
}, cb)
}
}
//Check for actual SOAP Fault generated by callback in soap/lib/client line 152
if (err) {
if (err.body &&
err.body.match(/<faultcode>107<\/faultcode>/) &&
err.body.match(/customer already member of/) )
return callback(null, [{msg: true, warning: err.body}]);
return callback(err);
}
//Check for No SOAP Fault, but result code not 0
if (result.code != 0) {
return callback(new Error('No SOAP Fault, but result code not 0: ' + JSON.stringify(result.msg)));
}
//Check for No SOAP Fault, but result.msg = false (probably delete that didn't work out)
if (result.msg === false) {
return callback(new Error('No SOAP Fault, but result msg is false (delete failed?): ' + JSON.stringify(result.msg)));
}
if (result.msg === null) {
// this occurs for example when doing a customer_getByExternalId with a externalId that is not found
//Private stuff starts here
const isObject = o => (!!o) && (o.constructor === Object)
const getResourceSet = (instance, resourceId, methodName, normalizedArgs, pagingArgs, cb) => {
const setMethods = {
CUSTOMER_SET: 'customerSet_getCustomers',
MAILING_SET : 'mailingSet_getMailings',
OPEN_SET : 'openSet_getOpens',
CLICK_SET : 'clickSet_getClicks',
BOUNCE_SET : 'bounceSet_getBounces'
}
let config = instance.config
let api = instance.api
let uuid = normalizedArgs.uuid
async.autoInject({
poll: (cb) => {
async.doWhilst(cb => {
if (config.debug) {
console.log(`Checking state of resource: ${resourceId} for ${methodName}`)
}
api.resource_getById({ uuid, resourceId }, (err, result) => {
if (err) return cb(err)
if (config.debug) {
console.log(`Resource ${result.msg.type} = ${result.msg.status} (id: ${result.msg.id}, records: ${result.msg.size})`)
}
if (result && result.msg.status === 'ERROR') {
return cb(new Error(`Error getting resource ${resourceId} (probably incorrect x_getByObject request parameters)`))
}
if (result && result.msg.status === 'CLOSED') {
return cb(new Error(`Resource ${resourceId} was closed unexpectedly`))
}
cb(null, result)
})
}, (result) => (result.msg.status === 'PROCESSING'), cb)
},
/**
* @param {object} poll - result of READY resource set
* {
* code: 0,
* msg: {
* id : "147082",
* type : "CUSTOMER_SET",
* status: "READY",
* size : "1"
* }
* }
*/
getData: (poll, cb) => {
if (config.debug) {
console.log('No records found in SOAP response (msg=null)');
console.log(`Getting records from resourceId: ${resourceId} using ${setMethods[poll.msg.type]}`)
}
return callback(null, []);
}
if (result.msg === undefined) {
return callback(new Error('No SOAP Fault, but result msg is undefined (unexpected): ' + JSON.stringify(result.msg)));
}
//When resource is READY fetch the data using the resourceMethod
if (result.msg.status === 'ERROR') {
return callback(new Error('Error getting resource (probably incorrect x_getByObject request parameters)'));
}
//Check for result that should be fetched using new SOAP call
if (typeof result.msg === 'string' || //first attempt trying if resource is READY
result.msg.status === 'PROCESSING' //next attempts trying if resource is READY
) {
resourceId = typeof result.msg === 'string' ? result.msg : result.msg.id;
if (config.debug) {
console.log('Invoking SOAP method to see if resourceId is READY: ' + resourceId);
if (!(poll.msg.size > 0)) {
if (config.debug) {
console.log(`No records found in SOAP response`)
}
// poll.msg.size = 0, no need to go fetch result set
return setImmediate(cb.bind(null, null, []))
}
api.resource_getById({
uuid : uuid,
resourceId: resourceId
}, apiCallback);
return;
}
if (result.msg.status === 'READY') {
resourceId = result.msg.id;
if (config.debug) {
console.log('Resource of resourceId: ' + resourceId + ' is READY ('+result.msg.size+ ' records)');
}
if (result.msg.size > 0) {
async.series([
function(callback){
if (config.debug) {
console.log('Getting data from resource with resourceId: ' + resourceId + ' using ' + setMethods[result.msg.type]);
}
api[setMethods[result.msg.type]]({
uuid : uuid,
resourceId: resourceId,
offset : pagingArgs.offset,
size : pagingArgs.size
}, function (err, result) {
if (config.logRequests) {
console.log(api.lastRequest);
}
if (err) {
if (config.debug) {
console.error('Error in getting resource set');
}
return callback(err);
}
callback(null, normalizeResponse(config, result));
});
},
function(callback){
if (config.debug) {
console.log('Free resource with resourceId: ' + resourceId);
}
api.resource_free({
uuid : uuid,
resourceId: resourceId
}, callback);
let setMethodName = setMethods[poll.msg.type]
api[setMethodName]({
uuid, resourceId,
offset : pagingArgs.offset,
size : pagingArgs.size
}, (err, result) => {
if (config.logPayload) {
console.log('payload outgoing', api.lastRequest)
// console.log('payload incoming', raw)
}
if (err) {
if (config.debug) {
console.error('Error in getting resource set')
}
],
function(err, results) {
if (config.logRequests) {
console.log(api.lastRequest);
}
callback(err, results[0]);
});
return;
}
return cb(err)
}
cb(null, instance.normalizeOutput(setMethodName, result))
})
},
freeResource: (getData, cb) => {
if (config.debug) {
console.log('No records found in SOAP response');
console.log(`Free resource with resourceId: ${resourceId}`)
}
return callback(null, []);
api.resource_free({ uuid, resourceId }, cb)
}
if (result.msg) {
return callback(null, normalizeResponse(config, result));
}, (err, result) => {
if (config.logPayload) {
console.log('payload outgoing', api.lastRequest)
// console.log('payload incoming', raw)
}
cb(err, result.getData)
})
}
return callback(new Error('Unexpected and unhandled response: ' + JSON.stringify(result.msg)));
};
};
module.exports = Clang;
module.exports = Clang

@@ -1,69 +0,88 @@

module.exports = [
{
methodName: 'customer_insert',
result: [
{
smsOptIn: 'Y',
optIn: 'Y',
id: '14880',
title: '',
lastname: '',
firstname: '',
middlename: '',
prefix: '',
suffix: '',
initials: '',
gender: 'UNKNOWN',
maritalStatus: 'UNKNOWN',
birthday: '0000-00-00',
birthplace: '',
address: '',
address2: '',
address3: '',
addressNumber: '',
addressNumberSuffix: '',
zipCode: '',
poBox: '0',
city: '',
state: '',
country: '',
companyName: '',
department: '',
alternateAddress: '',
alternateAddress2: '',
alternateAddress3: '',
alternateAddressNumber: '',
alternateAddressNumberSuffix: '',
alternateZipCode: '',
alternateCity: '',
alternateState: '',
alternateCountry: '',
contactMan: '',
jobTitle: '',
workPhone: '',
workExtension: '',
workMobile: '',
homePhone: '',
mobilePhone: '',
faxNumber: '',
emailAddress: '',
alternateEmailAddress: '',
emailType: 'MULTIPART',
mailStatus: 'ACTIVE',
softBounceCount: '0',
hardBounceCount: '0',
website: '',
imageUrl: '',
status: 'SUSPECT',
externalId: '',
userName: '',
password: '',
createdBy: 'SOAP user (int)',
createdAt: '2017-06-23 18:01:57',
modifiedBy: 'SOAP user (int)',
modifiedAt: '2017-06-23 18:01:57',
options: []
}
]
module.exports = {
customer: {
smsOptIn: 'Y',
optIn: 'Y',
id: '14880',
title: '',
lastname: '',
firstname: '',
middlename: '',
prefix: '',
suffix: '',
initials: '',
gender: 'UNKNOWN',
maritalStatus: 'UNKNOWN',
birthday: '0000-00-00',
birthplace: '',
address: '',
address2: '',
address3: '',
addressNumber: '',
addressNumberSuffix: '',
zipCode: '',
poBox: '0',
city: '',
state: '',
country: '',
companyName: '',
department: '',
alternateAddress: '',
alternateAddress2: '',
alternateAddress3: '',
alternateAddressNumber: '',
alternateAddressNumberSuffix: '',
alternateZipCode: '',
alternateCity: '',
alternateState: '',
alternateCountry: '',
contactMan: '',
jobTitle: '',
workPhone: '',
workExtension: '',
workMobile: '',
homePhone: '',
mobilePhone: '',
faxNumber: '',
emailAddress: '',
alternateEmailAddress: '',
emailType: 'MULTIPART',
mailStatus: 'ACTIVE',
softBounceCount: '0',
hardBounceCount: '0',
website: '',
imageUrl: '',
status: 'SUSPECT',
externalId: '',
userName: '',
password: '',
createdBy: 'SOAP user (int)',
createdAt: '2017-06-23 18:01:57',
modifiedBy: 'SOAP user (int)',
modifiedAt: '2017-06-23 18:01:57',
options: []
},
email: {
folder: '/',
class: 'EMAIL',
id: '59846',
type: 'MULTIPART',
templateId: '0',
name: 'test-name',
campaignId: '0',
fromAddress: 'user@example.com',
fromName: 'Smith',
replyToAddress: '',
replyToName: '',
subject: 'test-name',
createdBy: 'SOAP user (ext)',
createdAt: '2017-07-05 14:34:41',
modifiedBy: 'SOAP user (ext)',
modifiedAt: '2017-07-05 14:34:41',
htmlContent: '<html><body><h1>Newsletter</h1>HTML body</body></html>',
htmlBlocks: null,
textContent: 'Newsletter text body'
},
empty_email: {
type: 'MULTIPART'
}
]
}

@@ -5,14 +5,152 @@ 'use strict'

module.exports = function(clang) {
clang.request = function(methodName, data, cb) {
let result = (fixtures.find(item => item.methodName === methodName) || {
result: null
}).result
const database = {
customer:[],
email :[],
group :[]
}
if (methodName === 'customer_insert') {
result[0] = Object.assign({}, result[0], data.customer)
const newId = (resourceName) => {
const table = database[resourceName]
if (!table) return 1
return 1 + table.reduce((acc, item) => Math.max(acc, item.id), 0)
}
module.exports.api = (clang, methodName) => {
return (normalizedArgs, cb) => {
const [,resourceName, resourceMethod] = methodName.match(/^(\w+)_(\w+)/)
const table = database[resourceName]
let items, index, stripped
let nestedArgs = normalizedArgs[resourceName]
if (resourceMethod === 'create') {
items = Object.assign({}, fixtures['empty_' + resourceName] || fixtures[resourceName])
}
if (resourceMethod === 'insert') {
if (resourceName === 'email') {
if (!nestedArgs) {
return setImmediate(cb.bind(null, {Fault: {faultcode: 105}}))
}
if (!nestedArgs.type) {
return setImmediate(cb.bind(null, {Fault: {faultcode: 105}}))
}
if (!nestedArgs.name) {
return setImmediate(cb.bind(null, {Fault: {faultcode: 751}}))
}
}
stripped = Object.assign({}, fixtures[resourceName], nestedArgs, {id: newId(resourceName)})
setImmediate(cb.bind(null, null, result))
table.push(stripped)
items = table[table.length - 1]
}
if (resourceMethod === 'update') {
items = table.find(item => item.id === (nestedArgs && nestedArgs.id))
if (items) {
items = Object.assign({}, items, nestedArgs)
} else {
return setImmediate(cb.bind(null, {Fault: {faultcode: 213}}))
}
}
if (resourceMethod === 'delete') {
index = table.findIndex(item => {
// fix inconsistency between xxx_delete methods
if (resourceName === 'customer') {
return item.id === (nestedArgs && nestedArgs.id)
}
return item.id === normalizedArgs[resourceName + 'Id']
})
if (index > -1) {
table.splice(index, 1)
}
items = true
}
if (resourceMethod === 'getById') {
items = table.find(item => item.id === normalizedArgs[resourceName + 'Id'])
items = Object.assign({}, items)
}
if (resourceMethod === 'getByEmailAddress') {
items = table.filter(item => item.id === normalizedArgs['emailAddress'])
}
if (resourceMethod === 'getByExternalId') {
items = table.filter(item => item.id === normalizedArgs['externalId'])
}
if (resourceMethod === 'getByObject') {
items = table.filter(item => !Object.keys(nestedArgs).find(key => nestedArgs[key] != item[key]))
}
if (resourceMethod === 'getAll') {
items = table.filter(item => !!item) // make copy
}
const result = {
code: 0,
msg: getOutput(
clang,
methodName,
items
)
}
setImmediate(cb.bind(null, null, [result, 'fake-soap-reponse', 'soap-headers']))
}
}
/**
* Basically the result is always {code: [int], msg: [object]}
* Some methods return an immediate object as the msg.
* For example: group_getById outputs:
* {
* code: 0,
* msg: { id: '60', parentId: '0', name: 'test', description: '', ... }
* }
*
* But group_getByObject outputs:
* {
* code: 0,
* msg: {
* Group: [
* { id: '60', parentId: '0', name: 'test', description: ', ... },
* ...
* ]
* }
* }
* And some methods like customer_delete output:
* {
* code: 0,
* msg: true
* }
*/
const getOutput = (clang, clangMethodName, data) => {
const outputDefinition = clang.description.clangAPI.clangPort[clangMethodName].output
outputDefinition.outputType = outputDefinition.outputType || (() => {
let outputKeys, match
if (typeof outputDefinition.msg === 'string') {// like "xsd:boolean"
return 'primitive'
}
outputKeys = Object.keys(outputDefinition.msg)
.filter(key=>(
key !== 'targetNSAlias' &&
key !== 'targetNamespace'
))
if (outputKeys.length === 1 && (match = outputKeys[0].match(/(.*)\[\]$/))) {
// Matches Group[]
return match[1]
}
return 'object'
})()
if (outputDefinition.outputType === 'primitive') {
return data
}
if (outputDefinition.outputType === 'object') {
return data
}
return {[outputDefinition.outputType]: data}
}
{
"name": "clang",
"version": "0.2.10",
"version": "1.0.1",
"description": "Node.js api wrapper for Clang's SOAP api",

@@ -11,22 +11,33 @@ "author": "Christiaan Westerbeek <chris@devotis.nl>",

],
"repository": {
"type": "git",
"url": "https://github.com/devotis/clang.git"
"scripts": {
"pretest": "npm run lint",
"test": "node ./test",
"lint": "eslint --ext=.js ."
},
"main": "./index.js",
"directories": {
"lib": "./lib"
},
"engines": {
"node": ">=4.4.5"
"node": ">=6.5"
},
"dependencies": {
"async": "2.4.1",
"lodash": "4.17.4",
"moment": "2.18.1",
"prompt": "1.0.0",
"soap": "0.19.2"
},
"devDependencies": {
"mocha": "^3.4.2",
"should": "^11.2.1"
"config": "1.26.1",
"eslint": "4.1.1",
"tape": "4.6.3"
},
"directories": {
"lib": "./lib"
"homepage": "https://github.com/devotis/clang",
"repository": {
"type": "git",
"url": "https://github.com/devotis/clang.git"
},
"bugs": {
"url": "https://github.com/devotis/clang/issues"
},
"licenses": [

@@ -37,10 +48,3 @@ {

}
],
"homepage": "https://github.com/devotis/clang",
"bugs": {
"url": "https://github.com/devotis/clang/issues"
},
"scripts": {
"test": "mocha"
}
]
}

@@ -7,32 +7,41 @@ [![Build Status](https://travis-ci.org/devotis/node-clang.svg)](https://travis-ci.org/devotis/node-clang)

Anyone using <a href="http://www.createaclang.com/">Clang</a>'s SOAP api? Here's something that will make your life easier. A Node.js api that wraps Clang's SOAP api. You don't need to bother with the extra resource callbacks that some methods like customer_getByObject require. Internally these resources are polled and the final dataset is returned in the callback.
Using <a href="http://www.createaclang.com/">Clang</a>'s SOAP api? Here's a module that will make your life easier. A Node.js api that wraps Clang's SOAP api. You don't need to bother with the extra resource callbacks that some methods like customer_getByObject require. Internally these resources are polled and the final dataset is returned in the callback.
I had a ton of fun building this. Node.js rocks!
##Install
## Install
npm install clang
```javascript
npm install clang
```
##Example
## 1.0 breaking changes
- instantiation without new keyword is not possible anymore.
- config.normalizeOptionFields does not default to true anymore. It doesn't default at all.
- config.logRequests is renamed to config.logPayload and also logs raw xml output
- customer_getById and similar methods that would normally output an object have always been wrapped in an array by this module. From 1.0 on when single objects are expected they are no longer wrapped in an array. methods that would normally output an array like customer_getAll, but output just one because of the available data (and/or filter set) continue to be wrapped in an array.
var Clang = require("./lib/clang");
## Example
var clang = new Clang({uuid: '12345678-1234-1234-1234-123456781234'});
```javascript
const Clang = require('clang')
const clang = new Clang({uuid: '12345678-1234-1234-1234-123456781234'})
clang.request('group_getMembers', {
groupId: 2
}, function(err, result) {
console.log(arguments);
});
clang.request('group_getMembers', {
groupId: 2
}, (err, result) => {
console.log(err, result)
})
//Paging using the special parameters _size and _offset. Defaults are 50 and 0
clang.request('customer_getAll', {
_size: 10
}, function(err, result) {
console.log(arguments);
});
// Paging using the special parameters _size and _offset. Defaults are 50 and 0
clang.request('customer_getAll', {
_size: 10
}, (err, result) => {
console.log(err, result)
})
```
##WSDL
This is the underlying WSDL document that is used
## WSDL
This is the underlying WSDL document.
https://secure.myclang.com/app/api/soap/public/index.php?wsdl

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc