master-zapier-plan-draft/vendor/zapier-platform/packages/legacy-scripting-runner/zfactory.js
George Lambert b4150c8250 Milestone 0: import zappier billing, Verae middleware, and Zapier research
Compose-ready workspace: packages/zappier (rate card, portal, Stripe),
packages/verae-zapier-middleware (timestamp + NATS), packages/verae-zapier
(CLI app), vendor/zapier-platform, and research/zapier vendor corpus.

Gate 0 structure checks pass. Product code and research are not yet wired.
2026-09-09 02:37:36 -04:00

207 lines
5 KiB
JavaScript

const crypto = require('crypto');
const _ = require('lodash');
const request = require('request');
const { createSyncFn } = require('synckit');
// So `zapier build` doesn't forget to include request-worker.js
require('./request-worker');
// Converts WB `bundle.request` format to something `request` can use
const convertBundleRequest = (bundleOrBundleRequest) => {
bundleOrBundleRequest = _.extend({}, bundleOrBundleRequest);
// LEGACY: allow for the whole bundle to mistakingly be sent over
const bundleRequest = bundleOrBundleRequest.request
? bundleOrBundleRequest.request
: bundleOrBundleRequest;
if (
bundleRequest.auth &&
Array.isArray(bundleRequest.auth) &&
bundleRequest.auth.length === 2
) {
bundleRequest.auth = {
user: bundleRequest.auth[0],
password: bundleRequest.auth[1],
};
}
if (!bundleRequest.qs && bundleRequest.params) {
bundleRequest.qs = bundleRequest.params;
}
if (!bundleRequest.body && bundleRequest.data) {
bundleRequest.body = bundleRequest.data;
}
delete bundleRequest.params;
delete bundleRequest.data;
return bundleRequest;
};
const parseBody = (body) => {
if (body) {
if (typeof body === 'string' || body.writeInt32BE) {
return String(body);
}
return body;
}
return null;
};
// Converts `request`'s response into a simplified object
const convertResponse = (response) => {
if (response) {
return {
status_code: response.statusCode,
headers: _.extend({}, response.headers),
content: parseBody(response.body),
};
}
return {};
};
const syncRequest = createSyncFn(require.resolve('./request-worker'));
const zfactory = (zcli, app, logger) => {
const AWS = () => {
// Direct require breaks the build as the module isn't found by browserify
const moduleName = 'aws-sdk';
return require(moduleName);
};
const jsonParse = (str) => {
try {
return JSON.parse(str);
} catch (err) {
let preview = str;
if (str && str.length > 100) {
preview = str.substr(0, 100);
}
throw new Error(`Error parsing response. We got: "${preview}"`);
}
};
const jsonStringify = (obj) => {
try {
return JSON.stringify(obj);
} catch (err) {
throw new Error(err.message);
}
};
const sendHttpLog = (req, res) => {
// Log fields here intend to match the ones in createHttpPatch in core
const method = (req.method || 'GET').toUpperCase();
const url = req.url || req.uri;
const responseBody =
typeof res.content === 'string'
? res.content
: 'Could not show response content';
logger(`${res.status_code} ${method} ${url}`, {
log_type: 'http',
request_type: 'devplatform-outbound',
request_url: url,
request_method: method,
request_headers: req.headers,
request_params: req.qs,
request_data: req.data,
request_via_client: false,
response_status_code: res.status_code,
response_headers: res.headers,
response_content: responseBody,
});
};
const requestMethod = (bundleRequest, callback) => {
const options = convertBundleRequest(bundleRequest);
if (_.isFunction(callback)) {
return request(options, (err, response) =>
callback(err, convertResponse(response)),
);
}
const normalizedOptions = request.initParams(options);
const response = syncRequest(normalizedOptions);
const convertedResponse = convertResponse(response);
// syncRequest() is done by a worker thread, which isn't httpPatch'ed, so we
// need to explicit write the http log here
if (logger) {
sendHttpLog(normalizedOptions, convertedResponse);
}
return convertedResponse;
};
const hash = (
algorithm,
string,
encoding = 'hex',
inputEncoding = 'binary',
) => {
const hasher = crypto.createHash(algorithm);
hasher.update(string, inputEncoding);
return hasher.digest(encoding);
};
const hmac = (algorithm, key, string, encoding = 'hex') => {
const hasher = crypto.createHmac(algorithm, key);
hasher.update(string);
return hasher.digest(encoding);
};
const snipify = (string) => {
const SALT = process.env.SECRET_SALT || 'doesntmatterreally';
if (!_.isString(string)) {
return null;
}
const length = string.length;
string += SALT;
const result = hash('sha256', string);
return `:censored:${length}:${result.substr(0, 10)}:`;
};
const dehydrate = (method, bundle) => {
return zcli.dehydrate(app.hydrators.legacyMethodHydrator, {
method,
bundle,
});
};
const dehydrateFile = (url, requestOptions, meta) => {
return zcli.dehydrateFile(app.hydrators.legacyFileHydrator, {
url,
request: requestOptions,
meta,
});
};
return {
AWS,
JSON: {
parse: jsonParse,
stringify: jsonStringify,
},
request: requestMethod,
hash,
hmac,
snipify,
dehydrate,
dehydrateFile,
};
};
module.exports = zfactory;