diff --git a/src/components/CollectionShare.tsx b/src/components/CollectionShare.tsx
index 3aabd3b0b..fe36939ea 100644
--- a/src/components/CollectionShare.tsx
+++ b/src/components/CollectionShare.tsx
@@ -7,11 +7,16 @@ import FormControl from 'react-bootstrap/FormControl';
import { Button, Col, Table } from 'react-bootstrap';
import { DeadCenter } from 'pages/gallery';
import { User } from 'types/user';
-import { shareCollection, unshareCollection } from 'services/collectionService';
+import {
+ shareCollection,
+ unshareCollection,
+ createShareableUrl,
+} from 'services/collectionService';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
import SubmitButton from './SubmitButton';
import MessageDialog from './MessageDialog';
import { Collection } from 'types/collection';
+import { transformShareURLForHost } from 'utils/collection';
interface Props {
show: boolean;
@@ -72,6 +77,11 @@ function CollectionShare(props: Props) {
await props.syncWithRemote();
};
+ const createSharableUrlHelper = async () => {
+ await createShareableUrl(props.collection);
+ await props.syncWithRemote();
+ };
+
const ShareeRow = ({ sharee, collectionUnshare }: ShareeProps) => (
{sharee.email} |
@@ -154,6 +164,11 @@ function CollectionShare(props: Props) {
)}
+
+ {props.collection?.publicAccessUrls.length > 0 && (
+
+
{constants.PUBLIC_URL}
+
+
+
+ )}
{props.collection?.sharees.length > 0 ? (
<>
{constants.SHAREES}
diff --git a/src/components/PhotoFrame.tsx b/src/components/PhotoFrame.tsx
index 2532c6aeb..61ae240b4 100644
--- a/src/components/PhotoFrame.tsx
+++ b/src/components/PhotoFrame.tsx
@@ -9,7 +9,6 @@ import constants from 'utils/strings/constants';
import AutoSizer from 'react-virtualized-auto-sizer';
import PhotoSwipe from 'components/PhotoSwipe/PhotoSwipe';
import { isInsideBox, isSameDay as isSameDayAnyYear } from 'utils/search';
-import { SetDialogMessage } from './MessageDialog';
import { fileIsArchived, formatDateRelative } from 'utils/file';
import {
ALL_SECTION,
@@ -64,7 +63,6 @@ interface Props {
search: Search;
setSearchStats: setSearchStats;
deleted?: number[];
- setDialogMessage: SetDialogMessage;
activeCollection: number;
isSharedCollection: boolean;
}
diff --git a/src/constants/collection/index.ts b/src/constants/collection/index.ts
index afb10f0e4..0e68e9a47 100644
--- a/src/constants/collection/index.ts
+++ b/src/constants/collection/index.ts
@@ -13,3 +13,7 @@ export enum COLLECTION_SORT_BY {
MODIFICATION_TIME,
NAME,
}
+
+export const COLLECTION_SHARE_DEFAULT_VALID_DURATION =
+ 10 * 24 * 60 * 60 * 1000 * 1000;
+export const COLLECTION_SHARE_DEFAULT_DEVICE_LIMIT = 4;
diff --git a/src/pages/gallery/index.tsx b/src/pages/gallery/index.tsx
index 9a7d6136d..71152bcbc 100644
--- a/src/pages/gallery/index.tsx
+++ b/src/pages/gallery/index.tsx
@@ -661,7 +661,6 @@ export default function Gallery() {
search={search}
setSearchStats={setSearchStats}
deleted={deleted}
- setDialogMessage={setDialogMessage}
activeCollection={activeCollection}
isSharedCollection={isSharedCollection(
activeCollection,
diff --git a/src/pages/shared-album/index.tsx b/src/pages/shared-album/index.tsx
new file mode 100644
index 000000000..95358e847
--- /dev/null
+++ b/src/pages/shared-album/index.tsx
@@ -0,0 +1,48 @@
+import { ALL_SECTION } from 'constants/collection';
+import PhotoFrame from 'components/PhotoFrame';
+import React, { useEffect, useState } from 'react';
+import { getSharedCollectionFiles } from 'services/sharedCollectionService';
+
+export default function sharedAlbum() {
+ const [token, setToken] = useState(null);
+ const [collectionKey, setCollectionKey] = useState(null);
+ const [files, setFiles] = useState([]);
+ useEffect(() => {
+ const urlParams = new URLSearchParams(window.location.search);
+ const token = urlParams.get('accessToken');
+ const collectionKey = urlParams.get('collectionKey');
+ setToken(token);
+ setCollectionKey(collectionKey);
+ syncWithRemote(token, collectionKey);
+ }, []);
+
+ const syncWithRemote = async (t?: string, c?: string) => {
+ const files = await getSharedCollectionFiles(
+ t ?? token,
+ c ?? collectionKey,
+ setFiles
+ );
+ console.log(files);
+ setFiles(files);
+ };
+
+ return (
+ null}
+ selected={{ count: 0, collectionID: null }}
+ isFirstLoad={false}
+ openFileUploader={() => null}
+ loadingBar={null}
+ isInSearchMode={false}
+ search={{}}
+ setSearchStats={() => null}
+ deleted={null}
+ activeCollection={ALL_SECTION}
+ isSharedCollection={true}
+ />
+ );
+}
diff --git a/src/services/collectionService.ts b/src/services/collectionService.ts
index cd63c2e56..3099eb017 100644
--- a/src/services/collectionService.ts
+++ b/src/services/collectionService.ts
@@ -20,8 +20,15 @@ import {
MoveToCollectionRequest,
EncryptedFileKey,
RemoveFromCollectionRequest,
+ CreatePublicAccessTokenRequest,
+ PublicAccessUrl,
} from 'types/collection';
-import { COLLECTION_SORT_BY, CollectionType } from 'constants/collection';
+import {
+ COLLECTION_SORT_BY,
+ CollectionType,
+ COLLECTION_SHARE_DEFAULT_DEVICE_LIMIT,
+ COLLECTION_SHARE_DEFAULT_VALID_DURATION,
+} from 'constants/collection';
const ENDPOINT = getEndpoint();
const COLLECTION_TABLE = 'collections';
@@ -574,6 +581,31 @@ export const unshareCollection = async (
}
};
+export const createShareableUrl = async (collection: Collection) => {
+ try {
+ const token = getToken();
+ const createPublicAccessTokenRequest: CreatePublicAccessTokenRequest = {
+ collectionID: collection.id,
+ validTill:
+ Date.now() * 1000 + COLLECTION_SHARE_DEFAULT_VALID_DURATION,
+ deviceLimit: COLLECTION_SHARE_DEFAULT_DEVICE_LIMIT,
+ };
+ const resp = await HTTPService.post(
+ `${ENDPOINT}/collections/share-url`,
+ createPublicAccessTokenRequest,
+ null,
+ {
+ 'X-Auth-Token': token,
+ }
+ );
+ console.log(resp);
+ return resp.data as PublicAccessUrl;
+ } catch (e) {
+ logError(e, 'createShareableUrl failed ');
+ throw e;
+ }
+};
+
export const getFavCollection = async () => {
const collections = await getLocalCollections();
for (const collection of collections) {
diff --git a/src/services/fileService.ts b/src/services/fileService.ts
index 8c9f0ece3..8d1ad4944 100644
--- a/src/services/fileService.ts
+++ b/src/services/fileService.ts
@@ -105,7 +105,7 @@ export const getFiles = async (
...(await Promise.all(
resp.data.diff.map(async (file: EnteFile) => {
if (!file.isDeleted) {
- file = await decryptFile(file, collection);
+ file = await decryptFile(file, collection.key);
}
return file;
}) as Promise[]
diff --git a/src/services/sharedCollectionService.ts b/src/services/sharedCollectionService.ts
new file mode 100644
index 000000000..281ba9623
--- /dev/null
+++ b/src/services/sharedCollectionService.ts
@@ -0,0 +1,58 @@
+import { EnteFile } from 'types/file';
+import { getEndpoint } from 'utils/common/apiUtil';
+import { decryptFile, sortFiles, mergeMetadata } from 'utils/file';
+import { logError } from 'utils/sentry';
+import HTTPService from './HTTPService';
+
+const ENDPOINT = getEndpoint();
+
+export const getSharedCollectionFiles = async (
+ token: string,
+ collectionKey: string,
+ setFiles: (files: EnteFile[]) => void
+) => {
+ try {
+ if (!token || !collectionKey) {
+ throw Error('token or collectionKey missing');
+ }
+ const decryptedFiles: EnteFile[] = [];
+ let time = 0;
+ let resp;
+ do {
+ resp = await HTTPService.get(
+ `${ENDPOINT}/public-collection/diff`,
+ {
+ sinceTime: time,
+ },
+ {
+ 'X-Auth-Access-Token': token,
+ }
+ );
+
+ decryptedFiles.push(
+ ...(await Promise.all(
+ resp.data.diff.map(async (file: EnteFile) => {
+ if (!file.isDeleted) {
+ file = await decryptFile(file, collectionKey);
+ }
+ return file;
+ }) as Promise[]
+ ))
+ );
+
+ if (resp.data.diff.length) {
+ time = resp.data.diff.slice(-1)[0].updationTime;
+ }
+ setFiles(
+ sortFiles(
+ mergeMetadata(
+ decryptedFiles.filter((item) => !item.isDeleted)
+ )
+ )
+ );
+ } while (resp.data.hasMore);
+ return decryptedFiles;
+ } catch (e) {
+ logError(e, 'Get files failed');
+ }
+};
diff --git a/src/services/trashService.ts b/src/services/trashService.ts
index 36ea04756..1dee6537c 100644
--- a/src/services/trashService.ts
+++ b/src/services/trashService.ts
@@ -107,7 +107,7 @@ export const updateTrash = async (
if (!trashItem.isDeleted && !trashItem.isRestored) {
trashItem.file = await decryptFile(
trashItem.file,
- collection
+ collection.key
);
}
updatedTrash.push(trashItem);
diff --git a/src/services/upload/uploader.ts b/src/services/upload/uploader.ts
index aee78c2b9..ce6662079 100644
--- a/src/services/upload/uploader.ts
+++ b/src/services/upload/uploader.ts
@@ -103,7 +103,7 @@ export default async function uploader(
);
const uploadedFile = await UploadHttpClient.uploadFile(uploadFile);
- const decryptedFile = await decryptFile(uploadedFile, collection);
+ const decryptedFile = await decryptFile(uploadedFile, collection.key);
UIService.setFileProgress(rawFile.name, FileUploadResults.UPLOADED);
UIService.increaseFileUploaded();
diff --git a/src/types/collection/index.ts b/src/types/collection/index.ts
index b6e35693a..b734740c4 100644
--- a/src/types/collection/index.ts
+++ b/src/types/collection/index.ts
@@ -17,6 +17,19 @@ export interface Collection {
keyDecryptionNonce: string;
isDeleted: boolean;
isSharedCollection?: boolean;
+ publicAccessUrls?: PublicAccessUrl[];
+}
+
+export interface PublicAccessUrl {
+ url: string;
+ deviceLimit: number;
+ validTill: number;
+}
+
+export interface CreatePublicAccessTokenRequest {
+ collectionID: number;
+ validTill: number;
+ deviceLimit: number;
}
export interface EncryptedFileKey {
diff --git a/src/utils/collection/index.ts b/src/utils/collection/index.ts
index e63500b60..dbe476dcb 100644
--- a/src/utils/collection/index.ts
+++ b/src/utils/collection/index.ts
@@ -107,3 +107,11 @@ export async function downloadCollection(
});
}
}
+
+export function transformShareURLForHost(url: string, collectionKey: string) {
+ const host = window.location.host;
+ return `${url}&collectionKey=${collectionKey}`.replace(
+ 'https://albums.ente.io',
+ `http://${host}/shared-album`
+ );
+}
diff --git a/src/utils/file/index.ts b/src/utils/file/index.ts
index 929c319ea..88afd9d96 100644
--- a/src/utils/file/index.ts
+++ b/src/utils/file/index.ts
@@ -1,5 +1,4 @@
import { SelectedState } from 'types/gallery';
-import { Collection } from 'types/collection';
import {
EnteFile,
fileAttribute,
@@ -202,13 +201,13 @@ export function sortFiles(files: EnteFile[]) {
return files;
}
-export async function decryptFile(file: EnteFile, collection: Collection) {
+export async function decryptFile(file: EnteFile, collectionKey: string) {
try {
const worker = await new CryptoWorker();
file.key = await worker.decryptB64(
file.encryptedKey,
file.keyDecryptionNonce,
- collection.key
+ collectionKey
);
const encryptedMetadata = file.metadata as unknown as fileAttribute;
file.metadata = await worker.decryptMetadata(
diff --git a/src/utils/strings/englishConstants.tsx b/src/utils/strings/englishConstants.tsx
index 0125d4fcf..074431a16 100644
--- a/src/utils/strings/englishConstants.tsx
+++ b/src/utils/strings/englishConstants.tsx
@@ -377,6 +377,7 @@ const englishConstants = {
>
),
+ PUBLIC_URL: 'public share url',
SHARE_WITH_SELF: 'oops, you cannot share with yourself',
ALREADY_SHARED: (email) =>
`oops, you're already sharing this with ${email}`,