diff --git a/src/@inxt-js/api/FileObject.ts b/src/@inxt-js/api/FileObject.ts index 9d912bfe8..dc41eee80 100644 --- a/src/@inxt-js/api/FileObject.ts +++ b/src/@inxt-js/api/FileObject.ts @@ -15,7 +15,6 @@ import { Logger } from '../lib/download'; import { wrap } from '../lib/utils/error'; import { EventEmitter } from '../lib/utils/eventEmitter'; import { logger } from '../lib/utils/logger'; -import { Bridge, InxtApiI } from '../services/api'; import FileManager from './FileManager'; import { DEFAULT_INXT_MIRRORS, DOWNLOAD_CANCELLED } from './constants'; @@ -34,7 +33,6 @@ export class FileObject extends EventEmitter { fileKey: Buffer; private aborted = false; - private api: InxtApiI; private file: FileManager; constructor(config: EnvironmentConfig, bucketId: string, fileId: string, debug: Logger, file: FileManager) { @@ -43,8 +41,6 @@ export class FileObject extends EventEmitter { this.bucketId = bucketId; this.fileId = fileId; this.fileKey = Buffer.alloc(0); - - this.api = new Bridge(config); this.file = file; this.once(DOWNLOAD_CANCELLED, this.abort.bind(this)); diff --git a/src/@inxt-js/api/FileObjectUpload.ts b/src/@inxt-js/api/FileObjectUpload.ts deleted file mode 100644 index 5bc111b65..000000000 --- a/src/@inxt-js/api/FileObjectUpload.ts +++ /dev/null @@ -1,315 +0,0 @@ -import { randomBytes, createCipheriv } from 'react-native-crypto'; - -import { EnvironmentConfig, UploadProgressCallback } from '..'; - -import EncryptStream from '../lib/encryptStream'; -import { GenerateFileKey, sha512HmacBuffer } from '../lib/crypto'; -import { getShardMeta, ShardMeta } from '../lib/shardMeta'; -import { ContractNegotiated } from '../lib/contracts'; -import { logger } from '../lib/utils/logger'; - -import { ExchangeReport } from './reports'; -import { determineShardSize } from '../lib/utils'; -import { - Bridge, - CreateEntryFromFrameBody, - CreateEntryFromFrameResponse, - FrameStaging, - InxtApiI, -} from '../services/api'; -import { EventEmitter } from '../lib/utils/eventEmitter'; -import { INXTRequest } from '../lib'; - -import { ShardObject } from './ShardObject'; -import { wrap } from '../lib/utils/error'; -import FileManager from './FileManager'; -import errorService from '../../services/ErrorService'; - -export interface FileMeta { - size: number; - name: string; - fileUri: string; -} - -export class FileObjectUpload extends EventEmitter { - private config: EnvironmentConfig; - private fileMeta: FileMeta; - private requests: INXTRequest[] = []; - private api: InxtApiI; - private id = ''; - private aborted = false; - - shardMetas: ShardMeta[] = []; - bucketId: string; - frameId: string; - index: Buffer; - encrypted = false; - - cipher: EncryptStream; - // funnel: FunnelStream; - fileEncryptionKey: Buffer; - - constructor(config: EnvironmentConfig, fileMeta: FileMeta, bucketId: string, api?: InxtApiI) { - super(); - - this.config = config; - this.index = Buffer.alloc(0); - this.fileMeta = fileMeta; - this.bucketId = bucketId; - this.frameId = ''; - // this.funnel = new FunnelStream(determineShardSize(fileMeta.size)); - this.cipher = new EncryptStream(randomBytes(32), randomBytes(16)); - this.fileEncryptionKey = randomBytes(32); - this.api = api ?? new Bridge(this.config); - } - - getSize(): number { - return this.fileMeta.size; - } - - getId(): string { - return this.id; - } - - getEncryptionKey(): Buffer { - return this.fileEncryptionKey; - } - - getIndex(): Buffer { - return this.index.slice(0, 16); - } - - checkIfIsAborted(): void { - if (this.isAborted()) { - throw new Error('Upload aborted'); - } - } - - async init(): Promise { - this.index = randomBytes(32); - this.fileEncryptionKey = await GenerateFileKey(this.config.encryptionKey || '', this.bucketId, this.index); - - this.cipher = new EncryptStream(this.fileEncryptionKey, this.index.slice(0, 16)); - - return this; - } - - checkBucketExistence(): Promise { - this.checkIfIsAborted(); - - const req = this.api.getBucketById(this.bucketId); - - this.requests.push(req); - - return req - .start() - .then(() => { - logger.info(`Bucket ${this.bucketId} exists`); - - return true; - }) - .catch((err) => { - throw wrap('Bucket existence check error', err); - }); - } - - stage(): Promise { - this.checkIfIsAborted(); - - const req = this.api.createFrame(); - - this.requests.push(req); - - return req - .start() - .then((frame) => { - if (!frame || !frame.id) { - throw new Error('Frame response is empty'); - } - - this.frameId = frame.id; - - logger.info(`Stage a file with frame ${this.frameId}`); - }) - .catch((err) => { - throw wrap('Bridge frame creation error', err); - }); - } - - SaveFileInNetwork(bucketEntry: CreateEntryFromFrameBody): Promise { - this.checkIfIsAborted(); - - const req = this.api.createEntryFromFrame(this.bucketId, bucketEntry); - - this.requests.push(req); - - return req.start().catch((err) => { - throw wrap('Saving file in network error', err); - }); - } - - NegotiateContract(frameId: string, shardMeta: ShardMeta): Promise { - this.checkIfIsAborted(); - - const req = this.api.addShardToFrame(frameId, shardMeta); - - this.requests.push(req); - - return req.start().catch((err) => { - throw wrap('Contract negotiation error', err); - }); - } - - GenerateHmac(shardMetas: ShardMeta[]): string { - const shardMetasCopy = [...shardMetas].sort((sA, sB) => sA.index - sB.index); - const hmac = sha512HmacBuffer(this.fileEncryptionKey); - - for (const shardMeta of shardMetasCopy) { - hmac.update(Buffer.from(shardMeta.hash, 'hex')); - } - - return hmac.digest().toString('hex'); - } - - encrypt(): EncryptStream { - this.encrypted = true; - - return this.cipher; - } - - private async parallelUpload(callback: UploadProgressCallback): Promise { - const nShards = Math.ceil(this.fileMeta.size / determineShardSize(this.fileMeta.size)); - const cipher = createCipheriv('aes-256-ctr', this.fileEncryptionKey, this.index.slice(0, 16)); - - let progress = 0; - let shardBuffer: Buffer; - let encryptedChunk: Buffer; - let shardMeta: ShardMeta; - - const fileManager = new FileManager(this.fileMeta.fileUri); - const fileIterator = fileManager.iterator(determineShardSize(this.fileMeta.size)); - - for (let i = 0; i < nShards; i++) { - const chunk = await fileIterator.next(); - shardBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); - - cipher.write(shardBuffer); - - encryptedChunk = cipher.read(); - - shardMeta = await this.uploadShard(encryptedChunk, encryptedChunk.length, this.frameId, i, 3, false); - - progress += encryptedChunk.length / this.fileMeta.size; - callback(progress, encryptedChunk.length, this.fileMeta.size); - this.shardMetas.push(shardMeta); - } - - return this.shardMetas; - } - - upload(callback: UploadProgressCallback): Promise { - this.checkIfIsAborted(); - - if (!this.encrypted) { - throw new Error('Tried to upload a file not encrypted. Call encrypt() before upload()'); - } - - return this.parallelUpload(callback); - } - - async uploadShard( - encryptedShard: Buffer, - shardSize: number, - frameId: string, - index: number, - attemps: number, - parity: boolean, - ): Promise { - const shardMeta: ShardMeta = getShardMeta(encryptedShard, shardSize, index, parity); - - logger.info(`Uploading shard ${shardMeta.hash} index ${shardMeta.index} size ${shardMeta.index} parity ${parity}`); - - const shardObject = new ShardObject(this.api, frameId, shardMeta); - - shardObject.once(ShardObject.Events.NodeTransferFinished, ({ success, nodeID, hash }) => { - const exchangeReport = new ExchangeReport(this.config); - - exchangeReport.params.dataHash = hash; - exchangeReport.params.farmerId = nodeID; - exchangeReport.params.exchangeEnd = new Date(); - - if (success) { - logger.debug(`Node ${nodeID} accepted shard ${hash}`); - exchangeReport.UploadOk(); - } else { - exchangeReport.UploadError(); - } - - exchangeReport.sendReport().catch(() => { - // no op - }); - }); - - let retries = attemps; - - do { - try { - await shardObject.upload(encryptedShard); - - logger.info(`Shard ${shardMeta.hash} uploaded succesfully`); - - retries = 0; - } catch (err) { - const castedError = errorService.castError(err); - logger.error(`Upload for shard ${shardMeta.hash} failed. Reason: ${castedError.message}. Retrying ...`); - - retries--; - } - } while (retries > 0); - - return shardMeta; - } - - createBucketEntry(shardMetas: ShardMeta[]): Promise { - return this.SaveFileInNetwork(generateBucketEntry(this, this.fileMeta, shardMetas, false)) - .then((bucketEntry) => { - if (!bucketEntry) { - throw new Error('Can not save the file in the network'); - } - this.id = bucketEntry.id; - }) - .catch((err) => { - throw wrap('Bucket entry creation error', err); - }); - } - - abort(): void { - logger.warn('Aborting file upload'); - - this.aborted = true; - } - - isAborted(): boolean { - return this.aborted; - } -} - -export function generateBucketEntry( - fileObject: FileObjectUpload, - fileMeta: FileMeta, - shardMetas: ShardMeta[], - rs: boolean, -): CreateEntryFromFrameBody { - const bucketEntry: CreateEntryFromFrameBody = { - frame: fileObject.frameId, - filename: fileMeta.name, - index: fileObject.index.toString('hex'), - hmac: { type: 'sha512', value: fileObject.GenerateHmac(shardMetas) }, - }; - - if (rs) { - bucketEntry.erasure = { type: 'reedsolomon' }; - } - - return bucketEntry; -} diff --git a/src/@inxt-js/api/ShardObject.ts b/src/@inxt-js/api/ShardObject.ts index a746bae93..e5c864720 100644 --- a/src/@inxt-js/api/ShardObject.ts +++ b/src/@inxt-js/api/ShardObject.ts @@ -1,136 +1,9 @@ -import { AxiosError } from 'axios'; -import { EventEmitter } from '../lib/utils/eventEmitter'; - -import { INXTRequest } from '../lib'; -import { ContractNegotiated } from '../lib/contracts'; -import { ShardMeta } from '../lib/shardMeta'; -import { wrap } from '../lib/utils/error'; -import { logger } from '../lib/utils/logger'; -import { InxtApiI } from '../services/api'; import { Shard } from './shard'; -import { get, getBuffer } from '../services/request'; - -type GetUrl = string; - -export class ShardObject extends EventEmitter { - private meta: ShardMeta; - private api: InxtApiI; - private frameId: string; - private requests: INXTRequest[] = []; - private shard?: Shard; - - static Events = { - NodeTransferFinished: 'node-transfer-finished', - }; - - constructor(api: InxtApiI, frameId: string | null, meta: ShardMeta | null, shard?: Shard) { - super(); - - // TODO: Clarify if meta and shard variables are both required. - this.frameId = frameId ?? ''; - this.meta = meta ?? { - hash: '', - index: 0, - parity: false, - challenges_as_str: [], - size: 0, - tree: [], - challenges: [], - exclude: [], - }; - this.api = api; - this.shard = shard; - } - - getSize(): number { - return this.meta.size; - } - - getHash(): string { - return this.meta.hash; - } - - getIndex(): number { - return this.meta.index; - } - - async upload(content: Buffer): Promise { - if (!this.frameId) { - throw new Error('Frame id not provided'); - } - - const contract = await this.negotiateContract(); - - logger.debug( - `Negotiated succesfully contract for shard - ${this.getHash()} (index ${this.getIndex()}, size ${this.getSize()}) with token ${contract.token}`, - ); - - const farmer = { ...contract.farmer, lastSeen: new Date() }; - const shard: Omit = { - index: this.getIndex(), - replaceCount: 0, - hash: this.getHash(), - size: this.getSize(), - parity: this.meta.parity, - token: contract.token, - farmer, - operation: contract.operation, - }; - - await this.put(shard, content); - - return this.meta; - } - - private negotiateContract(): Promise { - const req = this.api.addShardToFrame(this.frameId, this.meta); - - this.requests.push(req); - - return req.start().catch((err) => { - throw wrap('Contract negotiation error', err); - }); - } - - private put(shard: Omit, content: Buffer): Promise { - let success = true; - - return this.api - .requestPut(shard) - .start<{ result: string }>() - .then((res) => { - const putUrl = res.result; - - logger.debug(`Put url for shard ${shard.index} is ${putUrl}`); - - return this.api.putShard(putUrl, content).start(); - }) - .catch((err: AxiosError) => { - logger.error(`Error uploading shard ${shard.index}: ${err.message}`); - - if (err.response && err.response.status < 400) { - return { result: err.response.data && err.response.data.error }; - } - - success = false; - - throw wrap('Farmer request error', err); - }) - .finally(() => { - const hash = shard.hash; - const nodeID = shard.farmer.nodeID; - - this.emit(ShardObject.Events.NodeTransferFinished, [{ hash, nodeID, success }]); - }); - } - - static requestGet(url: string, useProxy = true): Promise { - return get<{ result: string }>({ url }, { useProxy }).then((res) => res.result); - } +import { getBuffer } from '../services/request'; +export class ShardObject { static download(shard: Shard, cb: (err: Error | null, content: Buffer | null) => void): void { - getBuffer(shard.url, { useProxy: false }) + getBuffer(shard.url) .then((content) => { cb(null, content); }) @@ -138,22 +11,4 @@ export class ShardObject extends EventEmitter { cb(err, null); }); } - - abort(): void { - this.requests.forEach((r) => { - r.abort(); - }); - } - - download(): Promise { - if (!this.shard) { - throw new Error('Provide shard info before trying to download a shard'); - } - - const req = this.api.getShardFromNode(this.shard); - - this.requests.push(req); - - return req.buffer(); - } } diff --git a/src/@inxt-js/api/fileinfo.ts b/src/@inxt-js/api/fileinfo.ts index 7d0113bab..c0c21fc99 100644 --- a/src/@inxt-js/api/fileinfo.ts +++ b/src/@inxt-js/api/fileinfo.ts @@ -23,7 +23,7 @@ export interface FileInfo { } export function GetFileInfo(config: EnvironmentConfig, bucketId: string, fileId: string): Promise { - return request(config, 'get', `${config.bridgeUrl}/buckets/${bucketId}/files/${fileId}/info`, {}, false) + return request(config, 'get', `${config.bridgeUrl}/buckets/${bucketId}/files/${fileId}/info`, {}) .then((res: AxiosResponse) => res.data) .catch((err: AxiosError) => { switch (err.response?.status) { @@ -46,7 +46,7 @@ export function GetFileMirror( const excludeNodeIds: string = excludeNodes.join(','); const targetUrl = `${config.bridgeUrl}/buckets/${bucketId}/files/${fileId}?limit=${limit}&skip=${skip}&exclude=${excludeNodeIds}`; - return request(config, 'GET', targetUrl, { responseType: 'json' }, false).then((res: AxiosResponse) => res.data); + return request(config, 'GET', targetUrl, { responseType: 'json' }).then((res: AxiosResponse) => res.data); } export function ReplacePointer( diff --git a/src/@inxt-js/api/reports.ts b/src/@inxt-js/api/reports.ts index 5314d3d7c..aaa1bba74 100644 --- a/src/@inxt-js/api/reports.ts +++ b/src/@inxt-js/api/reports.ts @@ -87,7 +87,7 @@ export class ExchangeReport { return Promise.reject(Error('Not valid report to send')); } - return request(this.config, 'POST', `${this.config.bridgeUrl}/reports/exchanges`, { data: this.params }, false); + return request(this.config, 'POST', `${this.config.bridgeUrl}/reports/exchanges`, { data: this.params }); } DownloadOk() { diff --git a/src/@inxt-js/api/shard.ts b/src/@inxt-js/api/shard.ts index da573ab95..ee6b535ac 100644 --- a/src/@inxt-js/api/shard.ts +++ b/src/@inxt-js/api/shard.ts @@ -1,5 +1,3 @@ -import { EnvironmentConfig } from '..'; - export interface Shard { index: number; replaceCount: number; @@ -17,26 +15,5 @@ export interface Shard { lastSeen: Date; }; operation: string; - url: string -} - -export function DownloadShardRequest( - config: EnvironmentConfig, - address: string, - port: number, - hash: string, - token: string, - nodeID: string, -): void { - const fetchUrl = `http://${address}:${port}/shards/${hash}?token=${token}`; -} - -export async function DownloadShard( - config: EnvironmentConfig, - shard: Shard, - bucketId: string, - fileId: string, - excludedNodes: string[] = [], -): Promise { - return null; + url: string; } diff --git a/src/@inxt-js/index.ts b/src/@inxt-js/index.ts index 1071c74cc..f5702be1a 100644 --- a/src/@inxt-js/index.ts +++ b/src/@inxt-js/index.ts @@ -1,37 +1,31 @@ -import { download } from './lib/download'; +/** + * @deprecated LEGACY DOWNLOAD SYSTEM + * + * This module is maintained for backwards compatibility with old files only. + * Modern downloads use @internxt/sdk via NetworkFacade. + * + * Only used when: + * 1. File has multiple mirrors (old redundancy system) + * 2. After modern download and V1 download both fail + * + */ + import { GenerateFileKey } from './lib/crypto'; +import { download } from './lib/download'; import { logger } from './lib/utils/logger'; -import { BUCKET_ID_NOT_PROVIDED, ENCRYPTION_KEY_NOT_PROVIDED } from './api/constants'; import { ActionState, ActionTypes } from './api/actionState'; +import { BUCKET_ID_NOT_PROVIDED, ENCRYPTION_KEY_NOT_PROVIDED } from './api/constants'; import { FileInfo, GetFileInfo } from './api/fileinfo'; -import { Bridge, CreateFileTokenResponse } from './services/api'; import FileManager from './api/FileManager'; -export type OnlyErrorCallback = (err: Error | null) => void; -export type UploadFinishCallback = (err: Error | null, response: string | null) => void; export type DownloadFinishedCallback = (err: Error | null) => void; export type DownloadProgressCallback = ( progress: number, downloadedBytes: number | null, totalBytes: number | null, ) => void; -export type DecryptionProgressCallback = ( - progress: number, - decryptedBytes: number | null, - totalBytes: number | null, -) => void; -export type UploadProgressCallback = ( - progress: number, - uploadedBytes: number | null, - totalBytes: number | null, -) => void; - -export interface ResolveFileOptions { - progressCallback: DownloadProgressCallback; - finishedCallback: OnlyErrorCallback; - overwritte?: boolean; -} +type DecryptionProgressCallback = (progress: number, decryptedBytes: number | null, totalBytes: number | null) => void; export interface DownloadFileOptions { fileManager: FileManager; @@ -90,22 +84,6 @@ export class Environment { getFileInfo(bucketId: string, fileId: string): Promise { return GetFileInfo(this.config, bucketId, fileId); } - - /** - * Creates file token - * @param bucketId Bucket id where file is stored - * @param fileId File id - * @param operation - * @param cb - */ - createFileToken(bucketId: string, fileId: string, operation: 'PUSH' | 'PULL'): Promise { - return new Bridge(this.config) - .createFileToken(bucketId, fileId, operation) - .start() - .then((res) => { - return res.token; - }); - } } export interface EnvironmentConfig { diff --git a/src/@inxt-js/lib/INXTRequest.ts b/src/@inxt-js/lib/INXTRequest.ts deleted file mode 100644 index a11ba0d93..000000000 --- a/src/@inxt-js/lib/INXTRequest.ts +++ /dev/null @@ -1,85 +0,0 @@ -import axios, { AxiosRequestConfig, Canceler } from 'axios'; - -import { request } from '../services/request'; -import { EnvironmentConfig } from '..'; - -enum Methods { - Get = 'GET', - Post = 'POST', - Put = 'PUT', -} - -export class INXTRequest { - private req: Promise | undefined; - private config: EnvironmentConfig; - private cancel: Canceler; - private useProxy: boolean; - - method: Methods; - targetUrl: string; - params: AxiosRequestConfig; - - static Events = { - UploadProgress: 'upload-progress', - DownloadProgress: 'download-progress', - }; - - constructor( - config: EnvironmentConfig, - method: Methods, - targetUrl: string, - params: AxiosRequestConfig, - useProxy?: boolean, - ) { - this.method = method; - this.config = config; - this.targetUrl = targetUrl; - this.useProxy = useProxy ?? false; - this.params = params; - - this.cancel = () => null; - } - - start(): Promise { - // TODO: Abstract from axios - const source = axios.CancelToken.source(); - - this.cancel = source.cancel; - - const cancelToken = source.token; - - this.req = request(this.config, this.method, this.targetUrl, { ...this.params, cancelToken }, this.useProxy).then< - JSON | K - >((res) => res.data); - - return this.req; - } - - buffer(): Promise { - const source = axios.CancelToken.source(); - - this.cancel = source.cancel; - - const cancelToken = source.token; - - this.req = request( - this.config, - this.method, - this.targetUrl, - { ...this.params, cancelToken }, - this.useProxy, - ).then((res) => { - return Buffer.from(res.request._response, 'base64'); - }); - - return this.req; - } - - abort(): void { - this.cancel(); - } - - isCancelled(err: Error): boolean { - return axios.isCancel(err); - } -} diff --git a/src/@inxt-js/lib/concurrentQueue.ts b/src/@inxt-js/lib/concurrentQueue.ts deleted file mode 100644 index 45a3fb206..000000000 --- a/src/@inxt-js/lib/concurrentQueue.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { QueueObject, queue, ErrorCallback } from 'async'; - -export class ConcurrentQueue { - private totalTasks: number; - protected concurrency: number; - private finishedTasks = 0; - protected queue: QueueObject; - - constructor(concurrency = 1, totalTasks = 1, task?: (content: K) => Promise) { - if (concurrency > totalTasks) { - throw new Error('ConcurrentQueue error: Concurrency can not be greater than total tasks to perform'); - } - - this.totalTasks = totalTasks; - this.concurrency = concurrency; - - if (task) { - this.queue = queue(async (content: K, cb: ErrorCallback) => { - task(content) - .then(() => { - this.finishedTasks++; - cb(); - }) - .catch(cb); - }, concurrency); - } else { - this.queue = queue(() => undefined, 1); - } - } - - setQueueTask(task: (content: K) => Promise): void { - this.queue = queue(async (content: K, cb: ErrorCallback) => { - task(content) - .then(() => { - this.finishedTasks++; - cb(); - }) - .catch(cb); - }, this.concurrency); - } - - push(content: K): Promise { - return this.queue.push(content); - } - - end(cb?: () => void): void | Promise { - if (cb) { - const intervalId = setInterval(() => { - if (this.totalTasks === this.finishedTasks) { - clearInterval(intervalId); - cb(); - } - }, 500); - } else { - return new Promise((r) => { - const intervalId = setInterval(() => { - if (this.totalTasks === this.finishedTasks) { - clearInterval(intervalId); - r(); - } - }, 500); - }); - } - } -} diff --git a/src/@inxt-js/lib/contracts.ts b/src/@inxt-js/lib/contracts.ts deleted file mode 100644 index 46a54e741..000000000 --- a/src/@inxt-js/lib/contracts.ts +++ /dev/null @@ -1,13 +0,0 @@ -export interface ContractNegotiated { - hash: string; - token: string; - operation: 'PUSH'; - farmer: { - userAgent: string; - protocol: string; - address: string; - port: number; - nodeID: string; - lastSeen: number; - }; -} diff --git a/src/@inxt-js/lib/crypto/constants.ts b/src/@inxt-js/lib/crypto/constants.ts deleted file mode 100644 index e73e8ac83..000000000 --- a/src/@inxt-js/lib/crypto/constants.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const GCM_DIGEST_SIZE = 16; -export const SHA256_DIGEST_SIZE = 32; -export const BUCKET_META_MAGIC = [ - 66, 150, 71, 16, 50, 114, 88, 160, 163, 35, 154, 65, 162, 213, 226, 215, 70, 138, 57, 61, 52, 19, 210, 170, 38, 164, - 162, 200, 86, 201, 2, 81, -]; -export const BUCKET_NAME_MAGIC = '398734aab3c4c30c9f22590e83a95f7e43556a45fc2b3060e0c39fde31f50272'; diff --git a/src/@inxt-js/lib/crypto/crypto.ts b/src/@inxt-js/lib/crypto/crypto.ts index 5e3150de3..d873f74fb 100644 --- a/src/@inxt-js/lib/crypto/crypto.ts +++ b/src/@inxt-js/lib/crypto/crypto.ts @@ -1,26 +1,30 @@ +/** + * Internxt Cryptographic Functions + * + * These functions are used by BOTH legacy and modern download systems. + * They implement Internxt-specific key derivation and hashing algorithms. + * + * Usage: + * - sha256: Used for file hashing + * - ripemd160: Used for file integrity verification (SHA256 + RIPEMD160) + * - GenerateFileKey: Derives encryption key from mnemonic + bucketId + index + * + * Used by: + * - NetworkFacade (modern) + * - NetworkService/download (v1) + * - @inxt-js/FileObject (legacy fallback) + */ + import * as crypto from 'react-native-crypto'; import { createHash, pbkdf2 } from '@internxt/rn-crypto'; import { HMAC } from '@internxt/rn-crypto/src/types/crypto'; -import { isValidFilename } from 'src/helpers'; import unorm from 'unorm'; -import { BUCKET_META_MAGIC, GCM_DIGEST_SIZE, SHA256_DIGEST_SIZE } from './constants'; + export function sha256(input: Buffer): Buffer { return crypto.createHash('sha256').update(input).digest(); } -export function sha256HashBuffer(): crypto.Hash { - return crypto.createHash('sha256'); -} - -export function sha512(input: Buffer): Buffer { - return crypto.createHash('sha512').update(input).digest(); -} - -export function sha512HmacBuffer(key: Buffer | string): crypto.Hmac { - return crypto.createHmac('sha512', key); -} - export function ripemd160(input: Buffer | string): Buffer { return crypto.createHash('ripemd160').update(input).digest(); } @@ -50,100 +54,3 @@ export async function GenerateFileKey(mnemonic: string, bucketId: string, index: const deterministicKey = await GetDeterministicKey(bucketKey.slice(0, 32), index); return deterministicKey.slice(0, 32); } - -export async function EncryptFilename(mnemonic: string, bucketId: string, filename: string): Promise { - if (!isValidFilename(filename)) { - throw new Error('This filename is not valid'); - } - - const bucketKey = await GenerateBucketKey(mnemonic, bucketId); - const GenerateEncryptionKey = () => { - const hasher = sha512HmacBuffer(bucketKey); - - hasher.update(Buffer.from(BUCKET_META_MAGIC)); - - return hasher.digest().slice(0, 32); - }; - - const GenerateEncryptionIv = () => { - const hasher = sha512HmacBuffer(bucketKey); - - hasher.update(bucketId).update(filename); - - return hasher.digest().slice(0, 32); - }; - - const encryptionKey = GenerateEncryptionKey(); - const encryptionIv = GenerateEncryptionIv(); - - return EncryptMeta(filename, encryptionKey, encryptionIv); -} - -export async function DecryptFileName( - mnemonic: string, - bucketId: string, - encryptedName: string, -): Promise { - const bucketKey = (await GenerateBucketKey(mnemonic, bucketId)).toString('hex'); - - if (!bucketKey) { - throw Error('Bucket key missing'); - } - - const key = crypto - .createHmac('sha512', Buffer.from(bucketKey, 'hex')) - .update(Buffer.from(BUCKET_META_MAGIC)) - .digest('hex'); - - const decryptedFilename = decryptMeta(encryptedName, key); - - return decryptedFilename; -} - -function decryptMeta(bufferBase64: string, decryptKey: string) { - const data = Buffer.from(bufferBase64, 'base64'); - - const digest = data.slice(0, GCM_DIGEST_SIZE); - const iv = data.slice(GCM_DIGEST_SIZE, GCM_DIGEST_SIZE + SHA256_DIGEST_SIZE); - const buffer = data.slice(GCM_DIGEST_SIZE + SHA256_DIGEST_SIZE); - - const decipher = crypto.createDecipheriv('aes-256-gcm', Buffer.from(decryptKey, 'hex').slice(0, 32), iv); - - decipher.setAuthTag(digest); - - try { - const dec = Buffer.concat([decipher.update(buffer), decipher.final()]); - - return dec.toString('utf8'); - } catch (e) { - return null; - } -} - -export function EncryptMeta(fileMeta: string, key: Buffer, iv: Buffer): string { - const cipher: crypto.CipherCCM = Aes256gcmEncrypter(key, iv); - const cipherTextBuf = Buffer.concat([cipher.update(fileMeta, 'utf-8'), cipher.final()]); - const digest = cipher.getAuthTag(); - - return Buffer.concat([digest, iv, cipherTextBuf]).toString('base64'); -} - -export function EncryptMetaBuffer(fileMeta: string, encryptKey: Buffer, iv: Buffer): Buffer { - const cipher: crypto.CipherGCM = Aes256gcmEncrypter(encryptKey, iv); - const cipherTextBuf = Buffer.concat([cipher.update(fileMeta, 'utf-8'), cipher.final()]); - const digest = cipher.getAuthTag(); - - return Buffer.concat([digest, iv, cipherTextBuf]); -} - -export function Aes256ctrDecrypter(key: Buffer, iv: Buffer): crypto.Decipher { - return crypto.createDecipheriv('aes-256-ctr', key, iv); -} - -export function Aes256ctrEncrypter(key: Buffer, iv: Buffer): crypto.Cipher { - return crypto.createCipheriv('aes-256-ctr', key, iv); -} - -export function Aes256gcmEncrypter(key: Buffer, iv: Buffer): crypto.CipherGCM { - return crypto.createCipheriv('aes-256-gcm', key, iv); -} diff --git a/src/@inxt-js/lib/crypto/index.ts b/src/@inxt-js/lib/crypto/index.ts index 0705c5268..f17024754 100644 --- a/src/@inxt-js/lib/crypto/index.ts +++ b/src/@inxt-js/lib/crypto/index.ts @@ -1,2 +1 @@ -export * from './constants'; export * from './crypto'; diff --git a/src/@inxt-js/lib/decryptstream.ts b/src/@inxt-js/lib/decryptstream.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/@inxt-js/lib/download/download.ts b/src/@inxt-js/lib/download/download.ts index 4b74f4623..41ee5a13d 100644 --- a/src/@inxt-js/lib/download/download.ts +++ b/src/@inxt-js/lib/download/download.ts @@ -1,6 +1,6 @@ import { DownloadProgressCallback, EnvironmentConfig } from '../..'; import { FileObject } from '../../api/FileObject'; -import { DOWNLOAD } from '../events'; +import { Download } from '../events'; import { ActionState } from '../../api/actionState'; import { DOWNLOAD_CANCELLED } from '../../api/constants'; import FileManager from '../../api/FileManager'; @@ -44,7 +44,7 @@ function handleProgress(fl: FileObject, progressCb: DownloadProgressCallback) { throw new Error('Total file size can not be 0'); } - fl.on(DOWNLOAD.PROGRESS, (addedBytes: number) => { + fl.on(Download.Progress, (addedBytes: number) => { totalBytesDownloaded += addedBytes; progress = totalBytesDownloaded / totalBytes; progressCb(progress, totalBytesDownloaded, totalBytes); diff --git a/src/@inxt-js/lib/encryptStream.ts b/src/@inxt-js/lib/encryptStream.ts deleted file mode 100644 index 8b73fd9b4..000000000 --- a/src/@inxt-js/lib/encryptStream.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { createCipheriv, Cipher } from 'react-native-crypto'; - -interface RawShard { - size: number; - index: number; -} - -type ErrorEvent = 'error'; -type ErrorListener = (err: Error) => void; -type onErrorListener = (event: ErrorEvent, listener: ErrorListener) => void; - -type DataEvent = 'data'; -type DataListener = (chunk: Buffer) => void; -type onDataListener = (event: DataEvent, listener: DataListener) => void; - -type EndEvent = 'end'; -type EndListener = (err?: Error) => void; -type onEndListener = (event: EndEvent, listener: EndListener) => void; - -type StreamEvent = ErrorEvent | DataEvent | EndEvent; -type StreamListener = ErrorListener & DataListener & EndListener; -type onListener = onDataListener & onEndListener & onErrorListener; - -export class EncryptStream { - private cipher: Cipher; - private indexCounter = 0; - private listeners: Map = new Map(); - - public shards: RawShard[] = []; - - constructor(key: Buffer, iv: Buffer) { - this.cipher = createCipheriv('aes-256-ctr', key, iv); - - this.listeners.set('end', []); - this.listeners.set('data', []); - this.listeners.set('error', []); - } - - on: onListener = (event: any, listener: any) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - this.listeners.get(event)?.push(listener); - }; - - emit(event: StreamEvent, content?: any): void { - this.listeners.get(event)?.forEach((listener) => listener(content)); - } - - push(chunk: Buffer): void { - if (!chunk) { - return this.end(); - } - - this.cipher.write(chunk); - - this.shards.push({ size: chunk.byteLength, index: this.indexCounter }); - this.indexCounter++; - - this.emit('data', this.cipher.read()); - } - - end(): void { - const lastChunk = this.cipher.read(); - - if (lastChunk) { - this.emit('data', lastChunk); - } - this.emit('end'); - } -} - -export default EncryptStream; diff --git a/src/@inxt-js/lib/events.ts b/src/@inxt-js/lib/events.ts index 0ecb8a74a..6e86180b6 100644 --- a/src/@inxt-js/lib/events.ts +++ b/src/@inxt-js/lib/events.ts @@ -1,40 +1,3 @@ -export enum DOWNLOAD { - PROGRESS = 'download-progress', - ERROR = 'download-error', - END = 'download-end', -} - -export enum UPLOAD { - PROGRESS = 'upload-progress', - ERROR = 'upload-error', - END = 'upload-end', -} - -export enum DECRYPT { - PROGRESS = 'decrypt-progress', - ERROR = 'decrypt-error', - END = 'decrypt-end', -} - -export enum ENCRYPT { - PROGRESS = 'encrypt-progress', - ERROR = 'encrypt-error', - END = 'encrypt-end', -} - -export enum FILEMUXER { - PROGRESS = 'filemuxer-progress', - DATA = 'filemuxer-data', - ERROR = 'filemuxer-error', - END = 'filemuxer-end', -} - -export enum FILEOBJECT { - PROGRESS = 'fileobject-progress', - ERROR = 'fileobject-error', - END = 'fileobject-end', -} - export enum Download { Progress = 'download-progress', Error = 'download-error', diff --git a/src/@inxt-js/lib/filemuxer.ts b/src/@inxt-js/lib/filemuxer.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/@inxt-js/lib/funnelStream.ts b/src/@inxt-js/lib/funnelStream.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/@inxt-js/lib/hashstream.ts b/src/@inxt-js/lib/hashstream.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/@inxt-js/lib/index.ts b/src/@inxt-js/lib/index.ts deleted file mode 100644 index a63bb90be..000000000 --- a/src/@inxt-js/lib/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './INXTRequest'; diff --git a/src/@inxt-js/lib/merkleTree.ts b/src/@inxt-js/lib/merkleTree.ts deleted file mode 100644 index ef825a4f8..000000000 --- a/src/@inxt-js/lib/merkleTree.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { randomBytes } from 'react-native-crypto'; -import { ripemd160, sha256 } from './crypto'; - -interface MerkleTree { - leaf: string[]; - challenges: Buffer[]; - challenges_as_str: string[]; - preleaf: Buffer[]; -} - -const SHARD_CHALLENGES = 4; - -function arrayBufferToString(array: Buffer[]): string[] { - return array.map((b) => { - return b.toString('hex'); - }); -} - -export function preleaf(challenge: Uint8Array, encrypted: Uint8Array): Buffer { - const preleafContent = Buffer.concat([challenge, encrypted]); - - return ripemd160(sha256(preleafContent)); -} - -function preleafArray(encrypted: Buffer, challenge: Buffer[]): Buffer[] { - const preleafArray = challenge.map((challenge) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - return Buffer.concat([challenge, encrypted]); - }); - - return preleafArray; -} - -function leaf(preleaf: Buffer): Buffer { - return ripemd160(sha256(preleaf)); -} - -function leafArray(preleafArray: Buffer[]): Buffer[] { - return preleafArray.map((preleaf) => { - return leaf(preleaf); - }); -} -/* -function getChallenges(): Buffer[] { - let challenges: Buffer[] = new Array(SHARD_CHALLENGES); - for (let i = 0; i < SHARD_CHALLENGES; i++) { - challenges.push(randomBytes(16)) - } - return challenges -} -*/ - -function challenge(): Buffer { - return randomBytes(16); -} - -function challengeArray(): Buffer[] { - const challengeArray = []; - - for (let i = 0; i < SHARD_CHALLENGES; i++) { - challengeArray.push(challenge()); - } - - return challengeArray; -} - -function merkleTree(encrypted: Buffer): MerkleTree { - // set the challenges randomnly - const challenges = challengeArray(); - - const preleaves = preleafArray(encrypted, challenges); - const leaves = leafArray(preleaves); - - const merkleTree: MerkleTree = { - leaf: arrayBufferToString(leaves), - challenges, - challenges_as_str: arrayBufferToString(challenges), - preleaf: preleaves, - }; - - return merkleTree; -} - -function getChallenges(mT: MerkleTree): string[] { - const challenges = mT.challenges.map((challengeBuffer) => { - return challengeBuffer.toString('hex'); - }); - - return challenges; -} - -function getTree(mT: MerkleTree): string[] { - const tree = mT.leaf.map((leafBuffer) => { - return leafBuffer.toString(); - }); - - return tree; -} - -export { getChallenges, getTree, merkleTree, MerkleTree }; diff --git a/src/@inxt-js/lib/shardMeta.ts b/src/@inxt-js/lib/shardMeta.ts deleted file mode 100644 index 7cef68d9c..000000000 --- a/src/@inxt-js/lib/shardMeta.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { MerkleTree, merkleTree } from './merkleTree'; -import { ripemd160, sha256 } from './crypto'; - -// req object for put a frame -export interface ShardMeta { - hash: string; - size: number; // size of the actual file - index: number; - parity: boolean; - challenges?: Buffer[]; - challenges_as_str: string[]; - tree: string[]; - exclude?: any; -} - -const getShardHash = (encryptedShardData: Buffer) => ripemd160(sha256(encryptedShardData)); - -export function getShardMeta( - encryptedShardData: Buffer, - fileSize: number, - index: number, - parity: boolean, - exclude?: any, -): ShardMeta { - const mT: MerkleTree = merkleTree(encryptedShardData); - - return { - hash: getShardHash(encryptedShardData).toString('hex'), - size: fileSize, - index, - parity, - challenges_as_str: mT.challenges_as_str, - tree: mT.leaf, - }; -} diff --git a/src/@inxt-js/lib/utils/index.ts b/src/@inxt-js/lib/utils/index.ts deleted file mode 100644 index f90536605..000000000 --- a/src/@inxt-js/lib/utils/index.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { MIN_SHARD_SIZE, SHARD_MULTIPLE_BACK, MAX_SHARD_SIZE } from '../../api/constants'; - -/** - * Determines the best concurrency number of chunks in memory to fit - * desired ram usage - * @param desiredRamUsage Desired ram usage in bytes - * @param fileSize Size of the file to work with - * @returns Concurrency number - */ -export function determineConcurrency(desiredRamUsage: number, fileSize: number): number { - const shardSize = determineShardSize(fileSize); - - return Math.max(Math.floor(desiredRamUsage / shardSize), 1); -} - -function shardSize(hops: number): number { - return MIN_SHARD_SIZE * Math.pow(2, hops); -} - -export function _determineShardSize(fileSize: number, accumulator = 0): number { - if (fileSize < 0) { - return 0; - } - - let hops = accumulator - SHARD_MULTIPLE_BACK < 0 ? 0 : accumulator - SHARD_MULTIPLE_BACK; - - const byteMultiple = shardSize(accumulator); - - const check = fileSize / byteMultiple; - - if (check > 0 && check <= 1) { - while (hops > 0 && shardSize(hops) > MAX_SHARD_SIZE) { - hops = hops - 1 <= 0 ? 0 : hops - 1; - } - - return shardSize(hops); - } - - if (accumulator > 41) { - return 0; - } - - return _determineShardSize(fileSize, ++accumulator); -} - -export function determineParityShards(totalShards: number): number { - return Math.ceil((totalShards * 2) / 3); -} - -/** - * Determines the best shard size for a provided file size - * @param fileSize Size of the file to be sharded - * @returns Shard size - */ -export function determineShardSize(fileSize: number): number { - const oneMb = 1024 * 1024; - - const thirtyMb = 30 * oneMb; - const fiftyMb = 50 * oneMb; - const oneHundredMb = 100 * oneMb; - const twoHundredMb = 200 * oneMb; - const fourHundredMb = 400 * oneMb; - - if (fileSize < thirtyMb) { - return 4095 * 600; // 2Mb (rounded to base64 compatible size) - } - - if (fileSize < fiftyMb) { - return 4095 * 600 * 3; // 7Mb - } - - if (fileSize < oneHundredMb) { - return 4095 * 600 * 5; // 12Mb - } - - if (fileSize < twoHundredMb) { - return 4095 * 600 * 10; // 24Mb - } - - if (fileSize < fourHundredMb) { - return 4095 * 600 * 15; // 37Mb - } - - return 4095 * 600 * 20; // 49Mb -} - -export function determineTick(fileSize: number): number { - const oneMb = 1024 * 1024; - const oneHundredMb = 100 * oneMb; - const twoHundredMb = 200 * oneMb; - - if (fileSize < oneHundredMb) { - return 50; - } - - if (fileSize < twoHundredMb) { - return 200; - } - - return 1000; -} diff --git a/src/@inxt-js/lib/utils/mutex/index.ts b/src/@inxt-js/lib/utils/mutex/index.ts deleted file mode 100644 index b1ad937b0..000000000 --- a/src/@inxt-js/lib/utils/mutex/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './mutex'; diff --git a/src/@inxt-js/lib/utils/mutex/mutex.ts b/src/@inxt-js/lib/utils/mutex/mutex.ts deleted file mode 100644 index 059ac3712..000000000 --- a/src/@inxt-js/lib/utils/mutex/mutex.ts +++ /dev/null @@ -1,21 +0,0 @@ -export class Mutex { - private mutex = Promise.resolve(); - - lock(): PromiseLike<() => void> { - let begin: (unlock: () => void) => void; - - this.mutex = this.mutex.then(() => new Promise(begin)); - - return new Promise((res) => (begin = res)); - } - - async dispatch(fn: (() => void) | (() => PromiseLike)): Promise { - const unlock = await this.lock(); - - try { - return await Promise.resolve(fn()); - } finally { - unlock(); - } - } -} diff --git a/src/@inxt-js/lib/utils/shard.ts b/src/@inxt-js/lib/utils/shard.ts deleted file mode 100644 index 6c2836a54..000000000 --- a/src/@inxt-js/lib/utils/shard.ts +++ /dev/null @@ -1,50 +0,0 @@ -function computeShardSizeBits(fileSize: number): number { - // Check if fileSize == 0 - if (fileSize === 0) { - return 0; - } - - const MIN_SHARD_SIZE = 2097152; // 2Mb - const MAX_SHARD_SIZE = 4294967296; // 4 Gb - const SHARD_MULTIPLES_BACK = 4; - - const shardSize = function (hops: number): number { - return MIN_SHARD_SIZE * Math.pow(2, hops); - }; - - // Maximum of 2 ^ 41 * 8 * 1024 * 1024 - for (let accumulator = 0; accumulator < 41; accumulator++) { - let hops = accumulator - SHARD_MULTIPLES_BACK < 0 ? 0 : accumulator - SHARD_MULTIPLES_BACK; - const byteMultiple = shardSize(accumulator); - const check = fileSize / byteMultiple; - - if (check > 0 && check <= 1) { - while (hops > 0 && shardSize(hops) > MAX_SHARD_SIZE) { - hops = hops - 1 <= 0 ? 0 : hops - 1; - } - - return shardSize(hops); - } - } - - return 0; -} - -// Returns the shard size in Bytes -export function computeShardSize(fileSize: number): number { - const fileSizeBits = fileSize * 8; - const shardSizeBits = computeShardSizeBits(fileSizeBits); - // return the number of bytes - const shardBytes = Math.ceil(shardSizeBits / 8); - - return shardBytes; -} - -// Returns the number of shards -export function totalDataShards(fileSize: number): number { - // Convert to bits - const fileSizeBits = fileSize * 8; - const totalShards = Math.ceil(fileSizeBits / computeShardSize(fileSizeBits)); - - return totalShards; -} diff --git a/src/@inxt-js/lib/utils/slicer.ts b/src/@inxt-js/lib/utils/slicer.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/@inxt-js/services/api.ts b/src/@inxt-js/services/api.ts deleted file mode 100644 index bab60b31b..000000000 --- a/src/@inxt-js/services/api.ts +++ /dev/null @@ -1,335 +0,0 @@ -import { AxiosRequestConfig, AxiosResponse } from 'axios'; -import { EnvironmentConfig } from '..'; -import { ExchangeReport } from '../api/reports'; -import { Shard } from '../api/shard'; -import { INXTRequest } from '../lib'; -import { ShardMeta } from '../lib/shardMeta'; - -export enum Methods { - Get = 'GET', - Post = 'POST', - Put = 'PUT', -} - -export interface GetBucketByIdResponse { - user: string; - encryptionKey: string; - publicPermissions: string[]; - created: string; - name: string; - pubkeys: string[]; - status: 'Active' | 'Inactive'; - transfer: number; - storage: number; - id: string; -} - -export interface GetFileByIdResponse { - /* file-id */ - id: string; -} - -export interface FrameStaging { - /* frame id */ - id: string; - /* user email */ - user: string; - shards: []; - storageSize: number; - /* frame size */ - size: number; - locked: boolean; - /* created timestamp stringified */ - created: string; -} - -export interface CreateEntryFromFrameBody { - frame: string; - filename: string; - index: string; - hmac: { - type: string; - value: string; - }; - erasure?: { - type: string; - }; -} - -export interface CreateEntryFromFrameResponse { - /* bucket entry id */ - id: string; - index: string; - /* frame id */ - frame: string; - /* bucket id */ - bucket: string; - mimetype: string; - name: string; - renewal: string; - created: string; - hmac: { - value: string; - type: string; - }; - erasure: { - type: string; - }; - size: number; -} - -export interface SendShardToNodeResponse { - result: string; -} - -export interface AddShardToFrameBody { - /* shard hash */ - hash: string; - /* shard size */ - size: number; - /* shard index */ - index: number; - /* if exists a shard parity for this shard */ - parity: boolean; - /* shard challenges */ - challenges: string[]; - tree: string[]; - /* nodes excluded from being the shard's node */ - exclude: string[]; -} - -export interface SendShardToNodeResponse { - result: string; -} - -export interface CreateFileTokenResponse { - bucket: string; - encryptionKey: string; - expires: string; - id: string; - mimetype: string; - operation: 'PUSH' | 'PULL'; - size: number; - token: string; -} - -export interface InxtApiI { - getBucketById(bucketId: string, params?: AxiosRequestConfig): INXTRequest; - getFileById(bucketId: string, fileId: string, params?: AxiosRequestConfig): INXTRequest; - createFrame(params?: AxiosRequestConfig): INXTRequest; - createEntryFromFrame(bucketId: string, body: CreateEntryFromFrameBody, params?: AxiosRequestConfig): INXTRequest; - addShardToFrame(frameId: string, body: ShardMeta, params?: AxiosRequestConfig): INXTRequest; - sendUploadExchangeReport(exchangeReport: ExchangeReport): Promise>; - sendShardToNode(shard: Shard, shardContent: Buffer): INXTRequest; - getShardFromNode(shard: Shard): INXTRequest; - createFileToken(bucketId: string, fileId: string, operation: 'PUSH' | 'PULL'): INXTRequest; - requestPut(shard: Omit): INXTRequest; - requestGet(shard: Shard): INXTRequest; - putShard(url: string, content: Buffer): INXTRequest; -} - -function emptyINXTRequest(config: EnvironmentConfig): INXTRequest { - return new INXTRequest(config, Methods.Get, '', {}, false); -} - -class InxtApi implements InxtApiI { - protected config: EnvironmentConfig; - protected url: string; - - constructor(config: EnvironmentConfig) { - this.config = config; - this.url = config.bridgeUrl ?? ''; - } - - getBucketById(bucketId: string, params?: AxiosRequestConfig): INXTRequest { - return emptyINXTRequest(this.config); - } - - getFileById(bucketId: string, fileId: string, params?: AxiosRequestConfig): INXTRequest { - return emptyINXTRequest(this.config); - } - - createFrame(params?: AxiosRequestConfig): INXTRequest { - return emptyINXTRequest(this.config); - } - - createEntryFromFrame(bucketId: string, body: CreateEntryFromFrameBody, params?: AxiosRequestConfig): INXTRequest { - return emptyINXTRequest(this.config); - } - - addShardToFrame(frameId: string, body: ShardMeta, params?: AxiosRequestConfig): INXTRequest { - return emptyINXTRequest(this.config); - } - - sendUploadExchangeReport(exchangeReport: ExchangeReport): Promise> { - return exchangeReport.sendReport(); - } - - sendShardToNode(shard: Shard, shardContent: Buffer): INXTRequest { - return emptyINXTRequest(this.config); - } - - getShardFromNode(shard: Shard): INXTRequest { - return emptyINXTRequest(this.config); - } - - createFileToken(bucketId: string, fileId: string, operation: 'PUSH' | 'PULL'): INXTRequest { - return emptyINXTRequest(this.config); - } - - requestPut(shard: Shard): INXTRequest { - return emptyINXTRequest(this.config); - } - - requestGet(shard: Shard): INXTRequest { - return emptyINXTRequest(this.config); - } - - putShard(url: string, content: Buffer): INXTRequest { - return emptyINXTRequest(this.config); - } -} - -export class EmptyBridgeUrlError extends Error { - constructor() { - super('Empty bridge url'); - } -} -export class Bridge extends InxtApi { - constructor(config: EnvironmentConfig) { - if (config.bridgeUrl === '') { - throw new EmptyBridgeUrlError(); - } - super(config); - } - - getBucketById(bucketId: string, params?: AxiosRequestConfig): INXTRequest { - const targetUrl = `${this.url}/buckets/${bucketId}`; - const defParams: AxiosRequestConfig = { - headers: { - 'Content-Type': 'application/octet-stream', - }, - }; - - const finalParams = { ...defParams, ...params }; - - return new INXTRequest(this.config, Methods.Get, targetUrl, finalParams, false); - } - - getFileById(bucketId: string, fileId: string, params?: AxiosRequestConfig): INXTRequest { - const targetUrl = `${this.url}/buckets/${bucketId}/file-ids/${fileId}`; - const defParams: AxiosRequestConfig = { - headers: { - 'Content-Type': 'application/octet-stream', - }, - }; - - const finalParams = { ...defParams, ...params }; - - return new INXTRequest(this.config, Methods.Get, targetUrl, finalParams, false); - } - - createFrame(params?: AxiosRequestConfig): INXTRequest { - const targetUrl = `${this.url}/frames`; - const defParams: AxiosRequestConfig = { - headers: { - 'Content-Type': 'application/octet-stream', - }, - }; - - const finalParams = { ...defParams, ...params }; - - return new INXTRequest(this.config, Methods.Post, targetUrl, finalParams, false); - } - - createEntryFromFrame(bucketId: string, body: CreateEntryFromFrameBody, params?: AxiosRequestConfig): INXTRequest { - const targetUrl = `${this.url}/buckets/${bucketId}/files/ensure`; - const defParams: AxiosRequestConfig = { - headers: { - 'Content-Type': 'application/octet-stream', - }, - data: body, - }; - - const finalParams = { ...defParams, ...params }; - - return new INXTRequest(this.config, Methods.Post, targetUrl, finalParams, false); - } - - addShardToFrame(frameId: string, body: ShardMeta, params?: AxiosRequestConfig): INXTRequest { - const targetUrl = `${this.url}/frames/${frameId}`; - const defParams: AxiosRequestConfig = { - headers: { - 'Content-Type': 'application/octet-stream', - }, - data: { ...body, challenges: body.challenges_as_str }, - }; - - const finalParams = { ...defParams, ...params }; - - return new INXTRequest(this.config, Methods.Put, targetUrl, finalParams, false); - } - - sendUploadExchangeReport(exchangeReport: ExchangeReport): Promise> { - return exchangeReport.sendReport(); - } - - sendShardToNode(shard: Shard, shardContent: Buffer): INXTRequest { - const targetUrl = `http://${shard.farmer.address}:${shard.farmer.port}/shards/${shard.hash}?token=${shard.token}`; - - return new INXTRequest(this.config, Methods.Post, targetUrl, { data: shardContent }, true); - } - - getShardFromNode(shard: Shard): INXTRequest { - const { farmer, hash, token } = shard; - const { address, port } = farmer; - const targetUrl = `http://${address}:${port}/shards/${hash}?token=${token}`; - - return new INXTRequest( - this.config, - Methods.Get, - targetUrl, - { - headers: { - 'content-type': 'application/octet-stream', - }, - responseType: 'arraybuffer', - }, - this.config.useProxy ?? true, - ); - } - - createFileToken(bucketId: string, fileId: string, operation: 'PUSH' | 'PULL'): INXTRequest { - const targetUrl = `https://api.internxt.com/buckets/${bucketId}/tokens`; - - return new INXTRequest(this.config, Methods.Post, targetUrl, { data: { operation, file: fileId } }, false); - } - - requestPut(shard: Shard): INXTRequest { - const targetUrl = `http://${shard.farmer.address}:${shard.farmer.port}/upload/link/${shard.hash}`; - - return new INXTRequest(this.config, Methods.Get, targetUrl, {}, true); - } - - requestGet(shard: Shard): INXTRequest { - const targetUrl = `http://${shard.farmer.address}:${shard.farmer.port}/download/link/${shard.hash}`; - - return new INXTRequest(this.config, Methods.Get, targetUrl, {}, true); - } - - putShard(url: string, content: Buffer): INXTRequest { - return new INXTRequest(this.config, Methods.Put, url, { data: content }, false); - } - - getShard(url: string): INXTRequest { - return new INXTRequest( - this.config, - Methods.Get, - url, - { - responseType: 'arraybuffer', - }, - false, - ); - } -} diff --git a/src/@inxt-js/services/proxy.ts b/src/@inxt-js/services/proxy.ts deleted file mode 100644 index 162b40523..000000000 --- a/src/@inxt-js/services/proxy.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { Mutex } from '../lib/utils/mutex'; - -const wait = (ms: number) => new Promise((res) => setTimeout(res, ms)); - -const MAX_CONCURRENT_BROWSER_CONNECTIONS = 6; - -export class ProxyBalancer { - private proxies: Proxy[]; - - constructor() { - this.proxies = []; - } - - async getProxy(reqsLessThan: number): Promise { - const proxiesCopy = [...this.proxies]; - - let proxiesAvailable; - - while ((proxiesAvailable = proxiesCopy.filter((proxy) => proxy.requests() < reqsLessThan)).length === 0) { - await wait(500); - } - - return proxiesAvailable[0]; - } - - attach(p: Proxy): ProxyBalancer { - this.proxies.push(p); - - return this; - } - - del(p: Proxy): void { - this.proxies = this.proxies.filter((proxy) => proxy.url !== p.url); - } -} - -export class Proxy { - public url: string; - private currentRequests: ProxyRequest[]; - - constructor(url: string) { - this.url = url; - this.currentRequests = []; - } - - requests(): number { - return this.currentRequests.length; - } - - addReq(p: ProxyRequest): void { - this.currentRequests.push(p); - } - - removeReq(p: ProxyRequest): void { - this.currentRequests = this.currentRequests.filter((req) => req.id !== p.id); - } -} - -export interface ProxyRequest { - id: number; -} - -export interface ProxyManager { - url: string; - free: () => void; -} - -const proxyBalancer = new ProxyBalancer() - .attach(new Proxy('https://proxy01.api.internxt.com')) - .attach(new Proxy('https://proxy02.api.internxt.com')) - .attach(new Proxy('https://proxy03.api.internxt.com')) - .attach(new Proxy('https://proxy04.api.internxt.com')) - .attach(new Proxy('https://proxy05.api.internxt.com')) - .attach(new Proxy('https://proxy06.api.internxt.com')) - .attach(new Proxy('https://proxy07.api.internxt.com')); - -const mutex = new Mutex(); - -export const getProxy = async (): Promise => { - let response = { - ...new Proxy(''), - free: () => { - null; - }, - }; - - await mutex.dispatch(async () => { - const proxy = await proxyBalancer.getProxy(MAX_CONCURRENT_BROWSER_CONNECTIONS); - const proxyReq = { id: Math.random() * 9999999 }; - - proxy.addReq(proxyReq); - - response = { ...proxy, free: () => proxy.removeReq(proxyReq) }; - }); - - return response; -}; diff --git a/src/@inxt-js/services/request.ts b/src/@inxt-js/services/request.ts index abdd2b25a..aa1835af7 100644 --- a/src/@inxt-js/services/request.ts +++ b/src/@inxt-js/services/request.ts @@ -1,47 +1,27 @@ -import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; +import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; import { EnvironmentConfig } from '..'; import { sha256 } from '../lib/crypto'; -import { ExchangeReport } from '../api/reports'; - -import { ShardMeta } from '../lib/shardMeta'; -import { ContractNegotiated } from '../lib/contracts'; -import { Shard } from '../api/shard'; -import { getProxy, ProxyManager } from './proxy'; -import { constants } from 'src/services/AppService'; export async function request( config: EnvironmentConfig, method: AxiosRequestConfig['method'], targetUrl: string, params: AxiosRequestConfig, - useProxy = true, ): Promise> { - let reqUrl = targetUrl; - let proxy: ProxyManager; - - if (useProxy) { - proxy = await getProxy(); - reqUrl = `${proxy.url}/${targetUrl}`; - } - const DefaultOptions: AxiosRequestConfig = { method, auth: { username: config.bridgeUser, password: sha256(Buffer.from(config.bridgePass)).toString('hex'), }, - url: reqUrl, + url: targetUrl, maxContentLength: Infinity, }; const options = { ...DefaultOptions, ...params }; return axios.request(options).then((value: AxiosResponse) => { - if (useProxy && proxy) { - proxy.free(); - } - return value; }); } @@ -50,298 +30,28 @@ export async function plainRequest( method: AxiosRequestConfig['method'], targetUrl: string, params: AxiosRequestConfig, - useProxy = true, ): Promise> { - let reqUrl = targetUrl; - let proxy: ProxyManager; - - if (useProxy) { - proxy = await getProxy(); - reqUrl = `${proxy.url}/${targetUrl}`; - } - const DefaultOptions: AxiosRequestConfig = { method, - url: reqUrl, + url: targetUrl, maxContentLength: Infinity, }; const options = { ...DefaultOptions, ...params }; return axios.request(options).then((value: AxiosResponse) => { - if (useProxy && proxy) { - proxy.free(); - } - return value; }); } -export async function get(params: { responseType?: string; url: string }, config = { useProxy: false }): Promise { - return plainRequest('GET', params.url, { responseType: params.responseType as any }, config.useProxy).then( - (res) => { - return res.data as unknown as K; - }, - ); +export async function get(params: { responseType?: string; url: string }): Promise { + return plainRequest('GET', params.url, { responseType: params.responseType as any }).then((res) => { + return res.data as unknown as K; + }); } -export async function getBuffer(url: string, config = { useProxy: false }): Promise { - return plainRequest('GET', url, { responseType: 'arraybuffer' }, config.useProxy).then((res) => { +export async function getBuffer(url: string): Promise { + return plainRequest('GET', url, { responseType: 'arraybuffer' }).then((res) => { return Buffer.from(res.request._response, 'base64'); }); } - -interface getBucketByIdResponse { - user: string; - encryptionKey: string; - publicPermissions: string[]; - created: string; - name: string; - pubkeys: string[]; - status: 'Active' | 'Inactive'; - transfer: number; - storage: number; - id: string; -} - -/** - * Checks if a bucket exists given its id - * @param config App config - * @param bucketId - * @param token - * @param jwt JSON Web Token - * @param params - */ -export function getBucketById( - config: EnvironmentConfig, - bucketId: string, - params?: AxiosRequestConfig, -): Promise { - const URL = config.bridgeUrl ? config.bridgeUrl : constants.BRIDGE_URL; - const targetUrl = `${URL}/buckets/${bucketId}`; - const defParams: AxiosRequestConfig = { - headers: { - 'Content-Type': 'application/octet-stream', - }, - }; - - const finalParams = { ...defParams, ...params }; - - return request(config, 'get', targetUrl, finalParams, false).then( - (res: AxiosResponse) => res.data, - ); -} - -interface getFileByIdResponse { - /* file-id */ - id: string; -} - -/** - * Checks if a file exists given its id and a bucketId - * @param config App config - * @param bucketId - * @param fileId - * @param jwt JSON Web Token - * @param params - */ -export function getFileById( - config: EnvironmentConfig, - bucketId: string, - fileId: string, - params?: AxiosRequestConfig, -): Promise { - const URL = config.bridgeUrl ? config.bridgeUrl : constants.BRIDGE_URL; - const targetUrl = `${URL}/buckets/${bucketId}/file-ids/${fileId}`; - const defParams: AxiosRequestConfig = { - headers: { - 'Content-Type': 'application/octet-stream', - }, - }; - - const finalParams = { ...defParams, ...params }; - - return request(config, 'get', targetUrl, finalParams, false).then( - (res: AxiosResponse) => res.data, - ); -} - -export interface FrameStaging { - /* frame id */ - id: string; - /* user email */ - user: string; - shards: []; - storageSize: number; - /* frame size */ - size: number; - locked: boolean; - /* created timestamp stringified */ - created: string; -} - -/** - * Creates a file staging frame - * @param config App config - * @param params - */ -export function createFrame(config: EnvironmentConfig, params?: AxiosRequestConfig): Promise { - const URL = config.bridgeUrl ? config.bridgeUrl : constants.BRIDGE_URL; - const targetUrl = `${URL}/frames`; - const defParams: AxiosRequestConfig = { - headers: { - 'Content-Type': 'application/octet-stream', - }, - }; - - const finalParams = { ...defParams, ...params }; - - return request(config, 'post', targetUrl, finalParams, false).then((res: AxiosResponse) => res.data); -} - -export interface CreateEntryFromFrameBody { - frame: string; - filename: string; - index: string; - hmac: { - type: string; - value: string; - }; - erasure?: { - type: string; - }; -} - -export interface CreateEntryFromFrameResponse { - /* bucket entry id */ - id: string; - index: string; - /* frame id */ - frame: string; - /* bucket id */ - bucket: string; - mimetype: string; - name: string; - renewal: string; - created: string; - hmac: { - value: string; - type: string; - }; - erasure: { - type: string; - }; - size: number; -} - -/** - * Creates a bucket entry from the given frame object - * @param {EnvironmentConfig} config App config - * @param {string} bucketId - * @param {CreateEntryFromFrameBody} body - * @param {string} jwt JSON Web Token - * @param {AxiosRequestConfig} params - */ -export function createEntryFromFrame( - config: EnvironmentConfig, - bucketId: string, - body: CreateEntryFromFrameBody, - params?: AxiosRequestConfig, -): Promise { - const URL = config.bridgeUrl ? config.bridgeUrl : constants.BRIDGE_URL; - const targetUrl = `${URL}/buckets/${bucketId}/files/ensure`; - const defParams: AxiosRequestConfig = { - headers: { - 'Content-Type': 'application/octet-stream', - }, - data: body, - }; - - const finalParams = { ...defParams, ...params }; - - return request(config, 'post', targetUrl, finalParams, false) - .then((res: AxiosResponse) => res.data) - .catch((err: AxiosError) => { - const message = handleAxiosError(err); - - if (message.includes('duplicate key')) { - throw new Error('File already exists in the network'); - } - - throw new Error(message); - }); -} - -export function handleAxiosError(err: any): string { - return (err.response && err.response.data && err.response.data.error) || err.message; -} - -/** - * Negotiates a storage contract and adds the shard to the frame - * @param {EnvironmentConfig} config App config - * @param {string} frameId - * @param {AddShardToFrameBody} body - * @param {string} jwt JSON Web Token - * @param {AxiosRequestConfig} params - */ -export function addShardToFrame( - config: EnvironmentConfig, - frameId: string, - body: ShardMeta, - params?: AxiosRequestConfig, -): Promise { - const URL = config.bridgeUrl ? config.bridgeUrl : constants.BRIDGE_URL; - const targetUrl = `${URL}/frames/${frameId}`; - const defParams: AxiosRequestConfig = { - headers: { - 'Content-Type': 'application/octet-stream', - }, - data: { ...body, challenges: body.challenges_as_str }, - }; - - const finalParams = { ...defParams, ...params }; - - return request(config, 'put', targetUrl, finalParams, false).then( - (res: AxiosResponse) => res.data, - ); -} - -/** - * Sends an upload exchange report - * @param config App config - * @param body - */ -export function sendUploadExchangeReport( - config: EnvironmentConfig, - exchangeReport: ExchangeReport, -): Promise> { - return exchangeReport.sendReport(); -} - -interface SendShardToNodeResponse { - result: string; -} - -/** - * Stores a shard in a node - * @param config App config - * @param shard Interface that has the contact info - * @param content Buffer with shard content - */ -export function sendShardToNode( - config: EnvironmentConfig, - shard: Shard, - content: Buffer, -): Promise { - const targetUrl = `http://${shard.farmer.address}:${shard.farmer.port}/shards/${shard.hash}?token=${shard.token}`; - - const defParams: AxiosRequestConfig = { - headers: { - 'Content-Type': 'application/octet-stream', - 'x-storj-node-id': shard.farmer.nodeID, - }, - data: content, - }; - - return request(config, 'post', targetUrl, defParams).then((res: AxiosResponse) => res.data); -} diff --git a/src/lib/network.ts b/src/lib/network.ts index 4a390a937..c7f5a3e17 100644 --- a/src/lib/network.ts +++ b/src/lib/network.ts @@ -1,3 +1,15 @@ +/** + * Legacy Network Wrapper + * + * @deprecated This class wraps the legacy @inxt-js download system. + * Only used by downloadLegacy.ts as a final fallback for very old files. + * + * Modern downloads should use: + * - src/network/NetworkFacade.ts (primary) + * - src/services/NetworkService/download.ts (v1 fallback) + * + */ + import { Environment } from '../@inxt-js'; import { ActionState } from '../@inxt-js/api/actionState'; import { FileInfo } from '../@inxt-js/api/fileinfo'; @@ -5,7 +17,6 @@ import FileManager from '../@inxt-js/api/FileManager'; import appService from '../services/AppService'; import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; -import asyncStorage from '../services/AsyncStorageService'; type ProgressCallback = (progress: number, uploadedBytes: number, totalBytes: number) => void; @@ -97,10 +108,6 @@ export class Network { getFileInfo(bucketId: string, fileId: string): Promise { return this.environment.getFileInfo(bucketId, fileId); } - - createFileToken(bucketId: string, fileId: string, operation: 'PULL' | 'PUSH'): Promise { - return this.environment.createFileToken(bucketId, fileId, operation); - } } /** @@ -118,21 +125,4 @@ export function getEnvironmentConfigFromUser(user: UserSettings): EnvironmentCon }; } -/** - * Returns required config to download/upload files - * - * @deprecated Use getEnvironmentConfigFromUser(user) instead to avoid iOS SecureStore errors. - * This function reads from SecureStore which can fail in background contexts on iOS. - * - * @returns Promise resolving to environment configuration - */ -export function getEnvironmentConfig(): Promise { - return asyncStorage.getUser().then((user) => ({ - bridgeUser: user.bridgeUser, - bridgePass: user.userId, - encryptionKey: user.mnemonic, - bucketId: user.bucket, - })); -} - export const generateFileKey = Environment.utils.generateFileKey; diff --git a/src/services/NetworkService/downloadLegacy.ts b/src/services/NetworkService/downloadLegacy.ts index 4f1122096..5810f0557 100644 --- a/src/services/NetworkService/downloadLegacy.ts +++ b/src/services/NetworkService/downloadLegacy.ts @@ -1,3 +1,16 @@ +/** + * Legacy Download Entry Point + * + * This is the ONLY place that should call the legacy @inxt-js download system. + * + * Called when: + * 1. Modern download fails (FileVersionOneError) + * 2. V1 download fails (LegacyDownloadRequiredError - mirrors.length > 1) + * + * This handles very old files with multiple mirrors (legacy redundancy system). + * + */ + import FileManager from '../../@inxt-js/api/FileManager'; import { Network } from '../../lib/network'; import { Abortable, NetworkCredentials } from '../../types'; diff --git a/src/store/slices/app/index.ts b/src/store/slices/app/index.ts index 10b75df52..025b687eb 100644 --- a/src/store/slices/app/index.ts +++ b/src/store/slices/app/index.ts @@ -72,9 +72,7 @@ const initializeThunk = createAsyncThunk( const changeLanguageThunk = createAsyncThunk( 'app/changeLanguage', async (language) => { - console.log('[changeLanguageThunk] Starting with language:', language); await languageService.setLanguage(language); - console.log('[changeLanguageThunk] Completed, returning:', language); return language; }, ); diff --git a/src/useCases/drive/getShareLink.ts b/src/useCases/drive/getShareLink.ts index 7ae1744dd..d5308932b 100644 --- a/src/useCases/drive/getShareLink.ts +++ b/src/useCases/drive/getShareLink.ts @@ -9,7 +9,6 @@ import { aes } from '@internxt/lib'; import strings from 'assets/lang/strings'; import { setStringAsync } from 'expo-clipboard'; import { randomBytes } from 'react-native-crypto'; -import { Network } from 'src/lib/network'; /** * Gets an already generated share link @@ -100,10 +99,9 @@ export const onSharedLinksUpdated = (callback: () => void) => { */ export const generateShareLink = async ({ itemId, - fileId, + displayCopyNotification, type, - plainPassword, }: { itemId: string; fileId?: string | null; @@ -115,14 +113,10 @@ export const generateShareLink = async ({ if (!credentials?.user) throw new Error('User credentials not found'); - const { bucket, mnemonic, email, userId } = credentials.user; - const network = new Network(email, userId, mnemonic); + const { mnemonic } = credentials.user; // Random code for the file const plainCode = randomBytes(32).toString('hex'); - // 1. Get the file token - const itemToken = await network.createFileToken(bucket, fileId as string, 'PULL'); - // 2. Create an encrypted code for the file const encryptedCode = aes.encrypt(plainCode, mnemonic);