diff --git a/src/pages/gallery/components/CollectionSelector.tsx b/src/pages/gallery/components/CollectionSelector.tsx index ebf3c5c54..4c58fd499 100644 --- a/src/pages/gallery/components/CollectionSelector.tsx +++ b/src/pages/gallery/components/CollectionSelector.tsx @@ -13,11 +13,9 @@ function CollectionSelector(props) { collectionAndItsLatestFile, ...rest } = props; - const CollectionIcons = collectionAndItsLatestFile?.map((item) => ( -
+
- {constants.SELECT_COLLECTION} + + {constants.SELECT_COLLECTION} + { - const worker = await new CryptoWorker(); - const encryptionKey = await getActualKey(); - const token = getToken(); - const collectionKey: string = await worker.generateMasterKey(); - const { - encryptedData: encryptedKey, - nonce: keyDecryptionNonce, - }: B64EncryptionResult = await worker.encryptToB64( - collectionKey, - encryptionKey - ); - const { - encryptedData: encryptedName, - nonce: nameDecryptionNonce, - }: B64EncryptionResult = await worker.encryptUTF8( - collectionName, - collectionKey - ); - const newCollection: collection = { - id: null, - owner: null, - encryptedKey, - keyDecryptionNonce, - encryptedName, - nameDecryptionNonce, - type, - attributes: {}, - sharees: null, - updationTime: null, - isDeleted: false, - }; - let createdCollection: collection = await createCollection( - newCollection, - token - ); - createdCollection = await getCollectionSecrets( - createdCollection, - encryptionKey - ); - return createdCollection; + try { + const worker = await new CryptoWorker(); + const encryptionKey = await getActualKey(); + const token = getToken(); + const collectionKey: string = await worker.generateMasterKey(); + const { + encryptedData: encryptedKey, + nonce: keyDecryptionNonce, + }: B64EncryptionResult = await worker.encryptToB64( + collectionKey, + encryptionKey + ); + const { + encryptedData: encryptedName, + nonce: nameDecryptionNonce, + }: B64EncryptionResult = await worker.encryptUTF8( + collectionName, + collectionKey + ); + const newCollection: collection = { + id: null, + owner: null, + encryptedKey, + keyDecryptionNonce, + encryptedName, + nameDecryptionNonce, + type, + attributes: {}, + sharees: null, + updationTime: null, + isDeleted: false, + }; + let createdCollection: collection = await createCollection( + newCollection, + token + ); + createdCollection = await getCollectionSecrets( + createdCollection, + encryptionKey + ); + return createdCollection; + } catch (e) { + console.log('Add collection failed', e); + } }; const createCollection = async ( diff --git a/src/services/fileService.ts b/src/services/fileService.ts index a970103dc..11ae3bc57 100644 --- a/src/services/fileService.ts +++ b/src/services/fileService.ts @@ -3,6 +3,7 @@ import HTTPService from './HTTPService'; import * as Comlink from 'comlink'; import localForage from 'localforage'; import { collection } from './collectionService'; +import { MetadataObject } from './uploadService'; const CryptoWorker: any = typeof window !== 'undefined' && @@ -19,10 +20,9 @@ localForage.config({ const FILES = 'files'; export interface fileAttribute { - encryptedData: Uint8Array | string; + encryptedData?: Uint8Array; + objectKey?: string; decryptionHeader: string; - creationTime: number; - fileType: number; } @@ -31,7 +31,7 @@ export interface file { collectionID: number; file: fileAttribute; thumbnail: fileAttribute; - metadata: fileAttribute; + metadata: MetadataObject; encryptedKey: string; keyDecryptionNonce: string; key: string; diff --git a/src/services/uploadService.ts b/src/services/uploadService.ts index 7ae196659..8bdb453f9 100644 --- a/src/services/uploadService.ts +++ b/src/services/uploadService.ts @@ -10,12 +10,11 @@ const CryptoWorker: any = Comlink.wrap(new Worker('worker/crypto.worker.js', { type: 'module' })); const ENDPOINT = getEndpoint(); -const THUMBNAIL_WIDTH = 1920; -const THUMBNAIL_HEIGHT = 1080; +const THUMBNAIL_HEIGHT = 720; const MAX_ATTEMPTS = 3; const MIN_THUMBNAIL_SIZE = 50000; -interface encryptionResult { +interface EncryptionResult { file: fileAttribute; key: string; } @@ -25,62 +24,59 @@ export interface B64EncryptionResult { nonce: string; } -interface uploadURL { +interface UploadURL { url: string; objectKey: string; } +export interface MetadataObject { + title: string; + creationTime: number; + modificationTime: number; + latitude: number; + longitude: number; + fileType: FILE_TYPE; +} + interface FileinMemory { filedata: Uint8Array; thumbnail: Uint8Array; - filename: string; + metadata: MetadataObject; } -interface encryptedFile { - filedata: fileAttribute; - thumbnail: fileAttribute; +interface EncryptedFile { + file: ProcessedFile; fileKey: B64EncryptionResult; } - -interface objectKey { - objectKey: string; - decryptionHeader: string; -} -interface objectKeys { - file: objectKey; - thumbnail: objectKey; +interface ProcessedFile { + file: fileAttribute; + thumbnail: fileAttribute; + metadata: fileAttribute; } +interface BackupedFile extends ProcessedFile {} -interface uploadFile extends objectKeys { +interface uploadFile extends BackupedFile { collectionID: number; encryptedKey: string; keyDecryptionNonce: string; - metadata?: { - encryptedData: string | Uint8Array; - decryptionHeader: string; - }; -} - -interface UploadFileWithoutMetaData { - tempUploadFile: uploadFile; - encryptedFileKey: B64EncryptionResult; - fileName: string; } export enum UPLOAD_STAGES { START, - ENCRYPTION, - UPLOAD, + READING_GOOGLE_METADATA_FILES, + UPLOADING, FINISH, } class UploadService { - private uploadURLs: uploadURL[]; - private uploadURLFetchInProgress: Promise; - private perStepProgress: number; - private stepsCompleted: number; - private totalFilesCount: number; + private uploadURLs: UploadURL[] = []; + private uploadURLFetchInProgress: Promise = null; + private perFileProgress: number; + private filesCompleted: number; + private totalFileCount: number; private metadataMap: Map; + private filesToBeUploaded: File[]; + private progressBarProps; public async uploadFiles( recievedFiles: File[], @@ -89,11 +85,11 @@ class UploadService { progressBarProps ) { try { - const worker = await new CryptoWorker(); - this.stepsCompleted = 0; + progressBarProps.setUploadStage(UPLOAD_STAGES.START); + + this.filesCompleted = 0; this.metadataMap = new Map(); - this.uploadURLs = []; - this.uploadURLFetchInProgress = null; + this.progressBarProps = progressBarProps; let metadataFiles: File[] = []; let actualFiles: File[] = []; @@ -108,67 +104,33 @@ class UploadService { metadataFiles.push(file); } }); - this.totalFilesCount = actualFiles.length; - this.perStepProgress = 100 / (3 * actualFiles.length); + this.totalFileCount = actualFiles.length; + this.perFileProgress = 100 / actualFiles.length; + this.filesToBeUploaded = actualFiles; - progressBarProps.setUploadStage(UPLOAD_STAGES.START); - this.changeProgressBarProps(progressBarProps); - - const uploadFilesWithoutMetaData: UploadFileWithoutMetaData[] = []; - - while (actualFiles.length > 0) { - var promises = []; - for (var i = 0; i < 5 && actualFiles.length > 0; i++) - promises.push( - this.uploadHelper( - progressBarProps, - actualFiles.pop(), - collectionAndItsLatestFile.collection, - token - ) - ); - uploadFilesWithoutMetaData.push( - ...(await Promise.all(promises)) - ); - } + progressBarProps.setUploadStage( + UPLOAD_STAGES.READING_GOOGLE_METADATA_FILES + ); for await (const rawFile of metadataFiles) { - await this.updateMetadata(rawFile); + await this.seedMetadataMap(rawFile); } - progressBarProps.setUploadStage(UPLOAD_STAGES.ENCRYPTION); - const completeUploadFiles: uploadFile[] = await Promise.all( - uploadFilesWithoutMetaData.map( - async (file: UploadFileWithoutMetaData) => { - const { - file: encryptedMetaData, - } = await this.encryptMetadata( - worker, - file.fileName, - file.encryptedFileKey - ); - const completeUploadFile = { - ...file.tempUploadFile, - metadata: { - encryptedData: encryptedMetaData.encryptedData, - decryptionHeader: - encryptedMetaData.decryptionHeader, - }, - }; - this.changeProgressBarProps(progressBarProps); - return completeUploadFile; - } - ) - ); - - progressBarProps.setUploadStage(UPLOAD_STAGES.UPLOAD); - await Promise.all( - completeUploadFiles.map(async (uploadFile: uploadFile) => { - await this.uploadFile(uploadFile, token); - this.changeProgressBarProps(progressBarProps); - }) - ); + progressBarProps.setUploadStage(UPLOAD_STAGES.UPLOADING); + this.changeProgressBarProps(); + const uploadProcesses = []; + for (let i = 0; i < Math.min(5, this.totalFileCount); i++) { + uploadProcesses.push( + this.uploader( + await new CryptoWorker(), + this.filesToBeUploaded.pop(), + collectionAndItsLatestFile.collection, + token + ) + ); + } + await Promise.all(uploadProcesses); progressBarProps.setUploadStage(UPLOAD_STAGES.FINISH); progressBarProps.setPercentComplete(100); } catch (e) { @@ -176,43 +138,50 @@ class UploadService { throw e; } } - private async uploadHelper(progressBarProps, rawFile, collection, token) { + private async uploader(worker, rawFile, collection, token) { try { - const worker = await new CryptoWorker(); let file: FileinMemory = await this.readFile(rawFile); - let encryptedFile: encryptedFile = await this.encryptFile( + + let encryptedFile: EncryptedFile = await this.encryptFile( worker, file, collection.key ); - let objectKeys = await this.uploadtoBucket( - encryptedFile, - token, - 2 * this.totalFilesCount + let backupedFile: BackupedFile = await this.uploadtoBucket( + encryptedFile.file, + token ); - let uploadFileWithoutMetaData: uploadFile = this.getuploadFile( + let uploadFile: uploadFile = this.getuploadFile( collection, - encryptedFile.fileKey, - objectKeys + backupedFile, + encryptedFile.fileKey ); - this.changeProgressBarProps(progressBarProps); + await this.uploadFile(uploadFile, token); - return { - tempUploadFile: uploadFileWithoutMetaData, - encryptedFileKey: encryptedFile.fileKey, - fileName: file.filename, - }; + this.changeProgressBarProps(); + + if (this.filesToBeUploaded.length > 0) { + await this.uploader( + worker, + this.filesToBeUploaded.pop(), + collection, + token + ); + } } catch (e) { console.log(e); throw e; } } - private changeProgressBarProps({ setPercentComplete, setFileCounter }) { - this.stepsCompleted++; - const fileCompleted = this.stepsCompleted % this.totalFilesCount; - setFileCounter({ current: fileCompleted, total: this.totalFilesCount }); - setPercentComplete(this.perStepProgress * this.stepsCompleted); + private changeProgressBarProps() { + const { setPercentComplete, setFileCounter } = this.progressBarProps; + setFileCounter({ + current: this.filesCompleted + 1, + total: this.totalFileCount, + }); + setPercentComplete(this.filesCompleted * this.perFileProgress); + this.filesCompleted++; } private async readFile(recievedFile: File) { @@ -220,7 +189,9 @@ class UploadService { const filedata: Uint8Array = await this.getUint8ArrayView( recievedFile ); - let fileType; + const thumbnail = await this.generateThumbnail(recievedFile); + + let fileType: FILE_TYPE; switch (recievedFile.type.split('/')[0]) { case 'image': fileType = FILE_TYPE.IMAGE; @@ -235,18 +206,22 @@ class UploadService { const { location, creationTime } = await this.getExifData( recievedFile ); - this.metadataMap.set(recievedFile.name, { - title: recievedFile.name, - creationTime: creationTime || recievedFile.lastModified * 1000, - modificationTime: recievedFile.lastModified * 1000, - latitude: location?.latitude, - longitude: location?.latitude, - fileType, - }); + const metadata = Object.assign( + this.metadataMap.get(recievedFile.name) ?? {}, + { + title: recievedFile.name, + creationTime: + creationTime || recievedFile.lastModified * 1000, + modificationTime: recievedFile.lastModified * 1000, + latitude: location?.latitude, + longitude: location?.latitude, + fileType, + } + ); return { filedata, - filename: recievedFile.name, - thumbnail: await this.generateThumbnail(recievedFile), + thumbnail, + metadata, }; } catch (e) { console.log('error reading files ' + e); @@ -256,28 +231,37 @@ class UploadService { worker, file: FileinMemory, encryptionKey: string - ): Promise { + ): Promise { try { const { key: fileKey, file: encryptedFiledata, - }: encryptionResult = await worker.encryptFile(file.filedata); + }: EncryptionResult = await worker.encryptFile(file.filedata); const { file: encryptedThumbnail, - }: encryptionResult = await worker.encryptThumbnail( + }: EncryptionResult = await worker.encryptThumbnail( file.thumbnail, fileKey ); + const { + file: encryptedMetadata, + }: EncryptionResult = await worker.encryptMetadata( + file.metadata, + fileKey + ); - const encryptedKey: B64EncryptionResult = await worker.encryptB64( + const encryptedKey: B64EncryptionResult = await worker.encryptToB64( fileKey, encryptionKey ); - const result: encryptedFile = { - filedata: encryptedFiledata, - thumbnail: encryptedThumbnail, + const result: EncryptedFile = { + file: { + file: encryptedFiledata, + thumbnail: encryptedThumbnail, + metadata: encryptedMetadata, + }, fileKey: encryptedKey, }; return result; @@ -286,52 +270,26 @@ class UploadService { } } - private async encryptMetadata( - worker: any, - fileName: string, - encryptedFileKey: B64EncryptionResult - ) { - const metaData = this.metadataMap.get(fileName); - const fileKey = await worker.decryptB64( - encryptedFileKey.encryptedData, - encryptedFileKey.nonce, - encryptedFileKey.key - ); - const encryptedMetaData = await worker.encryptMetadata( - metaData, - fileKey - ); - return encryptedMetaData; - } - private async uploadtoBucket( - file: encryptedFile, - token, - count: number - ): Promise { + file: ProcessedFile, + token + ): Promise { try { - const fileUploadURL = await this.getUploadURL(token, count); - const fileObjectKey = await this.putFile( + const fileUploadURL = await this.getUploadURL(token); + file.file.objectKey = await this.putFile( fileUploadURL, - file.filedata.encryptedData + file.file.encryptedData ); - const thumbnailUploadURL = await this.getUploadURL(token, count); - const thumbnailObjectKey = await this.putFile( + const thumbnailUploadURL = await this.getUploadURL(token); + file.thumbnail.objectKey = await this.putFile( thumbnailUploadURL, file.thumbnail.encryptedData ); + delete file.file.encryptedData; + delete file.thumbnail.encryptedData; - return { - file: { - objectKey: fileObjectKey, - decryptionHeader: file.filedata.decryptionHeader, - }, - thumbnail: { - objectKey: thumbnailObjectKey, - decryptionHeader: file.thumbnail.decryptionHeader, - }, - }; + return file; } catch (e) { console.log('error uploading to bucket ' + e); throw e; @@ -340,14 +298,14 @@ class UploadService { private getuploadFile( collection: collection, - encryptedKey: B64EncryptionResult, - objectKeys: objectKeys + backupedFile: BackupedFile, + fileKey: B64EncryptionResult ): uploadFile { const uploadFile: uploadFile = { collectionID: collection.id, - encryptedKey: encryptedKey.encryptedData, - keyDecryptionNonce: encryptedKey.nonce, - ...objectKeys, + encryptedKey: fileKey.encryptedData, + keyDecryptionNonce: fileKey.nonce, + ...backupedFile, }; return uploadFile; } @@ -367,7 +325,7 @@ class UploadService { } } - private async updateMetadata(recievedFile: File) { + private async seedMetadataMap(recievedFile: File) { try { const metadataJSON: object = await new Promise( (resolve, reject) => { @@ -429,13 +387,15 @@ class UploadService { image.setAttribute('src', imageURL); await new Promise((resolve) => { image.onload = () => { - canvas.width = THUMBNAIL_WIDTH; + const thumbnailWidth = + (image.width * THUMBNAIL_HEIGHT) / image.height; + canvas.width = thumbnailWidth; canvas.height = THUMBNAIL_HEIGHT; canvas_CTX.drawImage( image, 0, 0, - THUMBNAIL_WIDTH, + thumbnailWidth, THUMBNAIL_HEIGHT ); image = undefined; @@ -447,13 +407,16 @@ class UploadService { let video = document.createElement('video'); imageURL = URL.createObjectURL(file); video.addEventListener('loadeddata', function () { - canvas.width = THUMBNAIL_WIDTH; + const thumbnailWidth = + (video.videoWidth * THUMBNAIL_HEIGHT) / + video.videoHeight; + canvas.width = thumbnailWidth; canvas.height = THUMBNAIL_HEIGHT; canvas_CTX.drawImage( video, 0, 0, - THUMBNAIL_WIDTH, + thumbnailWidth, THUMBNAIL_HEIGHT ); resolve(null); @@ -520,20 +483,23 @@ class UploadService { } } - private async getUploadURL(token: string, count: number) { + private async getUploadURL(token: string) { if (this.uploadURLs.length == 0) { - await this.fetchUploadURLs(token, count); + await this.fetchUploadURLs(token); } return this.uploadURLs.pop(); } - private async fetchUploadURLs(token: string, count: number): Promise { + private async fetchUploadURLs(token: string): Promise { try { if (!this.uploadURLFetchInProgress) { this.uploadURLFetchInProgress = HTTPService.get( `${ENDPOINT}/files/upload-urls`, { - count: Math.min(50, count).toString(), //m4gic number + count: Math.min( + 50, + (this.filesToBeUploaded.length + 1) * 2 + ).toString(), }, { 'X-Auth-Token': token } ); @@ -550,7 +516,7 @@ class UploadService { } private async putFile( - fileUploadURL: uploadURL, + fileUploadURL: UploadURL, file: Uint8Array | string ): Promise { try { @@ -586,10 +552,10 @@ class UploadService { } } private getUNIXTime(exifData: any) { - if (!exifData.DateTimeOriginal) { + let dateString: string = exifData.DateTimeOriginal || exifData.DateTime; + if (!dateString) { return null; } - let dateString: string = exifData.DateTimeOriginal; var parts = dateString.split(' ')[0].split(':'); var date = new Date( Number(parts[0]), @@ -603,10 +569,28 @@ class UploadService { if (!exifData.GPSLatitude) { return null; } - var latDegree = exifData.GPSLatitude[0].numerator; - var latMinute = exifData.GPSLatitude[1].numerator; - var latSecond = exifData.GPSLatitude[2].numerator; + + let latDegree: number, latMinute: number, latSecond: number; + let lonDegree: number, lonMinute: number, lonSecond: number; + if (exifData.GPSLatitude[0].numerator) { + latDegree = exifData.GPSLatitude[0].numerator; + latMinute = exifData.GPSLatitude[1].numerator; + latSecond = exifData.GPSLatitude[2].numerator; + + lonDegree = exifData.GPSLongitude[0].numerator; + lonMinute = exifData.GPSLongitude[1].numerator; + lonSecond = exifData.GPSLongitude[2].numerator; + } else { + latDegree = exifData.GPSLatitude[0]; + latMinute = exifData.GPSLatitude[1]; + latSecond = exifData.GPSLatitude[2]; + + lonDegree = exifData.GPSLongitude[0]; + lonMinute = exifData.GPSLongitude[1]; + lonSecond = exifData.GPSLongitude[2]; + } var latDirection = exifData.GPSLatitudeRef; + var lonDirection = exifData.GPSLongitudeRef; var latFinal = this.convertDMSToDD( latDegree, @@ -615,19 +599,12 @@ class UploadService { latDirection ); - // Calculate longitude decimal - var lonDegree = exifData.GPSLongitude[0].numerator; - var lonMinute = exifData.GPSLongitude[1].numerator; - var lonSecond = exifData.GPSLongitude[2].numerator; - var lonDirection = exifData.GPSLongitudeRef; - var lonFinal = this.convertDMSToDD( lonDegree, lonMinute, lonSecond, lonDirection ); - return { latitude: latFinal * 1.0, longitude: lonFinal * 1.0 }; } diff --git a/src/utils/crypto/libsodium.ts b/src/utils/crypto/libsodium.ts index ae875815c..87cd5e055 100644 --- a/src/utils/crypto/libsodium.ts +++ b/src/utils/crypto/libsodium.ts @@ -77,8 +77,6 @@ export async function encryptChaChaOneShot(data: Uint8Array, key?: string) { file: { encryptedData: pushResult, decryptionHeader: await toB64(header), - creationTime: Date.now(), - fileType: 0, }, }; } @@ -123,8 +121,6 @@ export async function encryptChaCha(data: Uint8Array, key?: string) { file: { encryptedData: new Uint8Array(encryptedData), decryptionHeader: await toB64(header), - creationTime: Date.now(), - fileType: 0, }, }; }