js-databox
Version:
databox & metabox
428 lines (427 loc) • 15.1 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataBox = void 0;
const databox_1 = require("./did/databox");
const agent_1 = require("@dfinity/agent");
const nanoid_1 = require("nanoid");
const string_random_1 = __importDefault(require("string-random"));
const utils_1 = require("../utils");
const metabox_1 = require("../metabox");
const chunkSize = 1992288;
const ONE_BYTE_UPLOAD_USE_CYCLES = 2260;
class DataBox {
constructor(canisterId, agent) {
this.agent = agent;
this.DataBoxActor = agent_1.Actor.createActor(databox_1.idlFactory, { agent, canisterId });
}
async boxState() {
try {
return await this.DataBoxActor.canisterState();
}
catch (e) {
throw e;
}
}
async cycleBalance() {
try {
return await this.DataBoxActor.cycleBalance();
}
catch (e) {
throw e;
}
}
async put_plain_files(files, is_private, key_arr, fileTypes) {
if (key_arr && key_arr.length !== files.length)
throw new Error("文件数量与key数量不匹配");
if (fileTypes && fileTypes.length !== files.length)
throw new Error("文件数量与类型数量不匹配");
try {
const Actor = this.DataBoxActor;
const allPromise = [];
const keyArr = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
const key = key_arr ? key_arr[i] : (0, nanoid_1.nanoid)();
keyArr.push(key);
const file_extension = fileTypes ? fileTypes[i] : file.type;
const total_size = file.size;
const total_index = Math.ceil(total_size / chunkSize);
const allData = await DataBox.FileRead(file);
for (let i = 0; i < allData.length; i++) {
const arg = {
PlainFilePut: {
IC: {
file_extension,
order: BigInt(i),
chunk_number: BigInt(total_index),
chunk: { data: allData[i] },
aes_pub_key: [],
file_name: file.name,
file_key: key,
total_size: BigInt(file.size),
is_private: is_private
}
}
};
allPromise.push(Actor.put(arg));
}
}
await Promise.all(allPromise);
return keyArr;
}
catch (e) {
throw e;
}
}
static async FileRead(file) {
try {
return new Promise((resolve, reject) => {
let start = 0;
let currentChunk = 0;
const total_index = Math.ceil(file.size / chunkSize);
const allData = [];
let reader = new FileReader();
reader.onload = async function (e) {
allData.push(new Uint8Array(e.target.result));
if (currentChunk === total_index)
return resolve(allData);
else
loadChunk();
};
reader.onerror = (error) => {
reject(error);
};
const loadChunk = () => {
const end = start + chunkSize;
currentChunk++;
reader.readAsArrayBuffer(file.slice(start, end));
start = end;
};
loadChunk();
});
}
catch (e) {
throw e;
}
}
static async encryptFileData(data, publicKey) {
try {
const AESKEY = await utils_1.EncryptApi.aesKeyGen();
const AESIv = (0, string_random_1.default)(128);
const encData = utils_1.AESEncryptApi.AESEncData(data, AESKEY, AESIv);
const encryptedAesKey = await utils_1.RSAEncryptApi.encryptMessage(publicKey, `${AESKEY}${AESIv}`);
return { encData, encryptedAesKey };
}
catch (e) {
throw e;
}
}
async put_encrypt_files(files, is_private, publicKey, key_arr) {
try {
if (key_arr && key_arr.length !== files.length)
throw new Error("文件数量与key数量不匹配");
const Actor = this.DataBoxActor;
const keyArr = [];
const allPromise = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
const key = key_arr ? key_arr[i] : (0, nanoid_1.nanoid)();
keyArr.push(key);
const total_size = file.size;
const allData = await DataBox.FileRead(file);
const data = new Uint8Array(total_size);
for (let i = 0; i < allData.length; i++) {
data.set(allData[i], i * chunkSize);
}
const { encData, encryptedAesKey } = await DataBox.encryptFileData(data, publicKey);
const NewBlob = new Blob([encData]);
const encryptedData = await DataBox.FileRead(NewBlob);
for (let i = 0; i < encryptedData.length; i++) {
const arg = {
EncryptFilePut: {
IC: {
file_extension: file.type,
order: BigInt(i),
chunk_number: BigInt(Math.ceil(NewBlob.size / chunkSize)),
chunk: { data: encryptedData[i] },
aes_pub_key: [encryptedAesKey],
file_name: file.name,
file_key: key,
total_size: BigInt(NewBlob.size),
is_private: is_private
}
}
};
allPromise.push(Actor.put(arg));
}
}
await Promise.all(allPromise);
return keyArr;
}
catch (e) {
throw e;
}
}
static async getFile(decodeArr, length) {
const File = new Uint8Array(length);
for (let i = 0; i < decodeArr.length; i++) {
let slice = decodeArr[i];
let start = 0;
for (let j = 0; j < i; j++) {
start += decodeArr[j].length;
}
File.set(slice, start);
}
return File;
}
async getData(file_info, isEncrypt) {
try {
const queryPromiseArr = [];
if (file_info.ok) {
const AssetExt = file_info.ok[isEncrypt ? "EncryptFileExt" : "PlainFileExt"];
if (AssetExt) {
const need_query_times = Number(AssetExt.need_query_times);
for (let i = 0; i < need_query_times; i++) {
queryPromiseArr.push(this.DataBoxActor[isEncrypt ? "getCipher" : "getPlain"]({
file_key: AssetExt.file_key,
flag: BigInt(i)
}));
}
return await Promise.all(queryPromiseArr);
}
else
throw new Error(`this is not a ${isEncrypt ? "encrypt" : "plain"} file`);
}
else
throw new Error(Object.keys(file_info.err)[0]);
}
catch (e) {
throw e;
}
}
async get_plain_file(file_key) {
try {
const dataArr = [];
let fileSize = 0;
const file_info = await this.get_file_info(file_key);
const fileType = file_info.ok.PlainFileExt.file_extension;
const res = await this.getData(file_info, false);
if (res[0] && res[0].ok) {
res.forEach(e => {
dataArr.push(e.ok);
fileSize += e.ok.length;
});
const metadata = await DataBox.getFile(dataArr, fileSize);
return new Blob([metadata.buffer], {
type: fileType,
});
}
else
throw new Error(Object.keys(res[0].err)[0]);
}
catch (e) {
throw e;
}
}
async get_encrypt_file(file_key, privatekey) {
try {
const dataArr = [];
let fileSize = 0;
const file_info = await this.get_file_info(file_key);
const fileType = file_info?.ok?.EncryptFileExt.file_extension;
const res = await this.getData(file_info, true);
if (res[0] && res[0].ok) {
res.forEach(e => {
e.ok.forEach(value => {
dataArr.push(value);
fileSize += value.length;
});
});
const metadata = await DataBox.getFile(dataArr, fileSize);
const privateKey = await utils_1.RSAEncryptApi.importPrivateKey(privatekey);
const preFileAesKey = await utils_1.RSAEncryptApi.decryptMessage(privateKey, file_info.ok.EncryptFileExt.aes_pub_key[0]);
const AesKey = preFileAesKey.slice(0, 256);
const AesIv = preFileAesKey.slice(256);
const plainText = utils_1.AESEncryptApi.AESDecData(metadata, AesKey, AesIv);
return new Blob([plainText.buffer], {
type: fileType,
});
}
else
throw new Error(Object.keys(res[0].err)[0]);
}
catch (e) {
throw e;
}
}
async delete_box_plain_file(file_key) {
try {
return await this.DataBoxActor.deleteFileFromKey(file_key, { 'Plain': null });
}
catch (e) {
throw e;
}
}
async delete_box_encrypted_file(file_key) {
try {
return await this.DataBoxActor.deleteFileFromKey(file_key, { 'EnCrypt': null });
}
catch (e) {
throw e;
}
}
async clear_box() {
try {
return await this.DataBoxActor.clearall();
}
catch (e) {
throw e;
}
}
async get_file_info(file_key) {
try {
return await this.DataBoxActor.getAssetextkey(file_key);
}
catch (e) {
throw e;
}
}
async getVersion() {
try {
return await this.DataBoxActor.getVersion();
}
catch (e) {
throw e;
}
}
async get_all_files_info() {
try {
return await this.DataBoxActor.getAssetexts();
}
catch (e) {
throw e;
}
}
async transferOwner(to) {
try {
return await this.DataBoxActor.transferOwner(to);
}
catch (e) {
throw e;
}
}
async get_owner() {
try {
return await this.DataBoxActor.getOwner();
}
catch (e) {
throw e;
}
}
async setPlainFilePubOrPri(changePlainFilePermissionArg) {
try {
return await this.DataBoxActor.setPlainFilePubOrPri(changePlainFilePermissionArg.file_key, changePlainFilePermissionArg.is_private);
}
catch (e) {
throw e;
}
}
async addPrivatePlainShare(shareFileArg) {
try {
return await this.DataBoxActor.addPrivatePlainShare(shareFileArg.file_key, shareFileArg.to);
}
catch (e) {
throw e;
}
}
async removePrivatePlainShare(shareFileArg) {
try {
return await this.DataBoxActor.removePrivatePlainShare(shareFileArg.file_key, shareFileArg.to);
}
catch (e) {
throw e;
}
}
async getShareFiles() {
try {
return await this.DataBoxActor.getShareFiles();
}
catch (e) {
throw e;
}
}
async is_need_upgrade() {
try {
const MBapi = new metabox_1.MetaBox(this.agent);
const version = Number(await this.getVersion());
const new_version = Number(await MBapi.getDataBoxVersion());
return version < new_version;
}
catch (e) {
throw e;
}
}
async is_enough_to_upload(total_size) {
return new Promise(async (resolve, reject) => {
try {
const res = await this.cycleBalance();
if (Object.keys(res)[0] === "ok") { //@ts-ignore
const balance = Number(res.ok);
if (total_size * ONE_BYTE_UPLOAD_USE_CYCLES < balance) {
return resolve(true);
}
else
return reject(Number(total_size * ONE_BYTE_UPLOAD_USE_CYCLES - balance));
//@ts-ignore
}
else
return reject(String(Object.keys(res.err)[0]));
}
catch (e) {
return reject(e);
}
});
}
/**
*
* @param {FileLocation} fileLocation 文件位置
* @return {Result_7} 数据个数
*/
async getFileNums(fileLocation) {
try {
return await this.DataBoxActor.getFileNums(fileLocation);
}
catch (e) {
throw e;
}
}
/**
* 分页get数据
*
* @param {FileLocation} fileLocation 文件位置
* @param {number} onePageFileNums 每一页的数据大小 不能超过5000
* @param {number} pageIndex 取哪一页
* @example
* getPageFiles({Plain:null},2,0) 取明文数据,每一页有两个数据,取第一页
*/
getPageFiles(fileLocation, onePageFileNums, pageIndex) {
return new Promise(async (resolve, reject) => {
try {
if (onePageFileNums > 5000)
return reject("A page of data cannot exceed 5000");
const res = await this.DataBoxActor.getPageFiles(fileLocation, BigInt(onePageFileNums), BigInt(pageIndex));
if (Object.keys(res)[0] === "ok")
return resolve(res.ok);
else
return reject(Object.keys(res.err)[0]);
}
catch (e) {
throw e;
}
});
}
}
exports.DataBox = DataBox;