react-native-avo-inspector
Version:
[](https://badge.fury.io/js/react-native-avo-inspector)
129 lines (128 loc) • 5.34 kB
JavaScript
const isArray = (obj) => {
return Object.prototype.toString.call(obj) === "[object Array]";
};
let _encryptValueFn;
export class AvoSchemaParser {
/**
* Returns true only if we have a valid encryption key and can send encrypted values.
* If no key is present, returns false and no property values will be sent.
*/
static canSendEncryptedValues(publicEncryptionKey, env) {
const hasEncryptionKey = publicEncryptionKey != null && publicEncryptionKey !== "";
const isDevOrStaging = env === "dev" || env === "staging";
return hasEncryptionKey && isDevOrStaging;
}
/**
* Returns the encrypted property value if encryption is enabled, otherwise undefined.
* Never returns unencrypted values - only encrypted or nothing.
*/
static async getEncryptedPropertyValueIfEnabled(propertyValue, canEncrypt, publicEncryptionKey) {
if (!canEncrypt || !publicEncryptionKey) {
return undefined; // No encryption key: do not send any property values
}
try {
if (!_encryptValueFn) {
const { encryptValue } = await import("./AvoEncryption");
_encryptValueFn = encryptValue;
}
return await _encryptValueFn(propertyValue, publicEncryptionKey); // Only send encrypted values
}
catch (error) {
// If encryption fails, log the error but don't fail the entire schema extraction
console.error("[Avo Inspector] Failed to encrypt property value:", error instanceof Error ? error.message : String(error));
return undefined;
}
}
static async extractSchema(eventProperties, publicEncryptionKey, env) {
if (eventProperties === null || eventProperties === undefined) {
return [];
}
const canSendEncryptedValues = this.canSendEncryptedValues(publicEncryptionKey, env);
const mapping = async (object) => {
if (isArray(object)) {
const list = await Promise.all(object.map(async (x) => {
return await mapping(x);
}));
return this.removeDuplicates(list);
}
else if (typeof object === "object") {
const mappedResult = [];
for (const key in object) {
if (object.hasOwnProperty(key)) {
const val = object[key];
const mappedEntry = {
propertyName: key,
propertyType: this.getPropValueType(val)
};
if (typeof val === "object" && val != null) {
// Object/array properties: children are encrypted individually, no need to encrypt parent
mappedEntry.children = (await mapping(val));
}
else if (val !== undefined) {
// Primitive properties: encrypt the value if encryption is enabled
// Skip undefined values - they can't be encrypted and shouldn't be sent
const encryptedValue = await this.getEncryptedPropertyValueIfEnabled(val, canSendEncryptedValues, publicEncryptionKey);
if (encryptedValue !== undefined) {
mappedEntry.encryptedPropertyValue = encryptedValue;
}
}
mappedResult.push(mappedEntry);
}
}
return mappedResult;
}
else {
return this.getPropValueType(object);
}
};
// eventProperties is always an object (Record), so mapping returns EventProperty[]
const mappedEventProps = (await mapping(eventProperties));
return mappedEventProps;
}
static removeDuplicates(array) {
const primitives = { boolean: {}, number: {}, string: {} };
const objects = [];
return array.filter((item) => {
const type = typeof item;
if (type in primitives && typeof item === "string") {
return primitives[type].hasOwnProperty(item)
? false
: (primitives[type][item] = true);
}
else {
return objects.includes(item) ? false : objects.push(item);
}
});
}
static getPropValueType(propValue) {
const propType = typeof propValue;
if (propValue == null) {
return "null";
}
else if (propType === "string") {
return "string";
}
else if (propType === "number" || propType === "bigint") {
if ((propValue + "").includes(".")) {
return "float";
}
else {
return "int";
}
}
else if (propType === "boolean") {
return "boolean";
}
else if (propType === "object") {
if (isArray(propValue)) {
return "list";
}
else {
return "object";
}
}
else {
return "unknown";
}
}
}