mirror of
https://github.com/usebruno/bruno.git
synced 2026-07-02 17:08:32 +00:00
* fix: update logic for checking formdata instance * fix: isFormData logic update * fix: review comment fix, add isFormData to @usebruno/common package * fix: review comment fix
165 lines
4.6 KiB
JavaScript
165 lines
4.6 KiB
JavaScript
const { customAlphabet } = require('nanoid');
|
|
const iconv = require('iconv-lite');
|
|
const { cloneDeep } = require('lodash');
|
|
const { formatMultipartData } = require('./form-data');
|
|
const { isFormData } = require('@usebruno/common').utils;
|
|
|
|
// a customized version of nanoid without using _ and -
|
|
const uuid = () => {
|
|
// https://github.com/ai/nanoid/blob/main/url-alphabet/index.js
|
|
const urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict';
|
|
const customNanoId = customAlphabet(urlAlphabet, 21);
|
|
|
|
return customNanoId();
|
|
};
|
|
|
|
const stringifyJson = async (str) => {
|
|
try {
|
|
return JSON.stringify(str, null, 2);
|
|
} catch (err) {
|
|
return Promise.reject(err);
|
|
}
|
|
};
|
|
|
|
const parseJson = async (obj) => {
|
|
try {
|
|
return JSON.parse(obj);
|
|
} catch (err) {
|
|
return Promise.reject(err);
|
|
}
|
|
};
|
|
|
|
const getCircularReplacer = () => {
|
|
const seen = new WeakSet();
|
|
return (key, value) => {
|
|
if (typeof value === 'object' && value !== null) {
|
|
if (seen.has(value)) return '[Circular]';
|
|
seen.add(value);
|
|
}
|
|
return value;
|
|
};
|
|
};
|
|
|
|
const safeStringifyJSON = (data, indent = null) => {
|
|
if (data === undefined) return undefined;
|
|
try {
|
|
// getCircularReplacer - removes circular references that cause an error when stringifying
|
|
return JSON.stringify(data, getCircularReplacer(), indent);
|
|
} catch (e) {
|
|
console.warn('Failed to stringify data:', e.message);
|
|
return data;
|
|
}
|
|
};
|
|
|
|
const safeParseJSON = (data) => {
|
|
try {
|
|
return JSON.parse(data);
|
|
} catch (e) {
|
|
return data;
|
|
}
|
|
};
|
|
|
|
const simpleHash = (str) => {
|
|
let hash = 0;
|
|
for (let i = 0; i < str.length; i++) {
|
|
const char = str.charCodeAt(i);
|
|
hash = (hash << 5) - hash + char;
|
|
hash &= hash; // Convert to 32bit integer
|
|
}
|
|
return new Uint32Array([hash])[0].toString(36);
|
|
};
|
|
|
|
const generateUidBasedOnHash = (str) => {
|
|
const hash = simpleHash(str);
|
|
|
|
return `${hash}`.padEnd(21, '0');
|
|
};
|
|
|
|
const flattenDataForDotNotation = (data) => {
|
|
var result = {};
|
|
function recurse(current, prop) {
|
|
if (Object(current) !== current) {
|
|
result[prop] = current;
|
|
} else if (Array.isArray(current)) {
|
|
for (var i = 0, l = current.length; i < l; i++) {
|
|
recurse(current[i], prop + '[' + i + ']');
|
|
}
|
|
if (l == 0) {
|
|
result[prop] = [];
|
|
}
|
|
} else {
|
|
var isEmpty = true;
|
|
for (var p in current) {
|
|
isEmpty = false;
|
|
recurse(current[p], prop ? prop + '.' + p : p);
|
|
}
|
|
if (isEmpty && prop) {
|
|
result[prop] = {};
|
|
}
|
|
}
|
|
}
|
|
|
|
recurse(data, '');
|
|
return result;
|
|
};
|
|
|
|
const parseDataFromResponse = (response, disableParsingResponseJson = false) => {
|
|
// Parse the charset from content type: https://stackoverflow.com/a/33192813
|
|
const charsetMatch = /charset=([^()<>@,;:"/[\]?.=\s]*)/i.exec(response.headers['content-type'] || '');
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec#using_exec_with_regexp_literals
|
|
const charsetValue = charsetMatch?.[1];
|
|
const dataBuffer = Buffer.from(response.data);
|
|
// Overwrite the original data for backwards compatibility
|
|
let data;
|
|
if (iconv.encodingExists(charsetValue)) {
|
|
data = iconv.decode(dataBuffer, charsetValue);
|
|
} else {
|
|
data = iconv.decode(dataBuffer, 'utf-8');
|
|
}
|
|
// Try to parse response to JSON, this can quietly fail
|
|
try {
|
|
// Filter out ZWNBSP character
|
|
// https://gist.github.com/antic183/619f42b559b78028d1fe9e7ae8a1352d
|
|
data = data.replace(/^\uFEFF/, '');
|
|
if (!disableParsingResponseJson) {
|
|
data = JSON.parse(data);
|
|
}
|
|
} catch { }
|
|
|
|
return { data, dataBuffer };
|
|
};
|
|
|
|
const parseDataFromRequest = (request) => {
|
|
let requestDataString;
|
|
|
|
// File uploads are redacted, multipart FormData is formatted from original data for readability, and other types are stringified as-is.
|
|
if (request.mode === 'file') {
|
|
requestDataString = '<request body redacted>';
|
|
} else if (isFormData(request?.data) && Array.isArray(request._originalMultipartData)) {
|
|
const boundary = request.data._boundary || 'boundary';
|
|
requestDataString = formatMultipartData(request._originalMultipartData, boundary);
|
|
} else {
|
|
requestDataString = typeof request?.data === 'string' ? request?.data : safeStringifyJSON(request?.data);
|
|
}
|
|
|
|
const requestCopy = cloneDeep(request);
|
|
if (!requestCopy.data) {
|
|
return { data: null, dataBuffer: null };
|
|
}
|
|
requestCopy.data = requestDataString;
|
|
return parseDataFromResponse(requestCopy);
|
|
};
|
|
|
|
module.exports = {
|
|
uuid,
|
|
stringifyJson,
|
|
parseJson,
|
|
safeStringifyJSON,
|
|
safeParseJSON,
|
|
simpleHash,
|
|
generateUidBasedOnHash,
|
|
flattenDataForDotNotation,
|
|
parseDataFromResponse,
|
|
parseDataFromRequest
|
|
};
|