From 1088b3d7017451a2c268e83cc48554d708410025 Mon Sep 17 00:00:00 2001 From: Sergio Gutierrez Villalba Date: Tue, 19 Sep 2023 14:17:01 +0200 Subject: [PATCH 1/4] feat(sharing): download files using the new API --- src/app/core/config/app.json | 2 +- src/app/share/services/share.service.ts | 34 +++++++++++++- .../share/views/ShareView/ShareFileView.tsx | 44 +++++++++++++------ 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/src/app/core/config/app.json b/src/app/core/config/app.json index dea285716..1934ceb05 100644 --- a/src/app/core/config/app.json +++ b/src/app/core/config/app.json @@ -188,7 +188,7 @@ { "id": "share-token", "layout": "share", - "path": "/sh/file/:token([a-z0-9]{20})/:code?", + "path": "/sh/file/:token/:code?", "exact": false }, { diff --git a/src/app/share/services/share.service.ts b/src/app/share/services/share.service.ts index 91b1f7347..b67889ebb 100644 --- a/src/app/share/services/share.service.ts +++ b/src/app/share/services/share.service.ts @@ -160,10 +160,40 @@ export function deleteShareLink(shareId: string): Promise<{ deleted: boolean; sh }); } -export function getSharedFileInfo(token: string, code: string, password?: string): Promise { +export function getSharedFileInfo( + sharingId: string, + code: string, + password?: string, +): Promise<{ + id: string; + itemId: string; + itemType: string; + ownerId: string; + sharedWith: string; + encryptionKey: string; + encryptionAlgorithm: string; + createdAt: string; + updatedAt: string; + type: string; + item: any; + itemToken: string; +}> { const newApiURL = SdkFactory.getNewApiInstance().getApiUrl(); return httpService - .get(newApiURL + '/storage/share/' + token + '?code=' + code, { + .get<{ + id: string; + itemId: string; + itemType: string; + ownerId: string; + sharedWith: string; + encryptionKey: string; + encryptionAlgorithm: string; + createdAt: string; + updatedAt: string; + type: string; + item: ShareTypes.ShareLink['item']; + itemToken: string; + }>(newApiURL + '/sharings/' + sharingId + '/meta?code=' + code, { headers: { 'x-share-password': password, }, diff --git a/src/app/share/views/ShareView/ShareFileView.tsx b/src/app/share/views/ShareView/ShareFileView.tsx index 8b44e3064..c4c8093fb 100644 --- a/src/app/share/views/ShareView/ShareFileView.tsx +++ b/src/app/share/views/ShareView/ShareFileView.tsx @@ -53,12 +53,29 @@ interface ShareViewState { export default function ShareFileView(props: ShareViewProps): JSX.Element { const { translate } = useTranslationContext(); - const token = props.match.params.token; + const sharingId = props.match.params.token; const code = props.match.params.code; const [progress, setProgress] = useState(TaskProgress.Min); const [blobProgress, setBlobProgress] = useState(TaskProgress.Min); const [isDownloading, setIsDownloading] = useState(false); - const [info, setInfo] = useState>({}); + const [info, setInfo] = useState< + | { + id: string; + itemId: string; + itemType: string; + ownerId: string; + sharedWith: string; + encryptionKey: string; + encryptionAlgorithm: string; + createdAt: string; + updatedAt: string; + type: string; + item: any; + itemToken: string; + name: string; + } + | Record + >({}); const [isLoaded, setIsLoaded] = useState(false); const [isError, setIsError] = useState(false); const [openPreview, setOpenPreview] = useState(false); @@ -117,7 +134,7 @@ export default function ShareFileView(props: ShareViewProps): JSX.Element { const getFormatFileName = (): string => { const hasType = info?.item?.type !== null; const extension = hasType ? `.${info?.item?.type}` : ''; - return `${info?.item?.name}${extension}`; + return `${info?.item?.plainName}${extension}`; }; const getFormatFileSize = (): string => { @@ -125,13 +142,13 @@ export default function ShareFileView(props: ShareViewProps): JSX.Element { }; function loadInfo(password?: string) { - return getSharedFileInfo(token, code, password) - .then((info) => { + return getSharedFileInfo(sharingId, code, password) + .then((res) => { setIsLoaded(true); setRequiresPassword(false); setInfo({ - ...info, - name: info.item.name, + ...res, + name: res.item.plainName, }); }) .catch((err) => { @@ -150,10 +167,10 @@ export default function ShareFileView(props: ShareViewProps): JSX.Element { const encryptionKey = fileInfo.encryptionKey; const readable = network.downloadFile({ - bucketId: fileInfo.bucket, + bucketId: fileInfo.item.bucket, fileId: fileInfo.item?.fileId, encryptionKey: Buffer.from(encryptionKey, 'hex'), - token: (fileInfo as any).fileToken, + token: fileInfo.itemToken, options: { abortController, notifyProgress: (totalProgress, downloadedBytes) => { @@ -174,7 +191,7 @@ export default function ShareFileView(props: ShareViewProps): JSX.Element { const download = async (): Promise => { if (!isDownloading) { - const fileInfo = info as unknown as ShareTypes.ShareLink; + const fileInfo = info; const MIN_PROGRESS = 0; if (fileInfo) { @@ -183,16 +200,15 @@ export default function ShareFileView(props: ShareViewProps): JSX.Element { setProgress(MIN_PROGRESS); setIsDownloading(true); const readable = await network.downloadFile({ - bucketId: fileInfo.bucket, + bucketId: fileInfo.item.bucket, fileId: fileInfo.item.fileId, encryptionKey: Buffer.from(encryptionKey, 'hex'), - token: (fileInfo as any).fileToken, + token: fileInfo.itemToken, options: { notifyProgress: (totalProgress, downloadedBytes) => { const progress = Math.trunc((downloadedBytes / totalProgress) * 100); setProgress(progress); if (progress == 100) { - shareService.incrementShareView(fileInfo.token); setIsDownloading(false); } }, @@ -336,7 +352,7 @@ export default function ShareFileView(props: ShareViewProps): JSX.Element { Date: Tue, 19 Sep 2023 17:26:52 +0200 Subject: [PATCH 2/4] Added share public file flow --- .../components/ShareDialog/ShareDialog.tsx | 30 ++++++++++++++++--- src/app/share/services/share.service.ts | 21 +++++++++++-- .../share/views/ShareView/ShareFileView.tsx | 25 ++++------------ src/react-app-env.d.ts | 1 + 4 files changed, 50 insertions(+), 27 deletions(-) diff --git a/src/app/drive/components/ShareDialog/ShareDialog.tsx b/src/app/drive/components/ShareDialog/ShareDialog.tsx index fe28aa236..bcc56f2a6 100644 --- a/src/app/drive/components/ShareDialog/ShareDialog.tsx +++ b/src/app/drive/components/ShareDialog/ShareDialog.tsx @@ -12,7 +12,6 @@ import { ArrowLeft, CaretDown, Check, CheckCircle, Globe, Link, UserPlus, Users, import Avatar from 'app/shared/components/Avatar'; import Spinner from 'app/shared/components/Spinner/Spinner'; import { sharedThunks } from '../../../store/slices/sharedLinks'; -import { DriveItemData } from '../../types'; import './ShareDialog.scss'; import shareService, { getSharingRoles } from '../../../share/services/share.service'; import errorService from '../../../core/services/error.service'; @@ -21,6 +20,9 @@ import notificationsService, { ToastType } from '../../../notifications/services import { Role } from 'app/store/slices/sharedLinks/types'; import copy from 'copy-to-clipboard'; +import crypto from 'crypto'; +import { aes } from '@internxt/lib'; + type AccessMode = 'public' | 'restricted'; type UserRole = 'owner' | 'editor' | 'reader'; type Views = 'general' | 'invite' | 'requests'; @@ -213,6 +215,25 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { dispatch(uiActions.setIsShareDialogOpen(false)); }; + const getPublicShareLink = async (uuid: string, itemType: 'folder' | 'file') => { + const user = props.user; + const { mnemonic } = user; + const code = crypto.randomBytes(32).toString('hex'); + + const encryptedMnemonic = aes.encrypt(mnemonic, code); + + const publicSharingItemData = await shareService.createPublicSharingItem({ + encryptionAlgorithm: 'inxt-v2', + encryptionKey: encryptedMnemonic, + itemType, + itemId: uuid, + }); + const { id: sharingId } = publicSharingItemData; + + copy(`${process.env.REACT_APP_HOSTNAME}/sh/file/${sharingId}/${code}`); + notificationsService.show({ text: translate('shared-links.toast.copy-to-clipboard'), type: ToastType.Success }); + }; + const onCopyLink = (): void => { if (accessMode === 'restricted') { copy(`${process.env.REACT_APP_HOSTNAME}/app/shared/?folderuuid=${itemToShare?.item.uuid}`); @@ -221,7 +242,8 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { return; } - dispatch(sharedThunks.getSharedLinkThunk({ item: itemToShare?.item as DriveItemData })); + if (itemToShare?.item.uuid) + getPublicShareLink(itemToShare?.item.uuid, itemToShare.item.isFolder ? 'folder' : 'file'); closeSelectedUserPopover(); }; @@ -450,7 +472,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { {({ close }) => ( <> {/* Public */} - {/* */} + {/* Restricted */}