UNPKG

@iexec/dataprotector

Version:

This product enables users to confidentially store data–such as mail address, documents, personal information ...

238 lines 8.47 kB
import { serialize } from 'borsh'; import JSZip from 'jszip'; import { filetypeinfo } from 'magic-bytes.js'; const ALLOWED_KEY_NAMES_REGEXP = /^[a-zA-Z0-9\-_]*$/; const LEGACY_TYPES = ['boolean', 'number', 'string']; const SUPPORTED_TYPES = ['bool', 'i128', 'f64', 'string']; const MIN_I128 = BigInt('-170141183460469231731687303715884105728'); const MAX_I128 = BigInt('170141183460469231731687303715884105728'); const SUPPORTED_MIME_TYPES = [ 'application/octet-stream', 'application/pdf', 'application/xml', 'application/zip', // 'audio/x-flac', // not implemented 'audio/midi', 'audio/mpeg', // 'audio/ogg', // https://github.com/LarsKoelpin/magic-bytes/pull/38 // 'audio/weba', // not implemented 'audio/x-wav', 'image/bmp', 'image/gif', 'image/jpeg', 'image/png', // 'image/svg+xml', // not implemented 'image/webp', 'video/mp4', 'video/mpeg', // 'video/ogg', // https://github.com/LarsKoelpin/magic-bytes/pull/38 // 'video/quicktime', // https://github.com/LarsKoelpin/magic-bytes/pull/39 // 'video/webm', // not implemented 'video/x-msvideo', ]; const supportedDataEntryTypes = new Set([ ...SUPPORTED_TYPES, ...SUPPORTED_MIME_TYPES, ]); const searchableDataEntryTypes = new Set([ ...supportedDataEntryTypes, ...LEGACY_TYPES, ]); const ensureKeyIsValid = (key) => { if (key === '') { throw Error(`Unsupported empty key`); } if (!ALLOWED_KEY_NAMES_REGEXP.test(key)) { throw Error(`Unsupported special character in key`); } }; export const ensureDataObjectIsValid = (data) => { if (data === undefined) { throw Error(`Unsupported undefined data`); } if (data === null) { throw Error(`Unsupported null data`); } if (Array.isArray(data)) { throw Error(`Unsupported array data`); } for (const key in data) { ensureKeyIsValid(key); const value = data[key]; const typeOfValue = typeof value; if (value instanceof Uint8Array || value instanceof ArrayBuffer || typeOfValue === 'boolean' || typeOfValue === 'string' || typeOfValue === 'bigint' || typeOfValue === 'number') { // valid scalar } else if (typeOfValue === 'object') { // nested object const nestedDataObject = value; ensureDataObjectIsValid(nestedDataObject); } else { throw Error(`Unsupported ${typeOfValue} data`); } } }; export const ensureSearchableDataSchemaIsValid = (schema) => { if (schema === undefined) { throw Error(`Unsupported undefined schema`); } if (schema === null) { throw Error(`Unsupported null schema`); } if (Array.isArray(schema)) { throw Error(`Unsupported array schema`); } for (const key in schema) { ensureKeyIsValid(key); const value = schema[key]; if (typeof value === 'object') { if (Array.isArray(value)) { if (value.length === 0) { throw Error(`Unsupported empty type array`); } const unsupportedType = value.find((v) => !searchableDataEntryTypes.has(v)); if (unsupportedType) { throw Error(`Unsupported type "${unsupportedType}" in type array`); } } else { ensureSearchableDataSchemaIsValid(value); } } else if (!searchableDataEntryTypes.has(value)) { throw Error(`Unsupported type "${value}" in schema`); } } }; export const extractDataSchema = async (data) => { const schema = {}; for (const key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { const value = data[key]; const typeOfValue = typeof value; if (value instanceof Uint8Array || value instanceof ArrayBuffer) { let guessedTypes = []; if (value instanceof Uint8Array) { guessedTypes = filetypeinfo(value); } else { guessedTypes = filetypeinfo(new Uint8Array(value)); } // use first supported mime type const [mime] = guessedTypes.reduce((acc, curr) => { if (supportedDataEntryTypes.has(curr.mime)) { return [...acc, curr.mime]; } return acc; }, []); // or fallback to 'application/octet-stream' schema[key] = mime || 'application/octet-stream'; } else if (typeOfValue === 'boolean') { schema[key] = 'bool'; } else if (typeOfValue === 'string') { schema[key] = 'string'; } else if (typeOfValue === 'number') { schema[key] = 'f64'; } else if (typeOfValue === 'bigint') { schema[key] = 'i128'; } else if (typeOfValue === 'object') { const nestedDataObject = value; const nestedSchema = await extractDataSchema(nestedDataObject); schema[key] = nestedSchema; } } } return schema; }; export const createArrayBufferFromFile = async (file) => { const fileReader = new FileReader(); return new Promise((resolve, reject) => { fileReader.onerror = () => { fileReader.abort(); reject(new DOMException('Error parsing input file.')); }; fileReader.onload = () => { resolve(fileReader.result); }; fileReader.readAsArrayBuffer(file); }); }; export const createZipFromObject = (obj) => { const zip = new JSZip(); const promises = []; const createFileOrDirectory = (key, value, path) => { const fullPath = path ? `${path}/${key}` : key; if (typeof value === 'object' && !(value instanceof Uint8Array) && !(value instanceof ArrayBuffer)) { zip.folder(fullPath); for (const [nestedKey, nestedValue] of Object.entries(value)) { createFileOrDirectory(nestedKey, nestedValue, fullPath); } } else { let content; if (typeof value === 'bigint') { if (value > MAX_I128 || value < MIN_I128) { promises.push(Promise.reject(Error(`Unsupported integer value: out of i128 range`))); } content = serialize('i128', value, true); } else if (typeof value === 'number') { if (!Number.isFinite(value)) { promises.push(Promise.reject(Error(`Unsupported number value: infinity`))); } // floats serializes as f64 content = serialize('f64', value, true); } else if (typeof value === 'boolean') { content = serialize('bool', value, true); } else if (typeof value === 'string') { content = serialize('string', value, true); } else if (value instanceof Uint8Array || value instanceof ArrayBuffer) { content = value; } else { promises.push(Promise.reject(Error('Unexpected data format'))); } promises.push(zip .file(fullPath, content) .generateAsync({ type: 'uint8array' }) .then(() => { })); } }; for (const [key, value] of Object.entries(obj)) { createFileOrDirectory(key, value, ''); } return Promise.all(promises).then(() => zip.generateAsync({ type: 'uint8array' })); }; export const reverseSafeSchema = (schema) => { if (!schema) return {}; return schema.reduce((acc, { id }) => { const [path, type] = id.split(':'); const keys = path.split('.'); let current = acc; for (let i = 0; i < keys.length - 1; i++) { current[keys[i]] ??= {}; current = current[keys[i]]; } const finalKey = keys[keys.length - 1]; current[finalKey] = type; return acc; }, {}); }; //# sourceMappingURL=data.js.map