ente/src/services/collectionService.ts

389 lines
12 KiB
TypeScript
Raw Normal View History

import { getEndpoint } from 'utils/common/apiUtil';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
2021-02-16 11:45:06 +00:00
import { file } from './fileService';
2021-03-12 06:58:27 +00:00
import localForage from 'utils/storage/localForage';
import HTTPService from './HTTPService';
2021-02-16 11:43:21 +00:00
import { B64EncryptionResult } from './uploadService';
import { getActualKey, getToken } from 'utils/common/key';
2021-02-16 11:45:06 +00:00
import { user } from './userService';
2021-04-03 04:36:15 +00:00
import CryptoWorker from 'utils/crypto';
import { ErrorHandler } from 'utils/common/errorUtil';
const ENDPOINT = getEndpoint();
2021-01-13 12:43:46 +00:00
enum CollectionType {
folder = 'folder',
favorites = 'favorites',
album = 'album',
2021-01-13 12:43:46 +00:00
}
const COLLECTION_UPDATION_TIME = 'collection-updation-time';
const FAV_COLLECTION = 'fav-collection';
const COLLECTIONS = 'collections';
export interface Collection {
2021-02-08 07:33:46 +00:00
id: number;
2021-01-13 12:43:46 +00:00
owner: user;
key?: string;
name?: string;
encryptedName?: string;
nameDecryptionNonce?: string;
type: CollectionType;
attributes: collectionAttributes;
2021-01-13 12:43:46 +00:00
sharees: user[];
updationTime: number;
encryptedKey: string;
keyDecryptionNonce: string;
isDeleted: boolean;
}
interface collectionAttributes {
encryptedPath?: string;
pathDecryptionNonce?: string;
}
2021-01-13 12:43:46 +00:00
export interface CollectionAndItsLatestFile {
collection: Collection;
file: file;
}
const getCollectionSecrets = async (
collection: Collection,
masterKey: string
) => {
const worker = await new CryptoWorker();
const userID = getData(LS_KEYS.USER).id;
let decryptedKey: string;
if (collection.owner.id == userID) {
decryptedKey = await worker.decryptB64(
collection.encryptedKey,
collection.keyDecryptionNonce,
masterKey
);
} else {
const keyAttributes = getData(LS_KEYS.KEY_ATTRIBUTES);
const secretKey = await worker.decryptB64(
keyAttributes.encryptedSecretKey,
keyAttributes.secretKeyDecryptionNonce,
masterKey
);
decryptedKey = await worker.boxSealOpen(
collection.encryptedKey,
keyAttributes.publicKey,
secretKey
);
}
collection.name =
collection.name ||
2021-02-16 11:43:21 +00:00
(await worker.decryptToUTF8(
collection.encryptedName,
collection.nameDecryptionNonce,
decryptedKey
));
return {
...collection,
key: decryptedKey,
};
};
const getCollections = async (
token: string,
sinceTime: number,
key: string
): Promise<Collection[]> => {
2021-01-25 07:05:45 +00:00
try {
const resp = await HTTPService.get(
`${ENDPOINT}/collections`,
{
sinceTime: sinceTime,
},
{ 'X-Auth-Token': token }
);
const promises: Promise<Collection>[] = resp.data.collections.map(
(collection: Collection) => getCollectionSecrets(collection, key)
2021-01-25 07:05:45 +00:00
);
return await Promise.all(promises);
} catch (e) {
2021-03-29 09:35:05 +00:00
console.error('getCollections failed- ', e);
ErrorHandler(e);
2021-01-25 07:05:45 +00:00
}
};
export const getLocalCollections = async (): Promise<Collection[]> => {
const collections: Collection[] =
(await localForage.getItem(COLLECTIONS)) ?? [];
return collections;
};
export const getCollectionUpdationTime = async (): Promise<number> => {
2021-02-22 07:50:06 +00:00
return (await localForage.getItem<number>(COLLECTION_UPDATION_TIME)) ?? 0;
};
export const syncCollections = async () => {
const localCollections = await getLocalCollections();
const lastCollectionUpdationTime = await getCollectionUpdationTime();
const key = await getActualKey(),
token = getToken();
const updatedCollections =
(await getCollections(token, lastCollectionUpdationTime, key)) ?? [];
if (updatedCollections.length == 0) {
return localCollections;
}
setLocalFavoriteCollection(updatedCollections);
const allCollectionsInstances = [
...localCollections,
...updatedCollections,
];
var latestCollectionsInstances = new Map<number, Collection>();
allCollectionsInstances.forEach((collection) => {
if (
!latestCollectionsInstances.has(collection.id) ||
latestCollectionsInstances.get(collection.id).updationTime <
collection.updationTime
) {
latestCollectionsInstances.set(collection.id, collection);
}
});
let collections = [],
updationTime = await localForage.getItem<number>(
COLLECTION_UPDATION_TIME
);
for (const [_, collection] of latestCollectionsInstances) {
if (!collection.isDeleted) {
2021-02-06 07:51:49 +00:00
collections.push(collection);
updationTime = Math.max(updationTime, collection.updationTime);
2021-02-08 07:33:46 +00:00
}
}
2021-02-16 12:41:27 +00:00
collections.sort((a, b) => b.updationTime - a.updationTime);
2021-03-24 11:01:57 +00:00
collections.sort((a, b) => (b.type === CollectionType.favorites ? 1 : 0));
await localForage.setItem(COLLECTION_UPDATION_TIME, updationTime);
await localForage.setItem(COLLECTIONS, collections);
return collections;
};
2021-01-13 12:43:46 +00:00
export const getCollectionAndItsLatestFile = (
collections: Collection[],
files: file[]
): CollectionAndItsLatestFile[] => {
const latestFile = new Map<number, file>();
files.forEach((file) => {
if (!latestFile.has(file.collectionID)) {
latestFile.set(file.collectionID, file);
}
});
let allCollectionAndItsLatestFile: CollectionAndItsLatestFile[] = [];
const userID = getData(LS_KEYS.USER)?.id;
for (const collection of collections) {
if (
collection.owner.id != userID ||
collection.type == CollectionType.favorites
) {
continue;
}
allCollectionAndItsLatestFile.push({
collection,
file: latestFile.get(collection.id),
});
}
return allCollectionAndItsLatestFile;
};
2021-01-20 12:05:04 +00:00
export const getFavItemIds = async (files: file[]): Promise<Set<number>> => {
let favCollection = await localForage.getItem<Collection>(FAV_COLLECTION);
if (!favCollection) return new Set();
2021-01-20 12:05:04 +00:00
return new Set(
files
2021-02-09 05:07:46 +00:00
.filter((file) => file.collectionID === favCollection.id)
.map((file): number => file.id)
);
};
2021-01-20 12:05:04 +00:00
export const createAlbum = async (albumName: string) => {
return createCollection(albumName, CollectionType.album);
};
2021-01-15 11:11:06 +00:00
export const createCollection = async (
collectionName: string,
type: CollectionType
): Promise<Collection> => {
try {
const existingCollections = await getLocalCollections();
for (let collection of existingCollections) {
if (collection.name === collectionName) {
return collection;
}
}
const worker = await new CryptoWorker();
const encryptionKey = await getActualKey();
const token = getToken();
const collectionKey: string = await worker.generateEncryptionKey();
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 postCollection(
newCollection,
token
);
createdCollection = await getCollectionSecrets(
createdCollection,
encryptionKey
);
return createdCollection;
} catch (e) {
console.error('Add collection failed', e);
}
};
2021-01-13 12:43:46 +00:00
const postCollection = async (
collectionData: Collection,
token: string
): Promise<Collection> => {
2021-01-25 07:05:45 +00:00
try {
const response = await HTTPService.post(
`${ENDPOINT}/collections`,
collectionData,
null,
{ 'X-Auth-Token': token }
);
2021-01-25 07:05:45 +00:00
return response.data.collection;
} catch (e) {
console.error('create Collection failed ', e);
2021-01-25 07:05:45 +00:00
}
};
2021-01-15 11:11:06 +00:00
export const addToFavorites = async (file: file) => {
let favCollection: Collection = await localForage.getItem<Collection>(
FAV_COLLECTION
);
2021-01-15 11:11:06 +00:00
if (!favCollection) {
favCollection = await createCollection(
'Favorites',
CollectionType.favorites
);
await localForage.setItem(FAV_COLLECTION, favCollection);
2021-01-15 11:11:06 +00:00
}
2021-02-09 06:18:46 +00:00
await addToCollection(favCollection, [file]);
};
2021-01-15 11:11:06 +00:00
2021-01-20 13:04:27 +00:00
export const removeFromFavorites = async (file: file) => {
let favCollection: Collection = await localForage.getItem<Collection>(
2021-02-09 05:07:46 +00:00
FAV_COLLECTION
);
await removeFromCollection(favCollection, [file]);
};
2021-01-20 13:04:27 +00:00
const addToCollection = async (collection: Collection, files: file[]) => {
2021-01-25 07:05:45 +00:00
try {
const params = new Object();
const worker = await new CryptoWorker();
const token = getToken();
params['collectionID'] = collection.id;
await Promise.all(
files.map(async (file) => {
2021-02-09 05:07:46 +00:00
file.collectionID = collection.id;
2021-02-16 11:43:21 +00:00
const newEncryptedKey: B64EncryptionResult = await worker.encryptToB64(
file.key,
collection.key
);
file.encryptedKey = newEncryptedKey.encryptedData;
file.keyDecryptionNonce = newEncryptedKey.nonce;
if (params['files'] == undefined) {
params['files'] = [];
}
params['files'].push({
id: file.id,
encryptedKey: file.encryptedKey,
keyDecryptionNonce: file.keyDecryptionNonce,
});
return file;
2021-01-25 07:05:45 +00:00
})
);
await HTTPService.post(
`${ENDPOINT}/collections/add-files`,
params,
null,
{ 'X-Auth-Token': token }
);
2021-01-25 07:05:45 +00:00
} catch (e) {
console.error('Add to collection Failed ', e);
2021-01-25 07:05:45 +00:00
}
};
const removeFromCollection = async (collection: Collection, files: file[]) => {
2021-01-25 07:05:45 +00:00
try {
const params = new Object();
const token = getToken();
params['collectionID'] = collection.id;
await Promise.all(
files.map(async (file) => {
if (params['fileIDs'] == undefined) {
params['fileIDs'] = [];
}
params['fileIDs'].push(file.id);
})
);
await HTTPService.post(
`${ENDPOINT}/collections/remove-files`,
params,
null,
{ 'X-Auth-Token': token }
);
2021-01-25 07:05:45 +00:00
} catch (e) {
console.error('remove from collection failed ', e);
2021-01-25 07:05:45 +00:00
}
};
2021-01-20 13:04:27 +00:00
const setLocalFavoriteCollection = async (collections: Collection[]) => {
const localFavCollection = await localForage.getItem(FAV_COLLECTION);
2021-02-09 05:07:46 +00:00
if (localFavCollection) {
return;
}
const favCollection = collections.filter(
(collection) => collection.type == CollectionType.favorites
);
2021-02-09 05:07:46 +00:00
if (favCollection.length > 0) {
await localForage.setItem(FAV_COLLECTION, favCollection[0]);
2021-02-09 05:07:46 +00:00
}
};
2021-03-15 17:30:49 +00:00
export const getNonEmptyCollections = (
collections: Collection[],
2021-03-15 17:30:49 +00:00
files: file[]
) => {
const nonEmptyCollectionsIds = new Set<number>();
for (let file of files) {
nonEmptyCollectionsIds.add(file.collectionID);
}
return collections.filter((collection) =>
nonEmptyCollectionsIds.has(collection.id)
);
};