2021-05-30 16:56:48 +00:00
|
|
|
import { getEndpoint } from 'utils/common/apiUtil';
|
|
|
|
import { getData, LS_KEYS } from 'utils/storage/localStorage';
|
2021-03-12 06:58:27 +00:00
|
|
|
import localForage from 'utils/storage/localForage';
|
2021-01-14 11:31:20 +00:00
|
|
|
|
2021-05-30 16:56:48 +00:00
|
|
|
import { getActualKey, getToken } from 'utils/common/key';
|
2021-04-03 04:36:15 +00:00
|
|
|
import CryptoWorker from 'utils/crypto';
|
2021-05-30 16:56:48 +00:00
|
|
|
import { SetDialogMessage } from 'components/MessageDialog';
|
2021-05-07 10:08:14 +00:00
|
|
|
import constants from 'utils/strings/constants';
|
2021-05-30 16:56:48 +00:00
|
|
|
import { getPublicKey, User } from './userService';
|
2021-08-13 03:19:48 +00:00
|
|
|
import { B64EncryptionResult } from 'utils/crypto';
|
2021-05-29 06:27:52 +00:00
|
|
|
import HTTPService from './HTTPService';
|
2021-05-30 16:56:48 +00:00
|
|
|
import { File } from './fileService';
|
2021-06-12 17:14:21 +00:00
|
|
|
import { logError } from 'utils/sentry';
|
2021-09-10 04:04:38 +00:00
|
|
|
import { CustomError } from 'utils/common/errorUtil';
|
2021-09-28 13:51:54 +00:00
|
|
|
import { sortFiles } from 'utils/file';
|
2021-01-13 05:31:02 +00:00
|
|
|
|
|
|
|
const ENDPOINT = getEndpoint();
|
|
|
|
|
2021-04-24 07:09:37 +00:00
|
|
|
export enum CollectionType {
|
2021-02-08 17:02:28 +00:00
|
|
|
folder = 'folder',
|
|
|
|
favorites = 'favorites',
|
|
|
|
album = 'album',
|
2021-01-13 12:43:46 +00:00
|
|
|
}
|
|
|
|
|
2021-02-08 17:02:28 +00:00
|
|
|
const COLLECTION_UPDATION_TIME = 'collection-updation-time';
|
|
|
|
const COLLECTIONS = 'collections';
|
|
|
|
|
2021-04-23 07:12:56 +00:00
|
|
|
export interface Collection {
|
2021-02-08 07:33:46 +00:00
|
|
|
id: number;
|
2021-04-26 07:44:34 +00:00
|
|
|
owner: User;
|
2021-01-18 02:27:00 +00:00
|
|
|
key?: string;
|
2021-01-21 02:24:17 +00:00
|
|
|
name?: string;
|
|
|
|
encryptedName?: string;
|
|
|
|
nameDecryptionNonce?: string;
|
2021-03-24 10:44:08 +00:00
|
|
|
type: CollectionType;
|
2021-02-08 17:02:28 +00:00
|
|
|
attributes: collectionAttributes;
|
2021-04-26 07:44:34 +00:00
|
|
|
sharees: User[];
|
2021-01-13 12:43:46 +00:00
|
|
|
updationTime: number;
|
|
|
|
encryptedKey: string;
|
|
|
|
keyDecryptionNonce: string;
|
|
|
|
isDeleted: boolean;
|
2021-09-20 13:46:08 +00:00
|
|
|
isSharedCollection?: boolean;
|
2021-01-13 12:43:46 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 06:21:31 +00:00
|
|
|
interface EncryptedFileKey {
|
|
|
|
id: number;
|
|
|
|
encryptedKey: string;
|
|
|
|
keyDecryptionNonce: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface AddToCollectionRequest {
|
|
|
|
collectionID: number;
|
|
|
|
files: EncryptedFileKey[];
|
|
|
|
}
|
|
|
|
|
|
|
|
interface MoveToCollectionRequest {
|
|
|
|
fromCollectionID: number;
|
|
|
|
toCollectionID: number;
|
|
|
|
files: EncryptedFileKey[];
|
|
|
|
}
|
|
|
|
|
2021-01-13 12:43:46 +00:00
|
|
|
interface collectionAttributes {
|
|
|
|
encryptedPath?: string;
|
2021-02-08 17:02:28 +00:00
|
|
|
pathDecryptionNonce?: string;
|
|
|
|
}
|
2021-01-13 12:43:46 +00:00
|
|
|
|
2021-02-09 06:04:19 +00:00
|
|
|
export interface CollectionAndItsLatestFile {
|
2021-04-23 07:12:56 +00:00
|
|
|
collection: Collection;
|
2021-04-27 09:17:38 +00:00
|
|
|
file: File;
|
2021-01-14 11:31:20 +00:00
|
|
|
}
|
|
|
|
|
2021-09-27 06:21:26 +00:00
|
|
|
export enum COLLECTION_SORT_BY {
|
|
|
|
LATEST_FILE,
|
|
|
|
MODIFICATION_TIME,
|
|
|
|
NAME,
|
|
|
|
}
|
|
|
|
|
2021-09-28 08:25:15 +00:00
|
|
|
interface RemoveFromCollectionRequest {
|
|
|
|
collectionID: number;
|
|
|
|
fileIDs: number[];
|
|
|
|
}
|
|
|
|
|
2021-04-28 07:39:45 +00:00
|
|
|
const getCollectionWithSecrets = async (
|
2021-04-23 07:12:56 +00:00
|
|
|
collection: Collection,
|
2021-08-13 02:38:38 +00:00
|
|
|
masterKey: string
|
2021-02-08 17:02:28 +00:00
|
|
|
) => {
|
2021-01-13 05:31:02 +00:00
|
|
|
const worker = await new CryptoWorker();
|
|
|
|
const userID = getData(LS_KEYS.USER).id;
|
2021-01-18 02:27:00 +00:00
|
|
|
let decryptedKey: string;
|
2021-05-29 06:27:52 +00:00
|
|
|
if (collection.owner.id === userID) {
|
2021-01-18 02:27:00 +00:00
|
|
|
decryptedKey = await worker.decryptB64(
|
|
|
|
collection.encryptedKey,
|
|
|
|
collection.keyDecryptionNonce,
|
2021-08-13 02:38:38 +00:00
|
|
|
masterKey
|
2021-01-13 05:31:02 +00:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
const keyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
|
2021-01-18 02:27:00 +00:00
|
|
|
const secretKey = await worker.decryptB64(
|
|
|
|
keyAttributes.encryptedSecretKey,
|
|
|
|
keyAttributes.secretKeyDecryptionNonce,
|
2021-08-13 02:38:38 +00:00
|
|
|
masterKey
|
2021-01-13 05:31:02 +00:00
|
|
|
);
|
|
|
|
decryptedKey = await worker.boxSealOpen(
|
2021-01-21 02:24:17 +00:00
|
|
|
collection.encryptedKey,
|
|
|
|
keyAttributes.publicKey,
|
2021-08-13 02:38:38 +00:00
|
|
|
secretKey
|
2021-01-13 05:31:02 +00:00
|
|
|
);
|
|
|
|
}
|
2021-08-13 02:38:38 +00:00
|
|
|
collection.name =
|
|
|
|
collection.name ||
|
2021-02-16 11:43:21 +00:00
|
|
|
(await worker.decryptToUTF8(
|
2021-02-08 17:02:28 +00:00
|
|
|
collection.encryptedName,
|
|
|
|
collection.nameDecryptionNonce,
|
2021-08-13 02:38:38 +00:00
|
|
|
decryptedKey
|
2021-02-08 17:02:28 +00:00
|
|
|
));
|
2021-01-13 05:31:02 +00:00
|
|
|
return {
|
|
|
|
...collection,
|
|
|
|
key: decryptedKey,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const getCollections = async (
|
|
|
|
token: string,
|
2021-02-17 09:47:14 +00:00
|
|
|
sinceTime: number,
|
2021-08-13 02:38:38 +00:00
|
|
|
key: string
|
2021-04-23 07:12:56 +00:00
|
|
|
): Promise<Collection[]> => {
|
2021-01-25 07:05:45 +00:00
|
|
|
try {
|
2021-02-08 17:02:28 +00:00
|
|
|
const resp = await HTTPService.get(
|
|
|
|
`${ENDPOINT}/collections`,
|
|
|
|
{
|
2021-05-29 06:27:52 +00:00
|
|
|
sinceTime,
|
2021-02-08 17:02:28 +00:00
|
|
|
},
|
2021-08-13 02:38:38 +00:00
|
|
|
{ 'X-Auth-Token': token }
|
2021-02-08 17:02:28 +00:00
|
|
|
);
|
2021-04-23 07:12:56 +00:00
|
|
|
const promises: Promise<Collection>[] = resp.data.collections.map(
|
2021-04-28 07:39:45 +00:00
|
|
|
async (collection: Collection) => {
|
2021-04-28 08:51:26 +00:00
|
|
|
if (collection.isDeleted) {
|
|
|
|
return collection;
|
|
|
|
}
|
|
|
|
let collectionWithSecrets = collection;
|
2021-04-28 07:39:45 +00:00
|
|
|
try {
|
|
|
|
collectionWithSecrets = await getCollectionWithSecrets(
|
|
|
|
collection,
|
2021-08-13 02:38:38 +00:00
|
|
|
key
|
2021-04-28 07:39:45 +00:00
|
|
|
);
|
|
|
|
} catch (e) {
|
2021-09-01 12:34:18 +00:00
|
|
|
logError(e, `decryption failed for collection`, {
|
|
|
|
collectionID: collection.id,
|
|
|
|
});
|
2021-04-28 07:39:45 +00:00
|
|
|
}
|
2021-08-31 11:23:43 +00:00
|
|
|
return collectionWithSecrets;
|
2021-08-13 02:38:38 +00:00
|
|
|
}
|
2021-04-28 07:39:45 +00:00
|
|
|
);
|
2021-08-31 11:23:43 +00:00
|
|
|
// only allow deleted or collection with key, filtering out collection whose decryption failed
|
|
|
|
const collections = (await Promise.all(promises)).filter(
|
|
|
|
(collection) => collection.isDeleted || collection.key
|
|
|
|
);
|
|
|
|
return collections;
|
2021-02-08 17:02:28 +00:00
|
|
|
} catch (e) {
|
2021-06-12 17:14:21 +00:00
|
|
|
logError(e, 'getCollections failed');
|
2021-05-24 13:11:08 +00:00
|
|
|
throw e;
|
2021-01-25 07:05:45 +00:00
|
|
|
}
|
2021-01-13 05:31:02 +00:00
|
|
|
};
|
|
|
|
|
2021-04-23 07:12:56 +00:00
|
|
|
export const getLocalCollections = async (): Promise<Collection[]> => {
|
2021-08-13 02:38:38 +00:00
|
|
|
const collections: Collection[] =
|
|
|
|
(await localForage.getItem(COLLECTIONS)) ?? [];
|
2021-01-31 12:10:45 +00:00
|
|
|
return collections;
|
2021-02-08 17:02:28 +00:00
|
|
|
};
|
|
|
|
|
2021-08-13 02:38:38 +00:00
|
|
|
export const getCollectionUpdationTime = async (): Promise<number> =>
|
|
|
|
(await localForage.getItem<number>(COLLECTION_UPDATION_TIME)) ?? 0;
|
2021-02-17 09:47:14 +00:00
|
|
|
|
2021-03-16 06:49:34 +00:00
|
|
|
export const syncCollections = async () => {
|
2021-01-31 12:10:45 +00:00
|
|
|
const localCollections = await getLocalCollections();
|
2021-02-17 09:47:14 +00:00
|
|
|
const lastCollectionUpdationTime = await getCollectionUpdationTime();
|
2021-04-29 08:58:34 +00:00
|
|
|
const token = getToken();
|
2021-05-29 06:27:52 +00:00
|
|
|
const key = await getActualKey();
|
2021-08-13 02:38:38 +00:00
|
|
|
const updatedCollections =
|
|
|
|
(await getCollections(token, lastCollectionUpdationTime, key)) ?? [];
|
2021-05-29 06:27:52 +00:00
|
|
|
if (updatedCollections.length === 0) {
|
2021-02-08 16:10:11 +00:00
|
|
|
return localCollections;
|
2021-02-08 10:37:59 +00:00
|
|
|
}
|
2021-02-08 17:02:28 +00:00
|
|
|
const allCollectionsInstances = [
|
|
|
|
...localCollections,
|
|
|
|
...updatedCollections,
|
|
|
|
];
|
2021-05-29 06:27:52 +00:00
|
|
|
const latestCollectionsInstances = new Map<number, Collection>();
|
2021-02-03 08:02:54 +00:00
|
|
|
allCollectionsInstances.forEach((collection) => {
|
2021-02-08 17:02:28 +00:00
|
|
|
if (
|
|
|
|
!latestCollectionsInstances.has(collection.id) ||
|
|
|
|
latestCollectionsInstances.get(collection.id).updationTime <
|
2021-08-13 02:38:38 +00:00
|
|
|
collection.updationTime
|
2021-02-08 17:02:28 +00:00
|
|
|
) {
|
2021-02-03 08:02:54 +00:00
|
|
|
latestCollectionsInstances.set(collection.id, collection);
|
|
|
|
}
|
|
|
|
});
|
2021-02-08 10:37:59 +00:00
|
|
|
|
2021-09-30 07:32:58 +00:00
|
|
|
let collections: Collection[] = [];
|
2021-05-29 06:27:52 +00:00
|
|
|
let updationTime = await localForage.getItem<number>(
|
2021-08-13 02:38:38 +00:00
|
|
|
COLLECTION_UPDATION_TIME
|
2021-05-29 06:27:52 +00:00
|
|
|
);
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
2021-02-03 08:02:54 +00:00
|
|
|
for (const [_, collection] of latestCollectionsInstances) {
|
2021-02-08 17:02:28 +00:00
|
|
|
if (!collection.isDeleted) {
|
2021-02-06 07:51:49 +00:00
|
|
|
collections.push(collection);
|
2021-02-08 17:02:28 +00:00
|
|
|
updationTime = Math.max(updationTime, collection.updationTime);
|
2021-02-08 07:33:46 +00:00
|
|
|
}
|
2021-02-03 08:02:54 +00:00
|
|
|
}
|
2021-09-30 07:32:58 +00:00
|
|
|
collections = sortCollections(
|
|
|
|
collections,
|
|
|
|
[],
|
|
|
|
COLLECTION_SORT_BY.MODIFICATION_TIME
|
|
|
|
);
|
2021-02-08 17:02:28 +00:00
|
|
|
await localForage.setItem(COLLECTIONS, collections);
|
2021-09-22 13:35:53 +00:00
|
|
|
await localForage.setItem(COLLECTION_UPDATION_TIME, updationTime);
|
2021-02-08 16:10:11 +00:00
|
|
|
return collections;
|
2021-01-13 05:31:02 +00:00
|
|
|
};
|
2021-01-13 12:43:46 +00:00
|
|
|
|
2021-10-01 06:45:44 +00:00
|
|
|
export const getCollection = async (
|
|
|
|
collectionID: number
|
|
|
|
): Promise<Collection> => {
|
|
|
|
try {
|
|
|
|
const token = getToken();
|
|
|
|
if (!token) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const resp = await HTTPService.get(
|
|
|
|
`${ENDPOINT}/collections/${collectionID}`,
|
|
|
|
null,
|
|
|
|
{ 'X-Auth-Token': token }
|
|
|
|
);
|
2021-10-04 08:15:32 +00:00
|
|
|
const key = await getActualKey();
|
|
|
|
const collectionWithSecrets = await getCollectionWithSecrets(
|
|
|
|
resp.data?.collection,
|
|
|
|
key
|
|
|
|
);
|
|
|
|
return collectionWithSecrets;
|
2021-10-01 06:45:44 +00:00
|
|
|
} catch (e) {
|
|
|
|
logError(e, 'failed to get collection', { collectionID });
|
|
|
|
}
|
2021-09-10 03:41:38 +00:00
|
|
|
};
|
|
|
|
|
2021-04-27 07:37:42 +00:00
|
|
|
export const getCollectionsAndTheirLatestFile = (
|
2021-04-23 07:12:56 +00:00
|
|
|
collections: Collection[],
|
2021-08-13 02:38:38 +00:00
|
|
|
files: File[]
|
2021-02-09 06:04:19 +00:00
|
|
|
): CollectionAndItsLatestFile[] => {
|
2021-04-27 09:17:38 +00:00
|
|
|
const latestFile = new Map<number, File>();
|
2021-02-05 16:57:41 +00:00
|
|
|
|
2021-02-08 17:02:28 +00:00
|
|
|
files.forEach((file) => {
|
2021-02-05 16:57:41 +00:00
|
|
|
if (!latestFile.has(file.collectionID)) {
|
2021-02-08 17:02:28 +00:00
|
|
|
latestFile.set(file.collectionID, file);
|
2021-02-05 16:57:41 +00:00
|
|
|
}
|
|
|
|
});
|
2021-05-29 06:27:52 +00:00
|
|
|
const collectionsAndTheirLatestFile: CollectionAndItsLatestFile[] = [];
|
2021-02-16 11:52:18 +00:00
|
|
|
|
2021-02-16 12:09:43 +00:00
|
|
|
for (const collection of collections) {
|
2021-04-27 07:37:42 +00:00
|
|
|
collectionsAndTheirLatestFile.push({
|
2021-02-16 11:52:18 +00:00
|
|
|
collection,
|
|
|
|
file: latestFile.get(collection.id),
|
2021-02-08 17:02:28 +00:00
|
|
|
});
|
2021-02-05 16:57:41 +00:00
|
|
|
}
|
2021-04-27 07:37:42 +00:00
|
|
|
return collectionsAndTheirLatestFile;
|
2021-02-08 17:02:28 +00:00
|
|
|
};
|
2021-01-14 11:31:20 +00:00
|
|
|
|
2021-04-27 09:17:38 +00:00
|
|
|
export const getFavItemIds = async (files: File[]): Promise<Set<number>> => {
|
2021-05-29 06:27:52 +00:00
|
|
|
const favCollection = await getFavCollection();
|
2021-02-08 17:02:28 +00:00
|
|
|
if (!favCollection) return new Set();
|
2021-01-20 12:05:04 +00:00
|
|
|
|
2021-02-08 17:02:28 +00:00
|
|
|
return new Set(
|
|
|
|
files
|
2021-02-09 05:07:46 +00:00
|
|
|
.filter((file) => file.collectionID === favCollection.id)
|
2021-08-13 02:38:38 +00:00
|
|
|
.map((file): number => file.id)
|
2021-02-08 17:02:28 +00:00
|
|
|
);
|
|
|
|
};
|
2021-01-20 12:05:04 +00:00
|
|
|
|
2021-08-17 09:08:54 +00:00
|
|
|
export const createAlbum = async (
|
|
|
|
albumName: string,
|
|
|
|
existingCollection?: Collection[]
|
|
|
|
) => createCollection(albumName, CollectionType.album, existingCollection);
|
2021-01-15 11:11:06 +00:00
|
|
|
|
2021-04-11 08:40:05 +00:00
|
|
|
export const createCollection = async (
|
2021-02-08 17:02:28 +00:00
|
|
|
collectionName: string,
|
2021-08-17 09:08:54 +00:00
|
|
|
type: CollectionType,
|
|
|
|
existingCollections?: Collection[]
|
2021-04-23 07:12:56 +00:00
|
|
|
): Promise<Collection> => {
|
2021-02-16 13:15:02 +00:00
|
|
|
try {
|
2021-08-17 09:08:54 +00:00
|
|
|
if (!existingCollections) {
|
|
|
|
existingCollections = await syncCollections();
|
|
|
|
}
|
2021-05-29 06:27:52 +00:00
|
|
|
for (const collection of existingCollections) {
|
2021-04-11 08:40:05 +00:00
|
|
|
if (collection.name === collectionName) {
|
|
|
|
return collection;
|
|
|
|
}
|
|
|
|
}
|
2021-02-16 13:15:02 +00:00
|
|
|
const worker = await new CryptoWorker();
|
|
|
|
const encryptionKey = await getActualKey();
|
|
|
|
const token = getToken();
|
2021-04-06 04:16:56 +00:00
|
|
|
const collectionKey: string = await worker.generateEncryptionKey();
|
2021-02-16 13:15:02 +00:00
|
|
|
const {
|
|
|
|
encryptedData: encryptedKey,
|
|
|
|
nonce: keyDecryptionNonce,
|
|
|
|
}: B64EncryptionResult = await worker.encryptToB64(
|
|
|
|
collectionKey,
|
2021-08-13 02:38:38 +00:00
|
|
|
encryptionKey
|
2021-02-16 13:15:02 +00:00
|
|
|
);
|
|
|
|
const {
|
|
|
|
encryptedData: encryptedName,
|
|
|
|
nonce: nameDecryptionNonce,
|
2021-02-16 13:16:24 +00:00
|
|
|
}: B64EncryptionResult = await worker.encryptUTF8(
|
2021-02-16 13:15:02 +00:00
|
|
|
collectionName,
|
2021-08-13 02:38:38 +00:00
|
|
|
collectionKey
|
2021-02-16 13:15:02 +00:00
|
|
|
);
|
2021-04-23 07:12:56 +00:00
|
|
|
const newCollection: Collection = {
|
2021-02-16 13:15:02 +00:00
|
|
|
id: null,
|
|
|
|
owner: null,
|
|
|
|
encryptedKey,
|
|
|
|
keyDecryptionNonce,
|
|
|
|
encryptedName,
|
|
|
|
nameDecryptionNonce,
|
|
|
|
type,
|
|
|
|
attributes: {},
|
|
|
|
sharees: null,
|
|
|
|
updationTime: null,
|
|
|
|
isDeleted: false,
|
|
|
|
};
|
2021-04-23 07:12:56 +00:00
|
|
|
let createdCollection: Collection = await postCollection(
|
2021-02-16 13:15:02 +00:00
|
|
|
newCollection,
|
2021-08-13 02:38:38 +00:00
|
|
|
token
|
2021-02-16 13:15:02 +00:00
|
|
|
);
|
2021-04-28 07:39:45 +00:00
|
|
|
createdCollection = await getCollectionWithSecrets(
|
2021-02-16 13:15:02 +00:00
|
|
|
createdCollection,
|
2021-08-13 02:38:38 +00:00
|
|
|
encryptionKey
|
2021-02-16 13:15:02 +00:00
|
|
|
);
|
|
|
|
return createdCollection;
|
|
|
|
} catch (e) {
|
2021-06-12 17:14:21 +00:00
|
|
|
logError(e, 'create collection failed');
|
2021-05-07 03:31:42 +00:00
|
|
|
throw e;
|
2021-02-16 13:15:02 +00:00
|
|
|
}
|
2021-02-08 17:02:28 +00:00
|
|
|
};
|
2021-01-13 12:43:46 +00:00
|
|
|
|
2021-04-11 08:40:05 +00:00
|
|
|
const postCollection = async (
|
2021-04-23 07:12:56 +00:00
|
|
|
collectionData: Collection,
|
2021-08-13 02:38:38 +00:00
|
|
|
token: string
|
2021-04-23 07:12:56 +00:00
|
|
|
): Promise<Collection> => {
|
2021-01-25 07:05:45 +00:00
|
|
|
try {
|
2021-02-08 17:02:28 +00:00
|
|
|
const response = await HTTPService.post(
|
|
|
|
`${ENDPOINT}/collections`,
|
|
|
|
collectionData,
|
|
|
|
null,
|
2021-08-13 02:38:38 +00:00
|
|
|
{ 'X-Auth-Token': token }
|
2021-02-08 17:02:28 +00:00
|
|
|
);
|
2021-01-25 07:05:45 +00:00
|
|
|
return response.data.collection;
|
|
|
|
} catch (e) {
|
2021-06-12 17:14:21 +00:00
|
|
|
logError(e, 'post Collection failed ');
|
2021-01-25 07:05:45 +00:00
|
|
|
}
|
2021-02-08 17:02:28 +00:00
|
|
|
};
|
2021-01-15 11:11:06 +00:00
|
|
|
|
2021-04-27 09:17:38 +00:00
|
|
|
export const addToFavorites = async (file: File) => {
|
2021-09-10 04:04:38 +00:00
|
|
|
try {
|
|
|
|
let favCollection = await getFavCollection();
|
|
|
|
if (!favCollection) {
|
|
|
|
favCollection = await createCollection(
|
|
|
|
'Favorites',
|
|
|
|
CollectionType.favorites
|
|
|
|
);
|
2021-10-09 09:49:33 +00:00
|
|
|
const localCollections = await getLocalCollections();
|
|
|
|
await localForage.setItem(COLLECTIONS, [
|
|
|
|
...localCollections,
|
|
|
|
favCollection,
|
|
|
|
]);
|
2021-09-10 04:04:38 +00:00
|
|
|
}
|
|
|
|
await addToCollection(favCollection, [file]);
|
|
|
|
} catch (e) {
|
|
|
|
logError(e, 'failed to add to favorite');
|
2021-01-15 11:11:06 +00:00
|
|
|
}
|
2021-02-08 17:02:28 +00:00
|
|
|
};
|
2021-01-15 11:11:06 +00:00
|
|
|
|
2021-04-27 09:17:38 +00:00
|
|
|
export const removeFromFavorites = async (file: File) => {
|
2021-09-10 04:04:38 +00:00
|
|
|
try {
|
|
|
|
const favCollection = await getFavCollection();
|
|
|
|
if (!favCollection) {
|
|
|
|
throw Error(CustomError.FAV_COLLECTION_MISSING);
|
|
|
|
}
|
|
|
|
await removeFromCollection(favCollection, [file]);
|
|
|
|
} catch (e) {
|
|
|
|
logError(e, 'remove from favorite failed');
|
|
|
|
}
|
2021-02-08 17:02:28 +00:00
|
|
|
};
|
2021-01-20 13:04:27 +00:00
|
|
|
|
2021-04-27 11:59:10 +00:00
|
|
|
export const addToCollection = async (
|
|
|
|
collection: Collection,
|
2021-08-13 02:38:38 +00:00
|
|
|
files: File[]
|
2021-04-27 11:59:10 +00:00
|
|
|
) => {
|
2021-01-25 07:05:45 +00:00
|
|
|
try {
|
|
|
|
const token = getToken();
|
2021-09-20 06:21:31 +00:00
|
|
|
const fileKeysEncryptedWithNewCollection =
|
|
|
|
await encryptWithNewCollectionKey(collection, files);
|
|
|
|
|
|
|
|
const requestBody: AddToCollectionRequest = {
|
|
|
|
collectionID: collection.id,
|
|
|
|
files: fileKeysEncryptedWithNewCollection,
|
|
|
|
};
|
2021-02-08 17:02:28 +00:00
|
|
|
await HTTPService.post(
|
|
|
|
`${ENDPOINT}/collections/add-files`,
|
2021-09-20 06:21:31 +00:00
|
|
|
requestBody,
|
2021-02-08 17:02:28 +00:00
|
|
|
null,
|
2021-09-20 06:21:31 +00:00
|
|
|
{
|
|
|
|
'X-Auth-Token': token,
|
|
|
|
}
|
2021-02-08 17:02:28 +00:00
|
|
|
);
|
2021-01-25 07:05:45 +00:00
|
|
|
} catch (e) {
|
2021-06-12 17:14:21 +00:00
|
|
|
logError(e, 'Add to collection Failed ');
|
2021-09-10 04:04:38 +00:00
|
|
|
throw e;
|
2021-01-25 07:05:45 +00:00
|
|
|
}
|
2021-02-08 17:02:28 +00:00
|
|
|
};
|
2021-10-04 07:14:45 +00:00
|
|
|
|
|
|
|
export const restoreToCollection = async (
|
|
|
|
collection: Collection,
|
|
|
|
files: File[]
|
|
|
|
) => {
|
|
|
|
try {
|
|
|
|
const token = getToken();
|
|
|
|
const fileKeysEncryptedWithNewCollection =
|
|
|
|
await encryptWithNewCollectionKey(collection, files);
|
|
|
|
|
|
|
|
const requestBody: AddToCollectionRequest = {
|
|
|
|
collectionID: collection.id,
|
|
|
|
files: fileKeysEncryptedWithNewCollection,
|
|
|
|
};
|
|
|
|
await HTTPService.post(
|
|
|
|
`${ENDPOINT}/collections/restore-files`,
|
|
|
|
requestBody,
|
|
|
|
null,
|
|
|
|
{
|
|
|
|
'X-Auth-Token': token,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
} catch (e) {
|
|
|
|
logError(e, 'restore to collection Failed ');
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
};
|
2021-09-20 06:21:31 +00:00
|
|
|
export const moveToCollection = async (
|
2021-09-21 12:22:31 +00:00
|
|
|
fromCollectionID: number,
|
|
|
|
toCollection: Collection,
|
2021-09-20 06:21:31 +00:00
|
|
|
files: File[]
|
|
|
|
) => {
|
2021-09-20 12:27:24 +00:00
|
|
|
try {
|
|
|
|
const token = getToken();
|
|
|
|
const fileKeysEncryptedWithNewCollection =
|
2021-09-21 12:22:31 +00:00
|
|
|
await encryptWithNewCollectionKey(toCollection, files);
|
2021-09-20 06:21:31 +00:00
|
|
|
|
2021-09-20 12:27:24 +00:00
|
|
|
const requestBody: MoveToCollectionRequest = {
|
2021-09-21 12:22:31 +00:00
|
|
|
fromCollectionID: fromCollectionID,
|
|
|
|
toCollectionID: toCollection.id,
|
2021-09-20 12:27:24 +00:00
|
|
|
files: fileKeysEncryptedWithNewCollection,
|
|
|
|
};
|
|
|
|
await HTTPService.post(
|
|
|
|
`${ENDPOINT}/collections/move-files`,
|
|
|
|
requestBody,
|
|
|
|
null,
|
|
|
|
{
|
|
|
|
'X-Auth-Token': token,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
} catch (e) {
|
|
|
|
logError(e, 'move to collection Failed ');
|
|
|
|
throw e;
|
|
|
|
}
|
2021-09-20 06:21:31 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
const encryptWithNewCollectionKey = async (
|
|
|
|
newCollection: Collection,
|
|
|
|
files: File[]
|
|
|
|
): Promise<EncryptedFileKey[]> => {
|
|
|
|
const fileKeysEncryptedWithNewCollection: EncryptedFileKey[] = [];
|
|
|
|
const worker = await new CryptoWorker();
|
|
|
|
for (const file of files) {
|
|
|
|
const newEncryptedKey: B64EncryptionResult = await worker.encryptToB64(
|
|
|
|
file.key,
|
|
|
|
newCollection.key
|
|
|
|
);
|
|
|
|
file.encryptedKey = newEncryptedKey.encryptedData;
|
|
|
|
file.keyDecryptionNonce = newEncryptedKey.nonce;
|
|
|
|
|
|
|
|
fileKeysEncryptedWithNewCollection.push({
|
|
|
|
id: file.id,
|
|
|
|
encryptedKey: file.encryptedKey,
|
|
|
|
keyDecryptionNonce: file.keyDecryptionNonce,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return fileKeysEncryptedWithNewCollection;
|
|
|
|
};
|
2021-09-28 08:25:15 +00:00
|
|
|
export const removeFromCollection = async (
|
|
|
|
collection: Collection,
|
|
|
|
files: File[]
|
|
|
|
) => {
|
2021-01-25 07:05:45 +00:00
|
|
|
try {
|
|
|
|
const token = getToken();
|
2021-09-28 08:25:15 +00:00
|
|
|
const request: RemoveFromCollectionRequest = {
|
|
|
|
collectionID: collection.id,
|
|
|
|
fileIDs: files.map((file) => file.id),
|
|
|
|
};
|
|
|
|
|
2021-02-08 17:02:28 +00:00
|
|
|
await HTTPService.post(
|
2021-09-28 08:25:15 +00:00
|
|
|
`${ENDPOINT}/collections/v2/remove-files`,
|
|
|
|
request,
|
2021-02-08 17:02:28 +00:00
|
|
|
null,
|
2021-08-13 02:38:38 +00:00
|
|
|
{ 'X-Auth-Token': token }
|
2021-02-08 17:02:28 +00:00
|
|
|
);
|
2021-01-25 07:05:45 +00:00
|
|
|
} catch (e) {
|
2021-06-12 17:14:21 +00:00
|
|
|
logError(e, 'remove from collection failed ');
|
2021-09-10 04:04:38 +00:00
|
|
|
throw e;
|
2021-01-25 07:05:45 +00:00
|
|
|
}
|
2021-02-08 17:02:28 +00:00
|
|
|
};
|
2021-01-20 13:04:27 +00:00
|
|
|
|
2021-04-24 07:09:37 +00:00
|
|
|
export const deleteCollection = async (
|
|
|
|
collectionID: number,
|
2021-04-28 09:56:57 +00:00
|
|
|
syncWithRemote: () => Promise<void>,
|
2021-05-07 10:08:14 +00:00
|
|
|
redirectToAll: () => void,
|
2021-08-13 02:38:38 +00:00
|
|
|
setDialogMessage: SetDialogMessage
|
2021-04-24 07:09:37 +00:00
|
|
|
) => {
|
|
|
|
try {
|
|
|
|
const token = getToken();
|
|
|
|
|
|
|
|
await HTTPService.delete(
|
2021-09-28 07:17:16 +00:00
|
|
|
`${ENDPOINT}/collections/v2/${collectionID}`,
|
2021-04-24 07:09:37 +00:00
|
|
|
null,
|
|
|
|
null,
|
2021-08-13 02:38:38 +00:00
|
|
|
{ 'X-Auth-Token': token }
|
2021-04-24 07:09:37 +00:00
|
|
|
);
|
2021-04-28 09:56:57 +00:00
|
|
|
await syncWithRemote();
|
|
|
|
redirectToAll();
|
2021-04-24 07:09:37 +00:00
|
|
|
} catch (e) {
|
2021-06-12 17:14:21 +00:00
|
|
|
logError(e, 'delete collection failed ');
|
2021-05-07 10:08:14 +00:00
|
|
|
setDialogMessage({
|
|
|
|
title: constants.ERROR,
|
|
|
|
content: constants.DELETE_COLLECTION_FAILED,
|
2021-05-30 16:56:48 +00:00
|
|
|
close: { variant: 'danger' },
|
2021-05-07 10:08:14 +00:00
|
|
|
});
|
2021-02-09 05:07:46 +00:00
|
|
|
}
|
2021-04-24 07:09:37 +00:00
|
|
|
};
|
|
|
|
|
2021-04-24 10:03:52 +00:00
|
|
|
export const renameCollection = async (
|
|
|
|
collection: Collection,
|
2021-08-13 02:38:38 +00:00
|
|
|
newCollectionName: string
|
2021-04-24 10:03:52 +00:00
|
|
|
) => {
|
|
|
|
const token = getToken();
|
|
|
|
const worker = await new CryptoWorker();
|
|
|
|
const {
|
|
|
|
encryptedData: encryptedName,
|
|
|
|
nonce: nameDecryptionNonce,
|
|
|
|
}: B64EncryptionResult = await worker.encryptUTF8(
|
|
|
|
newCollectionName,
|
2021-08-13 02:38:38 +00:00
|
|
|
collection.key
|
2021-04-24 10:03:52 +00:00
|
|
|
);
|
|
|
|
const collectionRenameRequest = {
|
|
|
|
collectionID: collection.id,
|
|
|
|
encryptedName,
|
|
|
|
nameDecryptionNonce,
|
|
|
|
};
|
|
|
|
await HTTPService.post(
|
|
|
|
`${ENDPOINT}/collections/rename`,
|
|
|
|
collectionRenameRequest,
|
|
|
|
null,
|
|
|
|
{
|
|
|
|
'X-Auth-Token': token,
|
2021-08-13 02:38:38 +00:00
|
|
|
}
|
2021-04-24 10:03:52 +00:00
|
|
|
);
|
|
|
|
};
|
2021-04-26 10:27:59 +00:00
|
|
|
export const shareCollection = async (
|
|
|
|
collection: Collection,
|
2021-08-13 02:38:38 +00:00
|
|
|
withUserEmail: string
|
2021-04-26 10:27:59 +00:00
|
|
|
) => {
|
|
|
|
try {
|
2021-04-28 08:00:15 +00:00
|
|
|
const worker = await new CryptoWorker();
|
|
|
|
|
2021-04-26 10:27:59 +00:00
|
|
|
const token = getToken();
|
2021-04-28 08:00:15 +00:00
|
|
|
const publicKey: string = await getPublicKey(withUserEmail);
|
2021-04-28 09:11:25 +00:00
|
|
|
const encryptedKey: string = await worker.boxSeal(
|
2021-04-28 08:00:15 +00:00
|
|
|
collection.key,
|
2021-08-13 02:38:38 +00:00
|
|
|
publicKey
|
2021-04-28 08:00:15 +00:00
|
|
|
);
|
2021-04-26 10:27:59 +00:00
|
|
|
const shareCollectionRequest = {
|
|
|
|
collectionID: collection.id,
|
|
|
|
email: withUserEmail,
|
2021-05-29 06:27:52 +00:00
|
|
|
encryptedKey,
|
2021-04-26 10:27:59 +00:00
|
|
|
};
|
|
|
|
await HTTPService.post(
|
|
|
|
`${ENDPOINT}/collections/share`,
|
|
|
|
shareCollectionRequest,
|
|
|
|
null,
|
|
|
|
{
|
|
|
|
'X-Auth-Token': token,
|
2021-08-13 02:38:38 +00:00
|
|
|
}
|
2021-04-26 10:27:59 +00:00
|
|
|
);
|
|
|
|
} catch (e) {
|
2021-06-12 17:14:21 +00:00
|
|
|
logError(e, 'share collection failed ');
|
2021-04-26 10:27:59 +00:00
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
export const unshareCollection = async (
|
|
|
|
collection: Collection,
|
2021-08-13 02:38:38 +00:00
|
|
|
withUserEmail: string
|
2021-04-26 10:27:59 +00:00
|
|
|
) => {
|
|
|
|
try {
|
|
|
|
const token = getToken();
|
|
|
|
const shareCollectionRequest = {
|
|
|
|
collectionID: collection.id,
|
|
|
|
email: withUserEmail,
|
|
|
|
};
|
|
|
|
await HTTPService.post(
|
|
|
|
`${ENDPOINT}/collections/unshare`,
|
|
|
|
shareCollectionRequest,
|
|
|
|
null,
|
|
|
|
{
|
|
|
|
'X-Auth-Token': token,
|
2021-08-13 02:38:38 +00:00
|
|
|
}
|
2021-04-26 10:27:59 +00:00
|
|
|
);
|
|
|
|
} catch (e) {
|
2021-06-12 17:14:21 +00:00
|
|
|
logError(e, 'unshare collection failed ');
|
2021-04-26 10:27:59 +00:00
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
};
|
2021-04-24 10:03:52 +00:00
|
|
|
|
2021-04-24 07:09:37 +00:00
|
|
|
export const getFavCollection = async () => {
|
|
|
|
const collections = await getLocalCollections();
|
2021-05-29 06:27:52 +00:00
|
|
|
for (const collection of collections) {
|
|
|
|
if (collection.type === CollectionType.favorites) {
|
2021-04-24 07:09:37 +00:00
|
|
|
return collection;
|
|
|
|
}
|
2021-02-09 05:07:46 +00:00
|
|
|
}
|
2021-04-24 07:09:37 +00:00
|
|
|
return null;
|
2021-02-08 17:02:28 +00:00
|
|
|
};
|
2021-03-15 17:30:49 +00:00
|
|
|
|
|
|
|
export const getNonEmptyCollections = (
|
2021-04-23 07:12:56 +00:00
|
|
|
collections: Collection[],
|
2021-08-13 02:38:38 +00:00
|
|
|
files: File[]
|
2021-03-15 17:30:49 +00:00
|
|
|
) => {
|
|
|
|
const nonEmptyCollectionsIds = new Set<number>();
|
2021-05-29 06:27:52 +00:00
|
|
|
for (const file of files) {
|
2021-03-15 17:30:49 +00:00
|
|
|
nonEmptyCollectionsIds.add(file.collectionID);
|
|
|
|
}
|
2021-08-13 02:38:38 +00:00
|
|
|
return collections.filter((collection) =>
|
|
|
|
nonEmptyCollectionsIds.has(collection.id)
|
|
|
|
);
|
2021-03-15 17:30:49 +00:00
|
|
|
};
|
2021-09-27 06:21:26 +00:00
|
|
|
|
|
|
|
export function sortCollections(
|
|
|
|
collections: Collection[],
|
|
|
|
collectionAndTheirLatestFile: CollectionAndItsLatestFile[],
|
|
|
|
sortBy: COLLECTION_SORT_BY
|
|
|
|
) {
|
2021-09-30 07:32:58 +00:00
|
|
|
return moveFavCollectionToFront(
|
|
|
|
collections.sort((collectionA, collectionB) => {
|
|
|
|
switch (sortBy) {
|
|
|
|
case COLLECTION_SORT_BY.LATEST_FILE:
|
|
|
|
return compareCollectionsLatestFile(
|
|
|
|
collectionAndTheirLatestFile,
|
|
|
|
collectionA,
|
|
|
|
collectionB
|
|
|
|
);
|
|
|
|
case COLLECTION_SORT_BY.MODIFICATION_TIME:
|
|
|
|
return collectionB.updationTime - collectionA.updationTime;
|
|
|
|
case COLLECTION_SORT_BY.NAME:
|
|
|
|
return collectionA.name.localeCompare(collectionB.name);
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
2021-09-27 06:21:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function compareCollectionsLatestFile(
|
|
|
|
collectionAndTheirLatestFile: CollectionAndItsLatestFile[],
|
|
|
|
collectionA: Collection,
|
|
|
|
collectionB: Collection
|
|
|
|
) {
|
2021-09-30 07:32:58 +00:00
|
|
|
if (!collectionAndTheirLatestFile?.length) {
|
2021-09-30 07:06:18 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2021-09-27 06:21:26 +00:00
|
|
|
const CollectionALatestFile = getCollectionLatestFile(
|
|
|
|
collectionAndTheirLatestFile,
|
|
|
|
collectionA
|
|
|
|
);
|
|
|
|
const CollectionBLatestFile = getCollectionLatestFile(
|
|
|
|
collectionAndTheirLatestFile,
|
|
|
|
collectionB
|
|
|
|
);
|
2021-09-28 13:51:54 +00:00
|
|
|
if (!CollectionALatestFile || !CollectionBLatestFile) {
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
const sortedFiles = sortFiles([
|
|
|
|
CollectionALatestFile,
|
|
|
|
CollectionBLatestFile,
|
|
|
|
]);
|
|
|
|
if (sortedFiles[0].id !== CollectionALatestFile.id) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
2021-09-27 06:21:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function getCollectionLatestFile(
|
|
|
|
collectionAndTheirLatestFile: CollectionAndItsLatestFile[],
|
|
|
|
collection: Collection
|
|
|
|
) {
|
|
|
|
const collectionAndItsLatestFile = collectionAndTheirLatestFile.filter(
|
|
|
|
(collectionAndItsLatestFile) =>
|
|
|
|
collectionAndItsLatestFile.collection.id === collection.id
|
|
|
|
);
|
|
|
|
if (collectionAndItsLatestFile.length === 1) {
|
|
|
|
return collectionAndItsLatestFile[0].file;
|
|
|
|
}
|
|
|
|
}
|
2021-09-30 07:32:58 +00:00
|
|
|
|
|
|
|
function moveFavCollectionToFront(collections: Collection[]) {
|
|
|
|
return collections.sort((collectionA, collectionB) =>
|
|
|
|
collectionA.type === CollectionType.favorites
|
|
|
|
? -1
|
|
|
|
: collectionB.type === CollectionType.favorites
|
|
|
|
? 1
|
|
|
|
: 0
|
|
|
|
);
|
|
|
|
}
|