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.
This commit is contained in:
commit
b4150c8250
1364 changed files with 6814366 additions and 0 deletions
310
vendor/zapier-platform/packages/legacy-scripting-runner/bundle.js
vendored
Normal file
310
vendor/zapier-platform/packages/legacy-scripting-runner/bundle.js
vendored
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
const _ = require('lodash');
|
||||
|
||||
const { isFileField, hasFileFields, LazyFile } = require('./file');
|
||||
|
||||
// Max parts a key can have for unflattening
|
||||
const MAX_KEY_PARTS = 6;
|
||||
|
||||
// Replace '{{bundle.inputData.abc}}' with '{{abc}}'
|
||||
const undoCurlyReplacement = (str) =>
|
||||
str ? str.replace(/{{\s*bundle\.[^.]+\.([^}\s]+)\s*}}/g, '{{$1}}') : str;
|
||||
|
||||
// Unflatten from {key__child: value} to {key: {child: value}}
|
||||
const unflattenObject = (data, separator = '__') => {
|
||||
if (Object(data) !== data || _.isArray(data)) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const keys = Object.keys(data);
|
||||
|
||||
_.each(keys, (key) => {
|
||||
if (
|
||||
key.includes(separator) &&
|
||||
!key.startsWith(separator) &&
|
||||
!key.endsWith(separator)
|
||||
) {
|
||||
const value = data[key];
|
||||
const keyParts = key.split(separator, MAX_KEY_PARTS);
|
||||
|
||||
let i;
|
||||
let previousProp = data;
|
||||
let currentProp = data;
|
||||
|
||||
for (i = 0; i < keyParts.length; i++) {
|
||||
currentProp[keyParts[i]] = currentProp[keyParts[i]] || {};
|
||||
previousProp = currentProp;
|
||||
currentProp = currentProp[keyParts[i]];
|
||||
}
|
||||
|
||||
previousProp[keyParts[i - 1]] = value;
|
||||
|
||||
delete data[key];
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
const convertToQueryString = (object) => {
|
||||
if (!object) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const objectKeys = Object.keys(object);
|
||||
const queryStringItems = _.map(objectKeys, (key) => `${key}=${object[key]}`);
|
||||
|
||||
return queryStringItems.join('&');
|
||||
};
|
||||
|
||||
//
|
||||
// Methods with logic for bundleConverter
|
||||
//
|
||||
|
||||
const addAuthData = (event, bundle, convertedBundle) => {
|
||||
// Get authData only for basic auth here, to include in the request
|
||||
const { username, password } = _.get(bundle, 'authData', {});
|
||||
|
||||
if (username && password) {
|
||||
convertedBundle.request.auth = [username, password];
|
||||
}
|
||||
|
||||
// OAuth2 specific
|
||||
if (event.name.startsWith('auth.oauth2')) {
|
||||
convertedBundle.oauth_data = {
|
||||
client_id: process.env.CLIENT_ID,
|
||||
client_secret: process.env.CLIENT_SECRET,
|
||||
};
|
||||
convertedBundle.load = _.get(bundle, 'request.body', {});
|
||||
}
|
||||
};
|
||||
|
||||
const addInputData = (event, bundle, convertedBundle) => {
|
||||
if (event.name === 'auth.connectionLabel') {
|
||||
convertedBundle.test_result = bundle.inputData;
|
||||
} else if (event.name.startsWith('trigger.')) {
|
||||
convertedBundle.trigger_fields = bundle.inputData;
|
||||
convertedBundle.trigger_fields_raw =
|
||||
bundle.inputDataRaw || bundle.inputData;
|
||||
} else if (event.name.startsWith('create.')) {
|
||||
convertedBundle.action_fields = bundle._unflatInputData;
|
||||
convertedBundle.action_fields_full = bundle.inputData;
|
||||
convertedBundle.action_fields_raw = bundle.inputDataRaw || bundle.inputData;
|
||||
} else if (event.name.startsWith('search.')) {
|
||||
convertedBundle.search_fields = bundle.inputData;
|
||||
|
||||
if (event.name.startsWith('search.resource')) {
|
||||
convertedBundle.read_fields = event.results || bundle.inputData;
|
||||
convertedBundle.read_context = bundle.inputData;
|
||||
}
|
||||
} else if (event.name === 'hydrate.method') {
|
||||
Object.assign(convertedBundle, bundle.inputData.bundle);
|
||||
} else if (event.name.startsWith('auth.oauth2.token')) {
|
||||
// Overwrite convertedBundle.auth_fields with bundle.inputData
|
||||
convertedBundle.auth_fields = Object.keys(
|
||||
convertedBundle.auth_fields,
|
||||
).reduce((result, k) => {
|
||||
result[k] = bundle.inputData[k] || convertedBundle.auth_fields[k];
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
};
|
||||
|
||||
const addHookData = (event, bundle, convertedBundle) => {
|
||||
if (event.name === 'trigger.hook') {
|
||||
convertedBundle.request = bundle.rawRequest || convertedBundle.request;
|
||||
convertedBundle.cleaned_request = bundle.cleanedRequest;
|
||||
|
||||
if (!convertedBundle.request.querystring) {
|
||||
convertedBundle.request.querystring = convertToQueryString(
|
||||
bundle.inputData,
|
||||
);
|
||||
}
|
||||
if (!convertedBundle.request.content) {
|
||||
convertedBundle.request.content = convertedBundle.request.data || '';
|
||||
}
|
||||
} else if (event.name.startsWith('trigger.hook.subscribe')) {
|
||||
convertedBundle.target_url = bundle.targetUrl;
|
||||
convertedBundle.subscription_url = bundle.targetUrl;
|
||||
convertedBundle.event = bundle._legacyEvent;
|
||||
convertedBundle.trigger_data = bundle.inputData;
|
||||
} else if (event.name.startsWith('trigger.hook.unsubscribe')) {
|
||||
convertedBundle.target_url = bundle.targetUrl;
|
||||
convertedBundle.subscription_url = bundle.targetUrl;
|
||||
convertedBundle.subscribe_data = bundle.subscribeData;
|
||||
convertedBundle.event = bundle._legacyEvent;
|
||||
convertedBundle.trigger_data = bundle.inputData;
|
||||
}
|
||||
};
|
||||
|
||||
const addRequestData = async (event, z, bundle, convertedBundle) => {
|
||||
Object.assign(
|
||||
convertedBundle.request.headers,
|
||||
_.get(bundle, 'request.headers'),
|
||||
);
|
||||
|
||||
Object.assign(
|
||||
convertedBundle.request.params,
|
||||
_.get(bundle, 'request.params'),
|
||||
);
|
||||
|
||||
const body = _.get(bundle, 'request.body');
|
||||
if (!_.isEmpty(body)) {
|
||||
let data = body;
|
||||
let files;
|
||||
|
||||
if (typeof data !== 'string' && !event.name.startsWith('auth.oauth2')) {
|
||||
if (hasFileFields(bundle)) {
|
||||
// Exclude file fields from request.data
|
||||
data = Object.keys(body)
|
||||
.filter((k) => !isFileField(k, bundle))
|
||||
.reduce((result, k) => {
|
||||
result[k] = body[k];
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
const fileFieldKeys = Object.keys(body).filter((k) =>
|
||||
isFileField(k, bundle),
|
||||
);
|
||||
const fileMetas = await Promise.all(
|
||||
fileFieldKeys.map((k) => LazyFile(body[k]).meta()),
|
||||
);
|
||||
|
||||
files = _.zip(fileFieldKeys, fileMetas)
|
||||
.map(([k, meta]) => {
|
||||
const urlOrContent = body[k];
|
||||
return [k, [meta.filename, urlOrContent, meta.contentType]];
|
||||
})
|
||||
.reduce((result, [k, file]) => {
|
||||
result[k] = file;
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
|
||||
convertedBundle.request.data = data;
|
||||
|
||||
if (!_.isEmpty(files)) {
|
||||
convertedBundle.request.files = files;
|
||||
delete convertedBundle.request.headers['Content-Type'];
|
||||
}
|
||||
} else if (event.name.startsWith('auth.oauth2.refresh.pre')) {
|
||||
// Make sure bundle.request.data is an object
|
||||
convertedBundle.request.data = convertedBundle.request.data || {};
|
||||
} else if (event.name.startsWith('create')) {
|
||||
if (
|
||||
!_.isEmpty(bundle._unflatInputData) ||
|
||||
(!event.name.includes('.input') && !event.name.includes('.output'))
|
||||
) {
|
||||
// Only stringify an empty object when we aren't fetching custom fields
|
||||
convertedBundle.request.data = JSON.stringify(bundle._unflatInputData);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const addResponse = (event, bundle, convertedBundle) => {
|
||||
if (event.name.endsWith('.post')) {
|
||||
convertedBundle.response = { ...event.response };
|
||||
convertedBundle.response.status_code = convertedBundle.response.status;
|
||||
if (convertedBundle.response.request) {
|
||||
convertedBundle.response.request = {
|
||||
...convertedBundle.response.request,
|
||||
};
|
||||
// `request.input` contains the entire app definition, which is big and
|
||||
// unnecessary for legacy scripting
|
||||
delete convertedBundle.response.request.input;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const convertBundleMeta = (meta) => {
|
||||
if (_.isEmpty(meta)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const newMeta = {
|
||||
auth_test:
|
||||
meta.auth_test === undefined ? meta.isTestingAuth : meta.auth_test,
|
||||
frontend:
|
||||
meta.frontend === undefined ? meta.isLoadingSample : meta.frontend,
|
||||
prefill:
|
||||
meta.prefill === undefined ? meta.isFillingDynamicDropdown : meta.prefill,
|
||||
hydrate: true,
|
||||
test_poll:
|
||||
meta.test_poll === undefined ? meta.isTestingAuth : meta.test_poll,
|
||||
first_poll:
|
||||
meta.first_poll === undefined ? meta.isPopulatingDedupe : meta.first_poll,
|
||||
limit: meta.limit,
|
||||
page: meta.page,
|
||||
isBulkRead: meta.isBulkRead || false,
|
||||
};
|
||||
newMeta.standard_poll = !newMeta.test_poll;
|
||||
return newMeta;
|
||||
};
|
||||
|
||||
// Convert bundle from CLI to WB based on which event to run
|
||||
const bundleConverter = async (bundle, event, z) => {
|
||||
let requestMethod = _.get(bundle, 'request.method');
|
||||
if (!requestMethod) {
|
||||
if (
|
||||
event.name.startsWith('create') ||
|
||||
event.name.startsWith('auth.oauth2') ||
|
||||
event.name.startsWith('trigger.hook.subscribe')
|
||||
) {
|
||||
requestMethod = 'POST';
|
||||
} else if (event.name === 'trigger.hook.unsubscribe.pre') {
|
||||
requestMethod = 'DELETE';
|
||||
} else {
|
||||
requestMethod = 'GET';
|
||||
}
|
||||
}
|
||||
|
||||
// From inputData, remove keys that are in fieldsExcludedFromBody
|
||||
const excludedFieldKeys = bundle._fieldsExcludedFromBody || [];
|
||||
const filteredInputData = Object.keys(bundle.inputData || {})
|
||||
.filter((key) => !excludedFieldKeys.includes(key))
|
||||
.reduce((fields, key) => {
|
||||
return { [key]: bundle.inputData[key], ...fields };
|
||||
}, {});
|
||||
|
||||
// Attach to bundle so we can reuse it
|
||||
bundle._unflatInputData = unflattenObject(filteredInputData);
|
||||
|
||||
const meta = convertBundleMeta(bundle.meta);
|
||||
const zap = _.get(bundle, 'meta.zap') || { id: 0 };
|
||||
|
||||
const convertedBundle = {
|
||||
request: {
|
||||
method: requestMethod,
|
||||
url: undoCurlyReplacement(_.get(bundle, 'request.url', '')),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
params: {},
|
||||
data: !requestMethod || requestMethod === 'GET' ? null : '',
|
||||
},
|
||||
auth_fields: _.get(bundle, 'authData', {}),
|
||||
meta,
|
||||
zap,
|
||||
};
|
||||
|
||||
if (bundle._legacyUrl) {
|
||||
convertedBundle.raw_url = convertedBundle.url_raw = undoCurlyReplacement(
|
||||
bundle._legacyUrl,
|
||||
);
|
||||
}
|
||||
|
||||
addAuthData(event, bundle, convertedBundle);
|
||||
addInputData(event, bundle, convertedBundle);
|
||||
await addRequestData(event, z, bundle, convertedBundle);
|
||||
addHookData(event, bundle, convertedBundle);
|
||||
addResponse(event, bundle, convertedBundle);
|
||||
|
||||
return convertedBundle;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
bundleConverter,
|
||||
unflattenObject,
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue